ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用Pandas做数据体检:五数概括、盒图与IQR异常值检测

用Pandas做数据体检:五数概括、盒图与IQR异常值检测 做数据分析这些年我有个很深的体会拿到一份数据别急着上模型、跑回归先老老实实做一次“摸底体检”比什么都重要。而这体检里最基础、也最容易被忽视的三件套就是五数概括、盒图和异常值检测。很多新手一上来就盯着均值、标准差不放结果被几个极端值带偏了方向后面所有分析都白做。这篇文章我用 Pandas 从头到尾把这三件事串一遍讲清楚每个数字背后的业务含义、IQR 检测异常的完整原理再配合一段真实销售数据的实战帮你一次性打通“描述分布—可视化—发现问题”这条数据分析的入门主线。无论你是刚学 Python、准备转行数据岗还是已经在用 Excel 做报表想升级一下这篇都值得读完。1. 先给数据做个体检五数概括到底在说什么1.1 五个数字分别回答什么问题五数概括Five-Number Summary就是用五个关键数字描述一组数据的分布形态分别是最小值Min、第一四分位数Q1、中位数Median、第三四分位数Q3和最大值Max。我更喜欢把这五个数理解成“数据面试官”依次要问的五个问题这组数据最低到多少25% 的数据线划在哪最中间的水平是多少75% 的数据线又划在哪最高冲到多少答案组合在一起基本就能在脑海里勾勒出这组数据的“骨架”。举个例子假设你在分析某款白酒在全国各地的月销售额均值是 80 万听着还不错。但如果 Q1 只有 30 万、Q3 已经到 120 万而中位数只有 55 万那说明大部分区域其实卖得并不好是少数几个头部市场把均值拉高的。这种情况下“均值 80 万”完全不能代表典型水平中位数 55 万才是更有业务参考价值的数据。还有一个容易忽略的点五数概括能让你迅速判断数据是不是“偏”的。中位数靠近 Q1 而远离 Q3说明数据右偏也就是存在一些特别大的极端值反过来就是左偏。这一步判断直接决定了你后续是用均值还是中位数做基准也决定要不要对异常值动手。1.2 四分位数的计算逻辑与 Pandas 细节四分位数的计算看起来简单其实里面有个小坑不同软件、不同方法算出来的 Q1、Q3 可能不一样。Pandas 的quantile()方法默认用的是线性插值这也是统计学里最通用的方式。用一个最简单的小数组来演示。假设有 5 个数import pandas as pd s pd.Series([1, 3, 5, 7, 9]) print(s.quantile(0.25)) # 3.0 print(s.quantile(0.5)) # 5.0 print(s.quantile(0.75)) # 7.0这个结果看起来“正好落在数据点上”是因为 5 个数据配合 0.25 的间隔位置刚好是整数。换成 6 个数据情况就变了s2 pd.Series([1, 3, 5, 7, 9, 11]) print(s2.quantile(0.25)) # 3.50.25 分位点在排序后第 0 位和第 1 位之间 25% 的位置也就是 1 和 3 中间取 25%得到 1 (3-1)×0.25 1.5等等这里就不细究插值公式了实际结果取决于 NumPy 的默认线性插值方式。我的建议是不要手动跟算分位数较劲只要统一用 Pandas/NumPy 的口径在团队内保持一致就足够了。真正要记住的是Q1 意味着“有 25% 的数据比它小”Q3 意味着“有 75% 的数据比它小”这个业务解读永远不会变。实际工作中我们用df.describe()就能一次性把五数概括连同均值、标准差都打出来df pd.DataFrame({销售额: [12, 15, 14, 18, 120, 16, 13, 17, 500]}) print(df[销售额].describe())你会看到 count、mean、std、min、25%、50%、75%、max 一排输出。不过要注意describe()默认对数值列输出的是 25% 和 75%对应的正是 Q1 和 Q3。很多人看完这行输出就走过去了其实里面藏着大量信息如果 max 和 75% 之间的距离远大于 Q1 到 min 的距离基本可以断定有右偏的大尾巴。2. 盒图不是装饰画怎么读、怎么画、怎么对比2.1 盒图的每个部件都有明确含义盒图Box Plot也叫箱线图是把五数概括画成一张图的可视化工具。很多人第一次看到盒图觉得就是“一个盒子加两根须须”没什么了不起。但你一旦学会正确读它它的信息密度比柱状图、折线图高得多。盒子的下边是 Q1上边是 Q3盒子高度就是四分位距IQR反映的是中间 50% 数据的波动范围。盒子中间那条线是中位数。盒子越窄说明大部分数据挤在一起稳定性好盒子越宽说明数据分散波动大。盒子外面那两根“须”也叫 whiskers并不是 min 和 max而是在正常范围内的最远数据点。它的边界通常取 Q1 - 1.5×IQR 和 Q3 1.5×IQR。超出这个范围的点会被单独画成圆点或菱形这些就是统计意义上的异常值候选者。我刚入门时一直以为须的端点就是最大值最小值后来才发现这俩概念不一样。如果一组数据没有异常值那须的端点恰好就是 min 和 max一旦有异常值须的端点就会“缩回来”异常点单独飘在外面。搞清楚这一点看盒图时才不会误读。2.2 并排盒图组间对比的利器盒图最大的优势不是画一组数据而是把多组数据并排放在一起对比。单看一组的均值和标准差你很难感受到分布全貌但三五个盒图一字排开中位数差异、离散程度差异、异常值分布一目了然。比如分析三个大区的销售数据华北、华东、华南各画一个盒图。如果华东的盒子明显更高且中位数偏上说明这个区域整体销售水平高且内部差距大如果华南的盒子很矮说明大家卖得都差不多竞争格局稳定如果华北出现两个孤零零的异常点飘在盒子上面那这两个点很可能就是某个爆款活动或者某个大经销商带来的值得单独查。在 Python 里画并排盒图有很多方式Pandas 自带的plot.box()最简单import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] # 解决中文乱码 plt.rcParams[axes.unicode_minus] False df[[华北, 华东, 华南]].plot.box() plt.title(各大区销售额分布对比) plt.show()如果你用 Seaborn画起来更灵活可以直接按某个分组列拆盒图import seaborn as sns sns.boxplot(x大区, y销售额, datadf) plt.show()Seaborn 的写法更适合数据已经整理成长表一行一条记录的情况。说实话真实项目里数据大多是长表所以我用sns.boxplot的频率更高。2.3 画盒图时最容易翻车的几个细节盒图本身不难画但细节上翻车的人真不少。第一个就是中文乱码。Matplotlib 默认字体不支持中文图上一片方框这个不提前设置好后面每张图都得返工。上面代码里那两行rcParams设置建议放进你的绘图工具函数里全局生效。第二个是数据没排序不影响分位数但会影响你看盒图时的直觉。盒图会自动按数值排列位置和原始数据顺序无关因此你不用担心导入的数据乱序。第三个是盒图会把异常值“藏”在须外这反而是它的优点。但如果你用plot.box()时没有单独看数据点可能忽略了异常值的数量。建议画图归画图数据层面还是要把异常值的具体值打出来一张图加一张明细表配合看。还有一个经验如果样本量太少比如一组只有 5 条数据盒图会显得很“秃”中位数、Q1、Q3 可能跟某些数据点重合这时候盒图参考价值有限别硬解读。3. 异常值检测IQR法的原理与 Pandas 落地3.1 异常值不等于错误值先分清业务含义在动手检测之前必须先明确一件事统计上的异常值和业务上的坏数据不是一回事。统计上的异常值是“偏离主体分布太远”的数据点它可能是真实发生的比如某天促销带来销售额暴涨 10 倍也可能是数据录入错误比如把 15 万录成 150 万。这两类必须区分对待前者要保留并单独分析后者要修正或剔除。我在实际项目里吃过亏一口气把检测出的异常值全删了结果删掉的恰恰是最有价值的爆款销售记录后面的销售预测模型完全偏离实际。从那以后我给自己定了一条铁律检测异常值只是第一步人工确认异常值背后的业务原因才是关键。3.2 IQR 检测法的完整计算过程IQR 法四分位距法是目前最通用的异常值检测方式核心思路是先找出数据的 Q1 和 Q3算出 IQR Q3 - Q1然后定义正常区间的上下限下限 Q1 - 1.5 × IQR 上限 Q3 1.5 × IQR所有小于下限或大于上限的数据点统称为异常值。这个“1.5”是一个经验常数来自统计学传统适用面很广。如果你的数据分布特别复杂可以把系数调成 3得到的是“极端异常值”的判断标准也可以改用百分位数法比如把 1% 和 99% 以外的都当异常但实际工作中 IQR 法因简单、鲁棒、无需假设分布形态而最常用。这里有个关键优势IQR 法用的是中位数和四分位数不受极端值本身影响。对比一下如果你用“均值 ± 3σ”来检测数据本身包含一个 1000 倍的大异常值时均值和标准差都会被带偏反而检测不出这个异常值——这种“掩蔽效应”在真实数据里非常常见。IQR 法就没有这个问题这也是它成为入门首选的根本原因。3.3 Pandas 实现从计算到标记的完整代码下面这段代码是我最常用的标准写法可以直接用到自己的项目里。假设销售额数据存在df[销售额]列import pandas as pd import numpy as np # 1. 计算四分位数和 IQR Q1 df[销售额].quantile(0.25) Q3 df[销售额].quantile(0.75) IQR Q3 - Q1 # 2. 计算正常区间 lower_bound Q1 - 1.5 * IQR upper_bound Q3 1.5 * IQR # 3. 用布尔索引筛选异常值 outliers df[(df[销售额] lower_bound) | (df[销售额] upper_bound)] # 4. 在原表上打标记方便后续追溯 df[是否异常] np.where( (df[销售额] lower_bound) | (df[销售额] upper_bound), 异常, 正常 ) print(f正常区间: [{lower_bound:.2f}, {upper_bound:.2f}]) print(f异常值数量: {len(outliers)}) print(outliers)这里我特别推荐第四步不要只筛出异常值而是给原始数据加一列标记。因为一旦你后续要复现分析、跟业务对口径这列标记能帮你随时追溯哪些数据被认定为异常、为什么被认定为异常。分析工作最怕的就是“当时处理了但处理过程没留痕”。还有一种更结构化的做法把检测逻辑封装成函数方便对多个字段批量复用。def detect_outliers_iqr(series, k1.5): Q1 series.quantile(0.25) Q3 series.quantile(0.75) IQR Q3 - Q1 lower Q1 - k * IQR upper Q3 k * IQR return (series lower) | (series upper) df[销售额_异常标记] detect_outliers_iqr(df[销售额])这个函数接收一个 Series返回一个布尔 SeriesTrue 就是异常。用k参数控制严格程度想宽松一点就调成 2 或 3想收紧就保持 1.5。每次检测都在函数里留下参数记录别人看到你的代码就知道你是用什么标准检测的。4. 完整实战一份销售数据的“全流程体检”4.1 构造数据与初步观察我在这里构造一份模拟的“白酒全国各区域月销售额”数据方便演示完整流程同时契合很多同学正在做的销售分析场景import pandas as pd import numpy as np np.random.seed(42) data { 区域: [华北] * 20 [华东] * 20 [华南] * 20, 销售额: ( list(np.random.normal(50, 8, 20)) list(np.random.normal(80, 12, 20)) list(np.random.normal(60, 5, 20)) ) } df pd.DataFrame(data) # 故意制造两个极端异常值一个录入错误一个真实爆款 df.loc[15, 销售额] 500 # 录入错误 df.loc[30, 销售额] 150 # 可能的真实极端值 df[销售额] df[销售额].round(2)这份数据有三个区域每个区域 20 条记录。正常情况下华北均值 50、华东均值 80、华南均值 60但我手动塞了两个极端点。如果只看整体均值你会觉得数据“还不错”但拆分区域后才能看出门道。先跑一份整体五数概括print(df[销售额].describe())输出大致会显示min 在 40 左右25% 在 53 左右50% 在 62 左右75% 在 82 左右max 是 500。max 和 75% 之间的距离非常夸张明显右偏这就是异常值在作祟。4.2 绘制盒图定位异常接着画全量数据的盒图以及按区域分组的并排盒图import matplotlib.pyplot as plt plt.rcParams[font.sans-serif] [SimHei] plt.rcParams[axes.unicode_minus] False fig, axes plt.subplots(1, 2, figsize(12, 4)) # 整体盒图 df[销售额].plot.box(axaxes[0]) axes[0].set_title(整体销售额盒图) # 分组盒图 df.boxplot(column销售额, by区域, axaxes[1]) axes[1].set_title(各区域销售额盒图) plt.tight_layout() plt.show()整体盒图会显示一个孤零零的点飘在须的上方那就是 500 那个极端值。分组盒图里华东区域也会出现一个 150 的点跟同组其他数据明显分离。到这里异常值“长什么样”已经视觉化清楚了。4.3 分组计算五数概括与 IQR 检测整体做一次 IQR 检测会出问题华东整体均值本来就高直接用整体区间判断可能把华南的偏高值误判为异常而华东的低值反而成了异常。所以正确做法是按组分别计算def detect_outliers_iqr(series, k1.5): Q1 series.quantile(0.25) Q3 series.quantile(0.75) IQR Q3 - Q1 lower Q1 - k * IQR upper Q3 k * IQR print(f区间: [{lower:.2f}, {upper:.2f}]) return (series lower) | (series upper) for region, sub_df in df.groupby(区域): mask detect_outliers_iqr(sub_df[销售额]) outliers sub_df[mask] print(f{region} 异常值数量: {len(outliers)}) if len(outliers) 0: print(outliers[[区域, 销售额]])分组之下华北那组会检测出异常值 500华东那组会检测出 150其他点基本都在区间内。比起整体一刀切这种分组检测的逻辑更贴合业务分析习惯——每个区域的销售基数和波动幅度本来就不同比“全国统一划线”合理得多。4.4 异常值处理先确认原因再动手检测出异常值之后最常见的动作就是“删掉”。但我在前面说过不能无脑删。针对本例的两个异常值正确的处理流程是确认数据来源查一下 500 这条记录是不是录入时多打了一个 0。如果是修正为 50如果不是核对是否来自某场大型团购。确认业务背景150 的华东记录如果对应一个真实的大订单那就应该保留但在做常规统计时单独标注或者在同比环比分析中剔除避免单月波动影响趋势判断。保留处理日志无论修正还是剔除都在代码里加注释甚至单独存一份“数据清洗说明”文件。这个习惯在团队协作里特别加分。我个人的惯例是异常值默认不直接删先标后审。给数据加一列“是否异常”再写清异常原因等业务方确认后再决定是剔除、修正还是保留。这样整个分析链条是可追溯的不会出现“上次分析结果换个说法就对不上”的尴尬。5. 实战避坑Pandas 做异常值检测的常见问题5.1 常见问题速查表我在带新人和自己实战中发现下面这些问题出现频率最高整理成一张速查表问题现象根因解决办法画图中文变方框Matplotlib 默认字体不支持中文设置plt.rcParams[font.sans-serif] [SimHei]或用其他中文字体quantile结果和手工算法对不上不同插值方法导致统一用 Pandas 默认线性插值跨工具对比时先对齐口径整体检测把本该正常的数据标成异常数据是分组结构却全局一刀切按业务维度分组后再做 IQR 检测数据里有 NaNquantile结果异常NaN 影响分位数计算先dropna()或按需用skipnaTrue加“是否异常”列时np.where报错条件表达式没有加括号每个比较条件单独加括号再用|连接异常值太多不知道该不该删正常区间设得过窄或数据确实分散先调k3看极端值再结合业务确认小样本数据盒图看不出异常样本量太少分位数不稳定换散点图/分布图辅助判断不强行解读5.2 几个我实测后的独家心得第一describe()和盒图要配合使用不是二选一。describe()给你精确数字盒图给你直观分布。先跑describe()感知数值范围再画盒图定位异常最后打印异常明细三步走下来基本不会漏。第二分组 IQR 检测要警惕“小分组陷阱”。如果一个组只有 5~10 条数据分位数波动很大正常区间会很宽或很窄不稳定。建议对样本量太小的组不做异常值检测直接标记为“样本不足”。我在那个白酒数据例子里每组 20 条还能用真实业务里一个区域只有 3 家门店的情况很常见这时候 IQR 法不如人工审核可靠。第三所有“自动检测”都要留手动覆盖的出口。我封装detect_outliers_iqr时永远保留k参数和手动指定上下限的能力。因为你不知道哪个数据集会出现什么特殊情况一个能调参、能覆盖的函数比写死的脚本好用得多。第四异常值检测不是一次性工作。数据是动态的这个月的“爆款”下个月可能变成“常态”所以要定期重新跑检测、重画盒图。我习惯把整套流程写成脚本每次数据更新后跑一遍输出异常值清单让业务复核比到月底才发现数据问题强得多。5.3 更进一步从单变量到多变量的扩展思路学会了单变量的五数概括和 IQR 检测实际上你已经掌握了数据分析中最核心的“分布思维”。再往下走有两个自然的扩展方向一是分组对比自动化。把上面那段groupby循环进一步封装对所有数值列批量输出五数概括、盒图、异常值清单一套代码搞定整个数据集的“体检报告”。我实际项目里就是这么做的省下大量重复手工时间。二是多变量异常检测。单变量检测只关注一个维度比如销售额异常但“销售额正常却利润率极低”的记录单看销售额是发现不了的。这时候可以用 Z-score 多维组合、聚类方法或孤立森林来做。不过那些都是进阶内容先把单变量的 IQR 法和盒图用熟练打好这个底子后面学任何复杂方法都会轻松很多。我个人的体会是数据分析入门阶段最重要的不是背 API而是建立“先描述、再可视化、最后下结论”的思维框架。五数概括、盒图、异常值检测这三件套恰好帮你把这条链路完整走一遍。把这套流程跑顺了再面对任何新数据集你都会知道第一步该干什么这比记住一百个函数重要得多。最后再分享一个小技巧每做一个分析项目都把上面的代码整理成自己的工具脚本下次直接 import你会发现效率翻倍。
返回列表