数据蛙强化课程第一期数据分析设计杂谈

练习1总结

2019-02-08  本文已影响9人  bf3780a4db09

这周主要学习了matplotlib和seaborn两个可视化库的基本操作,以及一个实战项目,实战项目已经结合了可视化内容,主要就是总结这个实战项目。
所用的数据是一份关于用户消费的数据,


image.png

包括四个地段:
user_id:用户ID
order_dt:消费时间
order_products:购买的产品数量
order_amount:消费金额
在正式开始分析之前,需要对数据做一些处理:

df.isnull().sum()

SQL中没有找到特别好的方法,只能一列一列看是否存在null

SELECT count(*) FROM aa.cdnow_master
where user_id is null;

返回Python的结果


image.png

关于如何处理缺失值问题,现在还没怎么实战过,看到的比较多的就是删除或者填充,填充这一块有很多方法【简单的平均值、中位数、前向、后向,复杂的就涉及到一些机器学习模型了,后面结合例子再来看吧】
这个数据集不存在缺失值,就不考虑这一块了。

df.info()  #
df['order_dt'] = pd.to_datetime(df['order_dt'],format='%Y%m%d') #修改日期的数据类型
df['month']=df['order_dt'].values.astype('datetime64[M]')  #将日期精确到月份

注:astype修改数据类型,type查看数据类型,dtype数组元素的类型
SQL的话,可以在导入之前就设定好字段类型为date,或者用SQL语句修改

alter table aa.cdnow_master modify column order_dt date #修改order_dt字段为日期类型
alter table aa.cdnow_master add column mon_th date not null;#新增一个精确到月的日期字段
update aa.cdnow_master set mon_th= date_format(order_dt,'%Y-%m-01') #更新新列

注意:在用sql处理的时候,不需要给表设定user_id为主键,否则一些重复的user_id就被自动过滤了。
简单的处理完原始数据,看一下数据的大体情况

image.png
从上面的数据可以看出

1、用户消费趋势的分析(按月份)

指标1是每月的消费总金额
指标2是每月的消费次数
指标3是每月的产品购买量
指标4是每月的消费人数

指标1 每月的消费总金额

Python

grouped_month = df.groupby('month')
order_month_amount = grouped_month['order_amount'].sum()  #每月的消费总额
order_month_amount.sort_values(ascending=False)

SQL

SELECT mon_th,sum(order_amount) as total
FROM aa.cdnow_master
group by mon_th
order by total desc

返回Python结果


image.png

可视化

import matplotlib.pyplot as plt
plt.rcParams['figure.figsize']=(10,6)#设置画布大小
plt.style.use('ggplot')  #设置图形显示风格为ggplot类型
order_month_amount.plot(marker='*') #折线图
#或者plt.plot(order_month_amount,marker='*') 

返回结果


image.png

结论:

指标2是每月的消费订单量

方法与消费金额类似
Python

month_count = grouped_month['user_id'].count()
month_count

SQL

SELECT mon_th,count(user_id)
FROM aa.cdnow_master
group by mon_th
order by mon_th 

结果


image.png

可视化

month_count.plot(marker='o',mec='r',mfc='w')

结果


image.png

结论:

指标3是每月的产品购买量

Python

grouped_month['order_products'].sum().plot()

SQL查询

SELECT mon_th,sum(order_products)
FROM aa.cdnow_master
group by mon_th
order by mon_th

返回Python结果


image.png

结论:

指标4是每月的消费人数

计算消费人数时注意一个用户会多次下单的问题【去重】
Python

grouped_month['user_id'].apply(lambda g: len(g.drop_duplicates())).plot()
#或者用分组来去重
df.groupby(['month','user_id']).count().reset_index().groupby('month')['order_dt'].count().plot()

SQL查询

SELECT mon_th,count(distinct user_id)
FROM aa.cdnow_master
group by mon_th
order by mon_th

返回结果


image.png

结论:

df.pivot_table(index='month',values=['user_id','order_products','order_amount'],
              aggfunc={'user_id':'count','order_products':np.sum,'order_amount':np.sum})

结果


image.png

除了上面四个指标,还可以看一下

每月用户平均消费金额趋势

Python

sum_1.div(sum_2,level='month').plot()  #每月用户平均消费金额
plt.yticks(range(35,90,20))

SQL查询

select  mon_th,totalamount/totaluser
from(
SELECT mon_th,
sum(order_amount) as totalamount,
count(distinct user_id) as totaluser
FROM aa.cdnow_master
group by mon_th) as t

结果


image.png

结论:

每月用户平均下单量的趋势

Python

sum_3 = grouped_month['user_id'].count() #每月订单总量
sum_3.div(sum_2,level='month')

SQL查询

select  mon_th,totalorder/totaluser
from(
SELECT mon_th,
count(user_id) as totalorder,
count(distinct user_id) as totaluser
FROM aa.cdnow_master
group by mon_th) as t
image.png

结论:

每月用户平均消费产品数的趋势

Python

sum_4 = grouped_month['order_products'].sum()
sum_4.div(sum_2,level='month') #每月用户平均消费产品数量

SQL查询

select  mon_th,totalproducts/totaluser
from(
SELECT mon_th,
sum(order_products) as totalproducts,
count(distinct user_id) as totaluser
FROM aa.cdnow_master
group by mon_th) as t

返回结果


image.png

结论:

month_pair=df.pivot_table(index='month',values=['user_id','order_products','order_amount'],
              aggfunc={'user_id':'count','order_products':np.sum,'order_amount':np.sum})
month_pair.rename(columns={'user_id':'ordertotal','order_products':'productstotal','order_amount':'amounttotal'},inplace=True)
month_pair['usertotal']=df.groupby('month')['user_id'].apply(lambda x:len(x.drop_duplicates()))
import seaborn as sns
sns.pairplot(month_pair)
plt.savefig('C:/Users/lenovo/Desktop/1.jpeg')

返回结果


1.jpeg

结论:

sns.lmplot(data=month_pair, x='usertotal',y='amounttotal')
sns.lmplot(data=month_pair, x='usertotal',y='amounttotal',robust=True)#去除离群点

返回第一个结果


image.png

返回第二个结果


image.png

处理离群点:看第一个结果,似乎三个离群点把拟合直线的斜率变得更平缓一些,不了解数据的背景时代,但从用户量和消费金额来说,这3个离群点似乎不能算异常值;用程序验证一下,设置函数的参数robust=True去除离群点的影响,两者差异不大【在拟合时并没有删除3个点】,自己每次看到离群点第一个反应就是删除,也算是给自己做个提醒吧。

2、用户个体消费分析

维度1用户消费金额、消费产品量的描述统计
维度2用户消费金额和消费的散点图
维度3用户消费金额的分布图
维度4用户消费产品量的分布图
维度5用户累计消费金额占比(百分之多少的用户占了百分之多少的消费额)【算好符合28规则之后,可以看看这些个用户的消费特点(看他们的消费金额、消费次数的描述统计以及相应的分布图)】

维度1用户消费金额、消费产品量的描述统计

金额和产品数的统计概况

grouped_user = df.groupby('user_id')
grouped_user.sum().describe()

返回结果


image.png

结论:

维度2用户消费金额和消费的散点图

grouped_user.sum().plot.scatter(x='order_products',y='order_amount')

返回结果


image.png

结论:

维度3用户消费金额的分布图

查看用户消费产品数和消费金额的分布趋势,此处对数据做一个简单清洗,借鉴切比雪夫定理:所有数据中,至少有24/25(或96%)的数据位于平均数5个标准差范围内。因此,消费产品数选取[0,7.12+5*16.98],即[0,92],消费金额选取[0,1310.73]的数据。
Python

grouped_user.sum().query('order_amount<=1310.73')['order_amount'].plot.hist(bins=100) #

SQL查询语句

select  user_id,totalamount
from(
SELECT user_id,
sum(order_amount) as totalamount
FROM aa.cdnow_master
group by user_id) as t
where totalamount<=1310.73

返回


image.png

结论:

维度4用户消费产品量的分布图

Python

grouped_user.sum().query('order_products<=92')['order_products'].plot.hist(bins=40)

SQL查询语句

select  user_id,totalproducts
from(
SELECT user_id,
sum(order_products) as totalproducts
FROM aa.cdnow_master
group by user_id) as t
where totalproducts<=92

返回


image.png

结论:

维度5用户累计消费金额占比

Python

amount_cumsum = grouped_user.sum().sort_values(by=['order_amount'],ascending=False).apply(lambda x:x.cumsum()/x.sum())['order_amount']
amount_cumsum.reset_index()['order_amount'].plot()

SQL查询语句

select t1.user_id,(@csum:=@csum+t1.totalamount) as csumamount
from
(select user_id,sum(order_amount)/(select sum(order_amount) from aa.cdnow_master) as totalamount 
from aa.cdnow_master
group by user_id
order by totalamount desc) as t1,(select @csum:=0) as t2;

返回Python结果


image.png

结论:

查看一下消费排名前5000的用户的消费金额和消费产品数的情况

grouped_user_2 = grouped_user.sum().sort_values(by=['order_amount'],ascending=False).apply(lambda x:x.cumsum()/x.sum())
grouped_user_80 = grouped_user.sum().loc[grouped_user_2.iloc[:5000].index]
grouped_user_80.describe() #描述统计

返回


image.png

由上表可知,数据结果相对集中,仍然存在极值影响。
消费产品数

df2 = df.set_index('user_id')
df2_80 = df2.loc[grouped_user_2.iloc[:5000].index]
product_80 = df2_80.groupby('month').sum()['order_products']
product_total = df.groupby('month').sum()['order_products']
product_80.div(product_total)

SQL查询语句

select mon_th,
sum(if(t2.user_id is null,0,t1.order_products))/sum(t1.order_products) as monthproducts
from aa.cdnow_master as t1
left join
(
select user_id
from aa.cdnow_master
group by user_id
order by sum(order_amount) desc
limit 5000) as t2
on t1.user_id=t2.user_id
group by t1.mon_th
order by t1.mon_th

注:由于计算精度原因,Python和SQL中的第五千个用户分别为用户20464和9654,所以两个结果有一点点不一样
返回


image.png

可视化

df2_80.groupby('month').sum()['order_products'].plot()

返回


image.png

结论:

amount_80 = df2_80.groupby('month').sum()['order_amount']
amount_total = df.groupby('month').sum()['order_amount']
amount_80.div(amount_total)
df2_80.groupby('month').sum()['order_amount'].plot()

SQL查询语句

select mon_th,
sum(if(t2.user_id is null,0,t1.order_amount))/sum(t1.order_amount) as monthamount
from aa.cdnow_master as t1
left join
(
select user_id
from aa.cdnow_master
group by user_id
order by sum(order_amount) desc
limit 5000) as t2
on t1.user_id=t2.user_id
group by t1.mon_th
order by t1.mon_th;

注:由于计算精度原因,Python和SQL中的第五千个用户分别为用户20464和9654,所以两个结果有一点点不一样
返回


image.png
image.png

结论与消费产品数的结论一致

3、用户消费行为

维度1用户第一次消费(首购)
维度2用户最后一次消费
维度3多少用户仅消费了一次

维度1用户第一次消费(首购)

Python

grouped_user.min()['order_dt'].value_counts().plot()

SQL查询

select firstorder, count(*) from
(select user_id,min(order_dt) as firstorder 
from aa.cdnow_master
group by user_id) as t
group by firstorder

返回


image.png

结论:

维度2用户最后一次消费

Python

grouped_user.max()['order_dt'].value_counts().plot()

SQL查询语句

select firstorder, count(*) from
(select user_id,max(order_dt) as firstorder 
from aa.cdnow_master
group by user_id) as t
group by firstorder
order by count(*) desc

返回


image.png

结论:

维度3多少用户仅消费了一次

Python

user_life = grouped_user['order_dt'].agg(['min','max'])
(user_life['min']==user_life['max']).value_counts()

SQL查询语句

select count(*) from
(select user_id
from aa.cdnow_master
group by user_id
having count(*) = 1) as t

返回

image.png
注:上面Python方法得出仅消费一次的用户为12054,但是按照SQL的方法得到11908,原因在于Python方法的时间只精确到天,有一部分用户同一天消费了两次,比如id为2的用户,在1月12号消费了两次,Python方法将他作为只消费了一次的用户。
因此按SQL的思路修改Python
user_life_2 = grouped_user.count()
(user_life_2['order_dt']==1).value_counts()

返回:


image.png

结论:

维度4用户分层

RFM(最近一次消费、消费次数【此处用消费产品数】、消费金额)

Python

rfm = df.pivot_table(index='user_id',values=['order_dt','order_products','order_amount'],aggfunc={'order_dt':'max',
                                                          'order_products':'sum','order_amount':'sum'})
rfm['R'] = -(rfm['order_dt']-rfm['order_dt'].max())/np.timedelta64(1,'D')  #计算用户的最近一次消费距今的时间【此处用的是所有用户中的最近一次消费】除法是为了消除单位
rfm.rename(columns={'order_products':'F','order_amount':'M'},inplace=True)#RFM模型
def rfm_func(x):
    level=x.apply(lambda x: '1' if x>=0 else '0')
    label = level.R+level.F+level.M
    d={
        '111':'重要价值客户',#消费时间较远,消费频次和消费金额够
        '011':'重要保持客户',  #最重要的客户,消费时间近,消费频次和消费金额够
        '101':'重要发展客户',#消费时间远,消费金额够,消费频次不够
        '001':'重要挽留客户',#消费时间近,消费频次不够,消费金额够
        '110':'一般价值客户',#消费时间远,消费频次够,消费金额不够
        '010':'一般保持客户',#消费时间近,消费频次够,消费金额不够
        '100':'一般发展客户',#消费时间远,消费频次和消费金额不够
        '000':'一般发展客户'#消费时间近,消费频次和消费金额都不够
    }
    return d[label]

rfm['label'] = rfm[['R','F','M']].apply(lambda x:x-x.mean()).apply(rfm_func,axis=1)  # 
计算每个字段与平均值的差距,根据差值进行分层
rfm.head()

SQL查询

create table test4 (select user_id,max(order_dt) as R,sum(order_products) as F,sum(order_amount) as M
from aa.cdnow_master
group by user_id);
alter table test4 add RR varchar(40) not Null;
update test4,(select * from (select user_id,datediff((select max(R) from test4),R) as R from test4) as t5) as t6
set test4.RR=t6.R
where test4.user_id=t6.user_id;
select label,count(*)
FROM
(select *,
(case
WHEN RFM='111' THEN '重要价值客户'
WHEN RFM='011' THEN '重要保持客户'  
WHEN RFM='101' THEN '重要发展客户'
WHEN RFM='001' THEN '重要挽留客户'
WHEN RFM='110' THEN '一般价值客户'
WHEN RFM='010' THEN '一般保持客户'
WHEN RFM='100' THEN '一般发展客户'
WHEN RFM='000' THEN '一般发展客户'
end) as label
from
(select *,
CONCAT(if((RR-(select AVG(RR) from test4))>=0,1,0),
       if((F-(select AVG(F) from test4))>=0,1,0),
       if((M-(select AVG(M) from test4))>=0,1,0)) as RFM
from test4) as t4) as t5
group by label;

返回结果中,已为每个用户打好标签


image.png

可视化
简单点划分为两种

rfm.loc[rfm['label']=='重要价值客户','color']='g' 
rfm.loc[~(rfm['label']=='重要价值客户'),'color']='r'
rfm.plot.scatter('F','R',c=rfm['color'])

返回


image.png

注:plot.scatter()中的参数c,只能填颜色名称或者数字,不能填c=rfm['label']会报错【c of shape (23570,) not acceptable as a color sequence for x with size 23570, y with size 23570】
按照原分层

plt.rcParams['font.sans-serif']=['SimHei']  #用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False  #用来正常显示负号 
pie_1= rfm['label'].value_counts()
plt.figure(figsize=(8,9))
pie_1.plot.pie(autopct='%1.1f%%',explode=[0.05,0,0,0,0,0,0],pctdistance = 0.9,labeldistance = 1.1)

返回


image.png

结论:

新用户【第一次消费】、活跃【持续在消费】、回流【隔了一段时间再次消费】、流失【不活跃,一段时间未消费】

Python

pivoted_counts = df.pivot_table(index='user_id',columns='month',values='order_dt',aggfunc='count').fillna(0) #每个用户每月的消费次数
#这边只看用户每个月有没有消费过,消费次数这边不考虑
df_purchase=pivoted_counts.applymap(lambda x:1 if x>0 else 0) #1代表本月消费,0代表未消费
def active_status(data): #data是df_purchase的一行
    status=[]
    for i in range(18): #一共有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 status
purchase_stats=df_purchase.apply(active_status,axis=1)#得到用户分层结果
purchase_stats_ct=purchase_stats.replace({'unreg':np.NaN}).apply(lambda x:x.value_counts()) #用NaN替代unreg,以便后续计算不包含这些数据,未注册不考虑
purchase_stats_ct.fillna(0).T.plot.area()#为了便于画图,将缺失值补齐

这个的SQL没写出来
返回

image.png
表格形式【每月各层用户占比】
purchase_stats_ct.fillna(0).T.apply(lambda x:x/x.sum(),axis=1)  # 只要首购了,这个用户就会被纳入分析范围

返回


image.png

此外,还可以计算每月的回流率

purchase_return = purchase_stats_ct.fillna(0).T[['return','unactive']]
purchase_return['return'].div(purchase_return['unactive'].shift(1)) #回流率一直都不太高

SQL查询?
返回


image.png

结论:

维度5用户购买周期(按订单)

用户消费周期描述
order_diff = df.groupby('user_id').apply(lambda x: x['order_dt']-x['order_dt'].shift()) #用户每笔订单的时间间隔
order_diff.describe()  #不计算空值

SQL查询

select t1.user_id,
@difft:=if(@id=user_id,DATEDIFF(order_dt,@pre),null) as dit,
@pre:=order_dt,
@id:=user_id
from aa.cdnow_master as t1,
(select @pre:=0,@id:=null) as t2
ORDER BY user_id,order_dt

返回


image.png

结论:

用户消费周期分布
sns.distplot((order_diff/np.timedelta64(1,'D')).dropna(),bins=20).set_xlim(0,600)

返回


image.png

结论:

维度6用户生命周期(按第一次和最后一次消费)

用户生命周期描述和分布

Python

user_life=grouped_user.agg(['min','max'])['order_dt']
user_life=(user_life['max']-user_life['min'])/np.timedelta64(1,'D')
user_life.plot.hist(bins=40)
user_life.describe()

SQL查询

select user_id,DATEDIFF(MAX(order_dt),MIN(order_dt)) as userlife
from aa.cdnow_master
GROUP BY user_id

返回


image.png

结论:

sns.distplot(user_life[user_life>0],bins=40).set_xlim(0,600)

返回


image.png

结论:

import heapq
def user_func(x):
    if len(x)>1:
        return (heapq.nsmallest(2,x)[1]-heapq.nsmallest(2,x)[0])/np.timedelta64(1,'D')
    else:
        return None
#user_fugou = grouped_user.count()[grouped_user.count()['order_dt']>1]
grouped_user_2 = df.groupby('user_id')
user_fugou_2 = grouped_user_2['order_dt'].apply(user_func )
user_fugou_2.describe()
user_fugou_2.plot.hist(bins=100)

返回


image.png
image.png

结论:

复购率和回购率

复购率

Python

purchase_r = pivoted_counts.applymap(lambda x: 1 if x>1 else np.NaN if x==0 else 0)#1表示本月复购了【购买次数大于1】,0表示本月只消费了一次,NaN表示本月未消费
((purchase_r.sum())/purchase_r.count()).plot(figsize=(10,4))

SQL查询

select mon_th ,
sum(if(ordertimes>1,1,0))/count(*) as f1
from
(select user_id,mon_th,count(*) as ordertimes
from aa.cdnow_master
GROUP BY user_id,mon_th) as t
GROUP BY mon_th
ORDER BY mon_th

返回


image.png

结论:

回购率

Python

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 status
purchase_b = df_purchase.apply(purchase_back,axis=1)
(purchase_b.sum()/purchase_b.count()).plot(figsize=(10,4))

SQL查询

select t1.mon_th,COUNT(DISTINCT t1.user_id) as totaluser,COUNT(t2.mon_th) as huser,
COUNT(t2.mon_th)/COUNT(DISTINCT t1.user_id) as h1
from
(select user_id,mon_th
from aa.cdnow_master
GROUP BY user_id,mon_th) as t1
LEFT JOIN
(select user_id,mon_th
from aa.cdnow_master
GROUP BY user_id,mon_th) as t2
on t1.user_id=t2.user_id
and t1.mon_th = DATE_SUB(t2.mon_th,INTERVAL 1 month)
GROUP BY t1.mon_th

返回


image.png

结论:

上一篇下一篇

猜你喜欢

热点阅读