同期群分析

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

1 同期群分析方法介绍

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

  • 横向上——分析同期群随着周期推移而发生的变化;
  • 纵向上——分析在生命周期相同阶段的群组之间的差异。

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

  1. 客户首次行为时间:这是划分同期群体的基点;
  2. 时间周期维度:比如N日留存率、N日转化率中的N日,一般即为+N日、+N月等
  3. 变化的指标:比如注册转化率、付款转化率、留存率等等。

2 同期群分析的意义

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

二、SQL实现同期群分析

Excel数据源部分数据截图:
image

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;

image

(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;

image

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.`首付月份`

image

-- ③ 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

image

-- ④分组,最终计算留存率
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.首付月份;

image

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

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()

image

# 清洗数据
order = df[df['order_status']=='交易成功'].copy()
order.loc[:, 'pay_time'] = order['pay_time'].astype(str).str[:7]
order.head()

image

# 计算留存量
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

image

# 计算留存率
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

image

posted @ 2021-03-25 21:21  Lu-顺  阅读(1194)  评论(1编辑  收藏  举报