2015-08-21 3 views
2

Этот вопрос связан с Speedup of pandas groupby. Речь идет о том, чтобы ускорить вычисление грабежа cumproduct. DataFrame является 2D и имеет мультииндекс, состоящий из 3 целых чисел.быстрые вычисления pandas groupby с cumprod

Файл HDF5 для dataframe можно найти здесь: http://filebin.ca/2Csy0E2QuF2w/phi.h5

Фактический расчет, что я выступаю похож на это:

>>> phi = pd.read_hdf('phi.h5', 'phi') 
    >>> %timeit phi.groupby(level='atomic_number').cumprod() 
    100 loops, best of 3: 5.45 ms per loop 

Другой убыстрение, что могло бы быть возможно, что я сделайте это вычисление примерно 100 раз, используя ту же структуру индекса, но с разными номерами. Интересно, может ли он каким-то образом кэшировать индекс.

Любая помощь будет оценена по достоинству.

+1

Я считаю, что 'cumprod' не имеет реализации Cython groupby. См. Соответствующую проблему здесь https://github.com/pydata/pandas/issues/4095. Я уверен, что PR будет приветствоваться! – chrisb

ответ

1

Numba похоже работает здесь очень хорошо. Фактически, эти результаты кажутся почти слишком хорошими, поскольку функция numba ниже примерно на 4000 быстрее, чем исходный метод, и 5x быстрее, чем обычный cumprod без groupby. Надеюсь, они верны, дайте мне знать, если есть ошибка.

np.random.seed(1234) 
df=pd.DataFrame({ 'x':np.repeat(range(200),4), 'y':np.random.randn(800) }) 
df = df.sort('x') 
df['cp_groupby'] = df.groupby('x').cumprod() 

from numba import jit 

@jit 
def group_cumprod(x,y): 
    z = np.ones(len(x)) 
    for i in range(len(x)): 
     if x[i] == x[i-1]: 
      z[i] = y[i] * z[i-1] 
     else: 
      z[i] = y[i] 
    return z 

df['cp_numba'] = group_cumprod(df.x.values,df.y.values) 

df['dif'] = df.cp_groupby - df.cp_numba 

Проверьте, что оба способа дают одинаковый ответ:

all(df.cp_groupby==df.cp_numba) 
Out[1447]: True 

Тайминги:

%timeit df.groupby('x').cumprod() 
10 loops, best of 3: 102 ms per loop 

%timeit df['y'].cumprod() 
10000 loops, best of 3: 133 µs per loop 

%timeit group_cumprod(df.x.values,df.y.values) 
10000 loops, best of 3: 24.4 µs per loop 
+0

нет, это приятное использование numba. когда у вас есть вычисления, которые не могут быть легко векторизованы (хотя вы неплохо справились с этим в другом ответе), это часто бывает легко. На самом деле он обычно может бить numpy, потому что он работает в режиме ожидания цикла (например, не выделяет почти столько же памяти). – Jeff

+0

@Jeff OK, спасибо за подтверждение. Думаю, я оставлю другой ответ тоже как сравнение. Кстати, я приурочен к numba без группового аспекта, и это примерно в 15 раз быстрее обычного cumprod. – JohnE

+0

Ваш пример не полностью сопоставим. вы не выполняете проверку на наном (но не огромную сделку, просто добавьте '' if x [i]! = x [i]: continue'' – Jeff

0

Если вы хотите быстро, но не очень красивое обходное решение, вы можете сделать что-то вроде следующего. Вот некоторые примеры данных и ваш подход по умолчанию.

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 (в частности, по количеству групп).

1

чистое решение NumPy условии, что данные будут отсортированы по индексу, хотя не обработки NaN :

res = np.empty_like(phi.values) 
l = 0 
r = phi.index.levels[0] 
for i in r: 
    phi.values[l:l+i,:].cumprod(axis=0, out=res[l:l+i]) 
    l += i 

a бит в 40 раз быстрее по данным мультииндекса из вопроса. Хотя проблема заключается в том, что это зависит от того, как pandas хранит данные в своем базовом массиве. Таким образом, он может перестать работать при изменении панд.

>>> phi = pd.read_hdf('phi.h5', 'phi') 
>>> %timeit phi.groupby(level='atomic_number').cumprod() 
100 loops, best of 3: 4.33 ms per loop 
>>> %timeit np_cumprod(phi) 
10000 loops, best of 3: 111 µs per loop 
+0

Было бы неплохо иметь чистое решение numpy в качестве опции здесь, но я немного неясен на чем именно вы сравниваете эту 40-кратную производительность? Может быть, вы можете быть более явным в показе таймингов? Ответ на панды был также 40x, но ответ numba был 4000x (в каждом случае, относительно метода OP). панды отвечают как точка сотрудничества mparison btw (b/c это чистые панды), не то, что я думаю, что это лучший подход в целом. – JohnE

+0

В 40 раз быстрее, чем код в вопросе на данные из вопроса, по очень разным структурированным данным в numba отвечают его меньше из-за меньшего количества данных на блок, в этом случае полный cumprod следует за делением, как в ваших пандах ответ, вероятно, будет лучше. – jtaylor

+0

Ну, я играл с этим еще несколько, а числовые результаты numba варьировались в зависимости от структуры данных намного больше, чем я думал ... Я думаю, что в этой теме не так много интереса, но я могу попытаться сделать более методическое сравнение в какой-то момент в будущем, чтобы попытаться выяснить, каковы ключевые параметры. Кроме того, похоже, что в версии 0.17.0 (запланировано на сентябрь 2015 г.) будет улучшена производительность groupby, что может ускорить подход по умолчанию. – JohnE