在科学计算与数据分析中,均值是最常用的统计量之一,但普通算术平均对异常值极其敏感。SciPy 作为 Python 的核心科学计算库,在 scipy.stats 模块中提供了 trim_mean 函数,用于计算截尾均值。这种统计方法通过剔除样本两端一定比例的极端数据,再对剩余部分求平均,从而获得更稳健的中心位置估计。

trim_mean 函数的基本用法
trim_mean 位于 scipy.stats 中,其最基础的调用形式为 trim_mean(a, proportiontocut),其中 a 为输入数组,proportiontocut 表示需要从每一端截断的比例。该函数会先对数据进行排序,然后丢弃最小的 proportiontocut 比例的数据和最大的同样比例的数据,最后对中间保留的部分计算均值。
需要注意,proportiontocut 是单侧截断比例,而不是总截断比例。例如当 proportiontocut=0.1 时,实际总共会丢弃 20% 的数据(两端各 10%)。如果样本量较小,截断后的有效样本数可能非常少,此时结果稳定性反而会下降。下面的示例展示了基本调用方式:
import numpy as np
from scipy.stats import trim_mean
# 构造含有明显异常值的样本
data = np.array([1, 2, 2, 3, 3, 3, 4, 4, 100])
# 普通均值
normal_mean = np.mean(data)
print("普通均值:", normal_mean)
# 截尾均值,截断两端各10%
t_mean = trim_mean(data, 0.1)
print("截尾均值:", t_mean)
在上面代码中,由于 100 是极端大值,普通均值会被显著拉高,而 trim_mean 在样本量为 9、截断 10% 时会从每端各丢弃约 0.9 个数据(实际向下取整丢弃 0 个,因此该例需更大比例或更多数据才能体现截断效果)。这提醒我们,在小样本下要谨慎选择 proportiontocut。
参数细节与轴处理
除了基础数组与截断比例,trim_mean 还支持 axis 参数,允许沿指定轴对多维数组进行计算。当处理二维数据(如多次实验的重复观测矩阵)时,可以设置 axis=0 对每列分别求截尾均值,或 axis=1 对每行处理。函数不会修改原始输入数组,而是返回新的计算结果。
另一个容易忽略的点是,trim_mean 在内部使用 numpy 的排序与切片,因此要求输入可以转换为浮点数组。如果传入包含缺失值(如 numpy.nan)的数组,结果也会是 nan,需要提前做清洗。以下示例展示多维数组的轴截断:
import numpy as np
from scipy.stats import trim_mean
# 3次实验,每次5个观测
mat = np.array([
[10, 12, 14, 100, 11],
[9, 11, 13, 10, 12],
[8, 90, 12, 11, 10]
])
# 沿轴0(跨实验)对每列截尾
col_trim = trim_mean(mat, 0.2, axis=0)
print("按列截尾均值:", col_trim)
# 沿轴1(实验内)对每行截尾
row_trim = trim_mean(mat, 0.2, axis=1)
print("按行截尾均值:", row_trim)
通过 axis 参数,我们能在不写循环的情况下批量获得稳健统计量。相比手动写掩码过滤,trim_mean 的代码更简洁且经过库内部优化。不过当某行或某列有效数据过少时,截断可能导致剩余样本为空,此时 NumPy 的 mean 会返回 nan,调用方需自行判断。
截尾均值与普通均值的对比
为了直观理解截尾均值的优势,我们构造一个含有离群点的评测得分场景。假设十位评委打分,其中一位给了极低或极高异常分,普通均值会被带偏,而截尾均值通过丢弃两端各 20% 的分数,能更好反映多数评委意见。
从统计性质看,截尾均值是稳健估计量(robust estimator),其崩溃点(breakdown point)高于普通均值。普通均值只要有一个无穷大数据,结果就失效;而截尾均值在截断比例内可抵抗相应比例的污染。下表列出二者特性差异:
| 对比项 | 普通均值 | 截尾均值(proportiontocut=0.2) |
|---|---|---|
| 对离群点敏感度 | 极高 | 低 |
| 计算效率 | 最高,O(n) | 需排序,O(n log n) |
| 小样本适用性 | 稳定但有偏风险 | 有效样本可能不足 |
| 参数依赖 | 无 | 依赖截断比例选择 |
可以看到,截尾均值以稍高的计算成本和参数调节为代价,换取了对异常值的抵抗力。在实际应用中,如传感器去噪、竞赛评分、延迟统计等,都常使用该方法。但若数据本身分布均匀且无离群点,普通均值反而更直接且高效。
使用中的常见误区
不少使用者在第一次接触 trim_mean 时会误以为 proportiontocut 是总截断比例,从而在想丢弃 20% 数据时传入 0.2,结果只丢弃了 10%,导致稳健性不足。正确做法是:若希望总共去掉 20%,应传入 0.1。
此外,trim_mean 并不会自动处理非数值或空值。如果数据来自真实业务库,可能混有文本或缺失标记,直接传入会抛出异常或得到 nan。推荐在调用前用 numpy.nan_to_num 或掩码清理,并确认截断后剩余长度大于零。示例:
import numpy as np
from scipy.stats import trim_mean
raw = np.array([2.1, 3.0, np.nan, 2.8, 99.0, 3.2])
# 清理缺失值
clean = raw[~np.isnan(raw)]
# 截断两端各15%
result = trim_mean(clean, 0.15)
print("清理后截尾均值:", result)
经过清洗,极端值与缺失项都不会干扰统计过程。总体来看,trim_mean 是 SciPy 中轻量且实用的稳健统计工具,理解其参数语义与数据前提,才能在工程中发挥最大价值。