CD网站用户行为分析项目

阅读路线:
  • 项目背景
  • 项目目的
  • 数据来源
  • 代码展示
一、项目背景

公司于1994年2月由双胞胎兄弟杰森汀和马修汀共同创立。公司在1994年8月推出telnet服务。公司在1994年9月成为一家零售CD网站。

二、项目目的:

利用python对销售数据进行分析,根据复购率、回购率、高额消费用户等指标及模型,获取高价值客户,从而进行有针对性的客户管理和维护。

三、数据来源

数据源于CDNow网站的用户购买明细
(数据:https://pan.baidu.com/s/1Mr1VyfgTOUyraKe_6z0Orw)

四、代码展示

(1)基本描述

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
plt.style.use('ggplot')
columns = ['user_id', 'order_dt', 'order_products', 'order_amount']
# 命名列名为用户id, 购买日期, 购买数量, 购买金额
df = pd.read_table('CDNOW_master.txt', names = columns, sep='\s+')
df.head()
# 简单看下数据前五行
df.info()
# 观察数据的属性

可以观察到无缺失值,同时也发现了日期没有正确识别,等下我再处理。

df.describe()
#数据基本信息

可以观察到大部分订单只消费了少量少量商品2.4件。
同时可以看到,用户的消费金额比较稳定,平均消费了35元,中位数为25元,最大值为1286元,明显受到了极值干扰,有些右偏分布。

接着我来处理刚刚没有正确识别的日期列,以及加一列新的月份,方便之后按月来分析。

df['order_dt'] = pd.to_datetime(df.order_dt, format='%Y%m%d')
df['month']=df.order_dt.values.astype('datetime64[M]')
df.head()

(2)按月进行用户消费趋势分析

grouped_month = df.groupby('month')
order_month_amount = grouped_month.order_amount.sum()
order_month_amount.head()
# 按月进行分组,并对消费金额进行求和
from matplotlib.font_manager import FontProperties
font = FontProperties(fname='/Users/quincy/Library/Fonts/simhei.ttf')
ax = order_month_amount.plot()
ax.set_xlabel('月份', fontproperties=font)
ax.set_ylabel('消费金额', fontproperties=font)
ax.set_title('不同月份的用户消费金额', fontproperties = font)
plt.show()

消费金额在前三个月逐步达到顶峰,后续消费额较为稳定,有下降趋势。

ax = grouped_month.user_id.count().plot()
ax.set_xlabel('月份', fontproperties = font)
ax.set_ylabel('订单数' , fontproperties=font)
ax.set_title('不同月份的订单数', fontproperties=font)
plt.show()

前三个月消费订单数在10000笔左右,后续月份平均消费订单数在2500笔左右。

由图片可知,无论是消费金额或者订单数都呈现大致相同的趋势,前三个月高,后续月份降低且趋于稳定。由于无法确定源数据从何而来,只能暂且假定前三个月有促销活动。

(3)用户个体消费行为分析

#以用户id为分组聚集
grouped_user = df.groupby('user_id')
grouped_user.sum().describe()

用户平均购买了7张cd, 但是中位数只有3,标准差较大波动较大,说明小部分用户买了大量cd,拉高了平均值
用户平均消费106元,中位数只有43,同样有极值干扰

ax = grouped_user.sum().query('order_amount <= 4000').plot.scatter(x='order_amount', y='order_products')
ax.set_xlabel('消费金额',font_properties = font)
ax.set_ylabel('CD碟数(张)', font_properties = font)
ax.set_title('单个用户消费金额与购买个数的关系', font_properties=font)
plt.show()
ax = grouped_user.sum().order_amount.plot.hist(bins=50)
ax.set_xlabel('消费金额(美元)', font_properties = font)
ax.set_ylabel('用户数(人)', font_properties = font)
ax.set_xlim(0,2000)
ax.set_title('用户消费金额分布直方图', font_properties = font)
plt.show()
ax = grouped_user.sum().order_products.plot.hist(bins=50)
ax.set_xlabel('CD碟数(张))', font_properties = font)
ax.set_ylabel('用户数(人)', font_properties = font)
ax.set_xlim(0,150)
ax.set_title('用户购买碟数分布直方图', font_properties = font)
plt.show()

可以从直方图观察到,绝大部分呈现集中趋势。大部分用户消费能力不高。购买张数在20张以内,金额在250美金以内。

user_cumsum = grouped_user.sum().sort_values('order_amount').apply(lambda x: x.cumsum()/ x.sum())
ax = user_cumsum.reset_index().order_amount.plot()
ax.set_title('总消费占比', font_properties = font)
ax.set_xlabel('消费金额', font_properties = font)
ax.set_ylabel('比例', font_properties = font)
plt.show()

按用户消费金额进行升序排列,50%的用户只有15%的消费额度。排名前5000的用户贡献了60%的消费额。 只要维护好这前5000个用户,就能效果很好。

(4) 复购率和回购率分析

#创建数据透视表,对每个用户每个月订单数统计
pivoted_counts = df.pivot_table(index='user_id',
                                columns='month', 
                                values='order_dt', 
                                aggfunc='count').fillna(0)
pivoted_counts.head()
# 把购买两次及以上的转化为1,购买1次为0,方便之后分析
purchase_r = pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)
purchase_r.head()

复购率:复购率的定义是在某时间窗口内消费两次及以上的用户在总消费用户中占比。这里的时间窗口是月,如果一个用户在同一天下了两笔订单,这里也将他算作复购用户。

ax = (purchase_r.sum() / purchase_r.count()).plot(figsize=(10,4))
ax.set_xlabel('时间(月)', font_properties = font)
ax.set_ylabel('百分比(%)', font_properties = font)
ax.set_title('每月用户复购率图', font_properties = font)
plt.show()

由图可知,前三个月由于大量新客户涌入,新用户只购买了一次,导致复购率较低。三个月后,复购率趋于稳定,在20%左右。

回购率:回购率是某一个时间窗口内消费的用户,在下一个时间窗口仍旧消费的占比。

#回购率只需考虑一段时间内是否购买过,所以购买过转化为1,无则为0
df_purchase = pivoted_counts.applymap(lambda x:1 if x>0 else 0)
df_purchase.head()
#定义函数,每个月都要跟后面一个月对比下,本月有消费且下月也有消费,则本月记为1,下月没有消费则为0,本月没有消费则为NaN,由于最后个月没有下月数据,规定全为NaN
def purchase_back(data):
    status = []
    for i in range(17):
        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 = df_purchase.columns
purchase_b.head()
ax = (purchase_b.sum() / purchase_b.count()).plot()
ax.set_xlabel('时间(月)', font_properties = font)
ax.set_ylabel('百分比(%)', font_properties = font)
ax.set_title('十八个月内用户回购率图', font_properties = font)
plt.show()

由图可知,用户的回购率大于复购率,约在30%左右上下波动,可能有营销淡旺季导致。但这部分的回购用户消费行为大致稳定,与之前的每月复购用户有一定重合,属于优质客户。

(5) 用户分层
我们按照用户的消费行为,简单划分成几个维度:未注册用户、新用户、活跃用户、不活跃用户、回流用户。
未注册用户(unreg)
新用户(new):新用户的定义是第一次消费的用户。
活跃用户(active):即连续两个时间窗口都消费过的用户。
不活跃用户(unactive):不活跃用户则是时间窗口内没有消费过的活跃用户,即一二月份都消费过,三月份没消费过。
回流用户(return):回流用户是在上一个窗口中没有消费,而在当前时间窗口内有过消费。

def active_status(data):
    status = []
    for i in range(18):
        
        #若本月没有消费
        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] == 'unactive':
                    status.append('return')
                elif status[i-1] == 'unreg':
                    status.append('new')
                else:
                    status.append('active')
    return pd.Series(status)
purchase_status = df_purchase.apply(active_status, axis=1)
purchase_status.head()
#去掉未注册用户unreg,统计下每个月不同层级用户人数
purchase_status_ct = purchase_status.replace('unreg', np.NaN).apply(lambda x: pd.value_counts(x))
purchase_status_ct
#把数据转置并把填充空值为0,看上去更为直观
purchase_status_ct.fillna(0).T
ax = purchase_status_ct.fillna(0).T.plot.area(figsize = (12,6))
ax.set_xlabel('时间(月)', font_properties = font)
ax.set_ylabel('用户数(人)', font_properties = font)
ax.set_title('每月各类用户类型占比面积图', font_properties = font)
ax.legend(loc='upper left')
plt.show()

由图可知,黑色的不活跃用户占据大部分,与我们图表结果相符。红色代表的活跃用户非常稳定,属于核心用户。与紫色的回流用户相加,大致是当月的消费用户人数。

(6)用户生命周期
定义第一次消费至最后一次消费为整个用户生命。我们需要找出每个用户的第一次消费和最后次消费

user_purchase = df[['user_id','order_products','order_amount','order_dt']]
order_date_min=user_purchase.groupby('user_id').order_dt.min()
order_date_max=user_purchase.groupby('user_id').order_dt.max()
life_time = (order_date_max-order_date_min).reset_index()
life_time.describe()

可以观察到,用户平均生命周期为134天,中位数为0。至少存在一般的用户只消费过一次,也就是低质量的客户。最大是544天,也就是数据集的总天数,说明从开始到最后都有消费意愿的高质量客户。

#二次消费以上客户的生命周期直方图
life_time['life_time'] = life_time.order_dt/np.timedelta64(1,'D')
ax=life_time[life_time > 0].life_time.plot.hist(bins =100,figsize = (12,6))
ax.set_xlabel('天数(天)', font_properties = font)
ax.set_ylabel('人数(人)', font_properties = font)
ax.set_title('二次消费以上用户的生命周期直方图', font_properties = font)
plt.show()

由图可观察到直方图呈双峰结构,部分质量较差的客户,虽然消费了两次,但仍然无法持续。若想提高用户转化率,应该在首次消费30天内尽量去引导。少部分用户集中在50-300天这个区间,属于普通型的生命周期。高质量用户的生命周期集中在400天以后,属于高忠诚客户,应尽量维护好这批客户。

(7)平均购买周期

order_diff = grouped_user.apply(lambda x: x.order_dt - x.order_dt.shift())
order_diff.describe()
ax = (order_diff / np.timedelta64(1, 'D')).hist(bins=20)
ax.set_xlabel('时间跨度(天)', font_properties  = font) 
ax.set_ylabel('人数(人)', font_properties = font) 
ax.set_title('用户平均购买周期直方图', font_properties = font)
plt.show()

如图,典型的长尾分布,大部分用户的消费间隔确实比较短。不妨将时间召回点设为消费后立即赠送优惠券,消费后10天询问用户CD怎么样,消费后30天提醒优惠券到期,消费后60天短信推送。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354