4.6 异常值识别
异常值是那些明显偏离数据整体分布的极端观测点,其成因可能包括“噪声”(如输入错误或测量偏差),也可能是真实存在的极端情况(例如高收入用户、罕见疾病病例等)。准确地识别这些异常值,是后续进行数据清洗或建模分析的重要前提。
4.6.1 常用识别方法
3σ 原则(Z-分数法)
适用前提:假设特征近似服从正态分布。
核心原理:通过计算每个样本的 Z 分数(即标准化后的值),判断其是否超出合理范围。若某样本满足 (|Z| > 3),则被判定为异常值。因为在标准正态分布中,约有 99.7% 的数据落在均值 ±3 倍标准差(μ±3σ)范围内。
箱线图法(基于四分位距 IQR)
基本流程:
- 确定上下四分位数:Q1(第25百分位)、Q3(第75百分位);
- 计算四分位距:IQR = Q3 - Q1;
- 定义异常值边界:小于 (Q1 - 1.5 × IQR) 或大于 (Q3 + 1.5 × IQR) 的数据点视为异常值。
优势:该方法不依赖于特定的数据分布形态,对偏态分布具有较强的鲁棒性,广泛应用于实际数据分析中。
聚类法
原理说明:利用聚类算法(如 K-Means)将数据划分为若干簇,然后计算各样本到所属聚类中心的距离。距离显著过大的样本可被视为潜在异常值。
适用场景:适用于数据结构复杂、无明显正态或偏态规律的情形。
孤立森林(Isolation Forest)
机制特点:一种基于决策树的无监督学习方法,其思想在于:异常值在特征空间中更容易被“孤立”,即只需较少次数的随机划分即可将其分离出来。
应用场景:特别适合处理高维数据中的异常检测任务,且运算效率较高。
问题描述
目标是从样本集中识别出一些极端的观测值——即异常值。
解决方案
识别异常值不仅依赖技术手段,更需要结合经验判断,某种程度上可以说是一门“艺术”。常用策略之一是假设数据服从多元正态分布,并在此基础上在特征空间中“绘制”一个椭圆区域。位于椭圆内部的样本被视为正常值(标记为1),而落在外部的则被认定为异常值(标记为-1)。
import numpy as np
from sklearn.covariance import EllipticEnvelope
from sklearn.datasets import make_blobs
# 创建模拟数据
feature, _ = make_blobs(n_samples=10, n_features=2, centers=1, random_state=1)
# 将第一个样本设为极端值
feature[0,0] = 10000
feature[0,1] = 10000
# 构建异常值检测器
outlier_detector = EllipticEnvelope(contamination=.1)
# 拟合模型
outlier_detector.fit(feature)
# 进行预测
x = outlier_detector.predict(feature)
print(x)
讨论
此方法的关键局限在于需预先设定 contamination 参数,表示数据中预期的异常值比例——而这一真实值通常是未知的。可以将该参数理解为对数据“清洁程度”的主观估计:如果认为异常值极少,可设置较小的值;反之,若怀疑存在较多离群点,则应适当提高该参数。
注意:
除了使用多变量方法外,也可针对单个特征采用四分位距(IQR)法来识别极端值。
import numpy as np
from sklearn.datasets import make_blobs
from sklearn.covariance import EllipticEnvelope
features, _ = make_blobs(n_samples=10, n_features=2, centers=1, random_state=1)
# 提取第一列作为目标特征
feature = features[:,0]
# 定义函数:返回异常值的索引位置
def indicies_of_outliers(x):
q1, q3 = np.percentile(x, [25, 75])
iqr = q3 - q1
lower_bound = q1 - (iqr * 1.5)
upper_bound = q3 + (iqr * 1.5)
return np.where((x > upper_bound) | (x < lower_bound))
# 调用函数并输出结果
x = indicies_of_outliers(feature)
print(x)
注释说明:
np.percentile(x, [25, 75]) 分别计算数组 x 的第一四分位数(Q1)和第三四分位数(Q3)。其中,Q1 表示有 25% 的数据小于该值,Q3 表示有 75% 的数据小于该值。
4.7 异常值处理
在数据预处理过程中,异常值的处理策略需综合考虑其产生原因(如噪声或真实极端情况)以及在整体数据中所占比例。核心目标是实现信息保留与模型鲁棒性之间的平衡。
常见异常值处理方法
直接删除法
适用于确认为噪声且占比极低(通常小于5%)的异常值,例如“年龄=1000”这类明显错误的数据。若删除后不会显著影响整体分布,则可采用此方法。
但需要注意:当异常值比例较高时,删除会导致大量样本丢失;若这些异常具有系统性特征(如某一类用户群体普遍数值偏高),则可能引入偏差。
修正异常值
当异常值源于录入错误并可通过业务逻辑追溯时(如“收入=1,000,000”实际应为“10,000”),应结合领域知识进行合理修正。
该方法保留原始样本的同时纠正错误,适用于有明确依据支持更正的情况。
替换处理
常用方式包括使用中位数替换(适合偏态分布,对极端值不敏感)或利用分位数边界替换,例如将超过 Q3 + 1.5×IQR 的值统一替换为该上限值。
优势在于避免样本丢失,同时降低异常值对建模的影响。
保留并标记异常状态
对于反映真实极端现象的异常值(如高净值客户消费记录),建议予以保留,并新增一个二元变量“是否异常”(0表示正常,1表示异常)。
这样可以让模型学习到异常样本的独特模式,提升预测能力。
分箱处理(Binning)
将连续变量划分为若干区间,把异常值归入特定极端箱体中。例如,“收入 > 10万元”的所有样本统一归类为“高收入”组。
通过这种方式弱化具体数值带来的冲击,增强模型稳定性。
注意:
问题描述
如何有效处理数据中存在的异常观测?
解决方案
通常采取以下三种典型策略:
1. 删除异常样本
通过条件筛选排除超出合理范围的观测值。
import numpy as np import pandas as pd # 创建数据帧 house = pd.DataFrame() house['price'] = [534433, 392333, 293222, 4322032] house['bathrooms'] = [2, 3.5, 2, 116] house['square_feet'] = [1500, 2500, 1500, 48000] # 筛选卫生间数量小于20的记录 x = house[house['bathrooms'] < 20] print(x)
2. 标记异常作为新特征
构建指示变量标识是否为异常值,使其成为模型输入的一部分。
import numpy as np import pandas as pd # 创建数据帧 house = pd.DataFrame() house['price'] = [534433, 392333, 293222, 4322032] house['bathrooms'] = [2, 3.5, 2, 116] house['square_feet'] = [1500, 2500, 1500, 48000] # 添加异常标记列 house['Outlier'] = np.where(house['bathrooms'] < 20, 0, 1) print(house)
3. 转换数值以减小影响
通过对数变换等方式压缩量纲差异,降低极端值的影响力。
import numpy as np import pandas as pd # 创建数据帧 house = pd.DataFrame() house['price'] = [534433, 392333, 293222, 4322032] house['bathrooms'] = [2, 3.5, 2, 116] house['square_feet'] = [1500, 2500, 1500, 48000] # 对面积取自然对数 house['Log_Of_Square_Feet'] = [np.log(x) for x in house['square_feet']] print(house)
讨论分析
如同异常值识别过程一样,处理方法并无绝对标准,关键在于从两个维度进行判断:
- 成因分析:首先要明确异常值来源。如果是由于设备故障、人为录入失误等导致的错误数据,应当剔除或用 NaN 替代;
- 数据性质:若属于真实的极端行为(如 VIP 用户高额消费),则不应简单删除,而应视作有价值的信息加以利用。
实战案例:电商用户消费数据中的异常值分析
背景说明
某电商平台采集了100名用户的月度消费行为数据,包含字段:user_id(用户ID)、monthly_spend(月消费额,单位:元)、purchase_times(购买次数)。数据中存在部分异常,包括输入错误和真实存在的高消费用户。
模拟数据生成代码如下:
import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.covariance import EllipticEnvelope # 设置随机种子 np.random.seed(42) # 正常用户消费数据(服从正态分布) spend_normal = np.random.normal(loc=1000, scale=200, size=95) purchase_normal = np.random.normal(loc=8, scale=2, size=95) # 异常值设定:共5个异常样本 # 其中3个为输入错误(极高消费额) # 另外2个代表真实高消费VIP用户[此处为图片2]
# 异常值检测与处理分析 import numpy as np import pandas as pd from scipy import stats from sklearn.covariance import EllipticEnvelope # 假设已有正常数据(示例中未给出,此处补充模拟) np.random.seed(42) spend_normal = np.random.normal(1000, 300, 95) # 正常月消费 purchase_normal = np.random.poisson(3, 95) # 正常购买次数 # 异常值数据 spend_outliers = np.array([10000, 0, 9999, 5000, 8000]) purchase_outliers = np.array([1, 0, 2, 20, 18]) # 合并完整数据集 monthly_spend = np.concatenate([spend_normal, spend_outliers]) purchase_times = np.concatenate([purchase_normal, purchase_outliers]) user_id = np.arange(1, 101) # 构造数据框 df = pd.DataFrame({ 'user_id': user_id, 'monthly_spend': monthly_spend, 'purchase_times': purchase_times }) print("原始数据前10行:") print(df.head(10))1. 使用3σ原则检测 monthly_spend 中的异常值 # 计算均值与标准差 mean_spend = df['monthly_spend'].mean() std_spend = df['monthly_spend'].std() lower_bound_3s = mean_spend - 3 * std_spend upper_bound_3s = mean_spend + 3 * std_spend # 判断是否为3σ异常值 outlier_3s = (df['monthly_spend'] < lower_bound_3s) | (df['monthly_spend'] > upper_bound_3s) df['is_outlier_3s'] = outlier_3s print(f"\n3σ法检测结果(上下限:{lower_bound_3s:.2f} ~ {upper_bound_3s:.2f}):") print(df[outlier_3s][['user_id', 'monthly_spend']]) [此处为图片2] 2. 使用箱线图法检测 purchase_times 中的异常值 # 计算四分位数和IQR Q1 = df['purchase_times'].quantile(0.25) Q3 = df['purchase_times'].quantile(0.75) IQR = Q3 - Q1 lower_bound_iqr = Q1 - 1.5 * IQR upper_bound_iqr = Q3 + 1.5 * IQR # 标记离群点 outlier_iqr = (df['purchase_times'] < lower_bound_iqr) | (df['purchase_times'] > upper_bound_iqr) df['is_outlier_iqr'] = outlier_iqr print(f"\n箱线图法检测结果(上下限:{lower_bound_iqr:.2f} ~ {upper_bound_iqr:.2f}):") print(df[outlier_iqr][['user_id', 'purchase_times']]) [此处为图片3] 3. 使用椭圆包络法检测双变量联合异常值(monthly_spend + purchase_times) # 提取双特征用于联合分析 X = df[['monthly_spend', 'purchase_times']].values # 椭圆包络模型(假设数据近似服从多元正态分布) envelope = EllipticEnvelope(contamination=0.1, random_state=42) y_pred = envelope.fit_predict(X) # -1 表示异常点 outlier_envelope = (y_pred == -1) df['is_outlier_envelope'] = outlier_envelope print("\n椭圆包络法检测的异常用户:") print(df[outlier_envelope][['user_id', 'monthly_spend', 'purchase_times']]) [此处为图片4] 4. 区分噪声异常值与真实极端值(VIP 用户)并分别处理 # 初始化标记列 df['user_type'] = 'Normal' df['to_remove'] = False # 是否需要删除 # 定义判断逻辑 for idx in df.index: spend = df.loc[idx, 'monthly_spend'] purchase = df.loc[idx, 'purchase_times'] is_3s = df.loc[idx, 'is_outlier_3s'] is_iqr = df.loc[idx, 'is_outlier_iqr'] is_env = df.loc[idx, 'is_outlier_envelope'] # 若在多个方法中被识别为异常,则进一步分类 if is_3s or is_iqr or is_env: if spend > 5000 and purchase >= 2: # 高消费且有一定购买频次 → VIP 用户 df.loc[idx, 'user_type'] = 'VIP' df.loc[idx, 'monthly_spend'] = df['monthly_spend'].median() # 替换为中位数 elif spend == 0 or purchase == 0: # 零消费或零购买 → 可能录入错误(噪声) df.loc[idx, 'to_remove'] = True else: # 其他极端情况视为噪声 df.loc[idx, 'to_remove'] = True print("\n用户类型分类结果:") print(df['user_type'].value_counts()) [此处为图片5] 5. 数据清理与输出 # 删除标记为噪声的数据行 df_cleaned = df[~df['to_remove']].copy() # 重置索引 df_cleaned.reset_index(drop=True, inplace=True) print("\n清洗后数据前10行:") print(df_cleaned.head(10)) print(f"\n共删除 {len(df) - len(df_cleaned)} 条噪声记录,保留 {len(df_cleaned)} 条有效样本。"注意:


雷达卡


京公网安备 11010802022788号







