【発注点の求め方】日/アイテム/在庫拠点別の出荷データからPythonで求める方法を実演
発注点は英語ではReorder PointまたはROPと呼ばれ、適正在庫管理における大変重要な設定点です。
在庫が「ある数量」を下回ったら発注するという不定期発注方式では、「ある数量」のことです。
また1週間とか1ヶ月おきに発注するという定期発注方式においては、目標在庫量のことです。
目標在庫量と現在庫量との差を発注することになります。
本記事では発注点の計算方法を紹介し、現場で収集可能なデータから実際にどのように計算しているのかを実演します。
現場で収集可能なデータとしては、日/アイテム/在庫拠点別の出荷データを使います。
発注点の計算方法
よく知られている発注点の計算式は次の通りです。
発注点=1日当たりの平均出荷量×リードタイム日数+安全在庫
この計算式には色々なバージョンがあります。
まず考慮すべきは出荷データの集計間隔です。
出荷数を1日単位で集計する場合には先の式になりますが、1週間単位で集計する場合には次のようになります。
発注点=1週間当たりの平均出荷量×リードタイム週数+安全在庫
出荷数を1日単位で集計するというのは、データが次のようになっていることです。
アイテム単位で1日ごとの出荷数が集計されていますね。
これが1週間単位で集計されている場合には
発注点=1週間当たりの平均出荷量×リードタイム週数+安全在庫
の式になります。
次に需要予測を行っているケースです。
その場合は次のような式になります。
発注点=リードタイム期間中の需要予測数+安全在庫
最初の式に出てきた1日当たりの平均出荷量×リードタイム日数はリードタイム期間中の出荷数の予測値です。
「1日に平均してこれくらい出荷されているから、納品されるまでのリードタイム期間中にはこれくらいの出荷数があるだろう」
という予測値なわけです。
これはもっと精緻な需要予測を別に行っていれば、その予測値を使うのが自然ですね。
最後に出てくる安全在庫は
安全在庫=√(リードタイム)×需要の標準偏差×安全係数
で計算できますが、詳細についてはこちらを参照下さい。
【安全在庫の計算式】標準偏差と安全係数から計算する式をわかりやすく
まとめると、発注点は汎用的に
発注点=リードタイム期間中の需要予測数+安全在庫
で計算できます。
英語では
ROP(Reordrer Point)=Lead Time Demand+Safety Stock
というように表現します。
定期発注方式に適用する場合の注意点
発注点とは発注を起こすトリガー在庫数のことですが、それは不定期発注方式の場合に限ります。
定期発注方式の場合は1週間おきのように一定のインターバルで発注するので、発注を起こすトリガー在庫数はありません。
では定期発注方式では発注点の計算式は関係ないかというと、そうでもありません。
在庫補充の目標数量として使えます。
定期的に在庫数をチェックして、その時点での在庫数と目標数量との差を発注するようにするのです。
但し、そのように使う場合は1つだけ注意点があります。
リードタイムに発注間隔を加えることです。
リードタイムとは発注してから納品されるまでの期間ですが、これに発注間隔を加えます。
1週間ごとの定期発注であれば、リードタイムに7を加えます。
そうすることにより、発注点の計算式は定期発注方式における在庫補充の目標数量の計算式として使えます。
詳細が気になる方はこちらを参照してみて下さい。
【無料サンプル付き】適正在庫シミュレーションをエクセルで作る方法
Pythonで発注点を一括計算する方法
発注点の計算式がわかったところで、実務に応用してみましょう。
発注点の式においてLead Time Demandは平均出荷数が、Safety Stockは1日当たりの標準偏差がわかれば計算できますが、現場で収集されるデータから直接計算することは難しいケースが少なくありません。
現場から収集される生データを前処理する必要があるわけです。
そこで典型的な生データの前処理のやり方から紹介します。
データを前処理する
ある日用品メーカーは4ヶ所の物流センター(DC)から全国の卸や小売に毎日配送しています。
アイテム数は117あり、それぞれのSKUごとに何月何日にどのDCからどの市に何箱出荷したかをWMSからcsvファイルでダウンロードできるようになっています。
下記データは2018年9月の出荷実績です。
このデータからDC_Aに在庫しているアイテム1100100の発注点をPythonで計算してみましょう。
まずPandasのデータフレームにcsvファイルを読み込みます。
raw_df = pd.read_csv('SEP2018.csv')
raw_df
今回はDC_Aの在庫について調べたいので、DC_Aのデータだけを抽出します。
DC_A_df = raw_df[raw_df['ShipFrom'] == 'DC_A']
DC_A_df
次に日別アイテム別に出荷数を集計します。
Excelだとピボットテーブルを使って集計するイメージです。
A_daily_df = DC_A_df.groupby(['DeliveryDate', 'ProductCode']).agg(daily_box = ('TotalBox', np.sum)).reset_index()
A_daily_df
次にアイテムごとの平均と標準偏差を求めます。
この時、アイテムによっては出荷頻度が極端に少なく月に1度しか出荷がない場合には標準偏差が計算できず計算結果がNaNと表示されてしまうので、その場合は平均値を標準偏差とすることにします。
A_grouped_df = A_daily_df.groupby(['ProductCode']).agg(average = ('daily_box', np.mean), std = ('daily_box', np.std)).reset_index()
A_grouped_df = A_grouped_df.fillna(method='ffill', axis=1)
A_grouped_df
これでアイテムごとに出荷数の平均値と標準偏差が求まったので、公式に当てはめて発注点が計算できます。
ライブラリーで発注点を1行で計算する
それでは1行目にあるアイテム1100100の発注点を求めてみましょう。
リードタイムと許容欠品率(サービス率)が必要なので、リードタイムは12日、許容欠品率は5%として話を進めます。
公式に当てはめると次のように計算できます。
許容欠品率5%の安全係数が1.645になることを使っています。
許容欠品率から安全係数の求め方についてはこちらの記事を参照してみて下さい。
安全在庫の計算に必要な安全係数の二通りの求め方をわかりやすく解説!
発注点=1341×12+√(12)×718×1.645=20182
この計算はPythonのinventorizeライブラリーのreorderpoint関数を使えば、安全係数の算出も含めて1行で済んでしまいます。
import inventorize as inv
inv.reorderpoint(int(A_grouped_df.iloc[0,1]), A_grouped_df.iloc[0,2], 12, 0.95)
このように結果は辞書形式で出力されますが、発注点は手計算と同じ20182となりました。
更にreorderpoint_leadtime_variability関数を使えば、リードタイムが変動する場合の発注点を求めることもできます。
リードタイムが変動する場合の安全在庫の求め方については
需要だけでなくリードタイムも変動する場合の安全在庫はどうやって計算するの?
をご覧下さい。
ここではリードタイムが12日から標準偏差3日で変動する場合の発注点を求めてみましょう。
inv.reorderpoint_leadtime_variability(int(A_grouped_df.iloc[0,1]), A_grouped_df.iloc[0,2], 12, 3, 0.95)
発注点は1508多い21689になりました。
全アイテムの発注点を一括計算する
このようにinventorizeライブラリーを使えば発注点の計算は1行で済んでしまいます。
これを使って全117アイテムの発注点をすべて求めてしまいましょう。
リードタイムが標準偏差3日で変動する場合には次のようになります。
pc_list = []
rp_list = []
for i in range(A_grouped_df['std'].shape[0]):
dict = inv.reorderpoint_leadtime_variability(int(A_grouped_df.iloc[i,1]), A_grouped_df.iloc[i,2], 12, 3, 0.95)
rp = [v for k, v in dict.items() if k == 'reorder_point'][0]
pc = A_grouped_df.iloc[i,0]
pc_list.append(pc)
rp_list.append(rp)
result2 = pd.DataFrame({'ProductCode' : pc_list, 'ReorderPoint2' : rp_list})
result2