前言
提到"统计分析",很多人第一反应是安装 NumPy 或 pandas。但如果只是要算一组 数据的平均值、中位数、标准差,标准库里的statistics模块就够了,不用 装任何第三方库。它从 Python 3.4 起就存在,import statistics即可使用。
这里有个常见误解:以为"标准差"只有一个数。实际上统计学里要区分样本 标准差和总体标准差——statistics.stdev()算的是样本标准差,分母是 n-1;statistics.pstdev()算的是总体标准差,分母是 n。用错一个字母,结果就 不一样,而报错往往是"数值对不上"这种很隐蔽的症状。
本文按"集中趋势 / 离散程度 / 两组数据的关系"三类,讲清楚statistics里 常用函数的签名、返回值类型和版本要求,并说明它和 NumPy 的适用边界。 文中所有函数签名都以 Python 官方文档为准,涉及 3.8 / 3.10 / 3.12 才加入的 特性会单独标注。
一、集中趋势:平均、中位数、众数
这一组回答"数据围绕哪个中心值分布"。
# 适用于 Python 3.8+
import statistics
data = [2, 4, 4, 4, 5, 5, 7, 9]
print(statistics.mean(data)) # 5.0 算术平均
print(statistics.median(data)) # 4.5 中位数(偶数个取中间两数平均)
print(statistics.mode(data)) # 4 出现次数最多的值几个必须记住的差别:
mean(data)返回的类型跟着输入走。如果传入的是Fraction或
Decimal,它会做精确运算并返回同样的类型;传普通整数列表时返回float。 想要"一定是浮点、且更快",用fmean(data, weights=None)(3.8 加入), 它永远返回float。
median()是一族函数。median_low()在偶数个元素时取偏低的中位数,
median_high()取偏高的;median_grouped(data, interval=1.0)用于分组 数据(已经按组统计过的频率表)。
mode()只返回一个值。如果数据有多个并列的众数,从 3.8 起它返回
先遇到的那个,而不是报错。想拿到全部众数,用multimode(data)(3.8 加入),返回一个列表。
# 适用于 Python 3.8+
import statistics
scores = [60, 70, 70, 80, 80, 90]
print(statistics.mode(scores)) # 70 (先遇到的)
print(statistics.multimode(scores)) # [70, 80] 全部众数另外还有三种平均:geometric_mean(data)(几何平均,3.8 加入,常用于比率、 增长率)、harmonic_mean(data, weights=None)(调和平均,3.6 加入,常用于 平均速度)、以及fmean(快速算术平均)。
二、离散程度:方差与标准差
这一组回答"数据散得有多开"。关键是分清样本还是总体。
# 适用于 Python 3.8+
import statistics
data = [2, 4, 4, 4, 5, 5, 7, 9]
print(statistics.pstdev(data)) # 总体标准差 (除以 n)
print(statistics.stdev(data)) # 样本标准差 (除以 n-1)
print(statistics.pvariance(data)) # 总体方差
print(statistics.variance(data)) # 样本方差| 函数 | 含义 | 分母 | 何时用 |
|---|
pstdev(data, mu=None) | 总体标准差 | n | 数据就是全部总体 |
stdev(data, xbar=None) | 样本标准差 | n-1 | 用样本估计总体 |
pvariance(data, mu=None) | 总体方差 | n | 同上 |
variance(data, xbar=None) | 样本方差 | n-1 | 同上 |
参数mu/xbar允许你把"已知的真实均值"传进去,避免函数再算一遍。默认 是None,表示由函数自己从数据里算。样本方差必须至少有两个数据点,否则 会抛statistics.StatisticsError;总体方差有一个点就够了。
三、分位数:把数据切成若干段
quantiles(data, *, n=4, method='exclusive')(3.8 加入)把数据切成 n 段, 返回 n-1 个切点。默认n=4就是四分位。
# 适用于 Python 3.8+
import statistics
data = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
print(statistics.quantiles(data, n=4)) # [2.75, 5.5, 8.25] (exclusive)
print(statistics.quantiles(data, n=4, method='inclusive')) # [3.25, 5.5, 7.75]
print(statistics.quantiles(data, n=10)) # 十分位注意这两个参数只能按关键字传(*之后的参数不能按位置传):n和method必须写成n=4、method='inclusive'这样的形式。method可取'exclusive'(默认,适用于样本)或'inclusive'(适用于总体)。
四、两组数据的关系
这一组函数只在 Python 3.10 及以上才有,用法是把两个等长序列一起传进去。
# 适用于 Python 3.10+
import statistics
x = [1, 2, 3, 4, 5]
y = [2, 4, 6, 8, 10]
print(statistics.covariance(x, y)) # 5.0 样本协方差
print(statistics.correlation(x, y)) # 1.0 皮尔逊相关系数
slope, intercept = statistics.linear_regression(x, y)
print(slope, intercept) # 2.0 0.0covariance(x, y, /)—— 样本协方差,两个参数是仅位置参数(/之前的
参数不能按关键字传)。
correlation(x, y, /, *, method='linear')—— 默认算皮尔逊相关系数;
从3.12 起可以把method设为'rank',改算斯皮尔曼等级相关系数。
linear_regression(x, y, /, *, proportional=False)—— 返回斜率与截距的
二元组(slope, intercept);从3.11 起有proportional参数, 为真时强制回归线过原点。
五、正态分布与 z 分数
statistics.NormalDist(mu=0.0, sigma=1.0)是一个表示正态分布的类(3.8 加入), 支持加减乘除、求累积分布、求分位数等。
# 适用于 Python 3.8+
import statistics
normal = statistics.NormalDist(mu=100, sigma=15)
print(round(normal.cdf(115), 4)) # 0.8413 累积概率
print(round(normal.inv_cdf(0.5), 1)) # 100.0 中位数即 mu
print(round(normal.zscore(130), 2)) # zscore 从 3.9 起可用zscore(x)(3.9 起加入)返回(x - mean) / stdev。NormalDist还能用from_samples(data)从数据里估计出均值和标准差。
六、和 NumPy 的分工
| 场景 | 推荐 | 原因 |
|---|
| 少量数据的常规统计 | statistics | 纯标准库,无需安装 |
| 需要精确的分数 / 小数运算 | statistics | 支持Fraction/Decimal |
| 大数组、逐元素向量化运算 | NumPy | statistics是纯 Python,逐个遍历 |
| 复杂分布、假设检验 | 需第三方库 | 标准库只提供基本正态分布 |
statistics是纯 Python 实现,对几万以内的数据完全够用;但要处理百万级 数组或需要广播运算,就该用 NumPy。这不是"哪个更好"的问题,而是用途不同。
常见坑点
- 把 stdev 和 pstdev 用反
``python data = [2, 4, 6, 8] # ❌ 数据本身就是全部总体,却用了样本公式 statistics.stdev(data) # ✅ 数据就是总体时用 pstdev statistics.pstdev(data)``
- 样本统计量数据太少
``python # ❌ 只有一个数据点,stdev 会抛 StatisticsError statistics.stdev([5]) # ✅ 样本标准差至少需要两个点 statistics.stdev([5, 7])``
- 对空序列调 mean / median
``python # ❌ 空列表直接抛 StatisticsError statistics.mean([]) # ✅ 先判空,给出自己的默认值 data = [] result = statistics.mean(data) if data else 0``
- 以为 mode 在有多个众数时会报错
``python # ❌ 以为会抛异常,其实 3.8+ 只返回第一个 # ✅ 要全部众数就用 multimode statistics.multimode([1, 1, 2, 2])``
- 把仅位置参数按关键字传
``python # ❌ covariance 的参数标记了 /,不能这么写 statistics.covariance(x=x, y=y) # ✅ 按位置传 statistics.covariance(x, y)``
- 把 quantiles 的 n 当位置参数
``python # ❌ n 在 * 之后,只能按关键字传 statistics.quantiles(data, 4) # ✅ statistics.quantiles(data, n=4)``
- 在 3.9 上调用 3.10 才有的函数
``python # ❌ correlation 是 3.10 加入的,旧版本没有 # ✅ 先确认版本,或用第三方库 import sys if sys.version_info >= (3, 10): statistics.correlation(x, y)``
总结
| 需求 | 函数 | 最低版本 |
|---|
| 算术平均 | mean/fmean | 3.4 / 3.8 |
| 中位数 | median家族 | 3.4 |
| 众数 | mode/multimode | 3.4 / 3.8 |
| 样本标准差 | stdev/variance | 3.4 |
| 总体标准差 | pstdev/pvariance | 3.4 |
| 分位数 | quantiles | 3.8 |
| 协方差 / 相关 | covariance/correlation | 3.10 |
| 线性回归 | linear_regression | 3.10 |
| 正态分布 | NormalDist | 3.8 |
statistics的价值在于"零依赖、语义清晰、能精确运算",代价是它是纯 Python、 不适合超大数组。记住stdev与pstdev的区别、以及各函数的加入版本,就 能避开绝大多数误用。