所有文章 > AI驱动 > Python实现数据预处理:常见缺失值处理方法解析

Python实现数据预处理:常见缺失值处理方法解析

缺失值处理方案

处理缺失值是数据预处理中的一个重要步骤,不同的处理方法可以显著影响后续数据分析和机器学习模型的效果,以下是一些常见的缺失值处理方案:

删除法:

  • 删除含有缺失值的样本:适用于缺失值较少且缺失值分布无规律的情况
  • 删除含有缺失值的特征:适用于某一特征缺失值过多的情况(如超过50%)

插补法:

  • 均值/中位数/众数插补:将缺失值替换为该特征的均值、中位数或众数,适用于数值型和分类型数据
  • 前向/后向填充:使用前一个或后一个值填补缺失值,适用于时间序列数据
  • 线性插值:基于线性关系插补缺失值,适用于时间序列数据
  • 回归插补:使用回归模型预测缺失值
  • 多重插补:使用多个插补值估计缺失值,适用于需要考虑插补不确定性的情况

模型预测法:

  • 使用机器学习模型(如K近邻、随机森林)预测缺失值,基于其他特征信息进行插补

填补指示法:

  • 引入缺失指示变量:在原始特征基础上增加一个表示该值是否缺失的二元变量,适用于缺失值可能具有实际意义的情况

不处理:

  • 某些机器学习算法(如决策树、XGBoost)可以处理缺失值,不需要显式插补

在选择缺失值处理方案时,应根据具体数据集的特征、缺失值的分布情况以及后续分析的需求来确定最合适的方法,接下来让我们逐一实现这些处理方法

代码实现

缺失值数据生成

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = 'SimHei'
plt.rcParams['axes.unicode_minus'] = False

np.random.seed(42)
data = np.random.randn(200)

# 随机选择20个位置引入缺失值
data[np.random.choice(200, 20, replace=False)] = np.nan
df = pd.DataFrame({'data': data})

plt.figure(figsize=(15, 5))
plt.plot(df['data'], marker='o', linestyle='-', label='数据', color='blue')
plt.title('包含缺失值的数据')
plt.xlabel('索引', fontsize=14)
plt.ylabel('数值', fontsize=14)
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

生成一组含有随机缺失值的正态分布数据,为接下来的实验数据

删除法

删除缺失样本

print("原始数据缺失值数量:", df.isnull().sum())
# 删除缺失值样本 并重置索引
df_1 = df.dropna().reset_index(drop=True)
print("数据处理后缺失值数量:", df_1.isnull().sum())

# 可视化数据(删除缺失值后的数据)
plt.figure(figsize=(15, 5))
plt.plot(df_1, marker='o', linestyle='-', label='删除缺失值数据', color='blue')
plt.title('删除缺失值数据')
plt.xlabel('索引', fontsize=14)
plt.ylabel('数值', fontsize=14)
plt.legend(fontsize=12)
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

使用 Pandas 中的 dropna() 方法,在给定的dataframe中,调用 dropna() 方法会删除包含任何缺失值(NaN)的行,默认情况下,它会返回一个新的数据框,其中不含有缺失值的行,这里删除数据后还进行了索引重置

删除缺失列

df.drop('B', axis=1)
df.drop(['A','B'], axis=1)

这里我们的实验数据为单序列数据,对于删除缺失列,直接给出相应用法,第一行代码表示删除列B,第二行代码表示同时删除列AB

插补法

均值/中位数/众数插补缺失值

# 用均值插补缺失值
df_mean = df.fillna(df['data'].mean())

# 用中位数插补缺失值
df_median = df.fillna(df['data'].median())

# 用众数插补缺失值
df_mode = df.fillna(df['data'].mode()[0])

# 标记缺失值的位置
missing_indices = df[df['data'].isna()].index

# 可视化均值、中位数和众数插补
plt.figure(figsize=(15, 5))
plt.plot(df_mean['data'], marker='o', linestyle='-', label='均值插补', color='green', alpha=0.8)
plt.plot(df_median['data'], marker='o', linestyle='-', label='中位数插补', color='orange', alpha=0.6)
plt.plot(df_mode['data'], marker='o', linestyle='-', label='众数插补', color='red', alpha=0.4)

# 标注插值的数据点
plt.scatter(missing_indices, df_mean.loc[missing_indices], color='green', edgecolors='black', zorder=5, s=100, marker='s', label='均值插补点')
plt.scatter(missing_indices, df_median.loc[missing_indices], color='orange', edgecolors='black', zorder=5, s=100, marker='^', label='中位数插补点')
plt.scatter(missing_indices, df_mode.loc[missing_indices], color='red', edgecolors='black', zorder=5, s=100, marker='v', label='众数插补点')

plt.title('均值/中位数/众数插补缺失值')
plt.xlabel('索引', fontsize=10)
plt.ylabel('数值', fontsize=10)
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

使用均值、中位数和众数三种方法对数据框 df 中的缺失值进行插补,并将插补后的数据可视化展示出来

前向/后向填充

# 前向填充
df_ffill = df.ffill()

# 后向填充
df_bfill = df.bfill()


# 可视化前向填充和后向填充
plt.figure(figsize=(15, 5))
plt.plot(df_ffill['data'], marker='o', linestyle='-', label='前向填充', color='green', alpha=0.8)
plt.plot(df_bfill['data'], marker='o', linestyle='-', label='后向填充', color='orange', alpha=0.6)

# 标注插值的数据点
plt.scatter(missing_indices, df_ffill.loc[missing_indices], color='green', edgecolors='black', zorder=5, s=100, marker='s', label='前向填充点')
plt.scatter(missing_indices, df_bfill.loc[missing_indices], color='orange', edgecolors='black', zorder=5, s=100, marker='^', label='后向填充点')

# 添加标题和标签
plt.title('前向/后向填充缺失值')
plt.xlabel('索引', fontsize=10)
plt.ylabel('数值', fontsize=10)
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

使用前向填充(ffill)和后向填充(bfill)方法来处理数据框 df 中的缺失值

线性样条插值/二次样条插值/三次样条插值

from scipy.interpolate import interp1d

# 线性样条插值
linear_interpolator = interp1d(df.dropna().index, df.dropna()['data'], kind='linear', fill_value="extrapolate")
df_linear = df.copy()
df_linear['data'] = df_linear['data'].combine_first(pd.Series(linear_interpolator(df.index), index=df.index))

# 二次样条插值
quadratic_interpolator = interp1d(df.dropna().index, df.dropna()['data'], kind='quadratic', fill_value="extrapolate")
df_quadratic = df.copy()
df_quadratic['data'] = df_quadratic['data'].combine_first(pd.Series(quadratic_interpolator(df.index), index=df.index))

# 三次样条插值
cubic_interpolator = interp1d(df.dropna().index, df.dropna()['data'], kind='cubic', fill_value="extrapolate")
df_cubic = df.copy()
df_cubic['data'] = df_cubic['data'].combine_first(pd.Series(cubic_interpolator(df.index), index=df.index))


# 可视化线性、二次和三次样条插值
plt.figure(figsize=(15, 5))
plt.plot(df_linear['data'], marker='o', linestyle='-', label='线性样条插值', color='blue', alpha=0.8)
plt.plot(df_quadratic['data'], marker='o', linestyle='-', label='二次样条插值', color='green', alpha=0.6)
plt.plot(df_cubic['data'], marker='o', linestyle='-', label='三次样条插值', color='red', alpha=0.4)

# 标注插值的数据点
plt.scatter(missing_indices, df_linear.loc[missing_indices], color='blue', edgecolors='black', zorder=5, s=100, marker='s', label='线性插值点')
plt.scatter(missing_indices, df_quadratic.loc[missing_indices], color='green', edgecolors='black', zorder=5, s=100, marker='^', label='二次插值点')
plt.scatter(missing_indices, df_cubic.loc[missing_indices], color='red', edgecolors='black', zorder=5, s=100, marker='v', label='三次插值点')

# 添加标题和标签
plt.title('线性样条插值/二次样条插值/三次样条插值填充缺失值')
plt.xlabel('索引', fontsize=10)
plt.ylabel('数值', fontsize=10)
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

使用线性、二次和三次样条插值方法来填充数据框 df 中的缺失值

  • 线性样条插值:利用线性插值方法,通过已知数据点之间的直线来填充缺失值,插值函数保证通过每个已知点,并在相邻两点之间形成一条线性曲线
  • 二次样条插值:使用二次插值方法,通过已知数据点之间的二次曲线来填充缺失值,这种方法比线性插值更加平滑,可以更好地拟合中间数据点
  • 三次样条插值:利用三次插值方法,通过已知数据点之间的三次曲线来填充缺失值,三次插值比二次插值更加平滑,同时也更加灵活,可以适应更复杂的数据模式

多重插补

# 进行多重插补
imputer = IterativeImputer(max_iter=10, random_state=42)
df_imputed = pd.DataFrame(imputer.fit_transform(df), columns=df.columns)


# 可视化原始数据和插补后的数据
plt.figure(figsize=(15, 5))
plt.plot(df_imputed['data'], marker='o', linestyle='-', label='多重插补', color='red', alpha=0.8)
# 标注插值的数据点
plt.scatter(missing_indices, df_imputed.loc[missing_indices], color='red', edgecolors='black', zorder=5, s=100, marker='s', label='插补点')

# 添加标题和标签
plt.title('多重插补填充缺失值')
plt.xlabel('索引', fontsize=10)
plt.ylabel('数值', fontsize=10)
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

使用多重插补(Multiple Imputation)方法来填补数据中的缺失值,多重插补是一种通过生成多个完整的数据集(每个数据集都是通过不同方式填补缺失值得到的)来处理缺失值的方法,这里使用了IterativeImputer类来进行多重插补,这个类在每次迭代中使用回归模型来估算缺失值,直到收敛或达到最大迭代次数,max_iter=10指定了最大迭代次数为10

模型预测法

K近邻、随机森林

from sklearn.impute import KNNImputer
from sklearn.ensemble import RandomForestRegressor
# 使用KNN插值填充缺失值
knn_imputer = KNNImputer(n_neighbors=30)
df_knn_imputed = pd.DataFrame(knn_imputer.fit_transform(df), columns=df.columns)

# 使用随机森林填补缺失值
# 需要将数据分为训练集和测试集,训练集不包含缺失值
df_non_missing = df.dropna()
X_train = df_non_missing.index.values.reshape(-1, 1)
y_train = df_non_missing['data'].values

# 创建并训练随机森林回归模型
rf_regressor = RandomForestRegressor(n_estimators=100, random_state=42)
rf_regressor.fit(X_train, y_train)

# 预测缺失值
X_missing = missing_indices.values.reshape(-1, 1)
y_missing_pred = rf_regressor.predict(X_missing)

# 将预测的缺失值填充回原始数据框
df_rf_imputed = df.copy()
df_rf_imputed.loc[missing_indices, 'data'] = y_missing_pred

# 可视化原始数据、KNN插值和随机森林插值后的数据
plt.figure(figsize=(15, 5))
plt.plot(df_knn_imputed['data'], marker='o', linestyle='-', label='KNN插值', color='blue', alpha=0.8)
plt.plot(df_rf_imputed['data'], marker='o', linestyle='-', label='随机森林插值', color='red', alpha=0.4)

# 标注插值的数据点
plt.scatter(missing_indices, df_knn_imputed.loc[missing_indices], color='blue', edgecolors='black', zorder=5, s=100, marker='s', label='KNN插值点')
plt.scatter(missing_indices, df_rf_imputed.loc[missing_indices], color='red', edgecolors='black', zorder=5, s=100, marker='^', label='随机森林插值点')

# 添加标题和标签
plt.title('KNN插值和随机森林插值填充缺失值')
plt.xlabel('索引', fontsize=10)
plt.ylabel('数值', fontsize=10)
plt.legend()
plt.grid(True, linestyle='--', alpha=0.7)
plt.show()

使用KNN插值和随机森林插值填补数据中的缺失值

KNN插值的思想是基于数据的相似性来填补缺失值,对于每个缺失值,算法找到最接近的K个邻居(即距离最近的K个数据点),然后使用这些邻居的值进行插值,通常是通过加权平均或简单平均来估计缺失值,侧重于利用数据点之间的距离关系进行插值,适合处理局部相关性强的数据

随机森林插值利用随机森林回归模型来预测缺失值,它的思想是利用已有数据的非线性关系和复杂交互效应来预测缺失值,首先,使用不含缺失值的部分作为训练集,训练一个随机森林回归模型,然后,利用训练好的模型预测缺失值,侧重于利用数据点之间的距离关系进行插值,适合处理局部相关性强的数据

本文章转载微信公众号@Python机器学习AI