diff --git a/CHANGELOG.md b/CHANGELOG.md index 930ea37690..c7cdc9db33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,10 @@ but cannot always guarantee backwards compatibility. Changes that may **break co - Fixed a bug when using `TFTExplainer` with a `TFTModel` running on GPU. [#1949](https://github.com/unit8co/darts/pull/1949) by [Dennis Bader](https://github.com/dennisbader). - Fixed a bug in `TorchForecastingModel.load_weights()` that raised an error when loading the weights from a valid architecture. [#1952](https://github.com/unit8co/darts/pull/1952) by [Antoine Madrona](https://github.com/madtoinou). +### For developers of the library: + +**Improved** +- Refactored all tests to use pytest instead of unittest. [#1950](https://github.com/unit8co/darts/pull/1950) by [Dennis Bader](https://github.com/dennisbader). ## [0.25.0](https://github.com/unit8co/darts/tree/0.25.0) (2023-08-04) ### For users of the library: diff --git a/darts/tests/ad/test_aggregators.py b/darts/tests/ad/test_aggregators.py index 05b3956074..52d2e227a7 100644 --- a/darts/tests/ad/test_aggregators.py +++ b/darts/tests/ad/test_aggregators.py @@ -1,6 +1,7 @@ from typing import Sequence import numpy as np +import pytest from sklearn.ensemble import GradientBoostingClassifier from darts import TimeSeries @@ -8,7 +9,6 @@ from darts.ad.aggregators.ensemble_sklearn_aggregator import EnsembleSklearnAggregator from darts.ad.aggregators.or_aggregator import OrAggregator from darts.models import MovingAverageFilter -from darts.tests.base_test_class import DartsBaseTestClass list_NonFittableAggregator = [ OrAggregator(), @@ -20,7 +20,7 @@ ] -class ADAggregatorsTestCase(DartsBaseTestClass): +class TestADAggregators: np.random.seed(42) @@ -78,18 +78,14 @@ def test_DetectNonFittableAggregator(self): aggregator = OrAggregator() # Check return types - self.assertTrue(isinstance(aggregator.predict(self.mts_anomalies1), TimeSeries)) - self.assertTrue( - isinstance( - aggregator.predict([self.mts_anomalies1]), - Sequence, - ) + assert isinstance(aggregator.predict(self.mts_anomalies1), TimeSeries) + assert isinstance( + aggregator.predict([self.mts_anomalies1]), + Sequence, ) - self.assertTrue( - isinstance( - aggregator.predict([self.mts_anomalies1, self.mts_anomalies2]), - Sequence, - ) + assert isinstance( + aggregator.predict([self.mts_anomalies1, self.mts_anomalies2]), + Sequence, ) def test_DetectFittableAggregator(self): @@ -99,18 +95,14 @@ def test_DetectFittableAggregator(self): aggregator.fit(self.real_anomalies, self.mts_anomalies1) # Check return types - self.assertTrue(isinstance(aggregator.predict(self.mts_anomalies1), TimeSeries)) - self.assertTrue( - isinstance( - aggregator.predict([self.mts_anomalies1]), - Sequence, - ) + assert isinstance(aggregator.predict(self.mts_anomalies1), TimeSeries) + assert isinstance( + aggregator.predict([self.mts_anomalies1]), + Sequence, ) - self.assertTrue( - isinstance( - aggregator.predict([self.mts_anomalies1, self.mts_anomalies2]), - Sequence, - ) + assert isinstance( + aggregator.predict([self.mts_anomalies1, self.mts_anomalies2]), + Sequence, ) def test_eval_accuracy(self): @@ -118,46 +110,38 @@ def test_eval_accuracy(self): aggregator = AndAggregator() # Check return types - self.assertTrue( - isinstance( - aggregator.eval_accuracy(self.real_anomalies, self.mts_anomalies1), - float, - ) + assert isinstance( + aggregator.eval_accuracy(self.real_anomalies, self.mts_anomalies1), + float, ) - self.assertTrue( - isinstance( - aggregator.eval_accuracy([self.real_anomalies], [self.mts_anomalies1]), - Sequence, - ) + assert isinstance( + aggregator.eval_accuracy([self.real_anomalies], [self.mts_anomalies1]), + Sequence, ) - self.assertTrue( - isinstance( - aggregator.eval_accuracy(self.real_anomalies, [self.mts_anomalies1]), - Sequence, - ) + assert isinstance( + aggregator.eval_accuracy(self.real_anomalies, [self.mts_anomalies1]), + Sequence, ) - self.assertTrue( - isinstance( - aggregator.eval_accuracy( - [self.real_anomalies, self.real_anomalies], - [self.mts_anomalies1, self.mts_anomalies2], - ), - Sequence, - ) + assert isinstance( + aggregator.eval_accuracy( + [self.real_anomalies, self.real_anomalies], + [self.mts_anomalies1, self.mts_anomalies2], + ), + Sequence, ) # intersection between 'actual_anomalies' and the series in the sequence 'list_series' # must be non empty - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.eval_accuracy(self.real_anomalies[:30], self.mts_anomalies1[40:]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.eval_accuracy( [self.real_anomalies, self.real_anomalies[:30]], [self.mts_anomalies1, self.mts_anomalies1[40:]], ) # window parameter must be smaller than the length of the input (len = 100) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.eval_accuracy( self.real_anomalies, self.mts_anomalies1, window=101 ) @@ -167,176 +151,167 @@ def test_NonFittableAggregator(self): for aggregator in list_NonFittableAggregator: # name must be of type str - self.assertEqual( - type(aggregator.__str__()), - str, - ) + assert type(aggregator.__str__()) == str # Check if trainable is False, being a NonFittableAggregator - self.assertTrue(not aggregator.trainable) + assert not aggregator.trainable # predict on (sequence of) univariate series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.real_anomalies]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict(self.real_anomalies) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.real_anomalies]) # input a (sequence of) non binary series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict(self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.mts_train]) # input a (sequence of) probabilistic series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict(self.mts_probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.mts_probabilistic]) # input an element that is not a series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, "random"]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, 1]) # Check width return # Check if return type is the same number of series in input - self.assertTrue( - len( - aggregator.eval_accuracy( - [self.real_anomalies, self.real_anomalies], - [self.mts_anomalies1, self.mts_anomalies2], - ) - ), - len([self.mts_anomalies1, self.mts_anomalies2]), - ) + assert len( + aggregator.eval_accuracy( + [self.real_anomalies, self.real_anomalies], + [self.mts_anomalies1, self.mts_anomalies2], + ) + ), len([self.mts_anomalies1, self.mts_anomalies2]) def test_FittableAggregator(self): for aggregator in list_FittableAggregator: # name must be of type str - self.assertEqual( - type(aggregator.__str__()), - str, - ) + assert type(aggregator.__str__()) == str # Need to call fit() before calling predict() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.mts_anomalies1]) # Check if trainable is True, being a FittableAggregator - self.assertTrue(aggregator.trainable) + assert aggregator.trainable # Check if _fit_called is False - self.assertTrue(not aggregator._fit_called) + assert not aggregator._fit_called # fit on sequence with series that have different width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.real_anomalies], [self.mts_anomalies1, self.mts_anomalies3], ) # fit on a (sequence of) univariate series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, self.real_anomalies) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, [self.real_anomalies]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.real_anomalies], [self.mts_anomalies1, self.real_anomalies], ) # fit on a (sequence of) non binary series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, [self.mts_train]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.real_anomalies], [self.mts_anomalies1, self.mts_train], ) # fit on a (sequence of) probabilistic series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, self.mts_probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, [self.mts_probabilistic]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.real_anomalies], [self.mts_anomalies1, self.mts_probabilistic], ) # input an element that is not a series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, "random") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, [self.mts_anomalies1, "random"]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.real_anomalies, [self.mts_anomalies1, 1]) # fit on a (sequence of) multivariate anomalies - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.mts_anomalies1, self.mts_anomalies1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit([self.mts_anomalies1], [self.mts_anomalies1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.mts_anomalies1], [self.mts_anomalies1, self.mts_anomalies1], ) # fit on a (sequence of) non binary anomalies - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.train, self.mts_anomalies1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit([self.train], self.mts_anomalies1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.train], [self.mts_anomalies1, self.mts_anomalies1], ) # fit on a (sequence of) probabilistic anomalies - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit(self.mts_probabilistic, self.mts_anomalies1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit([self.mts_probabilistic], self.mts_anomalies1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.mts_probabilistic], [self.mts_anomalies1, self.mts_anomalies1], ) # input an element that is not a anomalies - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit("random", self.mts_anomalies1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, "random"], [self.mts_anomalies1, self.mts_anomalies1], ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, 1], [self.mts_anomalies1, self.mts_anomalies1] ) # nbr of anomalies must match nbr of input series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.real_anomalies], self.mts_anomalies1 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies, self.real_anomalies], [self.mts_anomalies1] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.fit( [self.real_anomalies], [self.mts_anomalies1, self.mts_anomalies1] ) @@ -345,53 +320,50 @@ def test_FittableAggregator(self): aggregator.fit(self.real_anomalies, self.mts_anomalies1) # Check if _fit_called is True after being fitted - self.assertTrue(aggregator._fit_called) + assert aggregator._fit_called # series must be same width as series used for training - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict(self.mts_anomalies3) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies3]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.mts_anomalies3]) # predict on (sequence of) univariate series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.real_anomalies]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict(self.real_anomalies) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.real_anomalies]) # input a (sequence of) non binary series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict(self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.mts_train]) # input a (sequence of) probabilistic series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict(self.mts_probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, self.mts_probabilistic]) # input an element that is not a series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, "random"]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): aggregator.predict([self.mts_anomalies1, 1]) # Check width return # Check if return type is the same number of series in input - self.assertTrue( - len( - aggregator.eval_accuracy( - [self.real_anomalies, self.real_anomalies], - [self.mts_anomalies1, self.mts_anomalies2], - ) - ), - len([self.mts_anomalies1, self.mts_anomalies2]), - ) + assert len( + aggregator.eval_accuracy( + [self.real_anomalies, self.real_anomalies], + [self.mts_anomalies1, self.mts_anomalies2], + ) + ), len([self.mts_anomalies1, self.mts_anomalies2]) def test_OrAggregator(self): @@ -400,103 +372,121 @@ def test_OrAggregator(self): # simple case # aggregator must have an accuracy of 0 for input with 2 components # (only 1 and only 0) and ground truth is only 0 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyzero, - self.series_1_and_0, - metric="accuracy", - ), - 0, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyzero, + self.series_1_and_0, + metric="accuracy", + ) + - 0 + ) + < 1e-05 ) # aggregator must have an accuracy of 1 for input with 2 components # (only 1 and only 0) and ground truth is only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.series_1_and_0, - metric="accuracy", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.series_1_and_0, + metric="accuracy", + ) + - 1 + ) + < 1e-05 ) # aggregator must have an accuracy of 1 for the input containing only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.mts_onlyones, - metric="accuracy", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.mts_onlyones, + metric="accuracy", + ) + - 1 + ) + < 1e-05 ) # aggregator must have an accuracy of 1 for the input containing only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.mts_onlyones, - metric="recall", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.mts_onlyones, + metric="recall", + ) + - 1 + ) + < 1e-05 ) # aggregator must have an accuracy of 1 for the input containing only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.mts_onlyones, - metric="precision", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.mts_onlyones, + metric="precision", + ) + - 1 + ) + < 1e-05 ) # single series case (random example) # aggregator must found 67 anomalies in the input mts_anomalies1 - self.assertEqual( + assert ( aggregator.predict(self.mts_anomalies1) .sum(axis=0) .all_values() - .flatten()[0], - 67, + .flatten()[0] + == 67 ) # aggregator must have an accuracy of 0.56 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, - self.mts_anomalies1, - metric="accuracy", - ), - 0.56, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, + self.mts_anomalies1, + metric="accuracy", + ) + - 0.56 + ) + < 1e-05 ) # aggregator must have an recall of 0.72549 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, self.mts_anomalies1, metric="recall" - ), - 0.72549, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, self.mts_anomalies1, metric="recall" + ) + - 0.72549 + ) + < 1e-05 ) # aggregator must have an f1 of 0.62711 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, self.mts_anomalies1, metric="f1" - ), - 0.62711, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, self.mts_anomalies1, metric="f1" + ) + - 0.62711 + ) + < 1e-05 ) # aggregator must have an precision of 0.55223 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, - self.mts_anomalies1, - metric="precision", - ), - 0.55223, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, + self.mts_anomalies1, + metric="precision", + ) + - 0.55223 + ) + < 1e-05 ) # multiple series case (random example) @@ -564,103 +554,121 @@ def test_AndAggregator(self): # simple case # aggregator must have an accuracy of 0 for input with 2 components # (only 1 and only 0) and ground truth is only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.series_1_and_0, - metric="accuracy", - ), - 0, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.series_1_and_0, + metric="accuracy", + ) + - 0 + ) + < 1e-05 ) # aggregator must have an accuracy of 0 for input with 2 components # (only 1 and only 0) and ground truth is only 0 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyzero, - self.series_1_and_0, - metric="accuracy", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyzero, + self.series_1_and_0, + metric="accuracy", + ) + - 1 + ) + < 1e-05 ) # aggregator must have an accuracy of 1 for the input containing only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.mts_onlyones, - metric="accuracy", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.mts_onlyones, + metric="accuracy", + ) + - 1 + ) + < 1e-05 ) # aggregator must have an accuracy of 1 for the input containing only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.mts_onlyones, - metric="recall", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.mts_onlyones, + metric="recall", + ) + - 1 + ) + < 1e-05 ) # aggregator must have an accuracy of 1 for the input containing only 1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.onlyones, - self.mts_onlyones, - metric="precision", - ), - 1, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.onlyones, + self.mts_onlyones, + metric="precision", + ) + - 1 + ) + < 1e-05 ) # single series case (random example) # aggregator must found 27 anomalies in the input mts_anomalies1 - self.assertEqual( + assert ( aggregator.predict(self.mts_anomalies1) .sum(axis=0) .all_values() - .flatten()[0], - 27, + .flatten()[0] + == 27 ) # aggregator must have an accuracy of 0.44 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, - self.mts_anomalies1, - metric="accuracy", - ), - 0.44, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, + self.mts_anomalies1, + metric="accuracy", + ) + - 0.44 + ) + < 1e-05 ) # aggregator must have an recall of 0.21568 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, self.mts_anomalies1, metric="recall" - ), - 0.21568, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, self.mts_anomalies1, metric="recall" + ) + - 0.21568 + ) + < 1e-05 ) # aggregator must have an f1 of 0.28205 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, self.mts_anomalies1, metric="f1" - ), - 0.28205, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, self.mts_anomalies1, metric="f1" + ) + - 0.28205 + ) + < 1e-05 ) # aggregator must have an precision of 0.40740 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, - self.mts_anomalies1, - metric="precision", - ), - 0.40740, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, + self.mts_anomalies1, + metric="precision", + ) + - 0.40740 + ) + < 1e-05 ) # multiple series case (random example) @@ -724,7 +732,7 @@ def test_AndAggregator(self): def test_EnsembleSklearn(self): # Need to input an EnsembleSklearn model - with self.assertRaises(ValueError): + with pytest.raises(ValueError): EnsembleSklearnAggregator(model=MovingAverageFilter(window=10)) # simple case @@ -739,14 +747,16 @@ def test_EnsembleSklearn(self): ) aggregator.fit(self.real_anomalies_3w, self.mts_anomalies3) - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies_3w, - self.mts_anomalies3, - metric="accuracy", - ), - 0.92, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies_3w, + self.mts_anomalies3, + metric="accuracy", + ) + - 0.92 + ) + < 1e-05 ) np.testing.assert_array_almost_equal( @@ -770,49 +780,57 @@ def test_EnsembleSklearn(self): aggregator.fit(self.real_anomalies, self.mts_anomalies1) # aggregator must found 100 anomalies in the input mts_anomalies1 - self.assertEqual( + assert ( aggregator.predict(self.mts_anomalies1) .sum(axis=0) .all_values() - .flatten()[0], - 100, + .flatten()[0] + == 100 ) # aggregator must have an accuracy of 0.51 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, - self.mts_anomalies1, - metric="accuracy", - ), - 0.51, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, + self.mts_anomalies1, + metric="accuracy", + ) + - 0.51 + ) + < 1e-05 ) # aggregator must have an recall 1.0 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, self.mts_anomalies1, metric="recall" - ), - 1.0, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, self.mts_anomalies1, metric="recall" + ) + - 1.0 + ) + < 1e-05 ) # aggregator must have an f1 of 0.67549 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, self.mts_anomalies1, metric="f1" - ), - 0.67549, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, self.mts_anomalies1, metric="f1" + ) + - 0.67549 + ) + < 1e-05 ) # aggregator must have an precision of 0.51 for the input mts_anomalies1 - self.assertAlmostEqual( - aggregator.eval_accuracy( - self.real_anomalies, - self.mts_anomalies1, - metric="precision", - ), - 0.51, - delta=1e-05, + assert ( + abs( + aggregator.eval_accuracy( + self.real_anomalies, + self.mts_anomalies1, + metric="precision", + ) + - 0.51 + ) + < 1e-05 ) # multiple series case (random example) diff --git a/darts/tests/ad/test_anomaly_model.py b/darts/tests/ad/test_anomaly_model.py index cbb2bddc27..98f0dccb90 100644 --- a/darts/tests/ad/test_anomaly_model.py +++ b/darts/tests/ad/test_anomaly_model.py @@ -2,6 +2,7 @@ import numpy as np import pandas as pd +import pytest from pyod.models.knn import KNN from darts import TimeSeries @@ -30,10 +31,9 @@ ) from darts.ad.utils import eval_accuracy_from_scores, show_anomalies_from_scores from darts.models import MovingAverageFilter, NaiveSeasonal, RegressionModel -from darts.tests.base_test_class import DartsBaseTestClass -class ADAnomalyModelTestCase(DartsBaseTestClass): +class TestADAnomalyModel: np.random.seed(42) # univariate series @@ -101,7 +101,7 @@ def test_Scorer(self): ]: # scorer are trainable - self.assertTrue(anomaly_model.scorers_are_trainable is False) + assert not anomaly_model.scorers_are_trainable list_FittableAnomalyScorer = [ PyODScorer(model=KNN()), @@ -118,7 +118,7 @@ def test_Scorer(self): ]: # scorer are not trainable - self.assertTrue(anomaly_model.scorers_are_trainable is True) + assert anomaly_model.scorers_are_trainable def test_Score(self): @@ -134,22 +134,18 @@ def test_Score(self): for am in [am1, am2]: # Parameter return_model_prediction # parameter return_model_prediction must be bool - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.score(self.test, return_model_prediction=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.score(self.test, return_model_prediction="True") # if return_model_prediction set to true, output must be tuple - self.assertTrue( - isinstance(am.score(self.test, return_model_prediction=True), Tuple) - ) + assert isinstance(am.score(self.test, return_model_prediction=True), Tuple) # if return_model_prediction set to false output must be # Union[TimeSeries, Sequence[TimeSeries], Sequence[Sequence[TimeSeries]]] - self.assertTrue( - not isinstance( - am.score(self.test, return_model_prediction=False), Tuple - ) + assert not isinstance( + am.score(self.test, return_model_prediction=False), Tuple ) def test_FitFilteringAnomalyModelInput(self): @@ -168,23 +164,23 @@ def test_FitFilteringAnomalyModelInput(self): ]: # filter must be fittable if allow_filter_training is set to True - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit(self.train, allow_model_training=True) # input 'series' must be a series or Sequence of series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit([self.train, "str"], allow_model_training=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit([[self.train, self.train]], allow_model_training=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit("str", allow_model_training=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit([1, 2, 3], allow_model_training=True) # allow_model_training must be a bool - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit(self.train, allow_model_training=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit(self.train, allow_model_training="True") def test_FitForecastingAnomalyModelInput(self): @@ -202,28 +198,28 @@ def test_FitForecastingAnomalyModelInput(self): ]: # input 'series' must be a series or Sequence of series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit([self.train, "str"], allow_model_training=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit([[self.train, self.train]], allow_model_training=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit("str", allow_model_training=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit([1, 2, 3], allow_model_training=True) # allow_model_training must be a bool - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit(self.train, allow_model_training=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit(self.train, allow_model_training="True") # 'allow_model_training' must be set to True if forecasting model is not fitted if anomaly_model.scorers_are_trainable: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.fit(self.train, allow_model_training=False) anomaly_model.score(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # number of 'past_covariates' must be the same as the number of Timeseries in 'series' anomaly_model.fit( series=[self.train, self.train], @@ -231,7 +227,7 @@ def test_FitForecastingAnomalyModelInput(self): allow_model_training=True, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # number of 'past_covariates' must be the same as the number of Timeseries in 'series' anomaly_model.fit( series=self.train, @@ -239,7 +235,7 @@ def test_FitForecastingAnomalyModelInput(self): allow_model_training=True, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # number of 'future_covariates' must be the same as the number of Timeseries in 'series' anomaly_model.fit( series=[self.train, self.train], @@ -247,7 +243,7 @@ def test_FitForecastingAnomalyModelInput(self): allow_model_training=True, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # number of 'future_covariates' must be the same as the number of Timeseries in 'series' anomaly_model.fit( series=self.train, @@ -257,36 +253,44 @@ def test_FitForecastingAnomalyModelInput(self): fitted_model = RegressionModel(lags=10).fit(self.train) # Fittable scorer must be fitted before calling .score(), even if forecasting model is fitted - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel(model=fitted_model, scorer=KMeansScorer()).score( series=self.test ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel( model=fitted_model, scorer=[NormScorer(), KMeansScorer()] ).score(series=self.test) # forecasting model that do not accept past/future covariates - # with self.assertRaises(ValueError): - # ForecastingAnomalyModel(model=ExponentialSmoothing(), - # scorer=NormScorer()).fit( - # series=self.train, past_covariates=self.covariates, allow_model_training=True - # ) - # with self.assertRaises(ValueError): - # ForecastingAnomalyModel(model=ExponentialSmoothing(), - # scorer=NormScorer()).fit( - # series=self.train, future_covariates=self.covariates, allow_model_training=True - # ) + anomaly_model = ForecastingAnomalyModel( + model=NaiveSeasonal(), scorer=NormScorer() + ) + with pytest.raises(TypeError): + anomaly_model.fit( + series=self.train, + past_covariates=self.covariates, + allow_model_training=True, + ) + anomaly_model = ForecastingAnomalyModel( + model=NaiveSeasonal(), scorer=NormScorer() + ) + with pytest.raises(TypeError): + anomaly_model.fit( + series=self.train, + future_covariates=self.covariates, + allow_model_training=True, + ) # check window size # max window size is len(series.drop_before(series.get_timestamp_at_point(start))) + 1 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel( model=RegressionModel(lags=10), scorer=KMeansScorer(window=50) ).fit(series=self.train, start=0.9) # forecasting model that cannot be trained on a list of series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel(model=NaiveSeasonal(), scorer=NormScorer()).fit( series=[self.train, self.train], allow_model_training=True ) @@ -308,26 +312,26 @@ def test_ScoreForecastingAnomalyModelInput(self): anomaly_model.fit(self.train, allow_model_training=True) # number of 'past_covariates' must be the same as the number of Timeseries in 'series' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.score( series=[self.train, self.train], past_covariates=self.covariates ) # number of 'past_covariates' must be the same as the number of Timeseries in 'series' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.score( series=self.train, past_covariates=[self.covariates, self.covariates], ) # number of 'future_covariates' must be the same as the number of Timeseries in 'series' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.score( series=[self.train, self.train], future_covariates=self.covariates ) # number of 'future_covariates' must be the same as the number of Timeseries in 'series' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.score( series=self.train, future_covariates=[self.covariates, self.covariates], @@ -339,7 +343,7 @@ def test_ScoreForecastingAnomalyModelInput(self): model=RegressionModel(lags=10), scorer=KMeansScorer(window=30) ) anomaly_model.fit(self.train, allow_model_training=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.score(series=self.train, start=0.9) def test_ScoreFilteringAnomalyModelInput(self): @@ -387,30 +391,30 @@ def test_eval_accuracy(self): # if the anomaly_model have scorers that have the parameter univariate_scorer set to True, # 'actual_anomalies' must have widths of 1 if am.univariate_scoring: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.mts_anomalies, series=self.test ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.mts_anomalies, series=self.mts_test ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=[self.anomalies, self.mts_anomalies], series=[self.test, self.mts_test], ) # 'metric' must be str and "AUC_ROC" or "AUC_PR" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.anomalies, series=self.test, metric=1 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.anomalies, series=self.test, metric="auc_roc" ) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): am.eval_accuracy( actual_anomalies=self.anomalies, series=self.test, @@ -418,37 +422,37 @@ def test_eval_accuracy(self): ) # 'actual_anomalies' must be binary - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy(actual_anomalies=self.test, series=self.test) # 'actual_anomalies' must contain anomalies (at least one) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.only_0_anomalies, series=self.test ) # 'actual_anomalies' cannot contain only anomalies - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.only_1_anomalies, series=self.test ) # 'actual_anomalies' must match the number of series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.anomalies, series=[self.test, self.test] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=[self.anomalies, self.anomalies], series=self.test ) # 'actual_anomalies' must have non empty intersection with 'series' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=self.anomalies[:20], series=self.test[30:] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( actual_anomalies=[self.anomalies, self.anomalies[:20]], series=[self.test, self.test[40:]], @@ -456,69 +460,63 @@ def test_eval_accuracy(self): # Check input type # 'actual_anomalies' and 'series' must be of same length - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy([self.anomalies], [self.test, self.test]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy(self.anomalies, [self.test, self.test]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy([self.anomalies, self.anomalies], [self.test]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy([self.anomalies, self.anomalies], self.test) # 'actual_anomalies' and 'series' must be of type Timeseries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy([self.anomalies], [2, 3, 4]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy([self.anomalies], "str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy([2, 3, 4], self.test) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy("str", self.test) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy( [self.anomalies, self.anomalies], [self.test, [3, 2, 1]] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): am.eval_accuracy([self.anomalies, [3, 2, 1]], [self.test, self.test]) # Check return types # Check if return type is float when input is a series - self.assertTrue( - isinstance( - am.eval_accuracy(self.anomalies, self.test), - Dict, - ) + assert isinstance( + am.eval_accuracy(self.anomalies, self.test), + Dict, ) # Check if return type is Sequence when input is a Sequence of series - self.assertTrue( - isinstance( - am.eval_accuracy(self.anomalies, [self.test]), - Sequence, - ) + assert isinstance( + am.eval_accuracy(self.anomalies, [self.test]), + Sequence, ) - self.assertTrue( - isinstance( - am.eval_accuracy( - [self.anomalies, self.anomalies], [self.test, self.test] - ), - Sequence, - ) + assert isinstance( + am.eval_accuracy( + [self.anomalies, self.anomalies], [self.test, self.test] + ), + Sequence, ) def test_ForecastingAnomalyModelInput(self): # model input # model input must be of type ForecastingModel - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel(model="str", scorer=NormScorer()) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel(model=1, scorer=NormScorer()) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel( model=MovingAverageFilter(window=10), scorer=NormScorer() ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel( model=[RegressionModel(lags=10), RegressionModel(lags=5)], scorer=NormScorer(), @@ -526,15 +524,15 @@ def test_ForecastingAnomalyModelInput(self): # scorer input # scorer input must be of type AnomalyScorer - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel(model=RegressionModel(lags=10), scorer=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel(model=RegressionModel(lags=10), scorer="str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel( model=RegressionModel(lags=10), scorer=RegressionModel(lags=10) ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ForecastingAnomalyModel( model=RegressionModel(lags=10), scorer=[NormScorer(), "str"] ) @@ -543,13 +541,13 @@ def test_FilteringAnomalyModelInput(self): # model input # model input must be of type FilteringModel - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel(model="str", scorer=NormScorer()) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel(model=1, scorer=NormScorer()) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel(model=RegressionModel(lags=10), scorer=NormScorer()) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel( model=[MovingAverageFilter(window=10), MovingAverageFilter(window=10)], scorer=NormScorer(), @@ -557,16 +555,16 @@ def test_FilteringAnomalyModelInput(self): # scorer input # scorer input must be of type AnomalyScorer - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel(model=MovingAverageFilter(window=10), scorer=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel(model=MovingAverageFilter(window=10), scorer="str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel( model=MovingAverageFilter(window=10), scorer=MovingAverageFilter(window=10), ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FilteringAnomalyModel( model=MovingAverageFilter(window=10), scorer=[NormScorer(), "str"] ) @@ -613,15 +611,17 @@ def test_univariate_ForecastingAnomalyModel(self): ) # check that NormScorer is the abs difference of model_output and test_series_slope - self.assertEqual( - (model_output - test_series_slope.slice_intersect(model_output)).__abs__(), - NormScorer().score_from_prediction(test_series_slope, model_output), + assert ( + model_output - test_series_slope.slice_intersect(model_output) + ).__abs__() == NormScorer().score_from_prediction( + test_series_slope, model_output ) # check that Difference is the difference of model_output and test_series_slope - self.assertEqual( - test_series_slope.slice_intersect(model_output) - model_output, - Difference().score_from_prediction(test_series_slope, model_output), + assert test_series_slope.slice_intersect( + model_output + ) - model_output == Difference().score_from_prediction( + test_series_slope, model_output ) dict_auc_roc = anomaly_model.eval_accuracy( @@ -646,8 +646,8 @@ def test_univariate_ForecastingAnomalyModel(self): ) # function eval_accuracy_from_scores and eval_accuracy must return an input of same length - self.assertEqual(len(auc_roc_from_scores), len(dict_auc_roc)) - self.assertEqual(len(auc_pr_from_scores), len(dict_auc_pr)) + assert len(auc_roc_from_scores) == len(dict_auc_roc) + assert len(auc_pr_from_scores) == len(dict_auc_pr) # function eval_accuracy_from_scores and eval_accuracy must return the same values np.testing.assert_array_almost_equal( @@ -736,15 +736,17 @@ def test_univariate_FilteringAnomalyModel(self): ) # check that Difference is the difference of model_output and test_series_noise - self.assertEqual( - test_series_noise.slice_intersect(model_output) - model_output, - Difference().score_from_prediction(test_series_noise, model_output), + assert test_series_noise.slice_intersect( + model_output + ) - model_output == Difference().score_from_prediction( + test_series_noise, model_output ) # check that NormScorer is the abs difference of model_output and test_series_noise - self.assertEqual( - (test_series_noise.slice_intersect(model_output) - model_output).__abs__(), - NormScorer().score_from_prediction(test_series_noise, model_output), + assert ( + test_series_noise.slice_intersect(model_output) - model_output + ).__abs__() == NormScorer().score_from_prediction( + test_series_noise, model_output ) dict_auc_roc = anomaly_model.eval_accuracy( @@ -769,8 +771,8 @@ def test_univariate_FilteringAnomalyModel(self): ) # function eval_accuracy_from_scores and eval_accuracy must return an input of same length - self.assertEqual(len(auc_roc_from_scores), len(dict_auc_roc)) - self.assertEqual(len(auc_pr_from_scores), len(dict_auc_pr)) + assert len(auc_roc_from_scores) == len(dict_auc_roc) + assert len(auc_pr_from_scores) == len(dict_auc_pr) # function eval_accuracy_from_scores and eval_accuracy must return the same values np.testing.assert_array_almost_equal( @@ -871,15 +873,15 @@ def test_univariate_covariate_ForecastingAnomalyModel(self): ) # check that NormScorer is the abs difference of model_output and series_test - self.assertEqual( - (series_test.slice_intersect(model_output) - model_output).__abs__(), - NormScorer().score_from_prediction(series_test, model_output), - ) + assert ( + series_test.slice_intersect(model_output) - model_output + ).__abs__() == NormScorer().score_from_prediction(series_test, model_output) # check that Difference is the difference of model_output and series_test - self.assertEqual( - series_test.slice_intersect(model_output) - model_output, - Difference().score_from_prediction(series_test, model_output), + assert series_test.slice_intersect( + model_output + ) - model_output == Difference().score_from_prediction( + series_test, model_output ) dict_auc_roc = anomaly_model.eval_accuracy( @@ -904,8 +906,8 @@ def test_univariate_covariate_ForecastingAnomalyModel(self): ) # function eval_accuracy_from_scores and eval_accuracy must return an input of same length - self.assertEqual(len(auc_roc_from_scores), len(dict_auc_roc)) - self.assertEqual(len(auc_pr_from_scores), len(dict_auc_pr)) + assert len(auc_roc_from_scores) == len(dict_auc_roc) + assert len(auc_pr_from_scores) == len(dict_auc_pr) # function eval_accuracy_from_scores and eval_accuracy must return the same values np.testing.assert_array_almost_equal( @@ -1010,10 +1012,10 @@ def test_multivariate__FilteringAnomalyModel(self): ) # model_output must be multivariate (same width as input) - self.assertEqual(model_output.width, mts_series_test.width) + assert model_output.width == mts_series_test.width # scores must be of the same length as the number of scorers - self.assertEqual(len(scores), len(anomaly_model.scorers)) + assert len(scores) == len(anomaly_model.scorers) dict_auc_roc = anomaly_model.eval_accuracy( mts_anomalies, mts_series_test, metric="AUC_ROC" @@ -1037,8 +1039,8 @@ def test_multivariate__FilteringAnomalyModel(self): ) # function eval_accuracy_from_scores and eval_accuracy must return an input of same length - self.assertEqual(len(auc_roc_from_scores), len(dict_auc_roc)) - self.assertEqual(len(auc_pr_from_scores), len(dict_auc_pr)) + assert len(auc_roc_from_scores) == len(dict_auc_roc) + assert len(auc_pr_from_scores) == len(dict_auc_pr) # function eval_accuracy_from_scores and eval_accuracy must return the same values np.testing.assert_array_almost_equal( @@ -1095,10 +1097,10 @@ def test_multivariate__FilteringAnomalyModel(self): ) # model_output must be multivariate (same width as input) - self.assertEqual(model_output.width, mts_series_test.width) + assert model_output.width == mts_series_test.width # scores must be of the same length as the number of scorers - self.assertEqual(len(scores), len(anomaly_model.scorers)) + assert len(scores) == len(anomaly_model.scorers) dict_auc_roc = anomaly_model.eval_accuracy( ts_anomalies, mts_series_test, metric="AUC_ROC" @@ -1122,8 +1124,8 @@ def test_multivariate__FilteringAnomalyModel(self): ) # function eval_accuracy_from_scores and eval_accuracy must return an input of same length - self.assertEqual(len(auc_roc_from_scores), len(dict_auc_roc)) - self.assertEqual(len(auc_pr_from_scores), len(dict_auc_pr)) + assert len(auc_roc_from_scores) == len(dict_auc_roc) + assert len(auc_pr_from_scores) == len(dict_auc_pr) # function eval_accuracy_from_scores and eval_accuracy must return the same values np.testing.assert_array_almost_equal( @@ -1238,10 +1240,10 @@ def test_multivariate__ForecastingAnomalyModel(self): ) # model_output must be multivariate (same width as input) - self.assertEqual(model_output.width, mts_series_test.width) + assert model_output.width == mts_series_test.width # scores must be of the same length as the number of scorers - self.assertEqual(len(scores), len(anomaly_model.scorers)) + assert len(scores) == len(anomaly_model.scorers) dict_auc_roc = anomaly_model.eval_accuracy( mts_anomalies, mts_series_test, start=0.1, metric="AUC_ROC" @@ -1265,8 +1267,8 @@ def test_multivariate__ForecastingAnomalyModel(self): ) # function eval_accuracy_from_scores and eval_accuracy must return an input of same length - self.assertEqual(len(auc_roc_from_scores), len(dict_auc_roc)) - self.assertEqual(len(auc_pr_from_scores), len(dict_auc_pr)) + assert len(auc_roc_from_scores) == len(dict_auc_roc) + assert len(auc_pr_from_scores) == len(dict_auc_pr) # function eval_accuracy_from_scores and eval_accuracy must return the same values np.testing.assert_array_almost_equal( @@ -1323,10 +1325,10 @@ def test_multivariate__ForecastingAnomalyModel(self): ) # model_output must be multivariate (same width as input) - self.assertEqual(model_output.width, mts_series_test.width) + assert model_output.width == mts_series_test.width # scores must be of the same length as the number of scorers - self.assertEqual(len(scores), len(anomaly_model.scorers)) + assert len(scores) == len(anomaly_model.scorers) dict_auc_roc = anomaly_model.eval_accuracy( ts_anomalies, mts_series_test, start=0.1, metric="AUC_ROC" @@ -1350,8 +1352,8 @@ def test_multivariate__ForecastingAnomalyModel(self): ) # function eval_accuracy_from_scores and eval_accuracy must return an input of same length - self.assertEqual(len(auc_roc_from_scores), len(dict_auc_roc)) - self.assertEqual(len(auc_pr_from_scores), len(dict_auc_pr)) + assert len(auc_roc_from_scores) == len(dict_auc_roc) + assert len(auc_pr_from_scores) == len(dict_auc_pr) # function eval_accuracy_from_scores and eval_accuracy must return the same values np.testing.assert_array_almost_equal( @@ -1403,39 +1405,39 @@ def test_show_anomalies(self): for anomaly_model in [forecasting_anomaly_model, filtering_anomaly_model]: # must input only one series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies(series=[self.train, self.train]) # input must be a series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies(series=[1, 2, 4]) # metric must be "AUC_ROC" or "AUC_PR" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies( series=self.train, actual_anomalies=self.anomalies, metric="str" ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies( series=self.train, actual_anomalies=self.anomalies, metric="auc_roc" ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies( series=self.train, actual_anomalies=self.anomalies, metric=1 ) # actual_anomalies must be not none if metric is given - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies(series=self.train, metric="AUC_ROC") # actual_anomalies must be binary - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies( series=self.train, actual_anomalies=self.test, metric="AUC_ROC" ) # actual_anomalies must contain at least 1 anomaly if metric is given - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies( series=self.train, actual_anomalies=self.only_0_anomalies, @@ -1444,7 +1446,7 @@ def test_show_anomalies(self): # actual_anomalies must contain at least 1 non-anomoulous timestamp # if metric is given - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies( series=self.train, actual_anomalies=self.only_1_anomalies, @@ -1452,50 +1454,50 @@ def test_show_anomalies(self): ) # names_of_scorers must be str - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies(series=self.train, names_of_scorers=2) # nbr of names_of_scorers must match the nbr of scores (only 1 here) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies( series=self.train, names_of_scorers=["scorer1", "scorer2"] ) # title must be str - with self.assertRaises(ValueError): + with pytest.raises(ValueError): anomaly_model.show_anomalies(series=self.train, title=1) def test_show_anomalies_from_scores(self): # must input only one series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores(series=[self.train, self.train]) # input must be a series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores(series=[1, 2, 4]) # must input only one model_output - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, model_output=[self.test, self.train] ) # metric must be "AUC_ROC" or "AUC_PR" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, actual_anomalies=self.anomalies, metric="str", ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, actual_anomalies=self.anomalies, metric="auc_roc", ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, @@ -1504,13 +1506,13 @@ def test_show_anomalies_from_scores(self): ) # actual_anomalies must be not none if metric is given - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, metric="AUC_ROC" ) # actual_anomalies must be binary - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, @@ -1519,7 +1521,7 @@ def test_show_anomalies_from_scores(self): ) # actual_anomalies must contain at least 1 anomaly if metric is given - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, @@ -1529,7 +1531,7 @@ def test_show_anomalies_from_scores(self): # actual_anomalies must contain at least 1 non-anomoulous timestamp # if metric is given - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, @@ -1538,27 +1540,27 @@ def test_show_anomalies_from_scores(self): ) # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, window="1" ) # window must be an int positive - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, window=-1 ) # window must smaller than the score series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, window=200 ) # must have the same nbr of windows than scores - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, window=[1, 2] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=[self.test, self.test], @@ -1566,18 +1568,18 @@ def test_show_anomalies_from_scores(self): ) # names_of_scorers must be str - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, names_of_scorers=2 ) # nbr of names_of_scorers must match the nbr of scores - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=self.test, names_of_scorers=["scorer1", "scorer2"], ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores( series=self.train, anomaly_scores=[self.test, self.test], @@ -1585,5 +1587,5 @@ def test_show_anomalies_from_scores(self): ) # title must be str - with self.assertRaises(ValueError): + with pytest.raises(ValueError): show_anomalies_from_scores(series=self.train, title=1) diff --git a/darts/tests/ad/test_detectors.py b/darts/tests/ad/test_detectors.py index 3e346bd95d..3dc7e5a04f 100644 --- a/darts/tests/ad/test_detectors.py +++ b/darts/tests/ad/test_detectors.py @@ -1,18 +1,18 @@ from typing import Sequence import numpy as np +import pytest from darts import TimeSeries from darts.ad.detectors.quantile_detector import QuantileDetector from darts.ad.detectors.threshold_detector import ThresholdDetector -from darts.tests.base_test_class import DartsBaseTestClass list_NonFittableDetector = [ThresholdDetector(low_threshold=0.2)] list_FittableDetector = [QuantileDetector(low_quantile=0.2)] -class ADDetectorsTestCase(DartsBaseTestClass): +class TestADDetectors: np.random.seed(42) @@ -47,18 +47,18 @@ def test_DetectNonFittableDetector(self): # Check return types # Check if return TimeSeries is float when input is a series - self.assertTrue(isinstance(detector.detect(self.test), TimeSeries)) + assert isinstance(detector.detect(self.test), TimeSeries) # Check if return type is Sequence when input is a Sequence of series - self.assertTrue(isinstance(detector.detect([self.test]), Sequence)) + assert isinstance(detector.detect([self.test]), Sequence) # Check if return TimeSeries is Sequence when input is a multivariate series - self.assertTrue(isinstance(detector.detect(self.mts_test), TimeSeries)) + assert isinstance(detector.detect(self.mts_test), TimeSeries) # Check if return type is Sequence when input is a multivariate series - self.assertTrue(isinstance(detector.detect([self.mts_test]), Sequence)) + assert isinstance(detector.detect([self.mts_test]), Sequence) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # Input cannot be probabilistic detector.detect(self.probabilistic) @@ -69,19 +69,19 @@ def test_DetectFittableDetector(self): detector.fit(self.train) # Check if return type is float when input is a series - self.assertTrue(isinstance(detector.detect(self.test), TimeSeries)) + assert isinstance(detector.detect(self.test), TimeSeries) # Check if return type is Sequence when input is a sequence of series - self.assertTrue(isinstance(detector.detect([self.test]), Sequence)) + assert isinstance(detector.detect([self.test]), Sequence) detector.fit(self.mts_train) # Check if return type is Sequence when input is a multivariate series - self.assertTrue(isinstance(detector.detect(self.mts_test), TimeSeries)) + assert isinstance(detector.detect(self.mts_test), TimeSeries) # Check if return type is Sequence when input is a sequence of multivariate series - self.assertTrue(isinstance(detector.detect([self.mts_test]), Sequence)) + assert isinstance(detector.detect([self.mts_test]), Sequence) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # Input cannot be probabilistic detector.detect(self.probabilistic) @@ -91,30 +91,22 @@ def test_eval_accuracy(self): # Check return types # Check if return type is float when input is a series - self.assertTrue( - isinstance(detector.eval_accuracy(self.anomalies, self.test), float) - ) + assert isinstance(detector.eval_accuracy(self.anomalies, self.test), float) # Check if return type is Sequence when input is a Sequence of series - self.assertTrue( - isinstance(detector.eval_accuracy(self.anomalies, [self.test]), Sequence) - ) + assert isinstance(detector.eval_accuracy(self.anomalies, [self.test]), Sequence) # Check if return type is Sequence when input is a multivariate series - self.assertTrue( - isinstance( - detector.eval_accuracy(self.mts_anomalies, self.mts_test), Sequence - ) + assert isinstance( + detector.eval_accuracy(self.mts_anomalies, self.mts_test), Sequence ) # Check if return type is Sequence when input is a multivariate series - self.assertTrue( - isinstance( - detector.eval_accuracy(self.mts_anomalies, [self.mts_test]), Sequence - ) + assert isinstance( + detector.eval_accuracy(self.mts_anomalies, [self.mts_test]), Sequence ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # Input cannot be probabilistic detector.eval_accuracy(self.anomalies, self.probabilistic) @@ -123,17 +115,17 @@ def test_FittableDetector(self): for detector in list_FittableDetector: # Need to call fit() before calling detect() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.test) # Check if _fit_called is False - self.assertTrue(not detector._fit_called) + assert not detector._fit_called - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # fit on sequence with series that have different width detector.fit([self.train, self.mts_train]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # Input cannot be probabilistic detector.fit(self.probabilistic) @@ -142,9 +134,9 @@ def test_FittableDetector(self): detector1.fit(self.train) # Check if _fit_called is True after being fitted - self.assertTrue(detector1._fit_called) + assert detector1._fit_called - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # series must be same width as series used for training detector1.detect(self.mts_test) @@ -153,106 +145,106 @@ def test_FittableDetector(self): detector2.fit(self.mts_test) # Check if _fit_called is True after being fitted - self.assertTrue(detector2._fit_called) + assert detector2._fit_called - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # series must be same width as series used for training detector2.detect(self.train) def test_QuantileDetector(self): # Need to have at least one parameter (low, high) not None - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=None, high_quantile=None) # Parameter low must be float or Sequence of float - with self.assertRaises(TypeError): + with pytest.raises(TypeError): QuantileDetector(low_quantile="0.5") - with self.assertRaises(TypeError): + with pytest.raises(TypeError): QuantileDetector(low_quantile=[0.2, "0.1"]) # Parameter high must be float or Sequence of float - with self.assertRaises(TypeError): + with pytest.raises(TypeError): QuantileDetector(high_quantile="0.5") - with self.assertRaises(TypeError): + with pytest.raises(TypeError): QuantileDetector(high_quantile=[0.2, "0.1"]) # if high and low are both sequences of length>1, they must be of the same size - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=[0.2, 0.1], high_quantile=[0.95, 0.8, 0.9]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=[0.2, 0.1, 0.7], high_quantile=[0.95, 0.8]) # Parameter must be between 0 and 1 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(high_quantile=1.1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(high_quantile=-0.2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(high_quantile=[-0.1, 0.9]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=1.1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=-0.2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=[-0.2, 0.3]) # Parameter high must be higher or equal than parameter low - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=0.7, high_quantile=0.2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=[0.2, 0.9], high_quantile=[0.95, 0.1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=0.2, high_quantile=[0.95, 0.1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=[0.2, 0.9], high_quantile=0.8) # Parameter high/low cannot be sequence of only None - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=[None, None, None]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(high_quantile=[None, None, None]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileDetector(low_quantile=[None], high_quantile=[None, None, None]) # check that low_threshold and high_threshold are the same and no errors are raised detector = QuantileDetector(low_quantile=0.5, high_quantile=0.5) detector.fit(self.train) - self.assertEqual(detector.low_threshold, detector.high_threshold) + assert detector.low_threshold == detector.high_threshold # widths of series used for fitting must match the number of values given for high or/and low, # if high and low have a length higher than 1 detector = QuantileDetector(low_quantile=0.1, high_quantile=[0.8, 0.7]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit([self.train, self.mts_train]) detector = QuantileDetector(low_quantile=[0.1, 0.2], high_quantile=[0.8, 0.9]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit([self.train, self.mts_train]) detector = QuantileDetector(low_quantile=[0.1, 0.2], high_quantile=0.8) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit([self.train, self.mts_train]) detector = QuantileDetector(low_quantile=[0.1, 0.2]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit([self.train, self.mts_train]) detector = QuantileDetector(high_quantile=[0.1, 0.2]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.fit([self.train, self.mts_train]) # widths of series used for scoring must match the number of values given for high or/and low, @@ -260,37 +252,37 @@ def test_QuantileDetector(self): detector = QuantileDetector(low_quantile=0.1, high_quantile=[0.8, 0.7]) detector.fit(self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = QuantileDetector(low_quantile=[0.1, 0.2], high_quantile=[0.8, 0.9]) detector.fit(self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = QuantileDetector(low_quantile=[0.1, 0.2], high_quantile=0.8) detector.fit(self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = QuantileDetector(low_quantile=[0.1, 0.2]) detector.fit(self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = QuantileDetector(high_quantile=[0.1, 0.2]) detector.fit(self.mts_train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = QuantileDetector(low_quantile=0.05, high_quantile=0.95) @@ -299,54 +291,56 @@ def test_QuantileDetector(self): binary_detection = detector.detect(self.test) # Return of .detect() must be binary - self.assertTrue( - np.array_equal( - binary_detection.values(copy=False), - binary_detection.values(copy=False).astype(bool), - ) + np.testing.assert_array_equal( + binary_detection.values(copy=False), + binary_detection.values(copy=False).astype(bool), ) # Return of .detect() must be same len as input - self.assertTrue(len(binary_detection) == len(self.test)) + assert len(binary_detection) == len(self.test) # univariate test # detector parameter 'abs_low_' must be equal to 9.13658 when trained on the series 'train' - self.assertAlmostEqual(detector.low_threshold[0], 9.13658, delta=1e-05) + assert abs(detector.low_threshold[0] - 9.13658) < 1e-05 # detector parameter 'abs_high_' must be equal to 10.74007 when trained on the series 'train' - self.assertAlmostEqual(detector.high_threshold[0], 10.74007, delta=1e-05) + assert abs(detector.high_threshold[0] - 10.74007) < 1e-05 # detector must found 10 anomalies in the series 'train' - self.assertEqual( - detector.detect(self.train).sum(axis=0).all_values().flatten()[0], 10 - ) + assert detector.detect(self.train).sum(axis=0).all_values().flatten()[0] == 10 # detector must found 42 anomalies in the series 'test' - self.assertEqual(binary_detection.sum(axis=0).all_values().flatten()[0], 42) + assert binary_detection.sum(axis=0).all_values().flatten()[0] == 42 # detector must have an accuracy of 0.57 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="accuracy"), - 0.57, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="accuracy") + - 0.57 + ) + < 1e-05 ) # detector must have an recall of 0.4 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="recall"), - 0.4, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="recall") - 0.4 + ) + < 1e-05 ) # detector must have an f1 of 0.08510 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="f1"), - 0.08510, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="f1") - 0.08510 + ) + < 1e-05 ) # detector must have an precision of 0.04761 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="precision"), - 0.04761, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="precision") + - 0.04761 + ) + < 1e-05 ) # multivariate test @@ -362,41 +356,41 @@ def test_QuantileDetector(self): ) detector_2param.fit(self.mts_train) binary_detection_2param = detector_2param.detect(self.mts_test) - self.assertEqual(binary_detection, binary_detection_2param) + assert binary_detection == binary_detection_2param # width of output must be equal to 2 (same dimension as input) - self.assertEqual(binary_detection.width, 2) - self.assertEqual( + assert binary_detection.width == 2 + assert ( len( detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) - ), - 2, + ) + == 2 ) - self.assertEqual( + assert ( len( detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) - ), - 2, + ) + == 2 ) - self.assertEqual( + assert ( len( detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="f1" ) - ), - 2, + ) + == 2 ) - self.assertEqual( + assert ( len( detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) - ), - 2, + ) + == 2 ) abs_low_ = detector_1param.low_threshold @@ -404,58 +398,54 @@ def test_QuantileDetector(self): # detector_1param parameter 'abs_high_' must be equal to 10.83047 when trained # on the series 'train' for the 1st component - self.assertAlmostEqual(abs_high_[0], 10.83047, delta=1e-05) + assert abs(abs_high_[0] - 10.83047) < 1e-05 # detector_1param parameter 'abs_high_' must be equal to 6.47822 when trained # on the series 'train' for the 2nd component - self.assertAlmostEqual(abs_high_[1], 6.47822, delta=1e-05) + assert abs(abs_high_[1] - 6.47822) < 1e-05 # detector_1param parameter 'abs_low_' must be equal to 9.20248 when trained # on the series 'train' for the 1st component - self.assertAlmostEqual(abs_low_[0], 9.20248, delta=1e-05) + assert abs(abs_low_[0] - 9.20248) < 1e-05 # detector_1param parameter 'abs_low_' must be equal to 3.61853 when trained # on the series 'train' for the 2nd component - self.assertAlmostEqual(abs_low_[1], 3.61853, delta=1e-05) + assert abs(abs_low_[1] - 3.61853) < 1e-05 # detector_1param must found 37 anomalies on the first component of the series 'test' - self.assertEqual( - binary_detection["0"].sum(axis=0).all_values().flatten()[0], 37 - ) + assert binary_detection["0"].sum(axis=0).all_values().flatten()[0] == 37 # detector_1param must found 38 anomalies on the second component of the series 'test' - self.assertEqual( - binary_detection["1"].sum(axis=0).all_values().flatten()[0], 38 - ) + assert binary_detection["1"].sum(axis=0).all_values().flatten()[0] == 38 acc = detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) # detector_1param must have an accuracy of 0.58 on the first component of the series 'mts_test' - self.assertAlmostEqual(acc[0], 0.58, delta=1e-05) + assert abs(acc[0] - 0.58) < 1e-05 # detector_1param must have an accuracy of 0.58 on the second component of the series 'mts_test' - self.assertAlmostEqual(acc[1], 0.58, delta=1e-05) + assert abs(acc[1] - 0.58) < 1e-05 precision = detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) # detector_1param must have an precision of 0.08108 on the first component of the series 'mts_test' - self.assertAlmostEqual(precision[0], 0.08108, delta=1e-05) + assert abs(precision[0] - 0.08108) < 1e-05 # detector_1param must have an precision of 0.07894 on the second component of the series 'mts_test' - self.assertAlmostEqual(precision[1], 0.07894, delta=1e-05) + assert abs(precision[1] - 0.07894) < 1e-05 recall = detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) # detector_1param must have an recall of 0.2727 on the first component of the series 'mts_test' - self.assertAlmostEqual(recall[0], 0.27272, delta=1e-05) + assert abs(recall[0] - 0.27272) < 1e-05 # detector_1param must have an recall of 0.3 on the second component of the series 'mts_test' - self.assertAlmostEqual(recall[1], 0.3, delta=1e-05) + assert abs(recall[1] - 0.3) < 1e-05 f1 = detector_1param.eval_accuracy( self.mts_anomalies, self.mts_test, metric="f1" ) # detector_1param must have an f1 of 0.125 on the first component of the series 'mts_test' - self.assertAlmostEqual(f1[0], 0.125, delta=1e-05) + assert abs(f1[0] - 0.125) < 1e-05 # detector_1param must have an f1 of 0.125 on the second component of the series 'mts_test' - self.assertAlmostEqual(f1[1], 0.125, delta=1e-05) + assert abs(f1[1] - 0.125) < 1e-05 # exemple multivariate with Nones detector = QuantileDetector( @@ -465,34 +455,34 @@ def test_QuantileDetector(self): binary_detection = detector.detect(self.mts_test) # width of output must be equal to 2 (same dimension as input) - self.assertEqual(binary_detection.width, 2) - self.assertEqual( + assert binary_detection.width == 2 + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) - ), - 2, + ) + == 2 ) - self.assertEqual( + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) - ), - 2, + ) + == 2 ) - self.assertEqual( - len(detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1")), - 2, + assert ( + len(detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1")) + == 2 ) - self.assertEqual( + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) - ), - 2, + ) + == 2 ) # TODO: we should improve these tests to introduce some correlation @@ -501,146 +491,146 @@ def test_QuantileDetector(self): # detector must found 20 anomalies on the first component of the series 'test' # Note: there are 200 values (100 time step x 2 components) so this matches # well a detection rate of 10% (bottom 5% on first component and top 5% on second component) - self.assertEqual( - binary_detection["0"].sum(axis=0).all_values().flatten()[0], 20 - ) + assert binary_detection["0"].sum(axis=0).all_values().flatten()[0] == 20 # detector must found 19 anomalies on the second component of the series 'test' - self.assertEqual( - binary_detection["1"].sum(axis=0).all_values().flatten()[0], 19 - ) + assert binary_detection["1"].sum(axis=0).all_values().flatten()[0] == 19 acc = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) - self.assertAlmostEqual(acc[0], 0.69, delta=1e-05) - self.assertAlmostEqual(acc[1], 0.75, delta=1e-05) + assert abs(acc[0] - 0.69) < 1e-05 + assert abs(acc[1] - 0.75) < 1e-05 precision = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) - self.assertAlmostEqual(precision[0], 0.0, delta=1e-05) - self.assertAlmostEqual(precision[1], 0.10526, delta=1e-05) + assert abs(precision[0] - 0.0) < 1e-05 + assert abs(precision[1] - 0.10526) < 1e-05 recall = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) - self.assertAlmostEqual(recall[0], 0.0, delta=1e-05) - self.assertAlmostEqual(recall[1], 0.2, delta=1e-05) + assert abs(recall[0] - 0.0) < 1e-05 + assert abs(recall[1] - 0.2) < 1e-05 f1 = detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1") - self.assertAlmostEqual(f1[0], 0.0, delta=1e-05) - self.assertAlmostEqual(f1[1], 0.13793, delta=1e-05) + assert abs(f1[0] - 0.0) < 1e-05 + assert abs(f1[1] - 0.13793) < 1e-05 def test_ThresholdDetector(self): # Parameters # Need to have at least one parameter (low, high) not None - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=None, high_threshold=None) # if high and low are both sequences of length>1, they must be of the same size - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=[0.2, 0.1], high_threshold=[0.95, 0.8, 0.9]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=[0.2, 0.1, 0.7], high_threshold=[0.95, 0.8]) # Parameter high must be higher or equal than parameter low - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=0.7, high_threshold=0.2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=[0.2, 0.9], high_threshold=[0.95, 0.1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=0.2, high_threshold=[0.95, 0.1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=[0.2, 0.9], high_threshold=0.8) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=[0.2, 0.9, None], high_threshold=0.8) # Parameter high/low cannot be sequence of only None - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=[None, None, None]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(high_threshold=[None, None, None]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ThresholdDetector(low_threshold=[None], high_threshold=[None, None, None]) # widths of series used for scoring must match the number of values given for high or/and low, # if high and low have a length higher than 1 detector = ThresholdDetector(low_threshold=0.1, high_threshold=[0.8, 0.7]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = ThresholdDetector( low_threshold=[0.1, 0.2], high_threshold=[0.8, 0.9] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = ThresholdDetector(low_threshold=[0.1, 0.2], high_threshold=0.8) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = ThresholdDetector(low_threshold=[0.1, 0.2]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = ThresholdDetector(high_threshold=[0.1, 0.2]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): detector.detect([self.train, self.mts_train]) detector = ThresholdDetector(low_threshold=9.5, high_threshold=10.5) binary_detection = detector.detect(self.test) # Return of .detect() must be binary - self.assertTrue( - np.array_equal( - binary_detection.values(copy=False), - binary_detection.values(copy=False).astype(bool), - ) + np.testing.assert_array_equal( + binary_detection.values(copy=False), + binary_detection.values(copy=False).astype(bool), ) # Return of .detect() must be same len as input - self.assertTrue(len(binary_detection) == len(self.test)) + assert len(binary_detection) == len(self.test) # univariate test # detector must found 58 anomalies in the series 'test' - self.assertEqual(binary_detection.sum(axis=0).all_values().flatten()[0], 58) + assert binary_detection.sum(axis=0).all_values().flatten()[0] == 58 # detector must have an accuracy of 0.41 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="accuracy"), - 0.41, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="accuracy") + - 0.41 + ) + < 1e-05 ) # detector must have an recall of 0.4 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="recall"), - 0.4, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="recall") - 0.4 + ) + < 1e-05 ) # detector must have an f1 of 0.06349 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="f1"), - 0.06349, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="f1") - 0.06349 + ) + < 1e-05 ) # detector must have an precision of 0.03448 for the series 'test' - self.assertAlmostEqual( - detector.eval_accuracy(self.anomalies, self.test, metric="precision"), - 0.03448, - delta=1e-05, + assert ( + abs( + detector.eval_accuracy(self.anomalies, self.test, metric="precision") + - 0.03448 + ) + < 1e-05 ) # multivariate test @@ -654,151 +644,143 @@ def test_ThresholdDetector(self): low_threshold=[4.8, 4.8], high_threshold=[10.5, 10.5] ) binary_detection_2param = detector_2param.detect(self.mts_test) - self.assertEqual(binary_detection, binary_detection_2param) + assert binary_detection == binary_detection_2param # width of output must be equal to 2 (same dimension as input) - self.assertEqual(binary_detection.width, 2) - self.assertEqual( + assert binary_detection.width == 2 + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) - ), - 2, + ) + == 2 ) - self.assertEqual( + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) - ), - 2, + ) + == 2 ) - self.assertEqual( - len(detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1")), - 2, + assert ( + len(detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1")) + == 2 ) - self.assertEqual( + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) - ), - 2, + ) + == 2 ) # detector must found 28 anomalies on the first width of the series 'test' - self.assertEqual( - binary_detection["0"].sum(axis=0).all_values().flatten()[0], 28 - ) + assert binary_detection["0"].sum(axis=0).all_values().flatten()[0] == 28 # detector must found 52 anomalies on the second width of the series 'test' - self.assertEqual( - binary_detection["1"].sum(axis=0).all_values().flatten()[0], 52 - ) + assert binary_detection["1"].sum(axis=0).all_values().flatten()[0] == 52 acc = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) # detector must have an accuracy of 0.71 on the first width of the series 'mts_test' - self.assertAlmostEqual(acc[0], 0.71, delta=1e-05) + assert abs(acc[0] - 0.71) < 1e-05 # detector must have an accuracy of 0.48 on the second width of the series 'mts_test' - self.assertAlmostEqual(acc[1], 0.48, delta=1e-05) + assert abs(acc[1] - 0.48) < 1e-05 precision = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) # detector must have an precision of 0.17857 on the first width of the series 'mts_test' - self.assertAlmostEqual(precision[0], 0.17857, delta=1e-05) + assert abs(precision[0] - 0.17857) < 1e-05 # detector must have an precision of 0.09615 on the second width of the series 'mts_test' - self.assertAlmostEqual(precision[1], 0.09615, delta=1e-05) + assert abs(precision[1] - 0.09615) < 1e-05 recall = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) # detector must have an recall of 0.45454 on the first width of the series 'mts_test' - self.assertAlmostEqual(recall[0], 0.45454, delta=1e-05) + assert abs(recall[0] - 0.45454) < 1e-05 # detector must have an recall of 0.5 on the second width of the series 'mts_test' - self.assertAlmostEqual(recall[1], 0.5, delta=1e-05) + assert abs(recall[1] - 0.5) < 1e-05 f1 = detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1") # detector must have an f1 of 0.25641 on the first width of the series 'mts_test' - self.assertAlmostEqual(f1[0], 0.25641, delta=1e-05) + assert abs(f1[0] - 0.25641) < 1e-05 # detector must have an f1 of 0.16129 on the second width of the series 'mts_test' - self.assertAlmostEqual(f1[1], 0.16129, delta=1e-05) + assert abs(f1[1] - 0.16129) < 1e-05 # exemple multivariate with Nones detector = ThresholdDetector(low_threshold=[10, None], high_threshold=[None, 5]) binary_detection = detector.detect(self.mts_test) # width of output must be equal to 2 (same dimension as input) - self.assertEqual(binary_detection.width, 2) - self.assertEqual( + assert binary_detection.width == 2 + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) - ), - 2, + ) + == 2 ) - self.assertEqual( + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) - ), - 2, + ) + == 2 ) - self.assertEqual( - len(detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1")), - 2, + assert ( + len(detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1")) + == 2 ) - self.assertEqual( + assert ( len( detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) - ), - 2, + ) + == 2 ) # detector must found 48 anomalies on the first width of the series 'test' - self.assertEqual( - binary_detection["0"].sum(axis=0).all_values().flatten()[0], 48 - ) + assert binary_detection["0"].sum(axis=0).all_values().flatten()[0] == 48 # detector must found 43 anomalies on the second width of the series 'test' - self.assertEqual( - binary_detection["1"].sum(axis=0).all_values().flatten()[0], 43 - ) + assert binary_detection["1"].sum(axis=0).all_values().flatten()[0] == 43 acc = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="accuracy" ) # detector must have an accuracy of 0.51 on the first width of the series 'mts_test' - self.assertAlmostEqual(acc[0], 0.51, delta=1e-05) + assert abs(acc[0] - 0.51) < 1e-05 # detector must have an accuracy of 0.57 on the second width of the series 'mts_test' - self.assertAlmostEqual(acc[1], 0.57, delta=1e-05) + assert abs(acc[1] - 0.57) < 1e-05 precision = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="precision" ) # detector must have an precision of 0.10416 and on the first width of the series 'mts_test' - self.assertAlmostEqual(precision[0], 0.10416, delta=1e-05) + assert abs(precision[0] - 0.10416) < 1e-05 # detector must have an precision of 0.11627 on the second width of the series 'mts_test' - self.assertAlmostEqual(precision[1], 0.11627, delta=1e-05) + assert abs(precision[1] - 0.11627) < 1e-05 recall = detector.eval_accuracy( self.mts_anomalies, self.mts_test, metric="recall" ) # detector must have an recall of 0.45454 on the first width of the series 'mts_test' - self.assertAlmostEqual(recall[0], 0.45454, delta=1e-05) + assert abs(recall[0] - 0.45454) < 1e-05 # detector must have an recall of 0.5 on the second width of the series 'mts_test' - self.assertAlmostEqual(recall[1], 0.5, delta=1e-05) + assert abs(recall[1] - 0.5) < 1e-05 f1 = detector.eval_accuracy(self.mts_anomalies, self.mts_test, metric="f1") # detector must have an f1 of 0.16949 on the first width of the series 'mts_test' - self.assertAlmostEqual(f1[0], 0.16949, delta=1e-05) + assert abs(f1[0] - 0.16949) < 1e-05 # detector must have an f1 of 0.18867 on the second width of the series 'mts_test' - self.assertAlmostEqual(f1[1], 0.18867, delta=1e-05) + assert abs(f1[1] - 0.18867) < 1e-05 def test_fit_detect(self): @@ -809,4 +791,4 @@ def test_fit_detect(self): detector2 = QuantileDetector(low_quantile=0.05, high_quantile=0.95) prediction2 = detector2.fit_detect(self.train) - self.assertEqual(prediction1, prediction2) + assert prediction1 == prediction2 diff --git a/darts/tests/ad/test_scorers.py b/darts/tests/ad/test_scorers.py index c64dc9aabf..50afbe83b4 100644 --- a/darts/tests/ad/test_scorers.py +++ b/darts/tests/ad/test_scorers.py @@ -1,6 +1,7 @@ from typing import Sequence import numpy as np +import pytest import sklearn from pyod.models.knn import KNN from scipy.stats import cauchy, expon, gamma, laplace, norm, poisson @@ -18,7 +19,6 @@ from darts.ad.scorers import NormScorer as Norm from darts.ad.scorers import PoissonNLLScorer, PyODScorer, WassersteinScorer from darts.models import MovingAverageFilter -from darts.tests.base_test_class import DartsBaseTestClass list_NonFittableAnomalyScorer = [ Norm(), @@ -47,7 +47,7 @@ ] -class ADAnomalyScorerTestCase(DartsBaseTestClass): +class TestADAnomalyScorer: np.random.seed(42) @@ -106,34 +106,26 @@ def test_ScoreNonFittableAnomalyScorer(self): # Check return types for score_from_prediction() # Check if return type is float when input is a series - self.assertTrue( - isinstance( - scorer.score_from_prediction(self.test, self.modified_test), TimeSeries - ) + assert isinstance( + scorer.score_from_prediction(self.test, self.modified_test), TimeSeries ) # Check if return type is Sequence when input is a Sequence of series - self.assertTrue( - isinstance( - scorer.score_from_prediction([self.test], [self.modified_test]), - Sequence, - ) + assert isinstance( + scorer.score_from_prediction([self.test], [self.modified_test]), + Sequence, ) # Check if return type is Sequence when input is a multivariate series - self.assertTrue( - isinstance( - scorer.score_from_prediction(self.mts_test, self.modified_mts_test), - TimeSeries, - ) + assert isinstance( + scorer.score_from_prediction(self.mts_test, self.modified_mts_test), + TimeSeries, ) # Check if return type is Sequence when input is a multivariate series - self.assertTrue( - isinstance( - scorer.score_from_prediction([self.mts_test], [self.modified_mts_test]), - Sequence, - ) + assert isinstance( + scorer.score_from_prediction([self.mts_test], [self.modified_mts_test]), + Sequence, ) def test_ScoreFittableAnomalyScorer(self): @@ -142,50 +134,42 @@ def test_ScoreFittableAnomalyScorer(self): # Check return types for score() scorer.fit(self.train) # Check if return type is float when input is a series - self.assertTrue(isinstance(scorer.score(self.test), TimeSeries)) + assert isinstance(scorer.score(self.test), TimeSeries) # Check if return type is Sequence when input is a sequence of series - self.assertTrue(isinstance(scorer.score([self.test]), Sequence)) + assert isinstance(scorer.score([self.test]), Sequence) scorer.fit(self.mts_train) # Check if return type is Sequence when input is a multivariate series - self.assertTrue(isinstance(scorer.score(self.mts_test), TimeSeries)) + assert isinstance(scorer.score(self.mts_test), TimeSeries) # Check if return type is Sequence when input is a sequence of multivariate series - self.assertTrue(isinstance(scorer.score([self.mts_test]), Sequence)) + assert isinstance(scorer.score([self.mts_test]), Sequence) # Check return types for score_from_prediction() scorer.fit_from_prediction(self.train, self.modified_train) # Check if return type is float when input is a series - self.assertTrue( - isinstance( - scorer.score_from_prediction(self.test, self.modified_test), TimeSeries - ) + assert isinstance( + scorer.score_from_prediction(self.test, self.modified_test), TimeSeries ) # Check if return type is Sequence when input is a Sequence of series - self.assertTrue( - isinstance( - scorer.score_from_prediction([self.test], [self.modified_test]), - Sequence, - ) + assert isinstance( + scorer.score_from_prediction([self.test], [self.modified_test]), + Sequence, ) scorer.fit_from_prediction(self.mts_train, self.modified_mts_train) # Check if return type is Sequence when input is a multivariate series - self.assertTrue( - isinstance( - scorer.score_from_prediction(self.mts_test, self.modified_mts_test), - TimeSeries, - ) + assert isinstance( + scorer.score_from_prediction(self.mts_test, self.modified_mts_test), + TimeSeries, ) # Check if return type is Sequence when input is a multivariate series - self.assertTrue( - isinstance( - scorer.score_from_prediction([self.mts_test], [self.modified_mts_test]), - Sequence, - ) + assert isinstance( + scorer.score_from_prediction([self.mts_test], [self.modified_mts_test]), + Sequence, ) def test_eval_accuracy_from_prediction(self): @@ -193,85 +177,69 @@ def test_eval_accuracy_from_prediction(self): scorer = Norm(component_wise=False) # Check return types # Check if return type is float when input is a series - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.anomalies, self.test, self.modified_test - ), - float, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.anomalies, self.test, self.modified_test + ), + float, ) # Check if return type is Sequence when input is a Sequence of series - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.anomalies, [self.test], self.modified_test - ), - Sequence, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.anomalies, [self.test], self.modified_test + ), + Sequence, ) # Check if return type is a float when input is a multivariate series and component_wise is set to False - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.anomalies, self.mts_test, self.modified_mts_test - ), - float, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.anomalies, self.mts_test, self.modified_mts_test + ), + float, ) # Check if return type is Sequence when input is a multivariate series and component_wise is set to False - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.anomalies, [self.mts_test], self.modified_mts_test - ), - Sequence, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.anomalies, [self.mts_test], self.modified_mts_test + ), + Sequence, ) scorer = Norm(component_wise=True) # Check return types # Check if return type is float when input is a series - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.anomalies, self.test, self.modified_test - ), - float, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.anomalies, self.test, self.modified_test + ), + float, ) # Check if return type is Sequence when input is a Sequence of series - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.anomalies, [self.test], self.modified_test - ), - Sequence, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.anomalies, [self.test], self.modified_test + ), + Sequence, ) # Check if return type is a float when input is a multivariate series and component_wise is set to True - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.mts_anomalies, self.mts_test, self.modified_mts_test - ), - Sequence, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.mts_anomalies, self.mts_test, self.modified_mts_test + ), + Sequence, ) # Check if return type is Sequence when input is a multivariate series and component_wise is set to True - self.assertTrue( - isinstance( - scorer.eval_accuracy_from_prediction( - self.mts_anomalies, [self.mts_test], self.modified_mts_test - ), - Sequence, - ) + assert isinstance( + scorer.eval_accuracy_from_prediction( + self.mts_anomalies, [self.mts_test], self.modified_mts_test + ), + Sequence, ) non_fittable_scorer = Norm(component_wise=False) @@ -279,63 +247,63 @@ def test_eval_accuracy_from_prediction(self): fittable_scorer.fit(self.train) # if component_wise set to False, 'actual_anomalies' must have widths of 1 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=self.mts_anomalies, series=self.test ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=[self.anomalies, self.mts_anomalies], series=[self.test, self.test], ) # 'metric' must be str and "AUC_ROC" or "AUC_PR" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=self.anomalies, series=self.test, metric=1 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=self.anomalies, series=self.test, metric="auc_roc" ) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): fittable_scorer.eval_accuracy( actual_anomalies=self.anomalies, series=self.test, metric=["AUC_ROC"] ) # 'actual_anomalies' must be binary - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy(actual_anomalies=self.test, series=self.test) # 'actual_anomalies' must contain anomalies (at least one) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=self.only_0_anomalies, series=self.test ) # 'actual_anomalies' cannot contain only anomalies - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=self.only_1_anomalies, series=self.test ) # 'actual_anomalies' must match the number of series if length higher than 1 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=[self.anomalies, self.anomalies], series=self.test ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=[self.anomalies, self.anomalies], series=[self.test, self.test, self.test], ) # 'actual_anomalies' must have non empty intersection with 'series' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=self.anomalies[:20], series=self.test[30:] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy( actual_anomalies=[self.anomalies, self.anomalies[:20]], series=[self.test, self.test[40:]], @@ -344,27 +312,24 @@ def test_eval_accuracy_from_prediction(self): for scorer in [non_fittable_scorer, fittable_scorer]: # name must be of type str - self.assertEqual( - type(scorer.__str__()), - str, - ) + assert type(scorer.__str__()) == str # 'metric' must be str and "AUC_ROC" or "AUC_PR" - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy_from_prediction( actual_anomalies=self.anomalies, actual_series=self.test, pred_series=self.modified_test, metric=1, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fittable_scorer.eval_accuracy_from_prediction( actual_anomalies=self.anomalies, actual_series=self.test, pred_series=self.modified_test, metric="auc_roc", ) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): fittable_scorer.eval_accuracy_from_prediction( actual_anomalies=self.anomalies, actual_series=self.test, @@ -373,7 +338,7 @@ def test_eval_accuracy_from_prediction(self): ) # 'actual_anomalies' must be binary - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.eval_accuracy_from_prediction( actual_anomalies=self.test, actual_series=self.test, @@ -381,7 +346,7 @@ def test_eval_accuracy_from_prediction(self): ) # 'actual_anomalies' must contain anomalies (at least one) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.eval_accuracy_from_prediction( actual_anomalies=self.only_0_anomalies, actual_series=self.test, @@ -389,7 +354,7 @@ def test_eval_accuracy_from_prediction(self): ) # 'actual_anomalies' cannot contain only anomalies - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.eval_accuracy_from_prediction( actual_anomalies=self.only_1_anomalies, actual_series=self.test, @@ -397,7 +362,7 @@ def test_eval_accuracy_from_prediction(self): ) # 'actual_anomalies' must match the number of series if length higher than 1 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.eval_accuracy_from_prediction( actual_anomalies=[self.anomalies, self.anomalies], actual_series=[self.test, self.test, self.test], @@ -407,7 +372,7 @@ def test_eval_accuracy_from_prediction(self): self.modified_test, ], ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.eval_accuracy_from_prediction( actual_anomalies=[self.anomalies, self.anomalies], actual_series=self.test, @@ -415,13 +380,13 @@ def test_eval_accuracy_from_prediction(self): ) # 'actual_anomalies' must have non empty intersection with 'actual_series' and 'pred_series' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.eval_accuracy_from_prediction( actual_anomalies=self.anomalies[:20], actual_series=self.test[30:], pred_series=self.modified_test[30:], ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.eval_accuracy_from_prediction( actual_anomalies=[self.anomalies, self.anomalies[:20]], actual_series=[self.test, self.test[40:]], @@ -432,29 +397,29 @@ def test_NonFittableAnomalyScorer(self): for scorer in list_NonFittableAnomalyScorer: # Check if trainable is False, being a NonFittableAnomalyScorer - self.assertTrue(not scorer.trainable) + assert not scorer.trainable # checks for score_from_prediction() # input must be Timeseries or sequence of Timeseries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, "str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.train], [self.modified_train, "str"] ) # score on sequence with series that have different width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, self.modified_mts_train) # input sequences have different length - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.train], [self.modified_train] ) # two inputs must have a non zero intersection - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train[:50], self.train[55:]) # every pairwise element must have a non zero intersection - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.train[:50]], [self.train, self.train[55:]] ) @@ -464,25 +429,25 @@ def test_FittableAnomalyScorer(self): for scorer in list_FittableAnomalyScorer: # Need to call fit() before calling score() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score(self.test) # Need to call fit() before calling score_from_prediction() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.test, self.modified_test) # Check if trainable is True, being a FittableAnomalyScorer - self.assertTrue(scorer.trainable) + assert scorer.trainable # Check if _fit_called is False - self.assertTrue(not scorer._fit_called) + assert not scorer._fit_called # fit on sequence with series that have different width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit([self.train, self.mts_train]) # fit on sequence with series that have different width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit_from_prediction( [self.train, self.mts_train], [self.modified_train, self.modified_mts_train], @@ -490,79 +455,79 @@ def test_FittableAnomalyScorer(self): # checks for fit_from_prediction() # input must be Timeseries or sequence of Timeseries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, "str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.train], [self.modified_train, "str"] ) # two inputs must have the same length - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit_from_prediction( [self.train, self.train], [self.modified_train] ) # two inputs must have the same width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit_from_prediction([self.train], [self.modified_mts_train]) # every element must have the same width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit_from_prediction( [self.train, self.mts_train], [self.modified_train, self.modified_mts_train], ) # two inputs must have a non zero intersection - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit_from_prediction(self.train[:50], self.train[55:]) # every pairwise element must have a non zero intersection - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit_from_prediction( [self.train, self.train[:50]], [self.train, self.train[55:]] ) # checks for fit() # input must be Timeseries or sequence of Timeseries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit("str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit([self.modified_train, "str"]) # checks for score_from_prediction() scorer.fit_from_prediction(self.train, self.modified_train) # input must be Timeseries or sequence of Timeseries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, "str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.train], [self.modified_train, "str"] ) # two inputs must have the same length - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.train], [self.modified_train] ) # two inputs must have the same width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction([self.train], [self.modified_mts_train]) # every element must have the same width - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.mts_train], [self.modified_train, self.modified_mts_train], ) # two inputs must have a non zero intersection - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train[:50], self.train[55:]) # every pairwise element must have a non zero intersection - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( [self.train, self.train[:50]], [self.train, self.train[55:]] ) # checks for score() # input must be Timeseries or sequence of Timeseries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score("str") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score([self.modified_train, "str"]) # caseA: fit with fit() @@ -570,16 +535,16 @@ def test_FittableAnomalyScorer(self): scorerA1 = scorer scorerA1.fit(self.train) # Check if _fit_called is True after being fitted - self.assertTrue(scorerA1._fit_called) - with self.assertRaises(ValueError): + assert scorerA1._fit_called + with pytest.raises(ValueError): # series must be same width as series used for training scorerA1.score(self.mts_test) # case2: fit on MTS scorerA2 = scorer scorerA2.fit(self.mts_train) # Check if _fit_called is True after being fitted - self.assertTrue(scorerA2._fit_called) - with self.assertRaises(ValueError): + assert scorerA2._fit_called + with pytest.raises(ValueError): # series must be same width as series used for training scorerA2.score(self.test) @@ -588,86 +553,74 @@ def test_FittableAnomalyScorer(self): scorerB1 = scorer scorerB1.fit_from_prediction(self.train, self.modified_train) # Check if _fit_called is True after being fitted - self.assertTrue(scorerB1._fit_called) - with self.assertRaises(ValueError): + assert scorerB1._fit_called + with pytest.raises(ValueError): # series must be same width as series used for training scorerB1.score_from_prediction(self.mts_test, self.modified_mts_test) # case2: fit on MTS scorerB2 = scorer scorerB2.fit_from_prediction(self.mts_train, self.modified_mts_train) # Check if _fit_called is True after being fitted - self.assertTrue(scorerB2._fit_called) - with self.assertRaises(ValueError): + assert scorerB2._fit_called + with pytest.raises(ValueError): # series must be same width as series used for training scorerB2.score_from_prediction(self.test, self.modified_test) def test_Norm(self): # component_wise must be bool - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Norm(component_wise=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Norm(component_wise="string") # if component_wise=False must always return a univariate anomaly score scorer = Norm(component_wise=False) - self.assertTrue( - scorer.score_from_prediction(self.test, self.modified_test).width == 1 - ) - self.assertTrue( + assert scorer.score_from_prediction(self.test, self.modified_test).width == 1 + assert ( scorer.score_from_prediction(self.mts_test, self.modified_mts_test).width == 1 ) # if component_wise=True must always return the same width as the input scorer = Norm(component_wise=True) - self.assertTrue( - scorer.score_from_prediction(self.test, self.modified_test).width == 1 - ) - self.assertTrue( + assert scorer.score_from_prediction(self.test, self.modified_test).width == 1 + assert ( scorer.score_from_prediction(self.mts_test, self.modified_mts_test).width == self.mts_test.width ) scorer = Norm(component_wise=True) # always expects a deterministic input - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, self.probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.probabilistic, self.train) # univariate case (equivalent to abs diff) - self.assertEqual( - scorer.score_from_prediction(self.test, self.test + 1) - .sum(axis=0) - .all_values() - .flatten()[0], - len(self.test), - ) - self.assertEqual( - scorer.score_from_prediction(self.test + 1, self.test) - .sum(axis=0) - .all_values() - .flatten()[0], - len(self.test), - ) + assert scorer.score_from_prediction(self.test, self.test + 1).sum( + axis=0 + ).all_values().flatten()[0] == len(self.test) + assert scorer.score_from_prediction(self.test + 1, self.test).sum( + axis=0 + ).all_values().flatten()[0] == len(self.test) # multivariate case with component_wise set to True (equivalent to abs diff) # abs(a - 2a) = a - self.assertEqual( - scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["0"], - self.mts_test["0"], + assert ( + scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["0"] + == self.mts_test["0"] ) - self.assertEqual( - scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["1"], - self.mts_test["1"], + assert ( + scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["1"] + == self.mts_test["1"] ) # abs(2a - a) = a - self.assertEqual( - scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["0"], - self.mts_test["0"], + assert ( + scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["0"] + == self.mts_test["0"] ) - self.assertEqual( - scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["1"], - self.mts_test["1"], + assert ( + scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["1"] + == self.mts_test["1"] ) scorer = Norm(component_wise=False) @@ -675,131 +628,117 @@ def test_Norm(self): # always expects a deterministic input # univariate case (equivalent to abs diff) - self.assertEqual( - scorer.score_from_prediction(self.test, self.test + 1) - .sum(axis=0) - .all_values() - .flatten()[0], - len(self.test), - ) - self.assertEqual( - scorer.score_from_prediction(self.test + 1, self.test) - .sum(axis=0) - .all_values() - .flatten()[0], - len(self.test), - ) + assert scorer.score_from_prediction(self.test, self.test + 1).sum( + axis=0 + ).all_values().flatten()[0] == len(self.test) + assert scorer.score_from_prediction(self.test + 1, self.test).sum( + axis=0 + ).all_values().flatten()[0] == len(self.test) # multivariate case with component_wise set to False # norm(a - a + sqrt(2)) = 2 * len(a) with a being series of dim=2 - self.assertAlmostEqual( - scorer.score_from_prediction(self.mts_test, self.mts_test + np.sqrt(2)) - .sum(axis=0) - .all_values() - .flatten()[0], - 2 * len(self.mts_test), - delta=1e-05, + assert ( + abs( + scorer.score_from_prediction(self.mts_test, self.mts_test + np.sqrt(2)) + .sum(axis=0) + .all_values() + .flatten()[0] + - 2 * len(self.mts_test) + ) + < 1e-05 ) - self.assertFalse(scorer.is_probabilistic) + assert not scorer.is_probabilistic def test_Difference(self): scorer = Difference() # always expects a deterministic input - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, self.probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.probabilistic, self.train) # univariate case - self.assertEqual( - scorer.score_from_prediction(self.test, self.test + 1) - .sum(axis=0) - .all_values() - .flatten()[0], - -len(self.test), - ) - self.assertEqual( - scorer.score_from_prediction(self.test + 1, self.test) - .sum(axis=0) - .all_values() - .flatten()[0], - len(self.test), - ) + assert scorer.score_from_prediction(self.test, self.test + 1).sum( + axis=0 + ).all_values().flatten()[0] == -len(self.test) + assert scorer.score_from_prediction(self.test + 1, self.test).sum( + axis=0 + ).all_values().flatten()[0] == len(self.test) # multivariate case # output of score() must be the same width as the width of the input - self.assertEqual( - scorer.score_from_prediction(self.mts_test, self.mts_test).width, - self.mts_test.width, + assert ( + scorer.score_from_prediction(self.mts_test, self.mts_test).width + == self.mts_test.width ) # a - 2a = - a - self.assertEqual( - scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["0"], - -self.mts_test["0"], + assert ( + scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["0"] + == -self.mts_test["0"] ) - self.assertEqual( - scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["1"], - -self.mts_test["1"], + assert ( + scorer.score_from_prediction(self.mts_test, self.mts_test * 2)["1"] + == -self.mts_test["1"] ) # 2a - a = a - self.assertEqual( - scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["0"], - self.mts_test["0"], + assert ( + scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["0"] + == self.mts_test["0"] ) - self.assertEqual( - scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["1"], - self.mts_test["1"], + assert ( + scorer.score_from_prediction(self.mts_test * 2, self.mts_test)["1"] + == self.mts_test["1"] ) - self.assertFalse(scorer.is_probabilistic) + assert not scorer.is_probabilistic def test_WassersteinScorer(self): # component_wise parameter # component_wise must be bool - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(component_wise=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(component_wise="string") # if component_wise=False must always return a univariate anomaly score scorer = WassersteinScorer(component_wise=False) scorer.fit(self.train) - self.assertTrue(scorer.score(self.test).width == 1) + assert scorer.score(self.test).width == 1 scorer.fit(self.mts_train) - self.assertTrue(scorer.score(self.mts_test).width == 1) + assert scorer.score(self.mts_test).width == 1 # if component_wise=True must always return the same width as the input scorer = WassersteinScorer(component_wise=True) scorer.fit(self.train) - self.assertTrue(scorer.score(self.test).width == 1) + assert scorer.score(self.test).width == 1 scorer.fit(self.mts_train) - self.assertTrue(scorer.score(self.mts_test).width == self.mts_test.width) + assert scorer.score(self.mts_test).width == self.mts_test.width # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(window=0) # diff_fn paramter # must be None, 'diff' or 'abs_diff' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(diff_fn="random") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): WassersteinScorer(diff_fn=1) # test _diff_series() directly - with self.assertRaises(ValueError): + with pytest.raises(ValueError): s_tmp = WassersteinScorer() s_tmp.diff_fn = "random" s_tmp._diff_series(self.train, self.test) @@ -809,28 +748,28 @@ def test_WassersteinScorer(self): scorer = WassersteinScorer() # always expects a deterministic input - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, self.probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.probabilistic, self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score(self.probabilistic) # window must be smaller than the input of score() scorer = WassersteinScorer(window=101) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit(self.train) # len(self.train)=100 scorer = WassersteinScorer(window=80) scorer.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score(self.test[:50]) # len(self.test)=100 # test plotting (just call the functions) scorer = WassersteinScorer(window=2) scorer.fit(self.train) scorer.show_anomalies(self.test, self.anomalies) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # should fail for a sequence of series scorer.show_anomalies([self.test, self.test], self.anomalies) scorer.show_anomalies_from_prediction( @@ -838,14 +777,14 @@ def test_WassersteinScorer(self): pred_series=self.test + 1, actual_anomalies=self.anomalies, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # should fail for a sequence of series scorer.show_anomalies_from_prediction( actual_series=[self.test, self.test], pred_series=self.test + 1, actual_anomalies=self.anomalies, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # should fail for a sequence of series scorer.show_anomalies_from_prediction( actual_series=self.test, @@ -853,7 +792,7 @@ def test_WassersteinScorer(self): actual_anomalies=self.anomalies, ) - self.assertFalse(scorer.is_probabilistic) + assert not scorer.is_probabilistic def test_univariate_Wasserstein(self): @@ -906,10 +845,10 @@ def test_univariate_Wasserstein(self): anomalies_wasserstein, test_wasserstein, metric="AUC_PR" ) - self.assertAlmostEqual(auc_roc_w10, 0.80637, delta=1e-05) - self.assertAlmostEqual(auc_pr_w10, 0.83390, delta=1e-05) - self.assertAlmostEqual(auc_roc_w20, 0.77828, delta=1e-05) - self.assertAlmostEqual(auc_pr_w20, 0.93934, delta=1e-05) + assert abs(auc_roc_w10 - 0.80637) < 1e-05 + assert abs(auc_pr_w10 - 0.83390) < 1e-05 + assert abs(auc_roc_w20 - 0.77828) < 1e-05 + assert abs(auc_pr_w20 - 0.93934) < 1e-05 def test_multivariate_componentwise_Wasserstein(self): @@ -978,72 +917,72 @@ def test_multivariate_componentwise_Wasserstein(self): anomalies_wasserstein_per_width, mts_test_wasserstein, metric="AUC_ROC" ) - self.assertAlmostEqual(auc_roc_cwfalse, 0.94637, delta=1e-05) - self.assertAlmostEqual(auc_roc_cwtrue[0], 0.98606, delta=1e-05) - self.assertAlmostEqual(auc_roc_cwtrue[1], 0.96722, delta=1e-05) + assert abs(auc_roc_cwfalse - 0.94637) < 1e-05 + assert abs(auc_roc_cwtrue[0] - 0.98606) < 1e-05 + assert abs(auc_roc_cwtrue[1] - 0.96722) < 1e-05 def test_kmeansScorer(self): # component_wise parameter # component_wise must be bool - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(component_wise=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(component_wise="string") # if component_wise=False must always return a univariate anomaly score scorer = KMeansScorer(component_wise=False) scorer.fit(self.train) - self.assertTrue(scorer.score(self.test).width == 1) + assert scorer.score(self.test).width == 1 scorer.fit(self.mts_train) - self.assertTrue(scorer.score(self.mts_test).width == 1) + assert scorer.score(self.mts_test).width == 1 # if component_wise=True must always return the same width as the input scorer = KMeansScorer(component_wise=True) scorer.fit(self.train) - self.assertTrue(scorer.score(self.test).width == 1) + assert scorer.score(self.test).width == 1 scorer.fit(self.mts_train) - self.assertTrue(scorer.score(self.mts_test).width == self.mts_test.width) + assert scorer.score(self.mts_test).width == self.mts_test.width # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(window=0) # diff_fn paramter # must be None, 'diff' or 'abs_diff' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(diff_fn="random") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): KMeansScorer(diff_fn=1) scorer = KMeansScorer() # always expects a deterministic input - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, self.probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.probabilistic, self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score(self.probabilistic) # window must be smaller than the input of score() scorer = KMeansScorer(window=101) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit(self.train) # len(self.train)=100 scorer = KMeansScorer(window=80) scorer.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score(self.test[:50]) # len(self.test)=100 - self.assertFalse(scorer.is_probabilistic) + assert not scorer.is_probabilistic def test_univariate_kmeans(self): @@ -1124,8 +1063,8 @@ def test_univariate_kmeans(self): KMeans_mts_anomalies, KMeans_mts_test, metric="AUC_PR" ) - self.assertEqual(metric_AUC_ROC, 1.0) - self.assertEqual(metric_AUC_PR, 1.0) + assert metric_AUC_ROC == 1.0 + assert metric_AUC_PR == 1.0 def test_multivariate_window_kmeans(self): @@ -1198,10 +1137,10 @@ def test_multivariate_window_kmeans(self): ts_anomalies, ts_test, metric="AUC_PR" ) - self.assertAlmostEqual(auc_roc_w1, 0.41551, delta=1e-05) - self.assertAlmostEqual(auc_pr_w1, 0.064761, delta=1e-05) - self.assertAlmostEqual(auc_roc_w2, 0.957513, delta=1e-05) - self.assertAlmostEqual(auc_pr_w2, 0.88584, delta=1e-05) + assert abs(auc_roc_w1 - 0.41551) < 1e-05 + assert abs(auc_pr_w1 - 0.064761) < 1e-05 + assert abs(auc_roc_w2 - 0.957513) < 1e-05 + assert abs(auc_pr_w2 - 0.88584) < 1e-05 def test_multivariate_componentwise_kmeans(self): @@ -1271,81 +1210,81 @@ def test_multivariate_componentwise_kmeans(self): anomalies_kmeans_per_width, mts_test_kmeans, metric="AUC_ROC" ) - self.assertAlmostEqual(auc_roc_cwtrue[0], 1.0, delta=1e-05) - self.assertAlmostEqual(auc_roc_cwtrue[1], 0.97666, delta=1e-05) + assert abs(auc_roc_cwtrue[0] - 1.0) < 1e-05 + assert abs(auc_roc_cwtrue[1] - 0.97666) < 1e-05 # sklearn changed the centroid initialization in version 1.3.0 # so the results are slightly different for older versions if sklearn.__version__ < "1.3.0": - self.assertAlmostEqual(auc_roc_cwfalse, 0.9851, delta=1e-05) + assert abs(auc_roc_cwfalse - 0.9851) < 1e-05 else: - self.assertAlmostEqual(auc_roc_cwfalse, 0.99007, delta=1e-05) + assert abs(auc_roc_cwfalse - 0.99007) < 1e-05 def test_PyODScorer(self): # model parameter must be pyod.models typy BaseDetector - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=MovingAverageFilter(window=10)) # component_wise parameter # component_wise must be bool - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), component_wise=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), component_wise="string") # if component_wise=False must always return a univariate anomaly score scorer = PyODScorer(model=KNN(), component_wise=False) scorer.fit(self.train) - self.assertTrue(scorer.score(self.test).width == 1) + assert scorer.score(self.test).width == 1 scorer.fit(self.mts_train) - self.assertTrue(scorer.score(self.mts_test).width == 1) + assert scorer.score(self.mts_test).width == 1 # if component_wise=True must always return the same width as the input scorer = PyODScorer(model=KNN(), component_wise=True) scorer.fit(self.train) - self.assertTrue(scorer.score(self.test).width == 1) + assert scorer.score(self.test).width == 1 scorer.fit(self.mts_train) - self.assertTrue(scorer.score(self.mts_test).width == self.mts_test.width) + assert scorer.score(self.mts_test).width == self.mts_test.width # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), window=0) # diff_fn paramter # must be None, 'diff' or 'abs_diff' - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), diff_fn="random") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PyODScorer(model=KNN(), diff_fn=1) scorer = PyODScorer(model=KNN()) # always expects a deterministic input - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.train, self.probabilistic) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction(self.probabilistic, self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score(self.probabilistic) # window must be smaller than the input of score() scorer = PyODScorer(model=KNN(), window=101) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.fit(self.train) # len(self.train)=100 scorer = PyODScorer(model=KNN(), window=80) scorer.fit(self.train) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score(self.test[:50]) # len(self.test)=100 - self.assertFalse(scorer.is_probabilistic) + assert not scorer.is_probabilistic def test_univariate_PyODScorer(self): @@ -1427,8 +1366,8 @@ def test_univariate_PyODScorer(self): pyod_mts_anomalies, pyod_mts_test, metric="AUC_PR" ) - self.assertEqual(metric_AUC_ROC, 1.0) - self.assertEqual(metric_AUC_PR, 1.0) + assert metric_AUC_ROC == 1.0 + assert metric_AUC_PR == 1.0 def test_multivariate_window_PyODScorer(self): @@ -1501,10 +1440,10 @@ def test_multivariate_window_PyODScorer(self): ) auc_pr_w2 = pyod_scorer_w2.eval_accuracy(ts_anomalies, ts_test, metric="AUC_PR") - self.assertAlmostEqual(auc_roc_w1, 0.5, delta=1e-05) - self.assertAlmostEqual(auc_pr_w1, 0.07, delta=1e-05) - self.assertAlmostEqual(auc_roc_w2, 0.957513, delta=1e-05) - self.assertAlmostEqual(auc_pr_w2, 0.88584, delta=1e-05) + assert abs(auc_roc_w1 - 0.5) < 1e-05 + assert abs(auc_pr_w1 - 0.07) < 1e-05 + assert abs(auc_roc_w2 - 0.957513) < 1e-05 + assert abs(auc_pr_w2 - 0.88584) < 1e-05 def test_multivariate_componentwise_PyODScorer(self): @@ -1579,17 +1518,17 @@ def test_multivariate_componentwise_PyODScorer(self): anomalies_pyod_per_width, mts_test_PyOD, metric="AUC_ROC" ) - self.assertAlmostEqual(auc_roc_cwfalse, 0.990566, delta=1e-05) - self.assertAlmostEqual(auc_roc_cwtrue[0], 1.0, delta=1e-05) - self.assertAlmostEqual(auc_roc_cwtrue[1], 0.98311, delta=1e-05) + assert abs(auc_roc_cwfalse - 0.990566) < 1e-05 + assert abs(auc_roc_cwtrue[0] - 1.0) < 1e-05 + assert abs(auc_roc_cwtrue[1] - 0.98311) < 1e-05 def test_NLLScorer(self): for s in list_NLLScorer: # expects for 'actual_series' a deterministic input and for 'pred_series' a probabilistic input - with self.assertRaises(ValueError): + with pytest.raises(ValueError): s.score_from_prediction(actual_series=self.test, pred_series=self.test) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): s.score_from_prediction( actual_series=self.probabilistic, pred_series=self.train ) @@ -1598,20 +1537,20 @@ def test_GaussianNLLScorer(self): # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GaussianNLLScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GaussianNLLScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GaussianNLLScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GaussianNLLScorer(window=0) scorer = GaussianNLLScorer(window=101) # window must be smaller than the input of score_from_prediction() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( actual_series=self.test, pred_series=self.probabilistic ) # len(self.test)=100 @@ -1632,9 +1571,7 @@ def test_GaussianNLLScorer(self): ) # check if value_test1 is the - log likelihood - self.assertAlmostEqual( - value_test1, -np.log(norm.pdf(3, loc=0, scale=2)), delta=1e-01 - ) + assert abs(value_test1 + np.log(norm.pdf(3, loc=0, scale=2))) < 1e-01 # test 2 univariate (len=1 and window=1) gaussian_samples_2 = np.random.normal(loc=0, scale=2, size=10000) @@ -1649,9 +1586,7 @@ def test_GaussianNLLScorer(self): ) # check if value_test2 is the - log likelihood - self.assertAlmostEqual( - value_test2, -np.log(norm.pdf(-2, loc=0, scale=2)), delta=1e-01 - ) + assert abs(value_test2 + np.log(norm.pdf(-2, loc=0, scale=2))) < 1e-01 # test window univariate (len=2 and window=2) distribution_series = TimeSeries.from_values( @@ -1663,21 +1598,21 @@ def test_GaussianNLLScorer(self): value_window = scorer.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(value_window), 2) + assert len(value_window) == 2 # check width - self.assertEqual(value_window.width, 1) + assert value_window.width == 1 # check equal value_test1 and value_test2 - self.assertEqual(value_window.all_values().flatten()[0], value_test1) - self.assertEqual(value_window.all_values().flatten()[1], value_test2) + assert value_window.all_values().flatten()[0] == value_test1 + assert value_window.all_values().flatten()[1] == value_test2 scorer = GaussianNLLScorer(window=2) # check avg of two values - self.assertEqual( + assert ( scorer.score_from_prediction(actual_series, distribution_series) .all_values() - .flatten()[0], - (value_test1 + value_test2) / 2, + .flatten()[0] + == (value_test1 + value_test2) / 2 ) # test window multivariate (n_samples=2, len=1, window=1) @@ -1691,13 +1626,13 @@ def test_GaussianNLLScorer(self): ) # check length - self.assertEqual(len(value_multivariate), 1) + assert len(value_multivariate) == 1 # check width - self.assertEqual(value_multivariate.width, 2) + assert value_multivariate.width == 2 # check equal value_test1 and value_test2 - self.assertEqual(value_multivariate.all_values().flatten()[0], value_test1) - self.assertEqual(value_multivariate.all_values().flatten()[1], value_test2) + assert value_multivariate.all_values().flatten()[0] == value_test1 + assert value_multivariate.all_values().flatten()[1] == value_test2 # test window multivariate (n_samples=2, len=2, window=1 and 2) scorer_w1 = GaussianNLLScorer(window=1) @@ -1725,74 +1660,86 @@ def test_GaussianNLLScorer(self): score_w2 = scorer_w2.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(score_w1), 2) - self.assertEqual(len(score_w2), 1) + assert len(score_w1) == 2 + assert len(score_w2) == 1 # check width - self.assertEqual(score_w1.width, 2) - self.assertEqual(score_w2.width, 2) + assert score_w1.width == 2 + assert score_w2.width == 2 # check values for window=1 - self.assertAlmostEqual( - score_w1.all_values().flatten()[0], - -np.log(norm.pdf(1.5, loc=0, scale=2)), - delta=1e-01, + assert ( + abs( + score_w1.all_values().flatten()[0] + + np.log(norm.pdf(1.5, loc=0, scale=2)) + ) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[1], - -np.log(norm.pdf(2.1, loc=0, scale=2)), - delta=1e-01, + assert ( + abs( + score_w1.all_values().flatten()[1] + + np.log(norm.pdf(2.1, loc=0, scale=2)) + ) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[2], - -np.log(norm.pdf(0.1, loc=0, scale=2)), - delta=1e-01, + assert ( + abs( + score_w1.all_values().flatten()[2] + + np.log(norm.pdf(0.1, loc=0, scale=2)) + ) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[3], - -np.log(norm.pdf(0.001, loc=0, scale=2)), - delta=1e-01, + assert ( + abs( + score_w1.all_values().flatten()[3] + + np.log(norm.pdf(0.001, loc=0, scale=2)) + ) + < 1e-01 ) # check values for window=2 (must be equal to the mean of the past 2 values) - self.assertAlmostEqual( - score_w2.all_values().flatten()[0], - ( - -np.log(norm.pdf(1.5, loc=0, scale=2)) - - np.log(norm.pdf(0.1, loc=0, scale=2)) + assert ( + abs( + score_w2.all_values().flatten()[0] + - ( + -np.log(norm.pdf(1.5, loc=0, scale=2)) + - np.log(norm.pdf(0.1, loc=0, scale=2)) + ) + / 2 ) - / 2, - delta=1e-01, - ) - self.assertAlmostEqual( - score_w2.all_values().flatten()[1], - ( - -np.log(norm.pdf(2.1, loc=0, scale=2)) - - np.log(norm.pdf(0.001, loc=0, scale=2)) + < 1e-01 + ) + assert ( + abs( + score_w2.all_values().flatten()[1] + - ( + -np.log(norm.pdf(2.1, loc=0, scale=2)) + - np.log(norm.pdf(0.001, loc=0, scale=2)) + ) + / 2 ) - / 2, - delta=1e-01, + < 1e-01 ) - self.assertTrue(scorer.is_probabilistic) + assert scorer.is_probabilistic def test_LaplaceNLLScorer(self): # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): LaplaceNLLScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): LaplaceNLLScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): LaplaceNLLScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): LaplaceNLLScorer(window=0) scorer = LaplaceNLLScorer(window=101) # window must be smaller than the input of score_from_prediction() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( actual_series=self.test, pred_series=self.probabilistic ) # len(self.test)=100 @@ -1814,12 +1761,7 @@ def test_LaplaceNLLScorer(self): ) # check if value_test1 is the - log likelihood - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples - value_test1, - -np.log(laplace.pdf(3, loc=0, scale=2)), - delta=1e-01, - ) + assert abs(value_test1 + np.log(laplace.pdf(3, loc=0, scale=2))) < 1e-01 # test 2 univariate (len=1 and window=1) laplace_samples_2 = np.random.laplace(loc=0, scale=2, size=1000) @@ -1834,12 +1776,7 @@ def test_LaplaceNLLScorer(self): ) # check if value_test2 is the - log likelihood - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples - value_test2, - -np.log(laplace.pdf(-2, loc=0, scale=2)), - delta=1e-01, - ) + assert abs(value_test2 + np.log(laplace.pdf(-2, loc=0, scale=2))) < 1e-01 # test window univariate (len=2 and window=2) distribution_series = TimeSeries.from_values( @@ -1851,21 +1788,21 @@ def test_LaplaceNLLScorer(self): value_window = scorer.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(value_window), 2) + assert len(value_window) == 2 # check width - self.assertEqual(value_window.width, 1) + assert value_window.width == 1 # check equal value_test1 and value_test2 - self.assertAlmostEqual(value_window.all_values().flatten()[0], value_test1) - self.assertAlmostEqual(value_window.all_values().flatten()[1], value_test2) + assert round(abs(value_window.all_values().flatten()[0] - value_test1), 7) == 0 + assert round(abs(value_window.all_values().flatten()[1] - value_test2), 7) == 0 scorer = LaplaceNLLScorer(window=2) # check avg of two values - self.assertEqual( + assert ( scorer.score_from_prediction(actual_series, distribution_series) .all_values() - .flatten()[0], - (value_test1 + value_test2) / 2, + .flatten()[0] + == (value_test1 + value_test2) / 2 ) # test window multivariate (n_samples=2, len=1, window=1) @@ -1879,16 +1816,18 @@ def test_LaplaceNLLScorer(self): ) # check length - self.assertEqual(len(value_multivariate), 1) + assert len(value_multivariate) == 1 # check width - self.assertEqual(value_multivariate.width, 2) + assert value_multivariate.width == 2 # check equal value_test1 and value_test2 - self.assertAlmostEqual( - value_multivariate.all_values().flatten()[0], value_test1 + assert ( + round(abs(value_multivariate.all_values().flatten()[0] - value_test1), 7) + == 0 ) - self.assertAlmostEqual( - value_multivariate.all_values().flatten()[1], value_test2 + assert ( + round(abs(value_multivariate.all_values().flatten()[1] - value_test2), 7) + == 0 ) # test window multivariate (n_samples=2, len=2, window=1 and 2) @@ -1917,76 +1856,86 @@ def test_LaplaceNLLScorer(self): score_w2 = scorer_w2.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(score_w1), 2) - self.assertEqual(len(score_w2), 1) + assert len(score_w1) == 2 + assert len(score_w2) == 1 # check width - self.assertEqual(score_w1.width, 2) - self.assertEqual(score_w2.width, 2) + assert score_w1.width == 2 + assert score_w2.width == 2 # check values for window=1 - self.assertAlmostEqual( - score_w1.all_values().flatten()[0], - -np.log(laplace.pdf(1.5, loc=0, scale=2)), - delta=1e-01, + assert ( + abs( + score_w1.all_values().flatten()[0] + + np.log(laplace.pdf(1.5, loc=0, scale=2)) + ) + < 1e-01 ) - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples - score_w1.all_values().flatten()[1], - -np.log(laplace.pdf(2, loc=0, scale=2)), - delta=0.5, + assert ( + abs( + score_w1.all_values().flatten()[1] + + np.log(laplace.pdf(2, loc=0, scale=2)) + ) + < 0.5 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[2], - -np.log(laplace.pdf(0.1, loc=0, scale=2)), - delta=1e-01, + assert ( + abs( + score_w1.all_values().flatten()[2] + + np.log(laplace.pdf(0.1, loc=0, scale=2)) + ) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[3], - -np.log(laplace.pdf(0.001, loc=0, scale=2)), - delta=1e-01, + assert ( + abs( + score_w1.all_values().flatten()[3] + + np.log(laplace.pdf(0.001, loc=0, scale=2)) + ) + < 1e-01 ) # check values for window=2 (must be equal to the mean of the past 2 values) - self.assertAlmostEqual( - score_w2.all_values().flatten()[0], - ( - -np.log(laplace.pdf(1.5, loc=0, scale=2)) - - np.log(laplace.pdf(0.1, loc=0, scale=2)) + assert ( + abs( + score_w2.all_values().flatten()[0] + - ( + -np.log(laplace.pdf(1.5, loc=0, scale=2)) + - np.log(laplace.pdf(0.1, loc=0, scale=2)) + ) + / 2 ) - / 2, - delta=1e-01, - ) - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples - score_w2.all_values().flatten()[1], - ( - -np.log(laplace.pdf(2, loc=0, scale=2)) - - np.log(laplace.pdf(0.001, loc=0, scale=2)) + < 1e-01 + ) + assert ( + abs( + score_w2.all_values().flatten()[1] + - ( + -np.log(laplace.pdf(2, loc=0, scale=2)) + - np.log(laplace.pdf(0.001, loc=0, scale=2)) + ) + / 2 ) - / 2, - delta=0.5, + < 0.5 ) - self.assertTrue(scorer.is_probabilistic) + assert scorer.is_probabilistic def test_ExponentialNLLScorer(self): # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ExponentialNLLScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ExponentialNLLScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ExponentialNLLScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ExponentialNLLScorer(window=0) scorer = ExponentialNLLScorer(window=101) # window must be smaller than the input of score_from_prediction() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( actual_series=self.test, pred_series=self.probabilistic ) # len(self.test)=100 @@ -2007,12 +1956,7 @@ def test_ExponentialNLLScorer(self): ) # check if value_test1 is the - log likelihood - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples and also uses loc - value_test1, - -np.log(expon.pdf(3, scale=2.0)), - delta=1e-01, - ) + assert abs(value_test1 + np.log(expon.pdf(3, scale=2.0))) < 1e-01 # test 2 univariate (len=1 and window=1) exponential_samples_2 = np.random.exponential(scale=2.0, size=1000) @@ -2027,12 +1971,7 @@ def test_ExponentialNLLScorer(self): ) # check if value_test2 is the - log likelihood - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples and also uses loc - value_test2, - -np.log(expon.pdf(10, scale=2)), - delta=1e-01, - ) + assert abs(value_test2 + np.log(expon.pdf(10, scale=2))) < 1e-01 # test window univariate (len=2 and window=2) distribution_series = TimeSeries.from_values( @@ -2047,21 +1986,21 @@ def test_ExponentialNLLScorer(self): value_window = scorer.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(value_window), 2) + assert len(value_window) == 2 # check width - self.assertEqual(value_window.width, 1) + assert value_window.width == 1 # check equal value_test1 and value_test2 - self.assertEqual(value_window.all_values().flatten()[0], value_test1) - self.assertEqual(value_window.all_values().flatten()[1], value_test2) + assert value_window.all_values().flatten()[0] == value_test1 + assert value_window.all_values().flatten()[1] == value_test2 scorer = ExponentialNLLScorer(window=2) # check avg of two values - self.assertEqual( + assert ( scorer.score_from_prediction(actual_series, distribution_series) .all_values() - .flatten()[0], - (value_test1 + value_test2) / 2, + .flatten()[0] + == (value_test1 + value_test2) / 2 ) # test window multivariate (n_samples=2, len=1, window=1) @@ -2075,13 +2014,13 @@ def test_ExponentialNLLScorer(self): ) # check length - self.assertEqual(len(value_multivariate), 1) + assert len(value_multivariate) == 1 # check width - self.assertEqual(value_multivariate.width, 2) + assert value_multivariate.width == 2 # check equal value_test1 and value_test2 - self.assertEqual(value_multivariate.all_values().flatten()[0], value_test1) - self.assertEqual(value_multivariate.all_values().flatten()[1], value_test2) + assert value_multivariate.all_values().flatten()[0] == value_test1 + assert value_multivariate.all_values().flatten()[1] == value_test2 # test window multivariate (n_samples=2, len=2, window=1 and 2) scorer_w1 = ExponentialNLLScorer(window=1) @@ -2109,66 +2048,68 @@ def test_ExponentialNLLScorer(self): score_w2 = scorer_w2.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(score_w1), 2) - self.assertEqual(len(score_w2), 1) + assert len(score_w1) == 2 + assert len(score_w2) == 1 # check width - self.assertEqual(score_w1.width, 2) - self.assertEqual(score_w2.width, 2) + assert score_w1.width == 2 + assert score_w2.width == 2 # check values for window=1 - self.assertAlmostEqual( - score_w1.all_values().flatten()[0], - -np.log(expon.pdf(1.5, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[0] + np.log(expon.pdf(1.5, scale=2))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[1], - -np.log(expon.pdf(2, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[1] + np.log(expon.pdf(2, scale=2))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[2], - -np.log(expon.pdf(0.1, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[2] + np.log(expon.pdf(0.1, scale=2))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[3], - -np.log(expon.pdf(0.001, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[3] + np.log(expon.pdf(0.001, scale=2))) + < 1e-01 ) # check values for window=2 (must be equal to the mean of the past 2 values) - self.assertAlmostEqual( - score_w2.all_values().flatten()[0], - (-np.log(expon.pdf(1.5, scale=2)) - np.log(expon.pdf(0.1, scale=2))) / 2, - delta=1e-01, + assert ( + abs( + score_w2.all_values().flatten()[0] + - (-np.log(expon.pdf(1.5, scale=2)) - np.log(expon.pdf(0.1, scale=2))) + / 2 + ) + < 1e-01 ) - self.assertAlmostEqual( - score_w2.all_values().flatten()[1], - (-np.log(expon.pdf(2, scale=2)) - np.log(expon.pdf(0.001, scale=2))) / 2, - delta=1e-01, + assert ( + abs( + score_w2.all_values().flatten()[1] + - (-np.log(expon.pdf(2, scale=2)) - np.log(expon.pdf(0.001, scale=2))) + / 2 + ) + < 1e-01 ) - self.assertTrue(scorer.is_probabilistic) + assert scorer.is_probabilistic def test_GammaNLLScorer(self): # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GammaNLLScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GammaNLLScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GammaNLLScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): GammaNLLScorer(window=0) scorer = GammaNLLScorer(window=101) # window must be smaller than the input of score_from_prediction() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( actual_series=self.test, pred_series=self.probabilistic ) # len(self.test)=100 @@ -2187,12 +2128,7 @@ def test_GammaNLLScorer(self): ) # check if value_test1 is the - log likelihood - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples and also uses loc - value_test1, - -np.log(gamma.pdf(3, 2, scale=2)), - delta=1e-01, - ) + assert abs(value_test1 + np.log(gamma.pdf(3, 2, scale=2))) < 1e-01 # test 2 univariate (len=1 and window=1) gamma_samples_2 = np.random.gamma(2, scale=2, size=10000) @@ -2205,12 +2141,7 @@ def test_GammaNLLScorer(self): ) # check if value_test2 is the - log likelihood - self.assertAlmostEqual( - # This is approximate because our NLL scorer is fit from samples and also uses loc - value_test2, - -np.log(gamma.pdf(10, 2, scale=2)), - delta=1e-01, - ) + assert abs(value_test2 + np.log(gamma.pdf(10, 2, scale=2))) < 1e-01 # test window univariate (len=2 and window=2) distribution_series = TimeSeries.from_values( @@ -2220,21 +2151,21 @@ def test_GammaNLLScorer(self): value_window = scorer.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(value_window), 2) + assert len(value_window) == 2 # check width - self.assertEqual(value_window.width, 1) + assert value_window.width == 1 # check equal value_test1 and value_test2 - self.assertEqual(value_window.all_values().flatten()[0], value_test1) - self.assertEqual(value_window.all_values().flatten()[1], value_test2) + assert value_window.all_values().flatten()[0] == value_test1 + assert value_window.all_values().flatten()[1] == value_test2 scorer = GammaNLLScorer(window=2) # check avg of two values - self.assertEqual( + assert ( scorer.score_from_prediction(actual_series, distribution_series) .all_values() - .flatten()[0], - (value_test1 + value_test2) / 2, + .flatten()[0] + == (value_test1 + value_test2) / 2 ) # test window multivariate (n_samples=2, len=1, window=1) @@ -2248,13 +2179,13 @@ def test_GammaNLLScorer(self): ) # check length - self.assertEqual(len(value_multivariate), 1) + assert len(value_multivariate) == 1 # check width - self.assertEqual(value_multivariate.width, 2) + assert value_multivariate.width == 2 # check equal value_test1 and value_test2 - self.assertEqual(value_multivariate.all_values().flatten()[0], value_test1) - self.assertEqual(value_multivariate.all_values().flatten()[1], value_test2) + assert value_multivariate.all_values().flatten()[0] == value_test1 + assert value_multivariate.all_values().flatten()[1] == value_test2 # test window multivariate (n_samples=2, len=2, window=1 and 2) scorer_w1 = GammaNLLScorer(window=1) @@ -2277,68 +2208,74 @@ def test_GammaNLLScorer(self): score_w2 = scorer_w2.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(score_w1), 2) - self.assertEqual(len(score_w2), 1) + assert len(score_w1) == 2 + assert len(score_w2) == 1 # check width - self.assertEqual(score_w1.width, 2) - self.assertEqual(score_w2.width, 2) + assert score_w1.width == 2 + assert score_w2.width == 2 # check values for window=1 - self.assertAlmostEqual( - score_w1.all_values().flatten()[0], - -np.log(gamma.pdf(1.5, 2, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[0] + np.log(gamma.pdf(1.5, 2, scale=2))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[1], - -np.log(gamma.pdf(2, 2, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[1] + np.log(gamma.pdf(2, 2, scale=2))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[2], - -np.log(gamma.pdf(0.5, 2, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[2] + np.log(gamma.pdf(0.5, 2, scale=2))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[3], - -np.log(gamma.pdf(0.9, 2, scale=2)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[3] + np.log(gamma.pdf(0.9, 2, scale=2))) + < 1e-01 ) # check values for window=2 (must be equal to the mean of the past 2 values) - self.assertAlmostEqual( - score_w2.all_values().flatten()[0], - (-np.log(gamma.pdf(1.5, 2, scale=2)) - np.log(gamma.pdf(0.5, 2, scale=2))) - / 2, - delta=1e-01, - ) - self.assertAlmostEqual( - score_w2.all_values().flatten()[1], - (-np.log(gamma.pdf(2, 2, scale=2)) - np.log(gamma.pdf(0.9, 2, scale=2))) - / 2, - delta=1e-01, + assert ( + abs( + score_w2.all_values().flatten()[0] + - ( + -np.log(gamma.pdf(1.5, 2, scale=2)) + - np.log(gamma.pdf(0.5, 2, scale=2)) + ) + / 2 + ) + < 1e-01 + ) + assert ( + abs( + score_w2.all_values().flatten()[1] + - ( + -np.log(gamma.pdf(2, 2, scale=2)) + - np.log(gamma.pdf(0.9, 2, scale=2)) + ) + / 2 + ) + < 1e-01 ) - self.assertTrue(scorer.is_probabilistic) + assert scorer.is_probabilistic def test_CauchyNLLScorer(self): # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): CauchyNLLScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): CauchyNLLScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): CauchyNLLScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): CauchyNLLScorer(window=0) scorer = CauchyNLLScorer(window=101) # window must be smaller than the input of score_from_prediction() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( actual_series=self.test, pred_series=self.probabilistic ) # len(self.test)=100 @@ -2357,7 +2294,7 @@ def test_CauchyNLLScorer(self): ) # check if value_test1 is the - log likelihood - self.assertAlmostEqual(value_test1, -np.log(cauchy.pdf(3)), delta=1e-01) + assert abs(value_test1 + np.log(cauchy.pdf(3))) < 1e-01 # test 2 univariate (len=1 and window=1) cauchy_samples_2 = np.random.standard_cauchy(size=10000) @@ -2370,7 +2307,7 @@ def test_CauchyNLLScorer(self): ) # check if value_test2 is the - log likelihood - self.assertAlmostEqual(value_test2, -np.log(cauchy.pdf(-2)), delta=1e-01) + assert abs(value_test2 + np.log(cauchy.pdf(-2))) < 1e-01 # test window univariate (len=2 and window=2) distribution_series = TimeSeries.from_values( @@ -2380,21 +2317,21 @@ def test_CauchyNLLScorer(self): value_window = scorer.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(value_window), 2) + assert len(value_window) == 2 # check width - self.assertEqual(value_window.width, 1) + assert value_window.width == 1 # check equal value_test1 and value_test2 - self.assertEqual(value_window.all_values().flatten()[0], value_test1) - self.assertEqual(value_window.all_values().flatten()[1], value_test2) + assert value_window.all_values().flatten()[0] == value_test1 + assert value_window.all_values().flatten()[1] == value_test2 scorer = CauchyNLLScorer(window=2) # check avg of two values - self.assertEqual( + assert ( scorer.score_from_prediction(actual_series, distribution_series) .all_values() - .flatten()[0], - (value_test1 + value_test2) / 2, + .flatten()[0] + == (value_test1 + value_test2) / 2 ) # test window multivariate (n_samples=2, len=1, window=1) @@ -2408,13 +2345,13 @@ def test_CauchyNLLScorer(self): ) # check length - self.assertEqual(len(value_multivariate), 1) + assert len(value_multivariate) == 1 # check width - self.assertEqual(value_multivariate.width, 2) + assert value_multivariate.width == 2 # check equal value_test1 and value_test2 - self.assertEqual(value_multivariate.all_values().flatten()[0], value_test1) - self.assertEqual(value_multivariate.all_values().flatten()[1], value_test2) + assert value_multivariate.all_values().flatten()[0] == value_test1 + assert value_multivariate.all_values().flatten()[1] == value_test2 # test window multivariate (n_samples=2, len=2, window=1 and 2) scorer_w1 = CauchyNLLScorer(window=1) @@ -2437,58 +2374,54 @@ def test_CauchyNLLScorer(self): score_w2 = scorer_w2.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(score_w1), 2) - self.assertEqual(len(score_w2), 1) + assert len(score_w1) == 2 + assert len(score_w2) == 1 # check width - self.assertEqual(score_w1.width, 2) - self.assertEqual(score_w2.width, 2) + assert score_w1.width == 2 + assert score_w2.width == 2 # check values for window=1 - self.assertAlmostEqual( - score_w1.all_values().flatten()[0], -np.log(cauchy.pdf(1.5)), delta=1e-01 - ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[1], -np.log(cauchy.pdf(2)), delta=1e-01 - ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[2], -np.log(cauchy.pdf(0.5)), delta=1e-01 - ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[3], -np.log(cauchy.pdf(0.9)), delta=1e-01 - ) + assert abs(score_w1.all_values().flatten()[0] + np.log(cauchy.pdf(1.5))) < 1e-01 + assert abs(score_w1.all_values().flatten()[1] + np.log(cauchy.pdf(2))) < 1e-01 + assert abs(score_w1.all_values().flatten()[2] + np.log(cauchy.pdf(0.5))) < 1e-01 + assert abs(score_w1.all_values().flatten()[3] + np.log(cauchy.pdf(0.9))) < 1e-01 # check values for window=2 (must be equal to the mean of the past 2 values) - self.assertAlmostEqual( - score_w2.all_values().flatten()[0], - (-np.log(cauchy.pdf(1.5)) - np.log(cauchy.pdf(0.5))) / 2, - delta=1e-01, + assert ( + abs( + score_w2.all_values().flatten()[0] + - (-np.log(cauchy.pdf(1.5)) - np.log(cauchy.pdf(0.5))) / 2 + ) + < 1e-01 ) - self.assertAlmostEqual( - score_w2.all_values().flatten()[1], - (-np.log(cauchy.pdf(2)) - np.log(cauchy.pdf(0.9))) / 2, - delta=1e-01, + assert ( + abs( + score_w2.all_values().flatten()[1] + - (-np.log(cauchy.pdf(2)) - np.log(cauchy.pdf(0.9))) / 2 + ) + < 1e-01 ) - self.assertTrue(scorer.is_probabilistic) + assert scorer.is_probabilistic def test_PoissonNLLScorer(self): # window parameter # window must be int - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PoissonNLLScorer(window=True) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PoissonNLLScorer(window="string") # window must be non negative - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PoissonNLLScorer(window=-1) # window must be different from 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): PoissonNLLScorer(window=0) scorer = PoissonNLLScorer(window=101) # window must be smaller than the input of score_from_prediction() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): scorer.score_from_prediction( actual_series=self.test, pred_series=self.probabilistic ) # len(self.test)=100 @@ -2509,7 +2442,7 @@ def test_PoissonNLLScorer(self): ) # check if value_test1 is the - log likelihood - self.assertAlmostEqual(value_test1, -np.log(poisson.pmf(3, mu=1)), delta=1e-02) + assert abs(value_test1 + np.log(poisson.pmf(3, mu=1))) < 1e-02 # test 2 univariate (len=1 and window=1) poisson_samples_2 = np.random.poisson(size=10000, lam=1) @@ -2524,7 +2457,7 @@ def test_PoissonNLLScorer(self): ) # check if value_test2 is the - log likelihood - self.assertAlmostEqual(value_test2, -np.log(poisson.pmf(10, mu=1)), delta=1e-01) + assert abs(value_test2 + np.log(poisson.pmf(10, mu=1))) < 1e-01 # test window univariate (len=2 and window=2) distribution_series = TimeSeries.from_values( @@ -2536,21 +2469,21 @@ def test_PoissonNLLScorer(self): value_window = scorer.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(value_window), 2) + assert len(value_window) == 2 # check width - self.assertEqual(value_window.width, 1) + assert value_window.width == 1 # check equal value_test1 and value_test2 - self.assertEqual(value_window.all_values().flatten()[0], value_test1) - self.assertEqual(value_window.all_values().flatten()[1], value_test2) + assert value_window.all_values().flatten()[0] == value_test1 + assert value_window.all_values().flatten()[1] == value_test2 scorer = PoissonNLLScorer(window=2) # check avg of two values - self.assertEqual( + assert ( scorer.score_from_prediction(actual_series, distribution_series) .all_values() - .flatten()[0], - (value_test1 + value_test2) / 2, + .flatten()[0] + == (value_test1 + value_test2) / 2 ) # test window multivariate (n_samples=2, len=1, window=1) @@ -2564,13 +2497,13 @@ def test_PoissonNLLScorer(self): ) # check length - self.assertEqual(len(value_multivariate), 1) + assert len(value_multivariate) == 1 # check width - self.assertEqual(value_multivariate.width, 2) + assert value_multivariate.width == 2 # check equal value_test1 and value_test2 - self.assertEqual(value_multivariate.all_values().flatten()[0], value_test1) - self.assertEqual(value_multivariate.all_values().flatten()[1], value_test2) + assert value_multivariate.all_values().flatten()[0] == value_test1 + assert value_multivariate.all_values().flatten()[1] == value_test2 # test window multivariate (n_samples=2, len=2, window=1 and 2) scorer_w1 = PoissonNLLScorer(window=1) @@ -2596,44 +2529,44 @@ def test_PoissonNLLScorer(self): score_w2 = scorer_w2.score_from_prediction(actual_series, distribution_series) # check length - self.assertEqual(len(score_w1), 2) - self.assertEqual(len(score_w2), 1) + assert len(score_w1) == 2 + assert len(score_w2) == 1 # check width - self.assertEqual(score_w1.width, 2) - self.assertEqual(score_w2.width, 2) + assert score_w1.width == 2 + assert score_w2.width == 2 # check values for window=1 - self.assertAlmostEqual( - score_w1.all_values().flatten()[0], - -np.log(poisson.pmf(1, mu=1)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[0] + np.log(poisson.pmf(1, mu=1))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[1], - -np.log(poisson.pmf(2, mu=1)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[1] + np.log(poisson.pmf(2, mu=1))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[2], - -np.log(poisson.pmf(3, mu=1)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[2] + np.log(poisson.pmf(3, mu=1))) + < 1e-01 ) - self.assertAlmostEqual( - score_w1.all_values().flatten()[3], - -np.log(poisson.pmf(4, mu=1)), - delta=1e-01, + assert ( + abs(score_w1.all_values().flatten()[3] + np.log(poisson.pmf(4, mu=1))) + < 1e-01 ) # check values for window=2 (must be equal to the mean of the past 2 values) - self.assertAlmostEqual( - score_w2.all_values().flatten()[0], - (-np.log(poisson.pmf(1, mu=1)) - np.log(poisson.pmf(3, mu=1))) / 2, - delta=1e-01, + assert ( + abs( + score_w2.all_values().flatten()[0] + - (-np.log(poisson.pmf(1, mu=1)) - np.log(poisson.pmf(3, mu=1))) / 2 + ) + < 1e-01 ) - self.assertAlmostEqual( - score_w2.all_values().flatten()[1], - (-np.log(poisson.pmf(2, mu=1)) - np.log(poisson.pmf(4, mu=1))) / 2, - delta=1e-01, + assert ( + abs( + score_w2.all_values().flatten()[1] + - (-np.log(poisson.pmf(2, mu=1)) - np.log(poisson.pmf(4, mu=1))) / 2 + ) + < 1e-01 ) - self.assertTrue(scorer.is_probabilistic) + assert scorer.is_probabilistic diff --git a/darts/tests/base_test_class.py b/darts/tests/base_test_class.py deleted file mode 100644 index 111e91b183..0000000000 --- a/darts/tests/base_test_class.py +++ /dev/null @@ -1,25 +0,0 @@ -import logging -import shutil -import time -import unittest - -# Print something for all tests taking longer than this -DURATION_THRESHOLD = 2.0 -# prevent PyTorch Lightning from using GPU (M1 system compatibility) -tfm_kwargs = {"pl_trainer_kwargs": {"accelerator": "cpu"}} - - -class DartsBaseTestClass(unittest.TestCase): - @classmethod - def setUpClass(cls): - cls.tic = time.time() - logging.disable(logging.CRITICAL) - - def tearDown(self): - duration = time.time() - self.tic - if duration >= DURATION_THRESHOLD: - print(f"Test {self.id()} finished after {duration:.2f} s.") - try: - shutil.rmtree(".darts") - except FileNotFoundError: - pass diff --git a/darts/tests/conftest.py b/darts/tests/conftest.py new file mode 100644 index 0000000000..c4304bb392 --- /dev/null +++ b/darts/tests/conftest.py @@ -0,0 +1,42 @@ +import logging +import shutil +import tempfile + +import pytest + +tfm_kwargs = { + "pl_trainer_kwargs": { + "accelerator": "cpu", + "enable_progress_bar": False, + "enable_model_summary": False, + } +} + + +@pytest.fixture(scope="session", autouse=True) +def set_up_tests(request): + logging.disable(logging.CRITICAL) + + def tear_down_tests(): + try: + shutil.rmtree(".darts") + except FileNotFoundError: + pass + + request.addfinalizer(tear_down_tests) + + +@pytest.fixture(scope="module") +def tmpdir_module(): + """Sets up a temporary directory that will be deleted after the test module (script) finished.""" + temp_work_dir = tempfile.mkdtemp(prefix="darts") + yield temp_work_dir + shutil.rmtree(temp_work_dir) + + +@pytest.fixture(scope="function") +def tmpdir_fn(): + """Sets up a temporary directory that will be deleted after the test function finished.""" + temp_work_dir = tempfile.mkdtemp(prefix="darts") + yield temp_work_dir + shutil.rmtree(temp_work_dir) diff --git a/darts/tests/dataprocessing/dtw/test_dtw.py b/darts/tests/dataprocessing/dtw/test_dtw.py index d8986ff668..458fcec6ed 100644 --- a/darts/tests/dataprocessing/dtw/test_dtw.py +++ b/darts/tests/dataprocessing/dtw/test_dtw.py @@ -1,12 +1,10 @@ -import unittest - import matplotlib.pyplot as plt import numpy as np import pandas as pd +import pytest from darts.dataprocessing import dtw from darts.metrics import dtw_metric, mae, mape -from darts.tests.base_test_class import DartsBaseTestClass from darts.timeseries import TimeSeries from darts.utils import timeseries_generation as tg @@ -17,7 +15,7 @@ def _series_from_values(values): ) -class DynamicTimeWarpingTestCase(DartsBaseTestClass): +class TestDynamicTimeWarping: length = 20 freq = 1 / length series1 = tg.sine_timeseries( @@ -65,14 +63,12 @@ def test_shift(self): exact_alignment = dtw.dtw(series1, series2, multi_grid_radius=-1) - self.assertEqual( - exact_alignment.distance(), - 0, - "Minimum cost between two shifted series should be 0", - ) - self.assertTrue( - np.array_equal(exact_alignment.path(), expected_path), "Incorrect path" - ) + assert ( + exact_alignment.distance() == 0 + ), "Minimum cost between two shifted series should be 0" + np.testing.assert_array_equal( + exact_alignment.path(), expected_path + ), "Incorrect path" def test_multi_grid(self): size = 2**5 - 1 # test odd size @@ -88,7 +84,7 @@ def test_multi_grid(self): exact_distance = dtw.dtw(series1, series2, multi_grid_radius=-1).distance() approx_distance = dtw.dtw(series1, series2, multi_grid_radius=1).distance() - self.assertAlmostEqual(exact_distance, approx_distance, 3) + assert round(abs(exact_distance - approx_distance), 3) == 0 def test_sakoe_chiba_window(self): window = 2 @@ -98,7 +94,7 @@ def test_sakoe_chiba_window(self): path = alignment.path() for i, j in path: - self.assertGreaterEqual(window, abs(i - j)) + assert window >= abs(i - j) def test_itakura_window(self): n = 6 @@ -109,29 +105,26 @@ def test_itakura_window(self): window.init_size(n, m) cells = list(window) - self.assertEqual( - cells, - [ - (1, 1), - (1, 2), - (2, 1), - (2, 2), - (2, 3), - (3, 1), - (3, 2), - (3, 3), - (3, 4), - (4, 2), - (4, 3), - (4, 4), - (5, 2), - (5, 3), - (5, 4), - (5, 5), - (6, 4), - (6, 5), - ], - ) + assert cells == [ + (1, 1), + (1, 2), + (2, 1), + (2, 2), + (2, 3), + (3, 1), + (3, 2), + (3, 3), + (3, 4), + (4, 2), + (4, 3), + (4, 4), + (5, 2), + (5, 3), + (5, 4), + (5, 5), + (6, 4), + (6, 5), + ] sizes = [(10, 43), (543, 45), (34, 11)] @@ -144,7 +137,7 @@ def test_itakura_window(self): ) dist = dtw.dtw(series1, series2, window=dtw.Itakura(slope)).mean_distance() - self.assertGreater(1, dist) + assert 1 > dist def test_warp(self): # Support different time dimension names @@ -158,7 +151,7 @@ def test_warp(self): alignment = dtw.dtw(series1, series2) warped1, warped2 = alignment.warped() - self.assertAlmostEqual(alignment.mean_distance(), mae(warped1, warped2)) + assert round(abs(alignment.mean_distance() - mae(warped1, warped2)), 7) == 0 assert warped1.static_covariates.equals(series1.static_covariates) assert warped2.static_covariates.equals(series2.static_covariates) @@ -166,11 +159,11 @@ def test_warp(self): See DTWAlignment.warped for why this functionality is currently disabled #Mutually Exclusive Option - with self.assertRaises(ValueError): + with pytest.raises(ValueError): alignment.warped(take_dates=True, range_index=True) #Take_dates does not support indexing by RangeIndex - with self.assertRaises(ValueError): + with pytest.raises(ValueError): xa3 = xa1.copy() xa3["time1"] = pd.RangeIndex(0, len(self.series1)) @@ -185,11 +178,11 @@ def test_metric(self): metric1 = dtw_metric(self.series1, self.series2, metric=mae) metric2 = dtw_metric(self.series1, self.series2, metric=mape) - self.assertGreater(0.5, metric1) - self.assertGreater(5, metric2) + assert 0.5 > metric1 + assert 5 > metric2 def test_nans(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): series1 = _series_from_values([np.nan, 0, 1, 2, 3]) series2 = _series_from_values([0, 1, 2, 3, 4]) @@ -220,7 +213,7 @@ def test_multivariate(self): multi_series1, multi_series2, multi_grid_radius=radius ) - self.assertTrue(np.all(alignment_uni.path() == alignment_multi.path())) + assert np.all(alignment_uni.path() == alignment_multi.path()) # MINI_BENCHMARK @@ -251,7 +244,3 @@ def _benchmark_dtw(): cProfile.run("_dtw_exact()", sort="tottime") cProfile.run("_dtw_multigrid()", sort="tottime") - - -if __name__ == "__main__": - unittest.main() diff --git a/darts/tests/dataprocessing/encoders/test_covariate_index_generators.py b/darts/tests/dataprocessing/encoders/test_covariate_index_generators.py index 512c667eab..72c0d3fa22 100644 --- a/darts/tests/dataprocessing/encoders/test_covariate_index_generators.py +++ b/darts/tests/dataprocessing/encoders/test_covariate_index_generators.py @@ -9,13 +9,12 @@ PastCovariatesIndexGenerator, ) from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg logger = get_logger(__name__) -class CovariatesIndexGeneratorTestCase(DartsBaseTestClass): +class TestCovariatesIndexGenerator: n_target = 24 target_time = tg.linear_timeseries(length=n_target, freq="MS") cov_time_train = tg.datetime_attribute_timeseries( @@ -76,31 +75,31 @@ def helper_test_index_types(self, ig: CovariatesIndexGenerator): """test the index type of generated index""" # pd.DatetimeIndex idx, _ = ig.generate_train_idx(self.target_time, self.cov_time_train) - self.assertTrue(isinstance(idx, pd.DatetimeIndex)) + assert isinstance(idx, pd.DatetimeIndex) idx, _ = ig.generate_inference_idx( self.n_short, self.target_time, self.cov_time_inf_short ) - self.assertTrue(isinstance(idx, pd.DatetimeIndex)) + assert isinstance(idx, pd.DatetimeIndex) idx, _ = ig.generate_train_inference_idx( self.n_short, self.target_time, self.cov_time_inf_short ) - self.assertTrue(isinstance(idx, pd.DatetimeIndex)) + assert isinstance(idx, pd.DatetimeIndex) idx, _ = ig.generate_train_idx(self.target_time, None) - self.assertTrue(isinstance(idx, pd.DatetimeIndex)) + assert isinstance(idx, pd.DatetimeIndex) # pd.RangeIndex idx, _ = ig.generate_train_idx(self.target_int, self.cov_int_train) - self.assertTrue(isinstance(idx, pd.RangeIndex)) + assert isinstance(idx, pd.RangeIndex) idx, _ = ig.generate_inference_idx( self.n_short, self.target_int, self.cov_int_inf_short ) - self.assertTrue(isinstance(idx, pd.RangeIndex)) + assert isinstance(idx, pd.RangeIndex) idx, _ = ig.generate_train_inference_idx( self.n_short, self.target_int, self.cov_int_inf_short ) - self.assertTrue(isinstance(idx, pd.RangeIndex)) + assert isinstance(idx, pd.RangeIndex) idx, _ = ig.generate_train_idx(self.target_int, None) - self.assertTrue(isinstance(idx, pd.RangeIndex)) + assert isinstance(idx, pd.RangeIndex) def helper_test_index_generator_train(self, ig: CovariatesIndexGenerator): """ @@ -110,40 +109,40 @@ def helper_test_index_generator_train(self, ig: CovariatesIndexGenerator): # pd.DatetimeIndex # generated index must be equal to input covariate index idx, _ = ig.generate_train_idx(self.target_time, self.cov_time_train) - self.assertTrue(idx.equals(self.cov_time_train.time_index)) + assert idx.equals(self.cov_time_train.time_index) # generated index must be equal to input covariate index idx, _ = ig.generate_train_idx(self.target_time, self.cov_time_train_short) - self.assertTrue(idx.equals(self.cov_time_train_short.time_index)) + assert idx.equals(self.cov_time_train_short.time_index) # generated index must be equal to input target index when no covariates are defined idx, _ = ig.generate_train_idx(self.target_time, None) - self.assertEqual(idx[0], self.target_time.start_time()) + assert idx[0] == self.target_time.start_time() if isinstance(ig, PastCovariatesIndexGenerator): - self.assertEqual( - idx[-1], - self.target_time.end_time() - - self.output_chunk_length * self.target_time.freq, + assert ( + idx[-1] + == self.target_time.end_time() + - self.output_chunk_length * self.target_time.freq ) else: - self.assertEqual(idx[-1], self.target_time.end_time()) + assert idx[-1] == self.target_time.end_time() # integer index # generated index must be equal to input covariate index idx, _ = ig.generate_train_idx(self.target_int, self.cov_int_train) - self.assertTrue(idx.equals(self.cov_int_train.time_index)) + assert idx.equals(self.cov_int_train.time_index) # generated index must be equal to input covariate index idx, _ = ig.generate_train_idx(self.target_int, self.cov_int_train_short) - self.assertTrue(idx.equals(self.cov_int_train_short.time_index)) + assert idx.equals(self.cov_int_train_short.time_index) # generated index must be equal to input target index when no covariates are defined idx, _ = ig.generate_train_idx(self.target_int, None) - self.assertEqual(idx[0], self.target_int.start_time()) + assert idx[0] == self.target_int.start_time() if isinstance(ig, PastCovariatesIndexGenerator): - self.assertEqual( - idx[-1], - self.target_int.end_time() - - self.output_chunk_length * self.target_int.freq, + assert ( + idx[-1] + == self.target_int.end_time() + - self.output_chunk_length * self.target_int.freq ) else: - self.assertEqual(idx[-1], self.target_int.end_time()) + assert idx[-1] == self.target_int.end_time() def helper_test_index_generator_inference(self, ig, is_past=False): """ @@ -167,8 +166,8 @@ def helper_test_index_generator_inference(self, ig, is_past=False): n_out = self.input_chunk_length + self.output_chunk_length last_idx = self.cov_time_inf_short.end_time() - self.assertTrue(len(idx) == n_out) - self.assertTrue(idx[-1] == last_idx) + assert len(idx) == n_out + assert idx[-1] == last_idx # check generated inference index without passing covariates when n > output_chunk_length idx, _ = ig.generate_inference_idx(self.n_long, self.target_time, None) @@ -182,25 +181,25 @@ def helper_test_index_generator_inference(self, ig, is_past=False): n_out = self.input_chunk_length + self.n_long last_idx = self.cov_time_inf_long.end_time() - self.assertTrue(len(idx) == n_out) - self.assertTrue(idx[-1] == last_idx) + assert len(idx) == n_out + assert idx[-1] == last_idx idx, _ = ig.generate_inference_idx( self.n_short, self.target_time, self.cov_time_inf_short ) - self.assertTrue(idx.equals(self.cov_time_inf_short.time_index)) + assert idx.equals(self.cov_time_inf_short.time_index) idx, _ = ig.generate_inference_idx( self.n_long, self.target_time, self.cov_time_inf_long ) - self.assertTrue(idx.equals(self.cov_time_inf_long.time_index)) + assert idx.equals(self.cov_time_inf_long.time_index) idx, _ = ig.generate_inference_idx( self.n_short, self.target_int, self.cov_int_inf_short ) - self.assertTrue(idx.equals(self.cov_int_inf_short.time_index)) + assert idx.equals(self.cov_int_inf_short.time_index) idx, _ = ig.generate_inference_idx( self.n_long, self.target_int, self.cov_int_inf_long ) - self.assertTrue(idx.equals(self.cov_int_inf_long.time_index)) + assert idx.equals(self.cov_int_inf_long.time_index) def helper_test_index_generator_creation(self, ig_cls, is_past=False): # invalid parameter sets @@ -258,8 +257,8 @@ def test_past_index_generator_creation(self): 1, lags_covariates=[min_lag, max_lag], ) - self.assertEqual(ig.shift_start, min_lag + 1) - self.assertEqual(ig.shift_end, max_lag + 1) + assert ig.shift_start == min_lag + 1 + assert ig.shift_end == max_lag + 1 min_lag, max_lag = -1, -1 ig = PastCovariatesIndexGenerator( @@ -267,8 +266,8 @@ def test_past_index_generator_creation(self): 1, lags_covariates=[min_lag, max_lag], ) - self.assertEqual(ig.shift_start, min_lag + 1) - self.assertEqual(ig.shift_end, max_lag + 1) + assert ig.shift_start == min_lag + 1 + assert ig.shift_end == max_lag + 1 # check that min/max lags are extracted from list of lags min_lag, max_lag = -10, -3 @@ -277,8 +276,8 @@ def test_past_index_generator_creation(self): 1, lags_covariates=[-5, min_lag, max_lag, -4], ) - self.assertEqual(ig.shift_start, min_lag + 1) - self.assertEqual(ig.shift_end, max_lag + 1) + assert ig.shift_start == min_lag + 1 + assert ig.shift_end == max_lag + 1 def test_future_index_generator_creation(self): # test parameter scenarios @@ -293,8 +292,8 @@ def test_future_index_generator_creation(self): 1, lags_covariates=[min_lag, max_lag], ) - self.assertEqual(ig.shift_start, min_lag + 1) - self.assertEqual(ig.shift_end, max_lag + 1) + assert ig.shift_start == min_lag + 1 + assert ig.shift_end == max_lag + 1 min_lag, max_lag = -1, -1 ig = FutureCovariatesIndexGenerator( @@ -302,8 +301,8 @@ def test_future_index_generator_creation(self): 1, lags_covariates=[min_lag, max_lag], ) - self.assertEqual(ig.shift_start, min_lag + 1) - self.assertEqual(ig.shift_end, max_lag + 1) + assert ig.shift_start == min_lag + 1 + assert ig.shift_end == max_lag + 1 # different to past covariates ig, future ig can take positive and negative lags min_lag, max_lag = -2, 1 @@ -312,9 +311,9 @@ def test_future_index_generator_creation(self): 1, lags_covariates=[min_lag, max_lag], ) - self.assertEqual(ig.shift_start, min_lag + 1) + assert ig.shift_start == min_lag + 1 # when `max_lag` >= 0, we add one step to `shift_end`, as future lags start at 0 meaning first prediction step - self.assertEqual(ig.shift_end, max_lag + 1) + assert ig.shift_end == max_lag + 1 # check that min/max lags are extracted from list of lags min_lag, max_lag = -10, 5 @@ -323,8 +322,8 @@ def test_future_index_generator_creation(self): 1, lags_covariates=[-5, min_lag, max_lag, -1], ) - self.assertEqual(ig.shift_start, min_lag + 1) - self.assertEqual(ig.shift_end, max_lag + 1) + assert ig.shift_start == min_lag + 1 + assert ig.shift_end == max_lag + 1 def test_past_index_generator(self): ig = PastCovariatesIndexGenerator( @@ -348,38 +347,38 @@ def test_routine_train( lags_covariates=[min_lag, max_lag], ) idx, target_end = idxg.generate_train_idx(target, None) - self.assertEqual(idx[0], pd.Timestamp(start_expected)) - self.assertEqual(idx[-1], pd.Timestamp(end_expected)) - self.assertEqual(target_end, target.end_time()) + assert idx[0] == pd.Timestamp(start_expected) + assert idx[-1] == pd.Timestamp(end_expected) + assert target_end == target.end_time() # check case 0: we give covariates, index will always be the covariate time index idx, target_end = idxg.generate_train_idx(target, self.cov_time_train) - self.assertTrue(idx.equals(self.cov_time_train.time_index)) - self.assertEqual(target_end, target.end_time()) + assert idx.equals(self.cov_time_train.time_index) + assert target_end == target.end_time() return idxg def test_routine_inf(self, idxg, n, start_expected, end_expected): idx, target_end = idxg.generate_inference_idx(n, target, None) - self.assertEqual(idx[0], pd.Timestamp(start_expected)) - self.assertEqual(idx[-1], pd.Timestamp(end_expected)) - self.assertEqual(target_end, target.end_time()) + assert idx[0] == pd.Timestamp(start_expected) + assert idx[-1] == pd.Timestamp(end_expected) + assert target_end == target.end_time() # check case 0: we give covariates, index will always be the covariate time index idx, target_end = idxg.generate_inference_idx( n, target, self.cov_time_inf_short ) - self.assertTrue(idx.equals(self.cov_time_inf_short.time_index)) - self.assertEqual(target_end, target.end_time()) + assert idx.equals(self.cov_time_inf_short.time_index) + assert target_end == target.end_time() def test_routine_train_inf(self, idxg, n, start_expected, end_expected): idx, target_end = idxg.generate_train_inference_idx(n, target, None) - self.assertEqual(idx[0], pd.Timestamp(start_expected)) - self.assertEqual(idx[-1], pd.Timestamp(end_expected)) - self.assertEqual(target_end, target.end_time()) + assert idx[0] == pd.Timestamp(start_expected) + assert idx[-1] == pd.Timestamp(end_expected) + assert target_end == target.end_time() # check case 0: we give covariates, index will always be the covariate time index idx, target_end = idxg.generate_train_inference_idx( n, target, self.cov_time_inf_short ) - self.assertTrue(idx.equals(self.cov_time_inf_short.time_index)) - self.assertEqual(target_end, target.end_time()) + assert idx.equals(self.cov_time_inf_short.time_index) + assert target_end == target.end_time() # lags are required for RegressionModels # case 1: abs(min_lags) == icl and abs(max_lag) == -1: @@ -485,38 +484,38 @@ def test_routine_train( lags_covariates=[min_lag, max_lag], ) idx, target_end = idxg.generate_train_idx(target, None) - self.assertEqual(idx[0], pd.Timestamp(start_expected)) - self.assertEqual(idx[-1], pd.Timestamp(end_expected)) - self.assertEqual(target_end, target.end_time()) + assert idx[0] == pd.Timestamp(start_expected) + assert idx[-1] == pd.Timestamp(end_expected) + assert target_end == target.end_time() # check case 0: we give covariates, index will always be the covariate time index idx, target_end = idxg.generate_train_idx(target, self.cov_time_train) - self.assertTrue(idx.equals(self.cov_time_train.time_index)) - self.assertEqual(target_end, target.end_time()) + assert idx.equals(self.cov_time_train.time_index) + assert target_end == target.end_time() return idxg def test_routine_inf(self, idxg, n, start_expected, end_expected): idx, target_end = idxg.generate_inference_idx(n, target, None) - self.assertEqual(idx[0], pd.Timestamp(start_expected)) - self.assertEqual(idx[-1], pd.Timestamp(end_expected)) - self.assertEqual(target_end, target.end_time()) + assert idx[0] == pd.Timestamp(start_expected) + assert idx[-1] == pd.Timestamp(end_expected) + assert target_end == target.end_time() # check case 0: we give covariates, index will always be the covariate time index idx, target_end = idxg.generate_inference_idx( n, target, self.cov_time_inf_short ) - self.assertTrue(idx.equals(self.cov_time_inf_short.time_index)) - self.assertEqual(target_end, target.end_time()) + assert idx.equals(self.cov_time_inf_short.time_index) + assert target_end == target.end_time() def test_routine_train_inf(self, idxg, n, start_expected, end_expected): idx, target_end = idxg.generate_train_inference_idx(n, target, None) - self.assertEqual(idx[0], pd.Timestamp(start_expected)) - self.assertEqual(idx[-1], pd.Timestamp(end_expected)) - self.assertEqual(target_end, target.end_time()) + assert idx[0] == pd.Timestamp(start_expected) + assert idx[-1] == pd.Timestamp(end_expected) + assert target_end == target.end_time() # check case 0: we give covariates, index will always be the covariate time index idx, target_end = idxg.generate_train_inference_idx( n, target, self.cov_time_inf_short ) - self.assertTrue(idx.equals(self.cov_time_inf_short.time_index)) - self.assertEqual(target_end, target.end_time()) + assert idx.equals(self.cov_time_inf_short.time_index) + assert target_end == target.end_time() # INFO: test cases 1, 2, and 3 only have lags in the past which yields identical results as using a # PastCovariatesIndexGenerator @@ -697,17 +696,17 @@ def test_future_index_generator_local(self): idxg = FutureCovariatesIndexGenerator() idx, _ = idxg.generate_train_idx(target=target, covariates=None) - self.assertTrue(idx.equals(target.time_index)) + assert idx.equals(target.time_index) idx, _ = idxg.generate_train_idx(target=target, covariates=self.cov_time_train) - self.assertTrue(idx.equals(self.cov_time_train.time_index)) + assert idx.equals(self.cov_time_train.time_index) n = 10 idx, _ = idxg.generate_inference_idx(n=n, target=target, covariates=None) - self.assertEqual(idx.freq, freq) - self.assertEqual(idx[0], target.end_time() + 1 * freq) - self.assertEqual(idx[-1], target.end_time() + n * freq) + assert idx.freq == freq + assert idx[0] == target.end_time() + 1 * freq + assert idx[-1] == target.end_time() + n * freq idx, _ = idxg.generate_inference_idx( n=n, target=target, covariates=self.cov_int_inf_short ) - self.assertTrue(idx.equals(self.cov_int_inf_short.time_index)) + assert idx.equals(self.cov_int_inf_short.time_index) diff --git a/darts/tests/dataprocessing/encoders/test_encoders.py b/darts/tests/dataprocessing/encoders/test_encoders.py index 0376e6f0c6..513e470dcb 100644 --- a/darts/tests/dataprocessing/encoders/test_encoders.py +++ b/darts/tests/dataprocessing/encoders/test_encoders.py @@ -1,5 +1,4 @@ import copy -import unittest from typing import Optional, Sequence import numpy as np @@ -30,7 +29,6 @@ ) from darts.dataprocessing.transformers import Scaler from darts.logging import get_logger, raise_log -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -44,7 +42,7 @@ TORCH_AVAILABLE = False -class EncoderTestCase(DartsBaseTestClass): +class TestEncoder: encoders_cls = [ FutureCallableIndexEncoder, FutureCyclicEncoder, @@ -121,10 +119,7 @@ class EncoderTestCase(DartsBaseTestClass): for ts in target_multi ] - @unittest.skipUnless( - TORCH_AVAILABLE, - "Torch not available. SequentialEncoder tests with models will be skipped.", - ) + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_sequence_encoder_from_model_params(self): """test if sequence encoder is initialized properly from model params""" # valid encoder model parameters are ('past', 'future') for the main key and datetime attribute for sub keys @@ -133,45 +128,42 @@ def test_sequence_encoder_from_model_params(self): } encoders = self.helper_encoder_from_model(add_encoder_dict=valid_encoder_args) - self.assertTrue(len(encoders.past_encoders) == 1) - self.assertTrue(len(encoders.future_encoders) == 2) + assert len(encoders.past_encoders) == 1 + assert len(encoders.future_encoders) == 2 # test if encoders have the correct attributes - self.assertTrue(encoders.past_encoders[0].attribute == "month") - self.assertTrue( - [enc.attribute for enc in encoders.future_encoders] - == ["dayofyear", "dayofweek"] - ) + assert encoders.past_encoders[0].attribute == "month" + assert [enc.attribute for enc in encoders.future_encoders] == [ + "dayofyear", + "dayofweek", + ] valid_encoder_args = {"cyclic": {"past": ["month"]}} encoders = self.helper_encoder_from_model( add_encoder_dict=valid_encoder_args, takes_future_covariates=False ) - self.assertTrue(len(encoders.past_encoders) == 1) - self.assertTrue(len(encoders.future_encoders) == 0) + assert len(encoders.past_encoders) == 1 + assert len(encoders.future_encoders) == 0 # test invalid encoder kwarg at model creation bad_encoder = {"no_encoder": {"past": ["month"]}} - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = self.helper_encoder_from_model(add_encoder_dict=bad_encoder) # test invalid kwargs at model creation bad_time = {"cyclic": {"ppast": ["month"]}} - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = self.helper_encoder_from_model(add_encoder_dict=bad_time) bad_attribute = {"cyclic": {"past": ["year"]}} - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = self.helper_encoder_from_model(add_encoder_dict=bad_attribute) bad_type = {"cyclic": {"past": 1}} - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = self.helper_encoder_from_model(add_encoder_dict=bad_type) - @unittest.skipUnless( - TORCH_AVAILABLE, - "Torch not available. SequentialEncoder tests with models will be skipped.", - ) + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_encoder_sequence_train(self): """Test `SequentialEncoder.encode_train()` output""" # ====> Sequential Cyclic Encoder Tests <==== @@ -186,12 +178,12 @@ def test_encoder_sequence_train(self): ) # encoded multi TS covariates should have same number as input covariates - self.assertEqual(len(past_covs_train), 2) - self.assertEqual(len(future_covs_train), 2) + assert len(past_covs_train) == 2 + assert len(future_covs_train) == 2 # each attribute (i.e., 'month', ...) generates 2 output variables (+ 1 covariates from input covariates) - self.assertEqual(past_covs_train[0].n_components, 3) - self.assertEqual(future_covs_train[0].n_components, 5) + assert past_covs_train[0].n_components == 3 + assert future_covs_train[0].n_components == 5 # check with different inputs encoder_args = {"cyclic": {"past": ["month"], "future": ["month"]}} @@ -205,37 +197,31 @@ def test_encoder_sequence_train(self): ) # encoded multi TS covariates should have same number as input covariates - self.assertEqual(len(past_covs_train), 2) - self.assertEqual(len(future_covs_train), 2) + assert len(past_covs_train) == 2 + assert len(future_covs_train) == 2 # each attribute (i.e., 'month', ...) generates 2 output variables (+ 1 covariates from input covariates) - self.assertEqual(past_covs_train[0].n_components, 3) - self.assertEqual(future_covs_train[0].n_components, 3) + assert past_covs_train[0].n_components == 3 + assert future_covs_train[0].n_components == 3 # encoded past covariates must have equal index as input past covariates for pc, pc_in in zip(past_covs_train, self.covariates_multi): - self.assertTrue(pc.time_index.equals(pc_in.time_index)) + assert pc.time_index.equals(pc_in.time_index) # encoded future covariates must have equal index as input future covariates for fc, fc_in in zip(future_covs_train, self.covariates_multi): - self.assertTrue(fc.time_index.equals(fc_in.time_index)) + assert fc.time_index.equals(fc_in.time_index) # for training dataset: both encoded past and future covariates with cyclic encoder 'month' should be equal # (apart from component names) for pc, fc in zip(past_covs_train, future_covs_train): - self.assertEqual( - pc.with_columns_renamed( - list(pc.components), [f"comp{i}" for i in range(len(pc.components))] - ), - fc.with_columns_renamed( - list(fc.components), [f"comp{i}" for i in range(len(fc.components))] - ), + assert pc.with_columns_renamed( + list(pc.components), [f"comp{i}" for i in range(len(pc.components))] + ) == fc.with_columns_renamed( + list(fc.components), [f"comp{i}" for i in range(len(fc.components))] ) - @unittest.skipUnless( - TORCH_AVAILABLE, - "Torch not available. SequentialEncoder tests with models will be skipped.", - ) + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_encoder_sequence_inference(self): """Test `SequentialEncoder.encode_inference()` output""" # ==> test prediction <== @@ -301,9 +287,9 @@ def helper_sequence_encode_inference( ) # encoded past and future covariates must have equal index as expected past and future for pc, pc_in in zip(past_covs_pred, expected_past_idx_ts): - self.assertTrue(pc.time_index.equals(pc_in.time_index)) + assert pc.time_index.equals(pc_in.time_index) for fc, fc_in in zip(future_covs_pred, expected_future_idx_ts): - self.assertTrue(fc.time_index.equals(fc_in.time_index)) + assert fc.time_index.equals(fc_in.time_index) def helper_encoder_from_model( self, add_encoder_dict, takes_past_covariates=True, takes_future_covariates=True @@ -373,12 +359,12 @@ def some_f(idx): [base_comp_name + comp_name for comp_name in comps_expected] ) - self.assertTrue(not enc.fit_called) + assert not enc.fit_called # initially, no components - self.assertTrue(enc.components.empty) + assert enc.components.empty # some encoders must be fit before encoding inference part - self.assertTrue(requires_fit == enc.requires_fit) + assert requires_fit == enc.requires_fit if requires_fit: with pytest.raises(ValueError): enc.encode_inference(n=1, target=ts, covariates=covs) @@ -393,13 +379,13 @@ def test_routine(encoder, merge_covs: bool, comps_expected: pd.Index): assert covs_train.end_time() == ts.end_time() # check the encoded component names - self.assertTrue(encoder.components.equals(comps_expected)) + assert encoder.components.equals(comps_expected) if not merge_covs: - self.assertTrue(covs_train.components.equals(comps_expected)) + assert covs_train.components.equals(comps_expected) else: - self.assertTrue(comps_expected.isin(covs_train.components).all()) + assert comps_expected.isin(covs_train.components).all() # check that original components are in output when merging - self.assertTrue(covs_train[list(covs.components)] == covs) + assert covs_train[list(covs.components)] == covs # check the same for inference covs_inf = encoder.encode_inference( @@ -407,12 +393,12 @@ def test_routine(encoder, merge_covs: bool, comps_expected: pd.Index): ) # if we give input `covs` the encoder will use the same index as `covs` assert covs_inf.end_time() == covs.end_time() - self.assertTrue(encoder.components.equals(comps_expected)) + assert encoder.components.equals(comps_expected) if not merge_covs: - self.assertTrue(covs_inf.components.equals(comps_expected)) + assert covs_inf.components.equals(comps_expected) else: - self.assertTrue(comps_expected.isin(covs_inf.components).all()) - self.assertTrue(covs_inf[list(covs.components)] == covs) + assert comps_expected.isin(covs_inf.components).all() + assert covs_inf[list(covs.components)] == covs # check that train_inference gives equal results covs_train_inf = encoder.encode_train_inference( @@ -694,7 +680,7 @@ def test_cyclic_encoder(self): ) # check if encoded values for first 12 months are equal to values of last 12 months - self.assertTrue((first_halve.values() == second_halve.values()).all()) + assert (first_halve.values() == second_halve.values()).all() # test past cyclic encoder # pc: past covariates @@ -752,7 +738,7 @@ def test_datetime_attribute_encoder(self): ) # check if encoded values for first 12 months are equal to values of last 12 months - self.assertTrue((first_halve.values() == second_halve.values()).all()) + assert (first_halve.values() == second_halve.values()).all() # test past cyclic encoder expected_components = "darts_enc_pc_dta_month" @@ -795,24 +781,21 @@ def test_integer_positional_encoder(self): vals = np.arange(-len(ts) + 1, 1).reshape((len(ts), 1)) pc1, fc1 = encs.encode_train(ts) - self.assertTrue( + assert ( pc1.time_index.equals(ts.time_index[:-output_chunk_length]) and (pc1.values() == vals[:-output_chunk_length]).all() ) - self.assertTrue( - fc1.time_index.equals(ts.time_index) and (fc1.values() == vals).all() - ) + assert fc1.time_index.equals(ts.time_index) and (fc1.values() == vals).all() pc2, fc2 = encs.encode_train( TimeSeries.from_times_and_values( ts.time_index[:20] + ts.freq, ts[:20].values() ) ) - self.assertTrue( - (pc2.time_index.equals(ts.time_index[: 20 - output_chunk_length] + ts.freq)) - and (pc2.values() == vals[-20:-output_chunk_length]).all() - ) - self.assertTrue( + assert ( + pc2.time_index.equals(ts.time_index[: 20 - output_chunk_length] + ts.freq) + ) and (pc2.values() == vals[-20:-output_chunk_length]).all() + assert ( fc2.time_index.equals(ts.time_index[:20] + ts.freq) and (fc2.values() == vals[-20:]).all() ) @@ -822,11 +805,11 @@ def test_integer_positional_encoder(self): ts.time_index[:18] - ts.freq, ts[:18].values() ) ) - self.assertTrue( + assert ( pc3.time_index.equals(ts.time_index[: 18 - output_chunk_length] - ts.freq) and (pc3.values() == vals[-18:-output_chunk_length]).all() ) - self.assertTrue( + assert ( fc3.time_index.equals(ts.time_index[:18] - ts.freq) and (fc3.values() == vals[-18:]).all() ) @@ -835,12 +818,12 @@ def test_integer_positional_encoder(self): # n > output_chunk_length n = output_chunk_length + 1 pc4, fc4 = encs.encode_inference(n, ts) - self.assertTrue( - (pc4.univariate_values() == np.arange(-input_chunk_length + 1, 1 + 1)).all() - ) - self.assertTrue( - (fc4.univariate_values() == np.arange(-input_chunk_length + 1, 1 + n)).all() - ) + assert ( + pc4.univariate_values() == np.arange(-input_chunk_length + 1, 1 + 1) + ).all() + assert ( + fc4.univariate_values() == np.arange(-input_chunk_length + 1, 1 + n) + ).all() # n <= output_chunk_length n = output_chunk_length - 1 t5, fc5 = encs.encode_inference( @@ -849,15 +832,13 @@ def test_integer_positional_encoder(self): ts.time_index[:20] + ts.freq, ts[:20].values() ), ) - self.assertTrue( - (t5.univariate_values() == np.arange(-input_chunk_length + 1, 0 + 1)).all() - ) - self.assertTrue( - ( - fc5.univariate_values() - == np.arange(-input_chunk_length + 1, output_chunk_length + 1) - ).all() - ) + assert ( + t5.univariate_values() == np.arange(-input_chunk_length + 1, 0 + 1) + ).all() + assert ( + fc5.univariate_values() + == np.arange(-input_chunk_length + 1, output_chunk_length + 1) + ).all() # quickly test with lags min_pc_lag = -input_chunk_length - 2 # = -14 @@ -874,13 +855,13 @@ def test_integer_positional_encoder(self): lags_future_covariates=[min_fc_lag, max_fc_lag], ) pc1, fc1 = encs.encode_train(ts) - self.assertTrue( + assert ( pc1.start_time() == pd.Timestamp("1999-11-01") and pc1.end_time() == pd.Timestamp("2001-01-01") and (pc1.univariate_values() == np.arange(-25, -10)).all() and pc1[ts.start_time()].univariate_values()[0] == -23 ) - self.assertTrue( + assert ( fc1.start_time() == pd.Timestamp("2001-03-01") and fc1.end_time() == pd.Timestamp("2002-03-01") and (fc1.univariate_values() == np.arange(-9, 4)).all() @@ -889,12 +870,12 @@ def test_integer_positional_encoder(self): n = 2 pc2, fc2 = encs.encode_inference(n=n, target=ts) - self.assertTrue( + assert ( pc2.start_time() == pd.Timestamp("2000-11-01") and pc2.end_time() == pd.Timestamp("2001-07-01") and (pc2.univariate_values() == np.arange(-13, -4)).all() ) - self.assertTrue( + assert ( fc2.start_time() == pd.Timestamp("2002-03-01") and fc2.end_time() == pd.Timestamp("2002-09-01") and (fc2.univariate_values() == np.arange(3, 10)).all() @@ -953,19 +934,23 @@ def test_callable_encoder(self): def test_transformer_single_series(self): def test_routine_cyclic(past_covs): for curve in ["sin", "cos"]: - self.assertAlmostEqual( - past_covs[f"darts_enc_pc_cyc_minute_{curve}"] - .all_values(copy=False) - .min(), - -1.0, - delta=1e-9, + assert ( + abs( + past_covs[f"darts_enc_pc_cyc_minute_{curve}"] + .all_values(copy=False) + .min() + + 1.0 + ) + < 1e-9 ) - self.assertAlmostEqual( - past_covs[f"darts_enc_pc_cyc_minute_{curve}"] - .values(copy=False) - .max(), - 1.0, - delta=0.1e-9, + assert ( + abs( + past_covs[f"darts_enc_pc_cyc_minute_{curve}"] + .values(copy=False) + .max() + - 1.0 + ) + < 0.1e-9 ) ts1 = tg.linear_timeseries( @@ -989,15 +974,15 @@ def test_routine_cyclic(past_covs): # ===> train set test <=== # user supplied covariates should not be transformed - self.assertTrue(fc1["cov_in"] == ts1) + assert fc1["cov_in"] == ts1 # cyclic encodings should not be transformed test_routine_cyclic(pc1) # all others should be transformed to values between 0 and 1 - self.assertAlmostEqual( - fc1["darts_enc_fc_pos_relative"].values(copy=False).min(), 0.0, delta=10e-9 + assert ( + abs(fc1["darts_enc_fc_pos_relative"].values(copy=False).min() - 0.0) < 10e-9 ) - self.assertAlmostEqual( - fc1["darts_enc_fc_pos_relative"].values(copy=False).max(), 1.0, delta=10e-9 + assert ( + abs(fc1["darts_enc_fc_pos_relative"].values(copy=False).max() - 1.0) < 10e-9 ) # ===> validation set test <=== @@ -1013,11 +998,11 @@ def test_routine_cyclic(past_covs): # cyclic encodings should not be transformed test_routine_cyclic(pc2) # make sure that when calling encoders the second time, scalers are not fit again (for validation and inference) - self.assertAlmostEqual( - fc2["darts_enc_fc_pos_relative"].values(copy=False).min(), 0.0, delta=10e-9 + assert ( + abs(fc2["darts_enc_fc_pos_relative"].values(copy=False).min() - 0.0) < 10e-9 ) - self.assertAlmostEqual( - fc2["darts_enc_fc_pos_relative"].values(copy=False).max(), 1.0, delta=10e-9 + assert ( + abs(fc2["darts_enc_fc_pos_relative"].values(copy=False).max() - 1.0) < 10e-9 ) fc_inf = tg.linear_timeseries( @@ -1028,14 +1013,23 @@ def test_routine_cyclic(past_covs): # cyclic encodings should not be transformed test_routine_cyclic(pc3) # index 0 is also start of train target series and value should be 0 - self.assertAlmostEqual(fc3["darts_enc_fc_pos_relative"][0].values()[0, 0], 0.0) + assert ( + round(abs(fc3["darts_enc_fc_pos_relative"][0].values()[0, 0] - 0.0), 7) == 0 + ) # index len(ts1) - 1 is the prediction point and value should be 0 - self.assertAlmostEqual( - fc3["darts_enc_fc_pos_relative"][len(ts1) - 1].values()[0, 0], 1.0 + assert ( + round( + abs( + fc3["darts_enc_fc_pos_relative"][len(ts1) - 1].values()[0, 0] - 1.0 + ), + 7, + ) + == 0 ) # the future should scale proportional to distance to prediction point - self.assertAlmostEqual( - fc3["darts_enc_fc_pos_relative"][80 - 1].values()[0, 0], 80 / 60, delta=0.01 + assert ( + abs(fc3["darts_enc_fc_pos_relative"][80 - 1].values()[0, 0] - 80 / 60) + < 0.01 ) def test_transformer_multi_series(self): @@ -1073,33 +1067,23 @@ def test_transformer_multi_series(self): enc = copy.deepcopy(enc_base) pc, fc = enc.encode_train([ts1, ts2], future_covariates=[ts1, ts2]) # user supplied covariates should not be transformed - self.assertTrue(fc[0]["cov"] == ts1) - self.assertTrue(fc[1]["cov"] == ts2) + assert fc[0]["cov"] == ts1 + assert fc[1]["cov"] == ts2 # check that first covariate series ranges from 0. to 1. and second from ~0.7 to 1. for covs, cov_name in zip( [pc, fc], ["darts_enc_pc_dta_minute", "darts_enc_fc_dta_minute"] ): - self.assertAlmostEqual( - covs[0][cov_name].values(copy=False).min(), 0.0, delta=10e-9 - ) - self.assertAlmostEqual( - covs[0][cov_name].values(copy=False).max(), 1.0, delta=10e-9 - ) - self.assertEqual( - covs[0][cov_name].univariate_values(copy=False)[-4], - covs[1][cov_name].univariate_values(copy=False)[-4], + assert abs(covs[0][cov_name].values(copy=False).min() - 0.0) < 10e-9 + assert abs(covs[0][cov_name].values(copy=False).max() - 1.0) < 10e-9 + assert ( + covs[0][cov_name].univariate_values(copy=False)[-4] + == covs[1][cov_name].univariate_values(copy=False)[-4] ) if "pc" in cov_name: - self.assertAlmostEqual( - covs[1][cov_name].values(copy=False).min(), 0.714, delta=1e-2 - ) + assert abs(covs[1][cov_name].values(copy=False).min() - 0.714) < 1e-2 else: - self.assertAlmostEqual( - covs[1][cov_name].values(copy=False).min(), 0.5, delta=1e-2 - ) - self.assertAlmostEqual( - covs[1][cov_name].values(copy=False).max(), 1.0, delta=10e-9 - ) + assert abs(covs[1][cov_name].values(copy=False).min() - 0.5) < 1e-2 + assert abs(covs[1][cov_name].values(copy=False).max() - 1.0) < 10e-9 # check the same for inference pc, fc = enc.encode_inference( @@ -1110,13 +1094,9 @@ def test_transformer_multi_series(self): ): for cov in covs: if "pc" in cov_name: - self.assertEqual( - cov[cov_name][-(ocl + 1)].univariate_values()[0], 1.0 - ) + assert cov[cov_name][-(ocl + 1)].univariate_values()[0] == 1.0 else: - self.assertEqual( - cov[cov_name][ts1.end_time()].univariate_values()[0], 1.0 - ) + assert cov[cov_name][ts1.end_time()].univariate_values()[0] == 1.0 # check the same for only supplying single series as input pc, fc = enc.encode_inference(n=6, target=ts2, future_covariates=ts2_inf) @@ -1124,27 +1104,21 @@ def test_transformer_multi_series(self): [pc, fc], ["darts_enc_pc_dta_minute", "darts_enc_fc_dta_minute"] ): if "pc" in cov_name: - self.assertEqual(cov[cov_name][-(ocl + 1)].univariate_values()[0], 1.0) + assert cov[cov_name][-(ocl + 1)].univariate_values()[0] == 1.0 else: - self.assertEqual( - cov[cov_name][ts1.end_time()].univariate_values()[0], 1.0 - ) + assert cov[cov_name][ts1.end_time()].univariate_values()[0] == 1.0 # ====> TEST Transformation starting from single-TimeSeries input: transformer is fit per component of a single # encoded series enc = copy.deepcopy(enc_base) pc, fc = enc.encode_train(ts2, future_covariates=ts2) # user supplied covariates should not be transformed - self.assertTrue(fc["cov"] == ts2) + assert fc["cov"] == ts2 for covs, cov_name in zip( [pc, fc], ["darts_enc_pc_dta_minute", "darts_enc_fc_dta_minute"] ): - self.assertAlmostEqual( - covs[cov_name].values(copy=False).min(), 0.0, delta=10e-9 - ) - self.assertAlmostEqual( - covs[cov_name].values(copy=False).max(), 1.0, delta=10e-9 - ) + assert abs(covs[cov_name].values(copy=False).min() - 0.0) < 10e-9 + assert abs(covs[cov_name].values(copy=False).max() - 1.0) < 10e-9 # second time fitting will not fit transformers again pc, fc = enc.encode_train([ts1, ts2], future_covariates=[ts1, ts2]) @@ -1152,39 +1126,23 @@ def test_transformer_multi_series(self): [pc, fc], ["darts_enc_pc_dta_minute", "darts_enc_fc_dta_minute"] ): if "pc" in cov_name: - self.assertAlmostEqual( - covs[0][cov_name].values(copy=False).min(), -2.5, delta=10e-9 - ) + assert abs(covs[0][cov_name].values(copy=False).min() + 2.5) < 10e-9 else: - self.assertAlmostEqual( - covs[0][cov_name].values(copy=False).min(), -1.0, delta=10e-9 - ) - self.assertAlmostEqual( - covs[0][cov_name].values(copy=False).max(), 1.0, delta=10e-9 - ) - self.assertAlmostEqual( - covs[1][cov_name].values(copy=False).min(), 0.0, delta=10e-9 - ) - self.assertAlmostEqual( - covs[1][cov_name].values(copy=False).max(), 1.0, delta=10e-9 - ) + assert abs(covs[0][cov_name].values(copy=False).min() + 1.0) < 10e-9 + assert abs(covs[0][cov_name].values(copy=False).max() - 1.0) < 10e-9 + assert abs(covs[1][cov_name].values(copy=False).min() - 0.0) < 10e-9 + assert abs(covs[1][cov_name].values(copy=False).max() - 1.0) < 10e-9 # check inference with single series pc, fc = enc.encode_inference(n=6, target=ts2, future_covariates=ts2_inf) for cov, cov_name in zip( [pc, fc], ["darts_enc_pc_dta_minute", "darts_enc_fc_dta_minute"] ): - self.assertAlmostEqual( - cov[cov_name].values(copy=False).min(), 0.0, delta=10e-9 - ) + assert abs(cov[cov_name].values(copy=False).min() - 0.0) < 10e-9 if "pc" in cov_name: - self.assertAlmostEqual( - cov[cov_name].values(copy=False).max(), 2.5, delta=10e-9 - ) + assert abs(cov[cov_name].values(copy=False).max() - 2.5) < 10e-9 else: - self.assertAlmostEqual( - cov[cov_name].values(copy=False).max(), 1.0, delta=10e-9 - ) + assert abs(cov[cov_name].values(copy=False).max() - 1.0) < 10e-9 # check the same for supplying multiple series as input pc, fc = enc.encode_inference( @@ -1194,17 +1152,11 @@ def test_transformer_multi_series(self): [pc, fc], ["darts_enc_pc_dta_minute", "darts_enc_fc_dta_minute"] ): for cov in covs: - self.assertAlmostEqual( - cov[cov_name].values(copy=False).min(), 0.0, delta=10e-9 - ) + assert abs(cov[cov_name].values(copy=False).min() - 0.0) < 10e-9 if "pc" in cov_name: - self.assertAlmostEqual( - cov[cov_name].values(copy=False).max(), 2.5, delta=10e-9 - ) + assert abs(cov[cov_name].values(copy=False).max() - 2.5) < 10e-9 else: - self.assertAlmostEqual( - cov[cov_name].values(copy=False).max(), 1.0, delta=10e-9 - ) + assert abs(cov[cov_name].values(copy=False).max() - 1.0) < 10e-9 def helper_test_cyclic_encoder( self, @@ -1346,9 +1298,9 @@ def helper_test_encoder_single_train( ): expected_result = [res[: -self.output_chunk_length] for res in result] - self.assertTrue(encoded == expected_result) + assert encoded == expected_result for enc, enc_train_inf in zip(encoded, encoded_train_inf): - self.assertEqual(enc, enc_train_inf[enc.time_index]) + assert enc == enc_train_inf[enc.time_index] def helper_test_encoder_single_inference( self, @@ -1371,6 +1323,6 @@ def helper_test_encoder_single_inference( n, ts, cov, merge_covariates=merge_covariates ) ) - self.assertTrue(encoded == result) + assert encoded == result for enc, enc_train_inf in zip(encoded, encoded_train_inf): - self.assertEqual(enc, enc_train_inf[enc.time_index]) + assert enc == enc_train_inf[enc.time_index] diff --git a/darts/tests/dataprocessing/test_pipeline.py b/darts/tests/dataprocessing/test_pipeline.py index bf6edff06f..cd056007c1 100644 --- a/darts/tests/dataprocessing/test_pipeline.py +++ b/darts/tests/dataprocessing/test_pipeline.py @@ -1,7 +1,7 @@ -import logging -import unittest from typing import Any, Mapping +import pytest + from darts import TimeSeries from darts.dataprocessing import Pipeline from darts.dataprocessing.transformers import ( @@ -14,13 +14,7 @@ from darts.utils.timeseries_generation import constant_timeseries -class PipelineTestCase(unittest.TestCase): - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - +class TestPipeline: class DataTransformerMock1(BaseDataTransformer): def __init__(self): super().__init__() @@ -115,11 +109,11 @@ def test_transform(self): transformed = p.transform(data) # then - self.assertEqual(63, len(transformed)) - self.assertEqual([0] * 3 + [1] * 30 + [2] * 30, list(transformed.values())) + assert 63 == len(transformed) + assert [0] * 3 + [1] * 30 + [2] * 30 == list(transformed.values()) for t in transformers: - self.assertTrue(t.transform_called) - self.assertFalse(t.inverse_transform_called) + assert t.transform_called + assert not t.inverse_transform_called def test_inverse_raise_exception(self): # given @@ -127,7 +121,7 @@ def test_inverse_raise_exception(self): p = Pipeline([mock]) # when & then - with self.assertRaises(ValueError): + with pytest.raises(ValueError): p.inverse_transform(None) def test_transformers_not_modified(self): @@ -139,7 +133,7 @@ def test_transformers_not_modified(self): p.transform(constant_timeseries(value=1, length=10)) # then - self.assertFalse(mock.transform_called) + assert not mock.transform_called def test_fit(self): # given @@ -154,9 +148,9 @@ def test_fit(self): # then for i in range(10): - self.assertFalse(transformers[i].fit_called) + assert not transformers[i].fit_called for i in range(10, 20): - self.assertTrue(transformers[i].fit_called) + assert transformers[i].fit_called def test_fit_skips_superfluous_transforms(self): # given @@ -173,11 +167,11 @@ def test_fit_skips_superfluous_transforms(self): # then for i in range(10): - self.assertTrue(transformers[i].transform_called) - self.assertTrue(transformers[10].fit_called) - self.assertFalse(transformers[10].transform_called) + assert transformers[i].transform_called + assert transformers[10].fit_called + assert not transformers[10].transform_called for i in range(11, 21): - self.assertFalse(transformers[i].transform_called) + assert not transformers[i].transform_called def test_transform_fit(self): # given @@ -192,11 +186,11 @@ def test_transform_fit(self): # then for t in transformers: - self.assertTrue(t.transform_called) + assert t.transform_called for i in range(10): - self.assertFalse(transformers[i].fit_called) + assert not transformers[i].fit_called for i in range(10, 20): - self.assertTrue(transformers[i].fit_called) + assert transformers[i].fit_called def test_inverse_transform(self): # given @@ -210,7 +204,7 @@ def test_inverse_transform(self): back = p.inverse_transform(transformed) # then - self.assertEqual(data, back) + assert data == back def test_getitem(self): # given @@ -222,10 +216,10 @@ def test_getitem(self): # when & then # note : only compares string representations, since __getitem__() copies the transformers - self.assertEqual(str(p[1]._transformers), str([transformers[1]])) - self.assertEqual(str(p[4:8]._transformers), str(transformers[4:8])) + assert str(p[1]._transformers) == str([transformers[1]]) + assert str(p[4:8]._transformers) == str(transformers[4:8]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): p["invalid attempt"] def test_raises_on_non_transformers(self): @@ -233,20 +227,23 @@ def test_raises_on_non_transformers(self): input_list = list(range(10)) # when & then - with self.assertRaises( - ValueError, - msg="transformers should be objects deriving from BaseDataTransformer", - ): + with pytest.raises(ValueError) as err: Pipeline(input_list) + assert ( + str(err.value) + == "transformers should be objects deriving from BaseDataTransformer" + ) + def test_raises_on_bad_key(self): # given bad_key = 12.0 p = Pipeline([]) # when & then - with self.assertRaises(ValueError, msg="Key must be int, str or slice"): + with pytest.raises(ValueError) as err: p[bad_key] + assert str(err.value) == "key must be either an int or a slice" def test_multi_ts(self): @@ -266,7 +263,7 @@ def test_multi_ts(self): back = p.inverse_transform(transformed) # then - self.assertEqual(data, back) + assert data == back def test_pipeline_partial_inverse(self): series = constant_timeseries(value=0.0, length=3) @@ -284,13 +281,13 @@ def plus_ten(x): transformed = pipeline.transform(series) # should fail, since partial is False by default - with self.assertRaises(ValueError): + with pytest.raises(ValueError): pipeline.inverse_transform(transformed) back = pipeline.inverse_transform(transformed, partial=True) # while the +/- 2 is inverted, the +10 operation is not - self.assertEqual(series_plus_ten, back) + assert series_plus_ten == back def test_pipeline_verbose(self): """ @@ -310,7 +307,7 @@ def plus_ten(x): pipeline = Pipeline([mapper, mapper_inv], verbose=verbose_value) for transformer in pipeline: - self.assertEqual(transformer._verbose, verbose_value) + assert transformer._verbose == verbose_value def test_pipeline_n_jobs(self): """ @@ -330,4 +327,4 @@ def plus_ten(x): pipeline = Pipeline([mapper, mapper_inv], n_jobs=n_jobs_value) for transformer in pipeline: - self.assertEqual(transformer._n_jobs, n_jobs_value) + assert transformer._n_jobs == n_jobs_value diff --git a/darts/tests/dataprocessing/transformers/test_base_data_transformer.py b/darts/tests/dataprocessing/transformers/test_base_data_transformer.py index 7e5a8edcfc..35c2d7cee3 100644 --- a/darts/tests/dataprocessing/transformers/test_base_data_transformer.py +++ b/darts/tests/dataprocessing/transformers/test_base_data_transformer.py @@ -1,5 +1,3 @@ -import logging -import unittest from typing import Any, Mapping, Sequence, Union import numpy as np @@ -9,13 +7,7 @@ from darts.utils.timeseries_generation import constant_timeseries -class BaseDataTransformerTestCase(unittest.TestCase): - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - +class TestBaseDataTransformer: class DataTransformerMock(BaseDataTransformer): def __init__( self, @@ -80,25 +72,25 @@ def ts_transform( # Ensure manual masking only performed when `mask_components = False` # when transform constructed: if not mask_components and ("component_mask" in kwargs): - vals = BaseDataTransformerTestCase.DataTransformerMock.apply_component_mask( + vals = TestBaseDataTransformer.DataTransformerMock.apply_component_mask( series, kwargs["component_mask"], return_ts=False ) else: vals = series.all_values() if stack_samples: - vals = BaseDataTransformerTestCase.DataTransformerMock.stack_samples( - vals - ) + vals = TestBaseDataTransformer.DataTransformerMock.stack_samples(vals) vals = scale * vals + translation if stack_samples: - vals = BaseDataTransformerTestCase.DataTransformerMock.unstack_samples( + vals = TestBaseDataTransformer.DataTransformerMock.unstack_samples( vals, series=series ) if not mask_components and ("component_mask" in kwargs): - vals = BaseDataTransformerTestCase.DataTransformerMock.unapply_component_mask( - series, vals, kwargs["component_mask"] + vals = ( + TestBaseDataTransformer.DataTransformerMock.unapply_component_mask( + series, vals, kwargs["component_mask"] + ) ) return series.with_values(vals) @@ -115,7 +107,7 @@ def test_input_transformed_single_series(self): # 2 * 1 + 10 = 12 expected = constant_timeseries(value=12, length=10) - self.assertEqual(transformed, expected) + assert transformed == expected def test_input_transformed_multiple_series(self): """ @@ -133,9 +125,9 @@ def test_input_transformed_multiple_series(self): mock = self.DataTransformerMock(scale=2, translation=10, parallel_params=False) (transformed_1, transformed_2) = mock.transform((test_input_1, test_input_2)) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 2 * 2 + 10 = 14 - self.assertEqual(transformed_2, constant_timeseries(value=14, length=11)) + assert transformed_2 == constant_timeseries(value=14, length=11) # Have different `scale` param for different jobs: mock = self.DataTransformerMock( @@ -143,14 +135,14 @@ def test_input_transformed_multiple_series(self): ) (transformed_1, transformed_2) = mock.transform((test_input_1, test_input_2)) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 10 = 16 - self.assertEqual(transformed_2, constant_timeseries(value=16, length=11)) + assert transformed_2 == constant_timeseries(value=16, length=11) # If only one timeseries provided, should apply parameters defined for # for the first to that series: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # Have different `scale`, `translation`, and `stack_samples` params for different jobs: mock = self.DataTransformerMock( @@ -162,14 +154,14 @@ def test_input_transformed_multiple_series(self): ) (transformed_1, transformed_2) = mock.transform((test_input_1, test_input_2)) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 11 = 17 - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_2 == constant_timeseries(value=17, length=11) # If only one timeseries provided, should apply parameters defined for # for the first to that series: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # Specify three sets of fixed params, but pass only one or two series as inputs # to `transform`; transformer should apply `i`th set of fixed params to the `i`th @@ -185,13 +177,13 @@ def test_input_transformed_multiple_series(self): # fixed params, should transform using the first set of fixed # parameters: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # If two series provided to transformer with three sets of # fixed params, should transform using the first and second set of fixed # parameters: transformed_1, transformed_2 = mock.transform((test_input_1, test_input_2)) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_1 == constant_timeseries(value=12, length=10) + assert transformed_2 == constant_timeseries(value=17, length=11) def test_input_transformed_multiple_samples(self): """ @@ -212,7 +204,7 @@ def test_input_transformed_multiple_samples(self): expected = expected.concatenate( constant_timeseries(value=14, length=10), axis="sample" ) - self.assertEqual(transformed, expected) + assert transformed == expected def test_input_transformed_masking(self): """ @@ -236,9 +228,9 @@ def test_input_transformed_masking(self): scale=scale, translation=translation, mask_components=True ) transformed = mock.transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected # Manually apply component mask: mock = self.DataTransformerMock(scale=2, translation=10, mask_components=False) transformed = mock.transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected diff --git a/darts/tests/dataprocessing/transformers/test_boxcox.py b/darts/tests/dataprocessing/transformers/test_boxcox.py index bfa8573e1e..fddf9e4f84 100644 --- a/darts/tests/dataprocessing/transformers/test_boxcox.py +++ b/darts/tests/dataprocessing/transformers/test_boxcox.py @@ -1,15 +1,15 @@ -import unittest from copy import deepcopy import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.dataprocessing.transformers import BoxCox, Mapper from darts.utils.timeseries_generation import linear_timeseries, sine_timeseries -class BoxCoxTestCase(unittest.TestCase): +class TestBoxCox: sine_series = sine_timeseries(length=50, value_y_offset=5, value_frequency=0.05) lin_series = linear_timeseries(start_value=1, end_value=10, length=50) @@ -19,13 +19,13 @@ def test_boxbox_lambda(self): boxcox = BoxCox(lmbda=0.3) boxcox.fit(self.multi_series) - self.assertEqual(boxcox._fitted_params, [[0.3, 0.3]]) + assert boxcox._fitted_params == [[0.3, 0.3]] boxcox = BoxCox(lmbda=[0.3, 0.4]) boxcox.fit(self.multi_series) - self.assertEqual(boxcox._fitted_params, [[0.3, 0.4]]) + assert boxcox._fitted_params == [[0.3, 0.4]] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): boxcox = BoxCox(lmbda=[0.2, 0.4, 0.5]) boxcox.fit(self.multi_series) @@ -37,7 +37,7 @@ def test_boxbox_lambda(self): boxcox.fit(self.multi_series) lmbda2 = boxcox._fitted_params[0].tolist() - self.assertNotEqual(lmbda1, lmbda2) + assert lmbda1 != lmbda2 def test_boxcox_transform(self): log_mapper = Mapper(lambda x: np.log(x)) @@ -96,9 +96,9 @@ def test_boxcox_multiple_calls_to_fit(self): box_cox.fit(self.lin_series) lambda2 = deepcopy(box_cox._fitted_params)[0].tolist() - self.assertNotEqual( - lambda1, lambda2, "Lambdas should change when the transformer is retrained" - ) + assert ( + lambda1 != lambda2 + ), "Lambdas should change when the transformer is retrained" def test_multivariate_stochastic_series(self): transformer = BoxCox() @@ -125,4 +125,4 @@ def test_global_fitting(self): .fit([self.sine_series, self.lin_series]) ._fitted_params ) - self.assertEqual(local_params, global_params) + assert local_params == global_params diff --git a/darts/tests/dataprocessing/transformers/test_data_transformer.py b/darts/tests/dataprocessing/transformers/test_data_transformer.py index b40abf9e80..3d31b3ba46 100644 --- a/darts/tests/dataprocessing/transformers/test_data_transformer.py +++ b/darts/tests/dataprocessing/transformers/test_data_transformer.py @@ -1,6 +1,3 @@ -import logging -import unittest - import numpy as np from sklearn.preprocessing import MinMaxScaler, StandardScaler @@ -10,13 +7,7 @@ from darts.utils.timeseries_generation import linear_timeseries, sine_timeseries -class DataTransformerTestCase(unittest.TestCase): - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - +class TestDataTransformer: series1 = tg.random_walk_timeseries(length=100, column_name="series1") * 20 - 10.0 series2 = series1.stack(tg.random_walk_timeseries(length=100) * 20 - 100.0) @@ -32,16 +23,16 @@ def test_scaling(self): series3_tr2 = transformer2.transform(self.series3) # should have the defined name above - self.assertEqual(self.series1.columns[0], "series1") + assert self.series1.columns[0] == "series1" # should keep columns pd.Index - self.assertEqual(self.col_1, series1_tr1.columns) + assert self.col_1 == series1_tr1.columns # should comply with scaling constraints - self.assertAlmostEqual(min(series1_tr1.values().flatten()), 0.0) - self.assertAlmostEqual(max(series1_tr1.values().flatten()), 2.0) - self.assertAlmostEqual(np.mean(series1_tr2.values().flatten()), 0.0) - self.assertAlmostEqual(np.std(series1_tr2.values().flatten()), 1.0) + assert round(abs(min(series1_tr1.values().flatten()) - 0.0), 7) == 0 + assert round(abs(max(series1_tr1.values().flatten()) - 2.0), 7) == 0 + assert round(abs(np.mean(series1_tr2.values().flatten()) - 0.0), 7) == 0 + assert round(abs(np.std(series1_tr2.values().flatten()) - 1.0), 7) == 0 # test inverse transform series1_recovered = transformer2.inverse_transform(series1_tr2) @@ -49,8 +40,8 @@ def test_scaling(self): np.testing.assert_almost_equal( series1_recovered.values().flatten(), self.series1.values().flatten() ) - self.assertEqual(series1_recovered.width, self.series1.width) - self.assertEqual(series3_recovered, series1_recovered[:1]) + assert series1_recovered.width == self.series1.width + assert series3_recovered == series1_recovered[:1] def test_multi_ts_scaling(self): transformer1 = Scaler(MinMaxScaler(feature_range=(0, 2))) @@ -62,13 +53,21 @@ def test_multi_ts_scaling(self): series_array_tr2 = transformer2.fit_transform(series_array) for index in range(len(series_array)): - self.assertAlmostEqual(min(series_array_tr1[index].values().flatten()), 0.0) - self.assertAlmostEqual(max(series_array_tr1[index].values().flatten()), 2.0) - self.assertAlmostEqual( - np.mean(series_array_tr2[index].values().flatten()), 0.0 + assert ( + round(abs(min(series_array_tr1[index].values().flatten()) - 0.0), 7) + == 0 ) - self.assertAlmostEqual( - np.std(series_array_tr2[index].values().flatten()), 1.0 + assert ( + round(abs(max(series_array_tr1[index].values().flatten()) - 2.0), 7) + == 0 + ) + assert ( + round(abs(np.mean(series_array_tr2[index].values().flatten()) - 0.0), 7) + == 0 + ) + assert ( + round(abs(np.std(series_array_tr2[index].values().flatten()) - 1.0), 7) + == 0 ) series_array_rec1 = transformer1.inverse_transform(series_array_tr1) @@ -122,10 +121,10 @@ def test_component_mask_transformation(self): ss_vals = ss.all_values(copy=False) # test non-masked columns - self.assertTrue((ss_vals[:, 1, :] == vals[:, 1, :]).all()) + assert (ss_vals[:, 1, :] == vals[:, 1, :]).all() # test masked columns - self.assertAlmostEqual(ss_vals[:, [0, 2], :].max(), 1.0) - self.assertAlmostEqual(ss_vals[:, [0, 2], :].min(), 0.0) + assert round(abs(ss_vals[:, [0, 2], :].max() - 1.0), 7) == 0 + assert round(abs(ss_vals[:, [0, 2], :].min() - 0.0), 7) == 0 ssi = scaler.inverse_transform(ss, component_mask=component_mask) @@ -148,6 +147,4 @@ def test_global_fitting(self): global_fitted_scaler = ( Scaler(global_fit=True).fit([sine_series, lin_series])._fitted_params[0] ) - self.assertEqual( - local_fitted_scaler.get_params(), global_fitted_scaler.get_params() - ) + assert local_fitted_scaler.get_params() == global_fitted_scaler.get_params() diff --git a/darts/tests/dataprocessing/transformers/test_diff.py b/darts/tests/dataprocessing/transformers/test_diff.py index ceaa8eab5b..83634ab511 100644 --- a/darts/tests/dataprocessing/transformers/test_diff.py +++ b/darts/tests/dataprocessing/transformers/test_diff.py @@ -1,9 +1,9 @@ -import unittest from copy import deepcopy from typing import Optional, Sequence import numpy as np import pandas as pd +import pytest from darts.dataprocessing.transformers import Diff from darts.timeseries import TimeSeries @@ -11,7 +11,7 @@ from darts.utils.timeseries_generation import linear_timeseries, sine_timeseries -class DiffTestCase(unittest.TestCase): +class TestDiff: sine_series = [ 5 * sine_timeseries(length=50, value_frequency=f) for f in (0.05, 0.1, 0.15) ] @@ -53,7 +53,7 @@ def assert_series_equal( np.testing.assert_allclose( series1.all_values(), series2.all_values(), atol=1e-8, equal_nan=equal_nan ) - self.assertTrue(series1.time_index.equals(series2.time_index)) + assert series1.time_index.equals(series2.time_index) def test_diff_quad_series(self): """ @@ -186,16 +186,13 @@ def test_diff_dropna_and_component_mask_specified(self): length to differenced components. """ diff = Diff(lags=1, dropna=True) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as e: diff.fit(self.sine_series, component_mask=np.array([1, 0, 1], dtype=bool)) - self.assertEqual( - ( - "Cannot specify `component_mask` with `dropna = True`, " - "since differenced and undifferenced components will be " - "of different lengths." - ), - str(e.exception), - ) + assert ( + "Cannot specify `component_mask` with `dropna = True`, " + "since differenced and undifferenced components will be " + "of different lengths." + ) == str(e.value) def test_diff_series_too_short(self): """ @@ -204,16 +201,13 @@ def test_diff_series_too_short(self): """ lags = (1000,) diff = Diff(lags=lags) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as e: diff.fit(self.sine_series) - self.assertEqual( - ( - f"Series requires at least {sum(lags) + 1} timesteps " - f"to difference with lags {lags}; series only " - f"has {self.sine_series.n_timesteps} timesteps." - ), - str(e.exception), - ) + assert ( + f"Series requires at least {sum(lags) + 1} timesteps " + f"to difference with lags {lags}; series only " + f"has {self.sine_series.n_timesteps} timesteps." + ) == str(e.value) def test_diff_incompatible_inverse_transform_date(self): """ @@ -231,20 +225,17 @@ def test_diff_incompatible_inverse_transform_date(self): diff = Diff(lags=1, dropna=dropna) diff.fit(series1) series2_diffed = series2.diff(n=1, periods=1, dropna=dropna) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as e: diff.inverse_transform(series2_diffed) expected_start = ( series1.start_time() if (not dropna) else series1.start_time() + series1.freq ) - self.assertEqual( - ( - f"Expected series to begin at time {expected_start}; " - f"instead, it begins at time {series2_diffed.start_time()}." - ), - str(e.exception), - ) + assert ( + f"Expected series to begin at time {expected_start}; " + f"instead, it begins at time {series2_diffed.start_time()}." + ) == str(e.value) def test_diff_incompatible_inverse_transform_freq(self): """ @@ -260,11 +251,11 @@ def test_diff_incompatible_inverse_transform_freq(self): ) diff = Diff(lags=1, dropna=True) diff.fit(series1) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as e: diff.inverse_transform(series2.diff(n=1, periods=1, dropna=True)) - self.assertEqual( - f"Series is of frequency {series2.freq}, but transform was fitted to data of frequency {series1.freq}.", - str(e.exception), + assert ( + f"Series is of frequency {series2.freq}, but transform was fitted to data of frequency {series1.freq}." + == str(e.value) ) def test_diff_incompatible_inverse_transform_shape(self): @@ -280,22 +271,20 @@ def test_diff_incompatible_inverse_transform_shape(self): series_rm_comp = TimeSeries.from_times_and_values( values=vals[:, 1:, :], times=dates ) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as e: diff.inverse_transform(series_rm_comp.diff(n=1, periods=1, dropna=True)) - self.assertEqual( + assert ( f"Expected series to have {series.n_components} components; " - f"instead, it has {series.n_components-1}.", - str(e.exception), + f"instead, it has {series.n_components-1}." == str(e.value) ) series_rm_samp = TimeSeries.from_times_and_values( values=vals[:, :, 1:], times=dates ) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as e: diff.inverse_transform(series_rm_samp.diff(n=1, periods=1, dropna=True)) - self.assertEqual( + assert ( f"Expected series to have {series.n_samples} samples; " - f"instead, it has {series.n_samples-1}.", - str(e.exception), + f"instead, it has {series.n_samples-1}." == str(e.value) ) def test_diff_multiple_calls_to_fit(self): @@ -310,4 +299,4 @@ def test_diff_multiple_calls_to_fit(self): diff.fit(self.sine_series + 1) startvals2 = deepcopy(diff._fitted_params)[0][0] - self.assertFalse(np.allclose(startvals1, startvals2)) + assert not np.allclose(startvals1, startvals2) diff --git a/darts/tests/dataprocessing/transformers/test_fittable_data_transformer.py b/darts/tests/dataprocessing/transformers/test_fittable_data_transformer.py index a94881c5f4..465134cae5 100644 --- a/darts/tests/dataprocessing/transformers/test_fittable_data_transformer.py +++ b/darts/tests/dataprocessing/transformers/test_fittable_data_transformer.py @@ -1,5 +1,3 @@ -import logging -import unittest from typing import Any, Mapping, Sequence, Union import numpy as np @@ -11,18 +9,12 @@ from darts.utils.timeseries_generation import constant_timeseries -class LocalFittableDataTransformerTestCase(unittest.TestCase): +class TestLocalFittableDataTransformer: """ Tests that data transformers inheriting from `FittableDataTransformer` class behave correctly when `global_fit` attribute is `False`. """ - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - class DataTransformerMock(FittableDataTransformer): def __init__( self, @@ -105,24 +97,26 @@ def ts_transform( # Ensure manual masking only performed when `mask_components = False` # when transform constructed: if not mask_components and ("component_mask" in kwargs): - vals = LocalFittableDataTransformerTestCase.DataTransformerMock.apply_component_mask( + vals = TestLocalFittableDataTransformer.DataTransformerMock.apply_component_mask( series, kwargs["component_mask"], return_ts=False ) else: vals = series.all_values() if stack_samples: - vals = LocalFittableDataTransformerTestCase.DataTransformerMock.stack_samples( - vals + vals = ( + TestLocalFittableDataTransformer.DataTransformerMock.stack_samples( + vals + ) ) vals = scale * vals + translation if stack_samples: - vals = LocalFittableDataTransformerTestCase.DataTransformerMock.unstack_samples( + vals = TestLocalFittableDataTransformer.DataTransformerMock.unstack_samples( vals, series=series ) if not mask_components and ("component_mask" in kwargs): - vals = LocalFittableDataTransformerTestCase.DataTransformerMock.unapply_component_mask( + vals = TestLocalFittableDataTransformer.DataTransformerMock.unapply_component_mask( series, vals, kwargs["component_mask"] ) @@ -140,7 +134,7 @@ def test_input_transformed_single_series(self): # 2 * 1 + 10 = 12 expected = constant_timeseries(value=12, length=10) - self.assertEqual(transformed, expected) + assert transformed == expected def test_input_transformed_multiple_series(self): """ @@ -161,9 +155,9 @@ def test_input_transformed_multiple_series(self): (test_input_1, test_input_2) ) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 2 * 2 + 10 = 14 - self.assertEqual(transformed_2, constant_timeseries(value=14, length=11)) + assert transformed_2 == constant_timeseries(value=14, length=11) # Have different `scale` param for different jobs: mock = self.DataTransformerMock( @@ -173,14 +167,14 @@ def test_input_transformed_multiple_series(self): (test_input_1, test_input_2) ) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 10 = 16 - self.assertEqual(transformed_2, constant_timeseries(value=16, length=11)) + assert transformed_2 == constant_timeseries(value=16, length=11) # If only one timeseries provided, should apply parameters defined for # for the first to that series: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # Have different `scale`, `translation`, and `stack_samples` params for different jobs: mock = self.DataTransformerMock( @@ -194,14 +188,14 @@ def test_input_transformed_multiple_series(self): (test_input_1, test_input_2) ) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 11 = 17 - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_2 == constant_timeseries(value=17, length=11) # If only one timeseries provided, should apply parameters defined for # for the first to that series: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # Train on three series with three different fixed param values, # but pass only one or two series as inputs to `transform`; @@ -218,13 +212,13 @@ def test_input_transformed_multiple_series(self): # If single series provided to transformer trained on three # series, should transform using the first set of fitted parameters: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # If two series provided to transformer trained on three # series, should transform using the first and second set of # fitted parameters: transformed_1, transformed_2 = mock.transform((test_input_1, test_input_2)) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_1 == constant_timeseries(value=12, length=10) + assert transformed_2 == constant_timeseries(value=17, length=11) def test_input_transformed_multiple_samples(self): """ @@ -245,7 +239,7 @@ def test_input_transformed_multiple_samples(self): expected = expected.concatenate( constant_timeseries(value=14, length=10), axis="sample" ) - self.assertEqual(transformed, expected) + assert transformed == expected def test_input_transformed_masking(self): """ @@ -269,26 +263,20 @@ def test_input_transformed_masking(self): scale=scale, translation=translation, mask_components=True ) transformed = mock.fit_transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected # Manually apply component mask: mock = self.DataTransformerMock(scale=2, translation=10, mask_components=False) transformed = mock.fit_transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected -class GlobalFittableDataTransformerTestCase(unittest.TestCase): +class TestGlobalFittableDataTransformer: """ Tests that data transformers inheriting from `FittableDataTransformer` class behave correctly when `global_fit` attribute is `True`. """ - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - class DataTransformerMock(FittableDataTransformer): def __init__(self, global_fit: bool): """ @@ -357,8 +345,8 @@ def test_global_fitting(self): transformed_1, transformed_2 = self.DataTransformerMock( global_fit=False ).fit_transform([series_1, series_2]) - self.assertEqual(transformed_1, TimeSeries.from_values(np.zeros((3, 2, 1)))) - self.assertEqual(transformed_2, TimeSeries.from_values(np.zeros((3, 2, 1)))) + assert transformed_1 == TimeSeries.from_values(np.zeros((3, 2, 1))) + assert transformed_2 == TimeSeries.from_values(np.zeros((3, 2, 1))) # Global fitting - mean of `series_1` and `series_2` should be `1.5`, so # `series_1` values should be transformed to `-0.5` and `series_2` values @@ -366,9 +354,5 @@ def test_global_fitting(self): transformed_1, transformed_2 = self.DataTransformerMock( global_fit=True ).fit_transform([series_1, series_2]) - self.assertEqual( - transformed_1, TimeSeries.from_values(-0.5 * np.ones((3, 2, 1))) - ) - self.assertEqual( - transformed_2, TimeSeries.from_values(0.5 * np.ones((3, 2, 1))) - ) + assert transformed_1 == TimeSeries.from_values(-0.5 * np.ones((3, 2, 1))) + assert transformed_2 == TimeSeries.from_values(0.5 * np.ones((3, 2, 1))) diff --git a/darts/tests/dataprocessing/transformers/test_invertible_data_transformer.py b/darts/tests/dataprocessing/transformers/test_invertible_data_transformer.py index 7989f2935e..71163eb928 100644 --- a/darts/tests/dataprocessing/transformers/test_invertible_data_transformer.py +++ b/darts/tests/dataprocessing/transformers/test_invertible_data_transformer.py @@ -1,5 +1,3 @@ -import logging -import unittest from typing import Any, Mapping, Sequence, Union import numpy as np @@ -11,13 +9,7 @@ from darts.utils.timeseries_generation import constant_timeseries -class InvertibleDataTransformerTestCase(unittest.TestCase): - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - +class TestInvertibleDataTransformer: class DataTransformerMock(InvertibleDataTransformer): def __init__( self, @@ -82,26 +74,26 @@ def ts_transform( # Ensure manual masking only performed when `mask_components = False` # when transform constructed: if not mask_components and ("component_mask" in kwargs): - vals = InvertibleDataTransformerTestCase.DataTransformerMock.apply_component_mask( + vals = TestInvertibleDataTransformer.DataTransformerMock.apply_component_mask( series, kwargs["component_mask"], return_ts=False ) else: vals = series.all_values() if stack_samples: - vals = ( - InvertibleDataTransformerTestCase.DataTransformerMock.stack_samples( - vals - ) + vals = TestInvertibleDataTransformer.DataTransformerMock.stack_samples( + vals ) vals = scale * vals + translation if stack_samples: - vals = InvertibleDataTransformerTestCase.DataTransformerMock.unstack_samples( - vals, series=series + vals = ( + TestInvertibleDataTransformer.DataTransformerMock.unstack_samples( + vals, series=series + ) ) if not mask_components and ("component_mask" in kwargs): - vals = InvertibleDataTransformerTestCase.DataTransformerMock.unapply_component_mask( + vals = TestInvertibleDataTransformer.DataTransformerMock.unapply_component_mask( series, vals, kwargs["component_mask"] ) @@ -132,26 +124,26 @@ def ts_inverse_transform( # Ensure manual masking only performed when `mask_components = False` # when transform constructed: if not mask_components and ("component_mask" in kwargs): - vals = InvertibleDataTransformerTestCase.DataTransformerMock.apply_component_mask( + vals = TestInvertibleDataTransformer.DataTransformerMock.apply_component_mask( series, kwargs["component_mask"], return_ts=False ) else: vals = series.all_values() if stack_samples: - vals = ( - InvertibleDataTransformerTestCase.DataTransformerMock.stack_samples( - vals - ) + vals = TestInvertibleDataTransformer.DataTransformerMock.stack_samples( + vals ) vals = (vals - translation) / scale if stack_samples: - vals = InvertibleDataTransformerTestCase.DataTransformerMock.unstack_samples( - vals, series=series + vals = ( + TestInvertibleDataTransformer.DataTransformerMock.unstack_samples( + vals, series=series + ) ) if not mask_components and ("component_mask" in kwargs): - vals = InvertibleDataTransformerTestCase.DataTransformerMock.unapply_component_mask( + vals = TestInvertibleDataTransformer.DataTransformerMock.unapply_component_mask( series, vals, kwargs["component_mask"] ) @@ -169,10 +161,10 @@ def test_input_transformed_single_series(self): # 2 * 1 + 10 = 12 expected = constant_timeseries(value=12, length=10) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: - self.assertEqual(mock.inverse_transform(transformed), test_input) + assert mock.inverse_transform(transformed) == test_input def test_input_transformed_multiple_series(self): """ @@ -190,20 +182,20 @@ def test_input_transformed_multiple_series(self): mock = self.DataTransformerMock(scale=2, translation=10, parallel_params=False) (transformed_1, transformed_2) = mock.transform((test_input_1, test_input_2)) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 2 * 2 + 10 = 14 - self.assertEqual(transformed_2, constant_timeseries(value=14, length=11)) + assert transformed_2 == constant_timeseries(value=14, length=11) # Should get input back: inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 # If only one timeseries provided, should apply parameters defined for # for the first to that series: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) inv_1 = mock.inverse_transform(transformed_1) - self.assertEqual(inv_1, test_input_1) + assert inv_1 == test_input_1 # Have different `scale` param for different jobs: mock = self.DataTransformerMock( @@ -211,13 +203,13 @@ def test_input_transformed_multiple_series(self): ) (transformed_1, transformed_2) = mock.transform((test_input_1, test_input_2)) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 10 = 16 - self.assertEqual(transformed_2, constant_timeseries(value=16, length=11)) + assert transformed_2 == constant_timeseries(value=16, length=11) # Should get input back: inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 # Have different `scale`, `translation`, and `stack_samples` params for different jobs: mock = self.DataTransformerMock( @@ -229,19 +221,19 @@ def test_input_transformed_multiple_series(self): ) (transformed_1, transformed_2) = mock.transform((test_input_1, test_input_2)) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 11 = 17 - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_2 == constant_timeseries(value=17, length=11) # Should get input back: inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 # If only one timeseries provided, should apply parameters defined for # for the first to that series: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) inv_1 = mock.inverse_transform(transformed_1) - self.assertEqual(inv_1, test_input_1) + assert inv_1 == test_input_1 # Specify three sets of fixed params, but pass only one or two series as inputs # to `transform`/`inverse_transform`; transformer should apply `i`th set of fixed @@ -257,18 +249,18 @@ def test_input_transformed_multiple_series(self): # fixed params, should transform/inverse transform using the first set of fixed # parameters: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) inv_1 = mock.inverse_transform(transformed_1) - self.assertEqual(inv_1, test_input_1) + assert inv_1 == test_input_1 # If two series provided to transformer with three sets of # fixed params, should transform/inverse transform using the first and # second set of fixed parameters: transformed_1, transformed_2 = mock.transform((test_input_1, test_input_2)) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_1 == constant_timeseries(value=12, length=10) + assert transformed_2 == constant_timeseries(value=17, length=11) inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 def test_input_transformed_multiple_samples(self): """ @@ -289,10 +281,10 @@ def test_input_transformed_multiple_samples(self): expected = expected.concatenate( constant_timeseries(value=14, length=10), axis="sample" ) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: inv = mock.inverse_transform(transformed) - self.assertEqual(inv, test_input) + assert inv == test_input def test_input_transformed_masking(self): """ @@ -316,15 +308,15 @@ def test_input_transformed_masking(self): scale=scale, translation=translation, mask_components=True ) transformed = mock.transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: inv = mock.inverse_transform(transformed, component_mask=mask) - self.assertEqual(inv, test_input) + assert inv == test_input # Manually apply component mask: mock = self.DataTransformerMock(scale=2, translation=10, mask_components=False) transformed = mock.transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: inv = mock.inverse_transform(transformed, component_mask=mask) - self.assertEqual(inv, test_input) + assert inv == test_input diff --git a/darts/tests/dataprocessing/transformers/test_invertible_fittable_data_transformer.py b/darts/tests/dataprocessing/transformers/test_invertible_fittable_data_transformer.py index 1fb1758625..b699dd47bb 100644 --- a/darts/tests/dataprocessing/transformers/test_invertible_fittable_data_transformer.py +++ b/darts/tests/dataprocessing/transformers/test_invertible_fittable_data_transformer.py @@ -1,5 +1,3 @@ -import logging -import unittest from typing import Any, Mapping, Sequence, Union import numpy as np @@ -14,19 +12,13 @@ from darts.utils.timeseries_generation import constant_timeseries -class LocalFittableInvertibleDataTransformerTestCase(unittest.TestCase): +class TestLocalFittableInvertibleDataTransformer: """ Tests that data transformers inheriting from both `FittableDataTransformer` and `InvertibleDataTransformer` classes behave correctly when `global_fit` attribute is `False`. """ - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - class DataTransformerMock(FittableDataTransformer, InvertibleDataTransformer): """ Mock Fittable and Invertible data transformer that is locally fitted; @@ -114,24 +106,24 @@ def ts_transform( # Ensure manual masking only performed when `mask_components = False` # when transform constructed: if not mask_components and ("component_mask" in kwargs): - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.apply_component_mask( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.apply_component_mask( series, kwargs["component_mask"], return_ts=False ) else: vals = series.all_values() if stack_samples: - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.stack_samples( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.stack_samples( vals ) vals = scale * vals + translation if stack_samples: - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.unstack_samples( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.unstack_samples( vals, series=series ) if not mask_components and ("component_mask" in kwargs): - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.unapply_component_mask( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.unapply_component_mask( series, vals, kwargs["component_mask"] ) @@ -161,24 +153,24 @@ def ts_inverse_transform( # Ensure manual masking only performed when `mask_components = False` # when transform constructed: if not mask_components and ("component_mask" in kwargs): - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.apply_component_mask( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.apply_component_mask( series, kwargs["component_mask"], return_ts=False ) else: vals = series.all_values() if stack_samples: - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.stack_samples( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.stack_samples( vals ) vals = (vals - translation) / scale if stack_samples: - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.unstack_samples( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.unstack_samples( vals, series=series ) if not mask_components and ("component_mask" in kwargs): - vals = LocalFittableInvertibleDataTransformerTestCase.DataTransformerMock.unapply_component_mask( + vals = TestLocalFittableInvertibleDataTransformer.DataTransformerMock.unapply_component_mask( series, vals, kwargs["component_mask"] ) @@ -196,10 +188,10 @@ def test_input_transformed_single_series(self): # 2 * 1 + 10 = 12 expected = constant_timeseries(value=12, length=10) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: - self.assertEqual(mock.inverse_transform(transformed), test_input) + assert mock.inverse_transform(transformed) == test_input def test_input_transformed_multiple_series(self): """ @@ -220,20 +212,20 @@ def test_input_transformed_multiple_series(self): (test_input_1, test_input_2) ) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 2 * 2 + 10 = 14 - self.assertEqual(transformed_2, constant_timeseries(value=14, length=11)) + assert transformed_2 == constant_timeseries(value=14, length=11) # Should get input back: inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 # If only one timeseries provided, should apply parameters defined for # for the first to that series: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) inv_1 = mock.inverse_transform(transformed_1) - self.assertEqual(inv_1, test_input_1) + assert inv_1 == test_input_1 # Have different `scale` param for different jobs: mock = self.DataTransformerMock( @@ -243,13 +235,13 @@ def test_input_transformed_multiple_series(self): (test_input_1, test_input_2) ) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 10 = 16 - self.assertEqual(transformed_2, constant_timeseries(value=16, length=11)) + assert transformed_2 == constant_timeseries(value=16, length=11) # Should get input back: inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 # Have different `scale`, `translation`, and `stack_samples` params for different jobs: mock = self.DataTransformerMock( @@ -263,13 +255,13 @@ def test_input_transformed_multiple_series(self): (test_input_1, test_input_2) ) # 2 * 1 + 10 = 12 - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) # 3 * 2 + 11 = 17 - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_2 == constant_timeseries(value=17, length=11) # Should get input back: inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 # Train on three series with three different fixed param values, # but pass only one or two series as inputs to `transform` or @@ -288,18 +280,18 @@ def test_input_transformed_multiple_series(self): # series, should transform/inverse transform using the first set # of fitted parameters: transformed_1 = mock.transform(test_input_1) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) + assert transformed_1 == constant_timeseries(value=12, length=10) inv_1 = mock.inverse_transform(transformed_1) - self.assertEqual(inv_1, test_input_1) + assert inv_1 == test_input_1 # If two series provided to transformer trained on three # series, should transform/inverse transform using the first and # second set of fitted parameters: transformed_1, transformed_2 = mock.transform((test_input_1, test_input_2)) - self.assertEqual(transformed_1, constant_timeseries(value=12, length=10)) - self.assertEqual(transformed_2, constant_timeseries(value=17, length=11)) + assert transformed_1 == constant_timeseries(value=12, length=10) + assert transformed_2 == constant_timeseries(value=17, length=11) inv_1, inv_2 = mock.inverse_transform((transformed_1, transformed_2)) - self.assertEqual(inv_1, test_input_1) - self.assertEqual(inv_2, test_input_2) + assert inv_1 == test_input_1 + assert inv_2 == test_input_2 def test_input_transformed_multiple_samples(self): """ @@ -320,10 +312,10 @@ def test_input_transformed_multiple_samples(self): expected = expected.concatenate( constant_timeseries(value=14, length=10), axis="sample" ) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: inv = mock.inverse_transform(transformed) - self.assertEqual(inv, test_input) + assert inv == test_input def test_input_transformed_masking(self): """ @@ -347,33 +339,27 @@ def test_input_transformed_masking(self): scale=scale, translation=translation, mask_components=True ) transformed = mock.fit_transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: inv = mock.inverse_transform(transformed, component_mask=mask) - self.assertEqual(inv, test_input) + assert inv == test_input # Manually apply component mask: mock = self.DataTransformerMock(scale=2, translation=10, mask_components=False) transformed = mock.fit_transform(test_input, component_mask=mask) - self.assertEqual(transformed, expected) + assert transformed == expected # Should get input back: inv = mock.inverse_transform(transformed, component_mask=mask) - self.assertEqual(inv, test_input) + assert inv == test_input -class GlobalFittableInvertibleDataTransformerTestCase(unittest.TestCase): +class TestGlobalFittableInvertibleDataTransformer: """ Tests that data transformers inheriting from both `FittableDataTransformer` and `InvertibleDataTransformer` classes behave correctly when `global_fit` attribute is `True`. """ - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - class DataTransformerMock(FittableDataTransformer, InvertibleDataTransformer): """ Mock Fittable and Invertible data transformer that is globally fitted; @@ -458,29 +444,25 @@ def test_global_fitting(self): # zero-valued series: transformer = self.DataTransformerMock(global_fit=False) transformed_1, transformed_2 = transformer.fit_transform([series_1, series_2]) - self.assertEqual(transformed_1, TimeSeries.from_values(np.zeros((3, 2, 1)))) - self.assertEqual(transformed_2, TimeSeries.from_values(np.zeros((3, 2, 1)))) + assert transformed_1 == TimeSeries.from_values(np.zeros((3, 2, 1))) + assert transformed_2 == TimeSeries.from_values(np.zeros((3, 2, 1))) # Inverting transform should return input: untransformed_1, untransformed_2 = transformer.inverse_transform( [transformed_1, transformed_2] ) - self.assertEqual(untransformed_1, series_1) - self.assertEqual(untransformed_2, series_2) + assert untransformed_1 == series_1 + assert untransformed_2 == series_2 # Global fitting - mean of `series_1` and `series_2` should be `1.5`, so # `series_1` values should be transformed to `-0.5` and `series_2` values # should be transformed to `1.5`: transformer = self.DataTransformerMock(global_fit=True) transformed_1, transformed_2 = transformer.fit_transform([series_1, series_2]) - self.assertEqual( - transformed_1, TimeSeries.from_values(-0.5 * np.ones((3, 2, 1))) - ) - self.assertEqual( - transformed_2, TimeSeries.from_values(0.5 * np.ones((3, 2, 1))) - ) + assert transformed_1 == TimeSeries.from_values(-0.5 * np.ones((3, 2, 1))) + assert transformed_2 == TimeSeries.from_values(0.5 * np.ones((3, 2, 1))) # Inverting transform should return input: untransformed_1, untransformed_2 = transformer.inverse_transform( [transformed_1, transformed_2] ) - self.assertEqual(untransformed_1, series_1) - self.assertEqual(untransformed_2, series_2) + assert untransformed_1 == series_1 + assert untransformed_2 == series_2 diff --git a/darts/tests/dataprocessing/transformers/test_mappers.py b/darts/tests/dataprocessing/transformers/test_mappers.py index 3f418989a9..569855aa08 100644 --- a/darts/tests/dataprocessing/transformers/test_mappers.py +++ b/darts/tests/dataprocessing/transformers/test_mappers.py @@ -1,5 +1,3 @@ -import unittest - import numpy as np import pandas as pd @@ -8,7 +6,7 @@ from darts.utils.timeseries_generation import constant_timeseries, linear_timeseries -class MappersTestCase(unittest.TestCase): +class TestMappers: @staticmethod def func(x): return x + 10 @@ -59,7 +57,7 @@ def test_mapper(self): for to_transform, expected_output in test_cases: transformed = self.plus_ten.transform(to_transform) - self.assertEqual(transformed, expected_output) + assert transformed == expected_output def test_invertible_mapper(self): test_cases = [(self.zeroes), ([self.zeroes, self.tens])] @@ -67,7 +65,7 @@ def test_invertible_mapper(self): for data in test_cases: transformed = self.plus_ten_invertible.transform(data) back = self.plus_ten_invertible.inverse_transform(transformed) - self.assertEqual(back, data) + assert back == data def test_mapper_with_timestamp(self): @@ -87,7 +85,7 @@ def test_mapper_with_timestamp(self): expected_output = expected_output.with_columns_renamed( expected_output.components[0], transformed.components[0] ) - self.assertEqual(transformed, expected_output) + assert transformed == expected_output def test_invertible_mapper_with_timestamp(self): @@ -96,7 +94,7 @@ def test_invertible_mapper_with_timestamp(self): for data in test_cases: transformed = self.subtract_month_invertible.transform(data) back = self.subtract_month_invertible.inverse_transform(transformed) - self.assertEqual(back, data) + assert back == data def test_invertible_mappers_on_stochastic_series(self): vals = np.random.rand(10, 2, 100) + 2 diff --git a/darts/tests/dataprocessing/transformers/test_missing_values_filler.py b/darts/tests/dataprocessing/transformers/test_missing_values_filler.py index f526c81215..f8b75ef4ab 100644 --- a/darts/tests/dataprocessing/transformers/test_missing_values_filler.py +++ b/darts/tests/dataprocessing/transformers/test_missing_values_filler.py @@ -1,5 +1,3 @@ -import unittest - import numpy as np import pandas as pd @@ -7,7 +5,7 @@ from darts.timeseries import TimeSeries -class MissingValuesFillerTestCase(unittest.TestCase): +class TestMissingValuesFiller: time = pd.date_range("20130101", "20130130") static_covariate = pd.DataFrame({"0": [1]}) @@ -30,22 +28,22 @@ class MissingValuesFillerTestCase(unittest.TestCase): def test_fill_const_series_with_const_value(self): const_transformer = MissingValuesFiller(fill=2.0) transformed = const_transformer.transform(self.const_series_with_holes) - self.assertEqual(self.const_series, transformed) + assert self.const_series == transformed def test_fill_const_series_with_auto_value(self): auto_transformer = MissingValuesFiller() transformed = auto_transformer.transform(self.const_series_with_holes) - self.assertEqual(self.const_series, transformed) + assert self.const_series == transformed def test_fill_lin_series_with_auto_value(self): auto_transformer = MissingValuesFiller() transformed = auto_transformer.transform(self.lin_series_with_holes) - self.assertEqual(self.lin_series, transformed) + assert self.lin_series == transformed def test_fill_static_covariates_preserved(self): const_transformer = MissingValuesFiller(fill=2.0) transformed = const_transformer.transform(self.const_series_with_holes) - self.assertEqual( - self.const_series.static_covariates.values, - transformed.static_covariates.values, + assert ( + self.const_series.static_covariates.values + == transformed.static_covariates.values ) diff --git a/darts/tests/dataprocessing/transformers/test_reconciliation.py b/darts/tests/dataprocessing/transformers/test_reconciliation.py index 16136aceb0..5181972c04 100644 --- a/darts/tests/dataprocessing/transformers/test_reconciliation.py +++ b/darts/tests/dataprocessing/transformers/test_reconciliation.py @@ -1,6 +1,3 @@ -import logging -import unittest - import numpy as np from pandas import date_range @@ -15,13 +12,7 @@ from darts.utils import timeseries_generation as tg -class ReconciliationTestCase(unittest.TestCase): - __test__ = True - - @classmethod - def setUpClass(cls): - logging.disable(logging.CRITICAL) - +class TestReconciliation: np.random.seed(42) """ test case with a more intricate hierarchy """ @@ -159,7 +150,7 @@ def test_summation_matrix(self): ) def test_hierarchy_preserved_after_predict(self): - self.assertEqual(self.pred.hierarchy, self.series.hierarchy) + assert self.pred.hierarchy == self.series.hierarchy def test_more_intricate_hierarchy(self): recon = BottomUpReconciliator() @@ -199,7 +190,7 @@ def test_reconcilliation_is_order_independent(self): def assert_ts_are_equal(ts1, ts2): for comp in ["T1", "T2", "T3", "T_sum"]: - self.assertEqual(ts1[comp], ts2[comp]) + assert ts1[comp] == ts2[comp] hierarchy = {"T1": ["T_sum"], "T2": ["T_sum"], "T3": ["T_sum"]} ts_1 = ts_1.with_hierarchy(hierarchy) diff --git a/darts/tests/dataprocessing/transformers/test_static_covariates_transformer.py b/darts/tests/dataprocessing/transformers/test_static_covariates_transformer.py index a8e00033a2..9f0db5346b 100644 --- a/darts/tests/dataprocessing/transformers/test_static_covariates_transformer.py +++ b/darts/tests/dataprocessing/transformers/test_static_covariates_transformer.py @@ -5,11 +5,10 @@ from darts import TimeSeries from darts.dataprocessing.transformers import StaticCovariatesTransformer -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg -class StaticCovariatesTransformerTestCase(DartsBaseTestClass): +class TestStaticCovariatesTransformer: series = tg.linear_timeseries(length=10) static_covs1 = pd.DataFrame( data={ @@ -156,8 +155,8 @@ def test_scaling_multi_series(self): ), ) series_recovered2 = scaler.inverse_transform(series_tr2[0]) - self.assertTrue( - self.series1.static_covariates.equals(series_recovered2.static_covariates) + assert self.series1.static_covariates.equals( + series_recovered2.static_covariates ) np.testing.assert_almost_equal( @@ -167,20 +166,16 @@ def test_scaling_multi_series(self): ), ) series_recovered3 = scaler.inverse_transform(series_tr2[1]) - self.assertTrue( - self.series2.static_covariates.equals(series_recovered3.static_covariates) + assert self.series2.static_covariates.equals( + series_recovered3.static_covariates ) series_recovered_multi = scaler.inverse_transform(series_tr2) - self.assertTrue( - self.series1.static_covariates.equals( - series_recovered_multi[0].static_covariates - ) + assert self.series1.static_covariates.equals( + series_recovered_multi[0].static_covariates ) - self.assertTrue( - self.series2.static_covariates.equals( - series_recovered_multi[1].static_covariates - ) + assert self.series2.static_covariates.equals( + series_recovered_multi[1].static_covariates ) def helper_test_scaling(self, series, scaler, test_values): @@ -196,6 +191,4 @@ def helper_test_scaling(self, series, scaler, test_values): ) series_recovered = scaler.inverse_transform(series_tr) - self.assertTrue( - series.static_covariates.equals(series_recovered.static_covariates) - ) + assert series.static_covariates.equals(series_recovered.static_covariates) diff --git a/darts/tests/dataprocessing/transformers/test_window_transformations.py b/darts/tests/dataprocessing/transformers/test_window_transformations.py index 985355b085..65bc70d001 100644 --- a/darts/tests/dataprocessing/transformers/test_window_transformations.py +++ b/darts/tests/dataprocessing/transformers/test_window_transformations.py @@ -1,15 +1,15 @@ import itertools -import unittest import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.dataprocessing.pipeline import Pipeline from darts.dataprocessing.transformers import Mapper, WindowTransformer -class TimeSeriesWindowTransformTestCase(unittest.TestCase): +class TestTimeSeriesWindowTransform: times = pd.date_range("20130101", "20130110") series_from_values = TimeSeries.from_values( @@ -35,50 +35,50 @@ def test_ts_windowtransf_input_dictionary(self): Test that the forecasting window transformer dictionary input parameter is correctly formatted """ - with self.assertRaises(TypeError): + with pytest.raises(TypeError): window_transformations = None # None input self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = [] # empty list self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(KeyError): + with pytest.raises(KeyError): window_transformations = {} # empty dictionary self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = [1, 2, 3] # list of not dictionaries self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(KeyError): + with pytest.raises(KeyError): window_transformations = {"random_fn_name": "mean"} # no 'function' key self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): window_transformations = { "function": "wild_fn" } # not valid pandas built-in function self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = { "function": 1 } # not valid pandas built-in function nore callable self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = {"function": None} # None function value self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): window_transformations = { "function": "quantile", "window": [3], } # not enough mandatory arguments for quantile self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = { "function": "mean", "mode": "rolling", @@ -86,7 +86,7 @@ def test_ts_windowtransf_input_dictionary(self): } # negative window self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = { "function": "mean", "mode": "rolling", @@ -94,7 +94,7 @@ def test_ts_windowtransf_input_dictionary(self): } # None window self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = { "function": "mean", "mode": "rolling", @@ -102,7 +102,7 @@ def test_ts_windowtransf_input_dictionary(self): } # window list self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = { "function": "mean", "window": 3, @@ -111,7 +111,7 @@ def test_ts_windowtransf_input_dictionary(self): } # Negative step self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = { "function": "mean", "window": 3, @@ -119,7 +119,7 @@ def test_ts_windowtransf_input_dictionary(self): } # invalid mode self.series_univ_det.window_transform(transforms=window_transformations) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): window_transformations = { "function": "mean", "mode": "rolling", @@ -133,17 +133,13 @@ def test_ts_windowtransf_output_series(self): transforms = {"function": "sum", "mode": "rolling", "window": 1} transformed_ts = self.series_univ_det.window_transform(transforms=transforms) - self.assertEqual( - list(itertools.chain(*transformed_ts.values().tolist())), - list(itertools.chain(*self.series_univ_det.values().tolist())), - ) - self.assertEqual( - transformed_ts.components.to_list(), - [ - f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" - for comp in self.series_univ_det.components - ], + assert list(itertools.chain(*transformed_ts.values().tolist())) == list( + itertools.chain(*self.series_univ_det.values().tolist()) ) + assert transformed_ts.components.to_list() == [ + f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" + for comp in self.series_univ_det.components + ] # test customized function name that overwrites the pandas builtin transformation transforms = { @@ -153,13 +149,10 @@ def test_ts_windowtransf_output_series(self): "function_name": "customized_name", } transformed_ts = self.series_univ_det.window_transform(transforms=transforms) - self.assertEqual( - transformed_ts.components.to_list(), - [ - f"{transforms['mode']}_{transforms['function_name']}_{str(transforms['window'])}_{comp}" - for comp in self.series_univ_det.components - ], - ) + assert transformed_ts.components.to_list() == [ + f"{transforms['mode']}_{transforms['function_name']}_{str(transforms['window'])}_{comp}" + for comp in self.series_univ_det.components + ] del transforms["function_name"] # multivariate deterministic input @@ -167,25 +160,22 @@ def test_ts_windowtransf_output_series(self): transforms.update({"components": "0"}) transformed_ts = self.series_multi_det.window_transform(transforms=transforms) - self.assertEqual( - transformed_ts.components.to_list(), - [ - f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" - for comp in transforms["components"] - ], - ) + assert transformed_ts.components.to_list() == [ + f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" + for comp in transforms["components"] + ] transformed_ts = self.series_multi_det.window_transform( transforms=transforms, keep_non_transformed=True ) - self.assertEqual( - transformed_ts.components.to_list(), - [ + assert ( + transformed_ts.components.to_list() + == [ f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" for comp in transforms["components"] ] - + self.series_multi_det.components.to_list(), + + self.series_multi_det.components.to_list() ) # transform multiple components @@ -197,25 +187,22 @@ def test_ts_windowtransf_output_series(self): } transformed_ts = self.series_multi_det.window_transform(transforms=transforms) - self.assertEqual( - transformed_ts.components.to_list(), - [ - f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" - for comp in transforms["components"] - ], - ) + assert transformed_ts.components.to_list() == [ + f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" + for comp in transforms["components"] + ] transformed_ts = self.series_multi_det.window_transform( transforms=transforms, keep_non_transformed=True ) - self.assertEqual( - transformed_ts.components.to_list(), - [ + assert ( + transformed_ts.components.to_list() + == [ f"{transforms['mode']}_{transforms['function']}_{str(transforms['window'])}_{comp}" for comp in transforms["components"] ] - + self.series_multi_det.components.to_list(), + + self.series_multi_det.components.to_list() ) # multiple transformations @@ -230,31 +217,28 @@ def test_ts_windowtransf_output_series(self): ] transformed_ts = self.series_multi_det.window_transform(transforms=transforms) - self.assertEqual( - transformed_ts.components.to_list(), - [ - f"{transformation['mode']}_{transformation['function']}_{str(transformation['window'])}_{comp}" - for transformation in transforms - for comp in transformation["components"] - ], - ) + assert transformed_ts.components.to_list() == [ + f"{transformation['mode']}_{transformation['function']}_{str(transformation['window'])}_{comp}" + for transformation in transforms + for comp in transformation["components"] + ] transformed_ts = self.series_multi_det.window_transform( transforms=transforms, keep_non_transformed=True ) - self.assertEqual( - transformed_ts.components.to_list(), - [ + assert ( + transformed_ts.components.to_list() + == [ f"{transformation['mode']}_{transformation['function']}_{str(transformation['window'])}_{comp}" for transformation in transforms for comp in transformation["components"] ] - + self.series_multi_det.components.to_list(), + + self.series_multi_det.components.to_list() ) # multivariate probabilistic input transformed_ts = self.series_multi_prob.window_transform(transforms=transforms) - self.assertEqual(transformed_ts.n_samples, 2) + assert transformed_ts.n_samples == 2 def test_user_defined_function_behavior(self): def count_above_mean(array): @@ -274,20 +258,17 @@ def count_above_mean(array): np.array([0, 1, 1, 2, 2, 2, 2, 2, 2, 2]), columns=["rolling_udf_5_0"], ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series # test if a customized function name is provided transformation.update({"function_name": "count_above_mean"}) transformed_ts = self.target.window_transform( transformation, ) - self.assertEqual( - transformed_ts.components.to_list(), - [ - f"{transformation['mode']}_{transformation['function_name']}_{str(transformation['window'])}_{comp}" - for comp in self.target.components - ], - ) + assert transformed_ts.components.to_list() == [ + f"{transformation['mode']}_{transformation['function_name']}_{str(transformation['window'])}_{comp}" + for comp in self.target.components + ] def test_ts_windowtransf_output_nabehavior(self): window_transformations = { @@ -306,7 +287,7 @@ def test_ts_windowtransf_output_nabehavior(self): np.array([100, 3, 6, 9, 12, 15, 18, 21, 24, 27]), columns=["rolling_sum_3_2_0"], ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series # dropna transformed_ts = self.target.window_transform( @@ -317,7 +298,7 @@ def test_ts_windowtransf_output_nabehavior(self): np.array([3, 6, 9, 12, 15, 18, 21, 24, 27]), columns=["rolling_sum_3_2_0"], ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series # backfill na transformed_ts = self.target.window_transform( @@ -329,15 +310,15 @@ def test_ts_windowtransf_output_nabehavior(self): np.array([3, 3, 6, 9, 12, 15, 18, 21, 24, 27]), columns=["rolling_sum_3_2_0"], ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # uknonwn treat_na self.target.window_transform( window_transformations, treat_na="fillrnd", forecasting_safe=False ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # unauhtorized treat_na=bfill with forecasting_safe=True self.target.window_transform(window_transformations, treat_na="bfill") @@ -345,24 +326,22 @@ def test_tranformed_ts_index(self): # DateTimeIndex transformed_series = self.target.window_transform({"function": "sum"}) - self.assertEqual( - self.target._time_index.__class__, transformed_series._time_index.__class__ + assert ( + self.target._time_index.__class__ + == transformed_series._time_index.__class__ ) # length index should not change for default transformation configurations - self.assertEqual( - len(self.target._time_index), len(transformed_series._time_index) - ) + assert len(self.target._time_index) == len(transformed_series._time_index) # RangeIndex transformed_series = self.series_from_values.window_transform( {"function": "sum"} ) - self.assertEqual( - self.series_from_values._time_index.__class__, - transformed_series._time_index.__class__, + assert ( + self.series_from_values._time_index.__class__ + == transformed_series._time_index.__class__ ) - self.assertEqual( - len(self.series_from_values._time_index), - len(transformed_series._time_index), + assert len(self.series_from_values._time_index) == len( + transformed_series._time_index ) def test_include_current(self): @@ -381,7 +360,7 @@ def test_include_current(self): transformed_ts = self.target.window_transform( transformation, include_current=False ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series # shift the index transformation = {"function": "sum", "mode": "rolling", "window": 1} @@ -393,7 +372,7 @@ def test_include_current(self): transformed_ts = self.target.window_transform( transformation, include_current=False ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series transformation = [ {"function": "sum", "mode": "rolling", "window": 1, "closed": "left"}, @@ -420,7 +399,7 @@ def test_include_current(self): transformed_ts = self.target.window_transform( transformation, include_current=False ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series expected_transformed_series = TimeSeries.from_times_and_values( self.times, @@ -446,7 +425,7 @@ def test_include_current(self): forecasting_safe=False, treat_na="bfill", ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series transformation = [ { @@ -481,10 +460,10 @@ def test_include_current(self): transformed_ts = self.target.window_transform( transformation, include_current=False ) - self.assertEqual(transformed_ts, expected_transformed_series) + assert transformed_ts == expected_transformed_series -class WindowTransformerTestCase(unittest.TestCase): +class TestWindowTransformer: times = pd.date_range("20130101", "20130110") target = TimeSeries.from_times_and_values(times, np.array(range(1, 11))) @@ -522,15 +501,11 @@ def test_window_transformer_output(self): ) transformed_ts_list = transformer.transform(self.sequence_det) - self.assertEqual(len(transformed_ts_list), 2) - self.assertEqual(transformed_ts_list[0].n_components, 2) - self.assertEqual( - transformed_ts_list[0].n_timesteps, self.series_multi_det.n_timesteps - ) - self.assertEqual(transformed_ts_list[1].n_components, 5) - self.assertEqual( - transformed_ts_list[1].n_timesteps, self.series_multi_det.n_timesteps - ) + assert len(transformed_ts_list) == 2 + assert transformed_ts_list[0].n_components == 2 + assert transformed_ts_list[0].n_timesteps == self.series_multi_det.n_timesteps + assert transformed_ts_list[1].n_components == 5 + assert transformed_ts_list[1].n_timesteps == self.series_multi_det.n_timesteps def test_window_transformer_offset_parameter(self): """ @@ -558,10 +533,8 @@ def test_window_transformer_offset_parameter(self): np.testing.assert_equal( integer_transformed.values(), offset_transformed.values() ) - self.assertEqual( - offset_transformed.components[0], "rolling_mean_0 days 04:00:00_0" - ) - self.assertEqual(integer_transformed.components[0], "rolling_mean_4_0") + assert offset_transformed.components[0] == "rolling_mean_0 days 04:00:00_0" + assert integer_transformed.components[0] == "rolling_mean_4_0" invalid_parameters = base_parameters.copy() invalid_parameters.update({"window": pd.DateOffset(hours=4)}) @@ -569,7 +542,7 @@ def test_window_transformer_offset_parameter(self): transforms=invalid_parameters, ) # if pd.DateOffset, raise ValueError of non-fixed frequency - with self.assertRaises(ValueError): + with pytest.raises(ValueError): invalid_transformer.transform(self.target_hourly) def test_transformers_pipeline(self): @@ -605,4 +578,4 @@ def times_five(x): transformed_series = pipeline.fit_transform(series_1) - self.assertEqual(transformed_series, expected_transformed_series) + assert transformed_series == expected_transformed_series diff --git a/darts/tests/datasets/test_dataset_loaders.py b/darts/tests/datasets/test_dataset_loaders.py index c39858b7c5..8fd076b868 100644 --- a/darts/tests/datasets/test_dataset_loaders.py +++ b/darts/tests/datasets/test_dataset_loaders.py @@ -1,4 +1,6 @@ import os +import shutil +import tempfile import pytest @@ -37,7 +39,6 @@ DatasetLoaderMetadata, DatasetLoadingException, ) -from darts.tests.base_test_class import DartsBaseTestClass datasets = [ AirPassengersDataset, @@ -122,44 +123,62 @@ ) -class DatasetLoaderTestCase(DartsBaseTestClass): - def tearDown(self): - # we need to remove the cached datasets between each test - default_directory = DatasetLoader._DEFAULT_DIRECTORY - for f in os.listdir(default_directory): - os.remove(os.path.join(default_directory, f)) - os.rmdir(DatasetLoader._DEFAULT_DIRECTORY) +@pytest.fixture(scope="module", autouse=True) +def tmp_dir_dataset(): + """Configures the DataLoaders to use a temporary directory for storing the datasets, + and removes the path at the end of all tests in this module.""" + temp_work_dir = tempfile.mkdtemp(prefix="darts") + DatasetLoader._DEFAULT_DIRECTORY = temp_work_dir + yield temp_work_dir + shutil.rmtree(temp_work_dir) + +class TestDatasetLoader: @pytest.mark.slow - def test_ok_dataset(self): - for width, dataset_cls in zip(width_datasets, datasets): - dataset = dataset_cls() - ts: TimeSeries = dataset.load() - self.assertEqual(ts.width, width) - - def test_hash(self): - with self.assertRaises(DatasetLoadingException): + @pytest.mark.parametrize("dataset_config", zip(width_datasets, datasets)) + def test_ok_dataset(self, dataset_config, tmp_dir_dataset): + width, dataset_cls = dataset_config + dataset = dataset_cls() + assert dataset._DEFAULT_DIRECTORY == tmp_dir_dataset + ts: TimeSeries = dataset.load() + assert ts.width == width + assert os.path.exists(os.path.join(tmp_dir_dataset, dataset._metadata.name)) + + def test_hash(self, tmp_dir_dataset): + with pytest.raises(DatasetLoadingException): wrong_hash_dataset.load() + assert not os.path.exists( + os.path.join(tmp_dir_dataset, wrong_hash_dataset._metadata.name) + ) - def test_uri(self): - with self.assertRaises(DatasetLoadingException): + def test_uri(self, tmp_dir_dataset): + with pytest.raises(DatasetLoadingException): wrong_url_dataset.load() + assert not os.path.exists( + os.path.join(tmp_dir_dataset, wrong_hash_dataset._metadata.name) + ) - def test_zip_uri(self): - with self.assertRaises(DatasetLoadingException): + def test_zip_uri(self, tmp_dir_dataset): + with pytest.raises(DatasetLoadingException): wrong_zip_url_dataset.load() + assert not os.path.exists( + os.path.join(tmp_dir_dataset, wrong_hash_dataset._metadata.name) + ) - def test_pre_process_fn(self): - with self.assertRaises(DatasetLoadingException): + def test_pre_process_fn(self, tmp_dir_dataset): + with pytest.raises(DatasetLoadingException): no_pre_process_fn_dataset.load() + assert not os.path.exists( + os.path.join(tmp_dir_dataset, wrong_hash_dataset._metadata.name) + ) def test_multi_series_dataset(self): # processing _to_multi_series takes a long time. Test function with 5 cols. ts = ele_multi_series_dataset.load().pd_dataframe() ms = ElectricityDataset()._to_multi_series(ts) - self.assertEqual(len(ms), 5) - self.assertEqual(len(ms[0]), 105216) + assert len(ms) == 5 + assert len(ms[0]) == 105216 multi_series_datasets = [ UberTLCDataset, @@ -170,5 +189,5 @@ def test_multi_series_dataset(self): ] for dataset in multi_series_datasets: ms = dataset()._to_multi_series(ts) - self.assertEqual(len(ms), 5) - self.assertEqual(len(ms[0]), len(ts.index)) + assert len(ms) == 5 + assert len(ms[0]) == len(ts.index) diff --git a/darts/tests/datasets/test_datasets.py b/darts/tests/datasets/test_datasets.py index 7a7c4b8749..24260f337d 100644 --- a/darts/tests/datasets/test_datasets.py +++ b/darts/tests/datasets/test_datasets.py @@ -1,9 +1,9 @@ import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.timeseries_generation import gaussian_timeseries logger = get_logger(__name__) @@ -35,7 +35,7 @@ if TORCH_AVAILABLE: - class DatasetTestCase(DartsBaseTestClass): + class TestDataset: target1 = gaussian_timeseries(length=100).with_static_covariates( pd.Series([0, 1], index=["st1", "st2"]) ) @@ -62,7 +62,7 @@ def _assert_eq(self, lefts: tuple, rights: tuple): if isinstance(left, (pd.Series, pd.DataFrame)): assert left.equals(right) elif isinstance(left, np.ndarray): - assert np.array_equal(left, right) + np.testing.assert_array_equal(left, right) elif isinstance(left, (list, TimeSeries)): assert left == right else: @@ -85,7 +85,7 @@ def test_past_covariates_inference_dataset(self): self._assert_eq(ds[1][1:], (None, None, self.cov_st2, self.target2)) # fail if covariates do not have same size - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = PastCovariatesInferenceDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -127,7 +127,7 @@ def test_past_covariates_inference_dataset(self): ) # should fail if covariates are too short - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] # Should return correct values when covariates is long enough @@ -143,7 +143,7 @@ def test_past_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][1], long_cov.values()[-60:-50]) np.testing.assert_almost_equal(ds[0][2], long_cov.values()[-50:-30]) np.testing.assert_almost_equal(ds[0][3], self.cov_st2) - self.assertEqual(ds[0][4], target) + assert ds[0][4] == target # Should also work for integer-indexed series target = TimeSeries.from_times_and_values( @@ -165,7 +165,7 @@ def test_past_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][1], covariate.values()[20:30]) np.testing.assert_almost_equal(ds[0][2], covariate.values()[30:40]) np.testing.assert_almost_equal(ds[0][3], self.cov_st2) - self.assertEqual(ds[0][4], target) + assert ds[0][4] == target def test_future_covariates_inference_dataset(self): # one target series @@ -184,7 +184,7 @@ def test_future_covariates_inference_dataset(self): self._assert_eq(ds[1][1:], (None, self.cov_st2, self.target2)) # fail if covariates do not have same size - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = FutureCovariatesInferenceDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -210,7 +210,7 @@ def test_future_covariates_inference_dataset(self): ) # should fail if covariates are too short - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] # Should return correct values when covariates is long enough @@ -221,7 +221,7 @@ def test_future_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][0], target.values()[-10:]) np.testing.assert_almost_equal(ds[0][1], long_cov.values()[-50:-20]) np.testing.assert_almost_equal(ds[0][2], self.cov_st2) - self.assertEqual(ds[0][3], target) + assert ds[0][3] == target # Should also work for integer-indexed series target = TimeSeries.from_times_and_values( @@ -238,7 +238,7 @@ def test_future_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][0], target.values()[-10:]) np.testing.assert_almost_equal(ds[0][1], covariate.values()[30:50]) np.testing.assert_almost_equal(ds[0][2], self.cov_st2) - self.assertEqual(ds[0][3], target) + assert ds[0][3] == target def test_dual_covariates_inference_dataset(self): # one target series @@ -257,7 +257,7 @@ def test_dual_covariates_inference_dataset(self): self._assert_eq(ds[1][1:], (None, None, self.cov_st2, self.target2)) # fail if covariates do not have same size - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = DualCovariatesInferenceDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -287,7 +287,7 @@ def test_dual_covariates_inference_dataset(self): ) # should fail if covariates are too short - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] # Should return correct values when covariates is long enough @@ -303,7 +303,7 @@ def test_dual_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][1], long_cov.values()[-60:-50]) np.testing.assert_almost_equal(ds[0][2], long_cov.values()[-50:-20]) np.testing.assert_almost_equal(ds[0][3], self.cov_st2) - self.assertEqual(ds[0][4], target) + assert ds[0][4] == target # Should also work for integer-indexed series target = TimeSeries.from_times_and_values( @@ -325,7 +325,7 @@ def test_dual_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][1], covariate.values()[20:30]) np.testing.assert_almost_equal(ds[0][2], covariate.values()[30:50]) np.testing.assert_almost_equal(ds[0][3], self.cov_st2) - self.assertEqual(ds[0][4], target) + assert ds[0][4] == target def test_mixed_covariates_inference_dataset(self): # With future past covariates: @@ -357,7 +357,7 @@ def test_mixed_covariates_inference_dataset(self): ) # should fail if future covariates are too short - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] # Should return correct values when covariates is long enough @@ -378,7 +378,7 @@ def test_mixed_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][3], future_cov.values()[-50:-20]) np.testing.assert_almost_equal(ds[0][4], long_past_cov.values()[-50:-30]) np.testing.assert_almost_equal(ds[0][5], self.cov_st2) - self.assertEqual(ds[0][6], target) + assert ds[0][6] == target # Should also work for integer-indexed series target = TimeSeries.from_times_and_values( @@ -406,7 +406,7 @@ def test_mixed_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][3], future_cov.values()[20:40]) np.testing.assert_almost_equal(ds[0][4], past_cov.values()[30:40]) np.testing.assert_almost_equal(ds[0][5], self.cov_st2) - self.assertEqual(ds[0][6], target) + assert ds[0][6] == target def test_split_covariates_inference_dataset(self): # With future past covariates: @@ -438,7 +438,7 @@ def test_split_covariates_inference_dataset(self): ) # should fail if future covariates are too short - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] # Should return correct values when covariates is long enough @@ -458,7 +458,7 @@ def test_split_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][2], future_cov.values()[-50:-20]) np.testing.assert_almost_equal(ds[0][3], long_past_cov.values()[-50:-30]) np.testing.assert_almost_equal(ds[0][4], self.cov_st2) - self.assertEqual(ds[0][5], target) + assert ds[0][5] == target # Should also work for integer-indexed series target = TimeSeries.from_times_and_values( @@ -485,7 +485,7 @@ def test_split_covariates_inference_dataset(self): np.testing.assert_almost_equal(ds[0][2], future_cov.values()[20:40]) np.testing.assert_almost_equal(ds[0][3], past_cov.values()[30:40]) np.testing.assert_almost_equal(ds[0][4], self.cov_st2) - self.assertEqual(ds[0][5], target) + assert ds[0][5] == target def test_past_covariates_sequential_dataset(self): # one target series @@ -494,7 +494,7 @@ def test_past_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - self.assertEqual(len(ds), 81) + assert len(ds) == 81 self._assert_eq( ds[5], (self.target1[75:85], None, self.cov_st1, self.target1[85:95]) ) @@ -505,7 +505,7 @@ def test_past_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - self.assertEqual(len(ds), 262) + assert len(ds) == 262 self._assert_eq( ds[5], (self.target1[75:85], None, self.cov_st1, self.target1[85:95]) ) @@ -521,7 +521,7 @@ def test_past_covariates_sequential_dataset(self): output_chunk_length=10, max_samples_per_ts=50, ) - self.assertEqual(len(ds), 100) + assert len(ds) == 100 self._assert_eq( ds[5], (self.target1[75:85], None, self.cov_st1, self.target1[85:95]) ) @@ -531,7 +531,7 @@ def test_past_covariates_sequential_dataset(self): ) # two targets and one covariate - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = PastCovariatesSequentialDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -575,7 +575,7 @@ def test_past_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[5] # the same should fail when series are integer-indexed @@ -591,7 +591,7 @@ def test_past_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[5] # we should get the correct covariate slice even when target and covariates are not aligned @@ -635,7 +635,7 @@ def test_future_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - self.assertEqual(len(ds), 81) + assert len(ds) == 81 self._assert_eq( ds[5], (self.target1[75:85], None, self.cov_st1, self.target1[85:95]) ) @@ -646,7 +646,7 @@ def test_future_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - self.assertEqual(len(ds), 262) + assert len(ds) == 262 self._assert_eq( ds[5], (self.target1[75:85], None, self.cov_st1, self.target1[85:95]) ) @@ -662,7 +662,7 @@ def test_future_covariates_sequential_dataset(self): output_chunk_length=10, max_samples_per_ts=50, ) - self.assertEqual(len(ds), 100) + assert len(ds) == 100 self._assert_eq( ds[5], (self.target1[75:85], None, self.cov_st1, self.target1[85:95]) ) @@ -672,7 +672,7 @@ def test_future_covariates_sequential_dataset(self): ) # two targets and one covariate - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = FutureCovariatesSequentialDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -739,7 +739,7 @@ def test_future_covariates_sequential_dataset(self): output_chunk_length=2, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] def test_dual_covariates_sequential_dataset(self): @@ -751,7 +751,7 @@ def test_dual_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - self.assertEqual(len(ds), 81) + assert len(ds) == 81 self._assert_eq( ds[5], (self.target1[75:85], None, None, self.cov_st1, self.target1[85:95]), @@ -763,7 +763,7 @@ def test_dual_covariates_sequential_dataset(self): input_chunk_length=10, output_chunk_length=10, ) - self.assertEqual(len(ds), 262) + assert len(ds) == 262 self._assert_eq( ds[5], (self.target1[75:85], None, None, self.cov_st1, self.target1[85:95]), @@ -786,7 +786,7 @@ def test_dual_covariates_sequential_dataset(self): output_chunk_length=10, max_samples_per_ts=50, ) - self.assertEqual(len(ds), 100) + assert len(ds) == 100 self._assert_eq( ds[5], (self.target1[75:85], None, None, self.cov_st1, self.target1[85:95]), @@ -803,7 +803,7 @@ def test_dual_covariates_sequential_dataset(self): ) # two targets and one covariate - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = DualCovariatesSequentialDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -873,7 +873,7 @@ def test_dual_covariates_sequential_dataset(self): output_chunk_length=2, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] def test_past_covariates_shifted_dataset(self): @@ -881,7 +881,7 @@ def test_past_covariates_shifted_dataset(self): ds = PastCovariatesShiftedDataset( target_series=self.target1, length=10, shift=5 ) - self.assertEqual(len(ds), 86) + assert len(ds) == 86 self._assert_eq( ds[5], (self.target1[80:90], None, self.cov_st1, self.target1[85:95]) ) @@ -890,7 +890,7 @@ def test_past_covariates_shifted_dataset(self): ds = PastCovariatesShiftedDataset( target_series=[self.target1, self.target2], length=10, shift=5 ) - self.assertEqual(len(ds), 272) + assert len(ds) == 272 self._assert_eq( ds[5], (self.target1[80:90], None, self.cov_st1, self.target1[85:95]) ) @@ -906,7 +906,7 @@ def test_past_covariates_shifted_dataset(self): shift=5, max_samples_per_ts=50, ) - self.assertEqual(len(ds), 100) + assert len(ds) == 100 self._assert_eq( ds[5], (self.target1[80:90], None, self.cov_st1, self.target1[85:95]) ) @@ -916,7 +916,7 @@ def test_past_covariates_shifted_dataset(self): ) # two targets and one covariate - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = PastCovariatesShiftedDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -985,7 +985,7 @@ def test_past_covariates_shifted_dataset(self): ds = PastCovariatesShiftedDataset( target_series=[target1], covariates=[cov1], length=3, shift=2 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] def test_future_covariates_shifted_dataset(self): @@ -993,7 +993,7 @@ def test_future_covariates_shifted_dataset(self): ds = FutureCovariatesShiftedDataset( target_series=self.target1, length=10, shift=5 ) - self.assertEqual(len(ds), 86) + assert len(ds) == 86 self._assert_eq( ds[5], (self.target1[80:90], None, self.cov_st1, self.target1[85:95]) ) @@ -1002,7 +1002,7 @@ def test_future_covariates_shifted_dataset(self): ds = FutureCovariatesShiftedDataset( target_series=[self.target1, self.target2], length=10, shift=5 ) - self.assertEqual(len(ds), 272) + assert len(ds) == 272 self._assert_eq( ds[5], (self.target1[80:90], None, self.cov_st1, self.target1[85:95]) ) @@ -1018,7 +1018,7 @@ def test_future_covariates_shifted_dataset(self): shift=5, max_samples_per_ts=50, ) - self.assertEqual(len(ds), 100) + assert len(ds) == 100 self._assert_eq( ds[5], (self.target1[80:90], None, self.cov_st1, self.target1[85:95]) ) @@ -1028,7 +1028,7 @@ def test_future_covariates_shifted_dataset(self): ) # two targets and one covariate - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = FutureCovariatesShiftedDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -1097,7 +1097,7 @@ def test_future_covariates_shifted_dataset(self): ds = FutureCovariatesShiftedDataset( target_series=[target1], covariates=[cov1], length=3, shift=2 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] def test_dual_covariates_shifted_dataset(self): @@ -1105,7 +1105,7 @@ def test_dual_covariates_shifted_dataset(self): ds = DualCovariatesShiftedDataset( target_series=self.target1, length=10, shift=5 ) - self.assertEqual(len(ds), 86) + assert len(ds) == 86 self._assert_eq( ds[5], (self.target1[80:90], None, None, self.cov_st1, self.target1[85:95]), @@ -1115,7 +1115,7 @@ def test_dual_covariates_shifted_dataset(self): ds = DualCovariatesShiftedDataset( target_series=[self.target1, self.target2], length=10, shift=5 ) - self.assertEqual(len(ds), 272) + assert len(ds) == 272 self._assert_eq( ds[5], (self.target1[80:90], None, None, self.cov_st1, self.target1[85:95]), @@ -1138,7 +1138,7 @@ def test_dual_covariates_shifted_dataset(self): shift=5, max_samples_per_ts=50, ) - self.assertEqual(len(ds), 100) + assert len(ds) == 100 self._assert_eq( ds[5], (self.target1[80:90], None, None, self.cov_st1, self.target1[85:95]), @@ -1155,7 +1155,7 @@ def test_dual_covariates_shifted_dataset(self): ) # two targets and one covariate - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = DualCovariatesShiftedDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -1228,7 +1228,7 @@ def test_dual_covariates_shifted_dataset(self): ds = DualCovariatesShiftedDataset( target_series=[target1], covariates=[cov1], length=3, shift=2 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = ds[0] def test_horizon_based_dataset(self): @@ -1239,7 +1239,7 @@ def test_horizon_based_dataset(self): lh=(1, 3), lookback=2, ) - self.assertEqual(len(ds), 20) + assert len(ds) == 20 self._assert_eq( ds[5], (self.target1[65:85], None, self.cov_st1, self.target1[85:95]) ) @@ -1251,7 +1251,7 @@ def test_horizon_based_dataset(self): lh=(1, 3), lookback=2, ) - self.assertEqual(len(ds), 40) + assert len(ds) == 40 self._assert_eq( ds[5], (self.target1[65:85], None, self.cov_st1, self.target1[85:95]) ) @@ -1261,7 +1261,7 @@ def test_horizon_based_dataset(self): ) # two targets and one covariate - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ds = HorizonBasedDataset( target_series=[self.target1, self.target2], covariates=[self.cov1] ) @@ -1303,7 +1303,7 @@ def test_get_matching_index(self): times1, np.random.randn(len(times1)) ).with_static_covariates(self.cov_st2_df) cov = TimeSeries.from_times_and_values(times2, np.random.randn(len(times2))) - self.assertEqual(_get_matching_index(target, cov, idx=15), 5) + assert _get_matching_index(target, cov, idx=15) == 5 # check non-dividable freq times1 = pd.date_range(start="20100101", end="20120101", freq="M") @@ -1312,7 +1312,7 @@ def test_get_matching_index(self): times1, np.random.randn(len(times1)) ).with_static_covariates(self.cov_st2_df) cov = TimeSeries.from_times_and_values(times2, np.random.randn(len(times2))) - self.assertEqual(_get_matching_index(target, cov, idx=15), 15 - 7) + assert _get_matching_index(target, cov, idx=15) == 15 - 7 # check integer-indexed series times2 = pd.RangeIndex(start=10, stop=90) @@ -1320,4 +1320,4 @@ def test_get_matching_index(self): np.random.randn(100) ).with_static_covariates(self.cov_st2_df) cov = TimeSeries.from_times_and_values(times2, np.random.randn(len(times2))) - self.assertEqual(_get_matching_index(target, cov, idx=15), 5) + assert _get_matching_index(target, cov, idx=15) == 5 diff --git a/darts/tests/explainability/test_shap_explainer.py b/darts/tests/explainability/test_shap_explainer.py index 822ffa1024..e526d1b81b 100644 --- a/darts/tests/explainability/test_shap_explainer.py +++ b/darts/tests/explainability/test_shap_explainer.py @@ -24,13 +24,12 @@ RegressionModel, XGBModel, ) -from darts.tests.base_test_class import DartsBaseTestClass lgbm_available = not isinstance(LightGBMModel, NotImportedModule) cb_available = not isinstance(CatBoostModel, NotImportedModule) -class ShapExplainerTestCase(DartsBaseTestClass): +class TestShapExplainer: np.random.seed(42) scaler = MinMaxScaler(feature_range=(-1, 1)) @@ -143,13 +142,13 @@ def test_creation(self): output_chunk_length=4, add_encoders=self.add_encoders, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ShapExplainer(m, self.target_ts, self.past_cov_ts, self.fut_cov_ts) # Model should be a RegressionModel m = ExponentialSmoothing() m.fit(self.target_ts["price"]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ShapExplainer(m) # For now, multi_models=False not allowed @@ -157,7 +156,7 @@ def test_creation(self): m.fit( series=self.target_ts, ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ShapExplainer( m, self.target_ts, @@ -178,7 +177,7 @@ def test_creation(self): ) # Should have the same number of target, past and futures in the respective lists - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ShapExplainer( m, [self.target_ts, self.target_ts], @@ -187,21 +186,21 @@ def test_creation(self): ) # Missing a future covariate if you choose to use a new background - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ShapExplainer( m, self.target_ts, background_past_covariates=self.past_cov_ts ) # Missing a past covariate if you choose to use a new background - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ShapExplainer( m, self.target_ts, background_future_covariates=self.fut_cov_ts ) # Good type of explainers shap_explain = ShapExplainer(m) - self.assertTrue( - isinstance(shap_explain.explainers.explainers[0][0], shap.explainers.Tree) + assert isinstance( + shap_explain.explainers.explainers[0][0], shap.explainers.Tree ) # Linear model - also not a MultiOutputRegressor @@ -217,9 +216,7 @@ def test_creation(self): future_covariates=self.fut_cov_ts, ) shap_explain = ShapExplainer(m) - self.assertTrue( - isinstance(shap_explain.explainers.explainers, shap.explainers.Linear) - ) + assert isinstance(shap_explain.explainers.explainers, shap.explainers.Linear) # ExtraTreesRegressor - also not a MultiOutputRegressor m = RegressionModel( @@ -235,9 +232,7 @@ def test_creation(self): future_covariates=self.fut_cov_ts, ) shap_explain = ShapExplainer(m) - self.assertTrue( - isinstance(shap_explain.explainers.explainers, shap.explainers.Tree) - ) + assert isinstance(shap_explain.explainers.explainers, shap.explainers.Tree) # No past or future covariates m = LinearRegressionModel( @@ -249,9 +244,7 @@ def test_creation(self): ) shap_explain = ShapExplainer(m) - self.assertTrue( - isinstance(shap_explain.explainers.explainers, shap.explainers.Linear) - ) + assert isinstance(shap_explain.explainers.explainers, shap.explainers.Linear) # CatBoost model_cls = CatBoostModel if cb_available else XGBModel @@ -267,12 +260,12 @@ def test_creation(self): future_covariates=self.fut_cov_ts, ) shap_explain = ShapExplainer(m) - self.assertTrue( - isinstance(shap_explain.explainers.explainers[0][0], shap.explainers.Tree) + assert isinstance( + shap_explain.explainers.explainers[0][0], shap.explainers.Tree ) # Bad choice of shap explainer - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ShapExplainer(m, shap_method="bad_choice") def test_explain(self): @@ -291,45 +284,45 @@ def test_explain(self): ) shap_explain = ShapExplainer(m) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = shap_explain.explain(horizons=[1, 5]) # horizon > output_chunk_length - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = shap_explain.explain( horizons=[1, 2], target_components=["test"] ) # wrong name results = shap_explain.explain() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_explanation(horizon=5, component="price") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_feature_values(horizon=5, component="price") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_shap_explanation_object(horizon=5, component="price") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_explanation(horizon=1, component="test") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_feature_values(horizon=1, component="test") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_shap_explanation_object(horizon=1, component="test") results = shap_explain.explain(horizons=[1, 3], target_components=["power"]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_explanation(horizon=2, component="power") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_feature_values(horizon=2, component="power") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_shap_explanation_object(horizon=2, component="power") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_explanation(horizon=1, component="test") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_feature_values(horizon=1, component="test") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_shap_explanation_object(horizon=1, component="test") explanation = results.get_explanation(horizon=1, component="power") - self.assertEqual(len(explanation), 537) + assert len(explanation) == 537 feature_vals = results.get_feature_values(horizon=1, component="power") - self.assertEqual(len(feature_vals), 537) + assert len(feature_vals) == 537 # list of foregrounds: encoders have to be corrected first. results = shap_explain.explain( @@ -338,9 +331,9 @@ def test_explain(self): foreground_future_covariates=[self.fut_cov_ts, self.fut_cov_ts[:40]], ) ts_res = results.get_explanation(horizon=2, component="power") - self.assertEqual(len(ts_res), 2) + assert len(ts_res) == 2 feature_vals = results.get_feature_values(horizon=2, component="power") - self.assertEqual(len(feature_vals), 2) + assert len(feature_vals) == 2 # explain with a new foreground, minimum required. We should obtain one # timeseries with only one time element @@ -351,23 +344,23 @@ def test_explain(self): ) ts_res = results.get_explanation(horizon=2, component="power") - self.assertTrue(len(ts_res) == 1) - self.assertTrue(ts_res.time_index[-1] == pd.Timestamp(2014, 6, 5)) + assert len(ts_res) == 1 + assert ts_res.time_index[-1] == pd.Timestamp(2014, 6, 5) feature_vals = results.get_feature_values(horizon=2, component="power") - self.assertTrue(len(feature_vals) == 1) - self.assertTrue(feature_vals.time_index[-1] == pd.Timestamp(2014, 6, 5)) + assert len(feature_vals) == 1 + assert feature_vals.time_index[-1] == pd.Timestamp(2014, 6, 5) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_explanation(horizon=5, component="price") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_feature_values(horizon=5, component="price") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_explanation(horizon=1, component="test") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): results.get_feature_values(horizon=1, component="test") # right instance - self.assertTrue(isinstance(results, ShapExplainabilityResult)) + assert isinstance(results, ShapExplainabilityResult) components_list = [ "price_target_lag-4", @@ -415,13 +408,10 @@ def test_explain(self): results = shap_explain.explain() # all the features explained are here, in the right order - self.assertTrue( - [ - results.get_explanation(i, "price").components.to_list() - == components_list - for i in range(1, 5) - ] - ) + assert [ + results.get_explanation(i, "price").components.to_list() == components_list + for i in range(1, 5) + ] # No past or future covariates m = LinearRegressionModel( @@ -433,7 +423,7 @@ def test_explain(self): ) shap_explain = ShapExplainer(m) - self.assertTrue(isinstance(shap_explain.explain(), ShapExplainabilityResult)) + assert isinstance(shap_explain.explain(), ShapExplainabilityResult) def test_explain_with_lags_future_covariates_series_of_same_length_as_target(self): model_cls = LightGBMModel if lgbm_available else XGBModel @@ -460,9 +450,8 @@ def test_explain_with_lags_future_covariates_series_of_same_length_as_target(sel # The fut_cov_ts have the same length as the target_ts. Hence, if we pass lags_future_covariates this means # that the last prediction can be made max(lags_future_covariates) time periods before the end of the # series (in this case 2 time periods). - self.assertEqual( - explanation.end_time(), - self.target_ts.end_time() - relativedelta(days=2), + assert explanation.end_time() == self.target_ts.end_time() - relativedelta( + days=2 ) def test_explain_with_lags_future_covariates_series_extending_into_future(self): @@ -497,7 +486,7 @@ def test_explain_with_lags_future_covariates_series_extending_into_future(self): # The fut_cov_ts extends further into the future than the target_ts. Hence, at prediction time we know the # values of lagged future covariates and we thus no longer expect the end_time() of the explanation # TimeSeries to differ from the end_time() of the target TimeSeries - self.assertEqual(explanation.end_time(), self.target_ts.end_time()) + assert explanation.end_time() == self.target_ts.end_time() def test_explain_with_lags_covariates_series_older_timestamps_than_target(self): # Constructing covariates TimeSeries with older timestamps than target @@ -533,7 +522,7 @@ def test_explain_with_lags_covariates_series_older_timestamps_than_target(self): # The covariates series (past and future) start two time periods earlier than the target series. This in # combination with the LightGBM configuration (lags=None and 'largest' covariates lags equal to -2) means # that at the start of the target series we have sufficient information to explain the prediction. - self.assertEqual(explanation.start_time(), self.target_ts.start_time()) + assert explanation.start_time() == self.target_ts.start_time() def test_plot(self): model_cls = LightGBMModel if lgbm_available else XGBModel @@ -553,7 +542,7 @@ def test_plot(self): shap_explain = ShapExplainer(m_0) # We need at least 5 points for force_plot - with self.assertRaises(ValueError): + with pytest.raises(ValueError): shap_explain.force_plot_from_ts( self.target_ts[100:104], self.past_cov_ts[100:104], @@ -569,11 +558,11 @@ def test_plot(self): 2, "power", ) - self.assertTrue(isinstance(fplot, shap.plots._force.BaseVisualizer)) + assert isinstance(fplot, shap.plots._force.BaseVisualizer) plt.close() # no component name -> multivariate error - with self.assertRaises(ValueError): + with pytest.raises(ValueError): shap_explain.force_plot_from_ts( self.target_ts[100:108], self.past_cov_ts[100:108], @@ -582,7 +571,7 @@ def test_plot(self): ) # fake component - with self.assertRaises(ValueError): + with pytest.raises(ValueError): shap_explain.force_plot_from_ts( self.target_ts[100:108], self.past_cov_ts[100:108], @@ -592,7 +581,7 @@ def test_plot(self): ) # horizon 0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): shap_explain.force_plot_from_ts( self.target_ts[100:108], self.past_cov_ts[100:108], @@ -602,13 +591,13 @@ def test_plot(self): ) # Wrong component name - with self.assertRaises(ValueError): + with pytest.raises(ValueError): shap_explain.summary_plot(horizons=[1], target_components=["test"]) # Wrong horizon - with self.assertRaises(ValueError): + with pytest.raises(ValueError): shap_explain.summary_plot(horizons=[0], target_components=["test"]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): shap_explain.summary_plot(horizons=[10], target_components=["test"]) # No past or future covariates @@ -626,7 +615,7 @@ def test_plot(self): horizon=1, target_component="power", ) - self.assertTrue(isinstance(fplot, shap.plots._force.BaseVisualizer)) + assert isinstance(fplot, shap.plots._force.BaseVisualizer) plt.close() def test_feature_values_align_with_input(self): @@ -676,11 +665,11 @@ def test_feature_values_align_with_raw_output_shap(self): ).data assert_array_equal(feature_values.values(), comparison) - self.assertEqual( - feature_values.values().shape, - explanation_results.get_explanation(horizon=1, component="price") + assert ( + feature_values.values().shape + == explanation_results.get_explanation(horizon=1, component="price") .values() - .shape, + .shape ), "The shape of the feature values should be the same as the shap values" def test_shap_explanation_object_validity(self): @@ -699,7 +688,7 @@ def test_shap_explanation_object_validity(self): shap_explain = ShapExplainer(model) explanation_results = shap_explain.explain() - self.assertIsInstance( + assert isinstance( explanation_results.get_shap_explanation_object( horizon=1, component="power" ), @@ -803,7 +792,7 @@ def test_shapley_multiple_series_with_different_static_covs(self): ) explanation_results = shap_explain.explain() - self.assertTrue(len(explanation_results.feature_values) == 2) + assert len(explanation_results.feature_values) == 2 # model trained on multiple series will take column names of first series -> even though # static covs have different names, the output will show the same names diff --git a/darts/tests/explainability/test_tft_explainer.py b/darts/tests/explainability/test_tft_explainer.py index 8790196656..7b16e88bd5 100644 --- a/darts/tests/explainability/test_tft_explainer.py +++ b/darts/tests/explainability/test_tft_explainer.py @@ -8,7 +8,7 @@ from darts import TimeSeries from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -25,7 +25,7 @@ if TORCH_AVAILABLE: - class TFTExplainerTestCase(DartsBaseTestClass): + class TestTFTExplainer: freq = "MS" series_lin_pos = tg.linear_timeseries( length=10, freq=freq diff --git a/darts/tests/metrics/test_metrics.py b/darts/tests/metrics/test_metrics.py index b0a3e93135..18b23138f4 100644 --- a/darts/tests/metrics/test_metrics.py +++ b/darts/tests/metrics/test_metrics.py @@ -1,12 +1,12 @@ import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.metrics import metrics -from darts.tests.base_test_class import DartsBaseTestClass -class MetricsTestCase(DartsBaseTestClass): +class TestMetrics: np.random.seed(42) pd_train = pd.Series( np.sin(np.pi * np.arange(31) / 4) + 1, @@ -53,43 +53,48 @@ class MetricsTestCase(DartsBaseTestClass): ) def test_zero(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mape(self.series1, self.series1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.smape(self.series1, self.series1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mape(self.series12, self.series12) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.smape(self.series12, self.series12) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.ope( self.series1 - self.series1.pd_series().mean(), self.series1 - self.series1.pd_series().mean(), ) def test_same(self): - self.assertEqual(metrics.mape(self.series1 + 1, self.series1 + 1), 0) - self.assertEqual(metrics.smape(self.series1 + 1, self.series1 + 1), 0) - self.assertEqual( - metrics.mase(self.series1 + 1, self.series1 + 1, self.series_train, 1), 0 - ) - self.assertEqual(metrics.marre(self.series1 + 1, self.series1 + 1), 0) - self.assertEqual(metrics.r2_score(self.series1 + 1, self.series1 + 1), 1) - self.assertEqual(metrics.ope(self.series1 + 1, self.series1 + 1), 0) - self.assertEqual( - metrics.rho_risk(self.series1 + 1, self.series11_stochastic + 1), 0 + assert metrics.mape(self.series1 + 1, self.series1 + 1) == 0 + assert metrics.smape(self.series1 + 1, self.series1 + 1) == 0 + assert ( + metrics.mase(self.series1 + 1, self.series1 + 1, self.series_train, 1) == 0 ) + assert metrics.marre(self.series1 + 1, self.series1 + 1) == 0 + assert metrics.r2_score(self.series1 + 1, self.series1 + 1) == 1 + assert metrics.ope(self.series1 + 1, self.series1 + 1) == 0 + assert metrics.rho_risk(self.series1 + 1, self.series11_stochastic + 1) == 0 def helper_test_shape_equality(self, metric): - self.assertAlmostEqual( - metric(self.series12, self.series21), - metric( - self.series1.append(self.series2b), self.series2.append(self.series1b) - ), + assert ( + round( + abs( + metric(self.series12, self.series21) + - metric( + self.series1.append(self.series2b), + self.series2.append(self.series1b), + ) + ), + 7, + ) + == 0 ) def get_test_cases(self, **kwargs): @@ -116,11 +121,20 @@ def helper_test_multivariate_duplication_equality(self, metric, **kwargs): s11 = s1.stack(s1) s22 = s2.stack(s2) # default intra - self.assertAlmostEqual(metric(s1, s2, **kwargs), metric(s11, s22, **kwargs)) + assert ( + round(abs(metric(s1, s2, **kwargs) - metric(s11, s22, **kwargs)), 7) + == 0 + ) # custom intra - self.assertAlmostEqual( - metric(s1, s2, **kwargs, reduction=(lambda x: x[0])), - metric(s11, s22, **kwargs, reduction=(lambda x: x[0])), + assert ( + round( + abs( + metric(s1, s2, **kwargs, reduction=(lambda x: x[0])) + - metric(s11, s22, **kwargs, reduction=(lambda x: x[0])) + ), + 7, + ) + == 0 ) def helper_test_multiple_ts_duplication_equality(self, metric, **kwargs): @@ -136,9 +150,23 @@ def helper_test_multiple_ts_duplication_equality(self, metric, **kwargs): ) # custom intra and inter - self.assertAlmostEqual( - metric(s1, s2, **kwargs, reduction=np.mean, inter_reduction=np.max), - metric(s11, s22, **kwargs, reduction=np.mean, inter_reduction=np.max), + assert ( + round( + abs( + metric( + s1, s2, **kwargs, reduction=np.mean, inter_reduction=np.max + ) + - metric( + s11, + s22, + **kwargs, + reduction=np.mean, + inter_reduction=np.max + ) + ), + 7, + ) + == 0 ) def helper_test_nan(self, metric, **kwargs): @@ -150,7 +178,7 @@ def helper_test_nan(self, metric, **kwargs): nan_s1 = s1.copy() nan_s1._xa.values[-1, :, :] = np.nan nan_metric = metric(nan_s1 + 1, s2) - self.assertEqual(non_nan_metric, nan_metric) + assert non_nan_metric == nan_metric # multivariate + multi-TS s11 = [s1.stack(s1)] * 2 @@ -160,15 +188,14 @@ def helper_test_nan(self, metric, **kwargs): for s in nan_s11: s._xa.values[-1, :, :] = np.nan nan_metric = metric([s + 1 for s in nan_s11], s22) - self.assertEqual(non_nan_metric, nan_metric) + assert non_nan_metric == nan_metric def test_r2(self): from sklearn.metrics import r2_score - self.assertEqual(metrics.r2_score(self.series1, self.series0), 0) - self.assertEqual( - metrics.r2_score(self.series1, self.series2), - r2_score(self.series1.values(), self.series2.values()), + assert metrics.r2_score(self.series1, self.series0) == 0 + assert metrics.r2_score(self.series1, self.series2) == r2_score( + self.series1.values(), self.series2.values() ) self.helper_test_multivariate_duplication_equality(metrics.r2_score) @@ -176,16 +203,22 @@ def test_r2(self): self.helper_test_nan(metrics.r2_score) def test_marre(self): - self.assertAlmostEqual( - metrics.marre(self.series1, self.series2), - metrics.marre(self.series1 + 100, self.series2 + 100), + assert ( + round( + abs( + metrics.marre(self.series1, self.series2) + - metrics.marre(self.series1 + 100, self.series2 + 100) + ), + 7, + ) + == 0 ) self.helper_test_multivariate_duplication_equality(metrics.marre) self.helper_test_multiple_ts_duplication_equality(metrics.marre) self.helper_test_nan(metrics.marre) def test_season(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mase(self.series3, self.series3 * 1.3, self.series_train, 8) def test_mse(self): @@ -200,13 +233,22 @@ def test_rmse(self): self.helper_test_multivariate_duplication_equality(metrics.rmse) self.helper_test_multiple_ts_duplication_equality(metrics.rmse) - self.assertAlmostEqual( - metrics.rmse( - self.series1.append(self.series2b), self.series2.append(self.series1b) - ), - metrics.mse( - self.series12, self.series21, reduction=(lambda x: np.sqrt(np.mean(x))) - ), + assert ( + round( + abs( + metrics.rmse( + self.series1.append(self.series2b), + self.series2.append(self.series1b), + ) + - metrics.mse( + self.series12, + self.series21, + reduction=(lambda x: np.sqrt(np.mean(x))), + ) + ), + 7, + ) + == 0 ) self.helper_test_nan(metrics.rmse) @@ -241,73 +283,100 @@ def test_mase(self): for s1, s2 in test_cases: # multivariate, series as args - self.assertAlmostEqual( - metrics.mase( - s1.stack(s1), - s2.stack(s2), - insample.stack(insample), - reduction=(lambda x: x[0]), - ), - metrics.mase(s1, s2, insample), + assert ( + round( + abs( + metrics.mase( + s1.stack(s1), + s2.stack(s2), + insample.stack(insample), + reduction=(lambda x: x[0]), + ) + - metrics.mase(s1, s2, insample) + ), + 7, + ) + == 0 ) # multi-ts, series as kwargs - self.assertAlmostEqual( - metrics.mase( - actual_series=[s1] * 2, - pred_series=[s2] * 2, - insample=[insample] * 2, - reduction=(lambda x: x[0]), - inter_reduction=(lambda x: x[0]), - ), - metrics.mase(s1, s2, insample), + assert ( + round( + abs( + metrics.mase( + actual_series=[s1] * 2, + pred_series=[s2] * 2, + insample=[insample] * 2, + reduction=(lambda x: x[0]), + inter_reduction=(lambda x: x[0]), + ) + - metrics.mase(s1, s2, insample) + ), + 7, + ) + == 0 ) # checking with n_jobs and verbose - self.assertAlmostEqual( - metrics.mase( - [s1] * 5, - pred_series=[s2] * 5, - insample=[insample] * 5, - reduction=(lambda x: x[0]), - inter_reduction=(lambda x: x[0]), - ), - metrics.mase( - [s1] * 5, - [s2] * 5, - insample=[insample] * 5, - reduction=(lambda x: x[0]), - inter_reduction=(lambda x: x[0]), - n_jobs=-1, - verbose=True, - ), + assert ( + round( + abs( + metrics.mase( + [s1] * 5, + pred_series=[s2] * 5, + insample=[insample] * 5, + reduction=(lambda x: x[0]), + inter_reduction=(lambda x: x[0]), + ) + - metrics.mase( + [s1] * 5, + [s2] * 5, + insample=[insample] * 5, + reduction=(lambda x: x[0]), + inter_reduction=(lambda x: x[0]), + n_jobs=-1, + verbose=True, + ) + ), + 7, + ) + == 0 ) # checking with m=None - self.assertAlmostEqual( - metrics.mase( - self.series2, self.series2, self.series_train_not_periodic, m=None - ), - metrics.mase( - [self.series2] * 2, - [self.series2] * 2, - [self.series_train_not_periodic] * 2, - m=None, - inter_reduction=np.mean, - ), + assert ( + round( + abs( + metrics.mase( + self.series2, + self.series2, + self.series_train_not_periodic, + m=None, + ) + - metrics.mase( + [self.series2] * 2, + [self.series2] * 2, + [self.series_train_not_periodic] * 2, + m=None, + inter_reduction=np.mean, + ) + ), + 7, + ) + == 0 ) # fails because of wrong indexes (series1/2 indexes should be the continuation of series3) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mase(self.series1, self.series2, self.series3, 1) # multi-ts, second series is not a TimeSeries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mase([self.series1] * 2, self.series2, [insample] * 2) # multi-ts, insample series is not a TimeSeries - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mase([self.series1] * 2, [self.series2] * 2, insample) # multi-ts one array has different length - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mase([self.series1] * 2, [self.series2] * 2, [insample] * 3) # not supported input - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.mase(1, 2, 3) def test_ope(self): @@ -317,7 +386,7 @@ def test_ope(self): def test_rho_risk(self): # deterministic not supported - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.rho_risk(self.series1, self.series1) # general univariate, multivariate and multi-ts tests @@ -331,11 +400,29 @@ def test_rho_risk(self): # test perfect predictions -> risk = 0 for rho in [0.25, 0.5]: - self.assertAlmostEqual( - metrics.rho_risk(self.series1, self.series11_stochastic, rho=rho), 0.0 + assert ( + round( + abs( + metrics.rho_risk( + self.series1, self.series11_stochastic, rho=rho + ) + - 0.0 + ), + 7, + ) + == 0 + ) + assert ( + round( + abs( + metrics.rho_risk( + self.series12_mean, self.series12_stochastic, rho=0.5 + ) + - 0.0 + ), + 7, ) - self.assertAlmostEqual( - metrics.rho_risk(self.series12_mean, self.series12_stochastic, rho=0.5), 0.0 + == 0 ) # test whether stochastic sample from two TimeSeries (ts) represents the individual ts at 0. and 1. quantiles @@ -344,12 +431,12 @@ def test_rho_risk(self): s12_stochastic = TimeSeries.from_times_and_values( s1.time_index, np.stack([s1.values(), s2.values()], axis=2) ) - self.assertAlmostEqual(metrics.rho_risk(s1, s12_stochastic, rho=0.0), 0.0) - self.assertAlmostEqual(metrics.rho_risk(s2, s12_stochastic, rho=1.0), 0.0) + assert round(abs(metrics.rho_risk(s1, s12_stochastic, rho=0.0) - 0.0), 7) == 0 + assert round(abs(metrics.rho_risk(s2, s12_stochastic, rho=1.0) - 0.0), 7) == 0 def test_quantile_loss(self): # deterministic not supported - with self.assertRaises(ValueError): + with pytest.raises(ValueError): metrics.quantile_loss(self.series1, self.series1) # general univariate, multivariate and multi-ts tests @@ -363,9 +450,17 @@ def test_quantile_loss(self): # test perfect predictions -> risk = 0 for tau in [0.25, 0.5]: - self.assertAlmostEqual( - metrics.quantile_loss(self.series1, self.series11_stochastic, tau=tau), - 0.0, + assert ( + round( + abs( + metrics.quantile_loss( + self.series1, self.series11_stochastic, tau=tau + ) + - 0.0 + ), + 7, + ) + == 0 ) # test whether stochastic sample from two TimeSeries (ts) represents the individual ts at 0. and 1. quantiles @@ -374,38 +469,35 @@ def test_quantile_loss(self): s12_stochastic = TimeSeries.from_times_and_values( s1.time_index, np.stack([s1.values(), s2.values()], axis=2) ) - self.assertAlmostEqual(metrics.quantile_loss(s1, s12_stochastic, tau=1.0), 0.0) - self.assertAlmostEqual(metrics.quantile_loss(s2, s12_stochastic, tau=0.0), 0.0) + assert round(metrics.quantile_loss(s1, s12_stochastic, tau=1.0), 7) == 0 + assert round(metrics.quantile_loss(s2, s12_stochastic, tau=0.0), 7) == 0 def test_metrics_arguments(self): series00 = self.series0.stack(self.series0) series11 = self.series1.stack(self.series1) - self.assertEqual( - metrics.r2_score(series11, series00, True, reduction=np.mean), 0 + assert metrics.r2_score(series11, series00, True, reduction=np.mean) == 0 + assert metrics.r2_score(series11, series00, reduction=np.mean) == 0 + assert metrics.r2_score(series11, pred_series=series00, reduction=np.mean) == 0 + assert ( + metrics.r2_score(series00, actual_series=series11, reduction=np.mean) == 0 ) - self.assertEqual(metrics.r2_score(series11, series00, reduction=np.mean), 0) - self.assertEqual( - metrics.r2_score(series11, pred_series=series00, reduction=np.mean), 0 - ) - self.assertEqual( - metrics.r2_score(series00, actual_series=series11, reduction=np.mean), 0 - ) - self.assertEqual( + assert ( metrics.r2_score( True, reduction=np.mean, pred_series=series00, actual_series=series11 - ), - 0, + ) + == 0 ) - self.assertEqual( - metrics.r2_score(series00, True, reduction=np.mean, actual_series=series11), - 0, + assert ( + metrics.r2_score(series00, True, reduction=np.mean, actual_series=series11) + == 0 ) - self.assertEqual( - metrics.r2_score(series11, True, reduction=np.mean, pred_series=series00), 0 + assert ( + metrics.r2_score(series11, True, reduction=np.mean, pred_series=series00) + == 0 ) # should fail if kwargs are passed as args, because of the "*" - with self.assertRaises(TypeError): + with pytest.raises(TypeError): metrics.r2_score(series00, series11, False, np.mean) def test_multiple_ts(self): @@ -415,11 +507,11 @@ def test_multiple_ts(self): # simple test multi_ts_1 = [self.series1 + 1, self.series1 + 1] multi_ts_2 = [self.series1 + 2, self.series1 + 1] - self.assertEqual( + assert ( metrics.rmse( multi_ts_1, multi_ts_2, reduction=np.mean, inter_reduction=np.mean - ), - 0.5, + ) + == 0.5 ) # checking univariate, multivariate and multi-ts gives same metrics with same values @@ -442,9 +534,7 @@ def test_multiple_ts(self): ] for metric in test_metric: - self.assertEqual( - metric(self.series1 + 1, self.series2), metric(series11, series22) - ) + assert metric(self.series1 + 1, self.series2) == metric(series11, series22) np.testing.assert_array_almost_equal( np.array([metric(series11, series22)] * 2), np.array(metric(multi_1, multi_2)), @@ -455,30 +545,24 @@ def test_multiple_ts(self): shifted_2 = self.series1 + 2 shifted_3 = self.series1 + 3 - self.assertEqual( - metrics.rmse( - [shifted_1, shifted_1], - [shifted_2, shifted_3], - reduction=np.mean, - inter_reduction=np.max, - ), - metrics.rmse(shifted_1, shifted_3), - ) + assert metrics.rmse( + [shifted_1, shifted_1], + [shifted_2, shifted_3], + reduction=np.mean, + inter_reduction=np.max, + ) == metrics.rmse(shifted_1, shifted_3) # checking if the result is the same with different n_jobs and verbose True - self.assertEqual( - metrics.rmse( - [shifted_1, shifted_1], - [shifted_2, shifted_3], - reduction=np.mean, - inter_reduction=np.max, - ), - metrics.rmse( - [shifted_1, shifted_1], - [shifted_2, shifted_3], - reduction=np.mean, - inter_reduction=np.max, - n_jobs=-1, - verbose=True, - ), + assert metrics.rmse( + [shifted_1, shifted_1], + [shifted_2, shifted_3], + reduction=np.mean, + inter_reduction=np.max, + ) == metrics.rmse( + [shifted_1, shifted_1], + [shifted_2, shifted_3], + reduction=np.mean, + inter_reduction=np.max, + n_jobs=-1, + verbose=True, ) diff --git a/darts/tests/models/components/glu_variants.py b/darts/tests/models/components/glu_variants.py index c3ca25994b..e012c7ebe9 100644 --- a/darts/tests/models/components/glu_variants.py +++ b/darts/tests/models/components/glu_variants.py @@ -14,9 +14,8 @@ if TORCH_AVAILABLE: from darts.models.components import glu_variants from darts.models.components.glu_variants import GLU_FFN - from darts.tests.base_test_class import DartsBaseTestClass - class FFNTestCase(DartsBaseTestClass): + class TestFFN: def test_ffn(self): for FeedForward_network in GLU_FFN: self.feed_forward_block = getattr(glu_variants, FeedForward_network)( diff --git a/darts/tests/models/components/test_layer_norm_variants.py b/darts/tests/models/components/test_layer_norm_variants.py index a55c9f20fb..374fa8deb3 100644 --- a/darts/tests/models/components/test_layer_norm_variants.py +++ b/darts/tests/models/components/test_layer_norm_variants.py @@ -1,4 +1,5 @@ import numpy as np +import pytest from darts.logging import get_logger @@ -20,9 +21,8 @@ RINorm, RMSNorm, ) - from darts.tests.base_test_class import DartsBaseTestClass - class LayerNormVariantsTestCase(DartsBaseTestClass): + class TestLayerNormVariants: def test_lnv(self): for layer_norm in [RMSNorm, LayerNorm, LayerNormNoBias]: ln = layer_norm(4) @@ -49,5 +49,5 @@ def test_rin(self): # try invalid input_dim rin = RINorm(input_dim=3, affine=True) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): x_norm = rin(x) diff --git a/darts/tests/models/filtering/test_filters.py b/darts/tests/models/filtering/test_filters.py index 2140fa3c43..3af7b2c6c1 100755 --- a/darts/tests/models/filtering/test_filters.py +++ b/darts/tests/models/filtering/test_filters.py @@ -8,18 +8,17 @@ from darts.models.filtering.gaussian_process_filter import GaussianProcessFilter from darts.models.filtering.kalman_filter import KalmanFilter from darts.models.filtering.moving_average_filter import MovingAverageFilter -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg -class FilterBaseTestClass(DartsBaseTestClass): +class FilterBaseTestClass: @classmethod def setUpClass(cls): super().setUpClass() np.random.seed(42) -class KalmanFilterTestCase(FilterBaseTestClass): +class TestKalmanFilter(FilterBaseTestClass): def test_kalman(self): """KalmanFilter test. Creates an increasing sequence of numbers, adds noise and @@ -43,9 +42,9 @@ def test_kalman(self): noise_distance = testing_signal_with_noise - testing_signal prediction_distance = filtered_values - testing_signal - self.assertGreater(noise_distance.std(), prediction_distance.std()) - self.assertEqual(filtered_ts.width, 1) - self.assertEqual(filtered_ts.n_samples, 1) + assert noise_distance.std() > prediction_distance.std() + assert filtered_ts.width == 1 + assert filtered_ts.n_samples == 1 def test_kalman_covariates(self): kf = KalmanFilter(dim_x=2) @@ -56,8 +55,8 @@ def test_kalman_covariates(self): kf.fit(series, covariates=covariates) prediction = kf.filter(series, covariates=covariates) - self.assertEqual(prediction.width, 1) - self.assertEqual(prediction.n_samples, 1) + assert prediction.width == 1 + assert prediction.n_samples == 1 def test_kalman_covariates_multivariate(self): kf = KalmanFilter(dim_x=3) @@ -71,9 +70,9 @@ def test_kalman_covariates_multivariate(self): kf.fit(series, covariates=covariates) prediction = kf.filter(series, covariates=covariates) - self.assertEqual(kf.dim_u, 2) - self.assertEqual(prediction.width, 2) - self.assertEqual(prediction.n_samples, 1) + assert kf.dim_u == 2 + assert prediction.width == 2 + assert prediction.n_samples == 1 def test_kalman_multivariate(self): kf = KalmanFilter(dim_x=3) @@ -85,8 +84,8 @@ def test_kalman_multivariate(self): kf.fit(series) prediction = kf.filter(series) - self.assertEqual(prediction.width, 2) - self.assertEqual(prediction.n_samples, 1) + assert prediction.width == 2 + assert prediction.n_samples == 1 def test_kalman_samples(self): kf = KalmanFilter(dim_x=1) @@ -96,8 +95,8 @@ def test_kalman_samples(self): kf.fit(series) prediction = kf.filter(series, num_samples=10) - self.assertEqual(prediction.width, 1) - self.assertEqual(prediction.n_samples, 10) + assert prediction.width == 1 + assert prediction.n_samples == 10 def test_kalman_missing_values(self): sine = tg.sine_timeseries( @@ -116,7 +115,7 @@ def test_kalman_missing_values(self): filtered_series = kf.filter(sine_holes, num_samples=100) # reconstruction error should be sufficiently small - self.assertLess(rmse(filtered_series, sine), 0.1) + assert rmse(filtered_series, sine) < 0.1 def test_kalman_given_kf(self): nfoursid_ss = state_space.StateSpace( @@ -129,19 +128,19 @@ def test_kalman_given_kf(self): prediction = kf.filter(series, covariates=-series.copy()) - self.assertEqual(kf.dim_u, 1) - self.assertEqual(kf.dim_x, 2) - self.assertEqual(prediction.width, 1) - self.assertEqual(prediction.n_samples, 1) + assert kf.dim_u == 1 + assert kf.dim_x == 2 + assert prediction.width == 1 + assert prediction.n_samples == 1 -class MovingAverageTestCase(FilterBaseTestClass): +class TestMovingAverage(FilterBaseTestClass): def test_moving_average_univariate(self): ma = MovingAverageFilter(window=3, centered=False) sine_ts = tg.sine_timeseries(length=30, value_frequency=0.1) sine_filtered = ma.filter(sine_ts) - self.assertGreater( - np.mean(np.abs(sine_ts.values())), np.mean(np.abs(sine_filtered.values())) + assert np.mean(np.abs(sine_ts.values())) > np.mean( + np.abs(sine_filtered.values()) ) def test_moving_average_multivariate(self): @@ -151,17 +150,15 @@ def test_moving_average_multivariate(self): ts = sine_ts.stack(noise_ts) ts_filtered = ma.filter(ts) - self.assertGreater( - np.mean(np.abs(ts.values()[:, 0])), - np.mean(np.abs(ts_filtered.values()[:, 0])), + assert np.mean(np.abs(ts.values()[:, 0])) > np.mean( + np.abs(ts_filtered.values()[:, 0]) ) - self.assertGreater( - np.mean(np.abs(ts.values()[:, 1])), - np.mean(np.abs(ts_filtered.values()[:, 1])), + assert np.mean(np.abs(ts.values()[:, 1])) > np.mean( + np.abs(ts_filtered.values()[:, 1]) ) -class GaussianProcessFilterTestCase(FilterBaseTestClass): +class TestGaussianProcessFilter(FilterBaseTestClass): def test_gaussian_process(self): """GaussianProcessFilter test. Creates a sine wave, adds noise and assumes the GP filter @@ -181,15 +178,13 @@ def test_gaussian_process(self): noise_diff = testing_signal_with_noise - testing_signal prediction_diff = filtered_ts - testing_signal - self.assertGreater(noise_diff.values().std(), prediction_diff.values().std()) + assert noise_diff.values().std() > prediction_diff.values().std() filtered_ts_median = gpf.filter( testing_signal_with_noise, num_samples=100 ).quantile_timeseries() median_prediction_diff = filtered_ts_median - testing_signal - self.assertGreater( - noise_diff.values().std(), median_prediction_diff.values().std() - ) + assert noise_diff.values().std() > median_prediction_diff.values().std() def test_gaussian_process_multivariate(self): gpf = GaussianProcessFilter() @@ -200,7 +195,7 @@ def test_gaussian_process_multivariate(self): prediction = gpf.filter(ts) - self.assertEqual(prediction.width, 2) + assert prediction.width == 2 def test_gaussian_process_missing_values(self): ts = TimeSeries.from_values(np.ones(6)) @@ -212,12 +207,12 @@ def test_gaussian_process_missing_values(self): if __name__ == "__main__": - KalmanFilterTestCase().test_kalman() - KalmanFilterTestCase().test_kalman_multivariate() - KalmanFilterTestCase().test_kalman_covariates() - KalmanFilterTestCase().test_kalman_covariates_multivariate() - KalmanFilterTestCase().test_kalman_samples() - KalmanFilterTestCase().test_kalman_given_kf() - MovingAverageTestCase().test_moving_average_univariate() - MovingAverageTestCase().test_moving_average_multivariate() - GaussianProcessFilterTestCase().test_gaussian_process() + TestKalmanFilter().test_kalman() + TestKalmanFilter().test_kalman_multivariate() + TestKalmanFilter().test_kalman_covariates() + TestKalmanFilter().test_kalman_covariates_multivariate() + TestKalmanFilter().test_kalman_samples() + TestKalmanFilter().test_kalman_given_kf() + TestMovingAverage().test_moving_average_univariate() + TestMovingAverage().test_moving_average_multivariate() + TestGaussianProcessFilter().test_gaussian_process() diff --git a/darts/tests/models/forecasting/test_4theta.py b/darts/tests/models/forecasting/test_4theta.py index 85cd63e294..2c6dada41d 100644 --- a/darts/tests/models/forecasting/test_4theta.py +++ b/darts/tests/models/forecasting/test_4theta.py @@ -1,23 +1,23 @@ import random import numpy as np +import pytest from darts.metrics import mape from darts.models import FourTheta, Theta -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.timeseries_generation import linear_timeseries as lt from darts.utils.timeseries_generation import random_walk_timeseries as rt from darts.utils.timeseries_generation import sine_timeseries as st from darts.utils.utils import ModelMode, SeasonalityMode, TrendMode -class FourThetaTestCase(DartsBaseTestClass): +class TestFourTheta: def test_input(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FourTheta(model_mode=SeasonalityMode.ADDITIVE) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FourTheta(season_mode=ModelMode.ADDITIVE) - with self.assertRaises((ValueError, TypeError)): + with pytest.raises((ValueError, TypeError)): FourTheta(trend_mode="linear") def test_negative_series(self): @@ -29,14 +29,14 @@ def test_negative_series(self): normalization=False, ) model.fit(sine_series) - self.assertTrue( + assert ( model.model_mode is ModelMode.ADDITIVE and model.trend_mode is TrendMode.LINEAR ) def test_zero_mean(self): sine_series = st(length=50) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model = FourTheta( model_mode=ModelMode.MULTIPLICATIVE, trend_mode=TrendMode.EXPONENTIAL ) @@ -53,7 +53,7 @@ def test_theta(self): forecast_theta = theta.predict(20) forecast_fourtheta = fourtheta.predict(20) weighted_delta = (forecast_theta - forecast_fourtheta) / forecast_theta - self.assertTrue((weighted_delta <= 3e-5).all().item()) + assert (weighted_delta <= 3e-5).all().item() def test_best_model(self): random.seed(1) @@ -73,9 +73,7 @@ def test_best_model(self): best_model.fit(train_series) forecast_random = model.predict(10) forecast_best = best_model.predict(10) - self.assertTrue( - mape(val_series, forecast_best) <= mape(val_series, forecast_random) - ) + assert mape(val_series, forecast_best) <= mape(val_series, forecast_random) def test_min_train_series_length_with_seasonality(self): seasonality_period = 12 @@ -90,8 +88,8 @@ def test_min_train_series_length_with_seasonality(self): season_mode=SeasonalityMode.ADDITIVE, seasonality_period=seasonality_period, ) - self.assertEqual(fourtheta.min_train_series_length, 2 * seasonality_period) - self.assertEqual(theta.min_train_series_length, 2 * seasonality_period) + assert fourtheta.min_train_series_length == 2 * seasonality_period + assert theta.min_train_series_length == 2 * seasonality_period def test_min_train_series_length_without_seasonality(self): fourtheta = FourTheta( @@ -105,12 +103,12 @@ def test_min_train_series_length_without_seasonality(self): season_mode=SeasonalityMode.ADDITIVE, seasonality_period=None, ) - self.assertEqual(fourtheta.min_train_series_length, 3) - self.assertEqual(theta.min_train_series_length, 3) + assert fourtheta.min_train_series_length == 3 + assert theta.min_train_series_length == 3 def test_fit_insufficient_train_series_length(self): sine_series = st(length=21, freq="MS") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): fourtheta = FourTheta( model_mode=ModelMode.MULTIPLICATIVE, trend_mode=TrendMode.EXPONENTIAL, @@ -118,7 +116,7 @@ def test_fit_insufficient_train_series_length(self): seasonality_period=12, ) fourtheta.fit(sine_series) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): theta = Theta( season_mode=SeasonalityMode.ADDITIVE, seasonality_period=12, diff --git a/darts/tests/models/forecasting/test_TCN.py b/darts/tests/models/forecasting/test_TCN.py index 7ecdab1db9..0b17f8fc43 100644 --- a/darts/tests/models/forecasting/test_TCN.py +++ b/darts/tests/models/forecasting/test_TCN.py @@ -2,7 +2,7 @@ from darts.logging import get_logger from darts.metrics import mae -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -20,9 +20,9 @@ if TORCH_AVAILABLE: - class TCNModelTestCase(DartsBaseTestClass): + class TestTCNModel: def test_creation(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # cannot choose a kernel size larger than the input length TCNModel(input_chunk_length=20, output_chunk_length=1, kernel_size=100) TCNModel(input_chunk_length=12, output_chunk_length=1) @@ -52,11 +52,11 @@ def test_fit(self): ) model2.fit(small_ts[:98]) pred2 = model2.predict(n=2).values()[0] - self.assertTrue(abs(pred2 - 10) < abs(pred - 10)) + assert abs(pred2 - 10) < abs(pred - 10) # test short predict pred3 = model2.predict(n=1) - self.assertEqual(len(pred3), 1) + assert len(pred3) == 1 def test_performance(self): # test TCN performance on dummy time series @@ -74,7 +74,7 @@ def test_performance(self): model.fit(train) pred = model.predict(n=10) - self.assertTrue(mae(pred, test) < 0.3) + assert mae(pred, test) < 0.3 @pytest.mark.slow def test_coverage(self): @@ -133,7 +133,7 @@ def test_coverage(self): curr_output = model.model.forward((input_tensor, None))[ 0, -1, 0 ] - self.assertNotEqual(zero_output, curr_output) + assert zero_output != curr_output input_tensor[0, i, 0] = 0 # create model with all weights set to one and one layer less than is automatically detected @@ -188,7 +188,7 @@ def test_coverage(self): uncovered_input_found = True break input_tensor[0, i, 0] = 0 - self.assertTrue(uncovered_input_found) + assert uncovered_input_found def helper_test_pred_length(self, pytorch_model, series): model = pytorch_model( @@ -196,13 +196,13 @@ def helper_test_pred_length(self, pytorch_model, series): ) model.fit(series) pred = model.predict(7) - self.assertEqual(len(pred), 7) + assert len(pred) == 7 pred = model.predict(2) - self.assertEqual(len(pred), 2) - self.assertEqual(pred.width, 1) + assert len(pred) == 2 + assert pred.width == 1 pred = model.predict(4) - self.assertEqual(len(pred), 4) - self.assertEqual(pred.width, 1) + assert len(pred) == 4 + assert pred.width == 1 def test_pred_length(self): series = tg.linear_timeseries(length=100) diff --git a/darts/tests/models/forecasting/test_TFT.py b/darts/tests/models/forecasting/test_TFT.py index b3b6b27eda..a79d43b095 100644 --- a/darts/tests/models/forecasting/test_TFT.py +++ b/darts/tests/models/forecasting/test_TFT.py @@ -5,7 +5,7 @@ from darts import TimeSeries, concatenate from darts.dataprocessing.transformers import Scaler from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -27,17 +27,17 @@ if TORCH_AVAILABLE: - class TFTModelTestCase(DartsBaseTestClass): + class TestTFTModel: def test_quantile_regression(self): q_no_50 = [0.1, 0.4, 0.9] q_non_symmetric = [0.2, 0.5, 0.9] # if a QuantileLoss is used, it must have to q=0.5 quantile - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileRegression(q_no_50) # if a QuantileLoss is used, it must be symmetric around q=0.5 quantile (i.e. [0.1, 0.5, 0.9]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): QuantileRegression(q_non_symmetric) def test_future_covariate_handling(self): @@ -46,7 +46,7 @@ def test_future_covariate_handling(self): # model requires future covariates without cyclic encoding model = TFTModel(input_chunk_length=1, output_chunk_length=1, **tfm_kwargs) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.fit(ts_time_index, verbose=False) # should work with cyclic encoding for time index @@ -325,8 +325,8 @@ def helper_test_prediction_shape( ts_list = [ts] if isinstance(ts, TimeSeries) else ts for y_hat_i, ts_i in zip(y_hat_list, ts_list): - self.assertEqual(len(y_hat_i), predict_n) - self.assertEqual(y_hat_i.n_components, ts_i.n_components) + assert len(y_hat_i) == predict_n + assert y_hat_i.n_components == ts_i.n_components def helper_test_prediction_accuracy( self, @@ -352,12 +352,10 @@ def helper_test_prediction_accuracy( ) y_true = ts[y_hat.start_time() : y_hat.end_time()] - self.assertTrue( - np.allclose( - y_true[1:-1].all_values(), - y_hat[1:-1].all_values(), - atol=absolute_tolarance, - ) + assert np.allclose( + y_true[1:-1].all_values(), + y_hat[1:-1].all_values(), + atol=absolute_tolarance, ) @staticmethod @@ -419,7 +417,7 @@ def test_layer_norm(self): ) model2.fit(series, epochs=1) - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): model4 = base_model( input_chunk_length=1, output_chunk_length=1, diff --git a/darts/tests/models/forecasting/test_backtesting.py b/darts/tests/models/forecasting/test_backtesting.py index f09c453f84..8f87381c2b 100644 --- a/darts/tests/models/forecasting/test_backtesting.py +++ b/darts/tests/models/forecasting/test_backtesting.py @@ -1,5 +1,4 @@ import random -import unittest from itertools import product import numpy as np @@ -18,7 +17,7 @@ NaiveSeasonal, Theta, ) -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils.timeseries_generation import gaussian_timeseries as gt from darts.utils.timeseries_generation import linear_timeseries as lt from darts.utils.timeseries_generation import random_walk_timeseries as rt @@ -101,7 +100,7 @@ def compare_best_against_random(model_class, params, series, stride=1): return expanding_window_ok and split_ok -class BacktestingTestCase(DartsBaseTestClass): +class TestBacktesting: def test_backtest_forecasting(self): linear_series = lt(length=50) linear_series_int = TimeSeries.from_values(linear_series.values()) @@ -114,7 +113,7 @@ def test_backtest_forecasting(self): forecast_horizon=3, metric=r2_score, ) - self.assertEqual(score, 1.0) + assert score == 1.0 # univariate model + univariate series + historical_forecasts precalculated forecasts = NaiveDrift().historical_forecasts( @@ -130,7 +129,7 @@ def test_backtest_forecasting(self): forecast_horizon=3, metric=r2_score, ) - self.assertEqual(score, precalculated_forecasts_score) + assert score == precalculated_forecasts_score # very large train length should not affect the backtest score = NaiveDrift().backtest( @@ -140,7 +139,7 @@ def test_backtest_forecasting(self): forecast_horizon=3, metric=r2_score, ) - self.assertEqual(score, 1.0) + assert score == 1.0 # using several metric function should not affect the backtest score = NaiveDrift().backtest( @@ -153,7 +152,7 @@ def test_backtest_forecasting(self): np.testing.assert_almost_equal(score, np.array([1.0, 0.0])) # window of size 2 is too small for naive drift - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest( linear_series, train_length=2, @@ -166,9 +165,9 @@ def test_backtest_forecasting(self): score = NaiveDrift().backtest( linear_series_int, start=0.7, forecast_horizon=3, metric=r2_score ) - self.assertEqual(score, 1.0) + assert score == 1.0 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest( linear_series, start=pd.Timestamp("20000218"), @@ -199,31 +198,31 @@ def test_backtest_forecasting(self): NaiveDrift().backtest(linear_series, train_length=10, start=30) # Using invalid start and/or forecast_horizon values - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest(linear_series, start=0.7, forecast_horizon=-1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest(linear_series, start=-0.7, forecast_horizon=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest(linear_series, start=100) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest(linear_series, start=1.2) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): NaiveDrift().backtest(linear_series, start="wrong type") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest(linear_series, train_length=0, start=0.5) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): NaiveDrift().backtest(linear_series, train_length=1.2, start=0.5) - with self.assertRaises(TypeError): + with pytest.raises(TypeError): NaiveDrift().backtest(linear_series, train_length="wrong type", start=0.5) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveDrift().backtest( linear_series, start=49, forecast_horizon=2, overlap_end=False ) # univariate model + multivariate series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): FFT().backtest( linear_series_multi, start=pd.Timestamp("20000201"), forecast_horizon=3 ) @@ -255,8 +254,8 @@ def test_backtest_forecasting(self): verbose=False, last_points_only=True, ) - self.assertEqual(pred.width, 1) - self.assertEqual(pred.end_time(), linear_series.end_time()) + assert pred.width == 1 + assert pred.end_time() == linear_series.end_time() # multivariate model + multivariate series # historical forecasts doesn't overwrite model object -> we can use different input dimensions @@ -300,8 +299,8 @@ def test_backtest_forecasting(self): verbose=False, last_points_only=True, ) - self.assertEqual(pred.width, 2) - self.assertEqual(pred.end_time(), linear_series.end_time()) + assert pred.width == 2 + assert pred.end_time() == linear_series.end_time() def test_backtest_multiple_series(self): series = [AirPassengersDataset().load(), MonthlyMilkDataset().load()] @@ -318,11 +317,11 @@ def test_backtest_multiple_series(self): ) expected = [11.63104, 6.09458] - self.assertEqual(len(error), 2) - self.assertAlmostEqual(error[0], expected[0], places=4) - self.assertAlmostEqual(error[1], expected[1], places=4) + assert len(error) == 2 + assert round(abs(error[0] - expected[0]), 4) == 0 + assert round(abs(error[1] - expected[1]), 4) == 0 - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_backtest_regression(self): np.random.seed(4) @@ -353,7 +352,7 @@ def test_backtest_regression(self): metric=r2_score, last_points_only=True, ) - self.assertGreater(score, 0.9) + assert score > 0.9 # univariate feature test + train length score = LinearRegressionModel( @@ -367,7 +366,7 @@ def test_backtest_regression(self): metric=r2_score, last_points_only=True, ) - self.assertGreater(score, 0.9) + assert score > 0.9 # Using an int or float value for start score = RandomForest( @@ -379,7 +378,7 @@ def test_backtest_regression(self): forecast_horizon=3, metric=r2_score, ) - self.assertGreater(score, 0.9) + assert score > 0.9 score = RandomForest( lags=12, lags_future_covariates=[0], random_state=0 @@ -390,13 +389,13 @@ def test_backtest_regression(self): forecast_horizon=3, metric=r2_score, ) - self.assertGreater(score, 0.9) + assert score > 0.9 # Using a too small start value - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RandomForest(lags=12).backtest(series=target, start=0, forecast_horizon=3) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RandomForest(lags=12).backtest( series=target, start=0.01, forecast_horizon=3 ) @@ -405,7 +404,7 @@ def test_backtest_regression(self): score = RandomForest(lags=12, random_state=0).backtest( series=target, forecast_horizon=3, start=0.5, metric=r2_score ) - self.assertGreater(score, 0.95) + assert score > 0.95 # multivariate feature test score = RandomForest( @@ -417,7 +416,7 @@ def test_backtest_regression(self): forecast_horizon=3, metric=r2_score, ) - self.assertGreater(score, 0.94) + assert score > 0.94 # multivariate feature test with train window 35 score_35 = RandomForest( @@ -433,7 +432,7 @@ def test_backtest_regression(self): logger.info( "Score for multivariate feature test with train window 35 is: ", score_35 ) - self.assertGreater(score_35, 0.92) + assert score_35 > 0.92 # multivariate feature test with train window 45 score_45 = RandomForest( @@ -449,8 +448,8 @@ def test_backtest_regression(self): logger.info( "Score for multivariate feature test with train window 45 is: ", score_45 ) - self.assertGreater(score_45, 0.94) - self.assertGreater(score_45, score_35) + assert score_45 > 0.94 + assert score_45 > score_35 # multivariate with stride score = RandomForest( @@ -464,7 +463,7 @@ def test_backtest_regression(self): last_points_only=True, stride=3, ) - self.assertGreater(score, 0.9) + assert score > 0.9 def test_gridsearch(self): np.random.seed(1) @@ -473,20 +472,16 @@ def test_gridsearch(self): dummy_series_int_index = TimeSeries.from_values(dummy_series.values()) theta_params = {"theta": list(range(3, 10))} - self.assertTrue(compare_best_against_random(Theta, theta_params, dummy_series)) - self.assertTrue( - compare_best_against_random(Theta, theta_params, dummy_series_int_index) - ) - self.assertTrue( - compare_best_against_random(Theta, theta_params, dummy_series, stride=2) - ) + assert compare_best_against_random(Theta, theta_params, dummy_series) + assert compare_best_against_random(Theta, theta_params, dummy_series_int_index) + assert compare_best_against_random(Theta, theta_params, dummy_series, stride=2) fft_params = {"nr_freqs_to_keep": [10, 50, 100], "trend": [None, "poly", "exp"]} - self.assertTrue(compare_best_against_random(FFT, fft_params, dummy_series)) + assert compare_best_against_random(FFT, fft_params, dummy_series) es_params = {"seasonal_periods": list(range(5, 10))} - self.assertTrue( - compare_best_against_random(ExponentialSmoothing, es_params, dummy_series) + assert compare_best_against_random( + ExponentialSmoothing, es_params, dummy_series ) def test_gridsearch_metric_score(self): @@ -510,9 +505,9 @@ def test_gridsearch_metric_score(self): stride=1, ) - self.assertEqual(score, recalculated_score, "The metric scores should match") + assert score == recalculated_score, "The metric scores should match" - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_gridsearch_random_search(self): np.random.seed(1) @@ -526,35 +521,35 @@ def test_gridsearch_random_search(self): params, dummy_series, forecast_horizon=1, n_random_samples=5 ) - self.assertEqual(type(result[0]), RandomForest) - self.assertEqual(type(result[1]["lags"]), int) - self.assertEqual(type(result[2]), float) - self.assertTrue(min(param_range) <= result[1]["lags"] <= max(param_range)) + assert isinstance(result[0], RandomForest) + assert isinstance(result[1]["lags"], int) + assert isinstance(result[2], float) + assert min(param_range) <= result[1]["lags"] <= max(param_range) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_gridsearch_n_random_samples_bad_arguments(self): dummy_series = get_dummy_series(ts_length=50) params = {"lags": list(range(1, 11)), "past_covariates": list(range(1, 11))} - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RandomForest.gridsearch( params, dummy_series, forecast_horizon=1, n_random_samples=-5 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RandomForest.gridsearch( params, dummy_series, forecast_horizon=1, n_random_samples=105 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RandomForest.gridsearch( params, dummy_series, forecast_horizon=1, n_random_samples=-24.56 ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RandomForest.gridsearch( params, dummy_series, forecast_horizon=1, n_random_samples=1.5 ) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_gridsearch_n_random_samples(self): np.random.seed(1) @@ -564,15 +559,15 @@ def test_gridsearch_n_random_samples(self): # Test absolute sample absolute_sampled_result = RandomForest._sample_params(params_cross_product, 10) - self.assertEqual(len(absolute_sampled_result), 10) + assert len(absolute_sampled_result) == 10 # Test percentage sample percentage_sampled_result = RandomForest._sample_params( params_cross_product, 0.37 ) - self.assertEqual(len(percentage_sampled_result), 37) + assert len(percentage_sampled_result) == 37 - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_gridsearch_n_jobs(self): """ Testing that running gridsearch with multiple workers returns the same @@ -620,9 +615,9 @@ def test_gridsearch_n_jobs(self): parameters=parameters, series=ts_train, val_series=ts_val, n_jobs=2 ) - self.assertEqual(best_params1, best_params2) + assert best_params1 == best_params2 - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_gridsearch_multi(self): dummy_series = st(length=40, value_y_offset=10).stack( lt(length=40, end_value=20) diff --git a/darts/tests/models/forecasting/test_block_RNN.py b/darts/tests/models/forecasting/test_block_RNN.py index 41146dafef..676252d18c 100644 --- a/darts/tests/models/forecasting/test_block_RNN.py +++ b/darts/tests/models/forecasting/test_block_RNN.py @@ -1,11 +1,10 @@ -import shutil -import tempfile - +import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs logger = get_logger(__name__) @@ -20,8 +19,7 @@ if TORCH_AVAILABLE: - class BlockRNNModelTestCase(DartsBaseTestClass): - __test__ = True + class TestBlockRNNModel: times = pd.date_range("20130101", "20130410") pd_series = pd.Series(range(100), index=times) series: TimeSeries = TimeSeries.from_series(pd_series) @@ -38,14 +36,8 @@ class BlockRNNModelTestCase(DartsBaseTestClass): dropout=0, ) - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - def test_creation(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # cannot choose any string BlockRNNModel( input_chunk_length=1, output_chunk_length=1, model="UnknownRNN?" @@ -57,9 +49,9 @@ def test_creation(self): model2 = BlockRNNModel( input_chunk_length=1, output_chunk_length=1, model="RNN" ) - self.assertEqual(model1.model.__repr__(), model2.model.__repr__()) + assert model1.model.__repr__() == model2.model.__repr__() - def test_fit(self): + def test_fit(self, tmpdir_module): # Test basic fit() model = BlockRNNModel( input_chunk_length=1, output_chunk_length=1, n_epochs=2, **tfm_kwargs @@ -73,7 +65,7 @@ def test_fit(self): model="LSTM", n_epochs=1, model_name="unittest-model-lstm", - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, save_checkpoints=True, force_reset=True, **tfm_kwargs @@ -81,7 +73,7 @@ def test_fit(self): model2.fit(self.series) model_loaded = model2.load_from_checkpoint( model_name="unittest-model-lstm", - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, best=False, map_location="cpu", ) @@ -89,7 +81,7 @@ def test_fit(self): pred2 = model_loaded.predict(n=6) # Two models with the same parameters should deterministically yield the same output - self.assertEqual(sum(pred1.values() - pred2.values()), 0.0) + np.testing.assert_array_equal(pred1.values(), pred2.values()) # Another random model should not model3 = BlockRNNModel( @@ -101,16 +93,16 @@ def test_fit(self): ) model3.fit(self.series) pred3 = model3.predict(n=6) - self.assertNotEqual(sum(pred1.values() - pred3.values()), 0.0) + assert not np.array_equal(pred1.values(), pred3.values()) # test short predict pred4 = model3.predict(n=1) - self.assertEqual(len(pred4), 1) + assert len(pred4) == 1 # test validation series input model3.fit(self.series[:60], val_series=self.series[60:]) pred4 = model3.predict(n=6) - self.assertEqual(len(pred4), 6) + assert len(pred4) == 6 def helper_test_pred_length(self, pytorch_model, series): model = pytorch_model( @@ -118,13 +110,13 @@ def helper_test_pred_length(self, pytorch_model, series): ) model.fit(series) pred = model.predict(7) - self.assertEqual(len(pred), 7) + assert len(pred) == 7 pred = model.predict(2) - self.assertEqual(len(pred), 2) - self.assertEqual(pred.width, 1) + assert len(pred) == 2 + assert pred.width == 1 pred = model.predict(4) - self.assertEqual(len(pred), 4) - self.assertEqual(pred.width, 1) + assert len(pred) == 4 + assert pred.width == 1 def test_pred_length(self): self.helper_test_pred_length(BlockRNNModel, self.series) diff --git a/darts/tests/models/forecasting/test_dlinear_nlinear.py b/darts/tests/models/forecasting/test_dlinear_nlinear.py index 904c95aff4..9eee4423c3 100644 --- a/darts/tests/models/forecasting/test_dlinear_nlinear.py +++ b/darts/tests/models/forecasting/test_dlinear_nlinear.py @@ -1,5 +1,3 @@ -import shutil -import tempfile from itertools import product import numpy as np @@ -9,7 +7,7 @@ from darts import concatenate from darts.logging import get_logger from darts.metrics import rmse -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -29,18 +27,12 @@ if TORCH_AVAILABLE: - class DlinearNlinearModelsTestCase(DartsBaseTestClass): + class TestDlinearNlinearModels: np.random.seed(42) torch.manual_seed(42) - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - def test_creation(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): DLinearModel( input_chunk_length=1, output_chunk_length=1, @@ -48,7 +40,7 @@ def test_creation(self): likelihood=GaussianLikelihood(), ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NLinearModel( input_chunk_length=1, output_chunk_length=1, @@ -87,13 +79,13 @@ def test_fit(self): ) model2.fit(small_ts[:98]) pred2 = model2.predict(n=2).values()[0] - self.assertTrue(abs(pred2 - 10) < abs(pred - 10)) + assert abs(pred2 - 10) < abs(pred - 10) # test short predict pred3 = model2.predict(n=1) - self.assertEqual(len(pred3), 1) + assert len(pred3) == 1 - def test_logtensorboard(self): + def test_logtensorboard(self, tmpdir_module): ts = tg.constant_timeseries(length=50, value=10) for model_cls in [DLinearModel, NLinearModel]: @@ -103,7 +95,7 @@ def test_logtensorboard(self): output_chunk_length=1, n_epochs=1, log_tensorboard=True, - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, pl_trainer_kwargs={ "log_every_n_steps": 1, **tfm_kwargs["pl_trainer_kwargs"], @@ -141,8 +133,8 @@ def test_shared_weights(self): model_not_shared.fit(ts) pred_shared = model_shared.predict(n=2) pred_not_shared = model_not_shared.predict(n=2) - self.assertTrue( - np.any(np.not_equal(pred_shared.values(), pred_not_shared.values())) + assert np.any( + np.not_equal(pred_shared.values(), pred_not_shared.values()) ) def test_multivariate_and_covariates(self): @@ -227,8 +219,8 @@ def _eval_model( e1, e2 = _eval_model( train1, train2, val1, val2, fut_cov1, fut_cov2, cls=model, lkl=lkl ) - self.assertLessEqual(e1, 0.34) - self.assertLessEqual(e2, 0.28) + assert e1 <= 0.34 + assert e2 <= 0.28 e1, e2 = _eval_model( train1.with_static_covariates(None), @@ -240,14 +232,14 @@ def _eval_model( cls=model, lkl=lkl, ) - self.assertLessEqual(e1, 0.32) - self.assertLessEqual(e2, 0.28) + assert e1 <= 0.32 + assert e2 <= 0.28 e1, e2 = _eval_model( train1, train2, val1, val2, None, None, cls=model, lkl=lkl ) - self.assertLessEqual(e1, 0.40) - self.assertLessEqual(e2, 0.34) + assert e1 <= 0.40 + assert e2 <= 0.34 e1, e2 = _eval_model( train1.with_static_covariates(None), @@ -259,8 +251,8 @@ def _eval_model( cls=model, lkl=lkl, ) - self.assertLessEqual(e1, 0.40) - self.assertLessEqual(e2, 0.34) + assert e1 <= 0.40 + assert e2 <= 0.34 # can only fit models with past/future covariates when shared_weights=False for model in [DLinearModel, NLinearModel]: diff --git a/darts/tests/models/forecasting/test_ensemble_models.py b/darts/tests/models/forecasting/test_ensemble_models.py index 731dd98a08..ec6011ba69 100644 --- a/darts/tests/models/forecasting/test_ensemble_models.py +++ b/darts/tests/models/forecasting/test_ensemble_models.py @@ -1,7 +1,6 @@ -import unittest - import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.logging import get_logger @@ -14,7 +13,7 @@ StatsForecastAutoARIMA, Theta, ) -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -35,7 +34,7 @@ def _make_ts(start_value=0, n=100): return TimeSeries.from_series(pd_series) -class EnsembleModelsTestCase(DartsBaseTestClass): +class TestEnsembleModels: series1 = tg.sine_timeseries(value_frequency=(1 / 5), value_y_offset=10, length=50) series2 = tg.linear_timeseries(length=50) @@ -48,7 +47,7 @@ def test_untrained_models(self): # trained models should raise error model.fit(self.series1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveEnsembleModel([model]) # an untrained ensemble model should also give untrained underlying models @@ -83,13 +82,13 @@ def test_extreme_lag_inference(self): assert expected == ensemble.extreme_lags def test_input_models_local_models(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveEnsembleModel([]) # models are not instantiated - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveEnsembleModel([NaiveDrift, NaiveSeasonal, Theta, ExponentialSmoothing]) # one model is not instantiated - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveEnsembleModel( [NaiveDrift(), NaiveSeasonal, Theta(), ExponentialSmoothing()] ) @@ -99,7 +98,7 @@ def test_input_models_local_models(self): def test_call_predict_local_models(self): naive_ensemble = NaiveEnsembleModel([NaiveSeasonal(), Theta()]) - with self.assertRaises(Exception): + with pytest.raises(Exception): naive_ensemble.predict(5) naive_ensemble.fit(self.series1) pred1 = naive_ensemble.predict(5) @@ -121,8 +120,8 @@ def test_predict_univariate_ensemble_local_models(self): theta.fit(self.series1 + self.series2) forecast_mean = 0.5 * naive.predict(5) + 0.5 * theta.predict(5) - self.assertTrue( - np.array_equal(forecast_naive_ensemble.values(), forecast_mean.values()) + np.testing.assert_array_equal( + forecast_naive_ensemble.values(), forecast_mean.values() ) def test_predict_multivariate_ensemble_local_models(self): @@ -137,12 +136,10 @@ def test_predict_multivariate_ensemble_local_models(self): seasonal2.fit(multivariate_series) forecast_mean = 0.5 * seasonal1.predict(5) + 0.5 * seasonal2.predict(5) - self.assertTrue( - np.array_equal(forecast_naive_ensemble.values(), forecast_mean.values()) - ) - self.assertTrue( - all(forecast_naive_ensemble.components == multivariate_series.components) + np.testing.assert_array_equal( + forecast_naive_ensemble.values(), forecast_mean.values() ) + assert all(forecast_naive_ensemble.components == multivariate_series.components) def test_stochastic_naive_ensemble(self): num_samples = 100 @@ -157,18 +154,18 @@ def test_stochastic_naive_ensemble(self): # only probabilistic forecasting models naive_ensemble_proba = NaiveEnsembleModel([model_proba_1, model_proba_2]) - self.assertTrue(naive_ensemble_proba._is_probabilistic) + assert naive_ensemble_proba._is_probabilistic naive_ensemble_proba.fit(self.series1 + self.series2) # by default, only 1 sample pred_proba_1_sample = naive_ensemble_proba.predict(n=5) - self.assertEqual(pred_proba_1_sample.n_samples, 1) + assert pred_proba_1_sample.n_samples == 1 # possible to obtain probabilistic forecast by averaging samples across the models pred_proba_many_sample = naive_ensemble_proba.predict( n=5, num_samples=num_samples ) - self.assertEqual(pred_proba_many_sample.n_samples, num_samples) + assert pred_proba_many_sample.n_samples == num_samples # need to redefine the models to reset the random state model_alone_1 = LinearRegressionModel( @@ -183,12 +180,10 @@ def test_stochastic_naive_ensemble(self): 5, num_samples=num_samples ) + 0.5 * model_alone_2.predict(5, num_samples=num_samples) - self.assertEqual( - forecast_mean.values().shape, pred_proba_many_sample.values().shape - ) - self.assertEqual(forecast_mean.n_samples, pred_proba_many_sample.n_samples) - self.assertTrue( - np.array_equal(pred_proba_many_sample.values(), forecast_mean.values()) + assert forecast_mean.values().shape == pred_proba_many_sample.values().shape + assert forecast_mean.n_samples == pred_proba_many_sample.n_samples + np.testing.assert_array_equal( + pred_proba_many_sample.values(), forecast_mean.values() ) def test_predict_likelihood_parameters_wrong_args(self): @@ -211,7 +206,7 @@ def test_predict_likelihood_parameters_wrong_args(self): # one model is not probabilistic naive_ensemble = NaiveEnsembleModel([m_deterministic, m_proba_quantile1]) naive_ensemble.fit(self.series1 + self.series2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): naive_ensemble.predict(n=1, predict_likelihood_parameters=True) # one model has a different likelihood @@ -219,7 +214,7 @@ def test_predict_likelihood_parameters_wrong_args(self): [m_proba_quantile1.untrained_model(), m_proba_poisson] ) naive_ensemble.fit(self.series1 + self.series2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): naive_ensemble.predict(n=1, predict_likelihood_parameters=True) # n > shortest output_chunk_length @@ -227,10 +222,10 @@ def test_predict_likelihood_parameters_wrong_args(self): [m_proba_quantile1.untrained_model(), m_proba_quantile2] ) naive_ensemble.fit(self.series1 + self.series2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): naive_ensemble.predict(n=4, predict_likelihood_parameters=True) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_predict_likelihood_parameters_univariate_naive_ensemble(self): m_proba_quantile1 = LinearRegressionModel( lags=2, @@ -259,15 +254,15 @@ def test_predict_likelihood_parameters_univariate_naive_ensemble(self): ) naive_ensemble.fit(self.series1) pred_mix_ens = naive_ensemble.predict(n=1, predict_likelihood_parameters=True) - self.assertEqual(pred_ens.time_index, pred_mix_ens.time_index) - self.assertTrue(all(pred_ens.components == pred_mix_ens.components)) - self.assertTrue( + assert pred_ens.time_index == pred_mix_ens.time_index + assert all(pred_ens.components == pred_mix_ens.components) + assert ( pred_ens["sine_q0.05"].values() < pred_ens["sine_q0.50"].values() < pred_ens["sine_q0.95"].values() ) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_predict_likelihood_parameters_multivariate_naive_ensemble(self): m_proba_quantile1 = LinearRegressionModel( lags=2, @@ -298,40 +293,38 @@ def test_predict_likelihood_parameters_multivariate_naive_ensemble(self): ) naive_ensemble.fit(multivariate_series) pred_mix_ens = naive_ensemble.predict(n=1, predict_likelihood_parameters=True) - self.assertEqual(pred_ens.time_index, pred_mix_ens.time_index) - self.assertTrue( - all( - pred_ens.components - == [ - "sine_q0.05", - "sine_q0.50", - "sine_q0.95", - "linear_q0.05", - "linear_q0.50", - "linear_q0.95", - ] - ) + assert pred_ens.time_index == pred_mix_ens.time_index + assert all( + pred_ens.components + == [ + "sine_q0.05", + "sine_q0.50", + "sine_q0.95", + "linear_q0.05", + "linear_q0.50", + "linear_q0.95", + ] ) - self.assertTrue(all(pred_ens.components == pred_mix_ens.components)) - self.assertTrue( + assert all(pred_ens.components == pred_mix_ens.components) + assert ( pred_ens["sine_q0.05"].values() < pred_ens["sine_q0.50"].values() < pred_ens["sine_q0.95"].values() ) - self.assertTrue( + assert ( pred_ens["linear_q0.05"].values() < pred_ens["linear_q0.50"].values() < pred_ens["linear_q0.95"].values() ) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_input_models_global_models(self): # one model is not instantiated - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NaiveEnsembleModel([RNNModel(12), TCNModel(10, 2), NBEATSModel]) NaiveEnsembleModel([RNNModel(12), TCNModel(10, 2), NBEATSModel(10, 2)]) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_call_predict_global_models_univariate_input_no_covariates(self): naive_ensemble = NaiveEnsembleModel( [ @@ -340,13 +333,13 @@ def test_call_predict_global_models_univariate_input_no_covariates(self): NBEATSModel(10, 2, n_epochs=1, **tfm_kwargs), ] ) - with self.assertRaises(Exception): + with pytest.raises(Exception): naive_ensemble.predict(5) naive_ensemble.fit(self.series1) naive_ensemble.predict(5) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_call_predict_global_models_multivariate_input_no_covariates(self): naive_ensemble = NaiveEnsembleModel( [ @@ -358,7 +351,7 @@ def test_call_predict_global_models_multivariate_input_no_covariates(self): naive_ensemble.fit(self.seq1) naive_ensemble.predict(n=5, series=self.seq1) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_call_predict_global_models_multivariate_input_with_covariates(self): naive_ensemble = NaiveEnsembleModel( [ @@ -374,27 +367,27 @@ def test_call_predict_global_models_multivariate_input_with_covariates(self): n=2, series=predict_series, past_covariates=predict_covariates ) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_input_models_mixed(self): # NaiveDrift is local, RNNModel is global naive_ensemble = NaiveEnsembleModel( [NaiveDrift(), RNNModel(12, n_epochs=1, **tfm_kwargs)] ) # ensemble is neither local, nor global - self.assertFalse(naive_ensemble.is_local_ensemble) - self.assertFalse(naive_ensemble.is_global_ensemble) + assert not naive_ensemble.is_local_ensemble + assert not naive_ensemble.is_global_ensemble # ensemble contains one local model, no support for multiple ts fit - with self.assertRaises(ValueError): + with pytest.raises(ValueError): naive_ensemble.fit([self.series1, self.series2]) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_call_predict_different_covariates_support(self): # AutoARIMA support future covariates only local_ensemble_one_covs = NaiveEnsembleModel( [NaiveDrift(), StatsForecastAutoARIMA()] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): local_ensemble_one_covs.fit(self.series1, past_covariates=self.series2) local_ensemble_one_covs.fit(self.series1, future_covariates=self.series2) @@ -402,7 +395,7 @@ def test_call_predict_different_covariates_support(self): mixed_ensemble_one_covs = NaiveEnsembleModel( [NaiveDrift(), RNNModel(12, n_epochs=1, **tfm_kwargs)] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): mixed_ensemble_one_covs.fit(self.series1, past_covariates=self.series2) mixed_ensemble_one_covs.fit(self.series1, future_covariates=self.series2) @@ -414,7 +407,7 @@ def test_call_predict_different_covariates_support(self): ] ) mixed_ensemble_future_covs.fit(self.series1, future_covariates=self.series2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): mixed_ensemble_future_covs.fit(self.series1, past_covariates=self.series2) # RegressionModels with different covariates @@ -425,10 +418,10 @@ def test_call_predict_different_covariates_support(self): ] ) # missing future covariates - with self.assertRaises(ValueError): + with pytest.raises(ValueError): global_ensemble_both_covs.fit(self.series1, past_covariates=self.series2) # missing past covariates - with self.assertRaises(ValueError): + with pytest.raises(ValueError): global_ensemble_both_covs.fit(self.series1, future_covariates=self.series2) global_ensemble_both_covs.fit( self.series1, past_covariates=self.series2, future_covariates=self.series2 @@ -438,14 +431,14 @@ def test_fit_multivar_ts_with_local_models(self): naive = NaiveEnsembleModel( [NaiveDrift(), NaiveSeasonal(), Theta(), ExponentialSmoothing()] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): naive.fit(self.seq1) def test_fit_univar_ts_with_covariates_for_local_models(self): naive = NaiveEnsembleModel( [NaiveDrift(), NaiveSeasonal(), Theta(), ExponentialSmoothing()] ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): naive.fit(self.series1, self.series2) def test_predict_with_target(self): @@ -457,44 +450,44 @@ def test_predict_with_target(self): ensemble_model.fit(series_short, past_covariates=series_long) # predict after end of train series preds = ensemble_model.predict(n=5, past_covariates=series_long) - self.assertTrue(isinstance(preds, TimeSeries)) + assert isinstance(preds, TimeSeries) # predict a new target series preds = ensemble_model.predict( n=5, series=series_long, past_covariates=series_long ) - self.assertTrue(isinstance(preds, TimeSeries)) + assert isinstance(preds, TimeSeries) # predict multiple target series preds = ensemble_model.predict( n=5, series=[series_long] * 2, past_covariates=[series_long] * 2 ) - self.assertTrue(isinstance(preds, list) and len(preds) == 2) + assert isinstance(preds, list) and len(preds) == 2 # predict single target series in list preds = ensemble_model.predict( n=5, series=[series_long], past_covariates=[series_long] ) - self.assertTrue(isinstance(preds, list) and len(preds) == 1) + assert isinstance(preds, list) and len(preds) == 1 # train with multiple series ensemble_model = self.get_global_ensemble_model() ensemble_model.fit([series_short] * 2, past_covariates=[series_long] * 2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # predict without passing series should raise an error ensemble_model.predict(n=5, past_covariates=series_long) # predict a new target series preds = ensemble_model.predict( n=5, series=series_long, past_covariates=series_long ) - self.assertTrue(isinstance(preds, TimeSeries)) + assert isinstance(preds, TimeSeries) # predict multiple target series preds = ensemble_model.predict( n=5, series=[series_long] * 2, past_covariates=[series_long] * 2 ) - self.assertTrue(isinstance(preds, list) and len(preds) == 2) + assert isinstance(preds, list) and len(preds) == 2 # predict single target series in list preds = ensemble_model.predict( n=5, series=[series_long], past_covariates=[series_long] ) - self.assertTrue(isinstance(preds, list) and len(preds) == 1) + assert isinstance(preds, list) and len(preds) == 1 @staticmethod def get_global_ensemble_model(output_chunk_length=5): @@ -513,9 +506,3 @@ def get_global_ensemble_model(output_chunk_length=5): ), ], ) - - -if __name__ == "__main__": - import unittest - - unittest.main() diff --git a/darts/tests/models/forecasting/test_exponential_smoothing.py b/darts/tests/models/forecasting/test_exponential_smoothing.py index 4d04e45955..173a2ba508 100644 --- a/darts/tests/models/forecasting/test_exponential_smoothing.py +++ b/darts/tests/models/forecasting/test_exponential_smoothing.py @@ -2,16 +2,15 @@ from darts import TimeSeries from darts.models import ExponentialSmoothing -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg -class ExponentialSmoothingTestCase(DartsBaseTestClass): +class TestExponentialSmoothing: def helper_test_seasonality_inference(self, freq_string, expected_seasonal_periods): series = tg.sine_timeseries(length=200, freq=freq_string) model = ExponentialSmoothing() model.fit(series) - self.assertEqual(model.seasonal_periods, expected_seasonal_periods) + assert model.seasonal_periods == expected_seasonal_periods def test_seasonality_inference(self): @@ -31,7 +30,7 @@ def test_seasonality_inference(self): series = TimeSeries.from_values(np.arange(1, 30, 1)) model = ExponentialSmoothing() model.fit(series) - self.assertEqual(model.seasonal_periods, 12) + assert model.seasonal_periods == 12 # test whether a model that inferred a seasonality period before will do it again for a new series series1 = tg.sine_timeseries(length=100, freq="M") @@ -39,4 +38,4 @@ def test_seasonality_inference(self): model = ExponentialSmoothing() model.fit(series1) model.fit(series2) - self.assertEqual(model.seasonal_periods, 7) + assert model.seasonal_periods == 7 diff --git a/darts/tests/models/forecasting/test_fft.py b/darts/tests/models/forecasting/test_fft.py index f6ebf87468..17632b1538 100644 --- a/darts/tests/models/forecasting/test_fft.py +++ b/darts/tests/models/forecasting/test_fft.py @@ -1,16 +1,15 @@ import numpy as np from darts.models.forecasting.fft import _find_relevant_timestamp_attributes -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg -class FFTTestCase(DartsBaseTestClass): +class TestFFT: def helper_relevant_attributes(self, freq, length, period_attributes_tuples): # test random walk random_walk_ts = tg.random_walk_timeseries(freq=freq, length=length) - self.assertEqual(_find_relevant_timestamp_attributes(random_walk_ts), set()) + assert _find_relevant_timestamp_attributes(random_walk_ts) == set() for period, relevant_attributes in period_attributes_tuples: @@ -18,21 +17,18 @@ def helper_relevant_attributes(self, freq, length, period_attributes_tuples): seasonal_ts = tg.sine_timeseries( freq=freq, value_frequency=1 / period, length=length ) - self.assertEqual( - _find_relevant_timestamp_attributes(seasonal_ts), - relevant_attributes, - "failed to recognize season in non-noisy timeseries", - ) + assert ( + _find_relevant_timestamp_attributes(seasonal_ts) == relevant_attributes + ), "failed to recognize season in non-noisy timeseries" # test seasonal period with no noise seasonal_noisy_ts = seasonal_ts + tg.gaussian_timeseries( freq=freq, length=length ) - self.assertEqual( - _find_relevant_timestamp_attributes(seasonal_noisy_ts), - relevant_attributes, - "failed to recognize season in noisy timeseries", - ) + assert ( + _find_relevant_timestamp_attributes(seasonal_noisy_ts) + == relevant_attributes + ), "failed to recognize season in noisy timeseries" def test_find_relevant_timestamp_attributes(self): diff --git a/darts/tests/models/forecasting/test_global_forecasting_models.py b/darts/tests/models/forecasting/test_global_forecasting_models.py index abe59b48fa..c8238f130e 100644 --- a/darts/tests/models/forecasting/test_global_forecasting_models.py +++ b/darts/tests/models/forecasting/test_global_forecasting_models.py @@ -1,6 +1,4 @@ import os -import shutil -import tempfile from copy import deepcopy from unittest.mock import ANY, patch @@ -12,7 +10,7 @@ from darts.datasets import AirPassengersDataset from darts.logging import get_logger from darts.metrics import mape -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg from darts.utils.timeseries_generation import linear_timeseries @@ -155,7 +153,7 @@ ), ] - class GlobalForecastingModelsTestCase(DartsBaseTestClass): + class TestGlobalForecastingModels: # forecasting horizon used in runnability tests forecasting_horizon = 12 @@ -208,28 +206,18 @@ class GlobalForecastingModelsTestCase(DartsBaseTestClass): target = sine_1_ts + sine_2_ts + linear_ts + sine_3_ts target_past, target_future = target.split_after(split_ratio) - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - - def test_save_model_parameters(self): + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_save_model_parameters(self, config): # model creation parameters were saved before. check if re-created model has same params as original - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs - ) - self.assertTrue( - model._model_params, model.untrained_model()._model_params - ) - - def test_save_load_model(self): - # check if save and load methods work and if loaded model creates same forecasts as original model - cwd = os.getcwd() - os.chdir(self.temp_work_dir) + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + ) + assert model._model_params, model.untrained_model()._model_params - for model in [ + @pytest.mark.parametrize( + "model", + [ RNNModel( input_chunk_length=4, hidden_dim=10, @@ -244,193 +232,182 @@ def test_save_load_model(self): batch_size=32, **tfm_kwargs, ), - ]: - model_path_str = type(model).__name__ - full_model_path_str = os.path.join(self.temp_work_dir, model_path_str) - - model.fit(self.ts_pass_train) - model_prediction = model.predict(self.forecasting_horizon) - - # test save - model.save() - model.save(model_path_str) - - self.assertTrue(os.path.exists(full_model_path_str)) - self.assertTrue( - len( - [ - p - for p in os.listdir(self.temp_work_dir) - if p.startswith(type(model).__name__) - ] - ) - == 4 + ], + ) + def test_save_load_model(self, tmpdir_module, model): + # check if save and load methods work and if loaded model creates same forecasts as original model + cwd = os.getcwd() + os.chdir(tmpdir_module) + model_path_str = type(model).__name__ + full_model_path_str = os.path.join(tmpdir_module, model_path_str) + + model.fit(self.ts_pass_train) + model_prediction = model.predict(self.forecasting_horizon) + + # test save + model.save() + model.save(model_path_str) + + assert os.path.exists(full_model_path_str) + assert ( + len( + [ + p + for p in os.listdir(tmpdir_module) + if p.startswith(type(model).__name__) + ] ) + == 4 + ) - # test load - loaded_model = type(model).load(model_path_str) + # test load + loaded_model = type(model).load(model_path_str) - self.assertEqual( - model_prediction, loaded_model.predict(self.forecasting_horizon) - ) + assert model_prediction == loaded_model.predict(self.forecasting_horizon) os.chdir(cwd) - def test_single_ts(self): - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, - output_chunk_length=OUT_LEN, - random_state=0, - **kwargs, - ) - model.fit(self.ts_pass_train) - pred = model.predict(n=36) - mape_err = mape(self.ts_pass_val, pred) - self.assertTrue( - mape_err < err, - "Model {} produces errors too high (one time " - "series). Error = {}".format(model_cls, mape_err), - ) - self.assertTrue( - pred.static_covariates.equals(self.ts_passengers.static_covariates) - ) + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_single_ts(self, config): + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, + output_chunk_length=OUT_LEN, + random_state=0, + **kwargs, + ) + model.fit(self.ts_pass_train) + pred = model.predict(n=36) + mape_err = mape(self.ts_pass_val, pred) + assert mape_err < err, ( + "Model {} produces errors too high (one time " + "series). Error = {}".format(model_cls, mape_err) + ) + assert pred.static_covariates.equals(self.ts_passengers.static_covariates) - def test_multi_ts(self): - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, - output_chunk_length=OUT_LEN, - random_state=0, - **kwargs, - ) - model.fit([self.ts_pass_train, self.ts_pass_train_1]) - with self.assertRaises(ValueError): - # when model is fit from >1 series, one must provide a series in argument - model.predict(n=1) - pred = model.predict(n=36, series=self.ts_pass_train) + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_multi_ts(self, config): + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, + output_chunk_length=OUT_LEN, + random_state=0, + **kwargs, + ) + model.fit([self.ts_pass_train, self.ts_pass_train_1]) + with pytest.raises(ValueError): + # when model is fit from >1 series, one must provide a series in argument + model.predict(n=1) + pred = model.predict(n=36, series=self.ts_pass_train) + mape_err = mape(self.ts_pass_val, pred) + assert mape_err < err, ( + "Model {} produces errors too high (several time " + "series). Error = {}".format(model_cls, mape_err) + ) + + # check prediction for several time series + pred_list = model.predict( + n=36, series=[self.ts_pass_train, self.ts_pass_train_1] + ) + assert ( + len(pred_list) == 2 + ), f"Model {model_cls} did not return a list of prediction" + for pred in pred_list: mape_err = mape(self.ts_pass_val, pred) - self.assertTrue( - mape_err < err, - "Model {} produces errors too high (several time " - "series). Error = {}".format(model_cls, mape_err), + assert mape_err < err, ( + "Model {} produces errors too high (several time series 2). " + "Error = {}".format(model_cls, mape_err) ) - # check prediction for several time series - pred_list = model.predict( - n=36, series=[self.ts_pass_train, self.ts_pass_train_1] - ) - self.assertTrue( - len(pred_list) == 2, - f"Model {model_cls} did not return a list of prediction", - ) - for pred in pred_list: - mape_err = mape(self.ts_pass_val, pred) - self.assertTrue( - mape_err < err, - "Model {} produces errors too high (several time series 2). " - "Error = {}".format(model_cls, mape_err), - ) - - def test_covariates(self): - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, - output_chunk_length=OUT_LEN, - random_state=0, - **kwargs, - ) + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_covariates(self, config): + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, + output_chunk_length=OUT_LEN, + random_state=0, + **kwargs, + ) - # Here we rely on the fact that all non-Dual models currently are Past models - if isinstance(model, DualCovariatesTorchModel): - cov_name = "future_covariates" - is_past = False - else: - cov_name = "past_covariates" - is_past = True - - cov_kwargs = { - cov_name: [self.time_covariates_train, self.time_covariates_train] - } - model.fit( - series=[self.ts_pass_train, self.ts_pass_train_1], **cov_kwargs - ) - with self.assertRaises(ValueError): - # when model is fit from >1 series, one must provide a series in argument - model.predict(n=1) - - with self.assertRaises(ValueError): - # when model is fit using multiple covariates, covariates are required at prediction time - model.predict(n=1, series=self.ts_pass_train) - - cov_kwargs_train = {cov_name: self.time_covariates_train} - cov_kwargs_notrain = {cov_name: self.time_covariates} - with self.assertRaises(ValueError): - # when model is fit using covariates, n cannot be greater than output_chunk_length... - # (for short covariates) - # past covariates model can predict up until output_chunk_length - # with train future covariates we cannot predict at all after end of series - model.predict( - n=13 if is_past else 1, - series=self.ts_pass_train, - **cov_kwargs_train, - ) - - # ... unless future covariates are provided - pred = model.predict( - n=13, series=self.ts_pass_train, **cov_kwargs_notrain + # Here we rely on the fact that all non-Dual models currently are Past models + if isinstance(model, DualCovariatesTorchModel): + cov_name = "future_covariates" + is_past = False + else: + cov_name = "past_covariates" + is_past = True + + cov_kwargs = { + cov_name: [self.time_covariates_train, self.time_covariates_train] + } + model.fit(series=[self.ts_pass_train, self.ts_pass_train_1], **cov_kwargs) + with pytest.raises(ValueError): + # when model is fit from >1 series, one must provide a series in argument + model.predict(n=1) + + with pytest.raises(ValueError): + # when model is fit using multiple covariates, covariates are required at prediction time + model.predict(n=1, series=self.ts_pass_train) + + cov_kwargs_train = {cov_name: self.time_covariates_train} + cov_kwargs_notrain = {cov_name: self.time_covariates} + with pytest.raises(ValueError): + # when model is fit using covariates, n cannot be greater than output_chunk_length... + # (for short covariates) + # past covariates model can predict up until output_chunk_length + # with train future covariates we cannot predict at all after end of series + model.predict( + n=13 if is_past else 1, + series=self.ts_pass_train, + **cov_kwargs_train, ) - pred = model.predict( - n=12, series=self.ts_pass_train, **cov_kwargs_notrain + # ... unless future covariates are provided + _ = model.predict(n=13, series=self.ts_pass_train, **cov_kwargs_notrain) + + pred = model.predict(n=12, series=self.ts_pass_train, **cov_kwargs_notrain) + mape_err = mape(self.ts_pass_val, pred) + assert mape_err < err, ( + "Model {} produces errors too high (several time " + "series with covariates). Error = {}".format(model_cls, mape_err) + ) + + # when model is fit using 1 training and 1 covariate series, time series args are optional + if model._is_probabilistic: + return + model = model_cls( + input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + ) + model.fit(series=self.ts_pass_train, **cov_kwargs_train) + if is_past: + # with past covariates from train we can predict up until output_chunk_length + pred1 = model.predict(1) + pred2 = model.predict(1, series=self.ts_pass_train) + pred3 = model.predict(1, **cov_kwargs_train) + pred4 = model.predict(1, **cov_kwargs_train, series=self.ts_pass_train) + else: + # with future covariates we need additional time steps to predict + with pytest.raises(ValueError): + _ = model.predict(1) + with pytest.raises(ValueError): + _ = model.predict(1, series=self.ts_pass_train) + with pytest.raises(ValueError): + _ = model.predict(1, **cov_kwargs_train) + with pytest.raises(ValueError): + _ = model.predict(1, **cov_kwargs_train, series=self.ts_pass_train) + + pred1 = model.predict(1, **cov_kwargs_notrain) + pred2 = model.predict( + 1, series=self.ts_pass_train, **cov_kwargs_notrain ) - mape_err = mape(self.ts_pass_val, pred) - self.assertTrue( - mape_err < err, - "Model {} produces errors too high (several time " - "series with covariates). Error = {}".format(model_cls, mape_err), + pred3 = model.predict(1, **cov_kwargs_notrain) + pred4 = model.predict( + 1, **cov_kwargs_notrain, series=self.ts_pass_train ) - # when model is fit using 1 training and 1 covariate series, time series args are optional - if model._is_probabilistic: - continue - model = model_cls( - input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs - ) - model.fit(series=self.ts_pass_train, **cov_kwargs_train) - if is_past: - # with past covariates from train we can predict up until output_chunk_length - pred1 = model.predict(1) - pred2 = model.predict(1, series=self.ts_pass_train) - pred3 = model.predict(1, **cov_kwargs_train) - pred4 = model.predict( - 1, **cov_kwargs_train, series=self.ts_pass_train - ) - else: - # with future covariates we need additional time steps to predict - with pytest.raises(ValueError): - _ = model.predict(1) - with pytest.raises(ValueError): - _ = model.predict(1, series=self.ts_pass_train) - with pytest.raises(ValueError): - _ = model.predict(1, **cov_kwargs_train) - with pytest.raises(ValueError): - _ = model.predict( - 1, **cov_kwargs_train, series=self.ts_pass_train - ) - - pred1 = model.predict(1, **cov_kwargs_notrain) - pred2 = model.predict( - 1, series=self.ts_pass_train, **cov_kwargs_notrain - ) - pred3 = model.predict(1, **cov_kwargs_notrain) - pred4 = model.predict( - 1, **cov_kwargs_notrain, series=self.ts_pass_train - ) - - self.assertEqual(pred1, pred2) - self.assertEqual(pred1, pred3) - self.assertEqual(pred1, pred4) + assert pred1 == pred2 + assert pred1 == pred3 + assert pred1 == pred4 def test_future_covariates(self): # models with future covariates should produce better predictions over a long forecasting horizon @@ -455,24 +432,22 @@ def test_future_covariates(self): ) model.fit(series=self.target_past, past_covariates=self.covariates_past) long_pred_with_cov = model.predict(n=160, past_covariates=self.covariates) - self.assertTrue( - mape(self.target_future, long_pred_no_cov) - > mape(self.target_future, long_pred_with_cov), - "Models with future covariates should produce better predictions.", - ) + assert mape(self.target_future, long_pred_no_cov) > mape( + self.target_future, long_pred_with_cov + ), "Models with future covariates should produce better predictions." # block models can predict up to self.output_chunk_length points beyond the last future covariate... model.predict(n=165, past_covariates=self.covariates) # ... not more - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.predict(n=166, series=self.ts_pass_train) # recurrent models can only predict data points for time steps where future covariates are available model = RNNModel(12, n_epochs=1, **tfm_kwargs) model.fit(series=self.target_past, future_covariates=self.covariates_past) model.predict(n=160, future_covariates=self.covariates) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.predict(n=161, future_covariates=self.covariates) def test_batch_predictions(self): @@ -515,9 +490,7 @@ def _batch_prediction_test_helper_function(self, targets): batch_size=batch_size, ) for i in range(len(targets)): - self.assertLess( - sum(sum((preds[i] - preds_default[i]).values())), epsilon - ) + assert sum(sum((preds[i] - preds_default[i]).values())) < epsilon def test_predict_from_dataset_unsupported_input(self): # an exception should be thrown if an unsupported type is passed @@ -529,135 +502,135 @@ def test_predict_from_dataset_unsupported_input(self): ) model.fit([self.ts_pass_train, self.ts_pass_train_1]) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.predict_from_dataset(n=1, input_series_dataset=unsupported_type) - def test_prediction_with_different_n(self): + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_prediction_with_different_n(self, config): # test model predictions for n < out_len, n == out_len and n > out_len - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs - ) - self.assertTrue( - isinstance( - model, - ( - PastCovariatesTorchModel, - DualCovariatesTorchModel, - MixedCovariatesTorchModel, - ), - ), - "unit test not yet defined for the given {X}CovariatesTorchModel.", - ) + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + ) + assert isinstance( + model, + ( + PastCovariatesTorchModel, + DualCovariatesTorchModel, + MixedCovariatesTorchModel, + ), + ), "unit test not yet defined for the given {X}CovariatesTorchModel." - if isinstance(model, PastCovariatesTorchModel): - past_covs, future_covs = self.covariates, None - elif isinstance(model, DualCovariatesTorchModel): - past_covs, future_covs = None, self.covariates - else: - past_covs, future_covs = self.covariates, self.covariates - - model.fit( - self.target_past, - past_covariates=past_covs, - future_covariates=future_covs, - epochs=1, - ) + if isinstance(model, PastCovariatesTorchModel): + past_covs, future_covs = self.covariates, None + elif isinstance(model, DualCovariatesTorchModel): + past_covs, future_covs = None, self.covariates + else: + past_covs, future_covs = self.covariates, self.covariates - # test prediction for n < out_len, n == out_len and n > out_len - for n in [OUT_LEN - 1, OUT_LEN, 2 * OUT_LEN - 1]: - pred = model.predict( - n=n, past_covariates=past_covs, future_covariates=future_covs - ) - self.assertEqual(len(pred), n) - - def test_same_result_with_different_n_jobs(self): - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + model.fit( + self.target_past, + past_covariates=past_covs, + future_covariates=future_covs, + epochs=1, + ) + + # test prediction for n < out_len, n == out_len and n > out_len + for n in [OUT_LEN - 1, OUT_LEN, 2 * OUT_LEN - 1]: + pred = model.predict( + n=n, past_covariates=past_covs, future_covariates=future_covs ) + assert len(pred) == n - multiple_ts = [self.ts_pass_train] * 10 + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_same_result_with_different_n_jobs(self, config): + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + ) - model.fit(multiple_ts) + multiple_ts = [self.ts_pass_train] * 10 - # safe random state for two successive identical predictions - if model._is_probabilistic: - random_state = deepcopy(model._random_instance) - else: - random_state = None + model.fit(multiple_ts) - pred1 = model.predict(n=36, series=multiple_ts, n_jobs=1) + # safe random state for two successive identical predictions + if model._is_probabilistic: + random_state = deepcopy(model._random_instance) + else: + random_state = None - if random_state is not None: - model._random_instance = random_state + pred1 = model.predict(n=36, series=multiple_ts, n_jobs=1) - pred2 = model.predict( - n=36, series=multiple_ts, n_jobs=-1 - ) # assuming > 1 core available in the machine - self.assertEqual( - pred1, - pred2, - "Model {} produces different predictions with different number of jobs", - ) + if random_state is not None: + model._random_instance = random_state + + pred2 = model.predict( + n=36, series=multiple_ts, n_jobs=-1 + ) # assuming > 1 core available in the machine + assert ( + pred1 == pred2 + ), "Model {} produces different predictions with different number of jobs" @patch( "darts.models.forecasting.torch_forecasting_model.TorchForecastingModel._init_trainer" ) - def test_fit_with_constr_epochs(self, init_trainer): - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs - ) - multiple_ts = [self.ts_pass_train] * 10 - model.fit(multiple_ts) + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_fit_with_constr_epochs(self, init_trainer, config): + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + ) + multiple_ts = [self.ts_pass_train] * 10 + model.fit(multiple_ts) - init_trainer.assert_called_with( - max_epochs=kwargs["n_epochs"], trainer_params=ANY - ) + init_trainer.assert_called_with( + max_epochs=kwargs["n_epochs"], trainer_params=ANY + ) @patch( "darts.models.forecasting.torch_forecasting_model.TorchForecastingModel._init_trainer" ) - def test_fit_with_fit_epochs(self, init_trainer): - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs - ) - multiple_ts = [self.ts_pass_train] * 10 - epochs = 3 + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_fit_with_fit_epochs(self, init_trainer, config): + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + ) + multiple_ts = [self.ts_pass_train] * 10 + epochs = 3 - model.fit(multiple_ts, epochs=epochs) - init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) + model.fit(multiple_ts, epochs=epochs) + init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) - model.total_epochs = epochs - # continue training - model.fit(multiple_ts, epochs=epochs) - init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) + model.total_epochs = epochs + # continue training + model.fit(multiple_ts, epochs=epochs) + init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) @patch( "darts.models.forecasting.torch_forecasting_model.TorchForecastingModel._init_trainer" ) - def test_fit_from_dataset_with_epochs(self, init_trainer): - for model_cls, kwargs, err in models_cls_kwargs_errs: - model = model_cls( - input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs - ) - multiple_ts = [self.ts_pass_train] * 10 - train_dataset = model._build_train_dataset( - multiple_ts, - past_covariates=None, - future_covariates=None, - max_samples_per_ts=None, - ) - epochs = 3 + @pytest.mark.parametrize("config", models_cls_kwargs_errs) + def test_fit_from_dataset_with_epochs(self, init_trainer, config): + model_cls, kwargs, err = config + model = model_cls( + input_chunk_length=IN_LEN, output_chunk_length=OUT_LEN, **kwargs + ) + multiple_ts = [self.ts_pass_train] * 10 + train_dataset = model._build_train_dataset( + multiple_ts, + past_covariates=None, + future_covariates=None, + max_samples_per_ts=None, + ) + epochs = 3 - model.fit_from_dataset(train_dataset, epochs=epochs) - init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) + model.fit_from_dataset(train_dataset, epochs=epochs) + init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) - # continue training - model.fit_from_dataset(train_dataset, epochs=epochs) - init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) + # continue training + model.fit_from_dataset(train_dataset, epochs=epochs) + init_trainer.assert_called_with(max_epochs=epochs, trainer_params=ANY) def test_predit_after_fit_from_dataset(self): model_cls, kwargs, _ = models_cls_kwargs_errs[0] diff --git a/darts/tests/models/forecasting/test_historical_forecasts.py b/darts/tests/models/forecasting/test_historical_forecasts.py index 6258462077..6fc18d24d9 100644 --- a/darts/tests/models/forecasting/test_historical_forecasts.py +++ b/darts/tests/models/forecasting/test_historical_forecasts.py @@ -1,4 +1,4 @@ -import unittest +import itertools import numpy as np import pandas as pd @@ -18,7 +18,7 @@ NaiveSeasonal, NotImportedModule, ) -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg try: @@ -44,49 +44,58 @@ ) TORCH_AVAILABLE = False -models_reg_no_cov_cls_kwargs = [(LinearRegressionModel, {"lags": 8}, (8, 1))] +models_reg_no_cov_cls_kwargs = [(LinearRegressionModel, {"lags": 8}, {}, (8, 1))] if not isinstance(CatBoostModel, NotImportedModule): - models_reg_no_cov_cls_kwargs.append((CatBoostModel, {"lags": 6}, (6, 1))) + models_reg_no_cov_cls_kwargs.append( + (CatBoostModel, {"lags": 6}, {"iterations": 1}, (6, 1)) + ) if not isinstance(LightGBMModel, NotImportedModule): - models_reg_no_cov_cls_kwargs.append((LightGBMModel, {"lags": 4}, (4, 1))) + models_reg_no_cov_cls_kwargs.append( + (LightGBMModel, {"lags": 4}, {"n_estimators": 1}, (4, 1)) + ) models_reg_cov_cls_kwargs = [ # target + past covariates - (LinearRegressionModel, {"lags": 4, "lags_past_covariates": 6}, (6, 1)), + (LinearRegressionModel, {"lags": 4, "lags_past_covariates": 6}, {}, (6, 1)), # target + past covariates + outputchunk > 3, 6 > 3 ( LinearRegressionModel, {"lags": 3, "lags_past_covariates": 6, "output_chunk_length": 5}, + {}, (6, 5), ), # target + future covariates, 2 because to predict x, require x and x+1 - (LinearRegressionModel, {"lags": 4, "lags_future_covariates": [0, 1]}, (4, 2)), + (LinearRegressionModel, {"lags": 4, "lags_future_covariates": [0, 1]}, {}, (4, 2)), # target + fut cov + output_chunk_length > 3, ( LinearRegressionModel, {"lags": 2, "lags_future_covariates": [1, 2], "output_chunk_length": 5}, + {}, (2, 5), ), # fut cov + output_chunk_length > 3, 5 > 2 ( LinearRegressionModel, {"lags_future_covariates": [0, 1], "output_chunk_length": 5}, + {}, (0, 5), ), # past cov only - (LinearRegressionModel, {"lags_past_covariates": 6}, (6, 1)), + (LinearRegressionModel, {"lags_past_covariates": 6}, {}, (6, 1)), # fut cov only - (LinearRegressionModel, {"lags_future_covariates": [0, 1]}, (0, 2)), + (LinearRegressionModel, {"lags_future_covariates": [0, 1]}, {}, (0, 2)), # fut + past cov only ( LinearRegressionModel, {"lags_past_covariates": 6, "lags_future_covariates": [0, 1]}, + {}, (6, 2), ), # all ( LinearRegressionModel, {"lags": 3, "lags_past_covariates": 6, "lags_future_covariates": [0, 1]}, + {}, (6, 2), ), ] @@ -224,7 +233,7 @@ ] -class HistoricalforecastTestCase(DartsBaseTestClass): +class TestHistoricalforecast: np.random.seed(42) if TORCH_AVAILABLE: torch.manual_seed(42) @@ -312,6 +321,16 @@ class HistoricalforecastTestCase(DartsBaseTestClass): start=ts_pass_val_range.start_time(), ) + # optimized historical forecasts + start_ts = pd.Timestamp("2000-01-01") + ts_univariate = tg.linear_timeseries( + start_value=1, end_value=100, length=20, start=start_ts + ) + ts_multivariate = ts_univariate.stack(tg.sine_timeseries(length=20, start=start_ts)) + + # slightly longer to not affect the last predictable timestamp + ts_covs = tg.gaussian_timeseries(length=30, start=start_ts) + def test_historical_forecasts_transferrable_future_cov_local_models(self): model = ARIMA() assert model.min_train_series_length == 30 @@ -415,177 +434,170 @@ def test_historical_forecasts_negative_rangeindex(self): assert len(forecasts) == 2 assert (series.time_index[-2:] == forecasts.time_index).all() - def test_historical_forecasts(self): + @pytest.mark.parametrize("config", models_reg_no_cov_cls_kwargs) + def test_historical_forecasts(self, config): train_length = 10 forecast_horizon = 8 # if no fit and retrain=false, should fit at fist iteration - for model_cls, kwargs, bounds in models_reg_no_cov_cls_kwargs: - model = model_cls( - **kwargs, - ) + model_cls, kwargs, model_kwarg, bounds = config + model = model_cls(**kwargs, **model_kwarg) - # time index - forecasts = model.historical_forecasts( - series=self.ts_pass_val, - forecast_horizon=forecast_horizon, - stride=1, - train_length=train_length, - retrain=True, - overlap_end=False, - ) + # time index + forecasts = model.historical_forecasts( + series=self.ts_pass_val, + forecast_horizon=forecast_horizon, + stride=1, + train_length=train_length, + retrain=True, + overlap_end=False, + ) - theorical_forecast_length = ( - self.ts_val_length - - max( - [ - ( - bounds[0] + bounds[1] + 1 - ), # +1 as sklearn models require min 2 train samples - train_length, - ] - ) # because we train - - forecast_horizon # because we have overlap_end = False - + 1 # because we include the first element - ) + theorical_forecast_length = ( + self.ts_val_length + - max( + [ + ( + bounds[0] + bounds[1] + 1 + ), # +1 as sklearn models require min 2 train samples + train_length, + ] + ) # because we train + - forecast_horizon # because we have overlap_end = False + + 1 # because we include the first element + ) - self.assertTrue( - len(forecasts) == theorical_forecast_length, - f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " - f"of retrain=True and overlap_end=False, and a time index of type DateTimeIndex. " - f"Expected {theorical_forecast_length}, got {len(forecasts)}", - ) + assert len(forecasts) == theorical_forecast_length, ( + f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " + f"of retrain=True and overlap_end=False, and a time index of type DateTimeIndex. " + f"Expected {theorical_forecast_length}, got {len(forecasts)}" + ) - # range index - forecasts = model.historical_forecasts( - series=self.ts_pass_val_range, - forecast_horizon=forecast_horizon, - train_length=train_length, - stride=1, - retrain=True, - overlap_end=False, - ) + # range index + forecasts = model.historical_forecasts( + series=self.ts_pass_val_range, + forecast_horizon=forecast_horizon, + train_length=train_length, + stride=1, + retrain=True, + overlap_end=False, + ) - self.assertTrue( - len(forecasts) == theorical_forecast_length, - f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " - f"of retrain=True, overlap_end=False, and a time index of type RangeIndex." - f"Expected {theorical_forecast_length}, got {len(forecasts)}", - ) + assert len(forecasts) == theorical_forecast_length, ( + f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " + f"of retrain=True, overlap_end=False, and a time index of type RangeIndex." + f"Expected {theorical_forecast_length}, got {len(forecasts)}" + ) - # stride 2 - forecasts = model.historical_forecasts( - series=self.ts_pass_val_range, - forecast_horizon=forecast_horizon, - train_length=train_length, - stride=2, - retrain=True, - overlap_end=False, - ) + # stride 2 + forecasts = model.historical_forecasts( + series=self.ts_pass_val_range, + forecast_horizon=forecast_horizon, + train_length=train_length, + stride=2, + retrain=True, + overlap_end=False, + ) - theorical_forecast_length = np.floor( + theorical_forecast_length = np.floor( + ( ( - ( - self.ts_val_length - - max( - [ - ( - bounds[0] + bounds[1] + 1 - ), # +1 as sklearn models require min 2 train samples - train_length, - ] - ) # because we train - - forecast_horizon # because we have overlap_end = False - + 1 # because we include the first element - ) - - 1 + self.ts_val_length + - max( + [ + ( + bounds[0] + bounds[1] + 1 + ), # +1 as sklearn models require min 2 train samples + train_length, + ] + ) # because we train + - forecast_horizon # because we have overlap_end = False + + 1 # because we include the first element ) - / 2 - + 1 # because of stride - ) # if odd number of elements, we keep the floor - - self.assertTrue( - len(forecasts) == theorical_forecast_length, - f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " - f"of retrain=True and overlap_end=False and stride=2. " - f"Expected {theorical_forecast_length}, got {len(forecasts)}", + - 1 ) + / 2 + + 1 # because of stride + ) # if odd number of elements, we keep the floor - # stride 3 - forecasts = model.historical_forecasts( - series=self.ts_pass_val_range, - forecast_horizon=forecast_horizon, - train_length=train_length, - stride=3, - retrain=True, - overlap_end=False, - ) + assert len(forecasts) == theorical_forecast_length, ( + f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " + f"of retrain=True and overlap_end=False and stride=2. " + f"Expected {theorical_forecast_length}, got {len(forecasts)}" + ) - theorical_forecast_length = np.floor( + # stride 3 + forecasts = model.historical_forecasts( + series=self.ts_pass_val_range, + forecast_horizon=forecast_horizon, + train_length=train_length, + stride=3, + retrain=True, + overlap_end=False, + ) + + theorical_forecast_length = np.floor( + ( ( - ( - self.ts_val_length - - max( - [ - ( - bounds[0] + bounds[1] + 1 - ), # +1 as sklearn models require min 2 train samples - train_length, - ] - ) # because we train - - forecast_horizon # because we have overlap_end = False - + 1 # because we include the first element - ) - - 1 - ) # the first is always included, so we calculate a modulo on the rest - / 3 # because of stride - + 1 # and we readd the first - ) # if odd number of elements, we keep the floor - - # Here to adapt if forecast_horizon or train_length change - self.assertTrue( - len(forecasts) == theorical_forecast_length, - f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " - f"of retrain=True and overlap_end=False and stride=3. " - f"Expected {theorical_forecast_length}, got {len(forecasts)}", - ) + self.ts_val_length + - max( + [ + ( + bounds[0] + bounds[1] + 1 + ), # +1 as sklearn models require min 2 train samples + train_length, + ] + ) # because we train + - forecast_horizon # because we have overlap_end = False + + 1 # because we include the first element + ) + - 1 + ) # the first is always included, so we calculate a modulo on the rest + / 3 # because of stride + + 1 # and we readd the first + ) # if odd number of elements, we keep the floor + + # Here to adapt if forecast_horizon or train_length change + assert len(forecasts) == theorical_forecast_length, ( + f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " + f"of retrain=True and overlap_end=False and stride=3. " + f"Expected {theorical_forecast_length}, got {len(forecasts)}" + ) - # last points only False - forecasts = model.historical_forecasts( - series=self.ts_pass_val_range, - forecast_horizon=forecast_horizon, - train_length=train_length, - stride=1, - retrain=True, - overlap_end=False, - last_points_only=False, - ) + # last points only False + forecasts = model.historical_forecasts( + series=self.ts_pass_val_range, + forecast_horizon=forecast_horizon, + train_length=train_length, + stride=1, + retrain=True, + overlap_end=False, + last_points_only=False, + ) - theorical_forecast_length = ( - self.ts_val_length - - max( - [ - ( - bounds[0] + bounds[1] + 1 - ), # +1 as sklearn models require min 2 train samples - train_length, - ] - ) # because we train - - forecast_horizon # because we have overlap_end = False - + 1 # because we include the first element - ) + theorical_forecast_length = ( + self.ts_val_length + - max( + [ + ( + bounds[0] + bounds[1] + 1 + ), # +1 as sklearn models require min 2 train samples + train_length, + ] + ) # because we train + - forecast_horizon # because we have overlap_end = False + + 1 # because we include the first element + ) - self.assertTrue( - len(forecasts) == theorical_forecast_length, - f"Model {model_cls} does not return the right number of historical forecasts in the case of " - f"retrain=True and overlap_end=False, and last_points_only=False. " - f"expected {theorical_forecast_length}, got {len(forecasts)}", - ) + assert len(forecasts) == theorical_forecast_length, ( + f"Model {model_cls} does not return the right number of historical forecasts in the case of " + f"retrain=True and overlap_end=False, and last_points_only=False. " + f"expected {theorical_forecast_length}, got {len(forecasts)}" + ) - self.assertTrue( - len(forecasts[0]) == forecast_horizon, - f"Model {model_cls} does not return forecast_horizon points per historical forecast in the case of " - f"retrain=True and overlap_end=False, and last_points_only=False", - ) + assert len(forecasts[0]) == forecast_horizon, ( + f"Model {model_cls} does not return forecast_horizon points per historical forecast in the case of " + f"retrain=True and overlap_end=False, and last_points_only=False" + ) def test_sanity_check_invalid_start(self): timeidx_ = tg.linear_timeseries(length=10) @@ -676,602 +688,571 @@ def test_sanity_check_invalid_start(self): "`start` index `-11` is out of bounds for series of length 10" ) - def test_regression_auto_start_multiple_no_cov(self): + @pytest.mark.parametrize("config", models_reg_no_cov_cls_kwargs) + def test_regression_auto_start_multiple_no_cov(self, config): train_length = 15 forecast_horizon = 10 - for model_cls, kwargs, bounds in models_reg_no_cov_cls_kwargs: - model = model_cls( - **kwargs, - ) - model.fit(self.ts_pass_train) + model_cls, kwargs, model_kwargs, bounds = config + model = model_cls( + **kwargs, + **model_kwargs, + ) + model.fit(self.ts_pass_train) - forecasts = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - forecast_horizon=forecast_horizon, - train_length=train_length, - stride=1, - retrain=True, - overlap_end=False, - ) + forecasts = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + forecast_horizon=forecast_horizon, + train_length=train_length, + stride=1, + retrain=True, + overlap_end=False, + ) - self.assertTrue( - len(forecasts) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) + assert ( + len(forecasts) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" - theorical_forecast_length = ( - self.ts_val_length - - max( - [ - ( - bounds[0] + bounds[1] + 1 - ), # +1 as sklearn models require min 2 train samples - train_length, - ] - ) # because we train - - forecast_horizon # because we have overlap_end = False - + 1 # because we include the first element - ) + theorical_forecast_length = ( + self.ts_val_length + - max( + [ + ( + bounds[0] + bounds[1] + 1 + ), # +1 as sklearn models require min 2 train samples + train_length, + ] + ) # because we train + - forecast_horizon # because we have overlap_end = False + + 1 # because we include the first element + ) - self.assertTrue( - len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length, - f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " - f"of retrain=True and overlap_end=False, and a time index of type DateTimeIndex. " - f"Expected {theorical_forecast_length}, got {len(forecasts[0])} and {len(forecasts[1])}", - ) + assert len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length, ( + f"Model {model_cls.__name__} does not return the right number of historical forecasts in the case " + f"of retrain=True and overlap_end=False, and a time index of type DateTimeIndex. " + f"Expected {theorical_forecast_length}, got {len(forecasts[0])} and {len(forecasts[1])}" + ) @pytest.mark.slow - def test_optimized_historical_forecasts_regression(self): - start_ts = pd.Timestamp("2000-01-01") - ts_univariate = tg.linear_timeseries( - start_value=1, end_value=100, length=20, start=start_ts - ) - ts_multivariate = ts_univariate.stack( - tg.sine_timeseries(length=20, start=start_ts) - ) + @pytest.mark.parametrize( + "config", + itertools.product( + [ts_univariate, ts_multivariate], + models_reg_no_cov_cls_kwargs + models_reg_cov_cls_kwargs, + [True, False], + [1, 5], + ), + ) + def test_optimized_historical_forecasts_regression(self, config): + ts, model_config, multi_models, forecast_horizon = config # slightly longer to not affect the last predictable timestamp - ts_covs = tg.gaussian_timeseries(length=30, start=start_ts) + ts_covs = self.ts_covs start = 14 + model_cls = LinearRegressionModel - for ts in [ts_univariate, ts_multivariate]: - # cover several covariates combinations and several regression models - for _, model_kwargs, _ in ( - models_reg_no_cov_cls_kwargs + models_reg_cov_cls_kwargs - ): - for multi_models in [True, False]: - for forecast_horizon in [1, 5]: - # ocl == forecast horizon - model_kwargs_same = model_kwargs.copy() - model_kwargs_same["output_chunk_length"] = forecast_horizon - model_kwargs_same["multi_models"] = multi_models - model_same = model_cls(**model_kwargs_same) - model_same.fit( - series=ts[:start], - past_covariates=ts_covs - if model_same.supports_past_covariates - else None, - future_covariates=ts_covs - if model_same.supports_future_covariates - else None, - ) - # ocl >= forecast horizon - model_kwargs_diff = model_kwargs.copy() - model_kwargs_diff["output_chunk_length"] = 5 - model_kwargs_diff["multi_models"] = multi_models - model_diff = model_cls(**model_kwargs_same) - model_diff.fit( - series=ts[:start], - past_covariates=ts_covs - if model_diff.supports_past_covariates - else None, - future_covariates=ts_covs - if model_diff.supports_future_covariates - else None, + _, model_kwargs, _, _ = model_config + # cover several covariates combinations and several regression models + # ocl == forecast horizon + model_kwargs_same = model_kwargs.copy() + model_kwargs_same["output_chunk_length"] = forecast_horizon + model_kwargs_same["multi_models"] = multi_models + model_same = model_cls(**model_kwargs_same) + model_same.fit( + series=ts[:start], + past_covariates=ts_covs if model_same.supports_past_covariates else None, + future_covariates=ts_covs + if model_same.supports_future_covariates + else None, + ) + # ocl >= forecast horizon + model_kwargs_diff = model_kwargs.copy() + model_kwargs_diff["output_chunk_length"] = 5 + model_kwargs_diff["multi_models"] = multi_models + model_diff = model_cls(**model_kwargs_same) + model_diff.fit( + series=ts[:start], + past_covariates=ts_covs if model_diff.supports_past_covariates else None, + future_covariates=ts_covs + if model_diff.supports_future_covariates + else None, + ) + # no parametrization to save time on model training at the cost of test granularity + for model in [model_same, model_diff]: + for last_points_only in [True, False]: + for stride in [1, 2]: + hist_fct = model.historical_forecasts( + series=ts, + past_covariates=ts_covs + if model.supports_past_covariates + else None, + future_covariates=ts_covs + if model.supports_future_covariates + else None, + start=start, + retrain=False, + last_points_only=last_points_only, + stride=stride, + forecast_horizon=forecast_horizon, + enable_optimization=False, + ) + + # manually packing the series in list to match expected inputs + opti_hist_fct = model._optimized_historical_forecasts( + series=[ts], + past_covariates=[ts_covs] + if model.supports_past_covariates + else None, + future_covariates=[ts_covs] + if model.supports_future_covariates + else None, + start=start, + last_points_only=last_points_only, + stride=stride, + forecast_horizon=forecast_horizon, + ) + # pack the output to generalize the tests + if last_points_only: + hist_fct = [hist_fct] + opti_hist_fct = [opti_hist_fct] + + for fct, opti_fct in zip(hist_fct, opti_hist_fct): + assert (fct.time_index == opti_fct.time_index).all() + np.testing.assert_array_almost_equal( + fct.all_values(), opti_fct.all_values() ) - for model in [model_same, model_diff]: - for last_points_only in [True, False]: - for stride in [1, 2]: - hist_fct = model.historical_forecasts( - series=ts, - past_covariates=ts_covs - if model.supports_past_covariates - else None, - future_covariates=ts_covs - if model.supports_future_covariates - else None, - start=start, - retrain=False, - last_points_only=last_points_only, - stride=stride, - forecast_horizon=forecast_horizon, - enable_optimization=False, - ) - - # manually packing the series in list to match expected inputs - opti_hist_fct = ( - model._optimized_historical_forecasts( - series=[ts], - past_covariates=[ts_covs] - if model.supports_past_covariates - else None, - future_covariates=[ts_covs] - if model.supports_future_covariates - else None, - start=start, - last_points_only=last_points_only, - stride=stride, - forecast_horizon=forecast_horizon, - ) - ) - # pack the output to generalize the tests - if last_points_only: - hist_fct = [hist_fct] - opti_hist_fct = [opti_hist_fct] - - for fct, opti_fct in zip(hist_fct, opti_hist_fct): - self.assertTrue( - ( - fct.time_index == opti_fct.time_index - ).all() - ) - np.testing.assert_array_almost_equal( - fct.all_values(), opti_fct.all_values() - ) - - def test_optimized_historical_forecasts_regression_with_encoders(self): - for use_covs in [False, True]: - series_train, series_val = self.ts_pass_train, self.ts_pass_val - model = LinearRegressionModel( - lags=3, - lags_past_covariates=2, - lags_future_covariates=[2, 3], - add_encoders={ - "cyclic": {"future": ["month"]}, - "datetime_attribute": {"past": ["dayofweek"]}, - }, - output_chunk_length=5, - ) - if use_covs: - pc = tg.gaussian_timeseries( - start=series_train.start_time() - 2 * series_train.freq, - end=series_val.end_time(), - freq=series_train.freq, - ) - fc = tg.gaussian_timeseries( - start=series_train.start_time() + 3 * series_train.freq, - end=series_val.end_time() + 4 * series_train.freq, - freq=series_train.freq, - ) - else: - pc, fc = None, None - - model.fit(self.ts_pass_train, past_covariates=pc, future_covariates=fc) - - hist_fct = model.historical_forecasts( - series=self.ts_pass_val, - past_covariates=pc, - future_covariates=fc, - retrain=False, - last_points_only=True, - forecast_horizon=5, - enable_optimization=False, - ) - opti_hist_fct = model._optimized_historical_forecasts( - series=[self.ts_pass_val], - past_covariates=[pc], - future_covariates=[fc], - last_points_only=True, - forecast_horizon=5, - ) + @pytest.mark.parametrize("use_covs", [False, True]) + def test_optimized_historical_forecasts_regression_with_encoders(self, use_covs): + series_train, series_val = self.ts_pass_train, self.ts_pass_val + model = LinearRegressionModel( + lags=3, + lags_past_covariates=2, + lags_future_covariates=[2, 3], + add_encoders={ + "cyclic": {"future": ["month"]}, + "datetime_attribute": {"past": ["dayofweek"]}, + }, + output_chunk_length=5, + ) + if use_covs: + pc = tg.gaussian_timeseries( + start=series_train.start_time() - 2 * series_train.freq, + end=series_val.end_time(), + freq=series_train.freq, + ) + fc = tg.gaussian_timeseries( + start=series_train.start_time() + 3 * series_train.freq, + end=series_val.end_time() + 4 * series_train.freq, + freq=series_train.freq, + ) + else: + pc, fc = None, None + + model.fit(self.ts_pass_train, past_covariates=pc, future_covariates=fc) + + hist_fct = model.historical_forecasts( + series=self.ts_pass_val, + past_covariates=pc, + future_covariates=fc, + retrain=False, + last_points_only=True, + forecast_horizon=5, + enable_optimization=False, + ) - self.assertTrue((hist_fct.time_index == opti_hist_fct.time_index).all()) - np.testing.assert_array_almost_equal( - hist_fct.all_values(), opti_hist_fct.all_values() - ) + opti_hist_fct = model._optimized_historical_forecasts( + series=[self.ts_pass_val], + past_covariates=[pc], + future_covariates=[fc], + last_points_only=True, + forecast_horizon=5, + ) + + assert (hist_fct.time_index == opti_hist_fct.time_index).all() + np.testing.assert_array_almost_equal( + hist_fct.all_values(), opti_hist_fct.all_values() + ) @pytest.mark.slow - @unittest.skipUnless( - TORCH_AVAILABLE, - "Torch not available. auto start and multiple time series for torch models will be skipped.", - ) - def test_torch_auto_start_multiple_no_cov(self): + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") + @pytest.mark.parametrize("model_config", models_torch_cls_kwargs) + def test_torch_auto_start_multiple_no_cov(self, model_config): forecast_hrz = 10 - for model_cls, kwargs, bounds, _ in models_torch_cls_kwargs: - model = model_cls( - random_state=0, - **kwargs, - ) - model.fit(self.ts_pass_train) + model_cls, kwargs, bounds, _ = model_config + model = model_cls( + random_state=0, + **kwargs, + ) + model.fit(self.ts_pass_train) - # check historical forecasts for several time series, - # retrain True and overlap_end False - forecasts = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - forecast_horizon=forecast_hrz, - stride=1, - retrain=True, - overlap_end=False, - ) - self.assertTrue( - len(forecasts) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) - # If retrain=True and overlap_end=False, as ts has 72 values, we can only forecast - # (target length)-(training length=input_chunk_length+output_chunk_length) - (horizon - 1) - # indeed we start to predict after the first trainable point (input_chunk_length+output_chunk_length) - # and we stop in this case (overlap_end=False) at the end_time: - # target.end_time() - (horizon - 1) * target.freq - - # explanation: - # (bounds): train sample length - # (horizon - 1): with overlap_end=False, if entire horizon is available (overlap_end=False), - # we can predict 1 - theorical_forecast_length = ( - self.ts_val_length - (bounds[0] + bounds[1]) - (forecast_hrz - 1) - ) - self.assertTrue( - len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length, - f"Model {model_cls} does not return the right number of historical forecasts in the case of " - f"retrain=True and overlap_end=False. " - f"Expected {theorical_forecast_length}, got {len(forecasts[0])} and {len(forecasts[1])}", - ) + # check historical forecasts for several time series, + # retrain True and overlap_end False + forecasts = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + forecast_horizon=forecast_hrz, + stride=1, + retrain=True, + overlap_end=False, + ) + assert ( + len(forecasts) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" + # If retrain=True and overlap_end=False, as ts has 72 values, we can only forecast + # (target length)-(training length=input_chunk_length+output_chunk_length) - (horizon - 1) + # indeed we start to predict after the first trainable point (input_chunk_length+output_chunk_length) + # and we stop in this case (overlap_end=False) at the end_time: + # target.end_time() - (horizon - 1) * target.freq + + # explanation: + # (bounds): train sample length + # (horizon - 1): with overlap_end=False, if entire horizon is available (overlap_end=False), + # we can predict 1 + theorical_forecast_length = ( + self.ts_val_length - (bounds[0] + bounds[1]) - (forecast_hrz - 1) + ) + assert len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length, ( + f"Model {model_cls} does not return the right number of historical forecasts in the case of " + f"retrain=True and overlap_end=False. " + f"Expected {theorical_forecast_length}, got {len(forecasts[0])} and {len(forecasts[1])}" + ) - model = model_cls( - random_state=0, - **kwargs, - ) + model = model_cls( + random_state=0, + **kwargs, + ) - model.fit(self.ts_pass_train) - # check historical forecasts for several time series, - # retrain True and overlap_end True - forecasts = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - forecast_horizon=forecast_hrz, - stride=1, - retrain=True, - overlap_end=True, - ) + model.fit(self.ts_pass_train) + # check historical forecasts for several time series, + # retrain True and overlap_end True + forecasts = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + forecast_horizon=forecast_hrz, + stride=1, + retrain=True, + overlap_end=True, + ) - self.assertTrue( - len(forecasts) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) - theorical_forecast_length = ( - self.ts_val_length - - (bounds[0] + bounds[1]) # train sample length - - 0 # with overlap_end=True, we are not restricted by the end of the series or horizon - ) - self.assertTrue( - len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length - ) + assert ( + len(forecasts) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" + theorical_forecast_length = ( + self.ts_val_length + - (bounds[0] + bounds[1]) # train sample length + - 0 # with overlap_end=True, we are not restricted by the end of the series or horizon + ) + assert len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length - model = model_cls( - random_state=0, - **kwargs, - ) - model.fit(self.ts_pass_train) - # check historical forecasts for several time series, - # retrain False and overlap_end False - forecasts = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - forecast_horizon=forecast_hrz, - stride=1, - retrain=False, - overlap_end=False, - ) + model = model_cls( + random_state=0, + **kwargs, + ) + model.fit(self.ts_pass_train) + # check historical forecasts for several time series, + # retrain False and overlap_end False + forecasts = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + forecast_horizon=forecast_hrz, + stride=1, + retrain=False, + overlap_end=False, + ) - self.assertTrue( - len(forecasts) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) - theorical_forecast_length = ( - self.ts_val_length - - bounds[0] # prediction input sample length - - ( - forecast_hrz - 1 - ) # overlap_end=False -> if entire horizon is available, we can predict 1 - ) - self.assertTrue( - len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length - ) + assert ( + len(forecasts) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" + theorical_forecast_length = ( + self.ts_val_length + - bounds[0] # prediction input sample length + - ( + forecast_hrz - 1 + ) # overlap_end=False -> if entire horizon is available, we can predict 1 + ) + assert len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length - model = model_cls( - random_state=0, - **kwargs, - ) - model.fit(self.ts_pass_train) - # check historical forecasts for several time series, - # retrain False and overlap_end True - forecasts = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - forecast_horizon=forecast_hrz, - stride=1, - retrain=False, - overlap_end=True, - ) + model = model_cls( + random_state=0, + **kwargs, + ) + model.fit(self.ts_pass_train) + # check historical forecasts for several time series, + # retrain False and overlap_end True + forecasts = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + forecast_horizon=forecast_hrz, + stride=1, + retrain=False, + overlap_end=True, + ) - self.assertTrue( - len(forecasts) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) - theorical_forecast_length = ( - self.ts_val_length - - bounds[0] # prediction input sample length - - 0 # overlap_end=False -> we are not restricted by the end of the series or horizon - ) - self.assertTrue( - len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length - ) + assert ( + len(forecasts) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" + theorical_forecast_length = ( + self.ts_val_length + - bounds[0] # prediction input sample length + - 0 # overlap_end=False -> we are not restricted by the end of the series or horizon + ) + assert len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length - def test_regression_auto_start_multiple_with_cov_retrain(self): + @pytest.mark.parametrize("model_config", models_reg_cov_cls_kwargs) + def test_regression_auto_start_multiple_with_cov_retrain(self, model_config): forecast_hrz = 10 - for model_cls, kwargs, bounds in models_reg_cov_cls_kwargs: - model = model_cls( - random_state=0, - **kwargs, - ) + model_cls, kwargs, _, bounds = model_config + model = model_cls( + random_state=0, + **kwargs, + ) - forecasts_retrain = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - past_covariates=[ - self.ts_past_cov_valid_same_start, - self.ts_past_cov_valid_same_start, - ] - if "lags_past_covariates" in kwargs - else None, - future_covariates=[ - self.ts_past_cov_valid_same_start, - self.ts_past_cov_valid_same_start, - ] - if "lags_future_covariates" in kwargs - else None, - last_points_only=True, - forecast_horizon=forecast_hrz, - stride=1, - retrain=True, - overlap_end=False, - ) + forecasts_retrain = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + past_covariates=[ + self.ts_past_cov_valid_same_start, + self.ts_past_cov_valid_same_start, + ] + if "lags_past_covariates" in kwargs + else None, + future_covariates=[ + self.ts_past_cov_valid_same_start, + self.ts_past_cov_valid_same_start, + ] + if "lags_future_covariates" in kwargs + else None, + last_points_only=True, + forecast_horizon=forecast_hrz, + stride=1, + retrain=True, + overlap_end=False, + ) - self.assertTrue( - len(forecasts_retrain) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) + assert ( + len(forecasts_retrain) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" - ( - min_target_lag, - max_target_lag, - min_past_cov_lag, - max_past_cov_lag, - min_future_cov_lag, - max_future_cov_lag, - ) = model.extreme_lags - - past_lag = min( - min_target_lag if min_target_lag else 0, - min_past_cov_lag if min_past_cov_lag else 0, - min_future_cov_lag - if min_future_cov_lag is not None and min_future_cov_lag < 0 - else 0, - ) + ( + min_target_lag, + max_target_lag, + min_past_cov_lag, + max_past_cov_lag, + min_future_cov_lag, + max_future_cov_lag, + ) = model.extreme_lags + + past_lag = min( + min_target_lag if min_target_lag else 0, + min_past_cov_lag if min_past_cov_lag else 0, + min_future_cov_lag + if min_future_cov_lag is not None and min_future_cov_lag < 0 + else 0, + ) - future_lag = ( - max_future_cov_lag - if max_future_cov_lag is not None and max_future_cov_lag > 0 - else 0 - ) - # length input - biggest past lag - biggest future lag - forecast horizon - output_chunk_length - theorical_retrain_forecast_length = len(self.ts_pass_val) - ( - -past_lag - + future_lag - + forecast_hrz - + kwargs.get("output_chunk_length", 1) - ) + future_lag = ( + max_future_cov_lag + if max_future_cov_lag is not None and max_future_cov_lag > 0 + else 0 + ) + # length input - biggest past lag - biggest future lag - forecast horizon - output_chunk_length + theorical_retrain_forecast_length = len(self.ts_pass_val) - ( + -past_lag + future_lag + forecast_hrz + kwargs.get("output_chunk_length", 1) + ) - self.assertTrue( - len(forecasts_retrain[0]) - == len(forecasts_retrain[1]) - == theorical_retrain_forecast_length, - f"Model {model_cls} does not return the right number of historical forecasts in the case of " - f"retrain=True and overlap_end=False. " - f"Expected {theorical_retrain_forecast_length}, got {len(forecasts_retrain[0])} " - f"and {len(forecasts_retrain[1])}", - ) + assert ( + len(forecasts_retrain[0]) + == len(forecasts_retrain[1]) + == theorical_retrain_forecast_length + ), ( + f"Model {model_cls} does not return the right number of historical forecasts in the case of " + f"retrain=True and overlap_end=False. " + f"Expected {theorical_retrain_forecast_length}, got {len(forecasts_retrain[0])} " + f"and {len(forecasts_retrain[1])}" + ) - # with last_points_only=True: start is shifted by biggest past lag + training timestamps - # (forecast horizon + output_chunk_length) - expected_start = ( - self.ts_pass_val.start_time() - + (-past_lag + forecast_hrz + kwargs.get("output_chunk_length", 1)) - * self.ts_pass_val.freq - ) - self.assertEqual(forecasts_retrain[0].start_time(), expected_start) + # with last_points_only=True: start is shifted by biggest past lag + training timestamps + # (forecast horizon + output_chunk_length) + expected_start = ( + self.ts_pass_val.start_time() + + (-past_lag + forecast_hrz + kwargs.get("output_chunk_length", 1)) + * self.ts_pass_val.freq + ) + assert forecasts_retrain[0].start_time() == expected_start - # end is shifted back by the biggest future lag - expected_end = ( - self.ts_pass_val.end_time() - future_lag * self.ts_pass_val.freq - ) - self.assertEqual(forecasts_retrain[0].end_time(), expected_end) + # end is shifted back by the biggest future lag + expected_end = self.ts_pass_val.end_time() - future_lag * self.ts_pass_val.freq + assert forecasts_retrain[0].end_time() == expected_end - def test_regression_auto_start_multiple_with_cov_no_retrain(self): + @pytest.mark.parametrize("model_config", models_reg_cov_cls_kwargs) + def test_regression_auto_start_multiple_with_cov_no_retrain(self, model_config): forecast_hrz = 10 - for model_cls, kwargs, bounds in models_reg_cov_cls_kwargs: - model = model_cls( - random_state=0, - **kwargs, - ) + model_cls, kwargs, _, bounds = model_config + model = model_cls( + random_state=0, + **kwargs, + ) - model.fit( - series=[self.ts_pass_val, self.ts_pass_val], - past_covariates=[ - self.ts_past_cov_valid_same_start, - self.ts_past_cov_valid_same_start, - ] - if "lags_past_covariates" in kwargs - else None, - future_covariates=[ - self.ts_past_cov_valid_same_start, - self.ts_past_cov_valid_same_start, - ] - if "lags_future_covariates" in kwargs - else None, - ) - forecasts_no_retrain = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - past_covariates=[ - self.ts_past_cov_valid_same_start, - self.ts_past_cov_valid_same_start, - ] - if "lags_past_covariates" in kwargs - else None, - future_covariates=[ - self.ts_past_cov_valid_same_start, - self.ts_past_cov_valid_same_start, - ] - if "lags_future_covariates" in kwargs - else None, - last_points_only=True, - forecast_horizon=forecast_hrz, - stride=1, - retrain=False, - overlap_end=False, - ) + model.fit( + series=[self.ts_pass_val, self.ts_pass_val], + past_covariates=[ + self.ts_past_cov_valid_same_start, + self.ts_past_cov_valid_same_start, + ] + if "lags_past_covariates" in kwargs + else None, + future_covariates=[ + self.ts_past_cov_valid_same_start, + self.ts_past_cov_valid_same_start, + ] + if "lags_future_covariates" in kwargs + else None, + ) + forecasts_no_retrain = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + past_covariates=[ + self.ts_past_cov_valid_same_start, + self.ts_past_cov_valid_same_start, + ] + if "lags_past_covariates" in kwargs + else None, + future_covariates=[ + self.ts_past_cov_valid_same_start, + self.ts_past_cov_valid_same_start, + ] + if "lags_future_covariates" in kwargs + else None, + last_points_only=True, + forecast_horizon=forecast_hrz, + stride=1, + retrain=False, + overlap_end=False, + ) - ( - min_target_lag, - max_target_lag, - min_past_cov_lag, - max_past_cov_lag, - min_future_cov_lag, - max_future_cov_lag, - ) = model.extreme_lags - - past_lag = min( - min_target_lag if min_target_lag else 0, - min_past_cov_lag if min_past_cov_lag else 0, - min_future_cov_lag if min_future_cov_lag else 0, - ) + ( + min_target_lag, + max_target_lag, + min_past_cov_lag, + max_past_cov_lag, + min_future_cov_lag, + max_future_cov_lag, + ) = model.extreme_lags + + past_lag = min( + min_target_lag if min_target_lag else 0, + min_past_cov_lag if min_past_cov_lag else 0, + min_future_cov_lag if min_future_cov_lag else 0, + ) - future_lag = ( - max_future_cov_lag - if max_future_cov_lag is not None and max_future_cov_lag > 0 - else 0 - ) + future_lag = ( + max_future_cov_lag + if max_future_cov_lag is not None and max_future_cov_lag > 0 + else 0 + ) - # with last_points_only=True: start is shifted by the biggest past lag plus the forecast horizon - expected_start = ( - self.ts_pass_val.start_time() - + (-past_lag + forecast_hrz - 1) * self.ts_pass_val.freq - ) - self.assertEqual(forecasts_no_retrain[0].start_time(), expected_start) + # with last_points_only=True: start is shifted by the biggest past lag plus the forecast horizon + expected_start = ( + self.ts_pass_val.start_time() + + (-past_lag + forecast_hrz - 1) * self.ts_pass_val.freq + ) + assert forecasts_no_retrain[0].start_time() == expected_start - # end is shifted by the biggest future lag - expected_end = ( - self.ts_pass_val.end_time() - future_lag * self.ts_pass_val.freq - ) - self.assertEqual(forecasts_no_retrain[0].end_time(), expected_end) + # end is shifted by the biggest future lag + expected_end = self.ts_pass_val.end_time() - future_lag * self.ts_pass_val.freq + assert forecasts_no_retrain[0].end_time() == expected_end @pytest.mark.slow - @unittest.skipUnless( - TORCH_AVAILABLE, - "Torch not available. auto start and multiple time series for torch models and covariates " - "will be skipped.", - ) - def test_torch_auto_start_with_cov(self): + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") + @pytest.mark.parametrize("model_config", models_torch_cls_kwargs) + def test_torch_auto_start_with_past_cov(self, model_config): forecast_hrz = 10 # Past covariates only - for model_cls, kwargs, bounds, type in models_torch_cls_kwargs: - - if type == "DualCovariates": - continue + model_cls, kwargs, bounds, type = model_config + if type == "DualCovariates": + return - model = model_cls( - random_state=0, - **kwargs, - ) - model.fit(self.ts_pass_train, self.ts_past_cov_train) + model = model_cls( + random_state=0, + **kwargs, + ) + model.fit(self.ts_pass_train, self.ts_past_cov_train) - # same start - forecasts = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - past_covariates=[ - self.ts_past_cov_valid_same_start, - self.ts_past_cov_valid_same_start, - ], - forecast_horizon=forecast_hrz, - stride=1, - retrain=True, - overlap_end=False, - ) + # same start + forecasts = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + past_covariates=[ + self.ts_past_cov_valid_same_start, + self.ts_past_cov_valid_same_start, + ], + forecast_horizon=forecast_hrz, + stride=1, + retrain=True, + overlap_end=False, + ) - self.assertTrue( - len(forecasts) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) + assert ( + len(forecasts) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" - theorical_forecast_length = ( - self.ts_val_length - - (bounds[0] + bounds[1]) # train sample length - - (forecast_hrz - 1) # if entire horizon is available, we can predict 1 - - 0 # past covs have same start as target -> no shift - - 0 # we don't have future covs in output chunk -> no shift - ) - self.assertTrue( - len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length, - f"Model {model_cls} does not return the right number of historical forecasts in case " - f"of retrain=True and overlap_end=False and past_covariates with same start. " - f"Expected {theorical_forecast_length}, got {len(forecasts[0])} and {len(forecasts[1])}", - ) + theorical_forecast_length = ( + self.ts_val_length + - (bounds[0] + bounds[1]) # train sample length + - (forecast_hrz - 1) # if entire horizon is available, we can predict 1 + - 0 # past covs have same start as target -> no shift + - 0 # we don't have future covs in output chunk -> no shift + ) + assert len(forecasts[0]) == len(forecasts[1]) == theorical_forecast_length, ( + f"Model {model_cls} does not return the right number of historical forecasts in case " + f"of retrain=True and overlap_end=False and past_covariates with same start. " + f"Expected {theorical_forecast_length}, got {len(forecasts[0])} and {len(forecasts[1])}" + ) - model = model_cls( - random_state=0, - **kwargs, - ) - model.fit(self.ts_pass_train, past_covariates=self.ts_past_cov_train) + model = model_cls( + random_state=0, + **kwargs, + ) + model.fit(self.ts_pass_train, past_covariates=self.ts_past_cov_train) - # start before, after - forecasts = model.historical_forecasts( - series=[self.ts_pass_val, self.ts_pass_val], - past_covariates=[ - self.ts_past_cov_valid_5_aft_start, - self.ts_past_cov_valid_10_bef_start, - ], - forecast_horizon=forecast_hrz, - stride=1, - retrain=True, - overlap_end=False, - ) - theorical_forecast_length = ( - self.ts_val_length - - (bounds[0] + bounds[1]) # train sample length - - (forecast_hrz - 1) # if entire horizon is available, we can predict 1 - - 5 # past covs start 5 later -> shift - - 0 # we don't have future covs in output chunk -> no shift - ) - self.assertTrue( - len(forecasts[0]) == theorical_forecast_length, - f"Model {model_cls} does not return the right number of historical forecasts in case " - f"of retrain=True and overlap_end=False and past_covariates starting after. " - f"Expected {theorical_forecast_length}, got {len(forecasts[0])}", - ) - theorical_forecast_length = ( - self.ts_val_length - - (bounds[0] + bounds[1]) # train sample length - - (forecast_hrz - 1) # if entire horizon is available, we can predict 1 - - 0 # past covs have same start as target -> no shift - - 0 # we don't have future covs in output chunk -> no shift - ) - self.assertTrue( - len(forecasts[1]) == theorical_forecast_length, - f"Model {model_cls} does not return the right number of historical forecasts in case " - f"of retrain=True and overlap_end=False and past_covariates starting before. " - f"Expected {theorical_forecast_length}, got {len(forecasts[1])}", - ) + # start before, after + forecasts = model.historical_forecasts( + series=[self.ts_pass_val, self.ts_pass_val], + past_covariates=[ + self.ts_past_cov_valid_5_aft_start, + self.ts_past_cov_valid_10_bef_start, + ], + forecast_horizon=forecast_hrz, + stride=1, + retrain=True, + overlap_end=False, + ) + theorical_forecast_length = ( + self.ts_val_length + - (bounds[0] + bounds[1]) # train sample length + - (forecast_hrz - 1) # if entire horizon is available, we can predict 1 + - 5 # past covs start 5 later -> shift + - 0 # we don't have future covs in output chunk -> no shift + ) + assert len(forecasts[0]) == theorical_forecast_length, ( + f"Model {model_cls} does not return the right number of historical forecasts in case " + f"of retrain=True and overlap_end=False and past_covariates starting after. " + f"Expected {theorical_forecast_length}, got {len(forecasts[0])}" + ) + theorical_forecast_length = ( + self.ts_val_length + - (bounds[0] + bounds[1]) # train sample length + - (forecast_hrz - 1) # if entire horizon is available, we can predict 1 + - 0 # past covs have same start as target -> no shift + - 0 # we don't have future covs in output chunk -> no shift + ) + assert len(forecasts[1]) == theorical_forecast_length, ( + f"Model {model_cls} does not return the right number of historical forecasts in case " + f"of retrain=True and overlap_end=False and past_covariates starting before. " + f"Expected {theorical_forecast_length}, got {len(forecasts[1])}" + ) + @pytest.mark.slow + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") + @pytest.mark.parametrize("model_config", models_torch_cls_kwargs) + def test_torch_auto_start_with_past_future_cov(self, model_config): + forecast_hrz = 10 # Past and future covariates for model_cls, kwargs, bounds, type in models_torch_cls_kwargs: if not type == "MixedCovariates": - continue + return model = model_cls( random_state=0, @@ -1305,12 +1286,11 @@ def test_torch_auto_start_with_cov(self): - 7 # future covs start 7 after target (more than past covs) -> shift - 2 # future covs in output chunk -> difference between horizon=10 and output_chunk_length=12 ) - self.assertTrue( - len(forecasts[0]) == theorical_forecast_length, + assert len(forecasts[0]) == theorical_forecast_length, ( f"Model {model_cls} does not return the right number of historical forecasts in case " f"of retrain=True and overlap_end=False and past_covariates and future_covariates with " f"different start. " - f"Expected {theorical_forecast_length}, got {len(forecasts[0])}", + f"Expected {theorical_forecast_length}, got {len(forecasts[0])}" ) theorical_forecast_length = ( self.ts_val_length @@ -1321,18 +1301,22 @@ def test_torch_auto_start_with_cov(self): - 0 # all covs start at the same time as target -> no shift, - 2 # future covs in output chunk -> difference between horizon=10 and output_chunk_length=12 ) - self.assertTrue( - len(forecasts[1]) == theorical_forecast_length, + assert len(forecasts[1]) == theorical_forecast_length, ( f"Model {model_cls} does not return the right number of historical forecasts in case " f"of retrain=True and overlap_end=False and past_covariates with different start. " - f"Expected {theorical_forecast_length}, got {len(forecasts[1])}", + f"Expected {theorical_forecast_length}, got {len(forecasts[1])}" ) + @pytest.mark.slow + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") + @pytest.mark.parametrize("model_config", models_torch_cls_kwargs) + def test_torch_auto_start_with_future_cov(self, model_config): + forecast_hrz = 10 # Future covariates only for model_cls, kwargs, bounds, type in models_torch_cls_kwargs: # todo case of DualCovariates (RNN) if type == "PastCovariates" or type == "DualCovariates": - continue + return model = model_cls( random_state=0, @@ -1353,10 +1337,9 @@ def test_torch_auto_start_with_cov(self): overlap_end=False, ) - self.assertTrue( - len(forecasts) == 2, - f"Model {model_cls} did not return a list of historical forecasts", - ) + assert ( + len(forecasts) == 2 + ), f"Model {model_cls} did not return a list of historical forecasts" theorical_forecast_length = ( self.ts_val_length - (bounds[0] + bounds[1]) # train sample length @@ -1366,12 +1349,11 @@ def test_torch_auto_start_with_cov(self): - 7 # future covs start 7 after target (more than past covs) -> shift - 2 # future covs in output chunk -> difference between horizon=10 and output_chunk_length=12 ) - self.assertTrue( - len(forecasts[0]) == theorical_forecast_length, + assert len(forecasts[0]) == theorical_forecast_length, ( f"Model {model_cls} does not return the right number of historical forecasts in case " f"of retrain=True and overlap_end=False and no past_covariates and future_covariates " f"with different start. " - f"Expected {theorical_forecast_length}, got {len(forecasts[0])}", + f"Expected {theorical_forecast_length}, got {len(forecasts[0])}" ) theorical_forecast_length = ( self.ts_val_length @@ -1380,12 +1362,11 @@ def test_torch_auto_start_with_cov(self): - 0 # all covs start at the same time as target -> no shift - 2 # future covs in output chunk -> difference between horizon=10 and output_chunk_length=12 ) - self.assertTrue( - len(forecasts[1]) == theorical_forecast_length, + assert len(forecasts[1]) == theorical_forecast_length, ( f"Model {model_cls} does not return the right number of historical forecasts in case " f"of retrain=True and overlap_end=False and no past_covariates and future_covariates " f"with different start. " - f"Expected {theorical_forecast_length}, got {len(forecasts[1])}", + f"Expected {theorical_forecast_length}, got {len(forecasts[1])}" ) def test_retrain(self): @@ -1443,26 +1424,26 @@ def retrain_f_delayed_true( expected_msg = "the Callable `retrain` must have a signature/arguments matching the following positional" with pytest.raises(ValueError) as error_msg: helper_hist_forecasts(retrain_f_missing_arg, 0.9) - self.assertTrue(str(error_msg.value).startswith(expected_msg)) + assert str(error_msg.value).startswith(expected_msg) # returning a non-bool value (int) expected_msg = "Return value of `retrain` must be bool, received " with pytest.raises(ValueError) as error_msg: helper_hist_forecasts(retrain_f_invalid_ouput_int, 0.9) - self.assertTrue(str(error_msg.value).startswith(expected_msg)) + assert str(error_msg.value).startswith(expected_msg) # returning a non-bool value (str) expected_msg = "Return value of `retrain` must be bool, received " with pytest.raises(ValueError) as error_msg: helper_hist_forecasts(retrain_f_invalid_ouput_str, 0.9) - self.assertTrue(str(error_msg.value).startswith(expected_msg)) + assert str(error_msg.value).startswith(expected_msg) # predict fails but model could have been trained before the predict round expected_msg = "`retrain` is `False` in the first train iteration at prediction point (in time)" with pytest.raises(ValueError) as error_msg: helper_hist_forecasts(retrain_f_delayed_true, 0.9) - self.assertTrue(str(error_msg.value).startswith(expected_msg)) + assert str(error_msg.value).startswith(expected_msg) # always returns False, treated slightly different than `retrain=False` and `retrain=0` with pytest.raises(ValueError) as error_msg: helper_hist_forecasts(retrain_f_invalid, 0.9) - self.assertTrue(str(error_msg.value).startswith(expected_msg)) + assert str(error_msg.value).startswith(expected_msg) # test int helper_hist_forecasts(10, 0.9) @@ -1470,9 +1451,7 @@ def retrain_f_delayed_true( # `retrain=0` with not-trained model, encountering directly a predictable time index with pytest.raises(ValueError) as error_msg: helper_hist_forecasts(0, 0.9) - self.assertTrue( - str(error_msg.value).startswith(expected_msg), str(error_msg.value) - ) + assert str(error_msg.value).startswith(expected_msg), str(error_msg.value) # test bool helper_hist_forecasts(True, 0.9) @@ -1480,21 +1459,22 @@ def retrain_f_delayed_true( expected_msg = "The model has not been fitted yet, and `retrain` is ``False``." with pytest.raises(ValueError) as error_msg: helper_hist_forecasts(False, 0.9) - self.assertTrue(str(error_msg.value).startswith(expected_msg)) + assert str(error_msg.value).startswith(expected_msg) expected_start = pd.Timestamp("1949-10-01 00:00:00") # start before first trainable time index should still work res = helper_hist_forecasts(True, pd.Timestamp("1949-09-01 00:00:00")) - self.assertTrue(res.time_index[0] == expected_start) + assert res.time_index[0] == expected_start # start at first trainable time index should still work res = helper_hist_forecasts(True, expected_start) - self.assertTrue(res.time_index[0] == expected_start) + assert res.time_index[0] == expected_start # start at last trainable time index should still work expected_end = pd.Timestamp("1960-12-01 00:00:00") res = helper_hist_forecasts(True, expected_end) - self.assertTrue(res.time_index[0] == expected_end) + assert res.time_index[0] == expected_end - def test_predict_likelihood_parameters(self): + @pytest.mark.parametrize("model_type", ["regression", "torch"]) + def test_predict_likelihood_parameters(self, model_type): """standard checks that historical forecasts work with direct likelihood parameter predictions with regression and torch models.""" @@ -1520,105 +1500,104 @@ def create_model(ocl, use_ll=True, model_type="regression"): **tfm_kwargs, ) - for model_type in ["regression", "torch"]: - model = create_model(1, False, model_type=model_type) - # skip torch models if not installed - if model is None: - continue - # model doesn't use likelihood - with pytest.raises(ValueError): - model.historical_forecasts( - self.ts_pass_train, - predict_likelihood_parameters=True, - ) - - model = create_model(1, model_type=model_type) - # forecast_horizon > output_chunk_length doesn't work - with pytest.raises(ValueError): - model.historical_forecasts( - self.ts_pass_train, - predict_likelihood_parameters=True, - forecast_horizon=2, - ) - - model = create_model(1, model_type=model_type) - # num_samples != 1 doesn't work - with pytest.raises(ValueError): - model.historical_forecasts( - self.ts_pass_train, - predict_likelihood_parameters=True, - forecast_horizon=1, - num_samples=2, - ) - - n = 3 - target_name = self.ts_pass_train.components[0] - qs_expected = ["q0.05", "q0.40", "q0.50", "q0.60", "q0.95"] - qs_expected = pd.Index([target_name + "_" + q for q in qs_expected]) - # check that it works with retrain - model = create_model(1, model_type=model_type) - hist_fc = model.historical_forecasts( + model = create_model(1, False, model_type=model_type) + # skip torch models if not installed + if model is None: + return + # model doesn't use likelihood + with pytest.raises(ValueError): + model.historical_forecasts( self.ts_pass_train, predict_likelihood_parameters=True, - forecast_horizon=1, - num_samples=1, - start=len(self.ts_pass_train) - n, # predict on last 10 steps - retrain=True, ) - assert hist_fc.components.equals(qs_expected) - assert len(hist_fc) == n - # check for equal results between predict and hist fc without retraining - model = create_model(1, model_type=model_type) - model.fit(series=self.ts_pass_train[:-n]) - hist_fc = model.historical_forecasts( + model = create_model(1, model_type=model_type) + # forecast_horizon > output_chunk_length doesn't work + with pytest.raises(ValueError): + model.historical_forecasts( self.ts_pass_train, predict_likelihood_parameters=True, - forecast_horizon=1, - num_samples=1, - start=len(self.ts_pass_train) - n, # predict on last 10 steps - retrain=False, + forecast_horizon=2, ) - assert hist_fc.components.equals(qs_expected) - assert len(hist_fc) == n - - preds = [] - for n_i in range(n): - preds.append( - model.predict( - n=1, - series=self.ts_pass_train[: -(n - n_i)], - predict_likelihood_parameters=True, - ) - ) - preds = darts.concatenate(preds) - assert np.all(preds.values() == hist_fc.values()) - - # check equal results between predict and hist fc with higher output_chunk_length and horizon, - # and last_points_only=False - model = create_model(2, model_type=model_type) - # we take one more training step so that model trained on ocl=1 has the same training samples - # as model above - model.fit(series=self.ts_pass_train[: -(n - 1)]) - hist_fc = model.historical_forecasts( + + model = create_model(1, model_type=model_type) + # num_samples != 1 doesn't work + with pytest.raises(ValueError): + model.historical_forecasts( self.ts_pass_train, predict_likelihood_parameters=True, - forecast_horizon=2, - num_samples=1, - start=len(self.ts_pass_train) - n, # predict on last 10 steps - retrain=False, - last_points_only=False, - overlap_end=True, + forecast_horizon=1, + num_samples=2, + ) + + n = 3 + target_name = self.ts_pass_train.components[0] + qs_expected = ["q0.05", "q0.40", "q0.50", "q0.60", "q0.95"] + qs_expected = pd.Index([target_name + "_" + q for q in qs_expected]) + # check that it works with retrain + model = create_model(1, model_type=model_type) + hist_fc = model.historical_forecasts( + self.ts_pass_train, + predict_likelihood_parameters=True, + forecast_horizon=1, + num_samples=1, + start=len(self.ts_pass_train) - n, # predict on last 10 steps + retrain=True, + ) + assert hist_fc.components.equals(qs_expected) + assert len(hist_fc) == n + + # check for equal results between predict and hist fc without retraining + model = create_model(1, model_type=model_type) + model.fit(series=self.ts_pass_train[:-n]) + hist_fc = model.historical_forecasts( + self.ts_pass_train, + predict_likelihood_parameters=True, + forecast_horizon=1, + num_samples=1, + start=len(self.ts_pass_train) - n, # predict on last 10 steps + retrain=False, + ) + assert hist_fc.components.equals(qs_expected) + assert len(hist_fc) == n + + preds = [] + for n_i in range(n): + preds.append( + model.predict( + n=1, + series=self.ts_pass_train[: -(n - n_i)], + predict_likelihood_parameters=True, + ) ) - # generate the same predictions manually - preds = [] - for n_i in range(n): - preds.append( - model.predict( - n=2, - series=self.ts_pass_train[: -(n - n_i)], - predict_likelihood_parameters=True, - ) + preds = darts.concatenate(preds) + assert np.all(preds.values() == hist_fc.values()) + + # check equal results between predict and hist fc with higher output_chunk_length and horizon, + # and last_points_only=False + model = create_model(2, model_type=model_type) + # we take one more training step so that model trained on ocl=1 has the same training samples + # as model above + model.fit(series=self.ts_pass_train[: -(n - 1)]) + hist_fc = model.historical_forecasts( + self.ts_pass_train, + predict_likelihood_parameters=True, + forecast_horizon=2, + num_samples=1, + start=len(self.ts_pass_train) - n, # predict on last 10 steps + retrain=False, + last_points_only=False, + overlap_end=True, + ) + # generate the same predictions manually + preds = [] + for n_i in range(n): + preds.append( + model.predict( + n=2, + series=self.ts_pass_train[: -(n - n_i)], + predict_likelihood_parameters=True, ) - assert preds == hist_fc - assert len(hist_fc) == n + ) + assert preds == hist_fc + assert len(hist_fc) == n diff --git a/darts/tests/models/forecasting/test_local_forecasting_models.py b/darts/tests/models/forecasting/test_local_forecasting_models.py index 1e9959b773..dce11bdec8 100644 --- a/darts/tests/models/forecasting/test_local_forecasting_models.py +++ b/darts/tests/models/forecasting/test_local_forecasting_models.py @@ -1,8 +1,7 @@ import copy +import itertools import os import pathlib -import shutil -import tempfile from typing import Callable from unittest.mock import Mock, patch @@ -43,7 +42,6 @@ LocalForecastingModel, TransferableFutureCovariatesLocalForecastingModel, ) -from darts.tests.base_test_class import DartsBaseTestClass from darts.timeseries import TimeSeries from darts.utils import timeseries_generation as tg from darts.utils.utils import ModelMode, SeasonalityMode, TrendMode @@ -109,7 +107,7 @@ encoder_support_models.append(Prophet()) -class LocalForecastingModelsTestCase(DartsBaseTestClass): +class TestLocalForecastingModels: # forecasting horizon used in runnability tests forecasting_horizon = 5 @@ -136,72 +134,59 @@ def retrain_func( ): return len(train_series) % 2 == 0 - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - def test_save_model_parameters(self): # model creation parameters were saved before. check if re-created model has same params as original for model, _ in models: - self.assertTrue( - model._model_params == model.untrained_model()._model_params - ) + assert model._model_params == model.untrained_model()._model_params - def test_save_load_model(self): + @pytest.mark.parametrize("model", [ARIMA(1, 1, 1), LinearRegressionModel(lags=12)]) + def test_save_load_model(self, tmpdir_module, model): # check if save and load methods work and if loaded model creates same forecasts as original model cwd = os.getcwd() - os.chdir(self.temp_work_dir) - - for model in [ARIMA(1, 1, 1), LinearRegressionModel(lags=12)]: - model_path_str = type(model).__name__ - model_path_pathlike = pathlib.Path(model_path_str + "_pathlike") - model_path_binary = model_path_str + "_binary" - model_paths = [model_path_str, model_path_pathlike, model_path_binary] - full_model_paths = [ - os.path.join(self.temp_work_dir, p) for p in model_paths - ] - - model.fit(self.ts_gaussian) - model_prediction = model.predict(self.forecasting_horizon) - - # test save - model.save() - model.save(model_path_str) - model.save(model_path_pathlike) - with open(model_path_binary, "wb") as f: - model.save(f) - - for p in full_model_paths: - self.assertTrue(os.path.exists(p)) - - self.assertTrue( - len( - [ - p - for p in os.listdir(self.temp_work_dir) - if p.startswith(type(model).__name__) - ] - ) - == len(full_model_paths) + 1 + os.chdir(tmpdir_module) + model_path_str = type(model).__name__ + model_path_pathlike = pathlib.Path(model_path_str + "_pathlike") + model_path_binary = model_path_str + "_binary" + model_paths = [model_path_str, model_path_pathlike, model_path_binary] + full_model_paths = [os.path.join(tmpdir_module, p) for p in model_paths] + + model.fit(self.ts_gaussian) + model_prediction = model.predict(self.forecasting_horizon) + + # test save + model.save() + model.save(model_path_str) + model.save(model_path_pathlike) + with open(model_path_binary, "wb") as f: + model.save(f) + + for p in full_model_paths: + assert os.path.exists(p) + + assert ( + len( + [ + p + for p in os.listdir(tmpdir_module) + if p.startswith(type(model).__name__) + ] ) + == len(full_model_paths) + 1 + ) - # test load - loaded_model_str = type(model).load(model_path_str) - loaded_model_pathlike = type(model).load(model_path_pathlike) - with open(model_path_binary, "rb") as f: - loaded_model_binary = type(model).load(f) - loaded_models = [ - loaded_model_str, - loaded_model_pathlike, - loaded_model_binary, - ] - - for loaded_model in loaded_models: - self.assertEqual( - model_prediction, loaded_model.predict(self.forecasting_horizon) - ) + # test load + loaded_model_str = type(model).load(model_path_str) + loaded_model_pathlike = type(model).load(model_path_pathlike) + with open(model_path_binary, "rb") as f: + loaded_model_binary = type(model).load(f) + loaded_models = [ + loaded_model_str, + loaded_model_pathlike, + loaded_model_binary, + ] + + for loaded_model in loaded_models: + assert model_prediction == loaded_model.predict(self.forecasting_horizon) os.chdir(cwd) @@ -221,49 +206,53 @@ def test_save_load_model_invalid_path(self): with pytest.raises(ValueError): type(model).load(model_path_invalid) - def test_models_runnability(self): - for model, _ in models: - if not isinstance(model, RegressionModel): - self.assertTrue(isinstance(model, LocalForecastingModel)) - prediction = model.fit(self.ts_gaussian).predict(self.forecasting_horizon) - self.assertTrue(len(prediction) == self.forecasting_horizon) + @pytest.mark.parametrize("config", models) + def test_models_runnability(self, config): + model, _ = config + if not isinstance(model, RegressionModel): + assert isinstance(model, LocalForecastingModel) + prediction = model.fit(self.ts_gaussian).predict(self.forecasting_horizon) + assert len(prediction) == self.forecasting_horizon - def test_models_performance(self): + @pytest.mark.parametrize("config", models) + def test_models_performance(self, config): # for every model, check whether its errors do not exceed the given bounds - for model, max_mape in models: - np.random.seed(1) # some models are probabilist... - model.fit(self.ts_pass_train) - prediction = model.predict(len(self.ts_pass_val)) - current_mape = mape(self.ts_pass_val, prediction) - self.assertTrue( - current_mape < max_mape, - "{} model exceeded the maximum MAPE of {}. " - "with a MAPE of {}".format(str(model), max_mape, current_mape), - ) + model, max_mape = config + np.random.seed(1) # some models are probabilist... + model.fit(self.ts_pass_train) + prediction = model.predict(len(self.ts_pass_val)) + current_mape = mape(self.ts_pass_val, prediction) + assert ( + current_mape < max_mape + ), "{} model exceeded the maximum MAPE of {}. " "with a MAPE of {}".format( + str(model), max_mape, current_mape + ) - def test_multivariate_models_performance(self): + @pytest.mark.parametrize("config", multivariate_models) + def test_multivariate_models_performance(self, config): # for every model, check whether its errors do not exceed the given bounds - for model, max_mape in multivariate_models: - np.random.seed(1) - model.fit(self.ts_ice_heater_train) - prediction = model.predict(len(self.ts_ice_heater_val)) - current_mape = mape(self.ts_ice_heater_val, prediction) - self.assertTrue( - current_mape < max_mape, - "{} model exceeded the maximum MAPE of {}. " - "with a MAPE of {}".format(str(model), max_mape, current_mape), - ) + model, max_mape = config + np.random.seed(1) + model.fit(self.ts_ice_heater_train) + prediction = model.predict(len(self.ts_ice_heater_val)) + current_mape = mape(self.ts_ice_heater_val, prediction) + assert ( + current_mape < max_mape + ), "{} model exceeded the maximum MAPE of {}. " "with a MAPE of {}".format( + str(model), max_mape, current_mape + ) def test_multivariate_input(self): es_model = ExponentialSmoothing() ts_passengers_enhanced = self.ts_passengers.add_datetime_attribute("month") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): es_model.fit(ts_passengers_enhanced) es_model.fit(ts_passengers_enhanced["#Passengers"]) - with self.assertRaises(KeyError): + with pytest.raises(KeyError): es_model.fit(ts_passengers_enhanced["2"]) - def test_exogenous_variables_support(self): + @pytest.mark.parametrize("model", dual_models) + def test_exogenous_variables_support(self, model): # test case with pd.DatetimeIndex target_dt_idx = self.ts_gaussian fc_dt_idx = self.ts_gaussian_long @@ -281,90 +270,91 @@ def test_exogenous_variables_support(self): for target, future_covariates in zip( [target_dt_idx, target_num_idx], [fc_dt_idx, fc_num_idx] ): - for model in dual_models: - # skip models which do not support RangeIndex - if isinstance(target.time_index, pd.RangeIndex): - try: - # _supports_range_index raises a ValueError if model does not support RangeIndex - model._supports_range_index - except ValueError: - continue - - # Test models runnability - proper future covariates slicing - model.fit(target, future_covariates=future_covariates) - prediction = model.predict( - self.forecasting_horizon, future_covariates=future_covariates + if isinstance(target.time_index, pd.RangeIndex): + try: + # _supports_range_index raises a ValueError if model does not support RangeIndex + model._supports_range_index + except ValueError: + continue + + # Test models runnability - proper future covariates slicing + model.fit(target, future_covariates=future_covariates) + prediction = model.predict( + self.forecasting_horizon, future_covariates=future_covariates + ) + + assert len(prediction) == self.forecasting_horizon + + # Test mismatch in length between exogenous variables and forecasting horizon + with pytest.raises(ValueError): + model.predict( + self.forecasting_horizon, + future_covariates=tg.gaussian_timeseries( + start=future_covariates.start_time(), + length=self.forecasting_horizon - 1, + ), ) - self.assertTrue(len(prediction) == self.forecasting_horizon) + # Test mismatch in time-index/length between series and exogenous variables + with pytest.raises(ValueError): + model.fit(target, future_covariates=target[:-1]) + with pytest.raises(ValueError): + model.fit(target[1:], future_covariates=target[:-1]) - # Test mismatch in length between exogenous variables and forecasting horizon - with self.assertRaises(ValueError): - model.predict( - self.forecasting_horizon, - future_covariates=tg.gaussian_timeseries( - start=future_covariates.start_time(), - length=self.forecasting_horizon - 1, - ), - ) + @pytest.mark.parametrize("model_cls", [NaiveMean, Theta]) + def test_encoders_no_support(self, model_cls): + # test some models that do not support encoders + add_encoders = {"custom": {"future": [lambda x: x.dayofweek]}} - # Test mismatch in time-index/length between series and exogenous variables - with self.assertRaises(ValueError): - model.fit(target, future_covariates=target[:-1]) - with self.assertRaises(ValueError): - model.fit(target[1:], future_covariates=target[:-1]) + with pytest.raises(TypeError): + _ = model_cls(add_encoders=add_encoders) - def test_encoders_support(self): + @pytest.mark.parametrize( + "config", + itertools.product( + encoder_support_models, [ts_gaussian, None] + ), # tuple of (model class, future covs) + ) + def test_encoders_support(self, config): + # test some models that support encoders, once with user supplied covariates and once without + model_object, fc = config # test case with pd.DatetimeIndex n = 3 target = self.ts_gaussian[:-3] - future_covariates = self.ts_gaussian - add_encoders = {"custom": {"future": [lambda x: x.dayofweek]}} - # test some models that do not support encoders - no_support_model_cls = [NaiveMean, Theta] - for model_cls in no_support_model_cls: - with pytest.raises(TypeError): - _ = model_cls(add_encoders=add_encoders) - - # test some models that support encoders - for model_object in encoder_support_models: - series = ( - target - if not isinstance(model_object, VARIMA) - else target.stack(target.map(np.log)) - ) - # test once with user supplied covariates, and once without - for fc in [future_covariates, None]: - model_params = { - k: vals - for k, vals in copy.deepcopy(model_object.model_params).items() - } - model_params["add_encoders"] = add_encoders - model = model_object.__class__(**model_params) + series = ( + target + if not isinstance(model_object, VARIMA) + else target.stack(target.map(np.log)) + ) + model_params = { + k: vals for k, vals in copy.deepcopy(model_object.model_params).items() + } + model_params["add_encoders"] = add_encoders + model = model_object.__class__(**model_params) - # Test models with user supplied covariates - model.fit(series, future_covariates=fc) + # Test models with user supplied covariates + model.fit(series, future_covariates=fc) - prediction = model.predict(n, future_covariates=fc) - self.assertTrue(len(prediction) == n) + prediction = model.predict(n, future_covariates=fc) + assert len(prediction) == n - if isinstance(model, TransferableFutureCovariatesLocalForecastingModel): - prediction = model.predict(n, series=series, future_covariates=fc) - self.assertTrue(len(prediction) == n) + if isinstance(model, TransferableFutureCovariatesLocalForecastingModel): + prediction = model.predict(n, series=series, future_covariates=fc) + assert len(prediction) == n def test_dummy_series(self): values = np.random.uniform(low=-10, high=10, size=100) ts = TimeSeries.from_dataframe(pd.DataFrame({"V1": values})) varima = VARIMA(trend="t") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): varima.fit(series=ts) autoarima = AutoARIMA(trend="t") - with self.assertRaises(ValueError): + with pytest.raises(ValueError): autoarima.fit(series=ts) def test_forecast_time_index(self): @@ -378,239 +368,258 @@ def test_forecast_time_index(self): model = NaiveSeasonal(K=1) model.fit(ts) pred = model.predict(n=5) - self.assertTrue( - all(pred.time_index == pd.RangeIndex(start=50, stop=60, step=2)) - ) + assert all(pred.time_index == pd.RangeIndex(start=50, stop=60, step=2)) # datetime-index ts = tg.constant_timeseries(start=pd.Timestamp("20130101"), length=20, value=1) model = NaiveSeasonal(K=1) model.fit(ts) pred = model.predict(n=5) - self.assertEqual(pred.start_time(), pd.Timestamp("20130121")) - self.assertEqual(pred.end_time(), pd.Timestamp("20130125")) + assert pred.start_time() == pd.Timestamp("20130121") + assert pred.end_time() == pd.Timestamp("20130125") @pytest.mark.slow - def test_statsmodels_future_models(self): + @pytest.mark.parametrize( + "params", + [ # tuple of (model class, model params, uni/multivariate case) + (ARIMA, {}, "univariate"), + (VARIMA, {"d": 0}, "multivariate"), + (VARIMA, {"d": 1}, "multivariate"), + ], + ) + def test_statsmodels_future_models(self, params): # same tests, but VARIMA requires to work on a multivariate target series - UNIVARIATE = "univariate" - MULTIVARIATE = "multivariate" - - params = [ - (ARIMA, {}, UNIVARIATE), - (VARIMA, {"d": 0}, MULTIVARIATE), - (VARIMA, {"d": 1}, MULTIVARIATE), - ] - - for model_cls, kwargs, model_type in params: - pred_len = 5 - if model_type == MULTIVARIATE: - series1 = self.ts_ice_heater_train - series2 = self.ts_ice_heater_val - else: - series1 = self.ts_pass_train - series2 = self.ts_pass_val - - # creating covariates from series + noise - noise1 = tg.gaussian_timeseries(length=len(series1)) - noise2 = tg.gaussian_timeseries(length=len(series2)) - - for _ in range(1, series1.n_components): - noise1 = noise1.stack(tg.gaussian_timeseries(length=len(series1))) - noise2 = noise2.stack(tg.gaussian_timeseries(length=len(series2))) - - exog1 = series1 + noise1 - exog2 = series2 + noise2 - - exog1_longer = exog1.concatenate(exog1, ignore_time_axis=True) - exog2_longer = exog2.concatenate(exog2, ignore_time_axis=True) - - # shortening of pred_len so that exog are enough for the training series prediction - series1 = series1[:-pred_len] - series2 = series2[:-pred_len] - - # check runnability with different time series + model_cls, kwargs, model_type = params + pred_len = 5 + if model_type == "multivariate": + series1 = self.ts_ice_heater_train + series2 = self.ts_ice_heater_val + else: + series1 = self.ts_pass_train + series2 = self.ts_pass_val + + # creating covariates from series + noise + noise1 = tg.gaussian_timeseries(length=len(series1)) + noise2 = tg.gaussian_timeseries(length=len(series2)) + + for _ in range(1, series1.n_components): + noise1 = noise1.stack(tg.gaussian_timeseries(length=len(series1))) + noise2 = noise2.stack(tg.gaussian_timeseries(length=len(series2))) + + exog1 = series1 + noise1 + exog2 = series2 + noise2 + + exog1_longer = exog1.concatenate(exog1, ignore_time_axis=True) + exog2_longer = exog2.concatenate(exog2, ignore_time_axis=True) + + # shortening of pred_len so that exog are enough for the training series prediction + series1 = series1[:-pred_len] + series2 = series2[:-pred_len] + + # check runnability with different time series + model = model_cls(**kwargs) + model.fit(series1) + _ = model.predict(n=pred_len) + _ = model.predict(n=pred_len, series=series2) + + # check probabilistic forecast + n_samples = 3 + pred1 = model.predict(n=pred_len, num_samples=n_samples) + pred2 = model.predict(n=pred_len, series=series2, num_samples=n_samples) + + # check that the results with a second custom ts are different from the results given with the training ts + assert not np.array_equal(pred1.values, pred2.values()) + + # check runnability with exogeneous variables + model = model_cls(**kwargs) + model.fit(series1, future_covariates=exog1) + pred1 = model.predict(n=pred_len, future_covariates=exog1) + pred2 = model.predict(n=pred_len, series=series2, future_covariates=exog2) + + assert not np.array_equal(pred1.values(), pred2.values()) + + # check runnability with future covariates with extra time steps in the past compared to the target series + model = model_cls(**kwargs) + model.fit(series1, future_covariates=exog1_longer) + _ = model.predict(n=pred_len, future_covariates=exog1_longer) + _ = model.predict(n=pred_len, series=series2, future_covariates=exog2_longer) + + # check error is raised if model expects covariates but those are not passed when predicting with new data + with pytest.raises(ValueError): model = model_cls(**kwargs) - model.fit(series1) - pred1 = model.predict(n=pred_len) - pred2 = model.predict(n=pred_len, series=series2) - - # check probabilistic forecast - n_samples = 3 - pred1 = model.predict(n=pred_len, num_samples=n_samples) - pred2 = model.predict(n=pred_len, series=series2, num_samples=n_samples) - - # check that the results with a second custom ts are different from the results given with the training ts - self.assertFalse(np.array_equal(pred1.values, pred2.values())) + model.fit(series1, future_covariates=exog1) + model.predict(n=pred_len, series=series2) - # check runnability with exogeneous variables + # check error is raised if new future covariates are not wide enough for prediction (on the original series) + with pytest.raises(ValueError): model = model_cls(**kwargs) model.fit(series1, future_covariates=exog1) - pred1 = model.predict(n=pred_len, future_covariates=exog1) - pred2 = model.predict(n=pred_len, series=series2, future_covariates=exog2) - - self.assertFalse(np.array_equal(pred1.values(), pred2.values())) + model.predict(n=pred_len, future_covariates=exog1[:-pred_len]) - # check runnability with future covariates with extra time steps in the past compared to the target series + # check error is raised if new future covariates are not wide enough for prediction (on a new series) + with pytest.raises(ValueError): model = model_cls(**kwargs) - model.fit(series1, future_covariates=exog1_longer) - pred1 = model.predict(n=pred_len, future_covariates=exog1_longer) - pred2 = model.predict( - n=pred_len, series=series2, future_covariates=exog2_longer + model.fit(series1, future_covariates=exog1) + model.predict( + n=pred_len, series=series2, future_covariates=exog2[:-pred_len] ) - - # check error is raised if model expects covariates but those are not passed when predicting with new data - with self.assertRaises(ValueError): - model = model_cls(**kwargs) - model.fit(series1, future_covariates=exog1) - model.predict(n=pred_len, series=series2) - - # check error is raised if new future covariates are not wide enough for prediction (on the original series) - with self.assertRaises(ValueError): - model = model_cls(**kwargs) - model.fit(series1, future_covariates=exog1) - model.predict(n=pred_len, future_covariates=exog1[:-pred_len]) - - # check error is raised if new future covariates are not wide enough for prediction (on a new series) - with self.assertRaises(ValueError): - model = model_cls(**kwargs) - model.fit(series1, future_covariates=exog1) - model.predict( - n=pred_len, series=series2, future_covariates=exog2[:-pred_len] - ) - # and checking the case with insufficient historic future covariates - with self.assertRaises(ValueError): - model = model_cls(**kwargs) - model.fit(series1, future_covariates=exog1) - model.predict( - n=pred_len, series=series2, future_covariates=exog2[pred_len:] - ) - - # verify that we can still forecast the original training series after predicting a new target series + # and checking the case with insufficient historic future covariates + with pytest.raises(ValueError): model = model_cls(**kwargs) model.fit(series1, future_covariates=exog1) - pred1 = model.predict(n=pred_len, future_covariates=exog1) - model.predict(n=pred_len, series=series2, future_covariates=exog2) - pred3 = model.predict(n=pred_len, future_covariates=exog1) - - self.assertTrue(np.array_equal(pred1.values(), pred3.values())) - model.backtest(series1, future_covariates=exog1, start=0.5, retrain=False) - - @patch("typing.Callable", autospec=retrain_func, return_value=True) - def test_backtest_retrain( - self, - patch_retrain_func, - ): - """ - Test backtest method with different retrain arguments - """ + model.predict( + n=pred_len, series=series2, future_covariates=exog2[pred_len:] + ) - series = self.ts_pass_train + # verify that we can still forecast the original training series after predicting a new target series + model = model_cls(**kwargs) + model.fit(series1, future_covariates=exog1) + pred1 = model.predict(n=pred_len, future_covariates=exog1) + model.predict(n=pred_len, series=series2, future_covariates=exog2) + pred3 = model.predict(n=pred_len, future_covariates=exog1) - lr_univ_args = {"lags": [-1, -2, -3]} + np.testing.assert_array_equal(pred1.values(), pred3.values()) + model.backtest(series1, future_covariates=exog1, start=0.5, retrain=False) - lr_multi_args = { - "lags": [-1, -2, -3], - "lags_past_covariates": [-1, -2, -3], - } - params = [ # tuple of (model, retrain-able, multivariate, retrain parameter, model type) - (ExponentialSmoothing(), False, False, "hello", "LocalForecastingModel"), - (ExponentialSmoothing(), False, False, True, "LocalForecastingModel"), - (ExponentialSmoothing(), False, False, -2, "LocalForecastingModel"), - (ExponentialSmoothing(), False, False, 2, "LocalForecastingModel"), + @patch("typing.Callable", autospec=retrain_func, return_value=True) + @pytest.mark.parametrize( + "params", + [ # tuple of (model class, retrain-able, multivariate, retrain parameter, model type, uni/multivariate args) ( - ExponentialSmoothing(), + ExponentialSmoothing, False, False, - patch_retrain_func, + "hello", "LocalForecastingModel", + None, ), + (ExponentialSmoothing, False, False, True, "LocalForecastingModel", None), + (ExponentialSmoothing, False, False, -2, "LocalForecastingModel", None), + (ExponentialSmoothing, False, False, 2, "LocalForecastingModel", None), ( - LinearRegressionModel(**lr_univ_args), + ExponentialSmoothing, + False, + False, + "patch_retrain_func", + "LocalForecastingModel", + None, + ), + ( + LinearRegressionModel, True, False, True, "GlobalForecastingModel", + "lr_univ_args", ), ( - LinearRegressionModel(**lr_univ_args), + LinearRegressionModel, True, False, 2, "GlobalForecastingModel", + "lr_univ_args", ), ( - LinearRegressionModel(**lr_univ_args), + LinearRegressionModel, True, False, - patch_retrain_func, + "patch_retrain_func", "GlobalForecastingModel", + "lr_univ_args", ), ( - LinearRegressionModel(**lr_multi_args), + LinearRegressionModel, True, True, True, "GlobalForecastingModel", + "lr_multi_args", ), ( - LinearRegressionModel(**lr_multi_args), + LinearRegressionModel, True, True, 2, "GlobalForecastingModel", + "lr_multi_args", ), ( - LinearRegressionModel(**lr_multi_args), + LinearRegressionModel, True, True, - patch_retrain_func, + "patch_retrain_func", "GlobalForecastingModel", + "lr_multi_args,", ), - ] + ], + ) + def test_backtest_retrain(self, patch_retrain_func, params): + """ + Test backtest method with different retrain arguments + """ + model_cls, retrainable, multivariate, retrain, model_type, variate_args = params + if variate_args is not None: + if variate_args == "lr_univ_args": + model_args = {"lags": [-1, -2, -3]} + else: # "lr_multiv_args" + model_args = { + "lags": [-1, -2, -3], + "lags_past_covariates": [-1, -2, -3], + } + else: + model_args = dict() + model = model_cls(**model_args) + + if str(retrain) == "patch_retrain_func": + retrain = patch_retrain_func + + series = self.ts_pass_train - for model_cls, retrainable, multivariate, retrain, model_type in params: - if ( - not isinstance(retrain, (int, bool, Callable)) - or (isinstance(retrain, int) and retrain < 0) - or (isinstance(retrain, (Callable)) and (not retrainable)) - or ((retrain != 1) and (not retrainable)) - ): - with self.assertRaises(ValueError): - _ = model_cls.historical_forecasts(series, retrain=retrain) - - else: - if isinstance(retrain, Mock): - # resets patch_retrain_func call_count to 0 - retrain.call_count = 0 - retrain.side_effect = [True, False] * (len(series) // 2) - - fit_method_to_patch = f"darts.models.forecasting.forecasting_model.{model_type}._fit_wrapper" - predict_method_to_patch = f"darts.models.forecasting.forecasting_model.{model_type}._predict_wrapper" - - with patch(fit_method_to_patch) as patch_fit_method: - with patch( - predict_method_to_patch, side_effect=series - ) as patch_predict_method: - # Set _fit_called attribute to True, otherwise retrain function is never called - model_cls._fit_called = True - - # run backtest - _ = model_cls.historical_forecasts( - series, - past_covariates=series if multivariate else None, - retrain=retrain, - ) - - assert patch_predict_method.call_count > 1 - assert patch_fit_method.call_count > 1 - - if isinstance(retrain, Mock): - # check that patch_retrain_func has been called at each iteration - assert retrain.call_count > 1 - - def test_model_str_call(self): - model_expected_name_pairs = [ + if ( + not isinstance(retrain, (int, bool, Callable)) + or (isinstance(retrain, int) and retrain < 0) + or (isinstance(retrain, (Callable)) and (not retrainable)) + or ((retrain != 1) and (not retrainable)) + ): + with pytest.raises(ValueError): + _ = model.historical_forecasts(series, retrain=retrain) + + else: + if isinstance(retrain, Mock): + # resets patch_retrain_func call_count to 0 + retrain.call_count = 0 + retrain.side_effect = [True, False] * (len(series) // 2) + + fit_method_to_patch = ( + f"darts.models.forecasting.forecasting_model.{model_type}._fit_wrapper" + ) + predict_method_to_patch = f"darts.models.forecasting.forecasting_model.{model_type}._predict_wrapper" + + with patch(fit_method_to_patch) as patch_fit_method: + with patch( + predict_method_to_patch, side_effect=series + ) as patch_predict_method: + # Set _fit_called attribute to True, otherwise retrain function is never called + model._fit_called = True + + # run backtest + _ = model.historical_forecasts( + series, + past_covariates=series if multivariate else None, + retrain=retrain, + ) + + assert patch_predict_method.call_count > 1 + assert patch_fit_method.call_count > 1 + + if isinstance(retrain, Mock): + # check that patch_retrain_func has been called at each iteration + assert retrain.call_count > 1 + + @pytest.mark.parametrize( + "config", + [ # tuple of (model, expected string representation) (ExponentialSmoothing(), "ExponentialSmoothing()"), # no params changed (ARIMA(1, 1, 1), "ARIMA(p=1, q=1)"), # default value for a param ( @@ -625,12 +634,15 @@ def test_model_str_call(self): ), # params in wrong order "TBATS(use_box_cox=True, use_trend=True)", ), - ] - for model, expected in model_expected_name_pairs: - self.assertEqual(expected, str(model)) + ], + ) + def test_model_str_call(self, config): + model, expected = config + assert expected == str(model) - def test_model_repr_call(self): - model_expected_name_pairs = [ + @pytest.mark.parametrize( + "config", + [ # tuple of (model, expected representation) ( ExponentialSmoothing(), "ExponentialSmoothing(trend=ModelMode.ADDITIVE, damped=False, seasonal=SeasonalityMode.ADDITIVE, " @@ -640,6 +652,8 @@ def test_model_repr_call(self): ARIMA(1, 1, 1), "ARIMA(p=1, d=1, q=1, seasonal_order=(0, 0, 0, 0), trend=None, random_state=None, add_encoders=None)", ), # default value for a param - ] - for model, expected in model_expected_name_pairs: - self.assertEqual(expected, repr(model)) + ], + ) + def test_model_repr_call(self, config): + model, expected = config + assert expected == repr(model) diff --git a/darts/tests/models/forecasting/test_nbeats_nhits.py b/darts/tests/models/forecasting/test_nbeats_nhits.py index 243f4f6c1a..378d027379 100644 --- a/darts/tests/models/forecasting/test_nbeats_nhits.py +++ b/darts/tests/models/forecasting/test_nbeats_nhits.py @@ -1,10 +1,8 @@ -import shutil -import tempfile - import numpy as np +import pytest from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -21,15 +19,9 @@ if TORCH_AVAILABLE: - class NbeatsNhitsModelTestCase(DartsBaseTestClass): - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - + class TestNbeatsNhitsModel: def test_creation(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # if a list is passed to the `layer_widths` argument, it must have a length equal to `num_stacks` NBEATSModel( input_chunk_length=1, @@ -38,7 +30,7 @@ def test_creation(self): layer_widths=[1, 2], ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NHiTSModel( input_chunk_length=1, output_chunk_length=1, @@ -78,11 +70,11 @@ def test_fit(self): ) model2.fit(small_ts[:98]) pred2 = model2.predict(n=2).values()[0] - self.assertTrue(abs(pred2 - 10) < abs(pred - 10)) + assert abs(pred2 - 10) < abs(pred - 10) # test short predict pred3 = model2.predict(n=1) - self.assertEqual(len(pred3), 1) + assert len(pred3) == 1 def test_multivariate(self): # testing a 2-variate linear ts, first one from 0 to 1, second one from 0 to 0.5, length 100 @@ -104,10 +96,8 @@ def test_multivariate(self): # the theoretical result should be [[1.01, 1.02], [0.505, 0.51]]. # We just test if the given result is not too far on average. - self.assertTrue( - abs( - np.average(res - np.array([[1.01, 1.02], [0.505, 0.51]])) < 0.03 - ) + assert abs( + np.average(res - np.array([[1.01, 1.02], [0.505, 0.51]])) < 0.03 ) # Test Covariates @@ -127,12 +117,12 @@ def test_multivariate(self): n=3, series=series_multivariate, past_covariates=series_covariates ).values() - self.assertEqual(len(res), 3) - self.assertTrue(abs(np.average(res)) < 5) + assert len(res) == 3 + assert abs(np.average(res)) < 5 def test_nhits_sampling_sizes(self): # providing bad sizes or shapes should fail - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # wrong number of coeffs for stacks and blocks NHiTSModel( @@ -143,7 +133,7 @@ def test_nhits_sampling_sizes(self): pooling_kernel_sizes=((1,), (1,)), n_freq_downsample=((1,), (1,)), ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): NHiTSModel( input_chunk_length=1, output_chunk_length=1, @@ -170,9 +160,9 @@ def test_nhits_sampling_sizes(self): num_stacks=2, num_blocks=2, ) - self.assertEqual(model.n_freq_downsample[-1][-1], 1) + assert model.n_freq_downsample[-1][-1] == 1 - def test_logtensorboard(self): + def test_logtensorboard(self, tmpdir_module): ts = tg.constant_timeseries(length=50, value=10) # testing if both the modes (generic and interpretable) runs with tensorboard @@ -184,7 +174,7 @@ def test_logtensorboard(self): output_chunk_length=1, n_epochs=1, log_tensorboard=True, - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, generic_architecture=architecture, pl_trainer_kwargs={ "log_every_n_steps": 1, @@ -211,7 +201,7 @@ def test_activation_fns(self): ) model.fit(ts) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model = model_cls( input_chunk_length=1, output_chunk_length=1, diff --git a/darts/tests/models/forecasting/test_probabilistic_models.py b/darts/tests/models/forecasting/test_probabilistic_models.py index c4cd4f07fa..24e7a6a5e7 100644 --- a/darts/tests/models/forecasting/test_probabilistic_models.py +++ b/darts/tests/models/forecasting/test_probabilistic_models.py @@ -18,7 +18,7 @@ XGBModel, ) from darts.models.forecasting.forecasting_model import GlobalForecastingModel -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -188,7 +188,7 @@ @pytest.mark.slow -class ProbabilisticModelsTestCase(DartsBaseTestClass): +class TestProbabilisticModels: np.random.seed(0) constant_ts = tg.constant_timeseries(length=200, value=0.5) @@ -215,11 +215,11 @@ def test_fit_predict_determinism(self): model.fit(self.constant_noisy_ts_short, **fit_kwargs) pred2 = model.predict(n=10, num_samples=2).values() - self.assertTrue((pred1 == pred2).all()) + assert (pred1 == pred2).all() # test whether the next prediction of the same model is different pred3 = model.predict(n=10, num_samples=2).values() - self.assertTrue((pred2 != pred3).any()) + assert (pred2 != pred3).any() def test_probabilistic_forecast_accuracy(self): for model_cls, model_kwargs, err in models_cls_kwargs_errs: @@ -244,14 +244,14 @@ def helper_test_probabilistic_forecast_accuracy( # test accuracy of the median prediction compared to the noiseless ts mae_err_median = mae(ts[100:], pred) - self.assertLess(mae_err_median, err) + assert mae_err_median < err # test accuracy for increasing quantiles between 0.7 and 1 (it should ~decrease, mae should ~increase) tested_quantiles = [0.7, 0.8, 0.9, 0.99] mae_err = mae_err_median for quantile in tested_quantiles: new_mae = mae(ts[100:], pred.quantile_timeseries(quantile=quantile)) - self.assertLess(mae_err, new_mae + 0.1) + assert mae_err < new_mae + 0.1 mae_err = new_mae # test accuracy for decreasing quantiles between 0.3 and 0 (it should ~decrease, mae should ~increase) @@ -259,7 +259,7 @@ def helper_test_probabilistic_forecast_accuracy( mae_err = mae_err_median for quantile in tested_quantiles: new_mae = mae(ts[100:], pred.quantile_timeseries(quantile=quantile)) - self.assertLess(mae_err, new_mae + 0.1) + assert mae_err < new_mae + 0.1 mae_err = new_mae @pytest.mark.slow @@ -360,7 +360,7 @@ def test_predict_likelihood_parameters_regression_models(self): simplex_series = bounded_series["0"].stack(1.0 - bounded_series["0"]) lkl_series = [ - (GaussianLikelihood(), real_series, 0.1, 3), + (GaussianLikelihood(), real_series, 0.17, 3), (PoissonLikelihood(), discrete_pos_series, 2, 2), (NegativeBinomialLikelihood(), discrete_pos_series, 0.5, 0.5), (BernoulliLikelihood(), binary_series, 0.15, 0.15), @@ -379,40 +379,40 @@ def test_predict_likelihood_parameters_regression_models(self): (QuantileRegression(), real_series, 0.2, 1), ] - def test_likelihoods_and_resulting_mean_forecasts(self): + @pytest.mark.parametrize("lkl_config", lkl_series) + def test_likelihoods_and_resulting_mean_forecasts(self, lkl_config): def _get_avgs(series): return np.mean(series.all_values()[:, 0, :]), np.mean( series.all_values()[:, 1, :] ) - for lkl, series, diff1, diff2 in self.lkl_series: - model = RNNModel(input_chunk_length=5, likelihood=lkl, **tfm_kwargs) - model.fit(series, epochs=50) - pred = model.predict(n=50, num_samples=50) - - avgs_orig, avgs_pred = _get_avgs(series), _get_avgs(pred) - self.assertLess( - abs(avgs_orig[0] - avgs_pred[0]), - diff1, - "The difference between the mean forecast and the mean series is larger " - "than expected on component 0 for distribution {}".format(lkl), - ) - self.assertLess( - abs(avgs_orig[1] - avgs_pred[1]), - diff2, - "The difference between the mean forecast and the mean series is larger " - "than expected on component 1 for distribution {}".format(lkl), - ) - - @pytest.mark.slow - def test_predict_likelihood_parameters_univariate_torch_models(self): - """Checking convergence of model for each metric is too time consuming, making sure that the dimensions - of the predictions contain the proper elements for univariate input. - """ - # fix seed to avoid values outside of distribution's support + lkl, series, diff1, diff2 = lkl_config seed = 142857 torch.manual_seed(seed=seed) - list_lkl = [ + kwargs = { + "likelihood": lkl, + "n_epochs": 50, + "random_state": seed, + **tfm_kwargs, + } + + model = RNNModel(input_chunk_length=5, **kwargs) + model.fit(series) + pred = model.predict(n=50, num_samples=50) + + avgs_orig, avgs_pred = _get_avgs(series), _get_avgs(pred) + assert abs(avgs_orig[0] - avgs_pred[0]) < diff1, ( + "The difference between the mean forecast and the mean series is larger " + "than expected on component 0 for distribution {}".format(lkl) + ) + assert abs(avgs_orig[1] - avgs_pred[1]) < diff2, ( + "The difference between the mean forecast and the mean series is larger " + "than expected on component 1 for distribution {}".format(lkl) + ) + + @pytest.mark.parametrize( + "lkl_config", + [ # tuple of (likelihood, likelihood params) (GaussianLikelihood(), [10, 1]), (PoissonLikelihood(), [5]), (DirichletLikelihood(), [torch.Tensor([0.3, 0.3, 0.3])]), @@ -433,83 +433,69 @@ def test_predict_likelihood_parameters_univariate_torch_models(self): (LogNormalLikelihood(), [0, 0.25]), (WeibullLikelihood(), [1, 1.5]), ] - if not self.runs_on_m1: - list_lkl.append( - (CauchyLikelihood(), [0, 1]), - ) + + ([(CauchyLikelihood(), [0, 1])] if not runs_on_m1 else []), + ) + def test_predict_likelihood_parameters_univariate_torch_models( + self, lkl_config + ): + """Checking convergence of model for each metric is too time consuming, making sure that the dimensions + of the predictions contain the proper elements for univariate input. + """ + lkl, lkl_params = lkl_config + # fix seed to avoid values outside of distribution's support + seed = 142857 + torch.manual_seed(seed=seed) + kwargs = { + "likelihood": lkl, + "n_epochs": 1, + "random_state": seed, + **tfm_kwargs, + } - n_times = 100 + n_times = 5 n_comp = 1 n_samples = 1 - for lkl, lkl_params in list_lkl: - # QuantileRegression is not distribution - if isinstance(lkl, QuantileRegression): - values = np.random.normal( - loc=0, scale=1, size=(n_times, n_comp, n_samples) - ) - else: - values = lkl._distr_from_params(lkl_params).sample( - (n_times, n_comp, n_samples) - ) + # QuantileRegression is not distribution + if isinstance(lkl, QuantileRegression): + values = np.random.normal( + loc=0, scale=1, size=(n_times, n_comp, n_samples) + ) + else: + values = lkl._distr_from_params(lkl_params).sample( + (n_times, n_comp, n_samples) + ) - # Dirichlet must be handled sligthly differently since its multivariate - if isinstance(lkl, DirichletLikelihood): - values = torch.swapaxes(values, 1, 3) - values = torch.squeeze(values, 3) - lkl_params = lkl_params[0] + # Dirichlet must be handled sligthly differently since its multivariate + if isinstance(lkl, DirichletLikelihood): + values = torch.swapaxes(values, 1, 3) + values = torch.squeeze(values, 3) + lkl_params = lkl_params[0] - ts = TimeSeries.from_values( - values, columns=[f"dummy_{i}" for i in range(values.shape[1])] - ) + ts = TimeSeries.from_values( + values, columns=[f"dummy_{i}" for i in range(values.shape[1])] + ) - # [DualCovariatesModule, PastCovariatesModule, MixedCovariatesModule] - models = [ - RNNModel( - 4, - "RNN", - likelihood=lkl, - n_epochs=1, - random_state=seed, - **tfm_kwargs, - ), - NBEATSModel( - 4, - 1, - likelihood=lkl, - n_epochs=1, - random_state=seed, - **tfm_kwargs, - ), - DLinearModel( - 4, - 1, - likelihood=lkl, - n_epochs=1, - random_state=seed, - **tfm_kwargs, - ), - ] + # [DualCovariatesModule, PastCovariatesModule, MixedCovariatesModule] + models = [ + RNNModel(4, "RNN", training_length=4, **kwargs), + NBEATSModel(4, 1, **kwargs), + DLinearModel(4, 1, **kwargs), + ] - true_lkl_params = np.array(lkl_params) - for model in models: - # univariate - model.fit(ts) - pred_lkl_params = model.predict( - n=1, num_samples=1, predict_likelihood_parameters=True - ) + true_lkl_params = np.array(lkl_params) + for model in models: + # univariate + model.fit(ts) + pred_lkl_params = model.predict( + n=1, num_samples=1, predict_likelihood_parameters=True + ) - # check the dimensions, values require too much training - self.assertEqual( - pred_lkl_params.values().shape[1], len(true_lkl_params) - ) + # check the dimensions, values require too much training + assert pred_lkl_params.values().shape[1] == len(true_lkl_params) - @pytest.mark.slow - def test_predict_likelihood_parameters_multivariate_torch_models(self): - """Checking convergence of model for each metric is too time consuming, making sure that the dimensions - of the predictions contain the proper elements for multivariate inputs. - """ - torch.manual_seed(seed=142857) - list_lkl = [ + @pytest.mark.parametrize( + "lkl_config", + [ ( GaussianLikelihood(), [10, 1], @@ -528,46 +514,60 @@ def test_predict_likelihood_parameters_multivariate_torch_models(self): "dummy_1_q0.95", ], ), - ] + ], + ) + def test_predict_likelihood_parameters_multivariate_torch_models( + self, lkl_config + ): + """Checking convergence of model for each metric is too time consuming, making sure that the dimensions + of the predictions contain the proper elements for multivariate inputs. + """ + lkl, lkl_params, comp_names = lkl_config + # fix seed to avoid values outside of distribution's support - n_times = 100 + seed = 142857 + torch.manual_seed(seed=seed) + kwargs = { + "likelihood": lkl, + "n_epochs": 1, + "random_state": seed, + **tfm_kwargs, + } + + n_times = 5 n_comp = 2 n_samples = 1 - for lkl, lkl_params, comp_names in list_lkl: - if isinstance(lkl, QuantileRegression): - values = np.random.normal( - loc=0, scale=1, size=(n_times, n_comp, n_samples) - ) - else: - values = lkl._distr_from_params(lkl_params).sample( - (n_times, n_comp, n_samples) - ) - ts = TimeSeries.from_values( - values, columns=[f"dummy_{i}" for i in range(values.shape[1])] + if isinstance(lkl, QuantileRegression): + values = np.random.normal( + loc=0, scale=1, size=(n_times, n_comp, n_samples) + ) + else: + values = lkl._distr_from_params(lkl_params).sample( + (n_times, n_comp, n_samples) ) + ts = TimeSeries.from_values( + values, columns=[f"dummy_{i}" for i in range(values.shape[1])] + ) - # [DualCovariatesModule, PastCovariatesModule, MixedCovariatesModule] - models = [ - RNNModel(4, "RNN", likelihood=lkl, n_epochs=1, **tfm_kwargs), - TCNModel(4, 1, likelihood=lkl, n_epochs=1, **tfm_kwargs), - DLinearModel(4, 1, likelihood=lkl, n_epochs=1, **tfm_kwargs), - ] + # [DualCovariatesModule, PastCovariatesModule, MixedCovariatesModule] + models = [ + RNNModel(4, "RNN", training_length=4, **kwargs), + TCNModel(4, 1, **kwargs), + DLinearModel(4, 1, **kwargs), + ] - for model in models: - model.fit(ts) - pred_lkl_params = model.predict( - n=1, num_samples=1, predict_likelihood_parameters=True - ) - # check the dimensions - self.assertEqual( - pred_lkl_params.values().shape[1], n_comp * len(lkl_params) - ) - # check the component names - self.assertTrue( - list(pred_lkl_params.components) == comp_names, - f"Components names are not matching; expected {comp_names} " - f"but received {list(pred_lkl_params.components)}", - ) + for model in models: + model.fit(ts) + pred_lkl_params = model.predict( + n=1, num_samples=1, predict_likelihood_parameters=True + ) + # check the dimensions + assert pred_lkl_params.values().shape[1] == n_comp * len(lkl_params) + # check the component names + assert list(pred_lkl_params.components) == comp_names, ( + f"Components names are not matching; expected {comp_names} " + f"but received {list(pred_lkl_params.components)}" + ) def test_predict_likelihood_parameters_wrong_args(self): # deterministic model @@ -578,7 +578,7 @@ def test_predict_likelihood_parameters_wrong_args(self): **tfm_kwargs, ) model.fit(self.constant_noisy_ts) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.predict(n=1, predict_likelihood_parameters=True) model = DLinearModel( @@ -590,10 +590,10 @@ def test_predict_likelihood_parameters_wrong_args(self): ) model.fit(self.constant_noisy_ts) # num_samples > 1 - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.predict(n=1, num_samples=2, predict_likelihood_parameters=True) # n > output_chunk_length - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.predict(n=5, num_samples=1, predict_likelihood_parameters=True) model.predict(n=4, num_samples=1, predict_likelihood_parameters=True) @@ -616,4 +616,4 @@ def test_stochastic_inputs(self): preds = [model.predict(series=stochastic_series, n=10) for _ in range(2)] # random samples should differ - self.assertFalse(np.alltrue(preds[0].values() == preds[1].values())) + assert not np.array_equal(preds[0].values(), preds[1].values()) diff --git a/darts/tests/models/forecasting/test_prophet.py b/darts/tests/models/forecasting/test_prophet.py index ce4d8c34d8..21ec5b2b60 100644 --- a/darts/tests/models/forecasting/test_prophet.py +++ b/darts/tests/models/forecasting/test_prophet.py @@ -1,4 +1,3 @@ -import unittest from unittest.mock import Mock import numpy as np @@ -8,14 +7,13 @@ from darts import TimeSeries from darts.logging import get_logger from darts.models import NotImportedModule, Prophet -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg logger = get_logger(__name__) -@unittest.skipUnless(not isinstance(Prophet, NotImportedModule), "requires prophet") -class ProphetTestCase(DartsBaseTestClass): +@pytest.mark.skipif(isinstance(Prophet, NotImportedModule), reason="requires prophet") +class TestProphet: def test_add_seasonality_calls(self): # test if adding seasonality at model creation and with method model.add_seasonality() are equal kwargs_mandatory = { @@ -39,23 +37,23 @@ def test_add_seasonality_calls(self): model1 = Prophet(add_seasonalities=kwargs_all) model2 = Prophet() model2.add_seasonality(**kwargs_all) - self.assertEqual(model1._add_seasonalities, model2._add_seasonalities) + assert model1._add_seasonalities == model2._add_seasonalities # add multiple seasonalities model3 = Prophet(add_seasonalities=[kwargs_mandatory, kwargs_mandatory2]) - self.assertEqual(len(model3._add_seasonalities), 2) + assert len(model3._add_seasonalities) == 2 # seasonality already exists - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model1.add_seasonality(**kwargs_mandatory) # missing mandatory arguments - with self.assertRaises(ValueError): + with pytest.raises(ValueError): for kw, arg in kwargs_mandatory.items(): Prophet(add_seasonalities={kw: arg}) # invalid keywords - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Prophet( add_seasonalities=dict( kwargs_mandatory, **{"some_random_keyword": "custom"} @@ -63,10 +61,10 @@ def test_add_seasonality_calls(self): ) # invalid value dtypes - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Prophet(add_seasonalities=dict({kw: None for kw in kwargs_mandatory})) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): Prophet(add_seasonalities=dict([])) def test_prophet_model(self): @@ -162,7 +160,7 @@ def test_prophet_model_with_logistic_growth(self): pred = model.predict(len(val)) for val_i, pred_i in zip(val.univariate_values(), pred.univariate_values()): - self.assertAlmostEqual(val_i, pred_i, delta=0.1) + assert abs(val_i - pred_i) < 0.1 def helper_test_freq_coversion(self, test_cases): for freq, period in test_cases.items(): @@ -172,14 +170,15 @@ def helper_test_freq_coversion(self, test_cases): # this should not raise an error if frequency is known _ = Prophet._freq_to_days(freq=ts_sine.freq_str) - self.assertAlmostEqual( - Prophet._freq_to_days(freq="30S"), - 30 * Prophet._freq_to_days(freq="S"), - delta=10e-9, + assert ( + abs( + Prophet._freq_to_days(freq="30S") - 30 * Prophet._freq_to_days(freq="S") + ) + < 10e-9 ) # check bad frequency string - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = Prophet._freq_to_days(freq="30SS") def helper_test_prophet_model(self, period, freq, compare_all_models=False): @@ -243,7 +242,7 @@ def helper_test_prophet_model(self, period, freq, compare_all_models=False): # all predictions should fit the underlying curve very well for pred in compare_preds: for val_i, pred_i in zip(val.univariate_values(), pred.univariate_values()): - self.assertAlmostEqual(val_i, pred_i, delta=0.1) + assert abs(val_i - pred_i) < 0.1 def test_conditional_seasonality(self): """ @@ -281,7 +280,7 @@ def test_conditional_seasonality(self): for val_i, pred_i in zip( expected_result.univariate_values(), forecast.univariate_values() ): - self.assertAlmostEqual(val_i, pred_i, delta=0.1) + assert abs(val_i - pred_i) < 0.1 invalid_future_covariates = future_covariates.with_values( np.reshape(np.random.randint(0, 3, duration), (-1, 1, 1)).astype("float") diff --git a/darts/tests/models/forecasting/test_ptl_trainer.py b/darts/tests/models/forecasting/test_ptl_trainer.py index c45b91292f..d9449fa58d 100644 --- a/darts/tests/models/forecasting/test_ptl_trainer.py +++ b/darts/tests/models/forecasting/test_ptl_trainer.py @@ -1,10 +1,8 @@ -import shutil -import tempfile - import numpy as np +import pytest from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils.timeseries_generation import linear_timeseries logger = get_logger(__name__) @@ -22,7 +20,7 @@ if TORCH_AVAILABLE: - class TestTorchForecastingModel(DartsBaseTestClass): + class TestTorchForecastingModel: trainer_params = { "max_epochs": 1, "logger": False, @@ -35,13 +33,7 @@ class TestTorchForecastingModel(DartsBaseTestClass): 64: "64" if not pl_200_or_above else "64-true", } - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - - def test_prediction_loaded_custom_trainer(self): + def test_prediction_loaded_custom_trainer(self, tmpdir_module): """validate manual save with automatic save files by comparing output between the two""" auto_name = "test_save_automatic" model = RNNModel( @@ -50,7 +42,7 @@ def test_prediction_loaded_custom_trainer(self): 10, 10, model_name=auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, save_checkpoints=True, random_state=42, **tfm_kwargs, @@ -69,13 +61,13 @@ def test_prediction_loaded_custom_trainer(self): # load automatically saved model with manual load_model() and load_from_checkpoint() model_loaded = RNNModel.load_from_checkpoint( model_name=auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, best=False, map_location="cpu", ) # compare prediction of loaded model with original model - self.assertEqual(model.predict(n=4), model_loaded.predict(n=4)) + assert model.predict(n=4) == model_loaded.predict(n=4) def test_prediction_custom_trainer(self): model = RNNModel(12, "RNN", 10, 10, random_state=42, **tfm_kwargs) @@ -93,7 +85,7 @@ def test_prediction_custom_trainer(self): model2.fit(self.series, epochs=1) # both should produce identical prediction - self.assertEqual(model.predict(n=4), model2.predict(n=4)) + assert model.predict(n=4) == model2.predict(n=4) def test_custom_trainer_setup(self): model = RNNModel(12, "RNN", 10, 10, random_state=42, **tfm_kwargs) @@ -104,7 +96,7 @@ def test_custom_trainer_setup(self): precision=self.precisions[64], **tfm_kwargs["pl_trainer_kwargs"], ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model.fit(self.series, trainer=trainer) # no error with correct precision @@ -116,11 +108,11 @@ def test_custom_trainer_setup(self): model.fit(self.series, trainer=trainer) # check if number of epochs trained is same as trainer.max_epochs - self.assertEqual(trainer.max_epochs, model.epochs_trained) + assert trainer.max_epochs == model.epochs_trained def test_builtin_extended_trainer(self): # wrong precision parameter name - with self.assertRaises(TypeError): + with pytest.raises(TypeError): invalid_trainer_kwarg = { "precisionn": self.precisions[32], **tfm_kwargs["pl_trainer_kwargs"], @@ -136,7 +128,7 @@ def test_builtin_extended_trainer(self): model.fit(self.series, epochs=1) # flaot 16 not supported - with self.assertRaises(ValueError): + with pytest.raises(ValueError): invalid_trainer_kwarg = { "precision": "16-mixed", **tfm_kwargs["pl_trainer_kwargs"], @@ -152,7 +144,7 @@ def test_builtin_extended_trainer(self): model.fit(self.series.astype(np.float16), epochs=1) # precision value doesn't match `series` dtype - with self.assertRaises(ValueError): + with pytest.raises(ValueError): invalid_trainer_kwarg = { "precision": self.precisions[64], **tfm_kwargs["pl_trainer_kwargs"], @@ -188,7 +180,7 @@ def test_builtin_extended_trainer(self): assert model.trainer.precision == self.precisions[precision] assert preds.dtype == ts_dtype - def test_custom_callback(self): + def test_custom_callback(self, tmpdir_module): class CounterCallback(pl.callbacks.Callback): # counts the number of trained epochs starting from count_default def __init__(self, count_default): @@ -213,13 +205,13 @@ def on_train_epoch_end(self, *args, **kwargs): ) # check if callbacks were added - self.assertEqual(len(model.trainer_params["callbacks"]), 2) + assert len(model.trainer_params["callbacks"]) == 2 model.fit(self.series, epochs=2, verbose=True) # check that lightning did not mutate callbacks (verbosity adds a progress bar callback) - self.assertEqual(len(model.trainer_params["callbacks"]), 2) + assert len(model.trainer_params["callbacks"]) == 2 - self.assertEqual(my_counter_0.counter, model.epochs_trained) - self.assertEqual(my_counter_2.counter, model.epochs_trained + 2) + assert my_counter_0.counter == model.epochs_trained + assert my_counter_2.counter == model.epochs_trained + 2 # check that callbacks don't overwrite Darts' built-in checkpointer model = RNNModel( @@ -228,7 +220,7 @@ def on_train_epoch_end(self, *args, **kwargs): 10, 10, random_state=42, - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, save_checkpoints=True, pl_trainer_kwargs={ "callbacks": [CounterCallback(0), CounterCallback(2)], @@ -236,20 +228,16 @@ def on_train_epoch_end(self, *args, **kwargs): }, ) # we expect 3 callbacks - self.assertEqual(len(model.trainer_params["callbacks"]), 3) + assert len(model.trainer_params["callbacks"]) == 3 # first one is our Checkpointer - self.assertTrue( - isinstance( - model.trainer_params["callbacks"][0], pl.callbacks.ModelCheckpoint - ) + assert isinstance( + model.trainer_params["callbacks"][0], pl.callbacks.ModelCheckpoint ) # second and third are CounterCallbacks for i in range(1, 3): - self.assertTrue( - isinstance(model.trainer_params["callbacks"][i], CounterCallback) - ) + assert isinstance(model.trainer_params["callbacks"][i], CounterCallback) def test_early_stopping(self): my_stopper = pl.callbacks.early_stopping.EarlyStopping( @@ -271,7 +259,7 @@ def test_early_stopping(self): # training should stop immediately with high stopping_threshold model.fit(self.series, val_series=self.series, epochs=100, verbose=True) - self.assertEqual(model.epochs_trained, 1) + assert model.epochs_trained == 1 # check that early stopping only takes valid monitor variables my_stopper = pl.callbacks.early_stopping.EarlyStopping( @@ -291,5 +279,5 @@ def test_early_stopping(self): }, ) - with self.assertRaises(RuntimeError): + with pytest.raises(RuntimeError): model.fit(self.series, val_series=self.series, epochs=100, verbose=True) diff --git a/darts/tests/models/forecasting/test_regression_ensemble_model.py b/darts/tests/models/forecasting/test_regression_ensemble_model.py index 538d98d982..68904fe616 100644 --- a/darts/tests/models/forecasting/test_regression_ensemble_model.py +++ b/darts/tests/models/forecasting/test_regression_ensemble_model.py @@ -1,8 +1,8 @@ -import unittest from typing import List, Union import numpy as np import pandas as pd +import pytest from sklearn.ensemble import RandomForestRegressor from sklearn.linear_model import LinearRegression @@ -18,7 +18,7 @@ RegressionModel, Theta, ) -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.tests.models.forecasting.test_ensemble_models import _make_ts from darts.tests.models.forecasting.test_regression_models import train_test_split from darts.utils import timeseries_generation as tg @@ -36,7 +36,7 @@ TORCH_AVAILABLE = False -class RegressionEnsembleModelsTestCase(DartsBaseTestClass): +class TestRegressionEnsembleModels: RANDOM_SEED = 111 sine_series = tg.sine_timeseries( @@ -69,7 +69,7 @@ class RegressionEnsembleModelsTestCase(DartsBaseTestClass): def get_local_models(self): return [NaiveDrift(), NaiveSeasonal(5), NaiveSeasonal(10)] - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def get_global_models(self, output_chunk_length=5): return [ RNNModel( @@ -143,19 +143,19 @@ def test_train_n_points(self): # too big value to perform the split ensemble = RegressionEnsembleModel(self.get_local_models(), 100) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble.fit(self.combined) ensemble = RegressionEnsembleModel(self.get_local_models(), 50) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble.fit(self.combined) # too big value considering min_train_series_length ensemble = RegressionEnsembleModel(self.get_local_models(), 45) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble.fit(self.combined) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_torch_models_retrain(self): model1 = BlockRNNModel( input_chunk_length=12, @@ -181,11 +181,9 @@ def test_torch_models_retrain(self): model2.fit(self.combined) forecast2 = model2.predict(10) - self.assertAlmostEqual( - sum(forecast1.values() - forecast2.values())[0], 0.0, places=2 - ) + assert round(abs(sum(forecast1.values() - forecast2.values())[0] - 0.0), 2) == 0 - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_train_predict_global_models_univar(self): ensemble_models = self.get_global_models(output_chunk_length=10) ensemble_models.append(RegressionModel(lags=1)) @@ -193,7 +191,7 @@ def test_train_predict_global_models_univar(self): ensemble.fit(series=self.combined) ensemble.predict(10) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_train_predict_global_models_multivar_no_covariates(self): ensemble_models = self.get_global_models(output_chunk_length=10) ensemble_models.append(RegressionModel(lags=1)) @@ -201,7 +199,7 @@ def test_train_predict_global_models_multivar_no_covariates(self): ensemble.fit(self.seq1) ensemble.predict(10, self.seq1) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_train_predict_global_models_multivar_with_covariates(self): ensemble_models = self.get_global_models(output_chunk_length=10) ensemble_models.append(RegressionModel(lags=1, lags_past_covariates=[-1])) @@ -231,44 +229,44 @@ def test_predict_with_target(self): ensemble_model.fit(series_short, past_covariates=series_long) # predict after end of train series preds = ensemble_model.predict(n=5, past_covariates=series_long) - self.assertTrue(isinstance(preds, TimeSeries)) + assert isinstance(preds, TimeSeries) # predict a new target series preds = ensemble_model.predict( n=5, series=series_long, past_covariates=series_long ) - self.assertTrue(isinstance(preds, TimeSeries)) + assert isinstance(preds, TimeSeries) # predict multiple target series preds = ensemble_model.predict( n=5, series=[series_long] * 2, past_covariates=[series_long] * 2 ) - self.assertTrue(isinstance(preds, list) and len(preds) == 2) + assert isinstance(preds, list) and len(preds) == 2 # predict single target series in list preds = ensemble_model.predict( n=5, series=[series_long], past_covariates=[series_long] ) - self.assertTrue(isinstance(preds, list) and len(preds) == 1) + assert isinstance(preds, list) and len(preds) == 1 # train with multiple series ensemble_model = self.get_global_ensembe_model() ensemble_model.fit([series_short] * 2, past_covariates=[series_long] * 2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # predict without passing series should raise an error ensemble_model.predict(n=5, past_covariates=series_long) # predict a new target series preds = ensemble_model.predict( n=5, series=series_long, past_covariates=series_long ) - self.assertTrue(isinstance(preds, TimeSeries)) + assert isinstance(preds, TimeSeries) # predict multiple target series preds = ensemble_model.predict( n=5, series=[series_long] * 2, past_covariates=[series_long] * 2 ) - self.assertTrue(isinstance(preds, list) and len(preds) == 2) + assert isinstance(preds, list) and len(preds) == 2 # predict single target series in list preds = ensemble_model.predict( n=5, series=[series_long], past_covariates=[series_long] ) - self.assertTrue(isinstance(preds, list) and len(preds) == 1) + assert isinstance(preds, list) and len(preds) == 1 def helper_test_models_accuracy( self, model_instance, n, series, past_covariates, min_rmse @@ -283,10 +281,9 @@ def helper_test_models_accuracy( prediction = model_instance.predict(n=n, past_covariates=past_covariates) current_rmse = rmse(test_series, prediction) - self.assertTrue( - current_rmse <= min_rmse, - f"Model was not able to denoise data. A rmse score of {current_rmse} was recorded.", - ) + assert ( + current_rmse <= min_rmse + ), f"Model was not able to denoise data. A rmse score of {current_rmse} was recorded." def denoising_input(self): np.random.seed(self.RANDOM_SEED) @@ -306,7 +303,7 @@ def denoising_input(self): return ts_sum1, ts_cov1, ts_sum2, ts_cov2 - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_ensemble_models_denoising(self): # for every model, test whether it correctly denoises ts_sum using ts_gaussian and ts_sum as inputs # WARNING: this test isn't numerically stable, changing self.RANDOM_SEED can lead to exploding coefficients @@ -335,7 +332,7 @@ def test_ensemble_models_denoising(self): ensemble = RegressionEnsembleModel(ensemble_models, horizon) self.helper_test_models_accuracy(ensemble, horizon, ts_sum1, ts_cov1, 3) - @unittest.skipUnless(TORCH_AVAILABLE, "requires torch") + @pytest.mark.skipif(not TORCH_AVAILABLE, reason="requires torch") def test_ensemble_models_denoising_multi_input(self): # for every model, test whether it correctly denoises ts_sum_2 using ts_random_multi and ts_sum_2 as inputs # WARNING: this test isn't numerically stable, changing self.RANDOM_SEED can lead to exploding coefficients @@ -388,7 +385,7 @@ def test_extreme_lags(self): regression_train_n_points=train_n_points, ) - self.assertEqual(model.extreme_lags, (-train_n_points, 0, -3, -1, 0, 0)) + assert model.extreme_lags == (-train_n_points, 0, -3, -1, 0, 0) # mix of all the lags model3 = RandomForest( @@ -400,7 +397,7 @@ def test_extreme_lags(self): regression_train_n_points=train_n_points, ) - self.assertEqual(model.extreme_lags, (-7 - train_n_points, 0, -3, -1, -2, 5)) + assert model.extreme_lags == (-7 - train_n_points, 0, -3, -1, -2, 5) def test_stochastic_regression_ensemble_model(self): quantiles = [0.25, 0.5, 0.75] @@ -423,12 +420,12 @@ def test_stochastic_regression_ensemble_model(self): regression_model=linreg_prob.untrained_model(), ) - self.assertTrue(ensemble_allproba._models_are_probabilistic) - self.assertTrue(ensemble_allproba._is_probabilistic) + assert ensemble_allproba._models_are_probabilistic + assert ensemble_allproba._is_probabilistic ensemble_allproba.fit(self.ts_random_walk[:100]) # probabilistic forecasting is supported pred = ensemble_allproba.predict(5, num_samples=10) - self.assertEqual(pred.n_samples, 10) + assert pred.n_samples == 10 # forecasting models are a mix of probabilistic and deterministic, probabilistic regressor ensemble_mixproba = RegressionEnsembleModel( @@ -440,12 +437,12 @@ def test_stochastic_regression_ensemble_model(self): regression_model=linreg_prob.untrained_model(), ) - self.assertFalse(ensemble_mixproba._models_are_probabilistic) - self.assertTrue(ensemble_mixproba._is_probabilistic) + assert not ensemble_mixproba._models_are_probabilistic + assert ensemble_mixproba._is_probabilistic ensemble_mixproba.fit(self.ts_random_walk[:100]) # probabilistic forecasting is supported pred = ensemble_mixproba.predict(5, num_samples=10) - self.assertEqual(pred.n_samples, 10) + assert pred.n_samples == 10 # forecasting models are a mix of probabilistic and deterministic, probabilistic regressor # with regression_train_num_samples > 1 @@ -460,11 +457,11 @@ def test_stochastic_regression_ensemble_model(self): regression_train_samples_reduction="median", ) - self.assertFalse(ensemble_mixproba2._models_are_probabilistic) - self.assertTrue(ensemble_mixproba2._is_probabilistic) + assert not ensemble_mixproba2._models_are_probabilistic + assert ensemble_mixproba2._is_probabilistic ensemble_mixproba2.fit(self.ts_random_walk[:100]) pred = ensemble_mixproba2.predict(5, num_samples=10) - self.assertEqual(pred.n_samples, 10) + assert pred.n_samples == 10 # only regression model is probabilistic ensemble_proba_reg = RegressionEnsembleModel( @@ -476,12 +473,12 @@ def test_stochastic_regression_ensemble_model(self): regression_model=linreg_prob.untrained_model(), ) - self.assertFalse(ensemble_proba_reg._models_are_probabilistic) - self.assertTrue(ensemble_proba_reg._is_probabilistic) + assert not ensemble_proba_reg._models_are_probabilistic + assert ensemble_proba_reg._is_probabilistic ensemble_proba_reg.fit(self.ts_random_walk[:100]) # probabilistic forecasting is supported pred = ensemble_proba_reg.predict(5, num_samples=10) - self.assertEqual(pred.n_samples, 10) + assert pred.n_samples == 10 # every models but regression model are probabilistics ensemble_dete_reg = RegressionEnsembleModel( @@ -493,13 +490,13 @@ def test_stochastic_regression_ensemble_model(self): regression_model=linreg_dete.untrained_model(), ) - self.assertTrue(ensemble_dete_reg._models_are_probabilistic) - self.assertFalse(ensemble_dete_reg._is_probabilistic) + assert ensemble_dete_reg._models_are_probabilistic + assert not ensemble_dete_reg._is_probabilistic ensemble_dete_reg.fit(self.ts_random_walk[:100]) # deterministic forecasting is supported ensemble_dete_reg.predict(5, num_samples=1) # probabilistic forecasting is not supported - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble_dete_reg.predict(5, num_samples=10) # every models are deterministic @@ -512,17 +509,17 @@ def test_stochastic_regression_ensemble_model(self): regression_model=linreg_dete.untrained_model(), ) - self.assertFalse(ensemble_alldete._models_are_probabilistic) - self.assertFalse(ensemble_alldete._is_probabilistic) + assert not ensemble_alldete._models_are_probabilistic + assert not ensemble_alldete._is_probabilistic ensemble_alldete.fit(self.ts_random_walk[:100]) # deterministic forecasting is supported ensemble_alldete.predict(5, num_samples=1) # probabilistic forecasting is not supported - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble_alldete.predict(5, num_samples=10) # deterministic forecasters cannot be sampled - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RegressionEnsembleModel( forecasting_models=[ self.get_deterministic_global_model(lags=[-1, -3]), @@ -541,7 +538,7 @@ def test_stochastic_training_regression_ensemble_model(self): quantiles = [0.25, 0.5, 0.75] # cannot sample deterministic forecasting models - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RegressionEnsembleModel( forecasting_models=[ self.get_deterministic_global_model(lags=[-1, -3]), @@ -552,7 +549,7 @@ def test_stochastic_training_regression_ensemble_model(self): ) # must use apprioriate reduction method - with self.assertRaises(ValueError): + with pytest.raises(ValueError): RegressionEnsembleModel( forecasting_models=[ self.get_probabilistic_global_model( @@ -586,7 +583,7 @@ def test_stochastic_training_regression_ensemble_model(self): regression_train_n_points=50, regression_train_num_samples=500, ) - self.assertEqual(ensemble_model_median.train_samples_reduction, "median") + assert ensemble_model_median.train_samples_reduction == "median" ensemble_model_0_5_quantile = RegressionEnsembleModel( forecasting_models=[ @@ -607,18 +604,18 @@ def test_stochastic_training_regression_ensemble_model(self): pred_median_training = ensemble_model_median.predict(len(val)) pred_0_5_qt_training = ensemble_model_0_5_quantile.predict(len(val)) - self.assertEqual(pred_median_training, pred_0_5_qt_training) - self.assertEqual( - pred_mean_training.all_values().shape, - pred_median_training.all_values().shape, + assert pred_median_training == pred_0_5_qt_training + assert ( + pred_mean_training.all_values().shape + == pred_median_training.all_values().shape ) # deterministic regression model -> deterministic ensemble - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble_model_mean.predict(len(val), num_samples=100) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble_model_median.predict(len(val), num_samples=100) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ensemble_model_0_5_quantile.predict(len(val), num_samples=100) # possible to use very small regression_train_num_samples @@ -659,13 +656,10 @@ def test_predict_likelihood_parameters_univariate_regression_ensemble(self): ensemble.fit(self.sine_series) pred_ens = ensemble.predict(n=4, predict_likelihood_parameters=True) - self.assertTrue( - all(pred_ens.components == ["sine_q0.05", "sine_q0.50", "sine_q0.95"]) - ) - self.assertTrue( - all(pred_ens["sine_q0.05"].values() < pred_ens["sine_q0.50"].values()) - and all(pred_ens["sine_q0.50"].values() < pred_ens["sine_q0.95"].values()) - ) + assert all(pred_ens.components == ["sine_q0.05", "sine_q0.50", "sine_q0.95"]) + assert all( + pred_ens["sine_q0.05"].values() < pred_ens["sine_q0.50"].values() + ) and all(pred_ens["sine_q0.50"].values() < pred_ens["sine_q0.95"].values()) def test_predict_likelihood_parameters_multivariate_regression_ensemble(self): quantiles = [0.05, 0.5, 0.95] @@ -691,29 +685,23 @@ def test_predict_likelihood_parameters_multivariate_regression_ensemble(self): ensemble.fit(multivariate_series) pred_ens = ensemble.predict(n=4, predict_likelihood_parameters=True) - self.assertTrue( - all( - pred_ens.components - == [ - "sine_q0.05", - "sine_q0.50", - "sine_q0.95", - "linear_q0.05", - "linear_q0.50", - "linear_q0.95", - ] - ) - ) - self.assertTrue( - all(pred_ens["sine_q0.05"].values() < pred_ens["sine_q0.50"].values()) - and all(pred_ens["sine_q0.50"].values() < pred_ens["sine_q0.95"].values()) - ) - self.assertTrue( - all(pred_ens["linear_q0.05"].values() < pred_ens["linear_q0.50"].values()) - and all( - pred_ens["linear_q0.50"].values() < pred_ens["linear_q0.95"].values() - ) - ) + assert all( + pred_ens.components + == [ + "sine_q0.05", + "sine_q0.50", + "sine_q0.95", + "linear_q0.05", + "linear_q0.50", + "linear_q0.95", + ] + ) + assert all( + pred_ens["sine_q0.05"].values() < pred_ens["sine_q0.50"].values() + ) and all(pred_ens["sine_q0.50"].values() < pred_ens["sine_q0.95"].values()) + assert all( + pred_ens["linear_q0.05"].values() < pred_ens["linear_q0.50"].values() + ) and all(pred_ens["linear_q0.50"].values() < pred_ens["linear_q0.95"].values()) @staticmethod def get_probabilistic_global_model( diff --git a/darts/tests/models/forecasting/test_regression_models.py b/darts/tests/models/forecasting/test_regression_models.py index 3eadb8f704..4366a8ef3a 100644 --- a/darts/tests/models/forecasting/test_regression_models.py +++ b/darts/tests/models/forecasting/test_regression_models.py @@ -1,7 +1,7 @@ import copy import functools +import itertools import math -import unittest from unittest.mock import patch import numpy as np @@ -28,7 +28,6 @@ XGBModel, ) from darts.models.forecasting.forecasting_model import GlobalForecastingModel -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import timeseries_generation as tg from darts.utils.multioutput import MultiOutputRegressor @@ -158,7 +157,7 @@ class NewCls(cls): return NewCls -class RegressionModelsTestCase(DartsBaseTestClass): +class TestRegressionModels: np.random.seed(42) @@ -411,130 +410,114 @@ def _apply_promo_mechanism(promo_mechanism): return series, past_covariates, future_covariates - def test_model_construction(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - for model in self.models: - # TESTING SINGLE INT - # testing lags - model_instance = model(lags=5, multi_models=mode) - self.assertEqual( - model_instance.lags.get("target"), [-5, -4, -3, -2, -1] - ) - # testing lags_past_covariates - model_instance = model( - lags=None, lags_past_covariates=3, multi_models=mode - ) - self.assertEqual(model_instance.lags.get("past"), [-3, -2, -1]) - # testing lags_future covariates - model_instance = model( - lags=None, lags_future_covariates=(3, 5), multi_models=mode - ) - self.assertEqual( - model_instance.lags.get("future"), [-3, -2, -1, 0, 1, 2, 3, 4] - ) - - # TESTING LIST of int - # lags - values = [-5, -3, -1] - model_instance = model(lags=values, multi_models=mode) - self.assertEqual(model_instance.lags.get("target"), values) - # testing lags_past_covariates - model_instance = model(lags_past_covariates=values, multi_models=mode) - self.assertEqual(model_instance.lags.get("past"), values) - # testing lags_future_covariates - - with self.assertRaises(ValueError): - model(multi_models=mode) - with self.assertRaises(ValueError): - model(lags=0, multi_models=mode) - with self.assertRaises(ValueError): - model(lags=[-1, 0], multi_models=mode) - with self.assertRaises(ValueError): - model(lags=[3, 5], multi_models=mode) - with self.assertRaises(ValueError): - model(lags=[-3, -5.0], multi_models=mode) - with self.assertRaises(ValueError): - model(lags=-5, multi_models=mode) - with self.assertRaises(ValueError): - model(lags=3.6, multi_models=mode) - with self.assertRaises(ValueError): - model(lags=None, lags_past_covariates=False, multi_models=mode) - with self.assertRaises(ValueError): - model(lags=None, multi_models=mode) - with self.assertRaises(ValueError): - model(lags=5, lags_future_covariates=True, multi_models=mode) - with self.assertRaises(ValueError): - model(lags=5, lags_future_covariates=(1, -3), multi_models=mode) - with self.assertRaises(ValueError): - model(lags=5, lags_future_covariates=(1, 2, 3), multi_models=mode) - with self.assertRaises(ValueError): - model(lags=5, lags_future_covariates=(1, True), multi_models=mode) - with self.assertRaises(ValueError): - model(lags=5, lags_future_covariates=(1, 1.0), multi_models=mode) - - def test_training_data_creation(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - # testing _get_training_data function - model_instance = RegressionModel( - lags=self.lags_1["target"], - lags_past_covariates=self.lags_1["past"], - lags_future_covariates=self.lags_1["future"], - multi_models=mode, - ) - - max_samples_per_ts = 17 + @pytest.mark.parametrize("config", itertools.product(models, [True, False])) + def test_model_construction(self, config): + model, mode = config + # TESTING SINGLE INT + # testing lags + model_instance = model(lags=5, multi_models=mode) + assert model_instance.lags.get("target") == [-5, -4, -3, -2, -1] + # testing lags_past_covariates + model_instance = model(lags=None, lags_past_covariates=3, multi_models=mode) + assert model_instance.lags.get("past") == [-3, -2, -1] + # testing lags_future covariates + model_instance = model( + lags=None, lags_future_covariates=(3, 5), multi_models=mode + ) + assert model_instance.lags.get("future") == [-3, -2, -1, 0, 1, 2, 3, 4] + + # TESTING LIST of int + # lags + values = [-5, -3, -1] + model_instance = model(lags=values, multi_models=mode) + assert model_instance.lags.get("target") == values + # testing lags_past_covariates + model_instance = model(lags_past_covariates=values, multi_models=mode) + assert model_instance.lags.get("past") == values + # testing lags_future_covariates + + with pytest.raises(ValueError): + model(multi_models=mode) + with pytest.raises(ValueError): + model(lags=0, multi_models=mode) + with pytest.raises(ValueError): + model(lags=[-1, 0], multi_models=mode) + with pytest.raises(ValueError): + model(lags=[3, 5], multi_models=mode) + with pytest.raises(ValueError): + model(lags=[-3, -5.0], multi_models=mode) + with pytest.raises(ValueError): + model(lags=-5, multi_models=mode) + with pytest.raises(ValueError): + model(lags=3.6, multi_models=mode) + with pytest.raises(ValueError): + model(lags=None, lags_past_covariates=False, multi_models=mode) + with pytest.raises(ValueError): + model(lags=None, multi_models=mode) + with pytest.raises(ValueError): + model(lags=5, lags_future_covariates=True, multi_models=mode) + with pytest.raises(ValueError): + model(lags=5, lags_future_covariates=(1, -3), multi_models=mode) + with pytest.raises(ValueError): + model(lags=5, lags_future_covariates=(1, 2, 3), multi_models=mode) + with pytest.raises(ValueError): + model(lags=5, lags_future_covariates=(1, True), multi_models=mode) + with pytest.raises(ValueError): + model(lags=5, lags_future_covariates=(1, 1.0), multi_models=mode) + + @pytest.mark.parametrize("mode", [True, False]) + def test_training_data_creation(self, mode): + # testing _get_training_data function + model_instance = RegressionModel( + lags=self.lags_1["target"], + lags_past_covariates=self.lags_1["past"], + lags_future_covariates=self.lags_1["future"], + multi_models=mode, + ) - training_samples, training_labels = model_instance._create_lagged_data( - target_series=self.target_series, - past_covariates=self.past_covariates, - future_covariates=self.future_covariates, - max_samples_per_ts=max_samples_per_ts, - ) + max_samples_per_ts = 17 - # checking number of dimensions - self.assertEqual(len(training_samples.shape), 2) # samples, features - self.assertEqual( - len(training_labels.shape), 2 - ) # samples, components (multivariate) - self.assertEqual(training_samples.shape[0], training_labels.shape[0]) - self.assertEqual( - training_samples.shape[0], len(self.target_series) * max_samples_per_ts - ) - self.assertEqual( - training_samples.shape[1], - len(self.lags_1["target"]) * self.target_series[0].width - + len(self.lags_1["past"]) * self.past_covariates[0].width - + len(self.lags_1["future"]) * self.future_covariates[0].width, - ) + training_samples, training_labels = model_instance._create_lagged_data( + target_series=self.target_series, + past_covariates=self.past_covariates, + future_covariates=self.future_covariates, + max_samples_per_ts=max_samples_per_ts, + ) - # check last sample - self.assertListEqual( - list(training_samples[0, :]), - [ - 79.0, - 179.0, - 279.0, - 80.0, - 180.0, - 280.0, - 81.0, - 181.0, - 281.0, - 10078.0, - 10178.0, - 10080.0, - 10180.0, - 20077.0, - 20084.0, - ], - ) - self.assertListEqual(list(training_labels[0]), [82, 182, 282]) + # checking number of dimensions + assert len(training_samples.shape) == 2 # samples, features + assert len(training_labels.shape) == 2 # samples, components (multivariate) + assert training_samples.shape[0] == training_labels.shape[0] + assert training_samples.shape[0] == len(self.target_series) * max_samples_per_ts + assert ( + training_samples.shape[1] + == len(self.lags_1["target"]) * self.target_series[0].width + + len(self.lags_1["past"]) * self.past_covariates[0].width + + len(self.lags_1["future"]) * self.future_covariates[0].width + ) - def test_prediction_data_creation(self): - multi_models_modes = [True, False] + # check last sample + assert list(training_samples[0, :]) == [ + 79.0, + 179.0, + 279.0, + 80.0, + 180.0, + 280.0, + 81.0, + 181.0, + 281.0, + 10078.0, + 10178.0, + 10080.0, + 10180.0, + 20077.0, + 20084.0, + ] + assert list(training_labels[0]) == [82, 182, 282] + @pytest.mark.parametrize("mode", [True, False]) + def test_prediction_data_creation(self, mode): # assigning correct names to variables series = [ts[:-50] for ts in self.target_series] output_chunk_length = 5 @@ -546,210 +529,189 @@ def test_prediction_data_creation(self): "future": (self.future_covariates, self.lags_1.get("future")), } - for mode in multi_models_modes: - if mode: - shift = 0 - else: - shift = output_chunk_length - 1 - - # dictionary containing covariate data over time span required for prediction - covariate_matrices = {} - # dictionary containing covariate lags relative to minimum covariate lag - relative_cov_lags = {} - # number of prediction steps given forecast horizon and output_chunk_length - n_pred_steps = math.ceil(n / output_chunk_length) - remaining_steps = n % output_chunk_length # for multi_models = False - for cov_type, (covs, lags) in covariates.items(): - if covs is not None: - relative_cov_lags[cov_type] = np.array(lags) - lags[0] - covariate_matrices[cov_type] = [] - for idx, (ts, cov) in enumerate(zip(series, covs)): - first_pred_ts = ts.end_time() + 1 * ts.freq - last_pred_ts = ( - ( - first_pred_ts - + ((n_pred_steps - 1) * output_chunk_length) * ts.freq - ) - if mode - else (first_pred_ts + (n - 1) * ts.freq) + if mode: + shift = 0 + else: + shift = output_chunk_length - 1 + + # dictionary containing covariate data over time span required for prediction + covariate_matrices = {} + # dictionary containing covariate lags relative to minimum covariate lag + relative_cov_lags = {} + # number of prediction steps given forecast horizon and output_chunk_length + n_pred_steps = math.ceil(n / output_chunk_length) + remaining_steps = n % output_chunk_length # for multi_models = False + for cov_type, (covs, lags) in covariates.items(): + if covs is not None: + relative_cov_lags[cov_type] = np.array(lags) - lags[0] + covariate_matrices[cov_type] = [] + for idx, (ts, cov) in enumerate(zip(series, covs)): + first_pred_ts = ts.end_time() + 1 * ts.freq + last_pred_ts = ( + ( + first_pred_ts + + ((n_pred_steps - 1) * output_chunk_length) * ts.freq ) - first_req_ts = first_pred_ts + (lags[0] - shift) * ts.freq - last_req_ts = last_pred_ts + (lags[-1] - shift) * ts.freq - - # not enough covariate data checks excluded, they are tested elsewhere - - if cov.has_datetime_index: - covariate_matrices[cov_type].append( - cov.slice(first_req_ts, last_req_ts).values(copy=False) - ) - else: - # include last_req_ts when slicing series with integer indices - covariate_matrices[cov_type].append( - cov[first_req_ts : last_req_ts + 1].values(copy=False) - ) - - covariate_matrices[cov_type] = np.stack( - covariate_matrices[cov_type] + if mode + else (first_pred_ts + (n - 1) * ts.freq) ) + first_req_ts = first_pred_ts + (lags[0] - shift) * ts.freq + last_req_ts = last_pred_ts + (lags[-1] - shift) * ts.freq + + # not enough covariate data checks excluded, they are tested elsewhere + + if cov.has_datetime_index: + covariate_matrices[cov_type].append( + cov.slice(first_req_ts, last_req_ts).values(copy=False) + ) + else: + # include last_req_ts when slicing series with integer indices + covariate_matrices[cov_type].append( + cov[first_req_ts : last_req_ts + 1].values(copy=False) + ) + + covariate_matrices[cov_type] = np.stack(covariate_matrices[cov_type]) - series_matrix = None - if "target" in self.lags_1: - series_matrix = np.stack( - [ - ts.values(copy=False)[self.lags_1["target"][0] - shift :, :] - for ts in series - ] - ) - # prediction preprocessing end - self.assertTrue( - all([lag >= 0 for lags in relative_cov_lags.values() for lag in lags]) + series_matrix = None + if "target" in self.lags_1: + series_matrix = np.stack( + [ + ts.values(copy=False)[self.lags_1["target"][0] - shift :, :] + for ts in series + ] ) + # prediction preprocessing end + assert all([lag >= 0 for lags in relative_cov_lags.values() for lag in lags]) + + if mode: + # tests for multi_models = True + assert covariate_matrices["past"].shape == ( + len(series), + relative_cov_lags["past"][-1] + + (n_pred_steps - 1) * output_chunk_length + + 1, + covariates["past"][0][0].width, + ) + assert covariate_matrices["future"].shape == ( + len(series), + relative_cov_lags["future"][-1] + + (n_pred_steps - 1) * output_chunk_length + + 1, + covariates["future"][0][0].width, + ) + assert series_matrix.shape == ( + len(series), + -self.lags_1["target"][0], + series[0].width, + ) + assert list(covariate_matrices["past"][0, :, 0]) == [ + 10047.0, + 10048.0, + 10049.0, + 10050.0, + 10051.0, + 10052.0, + 10053.0, + 10054.0, + 10055.0, + 10056.0, + 10057.0, + 10058.0, + 10059.0, + ] + assert list(covariate_matrices["future"][0, :, 0]) == [ + 20046.0, + 20047.0, + 20048.0, + 20049.0, + 20050.0, + 20051.0, + 20052.0, + 20053.0, + 20054.0, + 20055.0, + 20056.0, + 20057.0, + 20058.0, + 20059.0, + 20060.0, + 20061.0, + 20062.0, + 20063.0, + ] + assert list(series_matrix[0, :, 0]) == [48.0, 49.0, 50.0] + else: + # tests for multi_models = False + assert covariate_matrices["past"].shape == ( + len(series), + relative_cov_lags["past"][-1] + + (n_pred_steps - 1) * output_chunk_length + + (remaining_steps - 1) + + 1, + covariates["past"][0][0].width, + ) + assert covariate_matrices["future"].shape == ( + len(series), + relative_cov_lags["future"][-1] + + (n_pred_steps - 1) * output_chunk_length + + (remaining_steps - 1) + + 1, + covariates["future"][0][0].width, + ) + assert series_matrix.shape == ( + len(series), + -self.lags_1["target"][0] + shift, + series[0].width, + ) + assert list(covariate_matrices["past"][0, :, 0]) == [ + 10043.0, + 10044.0, + 10045.0, + 10046.0, + 10047.0, + 10048.0, + 10049.0, + 10050.0, + 10051.0, + 10052.0, + 10053.0, + 10054.0, + 10055.0, + 10056.0, + ] + assert list(covariate_matrices["future"][0, :, 0]) == [ + 20042.0, + 20043.0, + 20044.0, + 20045.0, + 20046.0, + 20047.0, + 20048.0, + 20049.0, + 20050.0, + 20051.0, + 20052.0, + 20053.0, + 20054.0, + 20055.0, + 20056.0, + 20057.0, + 20058.0, + 20059.0, + 20060.0, + ] + assert list(series_matrix[0, :, 0]) == [ + 44.0, + 45.0, + 46.0, + 47.0, + 48.0, + 49.0, + 50.0, + ] - if mode: - # tests for multi_models = True - self.assertEqual( - covariate_matrices["past"].shape, - ( - len(series), - relative_cov_lags["past"][-1] - + (n_pred_steps - 1) * output_chunk_length - + 1, - covariates["past"][0][0].width, - ), - ) - self.assertEqual( - covariate_matrices["future"].shape, - ( - len(series), - relative_cov_lags["future"][-1] - + (n_pred_steps - 1) * output_chunk_length - + 1, - covariates["future"][0][0].width, - ), - ) - self.assertEqual( - series_matrix.shape, - (len(series), -self.lags_1["target"][0], series[0].width), - ) - self.assertListEqual( - list(covariate_matrices["past"][0, :, 0]), - [ - 10047.0, - 10048.0, - 10049.0, - 10050.0, - 10051.0, - 10052.0, - 10053.0, - 10054.0, - 10055.0, - 10056.0, - 10057.0, - 10058.0, - 10059.0, - ], - ) - self.assertListEqual( - list(covariate_matrices["future"][0, :, 0]), - [ - 20046.0, - 20047.0, - 20048.0, - 20049.0, - 20050.0, - 20051.0, - 20052.0, - 20053.0, - 20054.0, - 20055.0, - 20056.0, - 20057.0, - 20058.0, - 20059.0, - 20060.0, - 20061.0, - 20062.0, - 20063.0, - ], - ) - self.assertListEqual(list(series_matrix[0, :, 0]), [48.0, 49.0, 50.0]) - else: - # tests for multi_models = False - self.assertEqual( - covariate_matrices["past"].shape, - ( - len(series), - relative_cov_lags["past"][-1] - + (n_pred_steps - 1) * output_chunk_length - + (remaining_steps - 1) - + 1, - covariates["past"][0][0].width, - ), - ) - self.assertEqual( - covariate_matrices["future"].shape, - ( - len(series), - relative_cov_lags["future"][-1] - + (n_pred_steps - 1) * output_chunk_length - + (remaining_steps - 1) - + 1, - covariates["future"][0][0].width, - ), - ) - self.assertEqual( - series_matrix.shape, - (len(series), -self.lags_1["target"][0] + shift, series[0].width), - ) - self.assertListEqual( - list(covariate_matrices["past"][0, :, 0]), - [ - 10043.0, - 10044.0, - 10045.0, - 10046.0, - 10047.0, - 10048.0, - 10049.0, - 10050.0, - 10051.0, - 10052.0, - 10053.0, - 10054.0, - 10055.0, - 10056.0, - ], - ) - self.assertListEqual( - list(covariate_matrices["future"][0, :, 0]), - [ - 20042.0, - 20043.0, - 20044.0, - 20045.0, - 20046.0, - 20047.0, - 20048.0, - 20049.0, - 20050.0, - 20051.0, - 20052.0, - 20053.0, - 20054.0, - 20055.0, - 20056.0, - 20057.0, - 20058.0, - 20059.0, - 20060.0, - ], - ) - self.assertListEqual( - list(series_matrix[0, :, 0]), - [44.0, 45.0, 46.0, 47.0, 48.0, 49.0, 50.0], - ) - - def test_optional_static_covariates(self): + @pytest.mark.parametrize("model_cls", models) + def test_optional_static_covariates(self, model_cls): """adding static covariates to lagged data logic is tested in `darts.tests.utils.data.tabularization.test_add_static_covariates` """ @@ -758,63 +720,60 @@ def test_optional_static_covariates(self): .with_static_covariates(pd.DataFrame({"a": [1]})) .astype(np.float32) ) - for model_cls in self.models: - # training model with static covs and predicting without will raise an error - model = model_cls(lags=4, use_static_covariates=True) - model.fit(series) - assert model.uses_static_covariates - assert model._static_covariates_shape == series.static_covariates.shape - with pytest.raises(ValueError): - model.predict(n=2, series=series.with_static_covariates(None)) + # training model with static covs and predicting without will raise an error + model = model_cls(lags=4, use_static_covariates=True) + model.fit(series) + assert model.uses_static_covariates + assert model._static_covariates_shape == series.static_covariates.shape + with pytest.raises(ValueError): + model.predict(n=2, series=series.with_static_covariates(None)) + + # with `use_static_covariates=True`, all series must have static covs + model = model_cls(lags=4, use_static_covariates=True) + with pytest.raises(ValueError): + model.fit([series, series.with_static_covariates(None)]) + + # with `use_static_covariates=True`, all static covs must have same shape + model = model_cls(lags=4, use_static_covariates=True) + with pytest.raises(ValueError): + model.fit( + [ + series, + series.with_static_covariates(pd.DataFrame({"a": [1], "b": [2]})), + ] + ) - # with `use_static_covariates=True`, all series must have static covs - model = model_cls(lags=4, use_static_covariates=True) - with pytest.raises(ValueError): - model.fit([series, series.with_static_covariates(None)]) + # with `use_static_covariates=False`, static covariates are ignored and prediction works + model = model_cls(lags=4, use_static_covariates=False) + model.fit(series) + assert not model.uses_static_covariates + assert model._static_covariates_shape is None + preds = model.predict(n=2, series=series.with_static_covariates(None)) + assert preds.static_covariates is None + + # with `use_static_covariates=False`, static covariates are ignored and prediction works + model = model_cls(lags=4, use_static_covariates=False) + model.fit(series.with_static_covariates(None)) + assert not model.uses_static_covariates + assert model._static_covariates_shape is None + preds = model.predict(n=2, series=series) + np.testing.assert_almost_equal( + preds.static_covariates.values, + series.static_covariates.values, + ) - # with `use_static_covariates=True`, all static covs must have same shape - model = model_cls(lags=4, use_static_covariates=True) - with pytest.raises(ValueError): - model.fit( - [ - series, - series.with_static_covariates( - pd.DataFrame({"a": [1], "b": [2]}) - ), - ] - ) - - # with `use_static_covariates=False`, static covariates are ignored and prediction works - model = model_cls(lags=4, use_static_covariates=False) - model.fit(series) - assert not model.uses_static_covariates - assert model._static_covariates_shape is None - preds = model.predict(n=2, series=series.with_static_covariates(None)) - assert preds.static_covariates is None - - # with `use_static_covariates=False`, static covariates are ignored and prediction works - model = model_cls(lags=4, use_static_covariates=False) - model.fit(series.with_static_covariates(None)) - assert not model.uses_static_covariates - assert model._static_covariates_shape is None - preds = model.predict(n=2, series=series) + # with `use_static_covariates=True`, static covariates are included + model = model_cls(lags=4, use_static_covariates=True) + model.fit([series, series]) + assert model.uses_static_covariates + assert model._static_covariates_shape == series.static_covariates.shape + preds = model.predict(n=2, series=[series, series]) + for pred in preds: np.testing.assert_almost_equal( - preds.static_covariates.values, + pred.static_covariates.values, series.static_covariates.values, ) - # with `use_static_covariates=True`, static covariates are included - model = model_cls(lags=4, use_static_covariates=True) - model.fit([series, series]) - assert model.uses_static_covariates - assert model._static_covariates_shape == series.static_covariates.shape - preds = model.predict(n=2, series=[series, series]) - for pred in preds: - np.testing.assert_almost_equal( - pred.static_covariates.values, - series.static_covariates.values, - ) - def test_static_cov_accuracy(self): """ Tests that `RandomForest` regression model reproduces same behaviour as @@ -870,7 +829,7 @@ def test_static_cov_accuracy(self): train_series_static_cov, pred_no_static_cov, pred_static_cov ): rmses = [rmse(series, ps) for ps in [ps_no_st, ps_st_cat]] - self.assertLess(rmses[1], rmses[0]) + assert rmses[1] < rmses[0] # given series of different sizes in input train_series_no_cov = [sine_series[period:], irregular_series] @@ -890,10 +849,9 @@ def test_static_cov_accuracy(self): expected_features_in = [ f"smooth_target_lag{str(-i)}" for i in range(period // 2, 0, -1) ] - self.assertEqual(model_no_static_cov.lagged_feature_names, expected_features_in) - self.assertEqual( - len(model_no_static_cov.model.feature_importances_), - len(expected_features_in), + assert model_no_static_cov.lagged_feature_names == expected_features_in + assert len(model_no_static_cov.model.feature_importances_) == len( + expected_features_in ) fitting_series = [ @@ -908,10 +866,9 @@ def test_static_cov_accuracy(self): f"smooth_target_lag{str(-i)}" for i in range(period // 2, 0, -1) ] + ["curve_type_statcov_target_smooth"] - self.assertEqual(model_static_cov.lagged_feature_names, expected_features_in) - self.assertEqual( - len(model_static_cov.model.feature_importances_), - len(expected_features_in), + assert model_static_cov.lagged_feature_names == expected_features_in + assert len(model_static_cov.model.feature_importances_) == len( + expected_features_in ) pred_static_cov = model_static_cov.predict(n=period, series=fitting_series) @@ -921,285 +878,287 @@ def test_static_cov_accuracy(self): train_series_static_cov, pred_no_static_cov, pred_static_cov ): rmses = [rmse(series, ps) for ps in [ps_no_st, ps_st_cat]] - self.assertLess(rmses[1], rmses[0]) + assert rmses[1] < rmses[0] - def test_models_runnability(self): + @pytest.mark.parametrize("config", itertools.product(models, [True, False])) + def test_models_runnability(self, config): + model, mode = config train_y, test_y = self.sine_univariate1.split_before(0.7) - multi_models_modes = [True, False] - for mode in multi_models_modes: - for model in self.models: - # testing past covariates - with self.assertRaises(ValueError): - # testing lags_past_covariates None but past_covariates during training - model_instance = model( - lags=4, lags_past_covariates=None, multi_models=mode - ) - model_instance.fit( - series=self.sine_univariate1, - past_covariates=self.sine_multivariate1, - ) + # testing past covariates + with pytest.raises(ValueError): + # testing lags_past_covariates None but past_covariates during training + model_instance = model(lags=4, lags_past_covariates=None, multi_models=mode) + model_instance.fit( + series=self.sine_univariate1, + past_covariates=self.sine_multivariate1, + ) - with self.assertRaises(ValueError): - # testing lags_past_covariates but no past_covariates during fit - model_instance = model( - lags=4, lags_past_covariates=3, multi_models=mode - ) - model_instance.fit(series=self.sine_univariate1) + with pytest.raises(ValueError): + # testing lags_past_covariates but no past_covariates during fit + model_instance = model(lags=4, lags_past_covariates=3, multi_models=mode) + model_instance.fit(series=self.sine_univariate1) - # testing future_covariates - with self.assertRaises(ValueError): - # testing lags_future_covariates None but future_covariates during training - model_instance = model( - lags=4, lags_future_covariates=None, multi_models=mode - ) - model_instance.fit( - series=self.sine_univariate1, - future_covariates=self.sine_multivariate1, - ) + # testing future_covariates + with pytest.raises(ValueError): + # testing lags_future_covariates None but future_covariates during training + model_instance = model( + lags=4, lags_future_covariates=None, multi_models=mode + ) + model_instance.fit( + series=self.sine_univariate1, + future_covariates=self.sine_multivariate1, + ) - with self.assertRaises(ValueError): - # testing lags_covariate but no covariate during fit - model_instance = model( - lags=4, lags_future_covariates=3, multi_models=mode - ) - model_instance.fit(series=self.sine_univariate1) + with pytest.raises(ValueError): + # testing lags_covariate but no covariate during fit + model_instance = model(lags=4, lags_future_covariates=3, multi_models=mode) + model_instance.fit(series=self.sine_univariate1) - # testing input_dim - model_instance = model( - lags=4, lags_past_covariates=2, multi_models=mode - ) - model_instance.fit( - series=train_y, - past_covariates=self.sine_univariate1.stack(self.sine_univariate1), - ) + # testing input_dim + model_instance = model(lags=4, lags_past_covariates=2, multi_models=mode) + model_instance.fit( + series=train_y, + past_covariates=self.sine_univariate1.stack(self.sine_univariate1), + ) - self.assertEqual( - model_instance.input_dim, {"target": 1, "past": 2, "future": None} - ) + assert model_instance.input_dim == { + "target": 1, + "past": 2, + "future": None, + } - with self.assertRaises(ValueError): - prediction = model_instance.predict(n=len(test_y) + 2) + with pytest.raises(ValueError): + prediction = model_instance.predict(n=len(test_y) + 2) - # while it should work with n = 1 - prediction = model_instance.predict(n=1) - self.assertEqual(len(prediction), 1) + # while it should work with n = 1 + prediction = model_instance.predict(n=1) + assert len(prediction) == 1 @pytest.mark.slow - def test_fit(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - for model in self.models: - - # test fitting both on univariate and multivariate timeseries - for series in [self.sine_univariate1, self.sine_multivariate2]: - with self.assertRaises(ValueError): - model_instance = model( - lags=4, lags_past_covariates=4, multi_models=mode - ) - model_instance.fit( - series=series, past_covariates=self.sine_multivariate1 - ) - model_instance.predict(n=10) - - model_instance = model(lags=12, multi_models=mode) - model_instance.fit(series=series) - self.assertEqual(model_instance.lags.get("past"), None) - - model_instance = model( - lags=12, lags_past_covariates=12, multi_models=mode - ) - model_instance.fit( - series=series, past_covariates=self.sine_multivariate1 - ) - self.assertEqual(len(model_instance.lags.get("past")), 12) - - model_instance = model( - lags=12, lags_future_covariates=(0, 1), multi_models=mode - ) - model_instance.fit( - series=series, future_covariates=self.sine_multivariate1 - ) - self.assertEqual(len(model_instance.lags.get("future")), 1) - - model_instance = model( - lags=12, lags_past_covariates=[-1, -4, -6], multi_models=mode - ) - model_instance.fit( - series=series, past_covariates=self.sine_multivariate1 - ) - self.assertEqual(len(model_instance.lags.get("past")), 3) - - model_instance = model( - lags=12, - lags_past_covariates=[-1, -4, -6], - lags_future_covariates=[-2, 0], - multi_models=mode, - ) - model_instance.fit( - series=series, - past_covariates=self.sine_multivariate1, - future_covariates=self.sine_multivariate1, - ) - self.assertEqual(len(model_instance.lags.get("past")), 3) + @pytest.mark.parametrize( + "config", + itertools.product( + models, [True, False], [sine_univariate1, sine_multivariate1] + ), + ) + def test_fit(self, config): + # test fitting both on univariate and multivariate timeseries + model, mode, series = config + with pytest.raises(ValueError): + model_instance = model(lags=4, lags_past_covariates=4, multi_models=mode) + model_instance.fit(series=series, past_covariates=self.sine_multivariate1) + model_instance.predict(n=10) + + model_instance = model(lags=12, multi_models=mode) + model_instance.fit(series=series) + assert model_instance.lags.get("past") is None + + model_instance = model(lags=12, lags_past_covariates=12, multi_models=mode) + model_instance.fit(series=series, past_covariates=self.sine_multivariate1) + assert len(model_instance.lags.get("past")) == 12 + + model_instance = model( + lags=12, lags_future_covariates=(0, 1), multi_models=mode + ) + model_instance.fit(series=series, future_covariates=self.sine_multivariate1) + assert len(model_instance.lags.get("future")) == 1 - def helper_test_models_accuracy(self, series, past_covariates, min_rmse_model): + model_instance = model( + lags=12, lags_past_covariates=[-1, -4, -6], multi_models=mode + ) + model_instance.fit(series=series, past_covariates=self.sine_multivariate1) + assert len(model_instance.lags.get("past")) == 3 + + model_instance = model( + lags=12, + lags_past_covariates=[-1, -4, -6], + lags_future_covariates=[-2, 0], + multi_models=mode, + ) + model_instance.fit( + series=series, + past_covariates=self.sine_multivariate1, + future_covariates=self.sine_multivariate1, + ) + assert len(model_instance.lags.get("past")) == 3 + + def helper_test_models_accuracy( + self, + series, + past_covariates, + min_rmse_model, + model, + idx, + mode, + output_chunk_length, + ): # for every model, test whether it predicts the target with a minimum r2 score of `min_rmse` train_series, test_series = train_test_split(series, 70) train_past_covariates, _ = train_test_split(past_covariates, 70) + model_instance = model( + lags=12, + lags_past_covariates=2, + output_chunk_length=output_chunk_length, + multi_models=mode, + ) + model_instance.fit(series=train_series, past_covariates=train_past_covariates) + prediction = model_instance.predict( + n=len(test_series), + series=train_series, + past_covariates=past_covariates, + ) + current_rmse = rmse(prediction, test_series) + # in case of multi-series take mean rmse + mean_rmse = np.mean(current_rmse) + assert mean_rmse <= min_rmse_model[idx], ( + f"{str(model_instance)} model was not able to predict data as well as expected. " + f"A mean rmse score of {mean_rmse} was recorded." + ) - multi_models_modes = [True, False] - for mode in multi_models_modes: - for output_chunk_length in [1, 5]: - for idx, model in enumerate(self.models): - model_instance = model( - lags=12, - lags_past_covariates=2, - output_chunk_length=output_chunk_length, - multi_models=mode, - ) - model_instance.fit( - series=train_series, past_covariates=train_past_covariates - ) - prediction = model_instance.predict( - n=len(test_series), - series=train_series, - past_covariates=past_covariates, - ) - current_rmse = rmse(prediction, test_series) - # in case of multi-series take mean rmse - mean_rmse = np.mean(current_rmse) - self.assertTrue( - mean_rmse <= min_rmse_model[idx], - f"{str(model_instance)} model was not able to predict data as well as expected. " - f"A mean rmse score of {mean_rmse} was recorded.", - ) - - def test_models_accuracy_univariate(self): + @pytest.mark.parametrize( + "config", + itertools.product(zip(models, range(len(models))), [True, False], [1, 5]), + ) + def test_models_accuracy_univariate(self, config): + (model, idx), mode, ocl = config # for every model, and different output_chunk_lengths test whether it predicts the univariate time series # as well as expected, accuracies are defined at the top of the class self.helper_test_models_accuracy( self.sine_univariate1, self.sine_univariate2, self.univariate_accuracies, + model, + idx, + mode, + ocl, ) - def test_models_accuracy_multivariate(self): + @pytest.mark.parametrize( + "config", + itertools.product(zip(models, range(len(models))), [True, False], [1, 5]), + ) + def test_models_accuracy_multivariate(self, config): + (model, idx), mode, ocl = config # for every model, and different output_chunk_lengths test whether it predicts the multivariate time series # as well as expected, accuracies are defined at the top of the class self.helper_test_models_accuracy( self.sine_multivariate1, self.sine_multivariate2, self.multivariate_accuracies, + model, + idx, + mode, + ocl, ) - def test_models_accuracy_multiseries_multivariate(self): + @pytest.mark.parametrize( + "config", + itertools.product(zip(models, range(len(models))), [True, False], [1, 5]), + ) + def test_models_accuracy_multiseries_multivariate(self, config): + (model, idx), mode, ocl = config # for every model, and different output_chunk_lengths test whether it predicts the multiseries, multivariate # time series as well as expected, accuracies are defined at the top of the class self.helper_test_models_accuracy( self.sine_multiseries1, self.sine_multiseries2, self.multivariate_multiseries_accuracies, + model, + idx, + mode, + ocl, ) - def test_min_train_series_length(self): - mutli_models_modes = [True, False] + @pytest.mark.parametrize("mode", [True, False]) + def test_min_train_series_length(self, mode): lgbm_cls = LightGBMModel if lgbm_available else XGBModel cb_cls = CatBoostModel if cb_available else XGBModel - for mode in mutli_models_modes: - model = lgbm_cls(lags=4, multi_models=mode) - min_train_series_length_expected = ( - -model.lags["target"][0] + model.output_chunk_length + 1 - ) - self.assertEqual( - min_train_series_length_expected, model.min_train_series_length - ) - model = cb_cls(lags=2, multi_models=mode) - min_train_series_length_expected = ( - -model.lags["target"][0] + model.output_chunk_length + 1 - ) - self.assertEqual( - min_train_series_length_expected, model.min_train_series_length - ) - model = lgbm_cls(lags=[-4, -3, -2], multi_models=mode) - min_train_series_length_expected = ( - -model.lags["target"][0] + model.output_chunk_length + 1 - ) - self.assertEqual( - min_train_series_length_expected, model.min_train_series_length - ) - model = XGBModel(lags=[-4, -3, -2], multi_models=mode) - min_train_series_length_expected = ( - -model.lags["target"][0] + model.output_chunk_length + 1 - ) - self.assertEqual( - min_train_series_length_expected, model.min_train_series_length - ) + model = lgbm_cls(lags=4, multi_models=mode) + min_train_series_length_expected = ( + -model.lags["target"][0] + model.output_chunk_length + 1 + ) + assert min_train_series_length_expected == model.min_train_series_length + model = cb_cls(lags=2, multi_models=mode) + min_train_series_length_expected = ( + -model.lags["target"][0] + model.output_chunk_length + 1 + ) + assert min_train_series_length_expected == model.min_train_series_length + model = lgbm_cls(lags=[-4, -3, -2], multi_models=mode) + min_train_series_length_expected = ( + -model.lags["target"][0] + model.output_chunk_length + 1 + ) + assert min_train_series_length_expected == model.min_train_series_length + model = XGBModel(lags=[-4, -3, -2], multi_models=mode) + min_train_series_length_expected = ( + -model.lags["target"][0] + model.output_chunk_length + 1 + ) + assert min_train_series_length_expected == model.min_train_series_length - def test_historical_forecast(self): - mutli_models_modes = [True, False] - for mode in mutli_models_modes: - model = self.models[1](lags=5, multi_models=mode) - result = model.historical_forecasts( - series=self.sine_univariate1, - future_covariates=None, - start=0.8, - forecast_horizon=1, - stride=1, - retrain=True, - overlap_end=False, - last_points_only=True, - verbose=False, - ) - self.assertEqual(len(result), 21) + @pytest.mark.parametrize("mode", [True, False]) + def test_historical_forecast(self, mode): + model = self.models[1](lags=5, multi_models=mode) + result = model.historical_forecasts( + series=self.sine_univariate1, + future_covariates=None, + start=0.8, + forecast_horizon=1, + stride=1, + retrain=True, + overlap_end=False, + last_points_only=True, + verbose=False, + ) + assert len(result) == 21 - model = self.models[1](lags=5, lags_past_covariates=5, multi_models=mode) - result = model.historical_forecasts( - series=self.sine_univariate1, - past_covariates=self.sine_multivariate1, - start=0.8, - forecast_horizon=1, - stride=1, - retrain=True, - overlap_end=False, - last_points_only=True, - verbose=False, - ) - self.assertEqual(len(result), 21) + model = self.models[1](lags=5, lags_past_covariates=5, multi_models=mode) + result = model.historical_forecasts( + series=self.sine_univariate1, + past_covariates=self.sine_multivariate1, + start=0.8, + forecast_horizon=1, + stride=1, + retrain=True, + overlap_end=False, + last_points_only=True, + verbose=False, + ) + assert len(result) == 21 - model = self.models[1]( - lags=5, lags_past_covariates=5, output_chunk_length=5, multi_models=mode - ) - result = model.historical_forecasts( - series=self.sine_univariate1, - past_covariates=self.sine_multivariate1, - start=0.8, - forecast_horizon=1, - stride=1, - retrain=True, - overlap_end=False, - last_points_only=True, - verbose=False, - ) - self.assertEqual(len(result), 21) + model = self.models[1]( + lags=5, lags_past_covariates=5, output_chunk_length=5, multi_models=mode + ) + result = model.historical_forecasts( + series=self.sine_univariate1, + past_covariates=self.sine_multivariate1, + start=0.8, + forecast_horizon=1, + stride=1, + retrain=True, + overlap_end=False, + last_points_only=True, + verbose=False, + ) + assert len(result) == 21 - def test_multioutput_wrapper(self): - lags = 4 - models = [ - (RegressionModel(lags=lags), True), - (RegressionModel(lags=lags, model=LinearRegression()), True), - (RegressionModel(lags=lags, model=RandomForestRegressor()), True), + @pytest.mark.parametrize( + "config", + [ + (RegressionModel(lags=4), True), + (RegressionModel(lags=4, model=LinearRegression()), True), + (RegressionModel(lags=4, model=RandomForestRegressor()), True), ( - RegressionModel(lags=lags, model=HistGradientBoostingRegressor()), + RegressionModel(lags=4, model=HistGradientBoostingRegressor()), False, ), - ] - - for model, supports_multioutput_natively in models: - model.fit(series=self.sine_multivariate1) - if supports_multioutput_natively: - self.assertFalse(isinstance(model.model, MultiOutputRegressor)) - else: - self.assertIsInstance(model.model, MultiOutputRegressor) + ], + ) + def test_multioutput_wrapper(self, config): + model, supports_multioutput_natively = config + model.fit(series=self.sine_multivariate1) + if supports_multioutput_natively: + assert not isinstance(model.model, MultiOutputRegressor) + else: + assert isinstance(model.model, MultiOutputRegressor) def test_multioutput_validation(self): @@ -1230,226 +1189,191 @@ def test_multioutput_validation(self): for model in models: model.fit(series=train, val_series=val) if model.output_chunk_length > 1 and model.multi_models: - self.assertIsInstance(model.model, MultiOutputRegressor) - - def test_regression_model(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - lags = 4 - models = [ - RegressionModel(lags=lags, multi_models=mode), - RegressionModel(lags=lags, model=LinearRegression(), multi_models=mode), - RegressionModel( - lags=lags, model=RandomForestRegressor(), multi_models=mode - ), - RegressionModel( - lags=lags, model=HistGradientBoostingRegressor(), multi_models=mode - ), - ] + assert isinstance(model.model, MultiOutputRegressor) - for model in models: - model.fit(series=self.sine_univariate1) - self.assertEqual(len(model.lags.get("target")), lags) - model.predict(n=10) - - def test_multiple_ts(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - lags = 4 - lags_past_covariates = 3 - model = RegressionModel( - lags=lags, lags_past_covariates=lags_past_covariates, multi_models=mode - ) + @pytest.mark.parametrize("mode", [True, False]) + def test_regression_model(self, mode): + lags = 4 + models = [ + RegressionModel(lags=lags, multi_models=mode), + RegressionModel(lags=lags, model=LinearRegression(), multi_models=mode), + RegressionModel( + lags=lags, model=RandomForestRegressor(), multi_models=mode + ), + RegressionModel( + lags=lags, model=HistGradientBoostingRegressor(), multi_models=mode + ), + ] - target_series = tg.linear_timeseries(start_value=0, end_value=49, length=50) - past_covariates = tg.linear_timeseries( - start_value=100, end_value=149, length=50 - ) - past_covariates = past_covariates.stack( - tg.linear_timeseries(start_value=400, end_value=449, length=50) - ) + for model in models: + model.fit(series=self.sine_univariate1) + assert len(model.lags.get("target")) == lags + model.predict(n=10) - target_train, target_test = target_series.split_after(0.7) - past_covariates_train, past_covariates_test = past_covariates.split_after( - 0.7 - ) - model.fit( - series=[target_train, target_train + 0.5], - past_covariates=[past_covariates_train, past_covariates_train + 0.5], - ) + @pytest.mark.parametrize("mode", [True, False]) + def test_multiple_ts(self, mode): + lags = 4 + lags_past_covariates = 3 + model = RegressionModel( + lags=lags, lags_past_covariates=lags_past_covariates, multi_models=mode + ) - predictions = model.predict( - 10, - series=[target_train, target_train + 0.5], - past_covariates=[past_covariates, past_covariates + 0.5], - ) + target_series = tg.linear_timeseries(start_value=0, end_value=49, length=50) + past_covariates = tg.linear_timeseries( + start_value=100, end_value=149, length=50 + ) + past_covariates = past_covariates.stack( + tg.linear_timeseries(start_value=400, end_value=449, length=50) + ) - self.assertEqual( - len(predictions[0]), 10, f"Found {len(predictions)} instead" - ) + target_train, target_test = target_series.split_after(0.7) + past_covariates_train, past_covariates_test = past_covariates.split_after(0.7) + model.fit( + series=[target_train, target_train + 0.5], + past_covariates=[past_covariates_train, past_covariates_train + 0.5], + ) - # multiple TS, both future and past covariates, checking that both covariates lead to better results than - # using a single one (target series = past_cov + future_cov + noise) - np.random.seed(42) + predictions = model.predict( + 10, + series=[target_train, target_train + 0.5], + past_covariates=[past_covariates, past_covariates + 0.5], + ) - linear_ts_1 = tg.linear_timeseries(start_value=10, end_value=59, length=50) - linear_ts_2 = tg.linear_timeseries(start_value=40, end_value=89, length=50) + assert len(predictions[0]) == 10, f"Found {len(predictions)} instead" - past_covariates = tg.sine_timeseries(length=50) * 10 - future_covariates = ( - tg.sine_timeseries(length=50, value_frequency=0.015) * 50 - ) + # multiple TS, both future and past covariates, checking that both covariates lead to better results than + # using a single one (target series = past_cov + future_cov + noise) + np.random.seed(42) - target_series_1 = linear_ts_1 + 4 * past_covariates + 2 * future_covariates - target_series_2 = linear_ts_2 + 4 * past_covariates + 2 * future_covariates + linear_ts_1 = tg.linear_timeseries(start_value=10, end_value=59, length=50) + linear_ts_2 = tg.linear_timeseries(start_value=40, end_value=89, length=50) - target_series_1_noise = ( - linear_ts_1 - + 4 * past_covariates - + 2 * future_covariates - + tg.gaussian_timeseries(std=7, length=50) - ) + past_covariates = tg.sine_timeseries(length=50) * 10 + future_covariates = tg.sine_timeseries(length=50, value_frequency=0.015) * 50 - target_series_2_noise = ( - linear_ts_2 - + 4 * past_covariates - + 2 * future_covariates - + tg.gaussian_timeseries(std=7, length=50) - ) + target_series_1 = linear_ts_1 + 4 * past_covariates + 2 * future_covariates + target_series_2 = linear_ts_2 + 4 * past_covariates + 2 * future_covariates - target_train_1, target_test_1 = target_series_1.split_after(0.7) - target_train_2, target_test_2 = target_series_2.split_after(0.7) + target_series_1_noise = ( + linear_ts_1 + + 4 * past_covariates + + 2 * future_covariates + + tg.gaussian_timeseries(std=7, length=50) + ) - ( - target_train_1_noise, - target_test_1_noise, - ) = target_series_1_noise.split_after(0.7) - ( - target_train_2_noise, - target_test_2_noise, - ) = target_series_2_noise.split_after(0.7) + target_series_2_noise = ( + linear_ts_2 + + 4 * past_covariates + + 2 * future_covariates + + tg.gaussian_timeseries(std=7, length=50) + ) - # testing improved denoise with multiple TS + target_train_1, target_test_1 = target_series_1.split_after(0.7) + target_train_2, target_test_2 = target_series_2.split_after(0.7) - # test 1: with single TS, 2 covariates should be better than one - model = RegressionModel(lags=3, lags_past_covariates=5, multi_models=mode) - model.fit([target_train_1_noise], [past_covariates]) + ( + target_train_1_noise, + target_test_1_noise, + ) = target_series_1_noise.split_after(0.7) + ( + target_train_2_noise, + target_test_2_noise, + ) = target_series_2_noise.split_after(0.7) - prediction_past_only = model.predict( - n=len(target_test_1), - series=[target_train_1_noise, target_train_2_noise], - past_covariates=[past_covariates] * 2, - ) + # testing improved denoise with multiple TS - model = RegressionModel( - lags=3, - lags_past_covariates=5, - lags_future_covariates=(5, 0), - multi_models=mode, - ) - model.fit([target_train_1_noise], [past_covariates], [future_covariates]) - prediction_past_and_future = model.predict( - n=len(target_test_1), - series=[target_train_1_noise, target_train_2_noise], - past_covariates=[past_covariates] * 2, - future_covariates=[future_covariates] * 2, - ) + # test 1: with single TS, 2 covariates should be better than one + model = RegressionModel(lags=3, lags_past_covariates=5, multi_models=mode) + model.fit([target_train_1_noise], [past_covariates]) - error_past_only = rmse( - [target_test_1, target_test_2], - prediction_past_only, - inter_reduction=np.mean, - ) - error_both = rmse( - [target_test_1, target_test_2], - prediction_past_and_future, - inter_reduction=np.mean, - ) + prediction_past_only = model.predict( + n=len(target_test_1), + series=[target_train_1_noise, target_train_2_noise], + past_covariates=[past_covariates] * 2, + ) - self.assertGreater(error_past_only, error_both) - # test 2: with both covariates, 2 TS should learn more than one (with little noise) - model = RegressionModel( - lags=3, - lags_past_covariates=5, - lags_future_covariates=(5, 0), - multi_models=mode, - ) - model.fit( - [target_train_1_noise, target_train_2_noise], - [past_covariates] * 2, - [future_covariates] * 2, - ) - prediction_past_and_future_multi_ts = model.predict( - n=len(target_test_1), - series=[target_train_1_noise, target_train_2_noise], - past_covariates=[past_covariates] * 2, - future_covariates=[future_covariates] * 2, - ) - error_both_multi_ts = rmse( - [target_test_1, target_test_2], - prediction_past_and_future_multi_ts, - inter_reduction=np.mean, - ) + model = RegressionModel( + lags=3, + lags_past_covariates=5, + lags_future_covariates=(5, 0), + multi_models=mode, + ) + model.fit([target_train_1_noise], [past_covariates], [future_covariates]) + prediction_past_and_future = model.predict( + n=len(target_test_1), + series=[target_train_1_noise, target_train_2_noise], + past_covariates=[past_covariates] * 2, + future_covariates=[future_covariates] * 2, + ) - self.assertGreater(error_both, error_both_multi_ts) + error_past_only = rmse( + [target_test_1, target_test_2], + prediction_past_only, + inter_reduction=np.mean, + ) + error_both = rmse( + [target_test_1, target_test_2], + prediction_past_and_future, + inter_reduction=np.mean, + ) - def test_only_future_covariates(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - model = RegressionModel(lags_future_covariates=[-2], multi_models=mode) + assert error_past_only > error_both + # test 2: with both covariates, 2 TS should learn more than one (with little noise) + model = RegressionModel( + lags=3, + lags_past_covariates=5, + lags_future_covariates=(5, 0), + multi_models=mode, + ) + model.fit( + [target_train_1_noise, target_train_2_noise], + [past_covariates] * 2, + [future_covariates] * 2, + ) + prediction_past_and_future_multi_ts = model.predict( + n=len(target_test_1), + series=[target_train_1_noise, target_train_2_noise], + past_covariates=[past_covariates] * 2, + future_covariates=[future_covariates] * 2, + ) + error_both_multi_ts = rmse( + [target_test_1, target_test_2], + prediction_past_and_future_multi_ts, + inter_reduction=np.mean, + ) - target_series = tg.linear_timeseries(start_value=0, end_value=49, length=50) - covariates = tg.linear_timeseries(start_value=100, end_value=149, length=50) - covariates = covariates.stack( - tg.linear_timeseries(start_value=400, end_value=449, length=50) - ) + assert error_both > error_both_multi_ts - target_train, target_test = target_series.split_after(0.7) - covariates_train, covariates_test = covariates.split_after(0.7) - model.fit( - series=[target_train, target_train + 0.5], - future_covariates=[covariates_train, covariates_train + 0.5], - ) + @pytest.mark.parametrize("mode", [True, False]) + def test_only_future_covariates(self, mode): + model = RegressionModel(lags_future_covariates=[-2], multi_models=mode) - predictions = model.predict( - 10, - series=[target_train, target_train + 0.5], - future_covariates=[covariates, covariates + 0.5], - ) + target_series = tg.linear_timeseries(start_value=0, end_value=49, length=50) + covariates = tg.linear_timeseries(start_value=100, end_value=149, length=50) + covariates = covariates.stack( + tg.linear_timeseries(start_value=400, end_value=449, length=50) + ) - self.assertEqual( - len(predictions[0]), 10, f"Found {len(predictions[0])} instead" - ) + target_train, target_test = target_series.split_after(0.7) + covariates_train, covariates_test = covariates.split_after(0.7) + model.fit( + series=[target_train, target_train + 0.5], + future_covariates=[covariates_train, covariates_train + 0.5], + ) - def test_not_enough_covariates(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - target_series = tg.linear_timeseries( - start_value=0, end_value=100, length=50 - ) - past_covariates = tg.linear_timeseries( - start_value=100, end_value=200, length=50 - ) - future_covariates = tg.linear_timeseries( - start_value=200, end_value=300, length=50 - ) + predictions = model.predict( + 10, + series=[target_train, target_train + 0.5], + future_covariates=[covariates, covariates + 0.5], + ) - model = RegressionModel( - lags_past_covariates=[-10], - lags_future_covariates=[-5, 5], - output_chunk_length=7, - multi_models=mode, - ) - model.fit( - series=target_series, - past_covariates=past_covariates, - future_covariates=future_covariates, - max_samples_per_ts=1, - ) + assert len(predictions[0]) == 10, f"Found {len(predictions[0])} instead" - n = 10 - # output_chunk_length, required past_offset, required future_offset - test_cases = [ + @pytest.mark.parametrize( + "config", + itertools.product( + [True, False], + [ (1, 0, 13), (5, -4, 9), (7, -6, 7), @@ -1458,46 +1382,72 @@ def test_not_enough_covariates(self): -9, 4, ), # output_chunk_length > n -> covariate requirements are capped - ] + ], + ), + ) + def test_not_enough_covariates(self, config): + # mode, output_chunk_length, required past_offset, required future_offset + mode, (output_chunk_length, req_past_offset, req_future_offset) = config + target_series = tg.linear_timeseries(start_value=0, end_value=100, length=50) + past_covariates = tg.linear_timeseries( + start_value=100, end_value=200, length=50 + ) + future_covariates = tg.linear_timeseries( + start_value=200, end_value=300, length=50 + ) - for (output_chunk_length, req_past_offset, req_future_offset) in test_cases: - model = RegressionModel( - lags_past_covariates=[-10], - lags_future_covariates=[-4, 3], - output_chunk_length=output_chunk_length, - multi_models=mode, - ) - model.fit( - series=target_series, - past_covariates=past_covariates, - future_covariates=future_covariates, - ) - - # check that given the required offsets no ValueError is raised - model.predict( - n, - series=target_series[:-25], - past_covariates=past_covariates[: -25 + req_past_offset], - future_covariates=future_covariates[: -25 + req_future_offset], - ) - # check that one less past covariate time step causes ValueError - with self.assertRaises(ValueError): - model.predict( - n, - series=target_series[:-25], - past_covariates=past_covariates[: -26 + req_past_offset], - future_covariates=future_covariates[: -25 + req_future_offset], - ) - # check that one less future covariate time step causes ValueError - with self.assertRaises(ValueError): - model.predict( - n, - series=target_series[:-25], - past_covariates=past_covariates[: -25 + req_past_offset], - future_covariates=future_covariates[: -26 + req_future_offset], - ) + model = RegressionModel( + lags_past_covariates=[-10], + lags_future_covariates=[-5, 5], + output_chunk_length=7, + multi_models=mode, + ) + model.fit( + series=target_series, + past_covariates=past_covariates, + future_covariates=future_covariates, + max_samples_per_ts=1, + ) + + n = 10 + + model = RegressionModel( + lags_past_covariates=[-10], + lags_future_covariates=[-4, 3], + output_chunk_length=output_chunk_length, + multi_models=mode, + ) + model.fit( + series=target_series, + past_covariates=past_covariates, + future_covariates=future_covariates, + ) - @unittest.skipUnless(lgbm_available, "requires lightgbm") + # check that given the required offsets no ValueError is raised + model.predict( + n, + series=target_series[:-25], + past_covariates=past_covariates[: -25 + req_past_offset], + future_covariates=future_covariates[: -25 + req_future_offset], + ) + # check that one less past covariate time step causes ValueError + with pytest.raises(ValueError): + model.predict( + n, + series=target_series[:-25], + past_covariates=past_covariates[: -26 + req_past_offset], + future_covariates=future_covariates[: -25 + req_future_offset], + ) + # check that one less future covariate time step causes ValueError + with pytest.raises(ValueError): + model.predict( + n, + series=target_series[:-25], + past_covariates=past_covariates[: -25 + req_past_offset], + future_covariates=future_covariates[: -26 + req_future_offset], + ) + + @pytest.mark.skipif(not lgbm_available, reason="requires lightgbm") @patch.object( darts.models.forecasting.lgbm.lgb.LGBMRegressor if lgbm_available @@ -1534,7 +1484,8 @@ def test_xgboost_with_eval_set(self, xgb_fit_patch): assert xgb_fit_patch.call_args[1]["eval_set"] is not None assert xgb_fit_patch.call_args[1]["early_stopping_rounds"] == 2 - def test_integer_indexed_series(self): + @pytest.mark.parametrize("mode", [True, False]) + def test_integer_indexed_series(self, mode): values_target = np.random.rand(30) values_past_cov = np.random.rand(30) values_future_cov = np.random.rand(30) @@ -1542,38 +1493,43 @@ def test_integer_indexed_series(self): idx1 = pd.RangeIndex(start=0, stop=30, step=1) idx2 = pd.RangeIndex(start=10, stop=70, step=2) - multi_models_mode = [True, False] - for mode in multi_models_mode: - preds = [] - - for idx in [idx1, idx2]: - target = TimeSeries.from_times_and_values(idx, values_target) - past_cov = TimeSeries.from_times_and_values(idx, values_past_cov) - future_cov = TimeSeries.from_times_and_values(idx, values_future_cov) + preds = [] + for idx in [idx1, idx2]: + target = TimeSeries.from_times_and_values(idx, values_target) + past_cov = TimeSeries.from_times_and_values(idx, values_past_cov) + future_cov = TimeSeries.from_times_and_values(idx, values_future_cov) - train, _ = target[:20], target[20:] + train, _ = target[:20], target[20:] - model = LinearRegressionModel( - lags=[-2, -1], - lags_past_covariates=[-2, -1], - lags_future_covariates=[0], - multi_models=mode, - ) - model.fit( - series=train, past_covariates=past_cov, future_covariates=future_cov - ) + model = LinearRegressionModel( + lags=[-2, -1], + lags_past_covariates=[-2, -1], + lags_future_covariates=[0], + multi_models=mode, + ) + model.fit( + series=train, past_covariates=past_cov, future_covariates=future_cov + ) - preds.append(model.predict(n=10)) + preds.append(model.predict(n=10)) - # the predicted values should not depend on the time axis - np.testing.assert_equal(preds[0].values(), preds[1].values()) + # the predicted values should not depend on the time axis + np.testing.assert_equal(preds[0].values(), preds[1].values()) - # the time axis returned by the second model should be as expected - self.assertTrue( - all(preds[1].time_index == pd.RangeIndex(start=50, stop=70, step=2)) - ) + # the time axis returned by the second model should be as expected + assert all(preds[1].time_index == pd.RangeIndex(start=50, stop=70, step=2)) - def test_encoders(self): + @pytest.mark.parametrize( + "config", + itertools.product( + [RegressionModel, LinearRegressionModel, XGBModel] + + ([LightGBMModel] if lgbm_available else []), + [True, False], + [1, 2], + ), + ) + def test_encoders(self, config): + model_cls, mode, ocl = config max_past_lag = -4 max_future_lag = 4 # target @@ -1611,190 +1567,167 @@ def test_encoders(self): "cyclic": {"future": ["hour"]}, }, } - - multi_models_mode = [True, False] - models_cls = [RegressionModel, LinearRegressionModel, XGBModel] - if lgbm_available: - models_cls.append(LightGBMModel) - for mode in multi_models_mode: - for ocl in [1, 2]: - for model_cls in models_cls: - model_pc_valid0 = model_cls( - lags=2, - add_encoders=encoder_examples["past"], - multi_models=mode, - output_chunk_length=ocl, - ) - model_fc_valid0 = model_cls( - lags=2, - add_encoders=encoder_examples["future"], - multi_models=mode, - output_chunk_length=ocl, - ) - model_mixed_valid0 = model_cls( - lags=2, - add_encoders=encoder_examples["mixed"], - multi_models=mode, - output_chunk_length=ocl, - ) - - # encoders will not generate covariates without lags - for model in [model_pc_valid0, model_fc_valid0, model_mixed_valid0]: - model.fit(ts) - assert not model.encoders.encoding_available - _ = model.predict(n=1, series=ts) - _ = model.predict(n=3, series=ts) - - model_pc_valid0 = model_cls( - lags_past_covariates=[-2], - add_encoders=encoder_examples["past"], - multi_models=mode, - output_chunk_length=ocl, - ) - model_fc_valid0 = model_cls( - lags_future_covariates=[-1, 0], - add_encoders=encoder_examples["future"], - multi_models=mode, - output_chunk_length=ocl, - ) - model_mixed_valid0 = model_cls( - lags_past_covariates=[-2, -1], - lags_future_covariates=[-3, 3], - add_encoders=encoder_examples["mixed"], - multi_models=mode, - output_chunk_length=ocl, - ) - # check that fit/predict works with model internal covariate requirement checks - for model in [model_pc_valid0, model_fc_valid0, model_mixed_valid0]: - model.fit(ts) - assert model.encoders.encoding_available - _ = model.predict(n=1, series=ts) - _ = model.predict(n=3, series=ts) - - model_pc_valid1 = model_cls( - lags=2, - lags_past_covariates=[max_past_lag, -1], - add_encoders=encoder_examples["past"], - multi_models=mode, - output_chunk_length=ocl, - ) - model_fc_valid1 = model_cls( - lags=2, - lags_future_covariates=[0, max_future_lag], - add_encoders=encoder_examples["future"], - multi_models=mode, - output_chunk_length=ocl, - ) - model_mixed_valid1 = model_cls( - lags=2, - lags_past_covariates=[max_past_lag, -1], - lags_future_covariates=[0, max_future_lag], - add_encoders=encoder_examples["mixed"], - multi_models=mode, - output_chunk_length=ocl, - ) - - for model, ex in zip( - [model_pc_valid1, model_fc_valid1, model_mixed_valid1], examples - ): - covariates = covariates_examples[ex] - # don't pass covariates, let them be generated by encoders. Test single target series input - model_copy = copy.deepcopy(model) - model_copy.fit(ts[0]) - assert model_copy.encoders.encoding_available - self.helper_test_encoders_settings(model_copy, ex) - _ = model_copy.predict(n=1, series=ts) - self.helper_compare_encoded_covs_with_ref( - model_copy, ts, covariates, n=1, ocl=ocl, multi_model=mode - ) - - _ = model_copy.predict(n=3, series=ts) - self.helper_compare_encoded_covs_with_ref( - model_copy, ts, covariates, n=3, ocl=ocl, multi_model=mode - ) - - _ = model_copy.predict(n=8, series=ts) - self.helper_compare_encoded_covs_with_ref( - model_copy, ts, covariates, n=8, ocl=ocl, multi_model=mode - ) - - # manually pass covariates, let encoders add more - model.fit(ts, **covariates) - assert model.encoders.encoding_available - self.helper_test_encoders_settings(model, ex) - _ = model.predict(n=1, series=ts, **covariates) - _ = model.predict(n=3, series=ts, **covariates) - _ = model.predict(n=8, series=ts, **covariates) - - def test_encoders_from_covariates_input(self): - from darts.datasets import AirPassengersDataset - from darts.models import LinearRegressionModel - - model = LinearRegressionModel( - lags=3, - lags_past_covariates=[-3, -2], - add_encoders={ - "cyclic": {"past": ["month"]}, - }, + model_pc_valid0 = model_cls( + lags=2, + add_encoders=encoder_examples["past"], + multi_models=mode, + output_chunk_length=ocl, + ) + model_fc_valid0 = model_cls( + lags=2, + add_encoders=encoder_examples["future"], + multi_models=mode, + output_chunk_length=ocl, + ) + model_mixed_valid0 = model_cls( + lags=2, + add_encoders=encoder_examples["mixed"], + multi_models=mode, + output_chunk_length=ocl, ) - series = AirPassengersDataset().load() - # hc = model.historical_forecasts(series=series) + # encoders will not generate covariates without lags + for model in [model_pc_valid0, model_fc_valid0, model_mixed_valid0]: + model.fit(ts) + assert not model.encoders.encoding_available + _ = model.predict(n=1, series=ts) + _ = model.predict(n=3, series=ts) + + model_pc_valid0 = model_cls( + lags_past_covariates=[-2], + add_encoders=encoder_examples["past"], + multi_models=mode, + output_chunk_length=ocl, + ) + model_fc_valid0 = model_cls( + lags_future_covariates=[-1, 0], + add_encoders=encoder_examples["future"], + multi_models=mode, + output_chunk_length=ocl, + ) + model_mixed_valid0 = model_cls( + lags_past_covariates=[-2, -1], + lags_future_covariates=[-3, 3], + add_encoders=encoder_examples["mixed"], + multi_models=mode, + output_chunk_length=ocl, + ) + # check that fit/predict works with model internal covariate requirement checks + for model in [model_pc_valid0, model_fc_valid0, model_mixed_valid0]: + model.fit(ts) + assert model.encoders.encoding_available + _ = model.predict(n=1, series=ts) + _ = model.predict(n=3, series=ts) + + model_pc_valid1 = model_cls( + lags=2, + lags_past_covariates=[max_past_lag, -1], + add_encoders=encoder_examples["past"], + multi_models=mode, + output_chunk_length=ocl, + ) + model_fc_valid1 = model_cls( + lags=2, + lags_future_covariates=[0, max_future_lag], + add_encoders=encoder_examples["future"], + multi_models=mode, + output_chunk_length=ocl, + ) + model_mixed_valid1 = model_cls( + lags=2, + lags_past_covariates=[max_past_lag, -1], + lags_future_covariates=[0, max_future_lag], + add_encoders=encoder_examples["mixed"], + multi_models=mode, + output_chunk_length=ocl, + ) + for model, ex in zip( + [model_pc_valid1, model_fc_valid1, model_mixed_valid1], examples + ): + covariates = covariates_examples[ex] + # don't pass covariates, let them be generated by encoders. Test single target series input + model_copy = copy.deepcopy(model) + model_copy.fit(ts[0]) + assert model_copy.encoders.encoding_available + self.helper_test_encoders_settings(model_copy, ex) + _ = model_copy.predict(n=1, series=ts) + self.helper_compare_encoded_covs_with_ref( + model_copy, ts, covariates, n=1, ocl=ocl, multi_model=mode + ) + + _ = model_copy.predict(n=3, series=ts) + self.helper_compare_encoded_covs_with_ref( + model_copy, ts, covariates, n=3, ocl=ocl, multi_model=mode + ) + + _ = model_copy.predict(n=8, series=ts) + self.helper_compare_encoded_covs_with_ref( + model_copy, ts, covariates, n=8, ocl=ocl, multi_model=mode + ) + + # manually pass covariates, let encoders add more + model.fit(ts, **covariates) + assert model.encoders.encoding_available + self.helper_test_encoders_settings(model, ex) + _ = model.predict(n=1, series=ts, **covariates) + _ = model.predict(n=3, series=ts, **covariates) + _ = model.predict(n=8, series=ts, **covariates) + + @pytest.mark.parametrize("config", itertools.product([True, False], [True, False])) + def test_encoders_from_covariates_input(self, config): + multi_models, extreme_lags = config series = tg.linear_timeseries(length=10, freq="MS") pc = tg.linear_timeseries(length=12, freq="MS") fc = tg.linear_timeseries(length=14, freq="MS") # 1 == output_chunk_length, 3 > output_chunk_length ns = [1, 3] + model = self.helper_create_LinearModel( + multi_models=multi_models, extreme_lags=extreme_lags + ) + model.fit(series) + for n in ns: + _ = model.predict(n=n) + with pytest.raises(ValueError): + _ = model.predict(n=n, past_covariates=pc) + with pytest.raises(ValueError): + _ = model.predict(n=n, future_covariates=fc) + with pytest.raises(ValueError): + _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) + + model = self.helper_create_LinearModel( + multi_models=multi_models, extreme_lags=extreme_lags + ) + for n in ns: + model.fit(series, past_covariates=pc) + _ = model.predict(n=n) + _ = model.predict(n=n, past_covariates=pc) + with pytest.raises(ValueError): + _ = model.predict(n=n, future_covariates=fc) + with pytest.raises(ValueError): + _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) - for multi_models in [False, True]: - for extreme_lags in [False, True]: - model = self.helper_create_LinearModel( - multi_models=multi_models, extreme_lags=extreme_lags - ) - model.fit(series) - for n in ns: - _ = model.predict(n=n) - with pytest.raises(ValueError): - _ = model.predict(n=n, past_covariates=pc) - with pytest.raises(ValueError): - _ = model.predict(n=n, future_covariates=fc) - with pytest.raises(ValueError): - _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) - - model = self.helper_create_LinearModel( - multi_models=multi_models, extreme_lags=extreme_lags - ) - for n in ns: - model.fit(series, past_covariates=pc) - _ = model.predict(n=n) - _ = model.predict(n=n, past_covariates=pc) - with pytest.raises(ValueError): - _ = model.predict(n=n, future_covariates=fc) - with pytest.raises(ValueError): - _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) - - model = self.helper_create_LinearModel( - multi_models=multi_models, extreme_lags=extreme_lags - ) - for n in ns: - model.fit(series, future_covariates=fc) - _ = model.predict(n=n) - with pytest.raises(ValueError): - _ = model.predict(n=n, past_covariates=pc) - _ = model.predict(n=n, future_covariates=fc) - with pytest.raises(ValueError): - _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) - - model = self.helper_create_LinearModel( - multi_models=multi_models, extreme_lags=extreme_lags - ) - for n in ns: - model.fit(series, past_covariates=pc, future_covariates=fc) - _ = model.predict(n=n) - _ = model.predict(n=n, past_covariates=pc) - _ = model.predict(n=n, future_covariates=fc) - _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) + model = self.helper_create_LinearModel( + multi_models=multi_models, extreme_lags=extreme_lags + ) + for n in ns: + model.fit(series, future_covariates=fc) + _ = model.predict(n=n) + with pytest.raises(ValueError): + _ = model.predict(n=n, past_covariates=pc) + _ = model.predict(n=n, future_covariates=fc) + with pytest.raises(ValueError): + _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) + + model = self.helper_create_LinearModel( + multi_models=multi_models, extreme_lags=extreme_lags + ) + for n in ns: + model.fit(series, past_covariates=pc, future_covariates=fc) + _ = model.predict(n=n) + _ = model.predict(n=n, past_covariates=pc) + _ = model.predict(n=n, future_covariates=fc) + _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) @staticmethod def helper_compare_encoded_covs_with_ref( @@ -1951,7 +1884,7 @@ def helper_test_encoders_settings(model, example: str): assert len(model.encoders.future_encoders) == 1 assert isinstance(model.encoders.future_encoders[0], FutureCyclicEncoder) - @unittest.skipUnless(cb_available, "requires catboost") + @pytest.mark.skipif(not cb_available, reason="requires catboost") @patch.object( darts.models.forecasting.catboost_model.CatBoostRegressor if cb_available @@ -1974,7 +1907,7 @@ def test_catboost_model_with_eval_set(self, lgb_fit_patch): assert lgb_fit_patch.call_args[1]["eval_set"] is not None assert lgb_fit_patch.call_args[1]["early_stopping_rounds"] == 2 - @unittest.skipUnless(lgbm_available, "requires lightgbm") + @pytest.mark.skipif(not lgbm_available, reason="requires lightgbm") def test_quality_forecast_with_categorical_covariates(self): """Test case: two time series, a full sine wave series and a sine wave series with some irregularities every other period. Only models which use categorical @@ -2050,49 +1983,48 @@ def get_model_params(): ] ) - @unittest.skipUnless(lgbm_available, "requires lightgbm") - def test_fit_with_categorical_features_raises_error(self): + @pytest.mark.skipif(not lgbm_available, reason="requires lightgbm") + @pytest.mark.parametrize( + "model", + [ + LightGBMModel( + lags=1, + lags_past_covariates=1, + output_chunk_length=1, + categorical_past_covariates=["does_not_exist", "past_cov_cat_dummy"], + categorical_static_covariates=["product_id"], + ), + LightGBMModel( + lags=1, + lags_past_covariates=1, + output_chunk_length=1, + categorical_past_covariates=[ + "past_cov_cat_dummy", + ], + categorical_static_covariates=["does_not_exist"], + ), + LightGBMModel( + lags=1, + lags_past_covariates=1, + output_chunk_length=1, + categorical_future_covariates=["does_not_exist"], + ), + ], + ) + def test_fit_with_categorical_features_raises_error(self, model): ( series, past_covariates, future_covariates, ) = self.inputs_for_tests_categorical_covariates - model_incorrect_pastcov = LightGBMModel( - lags=1, - lags_past_covariates=1, - output_chunk_length=1, - categorical_past_covariates=["does_not_exist", "past_cov_cat_dummy"], - categorical_static_covariates=["product_id"], - ) - model_incorrect_statcov = LightGBMModel( - lags=1, - lags_past_covariates=1, - output_chunk_length=1, - categorical_past_covariates=[ - "past_cov_cat_dummy", - ], - categorical_static_covariates=["does_not_exist"], - ) - model_incorrect_futcov = LightGBMModel( - lags=1, - lags_past_covariates=1, - output_chunk_length=1, - categorical_future_covariates=["does_not_exist"], - ) - - for model in [ - model_incorrect_pastcov, - model_incorrect_statcov, - model_incorrect_futcov, - ]: - with self.assertRaises(ValueError): - model.fit( - series=series, - past_covariates=past_covariates, - future_covariates=future_covariates, - ) - - @unittest.skipUnless(lgbm_available, "requires lightgbm") + with pytest.raises(ValueError): + model.fit( + series=series, + past_covariates=past_covariates, + future_covariates=future_covariates, + ) + + @pytest.mark.skipif(not lgbm_available, reason="requires lightgbm") def test_get_categorical_features_helper(self): """Test helper function responsible for retrieving indices of categorical features""" ( @@ -2108,17 +2040,14 @@ def test_get_categorical_features_helper(self): past_covariates=past_covariates, future_covariates=future_covariates, ) - self.assertEqual(indices, [2, 3, 5]) - self.assertEqual( - column_names, - [ - "past_cov_past_cov_cat_dummy_lag-1", - "fut_cov_fut_cov_promo_mechanism_lag1", - "product_id", - ], - ) + assert indices == [2, 3, 5] + assert column_names == [ + "past_cov_past_cov_cat_dummy_lag-1", + "fut_cov_fut_cov_promo_mechanism_lag1", + "product_id", + ] - @unittest.skipUnless(lgbm_available, "requires lightgbm") + @pytest.mark.skipif(not lgbm_available, reason="requires lightgbm") @patch.object( darts.models.forecasting.lgbm.lgb.LGBMRegressor if lgbm_available @@ -2144,10 +2073,7 @@ def test_lgbm_categorical_features_passed_to_fit_correctly(self, lgb_fit_patch): cat_param_name, cat_param_default, ) = self.lgbm_w_categorical_covariates._categorical_fit_param - self.assertEqual( - kwargs[cat_param_name], - [2, 3, 5], - ) + assert kwargs[cat_param_name] == [2, 3, 5] def helper_create_LinearModel(self, multi_models=True, extreme_lags=False): if not extreme_lags: @@ -2169,7 +2095,7 @@ def helper_create_LinearModel(self, multi_models=True, extreme_lags=False): ) -class ProbabilisticRegressionModelsTestCase(DartsBaseTestClass): +class TestProbabilisticRegressionModels: models_cls_kwargs_errs = [ ( LinearRegressionModel, @@ -2299,47 +2225,49 @@ class ProbabilisticRegressionModelsTestCase(DartsBaseTestClass): num_samples = 5 @pytest.mark.slow - def test_fit_predict_determinism(self): - multi_models_modes = [False, True] - for mode in multi_models_modes: - for model_cls, model_kwargs, _ in self.models_cls_kwargs_errs: - # whether the first predictions of two models initiated with the same random state are the same - model_kwargs["multi_models"] = mode - model = model_cls(**model_kwargs) - model.fit(self.constant_noisy_multivar_ts) - pred1 = model.predict(n=10, num_samples=2).values() - - model = model_cls(**model_kwargs) - model.fit(self.constant_noisy_multivar_ts) - pred2 = model.predict(n=10, num_samples=2).values() - - self.assertTrue((pred1 == pred2).all()) - - # test whether the next prediction of the same model is different - pred3 = model.predict(n=10, num_samples=2).values() - self.assertTrue((pred2 != pred3).any()) + @pytest.mark.parametrize( + "config", itertools.product(models_cls_kwargs_errs, [True, False]) + ) + def test_fit_predict_determinism(self, config): + (model_cls, model_kwargs, _), mode = config + # whether the first predictions of two models initiated with the same random state are the same + model_kwargs["multi_models"] = mode + model = model_cls(**model_kwargs) + model.fit(self.constant_noisy_multivar_ts) + pred1 = model.predict(n=10, num_samples=2).values() + + model = model_cls(**model_kwargs) + model.fit(self.constant_noisy_multivar_ts) + pred2 = model.predict(n=10, num_samples=2).values() + + assert (pred1 == pred2).all() + + # test whether the next prediction of the same model is different + pred3 = model.predict(n=10, num_samples=2).values() + assert (pred2 != pred3).any() @pytest.mark.slow - def test_probabilistic_forecast_accuracy(self): - multi_models_modes = [True, False] - for mode in multi_models_modes: - for model_cls, model_kwargs, err in self.models_cls_kwargs_errs: - model_kwargs["multi_models"] = mode - self.helper_test_probabilistic_forecast_accuracy( - model_cls, - model_kwargs, - err, - self.constant_ts, - self.constant_noisy_ts, - ) - if issubclass(model_cls, GlobalForecastingModel): - self.helper_test_probabilistic_forecast_accuracy( - model_cls, - model_kwargs, - err, - self.constant_multivar_ts, - self.constant_noisy_multivar_ts, - ) + @pytest.mark.parametrize( + "config", itertools.product(models_cls_kwargs_errs, [True, False]) + ) + def test_probabilistic_forecast_accuracy(self, config): + (model_cls, model_kwargs, err), mode = config + model_kwargs["multi_models"] = mode + self.helper_test_probabilistic_forecast_accuracy( + model_cls, + model_kwargs, + err, + self.constant_ts, + self.constant_noisy_ts, + ) + if issubclass(model_cls, GlobalForecastingModel): + self.helper_test_probabilistic_forecast_accuracy( + model_cls, + model_kwargs, + err, + self.constant_multivar_ts, + self.constant_noisy_multivar_ts, + ) def helper_test_probabilistic_forecast_accuracy( self, model_cls, model_kwargs, err, ts, noisy_ts @@ -2350,14 +2278,14 @@ def helper_test_probabilistic_forecast_accuracy( # test accuracy of the median prediction compared to the noiseless ts mae_err_median = mae(ts[100:], pred) - self.assertLess(mae_err_median, err) + assert mae_err_median < err # test accuracy for increasing quantiles between 0.7 and 1 (it should ~decrease, mae should ~increase) tested_quantiles = [0.7, 0.8, 0.9, 0.99] mae_err = mae_err_median for quantile in tested_quantiles: new_mae = mae(ts[100:], pred.quantile_timeseries(quantile=quantile)) - self.assertLess(mae_err, new_mae + 0.1) + assert mae_err < new_mae + 0.1 mae_err = new_mae # test accuracy for decreasing quantiles between 0.3 and 0 (it should ~decrease, mae should ~increase) @@ -2365,5 +2293,5 @@ def helper_test_probabilistic_forecast_accuracy( mae_err = mae_err_median for quantile in tested_quantiles: new_mae = mae(ts[100:], pred.quantile_timeseries(quantile=quantile)) - self.assertLess(mae_err, new_mae + 0.1) + assert mae_err < new_mae + 0.1 mae_err = new_mae diff --git a/darts/tests/models/forecasting/test_sf_auto_ets.py b/darts/tests/models/forecasting/test_sf_auto_ets.py index 00927b9977..7fb35f7af5 100644 --- a/darts/tests/models/forecasting/test_sf_auto_ets.py +++ b/darts/tests/models/forecasting/test_sf_auto_ets.py @@ -5,10 +5,9 @@ from darts.datasets import AirPassengersDataset from darts.metrics import mae from darts.models import LinearRegressionModel, StatsForecastAutoETS -from darts.tests.base_test_class import DartsBaseTestClass -class StatsForecastAutoETSTestCase(DartsBaseTestClass): +class TestStatsForecastAutoETS: # real timeseries for functionality tests ts_passengers = AirPassengersDataset().load() ts_pass_train, ts_pass_val = ts_passengers.split_after(pd.Timestamp("19570101")) @@ -45,19 +44,19 @@ def test_fit_on_residuals(self): # compare in-sample predictions to the residuals they have supposedly been fitted on current_mae = mae(resids, ts_in_sample_preds) - self.assertTrue(current_mae < 9) + assert current_mae < 9 def test_fit_a_linreg(self): model = StatsForecastAutoETS(season_length=12, model="ZZZ") model.fit(series=self.ts_pass_train, future_covariates=self.ts_trend_train) # check if linear regression was fit - self.assertIsNotNone(model._linreg) - self.assertTrue(model._linreg._fit_called) + assert model._linreg is not None + assert model._linreg._fit_called # fit a linear regression linreg = LinearRegressionModel(lags_future_covariates=[0]) linreg.fit(series=self.ts_pass_train, future_covariates=self.ts_trend_train) # check if the linear regression was fit on the same data by checking if the coefficients are equal - self.assertEqual(model._linreg.model.coef_, linreg.model.coef_) + assert model._linreg.model.coef_ == linreg.model.coef_ diff --git a/darts/tests/models/forecasting/test_tide_model.py b/darts/tests/models/forecasting/test_tide_model.py index e981088886..3b946f6a25 100644 --- a/darts/tests/models/forecasting/test_tide_model.py +++ b/darts/tests/models/forecasting/test_tide_model.py @@ -1,13 +1,10 @@ -import shutil -import tempfile - import numpy as np import pandas as pd import pytest from darts import concatenate from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -26,16 +23,10 @@ if TORCH_AVAILABLE: - class TiDEModelModelTestCase(DartsBaseTestClass): + class TestTiDEModel: np.random.seed(42) torch.manual_seed(42) - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - def test_creation(self): model = TiDEModel( input_chunk_length=1, @@ -43,7 +34,7 @@ def test_creation(self): likelihood=GaussianLikelihood(), ) - self.assertEqual(model.input_chunk_length, 1) + assert model.input_chunk_length == 1 def test_fit(self): large_ts = tg.constant_timeseries(length=100, value=1000) @@ -71,13 +62,13 @@ def test_fit(self): model2.fit(small_ts[:98]) pred2 = model2.predict(n=2).values()[0] - self.assertTrue(abs(pred2 - 10) < abs(pred - 10)) + assert abs(pred2 - 10) < abs(pred - 10) # test short predict pred3 = model2.predict(n=1) - self.assertEqual(len(pred3), 1) + assert len(pred3) == 1 - def test_logtensorboard(self): + def test_logtensorboard(self, tmpdir_module): ts = tg.constant_timeseries(length=50, value=10) # Test basic fit and predict @@ -86,7 +77,7 @@ def test_logtensorboard(self): output_chunk_length=1, n_epochs=1, log_tensorboard=True, - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, pl_trainer_kwargs={ "log_every_n_steps": 1, **tfm_kwargs["pl_trainer_kwargs"], diff --git a/darts/tests/models/forecasting/test_torch_forecasting_model.py b/darts/tests/models/forecasting/test_torch_forecasting_model.py index bc57a66b28..c9e12be408 100644 --- a/darts/tests/models/forecasting/test_torch_forecasting_model.py +++ b/darts/tests/models/forecasting/test_torch_forecasting_model.py @@ -1,6 +1,4 @@ import os -import shutil -import tempfile from typing import Any, Dict, Optional from unittest.mock import patch @@ -13,7 +11,7 @@ from darts.dataprocessing.transformers import BoxCox, Scaler from darts.logging import get_logger from darts.metrics import mape -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils.timeseries_generation import linear_timeseries logger = get_logger(__name__) @@ -42,29 +40,23 @@ if TORCH_AVAILABLE: - class TestTorchForecastingModel(DartsBaseTestClass): - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") + class TestTorchForecastingModel: + times = pd.date_range("20130101", "20130410") + pd_series = pd.Series(range(100), index=times) + series = TimeSeries.from_series(pd_series) - times = pd.date_range("20130101", "20130410") - pd_series = pd.Series(range(100), index=times) - self.series = TimeSeries.from_series(pd_series) - - df = pd.DataFrame({"var1": range(100), "var2": range(100)}, index=times) - self.multivariate_series = TimeSeries.from_dataframe(df) - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) + df = pd.DataFrame({"var1": range(100), "var2": range(100)}, index=times) + multivariate_series = TimeSeries.from_dataframe(df) def test_save_model_parameters(self): # check if re-created model has same params as original model = RNNModel(12, "RNN", 10, 10, **tfm_kwargs) - self.assertTrue(model._model_params, model.untrained_model()._model_params) + assert model._model_params, model.untrained_model()._model_params @patch( "darts.models.forecasting.torch_forecasting_model.TorchForecastingModel.save" ) - def test_suppress_automatic_save(self, patch_save_model): + def test_suppress_automatic_save(self, patch_save_model, tmpdir_fn): model_name = "test_model" model1 = RNNModel( 12, @@ -72,7 +64,7 @@ def test_suppress_automatic_save(self, patch_save_model): 10, 10, model_name=model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, save_checkpoints=False, **tfm_kwargs, ) @@ -82,7 +74,7 @@ def test_suppress_automatic_save(self, patch_save_model): 10, 10, model_name=model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, force_reset=True, save_checkpoints=False, **tfm_kwargs, @@ -96,13 +88,13 @@ def test_suppress_automatic_save(self, patch_save_model): patch_save_model.assert_not_called() - model1.save(path=os.path.join(self.temp_work_dir, model_name)) + model1.save(path=os.path.join(tmpdir_fn, model_name)) patch_save_model.assert_called() - def test_manual_save_and_load(self): + def test_manual_save_and_load(self, tmpdir_fn): """validate manual save with automatic save files by comparing output between the two""" - model_dir = os.path.join(self.temp_work_dir) + model_dir = os.path.join(tmpdir_fn) manual_name = "test_save_manual" auto_name = "test_save_automatic" model_manual_save = RNNModel( @@ -111,7 +103,7 @@ def test_manual_save_and_load(self): 10, 10, model_name=manual_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, save_checkpoints=False, random_state=42, **tfm_kwargs, @@ -122,7 +114,7 @@ def test_manual_save_and_load(self): 10, 10, model_name=auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, save_checkpoints=True, random_state=42, **tfm_kwargs, @@ -133,30 +125,27 @@ def test_manual_save_and_load(self): no_training_ckpt_path = os.path.join(model_dir, no_training_ckpt) model_manual_save.save(no_training_ckpt_path) # check that model object file was created - self.assertTrue(os.path.exists(no_training_ckpt_path)) + assert os.path.exists(no_training_ckpt_path) # check that the PyTorch Ligthning ckpt does not exist - self.assertFalse(os.path.exists(no_training_ckpt_path + ".ckpt")) + assert not os.path.exists(no_training_ckpt_path + ".ckpt") # informative exception about `fit()` not called - with self.assertRaises( - ValueError, - msg="The model must be fit before calling predict(). " - "For global models, if predict() is called without specifying a series, " - "the model must have been fit on a single training series.", - ): + with pytest.raises(ValueError) as err: no_train_model = RNNModel.load(no_training_ckpt_path) no_train_model.predict(n=4) + assert str(err.value) == ( + "Input `series` must be provided. This is the result either from " + "fitting on multiple series, or from not having fit the model yet." + ) model_manual_save.fit(self.series, epochs=1) model_auto_save.fit(self.series, epochs=1) # check that file was not created with manual save - self.assertFalse( - os.path.exists(os.path.join(model_dir, manual_name, "checkpoints")) + assert not os.path.exists( + os.path.join(model_dir, manual_name, "checkpoints") ) # check that file was created with automatic save - self.assertTrue( - os.path.exists(os.path.join(model_dir, auto_name, "checkpoints")) - ) + assert os.path.exists(os.path.join(model_dir, auto_name, "checkpoints")) # create manually saved model checkpoints folder checkpoint_path_manual = os.path.join(model_dir, manual_name) @@ -173,30 +162,26 @@ def test_manual_save_and_load(self): # save manually saved model model_manual_save.save(model_path_manual) - self.assertTrue(os.path.exists(model_path_manual)) + assert os.path.exists(model_path_manual) # check that the PTL checkpoint path is also there - self.assertTrue(os.path.exists(model_path_manual_ckpt)) + assert os.path.exists(model_path_manual_ckpt) # load manual save model and compare with automatic model results model_manual_save = RNNModel.load(model_path_manual, map_location="cpu") model_manual_save.to_cpu() - self.assertEqual( - model_manual_save.predict(n=4), model_auto_save.predict(n=4) - ) + assert model_manual_save.predict(n=4) == model_auto_save.predict(n=4) # load automatically saved model with manual load() and load_from_checkpoint() model_auto_save1 = RNNModel.load_from_checkpoint( model_name=auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, map_location="cpu", ) model_auto_save1.to_cpu() # compare loaded checkpoint with manual save - self.assertEqual( - model_manual_save.predict(n=4), model_auto_save1.predict(n=4) - ) + assert model_manual_save.predict(n=4) == model_auto_save1.predict(n=4) # save() model directly after load_from_checkpoint() checkpoint_file_name_2 = "checkpoint_1.pth.tar" @@ -210,7 +195,7 @@ def test_manual_save_and_load(self): ) model_auto_save2 = RNNModel.load_from_checkpoint( model_name=auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, map_location="cpu", ) @@ -218,18 +203,18 @@ def test_manual_save_and_load(self): model_auto_save2.save(model_path_manual_2) # assert original .ckpt checkpoint was correctly copied - self.assertTrue(os.path.exists(model_path_manual_ckpt_2)) + assert os.path.exists(model_path_manual_ckpt_2) model_chained_load_save = RNNModel.load( model_path_manual_2, map_location="cpu" ) # compare chained load_from_checkpoint() save() with manual save - self.assertEqual( - model_chained_load_save.predict(n=4), model_manual_save.predict(n=4) + assert model_chained_load_save.predict(n=4) == model_manual_save.predict( + n=4 ) - def test_valid_save_and_load_weights_with_different_params(self): + def test_valid_save_and_load_weights_with_different_params(self, tmpdir_fn): """ Verify that save/load does not break encoders. @@ -246,7 +231,7 @@ def create_model(**kwargs): **tfm_kwargs, ) - model_dir = os.path.join(self.temp_work_dir) + model_dir = os.path.join(tmpdir_fn) manual_name = "save_manual" # create manually saved model checkpoints folder checkpoint_path_manual = os.path.join(model_dir, manual_name) @@ -268,7 +253,7 @@ def create_model(**kwargs): model_new = create_model(**kwargs_) model_new.load_weights(model_path_manual) - def test_save_and_load_weights_w_encoders(self): + def test_save_and_load_weights_w_encoders(self, tmpdir_fn): """ Verify that save/load does not break encoders. @@ -276,8 +261,7 @@ def test_save_and_load_weights_w_encoders(self): for all but one test. Note: Using DLinear since it supports both past and future covariates """ - - model_dir = os.path.join(self.temp_work_dir) + model_dir = os.path.join(tmpdir_fn) manual_name = "save_manual" auto_name = "save_auto" auto_name_other = "save_auto_other" @@ -315,36 +299,42 @@ def test_save_and_load_weights_w_encoders(self): } model_auto_save = self.helper_create_DLinearModel( - auto_name, save_checkpoints=True, add_encoders=encoders_past + work_dir=tmpdir_fn, + model_name=auto_name, + save_checkpoints=True, + add_encoders=encoders_past, ) model_auto_save.fit(self.series, epochs=1) model_manual_save = self.helper_create_DLinearModel( - manual_name, save_checkpoints=False, add_encoders=encoders_past + work_dir=tmpdir_fn, + model_name=manual_name, + save_checkpoints=False, + add_encoders=encoders_past, ) model_manual_save.fit(self.series, epochs=1) model_manual_save.save(model_path_manual) model_auto_save_other = self.helper_create_DLinearModel( - auto_name_other, save_checkpoints=True, add_encoders=encoders_other_past + work_dir=tmpdir_fn, + model_name=auto_name_other, + save_checkpoints=True, + add_encoders=encoders_other_past, ) model_auto_save_other.fit(self.series, epochs=1) # prediction are different when using different encoders - self.assertNotEqual( - model_auto_save.predict(n=4), - model_auto_save_other.predict(n=4), - ) + assert model_auto_save.predict(n=4) != model_auto_save_other.predict(n=4) # model with undeclared encoders model_no_enc = self.helper_create_DLinearModel( - "no_encoder", add_encoders=None + work_dir=tmpdir_fn, model_name="no_encoder", add_encoders=None ) # weights were trained with encoders, new model must be instantiated with encoders - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_no_enc.load_weights_from_checkpoint( auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, load_encoders=False, map_location="cpu", @@ -352,7 +342,7 @@ def test_save_and_load_weights_w_encoders(self): # overwritte undeclared encoders model_no_enc.load_weights_from_checkpoint( auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, load_encoders=True, map_location="cpu", @@ -364,14 +354,15 @@ def test_save_and_load_weights_w_encoders(self): model_auto_save.add_encoders, model_no_enc.add_encoders ) # cannot directly verify equality between encoders, using predict as proxy - self.assertEqual( - model_auto_save.predict(n=4), - model_no_enc.predict(n=4, series=self.series), + assert model_auto_save.predict(n=4) == model_no_enc.predict( + n=4, series=self.series ) # model with identical encoders (fittable) model_same_enc_noload = self.helper_create_DLinearModel( - "same_encoder_noload", add_encoders=encoders_past + work_dir=tmpdir_fn, + model_name="same_encoder_noload", + add_encoders=encoders_past, ) model_same_enc_noload.load_weights( model_path_manual, @@ -379,28 +370,31 @@ def test_save_and_load_weights_w_encoders(self): map_location="cpu", ) # cannot predict because of un-fitted encoder - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_same_enc_noload.predict(n=4, series=self.series) model_same_enc_load = self.helper_create_DLinearModel( - "same_encoder_load", add_encoders=encoders_past + work_dir=tmpdir_fn, + model_name="same_encoder_load", + add_encoders=encoders_past, ) model_same_enc_load.load_weights( model_path_manual, load_encoders=True, map_location="cpu", ) - self.assertEqual( - model_manual_save.predict(n=4), - model_same_enc_load.predict(n=4, series=self.series), + assert model_manual_save.predict(n=4) == model_same_enc_load.predict( + n=4, series=self.series ) # model with different encoders (fittable) model_other_enc_load = self.helper_create_DLinearModel( - "other_encoder_load", add_encoders=encoders_other_past + work_dir=tmpdir_fn, + model_name="other_encoder_load", + add_encoders=encoders_other_past, ) # cannot overwritte different declared encoders - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_other_enc_load.load_weights( model_path_manual, load_encoders=True, @@ -409,7 +403,9 @@ def test_save_and_load_weights_w_encoders(self): # model with different encoders but same dimensions (fittable) model_other_enc_noload = self.helper_create_DLinearModel( - "other_encoder_noload", add_encoders=encoders_other_past + work_dir=tmpdir_fn, + model_name="other_encoder_noload", + add_encoders=encoders_other_past, ) model_other_enc_noload.load_weights( model_path_manual, @@ -423,11 +419,9 @@ def test_save_and_load_weights_w_encoders(self): model_other_enc_noload.add_encoders, encoders_other_past ) # new encoders were instantiated - self.assertTrue( - isinstance(model_other_enc_noload.encoders, SequentialEncoder) - ) + assert isinstance(model_other_enc_noload.encoders, SequentialEncoder) # since fit() was not called, new fittable encoders were not trained - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_other_enc_noload.predict(n=4, series=self.series) # predict() can be called after fit() @@ -436,7 +430,9 @@ def test_save_and_load_weights_w_encoders(self): # model with same encoders but no scaler (non-fittable) model_new_enc_noscaler_noload = self.helper_create_DLinearModel( - "same_encoder_noscaler", add_encoders=encoders_past_noscaler + work_dir=tmpdir_fn, + model_name="same_encoder_noscaler", + add_encoders=encoders_past_noscaler, ) model_new_enc_noscaler_noload.load_weights( model_path_manual, @@ -455,11 +451,12 @@ def test_save_and_load_weights_w_encoders(self): # model with same encoders but different transformer (fittable) model_new_enc_other_transformer = self.helper_create_DLinearModel( - "same_encoder_other_transform", + work_dir=tmpdir_fn, + model_name="same_encoder_other_transform", add_encoders=encoders_past_other_transformer, ) # cannot overwritte different declared encoders - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_new_enc_other_transformer.load_weights( model_path_manual, load_encoders=True, @@ -472,7 +469,7 @@ def test_save_and_load_weights_w_encoders(self): map_location="cpu", ) # since fit() was not called, new fittable encoders were not trained - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_new_enc_other_transformer.predict(n=4, series=self.series) # predict() can be called after fit() @@ -481,17 +478,19 @@ def test_save_and_load_weights_w_encoders(self): # model with encoders containing more components (fittable) model_new_enc_2_past = self.helper_create_DLinearModel( - "encoder_2_components_past", add_encoders=encoders_2_past + work_dir=tmpdir_fn, + model_name="encoder_2_components_past", + add_encoders=encoders_2_past, ) # cannot overwritte different declared encoders - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_new_enc_2_past.load_weights( model_path_manual, load_encoders=True, map_location="cpu", ) # new encoders have one additional past component - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_new_enc_2_past.load_weights( model_path_manual, load_encoders=False, @@ -500,24 +499,26 @@ def test_save_and_load_weights_w_encoders(self): # model with encoders containing past and future covs (fittable) model_new_enc_past_n_future = self.helper_create_DLinearModel( - "encoder_past_n_future", add_encoders=encoders_past_n_future + work_dir=tmpdir_fn, + model_name="encoder_past_n_future", + add_encoders=encoders_past_n_future, ) # cannot overwritte different declared encoders - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_new_enc_past_n_future.load_weights( model_path_manual, load_encoders=True, map_location="cpu", ) # identical past components, but different future components - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_new_enc_past_n_future.load_weights( model_path_manual, load_encoders=False, map_location="cpu", ) - def test_save_and_load_weights_w_likelihood(self): + def test_save_and_load_weights_w_likelihood(self, tmpdir_fn): """ Verify that save/load does not break likelihood. @@ -525,7 +526,7 @@ def test_save_and_load_weights_w_likelihood(self): for all but one test. Note: Using DLinear since it supports both past and future covariates """ - model_dir = os.path.join(self.temp_work_dir) + model_dir = os.path.join(tmpdir_fn) manual_name = "save_manual" auto_name = "save_auto" # create manually saved model checkpoints folder @@ -537,7 +538,8 @@ def test_save_and_load_weights_w_likelihood(self): ) model_auto_save = self.helper_create_DLinearModel( - auto_name, + work_dir=tmpdir_fn, + model_name=auto_name, save_checkpoints=True, likelihood=GaussianLikelihood(prior_mu=0.5), ) @@ -545,7 +547,8 @@ def test_save_and_load_weights_w_likelihood(self): pred_auto = model_auto_save.predict(n=4, series=self.series) model_manual_save = self.helper_create_DLinearModel( - manual_name, + work_dir=tmpdir_fn, + model_name=manual_name, save_checkpoints=False, likelihood=GaussianLikelihood(prior_mu=0.5), ) @@ -554,11 +557,13 @@ def test_save_and_load_weights_w_likelihood(self): pred_manual = model_manual_save.predict(n=4, series=self.series) # predictions are identical when using the same likelihood - self.assertTrue(np.array_equal(pred_auto.values(), pred_manual.values())) + assert np.array_equal(pred_auto.values(), pred_manual.values()) # model with identical likelihood model_same_likelihood = self.helper_create_DLinearModel( - "same_likelihood", likelihood=GaussianLikelihood(prior_mu=0.5) + work_dir=tmpdir_fn, + model_name="same_likelihood", + likelihood=GaussianLikelihood(prior_mu=0.5), ) model_same_likelihood.load_weights(model_path_manual, map_location="cpu") model_same_likelihood.predict(n=4, series=self.series) @@ -566,7 +571,9 @@ def test_save_and_load_weights_w_likelihood(self): # loading models weights with respective methods model_manual_same_likelihood = self.helper_create_DLinearModel( - "same_likelihood", likelihood=GaussianLikelihood(prior_mu=0.5) + work_dir=tmpdir_fn, + model_name="same_likelihood", + likelihood=GaussianLikelihood(prior_mu=0.5), ) model_manual_same_likelihood.load_weights( model_path_manual, map_location="cpu" @@ -576,25 +583,26 @@ def test_save_and_load_weights_w_likelihood(self): ) model_auto_same_likelihood = self.helper_create_DLinearModel( - "same_likelihood", likelihood=GaussianLikelihood(prior_mu=0.5) + work_dir=tmpdir_fn, + model_name="same_likelihood", + likelihood=GaussianLikelihood(prior_mu=0.5), ) model_auto_same_likelihood.load_weights_from_checkpoint( - auto_name, work_dir=self.temp_work_dir, best=False, map_location="cpu" + auto_name, work_dir=tmpdir_fn, best=False, map_location="cpu" ) preds_auto_from_weights = model_auto_same_likelihood.predict( n=4, series=self.series ) # check that weights from checkpoint give identical predictions as weights from manual save - self.assertTrue(preds_manual_from_weights == preds_auto_from_weights) - + assert preds_manual_from_weights == preds_auto_from_weights # model with explicitely no likelihood model_no_likelihood = self.helper_create_DLinearModel( - "no_likelihood", likelihood=None + work_dir=tmpdir_fn, model_name="no_likelihood", likelihood=None ) with pytest.raises(ValueError) as error_msg: model_no_likelihood.load_weights_from_checkpoint( auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, map_location="cpu", ) @@ -609,7 +617,7 @@ def test_save_and_load_weights_w_likelihood(self): output_chunk_length=1, model_name="no_likelihood_bis", add_encoders=None, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, save_checkpoints=False, random_state=42, force_reset=True, @@ -620,7 +628,7 @@ def test_save_and_load_weights_w_likelihood(self): with pytest.raises(ValueError) as error_msg: model_no_likelihood_bis.load_weights_from_checkpoint( auto_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, map_location="cpu", ) @@ -631,7 +639,9 @@ def test_save_and_load_weights_w_likelihood(self): # model with a different likelihood model_other_likelihood = self.helper_create_DLinearModel( - "other_likelihood", likelihood=LaplaceLikelihood() + work_dir=tmpdir_fn, + model_name="other_likelihood", + likelihood=LaplaceLikelihood(), ) with pytest.raises(ValueError) as error_msg: model_other_likelihood.load_weights( @@ -644,7 +654,9 @@ def test_save_and_load_weights_w_likelihood(self): # model with the same likelihood but different parameters model_same_likelihood_other_prior = self.helper_create_DLinearModel( - "same_likelihood_other_prior", likelihood=GaussianLikelihood() + work_dir=tmpdir_fn, + model_name="same_likelihood_other_prior", + likelihood=GaussianLikelihood(), ) with pytest.raises(ValueError) as error_msg: model_same_likelihood_other_prior.load_weights( @@ -655,57 +667,52 @@ def test_save_and_load_weights_w_likelihood(self): "incorrect" ) - def test_load_weights_params_check(self): + def test_load_weights_params_check(self, tmpdir_fn): """ Verify that the method comparing the parameters between the saved model and the loading model behave as expected, used to return meaningful error message instead of the torch.load ones. """ model_name = "params_check" - ckpt_name = f"{model_name}.pt" + ckpt_path = os.path.join(tmpdir_fn, f"{model_name}.pt") # barebone model model = DLinearModel( input_chunk_length=4, output_chunk_length=1, - work_dir=self.temp_work_dir, n_epochs=1, ) model.fit(self.series[:10]) - model.save(ckpt_name) + model.save(ckpt_path) # identical model loading_model = DLinearModel( input_chunk_length=4, output_chunk_length=1, - work_dir=self.temp_work_dir, ) - loading_model.load_weights(ckpt_name) + loading_model.load_weights(ckpt_path) # different optimizer loading_model = DLinearModel( input_chunk_length=4, output_chunk_length=1, - work_dir=self.temp_work_dir, optimizer_cls=torch.optim.AdamW, ) - loading_model.load_weights(ckpt_name) + loading_model.load_weights(ckpt_path) # different pl_trainer_kwargs loading_model = DLinearModel( input_chunk_length=4, output_chunk_length=1, - work_dir=self.temp_work_dir, pl_trainer_kwargs={"enable_model_summary": False}, ) - loading_model.load_weights(ckpt_name) + loading_model.load_weights(ckpt_path) # different input_chunk_length (tfm parameter) loading_model = DLinearModel( input_chunk_length=4 + 1, output_chunk_length=1, - work_dir=self.temp_work_dir, ) with pytest.raises(ValueError) as error_msg: - loading_model.load_weights(ckpt_name) + loading_model.load_weights(ckpt_path) assert str(error_msg.value).startswith( "The values of the hyper-parameters in the model and loaded checkpoint should be identical.\n" "incorrect" @@ -716,27 +723,26 @@ def test_load_weights_params_check(self): input_chunk_length=4, output_chunk_length=1, kernel_size=10, - work_dir=self.temp_work_dir, ) with pytest.raises(ValueError) as error_msg: - loading_model.load_weights(ckpt_name) + loading_model.load_weights(ckpt_path) assert str(error_msg.value).startswith( "The values of the hyper-parameters in the model and loaded checkpoint should be identical.\n" "incorrect" ) - def test_create_instance_new_model_no_name_set(self): - RNNModel(12, "RNN", 10, 10, work_dir=self.temp_work_dir, **tfm_kwargs) + def test_create_instance_new_model_no_name_set(self, tmpdir_fn): + RNNModel(12, "RNN", 10, 10, work_dir=tmpdir_fn, **tfm_kwargs) # no exception is raised - def test_create_instance_existing_model_with_name_no_fit(self): + def test_create_instance_existing_model_with_name_no_fit(self, tmpdir_fn): model_name = "test_model" RNNModel( 12, "RNN", 10, 10, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=model_name, **tfm_kwargs, ) @@ -746,7 +752,7 @@ def test_create_instance_existing_model_with_name_no_fit(self): "darts.models.forecasting.torch_forecasting_model.TorchForecastingModel.reset_model" ) def test_create_instance_existing_model_with_name_force( - self, patch_reset_model + self, patch_reset_model, tmpdir_fn ): model_name = "test_model" RNNModel( @@ -754,7 +760,7 @@ def test_create_instance_existing_model_with_name_force( "RNN", 10, 10, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=model_name, **tfm_kwargs, ) @@ -766,7 +772,7 @@ def test_create_instance_existing_model_with_name_force( "RNN", 10, 10, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=model_name, force_reset=True, **tfm_kwargs, @@ -777,7 +783,7 @@ def test_create_instance_existing_model_with_name_force( "darts.models.forecasting.torch_forecasting_model.TorchForecastingModel.reset_model" ) def test_create_instance_existing_model_with_name_force_fit_with_reset( - self, patch_reset_model + self, patch_reset_model, tmpdir_fn ): model_name = "test_model" model1 = RNNModel( @@ -785,7 +791,7 @@ def test_create_instance_existing_model_with_name_force_fit_with_reset( "RNN", 10, 10, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=model_name, save_checkpoints=True, **tfm_kwargs, @@ -799,7 +805,7 @@ def test_create_instance_existing_model_with_name_force_fit_with_reset( "RNN", 10, 10, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=model_name, save_checkpoints=True, force_reset=True, @@ -819,13 +825,12 @@ def test_train_from_0_n_epochs_20_no_fit_epochs(self): 10, 10, n_epochs=20, - work_dir=self.temp_work_dir, **tfm_kwargs, ) model1.fit(self.series) - self.assertEqual(20, model1.epochs_trained) + assert 20 == model1.epochs_trained # n_epochs = 20, fit|epochs=None, epochs_trained=20 - train for another 20 epochs def test_train_from_20_n_epochs_40_no_fit_epochs(self): @@ -835,15 +840,14 @@ def test_train_from_20_n_epochs_40_no_fit_epochs(self): 10, 10, n_epochs=20, - work_dir=self.temp_work_dir, **tfm_kwargs, ) model1.fit(self.series) - self.assertEqual(20, model1.epochs_trained) + assert 20 == model1.epochs_trained model1.fit(self.series) - self.assertEqual(20, model1.epochs_trained) + assert 20 == model1.epochs_trained # n_epochs = 20, fit|epochs=None, epochs_trained=10 - train for another 20 epochs def test_train_from_10_n_epochs_20_no_fit_epochs(self): @@ -853,16 +857,15 @@ def test_train_from_10_n_epochs_20_no_fit_epochs(self): 10, 10, n_epochs=20, - work_dir=self.temp_work_dir, **tfm_kwargs, ) # simulate the case that user interrupted training with Ctrl-C after 10 epochs model1.fit(self.series, epochs=10) - self.assertEqual(10, model1.epochs_trained) + assert 10 == model1.epochs_trained model1.fit(self.series) - self.assertEqual(20, model1.epochs_trained) + assert 20 == model1.epochs_trained # n_epochs = 20, fit|epochs=15, epochs_trained=10 - train for 15 epochs def test_train_from_10_n_epochs_20_fit_15_epochs(self): @@ -872,18 +875,17 @@ def test_train_from_10_n_epochs_20_fit_15_epochs(self): 10, 10, n_epochs=20, - work_dir=self.temp_work_dir, **tfm_kwargs, ) # simulate the case that user interrupted training with Ctrl-C after 10 epochs model1.fit(self.series, epochs=10) - self.assertEqual(10, model1.epochs_trained) + assert 10 == model1.epochs_trained model1.fit(self.series, epochs=15) - self.assertEqual(15, model1.epochs_trained) + assert 15 == model1.epochs_trained - def test_load_weights_from_checkpoint(self): + def test_load_weights_from_checkpoint(self, tmpdir_fn): ts_training, ts_test = self.series.split_before(90) original_model_name = "original" retrained_model_name = "retrained" @@ -894,7 +896,7 @@ def test_load_weights_from_checkpoint(self): 5, 1, n_epochs=5, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, save_checkpoints=True, model_name=original_model_name, random_state=1, @@ -911,14 +913,14 @@ def test_load_weights_from_checkpoint(self): 5, 1, n_epochs=5, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=retrained_model_name, random_state=1, **tfm_kwargs, ) model_rt.load_weights_from_checkpoint( model_name=original_model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, map_location="cpu", ) @@ -926,19 +928,18 @@ def test_load_weights_from_checkpoint(self): # must indicate series otherwise self.training_series must be saved in checkpoint loaded_preds = model_rt.predict(10, ts_training) # save/load checkpoint should produce identical predictions - self.assertEqual(original_preds, loaded_preds) + assert original_preds == loaded_preds model_rt.fit(ts_training) retrained_preds = model_rt.predict(10) retrained_mape = mape(retrained_preds, ts_test) - self.assertTrue( - retrained_mape < original_mape, + assert retrained_mape < original_mape, ( f"Retrained model has a greater error (mape) than the original model, " - f"respectively {retrained_mape} and {original_mape}", + f"respectively {retrained_mape} and {original_mape}" ) # raise Exception when trying to load ckpt weights in different architecture - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_rt = RNNModel( 12, "RNN", @@ -947,23 +948,23 @@ def test_load_weights_from_checkpoint(self): ) model_rt.load_weights_from_checkpoint( model_name=original_model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, map_location="cpu", ) # raise Exception when trying to pass `weights_only`=True to `torch.load()` - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_rt = RNNModel(12, "RNN", 5, 5, **tfm_kwargs) model_rt.load_weights_from_checkpoint( model_name=original_model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=False, weights_only=True, map_location="cpu", ) - def test_load_weights(self): + def test_load_weights(self, tmpdir_fn): ts_training, ts_test = self.series.split_before(90) original_model_name = "original" retrained_model_name = "retrained" @@ -974,14 +975,14 @@ def test_load_weights(self): 5, 1, n_epochs=5, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, save_checkpoints=False, model_name=original_model_name, random_state=1, **tfm_kwargs, ) model.fit(ts_training) - path_manual_save = os.path.join(self.temp_work_dir, "RNN_manual_save.pt") + path_manual_save = os.path.join(tmpdir_fn, "RNN_manual_save.pt") model.save(path_manual_save) original_preds = model.predict(10) original_mape = mape(original_preds, ts_test) @@ -993,7 +994,7 @@ def test_load_weights(self): 5, 1, n_epochs=5, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=retrained_model_name, random_state=1, **tfm_kwargs, @@ -1003,34 +1004,33 @@ def test_load_weights(self): # must indicate series otherwise self.training_series must be saved in checkpoint loaded_preds = model_rt.predict(10, ts_training) # save/load checkpoint should produce identical predictions - self.assertEqual(original_preds, loaded_preds) + assert original_preds == loaded_preds model_rt.fit(ts_training) retrained_preds = model_rt.predict(10) retrained_mape = mape(retrained_preds, ts_test) - self.assertTrue( - retrained_mape < original_mape, + assert retrained_mape < original_mape, ( f"Retrained model has a greater mape error than the original model, " - f"respectively {retrained_mape} and {original_mape}", + f"respectively {retrained_mape} and {original_mape}" ) - def test_multi_steps_pipeline(self): + def test_multi_steps_pipeline(self, tmpdir_fn): ts_training, ts_val = self.series.split_before(75) pretrain_model_name = "pre-train" retrained_model_name = "re-train" # pretraining - model = self.helper_create_RNNModel(pretrain_model_name) + model = self.helper_create_RNNModel(pretrain_model_name, tmpdir_fn) model.fit( ts_training, val_series=ts_val, ) # finetuning - model = self.helper_create_RNNModel(retrained_model_name) + model = self.helper_create_RNNModel(retrained_model_name, tmpdir_fn) model.load_weights_from_checkpoint( model_name=pretrain_model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=True, map_location="cpu", ) @@ -1042,13 +1042,13 @@ def test_multi_steps_pipeline(self): # prediction model = model.load_from_checkpoint( model_name=retrained_model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, best=True, map_location="cpu", ) model.predict(4, series=ts_training) - def test_load_from_checkpoint_w_custom_loss(self): + def test_load_from_checkpoint_w_custom_loss(self, tmpdir_fn): model_name = "pretraining_custom_loss" # model with a custom loss model = RNNModel( @@ -1057,7 +1057,7 @@ def test_load_from_checkpoint_w_custom_loss(self): 5, 1, n_epochs=1, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=model_name, save_checkpoints=True, force_reset=True, @@ -1067,16 +1067,16 @@ def test_load_from_checkpoint_w_custom_loss(self): model.fit(self.series) loaded_model = RNNModel.load_from_checkpoint( - model_name, self.temp_work_dir, best=False, map_location="cpu" + model_name, tmpdir_fn, best=False, map_location="cpu" ) # custom loss function should be properly restored from ckpt - self.assertTrue(isinstance(loaded_model.model.criterion, torch.nn.L1Loss)) + assert isinstance(loaded_model.model.criterion, torch.nn.L1Loss) loaded_model.fit(self.series, epochs=2) # calling fit() should not impact the loss function - self.assertTrue(isinstance(loaded_model.model.criterion, torch.nn.L1Loss)) + assert isinstance(loaded_model.model.criterion, torch.nn.L1Loss) - def test_load_from_checkpoint_w_metrics(self): + def test_load_from_checkpoint_w_metrics(self, tmpdir_fn): model_name = "pretraining_metrics" # model with one torch_metrics pl_trainer_kwargs = dict( @@ -1089,7 +1089,7 @@ def test_load_from_checkpoint_w_metrics(self): 5, 1, n_epochs=1, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, model_name=model_name, save_checkpoints=True, force_reset=True, @@ -1098,20 +1098,18 @@ def test_load_from_checkpoint_w_metrics(self): ) model.fit(self.series) # check train_metrics before loading - self.assertTrue(isinstance(model.model.train_metrics, MetricCollection)) - self.assertEqual(len(model.model.train_metrics), 1) + assert isinstance(model.model.train_metrics, MetricCollection) + assert len(model.model.train_metrics) == 1 loaded_model = RNNModel.load_from_checkpoint( model_name, - self.temp_work_dir, + tmpdir_fn, best=False, map_location="cpu", ) # custom loss function should be properly restored from ckpt torchmetrics.Metric - self.assertTrue( - isinstance(loaded_model.model.train_metrics, MetricCollection) - ) - self.assertEqual(len(loaded_model.model.train_metrics), 1) + assert isinstance(loaded_model.model.train_metrics, MetricCollection) + assert len(loaded_model.model.train_metrics) == 1 def test_optimizers(self): @@ -1165,7 +1163,7 @@ def test_wrong_model_creation_params(self): _ = RNNModel(12, "RNN", 10, 10, **valid_kwarg) # invalid params should raise an error - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = RNNModel(12, "RNN", 10, 10, **invalid_kwarg) def test_metrics(self): @@ -1266,7 +1264,7 @@ def test_metrics_w_likelihood(self): def test_invalid_metrics(self): torch_metrics = ["invalid"] - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): model = RNNModel( 12, "RNN", @@ -1320,7 +1318,7 @@ def test_lr_find(self): ) assert scores["worst"] > scores["suggested"] - def test_encoders(self): + def test_encoders(self, tmpdir_fn): series = linear_timeseries(length=10) pc = linear_timeseries(length=12) fc = linear_timeseries(length=13) @@ -1328,9 +1326,10 @@ def test_encoders(self): ns = [1, 3] model = self.helper_create_DLinearModel( + work_dir=tmpdir_fn, add_encoders={ "datetime_attribute": {"past": ["hour"], "future": ["month"]} - } + }, ) model.fit(series) for n in ns: @@ -1343,9 +1342,10 @@ def test_encoders(self): _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) model = self.helper_create_DLinearModel( + work_dir=tmpdir_fn, add_encoders={ "datetime_attribute": {"past": ["hour"], "future": ["month"]} - } + }, ) for n in ns: model.fit(series, past_covariates=pc) @@ -1357,9 +1357,10 @@ def test_encoders(self): _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) model = self.helper_create_DLinearModel( + work_dir=tmpdir_fn, add_encoders={ "datetime_attribute": {"past": ["hour"], "future": ["month"]} - } + }, ) for n in ns: model.fit(series, future_covariates=fc) @@ -1371,9 +1372,10 @@ def test_encoders(self): _ = model.predict(n=n, past_covariates=pc, future_covariates=fc) model = self.helper_create_DLinearModel( + work_dir=tmpdir_fn, add_encoders={ "datetime_attribute": {"past": ["hour"], "future": ["month"]} - } + }, ) for n in ns: model.fit(series, past_covariates=pc, future_covariates=fc) @@ -1389,10 +1391,9 @@ def helper_equality_encoders( first_encoders = {} if second_encoders is None: second_encoders = {} - self.assertEqual( - {k: v for k, v in first_encoders.items() if k != "transformer"}, - {k: v for k, v in second_encoders.items() if k != "transformer"}, - ) + assert {k: v for k, v in first_encoders.items() if k != "transformer"} == { + k: v for k, v in second_encoders.items() if k != "transformer" + } def helper_equality_encoders_transfo( self, first_encoders: Dict[str, Any], second_encoders: Dict[str, Any] @@ -1401,12 +1402,12 @@ def helper_equality_encoders_transfo( first_encoders = {} if second_encoders is None: second_encoders = {} - self.assertEqual( - type(first_encoders.get("transformer", None)), - type(second_encoders.get("transformer", None)), + assert ( + first_encoders.get("transformer", None).__class__ + == second_encoders.get("transformer", None).__class__ ) - def helper_create_RNNModel(self, model_name: str): + def helper_create_RNNModel(self, model_name: str, tmpdir_fn): return RNNModel( input_chunk_length=4, hidden_dim=3, @@ -1419,7 +1420,7 @@ def helper_create_RNNModel(self, model_name: str): }, n_epochs=2, model_name=model_name, - work_dir=self.temp_work_dir, + work_dir=tmpdir_fn, force_reset=True, save_checkpoints=True, **tfm_kwargs, @@ -1427,6 +1428,7 @@ def helper_create_RNNModel(self, model_name: str): def helper_create_DLinearModel( self, + work_dir: Optional[str] = None, model_name: str = "unitest_model", add_encoders: Optional[Dict] = None, save_checkpoints: bool = False, @@ -1437,7 +1439,7 @@ def helper_create_DLinearModel( output_chunk_length=1, model_name=model_name, add_encoders=add_encoders, - work_dir=self.temp_work_dir, + work_dir=work_dir, save_checkpoints=save_checkpoints, random_state=42, force_reset=True, diff --git a/darts/tests/models/forecasting/test_transformer_model.py b/darts/tests/models/forecasting/test_transformer_model.py index cd65284d22..3b4958411c 100644 --- a/darts/tests/models/forecasting/test_transformer_model.py +++ b/darts/tests/models/forecasting/test_transformer_model.py @@ -1,11 +1,10 @@ -import shutil -import tempfile - +import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass, tfm_kwargs +from darts.tests.conftest import tfm_kwargs from darts.utils import timeseries_generation as tg logger = get_logger(__name__) @@ -30,8 +29,7 @@ if TORCH_AVAILABLE: - class TransformerModelTestCase(DartsBaseTestClass): - __test__ = True + class TestTransformerModel: times = pd.date_range("20130101", "20130410") pd_series = pd.Series(range(100), index=times) series: TimeSeries = TimeSeries.from_series(pd_series) @@ -54,20 +52,14 @@ class TransformerModelTestCase(DartsBaseTestClass): custom_decoder=None, ) - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - shutil.rmtree(self.temp_work_dir) - - def test_fit(self): + def test_fit(self, tmpdir_module): # Test fit-save-load cycle model2 = TransformerModel( input_chunk_length=1, output_chunk_length=1, n_epochs=2, model_name="unittest-model-transformer", - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, save_checkpoints=True, force_reset=True, **tfm_kwargs @@ -75,7 +67,7 @@ def test_fit(self): model2.fit(self.series) model_loaded = model2.load_from_checkpoint( model_name="unittest-model-transformer", - work_dir=self.temp_work_dir, + work_dir=tmpdir_module, best=False, map_location="cpu", ) @@ -83,7 +75,7 @@ def test_fit(self): pred2 = model_loaded.predict(n=6) # Two models with the same parameters should deterministically yield the same output - self.assertEqual(sum(pred1.values() - pred2.values()), 0.0) + np.testing.assert_array_equal(pred1.values(), pred2.values()) # Another random model should not model3 = TransformerModel( @@ -91,16 +83,16 @@ def test_fit(self): ) model3.fit(self.series) pred3 = model3.predict(n=6) - self.assertNotEqual(sum(pred1.values() - pred3.values()), 0.0) + assert not np.array_equal(pred1.values(), pred3.values()) # test short predict pred4 = model3.predict(n=1) - self.assertEqual(len(pred4), 1) + assert len(pred4) == 1 # test validation series input model3.fit(self.series[:60], val_series=self.series[60:]) pred4 = model3.predict(n=6) - self.assertEqual(len(pred4), 6) + assert len(pred4) == 6 def helper_test_pred_length(self, pytorch_model, series): model = pytorch_model( @@ -108,20 +100,20 @@ def helper_test_pred_length(self, pytorch_model, series): ) model.fit(series) pred = model.predict(7) - self.assertEqual(len(pred), 7) + assert len(pred) == 7 pred = model.predict(2) - self.assertEqual(len(pred), 2) - self.assertEqual(pred.width, 1) + assert len(pred) == 2 + assert pred.width == 1 pred = model.predict(4) - self.assertEqual(len(pred), 4) - self.assertEqual(pred.width, 1) + assert len(pred) == 4 + assert pred.width == 1 def test_pred_length(self): series = tg.linear_timeseries(length=100) self.helper_test_pred_length(TransformerModel, series) def test_activations(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model1 = TransformerModel( input_chunk_length=1, output_chunk_length=1, @@ -201,7 +193,7 @@ def test_layer_norm(self): assert y0 != y3 assert y1 != y3 - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): model4 = base_model( input_chunk_length=1, output_chunk_length=1, diff --git a/darts/tests/test_logging.py b/darts/tests/test_logging.py index f27fce996d..b980dc9c0e 100644 --- a/darts/tests/test_logging.py +++ b/darts/tests/test_logging.py @@ -1,9 +1,9 @@ import logging import re -import unittest import numpy as np import pandas as pd +import pytest import xarray as xr from testfixtures import LogCapture @@ -11,98 +11,103 @@ from darts.logging import get_logger, raise_if_not, raise_log, time_log -class LoggingTestCase(unittest.TestCase): - @classmethod - def setUpClass(cls): - logging.disable(logging.NOTSET) - - def test_raise_log(self): - exception_was_raised = False - with LogCapture() as lc: - logger = get_logger(__name__) - logger.handlers = [] - try: - raise_log(Exception("test"), logger) - except Exception: - exception_was_raised = True - - # testing correct log message - lc.check((__name__, "ERROR", "Exception: test")) - - # checking whether exception was properly raised - self.assertTrue(exception_was_raised) - - def test_raise_if_not(self): - exception_was_raised = False - with LogCapture() as lc: - logger = get_logger(__name__) - logger.handlers = [] - try: - raise_if_not(True, "test", logger) - raise_if_not(False, "test", logger) - except Exception: - exception_was_raised = True - - # testing correct log message - lc.check((__name__, "ERROR", "ValueError: test")) - - # checking whether exception was properly raised - self.assertTrue(exception_was_raised) - - def test_timeseries_constructor_error_log(self): - # test assert error log when trying to construct a TimeSeries that is too short - empty_series = pd.DataFrame() - with LogCapture() as lc: - get_logger("darts.timeseries").handlers = [] - try: - TimeSeries(xr.DataArray(empty_series)) - except Exception: - pass - - lc.check( - ( - "darts.timeseries", - "ERROR", - "ValueError: TimeSeries require DataArray of dimensionality 3 (('time', 'component', 'sample')).", - ) - ) +@pytest.fixture(scope="module", autouse=True) +def setup_logging(): + logging.disable(logging.NOTSET) - def test_timeseries_split_error_log(self): - # test raised error log that occurs when trying to split TimeSeries at a point outside of the time index range - times = pd.date_range(start="2000-01-01", periods=3, freq="D") - values = np.array(range(3)) - ts = TimeSeries.from_times_and_values(times, values) - with LogCapture() as lc: - get_logger("darts.timeseries").handlers = [] - try: - ts.split_after(pd.Timestamp("2020-02-01")) - except Exception: - pass - - lc.check( - ( - "darts.timeseries", - "ERROR", - "ValueError: Timestamp must be between 2000-01-01 00:00:00 and 2000-01-03 00:00:00", - ) - ) - def test_time_log(self): +def test_raise_log(): + exception_was_raised = False + with LogCapture() as lc: + logger = get_logger(__name__) + logger.handlers = [] + try: + raise_log(Exception("test"), logger) + except Exception: + exception_was_raised = True + + # testing correct log message + lc.check((__name__, "ERROR", "Exception: test")) + + # checking whether exception was properly raised + assert exception_was_raised + + +def test_raise_if_not(): + exception_was_raised = False + with LogCapture() as lc: logger = get_logger(__name__) logger.handlers = [] + try: + raise_if_not(True, "test", logger) + raise_if_not(False, "test", logger) + except Exception: + exception_was_raised = True + + # testing correct log message + lc.check((__name__, "ERROR", "ValueError: test")) + + # checking whether exception was properly raised + assert exception_was_raised + + +def test_timeseries_constructor_error_log(): + # test assert error log when trying to construct a TimeSeries that is too short + empty_series = pd.DataFrame() + with LogCapture() as lc: + get_logger("darts.timeseries").handlers = [] + try: + TimeSeries(xr.DataArray(empty_series)) + except Exception: + pass + + lc.check( + ( + "darts.timeseries", + "ERROR", + "ValueError: TimeSeries require DataArray of dimensionality 3 (('time', 'component', 'sample')).", + ) + ) + + +def test_timeseries_split_error_log(): + # test raised error log that occurs when trying to split TimeSeries at a point outside of the time index range + times = pd.date_range(start="2000-01-01", periods=3, freq="D") + values = np.array(range(3)) + ts = TimeSeries.from_times_and_values(times, values) + with LogCapture() as lc: + get_logger("darts.timeseries").handlers = [] + try: + ts.split_after(pd.Timestamp("2020-02-01")) + except Exception: + pass + + lc.check( + ( + "darts.timeseries", + "ERROR", + "ValueError: Timestamp must be between 2000-01-01 00:00:00 and 2000-01-03 00:00:00", + ) + ) + + +def test_time_log(): + logger = get_logger(__name__) + logger.handlers = [] - @time_log(logger) - def _my_timed_fn(): - # do something for some time - for _ in range(2): - pass + @time_log(logger) + def _my_timed_fn(): + # do something for some time + for _ in range(2): + pass - with LogCapture() as lc: - _my_timed_fn() + with LogCapture() as lc: + _my_timed_fn() - logged_message = lc.records[-1].getMessage() - self.assertTrue( - re.match( - "_my_timed_fn function ran for [0-9]+ milliseconds", logged_message - ) + logged_message = lc.records[-1].getMessage() + assert ( + re.fullmatch( + "_my_timed_fn function ran for [0-9]+ milliseconds", logged_message ) + is not None + ) diff --git a/darts/tests/test_timeseries.py b/darts/tests/test_timeseries.py index f2e0eeccef..8981c21d84 100644 --- a/darts/tests/test_timeseries.py +++ b/darts/tests/test_timeseries.py @@ -9,7 +9,6 @@ from scipy.stats import kurtosis, skew from darts import TimeSeries, concatenate -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.timeseries_generation import ( constant_timeseries, generate_index, @@ -17,7 +16,7 @@ ) -class TimeSeriesTestCase(DartsBaseTestClass): +class TestTimeSeries: times = pd.date_range("20130101", "20130110", freq="D") pd_series1 = pd.Series(range(10), index=times) @@ -29,7 +28,7 @@ class TimeSeriesTestCase(DartsBaseTestClass): def test_creation(self): series_test = TimeSeries.from_series(self.pd_series1) - self.assertTrue(series_test.pd_series().equals(self.pd_series1.astype(float))) + assert series_test.pd_series().equals(self.pd_series1.astype(float)) # Creation with a well formed array: ar = xr.DataArray( @@ -39,7 +38,7 @@ def test_creation(self): name="time series", ) ts = TimeSeries(ar) - self.assertTrue(ts.is_stochastic) + assert ts.is_stochastic ar = xr.DataArray( np.random.randn(10, 2, 1), @@ -48,10 +47,10 @@ def test_creation(self): name="time series", ) ts = TimeSeries(ar) - self.assertTrue(ts.is_deterministic) + assert ts.is_deterministic # creation with ill-formed arrays - with self.assertRaises(ValueError): + with pytest.raises(ValueError): ar2 = xr.DataArray( np.random.randn(10, 2, 1), dims=("time", "wrong", "sample"), @@ -60,7 +59,7 @@ def test_creation(self): ) _ = TimeSeries(ar2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # duplicated column names ar3 = xr.DataArray( np.random.randn(10, 2, 1), @@ -86,29 +85,23 @@ def test_integer_range_indexing(self): ) series_int: TimeSeries = TimeSeries.from_values(range_indexed_data) - self.assertTrue(series_int[0].values().item() == range_indexed_data[0]) - self.assertTrue(series_int[10].values().item() == range_indexed_data[10]) + assert series_int[0].values().item() == range_indexed_data[0] + assert series_int[10].values().item() == range_indexed_data[10] - self.assertTrue( - np.all(series_int[10:20].univariate_values() == range_indexed_data[10:20]) - ) - self.assertTrue( - np.all(series_int[10:].univariate_values() == range_indexed_data[10:]) + assert np.all( + series_int[10:20].univariate_values() == range_indexed_data[10:20] ) + assert np.all(series_int[10:].univariate_values() == range_indexed_data[10:]) - self.assertTrue( - np.all( - series_int[pd.RangeIndex(start=10, stop=40, step=1)].univariate_values() - == range_indexed_data[10:40] - ) + assert np.all( + series_int[pd.RangeIndex(start=10, stop=40, step=1)].univariate_values() + == range_indexed_data[10:40] ) # check the RangeIndex when indexing with a list indexed_ts = series_int[[2, 3, 4, 5, 6]] - self.assertTrue(isinstance(indexed_ts.time_index, pd.RangeIndex)) - self.assertTrue( - list(indexed_ts.time_index) == list(pd.RangeIndex(2, 7, step=1)) - ) + assert isinstance(indexed_ts.time_index, pd.RangeIndex) + assert list(indexed_ts.time_index) == list(pd.RangeIndex(2, 7, step=1)) # check integer indexing features when series index does not start at 0 values = np.random.random(100) @@ -116,16 +109,16 @@ def test_integer_range_indexing(self): series: TimeSeries = TimeSeries.from_times_and_values(times, values) # getting index for idx should return i s.t., series[i].time == idx - self.assertEqual(series.get_index_at_point(101), 91) + assert series.get_index_at_point(101) == 91 # slicing outside of the index range should return an empty ts - self.assertEqual(len(series[120:125]), 0) - self.assertEqual(series[120:125], series.slice(120, 125)) + assert len(series[120:125]) == 0 + assert series[120:125] == series.slice(120, 125) # slicing with a partial index overlap should return the ts subset - self.assertEqual(len(series[95:105]), 5) + assert len(series[95:105]) == 5 # adding the 10 values index shift to compare the same values - self.assertEqual(series[95:105], series.slice(105, 115)) + assert series[95:105] == series.slice(105, 115) # check integer indexing features when series index starts at 0 with a step > 1 values = np.random.random(100) @@ -133,24 +126,24 @@ def test_integer_range_indexing(self): series: TimeSeries = TimeSeries.from_times_and_values(times, values) # getting index for idx should return i s.t., series[i].time == idx - self.assertEqual(series.get_index_at_point(100), 50) + assert series.get_index_at_point(100) == 50 # getting index outside of the index range should raise an exception - with self.assertRaises(IndexError): + with pytest.raises(IndexError): series[100] # slicing should act the same irrespective of the initial time stamp np.testing.assert_equal(series[10:20].values().flatten(), values[10:20]) # slicing outside of the range should return an empty ts - self.assertEqual(len(series[105:110]), 0) + assert len(series[105:110]) == 0 # multiply the slice start and end values by 2 to compare the same values - self.assertEqual(series[105:110], series.slice(210, 220)) + assert series[105:110] == series.slice(210, 220) # slicing with an index overlap should return the ts subset - self.assertEqual(len(series[95:105]), 5) + assert len(series[95:105]) == 5 # multiply the slice start and end values by 2 to compare the same values - self.assertEqual(series[95:105], series.slice(190, 210)) + assert series[95:105] == series.slice(190, 210) # drop_after should act on the timestamp np.testing.assert_equal(series.drop_after(20).values().flatten(), values[:10]) @@ -161,7 +154,7 @@ def test_integer_range_indexing(self): series: TimeSeries = TimeSeries.from_times_and_values(times, values) # getting index for idx should return i s.t., series[i].time == idx - self.assertEqual(series.get_index_at_point(16), 3) + assert series.get_index_at_point(16) == 3 def test_integer_indexing(self): n = 10 @@ -218,26 +211,24 @@ def test_datetime_indexing(self): # https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html # getting index outside of the index range should raise an exception - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.series1[pd.Timestamp("20130111")] # slicing outside of the range should return an empty ts - self.assertEqual( - len(self.series1[pd.Timestamp("20130111") : pd.Timestamp("20130115")]), 0 - ) - self.assertEqual( - self.series1[pd.Timestamp("20130111") : pd.Timestamp("20130115")], - self.series1.slice(pd.Timestamp("20130111"), pd.Timestamp("20130115")), + assert ( + len(self.series1[pd.Timestamp("20130111") : pd.Timestamp("20130115")]) == 0 ) + assert self.series1[ + pd.Timestamp("20130111") : pd.Timestamp("20130115") + ] == self.series1.slice(pd.Timestamp("20130111"), pd.Timestamp("20130115")) # slicing with an partial index overlap should return the ts subset (start and end included) - self.assertEqual( - len(self.series1[pd.Timestamp("20130105") : pd.Timestamp("20130112")]), 6 - ) - self.assertEqual( - self.series1[pd.Timestamp("20130105") : pd.Timestamp("20130112")], - self.series1.slice(pd.Timestamp("20130105"), pd.Timestamp("20130112")), + assert ( + len(self.series1[pd.Timestamp("20130105") : pd.Timestamp("20130112")]) == 6 ) + assert self.series1[ + pd.Timestamp("20130105") : pd.Timestamp("20130112") + ] == self.series1.slice(pd.Timestamp("20130105"), pd.Timestamp("20130112")) def test_univariate_component(self): series = TimeSeries.from_values(np.array([10, 20, 30])).with_columns_renamed( @@ -259,10 +250,10 @@ def test_univariate_component(self): mseries.univariate_component("component_1"), ]: # hierarchy should be dropped - self.assertIsNone(univ_series.hierarchy) + assert univ_series.hierarchy is None # only the right static covariate column should be retained - self.assertEqual(univ_series.static_covariates.sum().sum(), 1.1) + assert univ_series.static_covariates.sum().sum() == 1.1 def test_column_names(self): # test the column names resolution @@ -287,7 +278,7 @@ def test_column_names(self): coords={"time": self.times, "component": cs_before}, ) ts = TimeSeries.from_xarray(ar) - self.assertEqual(ts.columns.tolist(), cs_after) + assert ts.columns.tolist() == cs_after def test_quantiles(self): values = np.random.rand(10, 2, 1000) @@ -300,9 +291,7 @@ def test_quantiles(self): for q in [0.01, 0.1, 0.5, 0.95]: q_ts = ts.quantile_timeseries(quantile=q) - self.assertTrue( - (abs(q_ts.values() - np.quantile(values, q=q, axis=2)) < 1e-3).all() - ) + assert (abs(q_ts.values() - np.quantile(values, q=q, axis=2)) < 1e-3).all() def test_quantiles_df(self): q = (0.01, 0.1, 0.5, 0.95) @@ -316,23 +305,20 @@ def test_quantiles_df(self): q_ts = ts.quantiles_df(q) for col in q_ts: q = float(str(col).replace("a_", "")) - self.assertTrue( - abs( - q_ts[col].to_numpy().reshape(10, 1) - - np.quantile(values, q=q, axis=2) - < 1e-3 - ).all() - ) + assert abs( + q_ts[col].to_numpy().reshape(10, 1) - np.quantile(values, q=q, axis=2) + < 1e-3 + ).all() def test_alt_creation(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # Series cannot be lower than three without passing frequency as argument to constructor, # if fill_missing_dates is True (otherwise it works) index = pd.date_range("20130101", "20130102") TimeSeries.from_times_and_values( index, self.pd_series1.values[:2], fill_missing_dates=True ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # all arrays must have same length TimeSeries.from_times_and_values( self.pd_series1.index, self.pd_series1.values[:-1] @@ -345,47 +331,47 @@ def test_alt_creation(self): index, self.pd_series1.values[rand_perm - 1] ) - self.assertTrue(series_test.start_time() == pd.to_datetime("20130101")) - self.assertTrue(series_test.end_time() == pd.to_datetime("20130110")) - self.assertTrue(all(series_test.pd_series().values == self.pd_series1.values)) - self.assertTrue(series_test.freq == self.series1.freq) + assert series_test.start_time() == pd.to_datetime("20130101") + assert series_test.end_time() == pd.to_datetime("20130110") + assert all(series_test.pd_series().values == self.pd_series1.values) + assert series_test.freq == self.series1.freq # TODO test over to_dataframe when multiple features choice is decided def test_eq(self): seriesA: TimeSeries = TimeSeries.from_series(self.pd_series1) - self.assertTrue(self.series1 == seriesA) - self.assertFalse(self.series1 != seriesA) + assert self.series1 == seriesA + assert not (self.series1 != seriesA) # with different dates seriesC = TimeSeries.from_series( pd.Series(range(10), index=pd.date_range("20130102", "20130111")) ) - self.assertFalse(self.series1 == seriesC) + assert not (self.series1 == seriesC) def test_dates(self): - self.assertEqual(self.series1.start_time(), pd.Timestamp("20130101")) - self.assertEqual(self.series1.end_time(), pd.Timestamp("20130110")) - self.assertEqual(self.series1.duration, pd.Timedelta(days=9)) + assert self.series1.start_time() == pd.Timestamp("20130101") + assert self.series1.end_time() == pd.Timestamp("20130110") + assert self.series1.duration == pd.Timedelta(days=9) @staticmethod def helper_test_slice(test_case, test_series: TimeSeries): # base case seriesA = test_series.slice(pd.Timestamp("20130104"), pd.Timestamp("20130107")) - test_case.assertEqual(seriesA.start_time(), pd.Timestamp("20130104")) - test_case.assertEqual(seriesA.end_time(), pd.Timestamp("20130107")) + assert seriesA.start_time() == pd.Timestamp("20130104") + assert seriesA.end_time() == pd.Timestamp("20130107") # time stamp not in series seriesB = test_series.slice( pd.Timestamp("20130104 12:00:00"), pd.Timestamp("20130107") ) - test_case.assertEqual(seriesB.start_time(), pd.Timestamp("20130105")) - test_case.assertEqual(seriesB.end_time(), pd.Timestamp("20130107")) + assert seriesB.start_time() == pd.Timestamp("20130105") + assert seriesB.end_time() == pd.Timestamp("20130107") # end timestamp after series seriesC = test_series.slice(pd.Timestamp("20130108"), pd.Timestamp("20130201")) - test_case.assertEqual(seriesC.start_time(), pd.Timestamp("20130108")) - test_case.assertEqual(seriesC.end_time(), pd.Timestamp("20130110")) + assert seriesC.start_time() == pd.Timestamp("20130108") + assert seriesC.end_time() == pd.Timestamp("20130110") # integer-indexed series, starting at 0 values = np.random.rand(30) @@ -419,34 +405,34 @@ def helper_test_slice(test_case, test_series: TimeSeries): # n points, base case seriesD = test_series.slice_n_points_after(pd.Timestamp("20130102"), n=3) - test_case.assertEqual(seriesD.start_time(), pd.Timestamp("20130102")) - test_case.assertTrue(len(seriesD.values()) == 3) - test_case.assertEqual(seriesD.end_time(), pd.Timestamp("20130104")) + assert seriesD.start_time() == pd.Timestamp("20130102") + assert len(seriesD.values()) == 3 + assert seriesD.end_time() == pd.Timestamp("20130104") seriesE = test_series.slice_n_points_after( pd.Timestamp("20130107 12:00:10"), n=10 ) - test_case.assertEqual(seriesE.start_time(), pd.Timestamp("20130108")) - test_case.assertEqual(seriesE.end_time(), pd.Timestamp("20130110")) + assert seriesE.start_time() == pd.Timestamp("20130108") + assert seriesE.end_time() == pd.Timestamp("20130110") seriesF = test_series.slice_n_points_before(pd.Timestamp("20130105"), n=3) - test_case.assertEqual(seriesF.end_time(), pd.Timestamp("20130105")) - test_case.assertTrue(len(seriesF.values()) == 3) - test_case.assertEqual(seriesF.start_time(), pd.Timestamp("20130103")) + assert seriesF.end_time() == pd.Timestamp("20130105") + assert len(seriesF.values()) == 3 + assert seriesF.start_time() == pd.Timestamp("20130103") seriesG = test_series.slice_n_points_before( pd.Timestamp("20130107 12:00:10"), n=10 ) - test_case.assertEqual(seriesG.start_time(), pd.Timestamp("20130101")) - test_case.assertEqual(seriesG.end_time(), pd.Timestamp("20130107")) + assert seriesG.start_time() == pd.Timestamp("20130101") + assert seriesG.end_time() == pd.Timestamp("20130107") # test slice_n_points_after and slice_n_points_before with integer-indexed series s = TimeSeries.from_times_and_values(pd.RangeIndex(6, 10), np.arange(16, 20)) sliced_idx = s.slice_n_points_after(7, 2).time_index - test_case.assertTrue(all(sliced_idx == pd.RangeIndex(7, 9))) + assert all(sliced_idx == pd.RangeIndex(7, 9)) sliced_idx = s.slice_n_points_before(8, 2).time_index - test_case.assertTrue(all(sliced_idx == pd.RangeIndex(7, 9))) + assert all(sliced_idx == pd.RangeIndex(7, 9)) # integer indexed series, step = 1, timestamps not in series values = np.random.rand(30) @@ -469,44 +455,44 @@ def helper_test_slice(test_case, test_series: TimeSeries): np.testing.assert_equal(slice_vals, values[6:15]) slice_ts = ts.slice(40, 60) - test_case.assertEqual(ts.end_time(), slice_ts.end_time()) + assert ts.end_time() == slice_ts.end_time() @staticmethod def helper_test_split(test_case, test_series: TimeSeries): seriesA, seriesB = test_series.split_after(pd.Timestamp("20130104")) - test_case.assertEqual(seriesA.end_time(), pd.Timestamp("20130104")) - test_case.assertEqual(seriesB.start_time(), pd.Timestamp("20130105")) + assert seriesA.end_time() == pd.Timestamp("20130104") + assert seriesB.start_time() == pd.Timestamp("20130105") seriesC, seriesD = test_series.split_before(pd.Timestamp("20130104")) - test_case.assertEqual(seriesC.end_time(), pd.Timestamp("20130103")) - test_case.assertEqual(seriesD.start_time(), pd.Timestamp("20130104")) + assert seriesC.end_time() == pd.Timestamp("20130103") + assert seriesD.start_time() == pd.Timestamp("20130104") seriesE, seriesF = test_series.split_after(0.7) - test_case.assertEqual(len(seriesE), round(0.7 * len(test_series))) - test_case.assertEqual(len(seriesF), round(0.3 * len(test_series))) + assert len(seriesE) == round(0.7 * len(test_series)) + assert len(seriesF) == round(0.3 * len(test_series)) seriesG, seriesH = test_series.split_before(0.7) - test_case.assertEqual(len(seriesG), round(0.7 * len(test_series)) - 1) - test_case.assertEqual(len(seriesH), round(0.3 * len(test_series)) + 1) + assert len(seriesG) == round(0.7 * len(test_series)) - 1 + assert len(seriesH) == round(0.3 * len(test_series)) + 1 seriesI, seriesJ = test_series.split_after(5) - test_case.assertEqual(len(seriesI), 6) - test_case.assertEqual(len(seriesJ), len(test_series) - 6) + assert len(seriesI) == 6 + assert len(seriesJ) == len(test_series) - 6 seriesK, seriesL = test_series.split_before(5) - test_case.assertEqual(len(seriesK), 5) - test_case.assertEqual(len(seriesL), len(test_series) - 5) + assert len(seriesK) == 5 + assert len(seriesL) == len(test_series) - 5 - test_case.assertEqual(test_series.freq_str, seriesA.freq_str) - test_case.assertEqual(test_series.freq_str, seriesC.freq_str) - test_case.assertEqual(test_series.freq_str, seriesE.freq_str) - test_case.assertEqual(test_series.freq_str, seriesG.freq_str) - test_case.assertEqual(test_series.freq_str, seriesI.freq_str) - test_case.assertEqual(test_series.freq_str, seriesK.freq_str) + assert test_series.freq_str == seriesA.freq_str + assert test_series.freq_str == seriesC.freq_str + assert test_series.freq_str == seriesE.freq_str + assert test_series.freq_str == seriesG.freq_str + assert test_series.freq_str == seriesI.freq_str + assert test_series.freq_str == seriesK.freq_str # Test split points outside of range for value in [-5, 1.1, pd.Timestamp("21300104")]: - with test_case.assertRaises(ValueError): + with pytest.raises(ValueError): test_series.split_before(value) # Test split points between series indeces @@ -516,27 +502,23 @@ def helper_test_split(test_case, test_series: TimeSeries): split_date = pd.Timestamp("20130110") seriesM, seriesN = test_series2.split_before(split_date) seriesO, seriesP = test_series2.split_after(split_date) - test_case.assertLess(seriesM.end_time(), split_date) - test_case.assertGreaterEqual(seriesN.start_time(), split_date) - test_case.assertLessEqual(seriesO.end_time(), split_date) - test_case.assertGreater(seriesP.start_time(), split_date) + assert seriesM.end_time() < split_date + assert seriesN.start_time() >= split_date + assert seriesO.end_time() <= split_date + assert seriesP.start_time() > split_date @staticmethod def helper_test_drop(test_case, test_series: TimeSeries): seriesA = test_series.drop_after(pd.Timestamp("20130105")) - test_case.assertEqual( - seriesA.end_time(), pd.Timestamp("20130105") - test_series.freq - ) - test_case.assertTrue(np.all(seriesA.time_index < pd.Timestamp("20130105"))) + assert seriesA.end_time() == pd.Timestamp("20130105") - test_series.freq + assert np.all(seriesA.time_index < pd.Timestamp("20130105")) seriesB = test_series.drop_before(pd.Timestamp("20130105")) - test_case.assertEqual( - seriesB.start_time(), pd.Timestamp("20130105") + test_series.freq - ) - test_case.assertTrue(np.all(seriesB.time_index > pd.Timestamp("20130105"))) + assert seriesB.start_time() == pd.Timestamp("20130105") + test_series.freq + assert np.all(seriesB.time_index > pd.Timestamp("20130105")) - test_case.assertEqual(test_series.freq_str, seriesA.freq_str) - test_case.assertEqual(test_series.freq_str, seriesB.freq_str) + assert test_series.freq_str == seriesA.freq_str + assert test_series.freq_str == seriesB.freq_str @staticmethod def helper_test_intersect(test_case, test_series: TimeSeries): @@ -545,8 +527,8 @@ def helper_test_intersect(test_case, test_series: TimeSeries): ) seriesB = test_series.slice_intersect(seriesA) - test_case.assertEqual(seriesB.start_time(), pd.Timestamp("20130102")) - test_case.assertEqual(seriesB.end_time(), pd.Timestamp("20130107")) + assert seriesB.start_time() == pd.Timestamp("20130102") + assert seriesB.end_time() == pd.Timestamp("20130107") # Outside of range seriesD = test_series.slice_intersect( @@ -554,8 +536,8 @@ def helper_test_intersect(test_case, test_series: TimeSeries): pd.Series(range(6, 13), index=pd.date_range("20130106", "20130112")) ) ) - test_case.assertEqual(seriesD.start_time(), pd.Timestamp("20130106")) - test_case.assertEqual(seriesD.end_time(), pd.Timestamp("20130110")) + assert seriesD.start_time() == pd.Timestamp("20130106") + assert seriesD.end_time() == pd.Timestamp("20130110") # Small intersect seriesE = test_series.slice_intersect( @@ -563,10 +545,10 @@ def helper_test_intersect(test_case, test_series: TimeSeries): pd.Series(range(9, 13), index=pd.date_range("20130109", "20130112")) ) ) - test_case.assertEqual(len(seriesE), 2) + assert len(seriesE) == 2 # No intersect - with test_case.assertRaises(ValueError): + with pytest.raises(ValueError): test_series.slice_intersect( TimeSeries( pd.Series(range(6, 13), index=pd.date_range("20130116", "20130122")) @@ -574,86 +556,80 @@ def helper_test_intersect(test_case, test_series: TimeSeries): ) def test_rescale(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.series1.rescale_with_value(1) seriesA = self.series3.rescale_with_value(0) - self.assertTrue(np.all(seriesA.values() == 0)) + assert np.all(seriesA.values() == 0) seriesB = self.series3.rescale_with_value(-5) - self.assertTrue(self.series3 * -1.0 == seriesB) + assert self.series3 * -1.0 == seriesB seriesC = self.series3.rescale_with_value(1) - self.assertTrue(self.series3 * 0.2 == seriesC) + assert self.series3 * 0.2 == seriesC seriesD = self.series3.rescale_with_value( 1e20 ) # TODO: test will fail if value > 1e24 due to num imprecision - self.assertTrue(self.series3 * 0.2e20 == seriesD) + assert self.series3 * 0.2e20 == seriesD @staticmethod def helper_test_shift(test_case, test_series: TimeSeries): seriesA = test_case.series1.shift(0) - test_case.assertTrue(seriesA == test_case.series1) + assert seriesA == test_case.series1 seriesB = test_series.shift(1) - test_case.assertTrue( - seriesB.time_index.equals( - test_series.time_index[1:].append( - pd.DatetimeIndex([test_series.time_index[-1] + test_series.freq]) - ) + assert seriesB.time_index.equals( + test_series.time_index[1:].append( + pd.DatetimeIndex([test_series.time_index[-1] + test_series.freq]) ) ) seriesC = test_series.shift(-1) - test_case.assertTrue( - seriesC.time_index.equals( - pd.DatetimeIndex([test_series.time_index[0] - test_series.freq]).append( - test_series.time_index[:-1] - ) + assert seriesC.time_index.equals( + pd.DatetimeIndex([test_series.time_index[0] - test_series.freq]).append( + test_series.time_index[:-1] ) ) - with test_case.assertRaises(Exception): + with pytest.raises(Exception): test_series.shift(1e6) seriesM = TimeSeries.from_times_and_values( pd.date_range("20130101", "20130601", freq="m"), range(5) ) - with test_case.assertRaises(OverflowError): + with pytest.raises(OverflowError): seriesM.shift(1e4) seriesD = TimeSeries.from_times_and_values( pd.date_range("20130101", "20130101"), range(1), freq="D" ) seriesE = seriesD.shift(1) - test_case.assertEqual(seriesE.time_index[0], pd.Timestamp("20130102")) + assert seriesE.time_index[0] == pd.Timestamp("20130102") seriesF = TimeSeries.from_times_and_values(pd.RangeIndex(2, 10), range(8)) seriesG = seriesF.shift(4) - test_case.assertEqual(seriesG.time_index[0], 6) + assert seriesG.time_index[0] == 6 @staticmethod def helper_test_append(test_case, test_series: TimeSeries): # reconstruct series seriesA, seriesB = test_series.split_after(pd.Timestamp("20130106")) - test_case.assertEqual(seriesA.append(seriesB), test_series) - test_case.assertEqual(seriesA.append(seriesB).freq, test_series.freq) - test_case.assertTrue( - test_series.time_index.equals(seriesA.append(seriesB).time_index) - ) + assert seriesA.append(seriesB) == test_series + assert seriesA.append(seriesB).freq == test_series.freq + assert test_series.time_index.equals(seriesA.append(seriesB).time_index) # Creating a gap is not allowed seriesC = test_series.drop_before(pd.Timestamp("20130108")) - with test_case.assertRaises(ValueError): + with pytest.raises(ValueError): seriesA.append(seriesC) # Changing frequency is not allowed seriesM = TimeSeries.from_times_and_values( pd.date_range("20130107", "20130507", freq="30D"), range(5) ) - with test_case.assertRaises(ValueError): + with pytest.raises(ValueError): seriesA.append(seriesM) @staticmethod @@ -661,40 +637,34 @@ def helper_test_append_values(test_case, test_series: TimeSeries): # reconstruct series seriesA, seriesB = test_series.split_after(pd.Timestamp("20130106")) arrayB = seriesB.all_values() - test_case.assertEqual(seriesA.append_values(arrayB), test_series) - test_case.assertTrue( - test_series.time_index.equals(seriesA.append_values(arrayB).time_index) - ) + assert seriesA.append_values(arrayB) == test_series + assert test_series.time_index.equals(seriesA.append_values(arrayB).time_index) # arrayB shape shouldn't affect append_values output: squeezed_arrayB = arrayB.squeeze() - test_case.assertEqual(seriesA.append_values(squeezed_arrayB), test_series) - test_case.assertTrue( - test_series.time_index.equals( - seriesA.append_values(squeezed_arrayB).time_index - ) + assert seriesA.append_values(squeezed_arrayB) == test_series + assert test_series.time_index.equals( + seriesA.append_values(squeezed_arrayB).time_index ) @staticmethod def helper_test_prepend(test_case, test_series: TimeSeries): # reconstruct series seriesA, seriesB = test_series.split_after(pd.Timestamp("20130106")) - test_case.assertEqual(seriesB.prepend(seriesA), test_series) - test_case.assertEqual(seriesB.prepend(seriesA).freq, test_series.freq) - test_case.assertTrue( - test_series.time_index.equals(seriesB.prepend(seriesA).time_index) - ) + assert seriesB.prepend(seriesA) == test_series + assert seriesB.prepend(seriesA).freq == test_series.freq + assert test_series.time_index.equals(seriesB.prepend(seriesA).time_index) # Creating a gap is not allowed seriesC = test_series.drop_before(pd.Timestamp("20130108")) - with test_case.assertRaises(ValueError): + with pytest.raises(ValueError): seriesC.prepend(seriesA) # Changing frequency is not allowed seriesM = TimeSeries.from_times_and_values( pd.date_range("20130107", "20130507", freq="30D"), range(5) ) - with test_case.assertRaises(ValueError): + with pytest.raises(ValueError): seriesM.prepend(seriesA) @staticmethod @@ -702,37 +672,33 @@ def helper_test_prepend_values(test_case, test_series: TimeSeries): # reconstruct series seriesA, seriesB = test_series.split_after(pd.Timestamp("20130106")) arrayA = seriesA.data_array().values - test_case.assertEqual(seriesB.prepend_values(arrayA), test_series) - test_case.assertTrue( - test_series.time_index.equals(seriesB.prepend_values(arrayA).time_index) - ) + assert seriesB.prepend_values(arrayA) == test_series + assert test_series.time_index.equals(seriesB.prepend_values(arrayA).time_index) # arrayB shape shouldn't affect append_values output: squeezed_arrayA = arrayA.squeeze() - test_case.assertEqual(seriesB.prepend_values(squeezed_arrayA), test_series) - test_case.assertTrue( - test_series.time_index.equals( - seriesB.prepend_values(squeezed_arrayA).time_index - ) + assert seriesB.prepend_values(squeezed_arrayA) == test_series + assert test_series.time_index.equals( + seriesB.prepend_values(squeezed_arrayA).time_index ) def test_slice(self): - TimeSeriesTestCase.helper_test_slice(self, self.series1) + TestTimeSeries.helper_test_slice(self, self.series1) def test_split(self): - TimeSeriesTestCase.helper_test_split(self, self.series1) + TestTimeSeries.helper_test_split(self, self.series1) def test_drop(self): - TimeSeriesTestCase.helper_test_drop(self, self.series1) + TestTimeSeries.helper_test_drop(self, self.series1) def test_intersect(self): - TimeSeriesTestCase.helper_test_intersect(self, self.series1) + TestTimeSeries.helper_test_intersect(self, self.series1) def test_shift(self): - TimeSeriesTestCase.helper_test_shift(self, self.series1) + TestTimeSeries.helper_test_shift(self, self.series1) def test_append(self): - TimeSeriesTestCase.helper_test_append(self, self.series1) + TestTimeSeries.helper_test_append(self, self.series1) # Check `append` deals with `RangeIndex` series correctly: series_1 = linear_timeseries(start=1, length=5, freq=2) series_2 = linear_timeseries(start=11, length=2, freq=2) @@ -741,11 +707,11 @@ def test_append(self): [series_1.all_values(), series_2.all_values()], axis=0 ) expected_idx = pd.RangeIndex(start=1, stop=15, step=2) - self.assertTrue(np.allclose(appended.all_values(), expected_vals)) - self.assertTrue(appended.time_index.equals(expected_idx)) + assert np.allclose(appended.all_values(), expected_vals) + assert appended.time_index.equals(expected_idx) def test_append_values(self): - TimeSeriesTestCase.helper_test_append_values(self, self.series1) + TestTimeSeries.helper_test_append_values(self, self.series1) # Check `append_values` deals with `RangeIndex` series correctly: series = linear_timeseries(start=1, length=5, freq=2) appended = series.append_values(np.ones((2, 1, 1))) @@ -753,11 +719,11 @@ def test_append_values(self): [series.all_values(), np.ones((2, 1, 1))], axis=0 ) expected_idx = pd.RangeIndex(start=1, stop=15, step=2) - self.assertTrue(np.allclose(appended.all_values(), expected_vals)) - self.assertTrue(appended.time_index.equals(expected_idx)) + assert np.allclose(appended.all_values(), expected_vals) + assert appended.time_index.equals(expected_idx) def test_prepend(self): - TimeSeriesTestCase.helper_test_prepend(self, self.series1) + TestTimeSeries.helper_test_prepend(self, self.series1) # Check `prepend` deals with `RangeIndex` series correctly: series_1 = linear_timeseries(start=1, length=5, freq=2) series_2 = linear_timeseries(start=11, length=2, freq=2) @@ -766,11 +732,11 @@ def test_prepend(self): [series_1.all_values(), series_2.all_values()], axis=0 ) expected_idx = pd.RangeIndex(start=1, stop=15, step=2) - self.assertTrue(np.allclose(prepended.all_values(), expected_vals)) - self.assertTrue(prepended.time_index.equals(expected_idx)) + assert np.allclose(prepended.all_values(), expected_vals) + assert prepended.time_index.equals(expected_idx) def test_prepend_values(self): - TimeSeriesTestCase.helper_test_prepend_values(self, self.series1) + TestTimeSeries.helper_test_prepend_values(self, self.series1) # Check `prepend_values` deals with `RangeIndex` series correctly: series = linear_timeseries(start=1, length=5, freq=2) prepended = series.prepend_values(np.ones((2, 1, 1))) @@ -778,8 +744,8 @@ def test_prepend_values(self): [np.ones((2, 1, 1)), series.all_values()], axis=0 ) expected_idx = pd.RangeIndex(start=-3, stop=11, step=2) - self.assertTrue(np.allclose(prepended.all_values(), expected_vals)) - self.assertTrue(prepended.time_index.equals(expected_idx)) + assert np.allclose(prepended.all_values(), expected_vals) + assert prepended.time_index.equals(expected_idx) def test_with_values(self): vals = np.random.rand(5, 10, 3) @@ -792,7 +758,7 @@ def test_with_values(self): np.testing.assert_allclose(series2.all_values(), vals + 1) # should fail if nr components is not the same: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): series.with_values(np.random.rand(5, 11, 3)) # should not fail if nr samples is not the same: @@ -815,21 +781,21 @@ def test_diff(self): diff_shift2.pd_dataframe().diff(periods=2) ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.series1.diff(n=0) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.series1.diff(n=-5) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.series1.diff(n=0.2) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.series1.diff(periods=0.2) - self.assertEqual(self.series1.diff(), diff1_no_na) - self.assertEqual(self.series1.diff(n=2, dropna=True), diff2_no_na) - self.assertEqual(self.series1.diff(dropna=False), diff1) - self.assertEqual(self.series1.diff(n=2, dropna=0), diff2) - self.assertEqual(self.series1.diff(periods=2, dropna=True), diff_shift2_no_na) - self.assertEqual(self.series1.diff(n=2, periods=2, dropna=False), diff2_shift2) + assert self.series1.diff() == diff1_no_na + assert self.series1.diff(n=2, dropna=True) == diff2_no_na + assert self.series1.diff(dropna=False) == diff1 + assert self.series1.diff(n=2, dropna=0) == diff2 + assert self.series1.diff(periods=2, dropna=True) == diff_shift2_no_na + assert self.series1.diff(n=2, periods=2, dropna=False) == diff2_shift2 def test_ops(self): seriesA = TimeSeries.from_series( @@ -851,47 +817,44 @@ def test_ops(self): pd.Series([float(i**2) for i in range(10)], index=self.pd_series1.index) ) - self.assertEqual(self.series1 + seriesA, targetAdd) - self.assertEqual(self.series1 + 2, targetAdd) - self.assertEqual(2 + self.series1, targetAdd) - self.assertEqual(self.series1 - seriesA, targetSub) - self.assertEqual(self.series1 - 2, targetSub) - self.assertEqual(self.series1 * seriesA, targetMul) - self.assertEqual(self.series1 * 2, targetMul) - self.assertEqual(2 * self.series1, targetMul) - self.assertEqual(self.series1 / seriesA, targetDiv) - self.assertEqual(self.series1 / 2, targetDiv) - self.assertEqual(self.series1**2, targetPow) + assert self.series1 + seriesA == targetAdd + assert self.series1 + 2 == targetAdd + assert 2 + self.series1 == targetAdd + assert self.series1 - seriesA == targetSub + assert self.series1 - 2 == targetSub + assert self.series1 * seriesA == targetMul + assert self.series1 * 2 == targetMul + assert 2 * self.series1 == targetMul + assert self.series1 / seriesA == targetDiv + assert self.series1 / 2 == targetDiv + assert self.series1**2 == targetPow - with self.assertRaises(ZeroDivisionError): + with pytest.raises(ZeroDivisionError): # Cannot divide by a TimeSeries with a value 0. self.series1 / self.series1 - with self.assertRaises(ZeroDivisionError): + with pytest.raises(ZeroDivisionError): # Cannot divide by 0. self.series1 / 0 def test_getitem_datetime_index(self): seriesA: TimeSeries = self.series1.drop_after(pd.Timestamp("20130105")) - self.assertEqual(self.series1[pd.date_range("20130101", " 20130104")], seriesA) - self.assertEqual(self.series1[:4], seriesA) - self.assertTrue( - self.series1[pd.Timestamp("20130101")] - == TimeSeries.from_dataframe( - self.series1.pd_dataframe()[:1], freq=self.series1.freq - ) + assert self.series1[pd.date_range("20130101", " 20130104")] == seriesA + assert self.series1[:4] == seriesA + assert self.series1[pd.Timestamp("20130101")] == TimeSeries.from_dataframe( + self.series1.pd_dataframe()[:1], freq=self.series1.freq ) - self.assertEqual( - self.series1[pd.Timestamp("20130101") : pd.Timestamp("20130104")], seriesA + assert ( + self.series1[pd.Timestamp("20130101") : pd.Timestamp("20130104")] == seriesA ) - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.series1[pd.date_range("19990101", "19990201")] - with self.assertRaises(KeyError): + with pytest.raises(KeyError): self.series1["19990101"] - with self.assertRaises(IndexError): + with pytest.raises(IndexError): self.series1[::-1] def test_getitem_integer_index(self): @@ -939,7 +902,7 @@ def test_getitem_integer_index(self): _ = series[pd.RangeIndex(start, stop=end + 2 * freq, step=freq)] def test_fill_missing_dates(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # Series cannot have date holes without automatic filling range_ = pd.date_range("20130101", "20130104").append( pd.date_range("20130106", "20130110") @@ -948,7 +911,7 @@ def test_fill_missing_dates(self): pd.Series(range(9), index=range_), fill_missing_dates=False ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): # Main series should have explicit frequency in case of date holes range_ = pd.date_range("20130101", "20130104").append( pd.date_range("20130106", "20130110", freq="2D") @@ -963,7 +926,7 @@ def test_fill_missing_dates(self): series_test = TimeSeries.from_series( pd.Series(range(9), index=range_), fill_missing_dates=True ) - self.assertEqual(series_test.freq_str, "D") + assert series_test.freq_str == "D" range_ = pd.date_range("20130101", "20130104", freq="2D").append( pd.date_range("20130107", "20130111", freq="2D") @@ -971,10 +934,10 @@ def test_fill_missing_dates(self): series_test = TimeSeries.from_series( pd.Series(range(5), index=range_), fill_missing_dates=True ) - self.assertEqual(series_test.freq_str, "2D") - self.assertEqual(series_test.start_time(), range_[0]) - self.assertEqual(series_test.end_time(), range_[-1]) - self.assertTrue(math.isnan(series_test.pd_series().get("20130105"))) + assert series_test.freq_str == "2D" + assert series_test.start_time() == range_[0] + assert series_test.end_time() == range_[-1] + assert math.isnan(series_test.pd_series().get("20130105")) # ------ test infer frequency for all offset aliases from ------ # https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases @@ -1058,7 +1021,7 @@ def test_fill_missing_dates(self): # fill_missing_dates will find multiple inferred frequencies (i.e. for 'B' it finds {'B', 'D'}) if offset_alias in offset_aliases_raise: - with self.assertRaises(ValueError): + with pytest.raises(ValueError): _ = TimeSeries.from_dataframe( df, time_col="date", fill_missing_dates=True ) @@ -1077,8 +1040,8 @@ def test_fill_missing_dates(self): for series in [series_out_freq1, series_out_freq2, series_out_fill]: if df_name == "full": - self.assertTrue(series == series_target) - self.assertTrue(series.time_index.equals(series_target.time_index)) + assert series == series_target + assert series.time_index.equals(series_target.time_index) def test_fillna_value(self): range_ = pd.date_range("20130101", "20130108", freq="D") @@ -1098,15 +1061,15 @@ def test_fillna_value(self): ) for series_with_nan in [series_nan, series_holes]: - self.assertTrue(np.isnan(series_with_nan.all_values(copy=False)).any()) + assert np.isnan(series_with_nan.all_values(copy=False)).any() for series_no_nan in [ series_1, series_nan_fillna, series_1_fillna, series_holes_fillna, ]: - self.assertTrue(not np.isnan(series_no_nan.all_values(copy=False)).any()) - self.assertTrue(series_1 == series_no_nan) + assert not np.isnan(series_no_nan.all_values(copy=False)).any() + assert series_1 == series_no_nan def test_resample_timeseries(self): times = pd.date_range("20130101", "20130110") @@ -1114,28 +1077,18 @@ def test_resample_timeseries(self): timeseries = TimeSeries.from_series(pd_series) resampled_timeseries = timeseries.resample("H") - self.assertEqual(resampled_timeseries.freq_str, "H") - self.assertEqual( - resampled_timeseries.pd_series().at[pd.Timestamp("20130101020000")], 0 - ) - self.assertEqual( - resampled_timeseries.pd_series().at[pd.Timestamp("20130102020000")], 1 - ) - self.assertEqual( - resampled_timeseries.pd_series().at[pd.Timestamp("20130109090000")], 8 - ) + assert resampled_timeseries.freq_str == "H" + assert resampled_timeseries.pd_series().at[pd.Timestamp("20130101020000")] == 0 + assert resampled_timeseries.pd_series().at[pd.Timestamp("20130102020000")] == 1 + assert resampled_timeseries.pd_series().at[pd.Timestamp("20130109090000")] == 8 resampled_timeseries = timeseries.resample("2D") - self.assertEqual(resampled_timeseries.freq_str, "2D") - self.assertEqual( - resampled_timeseries.pd_series().at[pd.Timestamp("20130101")], 0 - ) - with self.assertRaises(KeyError): + assert resampled_timeseries.freq_str == "2D" + assert resampled_timeseries.pd_series().at[pd.Timestamp("20130101")] == 0 + with pytest.raises(KeyError): resampled_timeseries.pd_series().at[pd.Timestamp("20130102")] - self.assertEqual( - resampled_timeseries.pd_series().at[pd.Timestamp("20130109")], 8 - ) + assert resampled_timeseries.pd_series().at[pd.Timestamp("20130109")] == 8 # using offset to avoid nan in the first value times = pd.date_range( @@ -1146,20 +1099,18 @@ def test_resample_timeseries(self): resampled_timeseries = timeseries.resample( freq="1h", offset=pd.Timedelta("30T") ) - self.assertEqual( - resampled_timeseries.pd_series().at[pd.Timestamp("20200101233000")], 0 - ) + assert resampled_timeseries.pd_series().at[pd.Timestamp("20200101233000")] == 0 def test_short_series_creation(self): # test missing freq argument error when filling missing dates on short time series - with self.assertRaises(ValueError): + with pytest.raises(ValueError): TimeSeries.from_times_and_values( pd.date_range("20130101", "20130102"), range(2), fill_missing_dates=True ) # test empty pandas series with DatetimeIndex freq = "D" # fails without freq - with self.assertRaises(ValueError): + with pytest.raises(ValueError): TimeSeries.from_series(pd.Series(index=pd.DatetimeIndex([]))) # works with index having freq, or setting freq at TimeSeries creation series_a = TimeSeries.from_series( @@ -1175,7 +1126,7 @@ def test_short_series_creation(self): # test empty pandas series with DatetimeIndex freq = 2 # fails pd.Index (IntIndex) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): TimeSeries.from_series(pd.Series(index=pd.Index([]))) # works with pd.RangeIndex as freq (step) is given by default (step=1) series_a = TimeSeries.from_series(pd.Series(index=pd.RangeIndex(start=0))) @@ -1198,7 +1149,7 @@ def test_short_series_creation(self): fill_missing_dates=False, freq="M", ) - self.assertEqual(seriesA.freq, "D") + assert seriesA.freq == "D" # test successful instantiation of TimeSeries with length 2 TimeSeries.from_times_and_values( pd.date_range("20130101", "20130102"), range(2), freq="D" @@ -1231,7 +1182,7 @@ def test_from_csv(self): filepath_or_buffer=f2.name, time_col="Time", sep="." ) - self.assertEqual(data_darts1, data_darts2) + assert data_darts1 == data_darts2 def test_index_creation(self): times = pd.date_range(start="20210312", periods=15, freq="MS") @@ -1245,46 +1196,46 @@ def test_index_creation(self): series2 = pd.Series(values1, index=times) ts1 = TimeSeries.from_dataframe(df1) - self.assertTrue(ts1.has_range_index) + assert ts1.has_range_index ts2 = TimeSeries.from_dataframe(df2) - self.assertTrue(ts2.has_datetime_index) + assert ts2.has_datetime_index ts3 = TimeSeries.from_dataframe(df3, time_col="Time") - self.assertTrue(ts3.has_datetime_index) + assert ts3.has_datetime_index ts4 = TimeSeries.from_series(series1) - self.assertTrue(ts4.has_range_index) + assert ts4.has_range_index ts5 = TimeSeries.from_series(series2) - self.assertTrue(ts5.has_datetime_index) + assert ts5.has_datetime_index ts6 = TimeSeries.from_times_and_values(times=times, values=values1) - self.assertTrue(ts6.has_datetime_index) + assert ts6.has_datetime_index ts7 = TimeSeries.from_times_and_values(times=times, values=df1) - self.assertTrue(ts7.has_datetime_index) + assert ts7.has_datetime_index ts8 = TimeSeries.from_values(values1) - self.assertTrue(ts8.has_range_index) + assert ts8.has_range_index def test_short_series_slice(self): seriesA, seriesB = self.series1.split_after(pd.Timestamp("20130108")) - self.assertEqual(len(seriesA), 8) - self.assertEqual(len(seriesB), 2) + assert len(seriesA) == 8 + assert len(seriesB) == 2 seriesA, seriesB = self.series1.split_after(pd.Timestamp("20130109")) - self.assertEqual(len(seriesA), 9) - self.assertEqual(len(seriesB), 1) - self.assertEqual(seriesB.time_index[0], self.series1.time_index[-1]) + assert len(seriesA) == 9 + assert len(seriesB) == 1 + assert seriesB.time_index[0] == self.series1.time_index[-1] seriesA, seriesB = self.series1.split_before(pd.Timestamp("20130103")) - self.assertEqual(len(seriesA), 2) - self.assertEqual(len(seriesB), 8) + assert len(seriesA) == 2 + assert len(seriesB) == 8 seriesA, seriesB = self.series1.split_before(pd.Timestamp("20130102")) - self.assertEqual(len(seriesA), 1) - self.assertEqual(len(seriesB), 9) - self.assertEqual(seriesA.time_index[-1], self.series1.time_index[0]) + assert len(seriesA) == 1 + assert len(seriesB) == 9 + assert seriesA.time_index[-1] == self.series1.time_index[0] seriesC = self.series1.slice(pd.Timestamp("20130105"), pd.Timestamp("20130105")) - self.assertEqual(len(seriesC), 1) + assert len(seriesC) == 1 def test_map(self): fn = np.sin # noqa: E731 @@ -1307,13 +1258,13 @@ def test_map(self): series_01 = TimeSeries.from_dataframe(df_01, freq="D") series_012 = TimeSeries.from_dataframe(df_012, freq="D") - self.assertEqual(series_0["0"], series["0"].map(fn)) - self.assertEqual(series_2["2"], series["2"].map(fn)) - self.assertEqual(series_01[["0", "1"]], series[["0", "1"]].map(fn)) - self.assertEqual(series_012, series[["0", "1", "2"]].map(fn)) - self.assertEqual(series_012, series.map(fn)) + assert series_0["0"] == series["0"].map(fn) + assert series_2["2"] == series["2"].map(fn) + assert series_01[["0", "1"]] == series[["0", "1"]].map(fn) + assert series_012 == series[["0", "1", "2"]].map(fn) + assert series_012 == series.map(fn) - self.assertNotEqual(series_01, series[["0", "1"]].map(fn)) + assert series_01 != series[["0", "1"]].map(fn) def test_map_with_timestamp(self): series = linear_timeseries( @@ -1332,7 +1283,7 @@ def function(ts, x): return x - ts.month new_series = series.map(function) - self.assertEqual(new_series, zeroes) + assert new_series == zeroes def test_map_wrong_fn(self): series = linear_timeseries( @@ -1346,12 +1297,12 @@ def test_map_wrong_fn(self): def add(x, y, z): return x + y + z - with self.assertRaises(ValueError): + with pytest.raises(ValueError): series.map(add) ufunc_add = np.frompyfunc(add, 3, 1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): series.map(ufunc_add) def test_gaps(self): @@ -1392,75 +1343,55 @@ def test_gaps(self): series7 = TimeSeries.from_series(pd_series7) gaps1 = series1.gaps() - self.assertTrue( - ( - gaps1["gap_start"] - == pd.DatetimeIndex( - [pd.Timestamp("20130103"), pd.Timestamp("20130109")] - ) - ).all() - ) - self.assertTrue( - ( - gaps1["gap_end"] - == pd.DatetimeIndex( - [pd.Timestamp("20130105"), pd.Timestamp("20130110")] - ) - ).all() - ) - self.assertEqual(gaps1["gap_size"].values.tolist(), [3, 2]) + assert ( + gaps1["gap_start"] + == pd.DatetimeIndex([pd.Timestamp("20130103"), pd.Timestamp("20130109")]) + ).all() + assert ( + gaps1["gap_end"] + == pd.DatetimeIndex([pd.Timestamp("20130105"), pd.Timestamp("20130110")]) + ).all() + assert gaps1["gap_size"].values.tolist() == [3, 2] gaps2 = series2.gaps() - self.assertEqual(gaps2["gap_size"].values.tolist(), [3, 3]) + assert gaps2["gap_size"].values.tolist() == [3, 3] gaps3 = series3.gaps() - self.assertEqual(gaps3["gap_size"].values.tolist(), [10]) + assert gaps3["gap_size"].values.tolist() == [10] gaps4 = series4.gaps() - self.assertEqual(gaps4["gap_size"].values.tolist(), [3, 7, 1]) + assert gaps4["gap_size"].values.tolist() == [3, 7, 1] gaps5 = series5.gaps() - self.assertEqual(gaps5["gap_size"].values.tolist(), [2, 2]) - self.assertTrue( - ( - gaps5["gap_start"] - == pd.DatetimeIndex( - [pd.Timestamp("20150101"), pd.Timestamp("20180101")] - ) - ).all() - ) - self.assertTrue( - ( - gaps5["gap_end"] - == pd.DatetimeIndex( - [pd.Timestamp("20160101"), pd.Timestamp("20190101")] - ) - ).all() - ) + assert gaps5["gap_size"].values.tolist() == [2, 2] + assert ( + gaps5["gap_start"] + == pd.DatetimeIndex([pd.Timestamp("20150101"), pd.Timestamp("20180101")]) + ).all() + assert ( + gaps5["gap_end"] + == pd.DatetimeIndex([pd.Timestamp("20160101"), pd.Timestamp("20190101")]) + ).all() gaps6 = series6.gaps() - self.assertEqual(gaps6["gap_size"].values.tolist(), [1, 5, 9]) - self.assertTrue( - ( - gaps6["gap_start"] - == pd.DatetimeIndex( - [ - pd.Timestamp("20130901"), - pd.Timestamp("20160101"), - pd.Timestamp("20191101"), - ] - ) - ).all() - ) - self.assertTrue( - ( - gaps6["gap_end"] - == pd.DatetimeIndex( - [ - pd.Timestamp("20130901"), - pd.Timestamp("20160901"), - pd.Timestamp("20210301"), - ] - ) - ).all() - ) + assert gaps6["gap_size"].values.tolist() == [1, 5, 9] + assert ( + gaps6["gap_start"] + == pd.DatetimeIndex( + [ + pd.Timestamp("20130901"), + pd.Timestamp("20160101"), + pd.Timestamp("20191101"), + ] + ) + ).all() + assert ( + gaps6["gap_end"] + == pd.DatetimeIndex( + [ + pd.Timestamp("20130901"), + pd.Timestamp("20160901"), + pd.Timestamp("20210301"), + ] + ) + ).all() gaps7 = series7.gaps() - self.assertTrue(gaps7.empty) + assert gaps7.empty # test gaps detection on integer-indexed series values = np.array([1, 2, np.nan, np.nan, 3, 4, np.nan, 6]) @@ -1471,7 +1402,7 @@ def test_gaps(self): values = np.array([1, 2, 7, 8, 3, 4, 0, 6]) times = pd.RangeIndex(8) ts = TimeSeries.from_times_and_values(times, values) - self.assertTrue(ts.gaps().empty) + assert ts.gaps().empty def test_longest_contiguous_slice(self): times = pd.date_range("20130101", "20130111") @@ -1480,8 +1411,8 @@ def test_longest_contiguous_slice(self): ) series1 = TimeSeries.from_series(pd_series1) - self.assertEqual(len(series1.longest_contiguous_slice()), 3) - self.assertEqual(len(series1.longest_contiguous_slice(2)), 6) + assert len(series1.longest_contiguous_slice()) == 3 + assert len(series1.longest_contiguous_slice(2)) == 6 def test_with_columns_renamed(self): series1 = linear_timeseries( @@ -1503,15 +1434,15 @@ def test_with_columns_renamed(self): series1 = series1.with_columns_renamed( ["linear", "linear_1"], ["linear1", "linear2"] ) - self.assertEqual(["linear1", "linear2"], series1.columns.to_list()) + assert ["linear1", "linear2"] == series1.columns.to_list() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): series1.with_columns_renamed( ["linear1", "linear2"], ["linear1", "linear3", "linear4"] ) # Linear7 doesn't exist - with self.assertRaises(ValueError): + with pytest.raises(ValueError): series1.with_columns_renamed("linear7", "linear5") def test_to_csv_probabilistic_ts(self): @@ -1519,7 +1450,7 @@ def test_to_csv_probabilistic_ts(self): linear_timeseries(start_value=val, length=10) for val in [10, 20, 30] ] ts = concatenate(samples, axis=2) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): ts.to_csv("blah.csv") @patch("darts.timeseries.TimeSeries.pd_dataframe") @@ -1551,11 +1482,11 @@ def test_to_csv_stochastic(self, pddf_mock): ) ) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): ts.to_csv("test.csv") -class TimeSeriesConcatenateTestCase(DartsBaseTestClass): +class TestTimeSeriesConcatenate: # # COMPONENT AXIS TESTS @@ -1575,7 +1506,7 @@ def test_concatenate_component_sunny_day(self): ] ts = concatenate(samples, axis="component") - self.assertEqual((10, 3, 1), ts._xa.shape) + assert (10, 3, 1) == ts._xa.shape def test_concatenate_component_different_time_axes_no_force(self): samples = [ @@ -1590,7 +1521,7 @@ def test_concatenate_component_different_time_axes_no_force(self): ), ] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): concatenate(samples, axis="component") def test_concatenate_component_different_time_axes_with_force(self): @@ -1607,9 +1538,9 @@ def test_concatenate_component_different_time_axes_with_force(self): ] ts = concatenate(samples, axis="component", ignore_time_axis=True) - self.assertEqual((10, 3, 1), ts._xa.shape) - self.assertEqual(pd.Timestamp("2000-01-01"), ts.start_time()) - self.assertEqual(pd.Timestamp("2000-01-10"), ts.end_time()) + assert (10, 3, 1) == ts._xa.shape + assert pd.Timestamp("2000-01-01") == ts.start_time() + assert pd.Timestamp("2000-01-10") == ts.end_time() def test_concatenate_component_different_time_axes_with_force_uneven_series(self): samples = [ @@ -1624,7 +1555,7 @@ def test_concatenate_component_different_time_axes_with_force_uneven_series(self ), ] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): concatenate(samples, axis="component", ignore_time_axis=True) # @@ -1645,7 +1576,7 @@ def test_concatenate_sample_sunny_day(self): ] ts = concatenate(samples, axis="sample") - self.assertEqual((10, 1, 3), ts._xa.shape) + assert (10, 1, 3) == ts._xa.shape # # TIME AXIS TESTS @@ -1665,9 +1596,9 @@ def test_concatenate_time_sunny_day(self): ] ts = concatenate(samples, axis="time") - self.assertEqual((30, 1, 1), ts._xa.shape) - self.assertEqual(pd.Timestamp("2000-01-01"), ts.start_time()) - self.assertEqual(pd.Timestamp("2000-01-30"), ts.end_time()) + assert (30, 1, 1) == ts._xa.shape + assert pd.Timestamp("2000-01-01") == ts.start_time() + assert pd.Timestamp("2000-01-30") == ts.end_time() def test_concatenate_time_same_time_no_force(self): samples = [ @@ -1682,7 +1613,7 @@ def test_concatenate_time_same_time_no_force(self): ), ] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): concatenate(samples, axis="time") def test_concatenate_time_same_time_force(self): @@ -1699,9 +1630,9 @@ def test_concatenate_time_same_time_force(self): ] ts = concatenate(samples, axis="time", ignore_time_axis=True) - self.assertEqual((30, 1, 1), ts._xa.shape) - self.assertEqual(pd.Timestamp("2000-01-01"), ts.start_time()) - self.assertEqual(pd.Timestamp("2000-01-30"), ts.end_time()) + assert (30, 1, 1) == ts._xa.shape + assert pd.Timestamp("2000-01-01") == ts.start_time() + assert pd.Timestamp("2000-01-30") == ts.end_time() def test_concatenate_time_different_time_axes_no_force(self): samples = [ @@ -1716,7 +1647,7 @@ def test_concatenate_time_different_time_axes_no_force(self): ), ] - with self.assertRaises(ValueError): + with pytest.raises(ValueError): concatenate(samples, axis="time") def test_concatenate_time_different_time_axes_force(self): @@ -1733,9 +1664,9 @@ def test_concatenate_time_different_time_axes_force(self): ] ts = concatenate(samples, axis="time", ignore_time_axis=True) - self.assertEqual((30, 1, 1), ts._xa.shape) - self.assertEqual(pd.Timestamp("2000-01-01"), ts.start_time()) - self.assertEqual(pd.Timestamp("2000-01-30"), ts.end_time()) + assert (30, 1, 1) == ts._xa.shape + assert pd.Timestamp("2000-01-01") == ts.start_time() + assert pd.Timestamp("2000-01-30") == ts.end_time() def test_concatenate_time_different_time_axes_no_force_2_day_freq(self): samples = [ @@ -1751,10 +1682,10 @@ def test_concatenate_time_different_time_axes_no_force_2_day_freq(self): ] ts = concatenate(samples, axis="time") - self.assertEqual((30, 1, 1), ts._xa.shape) - self.assertEqual(pd.Timestamp("2000-01-01"), ts.start_time()) - self.assertEqual(pd.Timestamp("2000-02-28"), ts.end_time()) - self.assertEqual("2D", ts.freq) + assert (30, 1, 1) == ts._xa.shape + assert pd.Timestamp("2000-01-01") == ts.start_time() + assert pd.Timestamp("2000-02-28") == ts.end_time() + assert "2D" == ts.freq def test_concatenate_timeseries_method(self): ts1 = linear_timeseries( @@ -1765,13 +1696,13 @@ def test_concatenate_timeseries_method(self): ) result_ts = ts1.concatenate(ts2, axis="time") - self.assertEqual((20, 1, 1), result_ts._xa.shape) - self.assertEqual(pd.Timestamp("2000-01-01"), result_ts.start_time()) - self.assertEqual(pd.Timestamp("2000-01-20"), result_ts.end_time()) - self.assertEqual("D", result_ts.freq) + assert (20, 1, 1) == result_ts._xa.shape + assert pd.Timestamp("2000-01-01") == result_ts.start_time() + assert pd.Timestamp("2000-01-20") == result_ts.end_time() + assert "D" == result_ts.freq -class TimeSeriesHierarchyTestCase(DartsBaseTestClass): +class TestTimeSeriesHierarchy: components = ["total", "a", "b", "x", "y", "ax", "ay", "bx", "by"] @@ -1796,27 +1727,27 @@ def test_creation_with_hierarchy_sunny_day(self): columns=self.components, hierarchy=self.hierarchy, ) - self.assertEqual(hierarchical_series.hierarchy, self.hierarchy) + assert hierarchical_series.hierarchy == self.hierarchy def test_with_hierarchy_sunny_day(self): hierarchical_series = self.base_series.with_hierarchy(self.hierarchy) - self.assertEqual(hierarchical_series.hierarchy, self.hierarchy) + assert hierarchical_series.hierarchy == self.hierarchy def test_with_hierarchy_rainy_day(self): # wrong type - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.base_series.with_hierarchy(set()) # wrong keys - with self.assertRaises(ValueError): + with pytest.raises(ValueError): hierarchy = {"ax": ["a", "x"]} self.base_series.with_hierarchy(hierarchy) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): hierarchy = {"unknown": ["a", "x"]} self.base_series.with_hierarchy(hierarchy) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): hierarchy = { "unknown": ["a", "x"], "ay": ["a", "y"], @@ -1829,7 +1760,7 @@ def test_with_hierarchy_rainy_day(self): } self.base_series.with_hierarchy(hierarchy) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): hierarchy = { "total": ["a", "x"], "ay": ["a", "y"], @@ -1843,7 +1774,7 @@ def test_with_hierarchy_rainy_day(self): self.base_series.with_hierarchy(hierarchy) # wrong values - with self.assertRaises(ValueError): + with pytest.raises(ValueError): hierarchy = { "ax": ["unknown", "x"], "ay": ["a", "y"], @@ -1858,12 +1789,10 @@ def test_with_hierarchy_rainy_day(self): def test_hierarchy_processing(self): hierarchical_series = self.base_series.with_hierarchy(self.hierarchy) - self.assertTrue(hierarchical_series.has_hierarchy) - self.assertFalse(self.base_series.has_hierarchy) - self.assertEqual( - hierarchical_series.bottom_level_components, ["ax", "ay", "bx", "by"] - ) - self.assertEqual(hierarchical_series.top_level_component, "total") + assert hierarchical_series.has_hierarchy + assert not self.base_series.has_hierarchy + assert hierarchical_series.bottom_level_components == ["ax", "ay", "bx", "by"] + assert hierarchical_series.top_level_component == "total" top_level_idx = self.components.index("total") np.testing.assert_equal( @@ -1882,24 +1811,24 @@ def test_concat(self): # concat on time or samples should preserve hierarchy: concat_s = concatenate([series1, series2], axis=0, ignore_time_axis=True) - self.assertEqual(concat_s.hierarchy, self.hierarchy) + assert concat_s.hierarchy == self.hierarchy concat_s = concatenate([series1, series2], axis=2) - self.assertEqual(concat_s.hierarchy, self.hierarchy) + assert concat_s.hierarchy == self.hierarchy # concat on components should fail when not ignoring hierarchy - with self.assertRaises(ValueError): + with pytest.raises(ValueError): concat_s = concatenate([series1, series2], axis=1, drop_hierarchy=False) # concat on components should work when dropping hierarchy concat_s = concatenate([series1, series2], axis=1, drop_hierarchy=True) - self.assertFalse(concat_s.has_hierarchy) + assert not concat_s.has_hierarchy # hierarchy should be dropped when selecting components: subs1 = series1[["ax", "ay", "bx", "by"]] - self.assertFalse(subs1.has_hierarchy) + assert not subs1.has_hierarchy subs2 = series1["total"] - self.assertFalse(subs2.has_hierarchy) + assert not subs2.has_hierarchy def test_ops(self): # another hierarchy different than the original @@ -1918,14 +1847,12 @@ def test_ops(self): series1 = self.base_series.with_hierarchy(self.hierarchy) series2 = self.base_series.with_hierarchy(hierarchy2) - self.assertEqual(series1[:10].hierarchy, self.hierarchy) - self.assertEqual((series1 + 10).hierarchy, self.hierarchy) + assert series1[:10].hierarchy == self.hierarchy + assert (series1 + 10).hierarchy == self.hierarchy # combining series should keep hierarchy of first series - self.assertEqual((series1 / series2).hierarchy, self.hierarchy) - self.assertEqual( - (series1.slice_intersect(series2[10:20])).hierarchy, self.hierarchy - ) + assert (series1 / series2).hierarchy == self.hierarchy + assert (series1.slice_intersect(series2[10:20])).hierarchy == self.hierarchy def test_with_string_items(self): # Single parents may be specified as string rather than [string] @@ -1952,7 +1879,7 @@ def test_with_string_items(self): assert ts_with_string_hierarchy.hierarchy == ts_with_list_hierarchy.hierarchy -class TimeSeriesHeadTailTestCase(DartsBaseTestClass): +class TestTimeSeriesHeadTail: ts = TimeSeries( xr.DataArray( @@ -1967,49 +1894,45 @@ class TimeSeriesHeadTailTestCase(DartsBaseTestClass): def test_head_sunny_day_time_axis(self): result = self.ts.head() - self.assertEqual(5, result.n_timesteps) - self.assertEqual(pd.Timestamp("2000-01-05"), result.end_time()) + assert 5 == result.n_timesteps + assert pd.Timestamp("2000-01-05") == result.end_time() def test_head_sunny_day_component_axis(self): result = self.ts.head(axis=1) - self.assertEqual(5, result.n_components) - self.assertEqual( - ["comp_0", "comp_1", "comp_2", "comp_3", "comp_4"], - result._xa.coords["component"].values.tolist(), - ) + assert 5 == result.n_components + assert ["comp_0", "comp_1", "comp_2", "comp_3", "comp_4"] == result._xa.coords[ + "component" + ].values.tolist() def test_tail_sunny_day_time_axis(self): result = self.ts.tail() - self.assertEqual(5, result.n_timesteps) - self.assertEqual(pd.Timestamp("2000-01-06"), result.start_time()) + assert 5 == result.n_timesteps + assert pd.Timestamp("2000-01-06") == result.start_time() def test_tail_sunny_day_component_axis(self): result = self.ts.tail(axis=1) - self.assertEqual(5, result.n_components) - self.assertEqual( - ["comp_5", "comp_6", "comp_7", "comp_8", "comp_9"], - result._xa.coords["component"].values.tolist(), - ) + assert 5 == result.n_components + assert ["comp_5", "comp_6", "comp_7", "comp_8", "comp_9"] == result._xa.coords[ + "component" + ].values.tolist() def test_head_sunny_day_sample_axis(self): result = self.ts.tail(axis=2) - self.assertEqual(5, result.n_samples) - self.assertEqual( - list(range(5, 10)), result._xa.coords["sample"].values.tolist() - ) + assert 5 == result.n_samples + assert list(range(5, 10)) == result._xa.coords["sample"].values.tolist() def test_head_overshot_time_axis(self): result = self.ts.head(20) - self.assertEqual(10, result.n_timesteps) - self.assertEqual(pd.Timestamp("2000-01-10"), result.end_time()) + assert 10 == result.n_timesteps + assert pd.Timestamp("2000-01-10") == result.end_time() def test_head_overshot_component_axis(self): result = self.ts.head(20, axis="component") - self.assertEqual(10, result.n_components) + assert 10 == result.n_components def test_head_overshot_sample_axis(self): result = self.ts.head(20, axis="sample") - self.assertEqual(10, result.n_samples) + assert 10 == result.n_samples def test_head_numeric_time_index(self): s = TimeSeries.from_values(self.ts.values()) @@ -2018,16 +1941,16 @@ def test_head_numeric_time_index(self): def test_tail_overshot_time_axis(self): result = self.ts.tail(20) - self.assertEqual(10, result.n_timesteps) - self.assertEqual(pd.Timestamp("2000-01-01"), result.start_time()) + assert 10 == result.n_timesteps + assert pd.Timestamp("2000-01-01") == result.start_time() def test_tail_overshot_component_axis(self): result = self.ts.tail(20, axis="component") - self.assertEqual(10, result.n_components) + assert 10 == result.n_components def test_tail_overshot_sample_axis(self): result = self.ts.tail(20, axis="sample") - self.assertEqual(10, result.n_samples) + assert 10 == result.n_samples def test_tail_numeric_time_index(self): s = TimeSeries.from_values(self.ts.values()) @@ -2035,7 +1958,7 @@ def test_tail_numeric_time_index(self): s.tail() -class TimeSeriesFromDataFrameTestCase(DartsBaseTestClass): +class TestTimeSeriesFromDataFrame: def test_from_dataframe_sunny_day(self): data_dict = {"Time": pd.date_range(start="20180501", end="20200301", freq="MS")} data_dict["Values1"] = np.random.uniform( @@ -2054,8 +1977,8 @@ def test_from_dataframe_sunny_day(self): data_darts2 = TimeSeries.from_dataframe(df=data_pd2, time_col="Time") data_darts3 = TimeSeries.from_dataframe(df=data_pd3) - self.assertEqual(data_darts1, data_darts2) - self.assertEqual(data_darts1, data_darts3) + assert data_darts1 == data_darts2 + assert data_darts1 == data_darts3 def test_time_col_convert_string_integers(self): expected = np.array(list(range(3, 10))) @@ -2066,9 +1989,9 @@ def test_time_col_convert_string_integers(self): df = pd.DataFrame(data_dict) ts = TimeSeries.from_dataframe(df=df, time_col="Time") - self.assertEqual(set(ts.time_index.values.tolist()), set(expected)) - self.assertEqual(ts.time_index.dtype, int) - self.assertEqual(ts.time_index.name, "Time") + assert set(ts.time_index.values.tolist()) == set(expected) + assert ts.time_index.dtype == int + assert ts.time_index.name == "Time" def test_time_col_convert_integers(self): expected = np.array(list(range(10))) @@ -2079,9 +2002,9 @@ def test_time_col_convert_integers(self): df = pd.DataFrame(data_dict) ts = TimeSeries.from_dataframe(df=df, time_col="Time") - self.assertEqual(set(ts.time_index.values.tolist()), set(expected)) - self.assertEqual(ts.time_index.dtype, int) - self.assertEqual(ts.time_index.name, "Time") + assert set(ts.time_index.values.tolist()) == set(expected) + assert ts.time_index.dtype == int + assert ts.time_index.name == "Time" def test_fail_with_bad_integer_time_col(self): bad_time_col_vals = np.array([4, 0, 1, 2]) @@ -2090,7 +2013,7 @@ def test_fail_with_bad_integer_time_col(self): low=-10, high=10, size=len(data_dict["Time"]) ) df = pd.DataFrame(data_dict) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): TimeSeries.from_dataframe(df=df, time_col="Time") def test_time_col_convert_rangeindex(self): @@ -2104,17 +2027,17 @@ def test_time_col_convert_rangeindex(self): ts = TimeSeries.from_dataframe(df=df, time_col="Time") # check type (should convert to RangeIndex): - self.assertEqual(type(ts.time_index), pd.RangeIndex) + assert type(ts.time_index) == pd.RangeIndex # check values inside the index (should be sorted correctly): - self.assertEqual(list(ts.time_index), sorted(expected)) + assert list(ts.time_index) == sorted(expected) # check that values are sorted accordingly: ar1 = ts.values(copy=False)[:, 0] ar2 = data_dict["Values1"][ list(expected_l.index(i * step) for i in range(len(expected))) ] - self.assertTrue(np.all(ar1 == ar2)) + assert np.all(ar1 == ar2) def test_time_col_convert_datetime(self): expected = pd.date_range(start="20180501", end="20200301", freq="MS") @@ -2125,8 +2048,8 @@ def test_time_col_convert_datetime(self): df = pd.DataFrame(data_dict) ts = TimeSeries.from_dataframe(df=df, time_col="Time") - self.assertEqual(ts.time_index.dtype, "datetime64[ns]") - self.assertEqual(ts.time_index.name, "Time") + assert ts.time_index.dtype == "datetime64[ns]" + assert ts.time_index.name == "Time" def test_time_col_convert_datetime_strings(self): expected = pd.date_range(start="20180501", end="20200301", freq="MS") @@ -2137,8 +2060,8 @@ def test_time_col_convert_datetime_strings(self): df = pd.DataFrame(data_dict) ts = TimeSeries.from_dataframe(df=df, time_col="Time") - self.assertEqual(ts.time_index.dtype, "datetime64[ns]") - self.assertEqual(ts.time_index.name, "Time") + assert ts.time_index.dtype == "datetime64[ns]" + assert ts.time_index.name == "Time" def test_time_col_with_tz(self): # numpy and xarray don't support "timezone aware" pd.DatetimeIndex @@ -2152,20 +2075,20 @@ def test_time_col_with_tz(self): # (other columns are silently converted to UTC, with tz attribute set to None) df = pd.DataFrame(data=values, index=time_range_MS) ts = TimeSeries.from_dataframe(df=df) - self.assertEqual(list(ts.time_index), list(time_range_MS.tz_localize(None))) - self.assertEqual(list(ts.time_index.tz_localize("CET")), list(time_range_MS)) - self.assertEqual(ts.time_index.tz, None) + assert list(ts.time_index) == list(time_range_MS.tz_localize(None)) + assert list(ts.time_index.tz_localize("CET")) == list(time_range_MS) + assert ts.time_index.tz is None serie = pd.Series(data=values, index=time_range_MS) ts = TimeSeries.from_series(pd_series=serie) - self.assertEqual(list(ts.time_index), list(time_range_MS.tz_localize(None))) - self.assertEqual(list(ts.time_index.tz_localize("CET")), list(time_range_MS)) - self.assertEqual(ts.time_index.tz, None) + assert list(ts.time_index) == list(time_range_MS.tz_localize(None)) + assert list(ts.time_index.tz_localize("CET")) == list(time_range_MS) + assert ts.time_index.tz is None ts = TimeSeries.from_times_and_values(times=time_range_MS, values=values) - self.assertEqual(list(ts.time_index), list(time_range_MS.tz_localize(None))) - self.assertEqual(list(ts.time_index.tz_localize("CET")), list(time_range_MS)) - self.assertEqual(ts.time_index.tz, None) + assert list(ts.time_index) == list(time_range_MS.tz_localize(None)) + assert list(ts.time_index.tz_localize("CET")) == list(time_range_MS) + assert ts.time_index.tz is None time_range_H = pd.date_range( start="20200518", end="20200521", freq="H", tz="CET" @@ -2174,20 +2097,20 @@ def test_time_col_with_tz(self): df = pd.DataFrame(data=values, index=time_range_H) ts = TimeSeries.from_dataframe(df=df) - self.assertEqual(list(ts.time_index), list(time_range_H.tz_localize(None))) - self.assertEqual(list(ts.time_index.tz_localize("CET")), list(time_range_H)) - self.assertEqual(ts.time_index.tz, None) + assert list(ts.time_index) == list(time_range_H.tz_localize(None)) + assert list(ts.time_index.tz_localize("CET")) == list(time_range_H) + assert ts.time_index.tz is None serie = pd.Series(data=values, index=time_range_H) ts = TimeSeries.from_series(pd_series=serie) - self.assertEqual(list(ts.time_index), list(time_range_H.tz_localize(None))) - self.assertEqual(list(ts.time_index.tz_localize("CET")), list(time_range_H)) - self.assertEqual(ts.time_index.tz, None) + assert list(ts.time_index) == list(time_range_H.tz_localize(None)) + assert list(ts.time_index.tz_localize("CET")) == list(time_range_H) + assert ts.time_index.tz is None ts = TimeSeries.from_times_and_values(times=time_range_H, values=values) - self.assertEqual(list(ts.time_index), list(time_range_H.tz_localize(None))) - self.assertEqual(list(ts.time_index.tz_localize("CET")), list(time_range_H)) - self.assertEqual(ts.time_index.tz, None) + assert list(ts.time_index) == list(time_range_H.tz_localize(None)) + assert list(ts.time_index.tz_localize("CET")) == list(time_range_H) + assert ts.time_index.tz is None def test_time_col_convert_garbage(self): expected = [ @@ -2203,7 +2126,7 @@ def test_time_col_convert_garbage(self): ) df = pd.DataFrame(data_dict) - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): TimeSeries.from_dataframe(df=df, time_col="Time") def test_df_named_columns_index(self): @@ -2224,12 +2147,12 @@ def test_df_named_columns_index(self): columns=["y"], ) # check that series are exactly identical - self.assertEqual(ts, exp_ts) + assert ts == exp_ts # check that the original df was not changed - self.assertEqual(df.columns.name, "id") + assert df.columns.name == "id" -class SimpleStatisticsTestCase(DartsBaseTestClass): +class TestSimpleStatistics: times = pd.date_range("20130101", "20130110", freq="D") values = np.random.rand(10, 2, 100) @@ -2244,90 +2167,72 @@ def test_mean(self): for axis in range(3): new_ts = self.ts.mean(axis=axis) # check values - self.assertTrue( - np.isclose( - new_ts._xa.values, self.values.mean(axis=axis, keepdims=True) - ).all() - ) + assert np.isclose( + new_ts._xa.values, self.values.mean(axis=axis, keepdims=True) + ).all() def test_var(self): for ddof in range(5): new_ts = self.ts.var(ddof=ddof) # check values - self.assertTrue( - np.isclose(new_ts.values(), self.values.var(ddof=ddof, axis=2)).all() - ) + assert np.isclose(new_ts.values(), self.values.var(ddof=ddof, axis=2)).all() def test_std(self): for ddof in range(5): new_ts = self.ts.std(ddof=ddof) # check values - self.assertTrue( - np.isclose(new_ts.values(), self.values.std(ddof=ddof, axis=2)).all() - ) + assert np.isclose(new_ts.values(), self.values.std(ddof=ddof, axis=2)).all() def test_skew(self): new_ts = self.ts.skew() # check values - self.assertTrue(np.isclose(new_ts.values(), skew(self.values, axis=2)).all()) + assert np.isclose(new_ts.values(), skew(self.values, axis=2)).all() def test_kurtosis(self): new_ts = self.ts.kurtosis() # check values - self.assertTrue( - np.isclose( - new_ts.values(), - kurtosis(self.values, axis=2), - ).all() - ) + assert np.isclose( + new_ts.values(), + kurtosis(self.values, axis=2), + ).all() def test_min(self): for axis in range(3): new_ts = self.ts.min(axis=axis) # check values - self.assertTrue( - np.isclose( - new_ts._xa.values, self.values.min(axis=axis, keepdims=True) - ).all() - ) + assert np.isclose( + new_ts._xa.values, self.values.min(axis=axis, keepdims=True) + ).all() def test_max(self): for axis in range(3): new_ts = self.ts.max(axis=axis) # check values - self.assertTrue( - np.isclose( - new_ts._xa.values, self.values.max(axis=axis, keepdims=True) - ).all() - ) + assert np.isclose( + new_ts._xa.values, self.values.max(axis=axis, keepdims=True) + ).all() def test_sum(self): for axis in range(3): new_ts = self.ts.sum(axis=axis) # check values - self.assertTrue( - np.isclose( - new_ts._xa.values, self.values.sum(axis=axis, keepdims=True) - ).all() - ) + assert np.isclose( + new_ts._xa.values, self.values.sum(axis=axis, keepdims=True) + ).all() def test_median(self): for axis in range(3): new_ts = self.ts.median(axis=axis) # check values - self.assertTrue( - np.isclose( - new_ts._xa.values, np.median(self.values, axis=axis, keepdims=True) - ).all() - ) + assert np.isclose( + new_ts._xa.values, np.median(self.values, axis=axis, keepdims=True) + ).all() def test_quantile(self): for q in [0.01, 0.1, 0.5, 0.95]: new_ts = self.ts.quantile(quantile=q) # check values - self.assertTrue( - np.isclose( - new_ts.values(), - np.quantile(self.values, q=q, axis=2), - ).all() - ) + assert np.isclose( + new_ts.values(), + np.quantile(self.values, q=q, axis=2), + ).all() diff --git a/darts/tests/test_timeseries_multivariate.py b/darts/tests/test_timeseries_multivariate.py index 47099f1ff7..d7fd3904f6 100644 --- a/darts/tests/test_timeseries_multivariate.py +++ b/darts/tests/test_timeseries_multivariate.py @@ -1,12 +1,12 @@ import numpy as np import pandas as pd +import pytest from darts import TimeSeries -from darts.tests.base_test_class import DartsBaseTestClass -from darts.tests.test_timeseries import TimeSeriesTestCase +from darts.tests.test_timeseries import TestTimeSeries -class TimeSeriesMultivariateTestCase(DartsBaseTestClass): +class TestTimeSeriesMultivariate: times1 = pd.date_range("20130101", "20130110") times2 = pd.date_range("20130206", "20130215") @@ -39,80 +39,75 @@ class TimeSeriesMultivariateTestCase(DartsBaseTestClass): def test_creation(self): series_test = TimeSeries.from_dataframe(self.dataframe1) - self.assertTrue( - np.all(series_test.pd_dataframe().values == (self.dataframe1.values)) - ) + assert np.all(series_test.pd_dataframe().values == self.dataframe1.values) # Series cannot be lower than three without passing frequency as argument to constructor - with self.assertRaises(ValueError): + with pytest.raises(ValueError): TimeSeries(self.dataframe1.iloc[:2, :]) TimeSeries.from_dataframe(self.dataframe1.iloc[:2, :], freq="D") def test_eq(self): seriesA = TimeSeries.from_dataframe(self.dataframe1) - self.assertTrue(self.series1 == seriesA) - self.assertFalse(self.series1 != seriesA) + assert self.series1 == seriesA + assert not (self.series1 != seriesA) # with different dates dataframeB = self.dataframe1.copy() dataframeB.index = pd.date_range("20130102", "20130111") seriesB = TimeSeries.from_dataframe(dataframeB) - self.assertFalse(self.series1 == seriesB) + assert not (self.series1 == seriesB) # with one different value dataframeC = self.dataframe1.copy() dataframeC.iloc[2, 2] = 0 seriesC = TimeSeries.from_dataframe(dataframeC) - self.assertFalse(self.series1 == seriesC) + assert not (self.series1 == seriesC) def test_rescale(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.series1.rescale_with_value(1) seriesA = self.series2.rescale_with_value(0) - self.assertTrue(np.all(seriesA.values() == 0).all()) + assert np.all(seriesA.values() == 0).all() seriesB = self.series2.rescale_with_value(1) - self.assertEqual( - seriesB, - TimeSeries.from_dataframe( - pd.DataFrame( - { - "0": np.arange(1, 11), - "1": np.arange(1, 11), - "2": np.arange(1, 11), - }, - index=self.dataframe2.index, - ).astype(float) - ), + assert seriesB == TimeSeries.from_dataframe( + pd.DataFrame( + { + "0": np.arange(1, 11), + "1": np.arange(1, 11), + "2": np.arange(1, 11), + }, + index=self.dataframe2.index, + ).astype(float) ) def test_slice(self): - TimeSeriesTestCase.helper_test_slice(self, self.series1) + TestTimeSeries.helper_test_slice(self, self.series1) def test_split(self): - TimeSeriesTestCase.helper_test_split(self, self.series1) + TestTimeSeries.helper_test_split(self, self.series1) def test_drop(self): - TimeSeriesTestCase.helper_test_drop(self, self.series1) + TestTimeSeries.helper_test_drop(self, self.series1) def test_intersect(self): - TimeSeriesTestCase.helper_test_intersect(self, self.series1) + TestTimeSeries.helper_test_intersect(self, self.series1) def test_shift(self): - TimeSeriesTestCase.helper_test_shift(self, self.series1) + TestTimeSeries.helper_test_shift(self, self.series1) def test_append(self): - TimeSeriesTestCase.helper_test_append(self, self.series1) + TestTimeSeries.helper_test_append(self, self.series1) def test_append_values(self): - TimeSeriesTestCase.helper_test_append_values(self, self.series1) + TestTimeSeries.helper_test_append_values(self, self.series1) def test_prepend(self): - TimeSeriesTestCase.helper_test_prepend(self, self.series1) + TestTimeSeries.helper_test_prepend(self, self.series1) def test_prepend_values(self): - TimeSeriesTestCase.helper_test_prepend_values(self, self.series1) + TestTimeSeries.helper_test_prepend_values(self, self.series1) def test_strip(self): dataframe1 = pd.DataFrame( @@ -124,14 +119,14 @@ def test_strip(self): ) series1 = TimeSeries.from_dataframe(dataframe1) - self.assertTrue((series1.strip().time_index == self.times1[1:-1]).all()) + assert (series1.strip().time_index == self.times1[1:-1]).all() """ Testing new multivariate methods. """ def test_stack(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): self.series1.stack(self.series3) seriesA = self.series1.stack(self.series2) dataframeA = pd.concat([self.dataframe1, self.dataframe2], axis=1) @@ -143,57 +138,49 @@ def test_stack(self): "1_1", "2_1", ] # the names to expect after stacking - self.assertTrue((seriesA.pd_dataframe() == dataframeA).all().all()) - self.assertEqual( - seriesA.values().shape, - ( - len(self.dataframe1), - len(self.dataframe1.columns) + len(self.dataframe2.columns), - ), + assert (seriesA.pd_dataframe() == dataframeA).all().all() + assert seriesA.values().shape == ( + len(self.dataframe1), + len(self.dataframe1.columns) + len(self.dataframe2.columns), ) def test_univariate_component(self): - with self.assertRaises(IndexError): + with pytest.raises(IndexError): self.series1.univariate_component(-5) - with self.assertRaises(IndexError): + with pytest.raises(IndexError): self.series1.univariate_component(3) seriesA = self.series1.univariate_component(1) - self.assertTrue( - seriesA - == TimeSeries.from_times_and_values( - self.times1, range(5, 15), columns=["1"] - ) + assert seriesA == TimeSeries.from_times_and_values( + self.times1, range(5, 15), columns=["1"] ) seriesB = ( self.series1.univariate_component(0) .stack(seriesA) .stack(self.series1.univariate_component(2)) ) - self.assertTrue(self.series1 == seriesB) + assert self.series1 == seriesB def test_add_datetime_attribute(self): seriesA = self.series1.add_datetime_attribute("day") - self.assertEqual(seriesA.width, self.series1.width + 1) - self.assertTrue( - set(seriesA.pd_dataframe().iloc[:, seriesA.width - 1].values.flatten()) - == set(range(1, 11)) - ) + assert seriesA.width == self.series1.width + 1 + assert set( + seriesA.pd_dataframe().iloc[:, seriesA.width - 1].values.flatten() + ) == set(range(1, 11)) seriesB = self.series3.add_datetime_attribute("day", True) - self.assertEqual(seriesB.width, self.series3.width + 31) - self.assertEqual( - set(seriesB.pd_dataframe().iloc[:, self.series3.width :].values.flatten()), - {0, 1}, - ) + assert seriesB.width == self.series3.width + 31 + assert set( + seriesB.pd_dataframe().iloc[:, self.series3.width :].values.flatten() + ) == {0, 1} seriesC = self.series1.add_datetime_attribute("month", True) - self.assertEqual(seriesC.width, self.series1.width + 12) + assert seriesC.width == self.series1.width + 12 seriesD = TimeSeries.from_times_and_values( pd.date_range("20130206", "20130430"), range(84) ) seriesD = seriesD.add_datetime_attribute("month", True) - self.assertEqual(seriesD.width, 13) - self.assertEqual(sum(seriesD.values().flatten()), sum(range(84)) + 84) - self.assertEqual(sum(seriesD.values()[:, 1 + 3]), 30) - self.assertEqual(sum(seriesD.values()[:, 1 + 1]), 23) + assert seriesD.width == 13 + assert sum(seriesD.values().flatten()) == sum(range(84)) + 84 + assert sum(seriesD.values()[:, 1 + 3]) == 30 + assert sum(seriesD.values()[:, 1 + 1]) == 23 # test cyclic times_month = pd.date_range("20130101", "20140610") @@ -206,14 +193,12 @@ def test_add_datetime_attribute(self): values_sin = seriesF.values()[:, 1] values_cos = seriesF.values()[:, 2] - self.assertTrue( - np.allclose(np.add(np.square(values_sin), np.square(values_cos)), 1) - ) + assert np.allclose(np.add(np.square(values_sin), np.square(values_cos)), 1) df = seriesF.pd_dataframe() df = df[df.index.day == 1] - self.assertTrue(np.allclose(df["day_sin"].values, 0.2, atol=0.03)) - self.assertTrue(np.allclose(df["day_cos"].values, 0.97, atol=0.03)) + assert np.allclose(df["day_sin"].values, 0.2, atol=0.03) + assert np.allclose(df["day_cos"].values, 0.97, atol=0.03) def test_add_holidays(self): times = pd.date_range(start=pd.Timestamp("20201201"), periods=30, freq="D") @@ -222,85 +207,71 @@ def test_add_holidays(self): # testing for christmas and non-holiday in US seriesA = seriesA.add_holidays("US") last_column = seriesA.pd_dataframe().iloc[:, seriesA.width - 1] - self.assertEqual(last_column.at[pd.Timestamp("20201225")], 1) - self.assertEqual(last_column.at[pd.Timestamp("20201210")], 0) - self.assertEqual(last_column.at[pd.Timestamp("20201226")], 0) + assert last_column.at[pd.Timestamp("20201225")] == 1 + assert last_column.at[pd.Timestamp("20201210")] == 0 + assert last_column.at[pd.Timestamp("20201226")] == 0 # testing for christmas and non-holiday in PL seriesA = seriesA.add_holidays("PL") last_column = seriesA.pd_dataframe().iloc[:, seriesA.width - 1] - self.assertEqual(last_column.at[pd.Timestamp("20201225")], 1) - self.assertEqual(last_column.at[pd.Timestamp("20201210")], 0) - self.assertEqual(last_column.at[pd.Timestamp("20201226")], 1) - self.assertEqual(seriesA.width, 3) + assert last_column.at[pd.Timestamp("20201225")] == 1 + assert last_column.at[pd.Timestamp("20201210")] == 0 + assert last_column.at[pd.Timestamp("20201226")] == 1 + assert seriesA.width == 3 # testing hourly time series times = pd.date_range(start=pd.Timestamp("20201224"), periods=50, freq="H") seriesB = TimeSeries.from_times_and_values(times, range(len(times))) seriesB = seriesB.add_holidays("US") last_column = seriesB.pd_dataframe().iloc[:, seriesB.width - 1] - self.assertEqual(last_column.at[pd.Timestamp("2020-12-25 01:00:00")], 1) - self.assertEqual(last_column.at[pd.Timestamp("2020-12-24 23:00:00")], 0) + assert last_column.at[pd.Timestamp("2020-12-25 01:00:00")] == 1 + assert last_column.at[pd.Timestamp("2020-12-24 23:00:00")] == 0 def test_assert_univariate(self): - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): self.series1._assert_univariate() self.series1.univariate_component(0)._assert_univariate() def test_first_last_values(self): - self.assertEqual(self.series1.first_values().tolist(), [0, 5, 10]) - self.assertEqual(self.series3.last_values().tolist(), [10, 20]) - self.assertEqual( - self.series1.univariate_component(1).first_values().tolist(), [5] - ) - self.assertEqual( - self.series3.univariate_component(1).last_values().tolist(), [20] - ) + assert self.series1.first_values().tolist() == [0, 5, 10] + assert self.series3.last_values().tolist() == [10, 20] + assert self.series1.univariate_component(1).first_values().tolist() == [5] + assert self.series3.univariate_component(1).last_values().tolist() == [20] def test_drop_column(self): # testing dropping a single column seriesA = self.series1.drop_columns("0") - self.assertNotIn("0", seriesA.columns.values) - self.assertEqual(seriesA.columns.tolist(), ["1", "2"]) - self.assertEqual(len(seriesA.columns), 2) + assert "0" not in seriesA.columns.values + assert seriesA.columns.tolist() == ["1", "2"] + assert len(seriesA.columns) == 2 # testing dropping multiple columns seriesB = self.series1.drop_columns(["0", "1"]) - self.assertIn("2", seriesB.columns.values) - self.assertEqual(len(seriesB.columns), 1) + assert "2" in seriesB.columns.values + assert len(seriesB.columns) == 1 def test_gaps(self): gaps1_all = self.series1.gaps(mode="all") - self.assertTrue(gaps1_all.empty) + assert gaps1_all.empty gaps1_any = self.series1.gaps(mode="any") - self.assertTrue(gaps1_any.empty) + assert gaps1_any.empty gaps4_all = self.series4.gaps(mode="all") - self.assertTrue( - ( - gaps4_all["gap_start"] == pd.DatetimeIndex([pd.Timestamp("20130208")]) - ).all() - ) - self.assertTrue( - (gaps4_all["gap_end"] == pd.DatetimeIndex([pd.Timestamp("20130208")])).all() - ) - self.assertEqual(gaps4_all["gap_size"].values.tolist(), [1]) + assert ( + gaps4_all["gap_start"] == pd.DatetimeIndex([pd.Timestamp("20130208")]) + ).all() + assert ( + gaps4_all["gap_end"] == pd.DatetimeIndex([pd.Timestamp("20130208")]) + ).all() + assert gaps4_all["gap_size"].values.tolist() == [1] gaps4_any = self.series4.gaps(mode="any") - self.assertTrue( - ( - gaps4_any["gap_start"] - == pd.DatetimeIndex( - [pd.Timestamp("20130208"), pd.Timestamp("20130211")] - ) - ).all() - ) - self.assertTrue( - ( - gaps4_any["gap_end"] - == pd.DatetimeIndex( - [pd.Timestamp("20130208"), pd.Timestamp("20130214")] - ) - ).all() - ) - self.assertEqual(gaps4_any["gap_size"].values.tolist(), [1, 4]) + assert ( + gaps4_any["gap_start"] + == pd.DatetimeIndex([pd.Timestamp("20130208"), pd.Timestamp("20130211")]) + ).all() + assert ( + gaps4_any["gap_end"] + == pd.DatetimeIndex([pd.Timestamp("20130208"), pd.Timestamp("20130214")]) + ).all() + assert gaps4_any["gap_size"].values.tolist() == [1, 4] diff --git a/darts/tests/test_timeseries_static_covariates.py b/darts/tests/test_timeseries_static_covariates.py index 7548115fb6..2a6d1ee986 100644 --- a/darts/tests/test_timeseries_static_covariates.py +++ b/darts/tests/test_timeseries_static_covariates.py @@ -1,7 +1,5 @@ import copy import os -import shutil -import tempfile import numpy as np import pandas as pd @@ -9,61 +7,49 @@ from darts import TimeSeries, concatenate from darts.dataprocessing.transformers import BoxCox, Scaler -from darts.tests.base_test_class import DartsBaseTestClass from darts.timeseries import DEFAULT_GLOBAL_STATIC_COV_NAME, STATIC_COV_TAG from darts.utils.timeseries_generation import generate_index, linear_timeseries -class TimeSeriesStaticCovariateTestCase(DartsBaseTestClass): - @classmethod - def setUpClass(cls): - super().setUpClass() - - n_groups = 5 - len_ts = 10 - times = ( - pd.concat( - [ - pd.DataFrame( - generate_index(start=pd.Timestamp(2010, 1, 1), length=len_ts) - ) - ] - * n_groups, - axis=0, - ) - .reset_index(drop=True) - .rename(columns={0: "times"}) - ) - - x = pd.DataFrame(np.random.randn(n_groups * len_ts, 3), columns=["a", "b", "c"]) - static_multivar = pd.DataFrame( +def setup_test_case(): + n_groups = 5 + len_ts = 10 + times = ( + pd.concat( [ - [i, 0 if j < (len_ts // 2) else 1] - for i in range(n_groups) - for j in range(len_ts) - ], - columns=["st1", "st2"], - ) - - df_long_multi = pd.DataFrame( - pd.concat([times, x, static_multivar], axis=1), - ) - df_long_multi.loc[:, "constant"] = 1 - df_long_uni = df_long_multi.drop(columns=["st2"]) - - cls.n_groups = n_groups - cls.len_ts = len_ts - cls.df_long_multi = df_long_multi - cls.df_long_uni = df_long_uni - - def setUp(self): - self.temp_work_dir = tempfile.mkdtemp(prefix="darts") - - def tearDown(self): - super().tearDown() - shutil.rmtree(self.temp_work_dir) - - def test_ts_from_x(self): + pd.DataFrame( + generate_index(start=pd.Timestamp(2010, 1, 1), length=len_ts) + ) + ] + * n_groups, + axis=0, + ) + .reset_index(drop=True) + .rename(columns={0: "times"}) + ) + + x = pd.DataFrame(np.random.randn(n_groups * len_ts, 3), columns=["a", "b", "c"]) + static_multivar = pd.DataFrame( + [ + [i, 0 if j < (len_ts // 2) else 1] + for i in range(n_groups) + for j in range(len_ts) + ], + columns=["st1", "st2"], + ) + + df_long_multi = pd.DataFrame( + pd.concat([times, x, static_multivar], axis=1), + ) + df_long_multi.loc[:, "constant"] = 1 + df_long_uni = df_long_multi.drop(columns=["st2"]) + return n_groups, len_ts, df_long_uni, df_long_multi + + +class TestTimeSeriesStaticCovariate: + n_groups, len_ts, df_long_uni, df_long_multi = setup_test_case() + + def test_ts_from_x(self, tmpdir_module): ts = linear_timeseries(length=10).with_static_covariates( pd.Series([0.0, 1.0], index=["st1", "st2"]) ) @@ -101,8 +87,8 @@ def test_ts_from_x(self): ), ) - f_csv = os.path.join(self.temp_work_dir, "temp_ts.csv") - f_pkl = os.path.join(self.temp_work_dir, "temp_ts.pkl") + f_csv = os.path.join(tmpdir_module, "temp_ts.csv") + f_pkl = os.path.join(tmpdir_module, "temp_ts.pkl") ts.to_csv(f_csv) ts.to_pickle(f_pkl) ts_json = ts.to_json() diff --git a/darts/tests/utils/tabularization/test_add_static_covariates.py b/darts/tests/utils/tabularization/test_add_static_covariates.py index ec0012b55d..f212fb879a 100644 --- a/darts/tests/utils/tabularization/test_add_static_covariates.py +++ b/darts/tests/utils/tabularization/test_add_static_covariates.py @@ -4,12 +4,11 @@ import pandas as pd import pytest -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.data.tabularization import add_static_covariates_to_lagged_data from darts.utils.timeseries_generation import linear_timeseries -class AddStaticToLaggedDataTestCase(DartsBaseTestClass): +class TestAddStaticToLaggedData: series = linear_timeseries(length=6) series = series.stack(series) series_stcov_single = series.with_static_covariates(pd.DataFrame({"a": [0.0]})) diff --git a/darts/tests/utils/tabularization/test_create_lagged_prediction_data.py b/darts/tests/utils/tabularization/test_create_lagged_prediction_data.py index eca4cbe288..3c46330022 100644 --- a/darts/tests/utils/tabularization/test_create_lagged_prediction_data.py +++ b/darts/tests/utils/tabularization/test_create_lagged_prediction_data.py @@ -4,16 +4,16 @@ import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts import concatenate as darts_concatenate from darts.logging import get_logger, raise_if_not, raise_log -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.data.tabularization import create_lagged_prediction_data from darts.utils.timeseries_generation import linear_timeseries -class CreateLaggedPredictionDataTestCase(DartsBaseTestClass): +class TestCreateLaggedPredictionData: """ Tests `create_lagged_prediction_data` function defined in `darts.utils.data.tabularization`. There are broadly two 'groups' of tests defined in this module: @@ -85,15 +85,15 @@ def get_feature_times( is_target_or_past = i < 2 if lags_specified: if is_target_or_past: - times_i = CreateLaggedPredictionDataTestCase.get_feature_times_target_or_past( - series_i, lags_i - ) - else: times_i = ( - CreateLaggedPredictionDataTestCase.get_feature_times_future( + TestCreateLaggedPredictionData.get_feature_times_target_or_past( series_i, lags_i ) ) + else: + times_i = TestCreateLaggedPredictionData.get_feature_times_future( + series_i, lags_i + ) else: times_i = None if times_i is not None: @@ -397,11 +397,11 @@ def test_lagged_prediction_data_equal_freq_range_index(self): to_concat = [X for X in all_X if X is not None] expected_X = np.concatenate(to_concat, axis=1) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_prediction_data_equal_freq_datetime_index(self): """ @@ -485,11 +485,11 @@ def test_lagged_prediction_data_equal_freq_datetime_index(self): to_concat = [X for X in all_X if X is not None] expected_X = np.concatenate(to_concat, axis=1) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_prediction_data_unequal_freq_range_index(self): """ @@ -558,11 +558,11 @@ def test_lagged_prediction_data_unequal_freq_range_index(self): to_concat = [X for X in all_X if X is not None] expected_X = np.concatenate(to_concat, axis=1) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_prediction_data_unequal_freq_datetime_index(self): """ @@ -631,11 +631,11 @@ def test_lagged_prediction_data_unequal_freq_datetime_index(self): to_concat = [X for X in all_X if X is not None] expected_X = np.concatenate(to_concat, axis=1) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_prediction_data_method_consistency_range_index(self): """ @@ -714,8 +714,8 @@ def test_lagged_prediction_data_method_consistency_range_index(self): max_samples_per_ts=max_samples_per_ts, use_moving_windows=False, ) - self.assertTrue(np.allclose(X_mw, X_ti)) - self.assertTrue(times_mw[0].equals(times_ti[0])) + assert np.allclose(X_mw, X_ti) + assert times_mw[0].equals(times_ti[0]) def test_lagged_prediction_data_method_consistency_datetime_index(self): """ @@ -794,8 +794,8 @@ def test_lagged_prediction_data_method_consistency_datetime_index(self): max_samples_per_ts=max_samples_per_ts, use_moving_windows=False, ) - self.assertTrue(np.allclose(X_mw, X_ti)) - self.assertTrue(times_mw[0].equals(times_ti[0])) + assert np.allclose(X_mw, X_ti) + assert times_mw[0].equals(times_ti[0]) # # Specified Cases Tests @@ -839,11 +839,11 @@ def test_lagged_prediction_data_single_lag_single_component_same_series_range_id use_moving_windows=use_moving_windows, ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(expected_times)) - self.assertEqual(X.shape[0], len(times[0])) + assert X.shape[0] == len(expected_times) + assert X.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(expected_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert expected_times.equals(times[0]) def test_lagged_prediction_data_single_lag_single_component_same_series_datetime_idx( self, @@ -883,11 +883,11 @@ def test_lagged_prediction_data_single_lag_single_component_same_series_datetime use_moving_windows=use_moving_windows, ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(expected_times)) - self.assertEqual(X.shape[0], len(times[0])) + assert X.shape[0] == len(expected_times) + assert X.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(expected_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert expected_times.equals(times[0]) def test_lagged_prediction_data_extend_past_and_future_covariates_range_idx(self): """ @@ -931,8 +931,8 @@ def test_lagged_prediction_data_extend_past_and_future_covariates_range_idx(self max_samples_per_ts=max_samples_per_ts, use_moving_windows=use_moving_windows, ) - self.assertEqual(times[0][0], target.end_time() + target.freq) - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) + assert times[0][0] == target.end_time() + target.freq + assert np.allclose(expected_X, X[:, :, 0]) def test_lagged_prediction_data_extend_past_and_future_covariates_datetime_idx( self, @@ -993,8 +993,8 @@ def test_lagged_prediction_data_extend_past_and_future_covariates_datetime_idx( max_samples_per_ts=max_samples_per_ts, use_moving_windows=use_moving_windows, ) - self.assertEqual(times[0][0], target.end_time() + target.freq) - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) + assert times[0][0] == target.end_time() + target.freq + assert np.allclose(expected_X, X[:, :, 0]) def test_lagged_prediction_data_single_point_range_idx(self): """ @@ -1014,11 +1014,11 @@ def test_lagged_prediction_data_single_point_range_idx(self): use_moving_windows=use_moving_windows, uses_static_covariates=False, ) - self.assertTrue(np.allclose(expected_X, X)) + assert np.allclose(expected_X, X) # Should only have one sample, generated for # `t = target.end_time() + lag * target.freq`: - self.assertEqual(len(times), 1) - self.assertEqual(times[0], target.end_time() + lag * target.freq) + assert len(times) == 1 + assert times[0] == target.end_time() + lag * target.freq def test_lagged_prediction_data_single_point_datetime_idx(self): """ @@ -1040,11 +1040,11 @@ def test_lagged_prediction_data_single_point_datetime_idx(self): use_moving_windows=use_moving_windows, uses_static_covariates=False, ) - self.assertTrue(np.allclose(expected_X, X)) + assert np.allclose(expected_X, X) # Should only have one sample, generated for # `t = target.end_time() + lag * target.freq`: - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time() + lag * target.freq) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() + lag * target.freq def test_lagged_prediction_data_zero_lags_range_idx(self): """ @@ -1074,9 +1074,9 @@ def test_lagged_prediction_data_zero_lags_range_idx(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], future.start_time()) + assert np.allclose(expected_X, X) + assert len(times[0]) == 1 + assert times[0][0] == future.start_time() def test_lagged_prediction_data_zero_lags_datetime_idx(self): """ @@ -1110,9 +1110,9 @@ def test_lagged_prediction_data_zero_lags_datetime_idx(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], future.start_time()) + assert np.allclose(expected_X, X) + assert len(times[0]) == 1 + assert times[0][0] == future.start_time() def test_lagged_prediction_data_positive_lags_range_idx(self): """ @@ -1142,9 +1142,9 @@ def test_lagged_prediction_data_positive_lags_range_idx(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time() + target.freq) + assert np.allclose(expected_X, X) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() + target.freq def test_lagged_prediction_data_positive_lags_datetime_idx(self): """ @@ -1178,9 +1178,9 @@ def test_lagged_prediction_data_positive_lags_datetime_idx(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time() + target.freq) + assert np.allclose(expected_X, X) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() + target.freq def test_lagged_prediction_data_sequence_inputs(self): """ @@ -1209,10 +1209,10 @@ def test_lagged_prediction_data_sequence_inputs(self): lags_future_covariates=lags_future, uses_static_covariates=False, ) - self.assertTrue(np.allclose(X, expected_X)) - self.assertEqual(len(times), 2) - self.assertTrue(times[0].equals(expected_times_1)) - self.assertTrue(times[1].equals(expected_times_2)) + assert np.allclose(X, expected_X) + assert len(times) == 2 + assert times[0].equals(expected_times_1) + assert times[1].equals(expected_times_2) # Check when `concatenate = False`: X, times = create_lagged_prediction_data( target_series=(target_1, target_2), @@ -1224,12 +1224,12 @@ def test_lagged_prediction_data_sequence_inputs(self): uses_static_covariates=False, concatenate=False, ) - self.assertEqual(len(X), 2) - self.assertTrue(np.allclose(X[0], expected_X_1)) - self.assertTrue(np.allclose(X[1], expected_X_2)) - self.assertEqual(len(times), 2) - self.assertTrue(times[0].equals(expected_times_1)) - self.assertTrue(times[1].equals(expected_times_2)) + assert len(X) == 2 + assert np.allclose(X[0], expected_X_1) + assert np.allclose(X[1], expected_X_2) + assert len(times) == 2 + assert times[0].equals(expected_times_1) + assert times[1].equals(expected_times_2) def test_lagged_prediction_data_stochastic_series(self): """ @@ -1256,8 +1256,8 @@ def test_lagged_prediction_data_stochastic_series(self): lags_future_covariates=lags_future, uses_static_covariates=False, ) - self.assertTrue(np.allclose(X, expected_X)) - self.assertTrue(times[0].equals(expected_times)) + assert np.allclose(X, expected_X) + assert times[0].equals(expected_times) def test_lagged_prediction_data_no_shared_times_error(self): """ @@ -1271,7 +1271,7 @@ def test_lagged_prediction_data_no_shared_times_error(self): lags = [-1] # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_prediction_data( target_series=series_1, lags=lags, @@ -1280,11 +1280,11 @@ def test_lagged_prediction_data_no_shared_times_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "Specified series do not share any common times for which features can be created.", - str(e.exception), + assert ( + "Specified series do not share any common times for which features can be created." + == str(err.value) ) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_prediction_data( target_series=series_1, lags=lags, @@ -1292,9 +1292,9 @@ def test_lagged_prediction_data_no_shared_times_error(self): lags_past_covariates=lags, uses_static_covariates=False, ) - self.assertEqual( - "Specified series do not share any common times for which features can be created.", - str(e.exception), + assert ( + "Specified series do not share any common times for which features can be created." + == str(err.value) ) def test_lagged_prediction_data_no_specified_series_lags_pairs_error(self): @@ -1311,7 +1311,7 @@ def test_lagged_prediction_data_no_specified_series_lags_pairs_error(self): # versa - ignore these warnings: with warnings.catch_warnings(): warnings.simplefilter("ignore") - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_prediction_data( target_series=series, lags_future_covariates=[-1], @@ -1319,10 +1319,7 @@ def test_lagged_prediction_data_no_specified_series_lags_pairs_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "Must specify at least one series-lags pair.", - str(e.exception), - ) + assert "Must specify at least one series-lags pair." == str(err.value) def test_lagged_prediction_data_no_lags_specified_error(self): """ @@ -1332,15 +1329,15 @@ def test_lagged_prediction_data_no_lags_specified_error(self): target = linear_timeseries(start=1, length=20, freq=1) # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_prediction_data( target_series=target, use_moving_windows=use_moving_windows, uses_static_covariates=False, ) - self.assertEqual( - "Must specify at least one of: `lags`, `lags_past_covariates`, `lags_future_covariates`.", - str(e.exception), + assert ( + "Must specify at least one of: `lags`, `lags_past_covariates`, `lags_future_covariates`." + == str(err.value) ) def test_lagged_prediction_data_series_too_short_error(self): @@ -1352,36 +1349,30 @@ def test_lagged_prediction_data_series_too_short_error(self): series = linear_timeseries(start=1, length=2, freq=1) # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_prediction_data( target_series=series, lags=[-20, -1], uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`target_series` must have at least " - "`-min(lags) + max(lags) + 1` = 20 " - "timesteps; instead, it only has 2." - ), - str(e.exception), - ) - with self.assertRaises(ValueError) as e: + assert ( + "`target_series` must have at least " + "`-min(lags) + max(lags) + 1` = 20 " + "timesteps; instead, it only has 2." + ) == str(err.value) + with pytest.raises(ValueError) as err: create_lagged_prediction_data( past_covariates=series, lags_past_covariates=[-20, -1], uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`past_covariates` must have at least " - "`-min(lags_past_covariates) + max(lags_past_covariates) + 1` = 20 " - "timesteps; instead, it only has 2." - ), - str(e.exception), - ) + assert ( + "`past_covariates` must have at least " + "`-min(lags_past_covariates) + max(lags_past_covariates) + 1` = 20 " + "timesteps; instead, it only has 2." + ) == str(err.value) def test_lagged_prediction_data_invalid_lag_values_error(self): """ @@ -1397,33 +1388,27 @@ def test_lagged_prediction_data_invalid_lag_values_error(self): # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): # Test invalid `lags` values: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_prediction_data( target_series=series, lags=[0], uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`lags` must be a `Sequence` containing only `int` values less than 0." - ), - str(e.exception), - ) + assert ( + "`lags` must be a `Sequence` containing only `int` values less than 0." + ) == str(err.value) # Test invalid `lags_past_covariates` values: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_prediction_data( past_covariates=series, lags_past_covariates=[0], uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`lags_past_covariates` must be a `Sequence` containing only `int` values less than 0." - ), - str(e.exception), - ) + assert ( + "`lags_past_covariates` must be a `Sequence` containing only `int` values less than 0." + ) == str(err.value) # This should *not* throw an error: create_lagged_prediction_data( future_covariates=series, @@ -1450,14 +1435,11 @@ def test_lagged_prediction_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`future_covariates` was specified without accompanying " - "`lags_future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`future_covariates` was specified without accompanying " + "`lags_future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates` but not `future_covariates`: with warnings.catch_warnings(record=True) as w: @@ -1468,14 +1450,11 @@ def test_lagged_prediction_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates` but not `future_covariates`, and # `past_covariates` but not `lags_past_covariates`, and `target_series` @@ -1489,22 +1468,16 @@ def test_lagged_prediction_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 2) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue(issubclass(w[1].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`past_covariates` was specified without accompanying " - "`lags_past_covariates` and, thus, will be ignored." - ), + assert len(w) == 2 + assert issubclass(w[0].category, UserWarning) + assert issubclass(w[1].category, UserWarning) + assert str(w[0].message) == ( + "`past_covariates` was specified without accompanying " + "`lags_past_covariates` and, thus, will be ignored." ) - self.assertEqual( - str(w[1].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert str(w[1].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `target_series` but not `lags` - this *should* throw # a warning when creating prediction data: @@ -1516,14 +1489,11 @@ def test_lagged_prediction_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`target_series` was specified without accompanying " - "`lags` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`target_series` was specified without accompanying " + "`lags` and, thus, will be ignored." ) # Specify `target_series` but not `lags` - this *should* throw # a warning when creating prediction data: @@ -1535,12 +1505,9 @@ def test_lagged_prediction_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`lags` was specified without accompanying " - "`target_series` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`lags` was specified without accompanying " + "`target_series` and, thus, will be ignored." ) diff --git a/darts/tests/utils/tabularization/test_create_lagged_training_data.py b/darts/tests/utils/tabularization/test_create_lagged_training_data.py index 31df90ae4a..b17a3f862c 100644 --- a/darts/tests/utils/tabularization/test_create_lagged_training_data.py +++ b/darts/tests/utils/tabularization/test_create_lagged_training_data.py @@ -4,11 +4,11 @@ import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts import concatenate as darts_concatenate from darts.logging import get_logger, raise_if, raise_if_not, raise_log -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.data.tabularization import ( create_lagged_component_names, create_lagged_training_data, @@ -16,7 +16,7 @@ from darts.utils.timeseries_generation import linear_timeseries -class CreateLaggedTrainingDataTestCase(DartsBaseTestClass): +class TestCreateLaggedTrainingData: """ Tests the `create_lagged_training_data` function defined in `darts.utils.data.tabularization`. There are broadly two 'groups' of tests defined in this module: @@ -84,18 +84,18 @@ def get_feature_times( that only works for `is_training = True`. """ # Get feature times for `target_series`: - times = CreateLaggedTrainingDataTestCase.get_feature_times_target( + times = TestCreateLaggedTrainingData.get_feature_times_target( target, lags, output_chunk_length ) # Intersect `times` with `past_covariates` feature times if past covariates to be added to `X`: if lags_past is not None: - past_times = CreateLaggedTrainingDataTestCase.get_feature_times_past( + past_times = TestCreateLaggedTrainingData.get_feature_times_past( past, lags_past ) times = times.intersection(past_times) # Intersect `times` with `future_covariates` feature times if future covariates to be added to `X`: if lags_future is not None: - future_times = CreateLaggedTrainingDataTestCase.get_feature_times_future( + future_times = TestCreateLaggedTrainingData.get_feature_times_future( future, lags_future ) times = times.intersection(future_times) @@ -487,14 +487,14 @@ def test_lagged_training_data_equal_freq_range_index(self): target, feats_times, output_chunk_length, multi_models ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(y.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) - self.assertEqual(y.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert y.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) + assert y.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_training_data_equal_freq_datetime_index(self): """ @@ -594,14 +594,14 @@ def test_lagged_training_data_equal_freq_datetime_index(self): target, feats_times, output_chunk_length, multi_models ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(y.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) - self.assertEqual(y.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert y.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) + assert y.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_training_data_unequal_freq_range_index(self): """ @@ -686,14 +686,14 @@ def test_lagged_training_data_unequal_freq_range_index(self): target, feats_times, output_chunk_length, multi_models ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(y.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) - self.assertEqual(y.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert y.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) + assert y.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_training_data_unequal_freq_datetime_index(self): """ @@ -793,14 +793,14 @@ def test_lagged_training_data_unequal_freq_datetime_index(self): target, feats_times, output_chunk_length, multi_models ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(feats_times)) - self.assertEqual(y.shape[0], len(feats_times)) - self.assertEqual(X.shape[0], len(times[0])) - self.assertEqual(y.shape[0], len(times[0])) + assert X.shape[0] == len(feats_times) + assert y.shape[0] == len(feats_times) + assert X.shape[0] == len(times[0]) + assert y.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) - self.assertTrue(feats_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) + assert feats_times.equals(times[0]) def test_lagged_training_data_method_consistency_range_index(self): """ @@ -877,9 +877,9 @@ def test_lagged_training_data_method_consistency_range_index(self): multi_models=multi_models, use_moving_windows=False, ) - self.assertTrue(np.allclose(X_mw, X_ti)) - self.assertTrue(np.allclose(y_mw, y_ti)) - self.assertTrue(times_mw[0].equals(times_ti[0])) + assert np.allclose(X_mw, X_ti) + assert np.allclose(y_mw, y_ti) + assert times_mw[0].equals(times_ti[0]) def test_lagged_training_data_method_consistency_datetime_index(self): """ @@ -971,9 +971,9 @@ def test_lagged_training_data_method_consistency_datetime_index(self): multi_models=multi_models, use_moving_windows=False, ) - self.assertTrue(np.allclose(X_mw, X_ti)) - self.assertTrue(np.allclose(y_mw, y_ti)) - self.assertTrue(times_mw[0].equals(times_ti[0])) + assert np.allclose(X_mw, X_ti) + assert np.allclose(y_mw, y_ti) + assert times_mw[0].equals(times_ti[0]) # # Specified Cases Tests @@ -1021,14 +1021,14 @@ def test_lagged_training_data_single_lag_single_component_same_series_range_idx( use_moving_windows=use_moving_windows, ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(expected_times)) - self.assertEqual(X.shape[0], len(times[0])) - self.assertEqual(y.shape[0], len(expected_times)) - self.assertEqual(y.shape[0], len(times[0])) + assert X.shape[0] == len(expected_times) + assert X.shape[0] == len(times[0]) + assert y.shape[0] == len(expected_times) + assert y.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) - self.assertTrue(expected_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) + assert expected_times.equals(times[0]) def test_lagged_training_data_single_lag_single_component_same_series_datetime_idx( self, @@ -1072,14 +1072,14 @@ def test_lagged_training_data_single_lag_single_component_same_series_datetime_i use_moving_windows=use_moving_windows, ) # Number of observations should match number of feature times: - self.assertEqual(X.shape[0], len(expected_times)) - self.assertEqual(X.shape[0], len(times[0])) - self.assertEqual(y.shape[0], len(expected_times)) - self.assertEqual(y.shape[0], len(times[0])) + assert X.shape[0] == len(expected_times) + assert X.shape[0] == len(times[0]) + assert y.shape[0] == len(expected_times) + assert y.shape[0] == len(times[0]) # Check that outputs match: - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) - self.assertTrue(expected_times.equals(times[0])) + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) + assert expected_times.equals(times[0]) def test_lagged_training_data_extend_past_and_future_covariates_range_idx(self): """ @@ -1128,9 +1128,9 @@ def test_lagged_training_data_extend_past_and_future_covariates_range_idx(self): max_samples_per_ts=max_samples_per_ts, use_moving_windows=use_moving_windows, ) - self.assertEqual(times[0][0], target.end_time()) - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) + assert times[0][0] == target.end_time() + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) def test_lagged_training_data_extend_past_and_future_covariates_datetime_idx(self): """ @@ -1194,9 +1194,9 @@ def test_lagged_training_data_extend_past_and_future_covariates_datetime_idx(sel max_samples_per_ts=max_samples_per_ts, use_moving_windows=use_moving_windows, ) - self.assertEqual(times[0][0], target.end_time()) - self.assertTrue(np.allclose(expected_X, X[:, :, 0])) - self.assertTrue(np.allclose(expected_y, y[:, :, 0])) + assert times[0][0] == target.end_time() + assert np.allclose(expected_X, X[:, :, 0]) + assert np.allclose(expected_y, y[:, :, 0]) def test_lagged_training_data_single_point_range_idx(self): """ @@ -1222,11 +1222,11 @@ def test_lagged_training_data_single_point_range_idx(self): multi_models=multi_models, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertTrue(np.allclose(expected_y, y)) + assert np.allclose(expected_X, X) + assert np.allclose(expected_y, y) # Should only have one sample, generated for `t = target.end_time()`: - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time()) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() def test_lagged_training_data_single_point_datetime_idx(self): """ @@ -1254,11 +1254,11 @@ def test_lagged_training_data_single_point_datetime_idx(self): multi_models=multi_models, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertTrue(np.allclose(expected_y, y)) + assert np.allclose(expected_X, X) + assert np.allclose(expected_y, y) # Should only have one sample, generated for `t = target.end_time()`: - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time()) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() def test_lagged_training_data_zero_lags_range_idx(self): """ @@ -1293,10 +1293,10 @@ def test_lagged_training_data_zero_lags_range_idx(self): multi_models=multi_models, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertTrue(np.allclose(expected_y, y)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time()) + assert np.allclose(expected_X, X) + assert np.allclose(expected_y, y) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() def test_lagged_training_data_zero_lags_datetime_idx(self): """ @@ -1333,10 +1333,10 @@ def test_lagged_training_data_zero_lags_datetime_idx(self): multi_models=multi_models, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertTrue(np.allclose(expected_y, y)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time()) + assert np.allclose(expected_X, X) + assert np.allclose(expected_y, y) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() def test_lagged_training_data_positive_lags_range_idx(self): """ @@ -1371,10 +1371,10 @@ def test_lagged_training_data_positive_lags_range_idx(self): multi_models=multi_models, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertTrue(np.allclose(expected_y, y)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time()) + assert np.allclose(expected_X, X) + assert np.allclose(expected_y, y) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() def test_lagged_training_data_positive_lags_datetime_idx(self): """ @@ -1411,10 +1411,10 @@ def test_lagged_training_data_positive_lags_datetime_idx(self): multi_models=multi_models, use_moving_windows=use_moving_windows, ) - self.assertTrue(np.allclose(expected_X, X)) - self.assertTrue(np.allclose(expected_y, y)) - self.assertEqual(len(times[0]), 1) - self.assertEqual(times[0][0], target.end_time()) + assert np.allclose(expected_X, X) + assert np.allclose(expected_y, y) + assert len(times[0]) == 1 + assert times[0][0] == target.end_time() def test_lagged_training_data_sequence_inputs(self): """ @@ -1451,11 +1451,11 @@ def test_lagged_training_data_sequence_inputs(self): lags_future_covariates=lags_future, uses_static_covariates=False, ) - self.assertTrue(np.allclose(X, expected_X)) - self.assertTrue(np.allclose(y, expected_y)) - self.assertEqual(len(times), 2) - self.assertTrue(times[0].equals(expected_times_1)) - self.assertTrue(times[1].equals(expected_times_2)) + assert np.allclose(X, expected_X) + assert np.allclose(y, expected_y) + assert len(times) == 2 + assert times[0].equals(expected_times_1) + assert times[1].equals(expected_times_2) # Check when `concatenate = False`: X, y, times, _ = create_lagged_training_data( (target_1, target_2), @@ -1468,15 +1468,15 @@ def test_lagged_training_data_sequence_inputs(self): uses_static_covariates=False, concatenate=False, ) - self.assertEqual(len(X), 2) - self.assertEqual(len(y), 2) - self.assertTrue(np.allclose(X[0], expected_X_1)) - self.assertTrue(np.allclose(X[1], expected_X_2)) - self.assertTrue(np.allclose(y[0], expected_y_1)) - self.assertTrue(np.allclose(y[1], expected_y_2)) - self.assertEqual(len(times), 2) - self.assertTrue(times[0].equals(expected_times_1)) - self.assertTrue(times[1].equals(expected_times_2)) + assert len(X) == 2 + assert len(y) == 2 + assert np.allclose(X[0], expected_X_1) + assert np.allclose(X[1], expected_X_2) + assert np.allclose(y[0], expected_y_1) + assert np.allclose(y[1], expected_y_2) + assert len(times) == 2 + assert times[0].equals(expected_times_1) + assert times[1].equals(expected_times_2) def test_lagged_training_data_stochastic_series(self): """ @@ -1507,9 +1507,9 @@ def test_lagged_training_data_stochastic_series(self): lags_future_covariates=lags_future, uses_static_covariates=False, ) - self.assertTrue(np.allclose(X, expected_X)) - self.assertTrue(np.allclose(y, expected_y)) - self.assertTrue(times[0].equals(expected_times)) + assert np.allclose(X, expected_X) + assert np.allclose(y, expected_y) + assert times[0].equals(expected_times) def test_lagged_training_data_no_shared_times_error(self): """ @@ -1523,7 +1523,7 @@ def test_lagged_training_data_no_shared_times_error(self): lags = [-1] # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=series_1, output_chunk_length=1, @@ -1533,9 +1533,9 @@ def test_lagged_training_data_no_shared_times_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "Specified series do not share any common times for which features can be created.", - str(e.exception), + assert ( + "Specified series do not share any common times for which features can be created." + == str(err.value) ) def test_lagged_training_data_no_specified_series_lags_pairs_error(self): @@ -1553,7 +1553,7 @@ def test_lagged_training_data_no_specified_series_lags_pairs_error(self): # is specified without `lags_past_covariates` - ignore this: with warnings.catch_warnings(): warnings.simplefilter("ignore") - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=series_1, output_chunk_length=1, @@ -1561,17 +1561,14 @@ def test_lagged_training_data_no_specified_series_lags_pairs_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "Must specify at least one series-lags pair.", - str(e.exception), - ) + assert "Must specify at least one series-lags pair." == str(err.value) # Warnings will be thrown indicating that `past_covariates` # is specified without `lags_past_covariates`, and that # `lags_future_covariates` specified without # `future_covariates` - ignore both warnings: with warnings.catch_warnings(): warnings.simplefilter("ignore") - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=series_1, output_chunk_length=1, @@ -1580,10 +1577,7 @@ def test_lagged_training_data_no_specified_series_lags_pairs_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "Must specify at least one series-lags pair.", - str(e.exception), - ) + assert "Must specify at least one series-lags pair." == str(err.value) def test_lagged_training_data_invalid_output_chunk_length_error(self): """ @@ -1595,7 +1589,7 @@ def test_lagged_training_data_invalid_output_chunk_length_error(self): lags = [-1] # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=target, output_chunk_length=0, @@ -1603,11 +1597,8 @@ def test_lagged_training_data_invalid_output_chunk_length_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "`output_chunk_length` must be a positive `int`.", - str(e.exception), - ) - with self.assertRaises(ValueError) as e: + assert "`output_chunk_length` must be a positive `int`." == str(err.value) + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=target, output_chunk_length=1.1, @@ -1615,10 +1606,7 @@ def test_lagged_training_data_invalid_output_chunk_length_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "`output_chunk_length` must be a positive `int`.", - str(e.exception), - ) + assert "`output_chunk_length` must be a positive `int`." == str(err.value) def test_lagged_training_data_no_lags_specified_error(self): """ @@ -1628,16 +1616,16 @@ def test_lagged_training_data_no_lags_specified_error(self): target = linear_timeseries(start=1, length=20, freq=1) # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=target, output_chunk_length=1, uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - "Must specify at least one of: `lags`, `lags_past_covariates`, `lags_future_covariates`.", - str(e.exception), + assert ( + "Must specify at least one of: `lags`, `lags_past_covariates`, `lags_future_covariates`." + == str(err.value) ) def test_lagged_training_data_series_too_short_error(self): @@ -1654,7 +1642,7 @@ def test_lagged_training_data_series_too_short_error(self): series = linear_timeseries(start=1, length=2, freq=1) # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=series, output_chunk_length=5, @@ -1662,16 +1650,13 @@ def test_lagged_training_data_series_too_short_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`target_series` must have at least " - "`-min(lags) + output_chunk_length` = 25 " - "timesteps; instead, it only has 2." - ), - str(e.exception), - ) + assert ( + "`target_series` must have at least " + "`-min(lags) + output_chunk_length` = 25 " + "timesteps; instead, it only has 2." + ) == str(err.value) # `lags_past_covariates` too large test: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=series, output_chunk_length=1, @@ -1680,14 +1665,11 @@ def test_lagged_training_data_series_too_short_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`past_covariates` must have at least " - "`-min(lags_past_covariates) + max(lags_past_covariates) + 1` = 3 " - "timesteps; instead, it only has 2." - ), - str(e.exception), - ) + assert ( + "`past_covariates` must have at least " + "`-min(lags_past_covariates) + max(lags_past_covariates) + 1` = 3 " + "timesteps; instead, it only has 2." + ) == str(err.value) def test_lagged_training_data_invalid_lag_values_error(self): """ @@ -1704,7 +1686,7 @@ def test_lagged_training_data_invalid_lag_values_error(self): # Check error thrown by 'moving windows' method and by 'time intersection' method: for use_moving_windows in (False, True): # Test invalid `lags` values: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=series, output_chunk_length=1, @@ -1712,14 +1694,11 @@ def test_lagged_training_data_invalid_lag_values_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`lags` must be a `Sequence` containing only `int` values less than 0." - ), - str(e.exception), - ) + assert ( + "`lags` must be a `Sequence` containing only `int` values less than 0." + ) == str(err.value) # Test invalid `lags_past_covariates` values: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: create_lagged_training_data( target_series=series, output_chunk_length=1, @@ -1728,12 +1707,9 @@ def test_lagged_training_data_invalid_lag_values_error(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual( - ( - "`lags_past_covariates` must be a `Sequence` containing only `int` values less than 0." - ), - str(e.exception), - ) + assert ( + "`lags_past_covariates` must be a `Sequence` containing only `int` values less than 0." + ) == str(err.value) # Test invalid `lags_future_covariates` values: create_lagged_training_data( target_series=series, @@ -1769,14 +1745,11 @@ def test_lagged_training_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`future_covariates` was specified without accompanying " - "`lags_future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`future_covariates` was specified without accompanying " + "`lags_future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates`, but not `future_covariates`: with warnings.catch_warnings(record=True) as w: @@ -1788,14 +1761,11 @@ def test_lagged_training_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates` but not `future_covariates`, and # `past_covariates` but not `lags_past_covariates`: @@ -1809,22 +1779,16 @@ def test_lagged_training_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 2) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue(issubclass(w[1].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`past_covariates` was specified without accompanying " - "`lags_past_covariates` and, thus, will be ignored." - ), + assert len(w) == 2 + assert issubclass(w[0].category, UserWarning) + assert issubclass(w[1].category, UserWarning) + assert str(w[0].message) == ( + "`past_covariates` was specified without accompanying " + "`lags_past_covariates` and, thus, will be ignored." ) - self.assertEqual( - str(w[1].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert str(w[1].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `target_series`, but not `lags` - unlike previous tests, # this should *not* throw a warning: @@ -1837,7 +1801,7 @@ def test_lagged_training_data_unspecified_lag_or_series_warning(self): uses_static_covariates=False, use_moving_windows=use_moving_windows, ) - self.assertEqual(len(w), 0) + assert len(w) == 0 def test_create_lagged_component_names(self): """ @@ -1911,7 +1875,7 @@ def test_create_lagged_component_names(self): concatenate=False, use_static_covariates=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # target with static covariate (but don't use them in feature names) expected_lagged_features = [ @@ -1930,7 +1894,7 @@ def test_create_lagged_component_names(self): concatenate=False, use_static_covariates=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # target with static covariate (acting on global target components) expected_lagged_features = [ @@ -1950,7 +1914,7 @@ def test_create_lagged_component_names(self): concatenate=False, use_static_covariates=True, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # target with static covariate (component specific) expected_lagged_features = [ @@ -1971,7 +1935,7 @@ def test_create_lagged_component_names(self): concatenate=False, use_static_covariates=True, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # target with static covariate (component specific & multivariate) expected_lagged_features = [ @@ -1994,7 +1958,7 @@ def test_create_lagged_component_names(self): concatenate=False, use_static_covariates=True, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # target + past expected_lagged_features = [ @@ -2013,7 +1977,7 @@ def test_create_lagged_component_names(self): lags_future_covariates=None, concatenate=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # target + future expected_lagged_features = [ @@ -2033,7 +1997,7 @@ def test_create_lagged_component_names(self): lags_future_covariates=[3], concatenate=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # past + future expected_lagged_features = [ @@ -2054,7 +2018,7 @@ def test_create_lagged_component_names(self): lags_future_covariates=[2], concatenate=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # target with static + past + future expected_lagged_features = [ @@ -2079,7 +2043,7 @@ def test_create_lagged_component_names(self): lags_future_covariates=[2], concatenate=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # multiple series with same components, including past/future covariates expected_lagged_features = [ @@ -2102,7 +2066,7 @@ def test_create_lagged_component_names(self): lags_future_covariates=[2], concatenate=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features # multiple series with different components will use the first series as reference expected_lagged_features = [ @@ -2130,4 +2094,4 @@ def test_create_lagged_component_names(self): lags_future_covariates=[2], concatenate=False, ) - self.assertEqual(expected_lagged_features, created_lagged_features) + assert expected_lagged_features == created_lagged_features diff --git a/darts/tests/utils/tabularization/test_get_feature_times.py b/darts/tests/utils/tabularization/test_get_feature_times.py index e25f97c925..6402fc2d32 100644 --- a/darts/tests/utils/tabularization/test_get_feature_times.py +++ b/darts/tests/utils/tabularization/test_get_feature_times.py @@ -3,15 +3,15 @@ from typing import Sequence import pandas as pd +import pytest from darts import TimeSeries from darts.logging import get_logger, raise_log -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.data.tabularization import _get_feature_times from darts.utils.timeseries_generation import linear_timeseries -class GetFeatureTimesTestCase(DartsBaseTestClass): +class TestGetFeatureTimes: """ Tests `_get_feature_times` function defined in `darts.utils.data.tabularization`. There are broadly two 'groups' of tests defined in this module: @@ -102,7 +102,7 @@ def get_feature_times_target_prediction( of constructing prediction features using the `target_series` is identical to constructing features for the `past_covariates` series. """ - return GetFeatureTimesTestCase.get_feature_times_past(target_series, lags) + return TestGetFeatureTimes.get_feature_times_past(target_series, lags) @staticmethod def get_feature_times_future( @@ -234,9 +234,9 @@ def test_feature_times_training_range_idx(self): target_expected = self.get_feature_times_target_training(target, lags, ocl) past_expected = self.get_feature_times_past(past, lags_past) future_expected = self.get_feature_times_future(future, lags_future) - self.assertTrue(target_expected.equals(feature_times[0])) - self.assertTrue(past_expected.equals(feature_times[1])) - self.assertTrue(future_expected.equals(feature_times[2])) + assert target_expected.equals(feature_times[0]) + assert past_expected.equals(feature_times[1]) + assert future_expected.equals(feature_times[2]) def test_feature_times_training_datetime_idx(self): """ @@ -271,9 +271,9 @@ def test_feature_times_training_datetime_idx(self): target_expected = self.get_feature_times_target_training(target, lags, ocl) past_expected = self.get_feature_times_past(past, lags_past) future_expected = self.get_feature_times_future(future, lags_future) - self.assertTrue(target_expected.equals(feature_times[0])) - self.assertTrue(past_expected.equals(feature_times[1])) - self.assertTrue(future_expected.equals(feature_times[2])) + assert target_expected.equals(feature_times[0]) + assert past_expected.equals(feature_times[1]) + assert future_expected.equals(feature_times[2]) def test_feature_times_prediction_range_idx(self): """ @@ -304,9 +304,9 @@ def test_feature_times_prediction_range_idx(self): target_expected = self.get_feature_times_target_prediction(target, lags) past_expected = self.get_feature_times_past(past, lags_past) future_expected = self.get_feature_times_future(future, lags_future) - self.assertTrue(target_expected.equals(feature_times[0])) - self.assertTrue(past_expected.equals(feature_times[1])) - self.assertTrue(future_expected.equals(feature_times[2])) + assert target_expected.equals(feature_times[0]) + assert past_expected.equals(feature_times[1]) + assert future_expected.equals(feature_times[2]) def test_feature_times_prediction_datetime_idx(self): """ @@ -337,9 +337,9 @@ def test_feature_times_prediction_datetime_idx(self): target_expected = self.get_feature_times_target_prediction(target, lags) past_expected = self.get_feature_times_past(past, lags_past) future_expected = self.get_feature_times_future(future, lags_future) - self.assertTrue(target_expected.equals(feature_times[0])) - self.assertTrue(past_expected.equals(feature_times[1])) - self.assertTrue(future_expected.equals(feature_times[2])) + assert target_expected.equals(feature_times[0]) + assert past_expected.equals(feature_times[1]) + assert future_expected.equals(feature_times[2]) # # Specified Test Cases @@ -364,9 +364,7 @@ def test_feature_times_output_chunk_length_range_idx(self): output_chunk_length=ocl, is_training=True, ) - self.assertEqual( - feature_times[0][-1], target.end_time() - target.freq * (ocl - 1) - ) + assert feature_times[0][-1] == target.end_time() - target.freq * (ocl - 1) def test_feature_times_output_chunk_length_datetime_idx(self): """ @@ -388,9 +386,7 @@ def test_feature_times_output_chunk_length_datetime_idx(self): output_chunk_length=ocl, is_training=True, ) - self.assertEqual( - feature_times[0][-1], target.end_time() - target.freq * (ocl - 1) - ) + assert feature_times[0][-1] == target.end_time() - target.freq * (ocl - 1) def test_feature_times_lags_range_idx(self): """ @@ -411,9 +407,8 @@ def test_feature_times_lags_range_idx(self): lags=[-1, max_lags], is_training=is_training, ) - self.assertEqual( - feature_times[0][0], - target.start_time() + target.freq * abs(max_lags), + assert feature_times[0][0] == target.start_time() + target.freq * abs( + max_lags ) def test_feature_times_lags_datetime_idx(self): @@ -435,9 +430,8 @@ def test_feature_times_lags_datetime_idx(self): lags=[-1, max_lags], is_training=is_training, ) - self.assertEqual( - feature_times[0][0], - target.start_time() + target.freq * abs(max_lags), + assert feature_times[0][0] == target.start_time() + target.freq * abs( + max_lags ) def test_feature_times_training_single_time_range_idx(self): @@ -455,8 +449,8 @@ def test_feature_times_training_single_time_range_idx(self): lags=lags, is_training=True, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], 1) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == 1 # Can only create feature for time `6` (`-2` lags behind is time `2`): future = linear_timeseries(start=2, length=1, freq=2) @@ -469,10 +463,10 @@ def test_feature_times_training_single_time_range_idx(self): lags_future_covariates=future_lags, is_training=True, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], 1) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual(feature_times[2][0], 6) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == 1 + assert len(feature_times[2]) == 1 + assert feature_times[2][0] == 6 def test_feature_times_training_single_time_datetime_idx(self): """ @@ -489,8 +483,8 @@ def test_feature_times_training_single_time_datetime_idx(self): lags=lags, is_training=True, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], pd.Timestamp("1/2/2000")) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == pd.Timestamp("1/2/2000") # Can only create feature for "1/6/2000" (`-2` lags behind is "1/2/2000"): future = linear_timeseries(start=pd.Timestamp("1/2/2000"), length=1, freq="2d") @@ -503,10 +497,10 @@ def test_feature_times_training_single_time_datetime_idx(self): lags_future_covariates=future_lags, is_training=True, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], pd.Timestamp("1/2/2000")) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual(feature_times[2][0], pd.Timestamp("1/6/2000")) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == pd.Timestamp("1/2/2000") + assert len(feature_times[2]) == 1 + assert feature_times[2][0] == pd.Timestamp("1/6/2000") def test_feature_times_prediction_single_time_range_idx(self): """ @@ -522,8 +516,8 @@ def test_feature_times_prediction_single_time_range_idx(self): lags=lags, is_training=False, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], 1) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == 1 # Can only create feature for time `6` (`-2` lags behind is time `2`): future = linear_timeseries(start=2, length=1, freq=2) @@ -535,10 +529,10 @@ def test_feature_times_prediction_single_time_range_idx(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], 1) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual(feature_times[2][0], 6) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == 1 + assert len(feature_times[2]) == 1 + assert feature_times[2][0] == 6 def test_feature_times_prediction_single_time_datetime_idx(self): """ @@ -554,8 +548,8 @@ def test_feature_times_prediction_single_time_datetime_idx(self): lags=lags, is_training=False, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], pd.Timestamp("1/2/2000")) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == pd.Timestamp("1/2/2000") # Can only create feature for "1/6/2000" (`-2` lag behind is time "1/2/2000"): future = linear_timeseries(start=pd.Timestamp("1/2/2000"), length=1, freq="2d") @@ -567,10 +561,10 @@ def test_feature_times_prediction_single_time_datetime_idx(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual(feature_times[0][0], pd.Timestamp("1/2/2000")) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual(feature_times[2][0], pd.Timestamp("1/6/2000")) + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == pd.Timestamp("1/2/2000") + assert len(feature_times[2]) == 1 + assert feature_times[2][0] == pd.Timestamp("1/6/2000") def test_feature_times_extend_time_index_range_idx(self): """ @@ -593,17 +587,13 @@ def test_feature_times_extend_time_index_range_idx(self): lags_future_covariates=lags_future_1, is_training=False, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual( - feature_times[0][0], target.start_time() - lags[0] * target.freq - ) - self.assertEqual(len(feature_times[1]), 1) - self.assertEqual( - feature_times[1][0], past.start_time() - lags_past[0] * past.freq - ) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual( - feature_times[2][0], future.start_time() - lags_future_1[0] * future.freq + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == target.start_time() - lags[0] * target.freq + assert len(feature_times[1]) == 1 + assert feature_times[1][0] == past.start_time() - lags_past[0] * past.freq + assert len(feature_times[2]) == 1 + assert ( + feature_times[2][0] == future.start_time() - lags_future_1[0] * future.freq ) # Feature time occurs before start of series: lags_future_2 = [4] @@ -612,9 +602,9 @@ def test_feature_times_extend_time_index_range_idx(self): lags_future_covariates=lags_future_2, is_training=False, ) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual( - feature_times[2][0], future.start_time() - lags_future_2[0] * future.freq + assert len(feature_times[2]) == 1 + assert ( + feature_times[2][0] == future.start_time() - lags_future_2[0] * future.freq ) def test_feature_times_extend_time_index_datetime_idx(self): @@ -638,17 +628,13 @@ def test_feature_times_extend_time_index_datetime_idx(self): lags_future_covariates=lags_future_1, is_training=False, ) - self.assertEqual(len(feature_times[0]), 1) - self.assertEqual( - feature_times[0][0], target.start_time() - lags[0] * target.freq - ) - self.assertEqual(len(feature_times[1]), 1) - self.assertEqual( - feature_times[1][0], past.start_time() - lags_past[0] * past.freq - ) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual( - feature_times[2][0], future.start_time() - lags_future_1[0] * future.freq + assert len(feature_times[0]) == 1 + assert feature_times[0][0] == target.start_time() - lags[0] * target.freq + assert len(feature_times[1]) == 1 + assert feature_times[1][0] == past.start_time() - lags_past[0] * past.freq + assert len(feature_times[2]) == 1 + assert ( + feature_times[2][0] == future.start_time() - lags_future_1[0] * future.freq ) # Feature time occurs before start of series: lags_future_2 = [4] @@ -657,9 +643,9 @@ def test_feature_times_extend_time_index_datetime_idx(self): lags_future_covariates=lags_future_2, is_training=False, ) - self.assertEqual(len(feature_times[2]), 1) - self.assertEqual( - feature_times[2][0], future.start_time() - lags_future_2[0] * future.freq + assert len(feature_times[2]) == 1 + assert ( + feature_times[2][0] == future.start_time() - lags_future_2[0] * future.freq ) def test_feature_times_future_lags_range_idx(self): @@ -681,8 +667,8 @@ def test_feature_times_future_lags_range_idx(self): is_training=False, ) # All times will be feature times: - self.assertEqual(len(feature_times[2]), future.n_timesteps) - self.assertTrue(feature_times[2].equals(future.time_index)) + assert len(feature_times[2]) == future.n_timesteps + assert feature_times[2].equals(future.time_index) # Case 2 - Positive lag: lags_future = [1] @@ -693,8 +679,8 @@ def test_feature_times_future_lags_range_idx(self): ) # Need to include new time at start of series; only last time will be excluded: extended_future = future.prepend_values([0]) - self.assertEqual(len(feature_times[2]), extended_future.n_timesteps - 1) - self.assertTrue(feature_times[2].equals(extended_future.time_index[:-1])) + assert len(feature_times[2]) == extended_future.n_timesteps - 1 + assert feature_times[2].equals(extended_future.time_index[:-1]) # Case 3 - Combo of negative, zero, and positive lags: lags_future = [-1, 0, 1] @@ -704,8 +690,8 @@ def test_feature_times_future_lags_range_idx(self): is_training=False, ) # Only first and last times will be excluded: - self.assertEqual(len(feature_times[2]), future.n_timesteps - 2) - self.assertTrue(feature_times[2].equals(future.time_index[1:-1])) + assert len(feature_times[2]) == future.n_timesteps - 2 + assert feature_times[2].equals(future.time_index[1:-1]) def test_feature_times_future_lags_datetime_idx(self): """ @@ -726,8 +712,8 @@ def test_feature_times_future_lags_datetime_idx(self): is_training=False, ) # All times will be feature times: - self.assertEqual(len(feature_times[2]), future.n_timesteps) - self.assertTrue(feature_times[2].equals(future.time_index)) + assert len(feature_times[2]) == future.n_timesteps + assert feature_times[2].equals(future.time_index) # Case 2 - Positive lag: lags_future = [1] @@ -738,8 +724,8 @@ def test_feature_times_future_lags_datetime_idx(self): ) # Need to include new time at start of series; only last time will be excluded: extended_future = future.prepend_values([0]) - self.assertEqual(len(feature_times[2]), extended_future.n_timesteps - 1) - self.assertTrue(feature_times[2].equals(extended_future.time_index[:-1])) + assert len(feature_times[2]) == extended_future.n_timesteps - 1 + assert feature_times[2].equals(extended_future.time_index[:-1]) # Case 3 - Combo of negative, zero, and positive lags: lags_future = [-1, 0, 1] @@ -749,8 +735,8 @@ def test_feature_times_future_lags_datetime_idx(self): is_training=False, ) # Only first and last times will be excluded: - self.assertEqual(len(feature_times[2]), future.n_timesteps - 2) - self.assertTrue(feature_times[2].equals(future.time_index[1:-1])) + assert len(feature_times[2]) == future.n_timesteps - 2 + assert feature_times[2].equals(future.time_index[1:-1]) def test_feature_times_unspecified_series(self): """ @@ -774,9 +760,9 @@ def test_feature_times_unspecified_series(self): feature_times = _get_feature_times( target_series=target, lags=lags, is_training=False ) - self.assertTrue(expected_target.equals(feature_times[0])) - self.assertEqual(feature_times[1], None) - self.assertEqual(feature_times[2], None) + assert expected_target.equals(feature_times[0]) + assert feature_times[1] is None + assert feature_times[2] is None # Specify only past, without target and future: feature_times = _get_feature_times( @@ -784,9 +770,9 @@ def test_feature_times_unspecified_series(self): lags_past_covariates=lags_past, is_training=False, ) - self.assertEqual(feature_times[0], None) - self.assertTrue(expected_past.equals(feature_times[1])) - self.assertEqual(feature_times[2], None) + assert feature_times[0] is None + assert expected_past.equals(feature_times[1]) + assert feature_times[2] is None # Specify only future, without target and past: feature_times = _get_feature_times( @@ -794,9 +780,9 @@ def test_feature_times_unspecified_series(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertEqual(feature_times[0], None) - self.assertEqual(feature_times[1], None) - self.assertTrue(expected_future.equals(feature_times[2])) + assert feature_times[0] is None + assert feature_times[1] is None + assert expected_future.equals(feature_times[2]) # Specify target and past, without future: feature_times = _get_feature_times( @@ -806,9 +792,9 @@ def test_feature_times_unspecified_series(self): lags_past_covariates=lags_past, is_training=False, ) - self.assertTrue(expected_target.equals(feature_times[0])) - self.assertTrue(expected_past.equals(feature_times[1])) - self.assertEqual(feature_times[2], None) + assert expected_target.equals(feature_times[0]) + assert expected_past.equals(feature_times[1]) + assert feature_times[2] is None # Specify target and future, without past: feature_times = _get_feature_times( @@ -818,9 +804,9 @@ def test_feature_times_unspecified_series(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertTrue(expected_target.equals(feature_times[0])) - self.assertEqual(feature_times[1], None) - self.assertTrue(expected_future.equals(feature_times[2])) + assert expected_target.equals(feature_times[0]) + assert feature_times[1] is None + assert expected_future.equals(feature_times[2]) # Specify past and future, without target: feature_times = _get_feature_times( @@ -830,9 +816,9 @@ def test_feature_times_unspecified_series(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertEqual(feature_times[0], None) - self.assertTrue(expected_past.equals(feature_times[1])) - self.assertTrue(expected_future.equals(feature_times[2])) + assert feature_times[0] is None + assert expected_past.equals(feature_times[1]) + assert expected_future.equals(feature_times[2]) def test_feature_times_unspecified_lag_or_series_warning(self): """ @@ -859,14 +845,11 @@ def test_feature_times_unspecified_lag_or_series_warning(self): lags_past_covariates=lags_past, is_training=False, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`future_covariates` was specified without accompanying " - "`lags_future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`future_covariates` was specified without accompanying " + "`lags_future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates` but not `future_covariates` when `is_training = False` with warnings.catch_warnings(record=True) as w: @@ -876,14 +859,11 @@ def test_feature_times_unspecified_lag_or_series_warning(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `future_covariates` but not `lags_future_covariates` and # `target_series` but not lags, when `is_training = True` @@ -896,14 +876,11 @@ def test_feature_times_unspecified_lag_or_series_warning(self): output_chunk_length=1, is_training=True, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`future_covariates` was specified without accompanying " - "`lags_future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`future_covariates` was specified without accompanying " + "`lags_future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates` but not `future_covariates`, and # `target_series` but not lags, when `is_training = True` @@ -916,14 +893,11 @@ def test_feature_times_unspecified_lag_or_series_warning(self): output_chunk_length=1, is_training=True, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) + assert str(w[0].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates` but not `future_covariates`, and # `past_covariates` but not `lags_past_covariates`, when `is_training = True` @@ -935,22 +909,16 @@ def test_feature_times_unspecified_lag_or_series_warning(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertEqual(len(w), 2) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue(issubclass(w[1].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`past_covariates` was specified without accompanying " - "`lags_past_covariates` and, thus, will be ignored." - ), + assert len(w) == 2 + assert issubclass(w[0].category, UserWarning) + assert issubclass(w[1].category, UserWarning) + assert str(w[0].message) == ( + "`past_covariates` was specified without accompanying " + "`lags_past_covariates` and, thus, will be ignored." ) - self.assertEqual( - str(w[1].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert str(w[1].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `lags_future_covariates` but not `future_covariates`, and # `past_covariates` but not `lags_past_covariates`, and `target_series` @@ -963,22 +931,16 @@ def test_feature_times_unspecified_lag_or_series_warning(self): output_chunk_length=1, is_training=True, ) - self.assertEqual(len(w), 2) - self.assertTrue(issubclass(w[0].category, UserWarning)) - self.assertTrue(issubclass(w[1].category, UserWarning)) - self.assertEqual( - str(w[0].message), - ( - "`past_covariates` was specified without accompanying " - "`lags_past_covariates` and, thus, will be ignored." - ), + assert len(w) == 2 + assert issubclass(w[0].category, UserWarning) + assert issubclass(w[1].category, UserWarning) + assert str(w[0].message) == ( + "`past_covariates` was specified without accompanying " + "`lags_past_covariates` and, thus, will be ignored." ) - self.assertEqual( - str(w[1].message), - ( - "`lags_future_covariates` was specified without accompanying " - "`future_covariates` and, thus, will be ignored." - ), + assert str(w[1].message) == ( + "`lags_future_covariates` was specified without accompanying " + "`future_covariates` and, thus, will be ignored." ) # Specify `target_series` but not `lags` when `is_training = False`: with warnings.catch_warnings(record=True) as w: @@ -990,8 +952,8 @@ def test_feature_times_unspecified_lag_or_series_warning(self): lags_future_covariates=lags_future, is_training=False, ) - self.assertEqual(len(w), 1) - self.assertTrue(issubclass(w[0].category, UserWarning)) + assert len(w) == 1 + assert issubclass(w[0].category, UserWarning) # Specify `target_series` but not `lags` when `is_training = True`; # this should *not* throw a warning: with warnings.catch_warnings(record=True) as w: @@ -1003,7 +965,7 @@ def test_feature_times_unspecified_lag_or_series_warning(self): lags_future_covariates=lags_future, is_training=True, ) - self.assertEqual(len(w), 0) + assert len(w) == 0 def test_feature_times_unspecified_training_inputs_error(self): """ @@ -1013,20 +975,18 @@ def test_feature_times_unspecified_training_inputs_error(self): """ output_chunk_length = 1 # Don't specify `target_series`: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times( output_chunk_length=output_chunk_length, is_training=True ) - self.assertEqual( - ("Must specify `target_series` when `is_training = True`."), - str(e.exception), + assert ("Must specify `target_series` when `is_training = True`.") == str( + err.value ) # Don't specify neither `target_series` nor `output_chunk_length` - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times(is_training=True) - self.assertEqual( - ("Must specify `target_series` when `is_training = True`."), - str(e.exception), + assert ("Must specify `target_series` when `is_training = True`.") == str( + err.value ) def test_feature_times_no_lags_specified_error(self): @@ -1035,11 +995,11 @@ def test_feature_times_no_lags_specified_error(self): when no lags have been specified. """ target = linear_timeseries(start=1, length=20, freq=1) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times(target_series=target, is_training=False) - self.assertEqual( - "Must specify at least one of: `lags`, `lags_past_covariates`, `lags_future_covariates`.", - str(e.exception), + assert ( + "Must specify at least one of: `lags`, `lags_past_covariates`, `lags_future_covariates`." + == str(err.value) ) def test_feature_times_series_too_short_error(self): @@ -1050,32 +1010,26 @@ def test_feature_times_series_too_short_error(self): """ series = linear_timeseries(start=1, length=2, freq=1) # `target_series` too short when predicting: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times(target_series=series, lags=[-20, -1], is_training=False) - self.assertEqual( - ( - "`target_series` must have at least `-min(lags) + max(lags) + 1` = 20 " - "timesteps; instead, it only has 2." - ), - str(e.exception), - ) + assert ( + "`target_series` must have at least `-min(lags) + max(lags) + 1` = 20 " + "timesteps; instead, it only has 2." + ) == str(err.value) # `target_series` too short when training: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times( target_series=series, lags=[-20], output_chunk_length=5, is_training=True, ) - self.assertEqual( - ( - "`target_series` must have at least `-min(lags) + output_chunk_length` = 25 " - "timesteps; instead, it only has 2." - ), - str(e.exception), - ) + assert ( + "`target_series` must have at least `-min(lags) + output_chunk_length` = 25 " + "timesteps; instead, it only has 2." + ) == str(err.value) # `past_covariates` too short when training: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times( target_series=series, past_covariates=series, @@ -1083,14 +1037,11 @@ def test_feature_times_series_too_short_error(self): output_chunk_length=1, is_training=True, ) - self.assertEqual( - ( - "`past_covariates` must have at least " - "`-min(lags_past_covariates) + max(lags_past_covariates) + 1` = 20 timesteps; " - "instead, it only has 2." - ), - str(e.exception), - ) + assert ( + "`past_covariates` must have at least " + "`-min(lags_past_covariates) + max(lags_past_covariates) + 1` = 20 timesteps; " + "instead, it only has 2." + ) == str(err.value) def test_feature_times_invalid_lag_values_error(self): """ @@ -1101,23 +1052,19 @@ def test_feature_times_invalid_lag_values_error(self): """ series = linear_timeseries(start=1, length=3, freq=1) # `lags` not <= -1: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times(target_series=series, lags=[0], is_training=False) - self.assertEqual( - ("`lags` must be a `Sequence` containing only `int` values less than 0."), - str(e.exception), - ) + assert ( + "`lags` must be a `Sequence` containing only `int` values less than 0." + ) == str(err.value) # `lags_past_covariates` not <= -1: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: _get_feature_times( past_covariates=series, lags_past_covariates=[0], is_training=False ) - self.assertEqual( - ( - "`lags_past_covariates` must be a `Sequence` containing only `int` values less than 0." - ), - str(e.exception), - ) + assert ( + "`lags_past_covariates` must be a `Sequence` containing only `int` values less than 0." + ) == str(err.value) # `lags_future_covariates` can be positive, negative, and/or zero - no error should be thrown: _get_feature_times( future_covariates=series, diff --git a/darts/tests/utils/tabularization/test_get_shared_times.py b/darts/tests/utils/tabularization/test_get_shared_times.py index c276dc1228..3f2b399734 100644 --- a/darts/tests/utils/tabularization/test_get_shared_times.py +++ b/darts/tests/utils/tabularization/test_get_shared_times.py @@ -1,8 +1,8 @@ from math import gcd import pandas as pd +import pytest -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.data.tabularization import get_shared_times from darts.utils.timeseries_generation import linear_timeseries @@ -15,7 +15,7 @@ def lcm(*integers): return a -class GetSharedTimesTestCase(DartsBaseTestClass): +class TestGetSharedTimes: """ Tests `get_shared_times` function defined in `darts.utils.data.tabularization`. @@ -34,9 +34,9 @@ def test_shared_times_equal_freq_range_idx(self): series_3 = linear_timeseries(start=5, end=15, freq=2) # Intersection of a single time index is just the original time index: - self.assertTrue(series_1.time_index.equals(get_shared_times(series_1))) - self.assertTrue(series_2.time_index.equals(get_shared_times(series_2))) - self.assertTrue(series_3.time_index.equals(get_shared_times(series_3))) + assert series_1.time_index.equals(get_shared_times(series_1)) + assert series_2.time_index.equals(get_shared_times(series_2)) + assert series_3.time_index.equals(get_shared_times(series_3)) # Intersection of two time indices begins at start time of later series # and stops at end time of earlier series. @@ -44,23 +44,17 @@ def test_shared_times_equal_freq_range_idx(self): expected_12 = linear_timeseries( start=series_2.start_time(), end=series_1.end_time(), freq=series_1.freq ) - self.assertTrue( - expected_12.time_index.equals(get_shared_times(series_1, series_2)) - ) + assert expected_12.time_index.equals(get_shared_times(series_1, series_2)) # Since `series_2` is before `series_3`: expected_23 = linear_timeseries( start=series_3.start_time(), end=series_2.end_time(), freq=series_2.freq ) - self.assertTrue( - expected_23.time_index.equals(get_shared_times(series_2, series_3)) - ) + assert expected_23.time_index.equals(get_shared_times(series_2, series_3)) # Since `series_1` is before `series_3`: expected_13 = linear_timeseries( start=series_3.start_time(), end=series_1.end_time(), freq=series_1.freq ) - self.assertTrue( - expected_13.time_index.equals(get_shared_times(series_1, series_3)) - ) + assert expected_13.time_index.equals(get_shared_times(series_1, series_3)) # Intersection of all three time series should begin at start of series_3 (i.e. # the last series to begin) and end at the end of series_1 (i.e. the first series @@ -68,10 +62,8 @@ def test_shared_times_equal_freq_range_idx(self): expected_123 = linear_timeseries( start=series_3.start_time(), end=series_1.end_time(), freq=series_1.freq ) - self.assertTrue( - expected_123.time_index.equals( - get_shared_times(series_1, series_2, series_3) - ) + assert expected_123.time_index.equals( + get_shared_times(series_1, series_2, series_3) ) def test_shared_times_equal_freq_datetime_idx(self): @@ -93,9 +85,9 @@ def test_shared_times_equal_freq_datetime_idx(self): ) # Intersection of a single time index is just the original time index: - self.assertTrue(series_1.time_index.equals(get_shared_times(series_1))) - self.assertTrue(series_2.time_index.equals(get_shared_times(series_2))) - self.assertTrue(series_3.time_index.equals(get_shared_times(series_3))) + assert series_1.time_index.equals(get_shared_times(series_1)) + assert series_2.time_index.equals(get_shared_times(series_2)) + assert series_3.time_index.equals(get_shared_times(series_3)) # Intersection of two time indices begins at start time of later series # and stops at end time of earlier series. @@ -103,23 +95,17 @@ def test_shared_times_equal_freq_datetime_idx(self): expected_12 = linear_timeseries( start=series_2.start_time(), end=series_1.end_time(), freq=series_1.freq ) - self.assertTrue( - expected_12.time_index.equals(get_shared_times(series_1, series_2)) - ) + assert expected_12.time_index.equals(get_shared_times(series_1, series_2)) # Since `series_2` is before `series_3`: expected_23 = linear_timeseries( start=series_3.start_time(), end=series_2.end_time(), freq=series_2.freq ) - self.assertTrue( - expected_23.time_index.equals(get_shared_times(series_2, series_3)) - ) + assert expected_23.time_index.equals(get_shared_times(series_2, series_3)) # Since `series_1` is before `series_3`: expected_13 = linear_timeseries( start=series_3.start_time(), end=series_1.end_time(), freq=series_1.freq ) - self.assertTrue( - expected_13.time_index.equals(get_shared_times(series_1, series_3)) - ) + assert expected_13.time_index.equals(get_shared_times(series_1, series_3)) # Intersection of all three time series should begin at start of series_3 (i.e. # the last series to begin) and end at the end of series_1 (i.e. the first series @@ -127,10 +113,8 @@ def test_shared_times_equal_freq_datetime_idx(self): expected_123 = linear_timeseries( start=series_3.start_time(), end=series_1.end_time(), freq=series_1.freq ) - self.assertTrue( - expected_123.time_index.equals( - get_shared_times(series_1, series_2, series_3) - ) + assert expected_123.time_index.equals( + get_shared_times(series_1, series_2, series_3) ) def test_shared_times_unequal_freq_range_idx(self): @@ -147,9 +131,9 @@ def test_shared_times_unequal_freq_range_idx(self): series_3 = linear_timeseries(start=5, end=17, freq=3) # Intersection of a single time index is just the original time index: - self.assertTrue(series_1.time_index.equals(get_shared_times(series_1))) - self.assertTrue(series_2.time_index.equals(get_shared_times(series_2))) - self.assertTrue(series_3.time_index.equals(get_shared_times(series_3))) + assert series_1.time_index.equals(get_shared_times(series_1)) + assert series_2.time_index.equals(get_shared_times(series_2)) + assert series_3.time_index.equals(get_shared_times(series_3)) # Intersection of two time indices begins at start time of later series # and stops at end time of earlier series. The frequency of the intersection @@ -165,9 +149,7 @@ def test_shared_times_unequal_freq_range_idx(self): # remove this point if present: if expected_12.time_index[-1] > series_1.end_time(): expected_12 = expected_12.drop_after(expected_12.time_index[-1]) - self.assertTrue( - expected_12.time_index.equals(get_shared_times(series_1, series_2)) - ) + assert expected_12.time_index.equals(get_shared_times(series_1, series_2)) # `series_2` is before `series_3`: expected_23 = linear_timeseries( start=series_3.start_time(), @@ -178,9 +160,7 @@ def test_shared_times_unequal_freq_range_idx(self): # remove this point if present: if expected_23.time_index[-1] > series_2.end_time(): expected_23 = expected_23.drop_after(expected_23.time_index[-1]) - self.assertTrue( - expected_23.time_index.equals(get_shared_times(series_2, series_3)) - ) + assert expected_23.time_index.equals(get_shared_times(series_2, series_3)) # `series_1` is before `series_3`: expected_13 = linear_timeseries( start=series_3.start_time(), @@ -191,9 +171,7 @@ def test_shared_times_unequal_freq_range_idx(self): # remove this point if present: if expected_13.time_index[-1] > series_1.end_time(): expected_13 = expected_13.drop_after(expected_13.time_index[-1]) - self.assertTrue( - expected_13.time_index.equals(get_shared_times(series_1, series_3)) - ) + assert expected_13.time_index.equals(get_shared_times(series_1, series_3)) # Intersection of all three time series should begin at start of series_3 (i.e. # the last series to begin) and end at the end of series_1 (i.e. the first series @@ -206,10 +184,8 @@ def test_shared_times_unequal_freq_range_idx(self): ) if expected_123.time_index[-1] > series_1.end_time(): expected_123 = expected_123.drop_after(expected_123.time_index[-1]) - self.assertTrue( - expected_123.time_index.equals( - get_shared_times(series_1, series_2, series_3) - ) + assert expected_123.time_index.equals( + get_shared_times(series_1, series_2, series_3) ) def test_shared_times_unequal_freq_datetime_idx(self): @@ -232,9 +208,9 @@ def test_shared_times_unequal_freq_datetime_idx(self): ) # Intersection of a single time index is just the original time index: - self.assertTrue(series_1.time_index.equals(get_shared_times(series_1))) - self.assertTrue(series_2.time_index.equals(get_shared_times(series_2))) - self.assertTrue(series_3.time_index.equals(get_shared_times(series_3))) + assert series_1.time_index.equals(get_shared_times(series_1)) + assert series_2.time_index.equals(get_shared_times(series_2)) + assert series_3.time_index.equals(get_shared_times(series_3)) # Intersection of two time indices begins at start time of later series # and stops at end time of earlier series. The frequency of the intersection @@ -251,9 +227,7 @@ def test_shared_times_unequal_freq_datetime_idx(self): # remove this point if present: if expected_12.time_index[-1] > series_1.end_time(): expected_12 = expected_12.drop_after(expected_12.time_index[-1]) - self.assertTrue( - expected_12.time_index.equals(get_shared_times(series_1, series_2)) - ) + assert expected_12.time_index.equals(get_shared_times(series_1, series_2)) # `series_2` is before `series_3`: freq_23 = f"{lcm(series_2.freq.n, series_3.freq.n)}d" expected_23 = linear_timeseries( @@ -265,9 +239,7 @@ def test_shared_times_unequal_freq_datetime_idx(self): # remove this point if present: if expected_23.time_index[-1] > series_2.end_time(): expected_23 = expected_23.drop_after(expected_23.time_index[-1]) - self.assertTrue( - expected_23.time_index.equals(get_shared_times(series_2, series_3)) - ) + assert expected_23.time_index.equals(get_shared_times(series_2, series_3)) # `series_1` is before `series_3`: freq_13 = f"{lcm(series_1.freq.n, series_3.freq.n)}d" expected_13 = linear_timeseries( @@ -279,9 +251,7 @@ def test_shared_times_unequal_freq_datetime_idx(self): # remove this point if present: if expected_13.time_index[-1] > series_1.end_time(): expected_13 = expected_13.drop_after(expected_13.time_index[-1]) - self.assertTrue( - expected_13.time_index.equals(get_shared_times(series_1, series_3)) - ) + assert expected_13.time_index.equals(get_shared_times(series_1, series_3)) # Intersection of all three time series should begin at start of series_3 (i.e. # the last series to begin) and end at the end of series_1 (i.e. the first series @@ -295,10 +265,8 @@ def test_shared_times_unequal_freq_datetime_idx(self): ) if expected_123.time_index[-1] > series_1.end_time(): expected_123 = expected_123.drop_after(expected_123.time_index[-1]) - self.assertTrue( - expected_123.time_index.equals( - get_shared_times(series_1, series_2, series_3) - ) + assert expected_123.time_index.equals( + get_shared_times(series_1, series_2, series_3) ) def test_shared_times_no_overlap_range_idx(self): @@ -309,10 +277,10 @@ def test_shared_times_no_overlap_range_idx(self): # Define `series_2` so that it starts after `series_1` ends: series_1 = linear_timeseries(start=1, end=11, freq=2) series_2 = linear_timeseries(start=series_1.end_time() + 1, length=5, freq=3) - self.assertEqual(get_shared_times(series_1, series_2), None) - self.assertEqual(get_shared_times(series_1, series_1, series_2), None) - self.assertEqual(get_shared_times(series_1, series_2, series_2), None) - self.assertEqual(get_shared_times(series_1, series_1, series_2, series_2), None) + assert get_shared_times(series_1, series_2) is None + assert get_shared_times(series_1, series_1, series_2) is None + assert get_shared_times(series_1, series_2, series_2) is None + assert get_shared_times(series_1, series_1, series_2, series_2) is None def test_shared_times_no_overlap_datetime_idx(self): """ @@ -326,10 +294,10 @@ def test_shared_times_no_overlap_datetime_idx(self): series_2 = linear_timeseries( start=series_1.end_time() + pd.Timedelta(1, "d"), length=5, freq="3d" ) - self.assertEqual(get_shared_times(series_1, series_2), None) - self.assertEqual(get_shared_times(series_1, series_1, series_2), None) - self.assertEqual(get_shared_times(series_1, series_2, series_2), None) - self.assertEqual(get_shared_times(series_1, series_1, series_2, series_2), None) + assert get_shared_times(series_1, series_2) is None + assert get_shared_times(series_1, series_1, series_2) is None + assert get_shared_times(series_1, series_2, series_2) is None + assert get_shared_times(series_1, series_1, series_2, series_2) is None def test_shared_times_single_time_point_overlap_range_idx(self): """ @@ -340,12 +308,10 @@ def test_shared_times_single_time_point_overlap_range_idx(self): series_1 = linear_timeseries(start=1, end=11, freq=2) series_2 = linear_timeseries(start=series_1.end_time(), length=5, freq=3) overlap_val = series_1.end_time() - self.assertEqual(get_shared_times(series_1, series_2), overlap_val) - self.assertEqual(get_shared_times(series_1, series_1, series_2), overlap_val) - self.assertEqual(get_shared_times(series_1, series_2, series_2), overlap_val) - self.assertEqual( - get_shared_times(series_1, series_1, series_2, series_2), overlap_val - ) + assert get_shared_times(series_1, series_2) == overlap_val + assert get_shared_times(series_1, series_1, series_2) == overlap_val + assert get_shared_times(series_1, series_2, series_2) == overlap_val + assert get_shared_times(series_1, series_1, series_2, series_2) == overlap_val def test_shared_times_single_time_point_overlap_datetime_idx(self): """ @@ -358,12 +324,10 @@ def test_shared_times_single_time_point_overlap_datetime_idx(self): ) series_2 = linear_timeseries(start=series_1.end_time(), length=5, freq="3d") overlap_val = series_1.end_time() - self.assertEqual(get_shared_times(series_1, series_2), overlap_val) - self.assertEqual(get_shared_times(series_1, series_1, series_2), overlap_val) - self.assertEqual(get_shared_times(series_1, series_2, series_2), overlap_val) - self.assertEqual( - get_shared_times(series_1, series_1, series_2, series_2), overlap_val - ) + assert get_shared_times(series_1, series_2) == overlap_val + assert get_shared_times(series_1, series_1, series_2) == overlap_val + assert get_shared_times(series_1, series_2, series_2) == overlap_val + assert get_shared_times(series_1, series_1, series_2, series_2) == overlap_val def test_shared_times_identical_inputs_range_idx(self): """ @@ -372,11 +336,9 @@ def test_shared_times_identical_inputs_range_idx(self): we expect that the unaltered time index of the series is returned. """ series = linear_timeseries(start=0, length=5, freq=1) - self.assertTrue(series.time_index.equals(get_shared_times(series))) - self.assertTrue(series.time_index.equals(get_shared_times(series, series))) - self.assertTrue( - series.time_index.equals(get_shared_times(series, series, series)) - ) + assert series.time_index.equals(get_shared_times(series)) + assert series.time_index.equals(get_shared_times(series, series)) + assert series.time_index.equals(get_shared_times(series, series, series)) def test_shared_times_identical_inputs_datetime_idx(self): """ @@ -385,11 +347,9 @@ def test_shared_times_identical_inputs_datetime_idx(self): we expect that the unaltered time index of the series is returned. """ series = linear_timeseries(start=pd.Timestamp("1/1/2000"), length=5, freq="d") - self.assertTrue(series.time_index.equals(get_shared_times(series))) - self.assertTrue(series.time_index.equals(get_shared_times(series, series))) - self.assertTrue( - series.time_index.equals(get_shared_times(series, series, series)) - ) + assert series.time_index.equals(get_shared_times(series)) + assert series.time_index.equals(get_shared_times(series, series)) + assert series.time_index.equals(get_shared_times(series, series, series)) def test_shared_times_unspecified_inputs(self): """ @@ -401,14 +361,12 @@ def test_shared_times_unspecified_inputs(self): """ # Pass `None` with series/time index input: series = linear_timeseries(start=pd.Timestamp("1/1/2000"), length=5, freq="d") - self.assertEqual(get_shared_times(None), None) - self.assertTrue(series.time_index.equals(get_shared_times(series, None))) - self.assertTrue(series.time_index.equals(get_shared_times(None, series, None))) - self.assertTrue( - series.time_index.equals(get_shared_times(None, series.time_index, None)) - ) + assert get_shared_times(None) is None + assert series.time_index.equals(get_shared_times(series, None)) + assert series.time_index.equals(get_shared_times(None, series, None)) + assert series.time_index.equals(get_shared_times(None, series.time_index, None)) # Pass only `None` as input: - self.assertEqual(get_shared_times(None), None) + assert get_shared_times(None) is None def test_shared_times_time_index_inputs(self): """ @@ -422,16 +380,10 @@ def test_shared_times_time_index_inputs(self): intersection = pd.RangeIndex( start=series_2.start_time(), stop=series_1.end_time() + 1, step=2 ) - self.assertTrue( - intersection.equals(get_shared_times(series_1.time_index, series_2)) - ) - self.assertTrue( - intersection.equals(get_shared_times(series_1, series_2.time_index)) - ) - self.assertTrue( - intersection.equals( - get_shared_times(series_1.time_index, series_2.time_index) - ) + assert intersection.equals(get_shared_times(series_1.time_index, series_2)) + assert intersection.equals(get_shared_times(series_1, series_2.time_index)) + assert intersection.equals( + get_shared_times(series_1.time_index, series_2.time_index) ) def test_shared_times_empty_input(self): @@ -440,9 +392,9 @@ def test_shared_times_empty_input(self): given a non-`None` input with no timesteps. """ series = linear_timeseries(start=0, length=0, freq=1) - self.assertEqual(get_shared_times(series), None) - self.assertEqual(get_shared_times(series.time_index), None) - self.assertEqual(get_shared_times(series, series.time_index), None) + assert get_shared_times(series) is None + assert get_shared_times(series.time_index) is None + assert get_shared_times(series, series.time_index) is None def test_shared_times_different_time_index_types_error(self): """ @@ -451,13 +403,10 @@ def test_shared_times_different_time_index_types_error(self): """ series_1 = linear_timeseries(start=1, length=5, freq=1) series_2 = linear_timeseries(start=pd.Timestamp("1/1/2000"), length=5, freq="d") - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: get_shared_times(series_1, series_2) - self.assertEqual( - ( - "Specified series and/or times must all " - "have the same type of `time_index` (i.e. all " - "`pd.RangeIndex` or all `pd.DatetimeIndex`)." - ), - str(e.exception), - ) + assert ( + "Specified series and/or times must all " + "have the same type of `time_index` (i.e. all " + "`pd.RangeIndex` or all `pd.DatetimeIndex`)." + ) == str(err.value) diff --git a/darts/tests/utils/tabularization/test_get_shared_times_bounds.py b/darts/tests/utils/tabularization/test_get_shared_times_bounds.py index ef79853255..7435457021 100644 --- a/darts/tests/utils/tabularization/test_get_shared_times_bounds.py +++ b/darts/tests/utils/tabularization/test_get_shared_times_bounds.py @@ -1,11 +1,11 @@ import pandas as pd +import pytest -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.data.tabularization import get_shared_times_bounds from darts.utils.timeseries_generation import linear_timeseries -class GetSharedTimesBoundsTestCase(DartsBaseTestClass): +class TestGetSharedTimesBounds: """ Tests `get_shared_times_bounds` function defined in `darts.utils.data.tabularization`. """ @@ -19,10 +19,7 @@ def test_shared_times_bounds_overlapping_range_idx_series(self): series_1 = linear_timeseries(start=1, end=15, freq=3) series_2 = linear_timeseries(start=2, end=20, freq=2) expected_bounds = (series_2.start_time(), series_1.end_time()) - self.assertEqual( - get_shared_times_bounds(series_1, series_2), - expected_bounds, - ) + assert get_shared_times_bounds(series_1, series_2) == expected_bounds def test_shared_times_bounds_overlapping_datetime_idx_series(self): """ @@ -37,10 +34,7 @@ def test_shared_times_bounds_overlapping_datetime_idx_series(self): start=pd.Timestamp("1/2/2000"), end=pd.Timestamp("1/20/2000"), freq="2d" ) expected_bounds = (series_2.start_time(), series_1.end_time()) - self.assertEqual( - get_shared_times_bounds(series_1, series_2), - expected_bounds, - ) + assert get_shared_times_bounds(series_1, series_2) == expected_bounds def test_shared_times_bounds_time_idx_inputs(self): """ @@ -55,27 +49,21 @@ def test_shared_times_bounds_time_idx_inputs(self): expected_bounds = (series_2.start_time(), series_1.end_time()) # Pass only `series_1.time_index` - bounds are just start and # end times of this series: - self.assertEqual( - get_shared_times_bounds(series_1.time_index), - (series_1.start_time(), series_1.end_time()), + assert get_shared_times_bounds(series_1.time_index) == ( + series_1.start_time(), + series_1.end_time(), ) # Pass only `series_2.time_index` - bounds are just start and # end times of this series: - self.assertEqual( - get_shared_times_bounds(series_2.time_index), - (series_2.start_time(), series_2.end_time()), - ) - self.assertEqual( - get_shared_times_bounds(series_1.time_index, series_2), - expected_bounds, + assert get_shared_times_bounds(series_2.time_index) == ( + series_2.start_time(), + series_2.end_time(), ) - self.assertEqual( - get_shared_times_bounds(series_1, series_2.time_index), - expected_bounds, - ) - self.assertEqual( - get_shared_times_bounds(series_1.time_index, series_2.time_index), - expected_bounds, + assert get_shared_times_bounds(series_1.time_index, series_2) == expected_bounds + assert get_shared_times_bounds(series_1, series_2.time_index) == expected_bounds + assert ( + get_shared_times_bounds(series_1.time_index, series_2.time_index) + == expected_bounds ) def test_shared_times_bounds_subset_series_range_idx(self): @@ -100,9 +88,8 @@ def test_shared_times_bounds_subset_series_range_idx(self): .drop_before(subseries.time_index[1]) ) expected_bounds = (subsubseries.start_time(), subsubseries.end_time()) - self.assertEqual( - get_shared_times_bounds(series, subseries, subsubseries), - expected_bounds, + assert ( + get_shared_times_bounds(series, subseries, subsubseries) == expected_bounds ) def test_shared_times_bounds_subset_series_datetime_idx(self): @@ -127,9 +114,8 @@ def test_shared_times_bounds_subset_series_datetime_idx(self): .drop_before(subseries.time_index[1]) ) expected_bounds = (subsubseries.start_time(), subsubseries.end_time()) - self.assertEqual( - get_shared_times_bounds(series, subseries, subsubseries), - expected_bounds, + assert ( + get_shared_times_bounds(series, subseries, subsubseries) == expected_bounds ) def test_shared_times_bounds_identical_inputs_range_idx(self): @@ -142,8 +128,8 @@ def test_shared_times_bounds_identical_inputs_range_idx(self): """ series = linear_timeseries(start=0, length=5, freq=1) expected = (series.start_time(), series.end_time()) - self.assertEqual(get_shared_times_bounds(series, series), expected) - self.assertEqual(get_shared_times_bounds(series, series, series), expected) + assert get_shared_times_bounds(series, series) == expected + assert get_shared_times_bounds(series, series, series) == expected def test_shared_times_bounds_identical_inputs_datetime_idx(self): """ @@ -155,9 +141,9 @@ def test_shared_times_bounds_identical_inputs_datetime_idx(self): """ series = linear_timeseries(start=pd.Timestamp("1/1/2000"), length=5, freq="d") expected = (series.start_time(), series.end_time()) - self.assertEqual(get_shared_times_bounds(series), expected) - self.assertEqual(get_shared_times_bounds(series, series), expected) - self.assertEqual(get_shared_times_bounds(series, series, series), expected) + assert get_shared_times_bounds(series) == expected + assert get_shared_times_bounds(series, series) == expected + assert get_shared_times_bounds(series, series, series) == expected def test_shared_times_bounds_unspecified_inputs(self): """ @@ -170,13 +156,13 @@ def test_shared_times_bounds_unspecified_inputs(self): # `None` is passed alonside a non-`None` input: series = linear_timeseries(start=0, length=5, freq=1) expected = (series.start_time(), series.end_time()) - self.assertEqual(get_shared_times_bounds(series, None), expected) - self.assertEqual(get_shared_times_bounds(None, series), expected) - self.assertEqual(get_shared_times_bounds(None, series, None), expected) + assert get_shared_times_bounds(series, None) == expected + assert get_shared_times_bounds(None, series) == expected + assert get_shared_times_bounds(None, series, None) == expected # `None` should be returned if no series specified: - self.assertEqual(get_shared_times_bounds(None), None) - self.assertEqual(get_shared_times_bounds(None, None, None), None) + assert get_shared_times_bounds(None) is None + assert get_shared_times_bounds(None, None, None) is None def test_shared_times_bounds_single_idx_overlap_range_idx(self): """ @@ -189,17 +175,17 @@ def test_shared_times_bounds_single_idx_overlap_range_idx(self): # value - bounds should be start time and end time of # this single-valued series: series = linear_timeseries(start=0, length=1, freq=1) - self.assertEqual( - get_shared_times_bounds(series, series), - (series.start_time(), series.end_time()), + assert get_shared_times_bounds(series, series) == ( + series.start_time(), + series.end_time(), ) # `series_1` and `series_2` share only a single overlap point # at the end of `series_1`: series_1 = linear_timeseries(start=0, length=3, freq=1) series_2 = linear_timeseries(start=series_1.end_time(), length=2, freq=2) - self.assertEqual( - get_shared_times_bounds(series_1, series_2), - (series_1.end_time(), series_2.start_time()), + assert get_shared_times_bounds(series_1, series_2) == ( + series_1.end_time(), + series_2.start_time(), ) def test_shared_times_bounds_single_idx_overlap_datetime_idx(self): @@ -213,17 +199,17 @@ def test_shared_times_bounds_single_idx_overlap_datetime_idx(self): # value - bounds should be start time and end time of # this single-valued series: series = linear_timeseries(start=pd.Timestamp("1/1/2000"), length=1, freq="d") - self.assertEqual( - get_shared_times_bounds(series, series), - (series.start_time(), series.end_time()), + assert get_shared_times_bounds(series, series) == ( + series.start_time(), + series.end_time(), ) # `series_1` and `series_2` share only a single overlap point # at the end of `series_1`: series_1 = linear_timeseries(start=pd.Timestamp("1/1/2000"), length=3, freq="d") series_2 = linear_timeseries(start=series_1.end_time(), length=2, freq="2d") - self.assertEqual( - get_shared_times_bounds(series_1, series_2), - (series_1.end_time(), series_2.start_time()), + assert get_shared_times_bounds(series_1, series_2) == ( + series_1.end_time(), + series_2.start_time(), ) def test_shared_times_bounds_no_overlap_range_idx(self): @@ -235,8 +221,8 @@ def test_shared_times_bounds_no_overlap_range_idx(self): # Have `series_2` begin after the end of `series_1`: series_1 = linear_timeseries(start=0, length=5, freq=1) series_2 = linear_timeseries(start=series_1.end_time() + 1, length=6, freq=2) - self.assertEqual(get_shared_times_bounds(series_1, series_2), None) - self.assertEqual(get_shared_times_bounds(series_2, series_1, series_2), None) + assert get_shared_times_bounds(series_1, series_2) is None + assert get_shared_times_bounds(series_2, series_1, series_2) is None def test_shared_times_bounds_no_overlap_datetime_idx(self): """ @@ -249,8 +235,8 @@ def test_shared_times_bounds_no_overlap_datetime_idx(self): series_2 = linear_timeseries( start=series_1.end_time() + pd.Timedelta("1d"), length=6, freq="2d" ) - self.assertEqual(get_shared_times_bounds(series_1, series_2), None) - self.assertEqual(get_shared_times_bounds(series_2, series_1, series_2), None) + assert get_shared_times_bounds(series_1, series_2) is None + assert get_shared_times_bounds(series_2, series_1, series_2) is None def test_shared_times_bounds_different_time_idx_types_error(self): """ @@ -260,16 +246,13 @@ def test_shared_times_bounds_different_time_idx_types_error(self): """ series_1 = linear_timeseries(start=1, length=5, freq=1) series_2 = linear_timeseries(start=pd.Timestamp("1/1/2000"), length=5, freq="d") - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: get_shared_times_bounds(series_1, series_2) - self.assertEqual( - ( - "Specified series and/or times must all " - "have the same type of `time_index` (i.e. all " - "`pd.RangeIndex` or all `pd.DatetimeIndex`)." - ), - str(e.exception), - ) + assert ( + "Specified series and/or times must all " + "have the same type of `time_index` (i.e. all " + "`pd.RangeIndex` or all `pd.DatetimeIndex`)." + ) == str(err.value) def test_shared_times_bounds_empty_input(self): """ @@ -277,6 +260,6 @@ def test_shared_times_bounds_empty_input(self): handed a non-`None` input that has no timesteps. """ series = linear_timeseries(start=0, length=0, freq=1) - self.assertEqual(get_shared_times_bounds(series), None) - self.assertEqual(get_shared_times_bounds(series.time_index), None) - self.assertEqual(get_shared_times_bounds(series, series.time_index), None) + assert get_shared_times_bounds(series) is None + assert get_shared_times_bounds(series.time_index) is None + assert get_shared_times_bounds(series, series.time_index) is None diff --git a/darts/tests/utils/tabularization/test_strided_moving_window.py b/darts/tests/utils/tabularization/test_strided_moving_window.py index 1c3bdeff3b..164e9bea94 100644 --- a/darts/tests/utils/tabularization/test_strided_moving_window.py +++ b/darts/tests/utils/tabularization/test_strided_moving_window.py @@ -1,12 +1,12 @@ from itertools import product import numpy as np +import pytest -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.data.tabularization import strided_moving_window -class StridedMovingWindowTestCase(DartsBaseTestClass): +class TestStridedMovingWindow: """ Tests `strided_moving_window` function defined in `darts.utils.data.tabularization`. @@ -45,7 +45,7 @@ def test_strided_moving_windows_extracted_windows(self): expected = np.moveaxis(x, axis, -1)[ :, :, window_start_idx : window_start_idx + window_len ] - self.assertTrue(np.allclose(window, expected)) + assert np.allclose(window, expected) def test_strided_moving_window_invalid_stride_error(self): """ @@ -54,19 +54,13 @@ def test_strided_moving_window_invalid_stride_error(self): """ x = np.arange(1) # `stride` isn't positive: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: strided_moving_window(x, window_len=1, stride=0) - self.assertEqual( - ("`stride` must be a positive `int`."), - str(e.exception), - ) + assert ("`stride` must be a positive `int`.") == str(err.value) # `stride` is `float`, not `int`: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: strided_moving_window(x, window_len=1, stride=1.1) - self.assertEqual( - ("`stride` must be a positive `int`."), - str(e.exception), - ) + assert ("`stride` must be a positive `int`.") == str(err.value) def test_strided_moving_window_negative_window_len_error(self): """ @@ -75,19 +69,13 @@ def test_strided_moving_window_negative_window_len_error(self): """ x = np.arange(1) # `window_len` isn't positive: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: strided_moving_window(x, window_len=0, stride=1) - self.assertEqual( - ("`window_len` must be a positive `int`."), - str(e.exception), - ) + assert ("`window_len` must be a positive `int`.") == str(err.value) # `window_len` is `float`, not `int`: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: strided_moving_window(x, window_len=1.1, stride=1) - self.assertEqual( - ("`window_len` must be a positive `int`."), - str(e.exception), - ) + assert ("`window_len` must be a positive `int`.") == str(err.value) def test_strided_moving_window_pass_invalid_axis_error(self): """ @@ -97,19 +85,13 @@ def test_strided_moving_window_pass_invalid_axis_error(self): """ x = np.arange(1) # `axis` NOT an int: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: strided_moving_window(x, window_len=1, stride=1, axis=0.1) - self.assertEqual( - ("`axis` must be an `int` that is less than `x.ndim`."), - str(e.exception), - ) + assert ("`axis` must be an `int` that is less than `x.ndim`.") == str(err.value) # `axis` NOT < x.ndim: - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: strided_moving_window(x, window_len=1, stride=1, axis=1) - self.assertEqual( - ("`axis` must be an `int` that is less than `x.ndim`."), - str(e.exception), - ) + assert ("`axis` must be an `int` that is less than `x.ndim`.") == str(err.value) def test_strided_moving_window_window_len_too_large_error(self): """ @@ -117,9 +99,8 @@ def test_strided_moving_window_window_len_too_large_error(self): is set to a value larger than `x.shape[axis]`. """ x = np.arange(1) - with self.assertRaises(ValueError) as e: + with pytest.raises(ValueError) as err: strided_moving_window(x, window_len=2, stride=1) - self.assertEqual( - ("`window_len` must be less than or equal to x.shape[axis]."), - str(e.exception), + assert ("`window_len` must be less than or equal to x.shape[axis].") == str( + err.value ) diff --git a/darts/tests/utils/test_likelihood_models.py b/darts/tests/utils/test_likelihood_models.py index 72331eca44..3c0dd67bc9 100644 --- a/darts/tests/utils/test_likelihood_models.py +++ b/darts/tests/utils/test_likelihood_models.py @@ -1,7 +1,6 @@ from itertools import combinations from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass logger = get_logger(__name__) @@ -51,17 +50,17 @@ if TORCH_AVAILABLE: - class LikelihoodModelTestCase(DartsBaseTestClass): + class TestLikelihoodModel: def test_intra_class_equality(self): for _, model_pair in likelihood_models.items(): - self.assertEqual(model_pair[0], model_pair[0]) - self.assertEqual(model_pair[1], model_pair[1]) - self.assertNotEqual(model_pair[0], model_pair[1]) + assert model_pair[0] == model_pair[0] + assert model_pair[1] == model_pair[1] + assert model_pair[0] != model_pair[1] def test_inter_class_equality(self): model_combinations = combinations(likelihood_models.keys(), 2) for (first_model_name, second_model_name) in model_combinations: - self.assertNotEqual( - likelihood_models[first_model_name][0], - likelihood_models[second_model_name][0], + assert ( + likelihood_models[first_model_name][0] + != likelihood_models[second_model_name][0] ) diff --git a/darts/tests/utils/test_losses.py b/darts/tests/utils/test_losses.py index 92e0a50b1b..329ae45dbc 100644 --- a/darts/tests/utils/test_losses.py +++ b/darts/tests/utils/test_losses.py @@ -12,10 +12,9 @@ if TORCH_AVAILABLE: - from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.losses import MAELoss, MapeLoss, SmapeLoss - class LossesTestCase(DartsBaseTestClass): + class TestLosses: x = torch.tensor([1.1, 2.2, 0.6345, -1.436]) y = torch.tensor([1.5, 0.5]) @@ -26,8 +25,8 @@ def helper_test_loss(self, exp_loss_val, exp_w_grad, loss_fn): lval = loss_fn(y_hat, self.y) lval.backward() - self.assertTrue(torch.allclose(lval, exp_loss_val, atol=1e-3)) - self.assertTrue(torch.allclose(W.grad, exp_w_grad, atol=1e-3)) + assert torch.allclose(lval, exp_loss_val, atol=1e-3) + assert torch.allclose(W.grad, exp_w_grad, atol=1e-3) def test_smape_loss(self): exp_val = torch.tensor(0.7753) diff --git a/darts/tests/utils/test_missing_values.py b/darts/tests/utils/test_missing_values.py index 4e55a5ee36..5df8a1cf2e 100644 --- a/darts/tests/utils/test_missing_values.py +++ b/darts/tests/utils/test_missing_values.py @@ -2,11 +2,10 @@ import pandas as pd from darts import TimeSeries -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.missing_values import fill_missing_values, missing_values_ratio -class MissingValuesTestCase(DartsBaseTestClass): +class TestMissingValues: time = pd.date_range("20130101", "20130130") lin = [float(i) for i in range(len(time))] @@ -35,10 +34,10 @@ def test_fill_constant(self): ) # Check that no changes are made if there are no missing values - self.assertEqual(self.series1, fill_missing_values(self.series1, "auto")) + assert self.series1 == fill_missing_values(self.series1, "auto") # Check that a constant function is filled to a constant function - self.assertEqual(self.series1, fill_missing_values(seriesA, "auto")) + assert self.series1 == fill_missing_values(seriesA, "auto") def test_linear(self): seriesB: TimeSeries = TimeSeries.from_times_and_values( @@ -46,7 +45,7 @@ def test_linear(self): ) # Check for linear interpolation part - self.assertEqual(self.series2, fill_missing_values(seriesB, "auto")) + assert self.series2 == fill_missing_values(seriesB, "auto") def test_bfill(self): seriesC: TimeSeries = TimeSeries.from_times_and_values( @@ -54,22 +53,21 @@ def test_bfill(self): ) # Check that auto-backfill works properly - self.assertEqual(self.series3, fill_missing_values(seriesC, "auto")) + assert self.series3 == fill_missing_values(seriesC, "auto") def test_ffil(self): seriesD: TimeSeries = TimeSeries.from_times_and_values( self.time, np.array(self.lin[:20] + [np.nan] * 10) ) - self.assertEqual(self.series4, fill_missing_values(seriesD, "auto")) + assert self.series4 == fill_missing_values(seriesD, "auto") def test_fill_quad(self): seriesE: TimeSeries = TimeSeries.from_times_and_values( self.time, np.array(self.cub[:10] + [np.nan] * 10 + self.cub[-10:]) ) - self.assertEqual( - self.series5, - round(fill_missing_values(seriesE, "auto", method="quadratic"), 7), + assert self.series5 == round( + fill_missing_values(seriesE, "auto", method="quadratic"), 7 ) def test_multivariate_fill(self): @@ -82,9 +80,8 @@ def test_multivariate_fill(self): seriesB: TimeSeries = TimeSeries.from_times_and_values( self.time, np.array(self.lin[:10] + [np.nan] * 10 + self.lin[-10:]) ) - self.assertEqual( - self.series1.stack(self.series2), - fill_missing_values(seriesA.stack(seriesB), "auto"), + assert self.series1.stack(self.series2) == fill_missing_values( + seriesA.stack(seriesB), "auto" ) def test_missing_values_ratio(self): @@ -93,7 +90,7 @@ def test_missing_values_ratio(self): ) # univariate case - self.assertEqual(missing_values_ratio(seriesF), 0.1) + assert missing_values_ratio(seriesF) == 0.1 # multivariate case - self.assertEqual(missing_values_ratio(seriesF.stack(seriesF)), 0.1) + assert missing_values_ratio(seriesF.stack(seriesF)) == 0.1 diff --git a/darts/tests/utils/test_model_selection.py b/darts/tests/utils/test_model_selection.py index babad43915..7b33836b75 100644 --- a/darts/tests/utils/test_model_selection.py +++ b/darts/tests/utils/test_model_selection.py @@ -1,4 +1,5 @@ -from darts.tests.base_test_class import DartsBaseTestClass +import pytest + from darts.utils.model_selection import MODEL_AWARE, SIMPLE, train_test_split from darts.utils.timeseries_generation import constant_timeseries @@ -11,42 +12,40 @@ def verify_shape(dataset, rows, cols): return len(dataset) == rows and all(len(row) == cols for row in dataset) -class ClassTrainTestSplitTestCase(DartsBaseTestClass): +class TestClassTrainTestSplit: def test_parameters_for_axis_0(self): - train_test_split(make_dataset(2, 10), axis=0, test_size=1) - # expecting no exception - self.assertTrue(True) + train_test_split(make_dataset(2, 10), axis=0, test_size=1) def test_parameters_for_axis_1_no_n(self): - with self.assertRaisesRegex( - AttributeError, - "You need to provide non-zero `horizon` and `input_size` parameters when axis=1", - ): + with pytest.raises(AttributeError) as err: train_test_split( make_dataset(1, 10), axis=1, horizon=1, vertical_split_type=MODEL_AWARE ) + assert ( + str(err.value) + == "You need to provide non-zero `horizon` and `input_size` parameters when axis=1" + ) def test_parameters_for_axis_1_no_horizon(self): - with self.assertRaisesRegex( - AttributeError, - "You need to provide non-zero `horizon` and `input_size` parameters when axis=1", - ): + with pytest.raises(AttributeError) as err: train_test_split( make_dataset(1, 10), axis=1, input_size=1, vertical_split_type=MODEL_AWARE, ) + assert ( + str(err.value) + == "You need to provide non-zero `horizon` and `input_size` parameters when axis=1" + ) def test_empty_dataset(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): train_test_split([]) def test_horiz_number_of_samples_too_small(self): - with self.assertRaisesRegex( - AttributeError, "Not enough data to create training and test sets" - ): + with pytest.raises(AttributeError) as err: train_set, test_set = train_test_split( make_dataset(1, 10), axis=1, @@ -55,34 +54,31 @@ def test_horiz_number_of_samples_too_small(self): test_size=1, vertical_split_type=MODEL_AWARE, ) + assert str(err.value) == "Not enough data to create training and test sets" def test_sunny_day_horiz_split(self): train_set, test_set = train_test_split(make_dataset(8, 10)) - self.assertTrue( - verify_shape(train_set, 6, 10) and verify_shape(test_set, 2, 10), - "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( - len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) - ), + assert verify_shape(train_set, 6, 10) and verify_shape( + test_set, 2, 10 + ), "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( + len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) ) def test_sunny_day_horiz_split_absolute(self): train_set, test_set = train_test_split(make_dataset(8, 10), test_size=2) - self.assertTrue( - verify_shape(train_set, 6, 10) and verify_shape(test_set, 2, 10), - "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( - len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) - ), + assert verify_shape(train_set, 6, 10) and verify_shape( + test_set, 2, 10 + ), "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( + len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) ) def test_horiz_split_overindexing_train_set(self): train_set, test_set = train_test_split(make_dataset(8, 10), lazy=True) - - with self.assertRaisesRegex( - IndexError, "Exceeded the size of the training sequence." - ): + with pytest.raises(IndexError) as err: train_set[6] + assert str(err.value) == "Exceeded the size of the training sequence." def test_horiz_split_last_index_train_set(self): train_set, test_set = train_test_split(make_dataset(8, 10), lazy=True) @@ -92,10 +88,9 @@ def test_horiz_split_last_index_train_set(self): def test_horiz_split_overindexing_test_set(self): train_set, test_set = train_test_split(make_dataset(8, 10), lazy=True) - with self.assertRaisesRegex( - IndexError, "Exceeded the size of the test sequence." - ): + with pytest.raises(IndexError) as err: test_set[2] + assert str(err.value) == "Exceeded the size of the test sequence." def test_horiz_split_last_index_test_set(self): train_set, test_set = train_test_split(make_dataset(8, 10)) @@ -111,20 +106,17 @@ def test_sunny_day_vertical_split(self): vertical_split_type=MODEL_AWARE, ) - self.assertTrue( - verify_shape(train_set, 2, 151) and verify_shape(test_set, 2, 169), - "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( - len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) - ), + assert verify_shape(train_set, 2, 151) and verify_shape( + test_set, 2, 169 + ), "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( + len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) ) # test 7 def test_test_split_absolute_number_horiz(self): train_set, test_set = train_test_split(make_dataset(4, 10), axis=0, test_size=2) - self.assertTrue( - verify_shape(train_set, 2, 10) and verify_shape(test_set, 2, 10) - ) + assert verify_shape(train_set, 2, 10) and verify_shape(test_set, 2, 10) # test 8 def test_test_split_absolute_number_vertical(self): @@ -137,17 +129,14 @@ def test_test_split_absolute_number_vertical(self): vertical_split_type=MODEL_AWARE, ) - self.assertTrue( - verify_shape(train_set, 4, 7) and verify_shape(test_set, 4, 4), - "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( - len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) - ), + assert verify_shape(train_set, 4, 7) and verify_shape( + test_set, 4, 4 + ), "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( + len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) ) def test_negative_test_start_index(self): - with self.assertRaisesRegex( - AttributeError, "Not enough data to create training and test sets" - ): + with pytest.raises(AttributeError) as err: train_set, test_set = train_test_split( make_dataset(1, 10), axis=1, @@ -156,11 +145,10 @@ def test_negative_test_start_index(self): test_size=1, vertical_split_type=MODEL_AWARE, ) + assert str(err.value) == "Not enough data to create training and test sets" def test_horiz_split_horizon_equal_to_ts_length(self): - with self.assertRaisesRegex( - AttributeError, "Not enough data to create training and test sets" - ): + with pytest.raises(AttributeError) as err: train_set, test_set = train_test_split( make_dataset(1, 10), axis=1, @@ -169,9 +157,10 @@ def test_horiz_split_horizon_equal_to_ts_length(self): test_size=1, vertical_split_type=MODEL_AWARE, ) + assert str(err.value) == "Not enough data to create training and test sets" def test_single_timeseries_no_horizon_no_n(self): - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): # even if the default axis is 0, but since it is a single timeseries, default axis is 1 train_test_split( constant_timeseries(value=123, length=10), @@ -188,11 +177,10 @@ def test_single_timeseries_sunny_day(self): vertical_split_type=MODEL_AWARE, ) - self.assertTrue( - len(train_set) == 7 and len(test_set) == 4, - "Wrong shapes: training set shape: {}; test set shape {}".format( - len(train_set), len(test_set) - ), + assert ( + len(train_set) == 7 and len(test_set) == 4 + ), "Wrong shapes: training set shape: {}; test set shape {}".format( + len(train_set), len(test_set) ) def test_multi_timeseries_variable_ts_length_sunny_day(self): @@ -212,11 +200,12 @@ def test_multi_timeseries_variable_ts_length_sunny_day(self): train_lengths = [len(ts) for ts in train_set] test_lengths = [len(ts) for ts in test_set] - self.assertTrue( - train_lengths == [7, 97, 997] and test_lengths == [4, 4, 4], - "Wrong shapes: training set shape: {}; test set shape {}".format( - train_lengths, test_lengths - ), + assert train_lengths == [7, 97, 997] and test_lengths == [ + 4, + 4, + 4, + ], "Wrong shapes: training set shape: {}; test set shape {}".format( + train_lengths, test_lengths ) def test_multi_timeseries_variable_ts_length_one_ts_too_small(self): @@ -225,10 +214,7 @@ def test_multi_timeseries_variable_ts_length_one_ts_too_small(self): constant_timeseries(value=123, length=100), constant_timeseries(value=123, length=1000), ] - - with self.assertRaisesRegex( - AttributeError, "Not enough data to create training and test sets" - ): + with pytest.raises(AttributeError) as err: train_set, test_set = train_test_split( data, axis=1, @@ -237,17 +223,17 @@ def test_multi_timeseries_variable_ts_length_one_ts_too_small(self): horizon=18, vertical_split_type=MODEL_AWARE, ) + assert str(err.value) == "Not enough data to create training and test sets" def test_simple_vertical_split_sunny_day(self): train_set, test_set = train_test_split( make_dataset(4, 10), axis=1, vertical_split_type=SIMPLE, test_size=0.2 ) - self.assertTrue( - verify_shape(train_set, 4, 8) and verify_shape(test_set, 4, 2), - "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( - len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) - ), + assert verify_shape(train_set, 4, 8) and verify_shape( + test_set, 4, 2 + ), "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( + len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) ) def test_simple_vertical_split_sunny_day_absolute_split(self): @@ -255,16 +241,15 @@ def test_simple_vertical_split_sunny_day_absolute_split(self): make_dataset(4, 10), axis=1, vertical_split_type=SIMPLE, test_size=2 ) - self.assertTrue( - verify_shape(train_set, 4, 8) and verify_shape(test_set, 4, 2), - "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( - len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) - ), + assert verify_shape(train_set, 4, 8) and verify_shape( + test_set, 4, 2 + ), "Wrong shapes: training set shape: ({}, {}); test set shape ({}, {})".format( + len(train_set), len(train_set[0]), len(test_set), len(test_set[0]) ) def test_simple_vertical_split_exception_on_bad_param(self): # bad value for vertical_split_type - with self.assertRaises(AttributeError): + with pytest.raises(AttributeError): train_set, test_set = train_test_split( make_dataset(4, 10), axis=1, @@ -273,19 +258,14 @@ def test_simple_vertical_split_exception_on_bad_param(self): ) def test_simple_vertical_split_test_size_too_large(self): - - with self.assertRaisesRegex( - AttributeError, "`test_size` is bigger then timeseries length" - ): + with pytest.raises(AttributeError) as err: train_set, test_set = train_test_split( make_dataset(4, 10), axis=1, vertical_split_type=SIMPLE, test_size=11 ) + assert str(err.value) == "`test_size` is bigger then timeseries length" def test_model_aware_vertical_split_empty_training_set(self): - - with self.assertRaisesRegex( - AttributeError, "Not enough data to create training and test sets" - ): + with pytest.raises(AttributeError) as err: train_set, test_set = train_test_split( make_dataset(4, 10), axis=1, @@ -294,3 +274,4 @@ def test_model_aware_vertical_split_empty_training_set(self): horizon=3, input_size=2, ) + assert str(err.value) == "Not enough data to create training and test sets" diff --git a/darts/tests/utils/test_residuals.py b/darts/tests/utils/test_residuals.py index fa082b7944..664e49a1e5 100644 --- a/darts/tests/utils/test_residuals.py +++ b/darts/tests/utils/test_residuals.py @@ -1,8 +1,8 @@ import numpy as np +import pytest from darts.logging import get_logger from darts.models import LinearRegressionModel, NaiveSeasonal -from darts.tests.base_test_class import DartsBaseTestClass from darts.tests.models.forecasting.test_regression_models import dummy_timeseries from darts.utils.timeseries_generation import constant_timeseries as ct from darts.utils.timeseries_generation import linear_timeseries as lt @@ -10,7 +10,7 @@ logger = get_logger(__name__) -class TestResidualsTestCase(DartsBaseTestClass): +class TestResiduals: np.random.seed(42) @@ -103,11 +103,11 @@ def test_forecasting_residuals_cov_output(self): ) # if model is trained with covariates, should raise error when covariates are missing in residuals() - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_1.residuals(target_series_1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_1.residuals(target_series_1, past_covariates=past_covariates) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): model_1.residuals(target_series_1, future_covariates=future_covariates) diff --git a/darts/tests/utils/test_statistics.py b/darts/tests/utils/test_statistics.py index c8d9efe1f5..6b3ace96e3 100644 --- a/darts/tests/utils/test_statistics.py +++ b/darts/tests/utils/test_statistics.py @@ -1,10 +1,10 @@ import matplotlib.pyplot as plt import numpy as np import pandas as pd +import pytest from darts import TimeSeries from darts.datasets import AirPassengersDataset -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.statistics import ( check_seasonality, extract_trend_and_seasonality, @@ -25,16 +25,16 @@ from darts.utils.utils import ModelMode, SeasonalityMode -class TimeSeriesTestCase(DartsBaseTestClass): +class TestTimeSeries: def test_check_seasonality(self): pd_series = pd.Series(range(50), index=pd.date_range("20130101", "20130219")) pd_series = pd_series.map(lambda x: np.sin(x * np.pi / 3 + np.pi / 2)) series = TimeSeries.from_series(pd_series) - self.assertEqual((True, 6), check_seasonality(series)) - self.assertEqual((False, 3), check_seasonality(series, m=3)) + assert (True, 6) == check_seasonality(series) + assert (False, 3) == check_seasonality(series, m=3) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): check_seasonality(series.stack(series)) def test_granger_causality(self): @@ -52,26 +52,26 @@ def test_granger_causality(self): ) # Test univariate - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): granger_causality_tests(series_cause_1, series_effect_1, 10) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): granger_causality_tests(series_effect_1, series_cause_1, 10) # Test deterministic - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): granger_causality_tests(series_cause_1, series_effect_3, 10) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): granger_causality_tests(series_effect_3, series_cause_1, 10) # Test Frequency - with self.assertRaises(ValueError): + with pytest.raises(ValueError): granger_causality_tests(series_cause_2, series_effect_4, 10) # Test granger basics tests = granger_causality_tests(series_effect_2, series_effect_2, 10) - self.assertTrue(tests[1][0]["ssr_ftest"][1] > 0.99) + assert tests[1][0]["ssr_ftest"][1] > 0.99 tests = granger_causality_tests(series_cause_2, series_effect_2, 10) - self.assertTrue(tests[1][0]["ssr_ftest"][1] > 0.01) + assert tests[1][0]["ssr_ftest"][1] > 0.01 def test_stationarity_tests(self): series_1 = constant_timeseries(start=0, end=9999).stack( @@ -82,28 +82,28 @@ def test_stationarity_tests(self): series_3 = gaussian_timeseries(start=0, end=9999) # Test univariate - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): stationarity_tests(series_1) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): stationarity_test_adf(series_1) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): stationarity_test_kpss(series_1) # Test deterministic - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): stationarity_tests(series_2) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): stationarity_test_adf(series_2) - with self.assertRaises(AssertionError): + with pytest.raises(AssertionError): stationarity_test_kpss(series_2) # Test basics - self.assertTrue(stationarity_test_kpss(series_3)[1] > 0.05) - self.assertTrue(stationarity_test_adf(series_3)[1] < 0.05) - self.assertTrue(stationarity_tests) + assert stationarity_test_kpss(series_3)[1] > 0.05 + assert stationarity_test_adf(series_3)[1] < 0.05 + assert stationarity_tests -class SeasonalDecomposeTestCase(DartsBaseTestClass): +class TestSeasonalDecompose: pd_series = pd.Series(range(50), index=pd.date_range("20130101", "20130219")) pd_series = pd_series.map(lambda x: np.sin(x * np.pi / 3 + np.pi / 2)) season = TimeSeries.from_series(pd_series) @@ -116,54 +116,54 @@ def test_extract(self): # test default (naive) method calc_trend, _ = extract_trend_and_seasonality(self.ts, freq=6) diff = self.trend - calc_trend - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0) # test default (naive) method additive calc_trend, _ = extract_trend_and_seasonality( self.ts, freq=6, model=ModelMode.ADDITIVE ) diff = self.trend - calc_trend - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0) # test STL method calc_trend, _ = extract_trend_and_seasonality( self.ts, freq=6, method="STL", model=ModelMode.ADDITIVE ) diff = self.trend - calc_trend - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0) # test MSTL method calc_trend, calc_seasonality = extract_trend_and_seasonality( self.ts, freq=[3, 6], method="MSTL", model=ModelMode.ADDITIVE ) - self.assertTrue(len(calc_seasonality.components) == 2) + assert len(calc_seasonality.components) == 2 diff = self.trend - calc_trend # relaxed tolerance for MSTL since it will have a larger error from the # extrapolation of the trend, it is still a small number but is more # than STL or naive trend extraction - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0, atol=1e-5)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0, atol=1e-5) # test MSTL method with single freq calc_trend, calc_seasonality = extract_trend_and_seasonality( self.ts, freq=6, method="MSTL", model=ModelMode.ADDITIVE ) - self.assertTrue(len(calc_seasonality.components) == 1) + assert len(calc_seasonality.components) == 1 diff = self.trend - calc_trend - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0, atol=1e-5)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0, atol=1e-5) # make sure non MSTL methods fail with multiple freqs - with self.assertRaises(ValueError): + with pytest.raises(ValueError): calc_trend, calc_seasonality = extract_trend_and_seasonality( self.ts, freq=[1, 4, 6], method="STL", model=ModelMode.ADDITIVE ) # check if error is raised when using multiplicative model - with self.assertRaises(ValueError): + with pytest.raises(ValueError): calc_trend, _ = extract_trend_and_seasonality( self.ts, freq=6, method="STL", model=ModelMode.MULTIPLICATIVE ) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): calc_trend, _ = extract_trend_and_seasonality( self.ts, freq=[3, 6], method="MSTL", model=ModelMode.MULTIPLICATIVE ) @@ -172,12 +172,12 @@ def test_remove_seasonality(self): # test default (naive) method calc_trend = remove_seasonality(self.ts, freq=6) diff = self.trend - calc_trend - self.assertTrue(np.mean(diff.values() ** 2).item() < 0.5) + assert np.mean(diff.values() ** 2).item() < 0.5 # test default (naive) method additive calc_trend = remove_seasonality(self.ts, freq=6, model=SeasonalityMode.ADDITIVE) diff = self.trend - calc_trend - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0) # test STL method calc_trend = remove_seasonality( @@ -188,10 +188,10 @@ def test_remove_seasonality(self): low_pass=9, ) diff = self.trend - calc_trend - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0) # check if error is raised - with self.assertRaises(ValueError): + with pytest.raises(ValueError): calc_trend = remove_seasonality( self.ts, freq=6, method="STL", model=SeasonalityMode.MULTIPLICATIVE ) @@ -200,12 +200,12 @@ def test_remove_trend(self): # test naive method calc_season = remove_trend(self.ts, freq=6) diff = self.season - calc_season - self.assertTrue(np.mean(diff.values() ** 2).item() < 1.5) + assert np.mean(diff.values() ** 2).item() < 1.5 # test naive method additive calc_season = remove_trend(self.ts, freq=6, model=ModelMode.ADDITIVE) diff = self.season - calc_season - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0) # test STL method calc_season = remove_trend( @@ -216,16 +216,16 @@ def test_remove_trend(self): low_pass=9, ) diff = self.season - calc_season - self.assertTrue(np.isclose(np.mean(diff.values() ** 2), 0.0)) + assert np.isclose(np.mean(diff.values() ** 2), 0.0) # check if error is raised - with self.assertRaises(ValueError): + with pytest.raises(ValueError): calc_season = remove_trend( self.ts, freq=6, method="STL", model=ModelMode.MULTIPLICATIVE ) -class PlotTestCase(DartsBaseTestClass): +class TestPlot: series = AirPassengersDataset().load() def test_statistics_plot(self): diff --git a/darts/tests/utils/test_timeseries_generation.py b/darts/tests/utils/test_timeseries_generation.py index 1a7457cc22..5342879dcd 100644 --- a/darts/tests/utils/test_timeseries_generation.py +++ b/darts/tests/utils/test_timeseries_generation.py @@ -2,8 +2,8 @@ import numpy as np import pandas as pd +import pytest -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils.timeseries_generation import ( autoregressive_timeseries, constant_timeseries, @@ -16,7 +16,7 @@ ) -class TimeSeriesGenerationTestCase(DartsBaseTestClass): +class TestTimeSeriesGeneration: def test_constant_timeseries(self): # testing parameters value = 5 @@ -27,8 +27,8 @@ def test_routine(start, end=None, length=None): start=start, end=end, value=value, length=length ) value_set = set(constant_ts.values().flatten()) - self.assertTrue(len(value_set) == 1) - self.assertEqual(len(constant_ts), length_assert) + assert len(value_set) == 1 + assert len(constant_ts) == length_assert for length_assert in [1, 2, 5, 10, 100]: test_routine(start=0, length=length_assert) @@ -54,13 +54,20 @@ def test_routine(start, end=None, length=None): start_value=start_value, end_value=end_value, ) - self.assertEqual(linear_ts.values()[0][0], start_value) - self.assertEqual(linear_ts.values()[-1][0], end_value) - self.assertAlmostEqual( - linear_ts.values()[-1][0] - linear_ts.values()[-2][0], - (end_value - start_value) / (length_assert - 1), + assert linear_ts.values()[0][0] == start_value + assert linear_ts.values()[-1][0] == end_value + assert ( + round( + abs( + linear_ts.values()[-1][0] + - linear_ts.values()[-2][0] + - (end_value - start_value) / (length_assert - 1) + ), + 7, + ) + == 0 ) - self.assertEqual(len(linear_ts), length_assert) + assert len(linear_ts) == length_assert for length_assert in [2, 5, 10, 100]: test_routine(start=0, length=length_assert) @@ -86,9 +93,9 @@ def test_routine(start, end=None, length=None): value_amplitude=value_amplitude, value_y_offset=value_y_offset, ) - self.assertTrue((sine_ts <= value_y_offset + value_amplitude).all().all()) - self.assertTrue((sine_ts >= value_y_offset - value_amplitude).all().all()) - self.assertEqual(len(sine_ts), length_assert) + assert (sine_ts <= value_y_offset + value_amplitude).all().all() + assert (sine_ts >= value_y_offset - value_amplitude).all().all() + assert len(sine_ts) == length_assert for length_assert in [1, 2, 5, 10, 100]: test_routine(start=0, length=length_assert) @@ -104,7 +111,7 @@ def test_gaussian_timeseries(self): # testing for correct length def test_routine(start, end=None, length=None): gaussian_ts = gaussian_timeseries(start=start, end=end, length=length) - self.assertEqual(len(gaussian_ts), length_assert) + assert len(gaussian_ts) == length_assert for length_assert in [1, 2, 5, 10, 100]: test_routine(start=0, length=length_assert) @@ -120,7 +127,7 @@ def test_random_walk_timeseries(self): # testing for correct length def test_routine(start, end=None, length=None): random_walk_ts = random_walk_timeseries(start=start, end=end, length=length) - self.assertEqual(len(random_walk_ts), length_assert) + assert len(random_walk_ts) == length_assert for length_assert in [1, 2, 5, 10, 100]: test_routine(start=0, length=length_assert) @@ -152,9 +159,7 @@ def test_routine( ts = holidays_timeseries( time_index, country_code, until=until, add_length=add_length ) - self.assertTrue( - all(ts.pd_dataframe().groupby(pd.Grouper(freq="y")).sum().values) - ) + assert all(ts.pd_dataframe().groupby(pd.Grouper(freq="y")).sum().values) for time_index in [time_index_1, time_index_2, time_index_3]: for country_code in ["US", "CH", "AR"]: @@ -167,15 +172,15 @@ def test_routine( test_routine(time_index_1, "AR", until=pd.Timestamp("2016-01-01")) # test overflow - with self.assertRaises(ValueError): + with pytest.raises(ValueError): holidays_timeseries(time_index_1, "US", add_length=99999) # test date is too short - with self.assertRaises(ValueError): + with pytest.raises(ValueError): holidays_timeseries(time_index_2, "US", until="2016-01-01") # test wrong timestamp - with self.assertRaises(ValueError): + with pytest.raises(ValueError): holidays_timeseries(time_index_3, "US", until=163) def test_generate_index(self): @@ -189,9 +194,9 @@ def test_routine( freq=None, ): index = generate_index(start=start, end=end, length=length, freq=freq) - self.assertEqual(len(index), expected_length) - self.assertEqual(index[0], expected_start) - self.assertEqual(index[-1], expected_end) + assert len(index) == expected_length + assert index[0] == expected_start + assert index[-1] == expected_end for length in [1, 2, 5, 50]: for start in [0, 1, 9]: @@ -267,24 +272,24 @@ def test_routine( ) # `start`, `end` and `length` cannot both be set simultaneously - with self.assertRaises(ValueError): + with pytest.raises(ValueError): generate_index(start=0, end=9, length=10) # same as above but `start` defaults to timestamp '2000-01-01' in all timeseries generation functions - with self.assertRaises(ValueError): + with pytest.raises(ValueError): linear_timeseries(end=9, length=10) # exactly two of [`start`, `end`, `length`] must be set - with self.assertRaises(ValueError): + with pytest.raises(ValueError): generate_index(start=0) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): generate_index(start=None, end=1) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): generate_index(start=None, end=None, length=10) # `start` and `end` must have same type - with self.assertRaises(ValueError): + with pytest.raises(ValueError): generate_index(start=0, end=pd.Timestamp("2000-01-01")) - with self.assertRaises(ValueError): + with pytest.raises(ValueError): generate_index(start=pd.Timestamp("2000-01-01"), end=10) def test_autoregressive_timeseries(self): @@ -293,7 +298,7 @@ def test_length(start, end=None, length=None): autoregressive_ts = autoregressive_timeseries( coef=[-1, 1.618], start=start, end=end, length=length ) - self.assertEqual(len(autoregressive_ts), length_assert) + assert len(autoregressive_ts) == length_assert # testing for correct calculation def test_calculation(coef): @@ -301,11 +306,8 @@ def test_calculation(coef): coef=coef, length=100 ).values() for idx, val in enumerate(autoregressive_values[len(coef) :]): - self.assertTrue( - val - == np.dot( - coef, autoregressive_values[idx : idx + len(coef)].ravel() - ) + assert val == np.dot( + coef, autoregressive_values[idx : idx + len(coef)].ravel() ) for length_assert in [1, 2, 5, 10, 100]: diff --git a/darts/tests/utils/test_utils.py b/darts/tests/utils/test_utils.py index 90fa0ffc32..c8c7f8351c 100644 --- a/darts/tests/utils/test_utils.py +++ b/darts/tests/utils/test_utils.py @@ -1,13 +1,13 @@ import numpy as np import pandas as pd +import pytest from darts import TimeSeries -from darts.tests.base_test_class import DartsBaseTestClass from darts.utils import _with_sanity_checks, retain_period_common_to_all from darts.utils.missing_values import extract_subseries -class UtilsTestCase(DartsBaseTestClass): +class TestUtils: def test_retain_period_common_to_all(self): seriesA = TimeSeries.from_times_and_values( pd.date_range("20000101", "20000110"), range(10) @@ -24,13 +24,13 @@ def test_retain_period_common_to_all(self): # test start and end dates for common_series in common_series_list: - self.assertEqual(common_series.start_time(), pd.Timestamp("20000104")) - self.assertEqual(common_series.end_time(), pd.Timestamp("20000108")) + assert common_series.start_time() == pd.Timestamp("20000104") + assert common_series.end_time() == pd.Timestamp("20000108") # test widths - self.assertEqual(common_series_list[0].width, 1) - self.assertEqual(common_series_list[1].width, 1) - self.assertEqual(common_series_list[2].width, 2) + assert common_series_list[0].width == 1 + assert common_series_list[1].width == 1 + assert common_series_list[2].width == 2 def test_sanity_check_example(self): class Model: @@ -45,7 +45,7 @@ def fit(self, a, b=0, c=0): m = Model() # b != c should raise error - with self.assertRaises(ValueError): + with pytest.raises(ValueError): m.fit(5, b=3, c=2) # b == c should not raise error @@ -64,10 +64,10 @@ def test_extract_subseries(self): subseries = extract_subseries(series) - self.assertEqual(len(subseries), len(start_times)) + assert len(subseries) == len(start_times) for sub, start, end in zip(subseries, start_times, end_times): - self.assertEqual(sub.start_time(), pd.to_datetime(start)) - self.assertEqual(sub.end_time(), pd.to_datetime(end)) + assert sub.start_time() == pd.to_datetime(start) + assert sub.end_time() == pd.to_datetime(end) # Multivariate timeserie times = pd.date_range("20130206", "20130215") @@ -83,13 +83,13 @@ def test_extract_subseries(self): # gaps is characterized by NaN in all the covariate columns subseries_all = extract_subseries(series, mode="all") - self.assertEqual(len(subseries_all), 2) - self.assertEqual(subseries_all[0], series[:2]) - self.assertEqual(subseries_all[1], series[3:]) + assert len(subseries_all) == 2 + assert subseries_all[0] == series[:2] + assert subseries_all[1] == series[3:] # gaps is characterized by NaN in any of the covariate columns subseries_any = extract_subseries(series, mode="any") - self.assertEqual(len(subseries_any), 3) - self.assertEqual(subseries_any[0], series[:2]) - self.assertEqual(subseries_any[1], series[3:5]) - self.assertEqual(subseries_any[2], series[-1]) + assert len(subseries_any) == 3 + assert subseries_any[0] == series[:2] + assert subseries_any[1] == series[3:5] + assert subseries_any[2] == series[-1] diff --git a/darts/tests/utils/test_utils_torch.py b/darts/tests/utils/test_utils_torch.py index 177d536aa1..05cc92dc64 100644 --- a/darts/tests/utils/test_utils_torch.py +++ b/darts/tests/utils/test_utils_torch.py @@ -1,7 +1,7 @@ +import pytest from numpy.random import RandomState from darts.logging import get_logger -from darts.tests.base_test_class import DartsBaseTestClass logger = get_logger(__name__) @@ -28,9 +28,9 @@ def __init__(self, some_params=None, **kwargs): def fit(self, some_params=None): self.fit_value = torch.randn(5) - class RandomMethodTestCase(DartsBaseTestClass): + class TestRandomMethod: def test_it_raises_error_if_used_on_function(self): - with self.assertRaises(ValueError): + with pytest.raises(ValueError): @random_method def a_random_function(): @@ -39,19 +39,19 @@ def a_random_function(): def test_model_is_random_by_default(self): model1 = TorchModelMock() model2 = TorchModelMock() - self.assertFalse(torch.equal(model1.model, model2.model)) + assert not torch.equal(model1.model, model2.model) def test_model_is_random_when_None_random_state_specified(self): model1 = TorchModelMock(random_state=None) model2 = TorchModelMock(random_state=None) - self.assertFalse(torch.equal(model1.model, model2.model)) + assert not torch.equal(model1.model, model2.model) def helper_test_reproducibility(self, model1, model2): - self.assertTrue(torch.equal(model1.model, model2.model)) + assert torch.equal(model1.model, model2.model) model1.fit() model2.fit() - self.assertTrue(torch.equal(model1.fit_value, model2.fit_value)) + assert torch.equal(model1.fit_value, model2.fit_value) def test_model_is_reproducible_when_seed_specified(self): model1 = TorchModelMock(random_state=42) @@ -66,22 +66,22 @@ def test_model_is_reproducible_when_random_instance_specified(self): def test_model_is_different_for_different_seeds(self): model1 = TorchModelMock(random_state=42) model2 = TorchModelMock(random_state=43) - self.assertFalse(torch.equal(model1.model, model2.model)) + assert not torch.equal(model1.model, model2.model) def test_model_is_different_for_different_random_instance(self): model1 = TorchModelMock(random_state=RandomState(42)) model2 = TorchModelMock(random_state=RandomState(43)) - self.assertFalse(torch.equal(model1.model, model2.model)) + assert not torch.equal(model1.model, model2.model) def helper_test_successive_call_are_different(self, model): # different between init and fit model.fit() - self.assertFalse(torch.equal(model.model, model.fit_value)) + assert not torch.equal(model.model, model.fit_value) # different between 2 fit old_fit_value = model.fit_value.clone() model.fit() - self.assertFalse(torch.equal(model.fit_value, old_fit_value)) + assert not torch.equal(model.fit_value, old_fit_value) def test_successive_call_to_rng_are_different_when_seed_specified(self): model = TorchModelMock(random_state=42) @@ -103,7 +103,7 @@ def test_no_side_effect_between_rng_with_seeds(self): model2.fit() model.fit() - self.assertTrue(torch.equal(model.fit_value, fit_value)) + assert torch.equal(model.fit_value, fit_value) def test_no_side_effect_between_rng_with_random_instance(self): model = TorchModelMock(random_state=RandomState(42)) @@ -115,4 +115,4 @@ def test_no_side_effect_between_rng_with_random_instance(self): model2.fit() model.fit() - self.assertTrue(torch.equal(model.fit_value, fit_value)) + assert torch.equal(model.fit_value, fit_value)