一覧へ

Pythonで実験の統計分析をする

scipyでt-test、ANOVAを実行しp-valueを解釈する方法。バイオ研究者のための統計分析入門。

中級
|
90
|
検証済み (2026-06)
p-valuet-testANOVA仮説検定scipy統計的有意性
進捗0/12 (0%)

Pythonで実験の統計分析をする

このトピックを終えたら

Pythonで2つのグループの差をt-testで検定し、3グループ以上はANOVAで比較し、p-valueを正しく解釈できるようになります。


なぜ統計検定が必要か

薬物処理グループの細胞生存率の平均が80%、対照群が85%です。「薬物に効果がある」と結論づけられますか?

平均が違うからといって必ず「差がある」とは限りません。実験を繰り返すと毎回違う値が出ます — これが**変動(variation)**です。5%の差が薬物効果なのか、単なる実験誤差なのかを区別する必要があります。

これを判断するツールが統計検定(statistical test)で、その結果がp-valueです。

p-valueとは

p-valueは「差がないと仮定した時に、これくらいの差が偶然に生じる確率」です。

text
p-value = 0.03 → 「差がないのにこの程度の差が偶然に出る確率が3%」
                → 偶然とは考えにくい → 差があると判断

p-value = 0.42 → 「差がないのにこの程度の差が偶然に出る確率が42%」
                → 十分に偶然の範囲内 → 差があるとは言いにくい

慣例的にp < 0.05なら「統計的に有意(statistically significant)」とします。ただし0.05は絶対的な基準ではなく慣例です。

t-test:2つのグループを比較

Independent t-test — 2つの独立したグループの平均を比較します。

python
import numpy as np
from 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.05

stats.ttest_ind() — independent(独立)t-test。2つのグループが異なるサンプルの場合に使います。

Paired t-test:同じサンプルの前後比較

同じ患者で治療前/後を比較する時はpaired t-testを使います:

python
from scipy import stats
import 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-teststats.ttest_ind()
同じ対象の前/後比較Paired t-teststats.ttest_rel()

ANOVA:3グループ以上の比較

3種類の培地(DMEM、RPMI、MEM)で細胞増殖率を比較するには? t-testは2グループしか比較できないので、**ANOVA(分散分析)**を使います。

python
from scipy import stats
import 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.05

ANOVAのp-valueが有意であれば「3グループのうち少なくとも1つは異なる」という意味です。どのグループが異なるかは事後検定(post-hoc test)で確認します。

結果の可視化:箱ひげ図

数字だけでは分布を把握しにくいです。箱ひげ図で可視化します:

python
import matplotlib.pyplot as plt
import 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を実行してください。

穴埋め問題python
from import stats
import numpy as np
treated = 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()で欠損値を除去してから検定してください。

💬 質問・コメント

0件のコメント

ログインせずに投稿できます。ゲスト投稿は投稿者自身で編集・削除できません。

0/2000

読み込み中...