Day 024 — 確率分布(正規分布・二項分布)

2026-05-03 白 / Phase 1 コーディング + 理論 確率分布(正規分布・二項分布)

📚 背景知識(読んでから問題へ)

確率分布とは?

「コインを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)。

  1. 100人の訪問者のうち、クリック数の期待値(平均)と標準偏差を計算せよ
  2. 100人中ちょうど k=15人 がクリックする確率を計算せよ
  3. 100人中 20人以上 がクリックする確率を計算せよ
  4. np.random.binomial を使って10,000回シミュレーションし、結果をヒストグラムで可視化せよ

問題B: 正規分布の分析(コーディング + 理論)

あるECサイトのユーザーの購買金額が正規分布に従い、平均 μ=5,000円、標準偏差 σ=1,500円 とする。

  1. 購買金額が 7,000円以上 になる確率を計算せよ
  2. 購買金額が 3,000〜7,000円 の範囲に入る確率を計算せよ
  3. 上位5% のユーザーの購買金額の閾値(何円以上)を求めよ
  4. 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個」(離散・二項分布)
PDF確率密度関数正規分布のベルカーブ
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入門(配列操作・数値計算)
  • 発展: 中心極限定理(なぜ「サンプル平均」は正規分布になるのか)

🎯 自己評価

自分の回答

気づき・メモ