From 9d8e058d8174f12e66b769236180c36bd9537aaf Mon Sep 17 00:00:00 2001 From: Simon Hoxbro Date: Sun, 20 Mar 2022 17:05:56 +0100 Subject: [PATCH 1/3] Improve array-like handling --- hvplot/plotting/core.py | 8 ++++++-- hvplot/tests/plotting/testcore.py | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 hvplot/tests/plotting/testcore.py diff --git a/hvplot/plotting/core.py b/hvplot/plotting/core.py index 02d530639..bbc10a0d8 100644 --- a/hvplot/plotting/core.py +++ b/hvplot/plotting/core.py @@ -33,8 +33,8 @@ def __call__(self, x=None, y=None, kind=None, **kwds): Parameters ---------- - x, y : string, optional - Field name in the data to draw x- and y-positions from + x, y : string, list, or array-like, optional + Field name(s) in the data to draw x- and y-positions from kind : string, optional The kind of plot to generate, e.g. 'line', 'scatter', etc. **kwds : optional @@ -44,6 +44,10 @@ def __call__(self, x=None, y=None, kind=None, **kwds): ------- HoloViews object: Object representing the requested visualization """ + # Convert an array-like to a list + x = list(x) if hasattr(x, "all") else x + y = list(y) if hasattr(y, "all") else y + if isinstance(kind, str) and kind not in self.__all__: raise NotImplementedError("kind='{kind}' for data of type {type}".format( kind=kind, type=type(self._data))) diff --git a/hvplot/tests/plotting/testcore.py b/hvplot/tests/plotting/testcore.py new file mode 100644 index 000000000..17d8cff67 --- /dev/null +++ b/hvplot/tests/plotting/testcore.py @@ -0,0 +1,18 @@ +import numpy as np +import pandas as pd +import hvplot.pandas + +import pytest + +@pytest.mark.parametrize("y", ( + ["A", "B", "C", "D"], + np.array(["A", "B", "C", "D"]), + pd.Index(["A", "B", "C", "D"]), + )) +def test_diffent_input_types(y): + df = pd._testing.makeDataFrame() + types = {t for t in dir(df.hvplot) if not t.startswith("_")} + ignore_types = {'bivariate', 'heatmap', 'hexbin', 'labels', 'vectorfield'} + + for t in types - ignore_types: + df.hvplot(y=y, kind=t) From 78ba106861d55ffbb4e06076374ca0bd4b5dd54d Mon Sep 17 00:00:00 2001 From: Simon Hoxbro Date: Sun, 20 Mar 2022 17:32:08 +0100 Subject: [PATCH 2/3] Fix flake --- hvplot/tests/plotting/testcore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hvplot/tests/plotting/testcore.py b/hvplot/tests/plotting/testcore.py index 17d8cff67..942db8f84 100644 --- a/hvplot/tests/plotting/testcore.py +++ b/hvplot/tests/plotting/testcore.py @@ -1,6 +1,6 @@ import numpy as np import pandas as pd -import hvplot.pandas +import hvplot.pandas # noqa import pytest From 547e674a33f67be24a1d5115d1655ee77ecfe820 Mon Sep 17 00:00:00 2001 From: maximlt Date: Fri, 6 May 2022 07:48:34 +0200 Subject: [PATCH 3/3] add is_list_like utility --- hvplot/plotting/core.py | 6 +++--- hvplot/tests/plotting/testcore.py | 3 +++ hvplot/tests/testutil.py | 15 ++++++++++++++- hvplot/util.py | 16 ++++++++++++++++ 4 files changed, 36 insertions(+), 4 deletions(-) diff --git a/hvplot/plotting/core.py b/hvplot/plotting/core.py index bbc10a0d8..2da4aef4a 100644 --- a/hvplot/plotting/core.py +++ b/hvplot/plotting/core.py @@ -8,7 +8,7 @@ panel_available = False from ..converter import HoloViewsConverter -from ..util import process_dynamic_args +from ..util import is_list_like, process_dynamic_args class hvPlotBase: @@ -45,8 +45,8 @@ def __call__(self, x=None, y=None, kind=None, **kwds): HoloViews object: Object representing the requested visualization """ # Convert an array-like to a list - x = list(x) if hasattr(x, "all") else x - y = list(y) if hasattr(y, "all") else y + x = list(x) if is_list_like(x) else x + y = list(y) if is_list_like(y) else y if isinstance(kind, str) and kind not in self.__all__: raise NotImplementedError("kind='{kind}' for data of type {type}".format( diff --git a/hvplot/tests/plotting/testcore.py b/hvplot/tests/plotting/testcore.py index 942db8f84..a2ed1ea3a 100644 --- a/hvplot/tests/plotting/testcore.py +++ b/hvplot/tests/plotting/testcore.py @@ -6,8 +6,11 @@ @pytest.mark.parametrize("y", ( ["A", "B", "C", "D"], + ("A", "B", "C", "D"), + {"A", "B", "C", "D"}, np.array(["A", "B", "C", "D"]), pd.Index(["A", "B", "C", "D"]), + pd.Series(["A", "B", "C", "D"]), )) def test_diffent_input_types(y): df = pd._testing.makeDataFrame() diff --git a/hvplot/tests/testutil.py b/hvplot/tests/testutil.py index c0cf65c74..e14cb40bc 100644 --- a/hvplot/tests/testutil.py +++ b/hvplot/tests/testutil.py @@ -4,11 +4,12 @@ import sys import numpy as np +import pandas as pd import pytest from unittest import TestCase, SkipTest -from hvplot.util import check_crs, process_xarray +from hvplot.util import check_crs, is_list_like, process_xarray class TestProcessXarray(TestCase): @@ -275,3 +276,15 @@ def test_check_crs(): assert p.srs == '+proj=utm +zone=15 +datum=NAD83 +units=m +no_defs' p = check_crs('wrong') assert p is None + + +def test_is_list_like(): + assert not is_list_like(0) + assert not is_list_like('string') + assert not is_list_like(np.array('a')) + assert is_list_like(['a', 'b']) + assert is_list_like(('a', 'b')) + assert is_list_like({'a', 'b'}) + assert is_list_like(pd.Series(['a', 'b'])) + assert is_list_like(pd.Index(['a', 'b'])) + assert is_list_like(np.array(['a', 'b'])) diff --git a/hvplot/util.py b/hvplot/util.py index d9b3a28b0..9cc6e2ba6 100644 --- a/hvplot/util.py +++ b/hvplot/util.py @@ -8,6 +8,7 @@ from packaging.version import Version from types import FunctionType +import numpy as np import pandas as pd import holoviews as hv import param @@ -252,6 +253,21 @@ def process_crs(crs): raise ValueError("Projection must be defined as a EPSG code, proj4 string, cartopy CRS or pyproj.Proj.") return crs + +def is_list_like(obj): + """ + Adapted from pandas' is_list_like cython function. + """ + return ( + # equiv: `isinstance(obj, abc.Iterable)` + hasattr(obj, "__iter__") and not isinstance(obj, type) + # we do not count strings/unicode/bytes as list-like + and not isinstance(obj, (str, bytes)) + # exclude zero-dimensional numpy arrays, effectively scalars + and not (isinstance(obj, np.ndarray) and obj.ndim == 0) + ) + + def is_tabular(data): if check_library(data, ['dask', 'streamz', 'pandas', 'geopandas', 'cudf']): return True