用前几行的平均值填充 NaN 值?

Fill NaN values wit mean of previous rows?

我必须用前 3 个实例的平均值填充数据框中一列的 nan 值。 以下是示例:

df = pd.DataFrame({'col1': [1, 3, 4, 5, np.NaN, np.NaN, np.NaN, 7]})
df
col1
0   1.0
1   3.0
2   4.0
3   5.0
4   NaN
5   NaN
6   NaN 
7   7.0

这是我需要的输出:

col1
0   1.0
1   3.0
2   4.0
3   5.0
4   4.0
5   4.3
6   4.4 
7   7.0

我尝试了 pd.rolling,但是当该列在一卷中有多个 NaN 值时,它无法按我想要的方式工作:

df.fillna(df.rolling(3, min_periods=1).mean().shift())


col1
0   1.0
1   3.0
2   4.0
3   5.0
4   4.0 # np.nanmean([3, 4, 5])
5   4.5 # np.nanmean([np.NaN, 4, 5])
6   5.0 # np.nanmean([np.NaN, np.naN ,5])
7   7.0

有人可以帮我吗?提前致谢!

我尝试了两种方法来解决这个问题。一个是在数据帧上循环,第二个本质上是多次尝试您建议的方法,以收敛于正确的答案。

循环方法

对于数据框中的每一行,从 col1 获取值。然后,取最后几行的平均值。 (如果我们在数据帧的开头,此列表中的数量可能少于 3。)如果值为 NaN,请将其替换为平均值。然后,将值保存回数据框中。如果最后一行的值列表超过 3 个值,则删除最后一个。

def impute(df2, col_name):
    last_3 = []
    for index in df.index:
        val = df2.loc[index, col_name]
        if len(last_3) > 0:
            imputed = np.nanmean(last_3)
        else:
            imputed = None
        if np.isnan(val):
            val = imputed
        last_3.append(val)
        df2.loc[index, col_name] = val
        if len(last_3) > 3:
            last_3.pop(0)

重复列操作

这里的核心思想是注意在你的例子pd.rolling中,第一个NA替换值是正确的。因此,您应用滚动平均值,为每个 运行 的 NA 值取第一个 NA 值,并使用该数字。如果你重复应用这个,你会填写第一个缺失值,然后是第二个缺失值,然后是第三个。您需要 运行 这个循环次数与最长的连续 NA 值系列一样多。

def impute(df2, col_name):
    while df2[col_name].isna().any().any():
        # If there are multiple NA values in a row, identify just
        # the first one
        first_na = df2[col_name].isna().diff() & df2[col_name].isna()
        # Compute mean of previous 3 values
        imputed = df2.rolling(3, min_periods=1).mean().shift()[col_name]
        # Replace NA values with mean if they are very first NA
        # value in run of NA values
        df2.loc[first_na, col_name] = imputed

性能比较

运行 这两个都在一个 80000 行的数据帧上,我得到以下结果:

Loop approach takes 20.744 seconds
Repeated column operation takes 0.056 seconds

可能不是最有效的,但简洁并能完成工作

from functools import reduce
reduce(lambda d, _: d.fillna(d.rolling(3, min_periods=3).mean().shift()), range(df['col1'].isna().sum()), df)

输出


    col1
0   1.000000
1   3.000000
2   4.000000
3   5.000000
4   4.000000
5   4.333333
6   4.444444
7   7.000000

我们基本上使用 fillna 但需要 min_periods=3 意味着它一次只会填充一个 NaN,或者更确切地说是那些紧接其前的三个非 NaN 数字的 NaN。然后我们使用 reduce 重复此操作,次数与 col1

中的 NaN 一样多