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 章 案例三:股票行情时间序列分析 —— 收益率、均线、布林带、回撤与简单策略回测。
文章回复
0 条公开回复