Как использовать Pandas Groupby и применять лямбда для оценки логического состояния

Я учил себя Python, используя биржевые данные, но я застрял в этом вопросе. Я пытаюсь определить пересечение скользящей средней. Я работаю с ежедневными данными в панде MultiIndex DataFrame. Ниже приведен фрагмент структуры данных, с которой я работаю.

import pandas as pd
import numpy as np

data = {'date': pd.Series(['2016-1-4', '2016-1-4', '2016-1-4', 
                           '2016-1-5', '2016-1-5', '2016-1-5', 
                           '2016-1-6', '2016-1-6', '2016-1-6']),
        'ticker': pd.Series(['NYMX', 'EVAR', 'PMV', 
                             'NYMX', 'EVAR', 'PMV', 
                             'NYMX', 'EVAR', 'PMV']),
        'twohundredsma': pd.Series([2.3, 3.58, 0.458, 
                                    2.31, 3.56, 0.459, 
                                    2.32, 3.55, 0.46]),
        'fiveema': pd.Series([2.33, 1.31, 0.54, 
                              2.33, 1.28, 0.54, 
                              2.3, 1.25, 0.54])}

df = pd.DataFrame(data)
df['date'] = pd.to_datetime(df['date'])
df.set_index(['date', 'ticker'], inplace=True)

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

Тем не менее, проблема у меня с использованием groupby функция для применения этой функции к каждому биржевому тикеру. Мой первоначальный подход состоял в том, чтобы использовать applylambda функция. Код ниже добавляет 2 новых столбца, но столбец "Five200bull" заполняется значениями "nan" без ошибок.

def five_cross(df):
    df['fiveminus200'] = df['fiveema'] - df['twohundredsma']    

    df['five200bull'] = df.groupby(level='ticker').apply(lambda x: 
      np.sign(x['fiveminus200'])!=np.sign(x['fiveminus200'].shift(1)))

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

def add_five_bull(df):
    df['five200bull'] = np.sign(df['fiveminus200']) != np.sign(df['fiveminus200'].shift(1))

def five_cross(df):
    df['fiveminus200'] = df['fiveema'] - df['twohundredsma']    

    # group by ticker
    grouped = df.groupby(level='ticker')

    # pass each ticker in a df to function
    for tick, group in grouped:
        add_five_bull(group)

При таком подходе столбец "Five200bull" никогда не добавляется к df, и я получаю печально известную SettingWithCopyWarning, Я пытался добавить df.loc[:, 'fiveminus200'] к add_five_bull функционировать, но, кроме того, что с большим набором данных требуется гораздо больше времени, он, похоже, не дал никакого результата.

Очевидно, что в моей логике есть какой-то недостаток, и я был бы признателен за любую помощь в решении.

1 ответ

Решение

Я считаю, что вам нужен параметр group_keys=False для удаления добавляем новый уровень в выводе - тогда данные выровнены. Также shift вернуть первое значение NaN на группу, так np.sign поднять предупреждение:

RuntimeWarning: недопустимое значение, встречающееся в знаке np.sign(x['fiveminus200'])!= Np.sign(x['fiveminus200']. Shift(1)))

Решение заменить NaN к некоторому значению, например False или же True от fillna:

def five_cross(df):
    df['fiveminus200'] = df['fiveema'] - df['twohundredsma']    

    df['five200bull'] = df.groupby(level='ticker', group_keys=False).apply(lambda x: 
      np.sign(x['fiveminus200'])!=np.sign(x['fiveminus200'].shift(1).fillna(False)))
    return df  

print (five_cross(df))
                   fiveema  twohundredsma  fiveminus200  five200bull
date       ticker                                                   
2016-01-04 NYMX       2.33          2.300         0.030         True
           EVAR       1.31          3.580        -2.270         True
           PMV        0.54          0.458         0.082         True
2016-01-05 NYMX       2.33          2.310         0.020        False
           EVAR       1.28          3.560        -2.280        False
           PMV        0.54          0.459         0.081        False
2016-01-06 NYMX       2.30          2.320        -0.020         True
           EVAR       1.25          3.550        -2.300        False
           PMV        0.54          0.460         0.080        False

def five_cross(df):
    df['fiveminus200'] = df['fiveema'] - df['twohundredsma']    

    df1 = df.groupby(level='ticker').apply(lambda x: 
      np.sign(x['fiveminus200'])!=np.sign(x['fiveminus200'].shift(1).fillna(False)))
    return df1 

print (five_cross(df))
ticker  date        ticker
EVAR    2016-01-04  EVAR       True
        2016-01-05  EVAR      False
        2016-01-06  EVAR      False
NYMX    2016-01-04  NYMX       True
        2016-01-05  NYMX      False
        2016-01-06  NYMX       True
PMV     2016-01-04  PMV        True
        2016-01-05  PMV       False
        2016-01-06  PMV       False
Name: fiveminus200, dtype: bool
Другие вопросы по тегам