Панды: пользовательская функция агрегации функции WMAPE для нескольких столбцов без цикла for? - PullRequest
0 голосов
/ 22 февраля 2019

Цель: сгруппировать фрейм данных панд с помощью пользовательской функции WMAPE (средневзвешенная абсолютная процентная ошибка) для нескольких столбцов прогноза и одного фактического столбца данных без цикла for.Я знаю цикл for & слияния выходных фреймов данных добьются цели.Я хочу сделать это эффективно.

Имеют: Функция WMAPE, успешное использование функции WMAPE в одном столбце прогноза на фрейме данных.Один столбец фактических данных, переменное количество столбцов прогноза.

Входные данные: Pandas DataFrame с несколькими категориальными столбцами (City, Person, DT, HOUR), один столбец фактических данных (Actual)и четыре столбца прогноза (Forecast_1 ... Forecast_4).См. Ссылку для csv: https://www.dropbox.com/s/tidf9lj80a1dtd8/data_small_2.csv?dl=1

Необходимость: Функция WMAPE, применяемая при групповом режиме для нескольких столбцов со списком столбцов прогноза, введенных в строку группового режима.

Требуемый вывод: Выходной кадр данных со столбцами категориальных групп и всеми столбцами WMAPE.Маркировка предпочтительна, но не нужна (выводите изображение ниже).

Успешный код на данный момент: Две функции WMAPE: одна для ввода двух последовательностей и вывода одного значения с плавающей запятой (wmape), иодин структурированный для использования в групповом режиме (wmape_gr):

def wmape(actual, forecast):
    # we take two series and calculate an output a wmape from it

    # make a series called mape
    se_mape = abs(actual-forecast)/actual

    # get a float of the sum of the actual
    ft_actual_sum = actual.sum()

    # get a series of the multiple of the actual & the mape
    se_actual_prod_mape = actual * se_mape

    # summate the prod of the actual and the mape
    ft_actual_prod_mape_sum = se_actual_prod_mape.sum()

    # float: wmape of forecast
    ft_wmape_forecast = ft_actual_prod_mape_sum / ft_actual_sum

    # return a float
    return ft_wmape_forecast

def wmape_gr(df_in, st_actual, st_forecast):
    # we take two series and calculate an output a wmape from it

    # make a series called mape
    se_mape = abs(df_in[st_actual] - df_in[st_forecast]) / df_in[st_actual]

    # get a float of the sum of the actual
    ft_actual_sum = df_in[st_actual].sum()

    # get a series of the multiple of the actual & the mape
    se_actual_prod_mape = df_in[st_actual] * se_mape

    # summate the prod of the actual and the mape
    ft_actual_prod_mape_sum = se_actual_prod_mape.sum()

    # float: wmape of forecast
    ft_wmape_forecast = ft_actual_prod_mape_sum / ft_actual_sum

    # return a float
    return ft_wmape_forecast

# read in data directly from Dropbox
df = pd.read_csv('https://www.dropbox.com/s/tidf9lj80a1dtd8/data_small_2.csv?dl=1',sep=",",header=0)

# grouping with 3 columns. wmape_gr uses the Actual column, and Forecast_1 as inputs
df_gr = df.groupby(['City','Person','DT']).apply(wmape_gr,'Actual','Forecast_1')

Выход выглядит как (первые две строки):

enter image description here

В желаемом выводе все прогнозы будут записаны в одном кадре (фиктивные данные для Forecast_2 ... Forecast_4).Я могу уже сделать это с помощью цикла for.Я просто хочу сделать это в группе.Я хочу вызвать функцию wmape четыре раза.Буду признателен за любую помощь.

Ответы [ 3 ]

0 голосов
/ 25 февраля 2019

Это действительно хорошая проблема, чтобы показать, как оптимизировать groupby.apply в пандах.Для решения этих проблем я использую два принципа:

  1. Любые вычисления, независимые от группы, не должны выполняться внутри группы
  2. Если имеется встроенныйгрупповой метод, сначала используйте его перед применением apply

Давайте пройдем построчно через вашу wmape_gr функцию.

se_mape = abs(df_in[st_actual] - df_in[st_forecast]) / df_in[st_actual]

Эта строка полностью независима от любой группы.Вы должны сделать этот расчет за пределами заявки.Ниже я делаю это для каждого из столбцов прогноза:

df['actual_forecast_diff_1'] = (df['Actual'] - df['Forecast_1']).abs() / df['Actual']
df['actual_forecast_diff_2'] = (df['Actual'] - df['Forecast_2']).abs() / df['Actual']
df['actual_forecast_diff_3'] = (df['Actual'] - df['Forecast_3']).abs() / df['Actual']
df['actual_forecast_diff_4'] = (df['Actual'] - df['Forecast_4']).abs() / df['Actual']

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

ft_actual_sum = df_in[st_actual].sum()

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

Давайте перейдем к следующей строке:

se_actual_prod_mape = df_in[st_actual] * se_mape

Это снова не зависит от группы.Давайте вычислим это для DataFrame в целом.

df['forecast1_wampe'] = df['actual_forecast_diff_1'] *  df['Actual']
df['forecast2_wampe'] = df['actual_forecast_diff_2'] *  df['Actual']
df['forecast3_wampe'] = df['actual_forecast_diff_3'] *  df['Actual']
df['forecast4_wampe'] = df['actual_forecast_diff_4'] *  df['Actual']

Давайте перейдем к последним двум строкам:

ft_actual_prod_mape_sum = se_actual_prod_mape.sum()
ft_wmape_forecast = ft_actual_prod_mape_sum / ft_actual_sum

Эти строки снова зависят от группы, но мы по-прежнемуне нужно использовать применять.Теперь у нас есть каждый из 4 столбцов «cast_wampe », независимо от группы.Нам просто нужно сложить каждый из них на группу.То же самое относится и к столбцу «Фактический».

Мы можем выполнить две отдельные групповые операции для суммирования каждого из этих столбцов, например:

g = df.groupby(['City', 'Person', 'DT'])
actual_sum = g['Actual'].sum()
forecast_wampe_cols = ['forecast1_wampe', 'forecast2_wampe', 'forecast3_wampe', 'forecast4_wampe']
forecast1_wampe_sum = g[forecast_wampe_cols].sum()

Мы получаем следующие Series и DataFrame, возвращаемые

enter image description here

enter image description here

Тогда нам просто нужно разделить каждый из столбцов в DataFrame наСерии.Нам нужно будет использовать метод div, чтобы изменить ориентацию деления так, чтобы индексы совпали

forecast1_wampe_sum.div(actual_sum, axis='index')

И это вернет наш ответ:

enter image description here

0 голосов
/ 01 марта 2019

без изменения функций

применение четырех раз

df_gr1 = df.groupby(['City','Person','DT']).apply(wmape_gr,'Actual','Forecast_1')
df_gr2 = df.groupby(['City','Person','DT']).apply(wmape_gr,'Actual','Forecast_2')
df_gr3 = df.groupby(['City','Person','DT']).apply(wmape_gr,'Actual','Forecast_3')
df_gr4 = df.groupby(['City','Person','DT']).apply(wmape_gr,'Actual','Forecast_4')

объединение их вместе

all1= pd.concat([df_gr1, df_gr2,df_gr3,df_gr4],axis=1, sort=False)

получение столбцов для города, человека и DT

all1['city']= [all1.index[i][0]  for i in range(len(df_gr1))]
all1['Person']= [all1.index[i][1]  for i in range(len(df_gr1))]
all1['DT']= [all1.index[i][2]  for i in range(len(df_gr1))]

переименовать столбцы и изменить порядок

df = all1.rename(columns={0:'Forecast_1_wmape', 1:'Forecast_2_wmape',2:'Forecast_3_wmape',3:'Forecast_4_wmape'})

df = df[['city','Person','DT','Forecast_1_wmape','Forecast_2_wmape','Forecast_3_wmape','Forecast_4_wmape']]

df=df.reset_index(drop=True)
0 голосов
/ 22 февраля 2019

Если вы измените wmape для работы с массивами с использованием широковещания, то вы можете сделать это за один снимок:

def wmape(actual, forecast):
    # Take a series (actual) and a dataframe (forecast) and calculate wmape
    # for each forecast. Output shape is (1, num_forecasts)

    # Convert to numpy arrays for broadasting
    forecast = np.array(forecast.values)
    actual=np.array(actual.values).reshape((-1, 1))

    # Make an array of mape (same shape as forecast)
    se_mape = abs(actual-forecast)/actual

    # Calculate sum of actual values
    ft_actual_sum = actual.sum(axis=0)

    # Multiply the actual values by the mape
    se_actual_prod_mape = actual * se_mape

    # Take the sum of the product of actual values and mape
    # Make sure to sum down the rows (1 for each column)
    ft_actual_prod_mape_sum = se_actual_prod_mape.sum(axis=0)

    # Calculate the wmape for each forecast and return as a dictionary
    ft_wmape_forecast = ft_actual_prod_mape_sum / ft_actual_sum
    return {f'Forecast_{i+1}_wmape': wmape for i, wmape in enumerate(ft_wmape_forecast)}

Затем используйте apply в соответствующих столбцах:

# Group the dataframe and apply the function to appropriate columns
new_df = df.groupby(['City', 'Person', 'DT']).apply(lambda x: wmape(x['Actual'], 
                                        x[[c for c in x if 'Forecast' in c]])).\
            to_frame().reset_index()

Это приводит к кадру данных с одним словарным столбцом.Intermediate Results

Один столбец можно преобразовать в несколько столбцов для правильного формата:

# Convert the dictionary in a single column into 4 columns with proper names
# and concantenate column-wise
df_grp = pd.concat([new_df.drop(columns=[0]), 
                    pd.DataFrame(list(new_df[0].values))], axis=1)

Результат:

Result of operations

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...