statsmodelsによる一般化推定方程式 入門

StatsmodelsはPythonというプログラミング言語上で動く統計解析ソフトです。statsmodelsのサンプルを動かすにはPCにPythonがインストールされている必要があります。まだインストールされていない方は[Jupyter notebookのインストール](https://qiita.com/innovation1005/items/2f433d6d859f075033a7)を参照してください。Jupyter notebookはstatsmodelsを動かすのに大変便利です。

多くの部分は[statsmodelsのリファレンス](https://www.statsmodels.org/v0.10.0/regression.html)などを参考にしています。

# 一般化推定方程式 (Generalized Estimating Equations)

一般化推定方程式は、パネル、クラスタ、反復測定データという観測値がクラスター内で相関を持ち、クラスター外で相関をもたないデータの集団における平均的な効果を推定することを目的としたモデルで、[一般化線形モデル](https://qiita.com/innovation1005/items/8b8a2ca2176134692155)の1つである。クラスター内では相関をもつとしているが、相関が生じる原因を特定していない、または特定できないともいえる。平均構造と相関構造を別々に扱える。GLMは

- 従属変数$y_i$が指数分布族にしたがう。
- 従属変数がそれぞれ独立である。  

という仮定のもとに

- 確率変数$y_i$の確率分布を仮定する。  
- $y_i$の期待値$\mu$の線形回帰モデルを設定する。  
- $y_i$の仮定した確率分布の尤度関数を作りそれを最大化する$\beta(\hat{\beta})$を求める。  
- 最尤推定量$\hat{\beta}$は一致性と漸近正規性をもつ。  

として、モデルを推定する。

GEEでは、

- 従属変数$y_i$が指数分布族にしたがうという仮定を緩める。  
- 従属変数が独立であるという仮定も緩める。  
- 疑似尤度を用いて未知な分布に対応する。
- 作業用相関行列(working correlation matrix) $R_i(\alpha)$を相関構造を表現するために導入する。
- 一般化推定方程式を2段階に分けて数値計算で解く。  

という順序でモデルを推定する。したがって、データの多様性に対処できるが、情報量も必要になる。

## 例: てんかん患者の発作の回数

Thall と Vail (1990) は59人のてんかん患者の8週間のベースライン期間と4回の連続した2週間ごとの発作回数について分析した。ベースライン期間においては患者への処置は施さない。ベースライン期間後に患者は無作為にプラセボまたはプロガビドを処方される。処方後の観測は４ｘ２＝８週間行われる。

データの詳細：
使用:てんかん
フォマット：236行9列

y：2週間の発作の数
trt: 処置、プラセボ、またはプロガビド
base：8週間のベースライン期間の発作の数
age：被験者の年齢、歳
V4：4期間の0/1指示変数
subject：被験者の番号, 1 to 59.
period : 期間, 1 to 4.
lbase : ベースライン期間での発作数の対数、平均をゼロに標準化
lage : 年齢の対数、平均をゼロに標準化

てんかんの発作は生存時間解析の再発事象に相当する。再発事象データは，同一被験者の発作の発生を繰り返し測定することで得られ、経時データ (longitudinal data) や反復測定データ (repeated measures data) の一種としてとらえられる。被験者が異なればてんかんの発作の回数に相関がないと考えられるが，同一被験者についての測定であれば結果には相関があると考えるのが自然である。

薬剤投与前にベースライン期間として8週間，薬剤投与後は2週間間隔で計 4 回の観測を行い、区間ごとに発作回数を調べている。被験者の年齢だけが共変量である。


まず、初期化してデータを得る。

```Python
import statsmodels.formula.api as smf
data = sm.datasets.get_rdataset('epil', package='MASS').data
```

### 記述統計

データの様子を調べる。

```Python
data.head(10)
```
```
y	trt	base	age	V4	subject	period	lbase	lage
0	5	placebo	11	31	0	1	1	-0.756354	0.114204
1	3	placebo	11	31	0	1	2	-0.756354	0.114204
2	3	placebo	11	31	0	1	3	-0.756354	0.114204
3	3	placebo	11	31	1	1	4	-0.756354	0.114204
4	3	placebo	11	30	0	2	1	-0.756354	0.081414
5	5	placebo	11	30	0	2	2	-0.756354	0.081414
6	3	placebo	11	30	0	2	3	-0.756354	0.081414
7	3	placebo	11	30	1	2	4	-0.756354	0.081414
8	2	placebo	6	25	0	3	1	-1.362490	-0.100908
9	4	placebo	6	25	0	3	2	-1.362490	-0.100908
```


プラセボという有効成分を含まない薬とプロガビドというGABA類似体系抗てんかん薬を処方された後のてんかんの発作回数の分布を比べてみる。データの数が前者が112で後者が124である。

```Python
data[data.trt!="placebo"].y.hist()
data[data.trt=="placebo"].y.hist()

data[data.trt!="placebo"].y.count(),data[data.trt=="placebo"].y.count()
```
```
(124, 112)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/c1a578c8-bdcd-cde7-0f9e-f26e4aa1bcc5.png)

標準化したベースライン期間の発作回数の対数と年齢の対数を頻度図にしてみる。

```Python
data.lbase.hist()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/f5e76586-b771-1048-afa2-28fa6e198a9d.png)

```Python
data.lage.hist()
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/6cf6c5c4-3269-8b6d-5908-9923560696e4.png)

つぎに記述統計を得る。ベースライン期間の発作の回数の平均と標準偏差

```Python
data.base.mean(),data.base.std()
```
```
(31.220338983050848, 26.705051109822254)
```
プロガビドを施した後の発作の回数の平均と標準偏差

```Python
data[data.trt!="placebo"].y.mean(),data[data.trt!="placebo"].y.std()
```
```
(7.959677419354839, 13.92978863002629)
```
プロガビドを施した患者のベースライン期間の発作回数の平均と標準偏差

```Python
data[data.trt!="placebo"].base.mean(),data[data.trt!="placebo"].base.std()
```
```
(31.612903225806452, 27.638405492528324)
```
プラセボを施した後の発作の回数の平均と標準偏差

```Python
data[data.trt=="placebo"].y.mean(),data[data.trt=="placebo"].y.std()
```
```
(8.580357142857142, 10.369426989352696)
```
プラセボを施した患者のベースライン期間の発作回数の平均と標準偏差

```Python
data[data.trt=="placebo"].base.mean(),data[data.trt=="placebo"].base.std()
```
```
(30.785714285714285, 25.749111266541444)
```

被験者の年齢とベースライン期間の発作の回数の対数をプロットしている。

```Python
plt.scatter(data.lage,data.lbase)
rg=sm.OLS(data.lbase,data.lage).fit()
plt.plot(data.lage,rg.fittedvalues)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/658ea3a1-5010-5b21-b7f7-46d071515bfc.png)



### 推測統計
モデルを推定する。発作発生回数をポアソン分布にしたがうとする。

#### 作業用相関行列の違い

##### independence()

作業用相関行列について、それぞれの患者の反復測定の対となるデータは独立であると仮定する。
"subject"はgroupsを示している。

```Python
fam = sm.families.Poisson()
ind = sm.cov_struct.Independence()
mod = smf.gee("y ~ age + trt + base", "subject", data,
              cov_struct=ind, family=fam)

res = mod.fit()
print(res.summary())
```
```
GEE Regression Results                              
===================================================================================
Dep. Variable:                           y   No. Observations:                  236
Model:                                 GEE   No. clusters:                       59
Method:                        Generalized   Min. cluster size:                   4
                      Estimating Equations   Max. cluster size:                   4
Family:                            Poisson   Mean cluster size:                 4.0
Dependence structure:         Independence   Num. iterations:                     2
Date:                     Sat, 02 Nov 2019   Scale:                           1.000
Covariance type:                    robust   Time:                         13:06:54
====================================================================================
                       coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------------
Intercept            0.5730      0.361      1.589      0.112      -0.134       1.280
trt[T.progabide]    -0.1519      0.171     -0.888      0.375      -0.487       0.183
age                  0.0223      0.011      1.960      0.050    2.11e-06       0.045
base                 0.0226      0.001     18.451      0.000       0.020       0.025
==============================================================================
Skew:                          3.7823   Kurtosis:                      28.6672
Centered skew:                 2.7597   Centered kurtosis:             21.9865
==============================================================================
```
つぎに被験者ごとの発作の回数の平均値と分散の対数をとってブロットしてみる。

```Pythoh
yg = mod.cluster_list(np.asarray(data["y"]))
ymn = [x.mean() for x in yg]
yva = [x.var() for x in yg]
plt.grid(True)
plt.plot(np.log(ymn), np.log(yva), 'o')
plt.plot([-2, 6], [-2, 6], '-', color='grey')
plt.xlabel("Log variance", size=17)
plt.ylabel("Log mean", size=17)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/91e5cd23-28c5-a39b-774d-80390f868b5d.png)

##### exchangeable()
この場合には、作業用相関行列についてそれぞれの患者の反復測定の対となるデータは同じ相関をもつと仮定する。

```Python
fam = sm.families.Poisson()
ex = sm.cov_struct.Exchangeable()
mod = smf.gee("y ~ age + trt + base", "subject", data,
              cov_struct=ex, family=fam)

res = mod.fit()
print(res.summary())
```
```
 GEE Regression Results                              
===================================================================================
Dep. Variable:                           y   No. Observations:                  236
Model:                                 GEE   No. clusters:                       59
Method:                        Generalized   Min. cluster size:                   4
                      Estimating Equations   Max. cluster size:                   4
Family:                            Poisson   Mean cluster size:                 4.0
Dependence structure:         Exchangeable   Num. iterations:                     2
Date:                     Thu, 31 Oct 2019   Scale:                           1.000
Covariance type:                    robust   Time:                         07:23:48
====================================================================================
                       coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------------
Intercept            0.5730      0.361      1.589      0.112      -0.134       1.280
trt[T.progabide]    -0.1519      0.171     -0.888      0.375      -0.487       0.183
age                  0.0223      0.011      1.960      0.050    2.11e-06       0.045
base                 0.0226      0.001     18.451      0.000       0.020       0.025
==============================================================================
Skew:                          3.7823   Kurtosis:                      28.6672
Centered skew:                 2.7597   Centered kurtosis:             21.9865
==============================================================================
```

```Python
fig = res.plot_isotropic_dependence()
fig.get_axes()[0].set_ylim(0, 1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/35298635-4dd0-1d21-cd88-0ffe65adb1a3.png)

```Python
rslts = res.params_sensitivity(0., 0.9, 5)
dp = [x.cov_struct.dep_params for x in rslts]
age_params = np.asarray([x.params[2] for x in rslts])
age_se = np.asarray([x.bse[2] for x in rslts])
plt.grid(True)
plt.plot(dp, age_params, '-', color='orange', lw=4)
plt.plot(dp, age_params + age_se, '-', color='grey')
plt.plot(dp, age_params - age_se, '-', color='grey')
plt.axvline(res.cov_struct.dep_params, ls='--', color='black')
plt.xlabel("Autocorrelation parameter", size=14)
plt.ylabel("Age effect", size=14)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/6752d927-c563-0f62-781e-05cc28d7fcdb.png)


```Python
plt.plot(res.fittedvalues, res.resid, 'o', alpha=0.5)
plt.xlabel("Fitted values", size=17)
plt.ylabel("Residuals", size=17)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/04ee3acd-3928-015b-0e08-b2478902e928.png)

##### Autoregressive()
この場合には、作業用相関行列についてそれぞれの患者の反復測定の対となるデータは自己相関をもつと仮定する。

```Python
fam = sm.families.Poisson()
au = sm.cov_struct.Autoregressive()
mod = smf.gee("y ~ age + trt + base", "subject", data,
              cov_struct=au, family=fam)

res = mod.fit()
print(res.summary())
```
```
GEE Regression Results                              
===================================================================================
Dep. Variable:                           y   No. Observations:                  236
Model:                                 GEE   No. clusters:                       59
Method:                        Generalized   Min. cluster size:                   4
                      Estimating Equations   Max. cluster size:                   4
Family:                            Poisson   Mean cluster size:                 4.0
Dependence structure:       Autoregressive   Num. iterations:                    11
Date:                     Sat, 02 Nov 2019   Scale:                           1.000
Covariance type:                    robust   Time:                         13:08:52
====================================================================================
                       coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------------
Intercept            0.4123      0.422      0.977      0.329      -0.415       1.240
trt[T.progabide]    -0.0704      0.164     -0.430      0.667      -0.391       0.250
age                  0.0274      0.013      2.108      0.035       0.002       0.053
base                 0.0223      0.001     18.252      0.000       0.020       0.025
==============================================================================
Skew:                          3.9903   Kurtosis:                      30.1753
Centered skew:                 2.7597   Centered kurtosis:             21.9865
==============================================================================
```

```Python
fig = res.plot_isotropic_dependence()
fig.get_axes()[0].set_ylim(0, 1)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/6a482637-bda3-1220-cd35-f092204554d6.png)

```Python
rslts = res.params_sensitivity(0., 0.9, 5)
dp = [x.cov_struct.dep_params for x in rslts]
age_params = np.asarray([x.params[2] for x in rslts])
age_se = np.asarray([x.bse[2] for x in rslts])
plt.grid(True)
plt.plot(dp, age_params, '-', color='orange', lw=4)
plt.plot(dp, age_params + age_se, '-', color='grey')
plt.plot(dp, age_params - age_se, '-', color='grey')
plt.axvline(res.cov_struct.dep_params, ls='--', color='black')
plt.xlabel("Autocorrelation parameter", size=14)
plt.ylabel("Age effect", size=14)
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267055/25f324b0-62cb-c4af-d587-8dd47677e68a.png)

試験薬の投与がプラセボと比較して発作の回数を減らしているかどうかを、作業用相関行列を変えながら検定したが、この臨床試験から一般化推定方程式を用いた場合には有意だという結果は得られない。

## 例：てんかん発作（Leppik et.al.1987）

同じ59人のてんかん発作のデータを用いて、一般推定方程式を用いて臨床試験の結果を評価した場合を見てみる。

まずはデータの構造を変えて、ベースライン期間の発作回数を$y_{ij}$の中に入れる。
$$log E(Y_{ij})=log(T_{ij})+\beta_0+\beta_1x_{ij1}+\beta_2x_{ij2}+\beta_3x_{ij1}x_{ij2}$$
ここで$x_{ij1}$はランダム化の前(0)と後(1)の時期を二値で表現している。$x_{ij2}$はプラセボ(0)、試験薬(1)の治療群を二値で示している。$T_{ij}$はベースライン期間の8週間と治療後の各測定期間の2週間の区間の長さである。

```Python
import pandas as pd
yy=pd.DataFrame(np.arange(59)+1,columns=['subject'])
yy['period']=np.log(8)
yy['y']=np.NaN
yy['trt']=0#np.NaN
yy['btrt']=0
data['btrt']=1
data['period']=np.log(2)

ii=0
for i in range(len(data)):
    if data.subject.iloc[i]==yy.subject.iloc[ii]:
        yy.iloc[ii,2]=data.base.iloc[i]
        yy.iloc[ii,3]=data.trt.iloc[i]
        yy.iloc[ii,0]=data.subject.iloc[i]
        ii+=1
        if ii>=59:
            break
yy=pd.concat([data,yy],axis=0,join='inner')

for i in range(len(yy)):
    if yy.iloc[i,1]=='progabide':
        yy.iloc[i:i+1,1]=1
    if yy.iloc[i,1]=='placebo':
        yy.iloc[i:i+1,1]=0
```

```Python
mod = smf.gee("y ~ btrt + trt ", "subject", yy,
              cov_struct=ex, family=fam,offset='period')

res = mod.fit()
print(res.summary())
```
```
  GEE Regression Results                              
===================================================================================
Dep. Variable:                           y   No. Observations:                  295
Model:                                 GEE   No. clusters:                       59
Method:                        Generalized   Min. cluster size:                   5
                      Estimating Equations   Max. cluster size:                   5
Family:                            Poisson   Mean cluster size:                 5.0
Dependence structure:         Exchangeable   Num. iterations:                     6
Date:                     Sat, 02 Nov 2019   Scale:                           1.000
Covariance type:                    robust   Time:                         23:30:28
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept      1.3240      0.168      7.883      0.000       0.995       1.653
btrt           0.0560      0.106      0.530      0.596      -0.151       0.263
trt            0.0705      0.213      0.331      0.740      -0.346       0.487
==============================================================================
Skew:                          3.3538   Kurtosis:                      15.0311
Centered skew:                 2.0882   Centered kurtosis:              6.6169
==============================================================================
```
臨床試験の試験群が有意であるという結果は得られない。そこで少し変えて見える。共変量である被験者の年齢を加えてみる。

```Python
import pandas as pd
yy=pd.DataFrame(np.arange(59)+1,columns=['subject'])
yy['period']=0
yy['y']=np.NaN
yy['trt']=0#np.NaN
yy['btrt']=0
yy['age']=0
data = sm.datasets.get_rdataset('epil', package='MASS').data
data['btrt']=1

ii=0
for i in range(len(data)):
    if data.subject.iloc[i]==yy.subject.iloc[ii]:
        yy.iloc[ii,2]=data.base.iloc[i]
        #yy.iloc[ii,3]=data.trt.iloc[i]
        yy.iloc[ii,0]=data.subject.iloc[i]
        yy.iloc[ii,5]=data.age.iloc[i]
        ii+=1
        if ii>=59:
            break
#print(yy.head(200))
yy=pd.concat([data,yy],axis=0,join='inner')

for i in range(len(yy)):
    if yy.iloc[i,1]=='progabide':
        yy.iloc[i,1]=1
    if yy.iloc[i,1]=='placebo':
        yy.iloc[i,1]=0
yy[yy.trt==0].count()
```

```Python
mod = smf.gee("y ~ age + btrt + trt ", "subject", yy,
              cov_struct=ind, family=fam,offset='period')

res = mod.fit()
print(res.summary())
```
```
GEE Regression Results                              
===================================================================================
Dep. Variable:                           y   No. Observations:                  295
Model:                                 GEE   No. clusters:                       59
Method:                        Generalized   Min. cluster size:                   5
                      Estimating Equations   Max. cluster size:                   5
Family:                            Poisson   Mean cluster size:                 5.0
Dependence structure:         Independence   Num. iterations:                     2
Date:                     Sat, 02 Nov 2019   Scale:                           1.000
Covariance type:                    robust   Time:                         23:48:30
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
Intercept      3.9937      0.614      6.503      0.000       2.790       5.197
age           -0.0198      0.021     -0.954      0.340      -0.060       0.021
btrt          -4.3316      0.154    -28.050      0.000      -4.634      -4.029
trt           -0.1014      0.335     -0.303      0.762      -0.758       0.555
==============================================================================
Skew:                          2.6891   Kurtosis:                      13.3017
Centered skew:                 0.3066   Centered kurtosis:              3.7456
==============================================================================
```
有意にはならないという点は変わらない。

参考文献：

[再発事象データの解析](https://www.jstage.jst.go.jp/article/jjb/26/2/26_2_81/_pdf)
[Wiki notebooks for GEE](https://github.com/statsmodels/statsmodels/wiki/Examples#generalized-estimating-equations-gee)
[repeated measures of epileptic seizure counts](https://nbviewer.jupyter.org/urls/umich.box.com/shared/static/ir0bnkup9rywmqd54zvm.ipynb)


<A HREF="http://www.tradersshop.com/bin/showprod?a=17600&b=15&c=9784775941683">
     <IMG SRC="http://www.tradersshop.com/images/prod/9784775941683.jpg"
      </A>