- 技術 💻
確率分布とは|種類一覧・平均と分散・選び方をPythonで実演
結論
確率分布とは、確率変数がとる値と、その値が出る確率の対応を表したものです。すべての値の確率を足すと1になります。
この記事でわかること
- 主な9分布の平均・分散・使われる場面の早見表
- SciPyの確率・パーセント点と乱数100万個の一致
- 中心極限定理の実測と、分布の選び方の判断表
確率分布は、とる値が「飛び飛び」か「連続」かで2つに分かれます。どちらも、ある値以下になる確率(累積分布関数)で同じように表せます。
flowchart TD
A[確率変数 X<br>結果を数で表したもの] --> B{とる値は?}
B -->|飛び飛び<br>回数・件数| C[離散型<br>確率関数 P(X=x)]
B -->|連続した値<br>時間・長さ| D[連続型<br>確率密度関数 f(x)]
C --> E[累積分布関数<br>F(x)= P(X≤x)]
D --> E
急いでいる人向けに、よく調べられることの答えを先にまとめます。
| 知りたいこと | 答え | 詳しい節 |
|---|---|---|
| 確率分布とは | 値と確率の対応。確率の合計は1 | 確率分布とは |
| 離散型と連続型 | 回数・件数は離散、時間・長さは連続 | 離散型と連続型 |
| 確率密度関数とは | 面積が確率になる関数 | 3つの関数 |
| 累積分布関数とは | X が x 以下になる確率 | 3つの関数 |
| 平均・分散の求め方 | Σ x×P(x)、E[X²] − (E[X])² | 平均と分散 |
| 主な分布 | 二項・ポアソン・正規など9種 | 早見表 |
| Python で | scipy.stats の cdf・ppf |
scipy.stats |
| 中心極限定理 | 平均の分布は正規分布に近づく | 中心極限定理 |
確認環境:Ubuntu 24.04.4 LTS・Python 3.11.15・NumPy 2.4.4・SciPy 1.17.1。サイコロの確率分布表、9つの分布の確率・パーセント点・平均・分散、乱数100万個との比較、分布どうしの関係、中心極限定理、分布の当てはめ、よくある間違いのエラー文は、この記事のコードをすべて実行して出力を載せています。乱数は種(357)を固定しています。関数の仕様とパラメータの意味は SciPy v1.18.0 の公式ドキュメント(scipy.stats・各分布のページ・1.15.0 のリリースノート)、確率分布と中心極限定理の定義は日本語版 Wikipedia で確かめました(確認日 2026年10月23日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「確率分布とは|種類一覧・平均と分散・選び方をPythonで実演」
確率分布とは(サイコロで意味をつかむ)
この節では、確率分布が「何と何の対応」なのかを、サイコロの例で分かります。
確率分布(probability distribution)は、確率変数がとりうる値のそれぞれに、その値が出る確率を割り当てたものです。 確率変数とは、サイコロの目・1分あたりのアクセス数・API の応答時間のように、「やってみるまで値が決まらない数」のことで、大文字の X で書きます。
サイコロを1回振ったときの確率分布を表にしたものが「確率分布表」です。右の列は、後の節で説明する累積分布関数(x 以下になる確率)です。
| 出る目 x | 確率 P(X=x) | x 以下になる確率 F(x) |
|---|---|---|
| 1 | 1/6 | 1/6 |
| 2 | 1/6 | 2/6 |
| 3 | 1/6 | 3/6 |
| 4 | 1/6 | 4/6 |
| 5 | 1/6 | 5/6 |
| 6 | 1/6 | 6/6 = 1 |
確率分布には、次の2つの決まりがあります。
- 1どの値の確率も0以上1以下マイナスの確率や、1を超える確率はない(連続型の「密度」は1を超えることがある。後の節)
- 2すべての値の確率を足すと1サイコロなら 1/6 × 6 = 1。何かの値は必ず出る、という意味
サイコロを2個振って目を足すと、確率分布の形が変わります。和が7になる組み合わせは36通りのうち6通りで、いちばん出やすくなります。
「確率分布」は、この棒グラフの形そのものです。 どこに値が集まり、どこまで広がるかを表します。データを集めて作るヒストグラム(度数分布)は「実際に出た回数」、確率分布は「理論上の出やすさ」で、データを増やすとヒストグラムは確率分布に近づきます。
サイコロ1個の確率分布表・平均・分散を、手計算(分数のまま)・SciPy・乱数100万回で出しました。
"""サイコロ1個:確率関数・累積分布関数・平均・分散(手計算・SciPy・乱数)"""
from fractions import Fraction as F
import numpy as np
from scipy import stats
xs = range(1, 7)
p = {x: F(1, 6) for x in xs}
cum = F(0)
print("x P(X=x) F(x)=P(X<=x)")
for x in xs:
cum += p[x]
print(f"{x} {p[x]} {cum}")
mean = sum(x * p[x] for x in xs) # E[X] = Σ x・P(X=x)
ex2 = sum(x * x * p[x] for x in xs) # E[X²]
var = ex2 - mean ** 2 # V[X] = E[X²] − (E[X])²
print(f"平均 E[X] = {mean} = {float(mean)}")
print(f"分散 V[X] = {ex2} - ({mean})^2 = {var} = {float(var):.4f} 標準偏差 = {float(var) ** 0.5:.4f}")
d = stats.randint(1, 7) # 1以上7未満の整数の一様分布
print(f"\nSciPy randint(1, 7): 平均 {d.mean()} 分散 {d.var():.4f} pmf(3) {d.pmf(3):.4f} cdf(4) {d.cdf(4):.4f}")
rng = np.random.default_rng(357)
r = rng.integers(1, 7, 1_000_000)
print(f"乱数100万回 : 平均 {r.mean():.4f} 分散 {r.var():.4f} 3の割合 {(r == 3).mean():.4f} 4以下 {(r <= 4).mean():.4f}")
x P(X=x) F(x)=P(X<=x)
1 1/6 1/6
2 1/6 1/3
3 1/6 1/2
4 1/6 2/3
5 1/6 5/6
6 1/6 1
平均 E[X] = 7/2 = 3.5
分散 V[X] = 91/6 - (7/2)^2 = 35/12 = 2.9167 標準偏差 = 1.7078
SciPy randint(1, 7): 平均 3.5 分散 2.9167 pmf(3) 0.1667 cdf(4) 0.6667
乱数100万回 : 平均 3.5000 分散 2.9164 3の割合 0.1668 4以下 0.6666
手計算・SciPy・乱数の3つで、平均 3.5・分散 2.9167 がそろいました。 乱数で100万回振ったときの「3が出た割合」0.1668 も、理論値 1/6 = 0.1667 とほぼ同じです。
離散型と連続型の違い
この節では、確率分布の2つの種類と、どちらになるかの見分け方が分かります。
離散型(りさんがた)
とる値が飛び飛び(0, 1, 2, … のように数えられる)
1つの値の確率 P(X=3) に意味がある
例:サイコロの目、購入した人数、1分あたりのアクセス数、成功するまでの試行回数
連続型(れんぞくがた)
とる値が連続(間にいくらでも細かい値がある)
1点の確率は0。区間の確率 P(180 ≤ X ≤ 220) で考える
例:応答時間、身長、待ち時間、測定の誤差
| 比べること | 離散型 | 連続型 |
|---|---|---|
| とる値 | 0, 1, 2, … のように数えられる | 実数のある範囲すべて |
| 分布を表す関数 | 確率関数(確率質量関数)P(X=x) | 確率密度関数 f(x) |
| 確率の出し方 | 値ごとの確率を足す | 密度の面積(積分)を求める |
| 1点の確率 | 0 より大きいことがある | 常に0 |
| 平均の式 | Σ x × P(X=x) | ∫ x × f(x) dx |
| 主な分布 | ベルヌーイ・二項・ポアソン・幾何 | 一様・正規・指数・t・カイ二乗 |
見分け方は「数える量か、測る量か」です。 人数・件数・回数は数えるので離散型、時間・長さ・重さは測るので連続型です。お金(円)は厳密には離散ですが、金額が大きいときは連続型(正規分布など)で近似することもよくあります。
確率関数・確率密度関数・累積分布関数の違い
この節では、確率分布を表す3つの関数の意味と、SciPy でどのメソッドに当たるかが分かります。
1確率関数(PMF)
離散型で、値 x が出る確率 P(X=x) を返す関数。確率質量関数とも呼ぶ。SciPy では pmf。
2確率密度関数(PDF)
連続型で、値の出やすさの「濃さ」を表す関数。区間の面積が確率になる。SciPy では pdf。
3累積分布関数(CDF)
X が x 以下になる確率 F(x) = P(X ≤ x)。離散型・連続型の両方にある。分布関数とも呼ぶ。SciPy では cdf。
累積分布関数は、確率を左から足し上げたものです。サイコロなら1段ずつ 1/6 増える階段になり、最後は必ず1になります。
3つの関数の関係を式で書くと、次のとおりです。
離散型 F(x) = P(X ≤ x) = (x 以下の値の P(X=k) をすべて足したもの)
連続型 F(x) = P(X ≤ x) = (−∞ から x までの f(t) の面積)
区間の確率 P(a < X ≤ b) = F(b) − F(a)
上側の確率 P(X > x) = 1 − F(x) ← SciPy の sf(生存関数)
パーセント点 F(x) = q となる x ← SciPy の ppf(CDF の逆関数)
確率密度は確率ではない(1を超えてよい)
連続型の確率密度 f(x) は「その点の確率」ではなく、幅を掛けて初めて確率になる量です。 そのため、1点の確率は0になり、密度そのものは1を超えることがあります。実際に計算しました(後の節のコードの一部)。
連続型では1点の確率は0: P(X=200) = cdf(200)-cdf(200) = 0.0
密度は1を超えてよい: norm(0, 0.1).pdf(0) = 3.9894
標準偏差 0.1 の正規分布は 0 の近くに集中しているので、密度の高さは 3.99 になります。それでも、曲線の下の面積は全体で1です。
パーセント点(ppf)とは
パーセント点は「下から q の割合のところにある値」で、累積分布関数の逆です。 「応答時間の95%点」「1分あたりのアクセス数が99%の確率で収まる上限」のように、上限や基準値を決めるときに使います。SciPy では ppf(percent point function)で、上から数えるときは isf を使います。検定の「両側5%の境界 1.96」も、標準正規分布の ppf(0.975) です。
確率分布の期待値(平均)と分散の求め方
この節では、確率分布から平均と分散を計算する手順と、知っておくと便利な性質が分かります。
期待値(平均)は「値 × その確率」をすべて足したもの、分散は「平均からのずれの2乗」の期待値です。 サイコロ1個で手計算します。
期待値 E[X] を出す
1×1/6 + 2×1/6 + … + 6×1/6 = 21/6 = 3.5。くり返し振ったときの平均に当たる。
2乗の期待値 E[X²] を出す
1²×1/6 + 2²×1/6 + … + 6²×1/6 = 91/6 = 15.17。
分散 V[X] を出す
V[X] = E[X²] − (E[X])² = 91/6 − 49/4 = 35/12 = 2.9167。ばらつきの大きさ。
標準偏差を出す
分散の平方根で √(35/12) = 1.708。値と同じ単位でばらつきを表す。
期待値(離散型) E[X] = Σ x × P(X=x)
期待値(連続型) E[X] = ∫ x × f(x) dx
分散 V[X] = E[(X − E[X])²] = E[X²] − (E[X])²
標準偏差 σ = √V[X]
計算を楽にする性質です。a・b は定数です。
| 性質 | 式 | 例 |
|---|---|---|
| 定数倍と足し算 | E[aX + b] = a×E[X] + b | 目を2倍して1を足すと平均は 3.5×2+1 = 8 |
| 分散は2乗で効く | V[aX + b] = a²×V[X] | 目を2倍すると分散は4倍。足した定数は効かない |
| 和の期待値 | E[X + Y] = E[X] + E[Y] | サイコロ2個の和の平均は 3.5+3.5 = 7 |
| 独立なら分散も足せる | V[X + Y] = V[X] + V[Y] | 2個の和の分散は 35/12×2 = 35/6 = 5.8333 |
| n 個の平均の分散 | V[平均] = V[X] ÷ n | 中心極限定理の節で実測 |
分散の式 E[X²] − (E[X])² は、手計算でもプログラムでもよく使います。 ただし浮動小数点数で大きな値を扱うと桁落ちしやすいので、NumPy の var は平均からのずれを先に引いてから2乗しています。
主な確率分布の早見表(9種類)
この節では、よく使う9つの分布の形・パラメータ・平均と分散・使われる場面を一覧で引けます。
| 分布 | 型・パラメータ | 平均 / 分散 | 使われる場面 |
|---|---|---|---|
| ベルヌーイ分布 | 離散・成功確率 p | p / p(1−p) | 1回の成功・失敗(クリックしたか) |
| 二項分布 | 離散・回数 n、確率 p | np / np(1−p) | n 人中の購入者数、n 回中の失敗数 |
| ポアソン分布 | 離散・平均 λ | λ / λ | 一定時間のアクセス数・障害件数 |
| 幾何分布 | 離散・成功確率 p | 1/p / (1−p)/p² | 初めて成功するまでの回数 |
| 一様分布 | 連続・区間 a〜b | (a+b)/2 / (b−a)²/12 | 乱数、リトライの待ち時間のずらし |
| 正規分布 | 連続・平均 μ、標準偏差 σ | μ / σ² | 測定誤差、平均値の分布 |
| 指数分布 | 連続・発生率 λ | 1/λ / 1/λ² | 次のアクセスまでの間隔、故障までの時間 |
| t 分布 | 連続・自由度 ν | 0 / ν/(ν−2) | 少ない標本での平均の検定・区間推定 |
| カイ二乗分布 | 連続・自由度 k | k / 2k | 分割表の検定、分散の区間推定 |
(t 分布の平均は自由度 ν > 1、分散は ν > 2 のときの値です。)
SciPy v1.18.0 のドキュメントには、1変量の連続分布が109、離散分布が21載っています(多変量の分布は別)。データ分析でよく出るのは、この表の9つと、右に裾が長いデータに使う対数正規分布・ガンマ分布です。
離散型の主な分布(ベルヌーイ・二項・ポアソン・幾何)
この節では、数える量に使う4つの分布の式と、IT の例で確率・パーセント点を計算した結果が分かります。
ベルヌーイ分布(1回の成功・失敗)
ベルヌーイ分布は、結果が「成功(1)」か「失敗(0)」の2つしかない1回の試行の分布です。 成功の確率を p とすると、P(X=1) = p、P(X=0) = 1 − p です。例えば、成功率 98% の API 呼び出し1回が失敗する確率は 0.02 です。ほかの離散分布の土台になります。
二項分布(n 回中の成功回数)
二項分布は、成功確率 p のベルヌーイ試行を n 回くり返したときの成功回数の分布です。 各回が独立で、p が毎回同じことが前提です。
P(X=k) = nCk × p^k × (1 − p)^(n − k) (nCk は n 個から k 個を選ぶ組み合わせの数)
例:購入率(CVR)3% のページに100人来て、ちょうど3人買う確率
P(X=3) = 100C3 × 0.03^3 × 0.97^97 = 161700 × 0.000027 × 0.05210… = 0.2275
ポアソン分布(一定時間に起きる回数)
ポアソン分布は、まれな出来事が一定の時間・範囲で何回起きるかの分布です。 平均 λ(ラムダ)だけで形が決まり、平均と分散がどちらも λ になります。1分あたりの問い合わせ数・1日の障害件数・1ページの誤字の数などに使います。
P(X=k) = e^(−λ) × λ^k ÷ k! (e は約 2.718、k! は k の階乗)
例:1分あたり平均4件の問い合わせで、ちょうど2件来る確率
P(X=2) = e^(−4) × 4² ÷ 2 = 0.018316 × 16 ÷ 2 = 0.1465
幾何分布(初めて成功するまでの回数)
幾何分布は、成功確率 p の試行を「初めて成功するまで」くり返したときの回数の分布です。 平均は 1/p で、成功率 20% なら平均5回です。「成功するまでの回数」で数える定義と、「成功する前の失敗回数」で数える定義の2つがあり、SciPy の geom は前者(1回目から数える)です。
P(X=k) = (1 − p)^(k − 1) × p (k−1 回失敗して、k 回目に初めて成功)
P(X ≤ k) = 1 − (1 − p)^k (k 回以内に1回は成功する確率)
4つの分布を、手計算と SciPy で出しました。
"""離散型の分布:二項・ポアソン・幾何を手計算と SciPy で"""
from math import comb, exp, factorial
from scipy import stats
# 二項分布:CVR 3% のページに100人が来たときの購入数
n, p = 100, 0.03
hand = comb(n, 3) * p**3 * (1 - p) ** (n - 3)
b = stats.binom(n, p)
print(f"二項 P(X=3) 手計算 {hand:.6f} SciPy {b.pmf(3):.6f}")
print(" k :", " ".join(f"{k:>6d}" for k in range(9)))
print(" pmf:", " ".join(f"{b.pmf(k):.4f}" for k in range(9)))
print(" cdf:", " ".join(f"{b.cdf(k):.4f}" for k in range(9)))
print(f" P(X=0) = 0.97^100 = {0.97**100:.4f} P(X>=6) = sf(5) = {b.sf(5):.4f} 95%点 ppf(0.95) = {b.ppf(0.95):.0f}")
# ポアソン分布:1分あたり平均4件の問い合わせ
lam = 4
po = stats.poisson(lam)
print(f"\nポアソン P(X=2) 手計算 {exp(-lam) * lam**2 / factorial(2):.6f} SciPy {po.pmf(2):.6f}")
print(" k :", " ".join(f"{k:>6d}" for k in range(11)))
print(" pmf:", " ".join(f"{po.pmf(k):.4f}" for k in range(11)))
print(f" P(X=0) = e^-4 = {po.pmf(0):.4f} P(X>=9) = {po.sf(8):.4f}")
for q in (0.9, 0.99, 0.999):
print(f" 1分の件数が {q:.1%} の確率で収まる上限 ppf({q}) = {po.ppf(q):.0f}")
# 幾何分布:成功率 20% の処理を、成功するまでくり返す(SciPy は「初めて成功した回」を数える)
g = stats.geom(0.2)
print(f"\n幾何 P(X=1..6):", " ".join(f"{g.pmf(k):.4f}" for k in range(1, 7)))
print(f" P(X<=3) = 1-0.8^3 = {1 - 0.8**3:.4f} SciPy {g.cdf(3):.4f} 平均 {g.mean():.1f} 回")
for q in (0.5, 0.9, 0.95, 0.99):
print(f" {q:.0%} の確率で成功するまでに要る回数 ppf({q}) = {g.ppf(q):.0f}")
二項 P(X=3) 手計算 0.227474 SciPy 0.227474
k : 0 1 2 3 4 5 6 7 8
pmf: 0.0476 0.1471 0.2252 0.2275 0.1706 0.1013 0.0496 0.0206 0.0074
cdf: 0.0476 0.1946 0.4198 0.6472 0.8179 0.9192 0.9688 0.9894 0.9968
P(X=0) = 0.97^100 = 0.0476 P(X>=6) = sf(5) = 0.0808 95%点 ppf(0.95) = 6
ポアソン P(X=2) 手計算 0.146525 SciPy 0.146525
k : 0 1 2 3 4 5 6 7 8 9 10
pmf: 0.0183 0.0733 0.1465 0.1954 0.1954 0.1563 0.1042 0.0595 0.0298 0.0132 0.0053
P(X=0) = e^-4 = 0.0183 P(X>=9) = 0.0214
1分の件数が 90.0% の確率で収まる上限 ppf(0.9) = 7
1分の件数が 99.0% の確率で収まる上限 ppf(0.99) = 9
1分の件数が 99.9% の確率で収まる上限 ppf(0.999) = 11
幾何 P(X=1..6): 0.2000 0.1600 0.1280 0.1024 0.0819 0.0655
P(X<=3) = 1-0.8^3 = 0.4880 SciPy 0.4880 平均 5.0 回
50% の確率で成功するまでに要る回数 ppf(0.5) = 4
90% の確率で成功するまでに要る回数 ppf(0.9) = 11
95% の確率で成功するまでに要る回数 ppf(0.95) = 14
99% の確率で成功するまでに要る回数 ppf(0.99) = 21
| 結果 | 読み方 |
|---|---|
| 二項:100人で0人の確率 0.0476 | CVR 3% でも、100人で1件も売れない日は約5%ある |
| 二項:6人以上の確率 0.0808 | sf(5) は「5より大きい」= 6以上。sf(6) ではない |
| ポアソン:99%点は9件 | 1分に9件まで処理できれば、99%の分は待たせない |
| ポアソン:P(X=3) と P(X=4) が同じ 0.1954 | λ が整数のとき、λ−1 と λ の確率は等しい |
| 幾何:平均5回、95%点は14回 | 平均だけで上限を決めると、半分近くは足りない |
「平均」と「95%点・99%点」は大きく違います。 平均4件のアクセスに対して4件分の処理能力しか用意しないと、1分のうち約37%(1 − F(4) = 1 − 0.6288)は処理が追いつきません。上限を決めるときは平均ではなくパーセント点で考えます。
連続型の主な分布(一様・正規・指数)
この節では、測る量に使う3つの分布の特徴と、確率・パーセント点を計算した結果が分かります。
一様分布(どの値も同じ出やすさ)
一様分布は、区間 a〜b のどの値も同じ密度で出る分布です。 密度は 1/(b − a) で一定、平均は区間の真ん中です。プログラムの乱数(random.random() など)の基本の形で、リトライの待ち時間をずらす「ジッター」にも使います。SciPy の uniform は loc(下端)と scale(幅)で指定し、区間は loc〜loc+scale です。
正規分布(平均の周りに左右対称)
正規分布は、平均 μ を中心に左右対称の釣り鐘型の分布で、測定誤差や平均値の分布によく合います。 ガウス分布とも呼びます。平均 μ と標準偏差 σ の2つで形が決まり、x を z = (x − μ) ÷ σ に変換(標準化)すると、平均0・標準偏差1の標準正規分布になります。
f(x) = 1 ÷ (σ × √(2π)) × e^(−(x − μ)² ÷ (2σ²))
指数分布(次に起きるまでの時間)
指数分布は、ポアソン分布に従って起きる出来事の「間隔」の分布です。 1分あたり平均4件(λ = 4/分)のアクセスなら、間隔は平均 1/λ = 15秒の指数分布になります(後の節で実測)。「すでに10秒待った」ことは、この先の待ち時間に影響しない、という無記憶性を持ちます。
f(x) = λ × e^(−λx)(x ≥ 0) P(X > x) = e^(−λx) SciPy では scale = 1/λ
3つの分布と、次の節で使う t 分布・カイ二乗分布のパーセント点をまとめて計算しました。
"""連続型の分布:一様・正規・指数と、t・カイ二乗のパーセント点"""
import numpy as np
from scipy import stats
u = stats.uniform(loc=0, scale=10) # 0〜10 秒の一様分布(loc から loc+scale まで)
print(f"一様 0〜10: pdf(3) {u.pdf(3):.2f} P(2<=X<=5) {u.cdf(5) - u.cdf(2):.2f} 平均 {u.mean():.1f} 分散 {u.var():.4f}")
nm = stats.norm(loc=200, scale=20) # 応答時間 平均200ms・標準偏差20ms(scale は標準偏差)
print(f"\n正規 μ=200 σ=20: pdf(200) {nm.pdf(200):.5f} P(X>250) {nm.sf(250):.5f} P(X<=180) {nm.cdf(180):.4f}")
print(f" 95%点 ppf(0.95) {nm.ppf(0.95):.2f} ms 99%点 {nm.ppf(0.99):.2f} ms 中央95%の区間 {nm.interval(0.95)[0]:.2f}〜{nm.interval(0.95)[1]:.2f}")
z = stats.norm()
for k in (1, 2, 3):
print(f" 平均±{k}σ に入る確率 {z.cdf(k) - z.cdf(-k):.4f}")
print(f" 連続型では1点の確率は0: P(X=200) = cdf(200)-cdf(200) = {nm.cdf(200) - nm.cdf(200)}")
print(f" 密度は1を超えてよい: norm(0, 0.1).pdf(0) = {stats.norm(0, 0.1).pdf(0):.4f}")
ex = stats.expon(scale=15) # 平均15秒ごとに来るアクセスの間隔(scale = 1/λ)
print(f"\n指数 平均15秒: P(T>30) {ex.sf(30):.4f} = e^-2 {np.exp(-2):.4f} 中央値 {ex.median():.2f} 95%点 {ex.ppf(0.95):.2f}")
print(f" 無記憶性 P(T>40 | T>10) = {ex.sf(40) / ex.sf(10):.4f} P(T>30) = {ex.sf(30):.4f}")
print("\n両側95%の境界(t と正規)")
for df in (1, 2, 5, 9, 20, 30, 100):
print(f" t 自由度{df:>3}: {stats.t.ppf(0.975, df):.3f}")
print(f" 正規分布 : {stats.norm.ppf(0.975):.3f}")
print("\nカイ二乗の上側5%点 ppf(0.95)")
print(" " + " ".join(f"自由度{df}: {stats.chi2.ppf(0.95, df):.3f}" for df in (1, 2, 3, 4, 5, 10)))
一様 0〜10: pdf(3) 0.10 P(2<=X<=5) 0.30 平均 5.0 分散 8.3333
正規 μ=200 σ=20: pdf(200) 0.01995 P(X>250) 0.00621 P(X<=180) 0.1587
95%点 ppf(0.95) 232.90 ms 99%点 246.53 ms 中央95%の区間 160.80〜239.20
平均±1σ に入る確率 0.6827
平均±2σ に入る確率 0.9545
平均±3σ に入る確率 0.9973
連続型では1点の確率は0: P(X=200) = cdf(200)-cdf(200) = 0.0
密度は1を超えてよい: norm(0, 0.1).pdf(0) = 3.9894
指数 平均15秒: P(T>30) 0.1353 = e^-2 0.1353 中央値 10.40 95%点 44.94
無記憶性 P(T>40 | T>10) = 0.1353 P(T>30) = 0.1353
両側95%の境界(t と正規)
t 自由度 1: 12.706
t 自由度 2: 4.303
t 自由度 5: 2.571
t 自由度 9: 2.262
t 自由度 20: 2.086
t 自由度 30: 2.042
t 自由度100: 1.984
正規分布 : 1.960
カイ二乗の上側5%点 ppf(0.95)
自由度1: 3.841 自由度2: 5.991 自由度3: 7.815 自由度4: 9.488 自由度5: 11.070 自由度10: 18.307
正規分布では、平均±1σ に 68.27%、±2σ に 95.45%、±3σ に 99.73% が入ります(68-95-99.7 則)。 平均200ms・標準偏差20ms の応答時間なら、250ms を超えるのは 0.621%、95%点は 232.90ms です。
指数分布では、平均(15秒)より中央値(10.40秒)が短くなります。 短い間隔が多く、たまに長い間隔がある右に偏った形だからです。無記憶性は、「10秒待ったうえで、さらに30秒以上待つ確率」0.1353 が、最初から30秒以上待つ確率と同じになることで確かめられました。
推定・検定で使う分布(t分布・カイ二乗分布)
この節では、データから平均や割合を調べるときに出てくる2つの分布の役割が分かります。
両側95%の境界(上の出力)
母集団の標準偏差を知らず、データから推定するときは t 分布を使う
t 分布は、標準偏差をデータから推定したときの「平均の差 ÷ 標準誤差」が従う分布です。 正規分布より裾が厚く、自由度(データ数 − 1 など)が大きくなると正規分布に近づきます。t 検定や平均の信頼区間で使います。
カイ二乗分布は、標準正規分布に従う値を k 個2乗して足したものの分布です。 自由度 k で、平均 k・分散 2k です。分割表のカイ二乗検定、適合度検定、分散の区間推定で使います。この関係は「分布どうしの関係」の節で乱数を使って確かめています。
検定でこれらの分布から p 値を求める手順は、関連記事「p値とは|意味・有意水準・よくある誤解をPythonで実演」で扱っています。
scipy.statsで確率・パーセント点を計算する
この節では、SciPy の確率分布のメソッドと、分布ごとのパラメータの渡し方が分かります。
| メソッド | 返すもの | 例(正規 μ=200 σ=20) |
|---|---|---|
pmf(k) |
離散型の P(X=k) | 二項・ポアソンなどだけ |
pdf(x) |
連続型の確率密度 | pdf(200) → 0.01995 |
cdf(x) |
P(X ≤ x) | cdf(180) → 0.1587 |
sf(x) |
P(X > x) = 1 − cdf | sf(250) → 0.00621 |
ppf(q) |
下から q の点(cdf の逆) | ppf(0.95) → 232.90 |
isf(q) |
上から q の点(sf の逆) | isf(0.05) → 232.90 |
interval(c) |
中央 c の割合が入る区間 | interval(0.95) → 160.80〜239.20 |
rvs(size=) |
乱数 | rvs(size=1000, random_state=rng) |
stats(moments=) |
平均・分散・歪度・尖度 | stats(moments="mv") |
fit(data) |
データからパラメータを推定 | 選び方の節で使用 |
分布ごとに引数の意味が違うので、取り違えに気をつけます。 SciPy の連続分布はすべて loc(ずらす量)と scale(伸ばす量)を持ち、分布固有のパラメータはその前に置きます。
| 分布 | SciPy の書き方 | 注意 |
|---|---|---|
| 二項 n=100, p=0.03 | stats.binom(100, 0.03) |
n・p の順 |
| ポアソン λ=4 | stats.poisson(4) |
引数名は mu |
| 幾何 p=0.2 | stats.geom(0.2) |
1回目から数える。失敗回数なら loc=-1 |
| 一様 a〜b | stats.uniform(loc=a, scale=b-a) |
2つ目は上端ではなく幅 |
| 正規 μ, σ | stats.norm(loc=μ, scale=σ) |
scale は分散ではなく標準偏差 |
| 指数 λ | stats.expon(scale=1/λ) |
位置引数で λ を渡すと loc になる |
| t 自由度 ν | stats.t(ν) |
標準化された t 分布 |
| カイ二乗 自由度 k | stats.chi2(k) |
― |
stats.norm(loc=200, scale=20) のようにパラメータを先に固定したものを「凍結した分布(frozen distribution)」と呼び、その後は nm.cdf(180) のように値だけで呼べます。ドキュメントでは、sf は「1 − cdf と同じだが、より正確な場合がある」と説明されています。上側の小さな確率は 1 - cdf ではなく sf で出します(よくある間違いの節で実例)。
SciPy 1.15 以降の新しい書き方
SciPy 1.15.0 のリリースノートによると、新しい確率分布の仕組みが追加され、scipy.stats.Normal と scipy.stats.Uniform が用意されました。パラメータを名前(mu・sigma)で渡すので、loc・scale の取り違えが起きにくい書き方です。手元の 1.17.1 では Binomial も使えました。
"""SciPy 1.15 以降の新しい書き方(確率変数のクラス)と、これまでの書き方の比較"""
from scipy import stats
X = stats.Normal(mu=200, sigma=20) # 新しい書き方:引数名で指定
print(f"Normal: mean {X.mean():.1f} variance {X.variance():.1f} P(X>250) {X.ccdf(250):.5f} 95%点 {X.icdf(0.95):.2f}")
old = stats.norm(loc=200, scale=20) # これまでの書き方
print(f"norm : mean {old.mean():.1f} var {old.var():.1f} P(X>250) {old.sf(250):.5f} 95%点 {old.ppf(0.95):.2f}")
print(f"Binomial があるか: {hasattr(stats, 'Binomial')} Uniform: {hasattr(stats, 'Uniform')}")
B = stats.Binomial(n=100, p=0.03)
print(f"Binomial(n=100, p=0.03): pmf(3) {B.pmf(3):.6f} P(X>=6) {B.ccdf(5):.4f}")
Normal: mean 200.0 variance 400.0 P(X>250) 0.00621 95%点 232.90
norm : mean 200.0 var 400.0 P(X>250) 0.00621 95%点 232.90
Binomial があるか: True Uniform: True
Binomial(n=100, p=0.03): pmf(3) 0.227474 P(X>=6) 0.0808
新しい書き方では、sf が ccdf(補累積分布関数)、ppf が icdf(cdf の逆関数)、var が variance という名前です。結果はこれまでの書き方と一致しました。
乱数のシミュレーションで計算と一致するか確かめる
この節では、SciPy の理論値が正しいかを、各分布の乱数100万個で確かめた結果が分かります。
分布の式やパラメータの渡し方を間違えていないかは、乱数をたくさん作って割合を数えると確かめられます。 9つの分布それぞれで、理論値(左)と乱数100万個から数えた値(右)を並べました。
"""主な9つの分布:SciPy の理論値と、乱数100万個のシミュレーションを比べる"""
import numpy as np
from scipy import stats
rng = np.random.default_rng(357)
N = 1_000_000
cases = [ # 名前, 分布, 確かめる確率, 理論値の出し方, 乱数で数える方法
("ベルヌーイ p=0.98", stats.bernoulli(0.98), "P(X=0)", lambda d: d.pmf(0), lambda s: (s == 0).mean()),
("二項 n=100 p=0.03", stats.binom(100, 0.03), "P(X>=6)", lambda d: d.sf(5), lambda s: (s >= 6).mean()),
("ポアソン λ=4", stats.poisson(4), "P(X>=9)", lambda d: d.sf(8), lambda s: (s >= 9).mean()),
("幾何 p=0.2", stats.geom(0.2), "P(X<=3)", lambda d: d.cdf(3), lambda s: (s <= 3).mean()),
("一様 0〜10", stats.uniform(loc=0, scale=10), "P(X<=2.5)", lambda d: d.cdf(2.5), lambda s: (s <= 2.5).mean()),
("正規 μ=200 σ=20", stats.norm(loc=200, scale=20), "P(X>250)", lambda d: d.sf(250), lambda s: (s > 250).mean()),
("指数 平均15", stats.expon(scale=15), "P(X>30)", lambda d: d.sf(30), lambda s: (s > 30).mean()),
("t 自由度9", stats.t(9), "P(|X|>2.262)", lambda d: 2 * d.sf(2.262), lambda s: (np.abs(s) > 2.262).mean()),
("カイ二乗 自由度5", stats.chi2(5), "P(X>11.07)", lambda d: d.sf(11.07), lambda s: (s > 11.07).mean()),
]
for name, d, label, prob, emp in cases:
s = d.rvs(size=N, random_state=rng)
m, v = d.stats(moments="mv")
print(f"{name}\n 平均 {float(m):.4f} / {s.mean():.4f} 分散 {float(v):.4f} / {s.var():.4f} {label} {prob(d):.4f} / {emp(s):.4f}")
ベルヌーイ p=0.98
平均 0.9800 / 0.9800 分散 0.0196 / 0.0196 P(X=0) 0.0200 / 0.0200
二項 n=100 p=0.03
平均 3.0000 / 3.0006 分散 2.9100 / 2.9177 P(X>=6) 0.0808 / 0.0811
ポアソン λ=4
平均 4.0000 / 4.0006 分散 4.0000 / 4.0112 P(X>=9) 0.0214 / 0.0215
幾何 p=0.2
平均 5.0000 / 4.9991 分散 20.0000 / 19.9395 P(X<=3) 0.4880 / 0.4873
一様 0〜10
平均 5.0000 / 4.9983 分散 8.3333 / 8.3387 P(X<=2.5) 0.2500 / 0.2505
正規 μ=200 σ=20
平均 200.0000 / 200.0383 分散 400.0000 / 399.5585 P(X>250) 0.0062 / 0.0062
指数 平均15
平均 15.0000 / 14.9981 分散 225.0000 / 224.4000 P(X>30) 0.1353 / 0.1352
t 自由度9
平均 0.0000 / -0.0007 分散 1.2857 / 1.2855 P(|X|>2.262) 0.0500 / 0.0500
カイ二乗 自由度5
平均 5.0000 / 5.0012 分散 10.0000 / 10.0111 P(X>11.07) 0.0500 / 0.0499
9つすべてで、平均・分散・確率が理論値と小数第2〜3位まで一致しました。 早見表の式(二項の分散 np(1−p) = 2.91、幾何の分散 (1−p)/p² = 20、t 分布の分散 ν/(ν−2) = 9/7 = 1.2857 など)が正しいことも、この結果で確かめられます。
乱数で数えた割合には、標本の数で決まる誤差があります。割合 p を N 個から数えたときの標準誤差は √(p(1−p)/N) で、P(X>30) = 0.1353 を100万個で数えると約 0.0003 です。差がこの数倍以内なら「一致している」と読めます。乱数の作り方の基本は、関連記事「NumPyの使い方 基本一覧|配列の作成・演算・形状変更」で扱っています。
中心極限定理をシミュレーションで確かめる
この節では、元の分布がどんな形でも、平均をとると正規分布に近づく様子が数値で分かります。
中心極限定理は、平均 μ・分散 σ² を持つ分布から独立に n 個取って平均すると、n が大きいとき、その平均は平均 μ・分散 σ²/n の正規分布に近づく、という定理です。 正規分布がよく使われる理由で、t 検定や A/B テストの計算の土台になっています。
右に大きく偏った指数分布(平均1・標準偏差1)から n 個取って平均する、を10万回くり返しました。
"""中心極限定理:指数分布(右に長く偏った分布)から n 個取った平均の分布"""
import numpy as np
from scipy import stats
rng = np.random.default_rng(357)
REPS = 100_000 # 「n 個取って平均する」を10万回くり返す
print(" n 平均の平均 平均の標準偏差 理論1/√n 歪度 下側2.5% 上側2.5%")
zs = {}
for n in (1, 2, 5, 10, 30, 100):
means = rng.exponential(scale=1.0, size=(REPS, n)).mean(axis=1) # 母集団: 平均1・標準偏差1
z = (means - 1.0) * np.sqrt(n) # 標準化(平均0・標準偏差1になるはず)
zs[n] = z
print(f"{n:>3} {means.mean():.4f} {means.std():.4f} {1 / np.sqrt(n):.4f} {stats.skew(means):+.3f}"
f" {(z < -1.96).mean():6.2%} {(z > 1.96).mean():6.2%}")
edges = np.arange(-3, 3.01, 1.0)
print("\n標準化した平均の分布(1刻み)")
print("区間 n=1 n=5 n=30 標準正規分布")
for lo, hi in zip(edges[:-1], edges[1:]):
cells = " ".join(f"{((zs[n] >= lo) & (zs[n] < hi)).mean():6.2%} " for n in (1, 5, 30))
print(f"{lo:+.0f}〜{hi:+.0f} {cells} {stats.norm.cdf(hi) - stats.norm.cdf(lo):6.2%}")
n 平均の平均 平均の標準偏差 理論1/√n 歪度 下側2.5% 上側2.5%
1 1.0009 1.0054 1.0000 +1.989 0.00% 5.28%
2 0.9980 0.7080 0.7071 +1.441 0.00% 4.95%
5 1.0008 0.4488 0.4472 +0.925 0.03% 4.43%
10 1.0000 0.3168 0.3162 +0.623 0.61% 3.96%
30 0.9995 0.1829 0.1826 +0.371 1.41% 3.44%
100 0.9994 0.0998 0.1000 +0.184 1.99% 2.90%
標準化した平均の分布(1刻み)
区間 n=1 n=5 n=30 標準正規分布
-3〜-2 0.00% 0.01% 1.22% 2.14%
-2〜-1 0.00% 14.67% 14.55% 13.59%
-1〜+0 63.30% 41.13% 36.66% 34.13%
+0〜+1 23.06% 28.96% 31.89% 34.13%
+1〜+2 8.57% 11.03% 12.46% 13.59%
+2〜+3 3.18% 3.23% 2.80% 2.14%
| 結果 | 読み方 |
|---|---|
| 平均の標準偏差 0.4488(n=5)、0.0998(n=100) | 理論の 1/√n とほぼ一致。100個平均すると散らばりは 1/10 |
| 歪度 +1.989 → +0.184 | 0 が左右対称。n が増えるほど偏りが消える |
| 上側2.5% が 5.28% → 2.90% | 正規分布なら 2.50%。n=30 でも 3.44% と少しずれる |
| n=30 の中央2区間 36.66%・31.89% | 正規分布の 34.13% に近いが、まだ左寄り |
平均を取ると、元の分布が大きく偏っていても、分布は左右対称の釣り鐘型に近づきました。 ただし「n = 30 なら正規分布とみなせる」はいつも成り立つわけではなく、偏りの強い分布では n = 30 でも上側の確率が 3.44% と、正規分布の 2.50% から離れています。データが偏っているときは、このように乱数で確かめると安心です。
分散が無い分布では成り立たない
中心極限定理は「分散を持つ」ことが前提です。分散が定義されないコーシー分布(自由度1の t 分布)で、平均の散らばりを四分位範囲で比べました。
"""分散が無い分布(コーシー分布=自由度1の t 分布)では、平均を取っても散らばりが小さくならない"""
import numpy as np
rng = np.random.default_rng(357)
for n in (1, 10, 100, 1000):
means = rng.standard_cauchy((10_000, n)).mean(axis=1)
q1, q3 = np.percentile(means, [25, 75])
print(f"n={n:>4}: 平均の四分位範囲 {q3 - q1:.3f}")
n= 1: 平均の四分位範囲 1.981
n= 10: 平均の四分位範囲 2.015
n= 100: 平均の四分位範囲 2.004
n=1000: 平均の四分位範囲 2.021
1,000個平均しても、散らばりは1個のときと同じ約2のままでした。 ごくまれに極端な値が出る分布では、平均をとっても値が安定しません。
確率分布どうしの関係(二項・ポアソン・指数・正規)
この節では、主な分布がどうつながっているかと、近似がどれくらい合うかが分かります。
flowchart TD
A[ベルヌーイ<br>1回の成否] -->|n 回の合計| B[二項分布]
B -->|n 大・p 小<br>λ = np| C[ポアソン分布<br>回数]
C -->|出来事の間隔| D[指数分布<br>時間]
B -->|np が大きい| E[正規分布]
E -->|2乗して足す| F[カイ二乗分布]
この関係を乱数と SciPy で確かめました。
"""分布どうしの関係を数値で確かめる"""
import numpy as np
from scipy import stats
rng = np.random.default_rng(357)
# 1) 二項分布 → ポアソン分布(n が大きく p が小さい。λ = n×p)
print("二項(n=1000, p=0.004) と ポアソン(λ=4) の P(X=k)")
for k in range(0, 9, 2):
print(f" k={k}: 二項 {stats.binom.pmf(k, 1000, 0.004):.4f} ポアソン {stats.poisson.pmf(k, 4):.4f}")
# 2) 二項分布 → 正規分布(n×p と n×(1−p) が大きい。半整数の補正つき)
n, p = 1000, 0.3
mu, sd = n * p, np.sqrt(n * p * (1 - p))
print(f"\n二項(n=1000, p=0.3) の P(X<=280): 正確 {stats.binom.cdf(280, n, p):.4f}"
f" 正規近似 {stats.norm.cdf(280.5, mu, sd):.4f}(補正なし {stats.norm.cdf(280, mu, sd):.4f})")
# 3) ポアソン過程:間隔が指数分布(平均15秒)なら、1分あたりの件数はポアソン分布(λ=4)
gaps = rng.exponential(scale=15, size=2_000_000)
t = np.cumsum(gaps)
counts = np.bincount((t[t < 60 * 100_000] // 60).astype(int), minlength=100_000)
print(f"\n指数分布の間隔から数えた1分あたりの件数(10万分): 平均 {counts.mean():.4f} 分散 {counts.var():.4f}")
print(" k :", " ".join(f"{k:>6d}" for k in range(9)))
print(" 乱数:", " ".join(f"{(counts == k).mean():.4f}" for k in range(9)))
print(" 理論:", " ".join(f"{stats.poisson.pmf(k, 4):.4f}" for k in range(9)))
# 4) 標準正規分布の2乗を k 個足すとカイ二乗分布(自由度 k)
z = rng.standard_normal((1_000_000, 5))
q = (z ** 2).sum(axis=1)
print(f"\nZ1²+…+Z5² : 平均 {q.mean():.4f} 分散 {q.var():.4f} P(>11.07) {(q > 11.07).mean():.4f} カイ二乗(5) 平均 5・分散 10・0.0500")
# 5) t = Z ÷ √(カイ二乗(9) ÷ 9) は自由度9の t 分布
tt = rng.standard_normal(1_000_000) / np.sqrt(rng.chisquare(9, 1_000_000) / 9)
print(f"Z÷√(χ²(9)/9): P(|t|>2.262) {(np.abs(tt) > 2.262).mean():.4f} t(9) の理論値 {2 * stats.t.sf(2.262, 9):.4f}")
二項(n=1000, p=0.004) と ポアソン(λ=4) の P(X=k)
k=0: 二項 0.0182 ポアソン 0.0183
k=2: 二項 0.1464 ポアソン 0.1465
k=4: 二項 0.1958 ポアソン 0.1954
k=6: 二項 0.1043 ポアソン 0.1042
k=8: 二項 0.0297 ポアソン 0.0298
二項(n=1000, p=0.3) の P(X<=280): 正確 0.0886 正規近似 0.0892(補正なし 0.0838)
指数分布の間隔から数えた1分あたりの件数(10万分): 平均 4.0002 分散 3.9809
k : 0 1 2 3 4 5 6 7 8
乱数: 0.0177 0.0733 0.1464 0.1951 0.1971 0.1565 0.1030 0.0603 0.0293
理論: 0.0183 0.0733 0.1465 0.1954 0.1954 0.1563 0.1042 0.0595 0.0298
Z1²+…+Z5² : 平均 5.0025 分散 10.0316 P(>11.07) 0.0501 カイ二乗(5) 平均 5・分散 10・0.0500
Z÷√(χ²(9)/9): P(|t|>2.262) 0.0502 t(9) の理論値 0.0500
| 関係 | 確かめた結果 |
|---|---|
| 二項(n=1000, p=0.004)≒ ポアソン(λ=4) | 各 k の確率が小数第3位まで一致 |
| 二項(n=1000, p=0.3)≒ 正規 | P(X≤280) 正確 0.0886、半整数補正つき 0.0892、補正なし 0.0838 |
| 間隔が指数分布なら、件数はポアソン分布 | 平均 4.0002・分散 3.9809。確率も理論値とほぼ一致 |
| 標準正規の2乗5個の和 = カイ二乗(自由度5) | 平均 5.0025・分散 10.0316 |
| Z ÷ √(カイ二乗 ÷ 自由度) = t 分布 | 両側5%の割合が 0.0502 |
二項分布を正規分布で近似するときは、境界に 0.5 を足す(半整数補正)と正確な値に近づきます。 補正なしだと 0.0838 で、正確な 0.0886 から離れます。今は SciPy で二項分布を直接計算できるので、近似は手計算や見積もりのときに使います。
確率分布の選び方(判断表)
この節では、手元のデータや問題にどの分布を使うかを、質問に答えて決められます。
flowchart TD
A{数える量か?<br>回数・件数} -->|はい| B{回数の上限<br>n がある?}
B -->|ある| D[二項分布]
B -->|ない| E[ポアソン分布]
A -->|いいえ<br>測る量| C{0 以上で<br>右に裾が長い?}
E ~~~ C
C -->|いいえ| F[正規分布]
C -->|はい| G[対数正規<br>ガンマ・指数]
| 調べたいこと | 使う分布 | 確かめること |
|---|---|---|
| 1回の成功・失敗 | ベルヌーイ分布 | 結果が2通りか |
| n 回・n 人のうち何回成功するか | 二項分布 | 各回が独立で、確率が同じか |
| 一定時間に何件起きるか(上限なし) | ポアソン分布 | 平均と分散がほぼ等しいか |
| 初めて成功するまで何回か | 幾何分布 | 1回目から数えるか、失敗回数か |
| 次に起きるまでの時間 | 指数分布 | 発生率が時間で変わらないか |
| どの値も同じ出やすさ | 一様分布 | 範囲の端が決まっているか |
| 平均の周りに左右対称に散らばる量 | 正規分布 | ヒストグラムが左右対称か |
| 応答時間・金額など0以上で右に長い量 | 対数正規・ガンマ分布 | 対数を取ると左右対称になるか |
| 少ない標本の平均の比較 | t 分布 | 標準偏差をデータから出したか |
| 分割表・割合のばらつき | カイ二乗分布 | 期待度数が小さすぎないか |
データから分布を選ぶときは、形(ヒストグラム)を見て候補を絞り、当てはめて比べます。 右に裾が長い応答時間のデータ(2,000件)に、4つの分布を当てはめました。AIC(赤池情報量規準)は小さいほど当てはまりがよく、KS 統計量はデータと分布の累積分布関数の最大のずれです。
"""データにどの分布が合うかを比べる(応答時間の例:右に裾が長いデータ)"""
import numpy as np
from scipy import stats
rng = np.random.default_rng(357)
x = rng.lognormal(mean=np.log(200), sigma=0.35, size=2000) # 本当は対数正規分布から作ったデータ
print(f"件数 {x.size} 平均 {x.mean():.1f} 中央値 {np.median(x):.1f} 標準偏差 {x.std(ddof=1):.1f} 歪度 {stats.skew(x):+.3f}")
for name, dist, kw in (("正規", stats.norm, {}), ("対数正規", stats.lognorm, {"floc": 0}),
("ガンマ", stats.gamma, {"floc": 0}), ("指数", stats.expon, {"floc": 0})):
params = dist.fit(x, **kw) # 最尤推定
ll = dist.logpdf(x, *params).sum()
k = len(params) - len(kw) # 推定したパラメータの数
aic = 2 * k - 2 * ll
ks = stats.kstest(x, dist.name, args=params)
p99 = dist.ppf(0.99, *params)
print(f"{name:<5} AIC {aic:9.1f} KS統計量 {ks.statistic:.4f} 99%点 {p99:6.1f}")
print(f"データの99%点(実測) {np.percentile(x, 99):.1f}")
件数 2000 平均 213.2 中央値 200.5 標準偏差 76.5 歪度 +1.118
正規 AIC 23026.4 KS統計量 0.0774 99%点 391.1
対数正規 AIC 22670.6 KS統計量 0.0162 99%点 451.5
ガンマ AIC 22704.9 KS統計量 0.0311 99%点 420.2
指数 AIC 25451.3 KS統計量 0.3612 99%点 981.9
データの99%点(実測) 456.1
右に偏ったデータに正規分布を当てはめると、99%点を 391.1 と見積もり、実際の 456.1 より 65 も小さくなりました。 元の分布(対数正規)を当てはめると 451.5 で、実測に近くなります。SLO(応答時間の目標)のように裾の値で判断するときほど、分布の選び方が結果に効きます。なお、データから推定したパラメータで KS 検定の p 値を出すと、実際より当てはまりがよく見えるため、ここでは統計量だけで比べています。
確率分布でよくある間違いとエラー(SciPy)
この節では、SciPy で確率分布を使うときに出たエラー文と、エラーにならずに値がずれる間違いを引けます。
"""SciPy の確率分布でよくある間違い(エラーになるもの・ならずに値がずれるもの)"""
import numpy as np
from scipy import stats
def show(label, f):
try:
print(f"{label} -> {f()}")
except Exception as e:
print(f"{label} -> {type(e).__name__}: {e}")
show("stats.binom.pdf(3, 10, 0.5)", lambda: stats.binom.pdf(3, 10, 0.5))
show("stats.norm.pmf(0)", lambda: stats.norm.pmf(0))
show("stats.norm(0, 1).pmf(0)", lambda: stats.norm(0, 1).pmf(0))
show("stats.binom.pmf(3, 10, 1.5)", lambda: stats.binom.pmf(3, 10, 1.5))
show("stats.norm.pdf(0, 0, -1)", lambda: stats.norm.pdf(0, 0, -1))
show("stats.poisson.pmf(2.5, 4)", lambda: stats.poisson.pmf(2.5, 4))
show("stats.norm.ppf(1.2)", lambda: stats.norm.ppf(1.2))
show("stats.norm.ppf(1.0)", lambda: stats.norm.ppf(1.0))
show("stats.binom.pmf(3, 10)", lambda: stats.binom.pmf(3, 10))
show("stats.norm(0, 1, 2)", lambda: stats.norm(0, 1, 2))
show("np.random.default_rng(0).normal(0, -1)", lambda: np.random.default_rng(0).normal(0, -1))
show("stats.t.ppf(0.975)", lambda: stats.t.ppf(0.975))
print("\n値がずれる例")
print(f"expon.sf(30, 4) = {stats.expon.sf(30, 4):.2e} # 4 は loc(ずらす量)として読まれる")
print(f"expon.sf(30, scale=15) = {stats.expon.sf(30, scale=15):.4f} # λ=4/分=1/15 秒 → scale=15")
print(f"norm.sf(250, 200, 400) = {stats.norm.sf(250, 200, 400):.4f} # 分散 400 を scale に渡した")
print(f"norm.sf(250, 200, 20) = {stats.norm.sf(250, 200, 20):.4f} # scale は標準偏差 20")
print(f"geom.pmf(0, 0.2) = {stats.geom.pmf(0, 0.2):.4f} # SciPy の幾何分布は1回目から数える")
print(f"geom.pmf(0, 0.2, loc=-1) = {stats.geom.pmf(0, 0.2, loc=-1):.4f} # 失敗回数で数えるなら loc=-1")
print(f"binom.cdf(5, 100, 0.03) = {stats.binom.cdf(5, 100, 0.03):.4f} # P(X<=5)。P(X<5) は cdf(4) = {stats.binom.cdf(4, 100, 0.03):.4f}")
print(f"1 - norm.cdf(9) = {1 - stats.norm.cdf(9)} norm.sf(9) = {stats.norm.sf(9):.3e}")
stats.binom.pdf(3, 10, 0.5) -> AttributeError: 'binom_gen' object has no attribute 'pdf'
stats.norm.pmf(0) -> AttributeError: 'norm_gen' object has no attribute 'pmf'
stats.norm(0, 1).pmf(0) -> AttributeError: 'rv_continuous_frozen' object has no attribute 'pmf'
stats.binom.pmf(3, 10, 1.5) -> nan
stats.norm.pdf(0, 0, -1) -> nan
stats.poisson.pmf(2.5, 4) -> 0.0
stats.norm.ppf(1.2) -> nan
stats.norm.ppf(1.0) -> inf
stats.binom.pmf(3, 10) -> TypeError: _parse_args() missing 1 required positional argument: 'p'
stats.norm(0, 1, 2) -> TypeError: _parse_args() takes from 1 to 3 positional arguments but 4 were given
np.random.default_rng(0).normal(0, -1) -> ValueError: scale < 0
stats.t.ppf(0.975) -> TypeError: _parse_args() missing 1 required positional argument: 'df'
値がずれる例
expon.sf(30, 4) = 5.11e-12 # 4 は loc(ずらす量)として読まれる
expon.sf(30, scale=15) = 0.1353 # λ=4/分=1/15 秒 → scale=15
norm.sf(250, 200, 400) = 0.4503 # 分散 400 を scale に渡した
norm.sf(250, 200, 20) = 0.0062 # scale は標準偏差 20
geom.pmf(0, 0.2) = 0.0000 # SciPy の幾何分布は1回目から数える
geom.pmf(0, 0.2, loc=-1) = 0.2000 # 失敗回数で数えるなら loc=-1
binom.cdf(5, 100, 0.03) = 0.9192 # P(X<=5)。P(X<5) は cdf(4) = 0.8179
1 - norm.cdf(9) = 0.0 norm.sf(9) = 1.129e-19
| エラー文・症状 | 原因 | 直し方 |
|---|---|---|
'binom_gen' object has no attribute 'pdf' |
離散型に pdf を使った |
離散型は pmf |
'norm_gen' object has no attribute 'pmf' |
連続型に pmf を使った |
連続型は pdf。確率は cdf の差 |
nan が返る |
p が0〜1の外、scale が負、q が0〜1の外 | パラメータの範囲を確かめる |
pmf(2.5, 4) が 0.0 |
離散型に整数でない値を渡した | 整数で渡す |
ppf(1.0) が inf |
確率1の点は無限大 | 0.999 など1未満で上限を決める |
missing 1 required positional argument: 'p' |
分布固有のパラメータが足りない | binom.pmf(k, n, p) の順で渡す |
takes from 1 to 3 positional arguments |
正規分布に引数を多く渡した | norm(loc, scale) の2つまで |
ValueError: scale < 0 |
NumPy の乱数に負の標準偏差 | 標準偏差は0以上 |
| 指数分布の確率がほぼ0 | λ を位置引数で渡し loc になった | expon(scale=1/λ) |
| 正規分布の確率が大きすぎる | 分散を scale に渡した | scale は標準偏差(√分散) |
1 - cdf(x) が 0.0 |
1 に近い数の引き算で桁が消えた | sf(x) を使う |
エラーにならずに値がずれる間違いのほうが見つけにくいです。 指数分布で λ = 4 を位置引数に渡すと loc(ずらす量)として読まれ、0.1353 のはずの確率が 5.11e-12 になりました。前の節のように乱数で割合を数えて理論値と比べると、こうした取り違えに気づけます。
確率分布はどこで使われる?(IT・データ分析の例)
この節では、エンジニアの仕事で確率分布が出てくる場面が分かります。
| 場面 | 使う分布 | 何に使うか |
|---|---|---|
| A/B テスト | 二項分布・正規分布 | CVR の差が偶然か判定、必要な人数の計算 |
| 負荷・キャパシティの見積もり | ポアソン分布・指数分布 | 1分あたりの最大件数、待ち行列 |
| 応答時間の SLO | 対数正規分布・パーセント点 | 95%点・99%点の見積もり |
| リトライ設計 | 幾何分布・一様分布 | 何回で成功するか、待ち時間のずらし |
| 監視・異常検知 | 正規分布・ポアソン分布 | 平均±3σ を超えた値、件数の急増 |
| 機械学習 | 正規分布・ベルヌーイ分布など | 誤差の仮定(最小二乗法)、分類の出力 |
最小二乗法は、誤差が正規分布に従うと仮定したときの最尤推定と同じ答えになります。回帰の計算は、関連記事「最小二乗法とは|公式・手計算の例とPythonでの求め方を実例で解説」で扱っています。A/B テストで二項分布・正規分布を使って有意差と必要な人数を出す方法は、関連記事「ABテストとは|やり方・サンプルサイズ・有意差の判定をPythonで実演」で扱っています。確率分布の形をグラフで描きたいときは、関連記事「matplotlibの使い方と日本語文字化け対策|OS別・Colab・3.12対応」で扱っています。
よくある質問
Q. 確率分布とは簡単に言うと何ですか? A. 「どの値が、どれくらいの確率で出るか」の一覧です。サイコロなら1〜6の目がそれぞれ 1/6 で、すべての確率を足すと1になります。
Q. 確率分布表とは?どう作りますか? A. 確率変数がとる値と、その確率を並べた表です。値をすべて書き出し、それぞれの確率を計算して、合計が1になることを確かめます。累積の確率 F(x) の列を足すと、「x 以下になる確率」もすぐ読めます。
Q. 確率関数と確率密度関数の違いは? A. 確率関数は離散型で「その値が出る確率」、確率密度関数は連続型で「面積が確率になる密度」です。連続型では1点の確率は0で、区間の確率を密度の面積で求めます。
Q. 確率密度が1を超えるのはおかしくないですか? A. おかしくありません。密度は確率ではなく、幅を掛けて初めて確率になります。標準偏差 0.1 の正規分布の中心の密度は 3.9894 ですが、全体の面積は1です。
Q. 累積分布関数とは何ですか?
A. X が x 以下になる確率 F(x) = P(X ≤ x) です。右へ行くほど増え、最後は1になります。区間の確率は F(b) − F(a) で出せます。SciPy では cdf です。
Q. 確率分布の期待値と分散の求め方は? A. 期待値は「値 × 確率」の合計、分散は E[X²] − (E[X])² です。サイコロなら期待値 3.5、分散 35/12 = 2.9167 です。主な分布は早見表の式で求められます。
Q. 正規分布と二項分布の違いは? A. 二項分布は「n 回中の成功回数」を表す離散型、正規分布は平均の周りに左右対称に散らばる連続型です。n×p が大きいとき、二項分布は正規分布で近似でき、n=1000・p=0.3 では P(X≤280) が正確 0.0886、近似 0.0892 でした。
Q. ポアソン分布はどんなときに使いますか? A. 一定の時間や範囲で、まれな出来事が何回起きるかを表すときです。1分あたりのアクセス数・1日の障害件数などで、平均4件なら99%の分は9件以内に収まります。
Q. 確率分布と度数分布(ヒストグラム)の違いは? A. 度数分布は実際のデータで数えた回数、確率分布は理論上の出やすさです。データの数を増やすと、ヒストグラムの割合は確率分布に近づきます(乱数100万回でほぼ一致)。
Q. 確率分布を Python で計算するには?
A. scipy.stats を使います。stats.norm(loc=200, scale=20).sf(250) のように分布を作り、pmf・pdf・cdf・sf・ppf で確率やパーセント点を出します。乱数は rvs か NumPy の default_rng で作ります。
Q. 中心極限定理とは何ですか? A. 分散を持つ分布から n 個取って平均すると、n が大きいとき平均の分布が正規分布に近づく、という定理です。偏った指数分布でも、平均の歪度は n=1 の +1.989 から n=100 で +0.184 まで下がりました。
Q. 確率分布は機械学習でどう使われますか? A. 誤差を正規分布と仮定すると最小二乗法になる、2値分類の出力をベルヌーイ分布の確率として扱う、などモデルの仮定や出力の形に使われます。
まとめ
- 確率分布は、確率変数がとる値と確率の対応。確率はすべて0以上で、合計は1。
- 数える量は離散型(確率関数 pmf)、測る量は連続型(確率密度関数 pdf)。両方とも累積分布関数 F(x) = P(X ≤ x) で表せる。連続型は1点の確率が0で、密度は1を超えてよい。
- 期待値は Σ x×P(x)、分散は E[X²] − (E[X])²。サイコロは 3.5 と 2.9167。
- 主な9分布(ベルヌーイ・二項・ポアソン・幾何・一様・正規・指数・t・カイ二乗)は、SciPy の理論値と乱数100万個がすべて一致した。
- 上限や基準を決めるときは平均ではなくパーセント点(ppf)を使う。上側の小さな確率は 1 − cdf ではなく sf。
- 平均をとると分布は正規分布に近づく(中心極限定理)が、偏りが強いと n=30 でもずれが残り、分散の無い分布では成り立たない。
- 右に偏ったデータに正規分布を当てはめると、99%点を小さく見積もる。形を見て候補を選び、当てはめて比べる。
参考・出典
確認日はいずれも 2026年10月23日です。
- SciPy「Statistical functions (scipy.stats)」:https://docs.scipy.org/doc/scipy/reference/stats.html
- SciPy「scipy.stats.expon」:https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.expon.html
- SciPy「scipy.stats.geom」:https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.geom.html
- SciPy「scipy.stats.Normal」:https://docs.scipy.org/doc/scipy/reference/generated/scipy.stats.Normal.html
- SciPy「SciPy 1.15.0 Release Notes」:https://docs.scipy.org/doc/scipy/release/1.15.0-notes.html
- Wikipedia「確率分布」:https://ja.wikipedia.org/wiki/確率分布
- Wikipedia「中心極限定理」:https://ja.wikipedia.org/wiki/中心極限定理
- 統計WEB「11-1. 確率変数と確率分布」:https://bellcurve.jp/statistics/course/6596.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「確率分布とは|種類一覧・平均と分散・選び方をPythonで実演」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。