Mímisbrunnr知恵の泉

← オペレーションズマネジメント 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:経済的発注量EOQ(いくつ発注するか)・予測誤差の評価と追跡信号(σ_e→安全在庫の橋) | 確率の土台:正規分布(標準正規・標準化)(z=サービス水準の分位点) | 次:定量発注と定期発注

要点(BLUF)

1. なぜ安全在庫が要るのか:守る相手は「リードタイム需要」

発注したら瞬時に届くなら、在庫が0になった瞬間に補充すればよく、安全在庫は不要です。現実にはリードタイム LL(発注から入荷までの日数)があり、その間も需要は出続けます。だから「在庫が**LL 期間ぶんの需要まで下がったら発注する」のが基本——この発注をかける在庫水準が発注点 ROP**です。

ところが需要は毎日同じではありません。リードタイム中にたまたま需要が多ければ、補充が届く前に在庫が尽きて品切れします。そこで、平均的なリードタイム需要に上乗せして持つ緩衝が安全在庫 SS。守るべき確率変数は、補充が届くまでに発生する需要の合計=リードタイム需要 DLTです。

DLT=i=1Ldi(di:第 i 日の需要、平均 μd・標準偏差 σd・独立)\text{DLT}=\sum_{i=1}^{L} d_i \quad(\text{$d_i$:第 $i$ 日の需要、平均 $\mu_d$・標準偏差 $\sigma_d$・独立})
flowchart LR
  ROP["在庫が発注点 ROP に低下"] -->|"発注をかける"| WAIT["リードタイム L 待つ<br/>(この間も需要が出る)"]
  WAIT --> RISK["リードタイム需要 DLT がどれだけ出るか"]
  RISK -->|"DLT が ROP 以下"| OK["在庫が持つ(補充到着)"]
  RISK -->|"DLT が ROP 超過"| NG["在庫切れ(安全在庫を食い破る)"]

2. リードタイム需要の平均と標準偏差

DLT は独立な日次需要の和なので、期待値は足し算分散も足し算です。

μDLT=E ⁣[i=1Ldi]=Lμd,σDLT2=Var ⁣[i=1Ldi]=Lσd2\mu_{DLT}=\mathbb{E}\!\left[\sum_{i=1}^{L} d_i\right]=L\,\mu_d,\qquad \sigma_{DLT}^2=\mathrm{Var}\!\left[\sum_{i=1}^{L} d_i\right]=L\,\sigma_d^{2}

平方根をとると、ばらつきはリードタイムの平方根で伸びます——ここが在庫設計の急所です。

σDLT=σdL\sigma_{DLT}=\sigma_d\sqrt{L}

平均は LL比例(線形)するのに、標準偏差は L\sqrt{L} でしか増えません。だからリードタイムが伸びるほど、平均需要に対する相対的なばらつきは縮みます(変動係数 σDLT/μDLT=σdμdL\sigma_{DLT}/\mu_{DLT}=\dfrac{\sigma_d}{\mu_d\sqrt{L}} が小さくなる)。それでも安全在庫は L\sqrt{L} で増えるので、リードタイム短縮は在庫削減に直接効きます。

発注点とサービス水準

DLT が(中心極限定理で)おおむね正規分布 N(μDLT,σDLT2)N(\mu_{DLT},\sigma_{DLT}^2) に従うとみなすと、発注点を

ROP=μDLT+zσDLTSSROP=\mu_{DLT}+\underbrace{z\,\sigma_{DLT}}_{SS}

と置いたとき、そのサイクルで品切れしない確率(補充到着まで在庫が持つ確率)は

CSL=Pr(DLTROP)=Pr ⁣(Zz)=Φ(z)        z=Φ1(CSL)CSL=\Pr(\text{DLT}\le ROP)=\Pr\!\left(Z\le z\right)=\Phi(z) \;\;\Longrightarrow\;\; z=\Phi^{-1}(CSL)

です。欲しいサービス水準 CSLCSL(例 95%)を決めれば、対応する zz(標準正規の分位点)が決まり、安全在庫 SS=zσDLTSS=z\,\sigma_{DLT} が出ます。Φ1\Phi^{-1}(正規分位点)そのものの理論は 正規分布(標準正規・標準化) に譲り、ここでは scipy.stats.norm.ppf で値を取ります。

予測誤差からの接続予測誤差の評価と追跡信号 では、予測誤差の標準偏差を σe1.25MAD\sigma_e\approx1.25\,\text{MAD} と見積もりました。リードタイム1期なら σDLT=σe\sigma_{DLT}=\sigma_e、一般には σDLT=σeL\sigma_{DLT}=\sigma_e\sqrt{L}予測の外れ幅 σe\sigma_e がそのまま安全在庫の素材になります。SS=zσeLSS=z\,\sigma_e\sqrt{L}

3. サービス水準から z・SS・ROP を計算する(コード)

需要は1日平均 μd=50\mu_d=50 個・標準偏差 σd=12\sigma_d=12 個、リードタイム L=4L=4 日。目標 CSL を 90/95/99% としたときの zz・安全在庫・発注点を scipy.stats.norm.ppf で計算し、続けてリードタイムを伸ばすと安全在庫が L\sqrt{L} で増えることを見ます。

import numpy as np
import pandas as pd
from scipy.stats import norm

# 需要:1日あたり平均 μ_d、標準偏差 σ_d。リードタイム L 日(一定・需要は独立)
mu_d = 50.0     # 1日あたり平均需要(個/日)
sigma_d = 12.0  # 1日あたり需要の標準偏差(個/日)
L = 4           # リードタイム(日)

# リードタイム需要 DLT の平均と標準偏差
mu_DLT = L * mu_d
sigma_DLT = sigma_d * np.sqrt(L)
print(f"リードタイム需要 DLT: 平均 μ_DLT = L·μ_d        = {mu_DLT:.1f} 個")
print(f"                     標準偏差 σ_DLT = σ_d·sqrt(L) = {sigma_DLT:.2f} 個")
print()

# 目標CSL → z = Φ^{-1}(CSL) → 安全在庫 SS = z·σ_DLT、発注点 ROP = μ_DLT + SS
print("CSL    z=norm.ppf(CSL)   SS=z·σ_DLT    ROP=μ_DLT+SS")
for csl in [0.90, 0.95, 0.99]:
    z = norm.ppf(csl)
    SS = z * sigma_DLT
    ROP = mu_DLT + SS
    print(f"{csl:.2f}      {z:7.4f}       {SS:7.2f}       {ROP:7.2f}")
print()

# リードタイムが伸びると安全在庫は sqrt(L) で増える(z=1.645, 95% 固定)
z95 = norm.ppf(0.95)
print(f"[L の効果] z={z95:.3f}(95%)固定。SS = z·σ_d·sqrt(L)")
rows = []
for Lx in [1, 2, 4, 8, 16]:
    sdlt = sigma_d * np.sqrt(Lx)
    rows.append({"L(日)": Lx, "sqrt(L)": np.sqrt(Lx), "σ_DLT": sdlt, "SS(95%)": z95 * sdlt})
print(pd.DataFrame(rows).to_string(index=False, float_format=lambda x: f"{x:.3f}"))

出力:

リードタイム需要 DLT: 平均 μ_DLT = L·μ_d        = 200.0 個
                     標準偏差 σ_DLT = σ_d·sqrt(L) = 24.00 個

CSL    z=norm.ppf(CSL)   SS=z·σ_DLT    ROP=μ_DLT+SS
0.90       1.2816         30.76        230.76
0.95       1.6449         39.48        239.48
0.99       2.3263         55.83        255.83

[L の効果] z=1.645(95%)固定。SS = z·σ_d·sqrt(L)
 L(日)  sqrt(L)  σ_DLT  SS(95%)
    1    1.000 12.000   19.738
    2    1.414 16.971   27.914
    4    2.000 24.000   39.476
    8    2.828 33.941   55.828
   16    4.000 48.000   78.953

出力の意味:リードタイム需要は平均 200 個・標準偏差 24 個(=124=12\sqrt{4})。サービス水準を上げるほど zz が増え、安全在庫が膨らみます——90%なら z=1.28z=1.28 で SS 30.8 個、95%で 39.5 個、99%で 55.8 個。注目は最後の1%の高さ:95→99%(4ポイント)に上げるだけで安全在庫は 39.555.839.5\to55.84割増しサービス水準の上澄みは高くつく(正規分布の裾が薄いので、わずかな確率を潰すのに大きな zz=在庫が要る)。下段の表は L\sqrt{L} の効き方で、LL4164\to16 と4倍にしても、安全在庫は 39.579.039.5\to79.0 と2倍にしかなりません(4=2\sqrt{4}=2)。逆に言えば、リードタイムを 1/41/4 に縮めれば安全在庫は半分になる——調達リードタイム短縮が在庫に効く理由です。

4. モンテカルロで欠品率を実証する(コード)

ROP=μDLT+zσDLTROP=\mu_{DLT}+z\,\sigma_{DLT} なら欠品率は 1CSL1-CSL」は本当か。擬似需要でリードタイム需要を20万サイクルサンプリングし、設定した ROP を超える(=在庫切れ)割合を数えます。理屈どおりなら、z=1.645z=1.645(95%)で約5%、99%で約1%になるはずです。

import numpy as np
import matplotlib.pyplot as plt
import japanize_matplotlib
from scipy.stats import norm

rng = np.random.default_rng(7)

mu_d, sigma_d, L = 50.0, 12.0, 4
mu_DLT = L * mu_d
sigma_DLT = sigma_d * np.sqrt(L)

# 多数の発注サイクルをシミュレート:各サイクルで L 日分の需要を合計=DLT。
# 発注点 ROP を超えたら(DLT > ROP)在庫切れ。達成欠品率が 1-CSL になるか検証
n_cycles = 200000
print("目標CSL   ROP       理論欠品率   実測欠品率(MC)")
for csl in [0.90, 0.95, 0.99]:
    z = norm.ppf(csl)
    ROP = mu_DLT + z * sigma_DLT
    DLT = rng.normal(mu_d, sigma_d, size=(n_cycles, L)).sum(axis=1)
    stockout_rate = np.mean(DLT > ROP)
    print(f"{csl:.2f}     {ROP:7.2f}      {1 - csl:6.3f}       {stockout_rate:7.4f}")

# z=1.645(95%)の DLT 分布・ROP・SS を図示
z = norm.ppf(0.95)
ROP = mu_DLT + z * sigma_DLT
DLT = rng.normal(mu_d, sigma_d, size=(n_cycles, L)).sum(axis=1)
emp = np.mean(DLT > ROP)
print(f"\n図の設定:CSL=95%, ROP={ROP:.2f}, SS={z * sigma_DLT:.2f}")
print(f"  ROP 超過(欠品)割合 = {emp:.4f}(目標 0.05)")

plt.figure(figsize=(10, 5.5))
plt.hist(DLT, bins=80, density=True, color="#aec7e8", edgecolor="white", label="DLT 分布(MC)")
xs = np.linspace(DLT.min(), DLT.max(), 300)
plt.plot(xs, norm.pdf(xs, mu_DLT, sigma_DLT), color="#1f77b4", lw=2, label="正規近似")
plt.axvline(mu_DLT, color="gray", ls="--", label=f"μ_DLT={mu_DLT:.0f}")
plt.axvline(ROP, color="#d62728", lw=2, label=f"ROP={ROP:.1f}")
mask = xs >= ROP
plt.fill_between(xs, 0, norm.pdf(xs, mu_DLT, sigma_DLT), where=mask,
                 color="#d62728", alpha=0.3, label="欠品確率(約5%)")
plt.annotate("", xy=(ROP, 0.004), xytext=(mu_DLT, 0.004),
             arrowprops=dict(arrowstyle="<->", color="green"))
plt.text((mu_DLT + ROP) / 2, 0.0043, f"安全在庫 SS={z * sigma_DLT:.1f}", ha="center", color="green")
plt.xlabel("リードタイム需要 DLT(個)"); plt.ylabel("確率密度")
plt.title("発注点 ROP=μ_DLT+z·σ_DLT:右裾の面積が欠品確率(CSL=95%→約5%)")
plt.legend(); plt.tight_layout(); plt.show()

出力:

目標CSL   ROP       理論欠品率   実測欠品率(MC)
0.90      230.76       0.100        0.0996
0.95      239.48       0.050        0.0495
0.99      255.83       0.010        0.0101

図の設定:CSL=95%, ROP=239.48, SS=39.48
  ROP 超過(欠品)割合 = 0.0496(目標 0.05)

出力の意味:実測の欠品率は 0.0996/0.0495/0.0101 で、理論値 0.10/0.05/0.01 と小数第3位までほぼ一致。z=1.645z=1.645 の安全在庫を積めば、欠品は20サイクルに1回(5%)」が数値で裏取りできました。図は20万サイクルのリードタイム需要のヒストグラム(青)に正規近似(濃青)を重ねたもの。灰の破線が平均 μDLT=200\mu_{DLT}=200、赤い線が発注点 ROP=239.5ROP=239.5、その間の緑の矢印が安全在庫 SS=39.5SS=39.5 です。赤い線の右側の面積(約5%)がそのまま欠品確率——発注点を右にずらす(安全在庫を増やす)ほどこの裾が薄くなり、欠品が減ります。安全在庫とは、この右裾を許容水準まで削るために積む在庫だと、絵で腑に落ちます。

⚠️ よくある誤解

関連ノート