分析框架:
一、明确需求和目的
- 对一家全球超市的四年(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 | 类别 | 子类别 | 产品名称 | 销售额 | 数量 | 折扣 | 利润 | 装运成本 | 订单优先级 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
21696 | 22861 | IN-2013-BM117857-41593 | 2013-11-15 | 2013-11-17 | 二级 | BM-117857 | Bryan Mills | 消费者 | NaN | Nowra | 新南威尔士 | 澳大利亚 | 大洋洲 | 亚太地区 | OFF-AR-3453 | 办公用品 | 艺术 | BIC Highlighters, Fluorescent | 54.513 | 3 | 0.1 | 1.773 | 10.82 | 高 |
5749 | 12597 | ES-2015-IM1505545-42098 | 2015-04-04 | 2015-04-08 | 标准级 | IM-1505545 | Ionia McGrath | 消费者 | NaN | Tourcoing | 北部-加来海峡-庇卡底 | 法国 | 西欧 | 欧洲 | OFF-ST-5702 | 办公用品 | 存储 | Rogers Lockers, Single Width | 380.970 | 2 | 0.1 | 143.910 | 58.64 | 高 |
20879 | 51267 | MG-2015-DK289584-42210 | 2015-07-25 | 2015-07-28 | 一级 | DK-289584 | Dana Kaydos | 消费者 | NaN | Ulan Bator | 乌兰巴托 | 蒙古 | 东亚 | 亚太地区 | FUR-FU-3027 | 家具 | 用具 | Advantus Door Stop, Black | 45.030 | 1 | 0.0 | 9.450 | 11.58 | 高 |
23683 | 30075 | IN-2015-TN2104027-42136 | 2015-05-12 | 2015-05-15 | 一级 | TN-2104027 | Tanja Norvell | 家庭办公室 | NaN | Shangqiu | 河南省 | 中国 | 东亚 | 亚太地区 | OFF-AR-3502 | 办公用品 | 艺术 | Binney & Smith Sketch Pad, Water Color | 99.000 | 2 | 0.0 | 13.860 | 9.15 | 高 |
46704 | 12192 | ES-2014-AG10675120-41731 | 2014-04-02 | 2014-04-07 | 标准级 | AG-10675120 | Anna Gayman | 消费者 | NaN | Barcelona | 加泰罗尼亚 | 西班牙 | 南欧 | 欧洲 | OFF-BI-3290 | 办公用品 | 装订机 | Avery Hole Reinforcements, Durable | 23.640 | 4 | 0.0 | 8.400 | 1.51 | 媒介 |
# 查看数据行列数
data.shape
>(51290, 24)
# 查看数据的分布概况
data.describe()
行 ID | 邮政编码 | 销售额 | 数量 | 折扣 | 利润 | 装运成本 | |
---|---|---|---|---|---|---|---|
count | 51290.00000 | 9994.000000 | 51290.000000 | 51290.000000 | 51290.000000 | 51290.000000 | 51290.000000 |
mean | 25645.50000 | 55190.379428 | 246.490581 | 3.476545 | 0.142908 | 28.610982 | 26.478567 |
std | 14806.29199 | 32063.693350 | 487.565361 | 2.278766 | 0.212280 | 174.340972 | 57.251373 |
min | 1.00000 | 1040.000000 | 0.444000 | 1.000000 | 0.000000 | -6599.978000 | 1.002000 |
25% | 12823.25000 | 23223.000000 | 30.758625 | 2.000000 | 0.000000 | 0.000000 | 2.610000 |
50% | 25645.50000 | 56430.500000 | 85.053000 | 3.000000 | 0.000000 | 9.240000 | 7.790000 |
75% | 38467.75000 | 90008.000000 | 251.053200 | 5.000000 | 0.200000 | 36.810000 | 24.450000 |
max | 51290.00000 | 99301.000000 | 22638.480000 | 14.000000 | 0.850000 | 8399.976000 | 933.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()
Year | Month | |
---|---|---|
0 | 2014 | 2014-11-01 |
1 | 2014 | 2014-02-01 |
2 | 2014 | 2014-10-01 |
3 | 2014 | 2014-01-01 |
4 | 2014 | 2014-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 | |
---|---|---|---|---|---|---|---|
count | 51290.00000 | 51290.000000 | 51290.000000 | 51290.000000 | 51290.000000 | 51290.000000 | 51290.000000 |
mean | 25645.50000 | 246.490581 | 3.476545 | 0.142908 | 28.610982 | 26.478567 | 2013.777208 |
std | 14806.29199 | 487.565361 | 2.278766 | 0.212280 | 174.340972 | 57.251373 | 1.098931 |
min | 1.00000 | 0.444000 | 1.000000 | 0.000000 | -6599.978000 | 1.002000 | 2012.000000 |
25% | 12823.25000 | 30.758625 | 2.000000 | 0.000000 | 0.000000 | 2.610000 | 2013.000000 |
50% | 25645.50000 | 85.053000 | 3.000000 | 0.000000 | 9.240000 | 7.790000 | 2014.000000 |
75% | 38467.75000 | 251.053200 | 5.000000 | 0.200000 | 36.810000 | 24.450000 | 2015.000000 |
max | 51290.00000 | 22638.480000 | 14.000000 | 0.850000 | 8399.976000 | 933.570000 | 2015.000000 |
从上面展示的结果可以确定没有明显的异常值,因此不需要进行处理。
3.6 重复值处理
# 查看是否存在完全重复的行:
data.duplicated().sum()
0
0说明没有完全重复的记录,因此不需要进行处理
四、数据分析
4.1 整体销售情况分析
首先构造整体销售情况的数据子集:
- 包含:订购日期、年份、月份、销售额、销量、利润
sales_data = data[['订购日期','Year','Month','销售额','数量','利润']]
sales_data.head()
订购日期 | Year | Month | 销售额 | 数量 | 利润 | |
---|---|---|---|---|---|---|
0 | 2014-11-11 | 2014 | 2014-11-01 | 221.980 | 2 | 62.1544 |
1 | 2014-02-05 | 2014 | 2014-02-01 | 3709.395 | 9 | -288.7650 |
2 | 2014-10-17 | 2014 | 2014-10-01 | 5175.171 | 9 | 919.9710 |
3 | 2014-01-28 | 2014 | 2014-01-01 | 2892.510 | 5 | -96.5400 |
4 | 2014-11-05 | 2014 | 2014-11-01 | 2832.960 | 8 | 311.5200 |
按照年、月对销售数据子集进行分组求和统计:
sales_year_grouped = sales_data.groupby(by=['Month']).agg('sum')
sales_year_grouped.sample(5)
Year | 销售额 | 数量 | 利润 | |
---|---|---|---|---|
Month | ||||
2014-10-01 | 2215400 | 293406.64288 | 3883 | 42433.22258 |
2012-01-01 | 871196 | 98898.48886 | 1463 | 8321.80096 |
2014-04-01 | 1580990 | 177821.31684 | 2688 | 19462.03844 |
2012-08-01 | 1740380 | 208063.28372 | 3020 | 26452.99742 |
2014-12-01 | 3226428 | 405454.37802 | 5694 | 50202.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
Month | Year | 销售额 | 数量 | 利润 | |
---|---|---|---|---|---|
0 | 2015-01-01 | 1849770 | 241268.55566 | 3122 | 28001.38626 |
1 | 2015-02-01 | 1523340 | 184837.35556 | 2482 | 19751.69996 |
2 | 2015-03-01 | 2152020 | 263100.77262 | 3722 | 37357.26052 |
3 | 2015-04-01 | 2117765 | 242771.86130 | 3594 | 23782.30120 |
4 | 2015-05-01 | 2587260 | 288401.04614 | 4300 | 33953.55774 |
5 | 2015-06-01 | 3522220 | 401814.06310 | 6009 | 43778.60280 |
6 | 2015-07-01 | 2190305 | 258705.68048 | 3637 | 28035.87258 |
7 | 2015-08-01 | 3375125 | 456619.94236 | 5824 | 53542.89496 |
8 | 2015-09-01 | 4066270 | 481157.24370 | 6837 | 67979.45110 |
9 | 2015-10-01 | 3276390 | 422766.62916 | 5876 | 58209.83476 |
10 | 2015-11-01 | 4326205 | 555279.02700 | 7706 | 62856.58790 |
11 | 2015-12-01 | 4338295 | 503143.69348 | 7513 | 46916.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]