☰
第7讲:统计学基础——AB 实验中的显著性检验与陷阱
2026/11/5 1:58:59 网站建设 项目流程

一、为什么搞 AI 也要懂统计

工程师常犯的错误 ──────────────────────────────────────────────────── ❌ "实验组转化率 12%,对照组 10%,高了 20%!上线!" ✅ 先问三个问题: 1. 样本量够吗?—— 100 vs 100 还是 10000 vs 10000? 2. 差异是随机波动还是真实效果?—— p 值多少? 3. 即使显著,效果有实际意义吗?—— effect size 多大? 核心认知:没有统计检验的实验结论 ≈ 抛硬币决策

二、核心概念速览

你需要记住的 5 个概念 ──────────────────────────────────────────────────── 原假设 H₀:实验组 = 对照组(没有效果) 备择假设 H₁:实验组 ≠ 对照组(有效果) p 值:在原假设成立的前提下,观察到当前结果(或更极端结果)的概率 通俗理解:这个结果是巧合的可能性有多大 显著性水平 α:你愿意承担的风险,通常取 0.05 含义:如果 p < 0.05,我们说"结果显著" 第一类错误(假阳性):实际上没效果,你却说有效 —— α 就是它的概率 第二类错误(假阴性):实际上有效果,你却说无效 —— β 是它的概率,1-β 叫统计功效 置信区间:真实效果的取值范围,比如 [1.2%, 3.8%] 表示真实提升在 1.2%~3.8% 之间

三、什么时候用什么检验

检验方法选择树 ──────────────────────────────────────────────────── 你要比较的是什么? │ ├── 两组比较 │ ├── 指标是比率(转化率、点击率、通过率) │ │ └── 样本量大 → Z 检验 │ │ └── 样本量小 → Fisher 精确检验 │ │ │ └── 指标是均值(响应时间、得分、Token 数) │ ├── 数据正态 → T 检验 │ └── 数据非正态 → Mann-Whitney U 检验 │ ├── 多组比较(超过 2 个变体) │ ├── 比率指标 → 卡方检验 │ └── 均值指标 → ANOVA(方差分析) │ └── 配对比较(同一用户前后对比) └── 比率 → McNemar 检验 └── 均值 → 配对 T 检验

四、Z 检验实现(最常用)

// stats/ztest.go package stats import ( "math" ) // ─── Z 检验:比较两个比例的差异 ─── // TwoProportionZTest 两比例 Z 检验 type TwoProportionZTest struct { N1 int // 对照组样本量 P1 float64 // 对照组比例 N2 int // 实验组样本量 P2 float64 // 实验组比例 } // NewTwoProportionZTest 创建 Z 检验 func NewTwoProportionZTest(n1 int, p1 float64, n2 int, p2 float64) *TwoProportionZTest { return &TwoProportionZTest{ N1: n1, P1: p1, N2: n2, P2: p2, } } // ZScore 计算 Z 值 func (t *TwoProportionZTest) ZScore() float64 { // 合并比例 pooled := (float64(t.N1)*t.P1 + float64(t.N2)*t.P2) / float64(t.N1+t.N2) // 标准误差 se := math.Sqrt(pooled * (1 - pooled) * (1/float64(t.N1) + 1/float64(t.N2))) if se == 0 { return 0 } return (t.P2 - t.P1) / se } // PValue 计算 p 值(双尾) func (t *TwoProportionZTest) PValue() float64 { z := math.Abs(t.ZScore()) return 2 * (1 - standardNormalCDF(z)) } // ConfidenceInterval 计算置信区间(95%) func (t *TwoProportionZTest) ConfidenceInterval() (lower, upper float64) { diff := t.P2 - t.P1 se := math.Sqrt( t.P1*(1-t.P1)/float64(t.N1) + t.P2*(1-t.P2)/float64(t.N2), ) margin := 1.96 * se // 95% 置信水平 return diff - margin, diff + margin } // IsSignificant 是否显著 func (t *TwoProportionZTest) IsSignificant(alpha float64) bool { return t.PValue() < alpha } // MinimumSampleSize 计算所需最小样本量 // delta: 最小可检测效应大小(绝对值,如 0.02 表示 2%) // alpha: 显著性水平 // power: 统计功效(通常 0.8) func MinimumSampleSize(baseRate float64, delta float64, alpha float64, power float64) int { // Z 临界值 zAlpha := normInv(1 - alpha/2) zBeta := normInv(power) // 样本量公式 pAvg := baseRate + delta/2 numerator := math.Pow( zAlpha*math.Sqrt(2*pAvg*(1-pAvg))+ zBeta*math.Sqrt(baseRate*(1-baseRate)+(baseRate+delta)*(1-baseRate-delta)), 2, ) denominator := delta * delta return int(math.Ceil(numerator / denominator)) } // ─── 辅助函数 ─── // standardNormalCDF 标准正态分布累积分布函数 func standardNormalCDF(x float64) float64 { return 0.5 * (1 + math.Erf(x/math.Sqrt2)) } // normInv 正态分布逆累积分布函数(近似) func normInv(p float64) float64 { // Abramowitz and Stegun 近似 if p < 0.5 { return -normInv(1 - p) } a1 := -3.969683028665376e+01 a2 := 2.209460984245205e+02 a3 := -2.759285104469687e+02 a4 := 1.383577518672690e+02 a5 := -3.066479806614716e+01 a6 := 2.506628277459239e+00 b1 := -5.447609879822406e+01 b2 := 1.615858368580409e+02 b3 := -1.556989798598866e+02 b4 := 6.680131188771972e+01 b5 := -1.328068155288572e+01 c1 := -7.784894002430293e-03 c2 := -3.223964580411365e-01 c3 := -2.400758277161838e+00 c4 := -2.549732539343734e+00 c5 := 4.374664141464968e+00 c6 := 2.938163982698783e+00 d1 := 7.784695709041462e-03 d2 := 3.224671290700398e-01 d3 := 2.445134137142996e+00 d4 := 3.754408661907416e+00 q := p - 0.5 var r, x float64 if math.Abs(q) <= 0.425 { r = 0.180625 - q*q x = q * (((((((a6*r+a5)*r+a4)*r+a3)*r+a2)*r+a1)*r+1) / ((((((b6*r+b5)*r+b4)*r+b3)*r+b2)*r+b1)*r+1)) } else { r = p if q > 0 { r = 1 - p } r = math.Sqrt(-math.Log(r)) x = (((((((c6*r+c5)*r+c4)*r+c3)*r+c2)*r+c1)*r+1) / ((((((d5*r+d4)*r+d3)*r+d2)*r+d1)*r+1)) if q < 0 { x = -x } } return x }

五、T 检验实现

// stats/ttest.go package stats import ( "math" ) // ─── T 检验:比较两个均值的差异 ─── // TwoSampleTTest 独立样本 T 检验 type TwoSampleTTest struct { N1 int Mean1 float64 StdDev1 float64 N2 int Mean2 float64 StdDev2 float64 } // NewTwoSampleTTest 创建 T 检验 func NewTwoSampleTTest(n1 int, mean1, std1 float64, n2 int, mean2, std2 float64) *TwoSampleTTest { return &TwoSampleTTest{ N1: n1, Mean1: mean1, StdDev1: std1, N2: n2, Mean2: mean2, StdDev2: std2, } } // TStatistic 计算 T 统计量 func (t *TwoSampleTTest) TStatistic() float64 { se := math.Sqrt( math.Pow(t.StdDev1, 2)/float64(t.N1) + math.Pow(t.StdDev2, 2)/float64(t.N2), ) if se == 0 { return 0 } return (t.Mean2 - t.Mean1) / se } // DegreesOfFreedom 自由度(Welch-Satterthwaite 近似) func (t *TwoSampleTTest) DegreesOfFreedom() float64 { v1 := math.Pow(t.StdDev1, 2) / float64(t.N1) v2 := math.Pow(t.StdDev2, 2) / float64(t.N2) numerator := math.Pow(v1+v2, 2) denominator := math.Pow(v1, 2)/float64(t.N1-1) + math.Pow(v2, 2)/float64(t.N2-1) if denominator == 0 { return float64(t.N1 + t.N2 - 2) } return numerator / denominator } // PValue 计算 p 值(双尾) func (t *TwoSampleTTest) PValue() float64 { df := t.DegreesOfFreedom() tStat := math.Abs(t.TStatistic()) return 2 * studentTCDF(-tStat, df) } // ConfidenceInterval 置信区间(95%) func (t *TwoSampleTTest) ConfidenceInterval() (lower, upper float64) { diff := t.Mean2 - t.Mean1 se := math.Sqrt( math.Pow(t.StdDev1, 2)/float64(t.N1)+ math.Pow(t.StdDev2, 2)/float64(t.N2), ) // T 临界值(近似,自由度较大时接近 1.96) tCritical := 1.96 if t.DegreesOfFreedom() < 30 { tCritical = studentTInv(0.975, t.DegreesOfFreedom()) } margin := tCritical * se return diff - margin, diff + margin } // IsSignificant 是否显著 func (t *TwoSampleTTest) IsSignificant(alpha float64) bool { return t.PValue() < alpha } // ─── 学生 T 分布辅助函数 ─── // studentTCDF T 分布 CDF func studentTCDF(t float64, df float64) float64 { // 使用 Beta 不完全函数近似 x := df / (df + t*t) return 0.5 * betaInc(x, df/2, 0.5) } // studentTInv T 分布逆 CDF(二分法近似) func studentTInv(p float64, df float64) float64 { // 使用正态近似作为初始值 z := normInv(p) // 对于大自由度,T 分布接近正态 if df > 100 { return z } // 小自由度用 Cornish-Fisher 展开修正 g1 := (z*z*z + z) / 4 g2 := (5*z*z*z*z*z + 16*z*z*z + 3*z) / 96 g3 := (3*z*z*z*z*z*z*z + 19*z*z*z*z*z + 17*z*z*z - 15*z) / 384 g4 := (79*z*z*z*z*z*z*z*z*z + 776*z*z*z*z*z*z*z + 1482*z*z*z*z*z - 1920*z*z*z - 945*z) / 92160 return z + g1/df + g2/(df*df) + g3/(df*df*df) + g4/(df*df*df*df) } // betaInc 不完全 Beta 函数(使用连分式近似) func betaInc(x float64, a float64, b float64) float64 { if x < 0 || x > 1 { return 0 } // 使用正则化不完全 Beta 函数的连分式表示 // 简化实现:对于常用情况使用近似 return incompleteBetaCF(x, a, b) } func incompleteBetaCF(x, a, b float64) float64 { const maxIter = 200 const epsilon = 1e-10 if x == 0 || x == 1 { return x } // Lentz 连分式算法 lbeta := math.Lgamma(a) + math.Lgamma(b) - math.Lgamma(a+b) front := math.Exp(math.Log(x)*a + math.Log(1-x)*b - lbeta) / a var f, c, d float64 f = 1.0 c = 1.0 d = 1.0 - (a+b)*x/(a+1) if math.Abs(d) < epsilon { d = epsilon } d = 1.0 / d f = d for m := 1; m <= maxIter; m++ { // 偶数项 nu := float64(m) * (b - float64(m)) * x / ((a + 2*float64(m) - 1) * (a + 2*float64(m))) d = 1.0 + nu*d if math.Abs(d) < epsilon { d = epsilon } c = 1.0 + nu/c if math.Abs(c) < epsilon { c = epsilon } d = 1.0 / d f *= d * c // 奇数项 nu = -(a + float64(m)) * (a + b + float64(m)) * x / ((a + 2*float64(m)) * (a + 2*float64(m) + 1)) d = 1.0 + nu*d if math.Abs(d) < epsilon { d = epsilon } c = 1.0 + nu/c if math.Abs(c) < epsilon { c = epsilon } d = 1.0 / d delta := d * c f *= delta if math.Abs(delta-1) < epsilon { break } } return front * (f - 1) }

六、卡方检验实现(多组比较)

// stats/chisquare.go package stats import ( "math" ) // ─── 卡方检验:比较多个组的比例差异 ─── // ChiSquareTest 卡方检验 type ChiSquareTest struct { Observed [][]float64 // 观察频数矩阵 Expected [][]float64 // 期望频数矩阵 } // NewChiSquareTest 创建卡方检验 func NewChiSquareTest(observed [][]float64) *ChiSquareTest { return &ChiSquareTest{ Observed: observed, Expected: computeExpected(observed), } } // Statistic 计算卡方统计量 func (c *ChiSquareTest) Statistic() float64 { chi2 := 0.0 for i := range c.Observed { for j := range c.Observed[i] { if c.Expected[i][j] > 0 { diff := c.Observed[i][j] - c.Expected[i][j] chi2 += diff * diff / c.Expected[i][j] } } } return chi2 } // DegreesOfFreedom 自由度 func (c *ChiSquareTest) DegreesOfFreedom() int { rows := len(c.Observed) cols := len(c.Observed[0]) return (rows - 1) * (cols - 1) } // PValue 计算 p 值 func (c *ChiSquareTest) PValue() float64 { chi2 := c.Statistic() df := float64(c.DegreesOfFreedom()) return 1 - chiSquareCDF(chi2, df) } // IsSignificant 是否显著 func (c *ChiSquareTest) IsSignificant(alpha float64) bool { return c.PValue() < alpha } // computeExpected 计算期望频数 func computeExpected(observed [][]float64) [][]float64 { rows := len(observed) cols := len(observed[0]) rowSums := make([]float64, rows) colSums := make([]float64, cols) total := 0.0 for i := range observed { for j := range observed[i] { rowSums[i] += observed[i][j] colSums[j] += observed[i][j] total += observed[i][j] } } expected := make([][]float64, rows) for i := range expected { expected[i] = make([]float64, cols) for j := range expected[i] { expected[i][j] = rowSums[i] * colSums[j] / total } } return expected } // chiSquareCDF 卡方分布 CDF func chiSquareCDF(x float64, k float64) float64 { if x <= 0 { return 0 } return gammaInc(k/2, x/2) } // gammaInc 不完全 Gamma 函数 func gammaInc(a, x float64) float64 { // 使用正则化不完全 Gamma 函数的连分式表示 if x < a+1 { // 级数展开 return gammaIncSeries(a, x) } // 连分式展开 return 1 - gammaIncCF(a, x) } func gammaIncSeries(a, x float64) float64 { const maxIter = 200 const epsilon = 1e-14 if x == 0 { return 0 } ap := a sum := 1.0 / a del := sum for n := 1; n <= maxIter; n++ { ap++ del *= x / ap sum += del if math.Abs(del) < math.Abs(sum)*epsilon { break } } return sum * math.Exp(-x + a*math.Log(x) - math.Lgamma(a)) } func gammaIncCF(a, x float64) float64 { const maxIter = 200 const epsilon = 1e-14 b := x + 1 - a c := 1.0 / epsilon d := 1.0 / b h := d for i := 1; i <= maxIter; i++ { an := -float64(i) * (float64(i) - a) b += 2 d = an*d + b if math.Abs(d) < epsilon { d = epsilon } c = b + an/c if math.Abs(c) < epsilon { c = epsilon } d = 1.0 / d del := d * c h *= del if math.Abs(del-1) < epsilon { break } } return math.Exp(-x + a*math.Log(x) - math.Lgamma(a)) * h }

七、多重比较校正

// stats/multiple.go package stats import ( "sort" ) // ─── 多重比较校正 ─── // BonferroniCorrection Bonferroni 校正 // 最简单但最保守的方法 func BonferroniCorrection(pValues []float64, alpha float64) ([]bool, []float64) { m := len(pValues) corrected := make([]float64, m) significant := make([]bool, m) for i, p := range pValues { corrected[i] = p * float64(m) if corrected[i] > 1 { corrected[i] = 1 } significant[i] = corrected[i] < alpha } return significant, corrected } // BHCorrection Benjamini-Hochberg 校正(FDR 控制) // 比 Bonferroni 宽松,适合多组比较 func BHCorrection(pValues []float64, q float64) ([]bool, []float64) { m := len(pValues) // 创建索引并排序 type item struct { index int p float64 } items := make([]item, m) for i, p := range pValues { items[i] = item{index: i, p: p} } sort.Slice(items, func(i, j int) bool { return items[i].p < items[j].p }) // 找到最大的满足条件的 p 值 threshold := 0.0 maxIdx := -1 for i, it := range items { rank := i + 1 bhThreshold := float64(rank) / float64(m) * q if it.p <= bhThreshold { threshold = it.p maxIdx = i } } // 标记显著 significant := make([]bool, m) corrected := make([]float64, m) for i, it := range items { corrected[it.index] = it.p * float64(m) / float64(i+1) if corrected[it.index] > 1 { corrected[it.index] = 1 } if i <= maxIdx { significant[it.index] = true } } return significant, corrected } // HolmBonferroniCorrection Holm-Bonferroni 校正 // 比 Bonferroni 稍好,逐步淘汰 func HolmBonferroniCorrection(pValues []float64, alpha float64) ([]bool, []float64) { m := len(pValues) type item struct { index int p float64 } items := make([]item, m) for i, p := range pValues { items[i] = item{index: i, p: p} } sort.Slice(items, func(i, j int) bool { return items[i].p < items[j].p }) significant := make([]bool, m) corrected := make([]float64, m) for i, it := range items { rank := i + 1 threshold := alpha / float64(m-rank+1) corrected[it.index] = it.p * float64(m-rank+1) if corrected[it.index] > 1 { corrected[it.index] = 1 } if it.p <= threshold { significant[it.index] = true } else { // 后面的都不显著 break } } return significant, corrected }

八、统计功效分析

// stats/power.go package stats import ( "math" ) // ─── 统计功效分析 ─── // PowerAnalysis 功效分析 type PowerAnalysis struct { Alpha float64 // 显著性水平 SampleSize int // 每组样本量 EffectSize float64 // 效应大小 TestType string // "proportion" 或 "mean" } // CalculatePower 计算统计功效 func (pa *PowerAnalysis) CalculatePower() float64 { switch pa.TestType { case "proportion": return pa.proportionPower() case "mean": return pa.meanPower() default: return 0 } } // proportionPower 比例检验的功效 func (pa *PowerAnalysis) proportionPower() float64 { zAlpha := normInv(1 - pa.Alpha/2) zBeta := zAlpha - pa.EffectSize*math.Sqrt(float64(pa.SampleSize))/2 return standardNormalCDF(zBeta) } // meanPower 均值检验的功效 func (pa *PowerAnalysis) meanPower() float64 { zAlpha := normInv(1 - pa.Alpha/2) zBeta := zAlpha - pa.EffectSize*math.Sqrt(float64(pa.SampleSize)) return standardNormalCDF(zBeta) } // RequiredSampleSize 计算达到指定功效所需的最小样本量 func (pa *PowerAnalysis) RequiredSampleSize(targetPower float64) int { zAlpha := normInv(1 - pa.Alpha/2) zBeta := normInv(targetPower) var n float64 switch pa.TestType { case "proportion": n = 2 * math.Pow(zAlpha+zBeta, 2) / math.Pow(pa.EffectSize, 2) case "mean": n = 2 * math.Pow(zAlpha+zBeta, 2) / math.Pow(pa.EffectSize, 2) default: return 0 } return int(math.Ceil(n)) } // ─── 效应大小计算 ─── // CohenH Cohen's h(比例效应大小) func CohenH(p1, p2 float64) float64 { h1 := 2 * math.Asin(math.Sqrt(p1)) h2 := 2 * math.Asin(math.Sqrt(p2)) return math.Abs(h1 - h2) } // CohenD Cohen's d(均值效应大小) func CohenD(mean1, mean2, pooledStd float64) float64 { return math.Abs(mean2-mean1) / pooledStd } // InterpretEffectSize 解释效应大小 func InterpretEffectSize(d float64) string { absD := math.Abs(d) switch { case absD < 0.2: return "微不足道" case absD < 0.5: return "小效应" case absD < 0.8: return "中等效应" default: return "大效应" } }

九、完整示例:AI 模型实验的统计检验

// example/stats_demo.go package main import ( "fmt" "stats" ) func main() { fmt.Println("=== AI 模型 A/B 实验统计检验 ===\n") // ─── 场景 1:模型回答准确率比较 ─── fmt.Println("【场景 1】模型准确率比较") fmt.Println("对照组(GPT-4o Mini):1000 次请求,85% 正确率") fmt.Println("实验组(DeepSeek V3):1000 次请求,88% 正确率") zTest := stats.NewTwoProportionZTest( 1000, 0.85, // 对照组 1000, 0.88, // 实验组 ) fmt.Printf("\nZ 值: %.4f\n", zTest.ZScore()) fmt.Printf("P 值: %.4f\n", zTest.PValue()) fmt.Printf("95%% 置信区间: [%.4f, %.4f]\n", zTest.ConfidenceInterval()) fmt.Printf("是否显著 (α=0.05): %v\n\n", zTest.IsSignificant(0.05)) // ─── 场景 2:响应时间比较 ─── fmt.Println("【场景 2】模型响应时间比较") fmt.Println("对照组:平均 450ms,标准差 120ms,样本 500") fmt.Println("实验组:平均 380ms,标准差 110ms,样本 500") tTest := stats.NewTwoSampleTTest( 500, 450, 120, // 对照组 500, 380, 110, // 实验组 ) fmt.Printf("\nT 值: %.4f\n", tTest.TStatistic()) fmt.Printf("自由度: %.2f\n", tTest.DegreesOfFreedom()) fmt.Printf("P 值: %.4f\n", tTest.PValue()) fmt.Printf("95%% 置信区间: [%.2f, %.2f] ms\n", tTest.ConfidenceInterval()) fmt.Printf("是否显著 (α=0.05): %v\n\n", tTest.IsSignificant(0.05)) // ─── 场景 3:多模型比较(4 个模型) ─── fmt.Println("【场景 3】4 个模型的用户偏好比较") observed := [][]float64{ {120, 90, 80, 70}, // 喜欢 {30, 60, 70, 40}, // 不喜欢 } chi2 := stats.NewChiSquareTest(observed) fmt.Printf("\n卡方值: %.4f\n", chi2.Statistic()) fmt.Printf("自由度: %d\n", chi2.DegreesOfFreedom()) fmt.Printf("P 值: %.4f\n", chi2.PValue()) fmt.Printf("是否显著 (α=0.05): %v\n\n", chi2.IsSignificant(0.05)) // ─── 场景 4:多重比较校正 ─── fmt.Println("【场景 4】多重比较校正") pValues := []float64{0.023, 0.045, 0.003, 0.210, 0.067, 0.031} fmt.Printf("原始 P 值: %v\n", pValues) _, bonfCorrected := stats.BonferroniCorrection(pValues, 0.05) fmt.Printf("Bonferroni 校正: %v\n", bonfCorrected) _, bhCorrected := stats.BHCorrection(pValues, 0.05) fmt.Printf("BH 校正: %v\n", bhCorrected) // ─── 场景 5:样本量规划 ─── fmt.Println("\n【场景 5】样本量规划") fmt.Println("基线转化率 10%,想检测 2% 的提升") needed := stats.MinimumSampleSize(0.10, 0.02, 0.05, 0.80) fmt.Printf("每组需要的样本量: %d\n", needed) fmt.Printf("总共需要: %d\n", needed*2) // ─── 场景 6:功效分析 ─── fmt.Println("\n【场景 6】功效分析") powerAnalysis := &stats.PowerAnalysis{ Alpha: 0.05, SampleSize: 1000, EffectSize: 0.025, // 2.5% 的效应 TestType: "proportion", } power := powerAnalysis.CalculatePower() fmt.Printf("当前功效: %.2f%%\n", power*100) if power < 0.8 { required := powerAnalysis.RequiredSampleSize(0.80) fmt.Printf("要达到 80%% 功效,需要每组 %d 样本\n", required) } // ─── 总结 ─── fmt.Println("\n=== 总结 ===") fmt.Println("1. 准确率比较:p=0.0531,边缘显著,需要更大样本") fmt.Println("2. 响应时间:p<0.001,显著改善,可以上线") fmt.Println("3. 多模型偏好:p<0.001,模型间存在显著差异") fmt.Println("4. 注意多重比较校正,避免假阳性") fmt.Println("5. 实验前先用样本量计算器规划样本量") }

十、本讲坑位

#

坑位

现象

解决方案

1

P 值黑客​

反复看数据,等到 p<0.05 就停止

预注册分析计划 + 固定样本量

2

忽略效应大小​

p 很小但效果只有 0.1% 提升

同时报告 p 值和效应大小

3

多重比较不校正​

10 个指标里有 1 个显著就宣称成功

Bonferroni/BH 校正

4

样本量不足​

100 样本跑 AB 就说有 20% 提升

事前功效分析确定样本量

5

辛普森悖论​

每组都好,合起来差

分层分析 + 标准化

6

数据窥探​

边收数据边看结果,提前决策

序贯检验 + 边界停止规则


十一、本讲小结

┌─────────────────────────────────────────────────────────────┐ │ 第7讲 核心收获 │ │ │ │ ✅ 理解了假设检验的基本框架(H₀/H₁/p 值/α/β) │ │ ✅ 实现了 Z 检验(比例比较,最常用) │ │ ✅ 实现了 T 检验(均值比较,含 Welch 近似) │ │ ✅ 实现了卡方检验(多组比例比较) │ │ ✅ 实现了三种多重比较校正(Bonferroni/BH/Holm) │ │ ✅ 实现了功效分析和样本量计算 │ │ ✅ 知道了 6 个常见统计陷阱及其应对方案 │ │ │ │ 核心理念:统计不是用来证明自己对的,是用来排除巧合的 │ └─────────────────────────────────────────────────────────────┘

下一讲预告:《实验治理与组织落地——如何让 AB 测试成为团队习惯》

技术实现完了,最难的部分来了:怎么让产品经理愿意用?怎么建立实验文化?怎么避免实验污染?怎么处理长期效应?


配套工具推荐

🧰 开发之余的小工具推荐

在做统计分析时,经常需要快速计算样本量、查看 P 值对应的 Z 分数、或者验证数据分布是否符合正态假设。zz365.top 上的数学计算工具可以帮助你快速完成这些运算,省去手算或临时写脚本的麻烦。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询