用前序非NaN值滚动均值填充NaN的实现与优化问询
需求解析
要实现的逻辑:用前序两个非NaN值的滚动均值填充NaN,且新生成的均值会作为有效值参与下一个NaN的计算。比如示例中:
- 第5行NaN用
(6.0 + 5.0)/2 = 5.5填充 - 第6行NaN用
(5.5 + 6.0)/2 = 5.75填充 - 第7行NaN用
(5.75 + 5.5)/2 = 5.625填充
现有reduce方案拆解
核心参数说明
- 第三个参数
df是reduce的初始操作对象:reduce会从这个原始DataFrame开始,重复执行lambda函数,迭代次数等于第二个参数range(df['col1'].isna().sum())的长度(也就是NaN的总个数,示例中是3次)。 - lambda函数逻辑:每次迭代时,先对当前DataFrame计算滚动2期均值(必须有2个非NaN值),通过
shift(1)把均值移动到下一行的位置,再用这个结果填充当前DataFrame中的NaN。
整体填充流程
以示例的3个NaN为例,逐轮执行:
- 第一次迭代:基于原始df计算滚动均值,得到
[NaN, 2, 3.5, 4.5, 5.5, NaN, NaN, NaN],shift后变为[NaN, NaN, 2, 3.5, 4.5, 5.5, NaN, NaN],用这个值填充第5行的NaN,此时df第5行变为5.5。 - 第二次迭代:基于更新后的df计算滚动均值,得到
[NaN, 2, 3.5, 4.5, 5.5, 5.75, NaN, NaN],shift后填充第6行的NaN,变为5.75。 - 第三次迭代:再次计算滚动均值,得到
[NaN,2,3.5,4.5,5.5,5.75,5.625,NaN],shift后填充第7行的NaN,得到最终结果。
本质是逐轮填充一个NaN,每次用前两个有效值(包括上一轮填充的均值)计算新的均值,循环次数等于NaN总数。
更高效的替代方案
Pandas + Numpy 数组循环方案(性能最优)
直接操作numpy数组,避免多次DataFrame复制,适合大数据量场景:
import pandas as pd import numpy as np df = pd.DataFrame({'col1': [1, 3, 4, 5, 6, np.NaN, np.NaN, np.NaN]}) arr = df['col1'].values # 定位第一个NaN的位置 first_nan_idx = np.argmax(np.isnan(arr)) # 初始化前两个有效值 prev_prev, prev = arr[first_nan_idx-2], arr[first_nan_idx-1] # 迭代填充所有NaN for i in range(first_nan_idx, len(arr)): curr_val = (prev_prev + prev) / 2 arr[i] = curr_val # 更新前两个值,为下一次计算做准备 prev_prev, prev = prev, curr_val df['col1'] = arr
Numpy 纯向量化递推方案(适合NaN数量极大的场景)
观察填充规律,这是一个递推序列,可直接生成填充值:
import numpy as np arr = df['col1'].values nan_count = np.sum(np.isnan(arr)) # 取最后两个非NaN值作为递推起点 x_prev2, x_prev1 = arr[4], arr[3] # 对应示例中的6.0和5.0 # 生成填充序列 fill_vals = np.zeros(nan_count) fill_vals[0] = (x_prev2 + x_prev1) / 2 for i in range(1, nan_count): fill_vals[i] = (fill_vals[i-1] + fill_vals[i-2]) / 2 # 填充到原数组 arr[np.isnan(arr)] = fill_vals df['col1'] = arr
Pandas 简洁写法(用accumulate生成序列)
借助itertools.accumulate生成填充序列,代码更简洁:
from itertools import accumulate def rolling_mean_fill(series): non_nan_vals = series.dropna().tolist() nan_count = len(series) - len(non_nan_vals) # 定义递推规则:输入前两个值,返回(后一个值, 新均值) def gen_next_pair(prev_pair): return (prev_pair[1], (prev_pair[0] + prev_pair[1]) / 2) # 生成所有填充值 fill_seq = [pair[1] for pair in accumulate( [(non_nan_vals[-2], non_nan_vals[-1])]*nan_count, gen_next_pair )] return pd.Series(non_nan_vals + fill_seq, index=series.index) df['col1'] = rolling_mean_fill(df['col1'])
内容的提问来源于stack exchange,提问作者Anoushiravan R
相关产品推荐
相关产品推荐

