【定量発注方式】PythonのInventorizeライブラリで在庫シミュレーションしてみた

2024年5月18日

Photo by Alexander Grey on Unsplash

◆仕事や勉強の息抜きに。。。

定量発注方式とは?

定量発注方式とは毎日常に在庫数をチェックして、ある数量(A)より少なくなったら予め決めておいた数量(B)を発注するやり方です。

発注する数量Bは常に一定であるため定量発注方式と呼ばれます。

 

前回の記事【定期発注方式】PythonのInventorizeライブラリで適正在庫シミュレーションしてみたで紹介した定期発注方式発注間隔が一定でしたが、定量発注方式発注量が一定です。

ここで重要になるのはABをどう決めるかです。

 

A発注点と言います。

これは通常

発注点=リードタイム期間中の需要予測数+安全在庫

で計算します。

【発注点の求め方】日/アイテム/在庫拠点別の出荷データからPythonで求める方法を実演

 

B発注量です。

これは通常EOQ(経済的発注量)を使います。

EOQ=√((2×輸送コスト×年間需要量)/保管コスト)

EOQ(経済的発注量)を発注実務に応用する方法~海上輸入による調達を例に~

 

具体例で発注点と発注量を計算する

それでは【定期発注方式】PythonのInventorizeライブラリで適正在庫シミュレーションしてみたで使ったのと同じ需要実績データから発注点と発注量を計算してみましょう。

あるアイテムについて次のような需要実績データがあるものとします。

この時、最初の45日間を見たデータとして需要の平均と標準偏差を求め、残りの75日間をまだ見ぬデータとしてシミュレーションするものとします。

最初の45日間のデータから発注点と発注量を次のように計算することができます。

 

許容欠品率から安全係数を求める方法については、下記の記事を参照下さい。

安全在庫の計算に必要な安全係数の二通りの求め方をわかりやすく解説!

 

発注点と発注量をPythonで求める

実は、この発注点と発注量はPythoninventorizeライブラリを使えば一瞬で求まります。

発注点はreorderpoint関数を使い、引数としては平均、標準偏差、リードタイム、サービス率(=1-許容欠品率)を指定します。 

import inventorize as inv
inv.reorderpoint(
  dailydemand = 1103.133,
  dailystandarddeviation = 685.2797,
  leadtimein_days = 3,
  csl = 0.95
)

発注点(reorder point)がExcelで計算したのと同じ5,262になっていますね。

 

次に発注量、つまりEOQは同じくinventorizeライブラリのeoq関数で求められます。 

引数は年間需要量、発注コスト(輸送コスト)、商品単価、保管コスト率を指定します。

ここで保管コスト率とは、商品単価に対する保管コストの比率です。

だから商品単価も指定するのですね。

先のExcelでは保管コストを900円/個・年としましたが、ここでは例として商品単価を1,500円としましょう。

すると保管コスト率は900÷1,5000.6となります。

inv.eoq(annualdemand = 402643.6667,
        orderingcost = 50000,
        purchasecost = 1500,
        holdingrate = 0.6
       )

EOQExcelで計算したのと同じ6,689個になりましたね。

T_yearsT-weeksは発注回数です。

年間需要は402,644個で1度に6,689個を発注すれば、1年に6,689÷402,6440.0166回、1週間にはその52倍の0.864回発注されるという意味です。

 

日々の在庫推移をシミュレーションする

次に上で求めた発注点と発注量に基づいて定量発注を行った場合に、日々の在庫推移がどのようになるかを在庫シミュレーションしてみましょう。

これもinventorizeライブラリで一瞬にしてできてしまいます。

 

使う関数はsim_min_Q_normalです。

指定する引数は需要データ、平均、標準偏差、リードタイム、サービス率(=1-許容欠品率)、発注量です。

需要データはまだ見ぬ75日間の需要データをリストとして渡します。

その需要データがtest_df['Item_A’]に入っていて、平均、標準偏差、リードタイム、サービス率、発注量がそれぞれavsdld1-soeoqに入っているとすると、次のように一瞬にして在庫シミュレーションができてしまいます。

inv.sim_min_Q_normal(
  test_df['Item_A'],
  av,
  sd,
  ld,
  1-so,
  eoq
)

前半に在庫シミュレーションの結果が、後半にその統計値がまとめられています。

前半の在庫シミュレーション結果をcsvファイルにexportして中身を見てみましょう。

result[0].to_csv('result_minQ_1.csv')

 

 

1日目の初期在庫は発注点である5,262個から始まっています。

従って、発注点をヒットしているので決められた発注量(=EOQ)である6,689個を発注します。

但し、発注自体は次の日である2日目に行います。

このように発注量を決めた次の日に発注を行うという挙動は、定期発注をシミュレーションするPeriodic_review_normal関数と同様です。

 

次の留意点はinventory_levelinventory_positionの違いです。

前者は納品されて手元にある在庫数で、後者はそれに発注済であるが未納品の数量(パイプライン在庫)を加えた数量です。

そして発注点は後者の在庫数を参照することに注意です。

 

もう一つの留意点はdemandsalesの違いです。

需要実績として読み込んだデータがdemand、そしてそれを手元にある在庫で充足できた数量がsalesになります。

上の表にある18日間はすべて需要を充足できている(つまり在庫切れがない)ため、demand=salesになっています。

すると最後の列であるlost_orderはゼロになります。

もし充足できなかった場合には、その差がlost_orderに正の数で書き込まれます。

 

実際にこのシミュレーションでは42日目に在庫切れが起こっています。

 

42日目には最初1,398個の在庫がありました。

ところが実際には2,073個の需要があったため675個が欠品になったことがわかります。

 

最後に75日間の在庫推移をグラフにしてみましょう。

x = test_data[:, 0]
plt.figure(figsize=(10, 6))
plt.plot(x, result[0]['inventory_level'][1:])
plt.ylabel("inventory")
plt.show