# Python实战:A/B测试中的统计功效计算与样本量反推(附完整代码)
如果你在互联网公司负责过产品迭代或运营策略,大概率对A/B测试不陌生。我们满怀期待地设计了一个新按钮、优化了推荐算法,或者调整了落地页文案,然后小心翼翼地切一部分流量进行对比实验。但现实往往很骨感:辛辛苦苦跑了两周数据,最后发现两组指标“没有显著差异”。更让人头疼的是,你根本不知道是这个改动真的无效,还是因为样本量不够,导致一个本应有效的改动被“淹没”在了统计噪声里。这种不确定性,正是数据分析师和产品经理在日常工作中最常遇到的痛点。
这篇文章,我们就来彻底解决两个核心问题:第一,如何量化我们“发现真实差异”的能力?这就是**统计功效**。第二,在实验开始前,我们到底需要准备多少用户参与测试,才能有足够的把握检测到预期的效果?这就是**样本量反推**。我们将完全用Python来实现这些计算,并提供一套可以直接嵌入到你现有数据分析流水线中的代码模板。告别对黑盒工具的依赖,真正理解并掌控你实验的统计基础。
## 1. 从“不显著”的困惑到统计功效的清晰认知
很多朋友在做A/B测试时,只关注p值是否小于0.05。如果p值显著,皆大欢喜;如果不显著,往往就草草下结论说“改动无效”。这种思路忽略了一个关键环节:**统计功效**。简单来说,功效就是当两组间确实存在差异时,你的实验能成功检测出这个差异的概率。
想象一下,你是一名质检员,你的任务是检查一批零件是否有瑕疵。**显著性水平(α)** 相当于你把合格零件误判为瑕疵品的概率(第一类错误),通常我们设得很低,比如5%。而**统计功效(1-β)** 则相当于当零件真有瑕疵时,你能成功把它揪出来的概率。如果功效只有20%,那就意味着80%的瑕疵品会从你眼皮底下溜走。在A/B测试中,低功效的实验就像这个不称职的质检员,会让许多真正有效的产品改进方案被错误地“枪毙”。
> 注意:一个常见的误解是,p值不显著就等同于“没有效果”。实际上,这很可能只是你的实验“火力不足”,没有足够的能力去发现那个客观存在的微小效果。
那么,哪些因素会影响统计功效呢?主要有四个:
- **效应量**:你期望检测到的最小差异。差异越大,越容易被检测到,所需样本量越小。比如,点击率从2%提升到2.1%(相对提升5%)比从2%提升到2.02%(相对提升1%)需要更大的样本。
- **样本量**:参与实验的用户总数。样本量越大,对总体参数的估计就越精确,功效自然越高。
- **显著性水平(α)**:你设定的判断阈值,通常为0.05。α设得越大(比如0.1),越容易拒绝原假设,功效也会相应提高,但犯第一类错误的风险也增加了。
- **数据的变异性**:指标的方差。方差越大,数据越“嘈杂”,检测信号就越困难,需要更大的样本量来达到相同的功效。
在实际工作中,我们往往是在实验设计阶段,先确定我们希望检测的效应量、可接受的α水平(通常是0.05)以及期望达到的统计功效(行业惯例常取80%或90%),然后去反推需要的样本量。这个过程,就是我们接下来要实现的**样本量计算**。
## 2. 比率检验:A/B测试的统计基石
A/B测试中最常见的指标,如点击率、转化率、留存率,本质上都是比率(比例)。用户要么点击,要么不点击;要么转化,要么不转化。这类服从伯努利分布的数据,我们使用**比率检验**来比较其差异。这主要分为两种情况:单样本比率检验和双样本比率检验。
### 2.1 单样本比率检验:与历史基准对比
有时,我们不是同时跑A/B两个版本,而是只上线一个新版本(B版本),然后拿它的数据与一个已知的历史基准(例如,过去三个月的平均转化率)进行对比。这就是单样本比率检验。
其核心思想是,在大样本条件下(`n*p > 5` 且 `n*(1-p) > 5`),样本比率近似服从正态分布。我们可以构造一个Z统计量:
```
Z = (p_hat - p0) / sqrt( p0 * (1 - p0) / n )
```
其中,`p_hat`是样本比率,`p0`是已知的总体比率(历史基准),`n`是样本量。然后我们将计算出的Z值与标准正态分布的临界值进行比较,做出决策。
下面是一个Python函数实现,它封装了单样本比率检验的Z检验、功效计算和样本量反推:
```python
import math
import scipy.stats as stats
def single_proportion_inference(p_hat, p0, n=None, alpha=0.05, power=0.8, test_type='two-sided'):
"""
单样本比率检验的完整工具函数。
参数:
p_hat: 样本观测到的比率(或期望检测的比率)。
p0: 原假设下的基准比率。
n: 样本量。如果为None,则用于计算所需样本量。
alpha: 显著性水平(第一类错误概率)。
power: 期望的统计功效(1 - 第二类错误概率)。
test_type: 检验类型,'two-sided'(双侧),'greater'(右侧),'less'(左侧)。
返回:
一个包含Z检验结果、统计功效或所需样本量的字典。
"""
result = {}
# 1. 执行Z检验(如果提供了样本量n)
if n is not None:
se = math.sqrt(p0 * (1 - p0) / n) # 标准误
z_stat = (p_hat - p0) / se
if test_type == 'two-sided':
z_critical = stats.norm.ppf(1 - alpha/2)
p_value = 2 * (1 - stats.norm.cdf(abs(z_stat)))
is_significant = abs(z_stat) > z_critical
elif test_type == 'greater':
z_critical = stats.norm.ppf(1 - alpha)
p_value = 1 - stats.norm.cdf(z_stat)
is_significant = z_stat > z_critical
elif test_type == 'less':
z_critical = stats.norm.ppf(alpha)
p_value = stats.norm.cdf(z_stat)
is_significant = z_stat < z_critical
else:
raise ValueError("test_type must be 'two-sided', 'greater', or 'less'")
result['z_statistic'] = z_stat
result['p_value'] = p_value
result['is_significant'] = is_significant
result['critical_value'] = z_critical
# 2. 计算当前设置的统计功效
# 使用非中心化参数计算
if test_type == 'two-sided':
z_alpha = stats.norm.ppf(1 - alpha/2)
# 效应量的标准化形式(Cohen's h)
h = 2 * (math.asin(math.sqrt(p_hat)) - math.asin(math.sqrt(p0)))
# 非中心化参数
lambda_param = h * math.sqrt(n)
# 功效计算(近似)
power_calculated = (stats.norm.cdf(lambda_param - z_alpha) +
stats.norm.cdf(-lambda_param - z_alpha))
else: # 单侧检验
z_alpha = stats.norm.ppf(1 - alpha)
h = 2 * (math.asin(math.sqrt(p_hat)) - math.asin(math.sqrt(p0)))
lambda_param = h * math.sqrt(n)
if test_type == 'greater':
power_calculated = 1 - stats.norm.cdf(z_alpha - lambda_param)
else: # 'less'
power_calculated = stats.norm.cdf(-z_alpha - lambda_param)
result['achieved_power'] = power_calculated
# 3. 反推所需样本量(如果未提供样本量n)
else:
# 使用弧正弦变换法计算样本量,这是比率检验中更精确的方法
# Cohen's h 效应量
h = 2 * (math.asin(math.sqrt(p_hat)) - math.asin(math.sqrt(p0)))
if test_type == 'two-sided':
z_alpha = stats.norm.ppf(1 - alpha/2)
else:
z_alpha = stats.norm.ppf(1 - alpha)
z_beta = stats.norm.ppf(power)
n_required = ((z_alpha + z_beta) ** 2) / (h ** 2)
result['required_sample_size'] = math.ceil(n_required) # 向上取整
return result
# 示例:已知历史转化率p0=2%,新版本观测到p_hat=2.5%,样本量n=10000,做双侧检验
example_result = single_proportion_inference(p_hat=0.025, p0=0.02, n=10000, test_type='two-sided')
print(f"Z统计量: {example_result['z_statistic']:.4f}")
print(f"P值: {example_result['p_value']:.4f}")
print(f"是否显著: {example_result['is_significant']}")
print(f"当前功效: {example_result.get('achieved_power', 'N/A'):.2%}")
```
运行这段代码,你可以快速判断新版本是否显著优于历史基准,并了解当前实验设置的统计功效如何。如果功效太低(比如低于80%),你就需要慎重对待“不显著”的结论。
### 2.2 双样本比率检验:经典A/B测试场景
更常见的是经典的A/B测试场景:我们有对照组(A组)和实验组(B组),想比较两组的比率(如转化率)是否有显著差异。此时我们使用双样本比率检验。
原假设 H0: p_A = p_B,备择假设 H1: p_A ≠ p_B(或 >, <)。其检验统计量为:
```
Z = (p_A_hat - p_B_hat) / sqrt( p_pool * (1 - p_pool) * (1/n_A + 1/n_B) )
```
其中,`p_pool`是合并比率 `(x_A + x_B) / (n_A + n_B)`。
与单样本类似,我们也需要关心功效和样本量。双样本检验的样本量计算稍微复杂一点,因为它涉及到两个组的分配比例(通常是1:1,但也可能是其他比例)。下面是一个更实用的、面向A/B测试的完整类封装:
```python
class ABTestPowerCalculator:
"""
A/B测试(双样本比率检验)的统计功效与样本量计算器。
封装了假设检验、功效计算、样本量估算等核心功能。
"""
def __init__(self, alpha=0.05, power=0.8, test_type='two-sided', allocation_ratio=1.0):
"""
初始化计算器参数。
allocation_ratio: 实验组与对照组的样本量比例 (n_B / n_A)。默认为1(等比例分配)。
"""
self.alpha = alpha
self.power = power
self.test_type = test_type
self.allocation_ratio = allocation_ratio # k = n_B / n_A
# 根据检验类型确定Z临界值
if self.test_type == 'two-sided':
self.z_alpha = stats.norm.ppf(1 - self.alpha / 2)
else: # one-sided
self.z_alpha = stats.norm.ppf(1 - self.alpha)
self.z_beta = stats.norm.ppf(self.power)
def cohens_h(self, p1, p2):
"""计算比率差异的Cohen's h效应量。"""
return 2 * (math.asin(math.sqrt(p2)) - math.asin(math.sqrt(p1)))
def calculate_sample_size(self, p_control, p_treatment, mde=None):
"""
计算达到指定功效所需的总样本量(对照组+实验组)。
参数:
p_control: 对照组的预期比率(如基线转化率)。
p_treatment: 实验组的预期比率。
mde: 最小可检测效应(Minimum Detectable Effect),相对提升比例。
如果提供,p_treatment = p_control * (1 + mde)。
返回:
一个字典,包含对照组、实验组及总样本量。
"""
if mde is not None:
p_treatment = p_control * (1 + mde)
# 使用弧正弦变换法,这是比率检验样本量计算的推荐方法
h = self.cohens_h(p_control, p_treatment)
# 总样本量公式 (n_A + n_B)
# 当分配比例 k = n_B / n_A 时
k = self.allocation_ratio
n_A = ((self.z_alpha + self.z_beta) / h) ** 2 * (1 + 1/k)
n_B = n_A * k
total_n = n_A + n_B
return {
'control_sample_size': math.ceil(n_A),
'treatment_sample_size': math.ceil(n_B),
'total_sample_size': math.ceil(total_n),
'effect_size_h': h,
'expected_p_treatment': p_treatment
}
def calculate_power(self, p_control, p_treatment, n_control, n_treatment):
"""
给定样本量,计算当前实验设计的统计功效。
"""
# 计算非中心化参数 (lambda)
h = self.cohens_h(p_control, p_treatment)
# 对于双样本,有效样本量 n' = 1 / (1/n_A + 1/n_B)
n_prime = 1 / (1/n_control + 1/n_treatment)
lambda_param = h * math.sqrt(n_prime)
if self.test_type == 'two-sided':
power = (stats.norm.cdf(lambda_param - self.z_alpha) +
stats.norm.cdf(-lambda_param - self.z_alpha))
else: # one-sided
# 假设我们检测的是 p_treatment > p_control
power = 1 - stats.norm.cdf(self.z_alpha - lambda_param)
return min(max(power, 0), 1) # 确保功率在[0,1]区间
def run_hypothesis_test(self, pA_hat, pB_hat, nA, nB):
"""
执行双样本比率Z检验,返回检验结果。
"""
# 合并比率
p_pool = (pA_hat * nA + pB_hat * nB) / (nA + nB)
# 标准误
se = math.sqrt(p_pool * (1 - p_pool) * (1/nA + 1/nB))
# Z统计量
z_stat = (pB_hat - pA_hat) / se
if self.test_type == 'two-sided':
p_value = 2 * (1 - stats.norm.cdf(abs(z_stat)))
is_sig = abs(z_stat) > self.z_alpha
else: # 假设是“greater”检验,即检测B组是否大于A组
p_value = 1 - stats.norm.cdf(z_stat)
is_sig = z_stat > self.z_alpha
return {
'z_statistic': z_stat,
'p_value': p_value,
'is_significant': is_sig,
'confidence_interval': (
(pB_hat - pA_hat) - self.z_alpha * se,
(pB_hat - pA_hat) + self.z_alpha * se
)
}
def generate_sample_size_table(self, p_control, mde_list=[0.01, 0.02, 0.05, 0.1]):
"""
生成一个样本量需求表,用于快速评估不同效应量下的资源需求。
这对于实验前的资源规划和预期管理非常有用。
"""
table_data = []
for mde in mde_list:
sample_sizes = self.calculate_sample_size(p_control, mde=mde)
table_data.append({
'基线转化率': f"{p_control:.2%}",
'预期提升(MDE)': f"{mde:.1%}",
'预期新转化率': f"{sample_sizes['expected_p_treatment']:.2%}",
'对照组样本量': sample_sizes['control_sample_size'],
'实验组样本量': sample_sizes['treatment_sample_size'],
'总样本量': sample_sizes['total_sample_size']
})
return table_data
# 实例化计算器:使用默认参数(α=0.05, 功效=80%, 双侧检验,1:1分配)
calculator = ABTestPowerCalculator(alpha=0.05, power=0.8, test_type='two-sided')
# 场景:当前转化率2%,我们想检测5%的相对提升(即转化率提升到2.1%)
required_samples = calculator.calculate_sample_size(p_control=0.02, mde=0.05)
print("样本量估算结果:")
for key, value in required_samples.items():
print(f" {key}: {value}")
```
这个类提供了一个完整的工具箱。`calculate_sample_size` 方法是你实验设计阶段的利器,能告诉你要达到80%的把握检测出5%的提升,需要多少用户参与。`calculate_power` 方法则可以在实验中途或结束后,评估当前数据收集情况下的实际检测能力。
## 3. 超越基础:A/B测试中的高级考量与实战陷阱
掌握了核心的统计计算后,我们还需要把目光投向实际业务中更复杂的情况。纸上谈兵的计算完美,但真实数据往往“不听话”。
### 3.1 多重比较与“ peeking”问题
如果你同时测试多个变体(A/B/C/D...),或者频繁地查看实验中间结果(“peeking”),就会显著增加犯第一类错误(误报)的概率。这被称为**多重比较问题**。
* **邦费罗尼校正**:一种简单粗暴但保守的方法。如果你进行了 `m` 次比较,就将显著性水平调整为 `α/m`。例如,比较3个实验组和1个对照组(共3次比较),使用α=0.05/3≈0.0167作为新的阈值。这虽然控制了整体错误率,但会大大降低统计功效。
* **错误发现率控制**:在探索性分析中,FDR(False Discovery Rate)是比FWER(Family-Wise Error Rate)更实用的指标。`statsmodels` 库提供了相关实现。
* **序贯检验与贝叶斯方法**:专门为解决“peeking”问题而设计。它们允许你在实验过程中多次查看数据,而不会破坏错误率控制。例如,使用**序贯概率比检验**或**贝叶斯A/B测试**,可以更早地停止明显成功或失败的实验,提升效率。
```python
# 示例:使用statsmodels进行FDR校正(Benjamin-Hochberg方法)
import statsmodels.stats.multitest as multi
# 假设我们同时进行了5个A/B测试,得到了5个p值
p_values = [0.03, 0.01, 0.25, 0.004, 0.08]
# 使用BH方法校正
reject, pvals_corrected, _, _ = multi.multipletests(p_values, alpha=0.05, method='fdr_bh')
print("原始P值:", p_values)
print("FDR校正后P值:", pvals_corrected)
print("在α=0.05水平下是否拒绝原假设:", reject)
# 输出可能显示,虽然0.03和0.01原始显著,但校正后可能只有一个仍然显著。
```
### 3.2 效应量估计与置信区间
统计显著性(p值)只告诉你“有没有差异”,而**效应量**和**置信区间**则告诉你“差异有多大”以及“这个估计有多不确定”。报告效应量及其置信区间是更科学的做法。
对于比率差异,效应量就是 `p_treatment - p_control`,其 `(1-α)%` 置信区间为:
`(p_diff) ± Z * SE`,其中 `SE = sqrt( pA_hat*(1-pA_hat)/nA + pB_hat*(1-pB_hat)/nB )`。
一个宽泛的置信区间(例如,提升率在-1%到+3%之间)意味着我们的估计非常不确定,即使p值显著,这个结果的实用性也要打折扣。
### 3.3 样本量计算中的常见“坑”
1. **基于观测值计算样本量**:一个严重的错误是,先用小流量跑几天,得到一个“初步”的转化率差值,然后用这个差值去计算正式实验需要的样本量。这会导致严重的**样本选择偏差**,极大高估效应量,从而低估所需样本量。样本量计算应基于**业务上有意义的最小可检测效应**,而非前期观测值。
2. **忽略流失和不完全参与**:你计算需要10万用户,但实验过程中可能有20%的用户因为各种原因(如未登录、中途跳出)没有产生有效数据。因此,在计算流量分配时,需要预留缓冲,实际分配的流量应为 `计算样本量 / (1 - 流失率)`。
3. **指标方差估计不准**:对于非比率型指标(如人均消费金额、会话时长),样本量计算依赖于方差估计。如果方差估计得过小,样本量也会被低估。一个稳妥的做法是使用历史数据的方差,并考虑一定的上浮。
下表总结了不同场景下样本量估算的核心考量:
| 考量因素 | 比率指标 (如转化率) | 连续指标 (如客单价) | 注意事项 |
| :--- | :--- | :--- | :--- |
| **核心参数** | 基线转化率 (p)、最小相对提升 (MDE) | 基线均值 (μ)、标准差 (σ)、最小绝对提升 (Δ) | MDE应基于业务价值设定,而非统计敏感度 |
| **效应量度量** | Cohen‘s h (弧正弦变换) | Cohen’s d (标准化均值差) | |
| **样本量公式** | `N ≈ (Z_α + Z_β)² / h²` (每组) | `N ≈ 2 * (Z_α + Z_β)² * (σ/Δ)²` (每组) | 公式为近似值,实际使用推荐库函数 |
| **主要变异性来源** | 比率本身 (p(1-p)) | 数据标准差 (σ) | 连续指标的σ通常需要从历史数据估计 |
| **分配比例影响** | 非1:1分配时,总样本量需求增加 | 同上 | 最优分配比例通常是1:1,除非成本悬殊 |
> 提示:在启动一个长期或关键的A/B测试前,最好进行一次“**功效分析**”。即,使用计划中的样本量,反过来计算在当前MDE下能达到的功效。如果功效只有60%,你需要和业务方明确:我们有40%的风险错过一个真实存在的、有业务价值的改进。
## 4. 构建你的A/B测试分析流水线
理论最终要服务于实践。我们可以将上述所有功能整合到一个模块化的分析流水线中,使其能够处理从实验设计到结果分析的完整流程。
```python
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class ABTestPipeline:
"""一个简化的、端到端的A/B测试分析流水线模拟类。"""
def __init__(self, calculator):
self.calculator = calculator
self.results_log = []
def design_experiment(self, baseline_metric, mde, days, daily_traffic, allocation_ratio=1.0):
"""
实验设计阶段:估算所需样本量和实验周期。
"""
sample_req = self.calculator.calculate_sample_size(
p_control=baseline_metric,
mde=mde
)
total_needed = sample_req['total_sample_size']
# 考虑每日流量和分配比例
daily_exp_users = daily_traffic * (allocation_ratio / (1 + allocation_ratio))
estimated_days = math.ceil(total_needed / daily_exp_users)
design_summary = {
'design_timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'),
'baseline': baseline_metric,
'mde': mde,
'required_total_sample': total_needed,
'daily_traffic': daily_traffic,
'estimated_duration_days': estimated_days,
'sample_per_group': {
'control': sample_req['control_sample_size'],
'treatment': sample_req['treatment_sample_size']
}
}
self.results_log.append(('DESIGN', design_summary))
return design_summary
def simulate_data_collection(self, design_summary, true_effect=0.0):
"""
模拟数据收集过程。真实场景中,这里会连接数据仓库。
true_effect: 模拟的真实效应(例如0.05代表5%的真实提升)。0代表无真实效应。
"""
p_control = design_summary['baseline']
p_treatment = p_control * (1 + true_effect)
n_control = design_summary['sample_per_group']['control']
n_treatment = design_summary['sample_per_group']['treatment']
# 模拟生成二项分布数据
np.random.seed(42) # 可重复性
control_data = np.random.binomial(1, p_control, n_control)
treatment_data = np.random.binomial(1, p_treatment, n_treatment)
pA_hat = control_data.mean()
pB_hat = treatment_data.mean()
simulated_data = {
'simulation_timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'),
'true_effect': true_effect,
'observed_p_control': pA_hat,
'observed_p_treatment': pB_hat,
'observed_relative_lift': (pB_hat - pA_hat) / pA_hat,
'n_control': n_control,
'n_treatment': n_treatment,
'control_data': control_data,
'treatment_data': treatment_data
}
self.results_log.append(('SIMULATION', simulated_data))
return simulated_data
def analyze_results(self, simulated_data):
"""分析模拟收集到的数据,执行假设检验并计算功效。"""
obs_result = self.calculator.run_hypothesis_test(
pA_hat=simulated_data['observed_p_control'],
pB_hat=simulated_data['observed_p_treatment'],
nA=simulated_data['n_control'],
nB=simulated_data['n_treatment']
)
# 计算事后的统计功效(基于观测到的差异)
# 注意:这里基于观测值计算功效仅用于演示,实践中解释需谨慎
post_hoc_power = self.calculator.calculate_power(
p_control=simulated_data['observed_p_control'],
p_treatment=simulated_data['observed_p_treatment'],
n_control=simulated_data['n_control'],
n_treatment=simulated_data['n_treatment']
)
analysis_report = {
'analysis_timestamp': datetime.now().strftime('%Y-%m-%d %H:%M'),
'hypothesis_test_result': obs_result,
'post_hoc_power': post_hoc_power,
'interpretation': self._generate_interpretation(obs_result, simulated_data['true_effect'])
}
self.results_log.append(('ANALYSIS', analysis_report))
return analysis_report
def _generate_interpretation(self, test_result, true_effect):
"""根据检验结果和真实效应生成解读文本。"""
is_sig = test_result['is_significant']
ci_low, ci_high = test_result['confidence_interval']
if is_sig and true_effect != 0:
return "实验检测到显著差异,且这是一个正确的发现(统计功效生效)。"
elif is_sig and true_effect == 0:
return "实验检测到显著差异,但这可能是一个假阳性(第一类错误)。"
elif not is_sig and true_effect == 0:
return "实验未检测到显著差异,这是一个正确的结论。"
else: # not is_sig and true_effect != 0
return "实验未检测到显著差异,但真实效应存在。这可能是统计功效不足导致的第二类错误。"
def run_full_simulation(self, baseline=0.02, mde=0.05, true_effect=0.05):
"""运行一个完整的模拟流程。"""
print("=== A/B测试全流程模拟开始 ===")
# 1. 设计
design = self.design_experiment(baseline, mde, days=14, daily_traffic=5000)
print(f"1. 实验设计完成。预计需要总样本量 {design['required_total_sample']}, 约需 {design['estimated_duration_days']} 天。")
# 2. 模拟数据收集
data = self.simulate_data_collection(design, true_effect=true_effect)
print(f"2. 数据模拟完成。观测到对照组转化率: {data['observed_p_control']:.3%}, 实验组: {data['observed_p_treatment']:.3%}。")
# 3. 分析
analysis = self.analyze_results(data)
test_res = analysis['hypothesis_test_result']
print(f"3. 假设检验结果: Z = {test_res['z_statistic']:.3f}, p = {test_res['p_value']:.4f}, 显著: {test_res['is_significant']}。")
print(f" 差异的95%置信区间: [{ci_low:.3%}, {ci_high:.3%}]".format(ci_low=test_res['confidence_interval'][0], ci_high=test_res['confidence_interval'][1]))
print(f" 事后功效: {analysis['post_hoc_power']:.2%}")
print(f" 解读: {analysis['interpretation']}")
print("=== 模拟结束 ===")
return design, data, analysis
# 使用流水线进行模拟
pipeline = ABTestPipeline(calculator)
# 模拟一个真实存在5%提升的场景
design, data, analysis = pipeline.run_full_simulation(baseline=0.02, mde=0.05, true_effect=0.05)
```
这个流水线类将设计、模拟、分析串联起来,让你能在一个可控的环境里预演整个A/B测试过程。你可以通过调整 `true_effect` 参数(设为0或一个正数)来模拟“改动无效”和“改动有效”两种场景,直观感受统计功效和两类错误是如何发生的。
最后,记住这些工具的目的是为了辅助决策,而非替代业务判断。一个在统计上显著但提升微乎其微的改动,可能不值得上线;而一个统计功效不足、结果不显著的实验,也不应成为否定一个好创意的最终判决。将统计严谨性与产品直觉相结合,才是数据驱动决策的精髓。