在Pandas系列中填充连续的NaN值

4

我希望能够在pandas系列中填充缺失值,如果连续的NAN少于3个。

有缺失值的原始系列:

s=pd.Series(pd.np.random.randn(20))
s[[1,3,5,7,12,13,14,15, 18]]=pd.np.nan

提供:

0     0.444025
1          NaN
2     0.631753
3          NaN
4    -0.577121
5          NaN
6     1.299953
7          NaN
8    -0.252173
9     0.287641
10    0.941953
11   -1.624728
12         NaN
13         NaN
14         NaN
15         NaN
16    0.998952
17    0.195698
18         NaN
19   -0.788995

但是,使用pandas.fillna()函数并设置限制只会填充指定数量的值(而不是连续NaN的数量,这是预期的行为):

s.fillna(value=0, limit=3) #Fails to fill values at position 7 and forward

期望的输出将在位置1、3、5、7和18处用0填充NAN。它将保留12-15位置上一系列4个NaN。
文档和其他SO帖子均未解决此问题(例如这里)。文档似乎暗示此限制将在连续的NAN上起作用,而不是将要填充的整个数据集中的数量。谢谢!

谢谢您提供的解决方案。我只是惊讶地发现,没有更简单的方法来做到这一点! - EHB
使用“shift”是否会更简单?即首先使用long_nan_gaps= s.index[s.shift(1).isnull() & s.shift(-1).isnull() & s.isnull()] 存储所有长的NAN间隔的位置,然后将所有NAN填充为0,在最后恢复保存的位置为NAN。感谢提供多种解决方案; 只是想知道下面提出的解决方案是否比我自己想出来的更好(我认为自己想出来的太丑陋/混乱)。 - EHB
4个回答

5
我们先通过 pd.Series.notna 找到 nan 值的位置。
使用 cumsum 时,每当我们遇到非空值时,就会增加累计和,从而生成连续 nan 值的方便组。但是,除了第一组(也许是第一组),我们都是以非空值开始的。因此,我取 mask 的否定并计算每个组中空值的总数。
现在我使用 fillna 并使用 pd.DataFrame.where 来掩盖那些 nan 值的总和太多的位置。
mask = s.notna()
c_na = (~mask).groupby(mask.cumsum()).transform('sum')
filled = s.fillna(0).where(c_na.le(3))
s.fillna(filled)

0     1.418895
1     0.000000
2    -0.553732
3     0.000000
4    -0.101532
5     0.000000
6    -1.334803
7     0.000000
8     1.159115
9     0.309093
10   -0.047970
11    0.051567
12         NaN
13         NaN
14         NaN
15         NaN
16    0.623673
17   -0.786857
18    0.000000
19    0.310688
dtype: float64

这是一种使用Numpy/Pandas的高端方法,其中包括 np.bincountpd.factorize
v = s.values
m = np.isnan(v)
f, u = pd.factorize((~m).cumsum())
filled = np.where(
    ~m, v,
    np.where(np.bincount(f, weights=mask)[f] <= 3, 0, np.nan)
)

pd.Series(filled, s.index)

0     1.418895
1     0.000000
2    -0.553732
3     0.000000
4    -0.101532
5     0.000000
6    -1.334803
7     0.000000
8     1.159115
9     0.309093
10   -0.047970
11    0.051567
12         NaN
13         NaN
14         NaN
15         NaN
16    0.623673
17   -0.786857
18    0.000000
19    0.310688
dtype: float64

1
你可以使用以下代码将一个 fillna 简化为一行:masked = s.groupby(m.cumsum()).transform('size').gt(3); s.fillna(0).mask(masked) - cs95
我认为应该是 s.fillna(0).mask(sumna.ge(3)),对吧? - BENY
你能详细说明一下这段代码在做什么吗?我看它能运行,但不太明白 groupby 中具体发生了什么。 - EHB
刚到电脑旁,等我几分钟详细说明。 - piRSquared
@piRSquared,不用担心,我马上要离开电脑了,明天再看。干杯! - EHB
1
@cᴏʟᴅsᴘᴇᴇᴅ size 将捕获第一个非空加上随后的 null,并且通常比 null 的数量多一个。请注意,我们要求每个组中 null 的数量。 'count' 更好,或者反之亦然。正在处理中 (-: - piRSquared

2

首先,建立一个na累计计数列。连续的na将具有相同的累计计数。

df = s.to_frame('value').assign(na_ct=s.notna().cumsum())

然后,我们可以按照na的cum_count进行分组,检查每个组中的行数,并决定是否填充缺失值。

df.groupby(df.na_ct).apply(lambda x: x if len(x)>4 else x.fillna(0)).value
Out[76]: 
0     0.195634
1     0.000000
2    -0.818349
3     0.000000
4    -2.347686
5     0.000000
6    -0.464040
7     0.000000
8     0.179321
9     0.356661
10    0.471832
11   -1.217082
12         NaN
13         NaN
14         NaN
15         NaN
16   -0.112744
17   -2.630191
18    0.000000
19   -0.313592
Name: value, dtype: float64

2
也许可以尝试这个?
t=s[s.isnull()];
v=pd.Series(t.index,index=t.index).diff().ne(1).cumsum();
z=v[v.isin(v.value_counts()[v.value_counts().gt(3)].index.values)];
s.fillna(0).mask(s.index.isin(z.index))
Out[348]: 
0    -0.781728
1     0.000000
2    -1.114552
3     0.000000
4     1.242452
5     0.000000
6     0.599486
7     0.000000
8     0.757384
9    -1.559661
10    0.527451
11   -0.426890
12         NaN
13         NaN
14         NaN
15         NaN
16   -1.264962
17    0.703790
18    0.000000
19    0.953616
dtype: float64

1
你可以尝试使用以下方式中的rolling运算符:
1)创建一个函数,仅在窗口中少于X个值时返回0
fillnaiflessthan(series, count):
    if series.isnull().sum() < count and series.center == pd.NaN:
         return 0

2)然后在rolling内使用它。
s.rolling(window=5, center=True, min_periods=0).apply(lambda x: fillnaiflessthan(x, 4))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接