В 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