未记录事实核验 · 状态:现行内容
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适用于学习R中的样本量估计、假设检验、多重比较校正、Beta-Binomial后验模拟及报告模板复用,不是完整的生产实验决策系统。各示例使用模拟或设定数据,报告图中的转化率对比与累计趋势来自独立模拟;94.2%与3.7%仅为假设性报告措辞。单侧检验方向须在观察结果前确定,分析以有效随机分流和独立观测为前提;上线决策仍需结合业务效应、置信区间、护栏指标与预定停止规则。正文未提供SRM检查实现,相关待创建文档不属于完成所列模拟练习的必要材料。贝叶斯概率须结合所用模型和先验解释。本次仅作全文结构审阅,未运行代码或进行外部事实、包版本及链接可达性核验。 已取消无法确定目标的可点击引用「待创建:实验平台设计与SRM检验」,保留原提及;Missing link target: 待创建:实验平台设计与SRM检验
R语言在A/B测试与实验分析中的应用
abstract 学习目标
- 掌握A/B测试的完整统计分析流程(从样本量计算到结论输出)
- 理解多重比较问题及其校正方法
- 能够用贝叶斯方法做A/B测试并给出概率性结论
- 具备编写可复用A/B测试分析模板的能力
quote 核心洞察
频率派A/B测试可用假设检验、效应量和置信区间描述差异;贝叶斯A/B测试可在指定模型和先验下估计"B比A好的后验概率"。前者不应只简化为显著或不显著,后者也不等于上线风险已被完整量化。当业务方问"新版本有多大把握比旧版本好"时,可报告后验概率,并同时说明模型、先验及业务效应。
一、A/B测试的统计基础
1.1 核心概念速查
| 概念 | 含义 | 业务含义 |
|---|---|---|
| 零假设 H0 | 两组无差异 | 新版本和旧版本效果一样 |
| 备择假设 H1 | 两组有差异 | 新版本效果不同于旧版本 |
| 显著性水平 alpha | 犯第一类错误的概率(通常0.05) | 误报率:H0为真但拒绝H0的概率 |
| 统计功效 power | 在指定备择效应下正确拒绝H0的概率(常设为0.8) | 检出真实效应的概率;漏报率为1-power,即第二类错误概率beta |
| p值 | 在H0及检验模型假设成立时,得到至少与当前统计量一样极端结果的概率 | 在预先设定alpha=0.05且检验程序适用时,p < 0.05 则拒绝H0 |
| 效应量 effect size | 两组差异的实际大小 | 转化率从3%提升到3.1%,效应量很小 |
1.2 第一类错误与第二类错误
# 可视化两类错误的关系
library(ggplot2)
library(patchwork)
x <- seq(-4, 4, 0.01)
null_dist <- dnorm(x, 0, 1)
alt_dist <- dnorm(x, 1.5, 1) # 效应量1.5
df <- data.frame(
x = rep(x, 2),
y = c(null_dist, alt_dist),
dist = rep(c("H0(零假设)", "H1(备择假设)"), each = length(x))
)
ggplot(df, aes(x, y, color = dist)) +
geom_line(size = 1.2) +
geom_vline(xintercept = 1.645, linetype = "dashed", color = "red", size = 1) +
annotate("text", x = 1.645, y = 0.42, label = "临界值(alpha=0.05)", hjust = 1.1, color = "red") +
annotate("rect", xmin = 1.645, xmax = 4, ymin = 0, ymax = Inf,
fill = "red", alpha = 0.1) +
annotate("rect", xmin = -4, xmax = 1.645, ymin = 0, ymax = Inf,
fill = "blue", alpha = 0.05) +
annotate("text", x = 0.5, y = 0.05, label = "beta (第二类错误)", color = "blue") +
annotate("text", x = 2.5, y = 0.05, label = "alpha (第一类错误)", color = "red") +
scale_color_manual(values = c("#2196F3", "#E91E63")) +
labs(title = "两类错误示意图", x = "统计量", y = "概率密度", color = "分布") +
theme_minimal()
二、样本量计算:实验前的必修课
在启动A/B测试前,必须先确定需要多少样本量,否则实验可能:
- 样本太少:有真实效果但检测不出(功效不足)
- 样本太多:浪费资源,且增加线上风险
2.1 连续变量的样本量计算(如平均订单金额)
# power.t.test:内置函数
power.t.test(
delta = 5, # 期望检测到的差异(元)
sd = 50, # 历史标准差
sig.level = 0.05, # alpha
power = 0.8, # 统计功效
type = "two.sample",
alternative = "two.sided"
)
# 输出:每组需要的样本量 n
# 用pwr包做更多类型的功效分析
library(pwr)
# 二比例检验(如转化率)
pwr.2p.test(
h = ES.h(0.035, 0.03), # ES.h计算比例差异的效应量(Cohen's h)
sig.level = 0.05,
power = 0.8,
alternative = "greater" # 单侧检验(只关心B是否优于A)
)
2.2 样本量敏感性分析
library(tidyverse)
# 不同效应量需要的样本量
delta_range <- seq(1, 20, by = 0.5)
n_required <- map_dbl(delta_range, function(d) {
power.t.test(delta = d, sd = 50, sig.level = 0.05, power = 0.8)$n
})
tibble(delta = delta_range, n = ceiling(n_required)) %>%
ggplot(aes(x = delta, y = n)) +
geom_line(color = "#2196F3", size = 1.2) +
geom_point(size = 2, color = "#E91E63") +
scale_y_log10(labels = scales::comma) +
labs(
title = "效应量 vs 所需样本量",
subtitle = "alpha=0.05, power=0.8, SD=50",
x = "期望检测差异(元)",
y = "每组所需样本量(对数轴)"
) +
theme_minimal()
三、假设检验的R实现
3.1 独立样本t检验(连续变量)
set.seed(42)
# 模拟A/B实验数据
control <- rnorm(500, mean = 200, sd = 50) # 对照组:平均订单200元
treatment <- rnorm(500, mean = 212, sd = 52) # 实验组:平均订单212元
# Welch's t检验(不要求方差相等)
result_t <- t.test(treatment, control, alternative = "greater")
cat("=== t检验结果 ===\n")
cat("对照组均值:", round(mean(control), 2), "\n")
cat("实验组均值:", round(mean(treatment), 2), "\n")
cat("差异:", round(mean(treatment) - mean(control), 2), "\n")
cat("p值:", round(result_t$p.value, 4), "\n")
cat("95%置信区间:", round(result_t$conf.int, 2), "\n")
cat("结论:", ifelse(result_t$p.value < 0.05, "拒绝H0,差异显著", "不拒绝H0,差异不显著"), "\n")
3.2 比例检验(转化率)
# 场景:改版按钮颜色,测试转化率是否提升
n_control <- 5000 # 对照组用户数
n_treatment <- 5000 # 实验组用户数
x_control <- 150 # 对照组转化人数(转化率3%)
x_treatment <- 185 # 实验组转化人数(转化率3.7%)
result_prop <- prop.test(
x = c(x_treatment, x_control),
n = c(n_treatment, n_control),
alternative = "greater", # 单侧:实验组>对照组
correct = FALSE # 不做连续性校正(大样本下影响小)
)
# 结果解读
cat("对照组转化率:", scales::percent(x_control / n_control), "\n")
cat("实验组转化率:", scales::percent(x_treatment / n_treatment), "\n")
cat("相对提升:", scales::percent((x_treatment/n_treatment - x_control/n_control)/(x_control/n_control)), "\n")
cat("p值:", round(result_prop$p.value, 4), "\n")
3.3 卡方检验(多类别变量)
# 场景:测试不同推送文案对用户行为的影响
behavior_matrix <- matrix(
c(300, 150, 50, # 方案A:点击/加购/购买
280, 180, 80, # 方案B
320, 130, 60), # 方案C
nrow = 3, byrow = TRUE,
dimnames = list(
c("方案A", "方案B", "方案C"),
c("只点击", "加购", "购买")
)
)
result_chisq <- chisq.test(behavior_matrix)
cat("卡方统计量:", round(result_chisq$statistic, 3), "\n")
cat("p值:", round(result_chisq$p.value, 4), "\n")
# 标准化残差(找出差异最大的单元格)
result_chisq$stdres
四、多重比较:同时测试多个变体
4.1 为什么多重比较是陷阱
# 模拟:同时做20个A/A测试(零假设成立时仍可能出现p值小于0.05)
set.seed(123)
p_values <- replicate(20, {
t.test(rnorm(100), rnorm(100))$p.value
})
# 在检验假设成立时,单次检验的第一类错误概率约为5%,本次模拟的数量会随机波动
cat("20次测试中显著的数量:", sum(p_values < 0.05), "\n") # 期望为1个假阳性,不代表每次模拟恰有1个
cat("Familywise错误率:", 1 - (1-0.05)^20, "\n") # 约64%
4.2 多重比较校正方法
# 示例:同时测试5个按钮颜色的转化率
set.seed(42)
p_values_raw <- c(0.032, 0.041, 0.068, 0.002, 0.158)
results_correction <- data.frame(
方案 = paste0("方案", LETTERS[1:5]),
原始p值 = p_values_raw,
# Bonferroni(最保守):p_adjusted = p * n_tests
Bonferroni = p.adjust(p_values_raw, method = "bonferroni"),
# BH(Benjamini-Hochberg):控制FDR,比Bonferroni宽松
BH = p.adjust(p_values_raw, method = "BH"),
# Holm(比Bonferroni略宽松,仍控制FWER)
Holm = p.adjust(p_values_raw, method = "holm")
)
results_correction %>%
mutate(across(where(is.numeric), ~ round(.x, 4)))
选择建议:
- 若需控制至少一次误报的概率(FWER),可考虑Bonferroni或Holm,并结合预先指定的检验族与分析方案。
- 若目标是控制被拒绝假设中的预期错误发现比例(FDR),可在满足相应依赖条件时考虑BH;不能仅按行业选择校正方法。
五、贝叶斯A/B测试:给老板看的概率语言
频率派结果:"p=0.03,差异显著"(很多人不理解这是什么意思) 贝叶斯报告措辞示例(假设性数字,非下方代码的计算结果):"在指定模型和先验下,B比A好的后验概率是94.2%,期望相对提升3.7%"。
library(bayesAB) # 贝叶斯A/B测试专用包
# 二项分布(转化率场景)
AB_test_result <- bayesAB::bayesTest(
A_data = rbinom(n_control, 1, 0.03), # 对照组:3%转化率
B_data = rbinom(n_treatment, 1, 0.037), # 实验组:3.7%转化率
priors = c("alpha" = 1, "beta" = 1), # Beta(1,1)均匀先验
distribution = "bernoulli"
)
summary(AB_test_result)
# 关键输出:P(B > A) = ? 期望提升 = ?
plot(AB_test_result) # 后验分布可视化
手动实现贝叶斯A/B测试(不依赖包):
# Beta-Binomial贝叶斯更新
bayes_ab_test <- function(alpha_A, beta_A, alpha_B, beta_B, n_samples = 100000) {
# 从后验分布中采样
samples_A <- rbeta(n_samples, alpha_A, beta_A)
samples_B <- rbeta(n_samples, alpha_B, beta_B)
prob_B_better <- mean(samples_B > samples_A)
expected_lift <- mean((samples_B - samples_A) / samples_A)
# 可视化
df_plot <- data.frame(
value = c(samples_A[seq_len(min(10000, n_samples))], samples_B[seq_len(min(10000, n_samples))]),
group = rep(c("对照组A", "实验组B"), each = min(10000, n_samples))
)
p <- ggplot(df_plot, aes(x = value, fill = group)) +
geom_density(alpha = 0.5) +
scale_fill_manual(values = c("#2196F3", "#E91E63")) +
labs(
title = "贝叶斯A/B测试:后验分布",
subtitle = paste0("P(B > A) = ", scales::percent(prob_B_better, 0.1),
" | 期望相对提升 = ", scales::percent(expected_lift, 0.1)),
x = "转化率", y = "概率密度", fill = "分组"
) +
theme_minimal()
list(
prob_B_better = prob_B_better,
expected_lift = expected_lift,
plot = p
)
}
# 使用(后验参数 = 先验 + 观测数据)
# Alpha = 成功次数 + 先验alpha,Beta = 失败次数 + 先验beta
result <- bayes_ab_test(
alpha_A = 1 + x_control, # 1 + 150
beta_A = 1 + (n_control - x_control), # 1 + 4850
alpha_B = 1 + x_treatment, # 1 + 185
beta_B = 1 + (n_treatment - x_treatment)
)
cat("B比A好的概率:", scales::percent(result$prob_B_better), "\n")
cat("期望相对提升:", scales::percent(result$expected_lift), "\n")
result$plot
六、A/B测试报告的标准图表
library(tidyverse)
library(patchwork)
# 模拟实验数据
set.seed(42)
experiment_data <- tibble(
group = rep(c("对照组A", "实验组B"), each = 1000),
converted = c(rbinom(1000, 1, 0.03), rbinom(1000, 1, 0.037))
)
# 图1:转化率对比(含置信区间)
conv_summary <- experiment_data %>%
group_by(group) %>%
summarise(
n = n(),
x = sum(converted),
rate = x / n,
# prop.test默认返回含连续性校正的得分置信区间;若需未校正的Wilson区间,应设置correct = FALSE
ci_low = prop.test(x, n)$conf.int[1],
ci_high = prop.test(x, n)$conf.int[2]
)
p1 <- ggplot(conv_summary, aes(x = group, y = rate, fill = group)) +
geom_col(width = 0.5, alpha = 0.8) +
geom_errorbar(aes(ymin = ci_low, ymax = ci_high), width = 0.15, size = 1) +
geom_text(aes(label = scales::percent(rate, 0.1)), vjust = -0.5, fontface = "bold") +
scale_y_continuous(labels = scales::percent, limits = c(0, 0.06)) +
scale_fill_manual(values = c("#2196F3", "#E91E63")) +
labs(title = "A) 转化率对比(含95%置信区间)", x = NULL, y = "转化率") +
theme_minimal() +
theme(legend.position = "none")
# 图2:累计转化率随时间变化(漏斗监控)
daily_data <- tibble(
day = 1:14,
rate_A = cumsum(rbinom(14, 100, 0.03)) / cumsum(rep(100, 14)),
rate_B = cumsum(rbinom(14, 100, 0.037)) / cumsum(rep(100, 14))
) %>%
pivot_longer(c(rate_A, rate_B), names_to = "group", values_to = "rate") %>%
mutate(group = ifelse(group == "rate_A", "对照组A", "实验组B"))
p2 <- ggplot(daily_data, aes(x = day, y = rate, color = group)) +
geom_line(size = 1.2) +
geom_point(size = 2) +
scale_y_continuous(labels = scales::percent) +
scale_color_manual(values = c("#2196F3", "#E91E63")) +
labs(title = "B) 累计转化率趋势", x = "实验天数", y = "累计转化率", color = "分组") +
theme_minimal()
p1 + p2 + plot_annotation(
title = "A/B测试结果报告",
subtitle = "独立模拟数据:转化率对比各1000用户;趋势为14天、每天各100用户"
)
七、完整代码框架:从业务问题到统计结论
# ============================================
# A/B测试完整分析框架
# ============================================
analyze_ab_test <- function(
control_n, control_convert,
treatment_n, treatment_convert,
alpha = 0.05,
n_bayesian_samples = 100000
) {
# 1. 基本指标
cr_control <- control_convert / control_n
cr_treatment <- treatment_convert / treatment_n
lift <- (cr_treatment - cr_control) / cr_control
# 2. 频率派检验
test_result <- prop.test(
x = c(treatment_convert, control_convert),
n = c(treatment_n, control_n),
alternative = "greater"
)
# 3. 贝叶斯估计
samples_A <- rbeta(n_bayesian_samples, 1 + control_convert, 1 + control_n - control_convert)
samples_B <- rbeta(n_bayesian_samples, 1 + treatment_convert, 1 + treatment_n - treatment_convert)
prob_B_better <- mean(samples_B > samples_A)
# 4. 输出报告
cat("========================================\n")
cat(" A/B测试分析报告\n")
cat("========================================\n")
cat(sprintf("对照组:%d用户,%d转化,转化率 %.2f%%\n", control_n, control_convert, cr_control * 100))
cat(sprintf("实验组:%d用户,%d转化,转化率 %.2f%%\n", treatment_n, treatment_convert, cr_treatment * 100))
cat(sprintf("相对提升:%.1f%%\n", lift * 100))
cat("----------------------------------------\n")
cat(sprintf("频率派检验 p值:%.4f\n", test_result$p.value))
cat(sprintf("结论(alpha=%.2f):%s\n", alpha,
ifelse(test_result$p.value < alpha, "在预设单侧检验下差异显著;是否上线仍需评估业务效应、区间与护栏指标", "未达到预设显著性阈值;按预定停止规则处理,不据此认定无差异或自动延长实验")))
cat("----------------------------------------\n")
cat(sprintf("贝叶斯估计:B比A好的概率 = %.1f%%\n", prob_B_better * 100))
cat("========================================\n")
}
# 使用
analyze_ab_test(5000, 150, 5000, 185)
相关文档
- 10-统计建模与机器学习 — 假设检验的统计学基础
- 11-R-Markdown数据报告自动化 — 把A/B测试报告自动化
- 09-ggplot2数据可视化深度实战 — 实验结果可视化
- 待创建:实验平台设计与SRM检验 — 样本比率失配的检测与处理
tip 立即可用的技巧
在每次A/B测试报告中,除了p值,一定要同时报告效应量和置信区间。p值衡量在零假设及检验模型假设成立时,得到至少与当前统计量一样极端结果的概率,不能直接判定"有没有差异";效应量描述估计差异的大小,置信区间表达估计的不确定性。三者结合有助于解释统计结果,但业务决策还需结合实验设计、护栏指标与预定停止规则。