Numpy-версия прокатного максимума в пандах

TL; DR: Мой вопрос о том, как я могу улучшить свою функцию, чтобы превзойти собственную функцию движущегося максимума панд?


Справочная информация:

Поэтому я работаю со многими скользящими средними, скользящим максимумом и скользящим минимумом и т. д., и единственные движущиеся окна, подобные функциям, которые я нашел до сих пор, находятся в метод pandas.rolling. Дело в том, что данные, которые у меня есть, представляют собой массивы numpy, и конечный результат, который я хочу, также должен быть в массивах numpy; насколько я хочу просто преобразовать его в серию панд и обратно в массив numpy, чтобы выполнить эту работу следующим образом:

result2_max = pd.Series(data_array).rolling(window).max().to_numpy()

, это слишком непитоново, поскольку преобразование типов данных кажется ненужным, и могут быть способы сделать то же самое исключительно в реализации numpy.

Однако, как бы не питонично это ни казалось, это быстрее, чем любые подходы, которые я придумал или видел в Интернете. Ниже я приведу небольшие тесты:

import numpy as np
import pandas as pd

def numpy_rolling_max(data, window):

    data = data[::-1]
    data_strides = data.strides[0]

    movin_window = np.lib.stride_tricks.as_strided(data, 
                                                    shape=(data.shape[0] - window +1, window), 
                                                    strides = (data_strides ,data_strides)
                                                    )[::-1]
    max_window =np.amax(movin_window, axis = 1)#this line seems to be the bottleneck


    nan_array = np.full(window - 1, np.nan)
    return np.hstack((nan_array, max_window))


def pandas_rolling_max(data, window):
    return pd.Series(data).rolling(window).max().to_numpy()

length = 120000
window = 190
data = np.arange(length) + 0.5

result1_max = numpy_rolling_max(data, window)#21.9ms per loop
result2_max = pandas_rolling_max(data, window)#5.43ms per loop

result_comparision = np.allclose(result1_max, result2_max, equal_nan = True)

С размером массива = 120 КБ, окном = 190, максимальная прокрутка pandas примерно в 3 раза быстрее, чем версия с numpy. Я понятия не имею, что делать дальше, так как я уже максимально векторизовал свою собственную функцию, но она все еще намного медленнее, чем версия для панд, и я действительно не знаю, почему.

заранее спасибо

РЕДАКТИРОВАТЬ: я нашел узкое место, и это строка:

max_window =np.amax(movin_window, axis = 1)

Но видя, что это уже вызов векторизованной функции, я до сих пор не знаю, как действовать дальше.


person mathguy    schedule 20.05.2019    source источник
comment
Вы пробовали convolve в numpy   -  person BENY    schedule 20.05.2019
comment
@WeNYoBen Я не знаю, и я не знаю, как свертка может помочь .... Не могли бы вы показать мне, как?   -  person mathguy    schedule 20.05.2019
comment
Проверьте stackoverflow.com /вопросы/43288542/   -  person BENY    schedule 20.05.2019
comment
@WeNYoBen проверил это, к сожалению, версия по умолчанию для панд, кажется, превосходит ответы ниже этих сообщений; также np.convolve не играет никакой роли в приведенных ниже решениях:/   -  person mathguy    schedule 20.05.2019
comment
Вы пробовали версию Scipy (из scipy.ndimage.filters импортировать max_filter1d) - stackoverflow.com/a/43288787?   -  person Divakar    schedule 20.05.2019
comment
@Divakar Я ждал твоего ответа, так как я видел много твоих фантастических ответов среди других сообщений. Да, я пытался, но граница/горизонт/конечные точки результата не совпадают с версией для панд. Под этим я подразумеваю длину выходного массива, количество np.nan и т. Д. Я также пробовал некоторые его изменения, но не смог найти очевидную закономерность количества nan и прочего.   -  person mathguy    schedule 20.05.2019


Ответы (1)


Мы можем использовать фильтр 1D max из Scipy, чтобы воспроизвести то же поведение, что и pandas, но при этом быть немного более эффективным -

from scipy.ndimage.filters import maximum_filter1d

def max_filter1d_same(a, W, fillna=np.nan):
    out_dtype = np.full(0,fillna).dtype
    hW = (W-1)//2 # Half window size
    out = maximum_filter1d(a,size=W, origin=hW)
    if out.dtype is out_dtype:
        out[:W-1] = fillna
    else:
        out = np.concatenate((np.full(W-1,fillna), out[W-1:]))
    return out

Примеры запусков -

In [161]: np.random.seed(0)
     ...: a = np.random.randint(0,999,(20))
     ...: window = 3

In [162]: a
Out[162]: 
array([684, 559, 629, 192, 835, 763, 707, 359,   9, 723, 277, 754, 804,
       599,  70, 472, 600, 396, 314, 705])

In [163]: pd.Series(a).rolling(window).max().to_numpy()
Out[163]: 
array([ nan,  nan, 684., 629., 835., 835., 835., 763., 707., 723., 723.,
       754., 804., 804., 804., 599., 600., 600., 600., 705.])

In [164]: max_filter1d_same(a,window)
Out[164]: 
array([ nan,  nan, 684., 629., 835., 835., 835., 763., 707., 723., 723.,
       754., 804., 804., 804., 599., 600., 600., 600., 705.])

# Use same dtype fillna for better memory efficiency
In [165]: max_filter1d_same(a,window,fillna=0)
Out[165]: 
array([  0,   0, 684, 629, 835, 835, 835, 763, 707, 723, 723, 754, 804,
       804, 804, 599, 600, 600, 600, 705])

Тайминги на реальных размерах тест-кейсов -

In [171]: # Actual test-cases sizes
     ...: np.random.seed(0)
     ...: data_array = np.random.randint(0,999,(120000))
     ...: window = 190

In [172]: %timeit pd.Series(data_array).rolling(window).max().to_numpy()
100 loops, best of 3: 4.43 ms per loop

In [173]: %timeit max_filter1d_same(data_array,window)
100 loops, best of 3: 1.95 ms per loop
person Divakar    schedule 20.05.2019
comment
1 плюса мало! Я только что проверил ответ с циклом по размерам окна от 1 до 1000 с двумя разными массивами данных, и результаты совпадают с ускорением на 200+%. Спасибо, сэр - person mathguy; 20.05.2019
comment
Хотя у меня есть еще один вопрос. В чем «секрет» max/min filter1d в scipy, который делает вычисление max/min по движущемуся окну таким быстрым? Может ли такой «секрет» применяться к любым функциям, не только к max и min, к движущемуся окну вообще? Я все еще новичок в numpy и хотел бы узнать больше, если это возможно. - person mathguy; 20.05.2019
comment
@mathguy Думаю, он скрыто реализован на C и настроен для использования данных массива, поэтому предполагается, что таким образом он будет эффективен при использовании пространственной локальности данных массива. - person Divakar; 20.05.2019
comment
Есть ли способ использовать пространственную локализацию данных массива с помощью cython или любого эквивалента, если бы я написал другую функцию, которая будет применяться к движущемуся окну? Если есть, то это будет огромный шаг вперед для любых вычислений pandas. - person mathguy; 20.05.2019
comment
@mathguy Cython определенно должен помочь, и, насколько мне известно, он будет использовать пространственное расположение. Итак, попробуйте. - person Divakar; 20.05.2019