Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FIX] Clean up code #310

Merged
merged 2 commits into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions hierarchicalforecast/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,6 @@
'hierarchicalforecast/utils.py'),
'hierarchicalforecast.utils.aggregate': ( 'src/utils.html#aggregate',
'hierarchicalforecast/utils.py'),
'hierarchicalforecast.utils.cov2corr': ( 'src/utils.html#cov2corr',
'hierarchicalforecast/utils.py'),
'hierarchicalforecast.utils.is_strictly_hierarchical': ( 'src/utils.html#is_strictly_hierarchical',
'hierarchicalforecast/utils.py'),
'hierarchicalforecast.utils.level_to_outputs': ( 'src/utils.html#level_to_outputs',
Expand Down
18 changes: 9 additions & 9 deletions hierarchicalforecast/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from narwhals.typing import Frame, FrameT
from scipy.stats import norm
from scipy import sparse
from typing import Dict, List, Optional
from typing import Optional

import narwhals as nw
import numpy as np
Expand Down Expand Up @@ -104,7 +104,7 @@ class HierarchicalReconciliation:
[Rob J. Hyndman and George Athanasopoulos (2018). \"Forecasting principles and practice, Hierarchical and Grouped Series\".](https://otexts.com/fpp3/hierarchical.html)
"""

def __init__(self, reconcilers: List[HReconciler]):
def __init__(self, reconcilers: list[HReconciler]):
self.reconcilers = reconcilers
self.orig_reconcilers = copy.deepcopy(reconcilers) # TODO: elegant solution
self.insample = any([method.insample for method in reconcilers])
Expand All @@ -114,13 +114,13 @@ def _prepare_fit(
Y_hat_nw: Frame,
S_nw: Frame,
Y_nw: Optional[Frame],
tags: Dict[str, np.ndarray],
level: Optional[List[int]] = None,
tags: dict[str, np.ndarray],
level: Optional[list[int]] = None,
intervals_method: str = "normality",
id_col: str = "unique_id",
time_col: str = "ds",
target_col: str = "y",
) -> tuple[FrameT, FrameT, FrameT, List[str]]:
) -> tuple[FrameT, FrameT, FrameT, list[str]]:
"""
Performs preliminary wrangling and protections
"""
Expand Down Expand Up @@ -267,9 +267,9 @@ def reconcile(
self,
Y_hat_df: Frame,
S: Frame,
tags: Dict[str, np.ndarray],
tags: dict[str, np.ndarray],
Y_df: Optional[Frame] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: str = "normality",
num_samples: int = -1,
seed: int = 0,
Expand Down Expand Up @@ -505,9 +505,9 @@ def bootstrap_reconcile(
self,
Y_hat_df: Frame,
S_df: Frame,
tags: Dict[str, np.ndarray],
tags: dict[str, np.ndarray],
Y_df: Optional[Frame] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: str = "normality",
num_samples: int = -1,
num_seeds: int = 1,
Expand Down
24 changes: 12 additions & 12 deletions hierarchicalforecast/evaluation.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from inspect import signature
from narwhals.typing import Frame, FrameT
from scipy.stats import multivariate_normal
from typing import Callable, Dict, List, Optional, Union
from typing import Callable, Optional, Union

# %% ../nbs/src/evaluation.ipynb 7
def _metric_protections(
Expand Down Expand Up @@ -349,14 +349,14 @@ class HierarchicalEvaluation:
**References:**<br>
"""

def __init__(self, evaluators: List[Callable]):
def __init__(self, evaluators: list[Callable]):
self.evaluators = evaluators

def evaluate(
self,
Y_hat_df: Frame,
Y_test_df: Frame,
tags: Dict[str, np.ndarray],
tags: dict[str, np.ndarray],
Y_df: Optional[Frame] = None,
benchmark: Optional[str] = None,
id_col: str = "unique_id",
Expand Down Expand Up @@ -458,16 +458,16 @@ def evaluate(
evaluation_index_np[i_level * len(fn_names) + i_fn, 1] = fn_name

evaluation_np = evaluation_np.reshape(-1, len(model_names))
evaluation_index_dict = {
"level": evaluation_index_np[:, 0],
"metric": evaluation_index_np[:, 1],
}
evaluation_index_nw = nw.from_dict(
evaluation_index_dict, native_namespace=native_namespace
evaluation_nw = nw.from_dict(
{
**{
"level": evaluation_index_np[:, 0],
"metric": evaluation_index_np[:, 1],
},
**dict(zip(model_names, evaluation_np.T)),
},
native_namespace=native_namespace,
)
evaluation_dict = dict(zip(model_names, evaluation_np.T))
evaluation_nw = evaluation_index_nw.with_columns(**evaluation_dict)
evaluation_nw = evaluation_nw[["level", "metric"] + model_names]

evaluation = evaluation_nw.to_native()

Expand Down
56 changes: 28 additions & 28 deletions hierarchicalforecast/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from collections import OrderedDict
from concurrent.futures import ThreadPoolExecutor
from copy import deepcopy
from typing import Dict, List, Optional, Union
from typing import Optional, Union

import numpy as np
from quadprog import solve_qp
Expand Down Expand Up @@ -81,7 +81,7 @@ def _reconcile(
P: np.ndarray,
y_hat: np.ndarray,
SP: np.ndarray = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
sampler: Optional[Union[Normality, PERMBU, Bootstrap]] = None,
):

Expand All @@ -101,7 +101,7 @@ def _reconcile(
return res

def predict(
self, S: np.ndarray, y_hat: np.ndarray, level: Optional[List[int]] = None
self, S: np.ndarray, y_hat: np.ndarray, level: Optional[list[int]] = None
):
"""Predict using reconciler.

Expand Down Expand Up @@ -194,7 +194,7 @@ def fit(
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
):
"""Bottom Up Fit Method.

Expand Down Expand Up @@ -235,11 +235,11 @@ def fit_predict(
y_insample: Optional[np.ndarray] = None,
y_hat_insample: Optional[np.ndarray] = None,
sigmah: Optional[np.ndarray] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
):
"""BottomUp Reconciliation Method.

Expand Down Expand Up @@ -300,7 +300,7 @@ def _get_PW_matrices(self, S, idx_bottom):

# %% ../nbs/src/methods.ipynb 27
def _get_child_nodes(
S: Union[np.ndarray, sparse.csr_matrix], tags: Dict[str, np.ndarray]
S: Union[np.ndarray, sparse.csr_matrix], tags: dict[str, np.ndarray]
):
if isinstance(S, sparse.spmatrix):
S = S.toarray()
Expand All @@ -324,8 +324,8 @@ def _get_child_nodes(
def _reconcile_fcst_proportions(
S: np.ndarray,
y_hat: np.ndarray,
tags: Dict[str, np.ndarray],
nodes: Dict[str, Dict[int, np.ndarray]],
tags: dict[str, np.ndarray],
nodes: dict[str, dict[int, np.ndarray]],
idx_top: int,
):
reconciled = np.zeros_like(y_hat)
Expand Down Expand Up @@ -369,7 +369,7 @@ def _get_PW_matrices(
S: np.ndarray,
y_hat: np.ndarray,
y_insample: np.ndarray,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
):

n_hiers, n_bottom = S.shape
Expand Down Expand Up @@ -416,7 +416,7 @@ def fit(
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
idx_bottom: Optional[np.ndarray] = None,
):
"""TopDown Fit Method.
Expand Down Expand Up @@ -458,12 +458,12 @@ def fit_predict(
self,
S: np.ndarray,
y_hat: np.ndarray,
tags: Dict[str, np.ndarray],
tags: dict[str, np.ndarray],
idx_bottom: np.ndarray = None,
y_insample: Optional[np.ndarray] = None,
y_hat_insample: Optional[np.ndarray] = None,
sigmah: Optional[np.ndarray] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
Expand Down Expand Up @@ -540,7 +540,7 @@ def _get_PW_matrices(
S: sparse.csr_matrix,
y_hat: np.ndarray,
y_insample: np.ndarray,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
):
# Check if the data structure is strictly hierarchical.
if tags is not None and not is_strictly_hierarchical(S, tags):
Expand Down Expand Up @@ -625,9 +625,9 @@ def fit_predict(
self,
S: np.ndarray,
y_hat: np.ndarray,
tags: Dict[str, np.ndarray],
tags: dict[str, np.ndarray],
y_insample: Optional[np.ndarray] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: Optional[str] = None,
):
"""Middle Out Reconciliation Method.
Expand Down Expand Up @@ -727,11 +727,11 @@ def fit_predict(
self,
S: np.ndarray,
y_hat: np.ndarray,
tags: Dict[str, np.ndarray],
tags: dict[str, np.ndarray],
y_insample: Optional[np.ndarray] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: Optional[str] = None,
) -> Dict[str, np.ndarray]:
) -> dict[str, np.ndarray]:
# Check if the data structure is strictly hierarchical.
if not is_strictly_hierarchical(S, tags):
raise ValueError(
Expand Down Expand Up @@ -849,7 +849,7 @@ def _get_PW_matrices(
y_hat: np.ndarray,
y_insample: Optional[np.ndarray] = None,
y_hat_insample: Optional[np.ndarray] = None,
idx_bottom: Optional[List[int]] = None,
idx_bottom: Optional[list[int]] = None,
):
# shape residuals_insample (n_hiers, obs)
res_methods = ["wls_var", "mint_cov", "mint_shrink"]
Expand Down Expand Up @@ -954,7 +954,7 @@ def fit(
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
idx_bottom: Optional[np.ndarray] = None,
):
"""MinTrace Fit Method.
Expand Down Expand Up @@ -1056,11 +1056,11 @@ def fit_predict(
y_insample: Optional[np.ndarray] = None,
y_hat_insample: Optional[np.ndarray] = None,
sigmah: Optional[np.ndarray] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
):
"""MinTrace Reconciliation Method.

Expand Down Expand Up @@ -1130,7 +1130,7 @@ def _get_PW_matrices(
y_hat: np.ndarray,
y_insample: Optional[np.ndarray] = None,
y_hat_insample: Optional[np.ndarray] = None,
idx_bottom: Optional[List[int]] = None,
idx_bottom: Optional[list[int]] = None,
):
# shape residuals_insample (n_hiers, obs)
res_methods = ["wls_var", "mint_cov", "mint_shrink"]
Expand Down Expand Up @@ -1224,7 +1224,7 @@ def fit(
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
idx_bottom: Optional[np.ndarray] = None,
):
# Clip the base forecasts if required to align them with their use in practice.
Expand Down Expand Up @@ -1414,7 +1414,7 @@ def fit(
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
idx_bottom: Optional[np.ndarray] = None,
):
"""ERM Fit Method.
Expand Down Expand Up @@ -1463,11 +1463,11 @@ def fit_predict(
y_insample: Optional[np.ndarray] = None,
y_hat_insample: Optional[np.ndarray] = None,
sigmah: Optional[np.ndarray] = None,
level: Optional[List[int]] = None,
level: Optional[list[int]] = None,
intervals_method: Optional[str] = None,
num_samples: Optional[int] = None,
seed: Optional[int] = None,
tags: Optional[Dict[str, np.ndarray]] = None,
tags: Optional[dict[str, np.ndarray]] = None,
):
"""ERM Reconciliation Method.

Expand Down
9 changes: 5 additions & 4 deletions hierarchicalforecast/probabilistic_methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

# %% ../nbs/src/probabilistic_methods.ipynb 3
import warnings
from typing import Dict, Optional
from typing import Optional

import numpy as np
from scipy.stats import norm
from sklearn.preprocessing import OneHotEncoder

from .utils import is_strictly_hierarchical, cov2corr
from .utils import is_strictly_hierarchical

# %% ../nbs/src/probabilistic_methods.ipynb 6
class Normality:
Expand Down Expand Up @@ -64,7 +64,8 @@ def __init__(

# Base Normality Errors assume independence/diagonal covariance
# TODO: replace bilinearity with elementwise row multiplication
R1 = cov2corr(self.W)
std_ = np.sqrt(np.diag(self.W))
R1 = self.W / np.outer(std_, std_)
Wh = [np.diag(sigma) @ R1 @ np.diag(sigma).T for sigma in self.sigmah.T]

# Reconciled covariances across forecast horizon
Expand Down Expand Up @@ -262,7 +263,7 @@ class PERMBU:
def __init__(
self,
S: np.ndarray,
tags: Dict[str, np.ndarray],
tags: dict[str, np.ndarray],
y_hat: np.ndarray,
y_insample: np.ndarray,
y_hat_insample: np.ndarray,
Expand Down
Loading