Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ensure downsample works with non-contiguous arrays #6271

Merged
merged 5 commits into from
Jun 10, 2024
Merged
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
11 changes: 11 additions & 0 deletions holoviews/operation/downsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,13 @@ def _lttb_inner(x, y, n_out, sampled_x, offset):
)


def _ensure_contiguous(x, y):
"""
Ensures the arrays are contiguous in memory (required by tsdownsample).
"""
return np.ascontiguousarray(x), np.ascontiguousarray(y)


def _lttb(x, y, n_out, **kwargs):
"""
Downsample the data using the LTTB algorithm.
Expand All @@ -115,6 +122,7 @@ def _lttb(x, y, n_out, **kwargs):
"""
try:
from tsdownsample import LTTBDownsampler
x, y = _ensure_contiguous(x, y)
return LTTBDownsampler().downsample(x, y, n_out=n_out, **kwargs)
except ModuleNotFoundError:
pass
Expand Down Expand Up @@ -168,6 +176,7 @@ def _min_max(x, y, n_out, **kwargs):
'The min-max downsampling algorithm requires the tsdownsample '
'library to be installed.'
) from None
x, y = _ensure_contiguous(x, y)
return MinMaxDownsampler().downsample(x, y, n_out=n_out, **kwargs)

def _min_max_lttb(x, y, n_out, **kwargs):
Expand All @@ -178,6 +187,7 @@ def _min_max_lttb(x, y, n_out, **kwargs):
'The minmax-lttb downsampling algorithm requires the tsdownsample '
'library to be installed.'
) from None
x, y = _ensure_contiguous(x, y)
return MinMaxLTTBDownsampler().downsample(x, y, n_out=n_out, **kwargs)

def _m4(x, y, n_out, **kwargs):
Expand All @@ -188,6 +198,7 @@ def _m4(x, y, n_out, **kwargs):
'The m4 downsampling algorithm requires the tsdownsample '
'library to be installed.'
) from None
x, y = _ensure_contiguous(x, y)
n_out = n_out - (n_out % 4) # n_out must be a multiple of 4
return M4Downsampler().downsample(x, y, n_out=n_out, **kwargs)

Expand Down
21 changes: 16 additions & 5 deletions holoviews/tests/operation/test_downsample.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import pandas as pd
import pytest

import holoviews as hv
from holoviews.core.overlay import NdOverlay, Overlay
from holoviews.element import Curve
from holoviews.operation.downsample import _ALGORITHMS, downsample1d

try:
Expand All @@ -20,16 +21,26 @@ def test_downsample1d_multi(plottype):
assert N > downsample1d.width

if plottype == "overlay":
figure = hv.Overlay([hv.Curve(range(N)), hv.Curve(range(N))])
figure = Overlay([Curve(range(N)), Curve(range(N))])
elif plottype == "ndoverlay":
figure = hv.NdOverlay({"A": hv.Curve(range(N)), "B": hv.Curve(range(N))})
figure = NdOverlay({"A": Curve(range(N)), "B": Curve(range(N))})
philippjfr marked this conversation as resolved.
Show resolved Hide resolved

figure_values = downsample1d(figure, dynamic=False).data.values()
for n in figure_values:
for value in n.data.values():
assert value.size == downsample1d.width


@pytest.mark.skipif(not tsdownsample, reason="tsdownsample not installed")
@pytest.mark.parametrize("algorithm", algorithms)
def test_downsample1d_non_contiguous(algorithm):
x = np.arange(20)
y = np.arange(40).reshape(1, 40)[0, ::2]

downsampled = downsample1d(Curve((x, y), datatype=['array']), dynamic=False, width=10, algorithm=algorithm)
assert len(downsampled)


def test_downsample1d_shared_data():
runs = [0]

Expand All @@ -42,7 +53,7 @@ def _compute_mask(self, element):

N = 1000
df = pd.DataFrame({c: range(N) for c in "xyz"})
figure = hv.Overlay([hv.Curve(df, kdims="x", vdims=c) for c in "yz"])
figure = Overlay([Curve(df, kdims="x", vdims=c) for c in "yz"])

# We set x_range to trigger _compute_mask
mocksample(figure, dynamic=False, x_range=(0, 500))
Expand All @@ -61,7 +72,7 @@ def _compute_mask(self, element):

N = 1000
df = pd.DataFrame({c: range(N) for c in "xyz"})
figure = hv.Overlay([hv.Curve(df, kdims="index", vdims=c) for c in "xyz"])
figure = Overlay([Curve(df, kdims="index", vdims=c) for c in "xyz"])

# We set x_range to trigger _compute_mask
mocksample(figure, dynamic=False, x_range=(0, 500))
Expand Down