Панды отбрасывают нан, используя первый действительный индекс по группе - PullRequest
1 голос
/ 03 октября 2019

Я работаю со следующим DataFrame:

         Date    Id    Amount
   0    201301    1      nan
   1    201302    1      nan
   2    201303    1      100
   3    201304    1      120
   4    201305    1      nan
   5    201306    1      120
   6    201302    2      nan
   7    201303    2      150
   8    201304    2      180

Я пытаюсь получить первый действительный индекс Amount на Id. По какой-то причине это не работает:

df.groupby('Id').Amount.first_valid_index()

Я также пытаюсь это сделать:

df.groupby('Id').Amount.apply(lambda x: x.first_valid_index())

Но мой набор данных состоит из 20 миллионов строк, поэтому он занимает слишком много времени и выигралне работает для меня.

Есть ли более быстрый способ найти первый индекс по группе?

Мой желаемый результат будет:

first_idx = [2,7]

Или даже лучше:

         Date    Id    Amount

   2    201303    1      100
   3    201304    1      120
   4    201305    1      nan
   5    201306    1      120
   7    201303    2      150
   8    201304    2      180

Редактировать: df.groupby('Id').Amount.apply(lambda x: x.first_valid_index()) действительно работает, но я чувствую, что должен быть более быстрый вариант, проблема не кажется такой сложной.

Ответы [ 2 ]

2 голосов
/ 03 октября 2019

Создайте маску с помощью .notnull + .cumsum, чтобы получить все после первого ненулевого Amount в группе. Затем сделайте ломтик.

m = df.Amount.notnull().groupby(df.Id).cumsum().ge(1)

df.loc[m]
     Date  Id  Amount
2  201303   1   100.0
3  201304   1   120.0
4  201305   1     NaN
5  201306   1   120.0
7  201303   2   150.0
8  201304   2   180.0
2 голосов
/ 03 октября 2019

Вариант 1: Чтобы получить только первые индексы:

df[df.Amount.notna()].groupby('Id').Date.idxmin()
# 1.42 ms ± 14.1 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

вывод:

Id
1    2
2    7
Name: Date, dtype: int64

Вариант 2: , чтобы получитьдругие строки, используйте cumsum на notna()

df[df['Amount'].notna().groupby(df['Id']).cumsum().gt(0)]
# 2.09 ms ± 220 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Вариант 3: вы можете ffill() в группе и выбрать те, которые не заполнены:

df[df.groupby('Id').Amount.ffill().notna()]
# 831 µs ± 14.8 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

Вывод:

     Date  Id  Amount
2  201303   1   100.0
3  201304   1   120.0
4  201305   1     NaN
5  201306   1   120.0
7  201303   2   150.0
8  201304   2   180.0

Вывод : вариант 3 самый быстрый!


Обновление: для фильтрации обоих концов с использованиемВариант 3:

amt_group = df.groupby('Id').Amount
df[amt_group.bfill().notna() & amt_group.ffill().notna()]
...