Pandas: 自定义 WMAPE 函数聚合函数到没有 for 循环的多列?
Pandas: custom WMAPE function aggregation function to multiple columns without for-loop?
Objective: 组 pandas 数据框在多个预测列和一个实际数据列上使用自定义 WMAPE(加权平均绝对百分比误差)函数,没有for循环。我知道输出数据帧的 for 循环和合并可以解决问题。我想高效地做到这一点。
具有: WMAPE 函数,在数据框的一个预测列上成功使用 WMAPE 函数。一列实际数据,可变数量的预测列。
输入数据: Pandas DataFrame 具有多个分类列(City、Person、DT、HOUR)、一个实际数据列(Actual)和四个预测列(Forecast_1 ... Forecast_4)。请参阅 link 以获取 csv:
https://www.dropbox.com/s/tidf9lj80a1dtd8/data_small_2.csv?dl=1
需要: WMAPE 函数在 groupby 期间应用于多列,并将预测列列表馈入 groupby 行。
所需输出: 具有分类组列和 WMAPE 的所有列的输出数据框。标签是首选但不需要(输出图像如下)。
到目前为止成功的代码:
两个 WMAPE 函数:一个用于输入两个系列并输出一个浮点值 (wmape),一个用于 groupby (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')
输出看起来像(前两行):
所需的输出将一次性包含所有预测(Forecast_2 ... Forecast_4 的虚拟数据)。我可以 已经 使用 for 循环执行此操作。我只想在 groupby 中进行。我想调用一个 wmape 函数四次。如果有任何帮助,我将不胜感激。
如果您修改 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()
这会产生一个只有一个字典列的数据框。
为了正确的格式,单列可以转换为多列:
# 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)
结果:
这是一个非常好的问题,展示了如何在 pandas 中优化 groupby.apply。我使用两个原则来帮助解决这些问题。
- 任何独立于组的计算都不应该在 groupby 中进行
- 如果有内置的groupby方法,请先使用再使用
申请
让我们逐行查看您的 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()
这一行依赖于组所以我们必须在这里使用一个groupby,但是没有必要把它放在apply函数中。下面会计算出来。
让我们转到下一行:
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 'forecast_wampe' 列中的每一列都独立于组进行了计算。我们只需要对每组中的每一个求和。 'Actual' 列也是如此。
我们可以 运行 两个单独的 groupby 操作来对每一列求和,如下所示:
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()
我们得到以下系列和返回的 DataFrame
然后我们只需要将 DataFrame 中的每一列除以 Series。我们需要使用 div
方法来改变分区的方向,以便索引对齐
forecast1_wampe_sum.div(actual_sum, axis='index')
而这个 returns 我们的答案:
不改变功能
申请四次
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)
Objective: 组 pandas 数据框在多个预测列和一个实际数据列上使用自定义 WMAPE(加权平均绝对百分比误差)函数,没有for循环。我知道输出数据帧的 for 循环和合并可以解决问题。我想高效地做到这一点。
具有: WMAPE 函数,在数据框的一个预测列上成功使用 WMAPE 函数。一列实际数据,可变数量的预测列。
输入数据: Pandas DataFrame 具有多个分类列(City、Person、DT、HOUR)、一个实际数据列(Actual)和四个预测列(Forecast_1 ... Forecast_4)。请参阅 link 以获取 csv: https://www.dropbox.com/s/tidf9lj80a1dtd8/data_small_2.csv?dl=1
需要: WMAPE 函数在 groupby 期间应用于多列,并将预测列列表馈入 groupby 行。
所需输出: 具有分类组列和 WMAPE 的所有列的输出数据框。标签是首选但不需要(输出图像如下)。
到目前为止成功的代码: 两个 WMAPE 函数:一个用于输入两个系列并输出一个浮点值 (wmape),一个用于 groupby (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')
输出看起来像(前两行):
所需的输出将一次性包含所有预测(Forecast_2 ... Forecast_4 的虚拟数据)。我可以 已经 使用 for 循环执行此操作。我只想在 groupby 中进行。我想调用一个 wmape 函数四次。如果有任何帮助,我将不胜感激。
如果您修改 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()
这会产生一个只有一个字典列的数据框。
为了正确的格式,单列可以转换为多列:
# 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)
结果:
这是一个非常好的问题,展示了如何在 pandas 中优化 groupby.apply。我使用两个原则来帮助解决这些问题。
- 任何独立于组的计算都不应该在 groupby 中进行
- 如果有内置的groupby方法,请先使用再使用 申请
让我们逐行查看您的 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()
这一行依赖于组所以我们必须在这里使用一个groupby,但是没有必要把它放在apply函数中。下面会计算出来。
让我们转到下一行:
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 'forecast_wampe' 列中的每一列都独立于组进行了计算。我们只需要对每组中的每一个求和。 'Actual' 列也是如此。
我们可以 运行 两个单独的 groupby 操作来对每一列求和,如下所示:
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()
我们得到以下系列和返回的 DataFrame
然后我们只需要将 DataFrame 中的每一列除以 Series。我们需要使用 div
方法来改变分区的方向,以便索引对齐
forecast1_wampe_sum.div(actual_sum, axis='index')
而这个 returns 我们的答案:
不改变功能
申请四次
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)