基于Python的超市零售数据分析

分析框架:

一、明确需求和目的

  • 对一家全球超市的四年(2012-2015)销售数据进行“人、货、场”分析,并给出提升销量的针对性建议。
  • 场:整体运营情况分析,包括销售额、销量、利润、客单价、市场布局等具体情况分析。
  • 货:商品结构、优势/畅销商品、劣势/待优化商品等情况分析。
  • 人:客户数量、新老客户、RFM模型、复购率、回购率等用户行为分析。

二、数据介绍

  • 数据来源于Kaggle平台,这是一份全球大型超市五年的零售数据集,数据信息详尽。
  • 数据集为”全球超市订单数据.xlsx”,共51290条数据记录,每条记录共24个特征。

三、数据预处理

3.1 导入相关库并读取数据

import pandas as pd 
import numpy as np 
import matplotlib.pyplot as plt 
import seaborn as sns

plt.rcParams['font.sans-serif'] = ['SimHei'] # 设置中文显示 
plt.rcParams['axes.unicode_minus'] = False # 设置坐标轴负数显示

pd.set_option('display.width', 300) # 设置字符显示宽度
pd.set_option('display.max_rows', None) # 设置显示最大行 
pd.set_option('display.max_columns', None) # 设置显示最大列
data = pd.read_excel('全球超市订单数据.xlsx')
data.sample(5)
行 ID订单 ID订购日期装运日期装运方式客户 ID客户名称细分市场邮政编码城市省市国家地区市场产品 ID类别子类别产品名称销售额数量折扣利润装运成本订单优先级
2169622861IN-2013-BM117857-415932013-11-152013-11-17二级BM-117857Bryan Mills消费者NaNNowra新南威尔士澳大利亚大洋洲亚太地区OFF-AR-3453办公用品艺术BIC Highlighters, Fluorescent54.51330.11.77310.82
574912597ES-2015-IM1505545-420982015-04-042015-04-08标准级IM-1505545Ionia McGrath消费者NaNTourcoing北部-加来海峡-庇卡底法国西欧欧洲OFF-ST-5702办公用品存储Rogers Lockers, Single Width380.97020.1143.91058.64
2087951267MG-2015-DK289584-422102015-07-252015-07-28一级DK-289584Dana Kaydos消费者NaNUlan Bator乌兰巴托蒙古东亚亚太地区FUR-FU-3027家具用具Advantus Door Stop, Black45.03010.09.45011.58
2368330075IN-2015-TN2104027-421362015-05-122015-05-15一级TN-2104027Tanja Norvell家庭办公室NaNShangqiu河南省中国东亚亚太地区OFF-AR-3502办公用品艺术Binney & Smith Sketch Pad, Water Color99.00020.013.8609.15
4670412192ES-2014-AG10675120-417312014-04-022014-04-07标准级AG-10675120Anna Gayman消费者NaNBarcelona加泰罗尼亚西班牙南欧欧洲OFF-BI-3290办公用品装订机Avery Hole Reinforcements, Durable23.64040.08.4001.51媒介
# 查看数据行列数
data.shape
>(51290, 24)
# 查看数据的分布概况 
data.describe()
行 ID邮政编码销售额数量折扣利润装运成本
count51290.000009994.00000051290.00000051290.00000051290.00000051290.00000051290.000000
mean25645.5000055190.379428246.4905813.4765450.14290828.61098226.478567
std14806.2919932063.693350487.5653612.2787660.212280174.34097257.251373
min1.000001040.0000000.4440001.0000000.000000-6599.9780001.002000
25%12823.2500023223.00000030.7586252.0000000.0000000.0000002.610000
50%25645.5000056430.50000085.0530003.0000000.0000009.2400007.790000
75%38467.7500090008.000000251.0532005.0000000.20000036.81000024.450000
max51290.0000099301.00000022638.48000014.0000000.8500008399.976000933.570000

3.2 对列名格式进行规范

根据上面展示的数据可以发现数据的列名不符合Python的命名规范,列名不应该包含空格,应去除或用下划线连接,这里采用去除空格对列名进行重命名。

data.rename(columns=lambda x:x.replace(' ',''),inplace=True)
data.columns
Index(['行ID', '订单ID', '订购日期', '装运日期', '装运方式', '客户ID', '客户名称', '细分市场', '邮政编码', '城市', '省市', '国家', '地区', '市场', '产品ID', '类别', '子类别', '产品名称', '销售额', '数量', '折扣', '利润', '装运成本', '订单优先级'], dtype='object')

3.3 数据类型处理

# 查看各列数据类型 
# data.info() 
data.dtypes
行ID               int64
订单ID             object
订购日期     datetime64[ns]
装运日期     datetime64[ns]
装运方式             object
客户ID             object
客户名称             object
细分市场             object
邮政编码            float64
城市               object
省市               object
国家               object
地区               object
市场               object
产品ID             object
类别               object
子类别              object
产品名称             object
销售额             float64
数量                int64
折扣              float64
利润              float64
装运成本            float64
订单优先级            object
dtype: object

可以看到各列数据类型与其字段数据值相符,因此不需要进行数据类型转换。

为了 方便分析每年和每月的销售情况,增加“Year”和“Month”列:

data['Year'] = data['订购日期'].dt.year
data['Month'] = data['订购日期'].values.astype('datetime64[M]')
data[['Year','Month']].head()
YearMonth
020142014-11-01
120142014-02-01
220142014-10-01
320142014-01-01
420142014-11-01

3.4 缺失值处理

# 查看各字段的数据是否存在缺失 
data.isnull().any()
行ID      False
订单ID     False
订购日期     False
装运日期     False
装运方式     False
客户ID     False
客户名称     False
细分市场     False
邮政编码      True
城市       False
省市       False
国家       False
地区       False
市场       False
产品ID     False
类别       False
子类别      False
产品名称     False
销售额      False
数量       False
折扣       False
利润       False
装运成本     False
订单优先级    False
Year     False
Month    False
dtype: bool
# 发现只有邮政编码存在缺失值,统计查看一下存在多少缺失值(行数是51290)
data['邮政编码'].isnull().sum()
41296

发现邮政编码列几乎80%都是缺失值,显然该列已经对我们的分析没有太大作用,因此进行删除。

data.drop('邮政编码', axis=1, inplace=True) 

3.5 异常值处理

data.describe()
行ID销售额数量折扣利润装运成本Year
count51290.0000051290.00000051290.00000051290.00000051290.00000051290.00000051290.000000
mean25645.50000246.4905813.4765450.14290828.61098226.4785672013.777208
std14806.29199487.5653612.2787660.212280174.34097257.2513731.098931
min1.000000.4440001.0000000.000000-6599.9780001.0020002012.000000
25%12823.2500030.7586252.0000000.0000000.0000002.6100002013.000000
50%25645.5000085.0530003.0000000.0000009.2400007.7900002014.000000
75%38467.75000251.0532005.0000000.20000036.81000024.4500002015.000000
max51290.0000022638.48000014.0000000.8500008399.976000933.5700002015.000000

从上面展示的结果可以确定没有明显的异常值,因此不需要进行处理。

3.6 重复值处理

# 查看是否存在完全重复的行:
data.duplicated().sum()
0

0说明没有完全重复的记录,因此不需要进行处理

四、数据分析

4.1 整体销售情况分析

首先构造整体销售情况的数据子集:

  • 包含:订购日期、年份、月份、销售额、销量、利润
sales_data = data[['订购日期','Year','Month','销售额','数量','利润']]
sales_data.head()
订购日期YearMonth销售额数量利润
02014-11-1120142014-11-01221.980262.1544
12014-02-0520142014-02-013709.3959-288.7650
22014-10-1720142014-10-015175.1719919.9710
32014-01-2820142014-01-012892.5105-96.5400
42014-11-0520142014-11-012832.9608311.5200

按照年、月对销售数据子集进行分组求和统计:

sales_year_grouped = sales_data.groupby(by=['Month']).agg('sum')
sales_year_grouped.sample(5)
Year销售额数量利润
Month
2014-10-012215400293406.64288388342433.22258
2012-01-0187119698898.4888614638321.80096
2014-04-011580990177821.31684268819462.03844
2012-08-011740380208063.28372302026452.99742
2014-12-013226428405454.37802569450202.87112

对上面进行分组求和后的数据进行拆分,将每一年的数据分配一张独立的表:

year_2012 = sales_year_grouped['2012':'2012'].reset_index()
year_2013 = sales_year_grouped['2013':'2013'].reset_index() 
year_2014 = sales_year_grouped['2014':'2014'].reset_index() 
year_2015 = sales_year_grouped['2015':'2015'].reset_index()
year_2015
MonthYear销售额数量利润
02015-01-011849770241268.55566312228001.38626
12015-02-011523340184837.35556248219751.69996
22015-03-012152020263100.77262372237357.26052
32015-04-012117765242771.86130359423782.30120
42015-05-012587260288401.04614430033953.55774
52015-06-013522220401814.06310600943778.60280
62015-07-012190305258705.68048363728035.87258
72015-08-013375125456619.94236582453542.89496
82015-09-014066270481157.24370683767979.45110
92015-10-013276390422766.62916587658209.83476
102015-11-014326205555279.02700770662856.58790
112015-12-014338295503143.69348751346916.52068

4.1.1 销售额分析(按月)

# 构建销售额表 
sales = pd.concat([year_2012['销售额'],year_2013['销售额'],
                   year_2014['销售额'],year_2015['销售额']],axis=1)
# 重构行列名 
sales.index = [str(i+1)+'月' for i in range(12)]
sales.columns = ['sales_2012','sales_2013','sales_2014','sales_2015']

# 绘制热力图,颜色越深表示销售额越高
plt.figure(figsize=(8,6))
sns.heatmap(sales,cmap='RdPu',annot=True,fmt='.2f',linewidths=0.5)
# plt.yticks(rotation=90)
plt.show()

从上图可以看出,基本每年的下半年的销售额都比上半年要高,而且随着年份的增长,销售额也在逐年增加,可以说明该超市的发展还是比较好的

肉眼可以看见每一年的销售额都比前一年要好,那么现在来实际计算一下每年的销售总额和具体的增长率:

# 每年的总销售额 
sales_sum = sales.sum()
# 计算销售额增长率 
rise_rates = [0]
for i in range(len(sales_sum)-1):
    rise_rate = round((sales_sum[i+1]-sales_sum[i])/sales_sum[i],4)
    rise_rates.append(rise_rate)
rise_rates
[0, 0.185, 0.272, 0.2625]
# 可视化每年销售额 
sales_sum.plot(kind='bar', alpha=0.6, figsize=(8,6))
plt.ylabel('销售额')
plt.title('2012-2015每年销售额')
plt.show()

# 可视化销售额增长率 
rise_rates = pd.Series(rise_rates)
rise_rates.index = sales_sum.index
rise_rates.plot(color = 'r', figsize=(8,6))
plt.title('2012-2015每年销售额增长率变化趋势')
plt.ylim(0,0.5)
plt.show()

从上面可以看出,后两年销售额增长率达到26%,2015年销售额相比2012年,将近翻了一番,发展势头良好,经营在逐步稳定。结合年度销售额和增长率进行战略规划,可以规划或制定下一年度总销售额业绩指标。

了解了超市整体销售额后,在对每年每月的销售额进行分析。了解不同月份的销售情况,探究是否有淡旺季之分,找出重点销售月份,以便制定经营策略与业绩月度及季度指标拆分。

首先来看一下每年每月销售额的面积堆积图:

sales.plot.area(stacked=False,figsize=(8,6))
plt.title('每年每月的销售额变化')
plt.show()

从面积图可以大致看出该超市的销售存在季节性差异,总体情况是上半年为淡季,下半年为旺季。其中上半年6月份的销售额比较高,下半年的7月份销售额偏低。对此可采取的措施:

  • 1、对于旺季的月份,运营推广的策略要继续维持,也可以加大投入,提高整体销售额。
  • 2、对于淡季的月份,可以适当减少库存,也可以结合产品特点拓展新品,同时举办一些促销活动等来吸引用户。

4.1.2 销量分析(按月)

# 构建销量表 
quantity = pd.concat([year_2012['数量'],year_2013['数量'],
                   year_2014['数量'],year_2015['数量']],axis=1)
# 重构行列名 
quantity.index = [str(i+1)+'月' for i in range(12)]
quantity.columns = ['Quantity_2012','Quantity_2013','Quantity_2014','Quantity_2015']

# 绘制热力图,颜色越深表示销量越高
plt.figure(figsize=(8,6))
sns.heatmap(quantity, cmap='RdPu', annot=True,fmt='d', linewidths=0.5)
plt.show()

# 计算每年销量
quantity_sum = quantity.sum()
# 计算销量增长率 
rise_rates_quantity = [0]
for i in range(len(quantity_sum)-1):
    rise_rate = round((quantity_sum[i+1]-quantity_sum[i])/quantity_sum[i],4)
    rise_rates_quantity.append(rise_rate)
rise_rates_quantity
[0, 0.2121, 0.263, 0.2594]
# 可视化每年销量
quantity_sum.plot(kind='bar', alpha=0.6, figsize=(8,6))
plt.ylabel('销量')
plt.title('2012-2015每年销量')
plt.show()

# 可视化销量增长率 
rise_rates_quantity = pd.Series(rise_rates_quantity)
rise_rates_quantity.index = quantity_sum.index
rise_rates_quantity.plot(color = 'g', figsize=(8,6))
plt.title('2012-2015每年销量增长率变化趋势')
plt.ylim(0,0.5)
plt.show()

quantity.plot.area(stacked=False,figsize=(8,6))
plt.title('每年每月的销量变化')
plt.show()

可以看出2012-2014年销量的变化趋势和销售额是一样的,下半年销量整体高于上半年,同时销量同比上一年均是在不断提升。

4.1.3 利润分析

# 构建利润表 
profit = pd.concat([year_2012['利润'],year_2013['利润'],
                   year_2014['利润'],year_2015['利润']],axis=1)
# 重构行列名 
profit.index = [str(i+1)+'月' for i in range(12)]
profit.columns = ['profit_2012','profit_2013','profit_2014','profit_2015']

# 绘制热力图,颜色越深表示利润越高
plt.figure(figsize=(8,6))
sns.heatmap(profit, cmap='RdPu', annot=True,fmt='.2f', linewidths=0.5)
plt.show()

# 计算每年利润
profit_sum = profit.sum()

# 计算利润率
profit_sum = pd.DataFrame({'profit_sum':profit_sum})
profit_sum['sales_sum'] = sales.sum().values
profit_sum['profit_rate'] = profit_sum['profit_sum']/profit_sum['sales_sum']
profit_sum
profit_sumsales_sumprofit_rate
profit_2012248940.811542.259451e+060.110178
profit_2013307415.279102.677439e+060.114817
profit_2014406935.230183.405746e+060.119485
profit_2015504165.970464.299866e+060.117252
# 可视化每年总利润
profit_sum['profit_sum'].plot(kind='bar',alpha=0.6)
plt.title('2012-2015年每年总利润')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-avBhIgA8-1666229503178)(output_54_0.png)]

# 可视化利润率 
profit_sum['profit_rate'].plot(color = 'b',figsize=(8,6))
plt.title('2012-2015年利润变化趋势')
plt.ylim(0,0.5)
plt.show()

从上面的结果可以看出,每年的利润变化趋势跟销售额一样,都是在逐年增加,说明企业经营情况良好。但是利润率总体平稳,稳定在11%-12%之间,总体还是不错的。

4.1.4 客单价分析(按年)

客单价是指超市每一位客户平均购买商品的金额,即平均交易金额。
从某种程度上反映了企业的消费群体的许多特点以及企业的销售类目的盈利状态是否健康。
其中,需要注意的有:

  • 计算总消费次数要将同一天内同一个客户发生的所有消费算作一次消费;
  • 客单价 = 总消费金额 ÷ 总消费次数
data.head()
行ID订单ID订购日期装运日期装运方式客户ID客户名称细分市场城市省市国家地区市场产品ID类别子类别产品名称销售额数量折扣利润装运成本订单优先级YearMonth
040098CA-2014-AB10015140-419542014-11-112014-11-13一级AB-100151402Aaron Bergman消费者Oklahoma City俄克拉何马州美国美国中部美国TEC-PH-5816技术电话Samsung Convoy 3221.98020.062.154440.7720142014-11-01
126341IN-2014-JR162107-416752014-02-052014-02-07二级JR-162107Justin Ritter公司Wollongong新南威尔士澳大利亚大洋洲亚太地区FUR-CH-5379家具椅子Novimex Executive Leather Armchair, Black3709.39590.1-288.7650923.63紧急20142014-02-01
225330IN-2014-CR127307-419292014-10-172014-10-18一级CR-127307Craig Reiter消费者Brisbane昆士兰州澳大利亚大洋洲亚太地区TEC-PH-5356技术电话Nokia Smart Phone, with Caller ID5175.17190.1919.9710915.49媒介20142014-10-01
313524ES-2014-KM1637548-416672014-01-282014-01-30一级KM-1637548Katherine Murray家庭办公室柏林柏林德国西欧欧洲TEC-PH-5267技术电话Motorola Smart Phone, Cordless2892.51050.1-96.5400910.16媒介20142014-01-01
447221SG-2014-RH9495111-419482014-11-052014-11-06当日RH-9495111Rick Hansen消费者达喀尔达喀尔塞内加尔西非非洲TEC-CO-6011技术复印机Sharp Wireless Fax, High-Speed2832.96080.0311.5200903.04紧急20142014-11-01
total_num_list = []
unit_price_list = []
for i in range(2012,2016):
    df = data[data['Year']==i]
    price = df[['订购日期','客户ID','销售额']]
    
    # 计算每年总消费次数 
    price_dr = price.drop_duplicates(['订购日期','客户ID'])
    
    # 总消费次数统计 
    total_num_list.append(price_dr.shape[0])
    
    # 客单价 
    unit_price_list.append(round(price['销售额'].sum()/price_dr.shape[0],3))
unit_price = pd.DataFrame({'total_num':total_num_list,
                           'unit_price':unit_price_list})
unit_price.index = [str(i) for i in range(2012,2016)]
unit_price
total_numunit_price
20124512500.765
20135473489.209
20146883494.806
20158851485.806
# 可视化每年消费次数
unit_price['total_num'].plot(kind='bar',alpha=0.6,figsize=(8,6))
plt.grid() # 设置网格线
plt.title('2012-2015年年消费次数')
plt.show()

# 可视化每年客单价 
unit_price['unit_price'].plot(figsize=(8,6))
plt.ylim(400,550)
plt.grid()
plt.title('2012-2015年客单价变化趋势')
plt.show()

从上面图表来看,每年的消费次数呈不断上升趋势,但是客单价总体波动不大,基本稳定在500左右。

4.1.5 市场布局分析

因为这是宜家全球超市,在不同地区都有市场,因此来看一下不同地区之间的销售情况:

Market_year_sales = data.groupby(['市场','Year']).agg({'销售额':'sum'}).reset_index()
Market_year_sales
市场Year销售额
0亚太地区20127.136582e+05
1亚太地区20138.639840e+05
2亚太地区20141.092232e+06
3亚太地区20151.372784e+06
4拉丁美洲20123.850982e+05
5拉丁美洲20134.647333e+05
6拉丁美洲20146.081408e+05
7拉丁美洲20157.066329e+05
8欧洲20125.407506e+05
9欧洲20137.176114e+05
10欧洲20148.486702e+05
11欧洲20151.180304e+06
12美国20124.927566e+05
13美国20134.866293e+05
14美国20146.276350e+05
15美国20157.571081e+05
16非洲20121.271873e+05
17非洲20131.444807e+05
18非洲20142.290688e+05
19非洲20152.830364e+05
# 可视化各地区销售额 
plt.figure(figsize=(8,6))
sns.barplot(data=Market_year_sales,x='市场',y='销售额',hue='Year')
plt.title('2012-2015每年各地区销售情况')
plt.show()

再来看看这四年各地区销售额占总销售额的百分比:

Market_sales = data.groupby(['市场']).agg({'销售额':'sum'}) 
Market_sales['销售额占比'] = Market_sales['销售额']/data['销售额'].sum()
Market_sales.sort_values(by='销售额占比',ascending=False,inplace=True)

# 颜色越深,说明销售额和占比越大
Market_sales.style.background_gradient()

Market_sales['销售额占比'].plot.pie(autopct='%.2f%%', explode=[0.015 for i in range(len(Market_sales))],figsize=(8,6))
plt.axis('equal')
plt.title('2012-2015年各地区销售额占总销售额的百分比')
plt.show()

从以上图表可以看出,每个地区每年销售额总体处于上升趋势,其中亚太地区、欧洲、美国、拉丁美洲的销售额就超过了总销售额的90%,总体也与地区的经济发展相匹配。其中,非洲的销售额占比相比其它市场比较低,可以结合公司的整体战略布局进行调整。

4.2 商品情况分析

首先统计销量前10的商品:

product_count = data.groupby('产品ID').agg({'数量':'sum'}).sort_values(by='数量',ascending=False).reset_index()
product_count.head(10)
产品ID数量
0OFF-FA-6129876
1OFF-BI-3737337
2OFF-ST-4057321
3OFF-ST-5693262
4OFF-AR-5923259
5OFF-FA-6189253
6OFF-BI-3293252
7OFF-BI-4828251
8OFF-ST-6033250
9OFF-AR-6120242

统计销售额前10的商品:

product_amount = data.groupby('产品ID').agg({'销售额':'sum'}).sort_values(by='销售额',ascending=False).reset_index()
product_amount.head(10)
产品ID销售额
0TEC-PH-314886935.7786
1TEC-PH-380676441.5306
2TEC-PH-526873156.3030
3TEC-PH-535571904.5555
4TEC-CO-369161599.8240
5FUR-CH-465458193.4841
6FUR-CH-544150661.6840
7FUR-CH-453050121.5160
8TEC-PH-583948653.4600
9TEC-PH-535647877.7857

统计利润前10的商品:

product_profit = data.groupby(by='产品ID').sum()['利润'].sort_values(ascending=False)
product_profit.head(10)
产品ID
TEC-CO-3691    25199.9280
TEC-PH-3806    17238.5206
TEC-PH-5268    17027.1130
OFF-AP-4743    11807.9690
FUR-BO-5951    10672.0730
FUR-CH-4530    10427.3260
TEC-PH-5355     9938.1955
TEC-PH-3807     9786.6408
TEC-PH-5356     9465.3257
TEC-AC-3405     8955.0180
Name: 利润, dtype: float64

从上面结果可以看出,销量靠前的大部分是(OFFICE)办公用品;销售额靠前的大部分是电子产品,还有一些是家具商品;利润靠前的也绝大部分是电子产品。因此,可以重点提升电子类产品的销量,从而来增加整体利润。

下面来看一下具体商品种类的销售情况:

# 按照种类和子类进行分组,统计销售额和利润 
df_category_sub_category = data.groupby(['类别','子类别']).agg({'销售额':'sum','利润':'sum'})

# 按照销售额倒序排序 
df_category_sub_category.sort_values(by='销售额',ascending=False,inplace=True)
# 计算每个类别商品的销售额累计占比 
df_category_sub_category['cum_percent'] = df_category_sub_category['销售额'].cumsum()/df_category_sub_category['销售额'].sum()
df_category_sub_category.reset_index(inplace=True)
df_category_sub_category
类别子类别销售额利润cum_percent
0技术电话1.706824e+06216717.005800.135007
1技术复印机1.509436e+06258567.548180.254401
2家具椅子1.501682e+06140396.267500.373181
3家具书架1.466572e+06161924.419500.489184
4办公用品存储1.126813e+06108416.680600.578313
5办公用品电器1.010536e+06141562.587700.658245
6技术设备7.790601e+0558867.873000.719867
7家具桌子7.570419e+05-64083.388700.779748
8技术附件7.492370e+05129626.306200.839011
9办公用品装订机4.618694e+0572433.151600.875544
10家具用具3.851560e+0546845.431900.906010
11办公用品艺术3.716132e+0557829.859300.935403
12办公用品用品2.428111e+0522559.195300.954609
13办公用品纸张2.417875e+0558111.653500.973734
14办公用品信封1.692175e+0528849.487300.987119
15办公用品系固件8.949505e+0413844.288900.994198
16办公用品标签7.335028e+0414988.923701.000000
# !pip install waterfallcharts # 安装第三方瀑布图库 
import waterfall_chart 
waterfall_chart.plot(df_category_sub_category['子类别'], 
                     df_category_sub_category['cum_percent'],
                     rotation_value=90,
                     formatting='{:,.2f}',
                     net_label = '销售额总计')
plt.ylim(0,15)
plt.show()

  • 从以上图表可以清晰的看到不同种类商品的销售额贡献对比,差不多一半种类的商品的总销售额占比就达到了84%,这部分商品应是自家的优势主营产品,后续经营过程中应继续保持,可结合整体战略发展适当加大投入,逐渐形成自己的品牌。
  • 同时也发现,末尾销售额累计占比16%的产品中大部分属于办公类产品的小物件,可以考虑与其它主营产品结合,连带销售来提升销量,或者考虑对这些产品进行优化。
  • 最值得关注的是,家具品类的桌子的利润是负数,表面这个产品目前词语亏损状态,原因可能是促销让利太多。因为通过查看原数据,发现桌子大部分都在打折,打折的销量高达76%。如果是在清理库存,那属于正常现象,但如果不是,说明这个产品在市场推广中遇到了瓶颈或者是遇到了竞争对手,需要结合实际业务进行分析,适当改善经营策略。

4.3 用户情况分析

4.3.1 不同类型的客户占比分析

首先统计四年所有不同类型的客户占比:

plt.figure(figsize=(8,6))
data['细分市场'].value_counts().plot(kind='pie',autopct='%.2f%%',
                                     explode=[0.015 for i in range(len(data['细分市场'].value_counts()))])
plt.axis('equal')
plt.title('四年所有不同类型的客户占比')
plt.show()

从饼图可以看出,这四年中,普通消费者的客户占比最多,达到了51.7%。

接下来看一下每一年不同类别的客户数情况:

segment_year = data.groupby(['Year','细分市场']).agg({'客户ID':'count'}).reset_index()
plt.figure(figsize=(8,6))
sns.lineplot(data=segment_year,x='Year',y='客户ID',hue='细分市场')
plt.xticks(segment_year['Year'])
plt.title('2012-2015年不同类型的客户数量')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PXuPK43k-1666229503186)(output_86_0.png)]

从上图可以看出,各类客户每年都呈增长趋势,说明客户结构非常好。

再来看一下每一年不同类别的客户贡献销售额:

segment_amount = data.groupby(['Year','细分市场']).agg({'销售额':'sum'}).reset_index()
plt.figure(figsize=(8,6))
sns.lineplot(data=segment_amount,x='Year',y='销售额',hue='细分市场')
plt.xticks(segment_amount['Year'])
plt.title('2012-2015年不同类型客户的销售额贡献')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VmDWp9Nw-1666229503187)(output_89_0.png)]

从上图可以看到,各类客户每年贡献的销售额都在稳步上升,普通消费者贡献的销售额最多,当然这与客户群体大小有一定关系。

4.3.2 客户下单行为分析

截取客户ID、订购日期、数量、销售额、Month作为新的数据子集,并对订购日期进行排序,方便后续分析。

customer_df = data[['客户ID','订购日期','数量','销售额','Month']].sort_values(by='订购日期')
customer_grouped = customer_df.groupby('客户ID')

首先,查看一下用户的首购日期分布和最后一次购买日期分布:

customer_grouped.min()['订购日期'].value_counts().plot(figsize=(8,6))
plt.title('用户首购日期分布')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pgxRZb1-1666229503188)(output_94_0.png)]

customer_grouped.max()['订购日期'].value_counts().plot(figsize=(8,6))
plt.title('用户最后一次购买日期分布')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kTHVexLI-1666229503188)(output_95_0.png)]

  • 从上面可以看出日新增用户的增长速度平稳,稳定在15人左右,说明超市在拉新方面维持的比较好。通过观察最后一次购买日期分布,(最后一次消费距离数据的最大时间两年视为流失,即2013年以前最后消费的用户都是流失用户),可以看到用户的流失保持在10人左右,整体用户数量呈现一个上涨趋势,也验证了超市每年销售额的增长趋势。

接下来看一下只消费过一次的客户数量:

# 查看客户的第一次消费和最后一次消费时间
custom_life = customer_grouped['订购日期'].agg(['max','min'])
# 统计第一次消费时间和最后一次消费时间相同的客户。 
(custom_life['max']==custom_life['min']).value_counts()
True     11985
False     5430
dtype: int64

从结果来看,只购买一次的用户是购买多次的用户的两倍多,可以看出超市的用户流失严重,并没有采取很好的用户留存措施,导致很多用户只消费了一次就不再消费了,因此,超市可以采取一些会员制、促销活动等策略增加客户粘性,提高用户留存。

4.3.3 用户分层:RFM模型

RFM的含义:

  • R(Recency):客户最近一次交易时间的间隔。R值越大,表示客户交易发生的日期越久,反之则表示客户交易发生的日期越近。
  • F(Frequency):客户最近一段时间交易的次数。F越大,交易越频繁,反之,说明客户交易不够活跃。
  • M(Monetary):客户在最近一段时间内交易的金额。M值越大,说明客户价值越高,反之,客户价值越低。

RFM模型分析就是根据客户活跃长度和交易金额贡献,进行客户价值细分的一种方法。

首先构建RFM表:

rfm = data.groupby('客户ID').agg({'订购日期':'max','订单ID':'count','销售额':'sum'})

# 以所有用户中最大的交易日期为准,求每个用户最近一次交易的时间间隔R
rfm['R'] = -(rfm.订购日期.max()-rfm.订购日期)/np.timedelta64(1,'D')  # 去除days的单位

# 每个客户的订单ID数即为F,销售额为M 
rfm.rename(columns={'订单ID':'F','销售额':'M'}, inplace=True)
rfm = rfm[['R','F','M']] 
rfm.head()
RFM
客户ID
AA-10315102-358.06544.656
AA-10315120-959.012713.410
AA-10315139-149.0132955.798
AA-103151402-184.064780.552
AA-103151404-818.03753.508

接下来对客户价值进行标注,将客户划分为8个等级。
客户价值判断标准:以R、F、M各自的均值作为判别标准,超过均值则为1,反之则为0。

def rfm_func(x):
    level = x.apply(lambda x:'1' if x>0 else '0')
    level = level.R + level.F + level.M 
    d = {
        '111':'重要价值客户',
        '011':'重要保持客户',
        '101':'重要挽留客户',
        '001':'重要发展客户',
        '110':'一般价值客户',
        '010':'一般保持客户',
        '100':'一般挽留客户',
        '000':'一般发展客户'}
    return d[level]
rfm['Label'] = rfm.apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)
rfm.head()
RFMLabel
客户ID
AA-10315102-358.06544.656一般价值客户
AA-10315120-959.012713.410重要发展客户
AA-10315139-149.0132955.798重要价值客户
AA-103151402-184.064780.552重要价值客户
AA-103151404-818.03753.508重要保持客户

接着对重要价值客户和非重要价值客户进行可视化展示:

rfm['Label'].value_counts().plot(kind = 'pie',autopct='%.2f%%',
                                 shadow=True,center=(0.2,0.2))
plt.axis('equal')
plt.title('各类价值客户占比',fontsize=16)
plt.ylabel('') # 去掉y坐标轴标题
plt.show()

可能有0)(output_108_0.png)]

通过RFM模型可以识别不同的客户群体,能够衡量客户价值和客户利润创收能力,可以制定个性化的沟通和服务,为更多的营销决策提供有力支持,为企业创造更大的收益。

4.3.4 新用户、活跃用户、不活跃用户和回归用户分析

  • 将用户按照每一个月份分成:
    • unreg:观望用户,前两个月没买,第三个月才第一次购买,则用户前两个月为观望用户
    • unactive:首月购买后,后续月份没有购买则在没有购买的月份中该用户为非活跃用户
    • new:当前月就进行首次购买的用户在当前月为新用户
    • active:连续月份购买的用户在这些月中为活跃用户
    • return:购买之后间隔n个月后再次购买的第一个月份为该月份的回头客。

创建透视表,统计每一个月每个用户的购买次数:

pivoted_counts = data.pivot_table(index='客户ID',columns='Month',values='订购日期',aggfunc='count').fillna(0)
# 统计每个用户每个月是否消费,消费了设为1,没有消费设为0
df_purchase = pivoted_counts.applymap(lambda x : 1 if x>0 else 0)
df_purchase.head()
Month2012-01-012012-02-012012-03-012012-04-012012-05-012012-06-012012-07-012012-08-012012-09-012012-10-012012-11-012012-12-012013-01-012013-02-012013-03-012013-04-012013-05-012013-06-012013-07-012013-08-012013-09-012013-10-012013-11-012013-12-012014-01-012014-02-012014-03-012014-04-012014-05-012014-06-012014-07-012014-08-012014-09-012014-10-012014-11-012014-12-012015-01-012015-02-012015-03-012015-04-012015-05-012015-06-012015-07-012015-08-012015-09-012015-10-012015-11-012015-12-01
客户ID
AA-10315102000000100000000000000000000000000000100000000000
AA-10315120000000000000000010000000000000000000000000000000
AA-10315139000000000000000000000000000000001010000001010000
AA-103151402000000000000000000000000001000000000000001000000
AA-103151404001000000000000000000100000000000000000000000000
# 定义函数将df_purchase中的数据0和1转换为new、unactive、active、unreg、return状态。
def active_status(data):
    status = []
    for i in range(len(df_purchase.columns)):
        # 本月未消费 
        if data[i] == 0:
            if len(status) > 0:
                if status[i-1] == 'unreg':
                    status.append('unreg')
                else:
                    status.append('unactive')
            else:
                status.append('unreg')
        
        # 本月消费量 
        else:
            if len(status) == 0:
                status.append('new')
            else:
                if status[i-1] == 'unreg':
                    status.append('new')
                elif status[i-1] == 'unactive':
                    status.append('return')
                else:
                    status.append('active')
    return status
purchase_status = df_purchase.apply(active_status,axis=1)
purchase_status.head()
客户ID
AA-10315102     [unreg, unreg, unreg, unreg, unreg, unreg, new...
AA-10315120     [unreg, unreg, unreg, unreg, unreg, unreg, unr...
AA-10315139     [unreg, unreg, unreg, unreg, unreg, unreg, unr...
AA-103151402    [unreg, unreg, unreg, unreg, unreg, unreg, unr...
AA-103151404    [unreg, unreg, new, unactive, unactive, unacti...
dtype: object
purchase_status_ct = purchase_status.apply(lambda x:pd.Series(x))

# 将标注状态的列名修改回月份
purchase_status_ct.columns = df_purchase.columns

# 在实际场景中,任何没注册的人都可以是unreg,所以没有意义,用NaN替换“unreg”或后面进行删除
# 统计每个月各状态的客户数量
purchase_status_ct = purchase_status_ct.apply(lambda x:pd.value_counts(x)).head()
purchase_status_ct.head()
Month2012-01-012012-02-012012-03-012012-04-012012-05-012012-06-012012-07-012012-08-012012-09-012012-10-012012-11-012012-12-012013-01-012013-02-012013-03-012013-04-012013-05-012013-06-012013-07-012013-08-012013-09-012013-10-012013-11-012013-12-012014-01-012014-02-012014-03-012014-04-012014-05-012014-06-012014-07-012014-08-012014-09-012014-10-012014-11-012014-12-012015-01-012015-02-012015-03-012015-04-012015-05-012015-06-012015-07-012015-08-012015-09-012015-10-012015-11-012015-12-01
activeNaNNaN152454131488732729931015171611274414131219151820153421219151238282938.0
new215.0202.0264245306430242410464383484519228180267260330483255404494354505437252214290258352517292442518355470459264227292288366499297437526406509524.0
returnNaNNaN15615142736256065323869557675571131311231851678089114127164188139235286201296340168154232230284371223388438362519514.0
unactiveNaN215.04156719181213164318732265273930933572412543514501477750215345584660516430692772177741827085228706898692079525100921028910673112801153711961125971288713035133311355513827144781461314974155861583416339.0
unreg17200.016998.0167341648916183157531551115101146371425413770132511302312843125761231611986115031124810844103509996949190548802858882988040768871716879643759195564509446354371414438523564319826992402196514391033524NaN
# 用0填充NaN 
purchase_status_ct.iloc[:-2,:].fillna(0).T.plot.area(stacked=False,figsize=(8,6))
plt.title('每月各正向状态的用户数量')
plt.show()

![[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FZEOJHJJ-1666229503191(https://img-blog.csdnimg.cn/9de28f4d17824a64b9c13f6378e08e3f.png)

purchase_status_ct.iloc[-2,:].fillna(0).T.plot.area(stacked=False,figsize=(8,6))
plt.title('每月不活跃的用户数量')
plt.show()

从以上结果可以发现活跃用户、新用户和回归用户,每年呈一定的规律波动,这可能跟年终年末大促销有关,需要更多的数据进行佐证。同时发现不活跃用户数量每月都在不断增加,再次证明了该超市没有实施用户留存相关的策略,导致用户流失严重,如果能在用户留存方面取得突破,会给商家带来很大的增长空间。

4.3.5 复购率和回购率分析

复购计算指标:用户在本月内购买超过一次以上算作一次复购
复购率 = 本月复购次数/本月消费人数

purchase_r = pivoted_counts.applymap(lambda x:1 if x>1 else np.NaN if x==0 else 0)
purchase_r.head()
Month2012-01-012012-02-012012-03-012012-04-012012-05-012012-06-012012-07-012012-08-012012-09-012012-10-012012-11-012012-12-012013-01-012013-02-012013-03-012013-04-012013-05-012013-06-012013-07-012013-08-012013-09-012013-10-012013-11-012013-12-012014-01-012014-02-012014-03-012014-04-012014-05-012014-06-012014-07-012014-08-012014-09-012014-10-012014-11-012014-12-012015-01-012015-02-012015-03-012015-04-012015-05-012015-06-012015-07-012015-08-012015-09-012015-10-012015-11-012015-12-01
客户ID
AA-10315102NaNNaNNaNNaNNaNNaN1.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN1.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
AA-10315120NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN0.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
AA-10315139NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN1.0NaN0.0NaNNaNNaNNaNNaNNaN0.0NaN1.0NaNNaNNaNNaN
AA-103151402NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN1.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN1.0NaNNaNNaNNaNNaNNaN
AA-103151404NaNNaN1.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN0.0NaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaNNaN
# count是统计本月消费的人数
(purchase_r.sum()/purchase_r.count()).plot(figsize=(8,6))
plt.title('复购率变化趋势')
plt.show()

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4XTklmrn-1666229503193)(output_121_0.png)]

回购:在本月购买过,且在相邻下月也购买时计入回购
回购率 = 回购次数/消费人数

def purchase_back(data):
    status = []
    for i in range(len(data)-1):
        # 如果本月购买过
        if data[i] == 1:
            if data[i+1] == 1:
                status.append(1)
            if data[i+1] == 0:
                status.append(0)
                
        # 如果本月未购买
        else:
            status.append(np.NaN)
    status.append(np.NaN)
    return pd.Series(status)

purchase_b = df_purchase.apply(purchase_back, axis=1)
purchase_b.columns = purchase_r.columns
(purchase_b.sum()/purchase_b.count()).plot(figsize=(8,6))
# plt.xticks(purchase_r.columns)
plt.title('回购率变化趋势')
plt.show()

从上面可以发现复购率基本在50%左右,总体波动幅度不大,说明客户忠诚度比较高,回购率在年终年末呈峰形态,可能跟商家促销活动或节日有关。

五、总结

本项目分别通过“场、货、人”三个不同的角度去分析了一家全球超市的销售、商品、用户情况,并根据分析结果给出一些利于拓展用户、提高销量和利润的方法建议。当然,这份数据集包含信息很多,还可以继续进行其它一些方面的分析,来给出更好的建议和业务决策支持。

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
青葱年少的头像青葱年少普通用户
上一篇 2023年6月12日
下一篇 2023年6月12日

相关推荐