超市零售数据分析案例(粗浅、易懂型)(RFM模型)

数据源: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模型划分,并没有统一标准,按照业务情况自行定义。

金额用净利润算更准,如果能拿到数据。

就到这里吧,这数据有点假。

  • 24
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值