Подтвердить что ты не робот

Проблемы с производительностью pandas и фильтрация по столбцу datetime

У меня есть pandas датафрейм с объектом datetime64 на одном из столбцов.

    time    volume  complete    closeBid    closeAsk    openBid openAsk highBid highAsk lowBid  lowAsk  closeMid
0   2016-08-07 21:00:00+00:00   9   True    0.84734 0.84842 0.84706 0.84814 0.84734 0.84842 0.84706 0.84814 0.84788
1   2016-08-07 21:05:00+00:00   10  True    0.84735 0.84841 0.84752 0.84832 0.84752 0.84846 0.84712 0.8482  0.84788
2   2016-08-07 21:10:00+00:00   10  True    0.84742 0.84817 0.84739 0.84828 0.84757 0.84831 0.84735 0.84817 0.847795
3   2016-08-07 21:15:00+00:00   18  True    0.84732 0.84811 0.84737 0.84813 0.84737 0.84813 0.84721 0.8479  0.847715
4   2016-08-07 21:20:00+00:00   4   True    0.84755 0.84822 0.84739 0.84812 0.84755 0.84822 0.84739 0.84812 0.847885
5   2016-08-07 21:25:00+00:00   4   True    0.84769 0.84843 0.84758 0.84827 0.84769 0.84843 0.84758 0.84827 0.84806
6   2016-08-07 21:30:00+00:00   5   True    0.84764 0.84851 0.84768 0.84852 0.8478  0.84857 0.84764 0.84851 0.848075
7   2016-08-07 21:35:00+00:00   4   True    0.84755 0.84825 0.84762 0.84844 0.84765 0.84844 0.84755 0.84824 0.8479
8   2016-08-07 21:40:00+00:00   1   True    0.84759 0.84812 0.84759 0.84812 0.84759 0.84812 0.84759 0.84812 0.847855
9   2016-08-07 21:45:00+00:00   3   True    0.84727 0.84817 0.84743 0.8482  0.84743 0.84822 0.84727 0.84817 0.84772

Мое приложение следует (упрощенной) структуре ниже:

class Runner():
    def execute_tick(self, clock_tick, previous_tick):
        candles = self.broker.get_new_candles(clock_tick, previous_tick)
        if candles:
            run_calculations(candles)

class Broker():
    def get_new_candles(clock_tick, previous_tick)
        start = previous_tick - timedelta(minutes=1)
        end = clock_tick - timedelta(minutes=3)
        return df[(df.time > start) & (df.time <= end)]

Я заметил, что при профилировании приложения вызов df[(df.time > start) & (df.time <= end)] вызывает самые высокие проблемы с производительностью, и мне было интересно, есть ли способ ускорить эти вызовы?

EDIT: я добавляю дополнительную информацию о прецеденте здесь (также источник доступен по адресу: https://github.com/jmelett/pyFxTrader)

  • Приложение примет список instruments (например, EUR_USD, USD_JPY, GBP_CHF), а затем pre-fetch ticks/candles для каждого из них и их таймфреймов (например, 5 минут, 30 минут, 1 час и т.д.). Инициализированные данные в основном представляют собой dict инструментов, каждый из которых содержит еще один dict со свечными данными для M5, M30, H1 таймфреймов.
  • Каждый "таймфрейм" представляет собой фреймворк pandas, как показано вверху
  • A симулятор часов используется для запроса отдельных свечей в течение определенного времени (например, в 15:30:00, дайте мне последний x "5-минутные свечи" ) для EUR_USD
  • Этот фрагмент данных затем используется для "имитировать "конкретные рыночные условия (например, средняя цена за последний 1 час увеличилась на 10%, покупка рыночная позиция)
4b9b3361

Ответ 1

Если эффективность - это ваша цель, я бы использовал numpy для всего, что угодно

Я переписал get_new_candles как get_new_candles2

def get_new_candles2(clock_tick, previous_tick):
    start = previous_tick - timedelta(minutes=1)
    end = clock_tick - timedelta(minutes=3)
    ge_start = df.time.values >= start.to_datetime64()
    le_end = df.time.values <= end.to_datetime64()
    return pd.DataFrame(df.values[ge_start & le_end], df.index[mask], df.columns)

Настройка данных

from StringIO import StringIO
import pandas as pd

text = """time,volume,complete,closeBid,closeAsk,openBid,openAsk,highBid,highAsk,lowBid,lowAsk,closeMid
2016-08-07 21:00:00+00:00,9,True,0.84734,0.84842,0.84706,0.84814,0.84734,0.84842,0.84706,0.84814,0.84788
2016-08-07 21:05:00+00:00,10,True,0.84735,0.84841,0.84752,0.84832,0.84752,0.84846,0.84712,0.8482,0.84788
2016-08-07 21:10:00+00:00,10,True,0.84742,0.84817,0.84739,0.84828,0.84757,0.84831,0.84735,0.84817,0.847795
2016-08-07 21:15:00+00:00,18,True,0.84732,0.84811,0.84737,0.84813,0.84737,0.84813,0.84721,0.8479,0.847715
2016-08-07 21:20:00+00:00,4,True,0.84755,0.84822,0.84739,0.84812,0.84755,0.84822,0.84739,0.84812,0.847885
2016-08-07 21:25:00+00:00,4,True,0.84769,0.84843,0.84758,0.84827,0.84769,0.84843,0.84758,0.84827,0.84806
2016-08-07 21:30:00+00:00,5,True,0.84764,0.84851,0.84768,0.84852,0.8478,0.84857,0.84764,0.84851,0.848075
2016-08-07 21:35:00+00:00,4,True,0.84755,0.84825,0.84762,0.84844,0.84765,0.84844,0.84755,0.84824,0.8479
2016-08-07 21:40:00+00:00,1,True,0.84759,0.84812,0.84759,0.84812,0.84759,0.84812,0.84759,0.84812,0.847855
2016-08-07 21:45:00+00:00,3,True,0.84727,0.84817,0.84743,0.8482,0.84743,0.84822,0.84727,0.84817,0.84772
"""

df = pd.read_csv(StringIO(text), parse_dates=[0])

Контрольные входные переменные

previous_tick = pd.to_datetime('2016-08-07 21:10:00')
clock_tick = pd.to_datetime('2016-08-07 21:45:00')

get_new_candles2(clock_tick, previous_tick)

введите описание изображения здесь


Timing

введите описание изображения здесь

Ответ 2

Это немного спекуляция, потому что я не могу ее проверить, но мне приходят две идеи.

  • Использование серии поиска для определения начальных и конечных индексов возвращаемого кадра данных:

    s = pd.Series(np.arange(len(df)), index=df.time)
    start = s.asof(start)
    end = s.asof(end)
    ret = df.iloc[start + 1 : end]
    
  • Установка столбца df.time в качестве индекса и взятие фрагмента. (Это может быть или не быть хорошим решением по другим причинам, поскольку этот столбец содержит дубликаты.)

    df = df.set_index('time')
    ret = df.loc[start : end]
    

    Вам может потребоваться добавить небольшой Timedelta в start.

В обоих случаях вы можете сделать основной шаг (построить серию или установить индекс) только один раз для каждого кадра данных.

Ответ 4

мне кажется, что с помощью ix локатора быстрее

df.sort_values(by='time',inplace=True)
df.ix[(df.time > start) & (df.time <= end),:]

Ответ 5

Я узнал, что те объекты datetime могут стать очень голодными и потребовать больше вычислительных усилий, Особенно, если они установлены как индекс (объекты DatetimeIndex?)

Я думаю, что ваш лучший выбор - просто нарисовать df.time, начать и завершить в отметки времени UNIX (как ints, а не datetime dtypes) и провести простое целочисленное сравнение.

Временная метка UNIX будет выглядеть так: 1471554233 (время публикации). Подробнее об этом здесь: https://en.wikipedia.org/wiki/Unix_time

Некоторые соображения при этом (например, помните о часовых поясах): Преобразование даты и времени в временную метку Unix и преобразование ее обратно в python

Ответ 6

Я думаю, что вы уже делаете вещи относительно эффективным способом.

При работе с временными рядами обычно рекомендуется использовать столбец, используя ваши метки времени как индекс DataFrame. Использование RangeIndex, поскольку ваш индекс не очень полезен. Тем не менее, я провел пару тестов на (2650069, 2) DataFrame, содержащих 6 месяцев данных о торговых тиках из данной акции на данном обмене, и выясняется ваш подход (создание булевого массива и его использование для срезания DataFrame) похоже, в 10 раз быстрее, чем обычная DatetimeIndex резка (которая, как я думал, была быстрее).

Те данные, которые я тестировал, выглядят следующим образом:

                                Price  Volume
time                                         
2016-02-10 11:16:15.951403000  6197.0   200.0
2016-02-10 11:16:16.241380000  6197.0   100.0
2016-02-10 11:16:16.521871000  6197.0   900.0
2016-02-10 11:16:16.541253000  6197.0   100.0
2016-02-10 11:16:16.592049000  6196.0   200.0

Настройка start/end:

start = df.index[len(df)/4]
end = df.index[len(df)/4*3]

Тест 1:

%%time
_ = df[start:end]  # Same for df.ix[start:end]

CPU times: user 413 ms, sys: 20 ms, total: 433 ms
Wall time: 430 ms

С другой стороны, используя ваш подход:

df = df.reset_index()
df.columns = ['time', 'Price', 'Volume']

Тест 2:

%%time
u = (df['time'] > start) & (df['time'] <= end)

CPU times: user 21.2 ms, sys: 368 µs, total: 21.6 ms
Wall time: 20.4 ms

Тест 3:

%%time
_ = df[u]

CPU times: user 10.4 ms, sys: 27.6 ms, total: 38.1 ms
Wall time: 36.8 ms

Тест 4:

%%time
_ = df[(df['time'] > start) & (df['time'] <= end)]

CPU times: user 21.6 ms, sys: 24.3 ms, total: 45.9 ms
Wall time: 44.5 ms

Примечание. Каждый блок кода соответствует ячейке ноутбука Jupyter и ее выходному значению. Я использую магию %%time, потому что %%timeit обычно дает некоторые проблемы кэширования, которые делают код более быстрым, чем на самом деле. Кроме того, ядро ​​перезапустилось после каждого запуска.

Я не совсем уверен, почему это так (я думал, что нарезка с помощью DatetimeIndex сделает вещи быстрее), но я думаю, что это, вероятно, имеет какое-то отношение к тому, как все работает под капотом с numpy (скорее всего, Операция slatetime slices генерирует логический массив, который затем используется внутренне с помощью numpy, чтобы на самом деле выполнять нарезку - но не цитируйте меня на этом).