diff --git a/hierarchicalforecast/methods.py b/hierarchicalforecast/methods.py index 0c2e4e29..e3164c65 100644 --- a/hierarchicalforecast/methods.py +++ b/hierarchicalforecast/methods.py @@ -88,6 +88,8 @@ class BottomUp: - [Orcutt, G.H., Watts, H.W., & Edwards, J.B.(1968). \"Data aggregation and information loss\". The American Economic Review, 58 , 773{787)](http://www.jstor.org/stable/1815532). """ + insample = False + def reconcile(self, S: np.ndarray, y_hat: np.ndarray, @@ -253,12 +255,13 @@ class TopDown: def __init__(self, method: str): self.method = method + self.insample = method in ['average_proportions', 'proportion_averages'] def reconcile(self, S: np.ndarray, y_hat: np.ndarray, - y_insample: np.ndarray, tags: Dict[str, np.ndarray], + y_insample: Optional[np.ndarray] = None, sigmah: Optional[np.ndarray] = None, level: Optional[List[int]] = None, bootstrap: bool = False, @@ -268,6 +271,8 @@ def reconcile(self, **Parameters:**
`S`: Summing matrix of size (`base`, `bottom`).
`y_hat`: Forecast values of size (`base`, `horizon`).
+ `tags`: Each key is a level and each value its `S` indices.
+ `y_insample`: Insample values of size (`base`, `insample_size`). Optional for `forecast_proportions` method.
`idx_bottom`: Indices corresponding to the bottom level of `S`, size (`bottom`).
`sigmah`: float, estimate of the standard deviation of the h-step forecast of size (`base`, `horizon`)
`level`: float list 0-100, confidence levels for prediction intervals.
@@ -345,7 +350,7 @@ def middle_out(S: np.ndarray, counter += idxs_len td = top_down(S_node, y_hat[idxs_node], - y_insample[idxs_node], + y_insample[idxs_node] if y_insample is not None else None, levels_node_, method=top_down_method) reconciled[idxs_node] = td['mean'] @@ -375,19 +380,20 @@ def __init__(self, top_down_method: str): self.middle_level = middle_level self.top_down_method = top_down_method + self.insample = top_down_method in ['average_proportions', 'proportion_averages'] def reconcile(self, S: np.ndarray, y_hat: np.ndarray, - y_insample: np.ndarray, - tags: Dict[str, np.ndarray]): + tags: Dict[str, np.ndarray], + y_insample: Optional[np.ndarray] = None): """Middle Out Reconciliation Method. **Parameters:**
`S`: Summing matrix of size (`base`, `bottom`).
`y_hat`: Forecast values of size (`base`, `horizon`).
- `y_insample`: Insample values of size (`base`, `insample_size`).
- `levels`: Each key is a level and each value its `S` indices.
+ `tags`: Each key is a level and each value its `S` indices.
+ `y_insample`: Insample values of size (`base`, `insample_size`). Only used for `forecast_proportions`
**Returns:**
`y_tilde`: Reconciliated y_hat using the Middle Out approach. @@ -471,7 +477,7 @@ class MinTrace: \mathbf{S}^{\intercal}\mathbf{W}^{-1}_{h}$$ **Parameters:**
- `method`: str, one of `ols`, `wls_struct`, `wls_var`, `mint_shrink`, `mint_co`.
+ `method`: str, one of `ols`, `wls_struct`, `wls_var`, `mint_shrink`, `mint_cov`.
**References:**
- [Wickramasuriya, S. L., Athanasopoulos, G., & Hyndman, R. J. (2019). \"Optimal forecast reconciliation for @@ -481,12 +487,13 @@ class MinTrace: def __init__(self, method: str): self.method = method + self.insample = method in ['wls_var', 'mint_cov', 'mint_shrink'] def reconcile(self, S: np.ndarray, y_hat: np.ndarray, - y_insample: np.ndarray, - y_hat_insample: np.ndarray, + y_insample: Optional[np.ndarray] = None, + y_hat_insample: Optional[np.ndarray] = None, sigmah: Optional[np.ndarray] = None, level: Optional[List[int]] = None, bootstrap: bool = False, @@ -496,7 +503,8 @@ def reconcile(self, **Parameters:**
`S`: Summing matrix of size (`base`, `bottom`).
`y_hat`: Forecast values of size (`base`, `horizon`).
- `idx_bottom`: Indices corresponding to the bottom level of `S`, size (`bottom`).
+ `y_insample`: Insample values of size (`base`, `insample_size`). Only used by `wls_var`, `mint_cov`, `mint_shrink`
+ `y_hat_insample`: Insample fitted values of size (`base`, `insample_size`). Only used by `wls_var`, `mint_cov`, `mint_shrink`
`sigmah`: float, estimate of the standard deviation of the h-step forecast of size (`base`, `horizon`)
`level`: float list 0-100, confidence levels for prediction intervals.
`bootstrap`: bool, whether or not to use bootstraped prediction intervals, alternative normality assumption.
@@ -560,12 +568,11 @@ def __init__(self, raise ValueError(f"Optimal Combination class does not support method: \"{method}\"") self.method = method + self.insample = False def reconcile(self, S: np.ndarray, y_hat: np.ndarray, - y_insample: np.ndarray = None, - y_hat_insample: np.ndarray = None, sigmah: Optional[np.ndarray] = None, level: Optional[List[int]] = None, bootstrap: bool = False, @@ -575,7 +582,6 @@ def reconcile(self, **Parameters:**
`S`: Summing matrix of size (`base`, `bottom`).
`y_hat`: Forecast values of size (`base`, `horizon`).
- `idx_bottom`: Indices corresponding to the bottom level of `S`, size (`bottom`).
`sigmah`: float, estimate of the standard deviation of the h-step forecast of size (`base`, `horizon`)
`level`: float list 0-100, confidence levels for prediction intervals.
`bootstrap`: bool, whether or not to use bootstraped prediction intervals, alternative normality assumption.
@@ -586,8 +592,6 @@ def reconcile(self, """ return optimal_combination(S=S, y_hat=y_hat, - y_insample=y_insample, - y_hat_insample=y_hat_insample, method=self.method, sigmah=sigmah, level=level, bootstrap=bootstrap, bootstrap_samples=bootstrap_samples) @@ -704,6 +708,7 @@ def __init__(self, lambda_reg: float = 1e-2): self.method = method self.lambda_reg = lambda_reg + self.insample = True def reconcile(self, S: np.ndarray, diff --git a/nbs/methods.ipynb b/nbs/methods.ipynb index b101ae89..7a490a25 100644 --- a/nbs/methods.ipynb +++ b/nbs/methods.ipynb @@ -149,6 +149,8 @@ " - [Orcutt, G.H., Watts, H.W., & Edwards, J.B.(1968). \\\"Data aggregation and information loss\\\". The American \n", " Economic Review, 58 , 773{787)](http://www.jstor.org/stable/1815532).\n", " \"\"\"\n", + " insample = False\n", + " \n", " def reconcile(self,\n", " S: np.ndarray,\n", " y_hat: np.ndarray,\n", @@ -516,12 +518,13 @@ " def __init__(self, \n", " method: str):\n", " self.method = method\n", + " self.insample = method in ['average_proportions', 'proportion_averages']\n", " \n", " def reconcile(self, \n", " S: np.ndarray,\n", " y_hat: np.ndarray,\n", - " y_insample: np.ndarray,\n", " tags: Dict[str, np.ndarray],\n", + " y_insample: Optional[np.ndarray] = None,\n", " sigmah: Optional[np.ndarray] = None,\n", " level: Optional[List[int]] = None,\n", " bootstrap: bool = False,\n", @@ -531,6 +534,8 @@ " **Parameters:**
\n", " `S`: Summing matrix of size (`base`, `bottom`).
\n", " `y_hat`: Forecast values of size (`base`, `horizon`).
\n", + " `tags`: Each key is a level and each value its `S` indices.
\n", + " `y_insample`: Insample values of size (`base`, `insample_size`). Optional for `forecast_proportions` method.
\n", " `idx_bottom`: Indices corresponding to the bottom level of `S`, size (`bottom`).
\n", " `sigmah`: float, estimate of the standard deviation of the h-step forecast of size (`base`, `horizon`)
\n", " `level`: float list 0-100, confidence levels for prediction intervals.
\n", @@ -583,15 +588,26 @@ "# but it is not a general case\n", "for method in ['forecast_proportions', 'average_proportions', 'proportion_averages']:\n", " cls_top_down = TopDown(method=method)\n", - " test_close(\n", - " cls_top_down(\n", - " S=S, \n", - " y_hat=S @ y_hat_bottom, \n", - " y_insample=S @ y_bottom, \n", - " tags=tags\n", - " )['mean'],\n", - " S @ y_hat_bottom\n", - " )" + " if cls_top_down.insample:\n", + " assert method in ['average_proportions', 'proportion_averages']\n", + " test_close(\n", + " cls_top_down(\n", + " S=S, \n", + " y_hat=S @ y_hat_bottom, \n", + " y_insample=S @ y_bottom, \n", + " tags=tags\n", + " )['mean'],\n", + " S @ y_hat_bottom\n", + " )\n", + " else:\n", + " test_close(\n", + " cls_top_down(\n", + " S=S, \n", + " y_hat=S @ y_hat_bottom, \n", + " tags=tags\n", + " )['mean'],\n", + " S @ y_hat_bottom\n", + " )" ] }, { @@ -689,7 +705,7 @@ " counter += idxs_len\n", " td = top_down(S_node, \n", " y_hat[idxs_node], \n", - " y_insample[idxs_node], \n", + " y_insample[idxs_node] if y_insample is not None else None, \n", " levels_node_, \n", " method=top_down_method)\n", " reconciled[idxs_node] = td['mean']\n", @@ -733,19 +749,20 @@ " top_down_method: str):\n", " self.middle_level = middle_level\n", " self.top_down_method = top_down_method \n", + " self.insample = top_down_method in ['average_proportions', 'proportion_averages']\n", " \n", " def reconcile(self, \n", " S: np.ndarray,\n", " y_hat: np.ndarray,\n", - " y_insample: np.ndarray,\n", - " tags: Dict[str, np.ndarray]):\n", + " tags: Dict[str, np.ndarray],\n", + " y_insample: Optional[np.ndarray] = None):\n", " \"\"\"Middle Out Reconciliation Method.\n", "\n", " **Parameters:**
\n", " `S`: Summing matrix of size (`base`, `bottom`).
\n", " `y_hat`: Forecast values of size (`base`, `horizon`).
\n", - " `y_insample`: Insample values of size (`base`, `insample_size`).
\n", - " `levels`: Each key is a level and each value its `S` indices.
\n", + " `tags`: Each key is a level and each value its `S` indices.
\n", + " `y_insample`: Insample values of size (`base`, `insample_size`). Only used for `forecast_proportions`
\n", "\n", " **Returns:**
\n", " `y_tilde`: Reconciliated y_hat using the Middle Out approach.\n", @@ -792,15 +809,26 @@ "# but it is not a general case\n", "for method in ['forecast_proportions', 'average_proportions', 'proportion_averages']:\n", " cls_middle_out = MiddleOut(middle_level='level2', top_down_method=method)\n", - " test_close(\n", - " cls_middle_out(\n", - " S=S, \n", - " y_hat=S @ y_hat_bottom, \n", - " y_insample=S @ y_bottom, \n", - " tags=tags\n", - " )['mean'],\n", - " S @ y_hat_bottom\n", - " )" + " if cls_middle_out.insample:\n", + " assert method in ['average_proportions', 'proportion_averages']\n", + " test_close(\n", + " cls_middle_out(\n", + " S=S, \n", + " y_hat=S @ y_hat_bottom, \n", + " y_insample=S @ y_bottom, \n", + " tags=tags\n", + " )['mean'],\n", + " S @ y_hat_bottom\n", + " )\n", + " else:\n", + " test_close(\n", + " cls_middle_out(\n", + " S=S, \n", + " y_hat=S @ y_hat_bottom, \n", + " tags=tags\n", + " )['mean'],\n", + " S @ y_hat_bottom\n", + " )" ] }, { @@ -901,7 +929,7 @@ " \\mathbf{S}^{\\intercal}\\mathbf{W}^{-1}_{h}$$\n", " \n", " **Parameters:**
\n", - " `method`: str, one of `ols`, `wls_struct`, `wls_var`, `mint_shrink`, `mint_co`.
\n", + " `method`: str, one of `ols`, `wls_struct`, `wls_var`, `mint_shrink`, `mint_cov`.
\n", "\n", " **References:**
\n", " - [Wickramasuriya, S. L., Athanasopoulos, G., & Hyndman, R. J. (2019). \\\"Optimal forecast reconciliation for\n", @@ -911,12 +939,13 @@ " def __init__(self, \n", " method: str):\n", " self.method = method\n", + " self.insample = method in ['wls_var', 'mint_cov', 'mint_shrink']\n", "\n", " def reconcile(self, \n", " S: np.ndarray,\n", " y_hat: np.ndarray,\n", - " y_insample: np.ndarray,\n", - " y_hat_insample: np.ndarray,\n", + " y_insample: Optional[np.ndarray] = None,\n", + " y_hat_insample: Optional[np.ndarray] = None,\n", " sigmah: Optional[np.ndarray] = None,\n", " level: Optional[List[int]] = None,\n", " bootstrap: bool = False,\n", @@ -926,7 +955,8 @@ " **Parameters:**
\n", " `S`: Summing matrix of size (`base`, `bottom`).
\n", " `y_hat`: Forecast values of size (`base`, `horizon`).
\n", - " `idx_bottom`: Indices corresponding to the bottom level of `S`, size (`bottom`).
\n", + " `y_insample`: Insample values of size (`base`, `insample_size`). Only used by `wls_var`, `mint_cov`, `mint_shrink`
\n", + " `y_hat_insample`: Insample fitted values of size (`base`, `insample_size`). Only used by `wls_var`, `mint_cov`, `mint_shrink`
\n", " `sigmah`: float, estimate of the standard deviation of the h-step forecast of size (`base`, `horizon`)
\n", " `level`: float list 0-100, confidence levels for prediction intervals.
\n", " `bootstrap`: bool, whether or not to use bootstraped prediction intervals, alternative normality assumption.
\n", @@ -985,15 +1015,25 @@ "#| hide\n", "for method in ['ols', 'wls_struct', 'wls_var', 'mint_shrink']:\n", " cls_min_trace = MinTrace(method=method)\n", - " test_close(\n", - " cls_min_trace(\n", - " S=S, \n", - " y_hat=S @ y_hat_bottom, \n", - " y_insample=S @ y_bottom,\n", - " y_hat_insample=S @ y_hat_bottom_insample\n", - " )['mean'],\n", - " S @ y_hat_bottom\n", - " )\n", + " if cls_min_trace.insample:\n", + " assert method in ['wls_var', 'mint_cov', 'mint_shrink']\n", + " test_close(\n", + " cls_min_trace(\n", + " S=S, \n", + " y_hat=S @ y_hat_bottom, \n", + " y_insample=S @ y_bottom,\n", + " y_hat_insample=S @ y_hat_bottom_insample\n", + " )['mean'],\n", + " S @ y_hat_bottom\n", + " )\n", + " else:\n", + " test_close(\n", + " cls_min_trace(\n", + " S=S, \n", + " y_hat=S @ y_hat_bottom, \n", + " )['mean'],\n", + " S @ y_hat_bottom\n", + " )\n", "with ExceptionExpected(regex='min_trace (mint_cov)*'):\n", " cls_min_trace = MinTrace(method='mint_cov')\n", " cls_min_trace(\n", @@ -1101,12 +1141,11 @@ " raise ValueError(f\"Optimal Combination class does not support method: \\\"{method}\\\"\")\n", "\n", " self.method = method\n", + " self.insample = False\n", "\n", " def reconcile(self,\n", " S: np.ndarray,\n", " y_hat: np.ndarray,\n", - " y_insample: np.ndarray = None,\n", - " y_hat_insample: np.ndarray = None,\n", " sigmah: Optional[np.ndarray] = None,\n", " level: Optional[List[int]] = None,\n", " bootstrap: bool = False,\n", @@ -1116,7 +1155,6 @@ " **Parameters:**
\n", " `S`: Summing matrix of size (`base`, `bottom`).
\n", " `y_hat`: Forecast values of size (`base`, `horizon`).
\n", - " `idx_bottom`: Indices corresponding to the bottom level of `S`, size (`bottom`).
\n", " `sigmah`: float, estimate of the standard deviation of the h-step forecast of size (`base`, `horizon`)
\n", " `level`: float list 0-100, confidence levels for prediction intervals.
\n", " `bootstrap`: bool, whether or not to use bootstraped prediction intervals, alternative normality assumption.
\n", @@ -1127,8 +1165,6 @@ " \"\"\"\n", " return optimal_combination(S=S,\n", " y_hat=y_hat,\n", - " y_insample=y_insample,\n", - " y_hat_insample=y_hat_insample,\n", " method=self.method, sigmah=sigmah,\n", " level=level, bootstrap=bootstrap,\n", " bootstrap_samples=bootstrap_samples)\n", @@ -1338,6 +1374,7 @@ " lambda_reg: float = 1e-2):\n", " self.method = method\n", " self.lambda_reg = lambda_reg\n", + " self.insample = True\n", "\n", " def reconcile(self, \n", " S: np.ndarray,\n", @@ -1480,13 +1517,6 @@ "### Bootstraped Prediction Intervals\n", "- [Puwasala Gamakumara Ph. D. dissertation. Monash University, Econometrics and Business Statistics. \"Probabilistic Forecast Reconciliation\"](https://bridges.monash.edu/articles/thesis/Probabilistic_Forecast_Reconciliation_Theory_and_Applications/11869533)" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": {