在数据科学和统计分析的世界里,我们经常遇到一些“不听话”的数据点。它们像是集体中的“叛逆者”,表现得与大多数观测格格不入。我们把这些数据点称为异常值。
如果你曾经处理过真实世界的数据,你一定知道如果不能得到充分的识别和处理,这些异常值就像是一锅好汤里的老鼠屎,可能会导致统计分析出现严重偏差,甚至让我们得出完全错误的结论。对于金融行业的风险控制、医疗保健的异常诊断,以及任何依赖数据驱动决策的过程来说,准确地识别和处理异常值都具有极高的相关性。
在这篇文章中,我们将深入探讨异常值的定义、示例、类型、如何查找异常值,以及它们的用途和实际处理策略。我们将通过实际的代码示例,带你一步步掌握这一关键技能。
什么是异常值?
简单来说,异常值是位于数据集整体模式之外的数据点,与其他观测值显著不同。
异常值也是一个数据点,它与其在数据集中的其他记录截然不同。这种差异通常表现为数值过高或过低。如果这些极端值没有得到精确的识别和解决,它们可能会导致基于准备好的分析得出的正确结果“失序”。
异常值的成因
在深入如何检测之前,我们需要先了解它们从何而来。异常值的出现通常源于以下几种原因:
- 数据错误: 这是最常见的原因。在数据收集或记录过程中可能会发生错误,例如拼写错误、测量设备故障或数据录入时的手误。
例子:* 在记录身高时,单位误将“厘米”记为“米”,或者手误多输入了一个零。
- 自然变异: 数据本身可能就是非正态分布的,或者包含了真实的极端情况。这并不一定是错误,而是数据真实分布的一部分。
例子:* 在统计家庭收入时,亿万富翁的收入就是一个真实的异常值,它不是错误,但会极大地拉高平均数。
- 实验异常: 在实验过程中,某些条件发生了意想不到的变化,导致了个别样本的反应与其他样本截然不同。
为什么我们要关注异常值?
寻找异常值在各个层面都起着重要作用,特别是在统计结论的准确性和客观性至关重要的地方:
- skewed 统计量: 异常值可以显著影响均值和标准差,使其不再能代表数据的中心趋势。
- 模型假设: 许多统计模型(如线性回归)假设数据呈正态分布。异常值的存在会违反这些假设,导致模型失效。
异常值的类型:极端 vs 温和
并不是所有的异常值都是“生而平等”的。根据它们偏离数据集集中趋势的程度,我们可以将异常值分类为极端异常值和温和异常值。这通常基于四分位距(IQR)来判断。
1. 温和异常值
这些数据点与数据其余部分适度不同,虽然它们仍然被视为异常,但偏离程度相对较小。通常定义为落在距离四分位数 1.5 到 3 倍 IQR 范围内的值(或者正好落在 1.5 倍边界上)。
判断标准(公式):
Upper Outlier = Q3 + (1.5 * IQR)
Lower Outlier = Q1 - (1.5 * IQR)
2. 极端异常值
指那些远离平均值或中位数的数据点,通常超过四分位距(IQR)的 3 倍。这些点通常是我们需要重点关注的“致命”错误或极特殊的个案。
判断标准(公式):
Extreme Upper Outlier = Q3 + (3 * IQR)
Extreme Lower Outlier = Q1 - (3 * IQR)
类型示例解析:
假设我们有一个数据集,计算得出:INLINECODEaff891fa,INLINECODE81aa1882,IQR = 10。
温和异常值边界: 上界 = 20 + (1.5 10) = 35。
* 如果有一个数值是 40,它大于 35,但小于 50(极端边界),这就是一个温和异常值(有时也统称为异常值)。
极端异常值边界: 上界 = 20 + (3 10) = 50。
* 如果有一个数值是 55,它大于 50,这就是一个极端异常值。
如何识别异常值?(实战方法)
要识别数据集中的异常值,武器库中最常用的有两种方法:Tukey 方法(也常被称为箱线图法或栅栏法) 和 Z分数 方法。虽然名字听起来很学术,但理解起来其实非常直观。
方法 1:Tukey 方法(箱线图法/IQR法)
Tukey 方法,由著名统计学家 John Tukey 提出,是一种基于四分位距的鲁棒检测方法。它的优点是不受极端值本身的干扰(不像均值那样容易被拉偏)。
#### 核心原理
想象一下你的数据排成一列。我们把它切成四份,关键点在于 Q1(25%处的数)和 Q3(75%处的数)。这两个点中间的距离(IQR)代表了数据中间 50% 的密集程度。
Tukey 设定了一个“安全区”或者说“栅栏”:
- 下栅栏:
Q1 - 1.5 × IQR - 上栅栏:
Q3 + 1.5 × IQR
任何跑出这两个栅栏之外的数据,就被标记为异常值。
#### 代码实战:使用 Python 检测异常值
让我们来看一个实际的例子。假设我们有一组测试成绩,但里面混杂了一些极其错误的分数(比如有人考了 500 分,满分才 100)。
import numpy as np
# 模拟数据集:包含一个极端值 500
data = np.array([10, 12, 14, 16, 18, 500])
def detect_outliers_iqr(dataset):
# 步骤 1:对数据进行排序(虽然 numpy 会自动处理,但这是好习惯)
sorted_data = np.sort(dataset)
# 步骤 2:计算四分位数 Q1 (25%) 和 Q3 (75%)
Q1 = np.percentile(dataset, 25)
Q3 = np.percentile(dataset, 75)
# 步骤 3:计算四分位距
IQR = Q3 - Q1
# 步骤 4:计算上下限(栅栏)
lower_bound = Q1 - (1.5 * IQR)
upper_bound = Q3 + (1.5 * IQR)
print(f"Q1: {Q1}, Q3: {Q3}")
print(f"IQR: {IQR}")
print(f"下限: {lower_bound}, 上限: {upper_bound}")
# 步骤 5:筛选出异常值
outliers = []
for x in dataset:
if x upper_bound:
outliers.append(x)
return outliers
# 执行检测
found_outliers = detect_outliers_iqr(data)
print(f"检测到的异常值: {found_outliers}")
代码解析:
在这个例子中,INLINECODEa44be670 是 12,INLINECODEb5865a14 是 18。数据的正常范围主要集中在 10 到 20 之间。IQR 是 6。
- 正常数据的预期上限应该是
18 + 9 = 27。 - 但是,我们的数据中有一个
500。 -
500 > 27,所以程序准确地捕捉到了这个“格格不入”的家伙。
方法 2:Z分数 方法
如果你的数据呈现正态分布(钟形曲线),那么 Z分数 是另一种非常强大的方法。它告诉我们一个数据点距离平均值有多少个“标准差”。
#### 核心原理
- 均值: 数据的中心。
- 标准差: 数据的离散程度。
- Z分数公式:
Z = (X - 平均值) / 标准差
在正态分布中:
- 大约 68% 的数据落在距离平均值 1 个标准差范围内。
- 大约 95% 的数据落在距离平均值 2 个标准差范围内。
- 大约 99.7% 的数据落在距离平均值 3 个标准差范围内。
经验法则: 通常,如果一个数据点的 Z分数 绝对值大于 3,我们就可以认为它是一个异常值。
#### 代码实战:Z分数检测
让我们用一组更接近正态分布的数据来看看这个方法。
import numpy as np
# 模拟正态分布数据:均值为 25,标准差为 4
# 我们故意构建一个数据集,其中 30 是相对正常的,但 45 是可疑的
# 注意:真实场景下数据量需要足够大才能体现正态性
data = np.array([20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 45])
def detect_outliers_zscore(dataset):
threshold = 2 # 设定阈值,通常为 2.5 或 3,这里设为 2 用于演示
mean = np.mean(dataset)
std_dev = np.std(dataset)
print(f"平均值: {mean}, 标准差: {std_dev}")
outliers = []
for x in dataset:
z_score = (x - mean) / std_dev
print(f"数值 {x} 的 Z分数: {z_score:.2f}")
if np.abs(z_score) > threshold:
outliers.append(x)
return outliers
# 执行检测
found_outliers = detect_outliers_zscore(data)
print(f"检测到的异常值 (阈值=2): {found_outliers}")
深入理解:
在这个例子中:
- 30 的 Z分数计算为
(30 - 25) / 4 = 1.25。这意味着它只比平均值高了 1.25 个标准差。这在统计学上是非常常见的,所以它不是异常值。 - 45 的 Z分数计算为
(45 - 25) / 4 = 5.0。这意味着它比平均值高出了 5 个标准差!这在正态分布中发生的概率极低(小于 0.00003%)。因此,45 是一个强烈的异常值。
异常值的实际应用与处理策略
当我们找到了异常值,接下来该怎么办?直接删除吗?不一定。这取决于异常值的性质和分析的目标。
场景 1:数据清洗(如果是错误)
如果异常值是由于数据错误(如录入错误、传感器故障)造成的,我们的目标通常是修正或删除它们,以免污染模型。
- 策略: 删除、视为缺失值处理、或者用相邻值的平均值填充(插值法)。
- 注意: 删除前最好标记出来,检查一下是否真的全是错误。
场景 2:欺诈检测(如果是信号)
在金融交易监控中,异常值不是“噪音”,而是信号。
- 例子: 一张平时在国内消费的信用卡,突然在亚马逊热带雨林有一笔巨额消费。这个“异常值”可能代表着信用卡被盗刷。
- 策略: 建立预警系统,实时捕捉这些异常值进行人工审核。
场景 3:性能优化(鲁棒性)
有时候,我们不想删除异常值,但也不想被它们影响。我们需要使用对异常值不敏感的统计量。
- 用中位数代替平均数: 就像我们在文章开头提到的,如果你在酒吧里,比尔·盖茨走了进来,大家的“平均财富”会瞬间变成亿万富翁,但这没有意义。这时,“中位数财富”依然能反映真实情况。
- 算法选择: 使用基于树的模型(如随机森林、XGBoost),它们通常对异常值比线性回归更鲁棒。
最佳实践:处理异常值的流程建议
作为一名开发者,当你拿到数据集时,建议遵循以下流程:
- 可视化先行: 在做任何数学计算前,先用箱线图或散点图把数据画出来。人类的眼睛识别模式的能力很强,一眼就能看出那些“孤零零的点”。
- 分段处理: 不要只看总体,有时一个值在总体上看是异常,但在特定子群中是正常的(例如:新生儿体重 3kg 正常,但成年人 3kg 就是异常)。
- 记录决策: 如果你决定删除某个异常值,一定要在代码注释或文档中记录原因(例如:“删除了超过 3 倍标准差的数据点,推测为传感器故障”)。这有助于后续的代码审查和模型解释。
总结
异常值是数据分析中不可避免的一部分。它们既可能是干扰我们分析的噪音,也可能是隐藏着重大发现的金矿。
在这篇文章中,我们学习了:
- 定义: 什么是异常值及其成因。
- 类型: 区分温和异常值与极端异常值。
- 检测: 掌握了基于 IQR 的 Tukey 方法和基于标准差的 Z分数 方法。
- 代码实践: 通过 Python 代码亲手实现了检测逻辑。
下一步建议:
在你的下一个项目中,尝试加入异常值检测的步骤。不要仅仅满足于计算出平均值,试着看看那些“不在预期内”的数据,问问它们“为什么在这里”。你会发现,数据的故事往往就藏在这些异常之中。
希望这篇文章能帮助你更好地理解统计数据中的异常值,如果你在代码实现中有任何问题,欢迎随时交流探讨。