У меня есть фрейм данных, в котором я хочу выполнить функцию, которая проверяет, является ли фактическое значение относительным максимумом, и проверяет, меньше ли предыдущие значения «n», чем фактическое значение.

Имея фрейм данных 'df_data':

temp_list = [128.71, 130.2242, 131.0, 131.45, 129.69, 130.17, 132.63, 131.63, 131.0499, 131.74, 133.6116, 134.74, 135.99, 138.789, 137.34, 133.46, 132.43, 134.405, 128.31, 129.1]
df_data = pd.DataFrame(temp)

Сначала я создаю функцию, которая будет проверять предыдущие условия:

def get_max(high, rolling_max, prev,post):
if ((high > prev) & (high>post) & (high>rolling_max)):
    return 1
else: 
    return 0
df_data['rolling_max'] = df_data.high.rolling(n).max().shift()

Затем я применяю предыдущую строку условий:

df_data['ismax'] = df_data.apply(lambda x: get_max(df_data['high'], df_data['rolling_max'],df_data['high'].shift(1),df_data['high'].shift(-1)),axis = 1)

Проблема в том, что я всегда получаю следующую ошибку:

ValueError: значение истинности Серии неоднозначно. Используйте a.empty, a.bool (), a.item (), a.any () или a.all ().

Это происходит из-за применения логического условия из функции get_max к Serie.

Мне бы хотелось иметь векторизованную функцию без использования циклов.

0
arodrisa 15 Окт 2021 в 18:37

2 ответа

Лучший ответ

Пытаться:

df_data['ismax'] = ((df_data['high'].gt(df_data.high.rolling(n).max().shift())) & (df_data['high'].gt(df_data['high'].shift(1))) & (df_data['high'].gt(df_data['high'].shift(-1)))).astype(int)
1
Muhammad Hassan 15 Окт 2021 в 16:02

Ошибка возникает из-за того, что вы отправляете всю серию (весь столбец) в функцию get_max, а не выполняете ее построчно. Создание новых столбцов для смещенных значений «prev» и «post» с последующим использованием df.apply(func, axis = 1) здесь нормально работает.

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

На моем компьютере следующий код:

  • LIST_MULTIPLIER = 1, векторизованный код: 0,29 с, построчный код: 0,38 с
  • LIST_MULTIPLIER = 100, векторизованный код: 0,31 с, построчный код = 13,27 с

Поэтому в общем случае лучше избегать использования df.apply(..., axis = 1), поскольку вы почти всегда можете получить лучшее решение, используя логические операторы.

import pandas as pd
from datetime import datetime

LIST_MULTIPLIER = 100
ITERATIONS = 100

def get_dataframe():
    temp_list = [128.71, 130.2242, 131.0, 131.45, 129.69, 130.17, 132.63, 
                 131.63, 131.0499, 131.74, 133.6116, 134.74, 135.99, 
                 138.789, 137.34, 133.46, 132.43, 134.405, 128.31, 129.1] * LIST_MULTIPLIER
    df = pd.DataFrame(temp_list)
    df.columns = ['high']
    return df

df_original = get_dataframe()

t1 = datetime.now()

for i in range(ITERATIONS):
    df = df_original.copy()
    df['rolling_max'] = df.high.rolling(2).max().shift()
    df['high_prev'] = df['high'].shift(1)
    df['high_post'] = df['high'].shift(-1)
    
    mask_prev = df['high'] > df['high_prev']
    mask_post = df['high'] > df['high_post']
    mask_rolling = df['high'] > df['rolling_max']
    
    mask_max = mask_prev & mask_post & mask_rolling
    
    df['ismax'] = 0
    df.loc[mask_max, 'ismax'] = 1
    
    
t2 = datetime.now()
print(f"{t2 - t1}")
df_first_method = df.copy()


t3 = datetime.now()

def get_max_rowwise(row):
    if ((row.high > row.high_prev) & 
        (row.high > row.high_post) & 
        (row.high > row.rolling_max)):
        return 1
    else: 
        return 0
    
for i in range(ITERATIONS):
    df = df_original.copy()
    df['rolling_max'] = df.high.rolling(2).max().shift()
    df['high_prev'] = df['high'].shift(1)
    df['high_post'] = df['high'].shift(-1)
    df['ismax'] = df.apply(get_max_rowwise, axis = 1)

t4 = datetime.now()
print(f"{t4 - t3}")
df_second_method = df.copy()
0
Python on Toast 15 Окт 2021 в 16:20