📮 求内推
👩 个人简介:听障人,96年,计算机专业(本科),从事数据分析工作
🛠 语言与工具:包括但不限于Python、SQL、tableaubi、finebi、powerbi。
💻 期望岗位:数据分析、数据运营、报表开发等(hc偏技术方向)
📍 期望城市:杭州
用户消费行为在各行各业如电商、物流、医疗等非常重要,因此是在数据分析工作中经常接触到,本案例基于某电商2021年12个月的用户消费数据。
数据集:见文章顶部,可直接下载。
编程语言:Python
Python IDE:jupyer notebook
一、导入模块
import pandas as pd
from datetime import datetime as dt
import matplotlib.pyplot as plt
import numpy as np
二、数据基本情况
在正式开始分析前,对手里的数据要有一个基本的理解,例如数据体量,时间范围、字段,数据类型等。这个步骤万万不能省略。(悄悄跟你说,我刚开始那会儿习惯打开excel浏览数据,有没有人跟我一样- -一定要改掉噢...)
1、预览数据
# 读取数据
df = pd.read_excel(r'D:\pythonbag\Datas\order2021.xlsx')
# 数据预览
df.head()

通过预览了解下有哪些字段,这个数据集字段的字面意思上好理解,不过字段尽量用英文(得养成好习惯),稍后会修改列名。
2、数据信息
print(df.info())
# 数据量约10.4w,其中渠道编号有缺失值
# 订单顺序编号的数据类型需要改成object

通过info方法发现有几个小问题,订单顺序编号应该是object,渠道编号有缺失值,这些问题稍后处理。
3、描述统计信息
df.describe().round(2)
# 发现payAmount的min值为负数,有异常值,需要处理

通过describe统计指标可以掌握数据集的分布、集中趋势、离散程度等情况。
count:非缺失值的计数;mean:平均数;min:最小值;25%:第一个四分位数,即25%的数据小于或等于该值;max:最大值;std:标准差。
注意:describe只对数值、时间数据类型起到作用。
通过观察发现付款金额最小值为负数,此处应该为0才对,这个问题稍后处理。
三、数据处理
前面所发现的几个问题都在这个环节进行处理。
1、修改列名
df.columns=['id','orderID','userID','goodsID','orderAmount','payAmount','channelID','platformName','orderDate','payDate','isRefund']
df.head()
来,跟我一起回忆下。修改列名的方法还有哪些...
df.rename(index={...},columns={...})
2、修改数据类型
df['id'] = df['id'].astype('object')
df.info()

3、处理缺失值
df.isnull().sum()
# channelID有8个缺失值,可以直接删除空白所在的记录数据。
df.isnull()返回一个含有布尔值的对象。结合.sum()方法:求出每一列的缺失值数量。(也可以搭配其他方法例如.any()、.count()等等)

# 相当于df.dropna(axis=0,how=any,inplace=True)
df.dropna(inplace=True)
df.isnull().sum()
然后直接删除缺失值所在行,再检查是否还存在缺失值。

4、处理异常值
# 筛选订单金额小于0的数据,用来提取id
ycorder = df.query('payAmount < 0')
ycorder

# 提取异常值数据的索引并删除
ycindex = ycorder.index
print(ycindex) # 输出index的列表
df.drop(ycindex,inplace=True) # 默认删除行即axis=0
# 确认下是否删除成功
ycorder = df.query('payAmount < 0')
print(ycorder) # empty
df.shape

我们来梳理下思路,df.drop(index=[...])是按照index值删除数据块,所以我们想要获取需要删除数据的index是不是还得先筛选这些数据然后才能进行删除。
步骤:筛选需要删除的数据,并获取该数据块的index,再进行删除。
5、处理重复值
# 该代码是指当两条记录所有数据都相等时才是重复的
print(df.duplicated().sum())
# 可以指定某一列查看是否有重复
# print(df[columnname].duplicated().sum())
# 删除重复项
# df.drop_duplicates(colname,keep=['frise'|'last'],inplace=[True|False])
ok,该数据集没有重复值。
6、新增字段(列)
# 解析日期
# 新增paydate1列,只提取日期部分
df['payDate1'] = pd.to_datetime(df['payDate'],format='%Y-%m-%d').dt.date
# series.astype('datetime64[M]')可能会失效,在astype前加个values就好了
df['Month'] = df['payDate1'].values.astype('datetime64[M]') # 控制日期只精确到月份
df['payDate1'] = df['payDate1'].values.astype('datetime64[D]') # 虽然是精确到日,但是也会改变数据类型,把object改成datetime64
df.head()
如果数据集没有你想要的字段,可以自行增加计算字段。
在这个案例中我们要按月进行分析,所以需要提取日期部分,并且控制精确度,方便后续分组汇总计算。

7、筛选数据
数据处理工作基本上结束,最后一个步骤就是筛选需要参与数据分析的数据集。在本案例中,我们只要没有退货的数据集,所以需要剔除已退回的数据。
# 筛选退款为否的数据
ndf = df.query('isRefund == "否"')
ndf.describe()
ndf.info()
# 注意:这个数据框作为后面分析的基础

最后再info确认一遍,一共有90763行数据,数据类型、缺失值等都没问题了。
数据清洗工作到此结束啦~别看就这么点工作量,其实如果在真正的生产环境中,实际数据比想象的要复杂多了。听说,问题明确好后再到处收集数据和清洗数据等,光是这些工作其实在数据分析工作中占到至少50%以上。你想呀,如果数据错了,后面数据分析还有意义吗?都是徒劳的。另外如果你练习多了会发现数据分析基本上都是那几套方法,熟悉起来后上手比较快,只是解决问题稍微费时费脑子罢了。所以数据清洗环节是非常重要的,不可掉以轻心!
四、数据分析
1、用户消费特征
1.1.用户整体消费趋势分析
整体分析,可以按时间、地区、门店等维度统计,在这里是按月份统计商品购买次数、消费金额、消费次数、消费人数。
# 设定绘图窗口尺寸
fig,axes = plt.subplots(2,2,figsize=(10,8),dpi=100) # sharex=True,sharey=False 共享x轴,不共享y轴
# 设置中文字体为黑体,否则会报错
plt.rcParams['font.sans-serif'] = ['SimHei']
month_label = ['1月','2月','3月','4月','5月','6月','7月','8月','9月','10月','11月','12月']
axes[0,0].plot(month_label,ndf.groupby(by='Month')['goodsID'].count().values)
axes[0,0].set_title('商品购买次数')
axes[0,1].plot(month_label,ndf.groupby(by='Month')['payAmount'].sum().values)
axes[0,1].set_title('每月消费金额')
# # 由于每条记录为一个订单一个商品,所以消费次数的结果跟购买次数是一样的
axes[1,0].plot(month_label,ndf.groupby(by='Month')['userID'].count().values)
axes[1,0].set_title('每月消费次数')
axes[1,1].plot(month_label,ndf.groupby(by='Month')['userID'].apply(lambda x : len(x.drop_duplicates())).values) # 按照每月根据userID去重再统计
axes[1,1].set_title('每月消费人数')
fig.suptitle('用户消费特征分析',fontsize=16)
fig.subplots_adjust(hspace=0.3) # hspace调整子图之间的间距(高度),wspace则调整宽度

分析结果:
前四个月销量比较低,从四月份到六月份呈上升,七月份到十月份呈稍微的下滑,之后又上升。总体来说整个销量呈上升的趋势。
四个图标呈近于相同的变化趋势,初步估计四月份可能有大促销活动,或者是产品属性的原因,比如和季节有关。
1.2.用户个体消费趋势分析
个体分析,一般是按userID维度统计分析消费金额、消费次数、贡献率等。
# 因为数据源没有订单次数字段,按userID统计时计算订单次数应当使用count而不是sum
user_grouper = ndf.groupby(by='userID').agg({'orderID':'count', 'orderAmount':'sum', 'payAmount':'sum'})
user_grouper = user_grouper.rename(columns = {'orderID':'orderCount'})
user_grouper.reset_index().describe().round(2)

用户平均购买数量为1.27,中位数是1,最大值是7,说明小部分用户消费频次略高。
用户平均消费1487元,而中位值只有805元,最大值83270元,说明小部分用户消费金额较高(呈右偏分布)。
- 用户消费次数与消费金额的散点图
# 用户消费次数与消费金额的散点图
plt.rcParams['font.sans-serif']=['KaiTi']
plt.scatter(user_grouper['payAmount'],user_grouper['orderCount'],label='订单')
plt.title('用户消费次数与消费金额的散点图')
plt.xlabel('消费金额')
plt.ylabel('消费次数')
plt.legend()
plt.show()

分析:
由图可知,消费金额和消费次数并不呈线性关系。大部分用户在低价商品上购买比较多(包括薅羊毛用户),高价商品的购买次数可能只有一两三次。

- 用户消费分布图
# 通过直方图和密度看付款金额的分布数据
plt.hist(user_grouper['payAmount'],bins=60,label='付款金额')
plt.title('付款金额分布')
plt.xlabel('付款金额')
plt.ylabel('密度')
plt.legend()
plt.show()

分析:
付款金额分布图呈左偏分布,有极大值。
表明大多数用户在小儿金额范围进行多次小额付款。
- 用户累计消费金额占比分析
# 计算累计消费金额前时,一定要按金额升序排序,因此要加上sort_values()升序排序。呈现出来的才是一条弧线
user_cumsum=ndf.groupby(by='userID').payAmount.sum().sort_values().reset_index()
user_cumsum

# 新增一列payCumsum,计算付款金额累加的
user_cumsum['payCumsum'] = user_cumsum['payAmount'].cumsum()
user_cumsum.round(2)

# 新增一列prop,计算贡献率
amount_total = user_cumsum['payCumsum'].max() # 相当于user_cumsum['payAmount'].sum()
user_cumsum['prop'] = user_cumsum['payCumsum'].map(lambda x : x/amount_total) # 表示前几名用户的总贡献率
user_cumsum

user_cumsum['prop'].plot()
plt.title('用户累计消费金额的占比分析')
plt.xlabel('用户数')
plt.ylabel('贡献率')

分析:
前50%贡献率由约85%用户(约6万用户)创造的,后50%贡献率由约15%用户(约1万用户)创造的,表明一小部分用户(20%)产生了大部分的结果(80%),也符合二八定律。
1.3.用户消费周期分析
1.3.1.用户购买周期
购买周期是指,用户多长时间会购买一次,即购买频率。
# shift() 向下或向右移动到一定位置,默认值axis=0向下
data1 = pd.DataFrame({
'a':[0,5,2,9,4],
'b':[0,5,2,9,4]
})
data1['shift_b'] = data1['b'].shift()
data1['c'] = data1['a']-data1['shift_b'] # 做差
data1

这是shift()的使用方法,简单了解下即可。如果想把NaN改成0,可以这样写:
# num是指移动位置的个数,正数为向后移动,负数则向前移动
# fill_value指空值的填充值
data1['shift_b'] = data1['b'].shift(num,fill_value=0)
在需要移动数据时,shift()方法非常有用,比如想要最近七天的平均值,或者需要前一天的数据作为新的一列等。
本案例中,计算购买周期需要用到当前订单时间和上一次订单时间做差。
order_diff = ndf.groupby(by='userID').apply(lambda x : abs(x['payDate1']-x['payDate1'].shift())) # 当前订单日期-上一次订单日期
order_diff.head()

NaT:是指当前订单日期-NaT=NaT。说明这些用户只消费了一次。
order_diff.describe()

注意:NaT值不会参与describe()计算。
所以上面的数据集是指消费了两次以上的。中位数是90天而平均值是106天,说明50%以上的购买周期相当长,才会拉高了平均值(呈右偏分布)。
# order_diff/np.timedelta64(1,'D')将timedelta转换int型的天数,主要提取天数
# bins=30指将数据点分成大小相等的30个箱体,一般来说数值越大,直方图会更加精细,更好的反馈数分布的情况
(order_diff/np.timedelta64(1,'D')).hist(bins=30)
plt.title('用户购买周期')
plt.xlabel('购买周期')
plt.ylabel('用户人数')

分析:
如果主要产品是消耗品,购买周期会更短。像家电之类产品购买周期会更长。不同产品会有不同购买周期。通过该直方图推测该产品可能属于消耗品或者使用频率高的其他产品。
平均购买周期为106天,绝大多数用户的购买周期低于150天(75%用户是157days),150天以上用户(不积极用户)则占少数。
对于不积极用户可以采用在消费后3天之内通过短信形式回访会赠送优惠券的方式,刺激购买欲。
1.3.2.用户生命周期
生命周期是指首次购买距最后一次购买时间的时长。
user_life = df.groupby('userID')['payDate1'].agg(['min','max'])
# user_life['max'] = user_life['max'].values.astype('datetime64[D]')
# user_life['min'] = user_life['min'].values.astype('datetime64[D]')
user_life.head()
这个计算方法忽略了当天是否多次消费,即当天有多次消费视为消费一次。

plt.figure(figsize=(2,2),dpi=200)
(user_life['max'] == user_life['min']).value_counts().plot.pie(autopct='%.1f%%',labels=['仅消费一次','多次消费'],fontsize=5) # 统计是与否的个数,把结果格式化一位小数
plt.legend(['仅消费一次','多次消费'],prop={'size':4})
plt.title('仅消费一次和多次消费的比例',size=8)
value_counts()统计指定列中每个值的出现次数,返回series对象。
user_life['max'] == user_life['min']返回True或False,然后用value_counts去统计T和F的次数,T指仅消费一次,而F则多次消费。
这个方法太香了,后面还会用到,你细细品吧~

有74.2%用户只消费一次,可能因为运营不到位,都是低质量用户。
接下来计算user_life['max']和user_life['min']的差值,再describe看看,验证下。
(user_life['max']-user_life['min']).describe()

用户生命周期平均为33天,但是min、25%、50%都是0,再次验证了大多数用户只消费一次。
75%后有很多较长生命周期的用户,属于核心用户,需要着重维持。
plt.figure(figsize=(7,3),dpi=200)
# 绘制第一张图
# 所有用户的生命周期
plt.subplot(121)
all_ul=(user_life['max']-user_life['min'])/np.timedelta64(1,'D')
all_ul.hist(bins=30)
plt.xlabel('生命周期')
plt.ylabel('用户人数')
plt.title('所有用户的生命周期分布图')
# 绘制第二张图
# 多次消费用户的生命周期
plt.subplot(122)
# ((user_life['max']-user_life['min'])/np.timedelta64(1,'D')).apply(lambda x : x>0).hist(bins=30)
ul = all_ul[all_ul>0] # 把一次消费的用户过滤掉
ul.hist(bins=30)
plt.xlabel('生命周期')
plt.ylabel('用户人数')
plt.title('多次消费的用户的生命周期')

左图有较严重极值干扰,影响分析,故需要把一次消费的用户给过滤掉。
ul[ul>0].describe()

分析:
右图是将生命周期天数为0的用户过滤掉了,最小值为1。
随着生命周期天数的增加,而用户呈现逐渐减少的趋势。
虽然说用户生命周期越长越好,但还是要结合购买频率分析。即生命周期长且购买频率高的用户才是忠实的(此处尝试使用矩阵分析法,因为是刚学到的感觉好像可以用在这里)。
1.3.3.用户购买频次与生命周期的关系
gm_df = ndf.groupby(by='userID')['orderID'].count()
# 所有用户
gm_allul_df = pd.concat([gm_df,all_ul],axis=1).dropna()
gm_allul_df.columns=['orderCount','lifeTime']
# gm_allul_df.info()
gm_allul_df.describe()

# 筛选生命周期不为0的用户
gm_ul_df = pd.concat([gm_df,ul],axis=1)
gm_ul_df.columns=['orderCount','lifeTime']
gm_ul_df = gm_allul_df[gm_allul_df>0].dropna()
gm_ul_df.describe()

plt.figure(figsize=(7,3),dpi=200)
plt.subplot(121)
plt.scatter(gm_allul_df['lifeTime'],gm_allul_df['orderCount'],s=0.5)
plt.xlabel('生命周期')
plt.ylabel('购买频次')
plt.title('所有用户的生命周期与购买频次')
plt.subplot(122)
plt.scatter(gm_ul_df['lifeTime'],gm_ul_df['orderCount'],s=0.5)
plt.xlabel('生命周期')
plt.ylabel('购买频次')
plt.title('多次消费用户的生命周期与购买频次')

分析:
两张图表看起来差不多,那就选其中之一进行分析吧。
购买频次和生命周期的相关系度可能不高,可以用矩阵分析法看看,右上角为忠实用户(维持),左上角为普通用户(延长生命周期),左下角为低质量用户,右下角为潜在用户(做优惠活动刺激购买欲)。
2、用户消费行为
2.1.首购时间分析
# 先按userID分组找出他的首购时间,然后按首购时间统计用户人数
first_df = ndf.groupby(by='userID').payDate1.min().value_counts()
first_df
plt.figure(figsize=(10,4),dpi=200)
first_df.plot()
plt.title('用户首购时间分析')
plt.ylabel('用户数')

分析:
用户量在三月份呈上升趋势,在七月份开始逐渐回落。
猜测可能在三到七月份受到产品推广活动或者价格优惠的改变的影响。
2.2.最后一次购买时间分析
# 先按userID分组找出他的最后一次购买时间,然后按首购时间统计用户数
last_df = ndf.groupby(by='userID').payDate1.max().value_counts()
last_df
plt.figure(figsize=(8,4),dpi=200)
last_df.plot()
plt.title('用户最后一次购买时间分析')
plt.ylabel('用户数')

分析:
随着时间的推移,用户量整体呈上升趋势,说明忠诚用户越来越多。
2.3.复购率
# 计算用户每月购买次数
pivoted_counts = ndf.pivot_table(index='userID',columns='Month',values='orderID',aggfunc='count').fillna(0)
# 复购:1,非复购但有消费一次:0,无消费:NaN(不参与计算)
rp_df = pivoted_counts.applymap(lambda x:1 if x>1 else 0 if x==1 else np.NaN )
rp_df.head()
pivot_table()跟Excel中的数据透视表功能差不多的,如果你掌握了Excel的数据透视表,那么pivot_table()就不在话下了。
在一定时间段内,复购率=消费两次以上的人数/消费总人数(同一天内多次消费也算,故不需要去重)。
rp_df这行代码,主要为了计算复购用户数rp_df.sum()和消费总人数rp_df.count(),值为1和0都会被包含到count计算中,然后只有1会被包含到sum计算中。

# 复购用户数:rp_df.sum()
# 消费总人数:rp_df.count()
plt.figure(figsize=(10,4),dpi=200)
(rp_df.sum()/rp_df.count()).plot() # 计算复购率,默认值axis=0
plt.title('月度复购率折线图')
plt.xlabel('月份')
plt.ylabel('复购率')

分析:
前五个月复购率呈上升,后续逐渐趋于平稳,但整体来说复购率并不高,仅仅2.75%。
2.4.回购率
# 如果有消费则1,否则0
repo_df = pivoted_counts.applymap(lambda x : 1 if x>0 else 0)
# 回购含义:在一个时间窗口内消费了,在下一个时间窗口内也消费了
# 回购用户分类:1(当月消费了,次月也消费了),0:(当月消费了,次月未消费),NaN:(当月未消费)
def purchase_back(data):
status=[] # 存储用户状态 1;0;NaN
for i in range(11):
if data[i] == 1:
if data[i+1]==1:
status.append(1)
elif data[i+1]==0:
status.append(0)
else:
status.append(np.NaN)
# 因为for一共循环了11次即status长度为11,所以循环结束后需要填充最后一列
status.append(np.NaN)
return pd.Series(status,repo_df.columns)
repo_df = repo_df.apply(purchase_back,axis=1)
repo_df.head()

plt.figure(figsize=(10,6),dpi=200)
plt.subplot(211)
(repo_df.sum()/repo_df.count()).plot(label='回购率')
(rp_df.sum()/rp_df.count()).plot(label='复购率')
plt.ylabel('百分比')
plt.title('回购率和复购率的对比图')
plt.legend(prop={'size':6})
plt.subplot(212)
plt.plot(repo_df.sum(),label='回购人数') # 默认axis=0
plt.plot(repo_df.count(),label='购买总人数') # 默认axis=0
plt.ylabel('用户数')
plt.title('回购人数与购买总人数')
plt.legend(prop={'size':6})

分析:
回购率和复购率走势是类似的,但高于复购率,一月到五月一直呈上升趋势,后面逐渐趋于平稳,在5%左右。
在五个月前无论是回购率还是复购率。均呈上升趋势,说明新用户转换为复购/回购用户需要一定时间。
结合新老用户分析,新用户的忠实度远低于老用户的忠实度。
前五个月购买总人数和回购人数逐渐拉大,说明消费一次的用户即新用户增加了许多,有可能商家在期间做了促销活动,来刺激新用户消费。
但是复购率和回购率各表现不是很好,商家需要制定营销策略来引导用户再次消费以及持续消费
对于老客户,也要做反馈活动,来增加用户粘性度。
3、用户分层
3.1.RFM模型分析
RFM模型通过一个客户的近期购买行为、购买的总体频率以及花了多少钱三个指标来描述该客户的价值状况。那么我们要拉三个字段:订单日期、订单编号和支付金额,计算每个用户的RFM后根据设定的某个属性来打标签,以便更好地进行分类和分析。
rfm_df = ndf.pivot_table(index='userID',values=['payDate1','orderID','payAmount'],aggfunc={'payDate1':'max','orderID':'count','payAmount':'sum'})
rfm_df
orderID是用来计算订单次数,payAmount用来计算金额总和,payDate1先提取最大日期即最近最后一次购买日期,再计算和在一个时间窗口最后一天的相差天数。

# 2021年是一个时间窗口,rfm_df['payDate1'].max()就是2021年12月31日
rfm_df['R'] = ((rfm_df['payDate1'].max() - rfm_df['payDate1'])/pd.Timedelta(1,'D')).astype('int')
# rfm_df['R'] = (rfm_df['payDate1'].max() - rfm_df['payDate1']).astype('int')
rfm_df = rfm_df.rename(columns={'orderID':'F','payAmount':'M'})
del rfm_df['payDate1']
rfm_df

RFM已经算出来了,接下来要设定每个属性对应的标签了。这个环节主要根据实际业务情况和经验来决定的。
比如说,打标签规则其实没有统一标准,可以灵活制定。可以这样规定,和平均值作差比1大则记1,否则记0;也可以这样规定,比平均值大则记1,否则记0,然后RFM为‘111’是重要价值客户、’110‘是一般价值客户等,以此类推,最后再根据设定好的属性来打标签(字典映射)。
def rfm_func(x):
level_df = x.map(lambda x : '1' if x>=1 else '0')
label = level_df['R'] + level_df['F'] + level_df['M'] # 例如字符串:000,100等
label_dict = {
'111':'重要价值客户',
'011':'重要保持客户',
'101':'重要发展客户',
'001':'重要挽留客户',
'110':'一般价值客户',
'010':'一般保持客户',
'100':'一般发展客户',
'000':'一般挽留客户'
}
return label_dict[label]
# 第一个apply按axis=0计算返回一个新dataframe,第二个apply按axis=1汇总计算返回新dataframe
rfm_df['Label'] = rfm_df.apply(lambda x : x-x.mean()).apply(rfm_func,axis=1)
rfm_df

# label,grouped分别是每一组的标签和数据组
for label,grouped in rfm_df.groupby(by='Label'):
plt.scatter(grouped['F'],grouped['M'],label=label)
plt.legend()
plt.title('M和F的散点图')
plt.xlabel('F')
plt.ylabel('M')

3.2.用户活跃度分层
用户类型:新老用户、活跃用户、不活跃用户、回流用户
新用户:第一次消费
活跃用户:在某一个时间窗口有过消费的客户
不活跃用户:与活跃用户相反
回流用户:即回头客
pivoted_counts = ndf.pivot_table(index='userID',columns='Month',values='orderID',aggfunc='count').fillna(0)
pivoted_counts.head()

# 有消费的则1,没有消费的则0
purchase_df = pivoted_counts.applymap(lambda x : 1 if x>0 else 0)
purchase_df.head()

apply和applymap的区别:
apply:作用于dataframe时,用来某一行或某一列汇总运算,需要写明axis=[1|0]。也可以作用于series,这时效果相当于series.map()
applymap:想要在dataframe实现series.map()那样的效果,就要用applymap,可以作用于df每一个元素。
下面附一张map、apply和applymap使用方法的思维导图,仅供参考(可能有需要纠正的地方- -)。

接下来写一个判断用户是否为新用户、活跃用户、不活跃用户、回流用户的函数,该段code块可能有点绕。
首先将用户分为两种情况:当月没有消费和当月有消费。
第一个情况即当月没有消费:先判断status是否有元素,如果没有那么该月是首月,则应当赋值为unreg,否则有元素时(说明当月非首月),需要判断上个月状态是否为unreg,是的话则unreg否则unactive(因为当月是没有消费的,即使上个月有消费,当月状态也得为unactive)。
好,然后是第二个情况即当月有消费:先判断status是否有元素,如果没有则应当赋值为new,否则需要判断上个月状态是否为unactive,是的则赋值为return(因为上个月不活跃说明之前有消费过然后当月又来消费肯定是回头客),否则再判断是否为unreg,是的则new(说明之前从未消费过一次),除之外为active。
def active_status(data):
status = [] # 用来存储用户状态 new|unreg|unactive|active|return
for i in range(12):
# 本月没有消费
if data[i] == 0:
if len(status) == 0:
status.append('unreg')
else:
if status[i-1] == 'unreg':
status.append('unreg')
else:
status.append('unactive') # 前一个月可能是new|active|unactive,但当月没消费故当月状态是unactive
# 本月有消费
else:
if len(status) == 0:
status.append('new')
else:
if status[i-1] == 'unactive':
status.append('return')
elif status[i-1] == 'unreg':
status.append('new')
else:
status.append('active')
return pd.Series(status,purchase_df.columns)
purchase_status = purchase_df.apply(active_status,axis=1)
purchase_status.head()

# 把unreg换成NaN,再逐列按values统计,处理空值,返回一个新df
status_ct = purchase_status.replace('unreg',np.NAN).apply(lambda x : pd.value_counts(x),axis=0).fillna(0)
status_ct
unreg不参与计算,所以给换成NaN了。喏,在这里又用到value_counts()方法了,按月度维度统计new、active、unactive、return的用户数。

# 转置
status_ct.T.plot.area()

分析:
新用户从四月份起开始上升,到六月份有小幅的下降后趋近于平稳,说明拉新或者促活或者转化工作可能不足。
回流用户在三月份之后一直呈现逐渐攀升的趋势,是商家的重要对象之一,说明用户被唤回运营有明显的效果。
活跃用户在二月份起一直呈现逐渐攀升的趋势,整体来看比较平稳,也是商家的重要对象之一。
# 回流用户和活跃用户的占比分析
# 计算某一个月的回流用户、活跃用户等在整年的占比
rate_df = status_ct.T.apply(lambda x:x/x.sum(),axis=0)
rate_df

plt.plot(rate_df['return'],label='return')
plt.plot(rate_df['active'],label='active')
# plt.plot(rate_df['new'],label='new')
# plt.plot(rate_df['unactive'],label='unactive')
plt.legend()

分析:
回流用户整体呈现上升趋势,说明唤回运营效果不错。
活跃用户一月到五月份一直呈上升趋势,猜测在这个期间可能有活动,吸引了客户,到七月份有回落的现象,后来又回升。


1394

被折叠的 条评论
为什么被折叠?



