Levee Breach in New Orleans (NASA Earth Observatory)

このところテレビでは連日、ハリケーンカトリーナ」による被害の報道。いつも眺めている NASA Earth Observatory にもニューオーリンズでの冠水の様子を捉えたクイックバード衛星写真が。去年の画像と比較できるのだけど、市内の8割が冠水というのにも頷ける。運河に囲まれた一帯の状況はひどそうだ。
高解像度写真で最下部中央やや右に見える丸い屋根が避難施設となっているルイジアナスーパードーム。ハリケーンで屋根が壊れていることが分かる。その少し南東に見えるミシシッピ川沿いの白い大きな建築物がこれまた避難施設になったコンベンションセンターで、私も学会で何度か訪れたことがある。なるほど、この辺りに冠水はないみたい。コンベンションセンターからまっすぐ北に目をやって、やや小さいけど丸く見える公園がジャクソンスクエアで、その辺りがフレンチクオーター。ここでは略奪が横行しているらしい。

多項ロジスティック回帰、順序ロジスティック回帰

さて今度はロジスティック回帰でも目的変数が3分類以上あるときに使われる多項ロジスティック回帰と、その目的変数が順序尺度である場合の順序ロジスティック回帰(比例オッズ、累積ロジットモデル)をRでやってみる。データは Kutner et al. "Applied Linear Statistical Models" (5th Ed) の14章にある妊娠期間について。目的変数は、妊娠週数が36週未満の早産(= 1)、36-37週のやや早産(= 2)、38週以上の満期(= 3)の3分類。説明変数は栄養状態(連続変数)、年齢(カテゴリカル:20歳以下、21-30歳、31歳以上の3水準)、飲酒歴(有・無)、喫煙歴(有・無)の4つ。この場合、目的変数の分類は順序尺度なのでふつうは順序ロジスティック回帰を使うけど、これを名義尺度として解釈すれば多項ロジスティック回帰にもできるので、一緒にやってみよう。データの読み出しはテキストのウェブサイトから。年齢の変数は3水準あるので、2つのダミー変数(age1, age2)がデータにはすでに用意されており、ここでの基準は中間の「21-30歳」。またどういうわけか、目的変数のためにも3つのダミー変数が用意さえているけれども、ここでは使用しない。
まずは多項ロジスティック回帰から。データを読み込んで変数を名付けたら、ordered() で妊娠期間の変数を順序尺度にする。あとで実際にロジスティック回帰をするとき、最初のカテゴリが基準として扱われるので、ここでは満期出産を基準として levels=c(3,2,1) と指定。

> # Reading data...
> pregn <-read.table("http://apps.csom.umn.edu/Nachtsheim/5th/KutnerData/Chapter%2014%20Data%20Sets/CH14TA13.txt")
> names(pregn) <- c("case","dur","y1","y2","y3","nutr","age1","age2","alco","smk")
> pregn$dur <- ordered(pregn$dur,levels=c(3,2,1))

多項ロジスティック回帰には nnet パッケージにある multinom() 関数を使う。

> # Multinomial logistic regression
>
> library(nnet)
> pregn.mltnom <- multinom(dur~nutr+age1+age2+alco+smk,pregn,Hess=T)
# weights:  21 (12 variable)
initial  value 112.058453 
iter  10 value 84.619847
final  value 84.337718 
converged
> summary(pregn.mltnom,cor=F,Wald=T)
Call:
multinom(formula = dur ~ nutr + age1 + age2 + alco + smk, data = pregn)

Coefficients:
  (Intercept)        nutr     age1     age2     alco      smk
2    3.958370 -0.04644903 2.913475 1.887550 1.067001 2.230492
1    5.475147 -0.06541919 2.957028 2.059662 2.042900 2.452362

Std. Errors:
  (Intercept)       nutr      age1      age2      alco       smk
2    1.941063 0.01488581 0.8575544 0.8088255 0.6495262 0.6681955
1    2.271677 0.01823916 0.9644921 0.8947727 0.7097461 0.7315106

Value/SE (Wald statistics):
  (Intercept)      nutr     age1     age2     alco      smk
2    2.039280 -3.120357 3.397423 2.333693 1.642738 3.338083
1    2.410179 -3.586743 3.065891 2.301883 2.878354 3.352463

Residual Deviance: 168.6754 
AIC: 192.6754

線形予測子が2つあることに注意。多項ロジスティック回帰は目的変数のカテゴリのうち1つを基準として他のカテゴリと比較するので、(カテゴリ数 - 1)だけの線形予測子がある。詳しくはこの辺を参照のこと。モデルが全体として有意であるかどうかには切片だけのモデルとで尤度比検定を行なう。

> pregn.mltnom0 <- multinom(dur~1,pregn)
# weights:  6 (2 variable)
initial  value 112.058453 
final  value 110.343080 
converged
> anova(pregn.mltnom,pregn.mltnom0)
                            Model Resid. df Resid. Dev   Test    Df LR stat.      Pr(Chi)
1                               1       202   220.6862           NA       NA           NA
2 nutr + age1 + age2 + alco + smk       192   168.6754 1 vs 2    10 52.01072 1.135910e-07

ところで summary() では Wald 検定量は出せるけど、p値まではない。Wald 検定量をオブジェクトとして取り出すこともできないみたい。なのでヘッセ行列の逆行列をとって標準誤差を再計算、そこから求めた Wald 検定量をカイ2乗にしてからp値を無理やり算出してみた。

> se <- matrix(sqrt(diag(solve(pregn.mltnom$Hess))),nrow=2,byrow=T)
> (pval <- 1-pchisq((coef(pregn.mltnom)/se)^2,df=1))
  (Intercept)         nutr         age1       age2        alco          smk
2  0.04142210 0.0018063197 0.0006802383 0.01961182 0.100437243 0.0008435843
1  0.01594470 0.0003348344 0.0021702208 0.02134177 0.003997568 0.0008009609

というわけで最初の線形予測因子のなかの飲酒歴以外はみな有意だということがわかる。さらにオッズ比とその95%信頼区間の算出。

> (OR <- exp(coef(pregn.mltnom))[,2:6])
       nutr     age1     age2     alco       smk
2 0.9546132 18.42070 6.603172 2.906650  9.304445
1 0.9366747 19.24071 7.843318 7.712946 11.615748
> (lCI <- exp(coef(pregn.mltnom)-qnorm(.975)*se)[,2:6])
       nutr     age1     age2     alco      smk
2 0.9271641 3.430476 1.352942 0.813795 2.511432
1 0.9037818 2.905654 1.357900 1.919037 2.769391
> (uCI <- exp(coef(pregn.mltnom)+qnorm(.975)*se)[,2:6])
       nutr      age1     age2     alco      smk
2 0.9828749  98.91398 32.22746 10.38175 34.47145
1 0.9707648 127.40843 45.30349 30.99969 48.72032

例えば20歳以下の出産の場合(age1)、満期出産に対する早産のオッズは21-30歳での出産に比べて19倍以上高くなることがわかる(ただし信頼区間も大きいことに注意)。
さて同じデータを使って、次は順序ロジスティック回帰をやってみる。これには MASS パッケージの polr() 関数を使う。

> # Ordinal logistic regression (Proportional odds model)
> 
> library(MASS)
> pregn.polr <- polr(dur~nutr+age1+age2+alco+smk,pregn)
> summary(pregn.polr)

Re-fitting to get Hessian

Call:
polr(formula = dur ~ nutr + age1 + age2 + alco + smk, data = pregn)

Coefficients:
           Value Std. Error   t value
nutr -0.04886324 0.01182065 -4.133718
age1  1.97601134 0.57612022  3.429859
age2  1.36345136 0.54642384  2.495227
alco  1.66984881 0.47532016  3.513103
smk   1.59155788 0.45160776  3.524204

Intercepts:
    Value   Std. Error t value
3|2 -5.0236  1.5441    -3.2534
2|1 -2.9289  1.4926    -1.9623

Residual Deviance: 173.5122 
AIC: 187.5122 

今度はβ係数はそれぞれ1つしかないけど、切片が2つあることに注意。これはつまり、順序尺度カテゴリのどこで2つに分けてロジットをとっても切片が違うだけでその勾配が同じ、つまりオッズ比が常に同じであることを仮定しているから。順序ロジスティック回帰が比例オッズモデルとも呼ばれるのはそのため。詳しくはこの辺を参照のこと。
モデルが全体として有意であるかどうか。切片だけのモデルとで尤度比検定。

> pregn.polr0 <- polr(dur~1,pregn)
> anova(pregn.polr,pregn.polr0)
Likelihood ratio tests of ordinal regression models

Response: dur
                            Model Resid. df Resid. Dev   Test    Df LR stat.     Pr(Chi)
1                               1         2   220.6862                                  
2 nutr + age1 + age2 + alco + smk         7   173.5122 1 vs 2     5 47.17396 5.23592e-09

オッズ比とその信頼区間の計算については多重ロジスティック回帰のときに定義した関数が使える。

> orci(pregn.polr)

Re-fitting to get Hessian

              OR          lCI        uCI
nutr 0.952311356 0.9305017733  0.9746321
age1 7.213911681 2.3322569620 22.3133739
age2 3.909663710 1.3397463660 11.4092269
alco 5.311364686 2.0922465828 13.4833987
smk  4.911394341 2.0267285310 11.9018379
3|2  0.006580665 0.0003190898  0.1357146
2|1  0.053456459 0.0028675993  0.9965106

また順序ロジスティック回帰については Design パッケージのロジスティック回帰用の関数 lrm() も使うことができる。使い方は多重ロジスティック回帰のときと同じ。

> library(Design)
> attach(pregn)
> ddist <- datadist(nutr,age1,age2,alco,smk)
> options(datadist='ddist')
> detach(pregn)
> pregn.lrm <- lrm(dur~nutr+age1+age2+alco+smk,pregn,x=T,y=T)
> summary(pregn.lrm,nutr=c(100:101))
             Effects              Response : dur 

 Factor      Low High Diff. Effect S.E. Lower 0.95 Upper 0.95
 nutr        100 101  1     -0.05  0.01 -0.07      -0.03     
  Odds Ratio 100 101  1      0.95    NA  0.93       0.97     
 age1          0   1  1      1.98  0.58  0.85       3.11     
  Odds Ratio   0   1  1      7.21    NA  2.33      22.32     
 age2          0   1  1      1.36  0.55  0.29       2.43     
  Odds Ratio   0   1  1      3.91    NA  1.34      11.41     
 alco          0   1  1      1.67  0.48  0.74       2.60     
  Odds Ratio   0   1  1      5.31    NA  2.09      13.49     
 smk           0   1  1      1.59  0.45  0.71       2.48     
  Odds Ratio   0   1  1      4.91    NA  2.03      11.90  

順序ロジスティック回帰のいちばんの仮定である比例オッズを検証するにはどうすればいいか。SAS の PROC LOGISTIC だと Score Test というものを出してくれるけど、よく分からない。だけどこの辺によると、順序ロジスティックモデルと多項ロジスティックモデルとで尤度比検定すればいいらしい。もし多項ロジスティックモデルのほうのフィットがいいなら比例オッズの仮定は適切ではない。以下は自分で対数尤度を拾って計算したものと、リンク先にあった関数を使ったもの。

> # Test the proportional odds assumption
> 1-pchisq(173.5122-168.6754,df=5)
[1] 0.4361205
> parlines<-function(mod1, mod2){
+    dev2<-deviance(mod1)
+    dev<-deviance(mod2)
+    d<-abs(mod1$edf-mod2$edf)
+    ch2<-abs(dev2-dev)
+    p<-1-pchisq(ch2, d)
+    output<-round(cbind(ch2, d, p),5)
+    dimnames(output)[[2]]<-c("Chi-square", "df", "p-value")
+    output }
> parlines(pregn.mltnom,pregn.polr)
     Chi-square df p-value
[1,]    4.83677  5 0.43612

統計的に有意でないので比例オッズの仮定は適切といえる。