Если вы хотите быстро, но не очень красивое обходное решение, вы можете сделать что-то вроде следующего. Вот некоторые примеры данных и ваш подход по умолчанию.
df=pd.DataFrame({ 'x':np.repeat(range(200),4), 'y':np.random.randn(800) })
df = df.sort('x')
df['cp_group'] = df.groupby('x').cumprod()
И вот обходной путь. Это выглядит довольно долго (это так), но каждый отдельный шаг прост и быстр. (Тайминги внизу.) Ключ просто избегать использования groupby
вообще в этом случае, заменив shift
и тому подобное, но из-за этого вам также нужно убедиться, что ваши данные отсортированы по столбцу groupby.
df['cp_nogroup'] = df.y.cumprod()
df['last'] = np.where(df.x == df.x.shift(-1), 0, df.y.cumprod())
df['last'] = np.where(df['last'] == 0., np.nan, df['last'])
df['last'] = df['last'].shift().ffill().fillna(1)
df['cp_fast'] = df['cp_nogroup']/df['last']
df['dif'] = df.cp_group - df.cp_fast
Вот как он выглядит. «cp_group» является вашим значением по умолчанию, а «cp_fast» - это вышеописанное решение. Если вы посмотрите на столбец «dif», вы увидите, что некоторые из них отключены очень небольшими суммами. Это просто точная проблема, и не о чем беспокоиться.
x y cp_group cp_nogroup last cp_fast dif
0 0 1.364826 1.364826 1.364826 1.000000 1.364826 0.000000e+00
1 0 0.410126 0.559751 0.559751 1.000000 0.559751 0.000000e+00
2 0 0.894037 0.500438 0.500438 1.000000 0.500438 0.000000e+00
3 0 0.092296 0.046189 0.046189 1.000000 0.046189 0.000000e+00
4 1 1.262172 1.262172 0.058298 0.046189 1.262172 0.000000e+00
5 1 0.832328 1.050541 0.048523 0.046189 1.050541 2.220446e-16
6 1 -0.337245 -0.354289 -0.016364 0.046189 -0.354289 -5.551115e-17
7 1 0.758163 -0.268609 -0.012407 0.046189 -0.268609 -5.551115e-17
8 2 -1.025820 -1.025820 0.012727 -0.012407 -1.025820 0.000000e+00
9 2 1.175903 -1.206265 0.014966 -0.012407 -1.206265 0.000000e+00
Timings метод
По умолчанию:
In [86]: %timeit df.groupby('x').cumprod()
10 loops, best of 3: 100 ms per loop
Стандартный cumprod
но без groupby
. Это должно быть хорошим приближением максимально возможной скорости, которую вы могли бы достичь.
In [87]: %timeit df.cumprod()
1000 loops, best of 3: 536 µs per loop
И вот обходной путь:
In [88]: %%timeit
...: df['cp_nogroup'] = df.y.cumprod()
...: df['last'] = np.where(df.x == df.x.shift(-1), 0, df.y.cumprod())
...: df['last'] = np.where(df['last'] == 0., np.nan, df['last'])
...: df['last'] = df['last'].shift().ffill().fillna(1)
...: df['cp_fast'] = df['cp_nogroup']/df['last']
...: df['dif'] = df.cp_group - df.cp_fast
100 loops, best of 3: 2.3 ms per loop
Так что временное решение о 40x быстрее для этого образца dataframe но убыстрение будет зависеть от dataframe (в частности, по количеству групп).
Я считаю, что 'cumprod' не имеет реализации Cython groupby. См. Соответствующую проблему здесь https://github.com/pydata/pandas/issues/4095. Я уверен, что PR будет приветствоваться! – chrisb