关于Pandas DataFrame.interpolate()函数的插值公式及自定义实现的技术问询
首先得说清楚:interpolate() 不是靠单一公式运行的函数——它的行为完全由你指定的 method 参数决定,官方文档没把所有公式列全是因为不同方法对应不同的插值逻辑,下面我把最常用的几种方法的核心公式和逻辑给你理清楚:
常见插值方法的公式/逻辑
Linear(线性插值,默认):这是最常用的类型,核心就是两点之间的直线插值。假设你要填充位置
x处的NaN,左右最近的有效值是x0(对应值y0)和x1(对应值y1),公式是:y = y0 + (y1 - y0) * (x - x0) / (x1 - x0)注意:默认情况下,Pandas是按行/列的位置索引计算的(不是DataFrame的列名/行标签),除非你指定
method='index'或method='time',这时候会用实际的索引值来计算权重。Nearest(最近邻插值):没有复杂公式,直接取距离当前NaN最近的有效值填充即可。
Quadratic/Cubic(二次/三次插值):这类属于多项式插值,会用周围多个点拟合出二次或三次曲线,再计算NaN位置的值。底层一般调用Scipy的多项式插值实现,公式就是对应的多项式方程(比如三次插值是
y = ax³ + bx² + cx + d,通过周围点求解系数)。Time(时间插值):专门针对时间索引的DataFrame,本质是线性插值,但会用时间差代替位置差计算权重。比如两个时间点间隔2天,中间第1天的插值结果就是
(y1 - y0)*1/2 + y0。
自定义插值逻辑的方案
如果内置方法不符合你的需求,有几种灵活的实现方式:
1. 逐行/列自定义插值函数
用 apply() 遍历每一行或列,结合Scipy的插值工具或者自己编写逻辑。比如针对你给出的示例DataFrame,假设你想实现基于列名索引的加权线性插值(不是按位置),可以这么做:
首先导入所需库:
import pandas as pd import numpy as np from scipy.interpolate import interp1d
然后定义自定义插值函数:
def custom_interpolate(row): # 获取当前行非NaN的列索引(转为整数)和对应值 valid_cols = row.dropna().index.astype(int) valid_vals = row.dropna().values if len(valid_cols) < 2: # 少于两个有效值无法插值,返回原行 return row # 创建插值函数,这里用线性,你可以替换成自己的逻辑 f = interp1d(valid_cols, valid_vals, kind='linear', fill_value='extrapolate') # 对所有列应用插值 interpolated_vals = f(row.index.astype(int)) return pd.Series(interpolated_vals, index=row.index)
最后应用到你的DataFrame:
df = pd.DataFrame([[np.nan, 10, np.nan, 20, 17, np.nan, np.nan, 14, np.nan, 10, np.nan], [5, np.nan, 0, np.nan, np.nan, np.nan, 5, np.nan, 10, np.nan, np.nan], [3, np.nan, np.nan, np.nan, np.nan, np.nan, 2, np.nan, np.nan, np.nan, np.nan], [np.nan, np.nan, np.nan, 3, 4, 5, np.nan, 7, 8, 9, np.nan]], columns=['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10']) # 按行应用自定义插值 df_interpolated = df.apply(custom_interpolate, axis=1)
2. 手动填充NaN值
如果你的逻辑非常特殊(比如基于业务规则的加权、多列联动插值),可以直接遍历DataFrame的每个NaN,根据周围的值手动计算填充。比如:
for idx, row in df.iterrows(): # 获取当前行的NaN位置 nan_cols = row[row.isna()].index for col in nan_cols: col_int = int(col) # 找左边最近的非NaN值 left_vals = row[:col].dropna() # 找右边最近的非NaN值 right_vals = row[col:].dropna() if not left_vals.empty and not right_vals.empty: left_col = int(left_vals.index[-1]) left_val = left_vals.iloc[-1] right_col = int(right_vals.index[0]) right_val = right_vals.iloc[0] # 自定义加权公式:权重与列距成反比 weight_left = 1/(col_int - left_col) weight_right = 1/(right_col - col_int) total_weight = weight_left + weight_right df.loc[idx, col] = (left_val * weight_right + right_val * weight_left)/total_weight elif not left_vals.empty: # 只有左边有值,填充左边最后一个值 df.loc[idx, col] = left_vals.iloc[-1] elif not right_vals.empty: # 只有右边有值,填充右边第一个值 df.loc[idx, col] = right_vals.iloc[0]
3. 封装复用自定义逻辑(进阶)
如果需要频繁使用自定义插值逻辑,可以把它封装成一个函数,而不是每次都写重复代码。比如:
def custom_interpolate_df(df, axis=1): return df.apply(custom_interpolate, axis=axis)
之后直接调用 custom_interpolate_df(df) 即可。
总结
- 先明确你需要的插值逻辑,对应到Pandas内置的
method参数,如果能匹配上直接使用即可; - 如果内置方法不符合需求,用
apply结合自定义函数或手动填充是最灵活的方式; - 涉及复杂曲线拟合时,优先用Scipy的插值工具,再结合Pandas处理数据结构。
内容的提问来源于stack exchange,提问作者Murilo

