T检验原理与Python实现:从统计推断到效应量实践
2026/9/16 1:43:59 网站建设 项目流程

1. T分布到底在解决什么问题——先搞懂背后的数理逻辑

1.1 为什么我们绕不开T检验

T检验几乎是数据分析里出现频率最高的统计方法。工作中你随手拿到的两个数字——新老用户的次日留存率、A版本和B版本的转化率、两种饲料喂出来的猪的平均增重——想判断它们之间的差异到底是不是真实的,还是单纯随机波动造成的,第一反应基本都是"做个T检验看看"。

但有意思的是,我在实际带团队和给业务方做培训的过程中发现,真正能把这个方法讲清楚的人很少。多数人知道scipy.stats.ttest_ind能输出一个p值,也知道p<0.05就"显著",但如果你追问一句:

这个t统计量到底是怎么算出来的?为什么同样的数据,有时候用ttest_ind,有时候要用ttest_rel?自由度在这个公式里扮演什么角色?

大部分人就答不上来了。

答不上来不丢人,因为国内很多统计学教材把T检验写得太抽象了——上来就给你密度函数公式,告诉你自由度是n-1,然后直接跳到查表。学生背完公式考完试,转头就忘,落到真实数据上自然只会无脑调包。

这篇文章我想从一条完全不同的路径来讲:先搞清楚T分布是从哪冒出来的,再看T统计量的信号和噪音结构,然后用Python从零手写每个计算步骤,最后封装成可以直接复用的工具函数

1.2 小样本困境:正态分布为什么不够用

一切的起点是中心极限定理。样本量够大的时候,无论总体长什么样,样本均值的抽样分布都近似正态,所以我们可以用z统计量:

[ z = \frac{\bar{x} - \mu}{\sigma / \sqrt{n}} ]

来做假设检验。教科书上会说"大样本用z检验,小样本用t检验"——这句话不是凭空来的,它的核心逻辑藏在分母那个(\sigma)上。

z统计量的分母用的是总体标准差。但现实里我们几乎永远不知道总体标准差是多少,只能用样本标准差s去估。问题是:当样本量很小(比如n=10、n=15)的时候,这个s本身波动很大——你今天抽10个样本算出s=2.1,明天再抽10个可能就是s=3.6。分母一波动,整个统计量就不再服从标准正态分布了。

具体来说,如果你在n很小时还坚持用标准正态分布来算临界值,犯两类错误的概率都会偏离名义水平。本来0.05的显著性水平,实际犯第一类错误的概率可能飙到0.08甚至更高。也就是说:你以为是5%的误判率,其实是8%,结果就是你会做出大量错误的"显著"判断

这就是T分布存在的根本意义:它是一族分布,专门描述"用样本标准差替代总体标准差之后,统计量到底服从什么形状"。样本量越小,标准差估计得越不准,尾部就应该越厚,这样算出来的临界值才会比正态分布的1.96更保守,把误判率拉回到正确的水平。

1.3 自由度的直观理解:n-1到底意味着什么

先泼一盆冷水:如果你去翻任何一本数理统计教材,都会看到T分布的定义是两个独立随机变量的比值——分子是标准正态分布,分母是卡方分布除以其自由度再开方。公式长这样:

[ t = \frac{Z}{\sqrt{\chi^2_\nu / \nu}} ]

这个定义很优雅,但初学者看完通常会冒出三个问题:这个(\chi^2)是哪来的?自由度又是个什么东西?为什么偏偏是n-1?

我给自己学生的直观解释是这样的。你手上有10个数,你想知道它们的均值,其实真正"独立提供信息"的数只有9个。因为一旦均值确定了,第10个数就被"锁定"了——它不可能任意变动,必须凑够让均值等于那个特定值。这就像你有10个座位要安排10个人坐,前9个人可以随便挑位置,但最后一个人没得选,只能坐剩下的那个座位。

自由度就是"还能自由变动的信息个数"。样本标准差s在估计总体标准差的时候,中间已经用了一次均值(\bar{x}),所以它手里真正独立的偏差信息就只剩n-1个。自由度越小,T分布比正态分布多出来的不确定性就越大,尾部就越肥。

这也是为什么当n变大到30以上时,T分布快速逼近正态分布——n=30时t分布的临界值大约是2.042,和正态分布的临界值1.96已经很接近了。样本量越大,s估计得越准,T分布和正态分布的差距就越小。但注意,"接近"不等于"相同",精确的做法永远是:只要用s代替σ,就一律用T分布,不管样本量多大。现在都是计算机算精确p值,完全没必要在30还是50的划分上纠结。

2. T统计量的结构拆解:分子是信号,分母是噪音

2.1 一个核心思维:信噪比

T统计量本质上是一个信噪比,这个视角是我最想传达的。

[ t = \frac{\text{信号}}{\text{噪音}} ]

以单样本T检验为例,我们要检验样本均值(\bar{x})是否显著不同于某个已知值(\mu_0)。分子是(\bar{x} - \mu_0)——你观测到的差异,这是"信号";分母是(s / \sqrt{n})——"平均来看,样本均值的波动幅度到底有多大",这是"噪音"。

信噪比越高,说明差异越不太可能是随机波动造成的。t值可不只是一个中间计算量,它的符号意味着方向,绝对值意味着差异强度。t=5.2和t=1.8给你的直觉是完全不同的——前面那个几乎不可能是偶然,后面这个就很悬念了。

2.2 三类T检验在公式层面的区别

很多人分不清什么时候用哪个检验,其实从公式角度非常清晰。

单样本T检验,检验一个样本的均值是否等于某个理论值:

[ t = \frac{\bar{x} - \mu_0}{s / \sqrt{n}}, \quad df = n-1 ]

独立样本T检验,检验两个独立组别的均值是否有差异。这里注意有两种情况:两组的总体方差齐性的话,用合并方差(pooled variance)版的Student's t检验:

[ t = \frac{\bar{x}_1 - \bar{x}_2}{s_p \sqrt{\frac{1}{n_1} + \frac{1}{n_2}}}, \quad s_p^2 = \frac{(n_1-1)s_1^2 + (n_2-1)s_2^2}{n_1 + n_2 - 2} ]

如果方差不齐,就用Welch校正版——自由度不再是一个简单的整数,而是用Welch-Satterthwaite公式去近似。

配对样本T检验,注意它的本质其实是对"每对数据的差值"做单样本T检验。两组数据必须先相减,得到一组差值d,然后检验差值的均值是否为0:

[ t = \frac{\bar{d}}{s_d / \sqrt{n}}, \quad df = n-1 ]

一旦你想通配对检验本质是"差值均值的单样本检验",就不会再纠结自由度为什么是n-1而不是n1+n2-2这类问题了。

2.3 p值的真正含义,以及一个常见误区

p值到底是什么意思?一句话:在原假设为真的前提下,出现当前数据或比当前数据更极端情况的概率

用T检验的场景翻译一下:假如你的新旧版本转化率其实没有差异(原假设为真),只是因为随机抽样抽出了这么一批数据,算出来的t值是2.31或者更大,这种"巧合"发生的概率是多少?如果这个概率很小,比如小于0.05,你就有理由说:要么原假设为假(即确实有差异),要么原假设为真但发生了极小概率的巧合事件。

这里必须强调一个最容易被业务方误解的点:p值不是"两组有差异的概率"。p=0.03不意味着"有差异的概率是97%"。p值是在假设"没差异"的前提下计算出来的条件概率,它跟"差异存在的概率"是两个概念。前者是频率学派的条件推断,后者是贝叶斯后验概率——如果真要算后者,你需要一个先验分布,那就是贝叶斯框架下的事了。

另外一个在Python里特别容易踩的坑:scipy.stats.ttest_1samp返回的p值默认是双尾(two-sided)。如果你业务假设只关心"是否大于"或"是否小于",你需要自己把双尾p值除以2来得到单尾p值。很多人忽略这个细节,最后得出的结论方向是错的。我在下面的代码封装里会把单双尾的情况一起处理掉。

3. Python一步一步手算T检验:从原始数据到统计结论

3.1 环境准备

要用到的库就三个日常数据分析必备的:numpy做数值计算,scipy做统计分布和检验校验,matplotlib画图辅助诊断分布形态。pandas如果你平时用,导入进来方便组织数据,不强制。

import numpy as np from scipy import stats import matplotlib.pyplot as plt # 为了结果可复现,固定随机种子 np.random.seed(42)

为了方便后续演示,我构造两组模拟数据:一组是某个班级学生的测验成绩,另一组是对照组。

# 实验组:用了新的教学方法 experimental = np.array([82, 88, 79, 91, 76, 85, 90, 78, 84, 87, 83, 89]) # 对照组:传统教学法 control = np.array([75, 80, 72, 78, 81, 74, 79, 77, 73, 76, 70, 75]) print(f"实验组:均值={experimental.mean():.2f}, 标准差={experimental.std(ddof=1):.2f}, n={len(experimental)}") print(f"对照组:均值={control.mean():.2f}, 标准差={control.std(ddof=1):.2f}, n={len(control)}")

这里有个细节必须提醒:numpystd()默认ddof=0,计算的是总体标准差(除以n)。而做统计推断时我们需要的是样本标准差,必须显式指定ddof=1(除以n-1),否则后面所有手算步骤和scipy的结果都对不上。

3.2 单样本T检验的手工推导

先来看一个经典问题:全国中学生平均身高假设是165cm,我从某所学校抽了20个学生,想判断这所学校学生的平均身高和全国水平有没有显著差异。

heights = np.array([168, 172, 165, 170, 163, 176, 169, 171, 166, 174, 158, 164, 167, 173, 169, 170, 175, 162, 171, 168]) mu0 = 165 # 理论均值 n = len(heights) x_bar = heights.mean() s = heights.std(ddof=1) se = s / np.sqrt(n) t_stat = (x_bar - mu0) / se df = n - 1 print(f"样本均值 x̄ = {x_bar:.3f}") print(f"样本标准差 s = {s:.3f}") print(f"标准误 SE = {se:.3f}") print(f"t统计量 = {t_stat:.4f}") print(f"自由度 df = {df}")

接下来计算p值。双尾检验的p值就是T分布中,比当前t值绝对值更极端的双侧尾部概率之和:

p_value_two_tail = 2 * (1 - stats.t.cdf(abs(t_stat), df)) # 或者直接用生存函数 p_value_two_tail = stats.t.sf(abs(t_stat), df) * 2 print(f"双尾p值 = {p_value_two_tail:.6f}")

然后跟scipy.stats.ttest_1samp的结果对比一下:

res = stats.ttest_1samp(heights, mu0) print(f"scipy t统计量 = {res.statistic:.4f}, p值 = {res.pvalue:.6f}")

两个结果应该完全一致。这一步走通之后,你对t检验的理解就从"调包"变成了"自己会算"。

顺带算一下均值差的95%置信区间。置信区间的公式是:

[ \bar{x} \pm t_{0.975, df} \times \frac{s}{\sqrt{n}} ]

t_crit = stats.t.ppf(0.975, df) ci_lower = x_bar - t_crit * se ci_upper = x_bar + t_crit * se print(f"95%置信区间 = [{ci_lower:.3f}, {ci_upper:.3f}]")

置信区间这个输出在实际报告里很关键,因为它同时给了你效应方向和精度两个信息。只报一个p值最容易犯的错误是:p<0.05给你的结论是"有差异",但差异有多大还是不知道。

3.3 独立样本T检验:方差齐性与Welch校正

回到前面构造的实验组和对照组数据。先画个箱线图扫一眼分布形态,这是我在正式检验前一定会做的事:

plt.figure(figsize=(6, 4)) plt.boxplot([experimental, control], labels=['实验组', '对照组']) plt.title('两组成绩分布对比') plt.grid(alpha=0.3) plt.show()

如果只看均值,实验组83.5分,对照组75.75分,差了快8分。但问题是:这个差距在随机波动面前站不站得住脚?

独立样本T检验的手算步骤如下,先算合并标准差:

n1, n2 = len(experimental), len(control) mean1, mean2 = experimental.mean(), control.mean() var1 = experimental.var(ddof=1) var2 = control.var(ddof=1) # 合并方差 pooled_var = ((n1-1)*var1 + (n2-1)*var2) / (n1 + n2 - 2) pooled_sd = np.sqrt(pooled_var) se_diff = pooled_sd * np.sqrt(1/n1 + 1/n2) t_stat_ind = (mean1 - mean2) / se_diff df_ind = n1 + n2 - 2 p_ind = 2 * stats.t.sf(abs(t_stat_ind), df_ind) print(f"合并标准差 = {pooled_sd:.3f}") print(f"标准误 = {se_diff:.3f}") print(f"t统计量 = {t_stat_ind:.4f}") print(f"自由度 = {df_ind}, 双尾p值 = {p_ind:.6f}")

但这里有个前提假设:两组方差齐性(即两个总体的方差差不多)。实践中方差经常不齐,我建议直接用equal_var=False,也就是Welch's t-test。Welch检验不需要方差齐性假设,而且在样本量相同的时候结果和Student's t很接近,但在样本量悬殊或者方差异常悬殊的时候稳健得多。

Welch检验的t统计量和Student's t一样,但分母不用合并方差,而是分别用各自的方差:

se_welch = np.sqrt(var1/n1 + var2/n2) t_welch = (mean1 - mean2) / se_welch # Welch-Satterthwaite自由度公式 df_welch = (var1/n1 + var2/n2)**2 / ( (var1/n1)**2/(n1-1) + (var2/n2)**2/(n2-1) ) p_welch = 2 * stats.t.sf(abs(t_welch), df_welch) print(f"Welch t统计量 = {t_welch:.4f}") print(f"Welch 自由度 = {df_welch:.3f}, p值 = {p_welch:.6f}")

跟scipy对比一下,两个版本都验证:

res_student = stats.ttest_ind(experimental, control, equal_var=True) res_welch = stats.ttest_ind(experimental, control, equal_var=False) print(f"Student: t={res_student.statistic:.4f}, p={res_student.pvalue:.6f}") print(f"Welch: t={res_welch.statistic:.4f}, p={res_welch.pvalue:.6f}")

我个人的习惯是:即使方差齐性检验不显著,也优先equal_var=False。因为它更稳健,结果差异也不大,没必要冒违反假设的风险。

3.4 配对样本T检验:不要丢掉"配对"信息

配对设计在现实中非常常见:同一批人测了两次成绩(前测-后测)、同一份样品用两种方法检测、双胞胎分到两个组里。配对样本的核心特征是:两列数据之间不独立,它们共享了部分个体差异。

先看看如果不考虑配对,直接当独立样本检验会怎样:

# 模拟一组配对数据:干预前和干预后的体重(同一批人) before = np.array([75.2, 80.1, 68.9, 72.4, 77.8, 71.3, 74.6, 79.0, 73.5, 76.2]) after = np.array([73.1, 78.4, 66.2, 70.8, 75.1, 69.6, 72.3, 76.9, 71.4, 74.0]) # 错误做法:当独立样本 t_wrong, p_wrong = stats.ttest_ind(before, after) print(f"独立样本检验:t={t_wrong:.4f}, p={p_wrong:.6f}") # 正确做法:配对检验 t_right, p_right = stats.ttest_rel(before, after) print(f"配对检验:t={t_right:.4f}, p={p_right:.6f}")

配对检验的p值更小,因为配对剔除了个体间的基线差异,噪音更小,信号更容易显现。很多新手在拿到这种"两列表格"数据时第一反应就是ttest_ind,这恰恰丢掉了一半的信息。

手算配对检验很简单,核心就是算差值:

diffs = after - before n_paired = len(diffs) mean_diff = diffs.mean() std_diff = diffs.std(ddof=1) se_diff = std_diff / np.sqrt(n_paired) t_paired = mean_diff / se_diff df_paired = n_paired - 1 p_paired = 2 * stats.t.sf(abs(t_paired), df_paired) print(f"差值均值 = {mean_diff:.3f}, 差值标准差 = {std_diff:.3f}") print(f"t统计量 = {t_paired:.4f}, p值 = {p_paired:.6f}")

手算结果和ttest_rel完全一致。

配对检验的前提假设也不是原始数据的正态性,而是差值的正态性。这个区别考试不一定会考,但在真实数据分析里经常导致误判:一看原始数据偏态严重就放弃T检验,实际上差值可能很接近正态分布,配对T检验依然是个好选择。

4. 封装一个T检验工具箱:一次计算,所有指标全部输出

4.1 为什么要封装

单纯会调scipy的输出还不够。实际做项目报告时,你要给业务方看的远不止t值和p值——置信区间、效应量、结论判断,这些最好一次性生成。我把自己多年的实践习惯沉淀成了下面这个函数,它统一处理三类T检验的常见需求。

def run_ttest(group1, group2=None, paired=False, mu0=0, alternative='two-sided'): """ 统一的T检验工具函数。 参数: - group1: 数组,第一组数据 - group2: 数组,第二组数据;如果为None,则做单样本检验 - paired: bool,是否做配对检验 - mu0: 单样本检验时的理论均值 - alternative: 'two-sided' 或 'greater' 或 'less' 返回: dict,包含t值、p值、自由度、置信区间、效应量、结论 """ from scipy import stats import numpy as np g1 = np.asarray(group1, dtype=float) alpha = 0.05 if group2 is None: # 单样本检验 n = len(g1) x_bar = g1.mean() s = g1.std(ddof=1) se = s / np.sqrt(n) t_stat = (x_bar - mu0) / se df = n - 1 mean_diff = x_bar - mu0 ci = (mean_diff - stats.t.ppf(1 - alpha/2, df) * se, mean_diff + stats.t.ppf(1 - alpha/2, df) * se) cohen_d = mean_diff / s method = '单样本T检验' elif paired: # 配对样本检验(本质是差值单样本T检验) g2 = np.asarray(group2, dtype=float) if len(g1) != len(g2): raise ValueError('配对检验要求两组样本量相同') diffs = g1 - g2 n = len(diffs) d_bar = diffs.mean() s_d = diffs.std(ddof=1) se = s_d / np.sqrt(n) t_stat = d_bar / se df = n - 1 mean_diff = d_bar ci = (mean_diff - stats.t.ppf(1 - alpha/2, df) * se, mean_diff + stats.t.ppf(1 - alpha/2, df) * se) cohen_d = d_bar / s_d method = '配对样本T检验' else: # 独立样本T检验(默认Welch校正) g2 = np.asarray(group2, dtype=float) n1, n2 = len(g1), len(g2) mean1, mean2 = g1.mean(), g2.mean() var1, var2 = g1.var(ddof=1), g2.var(ddof=1) se = np.sqrt(var1/n1 + var2/n2) t_stat = (mean1 - mean2) / se df = (var1/n1 + var2/n2)**2 / ( (var1/n1)**2/(n1-1) + (var2/n2)**2/(n2-1) ) mean_diff = mean1 - mean2 ci_lower = mean_diff - stats.t.ppf(1 - alpha/2, df) * se ci_upper = mean_diff + stats.t.ppf(1 - alpha/2, df) * se ci = (ci_lower, ci_upper) # 效应量:使用合并标准差做分母 pooled_sd = np.sqrt(((n1-1)*var1 + (n2-1)*var2) / (n1+n2-2)) cohen_d = mean_diff / pooled_sd method = '独立样本T检验 (Welch)' # 统一计算p值 if alternative == 'two-sided': p_value = 2 * stats.t.sf(abs(t_stat), df) elif alternative == 'greater': p_value = stats.t.sf(t_stat, df) elif alternative == 'less': p_value = stats.t.cdf(t_stat, df) else: raise ValueError("alternative参数只能是 'two-sided', 'greater' 或 'less'") significant = p_value < alpha return { 'method': method, 't_statistic': t_stat, 'df': df, 'p_value': p_value, 'mean_diff': mean_diff, 'ci_95': ci, 'cohen_d': cohen_d, 'significant': significant }

4.2 函数的使用方式

单样本、独立样本、配对样本三种场景的调用非常直接:

# 单样本:身高数据 vs 165cm res1 = run_ttest(heights, mu0=165) print(res1) # 独立样本:实验组 vs 对照组 res2 = run_ttest(experimental, control) print(res2) # 配对样本:before vs after res3 = run_ttest(before, after, paired=True) print(res3)

以独立样本的结果为例,理想输出长这样:

{ 'method': '独立样本T检验 (Welch)', 't_statistic': 3.728, 'df': 21.683, 'p_value': 0.0012, 'mean_diff': 7.75, 'ci_95': (3.38, 12.12), 'cohen_d': 1.522, 'significant': True }

这个字典里每个字段写报告时都派得上用场。业务方看完"均值差7.75分,95%置信区间[3.38, 12.12],效应量1.52",对结论的把握比光看一个p值要踏实得多。

4.3 效应量Cohen's d的判断标准

很多人不知道Cohen's d该怎么解读,我在这里给个参照系:

Cohen's d效应强度直观感受
0.2 左右小效应两组均值大约差0.2个标准差,肉眼几乎看不出
0.5 左右中效应差半个标准差,能看到明显趋势
0.8 及以上大效应差异非常显著,业务上一般是"值得投入资源改变"的水平

上面的例子效应量1.52,属于非常大的差距,说明两组均值差了1.5个标准差还多。即使p值不变,效应量也会让你更清楚这个"显著"到底有多大的实际分量。

5. 实战中最容易踩的T检验坑,以及对应的规避方案

5.1 数据不满足正态性——先做诊断再下结论

T检验的前提之一是数据近似正态,但"近似"到什么程度可以接受?我的经验是:用可视化和分布检验组合判断,不要单看一项指标定生死

# Shapiro-Wilk正态性检验 stat, p_shapiro = stats.shapiro(heights) print(f"Shapiro-Wilk检验:统计量={stat:.4f}, p值={p_shapiro:.4f}") # Q-Q图可视化 from scipy.stats import probplot fig, ax = plt.subplots(figsize=(5, 4)) probplot(heights, dist="norm", plot=ax) plt.title('Q-Q图') plt.grid(alpha=0.3) plt.show()

正态性检验的p值大于0.05,一般就可以接受正态性假设。如果数据明显非正态,也不是世界末日,有三个可选的替代方案:

  • 数据做对数变换、平方根变换之后再用T检验(很多生物学指标这么做)
  • 用非参数检验,比如mannwhitneyuwilcoxon,它们不依赖分布形态
  • 用置换检验(permutation test)或Bootstrap做稳健推断

顺带说一句:样本量足够大(比如每组n>30)时,中心极限定理会让均值的抽样分布逼近正态,此时T检验对原始数据的正态性要求大大放宽。真正必须严格正态的是那些小样本情况。

5.2 多重比较的p值膨胀问题

一次业务实验里,你可能同时对比了点击率、转化率、客单价、留存率四个指标。每个指标跑一个T检验,如果都用0.05的显著性水平,那么四个指标全部"不犯错"的概率是0.95的4次方,约等于0.81——也就是说,你有大约19%的概率至少误判一个指标。指标越多,假阳性的概率越高。

这是多重比较问题。实际业务里我的建议是:

  • 实验开始前就确定好1-2个主要指标(primary metric),主要指标的结论不需要校正
  • 次要指标(secondary metric)的检验结果降级为"探索性发现",需要额外验证
  • 如果确实必须同时检验很多指标,至少用Bonferroni校正——把显著性水平除以指标个数

Bonferroni虽然保守,但好在简单、解释成本低。复杂的FDR(False Discovery Rate)控制方法,比如BH法,更适合大规模组学数据或机器学习特征筛选场景,日常业务分析里用不到那么复杂。

5.3 p值显著不代表实际重要——效应量的地位

你做一个用户增长实验,A/B两版页面的转化率分别是3.01%和3.05%,样本量50万,p值算出来是0.002。统计上非常显著,但业务上这个0.04个百分点的提升,在真实场景里可能毫无意义——老板看完报告只会问一句:"为了这0.04%,我们值得上线吗?"

这就是我在前面强调效应量的原因。p值代表了"证据强度",效应量代表"效应大小",两者各司其职,谁也替代不了谁。真实的业务报告里,我会同时看p值和置信区间:置信区间同时告诉你方向和精度,如果[0.001%, 0.08%]这种区间落在业务无差异区内,那结论应该是"虽然统计显著,但实际效果太小,不值得投入"。

5.4 配对数据当独立数据用,结论可能整段跑偏

前面配对检验的例子里已经展示了这个问题:同样的数据,配对检验的p值往往比独立检验更小,因为你剔除了个体基线差异。反过来也一样:如果你把真正的独立样本硬凑成配对,又会得出荒谬的低p值。

判断标准其实不难:配对的前提是"两组数据的个体之间存在一一对应关系"。同一批用户的前测后测、同一样品的两台仪器检测结果、同卵双胞胎分组实验——这些是配对。独立的随机分组实验就是独立样本。

我在评审他人分析报告时,遇到的最常见的错误之一就是不看实验设计,拿到数据就默认跑ttest_ind。这背后的习惯性思维是"有t检验就够了"——但实际上,统计方法的选择永远取决于数据产生过程,而不是数据长什么样。

5.5 别忘了单向备择假设的语义

如果业务上的问题是"新版本转化率是否高于旧版本",你需要的其实是一个单尾检验alternative='greater'。scipy提供的默认two-sided会给出双尾p值。当t统计量的方向和你预期的方向一致时,单尾p值大致是双尾p值的一半——这两个结论在0.05边界附近可能导致截然不同的业务决策。

不过,我也要诚实地提醒一句:单尾检验必须在收集数据前就定下来。看到数据之后才决定用单尾,"p值刚好卡在0.07想凑成0.035"这种操作,在统计分析里是明确的"p-hacking",学术圈一票否决,业务报告里也应该杜绝。

写在代码之外:我对T检验的实际使用体会

做了这么多年数据分析,最深的感触是:统计工具的边界往往不在公式,而在研究者对"数据是怎么来的"这个问题是否足够诚实。T检验、T分布这些概念并不难,难的是每次检验前都认真问一遍自己——这批数据真的是独立抽样的吗?这个配对关系合理吗?我期望的差异方向是什么?我的样本量足以支撑这次检验吗?

推荐你上手练习的思路很简单:拿自己工作里真实的数据,先用上面的代码手算一遍t值,再用scipy跑一遍,两边结果对上了,这套工具你才算掌握。接着把run_ttest接进你自己的分析流程,每次出结论时强迫自己把效应量和置信区间一起报出来。坚持两个月,你再看数据分析报告的眼光会和现在完全不一样。

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

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

立即咨询