数据清洗技巧:缺失值处理与异常值检测
在数据科学项目中,我们常说“Garbage In, Garbage Out”(垃圾进,垃圾出)。据行业估算,数据科学家约80%的时间都花在了数据清洗上,而真正用于建模和分析的时间寥寥无几。
缺失值和异常值是阻碍我们获得高质量数据集的两大“拦路虎”。如果处理不当,它们会导致模型偏差、预测失真,甚至得出完全错误的业务结论。今天,我们就来聊聊数据清洗中那些必须掌握的实战技巧,并附上完整的Python代码,助你打造高质量数据集!
为什么数据清洗如此重要?
数据清洗不仅仅是简单的“打扫卫生”,它直接关系到后续分析的准确性和可靠性。
- 提升模型性能:干净的数据能让机器学习算法收敛更快,准确率更高。
- 保证决策正确:在商业分析中,异常值可能导致平均值的严重偏斜,误导业务决策。
- 节省时间:前期清洗做得好,后期Debug的时间就能大幅减少。
缺失值处理的5种实用方法(附代码)
面对缺失值,我们不能简单地“一刀切”删除,而是要根据数据的特性和业务场景选择合适的策略。
1. 简单填充策略
这是最基础也是最常用的方法,适用于缺失比例较小的情况。
- 均值/中位数/众数填充:
- 均值:适用于数据分布较为均匀的情况。
- 中位数:如果数据存在偏态或异常值,中位数更稳健。
- 众数:适用于分类变量(如性别、城市)。
- 前向/后向填充:在时间序列数据中,用前一个或后一个时间点的值进行填充非常有效。
- 常数填充:用0或-999等特定值填充,有时“缺失”本身就是一种信息。
代码实战:
import pandas as pd
import numpy as np
# 创建示例数据
data = {
'Age': [25, 30, np.nan, 35, np.nan, 40],
'City': ['Beijing', 'Shanghai', 'Beijing', np.nan, 'Guangzhou', 'Shanghai'],
'Salary': [5000, 6000, 5500, np.nan, 7000, 12000], # 12000可能是异常值
'Date': pd.date_range('2023-01-01', periods=6)
}
df = pd.DataFrame(data)
# 1. 均值填充 (Age)
df['Age_Mean'] = df['Age'].fillna(df['Age'].mean())
# 2. 中位数填充 (Age - 更稳健)
df['Age_Median'] = df['Age'].fillna(df['Age'].median())
# 3. 众数填充 (City)
df['City_Mode'] = df['City'].fillna(df['City'].mode()[0])
# 4. 前向填充 (适用于时间序列)
df['Salary_Fill'] = df['Salary'].fillna(method='ffill')
# 5. 常数填充
df['City_Unknown'] = df['City'].fillna('Unknown')
print(df)
2. 模型驱动填充
当缺失值较多且与其他特征存在相关性时,我们可以利用模型来预测缺失值。
- K近邻算法:找到与缺失样本最相似的K个样本,用它们的均值填充。
- 回归预测:利用其他完整特征建立回归模型,预测缺失值。
- 多重插补:通过多次模拟生成多个完整数据集,最后汇总结果,这种方法能更好地保留数据的不确定性。
代码实战:
from sklearn.impute import KNNImputer
# 初始化KNN插补器,n_neighbors=3表示找3个最近邻
imputer = KNNImputer(n_neighbors=3)
# 注意:KNNImputer只能处理数值型数据,所以这里只选取数值列
# 我们选取Age和Salary两列进行演示
df_numeric = df
# 执行插补
df_imputed = imputer.fit_transform(df_numeric)
# 转换回DataFrame并查看Age列的填充结果
df['Age_KNN'] = df_imputed[:, 0]
print(df)
3. 删除处理策略
虽然不推荐随意删除数据,但在某些极端情况下,删除确实是必要的处理方式。以下是两种常见的删除场景及具体操作建议:
-
整行删除(样本删除)
- 适用条件:当某行数据中存在大量缺失值(如超过80%的特征值缺失);该样本在整体数据集中重要性较低(如异常样本或边缘案例);样本量足够大,删除少量样本不会影响模型训练。
- 操作示例:假设分析电商用户数据时,发现某些用户记录缺失了90%的特征值(如注册信息、浏览记录、购买行为等),且这些用户都是未完成注册的访客,则可考虑删除这些不完整的记录。
-
整列删除(特征删除)
- 适用条件:当某列数据的缺失比例极高(通常超过50%);该特征难以通过插值、均值填充等方式合理补充;特征重要性评估显示该列对模型预测贡献很小。
- 操作示例:在房价预测数据集中,若发现"地下室面积"特征有65%的缺失值,且该特征与其他面积特征高度相关,则可考虑直接移除该特征。但在删除前应进行相关性分析,确认删除不会损失关键信息。
-
注意事项:
- 删除前务必进行缺失模式分析
- 记录删除操作及删除比例
- 评估删除对数据分布的影响
- 考虑使用标记变量记录删除操作
代码实战:
# 删除包含任何缺失值的行
df_drop_row = df.dropna()
# 删除缺失值超过2个的列
df_drop_col = df.dropna(axis=1, thresh=2)
# 只删除特定列(如Age)为空的行
df_drop_subset = df.dropna(subset=['Age'])
异常值检测的4大关键技术(附代码)
异常值(离群值)可能是录入错误,也可能是珍贵的机遇。识别它们是处理的第一步。
1. 统计方法检测
- Z-score方法:基于正态分布假设。如果一个值距离均值超过3个标准差(3σ),通常被视为异常。
- IQR法则:基于四分位距(Q3-Q1)。凡是小于Q1-1.5×IQR或大于Q3+1.5×IQR的值,都被标记为异常。这种方法对非正态分布数据更稳健。
代码实战:
# --- Z-Score 方法 ---
from scipy import stats
# 计算Z-score
z_scores = np.abs(stats.zscore(df['Salary']))
# 筛选出Z-score大于3的异常值索引
outliers_z = np.where(z_scores > 3)[0]
print(f"Z-Score检测到的异常值索引: {outliers_z}")
# --- IQR 方法 ---
Q1 = df['Salary'].quantile(0.25)
Q3 = df['Salary'].quantile(0.75)
IQR = Q3 - Q1
# 定义上下界
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
# 筛选异常值
outliers_iqr = df
print(f"IQR检测到的异常值:\n{outliers_iqr}")
# 处理异常值:这里演示用上下界进行截断(Capping)
# 如果工资超过上限,就设为上限;如果低于下限,就设为下限;否则保持不变
df['Salary_Capped'] = np.where(df['Salary'] > upper_bound, upper_bound,
np.where(df['Salary'] < lower_bound, lower_bound, df['Salary']))
2. 可视化检测方法
有时候,一张图胜过千言万语。
箱线图是可视化异常值的绝佳工具。箱线图通过五个统计量来展示数据分布:
- 第一四分位数(Q1):箱体下边缘,表示25%的数据位于此值以下
- 第三四分位数(Q3):箱体上边缘,表示75%的数据位于此值以下
- 四分位距(IQR)= Q3 - Q1
箱线图的上下须(Whiskers)通常定义为:
- 上边缘:Q3 + 1.5×IQR
- 下边缘:Q1 - 1.5×IQR
边缘内的数据被认为是正常数据点,而超出边缘的点则被视为异常值。
箱线图的主要用途:
- 比较不同组别数据的分布(如不同班级的考试成绩)
- 检测数据中的异常值(如金融交易中的可疑交易)
- 观察数据的偏态(如箱体不对称显示数据偏斜)
- 不受极端值影响,比简单的均值更能反映数据真实分布
- 可以同时展示多个数据集的分布情况
- 便于快速识别数据的离散程度和异常情况
代码实战:
import matplotlib.pyplot as plt
import seaborn as sns
# 设置绘图风格
sns.set(style="whitegrid")
plt.figure(figsize=(10, 6))
# 绘制箱线图
sns.boxplot(x=df['Salary'])
plt.title('Salary Distribution (Boxplot)')
plt.show()
3. 机器学习检测
对于高维数据,传统的统计方法可能失效,这时机器学习算法就派上用场了。
孤立森林是一种基于树结构的异常检测算法,通过随机分割特征空间来隔离样本点。异常点通常因特征值稀少而能被更快隔离,路径长度较短。
算法流程:
- 数据准备:使用sklearn生成合成数据或加载真实数据集。示例采用make_blobs生成带离群点的数据;
- 模型训练:调用IsolationForest类进行训练,关键参数包括n_estimators(树的数量)和contamination(异常比例估计值);
- 预测与评估:获取异常分数和标签,负分数表示异常可能性更高;
- 结果可视化:使用matplotlib绘制结果,突出显示异常点。
参数说明:
- n_estimators:增加树的数量可提升稳定性,但计算成本更高。
- max_samples:控制每棵树的样本量,默认auto(256)。
- contamination:根据实际场景调整预期异常比例。
代码实战:
# 数据生成
from sklearn.datasets import make_blobs
X, _ = make_blobs(n_samples=300, centers=1, cluster_std=0.5, random_state=42)
X[-10:] += 5 # 人为制造10个异常值
# 模型训练与预测
from sklearn.ensemble import IsolationForest
model = IsolationForest(n_estimators=100, contamination=0.03, random_state=42)
model.fit(X)
labels = model.predict(X)
scores = model.decision_function(X)
# 可视化
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 6))
plt.scatter(X[:, 0], X[:, 1], c=labels, cmap='viridis', s=20)
plt.colorbar(label='Anomaly Score')
plt.title('Isolation Forest Anomaly Detection')
plt.show()
数据清洗的最佳实践流程
为了保证清洗工作的系统性和可复现性,建议遵循以下流程:
- 数据质量评估:先用df.info()和df.describe()了解数据的整体情况。
- 缺失值分析:识别缺失模式,判断是随机缺失还是完全随机缺失。
- 异常值识别:结合统计方法和可视化手段,交叉验证异常点。
- 处理方法选择:根据业务逻辑选择最合适的策略。例如,电商中的大额订单可能是真实的大客户,不能简单当作异常值删除。
- 效果验证:清洗后再次检查数据分布,确保没有引入新的错误。
常用工具与资源推荐
在Python生态中,我们有强大的工具库来辅助清洗:
- Pandas:数据清洗的核心库,提供丰富的数据操作功能。
- Scikit-learn:包含SimpleImputer、KNNImputer等强大的预处理工具。
- NumPy:数值计算的基础,用于处理数组运算。
- Seaborn/Matplotlib:可视化库,用于绘制箱线图和散点图。
结语
数据清洗是一个需要反复优化和调整的迭代过程,很难存在放之四海而皆准的"标准答案"。在实际业务场景中,最佳的数据清洗方案往往需要结合具体的业务需求、数据特征和使用场景来制定。
记住:干净的数据是准确分析的基石!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)