【SCM分析1】曜日/物流センター別の出荷傾向をグラフで可視化する

2023年10月16日

サプライチェーンに関わるデータは膨大で、それを有効活用することで物流を効率化できます。

中でも物流センターからの出荷データは基本で、そこから得られる洞察は数多いでしょう。

このような分析はExcelでもできますが、出荷データは出荷伝票別/SKU別になっていることが多いためデータ量が膨大で、処理能力が足りなかったり、分析に長い時間がかかったりします。

そこで、Pythonを使うとどれくらい簡単にできるかを試してみました。

今回は物流センターが複数ある会社において、曜日別/物流センター別の出荷傾向をPythonを使って分析してみます。

 

出荷データをPythonに読み込む

出荷データの項目は以下の通りで、期間は6ヶ月分、約59万行あります(ファイル名:raw_data6.csv)。

Date:出荷日

CustomerCode:得意先(出荷先)コード

DCCode:物流センターコード

Zone:出荷先エリア

Truck:配送トラックタイプ

SKU:出荷SKU(アイテム)コード

Qty:出荷数量

UnitM3:出荷SKUの単位当たり容積(M3)

M3:伝票当たりの出荷SKU容積(M3)

UnitKG:出荷SKUの単位当たり重量(Kg)

KG:伝票当たりの出荷SKU重量(Kg)

 

Pandasを使ってデータフレーム’raw_df’として読み込みます。

import numpy as np
import pandas as pd

raw_df = pd.read_csv('raw_data6.csv')
raw_df['Date'] = pd.to_datetime(raw_df['Date']) # 日付データを日付型に変換

SKU数を調べてみると、全部で559個あることがわかります。

raw_df['SKU'].nunique()
---
559

 

物流センター別の出荷量を集計する

同じように物流センター数を調べてみると4箇所あることがわかります。

raw_df['DCCode'].nunique()
---
4

次に物流センターごとに出荷数がどれくらい違うかを調べてみましょう。

Pandasのgroupbyを使います。

raw_df.groupby(['DCCode'])['Qty'].sum()
---
DCCode
DC01    5.500955e+06
DC02    3.253347e+06
DC03    9.190331e+06
DC04    2.621256e+06
Name: Qty, dtype: float64

DC03からの出荷数が一番多く、DC04が一番少ないことがわかります。

今後のデータ操作をやり易くするために、データフレーム型で結果を出力しておきます。

Pandasのgroupbyに加えてaggも使います。

Qty_by_DC = raw_df.groupby(['DCCode']).agg(Qty_by_DC = ('Qty', np.sum))
Qty_by_DC

ただ、これだとDCCodeがインデックスになっているので、これをリセットしておきます。

Qty_by_DC = raw_df.groupby(['DCCode']).agg(Qty_by_DC = ('Qty', np.sum)).reset_index()
Qty_by_DC

最後に、Seabornを使って棒グラフで可視化します。

sns.barplot(x = 'DCCode', y = 'Qty_by_DC', data = Qty_by_DC)

 

 

SKU別/日別の出荷量を集計する

次に、SKUごとの毎日の出荷数がどのように推移しているかを見てみましょう。

毎日、同じSKUがいろいろな出荷先に出荷されているため、それらを集計する必要があります。

Excelだとピボットテーブルを使うところですが、Pythonだと先程のgroupbyとaggを使うことで簡単に集計できます。

qty_by_sku_date = raw_df.groupby(['SKU', 'Date']).agg(Qty_by_SKU = ('Qty', np.sum)).reset_index()
qty_by_sku_date

各SKUごとに日別の出荷数量が集計されていますが、これだとわかりにくいのでSKUを横軸に取った表にしてみましょう。

Pandasのpivot_tableを使うことで、次のように見せ方を変えることができます。

pivot_by_sku_date = pd.pivot_table(qty_by_sku_date, values = 'Qty_by_SKU', columns = 'SKU', index = 'Date', fill_value = 0)
pivot_by_sku_date

縦軸は6ヶ月の日数である182行、横軸はSKU数である559列の表で表すことができました。

この結果を559個のSKU別にグラフで表すことも簡単にできます。

ここでは360番目から3つのSKUについてだけ抜き出してグラフにしまみましょう。

pivot_by_sku_date.iloc[:, 350:353].plot(subplots = True, figsize = (20, 10))

 

 

SKU別/曜日別の出荷量を集計する

出荷数量は曜日によって大きく違うことが予想されます。

そこで、日付から曜日を求めて、曜日別に出荷数量を集計してみましょう。

Pandasのdt.day_nameにより日付を曜日に変換できるので、データフレームに新しく’DOW’という列を作り、曜日を入れてみます。

raw_df['DOW'] = raw_df['Date'].dt.day_name()
raw_df['DOW']

新しく曜日名が入った列ができました。

そこで先ほどと同じようにSKU別/曜日別に出荷数量を集計すると次のようになります。

qty_by_sku_dow = raw_df.groupby(['SKU', 'DOW']).agg(Qty_by_DOW = ('Qty', np.sum)).reset_index()
qty_by_sku_dow

これを曜日別の棒グラフにすると次のようになります。

sns.barplot(x = 'DOW', y = 'Qty_by_DOW', data = qty_by_sku_dow)

このようにSeabornで棒グラフを描くと、エラーバーと呼ばれる信頼区間も合わせて表示させることができます。

デフォルトでは95%信頼区間になります。

例えば金曜日を例にとると、1日の平均出荷数量は1万個弱だけれども実際はばらついていて、約6千個から1万5千個までの間に95%が収まるということです。

 

しかしよくグラフを見てみると、曜日の並び順がバラバラになっていて美しくありません。

これを順番に並ぶようにしてみましょう。

やり方はいくつかあると思いますが、ここでは曜日番号順に並べ変えてみます。

先程はPandasのdt.day_nameにより日付を曜日名に変換しましたが、dt.dayofweekを使うと月曜日を0、火曜日を1というように曜日番号に変換できます。

このように日付を曜日番号に変換した後に、曜日番号と曜日名を結びつける辞書をかませることによって、曜日番号順に表示することができます。

raw_df['DOW_NUM'] = raw_df['Date'].dt.dayofweek
qty_by_sku_downum = raw_df.groupby(['DOW_NUM', 'SKU']).agg(Qty_by_DOW = ('Qty', np.sum)).reset_index()
week_dic = {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday', 4: 'Friday', 5: 'Saturday', 6: 'Sunday'}
qty_by_sku_downum['DOW_NUM'] = qty_by_sku_downum['DOW_NUM'].map(week_dic)
sns.barplot(x = 'DOW_NUM', y = 'Qty_by_DOW', data = qty_by_sku_downum)

 

 

曜日別/物流センター別の出荷量を集計する

最後に今までの合せ技を使って、物流センター別/曜日別に出荷数量を集計して、それをグラフで可視化してみましょう。

raw_df['DOW_NUM'] = raw_df['Date'].dt.dayofweek
qty_by_sku_downum = raw_df.groupby(['DOW_NUM', 'DCCode']).agg(Qty_by_DOW = ('Qty', np.sum)).reset_index()
week_dic = {0: 'Monday', 1: 'Tuesday', 2: 'Wednesday', 3: 'Thursday', 4: 'Friday', 5: 'Saturday', 6: 'Sunday'}
qty_by_sku_downum['DOW_NUM'] = qty_by_sku_downum['DOW_NUM'].map(week_dic)
sns.barplot(x = 'DCCode', y = 'Qty_by_DOW', data = qty_by_sku_downum, hue = 'DOW_NUM')