prophet.metrics
1# Copyright (c) Facebook, Inc. and its affiliates. 2 3# This source code is licensed under the MIT license found in the 4# LICENSE file in the root directory of this source tree. 5 6from __future__ import annotations 7 8import logging 9from typing import TYPE_CHECKING, Any, Callable, Final, Sequence, cast 10 11import numpy as np 12import pandas as pd 13 14if TYPE_CHECKING: 15 from typing import TypeAlias, TypeVar 16 17 _PerformanceMetric: TypeAlias = Callable[[pd.DataFrame, int], pd.DataFrame] 18 _PerformanceMetricT = TypeVar("_PerformanceMetricT", bound=_PerformanceMetric) 19 20 21logger: logging.Logger = logging.getLogger('prophet') 22 23PERFORMANCE_METRICS: Final[dict[str, Callable[..., Any]]] = {} 24 25 26def register_performance_metric(func: Callable[..., Any]) -> Callable[..., Any]: 27 """Register custom performance metric 28 29 Parameters that your metric should contain 30 ---------- 31 df: Cross-validation results dataframe. 32 w: Aggregation window size. 33 34 Registered metric should return following 35 ------- 36 Dataframe with columns horizon and metric. 37 """ 38 PERFORMANCE_METRICS[func.__name__] = func 39 return func 40 41 42def performance_metrics( 43 df: pd.DataFrame, 44 metrics: Sequence[str] | None = None, 45 rolling_window: float = 0.1, 46 monthly: bool = False, 47) -> pd.DataFrame | None: 48 """Compute performance metrics from cross-validation results. 49 50 Computes a suite of performance metrics on the output of cross-validation. 51 By default, the following metrics are included: 52 'mse': mean squared error 53 'rmse': root mean squared error 54 'mae': mean absolute error 55 'mape': mean absolute percent error 56 'mdape': median absolute percent error 57 'smape': symmetric mean absolute percentage error 58 'coverage': coverage of the upper and lower intervals 59 60 A subset of these can be specified by passing a list of names as the 61 `metrics` argument. 62 63 Metrics are calculated over a rolling window of cross validation 64 predictions, after sorting by horizon. Averaging is first done within each 65 value of horizon, and then across horizons as needed to reach the window 66 size. The size of that window (number of simulated forecast points) is 67 determined by the rolling_window argument, which specifies a proportion of 68 simulated forecast points to include in each window. rolling_window=0 will 69 compute it separately for each horizon. The default of rolling_window=0.1 70 will use 10% of the rows in df in each window. rolling_window=1 will 71 compute the metric across all simulated forecast points. The results are 72 set to the right edge of the window. 73 74 If rolling_window < 0, then metrics are computed at each datapoint with no 75 averaging (i.e., 'mse' will actually be squared error with no mean). 76 77 The output is a dataframe containing column 'horizon' along with columns 78 for each of the metrics computed. 79 80 Parameters 81 ---------- 82 df: The dataframe returned by cross_validation. 83 metrics: Performance metrics to compute. A sequence of names; if not 84 provided, uses ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 85 'coverage']. The caller's sequence is not modified. 86 rolling_window: Proportion of data to use in each rolling window for 87 computing the metrics. Should be in [0, 1] to average. 88 monthly: monthly=True will compute horizons as numbers of calendar months 89 from the cutoff date, starting from 0 for the cutoff month. 90 91 Returns 92 ------- 93 Dataframe with a column for each metric, and column 'horizon' 94 """ 95 valid_metrics = ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 'coverage'] 96 if metrics is None: 97 metrics = valid_metrics 98 # Copy so that removing skipped metrics below does not modify the caller's list 99 metrics = list(metrics) 100 if ('yhat_lower' not in df or 'yhat_upper' not in df) and ('coverage' in metrics): 101 metrics.remove('coverage') 102 if len(set(metrics)) != len(metrics): 103 raise ValueError('Input metrics must be a list of unique values') 104 if not set(metrics).issubset(set(PERFORMANCE_METRICS)): 105 raise ValueError( 106 'Valid values for metrics are: {}'.format(valid_metrics) 107 ) 108 df_m = df.copy() 109 if monthly: 110 df_m['horizon'] = df_m['ds'].dt.to_period('M').astype(int) - df_m['cutoff'].dt.to_period('M').astype(int) 111 else: 112 df_m['horizon'] = df_m['ds'] - df_m['cutoff'] 113 df_m.sort_values('horizon', inplace=True) 114 if 'mape' in metrics and df_m['y'].abs().min() < 1e-8: 115 logger.info('Skipping MAPE because y close to 0') 116 metrics.remove('mape') 117 if len(metrics) == 0: 118 return None 119 w = int(rolling_window * df_m.shape[0]) 120 if w >= 0: 121 w = max(w, 1) 122 w = min(w, df_m.shape[0]) 123 # Compute all metrics 124 dfs = {} 125 for metric in metrics: 126 dfs[metric] = PERFORMANCE_METRICS[metric](df_m, w) 127 res = dfs[metrics[0]] 128 for i in range(1, len(metrics)): 129 res_m = dfs[metrics[i]] 130 assert np.array_equal(res['horizon'].values, res_m['horizon'].values) 131 res[metrics[i]] = res_m[metrics[i]] 132 return res 133 134 135def rolling_mean_by_h(x: np.ndarray, h: np.ndarray, w: int, name: str) -> pd.DataFrame: 136 """Compute a rolling mean of x, after first aggregating by h. 137 138 Right-aligned. Computes a single mean for each unique value of h. Each 139 mean is over at least w samples. 140 141 Parameters 142 ---------- 143 x: Array. 144 h: Array of horizon for each value in x. 145 w: Integer window size (number of elements). 146 name: Name for metric in result dataframe 147 148 Returns 149 ------- 150 Dataframe with columns horizon and name, the rolling mean of x. 151 """ 152 # Aggregate over h 153 df = pd.DataFrame({'x': x, 'h': h}) 154 df2 = ( 155 df.groupby('h').agg(['sum', 'count']).reset_index().sort_values('h') 156 ) 157 xs = df2['x']['sum'].values 158 ns = df2['x']['count'].values 159 hs = df2.h.values 160 161 trailing_i = len(df2) - 1 162 x_sum = 0 163 n_sum = 0 164 # We don't know output size but it is bounded by len(df2) 165 res_x = np.empty(len(df2)) 166 167 # Start from the right and work backwards 168 for i in range(len(df2) - 1, -1, -1): 169 x_sum += xs[i] 170 n_sum += ns[i] 171 while n_sum >= w: 172 # Include points from the previous horizon. All of them if still 173 # less than w, otherwise weight the mean by the difference 174 excess_n = n_sum - w 175 excess_x = excess_n * xs[i] / ns[i] 176 res_x[trailing_i] = (x_sum - excess_x)/ w 177 x_sum -= xs[trailing_i] 178 n_sum -= ns[trailing_i] 179 trailing_i -= 1 180 181 res_h = hs[(trailing_i + 1):] 182 res_x = res_x[(trailing_i + 1):] 183 184 return pd.DataFrame({'horizon': res_h, name: res_x}) 185 186 187 188def rolling_median_by_h(x: np.ndarray, h: np.ndarray, w: int, name: str) -> pd.DataFrame: 189 """Compute a rolling median of x, after first aggregating by h. 190 191 Right-aligned. Computes a single median for each unique value of h. Each 192 median is over at least w samples. 193 194 For each h where there are fewer than w samples, we take samples from the previous h, 195 moving backwards. (In other words, we ~ assume that the x's are shuffled within each h.) 196 197 Parameters 198 ---------- 199 x: Array. 200 h: Array of horizon for each value in x. 201 w: Integer window size (number of elements). 202 name: Name for metric in result dataframe 203 204 Returns 205 ------- 206 Dataframe with columns horizon and name, the rolling median of x. 207 """ 208 # Aggregate over h 209 df = pd.DataFrame({'x': x, 'h': h}) 210 grouped = df.groupby('h') 211 df2 = grouped.size().reset_index().sort_values('h') 212 hs = df2['h'] 213 214 res_h = [] 215 res_x = [] 216 # Start from the right and work backwards 217 i = len(hs) - 1 218 while i >= 0: 219 h_i = hs[i] 220 xs = grouped.get_group(h_i).x.tolist() 221 222 # wrap in array so this works if h is pandas Series with custom index or numpy array 223 next_idx_to_add = np.array(h == h_i).argmax() - 1 224 while (len(xs) < w) and (next_idx_to_add >= 0): 225 # Include points from the previous horizon. All of them if still 226 # less than w, otherwise just enough to get to w. 227 xs.append(x[next_idx_to_add]) 228 next_idx_to_add -= 1 229 if len(xs) < w: 230 # Ran out of points before getting enough. 231 break 232 res_h.append(hs[i]) 233 res_x.append(np.median(xs)) 234 i -= 1 235 res_h.reverse() 236 res_x.reverse() 237 return pd.DataFrame({'horizon': res_h, name: res_x}) 238 239 240# The functions below specify performance metrics for cross-validation results. 241# Each takes as input the output of cross_validation, and returns the statistic 242# as a dataframe, given a window size for rolling aggregation. 243 244 245@register_performance_metric 246def mse(df: pd.DataFrame, w: int) -> pd.DataFrame: 247 """Mean squared error 248 249 Parameters 250 ---------- 251 df: Cross-validation results dataframe. 252 w: Aggregation window size. 253 254 Returns 255 ------- 256 Dataframe with columns horizon and mse. 257 """ 258 se = (df['y'] - df['yhat']) ** 2 259 if w < 0: 260 return pd.DataFrame({'horizon': df['horizon'], 'mse': se}) 261 return rolling_mean_by_h( 262 x=cast(np.ndarray, se.values), 263 h=cast(np.ndarray, df['horizon'].values), 264 w=w, 265 name='mse', 266 ) 267 268 269@register_performance_metric 270def rmse(df: pd.DataFrame, w: int) -> pd.DataFrame: 271 """Root mean squared error 272 273 Parameters 274 ---------- 275 df: Cross-validation results dataframe. 276 w: Aggregation window size. 277 278 Returns 279 ------- 280 Dataframe with columns horizon and rmse. 281 """ 282 res = mse(df, w) 283 res['mse'] = np.sqrt(res['mse']) 284 res.rename({'mse': 'rmse'}, axis='columns', inplace=True) 285 return res 286 287 288@register_performance_metric 289def mae(df: pd.DataFrame, w: int) -> pd.DataFrame: 290 """Mean absolute error 291 292 Parameters 293 ---------- 294 df: Cross-validation results dataframe. 295 w: Aggregation window size. 296 297 Returns 298 ------- 299 Dataframe with columns horizon and mae. 300 """ 301 ae = (df['y'] - df['yhat']).abs() 302 if w < 0: 303 return pd.DataFrame({'horizon': df['horizon'], 'mae': ae}) 304 return rolling_mean_by_h( 305 x=cast(np.ndarray, ae.values), 306 h=cast(np.ndarray, df['horizon'].values), 307 w=w, 308 name='mae', 309 ) 310 311 312@register_performance_metric 313def mape(df: pd.DataFrame, w: int) -> pd.DataFrame: 314 """Mean absolute percent error 315 316 Parameters 317 ---------- 318 df: Cross-validation results dataframe. 319 w: Aggregation window size. 320 321 Returns 322 ------- 323 Dataframe with columns horizon and mape. 324 """ 325 ape = ((df['y'] - df['yhat']) / df['y']).abs() 326 if w < 0: 327 return pd.DataFrame({'horizon': df['horizon'], 'mape': ape}) 328 return rolling_mean_by_h( 329 x=cast(np.ndarray, ape.values), 330 h=cast(np.ndarray, df['horizon'].values), 331 w=w, 332 name='mape', 333 ) 334 335 336@register_performance_metric 337def mdape(df: pd.DataFrame, w: int) -> pd.DataFrame: 338 """Median absolute percent error 339 340 Parameters 341 ---------- 342 df: Cross-validation results dataframe. 343 w: Aggregation window size. 344 345 Returns 346 ------- 347 Dataframe with columns horizon and mdape. 348 """ 349 ape = ((df['y'] - df['yhat']) / df['y']).abs() 350 if w < 0: 351 return pd.DataFrame({'horizon': df['horizon'], 'mdape': ape}) 352 return rolling_median_by_h( 353 x=cast(np.ndarray, ape.values), 354 h=cast(np.ndarray, df['horizon'].values), 355 w=w, 356 name='mdape', 357 ) 358 359 360@register_performance_metric 361def smape(df: pd.DataFrame, w: int) -> pd.DataFrame: 362 """Symmetric mean absolute percentage error 363 based on Chen and Yang (2004) formula 364 365 Parameters 366 ---------- 367 df: Cross-validation results dataframe. 368 w: Aggregation window size. 369 370 Returns 371 ------- 372 Dataframe with columns horizon and smape. 373 """ 374 sape = (df['y'] - df['yhat']).abs() / ((np.abs(df['y']) + np.abs(df['yhat'])) / 2) 375 sape = sape.fillna(0) 376 if w < 0: 377 return pd.DataFrame({'horizon': df['horizon'], 'smape': sape}) 378 return rolling_mean_by_h( 379 x=cast(np.ndarray, sape.values), 380 h=cast(np.ndarray, df['horizon'].values), 381 w=w, 382 name='smape', 383 ) 384 385 386@register_performance_metric 387def coverage(df: pd.DataFrame, w: int) -> pd.DataFrame: 388 """Coverage 389 390 Parameters 391 ---------- 392 df: Cross-validation results dataframe. 393 w: Aggregation window size. 394 395 Returns 396 ------- 397 Dataframe with columns horizon and coverage. 398 """ 399 is_covered = (df['y'] >= df['yhat_lower']) & (df['y'] <= df['yhat_upper']) 400 if w < 0: 401 return pd.DataFrame({'horizon': df['horizon'], 'coverage': is_covered}) 402 return rolling_mean_by_h( 403 x=cast(np.ndarray, is_covered.values), 404 h=cast(np.ndarray, df['horizon'].values), 405 w=w, 406 name='coverage', 407 ) 408 409 410__all__ = [ 411 "PERFORMANCE_METRICS", 412 "register_performance_metric", 413 "performance_metrics", 414 "rolling_mean_by_h", 415 "rolling_median_by_h", 416 "mse", 417 "rmse", 418 "mae", 419 "mape", 420 "mdape", 421 "smape", 422 "coverage", 423]
27def register_performance_metric(func: Callable[..., Any]) -> Callable[..., Any]: 28 """Register custom performance metric 29 30 Parameters that your metric should contain 31 ---------- 32 df: Cross-validation results dataframe. 33 w: Aggregation window size. 34 35 Registered metric should return following 36 ------- 37 Dataframe with columns horizon and metric. 38 """ 39 PERFORMANCE_METRICS[func.__name__] = func 40 return func
Register custom performance metric
Parameters that your metric should contain
df: Cross-validation results dataframe. w: Aggregation window size.
Registered metric should return following
Dataframe with columns horizon and metric.
43def performance_metrics( 44 df: pd.DataFrame, 45 metrics: Sequence[str] | None = None, 46 rolling_window: float = 0.1, 47 monthly: bool = False, 48) -> pd.DataFrame | None: 49 """Compute performance metrics from cross-validation results. 50 51 Computes a suite of performance metrics on the output of cross-validation. 52 By default, the following metrics are included: 53 'mse': mean squared error 54 'rmse': root mean squared error 55 'mae': mean absolute error 56 'mape': mean absolute percent error 57 'mdape': median absolute percent error 58 'smape': symmetric mean absolute percentage error 59 'coverage': coverage of the upper and lower intervals 60 61 A subset of these can be specified by passing a list of names as the 62 `metrics` argument. 63 64 Metrics are calculated over a rolling window of cross validation 65 predictions, after sorting by horizon. Averaging is first done within each 66 value of horizon, and then across horizons as needed to reach the window 67 size. The size of that window (number of simulated forecast points) is 68 determined by the rolling_window argument, which specifies a proportion of 69 simulated forecast points to include in each window. rolling_window=0 will 70 compute it separately for each horizon. The default of rolling_window=0.1 71 will use 10% of the rows in df in each window. rolling_window=1 will 72 compute the metric across all simulated forecast points. The results are 73 set to the right edge of the window. 74 75 If rolling_window < 0, then metrics are computed at each datapoint with no 76 averaging (i.e., 'mse' will actually be squared error with no mean). 77 78 The output is a dataframe containing column 'horizon' along with columns 79 for each of the metrics computed. 80 81 Parameters 82 ---------- 83 df: The dataframe returned by cross_validation. 84 metrics: Performance metrics to compute. A sequence of names; if not 85 provided, uses ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 86 'coverage']. The caller's sequence is not modified. 87 rolling_window: Proportion of data to use in each rolling window for 88 computing the metrics. Should be in [0, 1] to average. 89 monthly: monthly=True will compute horizons as numbers of calendar months 90 from the cutoff date, starting from 0 for the cutoff month. 91 92 Returns 93 ------- 94 Dataframe with a column for each metric, and column 'horizon' 95 """ 96 valid_metrics = ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 'coverage'] 97 if metrics is None: 98 metrics = valid_metrics 99 # Copy so that removing skipped metrics below does not modify the caller's list 100 metrics = list(metrics) 101 if ('yhat_lower' not in df or 'yhat_upper' not in df) and ('coverage' in metrics): 102 metrics.remove('coverage') 103 if len(set(metrics)) != len(metrics): 104 raise ValueError('Input metrics must be a list of unique values') 105 if not set(metrics).issubset(set(PERFORMANCE_METRICS)): 106 raise ValueError( 107 'Valid values for metrics are: {}'.format(valid_metrics) 108 ) 109 df_m = df.copy() 110 if monthly: 111 df_m['horizon'] = df_m['ds'].dt.to_period('M').astype(int) - df_m['cutoff'].dt.to_period('M').astype(int) 112 else: 113 df_m['horizon'] = df_m['ds'] - df_m['cutoff'] 114 df_m.sort_values('horizon', inplace=True) 115 if 'mape' in metrics and df_m['y'].abs().min() < 1e-8: 116 logger.info('Skipping MAPE because y close to 0') 117 metrics.remove('mape') 118 if len(metrics) == 0: 119 return None 120 w = int(rolling_window * df_m.shape[0]) 121 if w >= 0: 122 w = max(w, 1) 123 w = min(w, df_m.shape[0]) 124 # Compute all metrics 125 dfs = {} 126 for metric in metrics: 127 dfs[metric] = PERFORMANCE_METRICS[metric](df_m, w) 128 res = dfs[metrics[0]] 129 for i in range(1, len(metrics)): 130 res_m = dfs[metrics[i]] 131 assert np.array_equal(res['horizon'].values, res_m['horizon'].values) 132 res[metrics[i]] = res_m[metrics[i]] 133 return res
Compute performance metrics from cross-validation results.
Computes a suite of performance metrics on the output of cross-validation. By default, the following metrics are included: 'mse': mean squared error 'rmse': root mean squared error 'mae': mean absolute error 'mape': mean absolute percent error 'mdape': median absolute percent error 'smape': symmetric mean absolute percentage error 'coverage': coverage of the upper and lower intervals
A subset of these can be specified by passing a list of names as the
metrics argument.
Metrics are calculated over a rolling window of cross validation predictions, after sorting by horizon. Averaging is first done within each value of horizon, and then across horizons as needed to reach the window size. The size of that window (number of simulated forecast points) is determined by the rolling_window argument, which specifies a proportion of simulated forecast points to include in each window. rolling_window=0 will compute it separately for each horizon. The default of rolling_window=0.1 will use 10% of the rows in df in each window. rolling_window=1 will compute the metric across all simulated forecast points. The results are set to the right edge of the window.
If rolling_window < 0, then metrics are computed at each datapoint with no averaging (i.e., 'mse' will actually be squared error with no mean).
The output is a dataframe containing column 'horizon' along with columns for each of the metrics computed.
Parameters
df (The dataframe returned by cross_validation.):
metrics (Performance metrics to compute. A sequence of names; if not): provided, uses ['mse', 'rmse', 'mae', 'mape', 'mdape', 'smape', 'coverage']. The caller's sequence is not modified.
- rolling_window (Proportion of data to use in each rolling window for): computing the metrics. Should be in [0, 1] to average.
- monthly (monthly=True will compute horizons as numbers of calendar months): from the cutoff date, starting from 0 for the cutoff month.
Returns
- Dataframe with a column for each metric, and column 'horizon'
136def rolling_mean_by_h(x: np.ndarray, h: np.ndarray, w: int, name: str) -> pd.DataFrame: 137 """Compute a rolling mean of x, after first aggregating by h. 138 139 Right-aligned. Computes a single mean for each unique value of h. Each 140 mean is over at least w samples. 141 142 Parameters 143 ---------- 144 x: Array. 145 h: Array of horizon for each value in x. 146 w: Integer window size (number of elements). 147 name: Name for metric in result dataframe 148 149 Returns 150 ------- 151 Dataframe with columns horizon and name, the rolling mean of x. 152 """ 153 # Aggregate over h 154 df = pd.DataFrame({'x': x, 'h': h}) 155 df2 = ( 156 df.groupby('h').agg(['sum', 'count']).reset_index().sort_values('h') 157 ) 158 xs = df2['x']['sum'].values 159 ns = df2['x']['count'].values 160 hs = df2.h.values 161 162 trailing_i = len(df2) - 1 163 x_sum = 0 164 n_sum = 0 165 # We don't know output size but it is bounded by len(df2) 166 res_x = np.empty(len(df2)) 167 168 # Start from the right and work backwards 169 for i in range(len(df2) - 1, -1, -1): 170 x_sum += xs[i] 171 n_sum += ns[i] 172 while n_sum >= w: 173 # Include points from the previous horizon. All of them if still 174 # less than w, otherwise weight the mean by the difference 175 excess_n = n_sum - w 176 excess_x = excess_n * xs[i] / ns[i] 177 res_x[trailing_i] = (x_sum - excess_x)/ w 178 x_sum -= xs[trailing_i] 179 n_sum -= ns[trailing_i] 180 trailing_i -= 1 181 182 res_h = hs[(trailing_i + 1):] 183 res_x = res_x[(trailing_i + 1):] 184 185 return pd.DataFrame({'horizon': res_h, name: res_x})
Compute a rolling mean of x, after first aggregating by h.
Right-aligned. Computes a single mean for each unique value of h. Each mean is over at least w samples.
Parameters
x (Array.):
h (Array of horizon for each value in x.):
w (Integer window size (number of elements).):
name (Name for metric in result dataframe):
Returns
- Dataframe with columns horizon and name, the rolling mean of x.
189def rolling_median_by_h(x: np.ndarray, h: np.ndarray, w: int, name: str) -> pd.DataFrame: 190 """Compute a rolling median of x, after first aggregating by h. 191 192 Right-aligned. Computes a single median for each unique value of h. Each 193 median is over at least w samples. 194 195 For each h where there are fewer than w samples, we take samples from the previous h, 196 moving backwards. (In other words, we ~ assume that the x's are shuffled within each h.) 197 198 Parameters 199 ---------- 200 x: Array. 201 h: Array of horizon for each value in x. 202 w: Integer window size (number of elements). 203 name: Name for metric in result dataframe 204 205 Returns 206 ------- 207 Dataframe with columns horizon and name, the rolling median of x. 208 """ 209 # Aggregate over h 210 df = pd.DataFrame({'x': x, 'h': h}) 211 grouped = df.groupby('h') 212 df2 = grouped.size().reset_index().sort_values('h') 213 hs = df2['h'] 214 215 res_h = [] 216 res_x = [] 217 # Start from the right and work backwards 218 i = len(hs) - 1 219 while i >= 0: 220 h_i = hs[i] 221 xs = grouped.get_group(h_i).x.tolist() 222 223 # wrap in array so this works if h is pandas Series with custom index or numpy array 224 next_idx_to_add = np.array(h == h_i).argmax() - 1 225 while (len(xs) < w) and (next_idx_to_add >= 0): 226 # Include points from the previous horizon. All of them if still 227 # less than w, otherwise just enough to get to w. 228 xs.append(x[next_idx_to_add]) 229 next_idx_to_add -= 1 230 if len(xs) < w: 231 # Ran out of points before getting enough. 232 break 233 res_h.append(hs[i]) 234 res_x.append(np.median(xs)) 235 i -= 1 236 res_h.reverse() 237 res_x.reverse() 238 return pd.DataFrame({'horizon': res_h, name: res_x})
Compute a rolling median of x, after first aggregating by h.
Right-aligned. Computes a single median for each unique value of h. Each median is over at least w samples.
For each h where there are fewer than w samples, we take samples from the previous h, moving backwards. (In other words, we ~ assume that the x's are shuffled within each h.)
Parameters
x (Array.):
h (Array of horizon for each value in x.):
w (Integer window size (number of elements).):
name (Name for metric in result dataframe):
Returns
- Dataframe with columns horizon and name, the rolling median of x.
246@register_performance_metric 247def mse(df: pd.DataFrame, w: int) -> pd.DataFrame: 248 """Mean squared error 249 250 Parameters 251 ---------- 252 df: Cross-validation results dataframe. 253 w: Aggregation window size. 254 255 Returns 256 ------- 257 Dataframe with columns horizon and mse. 258 """ 259 se = (df['y'] - df['yhat']) ** 2 260 if w < 0: 261 return pd.DataFrame({'horizon': df['horizon'], 'mse': se}) 262 return rolling_mean_by_h( 263 x=cast(np.ndarray, se.values), 264 h=cast(np.ndarray, df['horizon'].values), 265 w=w, 266 name='mse', 267 )
Mean squared error
Parameters
df (Cross-validation results dataframe.):
w (Aggregation window size.):
Returns
- Dataframe with columns horizon and mse.
270@register_performance_metric 271def rmse(df: pd.DataFrame, w: int) -> pd.DataFrame: 272 """Root mean squared error 273 274 Parameters 275 ---------- 276 df: Cross-validation results dataframe. 277 w: Aggregation window size. 278 279 Returns 280 ------- 281 Dataframe with columns horizon and rmse. 282 """ 283 res = mse(df, w) 284 res['mse'] = np.sqrt(res['mse']) 285 res.rename({'mse': 'rmse'}, axis='columns', inplace=True) 286 return res
Root mean squared error
Parameters
df (Cross-validation results dataframe.):
w (Aggregation window size.):
Returns
- Dataframe with columns horizon and rmse.
289@register_performance_metric 290def mae(df: pd.DataFrame, w: int) -> pd.DataFrame: 291 """Mean absolute error 292 293 Parameters 294 ---------- 295 df: Cross-validation results dataframe. 296 w: Aggregation window size. 297 298 Returns 299 ------- 300 Dataframe with columns horizon and mae. 301 """ 302 ae = (df['y'] - df['yhat']).abs() 303 if w < 0: 304 return pd.DataFrame({'horizon': df['horizon'], 'mae': ae}) 305 return rolling_mean_by_h( 306 x=cast(np.ndarray, ae.values), 307 h=cast(np.ndarray, df['horizon'].values), 308 w=w, 309 name='mae', 310 )
Mean absolute error
Parameters
df (Cross-validation results dataframe.):
w (Aggregation window size.):
Returns
- Dataframe with columns horizon and mae.
313@register_performance_metric 314def mape(df: pd.DataFrame, w: int) -> pd.DataFrame: 315 """Mean absolute percent error 316 317 Parameters 318 ---------- 319 df: Cross-validation results dataframe. 320 w: Aggregation window size. 321 322 Returns 323 ------- 324 Dataframe with columns horizon and mape. 325 """ 326 ape = ((df['y'] - df['yhat']) / df['y']).abs() 327 if w < 0: 328 return pd.DataFrame({'horizon': df['horizon'], 'mape': ape}) 329 return rolling_mean_by_h( 330 x=cast(np.ndarray, ape.values), 331 h=cast(np.ndarray, df['horizon'].values), 332 w=w, 333 name='mape', 334 )
Mean absolute percent error
Parameters
df (Cross-validation results dataframe.):
w (Aggregation window size.):
Returns
- Dataframe with columns horizon and mape.
337@register_performance_metric 338def mdape(df: pd.DataFrame, w: int) -> pd.DataFrame: 339 """Median absolute percent error 340 341 Parameters 342 ---------- 343 df: Cross-validation results dataframe. 344 w: Aggregation window size. 345 346 Returns 347 ------- 348 Dataframe with columns horizon and mdape. 349 """ 350 ape = ((df['y'] - df['yhat']) / df['y']).abs() 351 if w < 0: 352 return pd.DataFrame({'horizon': df['horizon'], 'mdape': ape}) 353 return rolling_median_by_h( 354 x=cast(np.ndarray, ape.values), 355 h=cast(np.ndarray, df['horizon'].values), 356 w=w, 357 name='mdape', 358 )
Median absolute percent error
Parameters
df (Cross-validation results dataframe.):
w (Aggregation window size.):
Returns
- Dataframe with columns horizon and mdape.
361@register_performance_metric 362def smape(df: pd.DataFrame, w: int) -> pd.DataFrame: 363 """Symmetric mean absolute percentage error 364 based on Chen and Yang (2004) formula 365 366 Parameters 367 ---------- 368 df: Cross-validation results dataframe. 369 w: Aggregation window size. 370 371 Returns 372 ------- 373 Dataframe with columns horizon and smape. 374 """ 375 sape = (df['y'] - df['yhat']).abs() / ((np.abs(df['y']) + np.abs(df['yhat'])) / 2) 376 sape = sape.fillna(0) 377 if w < 0: 378 return pd.DataFrame({'horizon': df['horizon'], 'smape': sape}) 379 return rolling_mean_by_h( 380 x=cast(np.ndarray, sape.values), 381 h=cast(np.ndarray, df['horizon'].values), 382 w=w, 383 name='smape', 384 )
Symmetric mean absolute percentage error based on Chen and Yang (2004) formula
Parameters
df (Cross-validation results dataframe.):
w (Aggregation window size.):
Returns
- Dataframe with columns horizon and smape.
387@register_performance_metric 388def coverage(df: pd.DataFrame, w: int) -> pd.DataFrame: 389 """Coverage 390 391 Parameters 392 ---------- 393 df: Cross-validation results dataframe. 394 w: Aggregation window size. 395 396 Returns 397 ------- 398 Dataframe with columns horizon and coverage. 399 """ 400 is_covered = (df['y'] >= df['yhat_lower']) & (df['y'] <= df['yhat_upper']) 401 if w < 0: 402 return pd.DataFrame({'horizon': df['horizon'], 'coverage': is_covered}) 403 return rolling_mean_by_h( 404 x=cast(np.ndarray, is_covered.values), 405 h=cast(np.ndarray, df['horizon'].values), 406 w=w, 407 name='coverage', 408 )
Coverage
Parameters
df (Cross-validation results dataframe.):
w (Aggregation window size.):
Returns
- Dataframe with columns horizon and coverage.