Заполните последовательные NaN в серии Pandas

Я хочу заполнить отсутствующие значения в моей серии pandas, если имеется менее 3 последовательных NAN.

Исходная серия с пропущенными значениями:

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

Желаемый результат должен был бы заполнить NAN 0 в позициях 1,3,5,7 и 18. Это оставит серию из 4 NaN на месте в позициях 12-15.

В документации и других сообщениях по SO эта проблема не решена (например, здесь). Документация, похоже, подразумевает, что это ограничение будет работать для последовательных NAN, а не для общего # во всем наборе данных, который будет заполнен. Спасибо!


person EHB    schedule 20.03.2018    source источник
comment
Спасибо за решения. Я просто удивлен, что нет более простого способа сделать это!   -  person EHB    schedule 21.03.2018
comment
Было бы проще использовать shift? т.е. сначала сохранить местоположение всех длинных промежутков NAN с помощью long_nan_gaps= s.index[s.shift(1).isnull() & s.shift(-1).isnull() & s.isnull()], затем заполнить ВСЕ NAN с помощью 0, а затем восстановить сохраненные местоположения в NAN после факта? Я ценю множество решений; просто интересно, лучше ли то, что вы предложили ниже, чем то, что я понял здесь для себя (что я считал слишком уродливым / сбивающим с толку).   -  person EHB    schedule 21.03.2018


Ответы (4)


Начнем с поиска значений nan через pd.Series.notna.

Поскольку мы используем 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.bincount и pd.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
person piRSquared    schedule 20.03.2018
comment
Вы можете сохранить один fillna с помощью masked = s.groupby(m.cumsum()).transform('size').gt(3); s.fillna(0).mask(masked) - person cs95; 21.03.2018
comment
Думаю, должно быть s.fillna (0) .mask (sumna.ge (3)), верно? - person BENY; 21.03.2018
comment
Не могли бы вы рассказать немного о том, что делает код? Я вижу, что это работает, но не совсем понимаю, что происходит в groupby - person EHB; 21.03.2018
comment
Только что добрался до компьютера. Я уточню через несколько минут - person piRSquared; 21.03.2018
comment
@piRSquared, не волнуйтесь, я собираюсь оставить свой, завтра посмотрю. Ваше здоровье! - person EHB; 21.03.2018
comment
@ cᴏʟᴅsᴘᴇᴇᴅ size захватит первое ненулевое значение плюс последующие нули и обычно будет на единицу больше, чем количество нулей. Имейте в виду, мы следим за количеством нулей на группу. 'count' было бы лучше, точнее наоборот. Работаем над этим сейчас (-: - person piRSquared; 21.03.2018

Может попробовать это?

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
person BENY    schedule 21.03.2018

Сначала создайте столбец na cum_count. Последовательные nas будут иметь одинаковый cum_count.

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

Затем мы можем сгруппировать по ncum_count, проверить количество строк в каждой группе и решить, будет ли погода заполнять nas или нет.

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
person Allen    schedule 21.03.2018

Вы можете попробовать это с оператором rolling следующим образом:

1) Создайте функцию, которая возвращает 0, только если в окне меньше X значений

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))
person Julien Perrenoud    schedule 20.03.2018