Skip to content

Commit

Permalink
return single encoded covariates if called on single target (#1193)
Browse files Browse the repository at this point in the history
  • Loading branch information
dennisbader committed Sep 13, 2022
1 parent 28ca88d commit 1c49f86
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 31 deletions.
52 changes: 26 additions & 26 deletions darts/tests/models/forecasting/test_encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -402,32 +402,32 @@ 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
t4, _ = encs.encode_inference(output_chunk_length + 1, ts)

self.assertTrue(
(
t4[0].values()[:, 0]
t4.values()[:, 0]
== np.arange(len(ts) - input_chunk_length, len(ts) + 1)
).all()
)
# n <= output_chunk_length
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()
)

Expand 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):
Expand All @@ -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(
Expand All @@ -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 <===
Expand All @@ -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(
Expand All @@ -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(
Expand Down
4 changes: 2 additions & 2 deletions darts/tests/models/forecasting/test_regression_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
22 changes: 19 additions & 3 deletions darts/utils/data/encoders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand All @@ -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(
Expand Down

0 comments on commit 1c49f86

Please sign in to comment.