Skip to content

Commit

Permalink
Merge pull request #4 from Nixtla/fix/order
Browse files Browse the repository at this point in the history
[FIX] #1 and #2
  • Loading branch information
AzulGarza authored Jun 28, 2022
2 parents ee09e93 + 6eac3ed commit f5afd19
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 36 deletions.
14 changes: 9 additions & 5 deletions hierarchicalforecast/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,20 +43,24 @@ def reconcile(self, Y_h: pd.DataFrame, Y_df: pd.DataFrame, S: pd.DataFrame):
"""
drop_cols = ['ds', 'y'] if 'y' in Y_h.columns else ['ds']
model_names = Y_h.drop(columns=drop_cols, axis=1).columns.to_list()
uids = Y_h.index.unique()
# same order of Y_h to prevent errors
S_ = S.loc[uids]
common_vals = dict(
y = Y_df.pivot(columns='ds', values='y').loc[S.index].values,
S = S.values,
idx_bottom = [S.index.get_loc(col) for col in S.columns]
y = Y_df.pivot(columns='ds', values='y').loc[uids].values,
S = S_.values,
idx_bottom = [S_.index.get_loc(col) for col in S.columns]
)
fcsts = Y_h.copy()
for reconcile_fn in self.reconcilers:
reconcile_fn_name = _build_fn_name(reconcile_fn)
has_res = 'residuals' in signature(reconcile_fn).parameters
for model_name in model_names:
y_hat_model = Y_h.pivot(columns='ds', values=model_name).loc[S.index].values
# Remember: pivot sorts uid
y_hat_model = Y_h.pivot(columns='ds', values=model_name).loc[uids].values
if has_res:
if model_name in Y_df:
common_vals['residuals'] = Y_df.pivot(columns='ds', values=model_name).loc[S.index].values.T
common_vals['residuals'] = Y_df.pivot(columns='ds', values=model_name).loc[uids].values.T
else:
# some methods have the residuals argument
# but they don't need them
Expand Down
12 changes: 8 additions & 4 deletions hierarchicalforecast/methods.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ def _reconcile(S: np.ndarray, P: np.ndarray, W: np.ndarray,

# Cell
def bottom_up(S: np.ndarray,
y_hat: np.ndarray):
y_hat: np.ndarray,
idx_bottom: List[int]):
n_hiers, n_bottom = S.shape
P = np.eye(n_bottom, n_hiers, k=(n_hiers - n_bottom), dtype=np.float32)
P = np.zeros_like(S, dtype=np.float32)
P[idx_bottom] = S[idx_bottom]
P = P.T
W = np.eye(n_hiers, dtype=np.float32)
return _reconcile(S, P, W, y_hat)

Expand All @@ -28,8 +31,9 @@ class BottomUp:

def reconcile(self,
S: np.ndarray,
y_hat: np.ndarray):
return bottom_up(S=S, y_hat=y_hat)
y_hat: np.ndarray,
idx_bottom: np.ndarray):
return bottom_up(S=S, y_hat=y_hat, idx_bottom=idx_bottom)

__call__ = reconcile

Expand Down
25 changes: 10 additions & 15 deletions nbs/core.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,24 @@
" \"\"\"\n",
" drop_cols = ['ds', 'y'] if 'y' in Y_h.columns else ['ds']\n",
" model_names = Y_h.drop(columns=drop_cols, axis=1).columns.to_list()\n",
" uids = Y_h.index.unique()\n",
" # same order of Y_h to prevent errors\n",
" S_ = S.loc[uids]\n",
" common_vals = dict(\n",
" y = Y_df.pivot(columns='ds', values='y').loc[S.index].values,\n",
" S = S.values,\n",
" idx_bottom = [S.index.get_loc(col) for col in S.columns]\n",
" y = Y_df.pivot(columns='ds', values='y').loc[uids].values,\n",
" S = S_.values,\n",
" idx_bottom = [S_.index.get_loc(col) for col in S.columns]\n",
" )\n",
" fcsts = Y_h.copy()\n",
" for reconcile_fn in self.reconcilers:\n",
" reconcile_fn_name = _build_fn_name(reconcile_fn)\n",
" has_res = 'residuals' in signature(reconcile_fn).parameters\n",
" for model_name in model_names:\n",
" y_hat_model = Y_h.pivot(columns='ds', values=model_name).loc[S.index].values\n",
" # Remember: pivot sorts uid\n",
" y_hat_model = Y_h.pivot(columns='ds', values=model_name).loc[uids].values\n",
" if has_res:\n",
" if model_name in Y_df:\n",
" common_vals['residuals'] = Y_df.pivot(columns='ds', values=model_name).loc[S.index].values.T\n",
" common_vals['residuals'] = Y_df.pivot(columns='ds', values=model_name).loc[uids].values.T\n",
" else:\n",
" # some methods have the residuals argument\n",
" # but they don't need them\n",
Expand All @@ -125,7 +129,7 @@
"source": [
"#hide\n",
"from hierarchicalforecast.methods import (\n",
" BottomUp, TopDown, MinTrace, ERM\n",
" BottomUp, TopDown, MinTrace, ERM, bottom_up\n",
")\n",
"from hierarchicalforecast.utils import hierarchize"
]
Expand Down Expand Up @@ -185,15 +189,6 @@
" test_close(reconciled['y'], reconciled[model])"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"reconciled.loc[S.columns].sum()"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down
30 changes: 18 additions & 12 deletions nbs/methods.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,12 @@
"source": [
"#export\n",
"def bottom_up(S: np.ndarray,\n",
" y_hat: np.ndarray):\n",
" y_hat: np.ndarray,\n",
" idx_bottom: List[int]):\n",
" n_hiers, n_bottom = S.shape\n",
" P = np.eye(n_bottom, n_hiers, k=(n_hiers - n_bottom), dtype=np.float32)\n",
" P = np.zeros_like(S, dtype=np.float32)\n",
" P[idx_bottom] = S[idx_bottom]\n",
" P = P.T\n",
" W = np.eye(n_hiers, dtype=np.float32)\n",
" return _reconcile(S, P, W, y_hat)"
]
Expand All @@ -79,8 +82,9 @@
" \n",
" def reconcile(self,\n",
" S: np.ndarray,\n",
" y_hat: np.ndarray):\n",
" return bottom_up(S=S, y_hat=y_hat)\n",
" y_hat: np.ndarray,\n",
" idx_bottom: np.ndarray):\n",
" return bottom_up(S=S, y_hat=y_hat, idx_bottom=idx_bottom)\n",
" \n",
" __call__ = reconcile"
]
Expand All @@ -96,8 +100,8 @@
" [1., 1., 1., 1.],\n",
" [1., 1., 0., 0.],\n",
" [0., 0., 1., 1.],\n",
" [1., 0., 0., 0.],\n",
" [0., 1., 0., 0.],\n",
" [1., 0., 0., 0.],\n",
" [0., 0., 1., 0.],\n",
" [0., 0., 0., 1.],\n",
"])\n",
Expand All @@ -106,7 +110,8 @@
"y_bottom = np.vstack([i * _y for i in range(1, 5)])\n",
"resids_bottom = y_bottom - np.roll(y_bottom, 1)\n",
"resids_bottom[:, 0] = np.nan\n",
"y_hat_bottom = np.vstack([i * np.ones(h) for i in range(1, 5)])"
"y_hat_bottom = np.vstack([i * np.ones(h) for i in range(1, 5)])\n",
"idx_bottom = [4, 3, 5, 6]"
]
},
{
Expand All @@ -118,7 +123,7 @@
"#hide\n",
"cls_bottom_up = BottomUp()\n",
"test_eq(\n",
" cls_bottom_up(S=S, y_hat=S @ y_hat_bottom),\n",
" cls_bottom_up(S=S, y_hat=S @ y_hat_bottom, idx_bottom=idx_bottom),\n",
" S @ y_hat_bottom\n",
")"
]
Expand Down Expand Up @@ -191,10 +196,11 @@
" cls_top_down = TopDown(method=method)\n",
" test_close(\n",
" cls_top_down(\n",
" S, \n",
" S @ y_hat_bottom, \n",
" S @ y_bottom, \n",
" [3, 4, 5, 6]),\n",
" S=S, \n",
" y_hat=S @ y_hat_bottom, \n",
" y=S @ y_bottom, \n",
" idx_bottom=idx_bottom\n",
" ),\n",
" S @ y_hat_bottom\n",
" )"
]
Expand Down Expand Up @@ -320,7 +326,7 @@
"source": [
"#export\n",
"def erm(S: np.ndarray,\n",
" y_hat: np.ndarray, \n",
" y_hat: np.ndarray,\n",
" method: str,\n",
" lambda_reg: float = 1e-2):\n",
" n_hiers, n_bottom = S.shape\n",
Expand Down

0 comments on commit f5afd19

Please sign in to comment.