Python Получить k самых больших значений скользящего окна для каждого столбца

У меня есть набор данных в виде Pandas DataFrame, и я пытаюсь получить k самые большие значения в нескольких скользящих окнах разных размеров.

Упрощенная проблема:

import pandas as pd
import numpy as np
np.random.seed(42)

def GenerateData(N=20, num_cols=2):
    X = pd.DataFrame(np.random.rand(N, num_cols))
    return X
X = GenerateData()

## >>> X.head()
##    0         1
## 0  0.971595  0.329454
## 1  0.187766  0.138250
## 2  0.573455  0.976918
## 3  0.207987  0.672529
## 4  0.271034  0.549839

Моя цель — получить k самые большие значения в каждом скользящем окне для каждого столбца. Итак, если k_largest=3 и размеры скользящего окна равны windows=[4,7], нам нужны 3 самых больших значения для окон размером 4 и 7. В настоящее время я делаю это так:

def GetKLargestForWindow(windows=[4,7], k_largest=3, raw=False):
    laggedVals = []
    for L in windows:
        for k in range(k_largest):
            x_k_max = X.rolling(L).apply(lambda c: sorted(c, reverse=True)[k], raw=raw)
            x_k_max = x_k_max.add_prefix( f'W{L}_{k+1}_' )
            laggedVals.append( x_k_max )
    laggedVals = pd.concat(laggedVals, axis=1).sort_index(axis=1)
    return laggedVals
laggedVals = GetKLargestForWindow()

## >>> laggedVals.shape
## (20,12)

## >>> laggedVals.columns
## Index(['W4_1_0', 'W4_1_1', 'W4_2_0', 'W4_2_1', \
##  'W4_3_0', 'W4_3_1', 'W7_1_0','W7_1_1', \
##  'W7_2_0', 'W7_2_1', 'W7_3_0', 'W7_3_1'],dtype='object')

Обратите внимание, что в этом примере всего должно быть 12 столбцов. Имена столбцов там обозначают W{window_size}_{j}_{col}, где j=1,2,3, что соответствует 3 наибольшим значениям каждого размера окна для каждого столбца.

Однако мой набор данных очень велик, и я ищу более эффективный способ сделать это, поскольку код выполняется очень долго. Какие-либо предложения?


Ориентиры:

import timeit
## >>> timeit.timeit('GetKLargestForWindow()', globals=globals(), number=1000)
## 15.590040199999976

## >>> timeit.timeit('GetKLargestForWindow(raw=True)', globals=globals(), number=1000)
## 6.497314199999892

Редактировать: я в основном решил это - огромное ускорение (особенно в больших наборах данных, когда вы увеличиваете N, windows и k_largest), установив raw=True в функции apply-max. Думаю, я буду держать вознаграждение за все, что еще быстрее.

Обновленный пример для ясности.

Adam 29.04.2022 09:53
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения постов в Twitter с помощью Python, Tweepy и Flair
Анализ настроения текстовых сообщений может быть настолько сложным или простым, насколько вы его сделаете. Как и в любом ML-проекте, вы можете выбрать...
7 лайфхаков для начинающих Python-программистов
7 лайфхаков для начинающих Python-программистов
В этой статье мы расскажем о хитростях и советах по Python, которые должны быть известны разработчику Python.
Установка Apache Cassandra на Mac OS
Установка Apache Cassandra на Mac OS
Это краткое руководство по установке Apache Cassandra.
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
Сертификатная программа "Кванты Python": Бэктестер ансамблевых методов на основе ООП
В одном из недавних постов я рассказал о том, как я использую навыки количественных исследований, которые я совершенствую в рамках программы TPQ...
Создание персонального файлового хранилища
Создание персонального файлового хранилища
Вы когда-нибудь хотели поделиться с кем-то файлом, но он содержал конфиденциальную информацию? Многие думают, что электронная почта безопасна, но это...
Создание приборной панели для анализа данных на GCP - часть I
Создание приборной панели для анализа данных на GCP - часть I
Недавно я столкнулся с интересной бизнес-задачей - визуализацией сбоев в цепочке поставок лекарств, которую могут просматривать врачи и...
1
1
82
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать встроенную функцию pandas (больше информации здесь). Это берет фрейм данных и применяет вычисление скользящего окна на основе встроенной функции pandas или собственной определенной функции (с применением). Он принимает только одно целое число в качестве окна или подкласса окна BaseIndexer. Я считаю, что здесь вы можете указать несколько окон для нескольких столбцов, но мне проще перебирать столбцы.

X = pd.DataFrame([[((-1)**i) * i*10, ((-1)**i) * -i*5] for i in range(20)])
x = pd.DataFrame() #Emtpy dataframe, here roling window will be stored
windows = [4,7]
k = 3
for window, colname in zip(windows,X.columns):
    x[colname] = X[colname].rolling(window).max()

print(x.nlargest(k,columns=x.columns)) #find max k values

результат

19  180.0  95.0
18  180.0  85.0
17  160.0  85.0
16  160.0  75.0
0     NaN   NaN
1     NaN   NaN
2     NaN   NaN

Если я не ошибаюсь, каждый столбец x представляет максимальные значения каждого скользящего окна. Затем следующий nlargest() возвращает k самые большие значения максимальных значений. То, что я ищу, на самом деле состоит в том, чтобы каждая строка представляла k самые большие значения в пределах в скользящем окне, поэтому должно быть возвращено 6 столбцов.

Adam 23.04.2022 12:16

Код в вопросе возвращает то, что я ищу, но работает медленно.

Adam 23.04.2022 12:18
Ответ принят как подходящий

Как всегда, если вам нужна скорость, используйте numpy как можно больше. Циклы Python чрезвычайно медленны по сравнению с векторизованным кодом numpy:

from numpy.lib.stride_tricks import sliding_window_view

def GetKLargestForWindow_CodeDifferent(windows=[4,7], k_largest=3):
    n_row, n_col = X.shape

    data = []
    for w in windows:
        # Create a rolling view of size w for each column in the dataframe
        view = sliding_window_view(X, w, axis=0)
        # Sort each view, reverse it (so largest first), and take the first
        # k_largest elements
        view = np.sort(view)[..., ::-1][..., :k_largest]
        # Reshape the numpy array for easy conversion into a dataframe
        view = np.reshape(view, (n_row - w + 1, -1))
        # We know the first `w - 1` rows are all NaN since there are not enough
        # data for the rolling operation
        data.append(np.vstack([
            np.zeros((w - 1, view.shape[1])) + np.nan,
            view
        ]))

    # `data` is shaped in this order
    cols_1 = [f"W{w}_{k+1}_{col}" for w in windows for col in range(n_col) for k in range(k_largest)]
    # But we want the columns in this order for easy comparison with the original code
    cols_2 = [f"W{w}_{k+1}_{col}" for w in windows for k in range(k_largest) for col in range(n_col)]
    
    return pd.DataFrame(np.hstack(data), columns=cols_1)[cols_2]

Сначала сравним результат:

X = GenerateData(100_000, 2)
a = GetKLargestForWindow(raw=True)
b = GetKLargestForWindow_CodeDifferent()

assert a.compare(b).empty, "a and b are not the same"

Далее, давайте сравним их:

%timeit GetKLargestForWindow(raw=True)
5.31 s ± 128 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit GetKLargestForWindow_CodeDifferent()
54.1 ms ± 761 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)

Отличный ответ, спасибо! Следует отметить, что для этого решения требуется numpy >= v1.20.

Adam 30.04.2022 11:10

Другие вопросы по теме