哈喽,我是小白~
今儿和大家聊一个非常重要的数据处理方法:数据填补~
数据填补技术能有效减少因缺失数据带来的偏差,提高模型的稳定性和准确性。填补技术通过完整数据集的生成,避免信息损失,确保模型在训练和测试时利用到完整的数据特征。适当的数据填补还能防止样本量减少,增强模型的泛化能力,特别是在数据稀缺的情况下尤为关键。
涉及到的数据填补操作有:
平均值填补 中位数填补 众数填补 随机填补 常数填补 前向填充 后向填充 K-最近邻填补 多重插补法 回归填补
如果需要 本文PDF版本 的同学,文末获取~
另外,文末有总结性的干货~
一起来看下具体细化内容~
1. 平均值填补
平均值填补是填补缺失值时最为常见的方法之一,尤其适用于数值型数据。其基本思想是用该特征的均值(平均值)来替代缺失的值。因为均值代表了数据的“中心位置”,所以这种方法在没有严重偏斜的数据集上效果较好。
公式推理
设特征 ,其中有 个观测值缺失,剩余 个非缺失值。我们将缺失值填补为非缺失值的平均值。公式为:
其中 表示缺失值的索引, 是非缺失值。
优缺点:
优点:简单且易于实现,特别适用于数据缺失的比例较低时。 缺点:会低估数据的方差,影响数据的分布,尤其是对于有偏或有异常值的情况,它可能产生错误的填补值。
Python案例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import random
# 设置随机种子
np.random.seed(0)
random.seed(0)
# 创建虚拟数据集
data_size = 1000
data = pd.DataFrame({
'House Size (sq ft)': np.random.normal(1500, 300, data_size),
'Price ($1000s)': np.random.normal(300, 50, data_size),
'Number of Rooms': np.random.randint(2, 8, data_size)
})
# 引入一些缺失值
for col in data.columns:
data.loc[data.sample(frac=0.1).index, col] = np.nan
# 显示缺失值数据
print("数据集缺失情况:")
print(data.isnull().sum())
# 1. 计算填补前的均值
mean_before = data.mean()
# 2. 平均值填补
data_filled = data.fillna(data.mean())
# 3. 计算填补后的均值
mean_after = data_filled.mean()
# 绘图
plt.figure(figsize=(18, 10))
sns.set(style="whitegrid")
# 子图1:缺失值前后的分布对比(直方图)
plt.subplot(2, 2, 1)
for col in data.columns:
sns.histplot(data[col], color='red', label=f'{col} (before)', kde=True, element="step", fill=True)
sns.histplot(data_filled[col], color='blue', label=f'{col} (after)', kde=True, element="step", fill=True, alpha=0.5)
plt.title('Distribution Before and After Mean Imputation')
plt.xlabel('Value')
plt.ylabel('Frequency')
plt.legend()
# 子图2:缺失值前后的散点图对比
plt.subplot(2, 2, 2)
plt.scatter(data['House Size (sq ft)'], data['Price ($1000s)'], color='red', alpha=0.5, label='Before Imputation')
plt.scatter(data_filled['House Size (sq ft)'], data_filled['Price ($1000s)'], color='blue', alpha=0.3, label='After Imputation')
plt.title('Scatter Plot of House Size vs Price Before and After Imputation')
plt.xlabel('House Size (sq ft)')
plt.ylabel('Price ($1000s)')
plt.legend()
# 子图3:填补前后数据均值比较的条形图
plt.subplot(2, 2, 3)
mean_comparison = pd.DataFrame({'Before Imputation': mean_before, 'After Imputation': mean_after})
mean_comparison.plot(kind='bar', color=['red', 'blue'], ax=plt.gca())
plt.title('Mean Comparison Before and After Imputation')
plt.ylabel('Mean Value')
plt.xlabel('Features')
plt.xticks(rotation=0)
plt.tight_layout()
plt.show()
缺失值前后的分布对比:直方图展示填补前后各特征数据的分布情况,尤其关注填补后分布的合理性。红色表示填补前数据,蓝色表示填补后数据。我们可以通过比较两者的分布形状,查看平均值填补是否改变数据分布。
缺失值前后的散点图对比:通过散点图来观察“房屋面积”与“价格”之间的关系,红色表示填补前数据,蓝色表示填补后数据。散点图可以帮助我们直观地看到填补后是否保留了特征之间的关联性。
填补前后数据均值比较的条形图:条形图用于显示填补前后的各列均值,观察平均值填补是否显著影响了各特征的均值。
2. 中位数填补
中位数填补类似于平均值填补,但它使用中位数而不是均值来填补缺失数据。中位数对异常值(极端值)具有较好的鲁棒性,因为它不会受到异常值的影响,因此适用于有偏或含有异常值的特征。
公式推理
设数据集 ,其中有 个缺失值。我们先从非缺失数据中找出中位数。将数据按升序排列,若数据项个数为 ,则中位数公式为:
其中 表示数据排序后的第 个元素。
优缺点:
优点:相比均值,中位数对极端值更不敏感,适合于有异常值或数据呈偏态分布的情况。 缺点:虽然它能减少偏斜,但它的填补值可能不如均值填补值那么代表数据的“中心”。
Python案例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子保证重复性
np.random.seed(42)
# 生成虚拟数据集
data_size = 200
df = pd.DataFrame({
'Feature_1': np.random.normal(loc=50, scale=10, size=data_size),
'Feature_2': np.random.normal(loc=30, scale=5, size=data_size),
'Feature_3': np.random.normal(loc=60, scale=15, size=data_size)
})
# 随机插入缺失值
missing_rate = 0.2
for col in df.columns:
df.loc[df.sample(frac=missing_rate).index, col] = np.nan
# 绘制缺失值前的分布
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle("Data Imputation Analysis with Median Filling", fontsize=16, fontweight='bold')
# 缺失值情况可视化
sns.heatmap(df.isnull(), cbar=False, cmap='viridis', ax=axes[0, 0])
axes[0, 0].set_title("Missing Values Before Imputation", fontsize=14)
# 应用中位数填补
df_filled = df.fillna(df.median())
# 缺失值填补后的情况可视化
sns.heatmap(df_filled.isnull(), cbar=False, cmap='viridis', ax=axes[0, 1])
axes[0, 1].set_title("Missing Values After Median Imputation", fontsize=14)
# 绘制填补前后的分布对比
for idx, col in enumerate(df.columns):
sns.histplot(df[col], color='red', alpha=0.5, kde=True, ax=axes[1, idx % 2], label='Before Imputation')
sns.histplot(df_filled[col], color='blue', alpha=0.5, kde=True, ax=axes[1, idx % 2], label='After Imputation')
axes[1, idx % 2].set_title(f"{col} Distribution Before and After Imputation", fontsize=14)
axes[1, idx % 2].legend()
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
缺失值可视化图:第一行展示了数据填补前后的缺失值分布。通过热力图,用户可以清晰地看到缺失值的存在位置。在填补后,我们可以看到所有缺失值都已经被填补。这种可视化帮助用户验证填补的效果。
分布对比直方图:第二行展示每个特征填补前后的数据分布,通过直方图叠加了填补前后的分布对比。图中红色部分为填补前的分布,蓝色部分为填补后的分布。这些图有助于观察中位数填补是否改变了原始数据的分布特征,并验证填补方法的合理性。
3. 众数填补
众数填补通常用于类别数据。它用类别中出现次数最多的值(众数)来填补缺失值,适用于离散型(分类)特征。
公式推理
设特征 ,其中 个值缺失。将非缺失值排序为频率计数,选择频率最高的类别作为填补值:
其中 是类别 在数据集中出现的次数。
优缺点:
优点:简单且不需要假设数据的分布,适用于分类特征。 缺点:如果类别分布较为均匀,众数可能不足以代表数据的趋势,且它可能导致类别不均衡。
Python案例
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# 生成虚拟数据集
np.random.seed(42)
data = {
'feature1': np.random.choice(['A', 'B', 'C'], 100),
'feature2': np.random.normal(50, 15, 100),
'feature3': np.random.normal(30, 5, 100)
}
df = pd.DataFrame(data)
# 人为加入缺失值
missing_indices = np.random.choice(df.index, size=20, replace=False)
df.loc[missing_indices, 'feature1'] = np.nan
# 填补前的可视化
plt.figure(figsize=(15, 10))
plt.subplot(2, 2, 1)
sns.histplot(df['feature1'].dropna(), color='skyblue', kde=False)
plt.title('Missing Data - Feature1 Histogram')
plt.subplot(2, 2, 2)
sns.boxplot(x=df['feature1'].dropna(), y=df['feature2'], palette='Set2')
plt.title('Missing Data - Feature2 Boxplot by Feature1')
# 众数填补
mode_value = df['feature1'].mode()[0]
df['feature1'].fillna(mode_value, inplace=True)
# 填补后的可视化
plt.subplot(2, 2, 3)
sns.histplot(df['feature1'], color='coral', kde=False)
plt.title('Imputed Data - Feature1 Histogram')
plt.subplot(2, 2, 4)
sns.scatterplot(x=df['feature3'], y=df['feature2'], hue=df['feature1'], palette='viridis')
plt.title('Imputed Data - Feature2 vs Feature3 Scatter')
plt.tight_layout()
plt.show()
直方图:直方图展示了填补前后 feature1
分类的分布,填补前的缺失值导致分布不完整,而填补后直方图完整呈现数据变化。箱线图:用于展示填补前数据的集中趋势和潜在的异常值,有助于观察 feature1
各类别在feature2
上的分布情况。散点图:用于填补后分析 feature2
与feature3
之间的关系,查看不同类别的feature1
在二维空间的分布,以便更全面评估填补的合理性。
4. 随机填补
随机填补从非缺失值的观测中随机抽取一个值来填补缺失值,这种方法能更好地保持原始数据的分布特征,尤其是在数据存在较大方差时。
公式推理
设特征 ,其中 个数据缺失。非缺失数据的集合为 。通过从 中随机选择一个值填补缺失数据:
这保证了填补值与非缺失数据的分布一致。
优缺点:
优点:能较好地保留数据的方差和分布特征,避免了其他方法可能带来的偏倚。 缺点:由于填补是随机的,它会引入噪声,且不保证每次填补的结果一致。
Python案例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子确保可重复性
np.random.seed(0)
# 生成虚拟数据集
n = 200 # 数据量
data = pd.DataFrame({
'Feature1': np.random.normal(50, 10, n), # 正态分布数据
'Feature2': np.random.normal(30, 5, n) # 正态分布数据
})
# 随机引入缺失值
missing_rate = 0.2
data.loc[data.sample(frac=missing_rate).index, 'Feature1'] = np.nan
data.loc[data.sample(frac=missing_rate).index, 'Feature2'] = np.nan
# 随机填补缺失值
data_imputed = data.copy()
for col in data_imputed.columns:
missing = data_imputed[col].isnull()
data_imputed.loc[missing, col] = np.random.choice(data_imputed[col].dropna(), size=missing.sum())
# 设置绘图风格
sns.set(style="whitegrid")
plt.figure(figsize=(16, 10))
# 1. 缺失值填补前后的直方图
plt.subplot(2, 2, 1)
sns.histplot(data['Feature1'], color='blue', kde=True, label='Original (Feature1)', alpha=0.6, bins=15)
sns.histplot(data_imputed['Feature1'], color='orange', kde=True, label='Imputed (Feature1)', alpha=0.6, bins=15)
plt.title("Distribution Before and After Imputation (Feature1)")
plt.legend()
plt.subplot(2, 2, 2)
sns.histplot(data['Feature2'], color='green', kde=True, label='Original (Feature2)', alpha=0.6, bins=15)
sns.histplot(data_imputed['Feature2'], color='red', kde=True, label='Imputed (Feature2)', alpha=0.6, bins=15)
plt.title("Distribution Before and After Imputation (Feature2)")
plt.legend()
# 2. 缺失值填补前后的箱线图
plt.subplot(2, 2, 3)
sns.boxplot(data=[data['Feature1'], data_imputed['Feature1']], palette=['blue', 'orange'])
plt.xticks([0, 1], ['Original (Feature1)', 'Imputed (Feature1)'])
plt.title("Box Plot Before and After Imputation (Feature1)")
plt.subplot(2, 2, 4)
sns.boxplot(data=[data['Feature2'], data_imputed['Feature2']], palette=['green', 'red'])
plt.xticks([0, 1], ['Original (Feature2)', 'Imputed (Feature2)'])
plt.title("Box Plot Before and After Imputation (Feature2)")
# 3. 散点图对比
plt.figure(figsize=(8, 6))
sns.scatterplot(x=data['Feature1'], y=data['Feature2'], color='blue', label='Original Data', alpha=0.6)
sns.scatterplot(x=data_imputed['Feature1'], y=data_imputed['Feature2'], color='orange', label='Imputed Data', alpha=0.6)
plt.title("Scatter Plot Before and After Imputation")
plt.xlabel("Feature1")
plt.ylabel("Feature2")
plt.legend()
plt.tight_layout()
plt.show()
直方图(Histogram):直方图能够清晰展示填补前后的数据分布特征。通过比较填补前后的数据分布形状和集中趋势,可以观察随机填补是否显著改变了数据分布。如果填补后的分布形状和原始数据分布相似,那么随机填补的效果较好。
箱线图(Box Plot):箱线图是用于分析数据分布、集中趋势和异常值的有力工具。通过对比填补前后的箱线图,能直观地看到数据的中位数、四分位范围及潜在异常值,帮助判断随机填补是否改变了数据的集中趋势和离散性。
散点图(Scatter Plot):散点图用于观察两个特征之间的关系。在填补数据后绘制散点图,可以判断填补是否对数据整体的分布趋势产生了偏移。
5. 常数填补
常数填补是最简单的一种方法,它将所有缺失值替换为一个固定的常数(如零、一个特殊的标记或其他有意义的值)。这种方法适用于缺失值的原因是数据缺失的特殊含义,例如某些标记(如0表示“无”)。
公式推理
设定一个常数 填补所有缺失值:
例如,若缺失值表示“无”,则用 进行填补。
优缺点:
优点:非常简单且实现快速,适用于缺失值具有特殊含义的场景。 缺点:此方法对数据分布影响较大,会引入不必要的偏倚,尤其在数据呈现较大方差时效果不佳。
Python案例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import random
# 设置随机种子以保证结果的可重复性
np.random.seed(0)
# 生成虚拟数据集
n = 100
data = {
'Feature1': np.random.normal(50, 10, n), # 正态分布数据
'Feature2': np.random.normal(30, 5, n), # 正态分布数据
}
# 创建DataFrame
df = pd.DataFrame(data)
# 在 Feature1 和 Feature2 中引入一些随机缺失值
missing_indices = random.sample(range(n), 20)
df.loc[missing_indices, 'Feature1'] = np.nan
missing_indices = random.sample(range(n), 15)
df.loc[missing_indices, 'Feature2'] = np.nan
# 创建填补前的图形
fig, axs = plt.subplots(2, 3, figsize=(18, 12), sharey=False)
fig.suptitle('Constant Imputation Analysis', fontsize=18)
# 填补前的数据分析
sns.scatterplot(x='Feature1', y='Feature2', data=df, color="blue", ax=axs[0, 0])
axs[0, 0].set_title('Scatter Plot (Before Imputation)', fontsize=14)
sns.histplot(df['Feature1'], color="purple", kde=True, ax=axs[0, 1])
axs[0, 1].set_title('Histogram of Feature1 (Before Imputation)', fontsize=14)
sns.boxplot(y='Feature1', data=df, color="orange", ax=axs[0, 2])
axs[0, 2].set_title('Box Plot of Feature1 (Before Imputation)', fontsize=14)
# 使用常数值填补
fill_value = 40
df_filled = df.fillna(fill_value)
# 填补后的数据分析
sns.scatterplot(x='Feature1', y='Feature2', data=df_filled, color="green", ax=axs[1, 0])
axs[1, 0].set_title('Scatter Plot (After Imputation)', fontsize=14)
sns.histplot(df_filled['Feature1'], color="red", kde=True, ax=axs[1, 1])
axs[1, 1].set_title('Histogram of Feature1 (After Imputation)', fontsize=14)
sns.boxplot(y='Feature1', data=df_filled, color="yellow", ax=axs[1, 2])
axs[1, 2].set_title('Box Plot of Feature1 (After Imputation)', fontsize=14)
# 显示图形
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
散点图(填补前后对比):展示特征值在二维空间的分布,帮助判断填补是否对原分布有显著改变。 直方图(填补前后对比):通过直方图观察数据频率的分布差异,检测常数填补是否使数据有偏。 箱线图(填补前后对比):展示数据集的集中趋势、分布范围和异常值,便于观察填补对数据集中值和极值的影响。
6. 前向填充
前向填充常用于时间序列数据。它用上一个有效的观测值填充缺失的值,保持时间序列的连续性。这种方法假设缺失值会继承其前一个观测值的特征。
公式推理
设时间序列数据为 ,若 缺失,则填补值为:
这种方法保证了数据的连续性,特别是对于长期趋势或者周期性的时间序列数据。
优缺点:
优点:简单且适用于时间序列数据,保持了序列的连续性。 缺点:假设缺失值与前值相似,不适用于存在剧烈变化的时间序列。
Python案例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子以获得可重复结果
np.random.seed(42)
# 生成虚拟时间序列数据
date_range = pd.date_range(start="2023-01-01", periods=100, freq='D')
temperature_data = np.random.normal(loc=20, scale=5, size=len(date_range))
# 引入随机缺失值
missing_indices = np.random.choice(len(date_range), size=20, replace=False)
temperature_data[missing_indices] = np.nan
# 创建DataFrame
df = pd.DataFrame({"Date": date_range, "Temperature": temperature_data})
df.set_index("Date", inplace=True)
# 使用前向填充填补缺失值
df['Temperature_filled'] = df['Temperature'].fillna(method='ffill')
# 创建图表
plt.figure(figsize=(14, 8))
sns.set(style="whitegrid", palette="bright")
# 子图1:原始数据与填补后数据的时间序列图
plt.subplot(2, 2, 1)
plt.plot(df.index, df['Temperature'], label="Original Data", color="blue", marker='o')
plt.plot(df.index, df['Temperature_filled'], label="Filled Data", color="orange", linestyle='--', marker='x')
plt.title("Time Series of Original vs Filled Data")
plt.xlabel("Date")
plt.ylabel("Temperature (°C)")
plt.legend()
# 子图2:缺失值位置图
plt.subplot(2, 2, 2)
missing_data_indicator = df['Temperature'].isna().astype(int)
plt.plot(df.index, missing_data_indicator, label="Missing Data", color="red", marker='|')
plt.title("Missing Value Positions")
plt.xlabel("Date")
plt.ylabel("Missing Indicator (1 = Missing)")
# 子图3:填补前后的数据分布对比
plt.subplot(2, 1, 2)
sns.histplot(df['Temperature'], color="blue", kde=True, label="Original Data", alpha=0.6)
sns.histplot(df['Temperature_filled'], color="orange", kde=True, label="Filled Data", alpha=0.6)
plt.title("Distribution of Original vs Filled Data")
plt.xlabel("Temperature (°C)")
plt.ylabel("Density")
plt.legend()
# 显示图形
plt.tight_layout()
plt.show()
时间序列图:这个图展示了原始数据和填补后的数据随时间的变化。通过比较两条曲线,我们可以直观地观察到填补前后的趋势差异,尤其是在缺失值较多的位置。这种趋势分析对于了解填补后数据的连续性和完整性非常重要。
缺失值位置图:通过这张图可以明确看到缺失值在整个时间序列中的分布。对于有缺失数据的时间序列数据,这种缺失值位置图可以帮助我们理解缺失值的数量和分布,从而更好地评估填补方法的效果。
数据分布图:填补前后数据的分布图可以展示填补后数据是否产生了显著的偏移或集中倾向。这对于确保填补数据不会带来新的偏差非常重要,尤其是在数据建模时,保持数据的真实性和分布的一致性是关键。
7. 后向填充
后向填充与前向填充类似,但它用后一个有效观测值填补缺失数据。适用于时间序列数据,尤其是当后续值对当前值影响较大时。
公式推理
设时间序列数据为 ,若 缺失,则填补值为:
优缺点:
优点:适用于时间序列中的趋势延续,特别是在缺失值靠近数据末尾时。 缺点:假设数据之间变化较小,不适用于存在周期波动或剧烈变化的数据。
Python案例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子保证结果一致性
np.random.seed(0)
# 生成时间序列数据(带缺失值)
date_range = pd.date_range(start="2024-01-01", periods=100, freq="D")
temperature_data = np.random.normal(loc=20, scale=5, size=len(date_range))
# 随机引入缺失值
nan_indices = np.random.choice(len(temperature_data), size=20, replace=False)
temperature_data[nan_indices] = np.nan
# 创建DataFrame
df = pd.DataFrame({"Date": date_range, "Temperature": temperature_data})
df.set_index("Date", inplace=True)
# 使用后向填充来填补缺失值
df['Temperature_filled'] = df['Temperature'].fillna(method='bfill')
# 设置图形的颜色和风格
sns.set_palette("bright")
plt.figure(figsize=(15, 10))
# 子图1: 原始数据和填补后数据的时间序列图
plt.subplot(3, 1, 1)
plt.plot(df.index, df['Temperature'], label="Original Temperature", color="blue", marker='o', linestyle="--")
plt.plot(df.index, df['Temperature_filled'], label="Backward-Filled Temperature", color="red", marker='x')
plt.title("Original and Backward-Filled Temperature Data")
plt.xlabel("Date")
plt.ylabel("Temperature (°C)")
plt.legend()
# 子图2: 数据分布直方图
plt.subplot(3, 1, 2)
sns.histplot(df['Temperature'], kde=True, color="blue", label="Original Data", bins=15)
sns.histplot(df['Temperature_filled'], kde=True, color="red", label="Filled Data", bins=15)
plt.title("Distribution of Original and Filled Temperature Data")
plt.xlabel("Temperature (°C)")
plt.ylabel("Frequency")
plt.legend()
# 子图3: 填补前后数据的差值图
plt.subplot(3, 1, 3)
difference = df['Temperature_filled'] - df['Temperature']
plt.plot(df.index, difference, label="Difference After Filling", color="purple", marker='*', linestyle=":")
plt.axhline(0, color='gray', linestyle='--')
plt.title("Difference Between Original and Filled Data")
plt.xlabel("Date")
plt.ylabel("Temperature Difference (°C)")
plt.legend()
# 调整布局并显示图形
plt.tight_layout()
plt.show()
时间序列图:可以直观展示填补前后数据的变化,观察缺失数据的分布和填补后的趋势。这种图形有助于判断填补后的数据是否合理。 数据分布直方图:用于查看填补操作是否改变了数据的分布特征。通过比较填补前后的数据分布,可以评估后向填充的效果是否在数据分布上保持一致。 差值图:展示填补前后每个数据点的差值,有助于量化填补的具体影响。尤其在缺失数据较多时,这种图表可以帮助确定填补是否对整体数据有偏移。
8. K-最近邻填补
KNN填补方法基于样本间的相似性,通过计算缺失值与其他观测值的相似度(如欧几里得距离或曼哈顿距离)来填补缺失值。它使用与缺失值最相似的 个邻居的值来填充缺失数据。
公式推理
计算缺失样本与其他样本的距离(如欧几里得距离)。 选择 个距离最近的样本作为邻居。 3
. 将邻居的均值或众数填补缺失值。
例如,对于缺失值 ,通过以下公式来计算与其他点的距离:
然后,使用 个最近邻的平均值来填补:
优缺点:
优点:考虑了样本间的相似性,适用于数据存在某种规律或模式的情况。 缺点:计算复杂度高,尤其是在大规模数据集上,且对噪声数据较为敏感。
Python案例
import numpy as np
import pandas as pd
from sklearn.impute import KNNImputer
import matplotlib.pyplot as plt
import seaborn as sns
# 设置随机种子保证实验可重复
np.random.seed(42)
# 生成虚拟数据集(10个特征,100个样本,20%的缺失率)
n_samples = 100
n_features = 10
data = np.random.rand(n_samples, n_features)
# 随机生成缺失值位置
missingness = np.random.choice([1, 0], size=data.shape, p=[0.2, 0.8]).astype(bool)
data[missingness] = np.nan
# 创建DataFrame以便处理
columns = [f'feature_{i+1}' for i in range(n_features)]
df = pd.DataFrame(data, columns=columns)
# 查看缺失数据情况
print("缺失数据比例:")
print(df.isnull().mean())
# K-最近邻填补
imputer = KNNImputer(n_neighbors=5)
data_imputed = imputer.fit_transform(df)
df_imputed = pd.DataFrame(data_imputed, columns=columns)
# 绘图:缺失值填补前后的数据分布和相关性
fig, axes = plt.subplots(2, 2, figsize=(16, 12))
fig.suptitle('K-Nearest Neighbors Imputation Analysis', fontsize=20)
# 填补前的数据分布图
sns.histplot(df.melt(value_name='Value'), x='Value', hue='variable', multiple="stack", palette="viridis", ax=axes[0, 0])
axes[0, 0].set_title('Data Distribution Before Imputation', fontsize=15)
axes[0, 0].set_xlabel("Feature Values")
axes[0, 0].set_ylabel("Frequency")
# 填补后的数据分布图
sns.histplot(df_imputed.melt(value_name='Value'), x='Value', hue='variable', multiple="stack", palette="plasma", ax=axes[0, 1])
axes[0, 1].set_title('Data Distribution After Imputation', fontsize=15)
axes[0, 1].set_xlabel("Feature Values")
axes[0, 1].set_ylabel("Frequency")
# 填补前的相关性矩阵热图
sns.heatmap(df.corr(), cmap="coolwarm", annot=True, fmt=".2f", cbar_kws={'shrink': .8}, ax=axes[1, 0])
axes[1, 0].set_title('Correlation Before Imputation', fontsize=15)
# 填补后的相关性矩阵热图
sns.heatmap(df_imputed.corr(), cmap="coolwarm", annot=True, fmt=".2f", cbar_kws={'shrink': .8}, ax=axes[1, 1])
axes[1, 1].set_title('Correlation After Imputation', fontsize=15)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()
缺失值填补前后的数据分布图:使用了堆叠柱状图来比较数据填补前后的分布情况,这能让我们直观地观察到填补是否引入了异常值或对数据分布产生影响。
相关性热图(填补前后对比):相关性矩阵能够展示各特征之间的关系。通过填补前后的相关性热图对比,可以分析填补是否改变了特征之间的相互关系,这是评估填补效果的关键指标之一。
9. 多重插补法
多重插补法通过多次插补和预测缺失值来估算数据的分布不确定性,生成多个插补结果,并通过综合这些结果来最终填补缺失值。MICE方法通过回归建模实现对缺失数据的填补,适用于多变量缺失情况。
公式推理
对每一个缺失值通过回归模型进行插补。 使用多个回归模型进行多次插补,得到多个填补结果。 对所有插补结果进行合并,通常使用加权平均或最小方差估计来计算最终填补值。
优缺点:
优点:有效估计缺失数据的不确定性,生成多个插补数据集,提高填补结果的准确性。 缺点:计算复杂,时间开销大,不适合大规模数据集。
Python案例
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
from sklearn.linear_model import BayesianRidge
# 设置随机种子和绘图风格
np.random.seed(0)
sns.set(style="whitegrid")
# 创建虚拟数据集
n = 200
age = np.random.normal(40, 10, n)
income = np.random.normal(50000, 15000, n)
education_level = np.random.choice(['High School', 'Bachelor', 'Master', 'PhD'], n)
# 制造缺失数据
age[np.random.choice(n, 40, replace=False)] = np.nan # 年龄有缺失值
income[np.random.choice(n, 50, replace=False)] = np.nan # 收入有缺失值
# 创建DataFrame
df = pd.DataFrame({
'Age': age,
'Income': income,
'Education': education_level
})
# 设置MICE多重插补
imp = IterativeImputer(estimator=BayesianRidge(), max_iter=10, random_state=0, sample_posterior=True)
imputed_data = [imp.fit_transform(df[['Age', 'Income']]) for _ in range(5)]
# 将插补数据存为DataFrame
imputed_dfs = [pd.DataFrame(data, columns=['Age', 'Income']) for data in imputed_data]
# 绘制数据分析图
fig, axs = plt.subplots(2, 2, figsize=(16, 12))
colors = sns.color_palette("bright", 5)
# 1. 原始数据散点图
sns.scatterplot(data=df, x='Age', y='Income', ax=axs[0, 0], color='gray', label='Original Data', alpha=0.5)
axs[0, 0].set_title('Original Data Scatter Plot (with missing values)')
# 2. 插补数据散点图(叠加不同插补结果)
for i, imp_df in enumerate(imputed_dfs):
sns.scatterplot(x=imp_df['Age'], y=imp_df['Income'], ax=axs[0, 0], color=colors[i], label=f'Imputed Data {i+1}', alpha=0.7)
axs[0, 0].legend()
axs[0, 0].set_xlabel('Age')
axs[0, 0].set_ylabel('Income')
# 3. 箱线图比较插补前后的Age和Income
# 创建原始数据的melted格式,用于箱线图
df_melted = pd.melt(df[['Age', 'Income']], var_name='variable', value_name='value')
# 绘制原始和插补数据的箱线图
sns.boxplot(data=df_melted, x='variable', y='value', ax=axs[0, 1], color='gray', showfliers=False, width=0.4)
for i, imp_df in enumerate(imputed_dfs):
melted = pd.melt(imp_df, value_vars=['Age', 'Income'], var_name='variable', value_name='value')
# 使用 RGBA 颜色格式,增加透明度
sns.boxplot(data=melted, x='variable', y='value', ax=axs[0, 1], color=colors[i] + (0.5,), showfliers=False, width=0.4)
axs[0, 1].set_title('Box Plot of Original and Imputed Data')
# 4. 密度图显示不同插补结果的分布差异
for i, imp_df in enumerate(imputed_dfs):
sns.kdeplot(imp_df['Age'], ax=axs[1, 0], color=colors[i], label=f'Imputed Age {i+1}', fill=True, alpha=0.3)
sns.kdeplot(imp_df['Income'], ax=axs[1, 1], color=colors[i], label=f'Imputed Income {i+1}', fill=True, alpha=0.3)
axs[1, 0].set_title('Density Plot of Imputed Age')
axs[1, 1].set_title('Density Plot of Imputed Income')
axs[1, 0].legend()
axs[1, 1].legend()
plt.tight_layout()
plt.show()
多重插补:使用 IterativeImputer
执行多重插补,并生成5个不同的插补数据集,每次插补后的数据略有差异,展示了多重插补法对缺失值填补的不同可能性。散点图:第一个子图叠加展示原始数据与插补数据的分布,便于观察填补后的数值范围是否合理。 箱线图:第二个子图对比插补前后数据的分布情况,以便发现异常值或偏差。 密度图:第三和第四子图展示不同插补结果的分布差异,可以帮助理解多重插补数据集的均匀性和插补不确定性。
10. 回归填补
回归填补通过其他变量的回归模型来预测缺失值。该方法利用已知值的相关性,采用回归分析填补缺失数据。
公式推理
设定模型 ,其中 为自变量, 为因变量。通过已知数据拟合回归模型,预测缺失值 :
优缺点:
优点:通过回归模型对缺失值进行合理预测,可以较好地保留数据的结构和相关性。 缺点:需要假设数据之间有线性关系,且对于非线性数据不适用。
Python案例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.ensemble import RandomForestRegressor
from sklearn.impute import SimpleImputer
# 设置随机种子
np.random.seed(42)
# 生成虚拟数据集
n_samples = 200
X1 = np.random.normal(0, 1, n_samples)
X2 = 0.5 * X1 + np.random.normal(0, 0.1, n_samples)
y = 2 * X1 + 3 * X2 + np.random.normal(0, 0.5, n_samples)
# 创建DataFrame
data = pd.DataFrame({'X1': X1, 'X2': X2, 'y': y})
# 引入缺失值
missing_rate = 0.2
missing_indices = np.random.choice(n_samples, int(n_samples * missing_rate), replace=False)
data.loc[missing_indices, 'X2'] = np.nan
# 使用随机森林回归进行填补
X = data[['X1', 'X2']]
y = data['y']
imputer = SimpleImputer(strategy='mean')
X_filled = imputer.fit_transform(X) # 使用均值填补缺失值以便用于训练回归模型
# 建立随机森林回归模型并填补缺失值
regressor = RandomForestRegressor()
regressor.fit(X_filled[:, 0].reshape(-1, 1), X_filled[:, 1]) # 用X1预测X2
# 找到 X2 列中缺失值的位置
missing_X2_indices = data['X2'].isnull()
# 对缺失值对应的 X1 进行预测
X1_missing = data.loc[missing_X2_indices, 'X1'].values.reshape(-1, 1)
X2_predicted = regressor.predict(X1_missing)
# 将预测值填充回原始数据中的缺失位置
data_filled = data.copy()
data_filled.loc[missing_X2_indices, 'X2'] = X2_predicted
# 绘制图形
plt.figure(figsize=(12, 6))
# 绘制填补前后X2的直方图
plt.subplot(1, 2, 1)
plt.hist(data['X2'].dropna(), bins=20, color='skyblue', alpha=0.6, label='Before Imputation')
plt.hist(data_filled['X2'], bins=20, color='salmon', alpha=0.6, label='After Imputation')
plt.xlabel('X2')
plt.ylabel('Frequency')
plt.title('Histogram of X2 Before and After Imputation')
plt.legend()
# 绘制填补前后数据的散点图
plt.subplot(1, 2, 2)
plt.scatter(data['X1'], data['X2'], color='skyblue', alpha=0.6, label='Before Imputation')
plt.scatter(data_filled['X1'], data_filled['X2'], color='salmon', alpha=0.6, label='After Imputation')
plt.xlabel('X1')
plt.ylabel('X2')
plt.title('Scatter Plot of X1 vs X2 Before and After Imputation')
plt.legend()
plt.tight_layout()
plt.show()
数据生成:首先生成一个包含特征 X1
、X2
和目标变量y
的数据集。X2
是X1
的线性组合并加入了一些噪声。引入缺失值:在 X2
列中随机插入缺失值,以模拟现实中常见的数据缺失问题。回归填补:使用 X1
预测X2
,通过X1
建立随机森林回归模型,然后填补缺失的X2
数据。绘图:使用直方图和散点图来比较填补前后数据的分布和结构。
最后