Pandas скользящий применяется с использованием нескольких столбцов - PullRequest
3 голосов
/ 18 марта 2020

Я пытаюсь использовать функцию прокрутки pandas.DataFrame.rolling.apply() для нескольких столбцов. Python версия 3.7, pandas - 1.0.2.

import pandas as pd

#function to calculate
def masscenter(x):
    print(x); # for debug purposes
    return 0;

#simple DF creation routine
df = pd.DataFrame( [['02:59:47.000282', 87.60, 739],
                    ['03:00:01.042391', 87.51, 10],
                    ['03:00:01.630182', 87.51, 10],
                    ['03:00:01.635150', 88.00, 792],
                    ['03:00:01.914104', 88.00, 10]], 
                   columns=['stamp', 'price','nQty'])
df['stamp'] = pd.to_datetime(df2['stamp'], format='%H:%M:%S.%f')
df.set_index('stamp', inplace=True, drop=True)

'stamp' является монотонным c и уникальным, 'price' является двойным и не содержит NaN, 'nQty' является целым числом и также не содержит NaN.

Итак, мне нужно вычислить «центр масс», т. е. sum(price*nQty)/sum(nQty).

Что я пробовал до сих пор:

df.apply(masscenter, axis = 1)

masscenter вызывается 5 раз с одной строкой, и вывод будет выглядеть как

price     87.6
nQty     739.0
Name: 1900-01-01 02:59:47.000282, dtype: float64

Это желательный вход для masscenter, потому что я могу легко получить доступ к price и nQty используя x[0], x[1]. Тем не менее, я застрял с rolling.apply() Чтение документов DataFrame.rolling () и roll.apply () Я предполагал, что с использованием 'axis' в rolling() и 'raw' в apply человек достигает подобного поведения. Наивный подход

rol = df.rolling(window=2)
rol.apply(masscenter)

печатает строку за строкой (увеличивая количество строк до размера окна)

stamp
1900-01-01 02:59:47.000282    87.60
1900-01-01 03:00:01.042391    87.51
dtype: float64

затем

stamp
1900-01-01 02:59:47.000282    739.0
1900-01-01 03:00:01.042391     10.0
dtype: float64

Итак, столбцы передаются до masscenter отдельно (ожидается).

К сожалению, в документации почти нет информации о 'axis'. Однако следующий вариант был, очевидно,

rol = df.rolling(window=2, axis = 1)
rol.apply(masscenter)

Никогда не вызывает masscenter и вызывает ValueError in rol.apply(..)

> Length of passed values is 1, index implies 5

Я признаю, что я не уверен в параметре 'axis' и как это работает из-за отсутствия документации. Это первая часть вопроса: Что здесь происходит? Как правильно использовать «ось»? Для чего он предназначен?

Конечно, ранее были ответы, а именно:

Как применить функцию к двум столбцам- of- pandas -dataframe
Работает для всего DataFrame, не Rolling.

Как вызвать- pandas -rolling-apply-with-parameters- from-множественный столбец
Ответ предлагает написать мою собственную функцию прокрутки, но виновник для меня тот же, что и в комментариях : что, если нужно использовать размер окна смещения ( например, '1T') для неоднородных временных меток?
Мне не нравится идея заново изобретать колесо с нуля. Также я хотел бы использовать pandas для всего, чтобы предотвратить несоответствие между наборами, полученными из pandas и «самодельным броском». Есть другой ответ на этот вопрос, предлагающий заполнить фрейм данных отдельно и рассчитать все, что мне нужно, но это не сработает: размер хранимых данных будет огромен. Та же самая идея, представленная здесь:
Apply-Rolling-function-on- pandas -dataframe-with-множественные аргументы

Еще один вопрос и ответ, опубликованный здесь
Pandas -using-Rolling-On-Multi-Columns
Это хорошо и ближе всего к моей проблеме, но опять же, нет возможности использовать размеры смещенного окна (window = '1T').

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

Вторая часть вопрос в следующем: Есть ли возможность одновременно пролистывать несколько столбцов, используя pandas 1.0.x со смещением размера окна?

Большое спасибо.

Ответы [ 3 ]

1 голос
/ 24 марта 2020

Так что я не нашел способа перевернуть две колонки, но без встроенных pandas функций. Код указан ниже.

# function to find an index corresponding
# to current value minus offset value
def prevInd(series, offset, date):
    offset = to_offset(offset)
    end_date = date - offset
    end = series.index.searchsorted(end_date, side="left")
    return end

# function to find an index corresponding
# to the first value greater than current
# it is useful when one has timeseries with non-unique
# but monotonically increasing values
def nextInd(series, date):
    end = series.index.searchsorted(date, side="right")
    return end

def twoColumnsRoll(dFrame, offset, usecols, fn, columnName = 'twoColRol'):
    # find all unique indices
    uniqueIndices = dFrame.index.unique()
    numOfPoints = len(uniqueIndices)
    # prepare an output array
    moving = np.zeros(numOfPoints)
    # nameholders
    price = dFrame[usecols[0]]
    qty   = dFrame[usecols[1]]

    # iterate over unique indices
    for ii in range(numOfPoints):
        # nameholder
        pp = uniqueIndices[ii]
        # right index - value greater than current
        rInd = afta.nextInd(dFrame,pp)
        # left index - the least value that 
        # is bigger or equal than (pp - offset)
        lInd = afta.prevInd(dFrame,offset,pp)
        # call the actual calcuating function over two arrays
        moving[ii] = fn(price[lInd:rInd], qty[lInd:rInd])
    # construct and return DataFrame
    return pd.DataFrame(data=moving,index=uniqueIndices,columns=[columnName])

Этот код работает, но он относительно медленный и неэффективный. Я полагаю, можно использовать numpy .lib.stride_tricks из Как вызвать pandas .rolling.apply с параметрами из нескольких столбцов? , чтобы ускорить процесс. Однако go big или go home - я закончил писать функцию на C ++ и оболочку для нее.
Я бы не хотел публиковать его как ответ, поскольку это обходной путь, и я не ответил ни на одну из частей моего вопроса, но это слишком долго для комментария.

1 голос
/ 29 марта 2020

Как насчет этого:

def masscenter(ser):
    print(df.loc[ser.index])
    return 0

rol = df.price.rolling(window=2)
rol.apply(masscenter, raw=False)

Используется скользящая логика c для получения подмножеств из произвольного столбца. Опция raw = False предоставляет вам значения индекса для этих подмножеств (которые даются вам как Серии), затем вы используете эти значения индекса для получения срезов из нескольких столбцов из вашего исходного DataFrame.

0 голосов
/ 18 марта 2020

Вы можете использовать функцию roll_apply из numpy_ext модуля:

import numpy as np
import pandas as pd
from numpy_ext import rolling_apply


def masscenter(price, nQty):
    return np.sum(price * nQty) / np.sum(nQty)


df = pd.DataFrame( [['02:59:47.000282', 87.60, 739],
                    ['03:00:01.042391', 87.51, 10],
                    ['03:00:01.630182', 87.51, 10],
                    ['03:00:01.635150', 88.00, 792],
                    ['03:00:01.914104', 88.00, 10]], 
                   columns=['stamp', 'price','nQty'])
df['stamp'] = pd.to_datetime(df['stamp'], format='%H:%M:%S.%f')
df.set_index('stamp', inplace=True, drop=True)

window = 2
df['y'] = rolling_apply(masscenter, window, df.price.values, df.nQty.values)
print(df)

                            price  nQty          y
stamp                                             
1900-01-01 02:59:47.000282  87.60   739        NaN
1900-01-01 03:00:01.042391  87.51    10  87.598798
1900-01-01 03:00:01.630182  87.51    10  87.510000
1900-01-01 03:00:01.635150  88.00   792  87.993890
1900-01-01 03:00:01.914104  88.00    10  88.000000
...