Mímisbrunnr知恵の泉

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

🎓 レベル:応用 | 重要度:A(必須)

📎 前提:安全在庫と発注点(サービス水準 CSL を所与にした連続監視の発注点 ROP=μ+zσ。本稿はその対)・経済的発注量EOQ(多期・補充あり。本稿は単一期) | 確率の土台:正規分布(標準正規・標準化)(z=分位点 Φ^{-1}) | 次:収益管理(レベニューマネジメント)

要点(BLUF)

1. 単一期間の在庫:補充できない一発勝負

EOQ(経済的発注量EOQ)や発注点(安全在庫と発注点)は、在庫が減ればまた発注して補充できる多期の世界でした。新聞売り子問題はそうではありません。シーズン前に1回だけ仕入れ量 QQ を決め、需要 DD が実現したら終わり。売れ残りは捨てる(か叩き売る)、不足は機会損失。クリスマスケーキ、夏物アパレル、当日の生鮮、限定グッズ、印刷部数——「作り直しが間に合わない/持ち越せない」モノはすべてこの型です。

決めるべきは QQ ただ一つ。多すぎれば売れ残り(過剰)、少なすぎれば品切れ(過小)。需要 DD は確率変数なので、QQ をどちらに振ってもある確率で損が出ます。期待損失を最小にする QQ を選ぶのが目的です。

flowchart LR
  ORDER["需要が判明する前に<br/>発注量 Q を1回だけ決める"] --> REAL["需要 D が実現"]
  REAL -->|"D が Q 超過(品切れ)"| UNDER["不足 D-Q 個<br/>1個につき逸失利益 Cu=p-c"]
  REAL -->|"D が Q 以下(売れ残り)"| OVER["余り Q-D 個<br/>1個につき過剰損 Co=c-s"]

2つのコストを、売価 pp・仕入原価 cc・残存価値 ss(売れ残り1個の処分価値、p>c>sp>c>s)で定義します。

Cu=pc(過小:1個足りないと粗利 pc を取り逃す),Co=cs(過剰:1個余ると原価 c を s でしか回収できない)C_u=p-c\quad(\text{過小:1個足りないと粗利 }p-c\text{ を取り逃す}),\qquad C_o=c-s\quad(\text{過剰:1個余ると原価 }c\text{ を }s\text{ でしか回収できない})

2. 限界分析から臨界比を導く

最適 QQ限界分析(1個増やす価値)で出します。発注量を QQ から Q+1Q+1 に増やすと、その**(Q+1)(Q+1) 個目**の運命は需要次第です。

ここで F(Q)=Pr(DQ)F(Q)=\Pr(D\le Q) は需要の累積分布関数。(Q+1)(Q+1) 個目を仕入れる期待限界利得

Δ(Q)=Cu(1F(Q))CoF(Q)\Delta(Q)=C_u\bigl(1-F(Q)\bigr)-C_o\,F(Q)

これが非負である限り在庫を1個ずつ増やすべきです。Δ(Q)0\Delta(Q)\ge0 を解くと

Cu(1F(Q))CoF(Q)    Cu(Cu+Co)F(Q)    F(Q)CuCu+CoC_u\bigl(1-F(Q)\bigr)\ge C_o\,F(Q) \;\Longrightarrow\; C_u\ge (C_u+C_o)\,F(Q) \;\Longrightarrow\; F(Q)\le \frac{C_u}{C_u+C_o}

増やす価値がなくなる境目、すなわち F(Q)F(Q^*)臨界比 CR=CuCu+CoCR=\dfrac{C_u}{C_u+C_o} に達したところが最適です。

F(Q)=CuCu+Co=CR\boxed{\,F(Q^*)=\frac{C_u}{C_u+C_o}=CR\,}

コスト関数からの裏取り(同じ式が出る)

限界分析は直感的ですが、期待コストを微分しても同じ臨界比が出ます。完璧な予測(Q=DQ=D ちょうど)からの期待ミスマッチコストは、売れ残り分(CoC_o)と不足分(CuC_u)の和です。

C(Q)=Co0Q(Qx)f(x)dx+CuQ(xQ)f(x)dxC(Q)=C_o\int_0^{Q}(Q-x)f(x)\,dx+C_u\int_{Q}^{\infty}(x-Q)f(x)\,dx

ライプニッツの積分則で QQ について微分すると、被積分関数の QQ 依存だけが残り(境界項は被積分関数が0なので消える)、

C(Q)=Co0Qf(x)dxCuQf(x)dx=CoF(Q)Cu(1F(Q))C'(Q)=C_o\int_0^{Q}f(x)\,dx-C_u\int_{Q}^{\infty}f(x)\,dx=C_o\,F(Q)-C_u\bigl(1-F(Q)\bigr)

C(Q)=0C'(Q^*)=0 とおけば CoF(Q)=Cu(1F(Q))C_o\,F(Q^*)=C_u\bigl(1-F(Q^*)\bigr)、整理して F(Q)=CuCu+CoF(Q^*)=\dfrac{C_u}{C_u+C_o}——限界分析とまったく同じ。さらに2階微分は

C(Q)=(Co+Cu)f(Q)0C''(Q)=(C_o+C_u)\,f(Q)\ge 0

なので、この停留点は最小だと確定します。なお、期待利益の最大化も同じ QQ^* を与えます。完璧予測時の利益 (pc)E[D](p-c)\mathbb{E}[D]QQ に依らない定数で、期待利益 == 定数 C(Q)-\,C(Q) だから、利益最大化はミスマッチコスト最小化と同値だからです(第4節のコードで利益を直接最大化して確かめます)。

3. 正規需要での Q*

需要が正規分布 DN(μ,σ2)D\sim N(\mu,\sigma^2) なら、F(Q)=CRF(Q^*)=CR は標準化して

Φ ⁣(Qμσ)=CR    Qμσ=Φ1(CR)    Q=μ+zσ,z=Φ1(CR)\Phi\!\left(\frac{Q^*-\mu}{\sigma}\right)=CR \;\Longrightarrow\; \frac{Q^*-\mu}{\sigma}=\Phi^{-1}(CR) \;\Longrightarrow\; \boxed{\,Q^*=\mu+z\,\sigma,\quad z=\Phi^{-1}(CR)\,}

形は 安全在庫と発注点ROP=μ+zσROP=\mu+z\sigmaそっくりですが、zz の出どころが違います。03-02 は「守りたいサービス水準 CSL」を人が決めて z=Φ1(CSL)z=\Phi^{-1}(CSL) を引きました。新聞売り子は「コスト比 CRCR」が**zz を決め**、結果として達成されるサービス水準 P(DQ)=CRP(D\le Q^*)=CRコストから内生的に出てくるΦ1\Phi^{-1} そのものの理論は 正規分布(標準正規・標準化) に譲り、ここでは scipy.stats.norm.ppf で値を取ります。

4. Q* が期待利益を最大化することをモンテカルロで実証(コード)

Cu=6,Co=3C_u=6,C_o=3(売価10・原価4・残存1)で CR=2/3CR=2/3。正規需要 N(100,302)N(100,30^2) のもとで Q=μ+zσQ^*=\mu+z\sigma を計算し、続けて**QQ のグリッド上で期待利益を200万サンプルでモンテカルロ**して、最大化点が QQ^* に一致するか・達成サービス水準が CRCR になるかを確かめます。

import numpy as np
from scipy.stats import norm

rng = np.random.default_rng(20260627)

# 単一期の経済条件:売価 p・仕入原価 c・残存価値 s(売れ残りの処分価値, p>c>s)
p = 10.0   # 売価(円/個)
c = 4.0    # 仕入原価(円/個)
s = 1.0    # 残存価値(売れ残り1個の処分価値, 円/個)

# 過小コスト Cu(1個不足の逸失利益)と過剰コスト Co(1個売れ残りの損)
Cu = p - c   # = 6:1個足りないと粗利 p-c を取り逃す
Co = c - s   # = 3:1個余ると原価 c を s でしか回収できない
CR = Cu / (Cu + Co)   # 臨界比 critical ratio
print(f"Cu = p-c = {Cu:.1f},  Co = c-s = {Co:.1f}")
print(f"臨界比 CR = Cu/(Cu+Co) = {CR:.4f}")

# 正規需要 N(mu, sigma) のもとで最適発注量 Q* = mu + z*sigma, z = Phi^{-1}(CR)
mu, sigma = 100.0, 30.0
z = norm.ppf(CR)
Q_star = mu + z * sigma
print(f"需要 ~ N(mu={mu:.0f}, sigma={sigma:.0f})")
print(f"z = norm.ppf(CR) = {z:.4f}")
print(f"最適発注量 Q* = mu + z*sigma = {Q_star:.2f}")
print()

# --- モンテカルロ:Q のグリッドで期待利益を評価し、最大化点が Q* に一致するか ---
N = 2_000_000
D = rng.normal(mu, sigma, size=N)        # 需要サンプル(全 Q で共通=CRN)

def expected_profit(Q):
    sold = np.minimum(D, Q)              # 売れた数
    profit = p * sold + s * np.maximum(Q - D, 0.0) - c * Q
    return profit.mean()

Q_grid = np.arange(80.0, 145.0 + 0.5, 0.5)
profits = np.array([expected_profit(Q) for Q in Q_grid])
Q_best = Q_grid[np.argmax(profits)]
print(f"MC 期待利益を最大化する Q (グリッド探索) = {Q_best:.2f}")
print(f"臨界比から求めた Q*                       = {Q_star:.2f}")
print(f"Q* での期待利益 = {expected_profit(Q_star):.4f} 円/期")
print()

# 達成サービス水準 P(D <= Q*) は臨界比 CR に一致するはず
service = np.mean(D <= Q_star)
print(f"達成サービス水準 P(D<=Q*) 実測 = {service:.4f}(理論 CR = {CR:.4f})")

出力:

Cu = p-c = 6.0,  Co = c-s = 3.0
臨界比 CR = Cu/(Cu+Co) = 0.6667
需要 ~ N(mu=100, sigma=30)
z = norm.ppf(CR) = 0.4307
最適発注量 Q* = mu + z*sigma = 112.92

MC 期待利益を最大化する Q (グリッド探索) = 113.00
臨界比から求めた Q*                       = 112.92
Q* での期待利益 = 501.7746 円/期

達成サービス水準 P(D<=Q*) 実測 = 0.6668(理論 CR = 0.6667)

出力の意味:臨界比 CR=6/9=0.6667CR=6/9=0.6667、その正規分位点 z=Φ1(0.6667)=0.4307z=\Phi^{-1}(0.6667)=0.4307、最適発注量 Q=100+0.4307×30=112.92Q^*=100+0.4307\times30=112.92 個。コードはこの QQ^* を一切使わずQQ を80から145まで動かして期待利益をMCで評価しただけですが、最大化点 113.00 が臨界比から求めた 112.92 とぴたり一致しました(グリッド幅0.5の丸め差だけ)。限界分析・コスト微分・利益最大化の3つが同じ QQ^* を指す、という導出が数値で裏取りできています。最後の行が肝で、達成サービス水準 P(DQ)=0.6668P(D\le Q^*)=0.6668 が臨界比 CR=0.6667CR=0.6667 にそのまま一致——「過剰3:過小6」というコスト比が、67%という品切れ防止水準を勝手に決めているわけです。03-02 で人が「95%にしよう」と決めていたサービス水準が、ここではコストから自動的に出てくる。これが新聞売り子の本質です。

5. 臨界比の含意:粗利が高いほど多めに、腐りやすいほど少なめに(コード)

臨界比 CR=CuCu+CoCR=\dfrac{C_u}{C_u+C_o} は、粗利が高いCuC_u 大)と1に近づき多めに持て腐りやすい/値引きが効かないCoC_o 大)と0に近づき絞れ、と言っています。同じ需要 N(100,302)N(100,30^2) でも商品の経済性で QQ^* がどう動くかを、3つの商品で見ます。

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

# 同じ需要 N(mu, sigma) でも、粗利と廃棄リスクで臨界比=最適発注量が変わる
mu, sigma = 100.0, 30.0

# 3つの商品(売価 p・原価 c・残存価値 s)。Cu=p-c, Co=c-s, CR=Cu/(Cu+Co)
products = [
    ("高粗利・低廃棄(雑誌)", 500.0, 300.0, 250.0),
    ("バランス型",            300.0, 200.0, 100.0),
    ("腐りやすい・薄利(生鮮)", 300.0, 200.0,   0.0),
]
rows = []
for name, p, c, s in products:
    Cu, Co = p - c, c - s
    CR = Cu / (Cu + Co)
    z = norm.ppf(CR)
    Q_star = mu + z * sigma
    rows.append({"商品": name, "Cu": Cu, "Co": Co, "CR": CR,
                 "z": z, "Q*": Q_star, "対平均": Q_star - mu})
df = pd.DataFrame(rows)
print(df.to_string(index=False, float_format=lambda x: f"{x:.3f}"))
print()
print("CR が高い(粗利大・廃棄損小)ほど多めに持ち、低いほど絞る。")
print("達成サービス水準 P(D<=Q*) は CR にそのまま等しい(03-02 はこれを所与にした)。")

# --- 図:臨界比 CR と最適発注量 Q* の関係(CR が上がるほど Q* が増える) ---
CR_grid = np.linspace(0.02, 0.98, 400)
Q_curve = mu + norm.ppf(CR_grid) * sigma

plt.figure(figsize=(10, 5.5))
plt.plot(CR_grid, Q_curve, color="#1f77b4", lw=2,
         label=r"$Q^*=\mu+\Phi^{-1}(CR)\,\sigma$")
plt.axhline(mu, color="gray", ls="--", lw=1, label=f"平均需要 μ={mu:.0f}")
colors = ["#2ca02c", "#ff7f0e", "#d62728"]
offsets = [(12, -34), (12, 12), (-150, -30)]   # ラベルの位置(重なり回避)
for (name, p, c, s), col, off in zip(products, colors, offsets):
    Cu, Co = p - c, c - s
    CR = Cu / (Cu + Co)
    Q_star = mu + norm.ppf(CR) * sigma
    plt.scatter([CR], [Q_star], color=col, zorder=5, s=60)
    plt.annotate(f"{name}\nCR={CR:.2f}, Q*={Q_star:.0f}",
                 (CR, Q_star), textcoords="offset points",
                 xytext=off, fontsize=9, color=col)
plt.axvline(0.5, color="purple", ls=":", lw=1, label="CR=0.5(過小=過剰)")
plt.xlabel("臨界比 CR = Cu/(Cu+Co)  = 達成サービス水準 P(D≦Q*)")
plt.ylabel("最適発注量 Q*")
plt.title("新聞売り子:粗利が高い(CR→1)ほど多めに、腐りやすい(CR→0)ほど少なめに持つ")
plt.legend(loc="upper left"); plt.tight_layout(); plt.show()

出力:

          商品      Cu      Co    CR      z      Q*     対平均
 高粗利・低廃棄(雑誌) 200.000  50.000 0.800  0.842 125.249  25.249
       バランス型 100.000 100.000 0.500  0.000 100.000   0.000
腐りやすい・薄利(生鮮) 100.000 200.000 0.333 -0.431  87.078 -12.922

CR が高い(粗利大・廃棄損小)ほど多めに持ち、低いほど絞る。
達成サービス水準 P(D<=Q*) は CR にそのまま等しい(03-02 はこれを所与にした)。

出力の意味:同じ需要分布でも、最適発注量が商品で大きく変わります。粗利が大きく売れ残りロスが小さい雑誌(Cu=200,Co=50,CR=0.80C_u=200,C_o=50,CR=0.80)は Q=125Q^*=125平均より25個多く持つ。過小と過剰が拮抗するバランス型(CR=0.50CR=0.50)は z=0z=0 ぴったりで Q=μ=100Q^*=\mu=100中央値発注)。腐りやすく薄利の生鮮(Cu=100,Co=200,CR=0.33C_u=100,C_o=200,CR=0.33)は z<0z<0Q=87Q^*=87平均より13個少なく絞る。図は CRCR を横軸に取った Q=μ+Φ1(CR)σQ^*=\mu+\Phi^{-1}(CR)\sigma の曲線で、3商品がその上に乗っています。CR1CR\to1(粗利が圧倒的)で青曲線が跳ね上がり、CR0CR\to0(捨てるしかない)で沈む——「足りない痛み CuC_u と余る痛み CoC_o のどちらが大きいか」で発注量が決まる。横軸が「達成サービス水準 P(DQ)P(D\le Q^*)」でもあることに注意:03-02 で人が選んでいたサービス水準が、ここではコスト比そのものだという対応関係が、1本の曲線に集約されています。

⚠️ よくある誤解

関連ノート