Python pandas roll_apply ввод двух столбцов в функцию

Следуя этому вопросу пользовательской функции Python с помощью функции roll_apply для pandas, об использовании rolling_apply. Хотя я продвигался с помощью своей функции, я изо всех сил стараюсь иметь дело с функцией, для которой требуется два или более столбца:

Создание той же настройки, что и раньше

import pandas as pd
import numpy as np
import random

tmp  = pd.DataFrame(np.random.randn(2000,2)/10000, 
                    index=pd.date_range('2001-01-01',periods=2000),
                    columns=['A','B'])

Но слегка изменив функцию, чтобы взять два столбца.

def gm(df,p):
    df = pd.DataFrame(df)
    v =((((df['A']+df['B'])+1).cumprod())-1)*p
    return v.iloc[-1]

Выдается следующая ошибка:

pd.rolling_apply(tmp,50,lambda x: gm(x,5))

  KeyError: u'no item named A'

Я думаю, что это потому, что вход для лямбда-функции является ndarray длины 50 и только первого столбца и не принимает в качестве входных данных два столбца. Есть ли способ получить оба столбца в качестве входов и использовать его в функции rolling_apply.

Снова любая помощь будет принята с благодарностью...

Ответ 1

Похоже, roll_apply попытается преобразовать ввод пользовательских функций в ndarray (http://pandas.pydata.org/pandas-docs/stable/generated/pandas.stats.moments.rolling_apply.html?highlight=rolling_apply#pandas.stats.moments.rolling_apply).

Обходной путь основан на использовании вспомогательного столбца ii, который используется для выбора окна внутри управляющей функции gm:

import pandas as pd
import numpy as np
import random

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

def gm(ii, df, p):
    x_df = df.iloc[map(int, ii)]
    #print x_df
    v =((((x_df['A']+x_df['B'])+1).cumprod())-1)*p
    #print v
    return v.iloc[-1]

#print tmp.head()
res = pd.rolling_apply(tmp.ii, 50, lambda x: gm(x, tmp, 5))
print res

Ответ 2

Все функции roll_ * работают с массивом 1d. Я уверен, что можно изобрести некоторые обходные пути для прохождения 2-мерных массивов, но в вашем случае вы можете просто предкоммутировать значения строк для оценки прокатки:

>>> def gm(x,p):
...     return ((np.cumprod(x) - 1)*p)[-1]
...
>>> pd.rolling_apply(tmp['A']+tmp['B']+1, 50, lambda x: gm(x,5))
2001-01-01   NaN
2001-01-02   NaN
2001-01-03   NaN
2001-01-04   NaN
2001-01-05   NaN
2001-01-06   NaN
2001-01-07   NaN
2001-01-08   NaN
2001-01-09   NaN
2001-01-10   NaN
2001-01-11   NaN
2001-01-12   NaN
2001-01-13   NaN
2001-01-14   NaN
2001-01-15   NaN
...
2006-06-09   -0.000062
2006-06-10   -0.000128
2006-06-11    0.000185
2006-06-12   -0.000113
2006-06-13   -0.000962
2006-06-14   -0.001248
2006-06-15   -0.001962
2006-06-16   -0.003820
2006-06-17   -0.003412
2006-06-18   -0.002971
2006-06-19   -0.003882
2006-06-20   -0.003546
2006-06-21   -0.002226
2006-06-22   -0.002058
2006-06-23   -0.000553
Freq: D, Length: 2000

Ответ 3

Здесь другая версия этого вопроса: Использование roll_apply в объекте DataFrame. Используйте это, если ваша функция возвращает серию.

Так как ваш возвращает скаляр, сделайте это.

In [71]: df  = pd.DataFrame(np.random.randn(2000,2)/10000, 
                    index=pd.date_range('2001-01-01',periods=2000),
                    columns=['A','B'])

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

In [72]: def gm(df,p):
              v =((((df['A']+df['B'])+1).cumprod())-1)*p
              return (df.index[0],v.iloc[-1])


In [73]: Series(dict([ gm(df.iloc[i:min((i+1)+50,len(df)-1)],5) for i in xrange(len(df)-50) ]))

Out[73]: 
2001-01-01    0.000218
2001-01-02   -0.001048
2001-01-03   -0.002128
2001-01-04   -0.003590
2001-01-05   -0.004636
2001-01-06   -0.005377
2001-01-07   -0.004151
2001-01-08   -0.005155
2001-01-09   -0.004019
2001-01-10   -0.004912
2001-01-11   -0.005447
2001-01-12   -0.005258
2001-01-13   -0.004437
2001-01-14   -0.004207
2001-01-15   -0.004073
...
2006-04-20   -0.006612
2006-04-21   -0.006299
2006-04-22   -0.006320
2006-04-23   -0.005690
2006-04-24   -0.004316
2006-04-25   -0.003821
2006-04-26   -0.005102
2006-04-27   -0.004760
2006-04-28   -0.003832
2006-04-29   -0.004123
2006-04-30   -0.004241
2006-05-01   -0.004684
2006-05-02   -0.002993
2006-05-03   -0.003938
2006-05-04   -0.003528
Length: 1950

Ответ 4

Не уверен, что все еще актуально здесь, с новыми классами rolling на пандах, всякий раз, когда мы передаем raw=False в apply, мы на самом деле передаем ряд в оболочку, что означает, что у нас есть доступ к индексу каждого наблюдения, и может использовать это для дальнейшей обработки нескольких столбцов.

Из документов:

raw: bool, default None

  False: передает каждую строку или столбец в виде Series в функцию.

     True или None: переданная функция вместо этого будет получать объекты ndarray. Если вы просто применяете функцию уменьшения NumPy, это приведет к гораздо лучшей производительности.

В этом случае мы можем сделать следующее:

### create a func for multiple columns
def cust_func(s):

    val_for_col2 = df.loc[s.index, col2] #.values
    val_for_col3 = df.loc[s.index, col3] #.values
    val_for_col4 = df.loc[s.index, col4] #.values

    ## apply over multiple column values
    return np.max(s) *np.min(val_for_col2)*np.max(val_for_cal3)*np.mean(val_for_col4)


### Apply to the dataframe
df.rolling('10s')['col1'].apply(cust_func, raw=False)

Обратите внимание, что здесь мы все еще можем использовать все функциональные возможности класса pandas rolling, что особенно полезно при работе с окнами, связанными со временем.

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