100本ノック / ベイズ統計 / データ分析のためのベイズ統計100本ノック
製造業のベイズA/Bテスト入門|検査条件を確率と期待利益で比較する
検査条件を「勝率」ではなく事業価値で選ぶ:製造業のベイズA/Bテスト実践
製造現場では、画像検査のしきい値、作業標準、設備条件などを変更する際に、「改善して見えるが、偶然ではないか」「いつ試験を止めるか」「品質だけでなくコストも含めて採用できるか」が問題になります。本稿では、架空の画像検査工程を題材に、A/Bテストの設計から複数案比較、意思決定ルールまでをベイズ統計で一貫して扱います。
対象は No.051〜No.060(第6章:ベイズA/Bテスト) です。Aは現行条件、Bは新条件とし、良品を正しく通過させた割合をCVR(ここでは「正判定率」)として扱います。
[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。 掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。
1. はじめに:この記事で扱う製造業の実務課題
画像検査工程で、良品を不良品として排出する「過検出」が増えています。技術部門は新条件Bを提案しましたが、切替には再バリデーション費用がかかります。本稿の問いは、単にBの観測正判定率が高いかではなく、不確実性と誤判断コストを含めてBを採用すべきかです。
2. 現場でよくある状況
- AとBの試験数が揃う前に日次会議で結論を求められる
- 観測率の大小だけで判断し、改善幅の不確実性が共有されない
- 品種、シフト、設備の偏りがあり、比較可能性が崩れる
- 「95%なら採用」のような確率基準だけが独り歩きする
- 改善が小さすぎて、切替費用を回収できない場合がある
3. なぜこの問題は判断が難しいのか
有限サンプルの観測率は揺れます。また「統計的に差がありそう」と「事業上十分な改善」は別です。停止時点をデータに応じて変える運用、複数案の同時比較、導入損失の非対称性も意思決定を難しくします。ベイズA/Bテストは、未知の正判定率を分布として表し、意思決定に直接使える確率・期待改善量・期待損失へ変換できます。
4. 今回扱うノックの全体像
| No. | テーマ | 現場で答える問い |
|---|---|---|
| 051 | 目的 | 何を改善と定義するか |
| 052 | 比較 | 頻度主義とベイズの何が違うか |
| 053 | ベータ分布 | A/Bの未知の率をどう表すか |
| 054 | 事後分布 | 実験データでどう更新するか |
| 055 | 優越確率 | BがAより良い確率はいくつか |
| 056 | 差の分布 | 改善幅はどこまでぶれるか |
| 057 | 期待改善量 | 平均でどれだけ効果があるか |
| 058 | 少数標本 | 早期判断をどう抑制するか |
| 059 | A/B/n | 複数候補からどう選ぶか |
| 060 | 判断ルール | 品質・費用をどう統合するか |
5. Python環境の準備
NumPy、pandas、SciPy、matplotlibだけを使います。乱数生成器は固定し、再実行しても同じ結果になるようにします。
import platform
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
import scipy
from scipy.stats import beta, fisher_exact
rng = np.random.default_rng(20250715)
plt.rcParams["figure.figsize"] = (8, 4.5)
plt.rcParams["axes.unicode_minus"] = False
print("Python :", platform.python_version())
print("NumPy :", np.__version__)
print("pandas :", pd.__version__)
print("SciPy :", scipy.__version__)
print("matplotlib :", matplotlib.__version__)
Python : 3.13.1
NumPy : 2.5.1
pandas : 3.0.3
SciPy : 1.18.0
matplotlib : 3.11.0
6. 架空データの作成
同一品種・同一設備群で無作為に割り付けたと仮定し、A/Bそれぞれの検査数と正判定数を生成します。現実の導入では、品種・設備・シフトを層別し、割付比と除外基準を試験前に固定する必要があります。
true_rates = {"A(現行)": 0.930, "B(新条件)": 0.947}
n_per_arm = 800
rows = []
for variant, true_rate in true_rates.items():
correct = rng.binomial(n_per_arm, true_rate)
rows.append({"variant": variant, "inspected": n_per_arm,
"correct": correct, "incorrect": n_per_arm - correct})
ab = pd.DataFrame(rows)
ab["observed_rate"] = ab["correct"] / ab["inspected"]
ab.style.format({"observed_rate": "{:.2%}"})
| variant | inspected | correct | incorrect | observed_rate | |
|---|---|---|---|---|---|
| 0 | A(現行) | 800 | 750 | 50 | 93.75% |
| 1 | B(新条件) | 800 | 763 | 37 | 95.38% |
No.051:A/Bテストの目的を整理する
実務での意味
試験の目的は「Bの数字がAより大きいこと」ではありません。主KPI、ガードレール、最小実用差、対象母集団、評価期間を事前に合意し、採用後の損益につながる問いへ変換します。本例では主KPIを良品の正判定率、最小実用差を0.5ポイント、ガードレールを処理時間と見逃し率とします。
分析・モデル化の考え方
観測単位ごとの正判定を とし、条件 の正判定確率を とします。主な推定対象は です。試験前に定める評価表は、分析者と意思決定者の「改善」の意味を揃えます。
Pythonで確認する
test_charter = pd.DataFrame({
"項目": ["意思決定", "主KPI", "最小実用差", "ガードレール", "対象", "判定期限"],
"事前定義": ["新条件Bへ切り替えるか", "良品の正判定率", "B-A >= 0.5pt",
"見逃し率は悪化させない/処理時間+5%以内", "対象品種X・通常操業", "4週間または上限3200件"]
})
test_charter
| 項目 | 事前定義 | |
|---|---|---|
| 0 | 意思決定 | 新条件Bへ切り替えるか |
| 1 | 主KPI | 良品の正判定率 |
| 2 | 最小実用差 | B-A >= 0.5pt |
| 3 | ガードレール | 見逃し率は悪化させない/処理時間+5%以内 |
| 4 | 対象 | 対象品種X・通常操業 |
| 5 | 判定期限 | 4週間または上限3200件 |
結果の読み取り
この表が分析仕様書の最小単位です。観測率が高くても、0.5ポイント未満なら導入費を正当化できない、ガードレール違反なら採用しない、という判断を先に明文化できます。
No.052:頻度主義A/BテストとベイズA/Bテストを比較する
実務での意味
どちらも有効ですが、答える問いが違います。頻度主義のp値は「差がないと仮定した場合に、今回以上に極端なデータが出る確率」に関係し、仮説そのものの確率ではありません。ベイズでは「現在の情報のもとでBがAを上回る確率」を直接計算できます。
分析・モデル化の考え方
頻度主義では帰無仮説 を検定します。ベイズでは事前分布 と二項尤度を組み合わせ、事後分布を得ます。ここでは参考としてFisherの正確確率検定と後述のベイズ指標を並べます。
Pythonで確認する
table = ab[["correct", "incorrect"]].to_numpy()
odds_ratio, p_value = fisher_exact(table, alternative="two-sided")
comparison = pd.DataFrame({
"方法": ["頻度主義(Fisher検定)", "ベイズ"],
"代表的な出力": [f"両側p値 = {p_value:.4f}", "P(p_B > p_A | data)、差の信用区間"],
"主な読み方": ["H0下でのデータの極端さ", "データ観測後の未知量に対する不確実性"]
})
comparison
| 方法 | 代表的な出力 | 主な読み方 | |
|---|---|---|---|
| 0 | 頻度主義(Fisher検定) | 両側p値 = 0.1856 | H0下でのデータの極端さ |
| 1 | ベイズ | P(p_B > p_A | data)、差の信用区間 | データ観測後の未知量に対する不確実性 |
結果の読み取り
p値だけから「Bが優れる確率」を読み替えてはいけません。現場会議では、検定結果と、優越確率・改善幅・損失を別の指標として提示すると誤解を減らせます。
No.053:A案とB案のCVRをベータ分布で表現する
実務での意味
未知の正判定率を一点ではなく分布で持つことで、過去知見と不確実性を明示できます。強すぎる事前分布は新データを過度に抑えるため、根拠と感度分析が必要です。
分析・モデル化の考え方
とします。平均は 、擬似観測量は概ね です。今回は両案を対称に扱う弱情報事前分布 (一様分布)を採用します。
Pythonで確認する
x = np.linspace(0, 1, 1000)
prior_specs = [(1, 1, "Beta(1, 1): weak"), (18, 2, "Beta(18, 2): optimistic")]
for a, b, label in prior_specs:
plt.plot(x, beta.pdf(x, a, b), label=label)
plt.title("Candidate prior distributions for the correct-classification rate")
plt.xlabel("Correct-classification rate")
plt.ylabel("Probability density")
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()

結果の読み取り
Beta(1,1)は全範囲を同等に扱います。Beta(18,2)は90%付近を強く想定します。本例では比較の透明性を優先してBeta(1,1)を使いますが、量産実績を事前分布に使う場合は、期間差や設備差を割り引く設計が必要です。
No.054:A案とB案の事後分布を計算する
実務での意味
試験結果を得たら、各案の率について「もっともらしい範囲」を更新します。ベータ・二項モデルなら解析的に計算でき、説明・監査・再計算が容易です。
分析・モデル化の考え方
回の成功、 回の失敗に対して、
です。95%信用区間は「事後分布のもとでパラメータが区間内にある確率が95%」と解釈できます。
Pythonで確認する
prior_a, prior_b = 1, 1
ab["post_a"] = prior_a + ab["correct"]
ab["post_b"] = prior_b + ab["incorrect"]
ab["posterior_mean"] = ab["post_a"] / (ab["post_a"] + ab["post_b"])
ab["ci_low"] = beta.ppf(0.025, ab["post_a"], ab["post_b"])
ab["ci_high"] = beta.ppf(0.975, ab["post_a"], ab["post_b"])
ab[["variant", "inspected", "correct", "posterior_mean", "ci_low", "ci_high"]].style.format(
{c: "{:.2%}" for c in ["posterior_mean", "ci_low", "ci_high"]}
)
| variant | inspected | correct | posterior_mean | ci_low | ci_high | |
|---|---|---|---|---|---|---|
| 0 | A(現行) | 800 | 750 | 93.64% | 91.85% | 95.22% |
| 1 | B(新条件) | 800 | 763 | 95.26% | 93.69% | 96.62% |
x = np.linspace(0.88, 0.99, 800)
for label, row in zip(["A: current", "B: new"], ab.itertuples()):
plt.plot(x, beta.pdf(x, row.post_a, row.post_b), label=label)
plt.title("Posterior distributions of the correct-classification rate")
plt.xlabel("Correct-classification rate")
plt.ylabel("Probability density")
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()

結果の読み取り
事後平均は観測率に近い一方、各案には幅があります。Bの分布が右に寄っていても重なりが残るため、平均値の大小だけで確定的に扱わず、次の優越確率と差の分布を確認します。
No.055:B案がA案より優れている確率を計算する
実務での意味
「BがAを上回る確率」は会議で直感的に説明できます。ただし、わずかでも上回ればよいという確率なので、実用上意味のある差を上回る確率も併記します。
分析・モデル化の考え方
事後分布から を独立にサンプリングし、 の割合を求めます。同様に、 の割合が最小実用差を超える確率です。
Pythonで確認する
draws = 200_000
a_row, b_row = ab.iloc[0], ab.iloc[1]
p_a = rng.beta(a_row.post_a, a_row.post_b, draws)
p_b = rng.beta(b_row.post_a, b_row.post_b, draws)
delta = p_b - p_a
prob_superior = np.mean(delta > 0)
prob_practical = np.mean(delta > 0.005)
pd.DataFrame({
"指標": ["P(B > A | data)", "P(B - A > 0.5pt | data)"],
"値": [prob_superior, prob_practical]
}).style.format({"値": "{:.2%}"})
| 指標 | 値 | |
|---|---|---|
| 0 | P(B > A | data) | 92.37% |
| 1 | P(B - A > 0.5pt | data) | 83.90% |
結果の読み取り
優越確率が高くても、最小実用差を超える確率は低くなり得ます。前者は方向、後者は事業上の大きさを表します。採否には後者と導入損失を重視します。
No.056:CVR差の事後分布を可視化する
実務での意味
差の分布は、改善だけでなく悪化の可能性と改善幅の幅を同時に示します。経営・品質保証・技術部門が同じ図を見て、リスク許容度を議論できます。
分析・モデル化の考え方
のサンプルをヒストグラム化し、0(同等)と0.5ポイント(最小実用差)を示します。等裾95%信用区間も計算します。
Pythonで確認する
delta_ci = np.quantile(delta, [0.025, 0.5, 0.975])
plt.hist(delta * 100, bins=70, density=True, alpha=0.75, color="tab:blue")
plt.axvline(0, color="black", linestyle="--", label="No difference")
plt.axvline(0.5, color="tab:red", linestyle=":", label="Practical threshold (0.5 pt)")
plt.title("Posterior distribution of improvement: B - A")
plt.xlabel("Improvement in correct-classification rate (percentage points)")
plt.ylabel("Probability density")
plt.grid(True, alpha=0.3)
plt.legend()
plt.tight_layout()
plt.show()
print(f"Median: {delta_ci[1]*100:.2f} pt")
print(f"95% credible interval: [{delta_ci[0]*100:.2f}, {delta_ci[2]*100:.2f}] pt")

Median: 1.61 pt
95% credible interval: [-0.60, 3.87] pt
結果の読み取り
0より左の面積はBが悪化する可能性、0.5より右の面積は実用差を超える可能性です。区間が0をまたぐ場合でも「効果なし」と断定せず、上振れ・下振れの確率と損失を材料に継続・停止を判断します。
No.057:期待改善量を計算する
実務での意味
率の差を年間個数と金額へ変換すると、統計結果を設備投資判断に接続できます。一方、 の平均だけを「純便益」と呼ぶと悪化リスクを無視するため、符号付き期待差と期待損失も併記します。
分析・モデル化の考え方
年間対象数を 、誤排出1個当たり損失を とすると、期待粗便益は です。導入費 を引いて期待純便益を求めます。ここでは他KPIが同等という仮定です。
Pythonで確認する
annual_units = 1_200_000
loss_per_false_reject = 420
implementation_cost = 5_000_000
annual_avoided = annual_units * delta
gross_benefit = annual_avoided * loss_per_false_reject
net_benefit = gross_benefit - implementation_cost
business_case = pd.DataFrame({
"指標": ["期待改善幅", "年間の期待誤排出削減数", "期待粗便益", "導入費控除後の期待純便益", "純便益が正の確率"],
"値": [f"{delta.mean()*100:.2f} pt", f"{annual_avoided.mean():,.0f} 個",
f"{gross_benefit.mean()/1e6:,.2f} 百万円", f"{net_benefit.mean()/1e6:,.2f} 百万円",
f"{np.mean(net_benefit > 0):.1%}"]
})
business_case
| 指標 | 値 | |
|---|---|---|
| 0 | 期待改善幅 | 1.62 pt |
| 1 | 年間の期待誤排出削減数 | 19,478 個 |
| 2 | 期待粗便益 | 8.18 百万円 |
| 3 | 導入費控除後の期待純便益 | 3.18 百万円 |
| 4 | 純便益が正の確率 | 71.0% |
結果の読み取り
改善率が小さく見えても、年間数量が大きければ金額効果は大きくなります。逆に優越確率が高くても導入費を回収できないことがあります。実際には見逃し損失、停止時間、保守費も同じ単位で加えます。
No.058:サンプルサイズが少ない場合の判断を考える
実務での意味
初期データは大きく揺れます。早期の観測率だけで勝者を決めると、偶然よかった案を採用しやすくなります。ベイズ推定でもデータ不足そのものは消えません。
分析・モデル化の考え方
同じ真の率から標本数だけを変え、優越確率と差の95%信用区間幅を比較します。逐次監視をするなら、最小標本数、最大標本数、停止基準を事前に定めます。
Pythonで確認する
sample_sizes = [25, 50, 100, 200, 400, 800, 1600]
records = []
for n in sample_sizes:
xa = rng.binomial(n, true_rates["A(現行)"])
xb = rng.binomial(n, true_rates["B(新条件)"])
da = rng.beta(1 + xa, 1 + n - xa, 30_000)
db = rng.beta(1 + xb, 1 + n - xb, 30_000)
d = db - da
lo, hi = np.quantile(d, [0.025, 0.975])
records.append({"n_per_arm": n, "prob_B_superior": np.mean(d > 0),
"interval_width_pt": (hi - lo) * 100})
sequential = pd.DataFrame(records)
sequential.style.format({"prob_B_superior": "{:.1%}", "interval_width_pt": "{:.2f}"})
| n_per_arm | prob_B_superior | interval_width_pt | |
|---|---|---|---|
| 0 | 25 | 50.4% | 28.88 |
| 1 | 50 | 36.6% | 23.88 |
| 2 | 100 | 49.8% | 13.94 |
| 3 | 200 | 97.6% | 10.96 |
| 4 | 400 | 89.3% | 7.11 |
| 5 | 800 | 97.2% | 4.85 |
| 6 | 1600 | 92.1% | 3.32 |
fig, ax1 = plt.subplots()
ax1.plot(sequential["n_per_arm"], sequential["prob_B_superior"], marker="o", color="tab:blue")
ax1.set_xlabel("Sample size per arm")
ax1.set_ylabel("P(B > A | data)", color="tab:blue")
ax1.grid(True, alpha=0.3)
ax2 = ax1.twinx()
ax2.plot(sequential["n_per_arm"], sequential["interval_width_pt"], marker="s", color="tab:orange")
ax2.set_ylabel("95% interval width (percentage points)", color="tab:orange")
plt.title("Uncertainty decreases as sample size grows")
fig.tight_layout()
plt.show()

結果の読み取り
少数標本では優越確率が極端に動く一方、信用区間は広いままです。単一のシミュレーション経路は単調にならない点も重要です。たとえば「各案400件未満では採用しない」「判断不能なら上限まで継続」と運用ルールを置きます。
No.059:複数パターンのA/B/nテストを行う
実務での意味
設備条件を3案以上比較する場合、各案が最良である確率を使えます。ただし候補を増やすほど各案の試験数が減り、品種や時間帯の偏りも入りやすくなります。
分析・モデル化の考え方
各案の事後分布から同時にサンプリングし、各回で最大となった案を数えます。また、現行Aとの差と期待純便益も確認します。「最良確率」は差の大きさを保証しないため、単独では採用基準にしません。
Pythonで確認する
variants = pd.DataFrame({
"variant": ["A 現行", "B 高感度", "C バランス", "D 高速"],
"inspected": [600, 600, 600, 600],
"correct": [556, 568, 572, 561]
})
variants["incorrect"] = variants["inspected"] - variants["correct"]
post_draws = np.column_stack([
rng.beta(1 + r.correct, 1 + r.incorrect, 150_000) for r in variants.itertuples()
])
best = np.argmax(post_draws, axis=1)
variants["posterior_mean"] = post_draws.mean(axis=0)
variants["prob_best"] = np.bincount(best, minlength=len(variants)) / len(best)
variants[["variant", "inspected", "correct", "posterior_mean", "prob_best"]].style.format(
{"posterior_mean": "{:.2%}", "prob_best": "{:.1%}"}
)
| variant | inspected | correct | posterior_mean | prob_best | |
|---|---|---|---|---|---|
| 0 | A 現行 | 600 | 556 | 92.53% | 0.8% |
| 1 | B 高感度 | 600 | 568 | 94.52% | 28.2% |
| 2 | C バランス | 600 | 572 | 95.18% | 66.8% |
| 3 | D 高速 | 600 | 561 | 93.36% | 4.3% |
plot_labels = variants["variant"].str.split().str[0]
plt.bar(plot_labels, variants["prob_best"], color=["gray", "tab:blue", "tab:green", "tab:orange"])
plt.title("Posterior probability that each variant is best")
plt.xlabel("Inspection setting")
plt.ylabel("Probability of being best")
plt.grid(True, axis="y", alpha=0.3)
plt.tight_layout()
plt.show()

結果の読み取り
最良確率の高い案が候補になりますが、処理速度・見逃し率・変更費用を無視して即採用はできません。複数比較では同じ期間・同じ割付機構を使い、候補選定後に確認試験を設けると選択バイアスを抑えられます。
No.060:ベイズA/Bテストの意思決定ルールを設計する
実務での意味
分析を行動に変えるには、「採用」「継続」「見送り」の条件が必要です。確率のしきい値だけでなく、実用差、期待純便益、ガードレール、最低標本数を組み合わせます。
分析・モデル化の考え方
本例のルールは次です。
- 各案400件以上
- 見逃し率・処理時間のガードレールを満たす
基準値は普遍的な正解ではなく、誤採用と見送りの損失、追加試験の費用に応じて承認者が決めます。
Pythonで確認する
guardrail_pass = True # 架空の別評価で適合したと仮定
checks = pd.DataFrame({
"判定条件": ["各案400件以上", "P(B>A)>95%", "P(B-A>0.5pt)>70%", "P(純便益>0)>80%", "ガードレール適合"],
"実績": [ab["inspected"].min() >= 400, prob_superior > 0.95, prob_practical > 0.70,
np.mean(net_benefit > 0) > 0.80, guardrail_pass]
})
decision = "採用" if checks["実績"].all() else ("試験継続" if ab["inspected"].min() < 1600 else "見送り/再設計")
display(checks)
print("今回の判定:", decision)
| 判定条件 | 実績 | |
|---|---|---|
| 0 | 各案400件以上 | True |
| 1 | P(B>A)>95% | False |
| 2 | P(B-A>0.5pt)>70% | True |
| 3 | P(純便益>0)>80% | False |
| 4 | ガードレール適合 | True |
今回の判定: 試験継続
結果の読み取り
すべての条件を満たした場合だけ採用します。満たさない場合は、単に『有意差なし』と終えるのではなく、追加情報の価値が試験費用を上回るかを確認して継続または見送りを選びます。コード化したルールは、会議ごとの恣意的な基準変更を防ぎます。
7. 対象ノックを通して見える実務上の示唆
- 確率と価値を分ける:Bが勝つ確率、実用差を超える確率、期待純便益は別の問いです。
- 不確実性は消すのではなく価格付けする:悪化時の損失と追加試験費用を比較します。
- 事前にルールを固定する:KPI、最小実用差、停止条件、除外条件を試験前に承認します。
- 比較可能性がモデルより先:無作為割付、同時期比較、層別化、計測系の妥当性が崩れると計算が精密でも結論は弱くなります。
- 局所KPIだけで採用しない:見逃し、タクトタイム、停止時間、教育・保守費をガードレールまたは損失関数に含めます。
8. 実務導入する場合に必要なこと
- 品質保証・製造・技術・経理が参加する意思決定責任者の設定
- 対象母集団、割付単位、層別因子、欠測・除外基準を記した試験計画書
- 測定システム解析とログの時刻・ロット・設備IDの整備
- 事前分布と意思決定しきい値の根拠、別事前分布による感度分析
- 主KPIとガードレールを同時に監視するレビュー手順
- 採用後の効果検証、モデルドリフト監視、ロールバック条件
今回のベータ・二項モデルは独立で同質な観測を仮定します。品種・設備・シフトで率が異なる場合は層別モデルや階層ベイズ、時間依存が強ければ時系列モデルへ拡張します。
9. まとめ
No.051〜060では、A/Bテストの目的定義から、事前・事後分布、優越確率、改善幅、少数標本、A/B/n、意思決定ルールまでを確認しました。ベイズA/Bテストの強みは「Bが良さそう」という結果を、どの程度確からしく、どれほどの価値があり、どの条件なら行動するかへ接続できる点にあります。
10. 法人向けのご相談
数理工房では、製造条件比較、品質KPI設計、逐次試験、ベイズモデル構築、意思決定ルールの設計、現場向け研修まで、データの状態と業務制約に合わせて支援します。PoCだけで終わらせず、承認・運用・監視まで含む仕組みづくりをご相談いただけます。
📩 お問い合わせ: surikobo.co.jp/contact まずはお気軽にご相談ください。