首页 / Pandas 入门教程 / 异常值与边界处理

Pandas 入门教程

异常值与边界处理

本教程共 54 篇 · 第 28 篇 · 更新于 2026-08-11 · 约 5 分钟阅读

PandasPandas 入门教程异常值clipreplace标准化

本节目标:用 describe 定位异常值,用 clip 截断、replace 替换、条件赋值修正数据,并理解标准化的思路。

异常值不等于错误值

异常值(outlier)是明显偏离整体的值。它可能是录入错误(年龄 200 岁)、可能是特殊事件(双十一的销量)、也可能是真实现象。处理之前先搞清楚来源——直接删可能把真相删掉。

describe:先体检

describe 给出每列的 count、mean、std、min、25%、50%、75%、max(§18 详细讲过)。异常值通常让 min/max 与四分位数差距巨大:

import pandas as pd
import numpy as np

np.random.seed(42)
df = pd.DataFrame(np.random.randn(100, 3), columns=["A", "B", "C"])
df.loc[0, "A"] = 50     # 人为塞两个异常值
df.loc[1, "B"] = -40
print(df.describe())

A 列的 max 是 50,而 75% 分位只有 0.7 左右,一眼就能看出不对劲。想更直观,画个箱线图(§52 会讲)能直接看到离群点。

定位异常:阈值与 IQR

数据大致服从正态分布时,|值| 超过 3 倍标准差就很少见,可以当异常处理:

mask = np.abs(df["A"]) > 3
print(df[mask])

不知道分布时,用四分位距(IQR)法:低于 Q1 - 1.5×IQR 或高于 Q3 + 1.5×IQR 的视为异常。这个标准不依赖分布假设,对偏态数据更稳:

q1 = df["A"].quantile(0.25)
q3 = df["A"].quantile(0.75)
iqr = q3 - q1
mask = (df["A"] < q1 - 1.5 * iqr) | (df["A"] > q3 + 1.5 * iqr)
print(df[mask])

想”温和地”处理极端值,还可以用分位数定边界:把低于 1% 分位、高于 99% 分位的值截断,比如收入的 1% / 99% 边界:

lo = df["A"].quantile(0.01)
hi = df["A"].quantile(0.99)
print(df["A"].clip(lower=lo, upper=hi).describe())

这种”分位数截断”对长尾分布特别实用:既去掉了极端噪声,又不用主观定阈值。

异常值怎么处理:四种选择

  1. 保留:确认是真实事件(比如促销日的销量),不处理,分析时单独看待
  2. 截断:只是”记录溢出”,用 clip 拉到边界
  3. 替换:已知是脏值,replace 成标准值
  4. 删除:确属录入错误且数量很少,直接删行

先判断再动手,顺序不要反。

clip:截断到边界

异常值只是”记录溢出”(比如传感器读数、年龄)时,最省事的是截断:超过上限按上限算,低于下限按下限算。

s = pd.Series([-5, 0, 10, 99, 120, 200])
print(s.clip(lower=0, upper=120))    # 低于 0 变 0,高于 120 变 120

DataFrame 同样支持,对单列或整个表截断:

df2 = df.copy()
df2["A"] = df2["A"].clip(lower=-3, upper=3)
print(df2["A"].describe())

clip 只改越界的值,其余数据原样保留,是一种保守的处理方式。

Tip

老写法里有人用 data[np.abs(data) > 3] = np.sign(data) * 3 手工截断,clip 就是它的标准替代,语义更直白,也不用担心链式赋值的坑(§09)。

replace:替换已知脏值

老系统里常用 -999 标记缺失,读进来后要换成标准缺失值。replace 按值精确替换,支持单个值、列表和字典:

s2 = pd.Series([1.0, -999.0, 2.0, -999.0, 3.0])
print(s2.replace(-999, np.nan))               # 单个值
print(s2.replace([-999, -1000], np.nan))      # 多个值换成一个结果
print(s2.replace({-999: np.nan, -1000: 0}))   # 字典一一对应
Note

fillna(§25)只处理缺失值,replace 什么值都能换:把 “unknown” 换成 “未知”、把 0 换成 NaN、把错别字换成标准词,都归它管。

条件修正:loc + 布尔条件

规律性的错误(年龄超过 120)用条件赋值修正。3.0 的 Copy-on-Write 下,用 loc 写最安全(§09):

df3 = pd.DataFrame({"姓名": ["张三", "李四", "王五"], "年龄": [25, 200, 12345]})
df3.loc[df3["年龄"] > 120, "年龄"] = 120
print(df3)

想保持形状做条件替换,用 where/mask(§21 讲过):df.where(条件, 替代值) 把条件为 False 的位置换成替代值。

确认是错误数据且数量少,也可以直接删行:

df4 = df3.drop(df3[df3["年龄"] > 120].index)
print(df4)

标准化:消除量纲

异常值处理完,不同列往往量纲差很远(年龄几十、收入几万)。很多分析场景下要把列拉到同一尺度:

  • Z-score 标准化:减均值再除以标准差,结果均值 0、标准差 1
  • Min-Max 归一化:减最小值除以极差,结果落在 [0, 1]

pandas 直接算:

z = (df["A"] - df["A"].mean()) / df["A"].std()
print(z.describe())    # mean≈0,std≈1

minmax = (df["A"] - df["A"].min()) / (df["A"].max() - df["A"].min())
print(minmax.min(), minmax.max())    # 0 和 1
Warning

标准化前先处理缺失(§24/§25)和异常值:均值会被极端值拉偏,缺失会直接算出 NaN。标准顺序是:清洗缺失 → 去重 → 修异常 → 标准化。需要更专业的标准化工具时,再引入 sklearn 的 StandardScaler / MinMaxScaler,这里只给思路。

小结

异常值先定位再处理:describe、3 倍标准差、IQR 是三种定位法;clip 截断、replace 替换、loc 条件修正、删行是四种处理法。先弄清来源再动手,别把真相当噪声删掉。标准化(Z-score、Min-Max)是异常值处理完之后的事。