目录
背景
第一部分 - 数据整理
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条)