def test_static_covariates_support(self): target_multi = concatenate( [tg.sine_timeseries(length=10, freq="h")] * 2, axis=1) target_multi = target_multi.with_static_covariates( pd.DataFrame([[0.0, 1.0], [2.0, 3.0]], index=["st1", "st2"])) # should work with cyclic encoding for time index model = TFTModel( input_chunk_length=3, output_chunk_length=4, add_encoders={"cyclic": { "future": "hour" }}, pl_trainer_kwargs={"fast_dev_run": True}, ) model.fit(target_multi, verbose=False) assert len(model.model.static_variables) == len( target_multi.static_covariates.columns) model.predict(n=1, series=target_multi, verbose=False) # raise an error when trained with static covariates of wrong dimensionality target_multi = target_multi.with_static_covariates( pd.concat([target_multi.static_covariates] * 2, axis=1)) with pytest.raises(ValueError): model.predict(n=1, series=target_multi, verbose=False) # raise an error when trained with static covariates and trying to predict without target_multi = target_multi.with_static_covariates(None) with pytest.raises(ValueError): model.predict(n=1, series=target_multi, verbose=False)
def test_concatenate_dim_samples(self): """ Test concatenation with static covariates along sample dimension (axis=2) Along sample dimension, we only take the static covariates of the first series (as we components and time don't change). """ static_covs_left = pd.DataFrame([[0, 1]], columns=["st1", "st2"]).astype(int) static_covs_right = pd.DataFrame([[3, 4]], columns=["st3", "st4"]).astype(int) ts_left = linear_timeseries( length=10).with_static_covariates(static_covs_left) ts_right = linear_timeseries( length=10).with_static_covariates(static_covs_right) ts_concat = concatenate([ts_left, ts_right], axis=2) assert ts_concat.static_covariates.equals(ts_left.static_covariates)
def test_concatenate_dim_time(self): """ Test concatenation with static covariates along time dimension (axis=0) Along time dimension, we only take the static covariates of the first series (as static covariates are time-independant). """ static_covs_left = pd.DataFrame([[0, 1]], columns=["st1", "st2"]).astype(int) static_covs_right = pd.DataFrame([[3, 4]], columns=["st3", "st4"]).astype(int) ts_left = linear_timeseries( length=10).with_static_covariates(static_covs_left) ts_right = linear_timeseries( length=10, start=ts_left.end_time() + ts_left.freq).with_static_covariates(static_covs_right) ts_concat = concatenate([ts_left, ts_right], axis=0) assert ts_concat.static_covariates.equals(ts_left.static_covariates)
def _encode_sequence( self, encoders: Sequence[SingleEncoder], transformer: Optional[SequentialEncoderTransformer], target: Sequence[TimeSeries], covariate: Optional[SupportedTimeSeries], n: Optional[int] = None, ) -> List[TimeSeries]: """Sequentially encodes the index of all input target/covariate TimeSeries If `n` is `None` it is a prediction and method `encoder.encode_inference()` is called. Otherwise, it is a training case and `encoder.encode_train()` is called. """ encode_method = "encode_train" if n is None else "encode_inference" encoded_sequence = [] if covariate is None: covariate = [None] * len(target) else: covariate = [covariate] if isinstance(covariate, TimeSeries) else covariate for ts, pc in zip(target, covariate): encoded = concatenate( [ getattr(enc, encode_method)( target=ts, covariate=pc, merge_covariate=False, n=n) for enc in encoders ], axis=DIMS[1], ) encoded_sequence.append( self._merge_covariate(encoded=encoded, covariate=pc)) if transformer is not None: encoded_sequence = transformer.transform(encoded_sequence) return encoded_sequence
class ReconciliationTestCase(unittest.TestCase): __test__ = True @classmethod def setUpClass(cls): logging.disable(logging.CRITICAL) np.random.seed(42) """ test case with a more intricate hierarchy """ LENGTH = 200 total_series = (tg.sine_timeseries(value_frequency=0.03, length=LENGTH) + 1 + tg.gaussian_timeseries(length=LENGTH) * 0.2) bottom_1 = total_series / 3 + tg.gaussian_timeseries(length=LENGTH) * 0.01 bottom_2 = 2 * total_series / 3 + tg.gaussian_timeseries( length=LENGTH) * 0.01 series = concatenate([total_series, bottom_1, bottom_2], axis=1) hierarchy = {"sine_1": ["sine"], "sine_2": ["sine"]} series = series.with_hierarchy(hierarchy) # get a single forecast model = LinearRegressionModel(lags=30, output_chunk_length=10) model.fit(series) pred = model.predict(n=20) # get a backtest forecast to get residuals pred_back = model.historical_forecasts(series, start=0.75, forecast_horizon=10) intersection = series.slice_intersect(pred_back) residuals = intersection - pred_back """ test case with a more intricate hierarchy """ components_complex = ["total", "a", "b", "x", "y", "ax", "ay", "bx", "by"] hierarchy_complex = { "ax": ["a", "x"], "ay": ["a", "y"], "bx": ["b", "x"], "by": ["b", "y"], "a": ["total"], "b": ["total"], "x": ["total"], "y": ["total"], } series_complex = TimeSeries.from_values( values=np.random.rand(50, len(components_complex), 5), columns=components_complex, hierarchy=hierarchy_complex, ) def _assert_reconciliation(self, fitted_recon): pred_r = fitted_recon.transform(self.pred) np.testing.assert_almost_equal( pred_r["sine"].values(copy=False), (pred_r["sine_1"] + pred_r["sine_2"]).values(copy=False), ) def _assert_reconciliation_complex(self, fitted_recon): reconciled = fitted_recon.transform(self.series_complex) def _assert_comps(comp, comps): np.testing.assert_almost_equal( reconciled[comp].values(copy=False), sum(reconciled[c] for c in comps).values(copy=False), ) _assert_comps("a", ["ax", "ay"]) _assert_comps("b", ["bx", "by"]) _assert_comps("x", ["ax", "bx"]) _assert_comps("y", ["ay", "by"]) _assert_comps("total", ["ax", "ay", "bx", "by"]) _assert_comps("total", ["a", "b"]) _assert_comps("total", ["x", "y"]) def test_bottom_up(self): recon = BottomUpReconciliator() self._assert_reconciliation(recon) def test_top_down(self): # should work when fitting on training series recon = TopDownReconciliator() recon.fit(self.series) self._assert_reconciliation(recon) # or when fitting on forecasts recon = TopDownReconciliator() recon.fit(self.pred) self._assert_reconciliation(recon) def test_mint(self): # ols recon = MinTReconciliator("ols") recon.fit(self.series) self._assert_reconciliation(recon) # wls_struct recon = MinTReconciliator("wls_struct") recon.fit(self.series) self._assert_reconciliation(recon) # wls_var recon = MinTReconciliator("wls_var") recon.fit(self.residuals) self._assert_reconciliation(recon) # mint_cov recon = MinTReconciliator("mint_cov") recon.fit(self.residuals) self._assert_reconciliation(recon) # wls_val recon = MinTReconciliator("wls_val") recon.fit(self.series) self._assert_reconciliation(recon) def test_summation_matrix(self): np.testing.assert_equal( _get_summation_matrix(self.series_complex), np.array([ [1, 1, 1, 1], [1, 1, 0, 0], [0, 0, 1, 1], [1, 0, 1, 0], [0, 1, 0, 1], [1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1], ]), ) def test_hierarchy_preserved_after_predict(self): self.assertEqual(self.pred.hierarchy, self.series.hierarchy) def test_more_intricate_hierarchy(self): recon = BottomUpReconciliator() self._assert_reconciliation_complex(recon) recon = TopDownReconciliator() recon.fit(self.series_complex) self._assert_reconciliation_complex(recon) recon = MinTReconciliator("ols") recon.fit(self.series_complex) self._assert_reconciliation_complex(recon) recon = MinTReconciliator("wls_struct") recon.fit(self.series_complex) self._assert_reconciliation_complex(recon) recon = MinTReconciliator("wls_val") recon.fit(self.series_complex) self._assert_reconciliation_complex(recon)
def test_concatenate_dim_component(self): """ test concatenation with static covariates along component dimension (axis=1) Along component dimension, we concatenate/transfer the static covariates of the series only if one of below cases applies: 1) concatenate when for each series the number of static cov components is equal to the number of components in the series. The static variable names (columns in series.static_covariates) must be identical across all series 2) if only the first series contains static covariates transfer only those 3) if `ignore_static_covarites=True`, case 1) is ignored and only the static covariates of the first series are transferred """ ts_uni = linear_timeseries(length=10) ts_multi = ts_uni.stack(ts_uni) static_covs_uni1 = pd.DataFrame([[0, 1]], columns=["st1", "st2"]).astype(int) static_covs_uni2 = pd.DataFrame([[3, 4]], columns=["st3", "st4"]).astype(int) static_covs_uni3 = pd.DataFrame([[2, 3, 4]], columns=["st1", "st2", "st3"]).astype(int) static_covs_multi = pd.DataFrame([[0, 0], [1, 1]], columns=["st1", "st2"]).astype(int) ts_uni_static_uni1 = ts_uni.with_static_covariates(static_covs_uni1) ts_uni_static_uni2 = ts_uni.with_static_covariates(static_covs_uni2) ts_uni_static_uni3 = ts_uni.with_static_covariates(static_covs_uni3) ts_multi_static_uni1 = ts_multi.with_static_covariates( static_covs_uni1) ts_multi_static_multi = ts_multi.with_static_covariates( static_covs_multi) # concatenation without covariates ts_concat = concatenate([ts_uni, ts_uni], axis=1) assert ts_concat.static_covariates is None # concatenation along component dimension results in multi component static covariates ts_concat = concatenate([ts_uni_static_uni1, ts_uni_static_uni1], axis=1) assert ts_concat.static_covariates.shape == (2, 2) assert ts_concat.components.equals(ts_concat.static_covariates.index) np.testing.assert_almost_equal( ts_concat.static_covariates_values(copy=False), pd.concat([static_covs_uni1] * 2, axis=0).values, ) # concatenation with inconsistent static variable names should fail ... with pytest.raises(ValueError): _ = concatenate([ts_uni_static_uni1, ts_uni_static_uni2], axis=1) # ... by ignoring the static covariates, it should work and take only the covariates of the first series ts_concat = concatenate( [ts_uni_static_uni1, ts_uni_static_uni2], axis=1, ignore_static_covariates=True, ) assert ts_concat.static_covariates.shape == (1, 2) assert ts_concat.static_covariates.index.equals( pd.Index([DEFAULT_GLOBAL_STATIC_COV_NAME])) np.testing.assert_almost_equal( ts_concat.static_covariates_values(copy=False), ts_uni_static_uni1.static_covariates_values(copy=False), ) # concatenation with inconsistent number of static covariates should fail ... with pytest.raises(ValueError): _ = concatenate([ts_uni_static_uni1, ts_uni_static_uni3], axis=1) # concatenation will only work if for each series the number of static cov components is equal to the # number of components in the series with pytest.raises(ValueError): _ = concatenate([ts_uni_static_uni1, ts_multi_static_uni1], axis=1) ts_concat = concatenate([ts_uni_static_uni1, ts_multi_static_multi], axis=1) assert ts_concat.static_covariates.shape == (ts_concat.n_components, 2) assert ts_concat.components.equals(ts_concat.static_covariates.index) np.testing.assert_almost_equal( ts_concat.static_covariates_values(copy=False), pd.concat([static_covs_uni1, static_covs_multi], axis=0), )