Skip to content
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
13 changes: 4 additions & 9 deletions holoviews/core/data/narwhals.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,17 +192,12 @@ def range(cls, dataset, dimension):
return np.nan, np.nan
return cmin.item(), cmax.item()
else:
col = nw.col(name)
if dimension.nodata is not None:
expr = (
nw.when(nw.col(name) == dimension.nodata)
.then(None)
.otherwise(nw.col(name))
.alias(name)
df_column = df_column.select(
nw.when(col != dimension.nodata).then(col)
)
df_column = df_column.select(expr)
if dataset.data.implementation in _NO_DROP_NULL:
col = nw.col(name)
else:
if dataset.data.implementation not in _NO_DROP_NULL:
col = nw.col(name).drop_nulls()
# NOTE: Some narwhals backends (duckdb) will return nan as
# the max value
Expand Down
7 changes: 6 additions & 1 deletion holoviews/core/data/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,12 @@ def values(
data = (data if isindex else data.dt).tz_localize(None)
if not expanded:
return pd.unique(data)
return data.values if hasattr(data, 'values') else data
if hasattr(data, 'values'):
data = data.values
# https://pandas.pydata.org/docs/dev/user_guide/copy_on_write.html#read-only-numpy-arrays
if hasattr(data, "flags") and not data.flags.writeable:
data = data.copy()
return data


@classmethod
Expand Down
7 changes: 6 additions & 1 deletion holoviews/core/data/spatialpandas_dask.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,17 @@ def frame_type(cls):

@classmethod
def init(cls, eltype, data, kdims, vdims):
import dask
import dask.dataframe as dd
data, dims, params = super().init(
eltype, data, kdims, vdims
)
if not isinstance(data, cls.frame_type()):
data = dd.from_pandas(data, npartitions=1)
# convert-string will convert the object dtype to string
# even if the object is a list
# https://github.com/dask/dask/issues/10631
with dask.config.set({"dataframe.convert-string": False}):
data = dd.from_pandas(data, npartitions=1)
Comment on lines +43 to +47

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image
import pandas as pd
import dask
import dask.dataframe as dd

with dask.config.set({"dataframe.convert-string": False}):
    ddf = dd.from_pandas(pd.DataFrame([{'value': [5, 5, 5]}]))

return data, dims, params

@classmethod
Expand Down
4 changes: 1 addition & 3 deletions holoviews/core/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -860,9 +860,7 @@ def asarray(arraylike, strict=True):
return arraylike
elif isinstance(arraylike, list):
return np.asarray(arraylike, dtype=object)
elif not isinstance(arraylike, np.ndarray) and isinstance(arraylike, arraylike_types):
return arraylike.values
elif hasattr(arraylike, '__array__'):
elif hasattr(arraylike, '__array__') or isinstance(arraylike, arraylike_types):
return np.asarray(arraylike)
elif strict:
raise ValueError(f'Could not convert {type(arraylike)} type to array')
Expand Down
14 changes: 12 additions & 2 deletions holoviews/core/util/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,5 +112,15 @@ def __version__(self):
NUMPY_GE_2_0_0 = NUMPY_VERSION >= (2, 0, 0)
PANDAS_GE_2_1_0 = PANDAS_VERSION >= (2, 1, 0)
PANDAS_GE_2_2_0 = PANDAS_VERSION >= (2, 2, 0)

__all__ = ["NUMPY_GE_2_0_0", "NUMPY_VERSION", "PANDAS_GE_2_1_0", "PANDAS_GE_2_2_0", "PANDAS_VERSION", "PARAM_VERSION", "_LazyModule"]
PANDAS_GE_3_0_0 = PANDAS_VERSION >= (3, 0, 0)

__all__ = [
"NUMPY_GE_2_0_0",
"NUMPY_VERSION",
"PANDAS_GE_2_1_0",
"PANDAS_GE_2_2_0",
"PANDAS_GE_3_0_0",
"PANDAS_VERSION",
"PARAM_VERSION",
"_LazyModule",
]
14 changes: 10 additions & 4 deletions holoviews/operation/datashader.py
Original file line number Diff line number Diff line change
Expand Up @@ -489,15 +489,21 @@ def _apply_datashader(self, dfdata, cvs_fn, agg_fn, agg_kwargs, x, y, agg_state:
for col in dfdata.columns:
if col in agg.coords:
continue
val = dfdata[col].values[index]
if dtype_kind(val) == 'f':
ser = dfdata[col]
kind = dtype_kind(ser)
if kind == "O" and getattr(ser.dtype, "storage", None) == "pyarrow":
# pyarrow can only handle 1-dimensional
val = np.asarray(ser)[index]
else:
val = ser.values[index]
if kind == 'f':
val[neg1] = np.nan
elif isinstance(val.dtype, pd.CategoricalDtype):
val = val.to_numpy()
val[neg1] = "-"
elif dtype_kind(val) == "O":
elif kind == "O":
val[neg1] = "-"
elif dtype_kind(val) == "M":
elif kind == "M":
val[neg1] = np.datetime64("NaT")
else:
val = val.astype(np.float64)
Expand Down
20 changes: 14 additions & 6 deletions holoviews/operation/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from ..core.util import (
datetime_types,
dt_to_int,
dtype_kind,
group_sanitizer,
is_cupy_array,
is_dask_array,
Expand Down Expand Up @@ -1065,16 +1066,23 @@ class interpolate_curve(Operation):

_per_element = True

@staticmethod
def _get_dtype(obj):
if dtype_kind(obj) == "O":
# Handle non-numpy objects like Pandas 3.0 StringArray
return "O"
return obj.dtype

@classmethod
def pts_to_prestep(cls, x, values):
steps = np.zeros(2 * len(x) - 1)
value_steps = tuple(np.empty(2 * len(x) - 1, dtype=v.dtype) for v in values)
value_steps = tuple(np.empty(2 * len(x) - 1, dtype=cls._get_dtype(v)) for v in values)

steps[0::2] = x
steps[1::2] = steps[0:-2:2]

val_arrays = []
for v, s in zip(values, value_steps, strict=None):
for v, s in zip(values, value_steps, strict=True):
s[0::2] = v
s[1::2] = s[2::2]
val_arrays.append(s)
Expand All @@ -1084,13 +1092,13 @@ def pts_to_prestep(cls, x, values):
@classmethod
def pts_to_midstep(cls, x, values):
steps = np.zeros(2 * len(x))
value_steps = tuple(np.empty(2 * len(x), dtype=v.dtype) for v in values)
value_steps = tuple(np.empty(2 * len(x), dtype=cls._get_dtype(v)) for v in values)

steps[1:-1:2] = steps[2::2] = x[:-1] + (x[1:] - x[:-1])/2
steps[0], steps[-1] = x[0], x[-1]

val_arrays = []
for v, s in zip(values, value_steps, strict=None):
for v, s in zip(values, value_steps, strict=True):
s[0::2] = v
s[1::2] = s[0::2]
val_arrays.append(s)
Expand All @@ -1100,13 +1108,13 @@ def pts_to_midstep(cls, x, values):
@classmethod
def pts_to_poststep(cls, x, values):
steps = np.zeros(2 * len(x) - 1)
value_steps = tuple(np.empty(2 * len(x) - 1, dtype=v.dtype) for v in values)
value_steps = tuple(np.empty(2 * len(x) - 1, dtype=cls._get_dtype(v)) for v in values)

steps[0::2] = x
steps[1::2] = steps[2::2]

val_arrays = []
for v, s in zip(values, value_steps, strict=None):
for v, s in zip(values, value_steps, strict=True):
s[0::2] = v
s[1::2] = s[0:-2:2]
val_arrays.append(s)
Expand Down
4 changes: 2 additions & 2 deletions holoviews/plotting/bokeh/path.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from ...core import util
from ...core.dimension import Dimension
from ...core.util import dtype_kind
from ...core.util import arraylike_types, dtype_kind
from ...element import Contours, Polygons
from ...util.transform import dim
from .callbacks import PolyDrawCallback, PolyEditCallback
Expand Down Expand Up @@ -50,7 +50,7 @@ def _element_transform(self, transform, element, ranges):
data = super()._element_transform(transform, element, ranges)
new_data = []
for d in data:
if isinstance(d, np.ndarray) and len(d) == 1:
if isinstance(d, arraylike_types) and len(d) == 1:
new_data.append(d[0])
else:
new_data.append(d)
Expand Down
2 changes: 1 addition & 1 deletion holoviews/plotting/mpl/chart.py
Original file line number Diff line number Diff line change
Expand Up @@ -966,7 +966,7 @@ def _create_bars(self, axis, element, ranges, style):
xdiff = 1
else:
xdiff = np.min(xdiff)
width = (1 - self.bar_padding) * (date2num(xdiff) / 1000 if is_dt else xdiff)
width = (1 - self.bar_padding) * (date2num(xdiff.astype("timedelta64[us]")) if is_dt else xdiff)
data_width = (1 - self.bar_padding) * xdiff
else:
xdiff = len(values.get('category', [None]))
Expand Down
2 changes: 2 additions & 0 deletions holoviews/plotting/mpl/element.py
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,8 @@ def _apply_transforms(self, element, ranges, style):
if (not np.isscalar(val) and len(util.unique_array(val)) == 1 and
("color" not in k or validate('color', val))):
val = val[0]
if isinstance(val, util.arraylike_types):
val = np.asarray(val)

if not np.isscalar(val) and k in self._nonvectorized_styles:
element = type(element).__name__
Expand Down
7 changes: 0 additions & 7 deletions holoviews/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,6 @@ def pytest_collection_modifyitems(config, items):
mpl.use("agg")


with contextlib.suppress(Exception):
# From Dask 2023.7.1 they now automatically convert strings
# https://docs.dask.org/en/stable/changelog.html#v2023-7-1
import dask

dask.config.set({"dataframe.convert-string": False})


@pytest.fixture
def ibis_sqlite_backend():
Expand Down
25 changes: 24 additions & 1 deletion holoviews/tests/core/data/test_daskinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,26 @@
import pandas as pd

try:
import dask
import dask.dataframe as dd
except ImportError:
raise SkipTest("Could not import dask, skipping DaskInterface tests.")

from holoviews.core.data import Dataset
from holoviews.core.util import PANDAS_VERSION
from holoviews.core.util.dependencies import (
PANDAS_GE_3_0_0,
PANDAS_VERSION,
_no_import_version,
)
from holoviews.util.transform import dim

from .test_pandasinterface import BasePandasInterfaceTests

_DASK_CONVERT_STRING = (
_no_import_version("dask") >= (2023, 7, 1)
and dask.config.get("dataframe.convert-string") in (True, None)
)


class DaskDatasetTest(BasePandasInterfaceTests):
"""
Expand Down Expand Up @@ -137,3 +147,16 @@ def test_dataset_get_dframe_by_dimension(self):
df = self.dataset_hm.dframe(['x'])
expected = self.frame({'x': self.xs}, dtype=df.dtypes.iloc[0]).compute()
self.assertEqual(df, expected)

def test_dataset_dataset_ht_dtypes(self):
ds = self.table
if _DASK_CONVERT_STRING:
string_dtype = pd.StringDtype(na_value=pd.NA, storage="pyarrow")
elif PANDAS_GE_3_0_0:
string_dtype = pd.StringDtype(na_value=np.nan)
else:
string_dtype = np.dtype('object')
assert ds.interface.dtype(ds, 'Gender') == string_dtype
assert ds.interface.dtype(ds, 'Age') == np.dtype(int)
assert ds.interface.dtype(ds, 'Weight') == np.dtype(int)
assert ds.interface.dtype(ds, 'Height') == np.dtype('float64')
17 changes: 17 additions & 0 deletions holoviews/tests/core/data/test_pandasinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from holoviews.core.data.pandas import PandasInterface
from holoviews.core.dimension import Dimension
from holoviews.core.spaces import HoloMap
from holoviews.core.util.dependencies import PANDAS_GE_3_0_0
from holoviews.element import Distribution, Points, Scatter

from .base import HeterogeneousColumnTests, InterfaceTests
Expand Down Expand Up @@ -172,6 +173,14 @@ def test_dataset_range_with_object_index(self):
ds = Dataset(df, kdims='index')
assert ds.range('index') == ('A', 'D')

def test_dataset_dataset_ht_dtypes(self):
ds = self.table
string_dtype = pd.StringDtype(na_value=np.nan) if PANDAS_GE_3_0_0 else np.dtype('object')
assert ds.interface.dtype(ds, 'Gender') == string_dtype
assert ds.interface.dtype(ds, 'Age') == np.dtype(int)
assert ds.interface.dtype(ds, 'Weight') == np.dtype(int)
assert ds.interface.dtype(ds, 'Height') == np.dtype('float64')


class PandasInterfaceTests(BasePandasInterfaceTests):

Expand Down Expand Up @@ -412,6 +421,14 @@ def test_groupby_one_index_one_column(self):
for k, v in grouped.items():
pd.testing.assert_frame_equal(v.data, ds.select(values=k).data)

def test_dataset_dataset_ht_dtypes(self):
ds = self.table
string_dtype = pd.StringDtype(na_value=np.nan) if PANDAS_GE_3_0_0 else np.dtype('object')
assert ds.interface.dtype(ds, 'Gender') == string_dtype
assert ds.interface.dtype(ds, 'Age') == np.dtype(int)
assert ds.interface.dtype(ds, 'Weight') == np.dtype(int)
assert ds.interface.dtype(ds, 'Height') == np.dtype('float64')

def test_regression_no_auto_index(self):
# https://github.com/holoviz/holoviews/issues/6298

Expand Down
4 changes: 2 additions & 2 deletions holoviews/tests/core/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import unittest
from itertools import product
from pathlib import Path
from zoneinfo import ZoneInfo

import narwhals.stable.v2 as nw
import numpy as np
Expand Down Expand Up @@ -589,8 +590,7 @@ def test_date_range_1_sec(self):
self.assertEqual(drange[-1], end-np.timedelta64(50, 'ms'))

def test_timezone_to_int(self):
import pytz
timezone = pytz.timezone("Europe/Copenhagen")
timezone = ZoneInfo("Europe/Copenhagen")

values = [
datetime.datetime(2021, 4, 8, 12, 0, 0, 0),
Expand Down
Loading