早几年自己做数据分析那会儿,最头疼的不是模型多复杂,而是手边明明有数据,却不知道怎么科学地“说清楚”结论。Excel里算个p值还得装分析工具库,SPSS和EViews这类专用软件又不便宜,换了电脑还要重新激活。后来切到Python,用pandas做描述统计、scipy跑假设检验、statsmodels做回归,一套流程顺手到不行。这篇就认真聊聊怎么用Python把统计分析这件事从描述统计做到假设检验,不管是刚入门的数据分析师,还是想转行做金融风控、电商运营的朋友,这份路线图都可以直接照着走。
我的思路很明确:用一份模拟业务数据把全流程串起来,先讲清楚每一步在干什么,再给可运行的代码,最后补充实操中会踩的坑。过程中会把描述统计和假设检验的核心逻辑一次说明白,保证你跑完代码之后不只是复制粘贴,而是真的理解每一步背后的统计含义。
1. 分析环境准备与数据认知
1.1 统计分析的三大核心库选型
Python做统计分析和Excel或EViews最大的不同在于,它能打通“数据清洗—描述统计—推断统计—结果可视化”一整条流水线。我的主力组合一直很固定:pandas负责数据读写和处理,scipy.stats是假设检验的主力库,statsmodels做回归分析和更精细的统计建模,seaborn负责画图。辅助工具用numpy做数值计算,matplotlib做底层绘图。
有人会问,为什么不用R?其实R在统计上也很能打,但Python的优势在于它和学习机器学习、自动化脚本用的是同一套语言,日常分析完直接接模型或者写报表都是无缝衔接。对做金融统计分析、电商数据分析的人来说,这一套组合足够覆盖日常90%以上的分析需求,不需要频繁切软件。
库的安装没什么门槛,直接用pip安装即可。装完之后建议顺手检查一下版本,很多时候报错都是因为版本太旧。
pip install pandas numpy scipy statsmodels matplotlib seaborn提示:如果用的是Anaconda发行版,这几个库一般会自带。但要注意statsmodels的版本更新比较频繁,旧版本偶尔会出现和新版scipy不兼容的问题,所以装完最好跑一下
conda update statsmodels或pip install --upgrade statsmodels。
1.2 数据分析前的关键一步:读懂你的数据
很多同学拿到数据之后直接就开始算均值、跑检验,这是一个非常危险的坏习惯。我每次拿到新数据会强迫自己先回答三个问题:数据里有多少行、多少列?每条数据是什么颗粒度(一笔订单、一个用户、还是一天的汇总)?数据有没有缺失值和明显的异常值?
用pandas回答这些问题其实很快,常用的就是info()、head()和describe()。info()告诉我们数据完整度和类型,describe()直接输出数值列的基础描述统计量。这一眼扫过去,能补上你对着几百行原始数据盯半小时都得不到的信息。
import pandas as pd df = pd.read_csv('sales_data.csv') print(df.shape) print(df.info()) print(df.head())我习惯在清洗阶段就把列名统一改名,因为原始数据的列名千奇百怪,有的带空格、有的用中文、有的有大写。Python里处理名字带空格的列非常麻烦,所以建议抽5分钟把列名全部规范化:小写、用下划线分隔。
1.3 构造一份可复现的模拟业务数据
为了把后面所有知识点串起来,我准备了一份模拟的电商促销数据。场景是这样的:某店铺做了三种不同策略的促销活动(A策略领券满减、B策略直接打折、C策略买一送一),我们随机记录了每个订单的成交金额,以及用户是否在活动期内再次购买。这份数据看起来简单,但它涵盖了连续变量(成交金额)、分类变量(促销策略)、二分类结果(是否复购)三种最常见的数据类型,后面所有的方法都围绕它展开。
import numpy as np import pandas as pd np.random.seed(42) n_per_group = 120 group_a = np.random.normal(180, 40, n_per_group) group_b = np.random.normal(210, 50, n_per_group) group_c = np.random.normal(195, 45, n_per_group) df = pd.DataFrame({ 'order_amount': np.concatenate([group_a, group_b, group_c]), 'promotion': ['A'] * n_per_group + ['B'] * n_per_group + ['C'] * n_per_group, 'repurchase': np.random.choice([0, 1], size=n_per_group * 3, p=[0.6, 0.4]) })这份数据里,不同促销策略的订单金额本身存在真实差异,后期分析会把这个差异“检验”出来。用随机种子(random seed)固定数据,确保你看到的代码运行结果和我这里展示的一致。
2. 描述统计:先让数据开口说话
2.1 描述统计的本质:集中趋势与离散程度
描述统计是所有统计分析的起点,它的目标就一个词:概括。用几个数字把几万个数据的特征高度浓缩。浓缩方向上,第一看集中趋势(数据中心在哪里),第二看离散程度(数据分布有多散),第三看分布形状(数据偏不偏、峰不峰)。
集中趋势最常用的是均值、中位数、众数。离散程度最常用的是标准差、四分位数间距。分布形状用偏度和峰度。
但这里有个非常关键的细节:均值不是什么时候都能代表数据水平。如果数据的分布严重右偏(比如少数超高金额订单拉高均值),均值会显得比大多数订单都高,这时候中位数反而更能代表“典型水平”。所以我们看数据的第一步,不是看均值有多高,而是先看均值和中位数的关系:两者接近,说明数据分布比较对称;均值明显大于中位数,说明右偏;均值明显小于中位数,说明左偏。
标准差这里多说一句。pandas里std()计算的是样本标准差,分母是n-1。这和很多初学者直觉里的“标准差”不一样,因为默认你把数据当样本,而不是总体。这一点在后面做置信区间时会非常关键,如果搞混了,算出来的结果整个就偏了。
2.2 describe函数的高级用法与参数调整
pandas的describe()函数是描述统计里出场率最高的工具。默认情况下,它只对数值列计算统计量,包括计数、均值、标准差、最小值、25%分位数、中位数、75%分位数和最大值。
不过很多人不知道,这个函数可以自定义百分位。默认只给25%、50%、75%三个点,但现实中你可能关注5%和95%分位数,尤其在看尾部风险(比如金融场景下的极端亏损)时。pd.DataFrame.describe()接受一个percentiles参数,可以用列表指定你关心的百分位,在金融领域做VaR分析时非常实用,可以直观看出尾部订单金额的分布特征。
print(df['order_amount'].describe(percentiles=[0.05, 0.25, 0.5, 0.75, 0.95]))如果你需要同时看分类列的描述统计,记得加include='all',这样它会额外输出分类列的计数、唯一值数量和出现最多的类别。默认情况下pandas会过滤掉非数值列,初学者经常在这个地方卡住,怎么describe都看不到分类列的信息。
2.3 分组描述统计:差异藏在对比里
只看全量数据的描述统计远远不够。比如我这份数据里,三种促销策略混合在一起,均值和标准差都说不清策略B到底是不是真的带来了更高的订单金额。这时候必须做分组描述统计,也就是pandas里最经典的groupby()操作。
summary = df.groupby('promotion')['order_amount'].agg(['count', 'mean', 'std', 'median']) print(summary.round(2))这个输出是整个分析流程里信息量最大的一张表。我们还没跑任何假设检验,就已经能看到策略B的均值明显高于策略A,但问题是这个差异到底是真的因为策略有效,还是仅仅是随机的抽样波动?这正是后面假设检验要回答的问题。描述统计先把差异摆到桌面上,假设检验再去判断差异值不值得信。
如果说均值是对数据整体水平的“猜想”,那么标准差和标准误就是给这个猜想打的“置信分”。分组后的标准差差距也比较明显,策略B比策略A的标准差更大,说明它的订单金额波动更剧烈。这意味着即使B的均值高,也有可能只是在某些时间段特别高,平时和A差不多。这个观察就是我们下一步分析的伏笔。
2.4 用图形辅助描述统计:一张图胜过千行数据
统计数字能精确地告诉我们“什么情况”,但想快速理解数据形态,图比数字更直观。我在做描述统计时基本都会画三张图:直方图看分布形态、箱线图看离群点和分位数差异、密度图对比不同组的分布对称性。
import matplotlib.pyplot as plt import seaborn as sns sns.set_style('whitegrid') plt.figure(figsize=(12, 4)) plt.subplot(1, 3, 1) sns.histplot(df['order_amount'], bins=30, kde=True) plt.title('Order Amount Distribution') plt.subplot(1, 3, 2) sns.boxplot(data=df, x='promotion', y='order_amount') plt.title('Order Amount by Promotion') plt.subplot(1, 3, 3) sns.kdeplot(data=df, x='order_amount', hue='promotion', common_norm=False) plt.title('KDE by Promotion') plt.tight_layout() plt.show()运行这段代码以后,第一张图能看出订单金额整体呈近似正态分布,中间高两边低。第二张箱线图信息量最大:策略B的中位数线在三个组里最高,而且它的箱子(代表四分位数间距)也更高更宽,说明不仅整体客单价高,而且不同订单之间的差异也大。第三张密度曲线能看出来策略B和策略A的分布有明显偏移,而且策略C数据介于中间。
注意:用seaborn画图时,如果遇到中文字体显示为方块的问题,这是因为系统缺少中文字体。可以在画图前加一行
plt.rcParams['font.sans-serif'] = ['SimHei']来指定中文字体,或者把图例和标题都改成英文,代码示例中统一用英文就是为了避免这个麻烦。
3. 假设检验的基础逻辑
3.1 换个角度理解p值、显著性水平与两类错误
描述统计告诉我们数据长什么样,但真实业务里我们还要回答一个“然后呢”的问题:策略A和策略B的均值差了30元,这30元是稳定存在的,还是因为碰巧抽到的样本里策略B的大客户多一点?假设检验就是回答这个问题的标准化框架。
很多教程上来就堆术语,说原假设、备择假设、p值、显著性水平,但其实核心逻辑用一句话就能概括:做差异判断时,要先假设这些差异根本不存在,然后看手头的数据支不支持推翻这个假设。
我习惯把p值理解成“冤枉好人的概率”。原假设说“两个策略本身没有差异”,p值就是“在原假设为真时,观察到我们手头这么大的差异(甚至更大)的概率”。p值很小的时候,说明要么是小概率事件真的发生了,要么就是原假设根本不对。线上线下大家一般约定俗成用0.05作为判断线,p小于0.05就拒绝原假设。
但这里面有两个坑。第一,p值不是“策略有效的概率”,很多人都会误解。第二,p值小于0.05不代表差异真正重要,大样本下微小的差异也能跑出很小的p值,判断差异有没有实际价值得回到业务场景去,后面实战部分我会细说。
两类错误也要有个概念:第一类错误是“策略本无效果但你说了有效果”,它的概率是显著性水平α;第二类错误是“策略本有效果你却说没效果”,它的概率是β,统计功效1-β就是在说“真有差异时我们能发现它的能力”。做实验之前保证样本量足够,本质上就是在控制第二类错误。
3.2 业务分析中如何选对检验方法
假设检验的方法一抓一大把,但选方法其实不需要死记硬背。我做业务分析时用的是最简单粗暴的决策三步法:
- 第一步,看数据类型。结果是连续数值(比如金额、时长)还是分类计数(比如是否购买、好评差评)?
- 第二步,看比较组数。只有一组样本和参考值比(单样本),还是两组样本比(独立或配对),还是三组及以上比(方差分析)?
- 第三步,看数据是否满足对应检验的前提条件,正态性、方差齐性这些。
以我们的数据为例,研究问题时元素是订单金额(连续变量)、十个促销策略(三组),所以候选方法有单因素方差分析(即ANOVA)和事后两两比较。如果用户只关心策略B是否比A高,就可以用两独立样本t检验。而如果研究的是用户是否复购(二分类)和促销策略(三分类)的关系,那就要用卡方独立性检验。
| 数据类型 | 比较组数 | 适用方法 | 检验的目标 |
|---|---|---|---|
| 连续变量 | 1组 vs 已知值 | 单样本t检验 | 均值是否等于某个参考值 |
| 连续变量 | 2组独立样本 | 独立样本t检验(或Welch检验) | 两组的均值是否有差异 |
| 连续变量 | 2组配对样本 | 配对样本t检验 | 同一批对象前后是否有变化 |
| 连续变量 | 3组及以上 | 单因素方差分析(ANOVA) | 多组均值是否完全相等 |
| 分类变量 | 2组及以上 | 卡方独立性检验 | 分类变量之间是否关联 |
| 连续变量 | 2组及以上 | Mann-Whitney U / Kruskal-Wallis | 数据不满足正态性时的非参数替代 |
这张表基本覆盖了日常90%的分析需求。真判断不了的时候,最简单的做法是用scipy里全部的检验函数列个清单,一个个对照场景就可以,比翻教科书好用多了。
3.3 正态性检验与方差齐性检验
选择参数检验(t检验等)的前提是数据近似正态分布和方差齐性。这里的逻辑是,t检验的本质是比较两组数据的均值,而它计算标准误时用到了一个关键假设:数据在均值周围对称地散布,否则均值就没什么代表性。
判断正态性最常看的是偏度和峰度,两者同时介于-1和1之间说明分布形态温和。做统计检验的话可以用Shapiro-Wilk检验,scipy.stats.shapiro函数直接输出p值。样本量大于5000时,shapiro的检验功效很强,数据稍微有点偏离就判为拒绝正态,容易导致误判。分析大样本时更要结合Q-Q图或偏度峰度数值综合判断。
from scipy import stats stat_shapiro, p_shapiro = stats.shapiro(df['order_amount']) print(f'Shapiro-Wilk检验: 统计量={stat_shapiro:.4f}, p值={p_shapiro:.4f}') skewness = df['order_amount'].skew() kurtosis = df['order_amount'].kurtosis() print(f'偏度={skewness:.4f}, 峰度={kurtosis:.4f}')方差齐性检验可以用Levene检验或Bartlett检验。在有分组时,Bartlett对正态性敏感,Levene更稳健。默认推荐Levene,处理真实业务数据时更不容易误判。这一步逻辑也很清晰:如果两组数据离散程度本来就差异悬殊,即使均值一样,后面算合并标准误的结果也不可信。
group_data = [df[df['promotion'] == g]['order_amount'] for g in ['A', 'B', 'C']] stat_levene, p_levene = stats.levene(*group_data) print(f'Levene方差齐性检验: p值={p_levene:.4f}')4. Python中的常用假设检验实操
4.1 单样本t检验:判断样本是否与参考值有差异
单样本t检验最经典的业务场景是:产品历史平均客单价是200元,我们随机抽取了120个订单,想知道这批订单的客单价和200元是否存在显著差异。原假设是这批订单的均值等于200元,备择假设是不等于200元。
t_stat, p_value = stats.ttest_1samp(df['order_amount'], 200) print(f'单样本t检验: t值={t_stat:.4f}, p值={p_value:.4f}')看p值大于还是小于0.05来判断是否拒绝原假设。我这里跑出来的结果P值很小,说明这批订单的均值和200元之间确实存在显著差异。不过光看p值还不够,最好再结合置信区间看均值到底在什么范围内。scipy.stats.t.interval()可以计算总体均值95%的置信区间,这比单看p值更有说服力。
mean_val = df['order_amount'].mean() std_err = stats.sem(df['order_amount']) ci_low, ci_high = stats.t.interval(0.95, len(df) - 1, loc=mean_val, scale=std_err) print(f'95%置信区间: [{ci_low:.2f}, {ci_high:.2f}]')置信区间不仅告诉我们均值估计的精确度,更重要的一点是:如果我们想验证总体均值是不是某个特定值,直接看这个值在不在区间里就行。200落在区间之外,就拒绝;落在区间里面,就说明数据没有足够证据证明它不等于200。
4.2 两独立样本t检验:不要忽略Welch修正
两独立样本t检验是业务对比最常见的做法,比如对比策略A和策略B的订单金额是否存在显著差异。在scipy里调用stats.ttest_ind()就行,但这里有一个很多人都会忽略的参数,equal_var。
很多人直接用默认的equal_var=True,也就是假设两组方差相等。但实际数据里两组方差完全一样的情况很少见。Levene检验的结果已经告诉我们策略A和B的方差很可能不一样,那这个时候还假设方差相等就有问题了。更稳妥的做法是直接用equal_var=False,也就是跑Welch t检验,它不要求两组方差相等,在样本量也不一定相等的场景下表现更稳健。
a_data = df[df['promotion'] == 'A']['order_amount'] b_data = df[df['promotion'] == 'B']['order_amount'] t_stat, p_value = stats.ttest_ind(a_data, b_data, equal_var=False) print(f'策略A vs 策略B (Welch t检验): t值={t_stat:.4f}, p值={p_value:.4f}')Welch修正后的自由度比普通的n1+n2-2要小,因此p值通常更大一点,做出显著结论更谨慎,不容易犯第一类错误。统计软件里,有些新人跑出p=0.0499还是p=0.0501的差别都源于这里。我的习惯是只要能支持,一直用equal_var=False,这是被无数统计学家验证过的稳妥做法。
4.3 配对样本t检验:控制个体差异,提升检验灵敏度
配对样本t检验和独立样本t检验最大的区别在于,数据不是来自两组不同的人,而是同一批对象在不同条件下的两次测量。业务里最典型的就是“实验前后对比”:同一个用户看广告前和看广告后的消费金额,或者同一批用户试用新产品前后的满意度评分。
配对检验的原理是先把每个个体的前后差异算出来,再判断这些差异的均值是否等于0。这样做的好处是,它把个体之间天生的差异给消除了,只保留实验处理带来的变化量,因此在个体差异很大的时候,配对设计往往能更敏感地检测出真实效果。
np.random.seed(123) before = np.random.normal(50, 10, 40) after = before + np.random.normal(3, 5, 40) t_stat, p_value = stats.ttest_rel(before, after) print(f'配对样本t检验: t值={t_stat:.4f}, p值={p_value:.4f}')如果这里你错误地用了独立样本t检验,由于个体差异被算进了噪声里,得到的结果经常是“不显著”,但换成配对检验很快就变成“显著”了。这个细节在实际分析里价值很大,实验设计阶段就要考虑好数据配不配对,而不要拿到数据后才发现结构不对。
4.4 卡方独立性检验:分类变量间的关联分析
上面的方法都处理连续变量。业务里还经常遇到这样的问题:用户是否复购和促销策略有没有关系?这里“是否复购”是分类变量,“促销策略”也是分类变量,卡方检验就是对分类数据的关联性做判断。
卡方检验的输入是列联表,也就是每个促销策略下复购/不复购的人次分别有多少。原假设是两个分类变量相互独立,备择假设是它们存在关联。统计量的本质是“实际观测频数”和“假设无关情况下的期望频数”的差异程度,差异越大,越说明两个变量不独立。
crosstab = pd.crosstab(df['promotion'], df['repurchase']) print(crosstab) chi2_stat, p_value, dof, expected = stats.chi2_contingency(crosstab) print(f'卡方检验: chi2值={chi2_stat:.4f}, p值={p_value:.4f}, 自由度={dof}')stats.chi2_contingency()返回四个结果,其中expected是假设两者无关时的期望频数表,建议打印出来看一遍。如果某些格子的期望频数小于5,卡方检验的结果就不太可靠,这时候更适合用Fisher精确检验(fisher_exact,不过它一般只用于2x2表)。日常业务里分类数据的分析频率比很多人想象得要高,这个方法一定要掌握。
4.5 单因素方差分析:多组比较的正确打开方式
当比较三组及以上均值时,很多人图省事两两跑一遍t检验,这会带来严重的多重比较问题。假如我们有三组,两两对比要跑三次,每次显著性水平是0.05,那整体犯第一类错误的概率会上升到1-(0.95^3)≈14.25%,远高于设定的0.05。这就是为什么多组比较要用方差分析,它一个模型同时检验所有组的均值是否相等,从整体上控制了错误率。
f_stat, p_value = stats.f_oneway(a_data, b_data, df[df['promotion'] == 'C']['order_amount']) print(f'单因素方差分析: F值={f_stat:.4f}, p值={p_value:.4f}')方差分析的逻辑是:如果组间变异明显大于组内变异,就说明不同组之间确实存在差异。F值大,p值就小,差异就越显著。跑出来显著之后,方差分析本身不会告诉你到底是哪两组有差异,这时候需要做事后检验(post-hoc test),常见的是Tukey HSD,它能在控制住整体错误率的同时给出两两对比结果。statsmodels里提供了pairwise_tukeyhsd函数,可以直接输出结果表格。
from statsmodels.stats.multicomp import pairwise_tukeyhsd tukey = pairwise_tukeyhsd(df['order_amount'], df['promotion'], alpha=0.05) print(tukey)Tukey的输出表格里有diff(均值差)、p-adj(校正后的p值)和reject(是否显著)三列。这里有个值得留意的点:整体ANOVA显著不代表所有两两对比都显著,反过来ANOVA不显著时却可能发现某一对对比的边缘显著。都以Tukey结果为准比较稳妥。
5. 完整实战:从描述统计到假设检验的一次串联
5.1 业务场景与数据再梳理
现在把前面所有知识点串起来,做一个完整的分析。业务方关心的问题也很简单直接:三种促销策略,到底哪个更好?判断标准有两个,一是订单金额更高,二是复购率更高。
我们先直观地梳理一下这套数据:订单金额是连续变量,衡量促销带来的消费升级效果;是否复购是二分类变量,衡量策略对用户粘性的影响。三组数据互相独立,每组120个样本点。在做完描述统计和可视化之后,我们可以清晰地知道:策略B的订单金额均值和分位数都更高,但数据也相对更离散;复购率三组相差不大,等一会儿用卡方检验做个判断。
5.2 描述统计与可视化联动解读
用groupby计算三组订单金额的均值、标准差和中位数之后,大致结论是策略B均值最高。但要注意,光看均值还不够,我习惯把标准差也一起看,标准差偏大会让你反思“均值高”是否足够稳健可靠。策略B的标准差最大,恰好说明它拉高均值的同时也存在赔本买卖风险,业务上这叫“增收不增利”的隐患。
进一步画箱线图,策略B的中位数和上四分位数都明显高于策略A,而且策略C的位置介于A和B之间。这说明B组的高客单价不是靠少数大订单撑起来的,大多数订单都在拉升,整体趋势比较稳。这是描述统计阶段最有价值的发现,因为它告诉我们,差异不是个别案例的偶然现象。
5.3 检验策略B是否确实优于策略A
业务上最有价值的对比其实就是策略B和策略A,分别代表“冲高客单价”和“常规促销”的差异。因为两组方差不等,这里用Welch t检验:
t_stat, p_value = stats.ttest_ind(a_data, b_data, equal_var=False) print(f'Welch t检验结果: t={t_stat:.3f}, p={p_value:.4f}')p值远小于0.05,说明策略B的订单金额显著高于策略A。但这个显著性是不是意味着B策略就绝对好,还要结合置信区间看。计算差值的95%置信区间,如果区间下限是15元,上限是45元,那说明B策略平均能比A策略多带来15到45元,这个置信区间直接给了业务方一个可以预估的空间范围。
5.4 三组整体比较和事后检验
业务上只对比B和A还不够,我们还要回答“三种策略放在一起是不是真的不同”。这时先跑单因素方差分析,F检验结果显著后,再用Tukey HSD做两两比较。Tukey的结果会告诉你哪些组之间显著、哪些不显著。通常发现B和A显著不一样,而C可能分别和A、B都不显著,这个“中间状态”很有业务价值:说明策略C没有明显胜过A,但也没差到B那么多。
5.5 复购率差异的卡方检验
订单金额之外的另一条线是复购率。用交叉表统计每个策略下有没有用户在活动期内再次购买,然后用卡方独立性检验判断促销策略和复购行为有没有关系。跑出来的p值大概率比较大,说明复购率在三种策略之间没有显著差异。这其实是个常见的真实情况:促销送了钱,短期内把销售额拉上去了,但复购这件事本质还是看产品和体验,促销策略本身很难在短时间内改变用户对品牌的忠诚度。
5.6 统计结论的业务落地建议
做完这一整套分析以后,给业务方的汇报就非常立体了:策略B的客单价显著高于A和C,预计每位用户能多带来几十元的成交额,但复购率并没有显著优势。建议是,如果本次活动的核心目标是冲GMV,那策略B值得推荐;如果目标是拉新促活并培育用户长期复购,那单靠促销优惠显然不够,需要配合产品动作和会员体系来做。
统计结论落回业务的过程里,其实最考验分析师的一个点是,描述统计放在最前面,让业务方先看到有差异;然后用检验去验证“这不是偶然的”;最后再解释差异大小和真实业务影响。数据报告有层次,汇报时就不容易翻车。
6. 常见问题与排查技巧实录
6.1 常见报错与处理速查表
统计分析的报错很多是重复出现的,我这里整理了一个速查表,对照着排查比自己对着浏览器一条条搜要快得多。
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
describe()不显示分类列 | 默认只统计数值列 | 加include='all'参数 |
ttest_ind()结果和Excel对不上 | 默认假设方差相等,Excel默认Welch | 手动指定equal_var=False |
| Shapiro检验总是p<0.05 | 样本量太大时检验过于敏感 | 结合偏度峰度和Q-Q图判断 |
| 画图中文显示为方块 | 系统缺少中文字体 | 设置font.sans-serif或改用英文标签 |
| 方差分析不显著但个别t检验显著 | 多重比较造成错误率膨胀 | 以Tukey HSD事后检验结果为准 |
statsmodels导入报错 | 版本和scipy不兼容 | pip install --upgrade statsmodels |
6.2 一个经常被忽视的统计陷阱:多重比较
这节单独把多重比较拿出来说,是因为它在业务场景里实在太容易被忽略了。之前提过,三组做三次两两t检验,整体第一类错误率会明显上涨。组数越多,错误率涨得越夸张:5组两两对比要跑10次,整体错误率就是1-(0.95^10)≈40%,也就是说哪怕所有组之间其实没有差异,你也有四成的概率得到至少一个显著的假阳性结果。
在Python里,简单场景可以靠Tukey HSD解决,更复杂的多因素对比可以用statsmodels里的multipletests函数,支持Bonferroni、Holm、FDR-BH等多种校正方法。我的习惯是,正式报告里用Bonferroni,因为它最简单可控,虽然有时候过于保守,但理论上不容易被质疑。探索性分析阶段则可以用Holm或BH,能多挖出一些候选结论,等到了验证阶段再收紧标准。
6.3 统计显著不等于业务显著
这是我最后想强调的一点,也是很多新人最难过的一道坎。p值小只能说明差异在统计意义上不太可能是巧合,但“差异到底值不值得做”完全是另一个维度的判断。举个例子,样本量足够大的时候,策略A和B的差异哪怕只有1元,t检验也可能给你一个p<0.001,但为了这1元去改整个促销设计,可能是不划算的。
所以做统计分析,我给自己定了个规矩:先算差值,再看置信区间,最后确认p值。差值是业务上最需要的东西,它告诉你能不能赚到钱;置信区间告诉你怎么给业务方承诺范围;p值只负责回答“这个差异是不是真的存在”。三样摆在一起,结论才完整。尤其在金融统计分析里,哪怕某个因子回归系数的p值非常显著,但如果置信区间跨度过大,预测精度差,那实际部署时还是要谨慎。
还有一个经常被忽略的点:数据的质量永远比方法高级。跑了一堆漂亮统计检验,结果底层数据是脏的、口径是错的,那所有结论都是空中楼阁。所以在做任何假设检验之前,花最多的时间去理解数据是怎么产生的、每个字段是什么意思,并且亲自验证数据范围和逻辑,这比纠结用t检验还是Mann-Whitney U检验重要得多。我个人在做完一次完整分析后,最深的体会就是:统计工具只是帮我们把机械的计算自动化,真正的分析功夫还是在怎么把业务问题翻译成统计问题,再反向把统计结论翻译回业务语言。把这个翻译能力练好了,Python里的各种库学起来都会轻松很多。