RSS
菜单
全部文章快讯开发科技深度热点

第 25 章 案例二:用户留存 Cohort 分析

内容摘要

**Cohort(同期群)分析**是互联网运营最核心的分析方法之一:把用户按"首次行为时间"分组(如注册月份),追踪每个群组在后续每个周期的留存表现。本案例用 pandas 完整实现:**群组划分 → 留存矩阵 → 留存曲线 → 洞察结论**。

Cohort(同期群)分析是互联网运营最核心的分析方法之一:把用户按"首次行为时间"分组(如注册月份),追踪每个群组在后续每个周期的留存表现。本案例用 pandas 完整实现:群组划分 → 留存矩阵 → 留存曲线 → 洞察结论。

目录

1. 什么是 Cohort 分析

2. 案例背景与数据模拟

3. 构建 Cohort 群组

4. 留存矩阵

5. 留存曲线与热力图

6. 进阶:金额/活跃度 Cohort

7. 解读与运营建议

8. 扩展练习


1. 什么是 Cohort 分析

Cohort = 同期群:在同一时间窗口(天/周/月)内发生同一行为的用户群体。

核心问题:2025 年 1 月注册的用户,在 2 月、3 月、4 月……还有多少仍在使用?

为什么重要:

  • 整体留存率会掩盖新老用户差异;
  • 不同月份注册的用户,留存可能差异巨大(版本、渠道、季节);
  • 判断"产品体验是否在变好":新群组留存应该高于老群组。
Cohort 矩阵示例(% 留存):

注册月份    第0月  第1月  第2月  第3月
2025-01    100%   42%   35%   30%
2025-02    100%   45%   38%   ...
2025-03    100%   48%   ...

2. 案例背景与数据模拟

背景:一款内容 App,有用户注册时间和每日活跃记录,评估留存。

数据模拟:假设 2025 年 1-6 月每月新增一批用户,每人有若干天的活跃记录。

import pandas as pd
import numpy as np

np.random.seed(42)

# 生成注册记录:每月注册用户数
register_months = pd.period_range("2025-01", "2025-06", freq="M")
users = []
for m in register_months:
    n_new = np.random.randint(800, 1500)          # 每月新增用户
    for _ in range(n_new):
        users.append({
            "user_id": len(users) + 1,
            "register_date": m.start_time + pd.Timedelta(days=np.random.randint(0, 28)),
        })
users = pd.DataFrame(users)
users["注册月份"] = users["register_date"].dt.to_period("M")
print(users.head())
print(f"总注册用户: {len(users)}")
# 生成活跃记录:老用户留存概率递减
active_records = []
for _, u in users.iterrows():
    # 活跃概率随使用天数下降(模拟真实留存曲线)
    for day_offset in range(0, 120, 7):            # 每周采一个活跃点
        # 留存概率随月份增长而衰减
        prob = 0.9 * (0.85 ** (day_offset // 7))
        if np.random.rand() < prob:
            active_records.append({
                "user_id": u["user_id"],
                "active_date": u["register_date"] + pd.Timedelta(days=day_offset),
            })
active = pd.DataFrame(active_records)
active["活跃月份"] = active["active_date"].dt.to_period("M")
print(f"活跃记录: {len(active)} 条")

3. 构建 Cohort 群组

关键步骤:对每个用户,计算"注册月份"和"活跃月份",然后算"注册后第几个月"。

# 合并注册信息到活跃记录
df = active.merge(users[["user_id", "注册月份"]], on="user_id", how="left")

# 计算"注册后第几个月"(0 = 注册当月)
df["周期"] = (df["活跃月份"].astype("int64") - df["注册月份"].astype("int64"))

# 查看结构
print(df.head())
print("\n各注册月份用户数:")
print(users["注册月份"].value_counts().sort_index())
   user_id  active_date 活跃月份 注册月份  周期
0        1   2025-01-01 2025-01 2025-01    0
1        1   2025-01-08 2025-01 2025-01    0
...

4. 留存矩阵

# 每个"注册月份 × 周期"的去重活跃用户数
cohort_size = users.groupby("注册月份")["user_id"].nunique()   # 各群组总人数
cohort_active = (
    df.groupby(["注册月份", "周期"])["user_id"]
    .nunique()
    .reset_index(name="活跃人数")
)

# 留存率 = 活跃人数 / 群组总人数
cohort_active["群组人数"] = cohort_active["注册月份"].map(cohort_size)
cohort_active["留存率"] = cohort_active["活跃人数"] / cohort_active["群组人数"]

# 透视成矩阵:行=注册月份,列=周期
retention_matrix = cohort_active.pivot(index="注册月份", columns="周期", values="留存率")
print(retention_matrix.round(3))
周期         0      1      2      3      4
注册月份
2025-01  1.000  0.853  0.730  0.621  0.532
2025-02  1.000  0.861  0.739  0.633    NaN
2025-03  1.000  0.869  0.751    NaN    NaN
2025-04  1.000  0.874    NaN    NaN    NaN
2025-05  1.000    NaN    NaN    NaN    NaN
2025-06  1.000    NaN    NaN    NaN    NaN
第 0 列(注册当月)恒为 100%(当月一定活跃)。后续周期留存率逐步衰减。
# 简化展示:只看第 1/3 个月留存
retention_matrix[[0, 1, 3]].round(3)

5. 留存曲线与热力图

import matplotlib.pyplot as plt
import seaborn as sns

plt.rcParams["font.sans-serif"] = ["SimHei", "Microsoft YaHei"]
plt.rcParams["axes.unicode_minus"] = False

# 热力图
plt.figure(figsize=(10, 5))
sns.heatmap(
    retention_matrix * 100,
    annot=True, fmt=".1f",
    cmap="YlGnBu", vmin=0, vmax=100,
    cbar_kws={"label": "留存率 (%)"},
)
plt.title("Cohort 留存矩阵(%)")
plt.xlabel("注册后第 N 个月")
plt.ylabel("注册月份")
plt.show()
# 留存曲线:每个群组一条线
plt.figure(figsize=(10, 5))
for period, row in retention_matrix.iterrows():
    plt.plot(row.index, row.values * 100, marker="o", label=str(period))

plt.xlabel("注册后第 N 个月")
plt.ylabel("留存率 (%)")
plt.title("各月注册用户的留存曲线")
plt.legend(title="注册月份")
plt.grid(alpha=0.3)
plt.show()
# 对比首月留存趋势:看产品是否在变好
first_month = retention_matrix[1].dropna() * 100
print(first_month)
plt.figure(figsize=(8, 4))
first_month.plot(kind="bar", color="steelblue")
plt.title("各注册群组次月留存率")
plt.ylabel("%")
plt.xticks(rotation=0)
plt.show()

6. 进阶:金额/活跃度 Cohort

6.1 活跃天数 Cohort

# 每个用户每周活跃天数
active_daily = pd.DataFrame({
    "user_id": np.repeat(active["user_id"].values, 1),
    "active_date": active["active_date"],
})
active_daily["周"] = active_daily["active_date"].dt.to_period("W")
user_weekly = active_daily.groupby(["user_id", "周"]).size().rename("活跃天数")

# 平均每个群组每周活跃天数
df2 = active_daily.merge(users[["user_id", "注册月份"]], on="user_id", how="left")
df2["周期"] = (df2["周"].astype("int64") // 10000) - (df2["注册月份"].astype("int64"))
# (简化示意:实际按周算周期)

6.2 按渠道的 Cohort

# 给用户加渠道维度
np.random.seed(1)
users["渠道"] = np.random.choice(["自然增长", "广告投放", "老带新"], len(users), p=[0.5, 0.3, 0.2])

# 分渠道 × 群组 × 周期的留存
df3 = df.merge(users[["user_id", "渠道"]], on="user_id", how="left")

ch_ret = (
    df3.groupby(["渠道", "注册月份", "周期"])["user_id"]
    .nunique()
    .unstack(level="周期")
)
# 与各渠道群组人数相除得到留存率
ch_size = users.groupby(["渠道", "注册月份"]).size()
ret_by_channel = ch_ret.div(ch_size, level=1)   # 按渠道+群组对齐
print(ret_by_channel[1].round(3))   # 各渠道次月留存
渠道      注册月份
广告投放  2025-01    0.821
         2025-02    0.835
自然增长  2025-01    0.877
...
如果"自然增长"用户的留存显著高于"广告投放",说明投放带来的用户质量偏低,需要调整投放策略。

7. 解读与运营建议

=== 解读要点 ===
1. 次月留存(第 1 周期)在 85%-87% 之间且逐月小幅上升:
   → 产品体验或内容质量在缓慢改善;
2. 第 3 个月留存约 62%-63%,衰减趋缓:
   → 说明留下来的是"忠实用户",流失主要发生在早期;
3. 若渠道分析显示广告用户留存低:
   → 需检查投放人群定向、落地页承接。

=== 运营建议 ===
1. 重点做"新用户首月激活":新手任务、首次内容推荐;
2. 第 2-3 个月是关键流失窗口:推送召回、社区互动;
3. 高留存渠道加大投入,低留存渠道优化定向;
4. 每月监控 Cohort 矩阵,若新群组留存下滑,第一时间排查版本/运营事件。

8. 扩展练习

1. 把周期从"月"改成"周",观察周留存曲线。

2. 分别计算"周活跃用户数(WAU)"和"月活跃用户数(MAU)",计算 MAU/WAU 比值。

3. 按"是否完成新手引导"分组做 Cohort 对比。

4. 计算每个群组的 LT(生命周期价值):群组人均累计活跃次数。

5. 用留存率拟合指数衰减曲线,预测未来留存。


下一篇预告:第 26 章 案例三:股票行情时间序列分析 —— 收益率、均线、布林带、回撤与简单策略回测。
— 全文完 —回到顶部 ↑
下载推广海报

文章推广海报

《第 25 章 案例二:用户留存 Cohort 分析》完整推广海报
DISCUSSION

文章回复

0 条公开回复
未登录回复需要审核后公开
还没有回复,欢迎参与讨论。