Pandas удаление конечных нулей в конце столбца, группировка по другому столбцу - PullRequest
0 голосов
/ 05 мая 2020

Я создал следующую функцию для удаления конечных нулей в столбце «значение» для каждого «проекта». Но кажется это, так сказать, «неоптимально») Надеюсь, кто-нибудь сможет посоветовать, как это сделать.

Табл. выберите и отбросьте номер недели со «значением» == ноль, но только в конце (отсортируйте по «номер недели»).

В этом примере я должен получить индексы: 2, 3, 7

+---+------+---------+--------------+-------+
|   | proj | weeknum | weekduration | value |
+---+------+---------+--------------+-------+
| 0 |    1 |       1 |            4 |     0 |
| 1 |    1 |       2 |            4 |    11 |
| 4 |    2 |       1 |            4 |    10 |
| 5 |    2 |       2 |            4 |    11 |
| 6 |    2 |       3 |            4 |    12 |
+---+------+---------+--------------+-------+

Мой простой черновой код:

abnrm_list = []
for i in df.proj.unique():
    d = df[df['proj'] == i]
    last_week = d['weeknum'].max()
    cs_hrs = d[d['weeknum'] == last_week]['value'].values[0]
    while cs_hrs == 0:
        abnrm_list.append(d[d['weeknum'] == last_week].index[0])
        last_week = last_week - 1
        cs_hrs = d[d['weeknum'] == last_week]['value'].values[0]

Но я уверен, что это должно быть более простое и ясное решение.

Кроме того, необходимо обновить «weekduration» до нового значения после удаления нулевых строк .

Ответы [ 2 ]

2 голосов
/ 05 мая 2020

Используйте boolean indexing с фильтром последних 0 значений, сравниваемых с помощью Series.ne в помощнике Series, созданном путем замены порядка столбцов на GroupBy.cumsum :

mask = df['value'].iloc[::-1].groupby(df['proj']).cumsum().iloc[::-1].ne(0)
df = df[mask].copy()
print (df)
   proj  weeknum  weekduration  value
0     1        1             4      0
1     1        2             4     11
4     2        1             4     10
5     2        2             4     11
6     2        3             4     12

А затем используйте Series.map с Series.value_counts:

df['weekduration'] = df['proj'].map(df['proj'].value_counts())
print (df)
   proj  weeknum  weekduration  value
0     1        1             2      0
1     1        2             2     11
4     2        1             3     10
5     2        2             3     11
6     2        3             3     12

Подробности :

print (df['value'].iloc[::-1])
7     0
6    12
5    11
4    10
3     0
2     0
1    11
0     0
Name: value, dtype: int64

print (df['value'].iloc[::-1].groupby(df['proj']).cumsum())
7     0
6    12
5    23
4    33
3     0
2     0
1    11
0    11
Name: value, dtype: int64

Производительность :

Вам нужно избегать фильтрации по группам, потому что медленно:

np.random.seed(123)
N = 1000000

df = pd.DataFrame({'proj':np.random.randint(10000, size=N), 
                   'value': np.random.choice([0,1,2], N)}).sort_values('proj')
print (df)

In [76]: %timeit df.groupby('proj').apply(lambda grp: grp[grp.value[::-1].cumsum().gt(0)[::-1]]).reset_index(level=0, drop=True)
20.2 s ± 4.04 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

In [78]: %timeit df[df['value'].iloc[::-1].groupby(df['proj']).cumsum().iloc[::-1].ne(0)]
268 ms ± 1.22 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
1 голос
/ 05 мая 2020

Чтобы удалить завершающие нули, выполните:

result = df.groupby('proj').apply(
    lambda grp: grp[grp.value[::-1].cumsum().gt(0)[::-1]])\
    .reset_index(level=0, drop=True)

Лямбда-функция:

  • принимает значение столбец в обратном порядке,
  • вычисляет cumsum ,
  • преобразуется в bool (is cumsum> 0),
  • возвращается к «исходному» порядку,
  • использует логическое индексирование для удаления конечных нулей.

Вышеупомянутая процедура повторяется для всех proj групп.

Все, что остается сделать (на этом step) равен reset_index , чтобы сбросить верхний уровень индекса (ключи группировки).

А теперь вторая часть - update weekduration :

result.weekduration = result.groupby('proj').weekduration\
    .transform(lambda grp: grp.size)

Конечный результат:

   proj  weeknum  weekduration  value
0     1        1             2      0
1     1        2             2     11
4     2        1             3     10
5     2        2             3     11
6     2        3             3     12

Редактировать

Или, если вы предпочитаете решение «все в одном», определите следующую функцию для фильтрации группы и в то же время set weekduration :

def myFilter(grp):
    grp = grp[grp.value[::-1].cumsum().gt(0)[::-1]]
    grp.weekduration = grp.index.size
    return grp

Затем вычислите результат, применив эту функцию к каждой группе (сгруппированной по proj ):

result = df.groupby('proj').apply(myFilter).reset_index(level=0, drop=True)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...