Langchain系列文章目录
01-玩转LangChain:从模型调用到Prompt模板与输出解析的完整指南
02-玩转 LangChain Memory 模块:四种记忆类型详解及应用场景全覆盖
03-全面掌握 LangChain:从核心链条构建到动态任务分配的实战指南
04-玩转 LangChain:从文档加载到高效问答系统构建的全程实战
05-玩转 LangChain:深度评估问答系统的三种高效方法(示例生成、手动评估与LLM辅助评估)
06-从 0 到 1 掌握 LangChain Agents:自定义工具 + LLM 打造智能工作流!
07-【深度解析】从GPT-1到GPT-4:ChatGPT背后的核心原理全揭秘
PyTorch系列文章目录
Python系列文章目录
机器学习系列文章目录
01-什么是机器学习?从零基础到自动驾驶案例全解析
02-从过拟合到强化学习:机器学习核心知识全解析
03-从零精通机器学习:线性回归入门
04-逻辑回归 vs. 线性回归:一文搞懂两者的区别与应用
05-决策树算法全解析:从零基础到Titanic实战,一文搞定机器学习经典模型
06-集成学习与随机森林:从理论到实践的全面解析
07-支持向量机(SVM):从入门到精通的机器学习利器
08-【机器学习】KNN算法入门:从零到电影推荐实战
09-【机器学习】朴素贝叶斯入门:从零到垃圾邮件过滤实战
10-【机器学习】聚类算法全解析:K-Means、层次聚类、DBSCAN在市场细分的应用
11-【机器学习】降维与特征选择全攻略:PCA、LDA与特征选择方法详解
12-【机器学习】手把手教你构建神经网络:从零到手写数字识别实战
13-【机器学习】从零开始学习卷积神经网络(CNN):原理、架构与应用
14-【机器学习】RNN与LSTM全攻略:解锁序列数据的秘密
15-【机器学习】GAN从入门到实战:手把手教你实现生成对抗网络
16-【机器学习】强化学习入门:从零掌握 Agent 到 DQN 核心概念与 Gym 实战
17-【机器学习】AUC、F1分数不再迷茫:图解Scikit-Learn模型评估与选择核心技巧
18-【机器学习】Day 18: 告别盲猜!网格/随机/贝叶斯搜索带你精通超参数调优
19-【机器学习】从零精通特征工程:Kaggle金牌选手都在用的核心技术
20-【机器学习】模型性能差?90%是因为数据没洗干净!(缺失值/异常值/不平衡处理)
文章目录
前言
欢迎来到我们机器学习系列文章的第 20 天!在之前的学习中,我们探讨了各种强大的机器学习模型。然而,这些模型的能力很大程度上依赖于我们喂给它们的数据质量。“Garbage In, Garbage Out” (GIGO,垃圾进,垃圾出) 是数据科学领域一句广为流传的箴言。如果原始数据充满了错误、缺失、不一致或者噪音,那么即使是最先进的模型也难以产生可靠的结果。
数据预处理与清洗,正是解决“脏”数据问题的关键步骤。它像是为建造摩天大楼打地基,虽然繁琐,但至关重要。这个过程的目标是转换原始数据,使其变得整洁、一致、完整,从而提升模型的训练效率、预测精度和鲁棒性。
在本篇文章中,我们将系统地探讨数据预处理与清洗的核心技术,包括:
- 处理缺失值:如何优雅地应对数据中的空白?
- 检测与处理异常值:怎样揪出那些“捣乱”的数据点?
- 处理不平衡数据:当数据类别比例悬殊时,如何保证模型的公平性?
- 数据类型转换与一致性检查:确保数据的规范性和可用性。
我们将结合清晰的原理讲解、实用的 Pandas 代码示例和场景分析,带你一步步掌握这些必备技能,为你的机器学习项目打下坚实的基础。
一、为什么数据预处理与清洗至关重要?
在深入具体技术之前,我们首先要理解为什么投入时间和精力进行数据预处理是值得的。
1.1 “垃圾进,垃圾出”原则 (GIGO)
这个原则形象地说明了数据质量对模型输出结果的决定性影响。如果输入数据本身就有问题,那么基于这些数据训练出来的模型,其预测或决策也必然是不可靠的,甚至可能是完全错误的。想象一下,如果根据一份充满错误的销售报告来预测下季度业绩,结果会是多么糟糕。
1.2 数据质量对模型性能的影响
低质量数据会从多个方面损害模型性能:

- 降低模型精度:错误或不一致的数据会误导模型的学习过程。
- 引入模型偏见:缺失值处理不当或数据不平衡可能导致模型偏向于某些特征或类别。
- 影响模型泛化能力:含有大量噪音或异常值的数据训练出的模型,在面对新数据时可能表现很差。
- 增加训练时间和资源消耗:不必要的冗余数据或复杂格式会增加计算负担。
1.3 数据预处理的目标与挑战
数据预处理的主要目标是提高数据质量,使其满足模型训练的要求。具体目标包括:
- 完整性 (Completeness):处理缺失值。
- 准确性 (Accuracy):检测并处理错误或异常值。
- 一致性 (Consistency):确保数据格式、单位、编码等保持一致。
- 合规性 (Validity):确保数据在定义的范围或规则内。
- 时效性 (Timeliness): 确保使用的是最新的相关数据(有时需要考虑)。
常见的挑战包括数据来源多样、数据量大、错误类型隐蔽、缺乏领域知识等。
二、处理缺失值 (Handling Missing Values)
现实世界的数据往往是不完美的,缺失值(通常表示为 NaN
, None
, NA
等)是常见的问题。
2.1 识别缺失值
在 Pandas 中,识别缺失值非常方便。我们可以使用 .isnull()
或 .isna()
方法结合 .sum()
来统计每列的缺失值数量。
import pandas as pd
import numpy as np
# 创建一个包含缺失值的示例 DataFrame
data = {
'col_a': [1, 2, np.nan, 4, 5],
'col_b': ['A', np.nan, 'C', 'D', 'E'],
'col_c': [np.nan, 10, 20, 30, np.nan]}
df = pd.DataFrame(data)
print("原始数据:")
print(df)
# 检查每列的缺失值数量
print("\n每列缺失值数量:")
print(df.isnull().sum())
# 检查整个 DataFrame 的总缺失值数量
print(f"\n总缺失值数量: {
df.isnull().sum().sum()}")
# 检查每行是否有缺失值
print("\n每行缺失值情况:")
print(df.isnull().any(axis=1))
输出:
原始数据:
col_a col_b col_c
0 1.0 A NaN
1 2.0 NaN 10.0
2 NaN C 20.0
3 4.0 D 30.0
4 5.0 E NaN
每列缺失值数量:
col_a 1
col_b 1
col_c 2
dtype: int64
总缺失值数量: 4
每行缺失值情况:
0 True
1 True
2 True
3 False
4 True
dtype: bool
2.2 缺失值处理策略
处理缺失值没有万能的方法,需要根据数据的特性、缺失比例、业务场景以及后续模型的类型来选择。
2.2.1 删除法
最简单直接的方法是删除包含缺失值的行或列。
- 删除行 (Listwise Deletion):如果某一行有任何缺失值,就删除整行。适用于缺失值数量较少,且样本量足够大的情况。如果缺失值集中在少数样本中,这也是个不错的选择。Pandas 中使用
df.dropna()
。 - 删除列 (Column Deletion):如果某一列的缺失值比例非常高(例如超过 50% 或 70%),并且该特征不是关键特征,可以考虑删除整列。使用
df.dropna(axis=1)
。
优点:简单易行。
缺点:可能丢失大量有用信息,尤其是在缺失值较多的情况下。如果缺失模式不是随机的,可能会引入偏见。
# 删除包含任何缺失值的行
df_dropped_rows = df.dropna()
print("\n删除缺失行后:")
print(df_dropped_rows)
# 删除缺失值比例超过阈值的列 (假设阈值为 40%)
thresh = len(df) * 0.6 # 保留至少 60% 非缺失值的列
df_dropped_cols = df.dropna(axis=1, thresh=int(thresh))
print("\n删除缺失过多的列后:")
print(df_dropped_cols)
2.2.2 填充法 (Imputation)
填充法是用一个估计值来替换缺失值,目的是保留数据记录。
(1)简单填充 (均值、中位数、众数)
这是最常用的填充方法:
- 均值 (Mean):用该列的平均值填充。适用于数值型数据,且数据分布近似对称(如正态分布)。对异常值敏感。
- 中位数 (Median):用该列的中位数填充。适用于数值型数据,特别是当数据存在偏斜或有异常值时,比均值更稳健。
- 众数 (Mode):用该列出现次数最多的值填充。适用于类别型数据,也可用于数值型数据。
优点:实现简单,保留了样本量。
缺点:简单填充可能降低数据方差,扭曲特征内部或特征之间的关系。对于均值和中位数,只适用于数值型数据。
# 使用均值填充 col_a (数值型)
mean_val = df['col_a'].mean()
df_filled_mean = df.copy() # 创建副本以避免修改原始df
df_filled_mean['col_a'].fillna(mean_val, inplace=True)
print("\n均值填充 col_a:")
print(df_filled_mean)
# 使用中位数填充 col_c (数值型)
median_val = df['col_c'].median()
df_filled_median = df.copy()
df_filled_median['col_c'].fillna(median_val, inplace=True)
print("\n中位数填充 col_c:")
print(df_filled_median)
# 使用众数填充 col_b (类别型)
mode_val = df['col_b'].mode()[0] # mode()可能返回多个众数,取第一个
df_filled_mode = df.copy()
df_filled_mode['col_b'].fillna(mode_val, inplace=True)
print("\n众数填充 col_b:")
print(df_filled_mode)
(2)模型预测填充
更高级的方法是利用其他特征的信息来预测缺失值。
- 回归填充:将含有缺失值的特征作为目标变量,其他特征作为预测变量,训练一个回归模型(如线性回归、KNN)来预测缺失值。
- 多重插补 (Multiple Imputation):创建多个完整的数 据集,每个数据集中的缺失值用不同的预测值填充,然后综合这些数据集的结果。
优点:通常比简单填充更准确,能更好地保留数据结构和关系。
缺点:实现更复杂,计算成本更高。
2.3 Pandas 实战:缺失值处理汇总
import pandas as pd
import numpy as np
data = {
'Age': [25, 30, np.nan, 35, 40, 45, 50, np.nan, 55],
'Gender': ['M', 'F', 'M', 'F', np.nan, 'M', 'F', 'M', 'F'],
'Salary': [50000, 60000, 75000, 80000, 90000, np.nan, 110000, 120000, 130000]}
df = pd.DataFrame(data)
print("原始数据及缺失情况:")
print(df)
print(df.isnull().sum())
# 策略:
# 1. Age: 缺失比例不高,数值型,用中位数填充比较稳健
# 2. Gender: 类别型,用众数填充
# 3. Salary: 数值型,缺失比例不高,用均值填充 (也可以考虑中位数)
# 执行填充
df_clean = df.copy()
age_median = df_clean['Age'].median()
gender_mode = df_clean['Gender'].mode()[0]
salary_mean = df_clean['Salary'].mean()
df_clean['Age'].fillna(age_median, inplace=True)
df_clean['Gender'].fillna(gender_mode, inplace=True)
df_clean['Salary'].fillna(salary_mean, inplace=True)
print("\n填充后的数据及缺失情况:")
print(df_clean)
print(df_clean.isnull().sum())
三、检测与处理异常值 (Detecting & Handling Outliers)
异常值(Outliers)是指数据集中与其他观测值显著不同的数据点。它们可能是由于测量错误、数据输入错误或真实但罕见的事件引起的。
3.1 什么是异常值及其影响
异常值可能:
- 严重扭曲统计分析结果(如均值、标准差)。
- 对某些机器学习模型(如线性回归、基于距离的模型)产生不成比例的影响,降低模型性能。
- 在某些情况下,异常值本身就是有价值的信息(如欺诈检测、故障诊断)。
因此,处理异常值前需要理解其来源和业务含义。
3.2 异常值检测方法
3.2.1 统计方法 (Statistical Methods)
(1)3σ 法则 (3-Sigma Rule)
该方法假设数据服从正态分布。如果一个值偏离均值超过 3 倍标准差,则认为它是异常值。
计算公式: 数据点 x x x,均值 μ \mu μ,标准差 σ \sigma σ。如果 ∣ x − μ ∣ > 3 σ |x - \mu| > 3\sigma ∣x−μ∣>3σ,则 x x x 为异常值。
优点:简单直观。
缺点:仅适用于近似正态分布的数据;对异常值本身计算出的均值和标准差很敏感。
(2)IQR 法则 (Interquartile Range)
IQR (四分位距) 是第三四分位数 (Q3) 与第一四分位数 (Q1) 之间的差值 ( I Q R = Q 3 − Q 1 IQR = Q3 - Q1 IQR=Q3−Q1)。该方法认为低于 Q 1 − 1.5 × I Q R Q1 - 1.5 \times IQR Q1−1.5×IQR 或高于 Q 3 + 1.5 × I Q R Q3 + 1.5 \times IQR Q3+1.5×IQR 的值是异常值。
优点:对数据分布没有严格要求,对异常值不敏感,比 3σ 法则更稳健。
缺点:1.5 这个倍数是经验性的,可能需要调整。
# 使用 IQR 法则检测 Salary 列的异常值
Q1 = df_clean['Salary'].quantile(0.25)
Q3 = df_clean['Salary'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f"\nSalary IQR: {
IQR}, Lower Bound: {
lower_bound}, Upper Bound: {
upper_bound}")
# 找出异常值
outliers = df_clean[(df_clean['Salary'] < lower_bound) | (df_clean['Salary'] > upper_bound)]
print("\n检测到的异常值:")
print(outliers)
3.2.2 可视化方法:箱线图 (Box Plot)
箱线图是可视化 IQR 法则的绝佳工具。它能直观地展示数据的分布、中位数、四分位数以及潜在的异常值(通常表示为箱体外的点)。
graph TD
subgraph Box Plot Structure
direction LR
A[Lower Whisker (Q1 - 1.5*IQR)] --- B(Q1: First Quartile);
B --- C(Median: Q2);
C --- D(Q3: Third Quartile);
D --- E[Upper Whisker (Q3 + 1.5*IQR)];
F(Outlier) -- Beyond --> E;
G(Outlier) -- Beyond --> A;
style B fill:#f9f,stroke:#333,stroke-width:2px
style C fill:#ccf,stroke:#333,stroke-width:2px
style D fill:#f9f,stroke:#333,stroke-width:2px
end
(Mermaid 图示:箱线图结构,标出 Q1, Median(Q2), Q3, IQR, 上下须,以及须外的异常值点)
在 Python 中,可以使用 Matplotlib 或 Seaborn 绘制箱线图:
import matplotlib.pyplot as plt
import seaborn as sns
plt.figure(figsize=(6, 4))
sns.boxplot(y=df_clean['Salary'])
plt.title('Box Plot of Salary')
plt.ylabel('Salary')
plt.show()
3.2.3 基于模型的方法:孤立森林 (Isolation Forest)
孤立森林是一种适用于高维数据集的无监督异常检测算法。它基于“异常点更容易被随机分割(孤立)”的假设。算法通过随机选择特征和分割点来构建多棵决策树,异常点通常在树的较浅层就被分离出来。
优点:计算效率高,不需要数据满足特定分布,对高维数据有效。
缺点:对全局异常点敏感,可能不擅长检测局部异常点。
from sklearn.ensemble import IsolationForest
# 假设我们想在 Age 和 Salary 上检测异常
data_for_iso = df_clean[['Age', 'Salary']]
# 初始化并训练模型
iso_forest = IsolationForest(contamination='auto', random_state=42) # contamination='auto' 或指定一个比例
iso_forest.fit(data_for_iso)
# 预测异常 (-1 表示异常, 1 表示正常)
predictions = iso_forest.predict(data_for_iso)
df_clean['is_outlier_iso'] = predictions
print("\n孤立森林检测结果 (-1为异常):")
print(df_clean)
# 查看被标记为异常的数据
print("\n被孤立森林标记为异常的数据:")
print(df_clean[df_clean['is_outlier_iso'] == -1])
3.3 异常值处理策略
检测到异常值后,处理方式取决于异常值的性质:
- 删除 (Deletion):如果是明显的错误(如年龄 300 岁),或者异常值数量极少,可以删除。
- 替换/盖帽 (Capping/Flooring):将超出边界的异常值替换为预设的上限(如 Q3 + 1.5IQR)或下限(如 Q1 - 1.5IQR)。也称为 Winsorization。
- 转换 (Transformation):对数据进行数学转换(如对数转换
np.log
),可以压缩数据的尺度,减小异常值的影响。 - 视为缺失值 (Treat as Missing):将异常值标记为缺失值,然后使用前面讨论的缺失值填充方法处理。
- 单独建模 (Separate Modeling):如果异常值代表一种不同的模式,可以考虑将其分离出来单独分析或建模。
选择哪种方法需要结合业务理解和后续分析目标。
3.4 Pandas 与 Scikit-learn 实战:异常值处理(盖帽法)
# 对 Salary 应用盖帽法处理异常值 (基于 IQR)
df_capped = df_clean.copy()
df_capped['Salary'] = np.where(df_capped['Salary'] < lower_bound, lower_bound, df_capped['Salary'])
df_capped['Salary'] = np.where(df_capped['Salary'] > upper_bound, upper_bound, df_capped['Salary'])
print("\n盖帽法处理 Salary 异常值后:")
print(df_capped[['Salary']]) # 只显示 Salary 列对比
# 可以画个箱线图对比处理前后的效果
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
sns.boxplot(y=df_clean['Salary'])
plt.title('Original Salary')
plt.subplot(1, 2, 2)
sns.boxplot(y=df_capped['Salary'])
plt.title('Capped Salary')
plt.tight_layout()
plt.show()
四、处理不平衡数据 (Handling Imbalanced Data)
在许多分类问题中(如欺诈检测、疾病预测、用户流失预测),不同类别的数据样本数量可能相差悬殊,这就是数据不平衡问题。
4.1 什么是不平衡数据及其问题
当一个类别的样本数量远超其他类别时,数据集就是不平衡的。例如,信用卡欺诈交易可能只占总交易的 0.1%。
这会导致模型训练时出现问题:
- 模型偏向多数类:模型为了追求整体准确率(Accuracy),可能会倾向于将所有样本都预测为多数类,导致对少数类的识别能力很差。
- 评估指标失效:在高不平衡数据上,即使模型完全不识别少数类,准确率也可能很高,此时需要关注精确率 (Precision)、召回率 (Recall)、F1 分数或 AUC 等更能反映少数类性能的指标。
4.2 不平衡数据处理技术
主要思路是调整数据集中类别的比例,或者在算法层面进行补偿。
4.2.1 过采样 (Oversampling)
增加少数类样本的数量。
(1)随机过采样 (Random Oversampling)
简单地复制少数类的样本,直到达到期望的比例。
优点:实现简单。
缺点:可能导致模型对少数类的某些特定样本过拟合,因为只是重复了现有信息。
(2)SMOTE (Synthetic Minority Over-sampling Technique)
SMOTE 通过生成“合成”的少数类样本来避免过拟合。其基本思想是:对于每个少数类样本,找到其 K 个最近邻(同类样本),然后在这个样本与其邻居之间的连线上随机选择一点作为新的合成样本。
优点:比随机过采样效果通常更好,提供了新的信息。
缺点:可能生成噪音样本(如果原始少数类样本本身是噪音或边界点);可能导致类别边界模糊。
4.2.2 欠采样 (Undersampling)
减少多数类样本的数量。
(1)随机欠采样 (Random Undersampling)
随机地从多数类中删除样本,直到达到期望的比例。
优点:可以减少数据量,加快训练速度。
缺点:可能丢失多数类的重要信息,导致模型性能下降。
(2)其他欠采样方法简介
- NearMiss: 选择那些与其他类别样本距离最近的多数类样本。
- Tomek Links: 找出不同类别但互为最近邻的样本对 (Tomek Link),然后删除其中的多数类样本,以清晰化类别边界。
4.2.3 结合采样与集成方法
- 组合采样:如 SMOTE + ENN (Edited Nearest Neighbors) 或 SMOTE + Tomek Links,先用 SMOTE 生成样本,再用欠采样方法清理可能产生的噪音或模糊边界。
- 集成方法:如 EasyEnsemble、BalanceCascade,或者直接在集成模型(如随机森林)中使用类别权重(
class_weight='balanced'
)或专门为不平衡数据设计的集成算法(如 Balanced Random Forest)。
4.3 imbalanced-learn
库实战:SMOTE 应用
imbalanced-learn
是一个专门处理不平衡数据的 Python 库,提供了各种采样技术的实现。
# 假设我们有一个不平衡的二分类数据集 X, y
# 这里我们先创建一个模拟数据
from sklearn.datasets import make_classification
from collections import Counter
X, y = make_classification(n_samples=1000, n_features=10, n_informative=5,
n_redundant=0, n_repeated=0, n_classes=2,
n_clusters_per_class=1, weights=[0.95, 0.05], # 95% 多数类, 5% 少数类
class_sep=0.8, random_state=42)
print("原始数据类别分布:", Counter(y))
# 检查 imbalanced-learn 是否安装,没有则安装
try:
from imblearn.over_sampling import SMOTE
except ImportError:
print("请先安装 imbalanced-learn: pip install -U imbalanced-learn")
# exit() # 在实际脚本中可能需要退出或提示安装
# 应用 SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X, y)
print("SMOTE 过采样后类别分布:", Counter(y_resampled))
# 现在可以使用 X_resampled, y_resampled 来训练模型了
注意:采样操作(尤其是过采样)通常只应用于训练集,而不应用于验证集和测试集,以确保评估结果的公正性。可以使用 imblearn.pipeline.Pipeline
将采样步骤和模型训练结合起来,避免数据泄露。
五、数据类型转换与一致性检查
确保数据具有正确的类型和一致的格式对于后续的分析和建模至关重要。
5.1 数据类型的重要性
- 计算准确性:数值计算只能在数值类型上进行。
- 内存效率:选择合适的类型(如
int8
vsint64
)可以节省内存。 - 模型兼容性:许多模型要求输入是特定类型(如 Scikit-learn 通常要求数值输入)。
- 分析正确性:将日期识别为字符串会妨碍时间序列分析。
5.2 常见数据类型转换
Pandas 提供了强大的类型转换功能。
- 通用转换
astype()
: 可以尝试将列转换为多种类型。# df['column'] = df['column'].astype(int) # df['column'] = df['column'].astype(str) # df['column'] = df['column'].astype('category') # 转换为类别类型,节省内存
- 转换为数值
pd.to_numeric()
: 更稳健地将列转换为数值类型,可以处理无法转换的值(设置为 NaN 或引发错误)。# 假设 col_d 是 '1', '2', 'error', '4' # df['col_d_numeric'] = pd.to_numeric(df['col_d'], errors='coerce') # 'error'会变成 NaN
- 转换为日期时间
pd.to_datetime()
: 解析字符串为日期时间对象。# 假设 col_e 是 '2025-04-01', '2025/04/02' # df['col_e_datetime'] = pd.to_datetime(df['col_e'], errors='coerce')
5.3 数据一致性检查
检查并修正数据中的不一致性。
- 单位一致性:确保数值数据使用相同的单位(如都转换为千克,或都转换为米)。
- 格式一致性:如日期格式统一为 ‘YYYY-MM-DD’。
- 类别值一致性:处理同一含义但表示不同的值,如 ‘male’, ‘Male’, ‘M’ 应统一为一种。可以使用
.unique()
查看所有唯一值,然后用.replace()
或.map()
进行修正。# 查看 Gender 列的唯一值 print(df_clean['Gender'].unique()) # 输出: ['M' 'F'] (假设已经填充好了) # 假设原始数据中有 'm', 'f' # df['Gender'] = df['Gender'].replace({'m': 'M', 'f': 'F'})
- 去除多余空格:字符串前后的空格可能导致问题。
# df['string_col'] = df['string_col'].str.strip()
5.4 Pandas 实战:类型转换与一致性处理
# 创建一个需要清理的 DataFrame
data_inconsistent = {
'ID': [' 1 ', ' 2', '3 '],
'Value': ['10.5', '20', '$30'],
'Category': ['Type A', 'type a', 'Type B'],
'Date': ['2025-01-01', '02/01/2025', '2025.01.03']}
df_inc = pd.DataFrame(data_inconsistent)
print("\n需要清理的数据:")
print(df_inc)
print("\n原始数据类型:")
print(df_inc.dtypes)
# 清理步骤
df_fixed = df_inc.copy()
# 1. 清理 ID: 去除空格,转换为整数
df_fixed['ID'] = df_fixed['ID'].str.strip().astype(int)
# 2. 清理 Value: 去除 '$', 转换为浮点数 (处理无法转换的用 NaN)
df_fixed['Value'] = df_fixed['Value'].str.replace('$', '', regex=False)
df_fixed['Value'] = pd.to_numeric(df_fixed['Value'], errors='coerce')
# 3. 清理 Category: 统一大小写,转换为 category 类型
df_fixed['Category'] = df_fixed['Category'].str.lower().astype('category')
# 4. 清理 Date: 统一格式为 datetime 对象
df_fixed['Date'] = pd.to_datetime(df_fixed['Date'], errors='coerce')
print("\n清理后的数据:")
print(df_fixed)
print("\n清理后的数据类型:")
print(df_fixed.dtypes)
六、案例分析:数据预处理对模型鲁棒性的影响
现在,我们通过一个简单的案例来看看数据预处理是如何实实在在地提升模型性能的。
6.1 场景设定
假设我们有一个简单的二分类任务数据集,其中包含一些缺失值和可能的异常值。我们想用逻辑回归模型进行分类。
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
from sklearn.preprocessing import StandardScaler
# 创建一个模拟的 "脏" 数据集
np.random.seed(42)
X_dirty = np.random.rand(100, 2) * 10
y_dirty = (X_dirty[:, 0] + X_dirty[:, 1] > 10).astype(int)
# 人为制造缺失值 (约 10%)
mask = np.random.choice([True, False], size=X_dirty.shape, p=[0.1, 0.9])
X_dirty[mask] = np.nan
# 人为制造异常值
X_dirty[0, 0] = 100 # 明显异常
df_dirty = pd.DataFrame(X_dirty, columns=['Feature1', 'Feature2'])
df_dirty['Target'] = y_dirty
print("\n模拟的 '脏' 数据 (前 5 行):")
print(df_dirty.head())
print("\n缺失值统计:")
print(df_dirty.isnull().sum())
6.2 未经处理的数据建模
直接使用原始“脏”数据进行建模通常会遇到问题,比如很多模型不支持 NaN 输入。即使模型能处理(或我们简单删除 NaN),结果也可能不佳。这里我们先尝试删除 NaN 行。
df_dropna = df_dirty.dropna()
X_dropna = df_dropna[['Feature1', 'Feature2']]
y_dropna = df_dropna['Target']
if len(X_dropna) > 0: # 确保删除后还有数据
X_train_drop, X_test_drop, y_train_drop, y_test_drop = train_test_split(
X_dropna, y_dropna, test_size=0.3, random_state=42)
# 未经处理(仅删除了NaN,未处理异常值,未标准化)
model_dirty = LogisticRegression(random_state=42)
try:
model_dirty.fit(X_train_drop, y_train_drop)
y_pred_dirty = model_dirty.predict(X_test_drop)
print("\n--- 模型 1: 仅删除 NaN ---")
print("准确率:", accuracy_score(y_test_drop, y_pred_dirty))
print(classification_report(y_test_drop, y_pred_dirty, zero_division=0))
except ValueError as e:
print(f"\n模型 1 训练失败: {
e}. 可能是因为异常值或数据尺度问题。")
else:
print("\n删除 NaN 后数据不足,无法训练模型 1。")
注意:直接用含异常值的数据训练 Logistic Regression 可能会因为数值问题失败或效果很差。
6.3 应用预处理技术
现在我们应用前面讨论的技术来清理数据。
df_processed = df_dirty.copy()
# 1. 处理缺失值:使用中位数填充
for col in ['Feature1', 'Feature2']:
median_val = df_processed[col].median()
df_processed[col].fillna(median_val, inplace=True)
# 2. 处理异常值:使用 IQR 盖帽法 (这里简单演示,实际应用需更仔细)
for col in ['Feature1', 'Feature2']:
Q1 = df_processed[col].quantile(0.25)
Q3 = df_processed[col].quantile(0.75)
IQR = Q3 - Q1
lower = Q1 - 1.5 * IQR
upper = Q3 + 1.5 * IQR
df_processed[col] = np.clip(df_processed[col], lower, upper) # clip 实现盖帽
# 3. 数据标准化 (很多模型需要,特别是基于距离或梯度的)
scaler = StandardScaler()
X_processed = df_processed[['Feature1', 'Feature2']]
X_scaled = scaler.fit_transform(X_processed) # 标准化处理
y_processed = df_processed['Target']
print("\n处理并标准化后的数据 X (前 5 行):")
print(X_scaled[:5])
print("\n处理后缺失值统计:")
print(pd.DataFrame(X_scaled, columns=['Feature1', 'Feature2']).isnull().sum()) # 确认无缺失
6.4 处理后数据建模与对比
使用清理并标准化后的数据重新训练和评估模型。
X_train_proc, X_test_proc, y_train_proc, y_test_proc = train_test_split(
X_scaled, y_processed, test_size=0.3, random_state=42)
model_processed = LogisticRegression(random_state=42)
model_processed.fit(X_train_proc, y_train_proc)
y_pred_processed = model_processed.predict(X_test_proc)
print("\n--- 模型 2: 经过预处理 (填充缺失 + 盖帽异常 + 标准化) ---")
print("准确率:", accuracy_score(y_test_proc, y_pred_processed))
print(classification_report(y_test_proc, y_pred_processed, zero_division=0))
对比分析:通过对比模型 1(如果能成功运行)和模型 2 的评估指标(准确率、精确率、召回率、F1 分数),我们可以清晰地看到数据预处理对模型性能和稳定性的显著提升。处理后的数据使得模型能够更准确地学习数据中的模式,预测结果也更加可靠和鲁棒。
七、总结
数据预处理与清洗是机器学习流程中不可或缺的一环,其重要性不容忽视。高质量的数据是构建高性能模型的基础。本文系统地梳理了数据预处理的核心技术和实践方法,要点总结如下:
- 重要性认知:深刻理解“垃圾进,垃圾出”原则,认识到数据质量直接决定模型性能上限。
- 处理缺失值:掌握识别缺失值的方法,并根据情况选择删除法或填充法(均值、中位数、众数、模型预测),理解不同方法的优缺点和适用场景。
- 处理异常值:学会使用统计方法(3σ, IQR)、可视化(箱线图)和模型(孤立森林)检测异常值,并根据异常值的性质选择删除、替换(盖帽)、转换或视为缺失等处理策略。
- 处理不平衡数据:理解不平衡数据对分类模型的挑战,掌握过采样(随机过采样、SMOTE)和欠采样(随机欠采样)技术,以及何时应用它们。了解
imbalanced-learn
库的使用。 - 类型与一致性:重视数据类型的正确转换(数值、类别、日期时间)和数据内容的一致性(单位、格式、类别值),掌握 Pandas 相关操作。
- 实践出真知:数据预处理没有固定公式,需要结合业务理解、数据探索和反复试验来找到最适合当前任务的方法组合。
- 提升模型鲁棒性:通过案例分析,直观感受数据预处理对模型精度、稳定性和鲁棒性的关键作用。
希望通过本文的学习,你能更加从容地面对真实世界中的“脏”数据,运用所学技术有效地进行数据预处理与清洗,为你的机器学习项目打下坚实的基础!下一篇文章我们将探讨机器学习项目实战的完整流程,敬请期待!