查看全课大纲(第 10 / 35 节)
- 1.特征工程简介
- 2.特征工程的处理流程
- 3.数据与特征概述
- 4.数据统计、相似度与数据探索
- 5.数据探索工具 Pandas_profiling
- 6.工具应用:《泰坦尼克》分析报告
- 7.构造一个微型链家租房数据集用于演示
- 8.数据预处理综述
- 9.缺失值处理实操
- 10.异常值处理实操
- 11.连续值特征构建
- 12.类别型数据特征构建
- 13.特征幅度缩放
- 14.特征离散化
- 15.特征编码:让类别数据参与建模
- 16.特征变换
- 17.时间特征
- 18.特征衍生
- 19.自动化特征工具
- 20.特征选择综述
- 21.特征选择方法
- 22.过滤式特征选择方法实操
- 23.包裹式特征选择方法
- 24.嵌入式特征选择
- 25.混序法与混合法特征选择
- 26.特征降维综述
- 27.案例实操:特征降维
- 28.特征监控概述与指标
- 29.Iris 数据集特征漂移检测案例
- 30.特征监控实战:用分布对比检测数据漂移
- 31.实时特征监控
- 32.电商销量预测项目背景介绍
- 33.数据预处理和特征工程(一)
- 34.数据预处理和特征工程(二)
- 35.数据建模
异常值处理实操
约 12 分钟
小象实战讲义 · 特征工程精讲与实践
在上一节我们学习了缺失值的处理方法,本节我们将聚焦数据预处理的另一个核心任务——异常值处理。与缺失值不同,异常值往往“隐藏”在数据中,需要我们通过特定的方法去检测和识别。本节将系统讲解多种异常值检测方法(从简单规则到高级算法)以及检测后的处理策略,并通过泰坦尼克数据集的票价特征进行实操演示,帮助你掌握工业场景下异常值处理的完整流程。
💡 核心导读
- 异常值检测的挑战:理解为什么异常值没有绝对标准,需要结合业务场景选择方法。
- 四大经典检测方法:掌握指定边界法、IQR(箱线图)法、均值标准差法(3σ原则)和中位数绝对偏差法(MAD)的原理与应用。
- 异常值处理策略:学习对检测出的异常值进行填充(缩尾、均值填充)、删除等操作。
- 高级检测方法概览:了解基于机器学习的异常值检测工具(如孤立森林、KNN)及其适用场景。
一、异常值检测:没有绝对的标准
异常值(Outlier),也称为离群点,是指那些与数据集中其他观测值显著不同的数据点。与缺失值“一眼就能看出来”不同,异常值通常需要借助统计方法或模型进行检测。
一个关键认知是:异常值的判定没有绝对的标准。 如果我们有绝对的标准(比如业务规则明确规定超过某个值就是错误),那么直接按规则处理即可,根本不需要复杂的检测方法。因此,异常值处理很大程度上依赖于我们对业务场景的理解。例如,在泰坦尼克数据中,票价0美元和512美元都可能被视为异常,但前者可能是船员或特殊赠票,后者则是豪华舱位的真实票价。是否处理、如何处理,都需要结合背景知识判断。
下面,我们将以泰坦尼克数据集中的Fare(票价)特征为例,演示几种常见的异常值检测方法。首先,我们构造一个用于演示的小规模数据样本。
import pandas as pd
import numpy as np
# 构造一个简化的泰坦尼克票价数据样本(基于真实数据分布简化)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
print("演示数据样本(包含已知的潜在异常值0和512):")
print(data[['PassengerId', 'Fare']].sort_values('Fare'))演示数据样本(包含已知的潜在异常值0和512):
PassengerId Fare
12 179 0.00
16 806 0.00
0 1 7.25
2 3 7.92
4 5 8.05
5 6 8.46
8 9 11.13
13 258 13.00
10 11 16.70
7 8 21.07
11 12 26.55
9 10 30.07
14 680 50.00
6 7 51.86
3 4 53.10
1 2 71.28
15 737 110.88
17 1234 512.33可以看到,数据中包含了0美元和512.33美元这两个极端票价,它们是我们接下来要检测的目标。
二、经典异常值检测方法
1. 指定边界检测法
最简单直接的方法是根据业务知识或经验,人为设定一个正常值的范围(上界和下界),超出此范围即视为异常。
import pandas as pd
import numpy as np
# 演示数据(与本章首块一致)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
def outlier_detect_arbitrary(data, col, upper_fence, lower_fence):
"""
基于指定边界检测异常值。
参数:
data: DataFrame
col: 待检测的列名
upper_fence: 上边界
lower_fence: 下边界
返回:
outlier_index: 布尔序列,True表示异常
para: 使用的边界元组 (upper_fence, lower_fence)
"""
para = (upper_fence, lower_fence)
# 判断是否超过上界或低于下界
tmp = pd.concat([data[col] > upper_fence, data[col] < lower_fence], axis=1)
outlier_index = tmp.any(axis=1)
print(f'检测到异常值数量: {outlier_index.sum()}')
print(f'异常值占比: {outlier_index.sum()/len(outlier_index):.2%}')
return outlier_index, para
# 假设我们认为票价低于5美元或高于300美元为异常
outlier_index_arb, bounds_arb = outlier_detect_arbitrary(data, 'Fare', upper_fence=300, lower_fence=5)
print(f"\n边界: {bounds_arb}")
print("检测出的异常值样本:")
print(data.loc[outlier_index_arb, ['PassengerId', 'Fare']])检测到异常值数量: 3
异常值占比: 16.67%
边界: (300, 5)
检测出的异常值样本:
PassengerId Fare
12 179 0.00
16 806 0.00
17 1234 512.33该方法检测出3个异常样本:两个0美元票价和一个512美元票价。方法简单直观,但高度依赖先验知识。
2. IQR(箱线图)检测法
箱线图(Box plot)是可视化识别异常值的经典工具。其原理基于四分位距(IQR):
- IQR = 上四分位数(Q3,75%) - 下四分位数(Q1,25%)
- 上界 = Q3 + 1.5 * IQR
- 下界 = Q1 - 1.5 * IQR 落在上界之上或下界之下的点被视为异常值(离群点)。
import pandas as pd
import numpy as np
# 演示数据(与本章首块一致)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
def outlier_detect_IQR(data, col, threshold=1.5):
"""
使用IQR(箱线图)规则检测异常值。
参数:
data: DataFrame
col: 待检测的列名
threshold: IQR倍数,默认1.5
返回:
outlier_index: 布尔序列,True表示异常
para: 使用的边界元组 (upper_fence, lower_fence)
"""
Q1 = data[col].quantile(0.25)
Q3 = data[col].quantile(0.75)
IQR = Q3 - Q1
lower_fence = Q1 - threshold * IQR
upper_fence = Q3 + threshold * IQR
para = (upper_fence, lower_fence)
tmp = pd.concat([data[col] > upper_fence, data[col] < lower_fence], axis=1)
outlier_index = tmp.any(axis=1)
print(f'检测到异常值数量: {outlier_index.sum()}')
print(f'异常值占比: {outlier_index.sum()/len(outlier_index):.2%}')
print(f'Q1={Q1:.2f}, Q3={Q3:.2f}, IQR={IQR:.2f}, 上界={upper_fence:.2f}, 下界={lower_fence:.2f}')
return outlier_index, para
outlier_index_iqr, bounds_iqr = outlier_detect_IQR(data, 'Fare', threshold=1.5)
print("\n检测出的异常值样本:")
print(data.loc[outlier_index_iqr, ['PassengerId', 'Fare']])检测到异常值数量: 1
异常值占比: 5.56%
Q1=8.15, Q3=51.39, IQR=43.24, 上界=116.26, 下界=-56.71
检测出的异常值样本:
PassengerId Fare
17 1234 512.33IQR法只将512.33美元检测为异常,因为0美元虽然低,但仍在计算出的下界(-56.71)之内。阈值(1.5)可以调整,更严格的阈值(如3)会减少异常值数量。
3. 均值与标准差法(3σ原则)
对于近似服从正态分布的数据,可以使用均值(μ)和标准差(σ)来定义异常值范围。通常认为,距离均值超过3倍标准差的数据点概率极小,可能为异常。
- 上界 = μ + 3σ
- 下界 = μ - 3σ
import pandas as pd
import numpy as np
# 演示数据(与本章首块一致)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
def outlier_detect_mean_std(data, col, threshold=3):
"""
使用均值与标准差法检测异常值。
参数:
data: DataFrame
col: 待检测的列名
threshold: 标准差的倍数,默认3
返回:
outlier_index: 布尔序列,True表示异常
para: 使用的边界元组 (upper_fence, lower_fence)
"""
mean = data[col].mean()
std = data[col].std()
upper_fence = mean + threshold * std
lower_fence = mean - threshold * std
para = (upper_fence, lower_fence)
tmp = pd.concat([data[col] > upper_fence, data[col] < lower_fence], axis=1)
outlier_index = tmp.any(axis=1)
print(f'检测到异常值数量: {outlier_index.sum()}')
print(f'异常值占比: {outlier_index.sum()/len(outlier_index):.2%}')
print(f'均值={mean:.2f}, 标准差={std:.2f}, 上界={upper_fence:.2f}, 下界={lower_fence:.2f}')
return outlier_index, para
outlier_index_3sigma, bounds_3sigma = outlier_detect_mean_std(data, 'Fare', threshold=3)
print("\n检测出的异常值样本:")
print(data.loc[outlier_index_3sigma, ['PassengerId', 'Fare']])检测到异常值数量: 1
异常值占比: 5.56%
均值=55.54, 标准差=117.63, 上界=408.43, 下界=-297.35
检测出的异常值样本:
PassengerId Fare
17 1234 512.33同样,只有512美元被检出。注意:此方法对异常值本身敏感,因为异常值会拉高标准差,从而使边界变宽,可能“掩盖”其他异常点。
4. 中位数绝对偏差法(MAD)
为了克服均值标准差法对异常值敏感的缺点,可以使用更稳健的统计量——中位数(Median)和中位数绝对偏差(MAD)。
- MAD =
median(|X_i - median(X)|) - 修正Z分数 =
0.6745 * (X_i - median) / MAD通常认为修正Z分数的绝对值超过3.5(可调阈值)时为异常。
import pandas as pd
import numpy as np
# 演示数据(与本章首块一致)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
def outlier_detect_MAD(data, col, threshold=3.5):
"""
使用中位数绝对偏差法(MAD)检测异常值。
参数:
data: DataFrame
col: 待检测的列名
threshold: 修正Z分数的阈值,默认3.5
返回:
outlier_index: 布尔序列,True表示异常
"""
median = data[col].median()
# 计算绝对偏差的中位数(MAD)
deviations = np.abs(data[col] - median)
mad = deviations.median()
# 计算修正Z分数
modified_z_scores = 0.6745 * (data[col] - median) / mad if mad != 0 else pd.Series([0]*len(data))
outlier_index = np.abs(modified_z_scores) > threshold
print(f'检测到异常值数量: {outlier_index.sum()}')
print(f'异常值占比: {outlier_index.sum()/len(outlier_index):.2%}')
print(f'中位数={median:.2f}, MAD={mad:.2f}')
return outlier_index
outlier_index_mad = outlier_detect_MAD(data, 'Fare', threshold=3.5)
print("\n检测出的异常值样本:")
print(data.loc[outlier_index_mad, ['PassengerId', 'Fare']])检测到异常值数量: 2
异常值占比: 11.11%
中位数=18.88, MAD=11.41
检测出的异常值样本:
PassengerId Fare
15 737 110.88
17 1234 512.33在我们的小样本中,MAD 法检出了 2 个异常值(110.88 美元与 512.33 美元),比 IQR 法和 3σ 法更敏感——原因在于 MAD 基于中位数统计,边界不会被 512 这个极端值本身拉偏,因此更紧;而 3σ 的标准差被极端值拉大后,反而“掩盖”了 110.88 这样的偏离点。这正是 MAD 常被用作 3σ 稳健替代的原因。
四种方法敏感度对比(原课全量数据参照):在原课程 891 条全量泰坦尼克数据上,四种方法对同一 Fare 列的检出规模分别为:指定边界法(5~300 美元)19 个(占比约 2.13%)、IQR 法 31 个(原课此处阈值取 5,比本节示例的 1.5 严格——若用 1.5 会检出 116 个)、3σ 法 20 个、MAD 法 160 个。不同方法的敏感度差异在小样本中同样存在,实际选用时需要结合业务判断:是“宁可多检出再逐个核实”,还是“只处理最极端的明显错误”。
三、异常值的处理策略
检测出异常值后,我们有以下几种常见的处理方式:
1. 删除异常值
直接将含有异常值的样本从数据集中移除。适用于异常值数量很少,且确定其为错误数据的情况。
import pandas as pd
import numpy as np
# 演示数据(与本章首块一致)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
def outlier_detect_IQR(data, col, threshold=1.5):
Q1 = data[col].quantile(0.25)
Q3 = data[col].quantile(0.75)
IQR = Q3 - Q1
lower_fence = Q1 - threshold * IQR
upper_fence = Q3 + threshold * IQR
para = (upper_fence, lower_fence)
tmp = pd.concat([data[col] > upper_fence, data[col] < lower_fence], axis=1)
outlier_index = tmp.any(axis=1)
return outlier_index, para
outlier_index_iqr, bounds_iqr = outlier_detect_IQR(data, 'Fare', threshold=1.5)
def drop_outlier(data, outlier_index):
"""
删除异常值样本。
参数:
data: DataFrame
outlier_index: 异常值索引(布尔序列)
返回:
data_copy: 删除异常值后的DataFrame副本
"""
data_copy = data[~outlier_index].copy()
return data_copy
# 使用之前IQR法检测的结果进行删除
data_dropped = drop_outlier(data, outlier_index_iqr)
print("删除异常值后的数据形状:", data_dropped.shape)
print("剩余数据中Fare的范围: [{:.2f}, {:.2f}]".format(data_dropped['Fare'].min(), data_dropped['Fare'].max()))删除异常值后的数据形状: (17, 2)
剩余数据中Fare的范围: [0.00, 110.88]2. 视为缺失值并填充
将异常值视为一种特殊的缺失值,然后用处理缺失值的方法进行填充(如均值、中位数、众数或特定值)。
import pandas as pd
import numpy as np
# 演示数据(与本章首块一致)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
def outlier_detect_IQR(data, col, threshold=1.5):
Q1 = data[col].quantile(0.25)
Q3 = data[col].quantile(0.75)
IQR = Q3 - Q1
lower_fence = Q1 - threshold * IQR
upper_fence = Q3 + threshold * IQR
para = (upper_fence, lower_fence)
tmp = pd.concat([data[col] > upper_fence, data[col] < lower_fence], axis=1)
outlier_index = tmp.any(axis=1)
return outlier_index, para
outlier_index_iqr, bounds_iqr = outlier_detect_IQR(data, 'Fare', threshold=1.5)
def impute_outlier_with_avg(data, col, outlier_index, strategy='mean'):
"""
使用均值/中位数/众数填充异常值。
参数:
data: DataFrame
col: 列名
outlier_index: 异常值索引(布尔序列)
strategy: 'mean', 'median', 'mode'
返回:
data_copy: 处理后的DataFrame副本
"""
data_copy = data.copy()
if strategy == 'mean':
fill_value = data_copy[col].mean()
elif strategy == 'median':
fill_value = data_copy[col].median()
elif strategy == 'mode':
fill_value = data_copy[col].mode()[0]
else:
fill_value = np.nan
data_copy.loc[outlier_index, col] = fill_value
print(f"使用{strategy}填充,填充值为: {fill_value:.2f}")
return data_copy
# 用中位数填充IQR检出的异常值
data_imputed_median = impute_outlier_with_avg(data, 'Fare', outlier_index_iqr, strategy='median')
print("\n填充后,原异常值样本的Fare:")
print(data_imputed_median.loc[outlier_index_iqr, ['PassengerId', 'Fare']])使用median填充,填充值为: 18.88
填充后,原异常值样本的Fare:
PassengerId Fare
17 1234 18.8853. 缩尾处理(Winsorization)
不删除异常值,而是将其“拉回”到边界上。超过上界的用上界值替换,低于下界的用下界值替换。这种方法能保留样本量,同时削弱极端值的影响。
import pandas as pd
import numpy as np
# 演示数据(与本章首块一致)
data = pd.DataFrame({
'PassengerId': [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 179, 258, 680, 737, 806, 1234],
'Fare': [7.25, 71.28, 7.92, 53.10, 8.05, 8.46, 51.86, 21.07, 11.13, 30.07, 16.70, 26.55, 0.0, 13.0, 50.0, 110.88, 0.0, 512.33]
})
def outlier_detect_IQR(data, col, threshold=1.5):
Q1 = data[col].quantile(0.25)
Q3 = data[col].quantile(0.75)
IQR = Q3 - Q1
lower_fence = Q1 - threshold * IQR
upper_fence = Q3 + threshold * IQR
para = (upper_fence, lower_fence)
tmp = pd.concat([data[col] > upper_fence, data[col] < lower_fence], axis=1)
outlier_index = tmp.any(axis=1)
return outlier_index, para
outlier_index_iqr, bounds_iqr = outlier_detect_IQR(data, 'Fare', threshold=1.5)
def impute_outlier_with_boundary(data, col, outlier_index, para, strategy='both'):
"""
使用边界值填充异常值(缩尾处理,Winsorization)。
参数:
data: DataFrame
col: 列名
outlier_index: 异常值索引(布尔序列)
para: 边界元组 (upper_fence, lower_fence)
strategy: 'both'(上下都缩尾), 'top'(只缩上尾), 'bottom'(只缩下尾)
返回:
data_copy: 处理后的DataFrame副本
"""
data_copy = data.copy()
upper_fence, lower_fence = para
if strategy in ['both', 'top']:
data_copy.loc[data_copy[col] > upper_fence, col] = upper_fence
if strategy in ['both', 'bottom']:
data_copy.loc[data_copy[col] < lower_fence, col] = lower_fence
return data_copy
# 使用IQR法计算的边界进行缩尾处理(上下都缩)
data_winsorized = impute_outlier_with_boundary(data, 'Fare', outlier_index_iqr, bounds_iqr, strategy='both')
print("缩尾处理后,所有数据的Fare范围: [{:.2f}, {:.2f}]".format(data_winsorized['Fare'].min(), data_winsorized['Fare'].max()))
print("原异常值样本处理后的Fare:")
print(data_winsorized.loc[outlier_index_iqr, ['PassengerId', 'Fare']])缩尾处理后,所有数据的Fare范围: [0.00, 116.26]
原异常值样本处理后的Fare:
PassengerId Fare
17 1234 116.25875可以看到,512美元的票价被“拉回”到了116.26美元(IQR计算出的上界)。
四、高级异常值检测方法概览
除了上述基于统计规则的经典方法,还可以使用机器学习算法进行异常值检测,尤其适用于多维特征场景。这些方法通常将异常检测视为一个无监督学习问题。
讲师在视频中介绍了一个强大的Python工具库PyOD(Python Outlier Detection),它集成了多种异常检测算法,例如:
- Isolation Forest(孤立森林):通过随机划分特征空间来“孤立”异常点,异常点通常更容易被快速隔离。
- K Nearest Neighbors(KNN):基于距离,异常点的k近邻距离通常较大。
- One-Class SVM(OCSVM):学习一个包围正常数据的边界,边界外的点视为异常。
- Local Outlier Factor(LOF):基于局部密度,异常点相对于其邻居的密度较低。
- 此外还集成了基于 PCA 投影距离、MCD(最小协方差行列式)、CBLOF、HBOS、ABOD 等多种算法。
使用模式:这些算法在PyOD中具有统一的接口:初始化检测器 -> fit 数据 -> 使用 decision_function 或 predict 获取异常得分或标签。
原课用一份构造的 200 样本数据(其中约 25% 为异常点)对比了多种检测器的效果:KNN 与孤立森林(Isolation Forest)的误检率为 0,表现最好;而 ABOD、CBLOF 等方法的误检率较高。因此在日常实践中,KNN 与孤立森林是用得最多的高级异常检测方法,其中孤立森林在工业场景中尤其常用。
注意:原课程使用了PyOD库进行演示。在实际工业场景中,若数据维度高、分布复杂,可考虑引入此类高级方法。但经典统计方法因其可解释性强、计算简单,在多数场景下仍是首选。
本章小结
本节我们深入探讨了特征工程中异常值处理的完整流程。我们首先明确了异常值检测没有金科玉律,需要结合业务理解。随后,我们系统学习并实践了四种经典检测方法:指定边界法、IQR(箱线图)法、均值标准差法以及中位数绝对偏差法(MAD)。每种方法都有其适用场景和优缺点。最后,我们掌握了检测后的三大处理策略:删除、视为缺失值填充以及缩尾处理,并了解了基于机器学习的高级检测方法的存在与价值。
📋 行动清单
- 尝试不同阈值:对同一份数据,尝试调整IQR法或3σ原则中的阈值(如将1.5改为3),观察检测出的异常值数量如何变化,思考不同阈值适用的业务场景。
- 方法对比实验:在同一个特征上,依次运行本节介绍的四种经典检测方法,记录并对比它们检出的异常值有哪些异同,分析原因。
- 处理策略影响分析:对检出的异常值,分别尝试删除、中位数填充和缩尾处理三种策略,然后用处理后的数据计算该特征的均值、标准差等统计量,直观感受不同处理策略对数据分布的影响。
📝 动手练一练
- 概念辨析:为什么说“异常值检测没有绝对的标准”?请结合泰坦尼克票价0美元和512美元的例子说明。
👉 点击查看参考答案
因为一个值是否“异常”取决于业务背景和建模目标,而非纯粹的数学统计。例如:
- 0美元票价:从统计上看是极小值,可能是异常。但从业务上看,可能是船员、工作人员或获赠船票的乘客,是真实存在的合理情况。如果建模目标是预测乘客生存率,这些人的特征模式可能很有价值,删除或修改他们会损失信息。
- 512美元票价:统计上是极大值,可能是异常。但业务上对应的是极其豪华的舱位,是真实的高消费记录。这个值本身是正确且重要的,它反映了乘客的社会经济地位,对预测生存率可能是强特征。
因此,不能仅凭统计规则武断地将其作为“错误数据”处理。我们需要先判断异常值是“数据错误”还是“业务事实”,再决定处理策略。
- 动手实验:在下面的代码中,我们构造了一个包含“潜在异常值”的小数据集。请使用均值标准差法,但将阈值从3改为2(即2σ原则),运行代码并回答:检测出的异常值有哪些?与3σ原则相比,异常值数量是更多还是更少?为什么?
import pandas as pd
import numpy as np
# 构造练习数据
data_exercise = pd.DataFrame({
'Value': [10, 12, 11, 13, 9, 10, 100, 11, 12, 10, 9, 13, -50]
})
def outlier_detect_mean_std_exercise(data, col, threshold=3):
mean = data[col].mean()
std = data[col].std()
upper = mean + threshold * std
lower = mean - threshold * std
outlier_index = (data[col] > upper) | (data[col] < lower)
print(f'阈值={threshold}σ: 均值={mean:.2f}, 标准差={std:.2f}, 上界={upper:.2f}, 下界={lower:.2f}')
print(f'检测到异常值数量: {outlier_index.sum()}')
print(f'异常值: {data[col][outlier_index].tolist()}')
return outlier_index
print("--- 3σ原则检测结果 ---")
outlier_3sigma = outlier_detect_mean_std_exercise(data_exercise, 'Value', 3)
print("\n--- 2σ原则检测结果 ---")
outlier_2sigma = outlier_detect_mean_std_exercise(data_exercise, 'Value', 2)👉 点击查看参考答案
运行上述代码,输出如下:
--- 3σ原则检测结果 ---
阈值=3σ: 均值=13.08, 标准差=31.10, 上界=106.38, 下界=-80.22
检测到异常值数量: 0
异常值: []
--- 2σ原则检测结果 ---
阈值=2σ: 均值=13.08, 标准差=31.10, 上界=75.28, 下界=-49.12
检测到异常值数量: 2
异常值: [100, -50]答案:
- 使用2σ原则检测出的异常值是 100 和 -50。
- 与3σ原则相比,2σ原则检测出的异常值数量更多(2个 vs 0个)。
- 原因:阈值(σ的倍数)决定了判断异常的严格程度。阈值越小(如2σ),定义的正常值范围(μ ± 2σ)就越窄,因此更多偏离中心的数据点会被判定为异常。2σ原则比3σ原则更敏感、更严格。
—— 小象教研组