Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ but cannot always guarantee backwards compatibility. Changes that may **break co

**Dependencies**

- Moved `xarray` from core dependencies to optional dependencies. [#2974](https://github.com/unit8co/darts/pull/2974) by [Jakub Chłapek](https://github.com/jakubchlapek).
- We set an upper version cap on `scikit-learn<1.8.0` until CatBoost officially supports it. [#2972](https://github.com/unit8co/darts/pull/2972) by [Dennis Bader](https://github.com/dennisbader).

### For developers of the library:
Expand Down
27 changes: 15 additions & 12 deletions darts/ad/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -657,28 +657,31 @@ def _plot_series(series, ax_id, linewidth, label_name, **kwargs):
label_name
Name that will appear in the legend.
"""
data_array = series.data_array(copy=False)
for i, c in enumerate(data_array.component[:10]):
comp = data_array.sel(component=c)

if comp.sample.size > 1:
central_series = comp.mean(dim="sample")
low_series = comp.quantile(q=0.05, dim="sample")
high_series = comp.quantile(q=0.95, dim="sample")
for i, c in enumerate(series.components[:10]):
comp_series = series[c]

if series.is_stochastic:
central_series = comp_series.mean(axis=2)
low_series = comp_series.quantile(q=0.05)
high_series = comp_series.quantile(q=0.95)
else:
central_series = comp
central_series = comp_series

label_to_use = (
(label_name + ("_" + str(i) if len(series.components) > 1 else ""))
if label_name != ""
else "" + str(str(c.values))
else "" + str(c)
)

central_series.plot(ax=ax_id, linewidth=linewidth, label=label_to_use, **kwargs)

if comp.sample.size > 1:
if series.is_stochastic:
ax_id.fill_between(
series.time_index, low_series, high_series, alpha=0.25, **kwargs
series.time_index,
low_series.values().flatten(),
high_series.values().flatten(),
alpha=0.25,
**kwargs,
)


Expand Down
18 changes: 5 additions & 13 deletions darts/dataprocessing/dtw/_plot.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from typing import Union

import numpy as np
import xarray as xr
from matplotlib import pyplot as plt


Expand Down Expand Up @@ -142,23 +141,17 @@ def plot_alignment(
series1 += series1_y_offset
series2 += series2_y_offset

xa1 = series1.data_array(copy=False)
xa2 = series2.data_array(copy=False)

path = self.path()
n = len(path)

time_dim1 = series1.time_dim
time_dim2 = series2.time_dim

x_coords1 = np.array(xa1[time_dim1], dtype=xa1[time_dim1].dtype)[path[:, 0]]
x_coords2 = np.array(xa2[time_dim2], dtype=xa2[time_dim2].dtype)[path[:, 1]]
x_coords1 = series1.time_index[path[:, 0]]
x_coords2 = series2.time_index[path[:, 1]]

y_coords1 = series1.univariate_values()[path[:, 0]]
y_coords2 = series2.univariate_values()[path[:, 1]]

if series1.has_datetime_index:
x_dtype = xa1[time_dim1].dtype
x_dtype = series1.time_index.dtype
x_nan = np.datetime64("NaT")
else:
x_dtype = np.float64
Expand All @@ -175,8 +168,7 @@ def plot_alignment(
y_coords[1::3] = y_coords2
y_coords[2::3] = np.nan

arr = xr.DataArray(y_coords, dims=["value"], coords={"value": x_coords})
xr.plot.line(arr, x="value", **args_line)

series1.plot(**args_series1)
series2.plot(**args_series2)

plt.plot(x_coords, y_coords, **args_line)
19 changes: 13 additions & 6 deletions darts/tests/dataprocessing/dtw/test_dtw.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,13 +154,9 @@ def test_itakura_window(self):
assert 1 > dist

def test_warp(self):
# Support different time dimension names
xa1 = self.series1.data_array().rename({"time": "time1"})
xa2 = self.series2.data_array().rename({"time": "time2"})

static_covs = pd.DataFrame([[0.0, 1.0]], columns=["st1", "st2"])
series1 = TimeSeries.from_xarray(xa1).with_static_covariates(static_covs)
series2 = TimeSeries.from_xarray(xa2).with_static_covariates(static_covs)
series1 = self.series1.with_static_covariates(static_covs)
series2 = self.series2.with_static_covariates(static_covs)
series1_copy = series1.copy()
series2_copy = series2.copy()

Expand Down Expand Up @@ -214,6 +210,17 @@ def test_plot(self):
align.plot_alignment()
plt.close()

def test_plot_alignment_range_index(self):
series1 = TimeSeries.from_values(np.sin(np.linspace(0, 2 * np.pi, 20)))
series2 = TimeSeries.from_values(np.cos(np.linspace(0, 2 * np.pi, 20)))

assert not series1.has_datetime_index
assert not series2.has_datetime_index

align = dtw.dtw(series1, series2)
align.plot_alignment()
plt.close()

def test_multivariate(self):
n = 2

Expand Down
93 changes: 86 additions & 7 deletions darts/tests/test_timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import numpy as np
import pandas as pd
import pytest
import xarray as xr
from scipy.stats import kurtosis, skew

from darts import TimeSeries, concatenate, slice_intersect
Expand All @@ -17,7 +16,7 @@
quantile_names,
)
from darts.utils.timeseries_generation import constant_timeseries, linear_timeseries
from darts.utils.utils import expand_arr, freqs, generate_index
from darts.utils.utils import XARRAY_AVAILABLE, expand_arr, freqs, generate_index

TEST_BACKENDS = ["pandas"]

Expand Down Expand Up @@ -65,14 +64,38 @@ def test_creation(self):
)
assert ts.components.tolist() == ["a", "b", "a_1", "a_2", "b_1"]

# creation using from_xarray()
@pytest.mark.skipif(not XARRAY_AVAILABLE, reason="xarray required")
def test_xarray_creation(self):
import xarray as xr

ar = xr.DataArray(
np.random.randn(10, 2, 1),
dims=("time", "component", "sample"),
coords={"time": self.times, "component": ["a", "b"]},
name="time series",
)
_ = TimeSeries.from_xarray(ar)
ts = TimeSeries.from_xarray(ar)
assert ts.components.tolist() == ["a", "b"]

@pytest.mark.skipif(XARRAY_AVAILABLE, reason="xarray required disabled")
def test_xarray_import_error_from_xarray(self):
"""Test that from_xarray raises ImportError when xarray is not available."""
with pytest.raises(ImportError) as exc:
TimeSeries.from_xarray(None)
assert "xarray required" in str(exc.value)

@pytest.mark.skipif(XARRAY_AVAILABLE, reason="xarray required disabled")
def test_xarray_import_error_data_array(self):
"""Test that data_array raises ImportError when xarray is not available."""
with pytest.raises(ImportError) as exc:
self.series1.data_array()
assert "xarray required" in str(exc.value)

@pytest.mark.skipif(XARRAY_AVAILABLE, reason="xarray required disabled")
def test_xarray_import_error_resample(self):
"""Test that resample raises ImportError when xarray is not available."""
with pytest.raises(ImportError) as exc:
self.series1.resample(freq="2D")
assert "xarray required" in str(exc.value)

def test_from_times_and_values(self):
# Test creation from times and values
Expand Down Expand Up @@ -401,7 +424,10 @@ def test_univariate_component(self):
# only the right static covariate column should be retained
assert univ_series.static_covariates.sum().sum() == 1.1

@pytest.mark.skipif(not XARRAY_AVAILABLE, reason="requires xarray")
def test_column_names(self):
import xarray as xr

# test the column names resolution
columns_before = [
["0", "1", "2"],
Expand Down Expand Up @@ -810,14 +836,17 @@ def test_ops(self):
# Cannot divide by 0.
self.series1 / 0

def test_ops_array(self):
@pytest.mark.skipif(not XARRAY_AVAILABLE, reason="xarray required")
def test_ops_xarray(self):
# can work with xarray directly
series2_x = self.series2.data_array(copy=False)
assert self.series1 + self.series2 == self.series1 + series2_x
assert self.series1 - self.series2 == self.series1 - series2_x
assert self.series1 * self.series2 == self.series1 * series2_x
assert self.series1 / self.series2 == self.series1 / series2_x
assert self.series1**self.series2 == self.series1**series2_x

def test_ops_ndarray(self):
# can work with ndarray directly
series2_nd = self.series2.all_values(copy=False)
assert self.series1 + self.series2 == self.series1 + series2_nd
Expand Down Expand Up @@ -861,6 +890,55 @@ def test_ops_broadcasting(self, broadcast_components, broadcast_samples):
assert seriesA / arrayB == seriesDiv
assert seriesA**arrayB == seriesPow

def test_ops_unsupported_types(self):
invalid_operands = ["string", [1, 2, 3], {"key": "value"}, (1, 2)]

for invalid in invalid_operands:
# Test __add__
with pytest.raises(TypeError) as exc:
_ = self.series1 + invalid
assert "unsupported operand type(s) for + or add()" in str(exc.value)

# Test __sub__
with pytest.raises(TypeError) as exc:
_ = self.series1 - invalid
assert "unsupported operand type(s) for - or sub()" in str(exc.value)

# Test __mul__
with pytest.raises(TypeError) as exc:
_ = self.series1 * invalid
assert "unsupported operand type(s) for * or mul()" in str(exc.value)

# Test __truediv__
with pytest.raises(TypeError) as exc:
_ = self.series1 / invalid
assert "unsupported operand type(s) for / or truediv()" in str(exc.value)

# Test __pow__
with pytest.raises(TypeError) as exc:
_ = self.series1**invalid
assert "unsupported operand type(s) for ** or pow()" in str(exc.value)

# Test __lt__
with pytest.raises(TypeError) as exc:
_ = self.series1 < invalid
assert "unsupported operand type(s) for <" in str(exc.value)

# Test __gt__
with pytest.raises(TypeError) as exc:
_ = self.series1 > invalid
assert "unsupported operand type(s) for >" in str(exc.value)

# Test __le__
with pytest.raises(TypeError) as exc:
_ = self.series1 <= invalid
assert "unsupported operand type(s) for <=" in str(exc.value)

# Test __ge__
with pytest.raises(TypeError) as exc:
_ = self.series1 >= invalid
assert "unsupported operand type(s) for >=" in str(exc.value)

def test_getitem_datetime_index(self):
series_short: TimeSeries = self.series1.drop_after(pd.Timestamp("20130105"))
series_stride_2: TimeSeries = self.series1.with_times_and_values(
Expand Down Expand Up @@ -1277,6 +1355,7 @@ def test_fillna_value(self):
assert not np.isnan(series_no_nan.all_values(copy=False)).any()
assert series_1 == series_no_nan

@pytest.mark.skipif(not XARRAY_AVAILABLE, reason="xarray required")
def test_resample_timeseries(self):
# 01/01/2013 -> 10/01/2013, one value per day: 0 1 2 3 ... 9
times = pd.date_range("20130101", "20130110")
Expand Down Expand Up @@ -2404,7 +2483,7 @@ def helper_test_prepend(test_series: TimeSeries):
def helper_test_prepend_values(test_series: TimeSeries):
# reconstruct series
seriesA, seriesB = test_series.split_after(pd.Timestamp("20130106"))
arrayA = seriesA.data_array().values
arrayA = seriesA._values
prepended = seriesB.prepend_values(arrayA)
assert prepended == test_series
assert test_series.time_index.equals(prepended.time_index)
Expand Down
8 changes: 5 additions & 3 deletions darts/tests/test_timeseries_static_covariates.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
STATIC_COV_TAG,
)
from darts.utils.timeseries_generation import linear_timeseries
from darts.utils.utils import generate_index
from darts.utils.utils import XARRAY_AVAILABLE, generate_index

TEST_BACKENDS = ["pandas"]

Expand Down Expand Up @@ -92,7 +92,8 @@ def test_ts_from_x(self, tag, tmpdir_module):
ts, x = setup_tag(tag, ts)
kwargs = {tag: x}

self.helper_test_transfer(tag, ts, TimeSeries.from_xarray(ts.data_array()))
if XARRAY_AVAILABLE:
self.helper_test_transfer(tag, ts, TimeSeries.from_xarray(ts.data_array()))
self.helper_test_transfer(
tag, ts, TimeSeries.from_dataframe(ts.to_dataframe(), **kwargs)
)
Expand Down Expand Up @@ -922,7 +923,8 @@ def test_ts_methods(self, tag):
self.helper_test_transfer(tag, ts, ts.diff())
self.helper_test_transfer(tag, ts, ts.univariate_component(0))
self.helper_test_transfer(tag, ts, ts.map(lambda x: x + 1))
self.helper_test_transfer(tag, ts, ts.resample(ts.freq))
if XARRAY_AVAILABLE:
self.helper_test_transfer(tag, ts, ts.resample(ts.freq))
self.helper_test_transfer(tag, ts, ts[:5].append(ts[5:]))
self.helper_test_transfer(tag, ts, ts.append_values(ts.all_values()))

Expand Down
Loading