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

Add simple polars support #1129

Merged
merged 14 commits into from
Oct 2, 2023
14 changes: 14 additions & 0 deletions doc/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ alt: Works with GeoPandas
align: center
---
:::
:::{tab-item} Polars
```python
import polars
import hvplot.polars

df_polars = polars.from_pandas(df)
df_polars.hvplot.scatter(x='bill_length_mm', y='bill_depth_mm', by='species')
```
```{image} ./_static/home/dask.gif
---
alt: Works with Polars
align: center
---
:::

:::{tab-item} Intake
```python
Expand Down
1 change: 1 addition & 0 deletions examples/user_guide/Introduction.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"\n",
"* [Pandas](https://pandas.pydata.org): DataFrame, Series (columnar/tabular data)\n",
"* [Rapids cuDF](https://docs.rapids.ai/api/cudf/stable/): GPU DataFrame, Series (columnar/tabular data)\n",
"* [Polars](https://www.pola.rs/): Polars is a fast DataFrame library/in-memory query engine (columnar/tabular data)\n",
"* [Dask](https://www.dask.org): DataFrame, Series (distributed/out of core arrays and columnar data)\n",
"* [XArray](https://xarray.pydata.org): Dataset, DataArray (labelled multidimensional arrays)\n",
"* [Streamz](https://streamz.readthedocs.io): DataFrame(s), Series(s) (streaming columnar data)\n",
Expand Down
63 changes: 63 additions & 0 deletions hvplot/polars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import itertools
hoxbro marked this conversation as resolved.
Show resolved Hide resolved

from hvplot import hvPlotTabular, post_patch
from hvplot.converter import HoloViewsConverter
from hvplot.util import is_list_like

try:
import polars as pl
except:
raise ImportError(
"Could not patch plotting API onto Polars. Polars could not be imported."
)


@pl.api.register_dataframe_namespace("hvplot")
hoxbro marked this conversation as resolved.
Show resolved Hide resolved
@pl.api.register_series_namespace("hvplot")
@pl.api.register_lazyframe_namespace("hvplot")
hoxbro marked this conversation as resolved.
Show resolved Hide resolved
class hvPlotTabularPolars(hvPlotTabular):
def _get_converter(self, x=None, y=None, kind=None, **kwds):
params = dict(self._metadata, **kwds)
x = x or params.pop("x", None)
y = y or params.pop("y", None)
kind = kind or params.pop("kind", None)

# Find columns which should be converted for LazyDataFrame and DataFrame
if isinstance(self._data, (pl.LazyFrame, pl.DataFrame)):
if params.get("hover_cols") == "all":
columns = list(self._data.columns)
else:
possible_columns = [
[v] if isinstance(v, str) else v
for v in params.values()
if isinstance(v, (str, list))
]
columns = (
set(self._data.columns) & set(itertools.chain(*possible_columns))
) or {self._data.columns[0]}
xs = x if is_list_like(x) else (x,)
ys = y if is_list_like(y) else (y,)
columns |= {*xs, *ys}
columns.discard(None)

if isinstance(self._data, pl.DataFrame):
data = self._data.select(columns).to_pandas()
elif isinstance(self._data, pl.Series):
data = self._data.to_pandas()
elif isinstance(self._data, pl.LazyFrame):
data = self._data.select(columns).collect().to_pandas()
else:
raise ValueError(
"Only Polars DataFrame, Series, and LazyFrame are supported"
)

return HoloViewsConverter(data, x, y, kind=kind, **params)


def patch(name="hvplot", extension="bokeh", logo=False):
import hvplot.pandas # noqa
maximlt marked this conversation as resolved.
Show resolved Hide resolved

post_patch(extension, logo)
hoxbro marked this conversation as resolved.
Show resolved Hide resolved


patch()
61 changes: 53 additions & 8 deletions hvplot/tests/plotting/testcore.py
Original file line number Diff line number Diff line change
@@ -1,21 +1,66 @@
import numpy as np
import pandas as pd
import hvplot.pandas # noqa

import pytest

@pytest.mark.parametrize("y", (
from hvplot import hvPlotTabular

try:
import polars as pl
import hvplot.polars # noqa
skip_polar = False
except ImportError:
class pl:
DataFrame = None
LazyFrame = None
Series = None
skip_polar = True


TYPES = {t for t in dir(hvPlotTabular) if not t.startswith("_")}
FRAME_TYPES = TYPES - {"bivariate", "heatmap", "hexbin", "labels", "vectorfield"}
SERIES_TYPES = FRAME_TYPES - {"points", "polygons", "ohlc", "paths"}
frame_kinds = pytest.mark.parametrize("kind", FRAME_TYPES)
series_kinds = pytest.mark.parametrize("kind", SERIES_TYPES)

y_combinations = 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):
),
ids=lambda x: type(x).__name__
)


@frame_kinds
@y_combinations
def test_dataframe_pandas(kind, y):
df = pd._testing.makeDataFrame()
types = {t for t in dir(df.hvplot) if not t.startswith("_")}
ignore_types = {'bivariate', 'heatmap', 'hexbin', 'labels', 'vectorfield'}
df.hvplot(y=y, kind=kind)


@series_kinds
def test_series_pandas(kind):
ser = pd.Series(np.random.rand(10), name="A")
ser.hvplot(kind=kind)


@pytest.mark.skipif(skip_polar, reason="polars not installed")
maximlt marked this conversation as resolved.
Show resolved Hide resolved
@pytest.mark.parametrize("cast", (pl.DataFrame, pl.LazyFrame))
@frame_kinds
@y_combinations
def test_dataframe_polars(kind, y, cast):
df = cast(pd._testing.makeDataFrame())
assert isinstance(df, cast)
df.hvplot(y=y, kind=kind)


for t in types - ignore_types:
df.hvplot(y=y, kind=t)
@pytest.mark.skipif(skip_polar, reason="polars not installed")
@series_kinds
def test_series_polars(kind):
ser = pl.Series(values=np.random.rand(10), name="A")
assert isinstance(ser, pl.Series)
ser.hvplot(kind=kind)
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def get_setup_version(reponame):
'pooch',
'scipy',
'ipywidgets',
'polars',
maximlt marked this conversation as resolved.
Show resolved Hide resolved
]

# Dependencies required to run the notebooks
Expand Down