多重ロジスティック回帰
今度は説明変数を複数含む多重ロジスティック回帰を行なってみる。データは Kutner et al. "Applied Linear Statistical Models" (5th Ed) の14章にある疫病のアウトブレイクについて。目的変数はその疫病に感染したかどうか、説明変数は年齢、社会経済的地位(カテゴリカル、3水準)、都市での居住地区(2水準)の3つ。ただし、社会経済的地位の変数は3水準あるために2つのダミー変数(socio1, socio2)を使う。データにはあらかじめそのダミー変数が用意され、コーディングもされてある状態。glm() の使い方は基本的に説明変数が1つの場合と同じ。
> # Reading data... > epidem <-read.table("http://apps.csom.umn.edu/Nachtsheim/5th/KutnerData/Chapter%2014%20Data%20Sets/CH14TA03.txt") > names(epidem) <- c("case","age","socio1","socio2","city","status") > # Multiple logistic regression > epi.glm <- glm(status~age+socio1+socio2+city,epidem,family=binomial) > summary(epi.glm) Call: glm(formula = status ~ age + socio1 + socio2 + city, family = binomial, data = epidem) Deviance Residuals: Min 1Q Median 3Q Max -1.6552 -0.7529 -0.4788 0.8558 2.0977 Coefficients: Estimate Std. Error z value Pr(>|z|) (Intercept) -2.31293 0.64259 -3.599 0.000319 *** age 0.02975 0.01350 2.203 0.027577 * socio1 0.40879 0.59900 0.682 0.494954 socio2 -0.30525 0.60413 -0.505 0.613362 city 1.57475 0.50162 3.139 0.001693 ** --- Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 (Dispersion parameter for binomial family taken to be 1) Null deviance: 122.32 on 97 degrees of freedom Residual deviance: 101.05 on 93 degrees of freedom AIC: 111.05 Number of Fisher Scoring iterations: 4
モデルが全体として有意であるかどうか。切片だけのモデルとの尤度比検定。
> # Testing model fit: all beta = 0 ? > epi.int <- glm(status~1,epidem,family=binomial) > anova(epi.int,epi.glm,test="Chisq") Analysis of Deviance Table Model 1: status ~ 1 Model 2: status ~ age + socio1 + socio2 + city Resid. Df Resid. Dev Df Deviance P(>|Chi|) 1 97 122.318 2 93 101.054 4 21.263 0.0002808
いちいち回帰係数からオッズ比を計算するのは面倒なので、信頼区間も一緒に計算する関数を用意する。引数でパーセントも指定できる(デフォルトは95%)。
> # Function to calculate CI for OR > orci <- function(model,pct=.95){ + coeffs <- coef(summary(model)) + lCI <- exp(coeffs[,1] - qnorm((1+pct)/2) * coeffs[,2]) + OR <- exp(coeffs[,1]) + uCI <- exp(coeffs[,1] + qnorm((1+pct)/2) * coeffs[,2]) + orci <- cbind(OR,lCI,uCI) + orci + } > orci(epi.glm) OR lCI uCI (Intercept) 0.09897037 0.02808881 0.3487201 age 1.03019705 1.00329047 1.0578252 socio1 1.50499600 0.46522433 4.8686469 socio2 0.73693576 0.22552497 2.4080451 city 4.82953038 1.80686070 12.9087780
実はオッズ比は Design パッケージのロジスティック回帰用の関数 lrm() を使えば計算してくれる。ただしこれを使う場合、あらかじめ datadist() で使う説明変数を別に用意しておく。また年齢のような連続変数が含まれている場合、summary() の引数でその変数の範囲を指定しないと、その連続変数の第1四分位と第3四分位にわたるオッズ比が計算されてしまう。例えば年齢が1歳違うときのオッズ比を求めたいときには age=c(20:21) のように適当に範囲を指定する必要がある。
> library(Design) > attach(epidem) > ddist <- datadist(age,socio1,socio2,city) > options(datadist='ddist') > detach(epidem) > epi.lrm <- lrm(status~age+socio1+socio2+city,epidem,x=T,y=T) > summary(epi.lrm,age=c(20:21)) Effects Response : status Factor Low High Diff. Effect S.E. Lower 0.95 Upper 0.95 age 20 21 1 0.03 0.01 0.00 0.06 Odds Ratio 20 21 1 1.03 NA 1.00 1.06 socio1 0 1 1 0.41 0.60 -0.77 1.58 Odds Ratio 0 1 1 1.50 NA 0.47 4.87 socio2 0 1 1 -0.31 0.60 -1.49 0.88 Odds Ratio 0 1 1 0.74 NA 0.23 2.41 city 0 1 1 1.57 0.50 0.59 2.56 Odds Ratio 0 1 1 4.83 NA 1.81 12.91
この lrm() のいいところは適合度の検定も行なえること。ふつうロジスティックモデルに連続変数が含まれる場合、Hosmer-Lemeshow の適合度検定を使うけれど、以下のようにするとHosmer-Le Cessie test というより検出力のある検定を行なってくれるらしい。この辺参照。
> resid(epi.lrm,'gof') Sum of squared errors Expected value|H0 SD Z P 17.0317397 16.7003935 0.2133231 1.5532598 0.1203611
分散共分散行列のチェック。
> # Approximate var-cov matrix > print(summary(epi.glm)$cov.unscaled,digits=2) (Intercept) age socio1 socio2 city (Intercept) 0.4129 -0.00571 -0.1836 -0.20098 -0.16324 age -0.0057 0.00018 0.0011 0.00073 0.00034 socio1 -0.1836 0.00115 0.3588 0.14822 0.01289 socio2 -0.2010 0.00073 0.1482 0.36497 0.06227 city -0.1632 0.00034 0.0129 0.06227 0.25162
さてモデルには主効果だけしか入ってないけど、2次交互作用項を入れたときにモデルが改善するか尤度比検定で比較して調べてみよう。update(epi.glm,.~.^2) とすると式の表示のなかではダミー変数同士の交互作用(socio1:socio2)が表示されてるけど、実際には無視されてるみたい(自由度が5になっていることに注意)。えらい(当たり前?)。
> # Checking interactions... > epi.intx <- update(epi.glm,.~.^2) > anova(epi.glm,epi.intx,test="Chisq") Analysis of Deviance Table Model 1: status ~ age + socio1 + socio2 + city Model 2: status ~ age + socio1 + socio2 + city + age:socio1 + age:socio2 + age:city + socio1:socio2 + socio1:city + socio2:city Resid. Df Resid. Dev Df Deviance P(>|Chi|) 1 93 101.054 2 88 93.996 5 7.058 0.216
デビアンスは有意でないので、交互作用を心配する必要はない。また年齢を削ったモデルとを比較をしてみると、
> # Dropping age variable... > drop1(epi.glm,~age,test='Chisq') Single term deletions Model: status ~ age + socio1 + socio2 + city Df Deviance AIC LRT Pr(Chi) <none> 101.05 111.05 age 1 106.20 114.20 5.15 0.02325 * --- Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
デビアンスが有意なので年齢の変数は削れない。こんなことをひとつひとつやっていくわけにはいかないのでAICに基づいてステップワイズに変数選択をさせてみよう。これには step() を使う。切片だけのモデルから変数を増やしていく場合には direction="forward" を指定する。本来は社会経済的地位の2つのダミー変数は一緒に加えられるべきなのだけど、どうやったらいいかわからないので気にしないでやってみる。
> # Forward stepwise selection > epi.fstp <- step(epi.int,.~age+socio1+socio2+city,direction="forward") Start: AIC= 124.32 status ~ 1 Df Deviance AIC + city 1 107.53 111.53 + age 1 114.91 118.91 + socio2 1 118.23 122.23 <none> 122.32 124.32 + socio1 1 120.88 124.88 Step: AIC= 111.53 status ~ city Df Deviance AIC + age 1 102.26 108.26 <none> 107.53 111.53 + socio2 1 106.37 112.37 + socio1 1 106.88 112.88 Step: AIC= 108.26 status ~ city + age Df Deviance AIC <none> 102.26 108.26 + socio1 1 101.31 109.31 + socio2 1 101.52 109.52 > summary(epi.fstp) Call: glm(formula = status ~ city + age, family = binomial, data = epidem) Deviance Residuals: Min 1Q Median 3Q Max -1.7296 -0.7048 -0.4940 0.9870 2.0929 Coefficients: Estimate Std. Error z value Pr(>|z|) (Intercept) -2.33515 0.51113 -4.569 4.91e-06 *** city 1.67345 0.48734 3.434 0.000595 *** age 0.02929 0.01317 2.224 0.026153 * --- Signif. codes: 0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1 (Dispersion parameter for binomial family taken to be 1) Null deviance: 122.32 on 97 degrees of freedom Residual deviance: 102.26 on 95 degrees of freedom AIC: 108.26 Number of Fisher Scoring iterations: 4
結局、2つのダミー変数は要らないということになった。さらに残差分析と影響診断をやってみよう。そのまま plot() してやると、いつもの4つのプロット(残差プロットとそのQQプロット、影響プロットとクックの距離)がでてくる。
> par(mfrow=c(2,2)) > plot(epi.glm) > par(mfrow=c(1,1))
この場合の残差QQプロットはどう見ていいかよく分からない。標準化されたデビアンス残差は正規分布に従うと考えていいわけ?テキストのほうには半正規プロットをとっていて、「フィットしたモデルが正しいときでも必ずしも直線になるとは限らない」ってあるけど。だけどそれ以外はまあテキストと同じ図。ただし、残差プロットはそのままでは分かりにくいので、lowess() 関数で平滑化してやる。モデルが適切ならばその曲線が0のあたりでほぼ横ばいになっているはず。以下は残差を縦軸にとり、横軸に予測確率あるいはそのロジットをとったもの。ピアソン残差とデビアンス残差を取り出すには residuals.glm() の引数に type で指定する。スチューデント化された残差はないのかな?
> # Pearson & Deviance residual plots for diagonistics > pres = residuals(epi.glm, type='pearson') > dres = residuals(epi.glm, type='deviance') > par(mfrow=c(2,2)) > plot(pres~fitted(epi.glm),xlab='Estimated Probability',ylab='Pearson Residual') > lines(lowess(pres~fitted(epi.glm),f=0.7),lwd=2,col=2) > plot(pres~predict(epi.glm),xlab='Linear Predictor',ylab='Pearson Residual') > lines(lowess(pres~predict(epi.glm),f=0.7),lwd=2,col=2) > plot(dres~fitted(epi.glm),xlab='Estimated Probability',ylab='Deviance Residual') > lines(lowess(dres~fitted(epi.glm),f=0.7),lwd=2,col=2) > plot(dres~predict(epi.glm),xlab='Linear Predictor',ylab='Deviance Residual') > lines(lowess(dres~predict(epi.glm),f=0.7),lwd=2,col=2) > par(mfrow=c(1,1))