插值与高级缺失值填补
使用 interpolate() 进行平滑的基于时间的填充,并理解何时适合使用均值或中位数填补。
插值与高级缺失值填补 是 CoddyKit 上的免费 Pandas & NumPy Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Pandas & NumPy Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Pandas & NumPy Academy 课程共包含 4 节课。
插值何时优于 fillna()
向前填充和向后填充只会携带最近的已知值,而不会考虑数据的趋势。插值通过假设已知值之间平滑过渡来估算缺失值——例如,如果周一的温度为 20°C、周五为 30°C,插值会估算周三的温度为 25°C。对于传感器数据、价格或人口数量等平稳变化的信号,这样得到的插补结果更加符合实际。
import pandas as pd
import numpy as np
df = pd.DataFrame({
'day': [1, 2, 3, 4, 5],
'temp': [20.0, np.nan, np.nan, np.nan, 30.0]
})
# Linear interpolation fills gaps smoothly
df['temp_interp'] = df['temp'].interpolate(method='linear')
print(df)
# day temp temp_interp
# 0 1 20.0 20.0
# 1 2 NaN 22.5
# 2 3 NaN 25.0
# 3 4 NaN 27.5
# 4 5 30.0 30.0interpolate() 方法
Pandas 的 interpolate() 支持多种方法。最常见的包括:'linear'(在已知值之间等距插入)、'time'(设置 DatetimeIndex 时考虑不等的时间间隔)、'polynomial'(拟合多项式曲线,需要传入 order 参数)以及 'spline'(平滑的分段多项式)。线性插值是最稳妥的默认选项;高阶方法可能会对较小的间隔过拟合。
import pandas as pd
import numpy as np
s = pd.Series([0, np.nan, np.nan, 8.0])
print('linear:', s.interpolate('linear').tolist())
# [0.0, 2.6666..., 5.3333..., 8.0]
print('polynomial(2):', s.interpolate('polynomial', order=2).tolist())
# [0.0, 2.222..., 5.111..., 8.0]使用 DatetimeIndex 进行 time 插值
当 Series 具有不等时间间隔的 DatetimeIndex(例如只包含工作日而缺少周末)时,method='time' 会按照实际经过的时间成比例地进行插值,而不只是依据位置。这比线性插值更准确,因为线性插值会将每一行视为等间隔,而不考虑日历上的间隔。
import pandas as pd
import numpy as np
# Unequal gaps: Jan 1, Jan 3, Jan 10
idx = pd.to_datetime(['2024-01-01', '2024-01-03', '2024-01-10'])
s = pd.Series([100.0, np.nan, 170.0], index=idx)
# linear treats gaps as equal (position-based)
print(s.interpolate('linear').tolist()) # [100.0, 135.0, 170.0]
# time accounts for actual day count
# Jan 1 to Jan 3 = 2 days, Jan 1 to Jan 10 = 9 days
# fraction: 2/9 of the way from 100 to 170
print(s.interpolate('time').tolist()) # [100.0, 115.55..., 170.0]限制插值范围
与 ffill() 一样,interpolate() 接受 limit 参数,用于限制连续填充的 NaN 位置数量。超过限制的 NaN 会继续保持缺失。这可以防止插值跨越非常长的间隔,因为在这种情况下真实值可能完全无法确定——长间隔应标记出来,交由人工检查。
import pandas as pd
import numpy as np
s = pd.Series([1.0, np.nan, np.nan, np.nan, np.nan, 10.0])
# Only fill the first 2 NaN positions
filled = s.interpolate('linear', limit=2)
print(filled.tolist())
# [1.0, 2.8, 4.6, NaN, NaN, 10.0]选择均值还是中位数插补
均值和中位数插补虽然简单,但各有重要的取舍。均值容易受到异常值影响——少数特别大的值会将均值拉高,使其不适合收入或房价这类右偏分布的填充。中位数对异常值更稳健,因此更适合偏斜列。只有当数据大致对称且没有极端异常值时,才应使用均值。
import pandas as pd
import numpy as np
# Skewed income data with an outlier
income = pd.Series([30000, 35000, 32000, 1_000_000, np.nan])
print('Mean: ', income.mean()) # ~274000 — pulled by outlier
print('Median:', income.median()) # ~33500 — robust choice
# Prefer median for skewed data
filled = income.fillna(income.median())
print(filled.tolist())
# [30000, 35000, 32000, 1000000, 33500.0]KNN 插补概念
最近邻(KNN)插补会使用相似度最高的 k 行的均值(或加权均值)替换缺失值,相似度通过非缺失特征之间的距离衡量。这是一种多变量策略——它会利用其他列的信息来辅助填充;当各特征相关时,它比单列均值插补更加准确。
Scikit-learn 的 KNNImputer 可以直接与 NumPy 数组和 Pandas DataFrames 集成。
import pandas as pd
import numpy as np
from sklearn.impute import KNNImputer
df = pd.DataFrame({
'age': [25, 35, np.nan, 45],
'income': [50000, 70000, 60000, np.nan]
})
imputer = KNNImputer(n_neighbors=2)
df_imputed = pd.DataFrame(
imputer.fit_transform(df),
columns=df.columns
)
print(df_imputed.round(1))
# age income
# 0 25.0 50000.0
# 1 35.0 70000.0
# 2 30.0 60000.0 <- filled from neighbours
# 3 45.0 65000.0 <- filled from neighbours使用 IterativeImputer 进行多重插补
多重插补是统计研究中处理缺失数据的黄金标准。Scikit-learn 的 IterativeImputer 实现了 MICE(链式方程多重插补)方法:它将每个包含缺失值的列建模为其他列的函数,并不断迭代插补,直到数值收敛。与单列策略相比,这种方法能更好地捕捉特征之间的关系。
import pandas as pd
import numpy as np
from sklearn.experimental import enable_iterative_imputer # noqa
from sklearn.impute import IterativeImputer
df = pd.DataFrame({
'x1': [1, 2, np.nan, 4, 5],
'x2': [2, np.nan, 6, 8, 10],
'y': [3, 5, 7, np.nan, 11]
})
imp = IterativeImputer(max_iter=10, random_state=0)
df_filled = pd.DataFrame(imp.fit_transform(df), columns=df.columns)
print(df_filled.round(2))表示缺失数据的指示列
与其隐藏某个值曾被插补这一事实,不如添加一个二元指示列,标记哪些行在插补前包含缺失值,这是一种良好实践。这样,后续模型就可以从缺失模式本身中学习——有时,一个值是否缺失与该值本身一样具有预测性。
import pandas as pd
import numpy as np
df = pd.DataFrame({'salary': [50000, np.nan, 70000, np.nan, 90000]})
# Add indicator before filling
df['salary_was_missing'] = df['salary'].isna().astype(int)
# Then fill
df['salary'] = df['salary'].fillna(df['salary'].median())
print(df)
# salary salary_was_missing
# 0 50000.0 0
# 1 70000.0 1
# 2 70000.0 0
# 3 70000.0 1
# 4 90000.0 0插补与数据泄漏
机器学习管道中的一条关键规则是:只在训练集上计算插补统计量(均值、中位数、众数),然后将相同的值应用于测试集。在拆分数据之前对完整数据集计算统计量会造成数据泄漏——模型在训练期间间接看到了测试数据,导致性能估计偏高。请始终在训练数据上拟合插补器,然后对训练集和测试集执行转换。
import pandas as pd
import numpy as np
from sklearn.impute import SimpleImputer
from sklearn.model_selection import train_test_split
df = pd.DataFrame({'feature': [1, 2, np.nan, 4, np.nan, 6, 7, 8]})
train, test = train_test_split(df, test_size=0.25, random_state=0)
# Fit ONLY on training data
imp = SimpleImputer(strategy='mean')
train['feature'] = imp.fit_transform(train[['feature']])
# Transform test using training statistics
test['feature'] = imp.transform(test[['feature']])
print('Train mean used:', imp.statistics_[0])以可视化方式比较插补策略
应用多种插补方法后,可以将原始列和插补列的分布并排绘制,以比较不同方法的影响。良好的插补应尽可能保留原始分布的形状(均值、方差、偏度)。均值插补会使分布变窄;KNN 和 MICE 往往能更好地保留原分布。
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(0)
original = np.random.exponential(scale=5, size=200)
with_nan = original.copy()
with_nan[np.random.choice(200, 40, replace=False)] = np.nan
s = pd.Series(with_nan)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
s.dropna().hist(ax=axes[0], bins=20, title='Original (no NaN)')
s.fillna(s.mean()).hist(ax=axes[1], bins=20, title='Mean-imputed')
plt.tight_layout()
plt.savefig('imputation_comparison.png')
print('Saved comparison chart')选择合适的插补策略
不存在一种对所有情况都最优的插补策略,正确的选择取决于具体背景。对于缺失率较低的简单单变量情况,可使用均值/中位数。对于趋势稳定的时间序列,可使用ffill/bfill。当特征彼此相关,并且您希望利用这些关系时,可使用 KNN 或 IterativeImputer。当缺失值具有明确的业务含义时,可使用领域常量(例如,用 0 表示不存在,用 -1 表示未知)。请始终记录您的选择。
# Decision guide (no runnable code)
#
# Missing < 5% AND data is MCAR? -> Mean/median fill is fine
# Time series with stable trend? -> ffill() with limit
# Features are correlated? -> KNNImputer or IterativeImputer
# Categorical column? -> Mode fill or 'Unknown' sentinel
# Going into ML model? -> Add indicator column + fill
# Research / publication quality? -> Multiple imputation (MICE)
print('Strategy selected based on context')快速检查
检验您对插值和高级插补的理解。
课程回顾
本课您学到了:interpolate() 通过假设已知值之间存在平滑趋势来估算缺失值,method='time' 可以处理 DatetimeIndex 中不等的间隔,而 KNNImputer 和 IterativeImputer 等高级策略会利用其他列的信息进行填充。请始终在插补前添加指示列,并且只在训练集上拟合统计量,以避免数据泄漏。接下来,我们将检查并转换 DataFrame 列的数据类型。
用 AI 导师学习 Python — 免费
在浏览器中编写并运行真实代码,获得全天候 AI 导师的即时帮助,并在网页或应用中继续学习。
- 课程
- 30
- 课程
- 120
常见问题解答
「插值与高级缺失值填补」课时是免费的吗?
是的 — 「插值与高级缺失值填补」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Pandas & NumPy Academy 课程的其余内容,请升级到 CoddyKit PRO。 Pandas & NumPy Academy 课程共包含 4 节课。
「插值与高级缺失值填补」这节课中我会学到什么?
使用 interpolate() 进行平滑的基于时间的填充,并理解何时适合使用均值或中位数填补。 你通过在浏览器中直接运行的动手代码来练习 Pandas & NumPy Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 Pandas & NumPy Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 Pandas & NumPy Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「插值与高级缺失值填补」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 Pandas & NumPy Academy 课中编写并运行代码吗?
能。每节 Pandas & NumPy Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。