数据源:Superstore Dataset (kaggle.com)
一个超市2011到2014年销售数据,51290 rows × 24 columns,简单看看,本文不涉及算法;
需要的知识:numpy,pandas,pyecharts,seaborn
需要明白:数据是一个企业的核心,即便很多公开的数据,很多都是被“处理”过的,本文将带着揭露。
一、数据读取和处理
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
pd.set_option('display.max_columns', 40)
np.set_printoptions(suppress=True)
# df = pd.read_csv("C:/Users/Administrator/Downloads/superstore_dataset2011-2015.csv")
# 编码格式有问题,就那几个挨个试一下
df = pd.read_csv("C:/Users/Administrator/Downloads/superstore_dataset2011-2015.csv",encoding='ISO-8859-1')
df
长这样,51290 rows × 24 columns,字段基本上一眼看过去就秒懂,其中sales是销售额,Quantity是销售量,Profit是净利润;成本,别想着计算了,你根本没拿到正确和所需要的数据。
就一个邮政编码有缺失,还不错
发现好多列名还有空格,其中还有带横杠,受不了,加个下划线。
# 列明处理,好多空格
df.columns = df.columns.map(lambda x:x.replace(' ','_'))
1.2时间的处理
df['Ship_Date'] = pd.to_datetime(df['Ship_Date'])
df['Order_Date'] = pd.to_datetime(df['Order_Date'])
# 新增
df['month'] = df['Order_Date'].dt.month
df['year']= df['Order_Date'].dt.year
df['day_of_week'] = df['Order_Date'].dt.dayofweek
df['season'] = df['Order_Date'].dt.quarter
1.3销售情况的查看
增加一个单价
df['unit_price'] = df['Sales']/df['Quantity']
df.head()
有商品的净利润为负的:
而且占比还不少啊!1.3W/5.12W
二、查看经营情况
groups = df.groupby(['year','month'])[['Sales','Profit','Quantity']].sum()
# 净利润率
groups['profit_rate']=groups['Profit']/groups['Sales']
groups
2.1画图看看
plt.figure(figsize=(10,18))
plt.subplot(3,1,1)
sns.barplot(x=groups.index.values,y=groups.iloc[:,0])
plt.xticks(rotation=90)
plt.subplot(3,1,2)
sns.barplot(x=groups.index.values,y=groups.iloc[:,1])
plt.xticks(rotation=90)
plt.subplot(3,1,3)
sns.barplot(x=groups.index.values,y=groups.iloc[:,2])
plt.xticks(rotation=90)
plt.tight_layout()
有点放不下,一定是打开的方式不对
2.2换个工具
from pyecharts.charts import Bar,Line,Grid,Page,Pie
from pyecharts import options as opts
from pyecharts.charts import Map
# 绘制柱状图
bar =Bar()
bar.add_xaxis(groups.index.tolist())
bar.add_yaxis('销售额',[(round(x/1000,1)) for x in groups.iloc[:,0].values.tolist()])
bar.add_yaxis('净利润',[(round(x/1000,1)) for x in groups.iloc[:,1].values.tolist()])
bar.add_yaxis('销量',[(round(x/1000,2)) for x in groups.iloc[:,2].values.tolist()])
bar.set_global_opts(title_opts=opts.TitleOpts(title='销售额数据',pos_left='40%'),
legend_opts=opts.LegendOpts(type_='plain',is_show=True,pos_right='20%'),
datazoom_opts=opts.DataZoomOpts(type_='inside'),
xaxis_opts=opts.AxisOpts(name_rotate=90,split_number=4,name='月份',splitline_opts=opts.SplitLineOpts(is_show=False))
,yaxis_opts=opts.AxisOpts(splitline_opts=opts.SplitLineOpts(is_show=True))
)
bar.set_series_opts(label_opts=opts.LabelOpts(position='top'))
bar.render_notebook()
动态图,可以放大,设置要花点时间。
观察到有国家名,花个地图看看
group_map= df.groupby('Country')[['Sales','Profit','Quantity']].sum()
group_map
map_=Map() # 加_避免重名
map_.add('销售额',data_pair=list(zip(group_map.index.values,group_map.iloc[:,0].values)),maptype='world')
map_.add('净利润',data_pair=list(zip(group_map.index.values,group_map.iloc[:,1].values)),maptype='world')
map_.set_series_opts(label_opts=opts.LabelOpts(is_show=False),# 不显示国家名,太多会乱
showLegendSymbol=False # 不显示国家小点点
)
map_.set_global_opts(title_opts=opts.TitleOpts(title="销售情况", is_show=False,
pos_left='40%'), # 调整title位置
legend_opts=opts.LegendOpts(is_show=False,pos_left='10%'),
visualmap_opts=opts.VisualMapOpts(max_=group_map.iloc[:,0].values.max(),
min_=group_map.iloc[:,0].values.min(),
is_piecewise=True,
# 分段添加图例注释
pieces=[{"max": 100000, "min": 1, "label": "1-10K"},
{"max": 500000, "min": 100001, "label": "10-50K"},
{"max": 1000000, "min": 500001, "label": "50K-100k"},
{"max": 1500000, "min": 1000001, "label": "100K-150k"},
{"max": 2000000, "min": 1500001, "label": "150K-200k"},
{"max": 3000000, "min": 2000001, "label": "200K-300k"},
{"max": 9999999, "min": 3000001, "label": "300k+"},
]
)
)
map_.render_notebook()
可以尝试把一些图放在一块,不过地图太大,一般不太好搞;
grid = Grid()
grid.add(bar,opts.global_options.GridOpts(pos_left='55%'))
grid.add(map_,opts.global_options.GridOpts(pos_left=10))
grid.render_notebook()
市场占据情况:
groups_seg = df.groupby('Segment')[['Sales','Profit','Quantity']].sum()
groups_cate = df.groupby('Category')[['Sales','Profit','Quantity']].sum()
groups_sub_cate = df.groupby('Sub-Category')[['Sales','Profit','Quantity']].sum()
pie = Pie()
pie.add('顾客分类',list(zip(groups_seg.index.tolist(),groups_seg.iloc[:,0].tolist())),
radius=[30,80],center=[200, 200])
pie.add('产品分类',list(zip(groups_cate.index.tolist(),groups_cate.iloc[:,0].tolist())),
radius=[30,80],center=[200, 400])
pie.add('进一步分类',list(zip(groups_sub_cate.index.tolist(),groups_sub_cate.iloc[:,0].tolist())),
radius=[20,100],center=[550, 300],rosetype='radius')
pie.set_global_opts(title_opts=opts.TitleOpts(title='销售额市场划分',pos_left='40%')
,legend_opts=opts.LegendOpts(is_show=False))
pie.render_notebook()
可以把柱状图、折线图、环形图、地图都用grid弄到一块,就像一些BI工具一样,手动实现该操作,不过要调整的参数比较多。
三、分析情况
正常来说,应该套用一些常用模型,对经营情况进行详细分析,比如RARRA,AARRR之类的,本文讲一下RFM。
盲目分析没有头绪滴,一般以问--答的方式,查看数据比较好,找关键的问题
1.公司经营状况
temp = df.groupby('year')[['Sales','Profit','Quantity']].sum()
temp['profit_rate']=temp['Profit']/temp['Sales']
def bar_show(group_data,title):
bar =Bar()
bar.add_xaxis(group_data.index.tolist())
bar.add_yaxis('销售额',[(round(x/1000,1)) for x in group_data.iloc[:,0].values.tolist()])
bar.add_yaxis('净利润',[(round(x/1000,1)) for x in group_data.iloc[:,1].values.tolist()])
bar.add_yaxis('销量',[(round(x/1000,2)) for x in group_data.iloc[:,2].values.tolist()])
bar.add_yaxis('利润率',[(round(x*100,2)) for x in group_data.iloc[:,3].values.tolist()])
bar.set_global_opts(title_opts=opts.TitleOpts(title=title,pos_left='30%'),
legend_opts=opts.LegendOpts(type_='plain',is_show=True,pos_right='20%'),
datazoom_opts=opts.DataZoomOpts(type_='inside'),
xaxis_opts=opts.AxisOpts(name_rotate=90,split_number=4,name='月份',splitline_opts=opts.SplitLineOpts(is_show=False))
,yaxis_opts=opts.AxisOpts(splitline_opts=opts.SplitLineOpts(is_show=True))
)
bar.set_series_opts(label_opts=opts.LabelOpts(position='top'))
return bar.render_notebook()
bar_show(temp,'逐年数据')
# 逐年上升,蒸蒸日上
3.2 客户群体粗分
3.3我们的主要业务覆盖情况
3.4 销售规律,受到季节、月份影响
temp = df.groupby('month')[['Sales','Profit','Quantity']].sum()
temp['profit_rate']=temp['Profit']/temp['Sales']
bar_show(temp,'月份')
# 结合前面的每年每月的柱状图看
# 每年的11,12月都很高
# 每年的1,2,7月都相对较低
# 前半年总体都是比后半年要低
# 公布公开出来的数据,有很明显的规律
# 很难不让人怀疑,这个数据有"造"的痕迹
季节情况,同月份基本差不多的意思
3.4 优势地域
pie,map中查看,不水文章了
3.5 我们的优势产品是
# 即卖得数量高、利润率高的
def bar_show_desc(group_data,title,sub1='销售额',sub2='净利润',sub3='销量'):
"每组数据,换一组x轴,降序排列"
bar =Bar()
# 第一个图
data_1 = group_data.iloc[:,0]
data_1=data_1.sort_values(ascending=False)
bar.add_xaxis(data_1.index.tolist())
bar.add_yaxis(sub1,data_1.values.tolist())
# 第二个图
data_2 = group_data.iloc[:,1]
data_2=data_2.sort_values(ascending=False)
bar.add_xaxis(data_2.index.tolist())
bar.add_yaxis(sub2,data_2.values.tolist())
# 第二个图
data_3 = group_data.iloc[:,2]
data_3=data_3.sort_values(ascending=False)
bar.add_xaxis(data_3.index.tolist())
bar.add_yaxis(sub3,data_3.values.tolist())
bar.set_global_opts(title_opts=opts.TitleOpts(title=title,pos_left='30%'),
legend_opts=opts.LegendOpts(type_='plain',is_show=True,pos_right='20%'),
datazoom_opts=opts.DataZoomOpts(type_='inside'),
xaxis_opts=opts.AxisOpts(name_rotate=90,split_number=4,name='月份',splitline_opts=opts.SplitLineOpts(is_show=False))
,yaxis_opts=opts.AxisOpts(splitline_opts=opts.SplitLineOpts(is_show=True))
)
bar.set_series_opts(label_opts=opts.LabelOpts(position='top'))
return bar.render_notebook()
temp = df.groupby(['Sub-Category'])[['Sales','Profit','Quantity']].sum()
temp['profit_rate']=temp['Profit']/temp['Sales']
bar_show_desc(temp,'优势产品分析')
binder是粘合剂之类的东西
3.6 用户划分
这块稍微麻烦点,就是看我们的用户是不是忠实客户,买得多、买得频繁、流失情况、拉新情况,即RFM模型,不过这个数据“修改”得有点过头了,没太大意义。当然,也是做到这里才发现这个情况。
3.6.1 复购率
# 年月的组合
from datetime import *
# df['year_month_2'] = pd.to_datetime(df['Order_Date'],format='%Y-%m')
df['year_month'] = df['Order_Date'].dt.strftime('%Y-%m')
# 复购率=当月买至少两次的
gg = df.groupby(['year_month','Customer_ID']).count().reset_index()
gg1 = gg.groupby('year_month')['Customer_ID'].count() # 当月购买的不同人数
gg2 = gg[gg['Order_ID']>1].groupby('year_month')['Customer_ID'].count() # 当月多次购买人数
# 合并
merged = pd.concat([gg1,gg2],axis=1)
merged.columns=['当月消费人数','当月多次消费人数']
merged['复购率']=(merged['当月多次消费人数']/merged['当月消费人数']).apply(lambda x: format(x,'.2f')) #两位小数百分数
下面这个图,一看就十分明显:
合着就是那些人,买来买去...
3.6.2 回购率
回购率:这个月买了,下个月或者下个季度之类,还会购买,除以当月购买的去重人数
此处时间要处理一下:
# 讲道理,哪有客户每个月都买,比例如此之高,这数据明显有"修改痕迹"
# 当那我们将回购率,设为当月买了,近期还会再买
# 解除多重索引
hh1 = df.groupby(['Customer_ID','year_month']).count().reset_index()[['Customer_ID','year_month']]
# 必须从字符串转为datetime64
hh1['year_month']=pd.to_datetime(hh1['year_month'],format='%Y-%m')
# 两表连接算时间差
hh2 = pd.merge(hh1,hh1,on='Customer_ID',how='left')
hh2['diff']=hh2['year_month_y']-hh2['year_month_x']
# 当月购买去重的不同人数
hh3 = hh2.groupby(['year_month_x','Customer_ID']).count().reset_index()['year_month_x'].value_counts()
# 这里算的3个月的间隔
hh4 = hh2[hh2['diff']>timedelta(days=90)].groupby(['year_month_x','Customer_ID']).count().reset_index()['year_month_x'].value_counts()
merged2 = pd.concat([hh3,hh4],axis=1)
merged2.columns=['当月购买人数','次月回购人数']
merged2['回购率']=(merged2['次月回购人数']/merged2['当月购买人数']).apply(lambda x: format(x,'.4f'))
# 解决X轴时间太长的问题,转为年-月
new_index = [x.strftime('%Y-%m') for x in merged2.index]
merged2['new'] = new_index
merged3 = merged2.set_index(keys='new')
# 画图
bar_show_buy(merged3,title='回购情况',sub1='去重消费人次',sub2='多次消费人次',sub3='回购率')
3.7 RFM的演示
R,Rencency
F,Frequency
M,Monetary
方法1:简单点,直接用分位数切分
比如M累计购买金额,在数值范围前20%的为1,最后80%-100%的,为5这样;
比如R最近购买时间间隔,按天算,比较小的前20%给5,数字比较大的给1;
# 切出一部分字段,其实不切都行
small_df = df.loc[:,['Customer_ID','Order_ID','Order_Date','Sales','Profit','month','year','year_month']]
# 自定个时间,必须项
final_date = datetime(2015,1,1)
# distance---距离给定日期的天数
small_df['distance'] = (final_date-small_df['Order_Date']).dt.days
small_df
# 最近购买间隔R
aa_r = small_df.groupby('Customer_ID')['distance'].min()
# 注意这里labels要反着来
temp_a = pd.qcut(aa_r,q=5,labels=[5,4,3,2,1])
temp_a
同理把另外2个搞出来
# F
aa_f = small_df.groupby('Customer_ID')['Order_ID'].count()
temp_b = pd.qcut(aa_f,q=5,labels=list(range(1,6)))
# small_df.query('Order_Date>"2013-12-31"').groupby('Customer_ID')['Order_ID'].count()
# M 划了10等
aa_m = small_df.groupby('Customer_ID')['Sales'].sum()
temp_c = pd.qcut(aa_m,q=10,labels=list(range(1,11)))
# 合并
result = pd.merge(temp_a,temp_b,left_index=True,right_index=True)
result = pd.merge(result,temp_c,left_index=True,right_index=True)
result['score'] = result.sum(axis=1)
result
再根据score划分。
方法2--精确点,一般先自己定义好区间,分很多bins
方法3:2**3=8,8个类型划分
大概如下,类型就不详细写了,原理是根据金额、购买频率、最近购买间隔,将用户划分为好坏,这样三个标准会产生8种分类,依次对用户进行划分,是比较粗糙的方法。
RFM其实也只是提供了一个用户画像的方法思路,其实我们可以根据实际情况,进行修改,比如有的大客户,一个人的消费能占业务量的相当比例,这不妥妥的爹。有的生意不做回头客,比如某多多,很多人卖完一波产品,后面下架了,根本不用管留存、复购这种情况。
# 是否大于均值,用分位数也可
df['r_big_avg'] = (df['r'] >df['r'].mean(axis=0) )*1
df['f_big_avg'] = (df['f'] >df['f'].mean(axis=0) )*1
df['m_big_avg'] = (df['m'] >df['m'].mean(axis=0) )*1
df['need'] = df['r_big_avg']*100+df['f_big_avg']*10+df['m_big_avg']*1
def map_label(series):
if x ==111:
label='他是爹'
if x ==0:
label='流失客户'
return label
df['label'] = df['need'].map(map_label)
不过RFM模型划分,并没有统一标准,按照业务情况自行定义。
金额用净利润算更准,如果能拿到数据。
就到这里吧,这数据有点假。