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

return single encoded covariates if called on single target #1193

Merged
merged 3 commits into from
Sep 13, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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