Pythonで実験の統計分析をする
このトピックを終えたら
Pythonで2つのグループの差をt-testで検定し、3グループ以上はANOVAで比較し、p-valueを正しく解釈できるようになります。
なぜ統計検定が必要か
薬物処理グループの細胞生存率の平均が80%、対照群が85%です。「薬物に効果がある」と結論づけられますか?
平均が違うからといって必ず「差がある」とは限りません。実験を繰り返すと毎回違う値が出ます — これが**変動(variation)**です。5%の差が薬物効果なのか、単なる実験誤差なのかを区別する必要があります。
これを判断するツールが統計検定(statistical test)で、その結果がp-valueです。
p-valueとは
p-valueは「差がないと仮定した時に、これくらいの差が偶然に生じる確率」です。
p-value = 0.03 → 「差がないのにこの程度の差が偶然に出る確率が3%」
→ 偶然とは考えにくい → 差があると判断
p-value = 0.42 → 「差がないのにこの程度の差が偶然に出る確率が42%」
→ 十分に偶然の範囲内 → 差があるとは言いにくい慣例的にp < 0.05なら「統計的に有意(statistically significant)」とします。ただし0.05は絶対的な基準ではなく慣例です。
t-test:2つのグループを比較
Independent t-test — 2つの独立したグループの平均を比較します。
import numpy as npfrom scipy import stats
# 薬物処理グループの細胞生存率(%)drug = np.array([78, 82, 75, 80, 77, 83, 79, 76])
# 対照群の細胞生存率(%)control = np.array([85, 88, 82, 86, 90, 84, 87, 89])
t_stat, p_value = stats.ttest_ind(drug, control)
print(f"Drug 平均: {drug.mean():.1f}%")print(f"Control 平均: {control.mean():.1f}%")print(f"t-statistic: {t_stat:.3f}")print(f"p-value: {p_value:.4f}")
if p_value < 0.05: print("→ 統計的に有意な差あり (p < 0.05)")else: print("→ 有意な差なし")
assert p_value < 0.05stats.ttest_ind() — independent(独立)t-test。2つのグループが異なるサンプルの場合に使います。
Paired t-test:同じサンプルの前後比較
同じ患者で治療前/後を比較する時はpaired t-testを使います:
from scipy import statsimport numpy as np
before = np.array([120, 135, 128, 142, 138, 125])after = np.array([115, 128, 122, 130, 132, 118])
t_stat, p_value = stats.ttest_rel(before, after)
print(f"治療前平均: {before.mean():.1f}")print(f"治療後平均: {after.mean():.1f}")print(f"p-value: {p_value:.4f}")ttest_rel() — related(対応のある)t-test。同じ対象の前後の測定値を比較します。
| 状況 | 検定方法 | scipy関数 |
|---|---|---|
| 異なる2つのグループの比較 | Independent t-test | stats.ttest_ind() |
| 同じ対象の前/後比較 | Paired t-test | stats.ttest_rel() |
ANOVA:3グループ以上の比較
3種類の培地(DMEM、RPMI、MEM)で細胞増殖率を比較するには? t-testは2グループしか比較できないので、**ANOVA(分散分析)**を使います。
from scipy import statsimport numpy as np
dmem = np.array([1.2, 1.4, 1.3, 1.5, 1.1])rpmi = np.array([1.8, 1.7, 1.9, 2.0, 1.6])mem = np.array([1.0, 1.1, 0.9, 1.2, 1.0])
f_stat, p_value = stats.f_oneway(dmem, rpmi, mem)
print(f"DMEM 平均: {dmem.mean():.2f}")print(f"RPMI 平均: {rpmi.mean():.2f}")print(f"MEM 平均: {mem.mean():.2f}")print(f"F-statistic: {f_stat:.3f}")print(f"p-value: {p_value:.6f}")
assert p_value < 0.05ANOVAのp-valueが有意であれば「3グループのうち少なくとも1つは異なる」という意味です。どのグループが異なるかは事後検定(post-hoc test)で確認します。
結果の可視化:箱ひげ図
数字だけでは分布を把握しにくいです。箱ひげ図で可視化します:
import matplotlib.pyplot as pltimport numpy as np
drug = np.array([78, 82, 75, 80, 77, 83, 79, 76])control = np.array([85, 88, 82, 86, 90, 84, 87, 89])
fig, ax = plt.subplots(figsize=(6, 4))ax.boxplot([drug, control], labels=["Drug", "Control"])ax.set_ylabel("Cell Viability (%)")ax.set_title("Drug vs Control")plt.tight_layout()plt.savefig("drug_vs_control.png", dpi=150)plt.show()箱ひげ図は中央値、四分位範囲、外れ値を一目で表示します。論文Figureで最もよく見るグラフタイプの一つです。
注意点:p-valueの落とし穴
p-valueの解釈で注意すべき点:
1. p < 0.05は「重要な」差を意味しない
統計的有意性(statistical significance)と実質的有意性(practical significance)は異なります。サンプル数が非常に多いと、とても小さな差でもp < 0.05になり得ます。「この差は生物学的に意味があるか?」を常に一緒に考えてください。
2. p > 0.05は「差がない」を意味しない
「差があるという証拠が見つからなかった」という意味です。サンプル数が少なすぎると、実際に差があっても検出できません。
3. 多重比較問題
20,000遺伝子を一度に検定すると、5%の1,000個が偶然にp < 0.05になります。この場合Bonferroni補正または**FDR(偽発見率)**補正が必要です。
やってみよう(Faded Example)
空欄を埋めて、2つのグループのt-testを実行してください。
from import statsimport numpy as nptreated = np.array([4.2, 3.8, 4.5, 4.1, 3.9])control = np.array([5.1, 5.3, 4.9, 5.0, 5.2])t_stat, p_value = stats.ind(treated, control)print(f"p-value: {p_value:.4f}")if p_value < :print("有意な差あり")
よくあるエラーと解決法
Q: ModuleNotFoundError: No module named 'scipy'
pip install scipyでインストールしてください。Google Colabではすでにインストールされています。
Q: t-testとANOVAのどちらを使えばいいですか?
比較するグループが2つならt-test、3つ以上ならANOVAです。3グループをt-testで3回比較(A-B、A-C、B-C)すると多重比較問題が生じるので、ANOVAを使う必要があります。
Q: データが正規分布でない場合はどうしますか?
t-testとANOVAはデータがおおよそ正規分布に従うことを仮定しています。正規分布でない場合はノンパラメトリック検定を使います:Mann-Whitney U検定(stats.mannwhitneyu())、Kruskal-Wallis検定(stats.kruskal())。
Q: 結果にnanが出ます
データに欠損値(NaN)が含まれている可能性があります。np.nanmean(data)のようにnanを無視する関数を使うか、Pandasでdf.dropna()で欠損値を除去してから検定してください。