📚 背景知識(読んでから問題へ)
確率分布とは?
「コインを10回投げたとき、表が出る回数はどのくらい?」
「身長が175cm以上の人は全体の何%?」
こんな問いに答えるのが確率分布です。
二項分布(にこうぶんぷ)
- 使う場面: 「成功か失敗か」の2択が決まった回数ある場合
- 身近な例: コインを10回投げて表が出る回数・合格率20%の試験を5回受けて合格する回数
$$P(X=k) = \binom{n}{k} p^k (1-p)^{n-k}$$
→ 日本語で言うと: 「n回試して、ちょうどk回成功する確率」
重要な性質:
- 平均 (期待値) = n × p
- 分散 = n × p × (1-p)
正規分布(ガウス分布)
- 使う場面: 身長・体重・テスト点数など「自然界のほとんどのデータ」
- 特徴: 平均を中心に左右対称の「ベルカーブ」形状
- μ (ミュー) = 平均、σ (シグマ) = 標準偏差(ばらつきの大きさ)
黄金ルール(3シグマルール):
- 平均 ± 1σ 内 → 全データの約 68%
- 平均 ± 2σ 内 → 全データの約 95%
- 平均 ± 3σ 内 → 全データの約 99.7%
標準化(z-score): データを「平均0・標準偏差1」の正規分布に変換する
$$z = \frac{x - \mu}{\sigma}$$
📝 問題
問題A: 二項分布のシミュレーション(コーディング)
新しいECサイトの機能Aは、訪問者の20%がクリックするとわかっている(クリック率 p=0.2)。
- 100人の訪問者のうち、クリック数の期待値(平均)と標準偏差を計算せよ
- 100人中ちょうど k=15人 がクリックする確率を計算せよ
- 100人中 20人以上 がクリックする確率を計算せよ
- np.random.binomial を使って10,000回シミュレーションし、結果をヒストグラムで可視化せよ
問題B: 正規分布の分析(コーディング + 理論)
あるECサイトのユーザーの購買金額が正規分布に従い、平均 μ=5,000円、標準偏差 σ=1,500円 とする。
- 購買金額が 7,000円以上 になる確率を計算せよ
- 購買金額が 3,000〜7,000円 の範囲に入る確率を計算せよ
- 上位5% のユーザーの購買金額の閾値(何円以上)を求めよ
- 1,000人のユーザーデータをシミュレーションし、正規分布曲線と一緒に可視化せよ
🔍 ヒント(段階的開示)
ヒント1(方向性)
- Pythonの
scipy.statsモジュールに二項分布 (binom) と正規分布 (norm) が用意されている - pmf = 確率質量関数(ちょうどk個の確率)、cdf = 累積分布関数(k個以下の確率)
1 - cdf(k)で「k+1個以上の確率」が計算できる
ヒント2(アプローチ)
二項分布:
from scipy.stats import binom
# 期待値・標準偏差
mean, var = binom.stats(n=100, p=0.2) # var は分散
std = var ** 0.5
# ちょうどk=15の確率
prob_15 = binom.pmf(k=15, n=100, p=0.2)
# 20以上の確率 = 1 - P(19以下)
prob_20_or_more = 1 - binom.cdf(k=19, n=100, p=0.2)
正規分布:
from scipy.stats import norm
# 7000以上の確率 = 1 - cdf(7000)
prob_above_7000 = 1 - norm.cdf(7000, loc=5000, scale=1500)
# 上位5%の閾値 = ppf(0.95)
threshold = norm.ppf(0.95, loc=5000, scale=1500)
ヒント3(コード骨格)
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import binom, norm
# === 問題A ===
n, p = 100, 0.2
# 1. 期待値と標準偏差
mean_A = n * p # 手計算
std_A = (n * p * (1-p)) ** 0.5
# 2. ちょうど15人の確率
prob_exactly_15 = binom.pmf(k=15, n=n, p=p)
# 3. 20人以上の確率
prob_20_plus = 1 - binom.cdf(k=19, n=n, p=p)
# 4. シミュレーションとヒストグラム
simulations = np.random.binomial(n=n, p=p, size=10000)
plt.hist(simulations, bins=30, density=True, alpha=0.7)
plt.xlabel("クリック数")
plt.title("二項分布のシミュレーション (n=100, p=0.2)")
plt.show()
# === 問題B ===
mu, sigma = 5000, 1500
# 1. 7000以上の確率
prob_above_7000 = 1 - norm.cdf(7000, loc=mu, scale=sigma)
# 2. 3000〜7000の確率
prob_range = norm.cdf(7000, loc=mu, scale=sigma) - norm.cdf(3000, loc=mu, scale=sigma)
# 3. 上位5%の閾値
threshold_95 = norm.ppf(0.95, loc=mu, scale=sigma)
✅ 模範解答
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.stats import binom, norm
# =====================
# 問題A: 二項分布
# =====================
n, p = 100, 0.2
# 1. 期待値と標準偏差
mean_A = n * p
std_A = (n * p * (1 - p)) ** 0.5
print(f"=== 問題A ===")
print(f"1. 期待値 (平均クリック数): {mean_A:.1f}人")
print(f" 標準偏差: {std_A:.2f}人")
# 2. ちょうど15人の確率
prob_15 = binom.pmf(k=15, n=n, p=p)
print(f"\n2. ちょうど15人がクリックする確率: {prob_15:.4f} ({prob_15*100:.2f}%)")
# 3. 20人以上の確率
prob_20_plus = 1 - binom.cdf(k=19, n=n, p=p)
print(f"\n3. 20人以上がクリックする確率: {prob_20_plus:.4f} ({prob_20_plus*100:.2f}%)")
# 4. シミュレーション + 可視化
np.random.seed(42)
simulations_A = np.random.binomial(n=n, p=p, size=10000)
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
# ヒストグラム
axes[0].hist(simulations_A, bins=range(5, 36), density=True,
alpha=0.7, color='steelblue', label='シミュレーション')
# 理論値(PMF)を重ねる
k_range = np.arange(5, 36)
axes[0].plot(k_range, binom.pmf(k_range, n=n, p=p), 'ro-',
markersize=5, label='理論値 (PMF)')
axes[0].axvline(mean_A, color='red', linestyle='--', label=f'平均={mean_A}')
axes[0].set_xlabel('クリック数')
axes[0].set_ylabel('確率密度')
axes[0].set_title('二項分布 (n=100, p=0.2)')
axes[0].legend()
# =====================
# 問題B: 正規分布
# =====================
mu, sigma = 5000, 1500
# 1. 7000以上の確率
prob_above_7000 = 1 - norm.cdf(7000, loc=mu, scale=sigma)
z_7000 = (7000 - mu) / sigma
print(f"\n=== 問題B ===")
print(f"1. 購買金額が7000円以上の確率: {prob_above_7000:.4f} ({prob_above_7000*100:.2f}%)")
print(f" z-score: {z_7000:.2f}")
# 2. 3000〜7000円の確率
prob_range = norm.cdf(7000, loc=mu, scale=sigma) - norm.cdf(3000, loc=mu, scale=sigma)
print(f"\n2. 3000〜7000円の確率: {prob_range:.4f} ({prob_range*100:.2f}%)")
# 3. 上位5%の閾値
threshold_95 = norm.ppf(0.95, loc=mu, scale=sigma)
print(f"\n3. 上位5%の閾値: {threshold_95:.0f}円以上")
# 4. シミュレーション + 正規分布曲線
np.random.seed(42)
data_B = np.random.normal(loc=mu, scale=sigma, size=1000)
x = np.linspace(mu - 4*sigma, mu + 4*sigma, 300)
axes[1].hist(data_B, bins=40, density=True, alpha=0.6,
color='mediumseagreen', label='シミュレーション (n=1000)')
axes[1].plot(x, norm.pdf(x, loc=mu, scale=sigma), 'r-',
linewidth=2, label='理論値 (正規分布PDF)')
# 7000以上の領域を塗りつぶす
x_fill = np.linspace(7000, mu + 4*sigma, 100)
axes[1].fill_between(x_fill, norm.pdf(x_fill, loc=mu, scale=sigma),
alpha=0.3, color='orange', label=f'7000円以上 ({prob_above_7000*100:.1f}%)')
axes[1].axvline(threshold_95, color='purple', linestyle='--',
label=f'上位5%閾値 ({threshold_95:.0f}円)')
axes[1].axvline(mu, color='red', linestyle='--', label=f'平均={mu}円')
axes[1].set_xlabel('購買金額(円)')
axes[1].set_ylabel('確率密度')
axes[1].set_title('購買金額の正規分布 (μ=5000, σ=1500)')
axes[1].legend(fontsize=8)
plt.tight_layout()
plt.savefig('day024_distributions.png', dpi=100, bbox_inches='tight')
plt.show()
出力結果:
▶ 出力を見る
=== 問題A ===
1. 期待値 (平均クリック数): 20.0人
標準偏差: 4.00人
2. ちょうど15人がクリックする確率: 0.0481 (4.81%)
3. 20人以上がクリックする確率: 0.5398 (53.98%)
=== 問題B ===
1. 購買金額が7000円以上の確率: 0.0912 (9.12%)
z-score: 1.33
2. 3000〜7000円の確率: 0.8176 (81.76%)
3. 上位5%の閾値: 7468円以上🪜 Step-by-Step 解説
1
二項分布の計算
n * p = 100 × 0.2 = 20→ 平均20人クリックbinom.pmf(k=15, ...)→ ちょうど15人の確率(約4.8%)1 - binom.cdf(k=19, ...)= 「20以上の確率」= 「19以下でない確率」(約54%)
2
z-score の直感
7000円の z-score = (7000 - 5000) / 1500 = 1.33
→「平均より1.33σ高い」→ 右側の約9%が該当
3
ppf(パーセンタイル点関数)
norm.ppf(0.95, ...) は「累積確率95%になる値」= 上位5%の閾値
→ 約7468円以上が「上位5%の優良顧客」
4
PDF vs PMF vs CDF
| 関数 | 読み | 使う場面 |
|---|---|---|
| PMF | 確率質量関数 | 「ちょうどk個」(離散・二項分布) |
| 確率密度関数 | 正規分布のベルカーブ | |
| CDF | 累積分布関数 | 「k以下」の確率 |
| PPF | パーセンタイル点 | 「上位X%」の閾値を逆算 |
📐 数学・統計の補足(文系向け)
期待値 = 平均 のことです。「平均的に何回起きるか」という直感通りです。
3シグマルールの覚え方:
±1σ = 68% → 約2/3
±2σ = 95% → 約19/20
±3σ = 99.7% → ほぼ全員
z-scoreの直感:
- z = 0: 平均値
- z = 1: 平均より1.5倍のばらつき分高い
- z = -2: 平均より2倍のばらつき分低い(下位約2.5%)
🏆 Kaggleでの実践的な使い方
二項分布の活用:
- CTR(クリック率)の分析・A/Bテストの統計的有意差検定
- 欠損率・異常値の発生頻度モデリング
正規分布の活用:
- 数値特徴量の分布確認(歪んでいるか正規に近いか)
- 外れ値検出: 平均 ± 3σ から外れたデータは外れ値候補
- 特徴量の標準化 (StandardScaler) = z-score変換そのもの
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
# これは内部的に z = (x - mean) / std を行っている
X_scaled = scaler.fit_transform(X)
⚠️ よくある誤解・ミス
| 誤解・ミス | なぜ起こるか | 正しい理解 |
|---|---|---|
binom.cdf(19) で「19以上の確率」と思う | cdf = 累積 = 19以下 | 1 - binom.cdf(19) = 20以上の確率 |
| 正規分布は必ず左右対称と思い込む | 教科書の例が対称的 | 現実のデータは歪んでいることが多い(対数正規分布等) |
| 確率密度(PDF)の値が1を超えると驚く | 確率そのものではなく「密度」 | PDF×区間幅=確率(積分すると1になる) |
| 標準化すると情報が消えると誤解 | 「変換=変形」のイメージ | 分布の形は保たれる、スケールだけ変わる |
🚀 次のステップ
- 発展問題: ポアソン分布(「1時間あたりの注文数」の分布)
- 次回予告: Phase 1 テーマ5 — numpy入門(配列操作・数値計算)
- 発展: 中心極限定理(なぜ「サンプル平均」は正規分布になるのか)