
目录
背景
第一部分 - 数据整理
1. 数据录入
2. 清洗数据
3. 数据调整、聚合
第二部分 - 探索库存数据(EDA)
1. 了解库存整体情况
2. 可视化
背景
对于零售行业来讲,无论是跨境电商还是传统零售商,库存的高低决定企业流动资产的高低,库存的流动速度决定企业现金流动是否健康。不同的企业会有不同的衡量标准,也会在不同的时间段处于不同的状态。财务上有很多指标可以在整个公司的维度监控库存水平高低,但如果细致到SKU这个层面的差异化分析,则会衍生许多标准问题以及工作量。本文旨在探究使用非监督学习的方法,从数据的整理到最终的可视化,如何实现零售行业的库存监控问题。
第一部分 - 数据整理 1. 数据录入import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import gridspec
from math import ceil
# 读取数据
df = pd.read_csv('inventory.csv')
# 由于数据有重复,删除重复的数据(数据中包含产品分仓库的明细数据以及仓库栏为‘All’的加总数据)
df = df[df['Warehouse'] != 'All']
# 显示头5行数据
df.head()
Part Number Item Number Warehouse Total On Hand 0 BAILLIE WALNUT BF481825.39901727 KY - Hebron 0 1 BAILLIE BEECH BF481825.39901731 KY - Hebron 0 2 NIELSEN BROWN PU BF763074.46849385 KY - Hebron 0 3 Scargill Mint BF767982.46854251 KY - Hebron 0 4 Scargill Blue BF767982.46854254 KY - Hebron 34 Total Available Total Unavailable Total On Transfer Qty Allocated 0 0 0 0 0 1 0 0 0 0 2 0 0 0 0 3 0 0 0 0 4 0 1 33 0 Total On Order Qty Received(12 Months) Qty Shipped (30 Days) 0 0 201 0 1 0 0 0 2 0 0 0 3 0 0 0 4 0 170 0 Qty Reserved Qty Unpickable Qty On Hold Qty Unprocessed Cycle Count 0 0 0 0 0 1 0 0 0 0 2 0 0 0 0 3 0 0 0 0 4 0 0 1 0 Qty Unprocessed Adjustment 0 0 1 0 2 0 3 0 4 02. 清洗数据
# 检查数据,清洗
tab_info = pd.Dataframe(df.dtypes).T.rename(index={0:'column Type'})
tab_info = tab_info.append(pd.Dataframe(df.isnull().sum()).T.rename(index={0:'null values (nb)'}))
tab_info = tab_info.append(pd.Dataframe(df.isnull().sum()/df.shape[0]*100).T.
rename(index={0: 'null values (%)'}))
tab_info
Part Number Item Number Warehouse Total On Hand
column Type object object object int64
null values (nb) 3 0 0 0
null values (%) 0.0883132 0 0 0
Total Available Total Unavailable Total On Transfer
column Type int64 int64 int64
null values (nb) 0 0 0
null values (%) 0 0 0
Qty Allocated Total On Order Qty Received(12 Months)
column Type int64 int64 int64
null values (nb) 0 0 0
null values (%) 0 0 0
Qty Shipped (30 Days) Qty Reserved Qty Unpickable
column Type int64 int64 int64
null values (nb) 0 0 0
null values (%) 0 0 0
Qty On Hold Qty Unprocessed Cycle Count
column Type int64 int64
null values (nb) 0 0
null values (%) 0 0
Qty Unprocessed Adjustment
column Type int64
null values (nb) 0
null values (%) 0
会用到的列是:
- Total On Hand: 产品库存数量
- Qty Shipped (30 Days): 过去30天的销售数量总和
调查数据的Null缺失值情况良好,可以直接使用。
3. 数据调整、聚合# 将数据聚合到item number这个层面 df_alinv = df.groupby(['Item Number'])['Total On Hand','Qty Shipped (30 Days)'].sum().reset_index() df_alinv = df_alinv.sort_values(by = 'Total On Hand', ascending = False).reset_index(drop=True) # 剔除库存为0的产品 df_alinv = df_alinv[df_alinv['Total On Hand'] > 0] print(df_alinv.head())
Item Number Total On Hand Qty Shipped (30 Days) 0 TNFI1501.41585776 10737 340 1 TNFI1499.41585774 7723 161 2 TNFI1081.33297150 3171 620 3 TNFI1263.38188681 3135 1 4 TNFI1087.33298045 2530 101第二部分 - 探索库存数据(EDA) 1. 了解库存整体情况
# 产品种类、对应库存数量以及分布
no_sku = len(df['Item Number'].unique())
qty_all_inve = df['Total On Hand'].sum()
qtySold30days = df_alinv["Qty Shipped (30 Days)"].sum()
daysOfGoodSold = ceil(qty_all_inve / qtySold30days * 30)
print(f'产品种类数目(包含库存为0的产品):{no_sku}种')
print(f'有库存的产品种类数目:{len(df_alinv)}种')
print(f'库存总量:{qty_all_inve}件')
print(f'过去30天销量:{qtySold30days}件')
print(f'目前库存可以继续卖:{daysOfGoodSold}天')
产品种类数目(包含库存为0的产品):833种 有库存的产品种类数目:463种 库存总量:116142件 过去30天销量:18665件 目前库存可以继续卖:187天2. 可视化
# 库存量 TOP N SKU 占比
def get_pct(top_n):
return df_alinv.iloc[:top_n]['Total On Hand'].sum() / qty_all_inve
acc_pct = [i for i in map(get_pct, [i for i in range(len(df_alinv))])]
# 增加变量 - 周转天数
df_alinv['turnover days'] = df_alinv['Total On Hand'] / df_alinv['Qty Shipped (30 Days)'] * 30
df_alinv['turnover days (max=365)'] = df_alinv.apply(lambda x: x['turnover days'] if x['turnover days'] < 365 else 365, axis = 1)
# 库存分布图
fig = plt.figure(figsize=(10, 8))
gs = gridspec.GridSpec(4, 2, width_ratios=[10, 1])
ax0 = plt.subplot(gs[0])
ax0.set_title('SKU Total On Hand distribution')
ax0.bar(df_alinv.index, df_alinv['Total On Hand'])
ax1 = plt.subplot(gs[1])
ax1.set_title('boxplot')
ax1.boxplot(df_alinv['Total On Hand'])
ax2 = plt.subplot(gs[2])
ax2.set_title('SKU TOH acc% evolution')
ax2.plot([i for i in range(len(df_alinv))], acc_pct)
ax3 = plt.subplot(gs[4])
ax3.set_title('SKU Qty Sold in 30 days')
ax3.bar(df_alinv.index, df_alinv['Qty Shipped (30 Days)'])
ax4 = plt.subplot(gs[5])
ax4.set_title('boxplot')
ax4.boxplot(df_alinv['Qty Shipped (30 Days)'])
ax5 = plt.subplot(gs[6])
ax5.set_title('SKU Turnover Days (max=365)')
ax5.set_xlabel('SKU ranked by Total On Hand')
ax5.bar(df_alinv.index, df_alinv['turnover days (max=365)'])
ax6 = plt.subplot(gs[7])
ax6.set_title('boxplot')
ax6.boxplot(df_alinv['turnover days (max=365)'])
plt.tight_layout()
plt.show()
# top 5 sku
print(df_alinv.sort_values(by='Total On Hand', ascending = False).head())
Item Number Total On Hand Qty Shipped (30 Days) turnover days 0 TNFI1501.41585776 10737 340 947.382353 1 TNFI1499.41585774 7723 161 1439.068323 2 TNFI1081.33297150 3171 620 153.435484 3 TNFI1263.38188681 3135 1 94050.000000 4 TNFI1087.33298045 2530 101 751.485149 turnover days (max=365) 0 365.000000 1 365.000000 2 153.435484 3 365.000000 4 365.000000
从上图可得:
- 从库存量来看,大部分的库存集中在少数的商品当中(库存排名前100的sku占总库存量的80%以上),需要考虑在采购过程给单个sku设置上限;
- 从销量来看,库存量大的头部产品销售优势不明显,考虑加大去库存的促销力度;
- 从库存周转情况来看,库存周转天数中位数在300天左右,其中将近一半的sku周转天数在365天(实际大于等于365天),库存流动整体缓慢。结合之前计算的整体库存可卖187天来看(整体库存量/过去30天整体销售量*30),产品的库存和销售错配情况严重,需要重点关注流动率低的大库存产品。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)