做数据挖掘,最熟悉的一个场景就是——拿到一批新数据,先不管建模还是画图,先得把手头这批数据“摸清楚”。均值、中位数、众数,这些集中趋势指标上一轮已经聊过了,看得见数据的“中心”在哪。但中心不能代表全部,真实世界的数据往往波动得很厉害,两组数据可能均值完全相同,内部结构却天差地别。本篇就是“描述统计(下)”,重点补齐另一半拼图:离散程度、分布形态,以及这些统计量在数据挖掘里的真实应用场景。
上篇侧重“数据集中在哪”,这篇解决“数据到底散成什么样、偏成什么样”。这两块合起来,才算对一个变量有了完整的画像。比如看完一篇电商数据分析报告,只告诉客单价均值80元,你心里是没底的;如果加上标准差25元,你就知道波动不小,进一步结合偏度判断大多数订单其实集中在低价区,那才有决策价值。
这篇内容适合刚入门数据挖掘或数据分析的读者,也适合已经会用Python跑统计、但不太清楚每个指标到底该什么时候用、怎么解读的同学。侧重点不只是公式,而是每个统计量背后的“使用场景”和“解读套路”。看完你会知道,为什么只看均值容易被数据骗,标准差和方差到底谁更实用,偏度和峰度在建模前有什么预警作用,以及箱线图为什么是数据清洗阶段的一把快刀。
1. 只谈“平均”远远不够——离散趋势到底在回答什么问题
上篇讲均值、中位数、众数时,我提过一个观点:集中趋势描述的是数据的“重心”,但数据分布的世界里,“重心”相同的数据,形态可能完全不同。
举一个数据挖掘里特别常见的例子。假设你在做用户行为分析,统计两个渠道的每日活跃用户数:
- A渠道:100、101、99、100、102、98、100
- B渠道:40、160、90、120、70、150、100
两个渠道的日均活跃用户都约等于100,你会觉得这两个渠道“表现一样”吗?当然不会。A渠道非常稳定,几乎贴着100上下浮动,容量规划、趋势预测都能做得很准;B渠道波动剧烈,今天40明天160,运营节奏完全无法预判,广告投放的预算分配也极其难做。
这就是离散趋势要回答的问题:数据离开数据中心的程度有多大。没有离散指标,均值就是一张被粉饰过的脸。
衡量离散程度最基本的一个指标是极差,也叫全距(Range),公式极其简单:
[ R = \max(x) - \min(x) ]
也就是最大值减最小值。上面例子中A渠道的极差是4,B渠道的极差是120,一眼就能看出谁更“稳”。
但极差的毛病也很明显,它只用了两个极端值,中间的所有信息全部浪费。假设有100个用户,99个消费金额都在80到120元之间,只有1个土豪用户消费了10000元,极差会直接变成9920元,整个数据集的离散程度被这一个点彻底带偏。所以极差在数据挖掘里通常只用来做快速的数据质量检查,比如PDI(数据画像)阶段先跑一下min、max,看看有没有明显异常值,真正的离散度量还得靠下面更稳的指标。
顺着“极端值不靠谱”这个思路往下走,自然的改进方向是:不只看两个端点,而是看每一个数据点偏离中心的程度。
2. 方差与标准差——最实用的“波动尺子”,建模前必算
这个方向上的第一个产物是平均绝对偏差(Mean Absolute Deviation, MAD):
[ MAD = \frac{1}{n}\sum_{i=1}^{n}|x_i - \bar{x}| ]
思路很直接:先算出每个点和均值的距离,加总再求平均。这个指标其实非常直观——“平均每个点偏离均值多少”。但它在数学上有个麻烦:绝对值函数在0点处不可导,后续做最优化、求导、推导函数的性质时很不方便。于是统计学家改用了平方的方式来处理偏差,方差(Variance)就此登场:
[ \sigma^2 = \frac{1}{n}\sum_{i=1}^{n}(x_i - \mu)^2 ]
方差把偏差平方之后,有两个后果,一个是好处,一个是代价。好处是大了的偏差会被平方放大,能更灵敏地捕捉离群点对离散程度的影响;代价是平方之后单位也变了,原始数据要是“元”,方差就变成了“元的平方”,这个单位让人很难在直觉上把握。
标准差(Standard Deviation)就是为了解决“单位还原”问题而生的:
[ \sigma = \sqrt{\frac{1}{n}\sum_{i=1}^{n}(x_i - \mu)^2} ]
标准差和原始数据保持同一单位,所以在业务描述、报表展示、模型解读中远比方差常用。实务中还有个很重要的细节:如果用样本数据去估计总体标准差,分母要用 (n-1) 而不是 (n):
[ s = \sqrt{\frac{1}{n-1}\sum_{i=1}^{n}(x_i - \bar{x})^2} ]
为什么减1?这里必须讲清楚,因为太多人在这踩坑。样本均值 (\bar{x}) 是从样本里算出来的,它不是总体均值 (\mu),通常比 (\mu) 更靠近样本点本身。用 (\bar{x}) 代替 (\mu) 计算偏差时,平方和会被系统性低估一点。除以 (n-1) 正是为了修正这个低估,让估计在统计意义上保持无偏。自由度视角也能理解:样本均值一旦确定,n个偏差值里只有n-1个是“自由”的,最后一个被均值约束住了。这就是“自由度”三个字最朴素的含义。
Python里这个坑尤其容易踩,numpy默认的np.std()用的是总体标准差(分母n),而pandas的df.std()默认用的却是样本标准差(分母n-1)。同一组数据,两个函数算出来的结果不同,这不是bug,是分母约定不同。
离散程度在数据挖掘中绝不只是“报表上多一个数字”,它的实用场景很硬核:
- 特征筛选:方差极小的特征意味着所有样本取值几乎相同,提供的信息量接近于零,可以直接剔除。很多特征选择工具里就有“低方差过滤”这一步。
- 异常检测:经典的方法之一是“三西格玛规则”,如果数据近似正态分布,约99.7%的观测落在均值加减3倍标准差区间内,超出这个区间的点就有资格被怀疑是异常值。
- 数据标准化:z-score标准化公式是 ((x_i - \mu)/\sigma),标准差在分母上。如果某个特征的标准差接近0,标准化后的数值会被放大到离谱的程度,这也是预处理阶段必须筛查的特征。
- 质量监控:制造业或运维场景里,控制图就是围绕均值和3倍标准差划出警戒线,一旦某个点冲出警戒线,说明流程“失控”了。
做一个简单的对比感受一下量纲的影响:
| 场景 | 均值 | 标准差 | 解读 |
|---|---|---|---|
| 用户会话时长(分钟) | 8.5 | 2.3 | 波动较小,时长较稳定 |
| 单笔订单金额(元) | 8.5 | 15.6 | 均值和上面相同,但波动极大,存在大量小额和高额订单 |
一组均值几乎一样的数据,标准差可能差出数倍,业务含义完全不一样。所以很多数据挖掘项目的“特征工程”阶段,第一步不是建模,而是把每个特征的均值、标准差、极差、缺失率列成一张表逐个过一遍。
3. 形状比位置更有信息量——偏度与峰度让分布“现出原形”
离散趋势描述的是“散得多开”,但数据还可能有“往哪边偏”和“尖还是平”的问题,这就是偏度(Skewness)和峰度(Kurtosis)。这两个指标在描述统计的教科书里往往一笔带过,但在数据挖掘实战中却是重要的“预警雷达”。
偏度刻画的是分布对称性。计算公式本质上是三阶中心矩除以标准差的立方:
[ Skewness = \frac{\frac{1}{n}\sum_{i=1}^{n}(x_i - \mu)^3}{\sigma^3} ]
分母把量纲归一化,所以偏度是一个无量纲的纯数字,可以跨数据集比较。解读规则很直接:
- 偏度约等于0:左右基本对称,近似正态分布
- 偏度大于0(正偏/右偏):右边拖着一条长尾巴,少数极大值把均值拉高了
- 偏度小于0(负偏/左偏):左边拖着长尾巴,少数极小值把均值压低了
收入分布就是典型的右偏,绝大多数人收入在区间中低段徘徊,极少数高收入人群把均值拉得远远高于中位数。所以在收入分析场景里,均值往往比中位数“虚高”,报告里如果只报均值不报中位数,就是在刻意掩盖结构问题。
峰度刻画的是分布尾部的“厚度”和峰顶的“尖锐程度”,本质是四阶中心矩除以方差平方再减3:
[ Kurtosis = \frac{\frac{1}{n}\sum_{i=1}^{n}(x_i - \mu)^4}{\sigma^4} - 3 ]
减3是为了把正态分布的峰度归零,这个形式叫超额峰度。峰度大于0说明数据比正态分布有更厚的尾巴,极端值出现的概率更高;小于0说明尾部比正态更薄,数据分布更“短胖”。
偏度和峰度在数据挖掘里的价值,主要体现在三个方面。
第一,预防模型假设失效。很多统计模型和机器学习方法(比如线性回归的误差项假设)默认数据近似正态分布。如果某个特征的偏度绝对值超过1,或者峰度明显偏高,这个假设就很危险。一个典型的操作是:建模前先对连续特征做正态性筛查,偏度严重的特征做对数变换或Box-Cox变换拉直,峰度过高的特征考虑稳健缩放。我实际做用户消费金额特征时,原始数据偏度高达8.7,取对数之后降到0.6左右,模型表现才正常。
第二,异常值预警。峰度高意味着“厚尾”,厚尾分布里出现极端值的概率比正态分布大得多。如果你在用三西格玛规则筛异常值但总觉得“异常值怎么这么多”,先看看峰度,很可能数据集本身就是厚尾的,这时再用正态阈值就不合适了,必须换IQR或者分位数法。
第三,算法选型参考。对偏度很大的目标变量,直接用均方误差(MSE)评价模型会被极值主导,这时候改成平均绝对误差(MAE)或者对目标变量做变换,模型的训练稳定性会好很多。
另外补充一个经验:偏度、峰度这类高阶矩对异常值极其敏感,样本量小于50的时候,这些指标的波动非常大,参考价值有限。等到数据量上千以后,才适合谈“分布的形态”。
4. 四分位数与箱线图——不依赖正态假设的稳健描述工具
在实际数据里,“正态分布”其实是奢侈品。真实的数据集不是长尾就是多峰,还有大量离群点,在这种情况下,基于均值±标准差的离散描述并不稳健,这时候就该分位数出场了。
分位数(Quantile)可以理解为排序后“位置”的标尺。n分位数把数据从小到大排列后切成n段,最常用的四分位数把数据切成四段:
- (Q_1)(下四分位数):25%的数据比它小
- (Q_2)(中位数):50%的数据比它小
- (Q_3)(上四分位数):75%的数据比它小
真正实用的是四分位距(Interquartile Range, IQR):
[ IQR = Q_3 - Q_1 ]
IQR刻画的是“中间一半数据”的散布宽度。它和标准差最大的区别在于:标准差用了所有数据的平方差计算,个别极端值会显著拉高数值;IQR只看排序后25%和75%位置上的值,天然屏蔽了两端的极端干扰。所以当数据里有明显异常值或者分布严重偏态时,IQR比标准差稳健得多。
箱线图就是站在四分位数肩膀上做出来的可视化工具。一个箱子从 (Q_1) 延伸到 (Q_3),箱内横线是中位数,箱须向外延伸到 (Q_1 - 1.5 \times IQR) 和 (Q_3 + 1.5 \times IQR) 范围内的最远数据点,超出这个范围的点被单独画出来,视为离群点。
这里有一个初学者常问的问题:为什么是1.5倍IQR?这个系数的来源是:如果数据来自正态分布,落在 (Q_1 - 1.5 \times IQR) 到 (Q_3 + 1.5 \times IQR) 区间之外的概率大约只有0.7%,这形成了一个很自然的“异常值判定线”。当然它不是数学证明出来的唯一真理,而是一个经验约定。你可以根据业务需要把它改成2倍或3倍,异常值判定会变得更保守。
用Python画箱线图非常简单,几行代码就能看到数据全貌:
import matplotlib.pyplot as plt import pandas as pd # 假设df是数据框,col是要检查的连续特征列 df.boxplot(column=["col"], grid=False, showmeans=True) plt.title("Boxplot of col") plt.show()showmeans=True是我特别建议加上的参数。因为箱线图上默认只显示中位数,加上均值之后,你可以同时观察均值和中位数的位置关系:均值高于中位数很多,说明右偏;均值低于中位数,说明左偏;两者几乎重合,分布基本对称。这是最快的一个目测偏度的方法。
在实践中,IQR还有一个高频用途:数据清洗中的异常值筛除。比如你处理一个订单金额字段,里面有些金额乱填成了负数或者离谱的大数,用均值±3倍标准差去筛,会受到异常值本身的干扰(均值和标准差都被异常值污染了),而IQR法建立在排序位置上,更抗污染。典型代码:
Q1 = df["amount"].quantile(0.25) Q3 = df["amount"].quantile(0.75) IQR = Q3 - Q1 lower_bound = Q1 - 1.5 * IQR upper_bound = Q3 + 1.5 * IQR outliers = df[(df["amount"] < lower_bound) | (df["amount"] > upper_bound)] print(f"疑似异常值数量: {len(outliers)}")需要提醒一点:IQR筛出来的“离群点”只是统计意义上的离群,不等于真正的错误数据。很多业务场景里,高价值客户、超级大单恰恰是离群点。我见过团队把一批超大额订单当异常值直接删掉,导致线上销售额统计差了一大截。所以统计筛完必须做业务复核,筛选规则要保留记录,什么时候删除、什么时候单独建模,都要有明确的判断依据。
5. 实战:用Python一条龙算完描述统计量
这一节直接给出一套可以复制的全流程代码。我假设你已经有一个CSV文件,里面至少包含一个连续型特征列,我们用它跑完所有离散程度和分布形态指标。
环境准备阶段,建议创建一个干净的虚拟环境,安装依赖:
pip install pandas numpy scipy matplotlib然后执行完整脚本:
import pandas as pd import numpy as np from scipy import stats # 读取数据 df = pd.read_csv("data.csv") # 选择要分析的连续特征列 col = "value" # 集中趋势(上篇已覆盖,这里顺带输出方便对照) mean = df[col].mean() median = df[col].median() mode = df[col].mode().iloc[0] if not df[col].mode().empty else np.nan # 离散趋势 data_range = df[col].max() - df[col].min() variance = df[col].var() # 默认样本方差,ddof=1 std_dev = df[col].std() # 默认样本标准差,ddof=1 iqr = df[col].quantile(0.75) - df[col].quantile(0.25) # 分布形态 skewness = df[col].skew() # scipy的kurtosis默认返回超额峰度(正态=0),fisher=True表示减3 kurtosis = stats.kurtosis(df[col].dropna(), fisher=True, bias=False) # 分位数 q1 = df[col].quantile(0.25) q3 = df[col].quantile(0.75) # 汇总输出 summary = pd.DataFrame({ "指标": ["均值", "中位数", "众数", "极差", "方差", "标准差", "四分位距IQR", "偏度", "峰度", "Q1", "Q3"], "数值": [mean, median, mode, data_range, variance, std_dev, iqr, skewness, kurtosis, q1, q3] }) print(summary)跑完代码之后,一定要做的四步检查:
第一步,看极差。如果极差大到离谱(比如最小值是负数而业务上不可能为负),说明数据里极可能有脏数据,这是数据质量警报,不是单纯的统计问题。
第二步,比较均值和中位数。两者差异越大,数据偏态越严重。如果均值是中位数的两倍以上,这个特征基本不可能建模后直接喂给线性模型。
第三步,看偏度和峰度。偏度绝对值大于1,考虑做变换;峰度明显大于0,注意厚尾和异常值问题。
第四步,算一下异常值比例。用上文的1.5倍IQR方法筛一遍,如果异常值占比超过5%,说明数据本身生成机制可能有问题,或者业务上确实存在少量极端群体,需要在建模时单独处理。
我实际跑业务数据的习惯是先看这几个数字,再做可视化。先用脚本打印描述统计表,再画直方图和箱线图结合观察,一张图能看出描述统计表里看不出的多峰、断层这些复杂结构。
6. 常见坑与排查要点实录
这一节整理我这些年做数据挖掘踩过的、以及反复见到别人踩的坑,每一条都对应真实场景。整理成表格方便你对照排查:
| 问题现象 | 根本原因 | 解决思路 |
|---|---|---|
| 同一组数据标准差算出来不一样 | pandas和numpy对ddof默认值不同;或者没区分总体/样本 | 明确你到底在描述样本还是在估计总体,代码里写明ddof参数 |
| 特征标准化后数值巨大 | 分母用了接近0的标准差,说明该特征取值极其集中 | 先做低方差特征筛选,或者改用MinMax缩放、稳健缩放 |
| 三西格玛规则筛出来的“异常值”多得离谱 | 数据不是正态分布,是厚尾分布 | 改用IQR法或分位数阈值,不要迷信3倍标准差 |
| 均值被少数极端样本带飞 | 分布右偏严重,均值被右侧长尾拉高 | 报表中同时给出均值和中位数,决策指标建议用中位数或修剪均值 |
| 偏度、峰度计算结果前后矛盾 | 样本量太少,高阶矩本身方差太大 | 样本量在几十级别时只看离散趋势,不谈偏度峰度;至少要上千样本才稳定 |
| 删除异常值后模型效果反而变差 | 离群点承载了重要的业务信息(如大客户、风险事件) | 数据清洗前先做业务确认,异常值可以先单独标记,再决定删除、保留还是单独建模 |
除了表格里的问题,还有两个容易被忽视的细节,单独拎出来讲。
第一个是加权均值配错离散度。很多业务场景里数据是有权重的,比如按用户规模加权计算平均消费,但有些人算标准差时忘记加权,导致波动描述失真。不要混合加权均值和无权标准差,要么全程加权,要么全程等权,口径必须一致。
第二个是多峰分布下“离散度指标失效”。如果数据明显分成两个聚集簇(比如活跃用户和非活跃用户混在一起),整体标准差会被拉得很大,但这不代表“波动大”,而是“分层没做”。这时候强行看方差做业务结论大概率是错的,正确做法是先做分层或聚类,再分别统计各层的离散程度。
还有一个小众但好用的经验:在写数据分析报告的时候,建议表格里同时放“均值、标准差、中位数、IQR、样本量”五个数字。有了这五个数,无论数据长什么样,读者都能快速建立对变量分布的合理想象。我自己的所有数据挖掘项目探索性分析阶段都遵循这个规范,它让沟通成本大幅下降。
再说一个计算层面容易忽略的问题。遇到数据量很大的时候(比如千万级行),就算只是算这几个统计量,也尽量使用优化后的库函数。pandas的describe()函数和numpy的向量化操作性能都很好,自己用Python循环逐行累加属于典型的反模式。在分布式场景下,numpy和pandas单机算不动时,可以用Dask或者直接上Spark的describe接口,统计量语义保持一致即可。
最后分享一个实际工作习惯。我在探索一个新数据集的时候,从来不会只看机器算出来的统计表,一定会配合可视化走一遍。描述统计给你精确的数字,可视化帮你发现数字背后的结构。多峰形态、间断点、数据断层、长尾分布,这些微妙的结构信息在统计表上是隐形的,但一张直方图或者箱线图往往一眼就能看穿。两者结合,才能在正式建模之前把数据的“脾性”摸透。
这篇的主要内容到此基本讲完了。回到开头那个问题:数据挖掘中的描述统计,不是给老板看的报表装饰,它是建模之前最重要的体检环节。集中趋势告诉你数据“站”在哪,离散趋势告诉你数据“晃”得有多厉害,偏度峰度告诉你数据“长”什么样,四分位数告诉你数据“分布”有多稳健。这四个维度合起来,你才真正认识你手里的数据。当然,统计指标能描述现象,但解释现象背后的业务原因,那才是数据挖掘真正见功力的时候。