В Python Pandas используют cumsum с groupby и сбрасывают cumsum, когда значение равно 0

Я довольно новичок в питоне. Я пытаюсь получить совокупную сумму для каждого клиента, чтобы увидеть последующие месяцы бездействия (флаг: 1 или 0). Таким образом, кумулятивная сумма единиц должна быть сброшена, когда у нас есть 0. Сброс также должен произойти, когда у нас новый клиент. Ниже приведен пример, где a - столбец клиентов, а b - даты.

После некоторого исследования я нашел вопросы "Сброс Cumsum в NaN" и "В Python Pandas используя cumsum with groupby". Я предполагаю, что мне нужно собрать их вместе. Адаптация кода "Cumsum reset at NaN" к сбросу к 0, успешна:

cumsum = v.cumsum().fillna(method='pad')
reset = -cumsum[v.isnull() !=0].diff().fillna(cumsum)
result = v.where(v.notnull(), reset).cumsum()

Однако мне не удается добавить группу. Мой счет просто продолжается...

Итак, набор данных будет выглядеть так: импортировать панд как pd

df =  pd.DataFrame({'a' : [1,1,1,1,1,1,1,2,2,2,2,2,2,2], 
                    'b' : [1/15,2/15,3/15,4/15,5/15,6/15,1/15,2/15,3/15,4/15,5/15,6/15], 
                    'c' : [1,0,1,0,1,1,0,1,1,0,1,1,1,1]})

это должно привести к кадру данных со столбцами a, b, c и d с

'd' : [1,0,1,0,1,2,0,1,2,0,1,2,3,4]

Обратите внимание, что у меня очень большой набор данных, поэтому время расчета действительно важно.

Спасибо за помощь мне

2 ответа

Использование groupby.apply а также cumsum после нахождения смежных значений в группах. затем groupby.cumcount чтобы получить целое число, считая до каждого смежного значения и добавить 1 позже.

Умножьте исходную строку, чтобы создать логику AND, отменяющую все нули и учитывающую только положительные значения.

df['d'] = df.groupby('a')['c']                                                            \
            .apply(lambda x: x * (x.groupby((x != x.shift()).cumsum()).cumcount() + 1))

print(df['d'])

0     1
1     0
2     1
3     0
4     1
5     2
6     0
7     1
8     2
9     0
10    1
11    2
12    3
13    4
Name: d, dtype: int64

Другим способом было бы применить функцию после series.expanding на объекте groupby, который в основном вычисляет значения в серии, начиная с первого индекса до текущего индекса.

использование reduce позже применить функцию двух аргументов кумулятивно к элементам итерируемой, чтобы свести ее к одному значению.

from functools import reduce

df.groupby('a')['c'].expanding()                                         \
  .apply(lambda i: reduce(lambda x, y: x+1 if y==1 else 0, i, 0))

a    
1  0     1.0
   1     0.0
   2     1.0
   3     0.0
   4     1.0
   5     2.0
   6     0.0
2  7     1.0
   8     2.0
   9     0.0
   10    1.0
   11    2.0
   12    3.0
   13    4.0
Name: c, dtype: float64

Тайминги:

%%timeit
df.groupby('a')['c'].apply(lambda x: x * (x.groupby((x != x.shift()).cumsum()).cumcount() + 1))
100 loops, best of 3: 3.35 ms per loop

%%timeit
df.groupby('a')['c'].expanding().apply(lambda s: reduce(lambda x, y: x+1 if y==1 else 0, s, 0))
1000 loops, best of 3: 1.63 ms per loop

Я думаю, что вам нужна пользовательская функция с groupby:

#change row with index 6 to 1 for better testing
df =  pd.DataFrame({'a' : [1,1,1,1,1,1,1,2,2,2,2,2,2,2], 
                    'b' : [1/15,2/15,3/15,4/15,5/15,6/15,1/15,2/15,3/15,4/15,5/15,6/15,7/15,8/15], 
                    'c' : [1,0,1,0,1,1,1,1,1,0,1,1,1,1],
                    'd' : [1,0,1,0,1,2,3,1,2,0,1,2,3,4]})

print (df)
    a         b  c  d
0   1  0.066667  1  1
1   1  0.133333  0  0
2   1  0.200000  1  1
3   1  0.266667  0  0
4   1  0.333333  1  1
5   1  0.400000  1  2
6   1  0.066667  1  3
7   2  0.133333  1  1
8   2  0.200000  1  2
9   2  0.266667  0  0
10  2  0.333333  1  1
11  2  0.400000  1  2
12  2  0.466667  1  3
13  2  0.533333  1  4
def f(x):
    x.ix[x.c == 1, 'e'] = 1
    a = x.e.notnull()
    x.e = a.cumsum()-a.cumsum().where(~a).ffill().fillna(0).astype(int)
    return (x)

print (df.groupby('a').apply(f))
    a         b  c  d  e
0   1  0.066667  1  1  1
1   1  0.133333  0  0  0
2   1  0.200000  1  1  1
3   1  0.266667  0  0  0
4   1  0.333333  1  1  1
5   1  0.400000  1  2  2
6   1  0.066667  1  3  3
7   2  0.133333  1  1  1
8   2  0.200000  1  2  2
9   2  0.266667  0  0  0
10  2  0.333333  1  1  1
11  2  0.400000  1  2  2
12  2  0.466667  1  3  3
13  2  0.533333  1  4  4
Другие вопросы по тегам