news 2026/10/2 4:28:55

Python statistics模块全面教程:均值、中位数、方差与回归分析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python statistics模块全面教程:均值、中位数、方差与回归分析

Python入门:Python3 statistics模块全面学习教程

手上有一堆数字,比如一个班的期末成绩、门店一周的销售流水,或者传感器采回的温湿度值,你想快速算个平均分、中位数、方差,看看数据集中程度。多数人的第一反应是打开Excel,或者去装pandas、numpy这类重型库。但有个大多数入门教程不怎么提的工具,其实就够了——Python 3自带的statistics模块,标准库里的统计工具箱,零安装、零依赖,专门处理这类日常统计计算。

这篇文章不打算罗列官方文档的函数签名,而是从一个实际使用者的角度,把statistics模块的每个常用函数、容易踩的坑、以及为什么要这样设计的原因,一次讲透。你不需要深厚的数学背景,我尽量把每个概念都用大白话和案例解释清楚。学完之后,你至少能用纯标准库完成一份像样的数据统计分析报告,也为以后切换到pandas、numpy打下一个“知道统计指标到底在算什么”的地基。

1. statistics模块的定位:标准库里的轻量统计小刀

先说说这个模块的身世。statistics在Python 3.4版本加入标准库,后来在3.8、3.10、3.12等版本里陆续补充了协方差、相关系数、线性回归等功能,一直迭代到今天。它的定位非常明确:为普通Python程序员提供一套基础统计函数,覆盖平均数、中位数、众数、方差、标准差、协方差、相关系数、线性回归等常见场景。

为什么入门阶段特别建议先学它?我自己的体会是,学习路径的陡峭程度完全不同。用pandas做统计,你要先理解DataFrame的结构,要安装库、处理依赖,代码写起来确实强大,但当你还搞不清楚标准差为什么有两种算法的时候,一上来就把数据框塞进.describe(),得到一大张表,反而不知道每一项是什么意思。statistics模块没有这个负担,它的函数就是纯函数:给一个列表,返回一个数字,数学逻辑完全透明,每一行代码在算什么一目了然。

用生活场景类比,statistics就像一把瑞士军刀,pandas则是整套工具箱。旅行出门削个苹果,你不会扛一个装满电钻的箱子,对吧?同理,当你只是想搞清楚一组数据的平均水平或者波动大小时,标准库自带的函数已经足够,而且不需要考虑环境配置问题。

一句话总结它的适用范围:数据量在几千到几万级别、单次统计运算、不需要分组聚合、不需要复杂的表格运算。超过这个范围,比如百万级数组、按列批量处理、需要透视表,那再升级到numpy/pandas,这是合理的演进路线,而不是一上来就上重武器。

2. 功能总览与核心调用方式:一屏看完所有统计函数

在动手写代码之前,先对整个模块的“家底”有个概念。statistics模块的函数可以分为几大类,我用表格给你列一下:

功能类别函数作用
平均数mean()普通算术平均数
平均数fmean()浮点数算术平均数,运算更快,支持Decimal/Fraction
平均数geometric_mean()几何平均数,适用于比率、增长率数据
平均数harmonic_mean()调和平均数,适用于速率、单位价格等
中位数median()中位数
中位数median_low()/median_high()偶数样本时取偏低/偏高位置的“中位数”
中位数median_grouped()分组数据的中位数,需配合组距参数
众数mode()出现次数最多的值
众数multimode()返回所有出现次数最多的值(列表)
分位数quantiles()把数据按概率切割成若干等份
离散程度pvariance()/pstdev()总体方差 / 总体标准差
离散程度variance()/stdev()样本方差 / 样本标准差
相关性covariance()两个变量的协方差(样本)
相关性correlation()两个变量的皮尔逊相关系数
回归linear_regression()一元线性回归,返回斜率、截距、相关系数

调用方式上,绝大多数函数都接收一个可迭代对象,比如列表[1, 2, 3]、元组(1, 2, 3),甚至生成器表达式。但要注意几个默认行为:

第一,statistics的函数返回类型不完全是float,mean()在Python 3.11之前对整数输入会返回浮点数,median()也是浮点数。从3.12开始,某些函数对整数输入可以返回精确的Fraction或整数,这个变化对入门者影响不大,但要知道。

第二,空数据一定报错。传一个空列表给mean(),立刻抛出StatisticsError,这是有意设计的,因为空数据集在数学上没有定义平均值。实际编码时提前用if not data判空就好。

第三,混合类型输入,比如列表里既有整数又有浮点数,没问题;但如果混入了字符串,会直接抛TypeError,因为统计函数只接受数值类型。

我建议在本地环境里跑一下这个查看模块内容的命令,对函数清单形成直观印象:

import statistics print(dir(statistics))

会输出模块内所有公开名字,包括mean、median、stdev这些常用函数,还有内部的_convert、_ss之类的私有工具函数。看到这一串名字,你就知道这个模块能干什么了,比背文档强得多。

3. 三种均值的正确打开方式:mean、fmean、geometric_mean、harmonic_mean

3.1 mean:最常见,但小心“平均”骗了你

mean()是普通算术平均数,也是大家最熟悉的“平均分”。公式是sum(data) / len(data),但直接用sum和len算,有个小问题:如果数据里混入了非数值类型,比如['a', 'b'],sum会报错但错误信息不直观。用statistics.mean()能给出更明确的类型错误提示。

实际使用里,我发现很多新手对平均数有个误解:平均数能代表所有数据吗?答案是否定的。平均数对极端值极其敏感。举个例子,一个小组5个人的月收入是[5000, 6000, 5500, 5200, 30000],平均一下是10340,但前四个人的收入都在6000附近,这个平均数代表不了“典型水平”。这就是为什么后面要学中位数。平均数适合数据分布比较均匀、没有明显离群点的场景。

3.2 fmean:大数据量时的性能选择

fmean()是mean()的快速浮点版本。区别在于,mean()用了Python的高精度算术来规避浮点累加误差,而fmean()直接把输入统一转成float,用C语言级别的浮点累加,速度快很多。代价是精度略低,但对绝大多数日常数据分析,误差可以忽略。

它还有个额外好处:支持Decimal和Fraction类型。普通的mean()函数对这两种类型会拒绝处理,但fmean()会先把它们转成float再计算。实测下来,处理几万条数据时,fmean()比mean()快大概2到3倍,内存占用也更低。当你用列表推导式生成大量数据时,优先用fmean()。

来看一个对比:

import statistics # 模拟一万条温度数据 temps = [20 + (i % 5) * 0.5 for i in range(10000)] avg1 = statistics.mean(temps) avg2 = statistics.fmean(temps) print(avg1, avg2) # 两者数值相同,fmean更快

3.3 geometric_mean:增长率的正确平均法

geometric_mean()计算几何平均数,就是n个数的乘积再开n次方。什么时候用它?凡是数据之间存在“倍数关系”时,算术平均数会误导。最经典的例子是投资收益率:假设某基金三年收益率分别为10%、20%、-10%,用算术平均数得到(10+20-10)/3=6.7%,但你实际获得的总收益是1.1 * 1.2 * 0.9 - 1 = 0.188 - 1 = 18.8%,换算成年化复合收益率大约6.0%,而不是6.7%。几何平均数才是正确的答案。

Python代码验证:

import statistics returns = [1.10, 1.20, 0.90] # 增长率+1 geom = statistics.geometric_mean(returns) print(geom) # 约1.059,年化约5.9% arith = statistics.mean(returns) print(arith) # 1.0667,明显偏高

任何涉及百分比的增长率、比例、比率数据,无脑用几何平均数就对了。当然前提是所有数为正数,负值会得到复数结果,statistics会直接抛StatisticsError。

3.4 harmonic_mean:速率的平均问题

harmonic_mean()调和平均数,听起来高大上,其实解决的是“单位时间内完成的量”这类平均问题。经典例子:开车去程速度60km/h,返程速度40km/h,问平均车速。算术平均数(60+40)/2=50是错的,因为你在慢速路上花了更长时间。正确算法是2 / (1/60 + 1/40) = 48km/h。调和平均数天然地把“时间权重”因素考虑进去了。

代码示例:

import statistics speeds = [60, 40] harm = statistics.harmonic_mean(speeds) print(harm) # 48.0

日常应用还包括:CPU的平均处理速率、单位价格的平均、批量任务的平均吞吐量。判断标准很简单:如果你的数据是“每单位多少”的比率形式,且每个比率对应的时间或数量不同,调和平均数是最科学的。

4. 中位数系列与分位数:感知数据的“典型位置”

中位数是排序后位于中间位置的值,它对极端值的抵抗力比平均数强得多。statistics模块里中位数有四种变体,初学者容易搞混,我逐一说明。

基础版median():数据按升序排列,如果是奇数个,取中间那个;如果是偶数个,取中间两个的平均值。这是使用频率最高的一个。

median_low()和median_high():同样是偶数个数据时的处理方案,前者取中间两个中较小的那个,后者取较大的那个。什么时候用?当你的数据本质是离散整数时,median()算出的平均值可能是0.5这种非整数,不符合实际场景。比如调查问卷打分1到5分,中间两个是3和4,median()给你3.5,但实际你可能更想要么报告“中位数是3”,要么“中位数是4”,用median_low()或median_high()就行。

median_grouped():处理分组数据的中位数,需要传入interval参数表示组距。比如你有一批身高数据,按5cm一组统计,用interval=5调用这个函数,能基于频数分布估算中位数,而不是要求你提供每个个体的原始值。实际开发中,如果你的数据已经是汇总表而非明细表,这个是唯一能恢复“中间位置”的工具。

再来看分位数。quantiles(data, n=4)把数据按排序后切成n个等份,默认n=4就是四分位数。返回结果是一个长度为n-1的列表,比如n=4返回[25%, 50%, 75%]位置的值。这里要留心:它用的切分方法跟Excel的PERCENTILE.INC略有差异,具体结果是分位点估计,跟教科书上的计算方式可能有细微出入,但不影响实际分析。

做法示例:

import statistics scores = [55, 60, 62, 65, 70, 72, 78, 82, 85, 91] quartiles = statistics.quantiles(scores, n=4) print(quartiles) # [62.75, 71.0, 82.75]

解读:25%的人分数低于62.75,中位数是71,75%的人低于82.75。配合median()一起看,能快速判断数据是否偏态:如果中位数明显低于平均数,说明有少数高分把平均数拉高了,数据右偏。

mode()和multimode()是众数函数,返回出现频次最高的值。mode()对多个众数会报StatisticsError,新版推荐直接用multimode(),它永远返回一个列表,一个众数时长度也是1,方便统一处理。众数适合分类数据,比如统计用户最常用的操作系统、门店销量最高的sku,这些场景平均数、中位数都没意义。

5. 方差与标准差:总体和样本,两套函数千万别混用

方差和标准差是描述数据波动程度的核心指标,statistics模块里有四个关联函数:pvariance/pstdev对应“总体”,variance/stdev对应“样本”。这个“总体 vs 样本”的区分是整个模块里最容易踩的坑,我详细拆开讲。

5.1 为什么同一个数据有两种方差?

假设你手头有一整班50个人的成绩,这是全体数据,叫“总体”。你算所有人的成绩波动程度,用总体方差,分母是n。但更多时候,你手里只是一部分数据,比如从全市所有学生中随机抽了50个人来估算全市成绩波动,这叫“样本”。样本方差的分母是n-1而不是n。

为什么分母要减1?统计学里一个重要的概念是“自由度”。样本均值是被计算出来的,它像一条约束线,当你知道了样本中n-1个数值和均值,最后一个数就被“锁定”了,能自由变化的只有n-1个。为了让样本方差作为总体方差的无偏估计,分母必须用n-1。用人话说:样本方差天生倾向于低估总体方差,减一个自由度能把偏差修正回来。

实际工作中,你几乎永远用的是variance()和stdev(),因为真正的“总体数据”少之又少。除非你能确认你的数据覆盖了全部个体,比如“全班”“全公司”“全年产生的订单”这类完整集合,才用pvariance()和pstdev()。

5.2 标准差为什么比方差好用?

方差是标准差的平方,单位是原始单位的平方,比如数据单位是“分”,方差单位就是“分²”,不易直观理解。标准差开方后回到原始单位,可以直接和平均数对比。stdev()计算的就是样本标准差,一个数值,单位“分”,表示“大多数数据离平均值大约有多少”。配合经验法则:数据近似正态分布时,约68%的数据落在“均值±1个标准差”内,95%落在“±2个标准差”内。这个直觉比干看方差数字强得多。

实际代码:

import statistics q1_scores = [85, 90, 78, 92, 88, 76, 95, 89] q2_scores = [40, 60, 75, 90, 95, 88, 92, 98] avg1 = statistics.mean(q1_scores) # 86.6 avg2 = statistics.mean(q2_scores) # 79.8 std1 = statistics.stdev(q1_scores) # 6.39 std2 = statistics.stdev(q2_scores) # 19.5

虽然两个班平均分都还可以,但q2班的标准差是q1班的3倍,说明成绩差距悬殊,有的同学极低有的极高,教学问题要比q1班严重得多。平均数一致时,标准差就是鉴别数据质量的照妖镜。

5.3 使用里的小细节

传入数据只有1个时,variance()/stdev()会抛StatisticsError,因为分母n-1是0。这提醒了数据分析的一个基本原则:单个样本点说明不了任何波动程度。传2个值时,方差可以算,但毫无意义,我记得有个数学段子:“两个数据点的标准差,是你对这两个点有多不信任的度量。”所以实际分析时至少准备五六个数据以上。

6. 两个变量的关系:协方差、相关系数与最小线性回归

从版本3.10开始,statistics模块补全了两变量统计分析能力:covariance()、correlation()、linear_regression()。这三个函数让你不装scipy也能快速判断两个变量是否相关,甚至拟合出一条直线。

covariance(x, y)计算样本协方差,衡量两个变量一起变化的趋势。协方差为正,说明一个升高另一个也倾向于升高;为负,则相反。但协方差有个毛病:它的数值大小受变量单位影响,不好衡量相关程度强弱。

correlation(x, y)对协方差做归一化,除以两个变量的标准差,得到皮尔逊相关系数r,范围在-1到1之间。r接近1正相关强,接近-1负相关强,接近0无关。这是判断线性关系强度的标准指标,也是众多机器学习算法里特征筛选的常用依据。

linear_regression(x, y)是真正的线性回归实现:返回LinearRegression对象,有slope(斜率)、intercept(截距)和correlation(相关系数)三个属性。相当于找到一条直线y = slope * x + intercept,让所有点到直线的垂直距离平方和最小,也就是最小二乘法。

来看一个完整的例子,分析广告投入和销售额的关系:

import statistics # 各月广告投入(万元) ads = [2.5, 3.0, 3.5, 4.0, 4.2, 4.8, 5.5, 6.0] # 对应月销售额(万元) sales = [80, 88, 95, 100, 102, 108, 120, 126] r = statistics.correlation(ads, sales) print(f"相关系数: {r:.4f}") # 0.9963,强正相关 reg = statistics.linear_regression(ads, sales) print(f"斜率: {reg.slope:.2f}") # 每多投1万广告,销售额增约17.6万 print(f"截距: {reg.intercept:.2f}") # 不投广告时基础销售额约 33.1万 predict = reg.slope * 7.0 + reg.intercept print(f"投入7万元时预测销售额: {predict:.2f}")

这段代码相当于实现了一个最简版本的预测模型,全部基于标准库。要注意的是,linear_regression()要求传入的两个序列长度一致,否则抛StatisticsError;另外它假设自变量x没有测量误差,因变量y存在随机误差,这是经典的最小二乘模型假设。如果你要做更细致的回归诊断,比如看残差、p值、置信区间,那确实得上scipy.stats或statsmodels,这个模块帮你完成的是“快速看趋势”阶段。

7. 实践中最容易踩的边界与坑,一次补齐

这部分是真正的经验集锦。statistics模块自己不怎么出问题,出问题的往往是使用者对它的预期不对。我把实际用下来容易翻车的地方全部列一遍。

7.1 空序列与单元素序列

前面提过,mean()空序列报StatisticsError,variance()单元素报StatisticsError。但很多新手写代码时不检查,直接让用户的输入流进来,结果线上脚本半夜崩溃。标准做法是写个安全的包装:

import statistics def safe_mean(data): if not data: return None return statistics.mean(data)

7.2 非数值类型混入

列表里有一个字符串,整个统计就炸了。复杂一点的场景:从网页爬下来的数据是文本格式,比如带单位的"20°C",直接统计会TypeError。要先清洗转换成数值。这个不是模块的问题,但它是使用中最高频的问题。

7.3 Decimal与Fraction的基本盘

mean()明确不支持Decimal和Fraction,会报TypeError,理由是这类精确类型混进浮点运算会丢失精度语义。解决办法有两个:用fmean()(它会转换),或者先把Decimal转成float。做财务计算时不建议转float,会有精度损失,应该用decimal.Decimal手工计算,而不是依赖statistics。

7.4 大数据量下的性能问题

statistics的多数函数是先加载整个序列再运算,内部有些实现是先把数据转成float列表,两遍扫描。数据量上了几十万、上百万时,性能明显劣化,内存也可能紧张。这时候我建议切到numpy,它用C语言连续内存存储,速度是标准库的几十倍。什么时候切换?我的经验阈值是:单次统计处理的列表长度超过10万,且需要反复迭代计算,就该换工具了。

7.5 偏态数据不要只报告平均数

statistics模块给了你很多工具,但工具不会替你思考。遇到收入、房价这类右偏分布数据,只报告mean()会严重失真。我建议的标准配置是:同时报告mean()、median()、stdev()三个数,让它们互相验证。如果平均数明显大于中位数,说明存在拉高平均值的极端值;“平均数—中位数”的差值本身就是一个有价值的信息。

7.6quantiles()的切分方法差异

前面提到quantiles()实现的切分算法跟Excel的PERCENTILE.INC不完全一样。具体来说,它用了一种“线性插值定位法”,结果偏向于数据中的实际观测值,而不是严格按照(n-1)*p的公式插值。若你的分析报告需要跟Excel结果完全一致,建议用numpy.percentile()或者自己写插值公式,而不是直接用statistics.quantiles()。

7.7 两变量序列需要等长

covariance()、correlation()、linear_regression()都会先检查两个序列长度,不一致会抛StatisticsError,消息是“X and Y must be the same size”。实际从数据库拉数据时,常会遇到一列有缺失值、另一列没有的情况,需要先用zip()过滤或pandas对齐,再传入统计函数。

8. 综合实战:用statistics模块完成一份班级成绩分析

把前面所有内容串起来,做一个完整的实战任务。场景是:老师给了两个班的上学期期末成绩,要求输出一份分析报告,包含各班的平均分、中位数、标准差、四分位数,并判断两个班的成绩差异是否有参考意义。

import statistics # 两个班的成绩原始数据 class_a = [78, 82, 91, 65, 70, 85, 88, 92, 76, 83, 79, 95, 68, 74, 80] class_b = [58, 62, 45, 80, 95, 78, 82, 44, 60, 88, 72, 90, 55, 61, 70] def describe(className, data): avg = statistics.mean(data) med = statistics.median(data) std = statistics.stdev(data) q1, q2, q3 = statistics.quantiles(data, n=4) # 这时q2就是中位数 print(f"--- {className} ---") print(f"平均分: {avg:.1f}") print(f"中位数: {med:.1f}") print(f"标准差: {std:.1f}") print(f"四分位数: Q1={q1:.1f}, Q2={q2:.1f}, Q3={q3:.1f}") print(f"最高分: {max(data)}, 最低分: {min(data)}") print() describe("A班", class_a) describe("B班", class_b) # 两个班合并后的总体情况 all_data = class_a + class_b print("合并后样本标准差:", statistics.stdev(all_data))

再进一步,看看成绩和学习时长的关系:

# 假设每个学生对应的每周学习时长(小时) study_hours_a = [5, 6, 9, 3, 4, 8, 8, 10, 5, 7, 6, 12, 3, 4, 6] study_hours_b = [2, 3, 1, 6, 10, 5, 6, 1, 2, 9, 4, 11, 1, 2, 4] # 两个班合并后的学习时长与成绩 all_hours = study_hours_a + study_hours_b r = statistics.correlation(all_hours, all_data) reg = statistics.linear_regression(all_hours, all_data) print(f"学习时长与成绩的相关系数: {r:.3f}") print(f"回归方程: 成绩 = {reg.slope:.2f} * 学习时长 + {reg.intercept:.2f}")

这段代码跑完,你就能得到完整的趋势判断:相关系数约0.83,说明学习时长确实和成绩强正相关;回归斜率的含义是“每周多学1小时,成绩平均上涨约4.3分”。这种话术写进分析报告或周报里,比只说“我们分析了成绩”有说服力得多。

我把这个分析脚本保存在一个grade_report.py里,后续每次考试数据更新,改一下列表就能复用。如果你要拓展成更通用的工具,可以把它包装成一个函数,接收文件路径和班级编号,自动读取CSV并生成报告,完全用标准库的csv模块就能搞定。

我个人在实际项目中还有一个小习惯:任何用statistics算出的关键结论,都要打印出原始数据的规模和基本形态,比如样本量n、是否有异常值。这样万一结论被质疑,你能立刻回溯检查。数据统计这行,结论依据越透明,越值得信任。

statistics模块到这里就全覆盖了。它是Python标准库里被低估的宝藏,虽然名字朴素,但日常遇到的数据分析需求,八成都用得上。学完它再去看numpy和pandas,你会发现自己对统计指标的理解扎实很多,而不是条件反射一样地调用.mean()却不知道背后在算什么。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 4:28:50

无侵入APM怎么选?SkyWalking统一指标、日志与链路的实战经验

1. 被指标、日志、链路三张皮折磨之后,我为什么选了 SkyWalking1.1 故障定位等于做拼图,这才是监控最大的内耗做后端开发这几年,团队最痛苦的不是系统不稳定,而是每次线上出问题时,都在几个完全割裂的系统里来回横跳。…

作者头像 李华
网站建设 2026/10/2 4:28:40

OpenPlanner:开源TSN规划器解决时间确定性落地难题

简介:OpenPlanner是一个面向工业物联网与实时通信领域的开源TSN(时间敏感网络)规划器,主要服务于嵌入式系统工程师、网络协议开发者及算法研究人员,解决TSN网络中确定性调度难、时序保障弱等核心问题,适用于…

作者头像 李华
网站建设 2026/10/2 4:28:33

PHP8.5配置单元测试Mock数据怎么生成

前言写单元测试时最典型的几种症状:测试跑一次绿、跑两次红,因为造的数据里带随机值;一个"单元测试"启动了半天,因为它偷偷连了数据库和第三方支付接口;以及 mock(模拟对象)写了一大堆…

作者头像 李华
网站建设 2026/10/2 4:28:18

Simscape四旋翼可视化仿真搭建方法、避坑要点与控制器对接完整指南

从0搭建四旋翼Simscape可视化仿真:一次踩坑记录与完整方案先说个情境,很多刚接触四旋翼仿真的同学都会经历这个过程:PID调了半天,曲线图里姿态角变化很漂亮,但永远不知道自己控制的这个“抽象积分器”长什么样。我自己…

作者头像 李华
网站建设 2026/10/2 4:28:18

现代C++宏定义实战:从预处理原理到企业级应用

这几年写C项目,从嵌入式裸机到后端服务,宏定义算是我用得最多也最谨慎的一个工具。不少人对宏的印象停留在“用#define替换一个常量”,但真正到了企业级项目里,宏要承担的东西远不止这些——它可以是代码生成器、编译期分支开关、…

作者头像 李华
网站建设 2026/10/2 4:28:03

Vibe Coding 实战:从提示词工程到 Agent 模式的工程化落地

1. 从“会写代码”到“会描述意图”:Vibe Coding 到底改了什么“Vibe Coding”这个词最近在开发圈子里出现的频率越来越高,很多人第一次听到会以为是某种新的编程语言或者框架,其实它描述的是一种工作方式的转变:开发者不再逐行敲…

作者头像 李华