Skip to content

Commit

Permalink
Merge pull request #266 from unit8co/develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
hrzn authored Feb 2, 2021
2 parents df72876 + 14461dc commit 14fb2ab
Show file tree
Hide file tree
Showing 83 changed files with 20,282 additions and 5,293 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/develop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
example-name: [FFT-examples.ipynb, RNN-examples.ipynb, darts-intro.ipynb, multivariate-examples.ipynb, data-processing.ipynb]
example-name: [04-FFT-examples.ipynb, 05-RNN-examples.ipynb, 01-darts-intro.ipynb, 03-data-processing.ipynb, 02-multi-time-series-and-covariates.ipynb]
steps:
- name: "1. Clone repository"
uses: actions/checkout@v2
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/merge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
example-name: [FFT-examples.ipynb, TCN-examples.ipynb, RNN-examples.ipynb, darts-intro.ipynb, multivariate-examples.ipynb, Transformer-examples.ipynb, data-processing.ipynb]
example-name: [01-darts-intro.ipynb, 02-multi-time-series-and-covariates.ipynb, 03-data-processing.ipynb, 04-FFT-examples.ipynb, 05-RNN-examples.ipynb]
steps:
- name: "1. Clone repository"
uses: actions/checkout@v2
Expand Down
38 changes: 37 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,43 @@ Darts is still in an early development phase and we cannot always guarantee back

## [Unreleased](https://github.com/unit8co/darts/tree/develop)

[Full Changelog](https://github.com/unit8co/darts/compare/0.5.0...develop)
[Full Changelog](https://github.com/unit8co/darts/compare/0.6.0...develop)


## [0.6.0](https://github.com/unit8co/darts/tree/0.6.0) (2021-02-02)

[Full Changelog](https://github.com/unit8co/darts/compare/0.5.0...0.6.0)
### For users of the library:
**Added:**
- `Pipeline.invertible()` a getter which returns whether the pipeline is invertible or not.
- `TimeSeries.to_json()` and `TimeSeries.from_json()` methods to convert `TimeSeries` to/from a `JSON` string.
- New base class `GlobalForecastingModel` for all models supporting training on multiple time series, as well
as covariates. All PyTorch models are now `GlobalForecastingModel`s.
- As a consequence of the above, the `fit()` function of PyTorch models (all neural networks) can optionally be called
with a sequence of time series (instead of a single time series).
- Similarly, the `predict()` function of these models also accepts a specification of which series should be forecasted
- A new `TrainingDataset` base class.
- Some implementations of `TrainingDataset` containing some slicing logic for the training of neural networks on
several time series.
- A new `TimeSeriesInferenceDataset` base class.
- An implementation `SimpleInferenceDataset` of `TimeSeriesInferenceDataset`.
- All PyTorch models have a new `fit_from_dataset()` method which allows to directly fit the model from a specified
`TrainingDataset` instance (instead of using a default instance when going via the `fit()` method).
- A new explanatory notebooks for global models:
https://github.com/unit8co/darts/blob/master/examples/02-multi-time-series-and-covariates.ipynb

**Changed:**
🔴 removed the arguments `training_series` and `target_series` in `ForecastingModel`s. Please consult
the API documentation of forecasting models to see the new signatures.
🔴 removed `UnivariateForecastingModel` and `MultivariateForecastingModel` base classes. This distinction does
not exist anymore. Instead, now some models are "global" (can be trained on multiple series) or "local" (they cannot).
All implementations of `GlobalForecastingModel`s support multivariate time series out of the box, except N-BEATS.
- Improved the documentation and README.
- Re-ordered the example notebooks to improve the flow of examples.

**Fixed:**
- Many small bug fixes.
- Unit test speedup by about 15x.

## [0.5.0](https://github.com/unit8co/darts/tree/0.5.0) (2020-11-09)

Expand Down
23 changes: 13 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,20 +15,25 @@ It contains a variety of models, from classics such as ARIMA to neural networks.
The models can all be used in the same way, using `fit()` and `predict()` functions,
similar to scikit-learn. The library also makes it easy to backtest models,
and combine the predictions of several models and external regressors. Darts supports both
univariate and multivariate time series and models.
univariate and multivariate time series and models, and the neural networks can be trained
multiple time series.

## Documentation
* [Examples & Tutorials](https://unit8co.github.io/darts/examples.html)
* [API Documentation](https://unit8co.github.io/darts/generated_api/darts.html)

##### High Level Introductions
* [Introductory Blog Post](https://medium.com/unit8-machine-learning-publication/darts-time-series-made-easy-in-python-5ac2947a8878)
* [Introductory Video](https://www.youtube.com/watch?v=Sx-uI-PypmU&t=8s&ab_channel=Unit8)

## Install

We recommend to first setup a clean python environment for your project with at least python 3.6 using your favorite tool ([conda](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html "conda-env"), [venv](https://docs.python.org/3/library/venv.html), [virtualenv](https://virtualenv.pypa.io/en/latest/) with or without [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/)).

### Quick Install

Once your environment is setup you can install darts using the pip package:
Once your environment is setup you can install darts using pip:

pip install 'u8darts[all]'

### Step-by-step Install

For more detailed install instructions you can refer to our installation guide at the end of this page.

## Example Usage
Expand Down Expand Up @@ -70,11 +75,9 @@ plt.xlabel('Year')
<img src="https://github.com/unit8co/darts/raw/develop/static/images/example.png" alt="darts forecast example" />
</div>

We invite you to go over the example notebooks in the `examples` directory.

## Documentation
We invite you to go over the example and tutorial notebooks in
the [examples](https://github.com/unit8co/darts/tree/master/examples) directory.

The documentation of the API and models is available [here](https://unit8co.github.io/darts/).

## Features

Expand Down
20 changes: 0 additions & 20 deletions darts/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,28 +5,8 @@

from .timeseries import TimeSeries
import matplotlib as mpl
import matplotlib.pyplot as plt
from matplotlib import cycler

# Enums
from enum import Enum


class SeasonalityMode(Enum):
MULTIPLICATIVE = 'multiplicative'
ADDITIVE = 'additive'
NONE = None


class TrendMode(Enum):
LINEAR = 'linear'
EXPONENTIAL = 'exponential'


class ModelMode(Enum):
MULTIPLICATIVE = 'multiplicative'
ADDITIVE = 'additive'


__version__ = '0.5.0'

Expand Down
12 changes: 12 additions & 0 deletions darts/dataprocessing/pipeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,18 @@ def inverse_transform(self, data: TimeSeries) -> TimeSeries:
data = transformer.inverse_transform(data)
return data

def invertible(self) -> bool:
"""
Returns whether the pipeline is invertible or not.
A pipeline is invertible if all transformers in the pipeline are themselves invertible.
Returns
-------
bool
True if the pipeline is invertible, False otherwise
"""
return self._invertible

def __getitem__(self, key: Union[int, slice]) -> 'Pipeline':
"""
Gets subset of Pipeline based either on index or slice with indexes.
Expand Down
21 changes: 20 additions & 1 deletion darts/metrics/metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,17 @@ def _get_values_or_raise(series_a: TimeSeries,
return series_a_common.univariate_values(), series_b_common.univariate_values()


def _remove_nan_union(array_a: np.ndarray,
array_b: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
"""
Returnes the two inputs arrays where all elements are deleted that have an index that corresponds to
a NaN value in either of the two input arrays.
"""

isnan_mask = np.logical_or(np.isnan(array_a), np.isnan(array_b))
return np.delete(array_a, isnan_mask), np.delete(array_b, isnan_mask)


@multivariate_support
def mae(series1: TimeSeries,
series2: TimeSeries,
Expand Down Expand Up @@ -100,6 +111,7 @@ def mae(series1: TimeSeries,
"""

y1, y2 = _get_values_or_raise(series1, series2, intersect)
y1, y2 = _remove_nan_union(y1, y2)
return np.mean(np.abs(y1 - y2))


Expand Down Expand Up @@ -134,6 +146,7 @@ def mse(series1: TimeSeries,
"""

y_true, y_pred = _get_values_or_raise(series1, series2, intersect)
y_true, y_pred = _remove_nan_union(y_true, y_pred)
return np.mean((y_true - y_pred)**2)


Expand Down Expand Up @@ -202,6 +215,7 @@ def rmsle(series1: TimeSeries,
"""

y1, y2 = _get_values_or_raise(series1, series2, intersect)
y1, y2 = _remove_nan_union(y1, y2)
y1, y2 = np.log(y1 + 1), np.log(y2 + 1)
return np.sqrt(np.mean((y1 - y2)**2))

Expand Down Expand Up @@ -283,6 +297,7 @@ def mape(actual_series: TimeSeries,
"""

y_true, y_hat = _get_values_or_raise(actual_series, pred_series, intersect)
y_true, y_hat = _remove_nan_union(y_true, y_hat)
raise_if_not((y_true != 0).all(), 'The actual series must be strictly positive to compute the MAPE.', logger)
return 100. * np.mean(np.abs((y_true - y_hat) / y_true))

Expand Down Expand Up @@ -329,9 +344,10 @@ def smape(actual_series: TimeSeries,
"""

y_true, y_hat = _get_values_or_raise(actual_series, pred_series, intersect)
y_true, y_hat = _remove_nan_union(y_true, y_hat)
raise_if_not(np.logical_or(y_true != 0, y_hat != 0).all(),
'The actual series must be strictly positive to compute the sMAPE.', logger)
return 200. * np.mean(np.abs((y_true - y_hat) / (np.abs(y_true) + np.abs(y_hat))))
return 200. * np.mean(np.abs(y_true - y_hat) / (np.abs(y_true) + np.abs(y_hat)))


@multivariate_support
Expand Down Expand Up @@ -431,6 +447,7 @@ def ope(actual_series: TimeSeries,
"""

y_true, y_pred = _get_values_or_raise(actual_series, pred_series, intersect)
y_true, y_pred = _remove_nan_union(y_true, y_pred)
y_true_sum, y_pred_sum = np.sum(y_true), np.sum(y_pred)
raise_if_not(y_true_sum > 0, 'The series of actual value cannot sum to zero when computing OPE.', logger)
return np.abs((y_true_sum - y_pred_sum) / y_true_sum) * 100.
Expand Down Expand Up @@ -474,6 +491,7 @@ def marre(actual_series: TimeSeries,
"""

y_true, y_hat = _get_values_or_raise(actual_series, pred_series, intersect)
y_true, y_hat = _remove_nan_union(y_true, y_hat)
raise_if_not(y_true.max() > y_true.min(), 'The difference between the max and min values must be strictly'
'positive to compute the MARRE.', logger)
true_range = y_true.max() - y_true.min()
Expand Down Expand Up @@ -512,6 +530,7 @@ def r2_score(series1: TimeSeries,
"""

y1, y2 = _get_values_or_raise(series1, series2, intersect)
y1, y2 = _remove_nan_union(y1, y2)
ss_errors = np.sum((y1 - y2) ** 2)
y_hat = y1.mean()
ss_tot = np.sum((y1 - y_hat) ** 2)
Expand Down
4 changes: 2 additions & 2 deletions darts/models/arima.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@
from statsmodels.tsa.arima_model import ARMA as staARMA
from statsmodels.tsa.arima_model import ARIMA as staARIMA

from .forecasting_model import UnivariateForecastingModel
from .forecasting_model import ForecastingModel
from ..timeseries import TimeSeries
from ..logging import get_logger

logger = get_logger(__name__)


class ARIMA(UnivariateForecastingModel):
class ARIMA(ForecastingModel):
def __init__(self, p: int = 12, d: int = 1, q: int = 0):
""" ARIMA
Expand Down
4 changes: 2 additions & 2 deletions darts/models/auto_arima.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@

from pmdarima import AutoARIMA as PmdAutoARIMA

from .forecasting_model import UnivariateForecastingModel
from .forecasting_model import ForecastingModel
from ..timeseries import TimeSeries
from ..logging import get_logger

logger = get_logger(__name__)


class AutoARIMA(UnivariateForecastingModel):
class AutoARIMA(ForecastingModel):
def __init__(self, *autoarima_args, **autoarima_kwargs):
""" Auto-ARIMA
Expand Down
17 changes: 7 additions & 10 deletions darts/models/baselines.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@
Baseline Models
---------------
A collection of simple benchmark models.
A collection of simple benchmark models for univariate series.
"""

from typing import List, Optional
import numpy as np

from .forecasting_model import ForecastingModel, UnivariateForecastingModel
from .forecasting_model import ForecastingModel
from .ensemble_model import EnsembleModel
from ..timeseries import TimeSeries
from ..logging import raise_if_not, get_logger

logger = get_logger(__name__)


class NaiveMean(UnivariateForecastingModel):
class NaiveMean(ForecastingModel):
def __init__(self):
""" Naive Mean Model
Expand All @@ -39,7 +39,7 @@ def predict(self, n: int):
return self._build_forecast_series(forecast)


class NaiveSeasonal(UnivariateForecastingModel):
class NaiveSeasonal(ForecastingModel):
def __init__(self, K: int = 1):
""" Naive Seasonal Model
Expand Down Expand Up @@ -74,7 +74,7 @@ def predict(self, n: int):
return self._build_forecast_series(forecast)


class NaiveDrift(UnivariateForecastingModel):
class NaiveDrift(ForecastingModel):
def __init__(self):
""" Naive Drift Model
Expand Down Expand Up @@ -112,13 +112,10 @@ def __init__(self, models: List[ForecastingModel]):
super().__init__(models)

def fit(self, training_series: TimeSeries, target_series: Optional[TimeSeries] = None) -> None:
super().fit(training_series, target_series)
super().fit(training_series)

for model in self.models:
if isinstance(model, UnivariateForecastingModel):
model.fit(self.training_series)
else:
model.fit(self.training_series, self.target_series)
model.fit(self.training_series)

def ensemble(self, predictions: List[TimeSeries]):
return sum(predictions) / len(self.models)
8 changes: 4 additions & 4 deletions darts/models/ensemble_model.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""
Ensemble model
--------------
Ensemble Model Base Class
-------------------------
"""

from abc import abstractmethod
Expand Down Expand Up @@ -34,13 +34,13 @@ def __init__(self, models: List[ForecastingModel]):
super().__init__()
self.models = models

def fit(self, training_series: TimeSeries, target_series: Optional[TimeSeries] = None) -> None:
def fit(self, training_series: TimeSeries) -> None:
"""
Fits the model on the provided series.
Note that `EnsembleModel.fit()` does NOT call `fit()` on each of its constituent forecasting models.
It is left to classes inheriting from EnsembleModel to do so appropriately when overriding `fit()`
"""
super().fit(training_series, target_series)
super().fit(training_series)

def predict(self, n: int) -> TimeSeries:
super().predict(n)
Expand Down
6 changes: 3 additions & 3 deletions darts/models/exponential_smoothing.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
from typing import Optional
import statsmodels.tsa.holtwinters as hw

from .forecasting_model import UnivariateForecastingModel
from .forecasting_model import ForecastingModel
from ..logging import get_logger
from ..timeseries import TimeSeries
from .. import ModelMode
from ..utils.utils import ModelMode

logger = get_logger(__name__)


class ExponentialSmoothing(UnivariateForecastingModel):
class ExponentialSmoothing(ForecastingModel):
def __init__(self,
trend: Optional[ModelMode] = ModelMode.ADDITIVE,
damped: Optional[bool] = False,
Expand Down
Loading

0 comments on commit 14fb2ab

Please sign in to comment.