Up

実験群と統制群の平均値の差の分析例

共変量の分布に重なりが無い場合

 

実験群と統制群の平均値の差の分析において、傾向スコア(propensity score)による共変量の分布の調整が説明されている(Imbens & Rubin, 2015)。しかし、2群における共変量の分布に重なりが無いときは、傾向スコアによる調整はできない。共変量の分布に重なりが無いデータの例として、図1のものを考える。

図1

 

Unitはデータのユニット番号、Yは差を調べる変量、Wは条件(0:統制群、1:実験群)、Xは共変量である。

共変量Xのヒストグラムは、図2のようである。

図2

 

統制群(W0)における共変量Xの分布と実験群(W1)における共変量Xの分布に重なりがないことが分かる。

統制群と実験群のXYの散布図を図3に示す。

図3

 

統制群(W0、青丸)と実験群(W1、橙色丸)においてYの値の平均値に差が無いようであるが、Yの値は共変量Xの値が増加すると統制群および実験群において減少している。したがって、共変量Xの全域においてYXの増加とともに減少している可能性がある。Yと共変量Xとの関係を考えて統制群と実験群の平均値の差を検討する必要がある。

以下では、まず、統制群と実験群の比較を直接行い、続いて共変量とYとの関係を調べる。最後に、共変量の影響を考慮した統制群と実験群の比較を行う。

なお、Jupyter Notebook用のファイルも用意した。Jupyter Notebook用では最小2乗法により回帰分析を行っている。NotebookのファイルとデータファイルをまとめてScript_JN.zipとした。ダウンロード展開すれば自由に用いることができる。また、Jupyter Notebookでのhtmlファイル出力mvregression.htmlも用意した。クリックすれば表示される。

 

 

 

統制群と実験群の比較

 

統制群と実験群の比較を、モデル(1)により行う。

ここで、YWは図1のデータにおけるYWであり、比較対象の変量Yと条件を表す変量(W0:統制群、W1:実験群)である。Eは残差(誤差)項である。Wの係数tは統制群に対して実験群の平均値がどれだけ大きいかを表し、aは統制群におけるYの平均値である。Eは平均0の正規分布に従う確率変数である。

モデル(1)によるデータ(図1)のベイズ分析を行うスクリプトをリスト1のように用意した。

リスト1のスクリプトを実行すると、MCMCサンプルのトレース図が図W1のように表示される。

W1

 

パラメータtの事後分布は、図W2のようである。

W2

 

中央値が0.02mad sd11.21であるので、事後分布の中ほどに0があり、tの値は0と見なせるようである。

パラメータaとt、それぞれの事後分布の中央値を値とする回帰直線

のグラフを図W3に示す。

W3

 

回帰直線はほぼ水平である。

 

以上より、統制群と実験群の測定値Yの平均値には差はないと言えそうであるが、共変量が無視されている。次に、共変量Xと測定値Yとの関係を調べた後、共変量も考慮して、統制群と実験群の差を分析する。

 

 

 

リスト1 モデル(1)によるベイズ分析スクリプト(PyStan 3対応)

 

import pandas as pd

import numpy as np

import stan

import arviz as az

import matplotlib.pyplot as plt

 

infl_nm = 'YWX_1.xlsx'

with pd.ExcelFile(infl_nm) as f_xlsx: 

    data_xlsx = pd.read_excel(f_xlsx)

 

print(data_xlsx.keys())

w_data = data_xlsx.values

print(w_data)

Y = np.array(data_xlsx['Y'], dtype='float')

W = np.array(data_xlsx['W'], dtype='float')

X = np.array(data_xlsx['X'], dtype='float')

for v in zip(Y, W, X):

    print(v)

 

stan_code = """

    data {

        int N;

        array[N] real Y;

        array[N] real W;

    }

    parameters {

        real va;

        real vt;

        real<lower=0.0> vsgm;

    }

    transformed parameters {

        real a;

        real t;

        real sgm;

        a = va * 1000.0;

        t = vt * 1000.0;

        sgm = 0.0001 + vsgm * 100.0;

    }

    model {

        va ~ normal(0, 1);

        vt ~ normal(0, 1);

        vsgm ~ exponential(1.0);

        for (i in 1:N) {

            Y[i] ~ normal(a + t*W[i], sgm);

        

        }

    }

"""

 

 

Data = {'N':len(Y), 'Y':Y, 'W':W}

 

sm = stan.build(stan_code, data=Data)

fit = sm.sample()

 

infdata = az.from_pystan(posterior = fit, posterior_model = sm)

 

summary = az.summary(infdata)

print('\nSummary...\n', summary)

 

az.plot_trace(infdata, var_names=['a', 't', 'sgm'])

plt.tight_layout()

plt.show()

 

dframe = fit.to_frame()

a_med = np.median(dframe['a'])

t_med = np.median(dframe['t'])

t_mad_sd = 1.483 * np.median(np.abs(dframe['t'] - t_med))

 

az.plot_kde(dframe['t'])

plt.title(f'Med.={t_med:.2f},  mad sd ={t_mad_sd:.2f}', fontsize=18)

plt.xlabel('t', fontsize=18)

plt.show()

 

plt.scatter(W, Y)

plt.plot([W[0], W[9]], [a_med + t_med*W[0], a_med + t_med*W[9]],

         label=f'Y=a+tW')

plt.plot([], color='#ffffff', label=f'a={a_med:.2f}(med.)\nt={t_med:.2f}(med.)')

plt.xticks([0, 1], [0, 1], fontsize= 16)

plt.xlabel('W: 0/control, 1/treated', fontsize=16)

plt.ylabel('Y', fontsize=18)

plt.legend(fontsize=16)

plt.tight_layout()

plt.show()

 

 

 

共変量Xと測定値Yの関係

 

共変量Xと測定値Yとの関係を調べるために、次の単回帰モデルを設定する。

ここで、Eは残差(誤差)である。

モデル(2)により図1のデータのベイズ分析を行うために、リスト2のスクリプトを作成した。

リスト2を実行すると、図X1のトレース図を得る。

X1

 

測定値Yと共変量Xの関係を表すパラメータbの事後分布は図X2のようである。

X2

 

中央値が−1.96で、mad sd1.67であるので、共変量Xが増加すると観測値Yは減少していると言えそうである。

モデル(2)のパラメータabの中央値をパラメータの値とするときの回帰直線

のグラフを描くと図X3に示すものとなる。

X3

 

共変量Xと測定値Yとの間の関係は無視できないと思われる。測定値Yの統制群と実験群の差を共変量Xも考慮して分析する。

 

 

リスト2 モデル(2)による単回帰分析(PyStan 3対応)

 

import pandas as pd

import numpy as np

import stan

import arviz as az

import matplotlib.pyplot as plt

 

infl_nm = 'YWX_1.xlsx' # input('Input data file (*xlsx) = ')

with pd.ExcelFile(infl_nm) as f_xlsx: 

    data_xlsx = pd.read_excel(f_xlsx)

 

print(data_xlsx.keys())

w_data = data_xlsx.values

print(w_data)

Y = np.array(data_xlsx['Y'], dtype='float')

W = np.array(data_xlsx['W'], dtype='float')

X = np.array(data_xlsx['X'], dtype='float')

for v in zip(Y, W, X):

    print(v)

 

stan_code = """

    data {

        int N;

        array[N] real Y;

        array[N] real X;

    }

    parameters {

        real va;

        real vb;

        real<lower=0.0> vsgm;

    }

    transformed parameters {

        real a;

        real b;

        real sgm;

        a = va * 100.0;

        b = vb * 100.0;

        sgm = 0.0001 + vsgm * 100.0;

    }

    model {

        va ~ normal(0, 1);

        vb ~ normal(0, 1);

        vsgm ~ exponential(1.0);

        for (i in 1:N) {

            Y[i] ~ normal(a + b*X[i], sgm);

        }

    }

"""

 

Data = {'N':len(Y), 'Y':Y, 'X':X}

 

sm = stan.build(stan_code, data=Data)

fit = sm.sample()

 

infdata = az.from_pystan(posterior = fit, posterior_model = sm)

 

summary = az.summary(infdata)

print('\nSummary...\n', summary)

 

az.plot_trace(infdata, var_names=['a', 'b', 'sgm'])

plt.tight_layout()

plt.show()

 

dframe = fit.to_frame()

a_med = np.median(dframe['a'])

b_med = np.median(dframe['b'])

b_mad_sd = 1.483 * np.median(np.abs(dframe['b'] - b_med))

az.plot_kde(dframe['b'])

plt.title(f'Med.={b_med:.2f},  mad_sd ={b_mad_sd:.2f}', fontsize= 18)

plt.xlabel('b', fontsize=16)

plt.tight_layout()

plt.show()

 

plt.scatter(X, Y)

plt.plot([X[0], X[9]], [a_med + b_med*X[0], a_med + b_med*X[9]],

         label ='Y=a+bX')

plt.plot([], color="#ffffff", label=f'a={a_med:.2f}\nb={b_med:.2f}')

plt.xlabel('X', fontsize=16)

plt.ylabel('Y', fontsize=16)

plt.legend(fontsize=16)

plt.tight_layout()

plt.show()

 

 

 

共変量の影響を考慮した統制群と実験群の比較

 

共変量Xを考慮した統制群(W=0)と実験群(W=1)の測定値Yの差を次の重回帰モデルで表す。

ここで、Eは残差(誤差)を表す。

モデル(3)によるベイズ分析を行うスクリプトをリスト3のように用意した。

リスト3を実行すると、MCMCサンプリング後、サンプリングのトレース図が表示される(図WX1)。

WX

 

パラメータtの事後分布は、図WX2のようである。

WX

 

統制群と実験群の平均値の差を表すパラメータtの事後分布の中央値が50.21mad sd0.78であるので、実験群の測定値Yは統制群より大きいと言えそうである。

パラメータbの事後分布は、図WX3のようである。

WX

 

パラメータbは共変量の影響を表すが、事後分布の中央値が−10.00mad sd0.13であるので、共変量Xが大きくなると測定値Yは減少すると言えそうである。

モデル(3)の回帰モデルをパラメータの事後分布の中央値をパラメータ値として、W=0とW=1の場合に分けて描くと図WX4のようになる。

WX

 

W=0の場合の回帰直線

が赤の直線で描かれており、W=1の場合の回帰直線

が橙色の直線で描かれている。

直線(W0)は、統制群のデータ(緑色の小円)によく合っており、直線(W1)は実験群のデータ(青色の小円)によく合っている。

赤色の星印は、統制群の回帰モデル(W0)において、実験群の共変量に対応する値を統制群(W=0)であった場合の欠測値をimputeしたものである。同じ共変量に対する赤色の星印と青色の小円との縦方向の差が実験群の統制群に対する差(パラメータt)である。

 

 

 

リスト3 モデル(3)によるベイズ分析(PyStn 3対応)

 

import pandas as pd

import numpy as np

import stan

import arviz as az

import matplotlib.pyplot as plt

 

infl_nm = 'YWX_1.xlsx' # input('Input data file (*xlsx) = ')

with pd.ExcelFile(infl_nm) as f_xlsx: 

    data_xlsx = pd.read_excel(f_xlsx)

Y = np.array(data_xlsx['Y'], dtype='float')

W = np.array(data_xlsx['W'], dtype='float')

X = np.array(data_xlsx['X'], dtype='float')

for v in zip(Y, W, X):

    print(v)

 

IX = np.concatenate([np.ones((len(Y), 1)), np.reshape(W, (len(Y),1)),

                     np.reshape(X, (len(Y), 1))], axis=1)

beta = np.linalg.inv(IX.T @ IX) @ IX.T @ np.reshape(Y, (len(Y), 1))

print(beta)

 

stan_code = """

    data {

        int N;

        array[N] real Y;

        array[N] real W;

        array[N] real X;

        real t_a;

        real t_t;

        real t_b;

    }

    parameters {

        real a;

        real t;

        real b;

        real<lower=0.0> vsgm;

    }

    transformed parameters {

        real sgm;

        sgm = 0.0001 + vsgm;

    }

    model {

        a ~ normal(t_a, 10);

        t ~ normal(t_t, 10);

        b ~ normal(t_b, 10);

        vsgm ~ exponential(1.0);

        for (i in 1:N) {

            Y[i] ~ normal(a + t*W[i] + b*X[i], sgm);        

        }

    }

"""

 

Data = {'N':len(Y), 'Y':Y, 'W':W, 'X':X, 't_a':beta[0][0],

        't_t':beta[1][0], 't_b':beta[2][0]}

 

sm = stan.build(stan_code, data=Data)

fit = sm.sample(num_samples=10000)

 

infdata = az.from_pystan(posterior = fit, posterior_model = sm)

 

summary = az.summary(infdata, var_names=['a', 't', 'b', 'sgm'])

print('\nSummary...\n', summary)

 

az.plot_trace(infdata, var_names=['a', 't', 'b', 'sgm'])

plt.tight_layout()

plt.show()

 

dframe = fit.to_frame()

a_med = np.median(dframe['a'])

t_med = np.median(dframe['t'])

b_med = np.median(dframe['b'])

t_mad_sd = 1.483 * np.median(np.abs(dframe['t'] - t_med))

b_mad_sd = 1.483 * np.median(np.abs(dframe['b'] - b_med))

 

az.plot_kde(dframe['t'])

plt.title(f'med.={t_med:.2f},  mad sd ={t_mad_sd:.2f}', fontsize=18)

plt.xlabel('t', fontsize=18)

plt.show()

 

az.plot_kde(dframe['b'])

plt.title(f'med.={b_med:.2f},  mad sd ={b_mad_sd:.2f}', fontsize=18)

plt.xlabel('b', fontsize=16)

plt.show()

 

plt.scatter(X[:5], Y[:5], color='g', label='W=0')

plt.scatter(X[5:], Y[5:], color='b', label='W=1')

plt.plot([X[0], X[9]], [a_med + b_med*X[0], a_med + b_med*X[9]],

         color='magenta', label=f'W=0,  b={b_med:.2f}')

plt.plot([X[0], X[9]], [a_med + t_med + b_med*X[0],

                        a_med + t_med + b_med*X[9]],

         color='orange', label=f'W=1, t={t_med:.2f}')

Y_imp = [a_med + b_med*v for v in X[5:]]

plt.scatter(X[5:], Y_imp, marker='*', color='r', label='W=0(imputed)')

plt.xlabel('X', fontsize=16)

plt.ylabel('Y', fontsize=16)

plt.legend(fontsize=12)

plt.show()

 

 

 

引用文献

Imbens G. W. and Rubin, D. B. (2015). Causal Inference for Statistics, Social, and Biomedical Sciences: An Introduction. Cambridge University Press.

 

 

 

Home