From 7d2e83649f0c431cb34d4ada6b28f49904492e8c Mon Sep 17 00:00:00 2001 From: dennisbader Date: Wed, 7 Sep 2022 14:38:59 +0200 Subject: [PATCH] return single encoded covariates if called on single target --- .../tests/models/forecasting/test_encoders.py | 52 +++++++++---------- .../forecasting/test_regression_models.py | 4 +- darts/utils/data/encoders.py | 22 ++++++-- 3 files changed, 47 insertions(+), 31 deletions(-) diff --git a/darts/tests/models/forecasting/test_encoders.py b/darts/tests/models/forecasting/test_encoders.py index c1f076151a..5a91fe497a 100644 --- a/darts/tests/models/forecasting/test_encoders.py +++ b/darts/tests/models/forecasting/test_encoders.py @@ -402,16 +402,16 @@ def test_integer_positional_encoder(self): # absolute encoder takes the first observed index as a reference (from training) vals = np.arange(len(ts)).reshape((len(ts), 1)) self.assertTrue( - (t1[0].time_index == ts.time_index).all() and (t1[0].values() == vals).all() + (t1.time_index == ts.time_index).all() and (t1.values() == vals).all() ) # test that the position values are updated correctly self.assertTrue( - (t2[0].time_index == ts.time_index + ts.freq).all() - and (t2[0].values() == vals + 1).all() + (t2.time_index == ts.time_index + ts.freq).all() + and (t2.values() == vals + 1).all() ) self.assertTrue( - (t3[0].time_index == ts.time_index - ts.freq).all() - and (t3[0].values() == vals - 1).all() + (t3.time_index == ts.time_index - ts.freq).all() + and (t3.values() == vals - 1).all() ) # quickly test inference encoding # n > output_chunk_length @@ -419,7 +419,7 @@ def test_integer_positional_encoder(self): self.assertTrue( ( - t4[0].values()[:, 0] + t4.values()[:, 0] == np.arange(len(ts) - input_chunk_length, len(ts) + 1) ).all() ) @@ -427,7 +427,7 @@ def test_integer_positional_encoder(self): t5, _ = encs.encode_inference(output_chunk_length - 1, ts) self.assertTrue( ( - t5[0].values()[:, 0] == np.arange(len(ts) - input_chunk_length, len(ts)) + t5.values()[:, 0] == np.arange(len(ts) - input_chunk_length, len(ts)) ).all() ) @@ -451,26 +451,26 @@ def test_integer_positional_encoder(self): # relative encoder takes the end of the training series as reference vals = np.arange(-len(ts) + 1, 1).reshape((len(ts), 1)) self.assertTrue( - (t1[0].time_index == ts.time_index).all() and (t1[0].values() == vals).all() + (t1.time_index == ts.time_index).all() and (t1.values() == vals).all() ) self.assertTrue( - (t2[0].time_index == ts.time_index + ts.freq).all() - and (t2[0].values() == vals + 1).all() + (t2.time_index == ts.time_index + ts.freq).all() + and (t2.values() == vals + 1).all() ) self.assertTrue( - (t3[0].time_index == ts.time_index - ts.freq).all() - and (t3[0].values() == vals - 1).all() + (t3.time_index == ts.time_index - ts.freq).all() + and (t3.values() == vals - 1).all() ) # quickly test inference encoding # n > output_chunk_length t4, _ = encs.encode_inference(output_chunk_length + 1, ts) self.assertTrue( - (t4[0].values()[:, 0] == np.arange(-input_chunk_length + 1, 1 + 1)).all() + (t4.values()[:, 0] == np.arange(-input_chunk_length + 1, 1 + 1)).all() ) # n <= output_chunk_length t5, _ = encs.encode_inference(output_chunk_length - 1, ts) self.assertTrue( - (t5[0].values()[:, 0] == np.arange(-input_chunk_length + 1, 0 + 1)).all() + (t5.values()[:, 0] == np.arange(-input_chunk_length + 1, 0 + 1)).all() ) def test_callable_encoder(self): @@ -492,8 +492,8 @@ def test_callable_encoder(self): ) t1, _ = encs.encode_train(ts) - self.assertTrue((ts.time_index.year.values == t1[0].values()[:, 0]).all()) - self.assertTrue((ts.time_index.year.values - 1 == t1[0].values()[:, 1]).all()) + self.assertTrue((ts.time_index.year.values == t1.values()[:, 0]).all()) + self.assertTrue((ts.time_index.year.values - 1 == t1.values()[:, 1]).all()) def test_transformer(self): ts1 = tg.linear_timeseries( @@ -517,21 +517,21 @@ def test_transformer(self): # ===> train set test <=== # user supplied covariates should not be transformed - self.assertTrue(t1[0]["cov_in"] == ts1) + self.assertTrue(t1["cov_in"] == ts1) # cyclic encodings should not be transformed for curve in ["sin", "cos"]: self.assertAlmostEqual( - t1[0][f"minute_{curve}"].all_values(copy=False).min(), -1.0, delta=10e-9 + t1[f"minute_{curve}"].all_values(copy=False).min(), -1.0, delta=10e-9 ) self.assertAlmostEqual( - t1[0][f"minute_{curve}"].values(copy=False).max(), 1.0, delta=10e-9 + t1[f"minute_{curve}"].values(copy=False).max(), 1.0, delta=10e-9 ) # all others should be transformed to values between 0 and 1 self.assertAlmostEqual( - t1[0]["absolute_idx"].values(copy=False).min(), 0.0, delta=10e-9 + t1["absolute_idx"].values(copy=False).min(), 0.0, delta=10e-9 ) self.assertAlmostEqual( - t1[0]["absolute_idx"].values(copy=False).max(), 1.0, delta=10e-9 + t1["absolute_idx"].values(copy=False).max(), 1.0, delta=10e-9 ) # ===> validation set test <=== @@ -546,10 +546,10 @@ def test_transformer(self): _, t2 = encs.encode_train(ts2, future_covariate=ts2) # make sure that when calling encoders the second time, scalers are not fit again (for validation and inference) self.assertAlmostEqual( - t2[0]["absolute_idx"].values(copy=False).min(), 1.0, delta=10e-9 + t2["absolute_idx"].values(copy=False).min(), 1.0, delta=10e-9 ) self.assertAlmostEqual( - t2[0]["absolute_idx"].values(copy=False).max(), 2.0, delta=10e-9 + t2["absolute_idx"].values(copy=False).max(), 2.0, delta=10e-9 ) fc_inf = tg.linear_timeseries( @@ -558,12 +558,12 @@ def test_transformer(self): _, t3 = encs.encode_inference(n=12, target=ts1, future_covariate=fc_inf) # index 0 is also start of train target series and value should be 0 - self.assertAlmostEqual(t3[0]["absolute_idx"][0].values()[0, 0], 0.0) + self.assertAlmostEqual(t3["absolute_idx"][0].values()[0, 0], 0.0) # index len(ts1) - 1 is the prediction point and value should be 0 - self.assertAlmostEqual(t3[0]["absolute_idx"][len(ts1) - 1].values()[0, 0], 1.0) + self.assertAlmostEqual(t3["absolute_idx"][len(ts1) - 1].values()[0, 0], 1.0) # the future should scale proportional to distance to prediction point self.assertAlmostEqual( - t3[0]["absolute_idx"][80 - 1].values()[0, 0], 80 / 60, delta=0.01 + t3["absolute_idx"][80 - 1].values()[0, 0], 80 / 60, delta=0.01 ) def helper_test_cyclic_encoder( diff --git a/darts/tests/models/forecasting/test_regression_models.py b/darts/tests/models/forecasting/test_regression_models.py index 948b77bf88..b8fdc71e02 100644 --- a/darts/tests/models/forecasting/test_regression_models.py +++ b/darts/tests/models/forecasting/test_regression_models.py @@ -1055,9 +1055,9 @@ def test_encoders(self): [model_pc_valid1, model_fc_valid1, model_mixed_valid1], examples ): covariates = covariates_examples[ex] - # don't pass covariates, let them be generated by encoders + # don't pass covariates, let them be generated by encoders. Test single target series input model_copy = copy.deepcopy(model) - model_copy.fit(target_series) + model_copy.fit(target_series[0]) assert model_copy.encoders.encoding_available self.helper_test_encoders_settings(model_copy, ex) diff --git a/darts/utils/data/encoders.py b/darts/utils/data/encoders.py index 553c64a10f..f83efc04ac 100644 --- a/darts/utils/data/encoders.py +++ b/darts/utils/data/encoders.py @@ -732,7 +732,9 @@ def encode_train( future_covariate: Optional[SupportedTimeSeries] = None, encode_past: bool = True, encode_future: bool = True, - ) -> Tuple[Sequence[TimeSeries], Sequence[TimeSeries]]: + ) -> Tuple[ + Union[TimeSeries, Sequence[TimeSeries]], Union[TimeSeries, Sequence[TimeSeries]] + ]: """Returns encoded index for all past and/or future covariates for training. Which covariates are generated depends on the parameters used at model creation. @@ -791,7 +793,9 @@ def encode_inference( future_covariate: Optional[SupportedTimeSeries] = None, encode_past: bool = True, encode_future: bool = True, - ) -> Tuple[Sequence[TimeSeries], Sequence[TimeSeries]]: + ) -> Tuple[ + Union[TimeSeries, Sequence[TimeSeries]], Union[TimeSeries, Sequence[TimeSeries]] + ]: """Returns encoded index for all past and/or future covariates for inference/prediction. Which covariates are generated depends on the parameters used at model creation. @@ -845,7 +849,8 @@ def _launch_encoder( if not self.encoding_available: return past_covariate, future_covariate - target = [target] if isinstance(target, TimeSeries) else target + single_series = isinstance(target, TimeSeries) + target = [target] if single_series else target if self.past_encoders and encode_past: past_covariate = self._encode_sequence( @@ -864,6 +869,17 @@ def _launch_encoder( covariate=future_covariate, n=n, ) + + if single_series: + past_covariate = ( + past_covariate[0] if past_covariate is not None else past_covariate + ) + future_covariate = ( + future_covariate[0] + if future_covariate is not None + else future_covariate + ) + return past_covariate, future_covariate def _encode_sequence(