Pandas数据分析实战 — 同期群分析

一、同期群分析概念和理论

1 同期群分析方法介绍

  同期群分析(CohortAnalysis)实际上是一种用户分群的细分类型,是一种“纵横”结合的分析方法:

  • 横向上——分析同期群随着周期推移而发生的变化;

  • 纵向上——分析在生命周期相同阶段的群组之间的差异。

  “同期群”:同一时期的群体。可以是“同一天注册的用户”、同一天第一次发生付费行为的用户等等。“周期的指标变化”:用户在一定周期内的留存率、付费率等等。同期群分析包含三个核心的元素:

  1. 客户首次行为时间:这是划分同期群体的基点;

  1. 时间周期维度:比如N日留存率、N日转化率中的N日,一般即为+N日、+N月等

  1. 变化的指标:比如注册转化率、付款转化率、留存率等等。

2 同期群分析的意义

  为啥要做同期群分析,不分群不行吗?同期群分析,给到更加细致的衡量指标,帮助我们实时监控真实的用户行为、衡量用户价值,并为营销方案的优化和改进提供支撑,避免出现“被平均”的虚荣数据。

二、SQL实现同期群分析

Excel数据源部分数据截图:

1、python读取Excel数据导入MySQL

import pymysql
import xlrd
from datetime import datetime

# 1、读取本地Excel数据集
book = xlrd.open_workbook('同期群.xlsx')
sheet = book.sheet_by_name('1-数据源')
print("数据行数:",sheet.nrows,'---',"数据列数:",sheet.ncols)

# 2、连接数据库,创建游标、创建插入语句
db = pymysql.connect(host='localhost',user='root',password='123456',port=3306,db='同期群')
cursor = db.cursor()
sql = f"INSERT INTO tongqiqun (nick_name,pay_time,order_status,pay_amount,purchase_quantity,province) VALUES (%s,%s,%s,%s,%s,%s)"

# 3、插入函数
def insert_info():
    # 循环每行数据,跳过标题行,从第二行开始
    for r in range(1, 5):
        nick_name = sheet.cell(r,0).value  # 用户昵称
        # 支付时间处理
#         pay_time = sheet.cell(r,1).value  ->直接读取会报错,Excel的日期数据读出来是double
#         print(sheet.cell(1,1).value)     ->43709.00699074074
        try:
            pay_time = xlrd.xldate_as_tuple(sheet.cell(r,1).value,0)  # 转换成元组
            pay_time = datetime(*pay_time) # *args 任意位置参数传参
        except:
            pay_time = None
        order_status = sheet.cell(r,2).value  # 订单状态
        pay_amount = sheet.cell(r,3).value  # 支付金额
#         print(sheet.cell(1,4).value)  ->1.0
        purchase_quantity = int(sheet.cell(r,4).value)  # 购买数量
        province = sheet.cell(r,5).value  # 省份
        
        # 组装元组格式数据,执行SQL插入脚本
        data = (nick_name,pay_time,order_status,pay_amount,purchase_quantity,province)
#         print(data)
        cursor.execute(sql,data)
        
insert_info()

用了python的xlrd操作Excel文件,pymysql库连接MySQL数据库。用xlrd读取Excel数据时,会出现一些格式上的问题。比如,在Excel中的日期数据是以数值型存储的,所以需要做一下处理才能导入MySQL数据库。不太建议用python来读取Excel数据进行入库操作,可以用Navicat可视化工具导入更方便。导入datatime类型数据时,先以varchar类型导入,导入完成后,执行SQL语句:

ALTER TABLE tongqiqun CHANGE pay_time create_date DATETIME;

2、数据清洗

  订单状态为“交易失败”的行,付款时间是缺失的。

-- 筛选订单状态为:‘交易成功’的行,接下来分析只用到这两个字段:nick_name、pay_time
CREATE TABLE order_sheet1 AS
SELECT
    nick_name,
    pay_time
FROM tongqiqun
WHERE order_status = '交易成功';

3、计算留存量

(1)对用户进行分组,用min()函数计算日期最小值

-- 1、每个用户首单日期
SELECT
    nick_name,
    min(pay_time) as fir_time
FROM order_sheet1
GROUP BY nick_name;

(2)中间表:计算客户每次下单时间与首次下单时间的差值

  以用户名为连接条件,让每次下单的时间都与首次下单时间建立连接关系。假若数据量比较大,拼接需要遍历整个表很多遍,对于这种中间的过程查询,并不是最后的结果呈现。在保证查询准确度的情况下,可以用分页查询limit语句来限制查询的结果行数,从而提升运行效率。

-- 2、计算每单时间差、重采样首次订单时间
SELECT
    a.nick_name,
    b.fir_time,
    TIMESTAMPDIFF(month,b.fir_time,a.pay_time) AS m_diff,
    CONCAT(year(b.fir_time),'年',month(b.fir_time),'月') AS y_m
FROM order_sheet1 a
LEFT JOIN (
    SELECT
        nick_name,
        min(pay_time) as fir_time
    FROM order_sheet1
    GROUP BY nick_name
 -- 测试计算是否成功,提升运行效率
 -- LIMIT 20
) b ON a.nick_name=b.nick_name
WHERE b.fir_time IS NOT NULL;

(3)计算留存量

  对首付月份、月份差分组,去重统计nick_name数量即可。

CREATE TABLE order_sheet2 AS
SELECT
    t.y_m AS 首付月份,
    t.m_diff AS 月份差,
    COUNT(DISTINCT nick_name) AS 留存量
FROM
(SELECT
    a.nick_name,
    b.fir_time,
    TIMESTAMPDIFF(month,b.fir_time,a.pay_time) AS m_diff,
    CONCAT(year(b.fir_time),'年',month(b.fir_time),'月') AS y_m
FROM order_sheet1 a
LEFT JOIN (
    SELECT
        nick_name,
        min(pay_time) as fir_time
    FROM order_sheet1
    GROUP BY nick_name
) b ON a.nick_name=b.nick_name
WHERE b.fir_time IS NOT NULL) t
GROUP BY t.y_m,t.m_diff;

4、计算留存率

-- ① 提取首付月份、留存量
SELECT 
    首付月份,
    留存量
FROM order_sheet2
WHERE 月份差=0;
-- ② 左连接,计算同期首付月份,各差值月份的留存率
SELECT
    a.`首付月份`,
    b.`留存量`,
    a.`月份差`,
    round( (a.`留存量`/b.`留存量`)*100,2) AS 留存率
FROM order_sheet2 a
LEFT JOIN (
    SELECT 
        首付月份,
        留存量
    FROM order_sheet2
    WHERE 月份差=0
) b ON a.`首付月份`=b.`首付月份`
-- ③ case when表格转置,即月份差作为列名
SELECT
    c.首付月份,
    c.留存量,
    CASE c.月份差 WHEN 1 THEN c.留存率 ELSE 0 END AS '+1月',
    CASE c.月份差 WHEN 2 THEN c.留存率 ELSE 0 END AS '+2月',
    CASE c.月份差 WHEN 3 THEN c.留存率 ELSE 0 END AS '+3月',
    CASE c.月份差 WHEN 4 THEN c.留存率 ELSE 0 END AS '+4月',
    CASE c.月份差 WHEN 5 THEN c.留存率 ELSE 0 END AS '+5月'
FROM
(SELECT
    a.`首付月份`,
    b.`留存量`,
    a.`月份差`,
    round( (a.`留存量`/b.`留存量`)*100,2) AS 留存率
FROM order_sheet2 a
LEFT JOIN (
    SELECT 
        首付月份,
        留存量
    FROM order_sheet2
    WHERE 月份差=0
) b ON a.`首付月份`=b.`首付月份`) c
-- ④分组,最终计算留存率
SELECT
    d.首付月份,
    AVG(d.留存量) AS '本月新增',
    CONCAT(SUM(d.`+1月`),'%') AS `+1月`,
    CONCAT(SUM(d.`+2月`),'%') AS `+2月`,
    CONCAT(SUM(d.`+3月`),'%') AS `+3月`,
    CONCAT(SUM(d.`+4月`),'%') AS `+4月`,
    CONCAT(SUM(d.`+5月`),'%') AS `+5月`
FROM(
    SELECT
        c.首付月份,
        c.留存量,
        CASE c.月份差 WHEN 1 THEN c.留存率 ELSE 0 END AS '+1月',
        CASE c.月份差 WHEN 2 THEN c.留存率 ELSE 0 END AS '+2月',
        CASE c.月份差 WHEN 3 THEN c.留存率 ELSE 0 END AS '+3月',
        CASE c.月份差 WHEN 4 THEN c.留存率 ELSE 0 END AS '+4月',
        CASE c.月份差 WHEN 5 THEN c.留存率 ELSE 0 END AS '+5月'
    FROM(
        SELECT
            a.`首付月份`,
            b.`留存量`,
            a.`月份差`,
            round( (a.`留存量`/b.`留存量`)*100,2) AS 留存率
        FROM order_sheet2 a
        LEFT JOIN (
            SELECT 
                首付月份,
                留存量
            FROM order_sheet2
            WHERE 月份差=0
        ) b ON a.`首付月份`=b.`首付月份`) c
) d
GROUP BY d.首付月份;

思路总结:通过左外连接计算时间差值(与首次支付的时间差值、与首次登录的时间差值)中间表的产生,计算留存量,再计算留存率。

5 同期群简单分析

  有了同期群,就可以从横向和纵向比较。从横向上,可以看到同一个用户群在之后N月的留存率变化;而在纵向上,可以看到不同群组在N月后的留存率,可以比较各个群组用户的粘性。经过分析,发现9月份新增用户很少,但留存率比其他月份高4-6%左右;而10月份应该是做了促销活动,用户新增2.5倍,但次月留存率低了7%,往后月份的留存率低了3%,说明用户质量不行,促销只是为了短时间冲量,并没有考虑后期的留存。后续三个月,新增用户基本稳定在5000左右,10月份促销活动虽然打开增量,但是应该注意同期留存率却是在持续下降的。2月份的促销方案应该根据前面的经验做一些优化调整。

三、python实现同期群分析

import pandas as pd
import pymysql
import numpy as np

# 从数据库读表
sql = 'select nick_name,pay_time,order_status,pay_amount,purchase_quantity,province from tongqiqun'
con = pymysql.connect(host='localhost',user='root',passwd='123456',database='同期群',port=3306,charset='utf8')
df = pd.read_sql(sql,con)
df.head()
# 清洗数据
order = df[df['order_status']=='交易成功'].copy()
order.loc[:, 'pay_time'] = order['pay_time'].astype(str).str[:7]
order.head()
# 计算留存量
months = ['2019-09','2019-10','2019-11','2019-12','2020-01','2020-02']
months_1 = months[1:]
customer_num = pd.DataFrame(data=None,columns=['新增用户数','2019-10','2019-11','2019-12','2020-01','2020-02'],index=months)
m = 0
for i in months:
    m = m + 1
    if m == 1:
        cur_month = order.loc[order['pay_time']==i]
        cur_month_num = cur_month['nick_name'].nunique()
        customer_num.loc[i,'新增用户数'] = cur_month_num
    else:
        j = months[months.index(i) - 1]
        prv_month = order.loc[order['pay_time']<=j]
        now_month = order.loc[order['pay_time']==i]
        cur_month = now_month.loc[now_month['nick_name'].isin(prv_month['nick_name'])==False]
        cur_month_num = cur_month['nick_name'].nunique()
        customer_num.loc[i,'新增用户数'] = cur_month_num
    if m == 6:
        continue
    for k in months_1:
        next_month = order.loc[order['pay_time'] == k]
        next_remained = next_month.loc[next_month['nick_name'].isin(cur_month['nick_name']) == True]
        next_remained_num = next_remained['nick_name'].nunique()
        customer_num.loc[i, k] = next_remained_num
    months_1.pop(0)


customer_num
# 计算留存率
k=0
for i in range(6):
    for j in range(1,6):
        if (j+k)<=5:
            customer_num.iloc[i,j]=str(round((customer_num.iloc[i,(j+k)])/(customer_num.iloc[i,0]) * 100,2)) + '%'
        else:
            customer_num.iloc[i,j]=np.nan
    k+=1

customer_num.columns = ['本月新增','+1月','+2月','+3月','+4月','+5月']
customer_num

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

金戈鐡馬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值