100本ノック / 確率統計 / 確率・統計マーケティング応用100本ノック

製造業の品質管理をPythonで実践|不良率・Cp/Cpk・管理図10本ノック

品質データを「判断」に変える:製造業の確率・統計10本ノック

本記事は、製造業の品質管理を題材に、不良率の把握から工程能力、管理図、検定、信頼性、将来リスクのシミュレーションまでを一つの架空データでつなぐ実践 notebook です。数式を計算すること自体ではなく、「いつ工程を止めるか」「どこへ改善資源を配分するか」「顧客リスクをどう説明するか」という意思決定に統計を結び付けます。

この100本ノックでは、品質管理、設備保全、需要予測、在庫最適化、生産計画、マーケティングサイエンス、機械学習、意思決定科学、シミュレーション、数理工房DI実践を順に扱います。最初の10本は、その土台となる品質管理です。Pythonで再現可能な分析を作り、現場・品質保証・経営が同じ数字を見て議論できる状態を目指します。

[!NOTE] 本資料は、数理工房 (もしくは代表である和山個人) が過去に企業研修において使用した notebook を企業様の許可を得て再構成・編集のうえ公開しています。
掲載データはすべて架空のものであり、実在する企業・工場・数値とは一切関係ありません。

1. はじめに:この記事で扱う製造業の実務課題

架空の精密部品工場では、3ラインで同一部品を生産しています。重要寸法の規格は 49.5〜50.5 mm、各ロットから50個を測定し、外観検査では各ロット200個を検査します。最近、顧客から「不良率は安定しているのか」「ライン間に差があるのか」「来月の損失はいくらになり得るのか」と説明を求められました。

ここで必要なのは単一のKPIではありません。平均不良率が低くても突発異常を見逃すことがあり、Cpが高くても中心が規格中央からずれていれば不良を生みます。本記事では、水準・ばらつき・時間変化・推定誤差・経済影響を分けて観察し、最後に統合します。

2. 現場でよくある状況

  • 月次不良率だけが報告され、いつ・どのラインで悪化したか分からない
  • CpとCpkが混同され、「設備能力はあるが芯ずれしている」状態を見落とす
  • 管理限界と規格限界を同じものとして扱う
  • 少数サンプルの差を、そのまま工程差だと断定する
  • 異常検知の閾値や抜取検査の条件が、顧客リスクと結び付いていない
  • 点推定だけで計画し、推定誤差や翌月の変動幅を予算に反映していない

3. なぜこの問題は判断が難しいのか

品質指標には異なる役割があります。規格限界は顧客・設計が求める許容範囲、管理限界は安定工程で通常起こる変動範囲です。また、不良率は有限標本からの推定値であり、真の不良率そのものではありません。したがって「基準を超えた/超えていない」の二択だけでなく、データ生成過程、標本サイズ、時間順序、誤判定コストを明示する必要があります。

4. 今回扱うノックの全体像

No.テーマ主な意思決定
001不良率推定改善対象の優先順位
002工程能力指数 Cpばらつき削減の必要性
003工程能力指数 Cpk芯出しと設備改善の切り分け
004管理図通常変動と特殊原因の識別
005異常検知調査対象ロットの絞り込み
006抜取検査検査コストと流出リスクの設計
007信頼区間による品質評価推定の不確実性を含む説明
008工程の比較検定ライン差の統計的確認
009故障率推定保全・予備品計画
010品質シミュレーション翌月損失の確率的予算化

5. Python 環境の準備

numpypandas でデータを扱い、統計量は scipy、可視化は matplotlib を使います。乱数シードを固定するため、実行するたびに同じ結果を再現できます。グラフ内の表記は実行環境のフォント差を避けるため英語に統一します。

%matplotlib inline
import sys
import numpy as np
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from scipy import stats
from IPython.display import display

SEED = 20260711
rng = np.random.default_rng(SEED)
pd.set_option("display.precision", 4)

print(f"Python     : {sys.version.split()[0]}")
print(f"numpy      : {np.__version__}")
print(f"pandas     : {pd.__version__}")
print(f"matplotlib : {matplotlib.__version__}")
Python     : 3.13.1
numpy      : 2.5.1
pandas     : 3.0.3
matplotlib : 3.11.0

6. 架空データの作成

120ロット(3ライン×40ロット)について、寸法測定、外観検査、設備稼働時間、故障件数を生成します。ラインごとに平均・標準偏差・基礎不良確率・故障率をわずかに変え、さらに一部ロットへ特殊原因による寸法シフトを加えます。これは分析手法の挙動を確認するための架空設定です。

変数意味
dimension_mm重要寸法の実測値(各ロット50個)
inspected / defects外観検査数 / 不良数
exposure_hours / failures設備稼働時間 / 故障件数
LSL, USL寸法規格下限・上限
LSL, TARGET, USL = 49.5, 50.0, 50.5
n_lots, sample_per_lot, inspected_per_lot = 120, 50, 200
lines = np.tile(["A", "B", "C"], n_lots // 3)
dates = pd.date_range("2026-01-05", periods=n_lots, freq="D")

line_mean = {"A": 50.00, "B": 50.12, "C": 49.94}
line_sigma = {"A": 0.12, "B": 0.16, "C": 0.10}
base_defect_p = {"A": 0.007, "B": 0.015, "C": 0.009}
failure_rate = {"A": 1 / 1400, "B": 1 / 650, "C": 1 / 2200}
special_shift = {41: 0.32, 76: -0.28, 102: 0.35}  # 0始まりのロット位置

lot_rows, measurement_rows = [], []
for i, (date, line) in enumerate(zip(dates, lines)):
    mu = line_mean[line] + special_shift.get(i, 0.0)
    values = rng.normal(mu, line_sigma[line], sample_per_lot)
    p = min(base_defect_p[line] + 0.035 * abs(mu - TARGET) + (0.03 if i in special_shift else 0), 0.20)
    defects = rng.binomial(inspected_per_lot, p)
    exposure = int(rng.integers(650, 951))
    failures = rng.poisson(exposure * failure_rate[line])
    lot_id = f"L{i + 1:03d}"
    lot_rows.append((lot_id, date, line, inspected_per_lot, defects, exposure, failures,
                     values.mean(), values.std(ddof=1)))
    measurement_rows.extend((lot_id, date, line, x) for x in values)

lot_df = pd.DataFrame(lot_rows, columns=[
    "lot_id", "date", "line", "inspected", "defects", "exposure_hours",
    "failures", "dimension_mean", "dimension_std"
])
measurements = pd.DataFrame(measurement_rows, columns=["lot_id", "date", "line", "dimension_mm"])
lot_df["defect_rate"] = lot_df["defects"] / lot_df["inspected"]

print(f"ロット数: {len(lot_df):,} / 寸法測定数: {len(measurements):,}")
display(lot_df.head())
ロット数: 120 / 寸法測定数: 6,000
lot_id date line inspected defects exposure_hours failures dimension_mean dimension_std defect_rate
0 L001 2026-01-05 A 200 1 887 1 49.9851 0.1170 0.005
1 L002 2026-01-06 B 200 6 874 1 50.1248 0.1722 0.030
2 L003 2026-01-07 C 200 2 798 1 49.9519 0.1064 0.010
3 L004 2026-01-08 A 200 4 779 1 49.9892 0.1213 0.020
4 L005 2026-01-09 B 200 1 720 2 50.1023 0.1498 0.005

7. No.001:不良率推定

実務での意味

不良率は品質コスト、出荷判定、改善テーマ選定の入口です。ただし、ライン別不良率を単純平均すると、検査数が異なる場合に誤った値になります。基本は「不良総数 ÷ 検査総数」で集計します。

分析・モデル化の考え方

各製品が不良となる確率を pp、検査数を nn、不良数を XX とすると、独立かつ同一条件という近似の下で XBinomial(n,p)X\sim\mathrm{Binomial}(n,p) とみなせます。最尤推定量は

p^=Xn\hat{p}=\frac{X}{n}

です。まずライン別の点推定と寄与不良数を見て、全体KPIの悪化要因を特定します。

Pythonで確認する

defect_summary = lot_df.groupby("line").agg(
    lots=("lot_id", "count"), inspected=("inspected", "sum"), defects=("defects", "sum")
)
defect_summary["defect_rate_pct"] = 100 * defect_summary["defects"] / defect_summary["inspected"]
defect_summary["defect_share_pct"] = 100 * defect_summary["defects"] / defect_summary["defects"].sum()
display(defect_summary)

ax = defect_summary["defect_rate_pct"].plot(kind="bar", color=["#4C78A8", "#F58518", "#54A24B"], legend=False)
ax.set_title("Estimated Defect Rate by Line")
ax.set_xlabel("Line")
ax.set_ylabel("Defect rate (%)")
ax.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
lots inspected defects defect_rate_pct defect_share_pct
line
A 40 8000 61 0.7625 18.8272
B 40 8000 171 2.1375 52.7778
C 40 8000 92 1.1500 28.3951

png

結果の読み取り

ラインBは不良率と不良数への寄与がともに大きく、改善の第一候補です。ただし、点推定だけでは偶然変動との区別はできません。No.007の信頼区間、No.008の検定と組み合わせて、差の確からしさを確認します。

8. No.002:工程能力指数 Cp

実務での意味

Cpは、工程が規格幅に対して十分小さいばらつきで加工できるかを示します。設備更新、治具改善、条件標準化など「ばらつきを縮める投資」の判断材料になります。

分析・モデル化の考え方

工程が安定し、寸法がおおむね正規分布に従うとき、

Cp=USLLSL6sC_p=\frac{USL-LSL}{6s}

で評価します。6s6s は平均の周囲に広がる工程幅です。Cpは平均位置を考慮しないため、潜在能力の指標と解釈します。ここでは特殊原因3ロットを除いた基準データでライン別に計算します。

Pythonで確認する

special_lots = {"L042", "L077", "L103"}
baseline = measurements.loc[~measurements["lot_id"].isin(special_lots)].copy()
capability = baseline.groupby("line")["dimension_mm"].agg(mean="mean", std="std")
capability["Cp"] = (USL - LSL) / (6 * capability["std"])
display(capability)

ax = capability["Cp"].plot(kind="bar", color="#4C78A8", legend=False)
ax.axhline(1.33, color="#E45756", linestyle="--", label="Reference 1.33")
ax.set_title("Potential Process Capability (Cp)")
ax.set_xlabel("Line")
ax.set_ylabel("Cp")
ax.grid(axis="y", alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
mean std Cp
line
A 50.0002 0.1182 1.4100
B 50.1127 0.1612 1.0341
C 49.9389 0.0986 1.6895

png

結果の読み取り

ラインBは他ラインより標準偏差が大きく、潜在的な工程能力が低いことが分かります。一般に使われる1.33はあくまで参考線であり、採用基準は製品リスクや顧客要求に合わせて決めます。Cpだけでは中心ずれを評価できない点が次の課題です。

9. No.003:工程能力指数 Cpk

実務での意味

Cpkは、ばらつきに加えて工程平均が近い側の規格限界までどれだけ余裕を持つかを表します。Cpは良好なのにCpkが低いなら、設備能力そのものより、設定値・補正値・工具摩耗による芯ずれを優先して疑えます。

分析・モデル化の考え方

Cpk=min(USLxˉ3s,xˉLSL3s)C_{pk}=\min\left(\frac{USL-\bar{x}}{3s},\frac{\bar{x}-LSL}{3s}\right)

CpとCpkの差は中心ずれの情報を持ちます。ただし両指標とも工程の安定性を前提とするため、管理図で特殊原因を除外してから定常期間を評価します。

Pythonで確認する

capability["Cpu"] = (USL - capability["mean"]) / (3 * capability["std"])
capability["Cpl"] = (capability["mean"] - LSL) / (3 * capability["std"])
capability["Cpk"] = capability[["Cpu", "Cpl"]].min(axis=1)
capability["Cp_minus_Cpk"] = capability["Cp"] - capability["Cpk"]
display(capability[["mean", "std", "Cp", "Cpk", "Cp_minus_Cpk"]])

ax = capability[["Cp", "Cpk"]].plot(kind="bar", color=["#4C78A8", "#F58518"])
ax.set_title("Cp and Cpk by Line")
ax.set_xlabel("Line")
ax.set_ylabel("Capability index")
ax.grid(axis="y", alpha=0.3)
plt.tight_layout()
plt.show()
mean std Cp Cpk Cp_minus_Cpk
line
A 50.0002 0.1182 1.4100 1.4094 0.0006
B 50.1127 0.1612 1.0341 0.8010 0.2331
C 49.9389 0.0986 1.6895 1.4830 0.2065

png

結果の読み取り

ラインBではCpよりCpkがさらに低く、ばらつきと上側への中心ずれが同時に存在します。ラインCも中心が下側へ寄っているため、Cpだけを報告するとリスクを過小評価します。短期的には芯出し、恒久対策としてラインBのばらつき要因分析、という施策分解が可能です。

10. No.004:管理図

実務での意味

管理図は、不良率の時系列変化から「工程に内在する通常変動」と「調査すべき特殊原因」を分ける道具です。管理限界を超えたロットを起点に、材料、設備、作業、測定、環境の変更履歴を調べます。

分析・モデル化の考え方

検査数が一定のp管理図では、全期間のプール不良率を pˉ\bar{p} として

LCL=\max\left(0,\bar{p}-3\sqrt{\frac{\bar{p}(1-\bar{p})}{n}}\right)$$ を使います。管理限界は規格値ではなく、基準期間の工程変動から計算される点が重要です。 ### Pythonで確認する ```python p_bar = lot_df["defects"].sum() / lot_df["inspected"].sum() sigma_p = np.sqrt(p_bar * (1 - p_bar) / inspected_per_lot) ucl_p, lcl_p = p_bar + 3 * sigma_p, max(0, p_bar - 3 * sigma_p) lot_df["p_chart_signal"] = (lot_df["defect_rate"] > ucl_p) | (lot_df["defect_rate"] < lcl_p) fig, ax = plt.subplots(figsize=(11, 4)) ax.plot(lot_df["date"], 100 * lot_df["defect_rate"], marker="o", markersize=3, linewidth=1) ax.axhline(100 * p_bar, color="#54A24B", label="Center line") ax.axhline(100 * ucl_p, color="#E45756", linestyle="--", label="UCL") ax.axhline(100 * lcl_p, color="#E45756", linestyle="--", label="LCL") signals = lot_df[lot_df["p_chart_signal"]] ax.scatter(signals["date"], 100 * signals["defect_rate"], color="red", s=45, zorder=3, label="Signal") ax.set_title("p-Chart for Lot Defect Rate") ax.set_xlabel("Production date") ax.set_ylabel("Defect rate (%)") ax.grid(alpha=0.3) ax.legend(ncol=4) plt.tight_layout() plt.show() display(signals[["lot_id", "date", "line", "defects", "defect_rate"]]) ``` ![png](/blog/100-knock/07-probability-statistics-marketing-1/01_nb/01_nb_16_0.png) <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>lot_id</th> <th>date</th> <th>line</th> <th>defects</th> <th>defect_rate</th> </tr> </thead> <tbody> <tr> <th>16</th> <td>L017</td> <td>2026-01-21</td> <td>B</td> <td>8</td> <td>0.04</td> </tr> <tr> <th>28</th> <td>L029</td> <td>2026-02-02</td> <td>B</td> <td>10</td> <td>0.05</td> </tr> <tr> <th>41</th> <td>L042</td> <td>2026-02-15</td> <td>C</td> <td>8</td> <td>0.04</td> </tr> <tr> <th>102</th> <td>L103</td> <td>2026-04-17</td> <td>A</td> <td>8</td> <td>0.04</td> </tr> </tbody> </table> ### 結果の読み取り 赤点は、基準工程から見て偶然だけでは説明しにくい候補です。管理限界超過は「不良品確定」ではなく「特殊原因を調査する合図」です。逆に管理内でも顧客規格を満たす保証にはならないため、管理図と工程能力を併用します。 ## 11. No.005:異常検知 ### 実務での意味 多数のセンサーや品質特性を監視すると、人手ですべての推移を見ることは困難です。異常スコアで調査候補を絞ることで、品質担当者を原因究明へ集中させられます。 ### 分析・モデル化の考え方 ここでは説明可能性を優先し、ロット平均を全体中央値 $m$ とMADで標準化したロバストzスコアを用います。 $$z_i^{(robust)}=\frac{0.6745(x_i-m)}{\mathrm{median}(|x_i-m|)}$$ 平均・標準偏差によるzスコアより外れ値自身の影響を受けにくい方法です。$|z|>3.5$ を一次調査の候補としますが、閾値は見逃しコストと誤報対応コストから設計します。 ### Pythonで確認する ```python def within_line_robust_z(series): median = series.median() mad = np.median(np.abs(series - median)) return 0.6745 * (series - median) / mad lot_df["robust_z"] = lot_df.groupby("line")["dimension_mean"].transform(within_line_robust_z) anomalies = lot_df.loc[lot_df["robust_z"].abs() > 3.5] fig, ax = plt.subplots(figsize=(11, 4)) ax.plot(lot_df["date"], lot_df["robust_z"], color="#4C78A8", linewidth=1) ax.axhline(3.5, color="#E45756", linestyle="--") ax.axhline(-3.5, color="#E45756", linestyle="--") ax.scatter(anomalies["date"], anomalies["robust_z"], color="red", s=45, zorder=3) ax.set_title("Robust Anomaly Score for Lot Mean") ax.set_xlabel("Production date") ax.set_ylabel("Robust z-score") ax.grid(alpha=0.3) plt.tight_layout() plt.show() display(anomalies[["lot_id", "date", "line", "dimension_mean", "robust_z"]]) ``` ![png](/blog/100-knock/07-probability-statistics-marketing-1/01_nb/01_nb_19_0.png) <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>lot_id</th> <th>date</th> <th>line</th> <th>dimension_mean</th> <th>robust_z</th> </tr> </thead> <tbody> <tr> <th>41</th> <td>L042</td> <td>2026-02-15</td> <td>C</td> <td>50.2693</td> <td>20.1432</td> </tr> <tr> <th>76</th> <td>L077</td> <td>2026-03-22</td> <td>B</td> <td>49.8470</td> <td>-11.8641</td> </tr> <tr> <th>102</th> <td>L103</td> <td>2026-04-17</td> <td>A</td> <td>50.3314</td> <td>20.3775</td> </tr> </tbody> </table> ### 結果の読み取り 特殊シフトを加えたロットが大きなスコアとして抽出されます。実運用では、検知後に原材料ロット、工具交換、温湿度、作業者、測定器校正などの履歴を自動で紐付けると、アラートが原因究明へつながります。モデルが示すのは原因ではなく、調査優先度です。 ## 12. No.006:抜取検査 ### 実務での意味 全数検査が高価・破壊的な場合、ロットから一部を抜き取り、合格・不合格を判断します。サンプル数 $n$ と合格判定個数 $c$ は、検査工数だけでなく、良いロットを拒否する生産者危険と悪いロットを通す消費者危険を決めます。 ### 分析・モデル化の考え方 ロットが十分大きいとき、不良率 $p$ のロットから $n$ 個を抜き取り、不良が $c$ 個以下なら合格とする確率は $$P(\mathrm{accept}\mid p)=\sum_{x=0}^{c}{n\choose x}p^x(1-p)^{n-x}$$ です。この合格確率を不良率に対して描いたものがOC曲線です。有限ロットでは非復元抽出を反映した超幾何分布を使います。 ### Pythonで確認する ```python plans = [(50, 1), (80, 1), (80, 2)] p_grid = np.linspace(0, 0.10, 201) fig, ax = plt.subplots(figsize=(8, 4.5)) for n, c in plans: accept_prob = stats.binom.cdf(c, n, p_grid) ax.plot(100 * p_grid, 100 * accept_prob, label=f"n={n}, c={c}") ax.axvline(1, color="gray", linestyle=":", label="AQL example: 1%") ax.axvline(5, color="black", linestyle=":", label="LQ example: 5%") ax.set_title("Operating Characteristic Curves") ax.set_xlabel("Lot defect rate (%)") ax.set_ylabel("Probability of acceptance (%)") ax.grid(alpha=0.3) ax.legend() plt.tight_layout() plt.show() plan_table = pd.DataFrame([ {"n": n, "c": c, "P_accept_at_1%": stats.binom.cdf(c, n, 0.01), "P_accept_at_5%": stats.binom.cdf(c, n, 0.05)} for n, c in plans ]) display(plan_table) ``` ![png](/blog/100-knock/07-probability-statistics-marketing-1/01_nb/01_nb_22_0.png) <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>n</th> <th>c</th> <th>P_accept_at_1%</th> <th>P_accept_at_5%</th> </tr> </thead> <tbody> <tr> <th>0</th> <td>50</td> <td>1</td> <td>0.9106</td> <td>0.2794</td> </tr> <tr> <th>1</th> <td>80</td> <td>1</td> <td>0.8092</td> <td>0.0861</td> </tr> <tr> <th>2</th> <td>80</td> <td>2</td> <td>0.9534</td> <td>0.2306</td> </tr> </tbody> </table> ### 結果の読み取り サンプル数を増やし合格判定個数を厳しくすると、悪いロットを通す確率は下がりますが、良いロットを拒否する確率や検査費用が増えます。AQLやLQは契約・用途・損失額と整合させる必要があり、OC曲線を顧客と供給者の共通言語にします。 ## 13. No.007:信頼区間による品質評価 ### 実務での意味 「不良率1%」という点推定は、検査数200件と20,000件では意味が異なります。信頼区間を併記すると、追加検査が必要か、顧客へどの幅で説明すべきかを判断できます。 ### 分析・モデル化の考え方 二項比率の区間推定には複数の方法があります。単純な正規近似は不良数が少ないと不安定なため、ここではWilson区間を使います。$z=z_{1-\alpha/2}$ とすると中心と半幅を補正し、0〜1の範囲内で比較的安定した区間を得ます。95%信頼区間は「真値が95%の確率でこの区間にある」という意味ではなく、同じ手順を繰り返したとき95%の区間が真値を覆うという手続き上の性質です。 ### Pythonで確認する ```python def wilson_interval(x, n, alpha=0.05): z = stats.norm.ppf(1 - alpha / 2) phat = x / n denom = 1 + z**2 / n center = (phat + z**2 / (2 * n)) / denom half = z * np.sqrt(phat * (1 - phat) / n + z**2 / (4 * n**2)) / denom return center - half, center + half ci_rows = [] for line, row in defect_summary.iterrows(): low, high = wilson_interval(int(row["defects"]), int(row["inspected"])) ci_rows.append((line, row["defects"] / row["inspected"], low, high)) ci_df = pd.DataFrame(ci_rows, columns=["line", "estimate", "ci_low", "ci_high"]).set_index("line") display(100 * ci_df) fig, ax = plt.subplots(figsize=(7, 4)) y = np.arange(len(ci_df)) ax.errorbar(100 * ci_df["estimate"], y, xerr=[100 * (ci_df["estimate"] - ci_df["ci_low"]), 100 * (ci_df["ci_high"] - ci_df["estimate"])], fmt="o", capsize=4, color="#4C78A8") ax.set_yticks(y, ci_df.index) ax.set_title("Defect Rate with 95% Wilson Interval") ax.set_xlabel("Defect rate (%)") ax.set_ylabel("Line") ax.grid(axis="x", alpha=0.3) plt.tight_layout() plt.show() ``` <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>estimate</th> <th>ci_low</th> <th>ci_high</th> </tr> <tr> <th>line</th> <th></th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <th>A</th> <td>0.7625</td> <td>0.5941</td> <td>0.9782</td> </tr> <tr> <th>B</th> <td>2.1375</td> <td>1.8428</td> <td>2.4782</td> </tr> <tr> <th>C</th> <td>1.1500</td> <td>0.9387</td> <td>1.4082</td> </tr> </tbody> </table> ![png](/blog/100-knock/07-probability-statistics-marketing-1/01_nb/01_nb_25_1.png) ### 結果の読み取り 各点の横線が推定誤差を表します。区間が広ければ、点推定の順位だけで大きな投資を決めるのは危険です。一方、ラインBの区間が他より高い側に位置するなら、工程差の疑いが強まります。次に、差が偶然で説明できるかを明示的に検定します。 ## 14. No.008:工程の比較検定 ### 実務での意味 ライン変更、設備改修、仕入先切替の効果を判断するとき、観測差と再現可能な差を区別する必要があります。検定は「差がない」という仮説の下で、今回以上の差がどれほど起こりにくいかを評価します。 ### 分析・モデル化の考え方 ラインAとBの不良率について、帰無仮説 $H_0:p_A=p_B$、対立仮説 $H_1:p_A\ne p_B$ の二標本比率z検定を行います。プール比率 $\hat p$ を用いた統計量は $$z=\frac{\hat p_A-\hat p_B}{\sqrt{\hat p(1-\hat p)(1/n_A+1/n_B)}}$$ です。p値は差の大きさや事業価値そのものではありません。効果量として不良率差と相対リスクも併記し、事前に定めた有意水準5%で判断します。 ### Pythonで確認する ```python a = defect_summary.loc["A"] b = defect_summary.loc["B"] p_a, p_b = a["defects"] / a["inspected"], b["defects"] / b["inspected"] p_pool = (a["defects"] + b["defects"]) / (a["inspected"] + b["inspected"]) se = np.sqrt(p_pool * (1 - p_pool) * (1 / a["inspected"] + 1 / b["inspected"])) z_stat = (p_a - p_b) / se p_value = 2 * stats.norm.sf(abs(z_stat)) comparison = pd.DataFrame({ "metric": ["A defect rate", "B defect rate", "B - A (percentage points)", "B/A relative risk", "z statistic", "two-sided p-value"], "value": [p_a, p_b, 100 * (p_b - p_a), p_b / p_a, z_stat, p_value] }) display(comparison) print("5%水準の判定:", "ライン間に統計的な差あり" if p_value < 0.05 else "差ありとは結論できない") ``` <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>metric</th> <th>value</th> </tr> </thead> <tbody> <tr> <th>0</th> <td>A defect rate</td> <td>7.6250e-03</td> </tr> <tr> <th>1</th> <td>B defect rate</td> <td>2.1375e-02</td> </tr> <tr> <th>2</th> <td>B - A (percentage points)</td> <td>1.3750e+00</td> </tr> <tr> <th>3</th> <td>B/A relative risk</td> <td>2.8033e+00</td> </tr> <tr> <th>4</th> <td>z statistic</td> <td>-7.2748e+00</td> </tr> <tr> <th>5</th> <td>two-sided p-value</td> <td>3.4696e-13</td> </tr> </tbody> </table> 5%水準の判定: ライン間に統計的な差あり ### 結果の読み取り p値が5%未満なら、同じ不良率の工程から今回の差が出たとは考えにくいと判断します。ただし、ラインと材料・製品型式・作業条件が交絡していれば、ライン自体が原因とは断定できません。層別集計や計画的な比較試験を追加し、統計的有意差と改善による金額効果の両方で優先順位を決めます。 ## 15. No.009:故障率推定 ### 実務での意味 設備故障は品質変動、停止損失、納期遅延を同時に引き起こします。故障件数だけでなく稼働時間で割った故障率を使うと、稼働量の異なるラインを比較し、予備品・保全要員・更新計画へつなげられます。 ### 分析・モデル化の考え方 一定故障率 $\lambda$ のポアソン過程を仮定すると、総稼働時間 $T$ における故障数 $K$ の最尤推定量は $$\hat\lambda=\frac{K}{T},\qquad \widehat{MTBF}=\frac{1}{\hat\lambda}=\frac{T}{K}$$ です。故障数が少ないと推定誤差が大きいため、カイ二乗分布による故障率の95%信頼区間も示します。故障率が時間とともに変化する摩耗故障には、一定率モデルではなくワイブル分析などが必要です。 ### Pythonで確認する ```python reliability = lot_df.groupby("line").agg( exposure_hours=("exposure_hours", "sum"), failures=("failures", "sum") ) reliability["failures_per_1000h"] = 1000 * reliability["failures"] / reliability["exposure_hours"] reliability["estimated_MTBF_h"] = reliability["exposure_hours"] / reliability["failures"] alpha = 0.05 reliability["rate_ci_low_per_1000h"] = [ 1000 * (0 if k == 0 else stats.chi2.ppf(alpha / 2, 2 * k) / (2 * t)) for k, t in zip(reliability["failures"], reliability["exposure_hours"]) ] reliability["rate_ci_high_per_1000h"] = [ 1000 * stats.chi2.ppf(1 - alpha / 2, 2 * (k + 1)) / (2 * t) for k, t in zip(reliability["failures"], reliability["exposure_hours"]) ] display(reliability) ax = reliability["failures_per_1000h"].plot(kind="bar", color="#B279A2", legend=False) ax.set_title("Estimated Equipment Failure Rate") ax.set_xlabel("Line") ax.set_ylabel("Failures per 1,000 hours") ax.grid(axis="y", alpha=0.3) plt.tight_layout() plt.show() ``` <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>exposure_hours</th> <th>failures</th> <th>failures_per_1000h</th> <th>estimated_MTBF_h</th> <th>rate_ci_low_per_1000h</th> <th>rate_ci_high_per_1000h</th> </tr> <tr> <th>line</th> <th></th> <th></th> <th></th> <th></th> <th></th> <th></th> </tr> </thead> <tbody> <tr> <th>A</th> <td>32204</td> <td>23</td> <td>0.7142</td> <td>1400.1739</td> <td>0.4527</td> <td>1.0716</td> </tr> <tr> <th>B</th> <td>31725</td> <td>58</td> <td>1.8282</td> <td>546.9828</td> <td>1.3882</td> <td>2.3634</td> </tr> <tr> <th>C</th> <td>32193</td> <td>17</td> <td>0.5281</td> <td>1893.7059</td> <td>0.3076</td> <td>0.8455</td> </tr> </tbody> </table> ![png](/blog/100-knock/07-probability-statistics-marketing-1/01_nb/01_nb_31_1.png) ### 結果の読み取り ラインBは故障率も高く、品質不良と設備状態が同時に悪い可能性があります。ただし信頼区間が重なる場合、短期間の件数だけで更新投資を断定すべきではありません。故障モード、停止時間、修理費、品質への影響を設備ID単位で蓄積し、リスクベース保全へ進めます。 ## 16. No.010:品質シミュレーション ### 実務での意味 平均値だけの計画では、上振れ時の選別要員、廃棄費、顧客対応費を確保できません。モンテカルロシミュレーションで翌月の不良数と損失額の分布を作ると、予算とリスク許容度を結び付けられます。 ### 分析・モデル化の考え方 不良率の推定誤差をBeta分布、与えられた不良率での不良数を二項分布で表すBeta-Binomial型の予測を行います。一様な事前分布 $p\sim\mathrm{Beta}(1,1)$ と、不良数 $x$、良品数 $n-x$ から $$p\mid x,n\sim\mathrm{Beta}(1+x,1+n-x)$$ を得ます。各試行で $p$ を引き、翌月生産数 $N$ に対する不良数を生成します。これは工程条件が現状と同じという仮定のシナリオであり、将来を保証するものではありません。 ### Pythonで確認する ```python sim_rng = np.random.default_rng(SEED + 10) observed_x = int(lot_df["defects"].sum()) observed_n = int(lot_df["inspected"].sum()) next_month_units = 300_000 unit_loss_yen = 4_500 n_sim = 20_000 sim_p = sim_rng.beta(1 + observed_x, 1 + observed_n - observed_x, n_sim) sim_defects = sim_rng.binomial(next_month_units, sim_p) sim_loss_yen = sim_defects * unit_loss_yen simulation_summary = pd.Series({ "expected_defects": sim_defects.mean(), "median_defects": np.median(sim_defects), "P90_defects": np.quantile(sim_defects, 0.90), "P95_defects": np.quantile(sim_defects, 0.95), "expected_loss_yen": sim_loss_yen.mean(), "P95_loss_yen": np.quantile(sim_loss_yen, 0.95), }) display(simulation_summary.to_frame("value")) fig, ax = plt.subplots(figsize=(8, 4.5)) ax.hist(sim_loss_yen / 1_000_000, bins=40, color="#4C78A8", edgecolor="white") ax.axvline(np.quantile(sim_loss_yen, 0.95) / 1_000_000, color="#E45756", linestyle="--", label="95th percentile") ax.set_title("Simulated Monthly Quality Loss") ax.set_xlabel("Quality loss (million JPY)") ax.set_ylabel("Simulation count") ax.grid(axis="y", alpha=0.3) ax.legend() plt.tight_layout() plt.show() ``` <style scoped> .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } </style> <table border="1" class="dataframe"> <thead> <tr style="text-align: right;"> <th></th> <th>value</th> </tr> </thead> <tbody> <tr> <th>expected_defects</th> <td>4.0601e+03</td> </tr> <tr> <th>median_defects</th> <td>4.0570e+03</td> </tr> <tr> <th>P90_defects</th> <td>4.3660e+03</td> </tr> <tr> <th>P95_defects</th> <td>4.4520e+03</td> </tr> <tr> <th>expected_loss_yen</th> <td>1.8270e+07</td> </tr> <tr> <th>P95_loss_yen</th> <td>2.0034e+07</td> </tr> </tbody> </table> ![png](/blog/100-knock/07-probability-statistics-marketing-1/01_nb/01_nb_34_1.png) ### 結果の読み取り 平均損失は予算の基準、95パーセンタイルは厳しめの資源計画に使えます。対策後の不良率シナリオも同じ枠組みで計算し、削減損失と対策費を比較すれば投資評価になります。なお、ロット間相関や急な工程変化があると単純モデルは裾のリスクを過小評価するため、実績とのバックテストが必要です。 ## 17. 対象ノックを通して見える実務上の示唆 10本を通して重要なのは、指標を単独で使わないことです。 1. **不良率と信頼区間**で、現在水準と推定誤差を把握する 2. **管理図と異常検知**で、時間上の特殊原因を見つける 3. **CpとCpk**で、ばらつきと中心ずれを切り分ける 4. **検定**で、ライン差が偶然だけで説明できるか確認する 5. **抜取検査**で、検査負荷と流出リスクを明文化する 6. **故障率**で、品質と設備保全のデータを接続する 7. **シミュレーション**で、品質KPIを損失額と必要資源へ翻訳する この順序により、「Bラインが悪い」という指摘から、「中心補正、ばらつき低減、設備点検のどれを、いくらかけて、どのリスク水準まで実施するか」という意思決定へ進めます。 ## 18. 実務導入する場合に必要なこと - **定義の統一**:不良、手直し、廃棄、検査母数、停止の定義と集計粒度をそろえる - **トレーサビリティ**:製品、ロット、設備、工具、材料、作業条件、測定器をキーで接続する - **測定の信頼性**:Gage R&Rや校正履歴を確認し、測定誤差を工程変動と混同しない - **基準期間の管理**:工程変更後は管理限界や能力指数の基準期間を見直す - **アラート運用**:検知後の担当、期限、原因分類、是正処置、効果確認までワークフロー化する - **意思決定基準**:誤警報、見逃し、流出、停止、検査に伴うコストを定量化する - **モデル監視**:分布仮定、独立性、予測区間の被覆率を定期的に検証する ## 19. まとめ 品質管理の統計は、数値をきれいに説明するためではなく、不確実な状況で行動を選ぶためにあります。点推定、工程能力、時系列監視、仮説検定、信頼性、シミュレーションを同じデータ基盤上で運用すると、現場の異常対応から経営の投資判断まで一貫した説明が可能になります。 本 notebook の架空例は小規模ですが、実務では製品階層、工程経路、設備状態、原価、顧客影響まで接続することで価値が高まります。まずは重要な品質特性を一つ選び、定義とデータ粒度を整え、管理図と改善アクションの小さな運用から始めるのが現実的です。 ## 20. 法人向けのご相談 数理工房では、品質KPI設計、工程能力評価、異常検知、設備保全、品質損失シミュレーション、分析基盤の実装まで、企業ごとのデータと意思決定プロセスに合わせて支援します。研修だけでなく、現場で継続利用できる分析・運用設計についてもご相談いただけます。 > 📩 **お問い合わせ**: [surikobo.co.jp/contact](https://surikobo.co.jp/contact) > まずはお気軽にご相談ください。