课程 2:数据清洗与预处理技术
数据清洗与预处理的重要性
从各种来源收集的原始数据通常是混乱、不一致的,并且可能包含错误或缺失值。数据清洗和预处理是任何数据科学项目中至关重要的第一步。这些过程将原始数据转换为干净、一致且可用的格式,从而显著提高任何后续分析或模型构建的质量和准确性。
“垃圾进,垃圾出”(GIGO)是计算机科学中的一句名言,它在数据科学中尤其适用。高质量的数据才能带来可靠的见解和稳健的模型。
处理缺失值
缺失数据是一个常见问题。由于数据输入错误、传感器故障或隐私问题等多种原因,数据值可能会缺失。我们如何处理它们取决于数据的性质和缺失的程度。
1. 移除缺失数据
这是最简单的方法,但如果不小心使用,可能会导致有价值信息的丢失。
- 列表删除(Listwise Deletion):删除包含一个或多个缺失值的整个行(样本)。
- 列删除(Dropping Features):如果某列(特征)的大部分值缺失且该特征不重要,则删除整个列。
Python 示例 (Pandas):
import pandas as pd
import numpy as np
# 包含缺失值的示例 DataFrame
data = {'A': [1, 2, np.nan, 4, 5],
'B': [np.nan, 7, 8, 9, 10],
'C': [11, 12, 13, np.nan, 15],
'D': [16, 17, 18, 19, np.nan]}
df_missing = pd.DataFrame(data)
print("原始 DataFrame:\n", df_missing)
# 列表删除 (移除所有包含 NaN 的行)
df_removed_rows = df_missing.dropna()
print("\n移除包含 NaN 的行后的 DataFrame:\n", df_removed_rows)
# 列删除 (移除所有包含 NaN 的列)
# df_removed_cols = df_missing.dropna(axis=1)
# print("\n移除包含 NaN 的列后的 DataFrame:\n", df_removed_cols)
# 更实用的做法是,移除 NaN 比例较高的列
threshold = 0.5 # 移除缺失值超过 50% 的列
df_removed_cols_thresh = df_missing.dropna(axis=1, thresh=int(threshold*len(df_missing)))
print("\n移除超过 50% NaN 的列后的 DataFrame:\n", df_removed_cols_thresh)
2. 缺失数据插补
插补是指用估计值或计算值填充缺失值。常用方法包括:
- 均值插补:用该列观察值的均值替换数值列中的缺失值。对异常值敏感。
- 中位数插补:用中位数替换缺失值。比均值插补对异常值更稳健。
- 众数插补:用众数(最常见的值)替换缺失值。适用于分类特征,也可用于数值特征。
- 常量值插补:用特定常量(例如 0,“Unknown”)替换缺失值。
- 前向填充 (ffill) / 后向填充 (bfill):适用于时间序列数据,用前一个或后一个已知值填充。
Python 示例 (Pandas):
# 重用上面的 df_missing
print("原始 DataFrame:\n", df_missing)
# 对 'A' 列进行均值插补
df_mean_imputed = df_missing.copy()
df_mean_imputed['A'] = df_mean_imputed['A'].fillna(df_mean_imputed['A'].mean())
print("\n对 A 列进行均值插补后的 DataFrame:\n", df_mean_imputed)
# 对 'B' 列进行中位数插补
df_median_imputed = df_missing.copy()
df_median_imputed['B'] = df_median_imputed['B'].fillna(df_median_imputed['B'].median())
print("\n对 B 列进行中位数插补后的 DataFrame:\n", df_median_imputed)
# 对 'C' 列进行众数插补 (假设 C 可能是分类或离散值)
df_mode_imputed = df_missing.copy()
df_mode_imputed['C'] = df_mode_imputed['C'].fillna(df_mode_imputed['C'].mode()[0]) # mode() 可能返回多个值
print("\n对 C 列进行众数插补后的 DataFrame:\n", df_mode_imputed)
# 对 'D' 列进行前向填充
df_ffill_imputed = df_missing.copy()
df_ffill_imputed['D'] = df_ffill_imputed['D'].ffill()
print("\n对 D 列进行前向填充后的 DataFrame:\n", df_ffill_imputed)
检测和处理异常值
异常值是显著偏离其他观测值的数据点。它们可能是由于测量错误、实验错误或真实的极端值造成的。异常值会扭曲统计分析结果并降低机器学习模型的性能。
1. 检测异常值
- 可视化:箱线图非常适合可视化异常值。散点图也可以揭示异常数据点。
- 统计方法:
- Z-score:落在均值特定数量标准差(例如 +/- 3)之外的值被认为是异常值。
- 四分位距 (IQR):低于 Q1 - 1.5*IQR 或高于 Q3 + 1.5*IQR 的值被认为是异常值。(Q1 = 第25百分位数,Q3 = 第75百分位数)。
Python 示例 (Pandas & NumPy for IQR):
# 包含潜在异常值的示例数据
data_outliers = {'Score': [50, 55, 58, 60, 62, 65, 68, 70, 72, 75, 150, 5]}
df_outliers = pd.DataFrame(data_outliers)
print("包含异常值的原始 DataFrame:\n", df_outliers)
# IQR 方法检测异常值
Q1 = df_outliers['Score'].quantile(0.25)
Q3 = df_outliers['Score'].quantile(0.75)
IQR = Q3 - Q1
lower_bound = Q1 - 1.5 * IQR
upper_bound = Q3 + 1.5 * IQR
print(f"\nQ1: {Q1}, Q3: {Q3}, IQR: {IQR}")
print(f"异常值下限: {lower_bound}")
print(f"异常值上限: {upper_bound}")
outliers_iqr = df_outliers[(df_outliers['Score'] < lower_bound) | (df_outliers['Score'] > upper_bound)]
print("\n使用 IQR 方法检测到的异常值:\n", outliers_iqr)
# Z-score 方法 (使用 scipy)
from scipy import stats
df_outliers['Z_Score'] = np.abs(stats.zscore(df_outliers['Score']))
print("\n包含 Z-scores 的 DataFrame:\n", df_outliers)
outliers_z = df_outliers[df_outliers['Z_Score'] > 3] # 通常 Z-score 阈值为 3
print("\n使用 Z-score 方法检测到的异常值 (阈值 > 3):\n", outliers_z)
2. 处理异常值
- 移除:与缺失数据类似,可以移除异常值。如果确认它们是错误,则此方法适用。
- 封顶/设限 (Winsorization):用最接近的“可接受”值替换异常值(例如,用上限值替换高于上限的异常值)。
- 转换:应用数学转换(例如,对数、平方根)有时可以减少异常值引起的偏度。
- 插补:将异常值视为空缺值并进行插补。
Python 示例 (封顶):
# 使用上面 IQR 计算得到的 df_outliers 和边界值
df_capped = df_outliers.copy()
# 对高于上限和低于下限的值进行封顶
df_capped['Score_Capped'] = np.where(
df_capped['Score'] > upper_bound,
upper_bound,
np.where(
df_capped['Score'] < lower_bound,
lower_bound,
df_capped['Score']
)
)
print("\n封顶异常值后的 DataFrame:\n", df_capped[['Score', 'Score_Capped']])
数据转换技术
数据转换涉及更改变量的尺度或分布。这对于那些对输入特征尺度敏感的机器学习算法通常是必要的。
1. 归一化 (Min-Max Scaling)
将特征重新缩放到一个固定范围,通常是 [0, 1] 或 [-1, 1]。公式是:
X_normalized = (X - X_min) / (X_max - X_min)
当算法不假设数据的任何分布时(例如,k-最近邻、神经网络),此方法很有用。
Python 示例 (Scikit-learn):
from sklearn.preprocessing import MinMaxScaler
import pandas as pd
import numpy as np # 确保导入 numpy
# 示例数据
data_trans = {'Feature1': np.array([10, 20, 30, 40, 50.0]), # 确保为浮点数以便缩放
'Feature2': np.array([100, 200, 50, 300, 150.0])} # 确保为浮点数以便缩放
df_trans = pd.DataFrame(data_trans)
print("用于转换的原始 DataFrame:\n", df_trans)
scaler_minmax = MinMaxScaler()
df_normalized = pd.DataFrame(scaler_minmax.fit_transform(df_trans), columns=df_trans.columns)
print("\nMin-Max 归一化后的 DataFrame:\n", df_normalized)
2. 标准化 (Z-score Scaling)
重新缩放特征,使其均值为 0,标准差为 1。公式是:
X_standardized = (X - mean(X)) / std_dev(X)
当算法假设数据呈正态分布,或者当特征具有不同的单位和尺度时(例如,线性回归、逻辑回归、SVM),此方法很有用。
Python 示例 (Scikit-learn):
from sklearn.preprocessing import StandardScaler
# 使用上面的 df_trans
print("用于转换的原始 DataFrame:\n", df_trans)
scaler_standard = StandardScaler()
df_standardized = pd.DataFrame(scaler_standard.fit_transform(df_trans), columns=df_trans.columns)
print("\n标准化后的 DataFrame:\n", df_standardized)
结论
数据清洗和预处理是有效数据科学的基础。通过系统地解决缺失值和异常值等问题,并适当地转换数据,您可以为更准确、更可靠的分析结果和机器学习模型奠定基础。这些步骤虽然有时繁琐,但对于从数据中获取有意义的见解是必不可少的。