2014-08-30 3 views
3

Улучшение на this question, которое обеспечило умное решение для применения функции по нескольким столбцам в DataFrame, мне интересно, можно ли оптимизировать решение для скорости.Быстрее roll_apply на Pandas DataFrame?

Окружающая среда: Python 2.7.8, Pandas 14.1, Numpy 1.8.

Вот пример настройки:

import pandas as pd 
import numpy as np 
import random 

def meanmax(ii,df): 
    xdf = df.iloc[map(int,ii)] 
    n = max(xdf['A']) + max(xdf['B']) 
    return n/2.0 

df = pd.DataFrame(np.random.randn(2500,2)/10000, 
        index=pd.date_range('2001-01-01',periods=2500), 
        columns=['A','B'])    
df['ii'] = range(len(df))  

res = pd.rolling_apply(df.ii, 26, lambda x: meanmax(x, df)) 

Обратите внимание, что функция meanmax не попарно, что-то вроде rolling_mean(df['A'] + df['B'],26) не будет работать.

Однако я могу сделать что-то вроде:

res2 = (pd.rolling_max(df['A'],26) + pd.rolling_max(df['B'],26))/2 

который завершает примерно 3000X быстрее:

%timeit res = pd.rolling_apply(df.ii, 26, lambda x: meanmax(x, df)) 
1 loops, best of 3: 1 s per loop 

%timeit res2 = (pd.rolling_max(df['A'],26) + pd.rolling_max(df['B'],26))/2 
1000 loops, best of 3: 325 µs per loop 

Есть ли что-нибудь лучше/эквивалент, чем второй вариант выше, учитывая пример функции и используя rolling_apply ? В то время как второй вариант быстрее, он не использует rolling_apply, который может быть применен к более широкой задаче установить

Редактировать: Производительность коррекция времени

+0

Er ..это 325 микросекунд против 1 секунды, что более чем в 1000 раз быстрее, чем вы понимаете. – EdChum

+0

Действительно, я даже не удосужился заметить «μ» там ... Спасибо за исправление – bazel

ответ

7

вычисления обобщенной функции качению над массивом размера n с Размер окна m требует примерно O(n*m) раз. Встроенные методы rollin_xxx используют некоторые довольно умные алгоритмы, чтобы поддерживать время работы значительно ниже этого и часто могут гарантировать время O(n), что, если вы думаете об этом, является довольно впечатляющей вещью.

rolling_min и rolling_max, в частности, заимствованные их реализацию из bottleneck, который ссылается Richard Harter в качестве источника алгоритма, хотя я нашел то, что я думаю, что это раннее описание одного и того же алгоритма в this paper.

Итак, после урока истории: очень вероятно, что вы не сможете съесть свой торт. rolling_apply очень удобно, но он почти всегда будет жертвовать производительностью по конкретному алгоритму. По моему опыту, одна из наиболее приятных частей использования научного стека Python придумывает эффективные способы выполнения вычислений, используя быстрые примитивы, представленные творчески. Хорошим примером этого является ваше собственное решение, вызывающее rolling_max. Поэтому расслабьтесь и наслаждайтесь поездкой, зная, что у вас всегда будет rolling_apply, чтобы отступить, если вы, или хорошие люди SO, не можете прийти с более разумным решением.

+0

Спасибо - я полностью забыл о «узком месте» модуль, который объясняет, почему двойной roll_max так быстро. Мне просто интересно, можно ли улучшить исходную стратегию выше, скажем, если, возможно, «roll_apply» может взять нечто большее, чем одномерный ndarray. Тогда нам не придется беспокоиться о том, чтобы делать iloc в функции 'meanmax', а также дополнительный лямбда-вызов – bazel

3

Вы не сможете получить до rolling_max скорости, но вы можете часто сбрить на порядок или около того, понижая до numpy через .values:

def meanmax_np(ii, df): 
    ii = ii.astype(int) 
    n = df["A"].values[ii].max() + df["B"].values[ii].max() 
    return n/2.0 

дает мне

>>> %timeit res = pd.rolling_apply(df.ii, 26, lambda x: meanmax(x, df)) 
1 loops, best of 3: 701 ms per loop 
>>> %timeit res_np = pd.rolling_apply(df.ii, 26, lambda x: meanmax_np(x, df)) 
10 loops, best of 3: 31.2 ms per loop 
>>> %timeit res2 = (pd.rolling_max(df['A'],26) + pd.rolling_max(df['B'],26))/2 
1000 loops, best of 3: 247 µs per loop 

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

+0

Хороший улов. Я все еще ищу магию (которая, возможно, не существует), которая может быть применена вообще к проблемам, требующим «roll_apply», но этот пример информативен для использования numpy независимо - спасибо. – bazel

Смежные вопросы