🎓 レベル:基礎 | 重要度:A(必須)
📎 前提:プロセス分析とリトルの法則(平均在庫 Q/2 とリトルの法則) | 次:安全在庫と発注点(不確実性への備え) | 発展:第9章 確率的在庫モデル
要点(BLUF)
- 在庫には性格の違う2つのコストがあります。発注コスト(発注1回ごとに 。段取り・事務・輸送など、量によらず1回いくら)と、保管コスト(在庫を抱えるほど膨らむ。年・1個あたり )。前者はまとめ買いを、後者はこまめ買いを促す——真逆です。
- まとめて頼めば発注回数は減るが在庫が膨れる。こまめに頼めば在庫は薄いが発注回数がかさむ。この綱引きのちょうど良い1回の発注量が経済的発注量 EOQです。
- 年間総コスト を微分してゼロと置くと、閉形式 。最適点では発注コスト=保管コスト、最小総コストは です。
- EOQ は 「平ら」 です。発注量が最適から多少ずれても総コストはほとんど増えません( で 未満)。だから EOQ は桁を合わせる道具——小数点以下の精密さより、 の桁を外さないことが大事です。
1. 在庫の2大コストと「のこぎり波」
EOQ が答える問いはシンプルです——1回にいくつ発注すれば、年間の在庫関連コストが最小になるか。登場するコストは2つだけです。
- 発注コスト (円/回):発注1回ごとに固定でかかる費用。発注事務・段取り替え・受け入れ検品・輸送の固定費など。量に関係なく1回いくらなので、1回の発注量 を増やして発注回数を減らすほど年間では安くなります。年間需要 を ずつ発注するなら、年間発注回数は 、年間発注コストは 。
- 保管コスト (円/個/年):在庫を1個1年持つ費用。倉庫・保険・劣化・陳腐化、そして資本コスト(在庫に寝かせたお金の機会費用)。 はしばしば「単価 × 在庫保管率(年20〜30%など)」で見積もります(保管率は業種・金利で動く=要最新確認)。
保管コストには平均在庫が要ります。EOQ の標準前提は需要が一定・補充は瞬時(リードタイム0で発注したら即届く)。すると在庫は から一定の傾きで減って0になり、0になった瞬間に だけ補充される——のこぎり波を描きます。各サイクルで在庫は と0の間を直線で往復するので、時間平均の在庫は 。年間保管コストは です。
リトルの法則との接続:平均在庫 は プロセス分析とリトルの法則 の そのものです。需要(フローレート)、1個が倉庫に滞在する平均時間 と見れば 。在庫はフローの言葉で読み解けます。
flowchart LR Q["1回の発注量 Q を増やす"] --> A["発注回数 D/Q が減る<br/>発注コスト D/Q·S は減少"] Q --> B["平均在庫 Q/2 が増える<br/>保管コスト Q/2·H は増加"] A --> TC["総コスト TC(Q)"] B --> TC TC --> OPT["最小にする Q* = EOQ"]
2. EOQ の導出:総コストを微分する
年間総コストは2つの和です。
第1項は に対して減少()、第2項は増加( に比例)。和は下に凸の谷型になり、谷底が最適です。 で微分してゼロと置きます。
2階微分は なので、確かに最小です( は で凸)。
最適点では発注コストと保管コストが等しい——これは偶然ではありません。 は 、両辺に を掛けると 、すなわち発注コスト=保管コスト。 を代入すると、どちらも になり、最小総コストは
ついでに、最適な年間発注回数 、発注サイクル(1回の発注がもつ日数) 日も決まります。
3. EOQ を計算して数値最適と突き合わせる(コード)
具体的な数値で確かめます。年間需要 個、発注1回 円、保管 円/個/年。発注コスト・保管コスト・総コストの3曲線を描き、閉形式 と scipy.optimize.minimize_scalar の数値解が一致すること、最適点で発注コスト=保管コストになることを確認します。
import numpy as np
from scipy.optimize import minimize_scalar
import matplotlib.pyplot as plt
import japanize_matplotlib
# パラメータ:年間需要 D、1回あたり発注コスト S、年間・1個あたり保管コスト H
D = 12000.0 # 年間需要(個/年)
S = 80.0 # 1回あたり発注コスト(円/回)
H = 6.0 # 年間・1個あたり保管コスト(円/個/年)
# 総コスト TC(Q) = 発注コスト D/Q*S + 保管コスト Q/2*H
def order_cost(Q): return D / Q * S
def hold_cost(Q): return Q / 2.0 * H
def total_cost(Q): return order_cost(Q) + hold_cost(Q)
# 閉形式の最適発注量 Q* = sqrt(2DS/H)、最小総コスト TC* = sqrt(2DSH)
Q_star = np.sqrt(2 * D * S / H)
TC_star = np.sqrt(2 * D * S * H)
# 数値最適化で裏取り(scipy.optimize.minimize_scalar)
res = minimize_scalar(total_cost, bounds=(1, 5000), method="bounded")
Q_num = res.x
print(f"閉形式 Q* = sqrt(2DS/H) = {Q_star:.4f} 個")
print(f"数値解 Q* (minimize_scalar) = {Q_num:.4f} 個")
print(f"一致差 |閉形式 - 数値| = {abs(Q_star - Q_num):.2e}")
print(f"最小総コスト TC* = sqrt(2DSH) = {TC_star:.4f} 円/年")
print(f" 数値解での TC(Q*) = {total_cost(Q_num):.4f} 円/年")
print()
print(f"最適点での発注コスト D/Q*·S = {order_cost(Q_star):.4f} 円/年")
print(f"最適点での保管コスト Q*/2·H = {hold_cost(Q_star):.4f} 円/年 (発注=保管で一致)")
print(f"年間発注回数 D/Q* = {D / Q_star:.4f} 回/年")
print(f"発注サイクル Q*/D = {Q_star / D * 365:.2f} 日")
# 図:発注コスト・保管コスト・総コストの3曲線(交点が最適)
Q = np.linspace(50, 2000, 400)
plt.figure(figsize=(9, 5.5))
plt.plot(Q, order_cost(Q), label="発注コスト D/Q·S", color="#1f77b4")
plt.plot(Q, hold_cost(Q), label="保管コスト Q/2·H", color="#2ca02c")
plt.plot(Q, total_cost(Q), label="総コスト TC(Q)", color="#d62728", lw=2.5)
plt.axvline(Q_star, ls="--", color="gray")
plt.plot(Q_star, TC_star, "*", color="black", ms=18, label=f"最適 Q*={Q_star:.0f}")
plt.xlabel("発注量 Q(個)"); plt.ylabel("年間コスト(円/年)")
plt.title("EOQ:発注コストと保管コストのトレードオフ(交点が最適)")
plt.legend(); plt.ylim(0, 5000); plt.tight_layout(); plt.show()
出力:
閉形式 Q* = sqrt(2DS/H) = 565.6854 個
数値解 Q* (minimize_scalar) = 565.6854 個
一致差 |閉形式 - 数値| = 1.01e-05
最小総コスト TC* = sqrt(2DSH) = 3394.1125 円/年
数値解での TC(Q*) = 3394.1125 円/年
最適点での発注コスト D/Q*·S = 1697.0563 円/年
最適点での保管コスト Q*/2·H = 1697.0563 円/年 (発注=保管で一致)
年間発注回数 D/Q* = 21.2132 回/年
発注サイクル Q*/D = 17.21 日
出力の意味:閉形式 個と、数値最適化が探し当てた値が小数第4位まで一致(差は オーダー=最適化の収束許容誤差)。 では発注コストも保管コストもともに 1697.06 円/年でぴたりと等しく、合計が最小総コスト 円/年になります。図でも、青い発注コスト(右下がり)と緑の保管コスト(右上がり)が交わる真上で、赤い総コスト曲線が底(★)を打っています。 ずつ年21.2回、約17日に1回発注すればよい、と運用に落ちます。「2つのコストが釣り合う点が最適」——これが EOQ の幾何学的な正体です。
4. EOQ は「平ら」:頑健性を導く(コード)
EOQ の隠れた美点は頑健さです。 を から少々ずらしても、総コストはほとんど増えません。これは式で綺麗に言えます。 とおくと、
(途中:、、 を代入。) が式から消えて、コスト比は比率 だけの関数になります。 のまわりで と展開すると ——コスト増は相対誤差の2乗。だから小さなずれはほぼ無害(1次の項が消える=谷が平ら)です。数値で確かめます。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import japanize_matplotlib
D, S, H = 12000.0, 80.0, 6.0
Q_star = np.sqrt(2 * D * S / H)
TC_star = np.sqrt(2 * D * S * H)
def total_cost(Q):
return D / Q * S + Q / 2.0 * H
# Q を Q* の r 倍にずらしたときの総コスト比 TC(Q)/TC*
# 理論:TC(Q)/TC* = (1/2)(r + 1/r) (r = Q/Q*)
ratios = np.array([0.5, 0.7, 0.8, 0.9, 1.0, 1.1, 1.2, 1.5, 2.0])
rows = []
for r in ratios:
Q = r * Q_star
direct = total_cost(Q) / TC_star # 直接計算した比
formula = 0.5 * (r + 1.0 / r) # 公式 (1/2)(r+1/r)
rows.append({"Q/Q*": r, "TC/TC* 直接": direct,
"TC/TC* 公式": formula, "コスト増(%)": (direct - 1) * 100})
tbl = pd.DataFrame(rows)
print(tbl.to_string(index=False, float_format=lambda x: f"{x:.4f}"))
print()
print(f"+50%発注 (r=1.5): コスト増 {(0.5*(1.5+1/1.5)-1)*100:.2f}%")
print(f"-50%発注 (r=0.5): コスト増 {(0.5*(0.5+1/0.5)-1)*100:.2f}%")
print(f"+-20%発注 : コスト増 {(0.5*(1.2+1/1.2)-1)*100:.2f}% / {(0.5*(0.8+1/0.8)-1)*100:.2f}%")
# 図:正規化コスト曲線 (1/2)(r+1/r) と 2次近似 1+(r-1)^2/2
rr = np.linspace(0.4, 2.2, 300)
plt.figure(figsize=(9, 5.5))
plt.plot(rr, 0.5 * (rr + 1 / rr), color="#d62728", lw=2.5,
label="正規化総コスト (1/2)(r+1/r)")
plt.plot(rr, 1 + (rr - 1) ** 2 / 2, ls="--", color="#1f77b4",
label="2次近似 1+(r-1)²/2")
plt.axhline(1.0, color="gray", lw=0.8)
plt.axvline(1.0, color="gray", lw=0.8)
plt.fill_between(rr, 1.0, 1.03, where=(0.5 * (rr + 1 / rr) <= 1.03),
color="green", alpha=0.15, label="コスト増 3%以内の帯")
plt.xlabel("発注量比 r = Q/Q*"); plt.ylabel("総コスト比 TC(Q)/TC*")
plt.title("EOQは「平ら」:±20%ずれても総コストは数%しか増えない")
plt.legend(); plt.ylim(0.98, 1.30); plt.tight_layout(); plt.show()
出力:
Q/Q* TC/TC* 直接 TC/TC* 公式 コスト増(%)
0.5000 1.2500 1.2500 25.0000
0.7000 1.0643 1.0643 6.4286
0.8000 1.0250 1.0250 2.5000
0.9000 1.0056 1.0056 0.5556
1.0000 1.0000 1.0000 0.0000
1.1000 1.0045 1.0045 0.4545
1.2000 1.0167 1.0167 1.6667
1.5000 1.0833 1.0833 8.3333
2.0000 1.2500 1.2500 25.0000
+50%発注 (r=1.5): コスト増 8.33%
-50%発注 (r=0.5): コスト増 25.00%
+-20%発注 : コスト増 1.67% / 2.50%
出力の意味:「直接計算したコスト比」と「公式 」は全行ぴたり一致——導出が正しいことの確認です。注目は谷の平らさで、 ずらしても総コスト増はわずか 1.67%(+20%)/2.50%(−20%)。発注量を1〜2割読み違えても、コストはほとんど変わりません。さらに +50% も+8.3% にとどまります。ただし完全な対称ではありません—— と が同じコストになる(過剰発注 と過少発注 が同コスト)ため、−50%()は+25% と過少側のほうが急。 で発注回数が爆発するからです。図では赤い実線(厳密)と青い破線(2次近似 )が谷の近くでほぼ重なり、緑の帯(コスト増3%以内)が と幅広いことが見えます。結論:EOQ は桁を合わせる道具。 の見積りが多少甘くても、 がならして への影響を 倍に薄め、さらにコストへの影響は2乗で効くので、実務では端数を丸めて発注ロットに合わせて構いません。
⚠️ よくある誤解
- 「保管コスト は固定値」ではない: は単価 × 在庫保管率で見積もることが多く、資本コスト(金利)を含みます。金利や陳腐化リスクが上がれば も上がり、 は縮みます(こまめ買いが有利に)。 の中身を意識せず教科書の一定値を使うと、最適発注量を取り違えます。
- 「EOQ はいつでも使える」ではない:EOQ は需要一定・補充瞬時(リードタイム0)・在庫切れなしという強い前提の上の式です。需要やリードタイムがばらつく現実では、別途安全在庫で裾を守る必要があります → 安全在庫と発注点。EOQ は「平均的にいくつずつ」を、安全在庫は「ばらつきにいくら備えるか」を決め、役割が違います。
- 「数量割引があっても EOQ をそのまま使う」ではない:1回の発注量を増やすと単価が下がる数量割引があると、総コストに購入額が加わって段差ができ、単純な が最適とは限りません。割引境界の発注量ごとに総コストを比べる別の手続きが要ります(要最新確認:割引体系は取引条件しだい)。
- 「EOQ は小数点まで正確に守るべき」ではない:第4節の通り EOQ は平らで、 のずれでもコスト増は数%。ケース入数・パレット・最小発注単位に合わせて丸めるのが実務です。精密さより、 の桁を外さないこと。
- 「在庫切れ時の単一期の最適発注量も EOQ」ではない:腐る商品・売り切りの1回限りの仕入れ(新聞・季節品)は、発注コストと保管コストの綱引きではなく、**品切れ損失と売れ残り損失の綱引き(限界比)**で解きます。これは別物で、**第9章 確率的在庫モデル(新聞売り子問題)**で扱います。
関連ノート
- 安全在庫と発注点(次のトピック・需要とリードタイムの不確実性に EOQ の上から安全在庫を積む)
- 定量発注と定期発注(EOQ を「いつ・いくつ」発注するかの方式に組み込む)
- プロセス分析とリトルの法則(平均在庫 と の対応)
- 第9章 確率的在庫モデル(単一期=新聞売り子問題・限界比。EOQ とは綱引きの相手が違う)
- オペレーションズ・マネジメント 全体目次