📊

实战:用 Python 进行严谨的游戏 A/B 测试与 SRM 校验

从业务假设到数据决策的完整分析流程

01 业务背景与实验假设
场景 移动端游戏(Cookie Cats)。玩家在游戏中会遇到"强制等待门(Gate)"
A/B 测试设计
对照组(Control):门槛设在第30关(gate_30)
实验组(Treatment):门槛推迟到第40关(gate_40)
核心指标 次日留存率(retention_1)、七日留存率(retention_7)
原假设 (H₀) 推迟门槛对留存率没有显著影响
02 数据读取与基础勘探

首先,读取 Kaggle 开源的真实用户行为数据,检查数据结构的完整性。

Python
import pandas as pd

# 读取数据
df = pd.read_csv('cookie_cats.csv')

# 查看数据基本信息
print("数据基本信息:")
df.info()
执行结果
90,189 条数据,0 缺失值。包含 userid, version, sum_gamerounds, retention_1, retention_7 五个核心字段。
03 SRM 校验 (样本比例失调排雷)
⚠️
重要:在计算转化率前,必须检验底层分流系统是否出现 Bug(即两组人数是否符合 50:50 的预期分配)。运用卡方检验(Chi-Square Test)进行量化评估。
Python
from scipy.stats import chisquare

# 统计实际分流人数
version_counts = df.groupby('version')['userid'].nunique()
print("两组实际分流人数:\n", version_counts)

# 设定预期分流人数 (50/50)
total_users = df['userid'].nunique()
expected_counts = [total_users / 2, total_users / 2]
observed_counts = version_counts.values

# 卡方检验
chi2_stat, p_val = chisquare(f_obs=observed_counts, f_exp=expected_counts)
print(f"\nSRM 卡方检验 p-value: {p_val:.4f}")
执行结果与业务洞察
  • gate_30 人数:44,700;gate_40 人数:45,489
  • p-value = 0.0086(< 0.05)
  • 洞察:出现显著的 SRM(样本比例失调)。在真实业务中,此时应立刻叫停实验并排查分流代码 Bug。此处仅作演示,假设为合理波动,继续后续分析。
04 异常值清洗 (揪出数据噪音)

检查用户游戏局数(sum_gamerounds)的分布,防止极端数据拉偏大盘方差。

Python
print("游戏局数描述性统计:")
print(df['sum_gamerounds'].describe())

# 剔除极端异常值
df_clean = df[df.sum_gamerounds < df.sum_gamerounds.max()]
执行结果与业务洞察
  • 发现最大值(max)高达 49,854 局,远超 75% 分位数的 51 局
  • 洞察:识别出疑似测试账号或脚本外挂,将其从数据集中剔除,保障测试基准线的纯净。
05 核心指标假设检验 (Z-Test)
💡
留存属于"是/否"的二项分布数据,调用 statsmodels 库执行双样本比例 Z 检验(Two-Proportion Z-test)。
Python
from statsmodels.stats.proportion import proportions_ztest

# 查看各组大盘留存率
retention_rates = df_clean.groupby('version')[['retention_1', 'retention_7']].mean()
print("各组实际留存率(%):\n", retention_rates * 100)

# --- 次日留存 Z-test ---
successes_1 = [
    df_clean[df_clean.version == 'gate_30'].retention_1.sum(),
    df_clean[df_clean.version == 'gate_40'].retention_1.sum()
]
nobs_1 = [
    df_clean[df_clean.version == 'gate_30'].shape[0],
    df_clean[df_clean.version == 'gate_40'].shape[0]
]
z_stat_1, p_val_1 = proportions_ztest(count=successes_1, nobs=nobs_1)
print(f"\n【次日留存】Z-statistic: {z_stat_1:.4f}, p-value: {p_val_1:.4f}")

# --- 七日留存 Z-test ---
successes_7 = [
    df_clean[df_clean.version == 'gate_30'].retention_7.sum(),
    df_clean[df_clean.version == 'gate_40'].retention_7.sum()
]
z_stat_7, p_val_7 = proportions_ztest(count=successes_7, nobs=nobs_1)
print(f"【七日留存】Z-statistic: {z_stat_7:.4f}, p-value: {p_val_7:.4f}")
🎯 执行结果与最终业务决策
  1. 次日留存: gate_30 (44.82%) vs gate_40 (44.23%), p-value = 0.0739(> 0.05)。 统计上无显著差异。
  2. 七日留存: gate_30 (19.02%) vs gate_40 (18.20%), p-value = 0.0016(< 0.05)。 统计上出现极度显著的下降。
  3. 最终决策: 坚决拒绝上线 gate_40 版本。 数据证明推迟付费门槛不仅没有提升用户黏性,反而导致了七日留存的显著流失。 维持原有 gate_30 的设计。
返回首页