From 9ed883643ba172cabc00e77593040f5483d810b4 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 1 Aug 2024 12:38:49 +0100 Subject: [PATCH 01/42] feat(python!): Use Altair in DataFrame.plot --- py-polars/polars/dataframe/frame.py | 49 +++--- py-polars/polars/dataframe/plotting.py | 163 ++++++++++++++++++ py-polars/polars/dependencies.py | 10 +- py-polars/polars/meta/versions.py | 4 +- py-polars/polars/series/series.py | 41 ----- .../unit/operations/namespaces/test_plot.py | 10 +- 6 files changed, 202 insertions(+), 75 deletions(-) create mode 100644 py-polars/polars/dataframe/plotting.py diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index 816bbbebef5c..6707ccef2a9f 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -66,6 +66,7 @@ from polars._utils.wrap import wrap_expr, wrap_ldf, wrap_s from polars.dataframe._html import NotebookFormatter from polars.dataframe.group_by import DynamicGroupBy, GroupBy, RollingGroupBy +from polars.dataframe.plotting import Plot from polars.datatypes import ( N_INFER_DEFAULT, Boolean, @@ -82,15 +83,15 @@ ) from polars.datatypes.group import INTEGER_DTYPES from polars.dependencies import ( + _ALTAIR_AVAILABLE, _GREAT_TABLES_AVAILABLE, - _HVPLOT_AVAILABLE, _PANDAS_AVAILABLE, _PYARROW_AVAILABLE, _check_for_numpy, _check_for_pandas, _check_for_pyarrow, + altair, great_tables, - hvplot, import_optional, ) from polars.dependencies import numpy as np @@ -123,7 +124,6 @@ import numpy.typing as npt import torch from great_tables import GT - from hvplot.plotting.core import hvPlotTabularPolars from xlsxwriter import Workbook from polars import DataType, Expr, LazyFrame, Series @@ -603,7 +603,7 @@ def _replace(self, column: str, new_column: Series) -> DataFrame: @property @unstable() - def plot(self) -> hvPlotTabularPolars: + def plot(self) -> Plot: """ Create a plot namespace. @@ -611,9 +611,22 @@ def plot(self) -> hvPlotTabularPolars: This functionality is currently considered **unstable**. It may be changed at any point without it being considered a breaking change. + .. versionchanged:: 1.4.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + Polars does not implement plotting logic itself, but instead defers to - hvplot. Please see the `hvplot reference gallery `_ - for more information and documentation. + Altair: + + - `df.plot.line(*args, **kwargs)` + is shorthand for + `alt.Chart(df).mark_line().encode(*args, **kwargs).interactive()` + - `df.plot.point(*args, **kwargs)` + is shorthand for + `alt.Chart(df).mark_point().encode(*args, **kwargs).interactive()` + - ... (likewise, for any other attribute, e.g. `df.plot.bar`) Examples -------- @@ -626,32 +639,24 @@ def plot(self) -> hvPlotTabularPolars: ... "species": ["setosa", "setosa", "versicolor"], ... } ... ) - >>> df.plot.scatter(x="length", y="width", by="species") # doctest: +SKIP + >>> df.plot.point(x="length", y="width", color="species") # doctest: +SKIP Line plot: >>> from datetime import date >>> df = pl.DataFrame( ... { - ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)], - ... "stock_1": [1, 4, 6], - ... "stock_2": [1, 5, 2], + ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, + ... "price": [1, 4, 6, 1, 5, 2], + ... "stock": ["a", "a", "a", "b", "b", "b"], ... } ... ) - >>> df.plot.line(x="date", y=["stock_1", "stock_2"]) # doctest: +SKIP - - For more info on what you can pass, you can use ``hvplot.help``: - - >>> import hvplot # doctest: +SKIP - >>> hvplot.help("scatter") # doctest: +SKIP + >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP """ - if not _HVPLOT_AVAILABLE or parse_version(hvplot.__version__) < parse_version( - "0.9.1" - ): - msg = "hvplot>=0.9.1 is required for `.plot`" + if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 3, 0): + msg = "altair>=5.3.0 is required for `.plot`" raise ModuleUpgradeRequiredError(msg) - hvplot.post_patch() - return hvplot.plotting.core.hvPlotTabularPolars(self) + return Plot(self) @property @unstable() diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py new file mode 100644 index 000000000000..2b5e40661579 --- /dev/null +++ b/py-polars/polars/dataframe/plotting.py @@ -0,0 +1,163 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any + +if TYPE_CHECKING: + import altair as alt + + from polars import DataFrame + + +class Plot: + """DataFrame.plot namespace.""" + + chart: alt.Chart + + def __init__(self, df: DataFrame) -> None: + import altair as alt + + self.chart = alt.Chart(df) + + def line( + self, + x: str | Any|None=None, + y: str | Any|None=None, + color: str | Any|None=None, + order: str | Any|None=None, + tooltip: str | Any|None=None, + *args: Any, + **kwargs: Any, + ) -> alt.Chart: + """ + Draw line plot. + + Polars does not implement plottinng logic itself but instead defers to Altair. + `df.plot.line(*args, **kwargs)` is shorthand for + `alt.Chart(df).mark_line().encode(*args, **kwargs).interactive()`, + as is intended for convenience - for full customisatibility, use a plotting + library directly. + + .. versionchanged:: 1.4.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Parameters + ---------- + x + Column with x-coordinates of lines. + y + Column with y-coordinates of lines. + color + Column to color lines by. + order + Column to use for order of data points in lines. + tooltip + Columns to show values of when hovering over points with pointer. + *args, **kwargs + Additional arguments and keyword arguments passed to Altair. + + Examples + -------- + >>> from datetime import date + >>> df = pl.DataFrame( + ... { + ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, + ... "price": [1, 4, 6, 1, 5, 2], + ... "stock": ["a", "a", "a", "b", "b", "b"], + ... } + ... ) + >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP + """ + encodings = {} + if x is not None: + encodings["x"] = x + if y is not None: + encodings["y"] = y + if color is not None: + encodings["color"] = color + if order is not None: + encodings["order"] = order + if tooltip is not None: + encodings["tooltip"] = tooltip + return ( + self.chart.mark_line() + .encode(*args, **{**encodings, **kwargs}) + .interactive() + ) + + def point( + self, + x: str | Any |None= None, + y: str | Any |None= None, + color: str | Any|None = None, + size: str | Any |None= None, + tooltip: str | Any |None= None, + *args: Any, + **kwargs: Any, + ) -> alt.Chart: + """ + Draw scatter plot. + + Polars does not implement plottinng logic itself but instead defers to Altair. + `df.plot.point(*args, **kwargs)` is shorthand for + `alt.Chart(df).mark_point().encode(*args, **kwargs).interactive()`, + as is intended for convenience - for full customisatibility, use a plotting + library directly. + + .. versionchanged:: 1.4.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Parameters + ---------- + x + Column with x-coordinates of points. + y + Column with y-coordinates of points. + color + Column to color points by. + size + Column which determines points' sizes. + tooltip + Columns to show values of when hovering over points with pointer. + *args, **kwargs + Additional arguments and keyword arguments passed to Altair. + + Examples + -------- + >>> df = pl.DataFrame( + ... { + ... "length": [1, 4, 6], + ... "width": [4, 5, 6], + ... "species": ["setosa", "setosa", "versicolor"], + ... } + ... ) + >>> df.plot.point(x="length", y="width", color="species") # doctest: +SKIP + """ + encodings = {} + if x is not None: + encodings["x"] = x + if y is not None: + encodings["y"] = y + if color is not None: + encodings["color"] = color + if size is not None: + encodings["size"] = size + if tooltip is not None: + encodings["tooltip"] = tooltip + return ( + self.chart.mark_point() + .encode(*args, **{**encodings, **kwargs}) + .interactive() + ) + + def __getattr__(self, attr: str, *args: Any, **kwargs: Any) -> alt.Chart: + method = self.chart.getattr(f"mark_{attr}", None) + if method is None: + msg = "Altair has no method 'mark_{attr}'" + raise AttributeError(msg) + return method().encode(*args, **kwargs).interactive() diff --git a/py-polars/polars/dependencies.py b/py-polars/polars/dependencies.py index ce457255bb59..10548da8c904 100644 --- a/py-polars/polars/dependencies.py +++ b/py-polars/polars/dependencies.py @@ -8,11 +8,11 @@ from types import ModuleType from typing import TYPE_CHECKING, Any, ClassVar, Hashable, cast +_ALTAIR_AVAILABLE = True _DELTALAKE_AVAILABLE = True _FSSPEC_AVAILABLE = True _GEVENT_AVAILABLE = True _GREAT_TABLES_AVAILABLE = True -_HVPLOT_AVAILABLE = True _HYPOTHESIS_AVAILABLE = True _NUMPY_AVAILABLE = True _PANDAS_AVAILABLE = True @@ -150,11 +150,11 @@ def _lazy_import(module_name: str) -> tuple[ModuleType, bool]: import pickle import subprocess + import altair import deltalake import fsspec import gevent import great_tables - import hvplot import hypothesis import numpy import pandas @@ -175,10 +175,10 @@ def _lazy_import(module_name: str) -> tuple[ModuleType, bool]: subprocess, _ = _lazy_import("subprocess") # heavy/optional third party libs + altair, _ALTAIR_AVAILABLE = _lazy_import("altair") deltalake, _DELTALAKE_AVAILABLE = _lazy_import("deltalake") fsspec, _FSSPEC_AVAILABLE = _lazy_import("fsspec") great_tables, _GREAT_TABLES_AVAILABLE = _lazy_import("great_tables") - hvplot, _HVPLOT_AVAILABLE = _lazy_import("hvplot") hypothesis, _HYPOTHESIS_AVAILABLE = _lazy_import("hypothesis") numpy, _NUMPY_AVAILABLE = _lazy_import("numpy") pandas, _PANDAS_AVAILABLE = _lazy_import("pandas") @@ -301,11 +301,11 @@ def import_optional( "pickle", "subprocess", # lazy-load third party libs + "altair", "deltalake", "fsspec", "gevent", "great_tables", - "hvplot", "numpy", "pandas", "pydantic", @@ -318,11 +318,11 @@ def import_optional( "_check_for_pyarrow", "_check_for_pydantic", # exported flags/guards + "_ALTAIR_AVAILABLE", "_DELTALAKE_AVAILABLE", "_PYICEBERG_AVAILABLE", "_FSSPEC_AVAILABLE", "_GEVENT_AVAILABLE", - "_HVPLOT_AVAILABLE", "_HYPOTHESIS_AVAILABLE", "_NUMPY_AVAILABLE", "_PANDAS_AVAILABLE", diff --git a/py-polars/polars/meta/versions.py b/py-polars/polars/meta/versions.py index 02b71c6a92bb..98a08589ea76 100644 --- a/py-polars/polars/meta/versions.py +++ b/py-polars/polars/meta/versions.py @@ -20,13 +20,13 @@ def show_versions() -> None: Python: 3.11.8 (main, Feb 6 2024, 21:21:21) [Clang 15.0.0 (clang-1500.1.0.2.5)] ----Optional dependencies---- adbc_driver_manager: 0.11.0 + altair: 5.3.0 cloudpickle: 3.0.0 connectorx: 0.3.2 deltalake: 0.17.1 fastexcel: 0.10.4 fsspec: 2023.12.2 gevent: 24.2.1 - hvplot: 0.9.2 matplotlib: 3.8.4 nest_asyncio: 1.6.0 numpy: 1.26.4 @@ -63,6 +63,7 @@ def _get_dependency_info() -> dict[str, str]: # see the list of dependencies in pyproject.toml opt_deps = [ "adbc_driver_manager", + "altair", "cloudpickle", "connectorx", "deltalake", @@ -70,7 +71,6 @@ def _get_dependency_info() -> dict[str, str]: "fsspec", "gevent", "great_tables", - "hvplot", "matplotlib", "nest_asyncio", "numpy", diff --git a/py-polars/polars/series/series.py b/py-polars/polars/series/series.py index 07af04997841..c35f11a2a7ec 100644 --- a/py-polars/polars/series/series.py +++ b/py-polars/polars/series/series.py @@ -86,12 +86,10 @@ ) from polars.datatypes._utils import dtype_to_init_repr from polars.dependencies import ( - _HVPLOT_AVAILABLE, _PYARROW_AVAILABLE, _check_for_numpy, _check_for_pandas, _check_for_pyarrow, - hvplot, import_optional, ) from polars.dependencies import numpy as np @@ -117,7 +115,6 @@ import jax import numpy.typing as npt import torch - from hvplot.plotting.core import hvPlotTabularPolars from polars import DataFrame, DataType, Expr from polars._typing import ( @@ -7378,44 +7375,6 @@ def struct(self) -> StructNameSpace: """Create an object namespace of all struct related methods.""" return StructNameSpace(self) - @property - @unstable() - def plot(self) -> hvPlotTabularPolars: - """ - Create a plot namespace. - - .. warning:: - This functionality is currently considered **unstable**. It may be - changed at any point without it being considered a breaking change. - - Polars does not implement plotting logic itself, but instead defers to - hvplot. Please see the `hvplot reference gallery `_ - for more information and documentation. - - Examples - -------- - Histogram: - - >>> s = pl.Series("values", [1, 4, 2]) - >>> s.plot.hist() # doctest: +SKIP - - KDE plot (note: in addition to ``hvplot``, this one also requires ``scipy``): - - >>> s.plot.kde() # doctest: +SKIP - - For more info on what you can pass, you can use ``hvplot.help``: - - >>> import hvplot # doctest: +SKIP - >>> hvplot.help("hist") # doctest: +SKIP - """ - if not _HVPLOT_AVAILABLE or parse_version(hvplot.__version__) < parse_version( - "0.9.1" - ): - msg = "hvplot>=0.9.1 is required for `.plot`" - raise ModuleUpgradeRequiredError(msg) - hvplot.post_patch() - return hvplot.plotting.core.hvPlotTabularPolars(self) - def _resolve_temporal_dtype( dtype: PolarsDataType | None, diff --git a/py-polars/tests/unit/operations/namespaces/test_plot.py b/py-polars/tests/unit/operations/namespaces/test_plot.py index 34f8964512d8..542508a6f914 100644 --- a/py-polars/tests/unit/operations/namespaces/test_plot.py +++ b/py-polars/tests/unit/operations/namespaces/test_plot.py @@ -17,18 +17,18 @@ def test_dataframe_scatter() -> None: "species": ["setosa", "setosa", "versicolor"], } ) - df.plot.scatter(x="length", y="width", by="species") + df.plot.point(x="length", y="width", color="species") def test_dataframe_line() -> None: df = pl.DataFrame( { - "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 3)], - "stock_1": [1, 4, 6], - "stock_2": [1, 5, 2], + "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, + "price": [1, 4, 6, 1, 5, 2], + "stock": ["a", "a", "a", "b", "b", "b"], } ) - df.plot.line(x="date", y=["stock_1", "stock_2"]) + df.plot.line(x="date", y="price", color="stock") def test_series_hist() -> None: From 00f74132c6802f5df8415cc06428c93ad5784fc1 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 1 Aug 2024 15:06:04 +0100 Subject: [PATCH 02/42] missing file --- py-polars/polars/dataframe/plotting.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 2b5e40661579..5b956e09ea12 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -20,11 +20,11 @@ def __init__(self, df: DataFrame) -> None: def line( self, - x: str | Any|None=None, - y: str | Any|None=None, - color: str | Any|None=None, - order: str | Any|None=None, - tooltip: str | Any|None=None, + x: str | Any | None = None, + y: str | Any | None = None, + color: str | Any | None = None, + order: str | Any | None = None, + tooltip: str | Any | None = None, *args: Any, **kwargs: Any, ) -> alt.Chart: @@ -89,11 +89,11 @@ def line( def point( self, - x: str | Any |None= None, - y: str | Any |None= None, - color: str | Any|None = None, - size: str | Any |None= None, - tooltip: str | Any |None= None, + x: str | Any | None = None, + y: str | Any | None = None, + color: str | Any | None = None, + size: str | Any | None = None, + tooltip: str | Any | None = None, *args: Any, **kwargs: Any, ) -> alt.Chart: From f0c806fd6dd758e20b9f277eb999331023c1d1ea Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:05:09 +0100 Subject: [PATCH 03/42] use ChannelType --- .../docs/source/reference/series/index.rst | 1 - .../docs/source/reference/series/plot.rst | 7 -- py-polars/polars/dataframe/plotting.py | 90 ++++++++++++++++--- .../unit/operations/namespaces/test_plot.py | 7 +- 4 files changed, 79 insertions(+), 26 deletions(-) delete mode 100644 py-polars/docs/source/reference/series/plot.rst diff --git a/py-polars/docs/source/reference/series/index.rst b/py-polars/docs/source/reference/series/index.rst index a8476da64b97..6090e43ddd15 100644 --- a/py-polars/docs/source/reference/series/index.rst +++ b/py-polars/docs/source/reference/series/index.rst @@ -20,7 +20,6 @@ This page gives an overview of all public Series methods. list modify_select miscellaneous - plot string struct temporal diff --git a/py-polars/docs/source/reference/series/plot.rst b/py-polars/docs/source/reference/series/plot.rst deleted file mode 100644 index f7f719b8472e..000000000000 --- a/py-polars/docs/source/reference/series/plot.rst +++ /dev/null @@ -1,7 +0,0 @@ -==== -Plot -==== - -.. currentmodule:: polars - -.. autoproperty:: Series.plot \ No newline at end of file diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 5b956e09ea12..de8dfe900d3f 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,12 +1,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeAlias, Union if TYPE_CHECKING: import altair as alt from polars import DataFrame + ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] + class Plot: """DataFrame.plot namespace.""" @@ -18,13 +20,75 @@ def __init__(self, df: DataFrame) -> None: self.chart = alt.Chart(df) + def bar( + self, + x: ChannelType | None = None, + y: ChannelType | None = None, + color: ChannelType | None = None, + tooltip: ChannelType | None = None, + *args: Any, + **kwargs: Any, + ) -> alt.Chart: + """ + Draw bar plot. + + Polars does not implement plottinng logic itself but instead defers to Altair. + `df.plot.bar(*args, **kwargs)` is shorthand for + `alt.Chart(df).mark_bar().encode(*args, **kwargs).interactive()`, + as is intended for convenience - for full customisatibility, use a plotting + library directly. + + .. versionchanged:: 1.4.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Parameters + ---------- + x + Column with x-coordinates of bars. + y + Column with y-coordinates of bars. + color + Column to color bars by. + tooltip + Columns to show values of when hovering over points with pointer. + *args, **kwargs + Additional arguments and keyword arguments passed to Altair. + + Examples + -------- + >>> from datetime import date + >>> df = pl.DataFrame( + ... { + ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, + ... "price": [1, 4, 6, 1, 5, 2], + ... "stock": ["a", "a", "a", "b", "b", "b"], + ... } + ... ) + >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP + """ + encodings = {} + if x is not None: + encodings["x"] = x + if y is not None: + encodings["y"] = y + if color is not None: + encodings["color"] = color + if tooltip is not None: + encodings["tooltip"] = tooltip + return ( + self.chart.mark_bar().encode(*args, **{**encodings, **kwargs}).interactive() + ) + def line( self, - x: str | Any | None = None, - y: str | Any | None = None, - color: str | Any | None = None, - order: str | Any | None = None, - tooltip: str | Any | None = None, + x: ChannelType | None = None, + y: ChannelType | None = None, + color: ChannelType | None = None, + order: ChannelType | None = None, + tooltip: ChannelType | None = None, *args: Any, **kwargs: Any, ) -> alt.Chart: @@ -89,11 +153,11 @@ def line( def point( self, - x: str | Any | None = None, - y: str | Any | None = None, - color: str | Any | None = None, - size: str | Any | None = None, - tooltip: str | Any | None = None, + x: ChannelType | None = None, + y: ChannelType | None = None, + color: ChannelType | None = None, + size: ChannelType | None = None, + tooltip: ChannelType | None = None, *args: Any, **kwargs: Any, ) -> alt.Chart: @@ -155,7 +219,9 @@ def point( .interactive() ) - def __getattr__(self, attr: str, *args: Any, **kwargs: Any) -> alt.Chart: + def __getattr__( + self, attr: str, *args: Any, **kwargs: Any + ) -> Callable[..., alt.Chart]: method = self.chart.getattr(f"mark_{attr}", None) if method is None: msg = "Altair has no method 'mark_{attr}'" diff --git a/py-polars/tests/unit/operations/namespaces/test_plot.py b/py-polars/tests/unit/operations/namespaces/test_plot.py index 542508a6f914..f8f6710095e0 100644 --- a/py-polars/tests/unit/operations/namespaces/test_plot.py +++ b/py-polars/tests/unit/operations/namespaces/test_plot.py @@ -31,10 +31,5 @@ def test_dataframe_line() -> None: df.plot.line(x="date", y="price", color="stock") -def test_series_hist() -> None: - s = pl.Series("values", [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) - s.plot.hist() - - def test_empty_dataframe() -> None: - pl.DataFrame({"a": [], "b": []}).plot.scatter(x="a", y="b") + pl.DataFrame({"a": [], "b": []}).plot.point(x="a", y="b") From eaafc23ee9b0a08d2c3c6ee4e192343dc4d546c9 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:21:01 +0100 Subject: [PATCH 04/42] typing --- py-polars/polars/dataframe/plotting.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index de8dfe900d3f..3dd35f34ec79 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,12 +1,19 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeAlias, Union +from typing import TYPE_CHECKING, Any, Callable, Mapping, Union if TYPE_CHECKING: + import sys + import altair as alt from polars import DataFrame + if sys.version_info >= (3, 10): + from typing import TypeAlias + else: + from typing_extensions import TypeAlias + ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] From db6d8f76782aa02bb72b923cf46e2f638ede6bef Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:40:20 +0100 Subject: [PATCH 05/42] requirements --- py-polars/pyproject.toml | 4 ++-- py-polars/requirements-dev.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/py-polars/pyproject.toml b/py-polars/pyproject.toml index 913f96768b2d..4aeb6606cd0f 100644 --- a/py-polars/pyproject.toml +++ b/py-polars/pyproject.toml @@ -71,7 +71,7 @@ iceberg = ["pyiceberg >= 0.5.0"] async = ["gevent"] cloudpickle = ["cloudpickle"] graph = ["matplotlib"] -plot = ["hvplot >= 0.9.1", "polars[pandas]"] +plot = ["altair >= 5.3.0"] style = ["great-tables >= 0.8.0"] timezone = ["backports.zoneinfo; python_version < '3.9'", "tzdata; platform_system == 'Windows'"] @@ -103,6 +103,7 @@ module = [ "IPython.*", "adbc_driver_manager.*", "adbc_driver_sqlite.*", + "altair.*", "arrow_odbc", "backports", "connectorx", @@ -110,7 +111,6 @@ module = [ "fsspec.*", "gevent", "great_tables", - "hvplot.*", "jax.*", "kuzu", "matplotlib.*", diff --git a/py-polars/requirements-dev.txt b/py-polars/requirements-dev.txt index e9a964206fc0..f884918d5592 100644 --- a/py-polars/requirements-dev.txt +++ b/py-polars/requirements-dev.txt @@ -47,7 +47,7 @@ deltalake>=0.15.0 # Csv zstandard # Plotting -hvplot>=0.9.1 +altair>=5.3.0 # Styling great-tables>=0.8.0; python_version >= '3.9' # Async From 08e09d4ebca87d7aaaa7603a7999e127255d4007 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 15:07:44 +0100 Subject: [PATCH 06/42] add histogram example to docstring --- py-polars/polars/dataframe/frame.py | 11 +++- py-polars/polars/dataframe/plotting.py | 81 ++++++++++++++++---------- py-polars/polars/meta/versions.py | 2 +- py-polars/pyproject.toml | 2 +- 4 files changed, 60 insertions(+), 36 deletions(-) diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index b4e9eaae09ac..ddcda3a1fd8a 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -611,7 +611,7 @@ def plot(self) -> Plot: This functionality is currently considered **unstable**. It may be changed at any point without it being considered a breaking change. - .. versionchanged:: 1.4.0 + .. versionchanged:: 1.5.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace @@ -652,9 +652,14 @@ def plot(self) -> Plot: ... } ... ) >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP + + Histogram: + + >>> df = pl.DataFrame({"rating": [1, 3, 3, 3, 2, 3, 3, 2, 1, 4]}) + >>> df.plot.bar(x="rating", y="count()") # doctest: +SKIP """ - if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 3, 0): - msg = "altair>=5.3.0 is required for `.plot`" + if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 4, 0): + msg = "altair>=5.4.0 is required for `.plot`" raise ModuleUpgradeRequiredError(msg) return Plot(self) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 3dd35f34ec79..e15d4e179e09 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,11 +1,20 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Mapping, Union +from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeAlias, Union, Unpack if TYPE_CHECKING: import sys import altair as alt + from altair.typing import ( + ChannelColor, + ChannelOrder, + ChannelSize, + ChannelTooltip, + ChannelX, + ChannelY, + EncodeKwds, + ) from polars import DataFrame @@ -16,6 +25,16 @@ ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] + Encodings: TypeAlias = dict[ + str, + ChannelX + | ChannelY + | ChannelColor + | ChannelOrder + | ChannelSize + | ChannelTooltip, + ] + class Plot: """DataFrame.plot namespace.""" @@ -29,23 +48,23 @@ def __init__(self, df: DataFrame) -> None: def bar( self, - x: ChannelType | None = None, - y: ChannelType | None = None, - color: ChannelType | None = None, - tooltip: ChannelType | None = None, - *args: Any, - **kwargs: Any, + x: ChannelX | None = None, + y: ChannelY | None = None, + color: ChannelColor | None = None, + tooltip: ChannelTooltip | None = None, + /, + **kwargs: Unpack[EncodeKwds], ) -> alt.Chart: """ Draw bar plot. - Polars does not implement plottinng logic itself but instead defers to Altair. + Polars does not implement plotting logic itself but instead defers to Altair. `df.plot.bar(*args, **kwargs)` is shorthand for `alt.Chart(df).mark_bar().encode(*args, **kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. - .. versionchanged:: 1.4.0 + .. versionchanged:: 1.5.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace @@ -76,7 +95,7 @@ def bar( ... ) >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP """ - encodings = {} + encodings: Encodings = {} if x is not None: encodings["x"] = x if y is not None: @@ -86,29 +105,29 @@ def bar( if tooltip is not None: encodings["tooltip"] = tooltip return ( - self.chart.mark_bar().encode(*args, **{**encodings, **kwargs}).interactive() + self.chart.mark_bar().encode(**{**encodings, **kwargs}).interactive() # type: ignore[arg-type] ) def line( self, - x: ChannelType | None = None, - y: ChannelType | None = None, - color: ChannelType | None = None, - order: ChannelType | None = None, - tooltip: ChannelType | None = None, - *args: Any, - **kwargs: Any, + x: ChannelX | None = None, + y: ChannelY | None = None, + color: ChannelColor | None = None, + order: ChannelOrder | None = None, + tooltip: ChannelTooltip | None = None, + /, + **kwargs: Unpack[EncodeKwds], ) -> alt.Chart: """ Draw line plot. - Polars does not implement plottinng logic itself but instead defers to Altair. + Polars does not implement plotting logic itself but instead defers to Altair. `df.plot.line(*args, **kwargs)` is shorthand for `alt.Chart(df).mark_line().encode(*args, **kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. - .. versionchanged:: 1.4.0 + .. versionchanged:: 1.5.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace @@ -141,7 +160,7 @@ def line( ... ) >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP """ - encodings = {} + encodings: Encodings = {} if x is not None: encodings["x"] = x if y is not None: @@ -154,30 +173,30 @@ def line( encodings["tooltip"] = tooltip return ( self.chart.mark_line() - .encode(*args, **{**encodings, **kwargs}) + .encode(**{**encodings, **kwargs}) # type: ignore[arg-type] .interactive() ) def point( self, - x: ChannelType | None = None, - y: ChannelType | None = None, - color: ChannelType | None = None, - size: ChannelType | None = None, - tooltip: ChannelType | None = None, + x: ChannelX | None = None, + y: ChannelY | None = None, + color: ChannelColor | None = None, + size: ChannelSize | None = None, + tooltip: ChannelTooltip | None = None, *args: Any, **kwargs: Any, ) -> alt.Chart: """ Draw scatter plot. - Polars does not implement plottinng logic itself but instead defers to Altair. + Polars does not implement plotting logic itself but instead defers to Altair. `df.plot.point(*args, **kwargs)` is shorthand for `alt.Chart(df).mark_point().encode(*args, **kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. - .. versionchanged:: 1.4.0 + .. versionchanged:: 1.5.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace @@ -209,7 +228,7 @@ def point( ... ) >>> df.plot.point(x="length", y="width", color="species") # doctest: +SKIP """ - encodings = {} + encodings: Encodings = {} if x is not None: encodings["x"] = x if y is not None: @@ -227,7 +246,7 @@ def point( ) def __getattr__( - self, attr: str, *args: Any, **kwargs: Any + self, attr: str, *args: EncodeKwds, **kwargs: EncodeKwds ) -> Callable[..., alt.Chart]: method = self.chart.getattr(f"mark_{attr}", None) if method is None: diff --git a/py-polars/polars/meta/versions.py b/py-polars/polars/meta/versions.py index 610bf6fa1fe6..e5ba7fddf22f 100644 --- a/py-polars/polars/meta/versions.py +++ b/py-polars/polars/meta/versions.py @@ -20,7 +20,7 @@ def show_versions() -> None: Python: 3.11.8 (main, Feb 6 2024, 21:21:21) [Clang 15.0.0 (clang-1500.1.0.2.5)] ----Optional dependencies---- adbc_driver_manager: 0.11.0 - altair: 5.3.0 + altair: 5.4.0 cloudpickle: 3.0.0 connectorx: 0.3.2 deltalake: 0.17.1 diff --git a/py-polars/pyproject.toml b/py-polars/pyproject.toml index 4aeb6606cd0f..6d617fb767c3 100644 --- a/py-polars/pyproject.toml +++ b/py-polars/pyproject.toml @@ -71,7 +71,7 @@ iceberg = ["pyiceberg >= 0.5.0"] async = ["gevent"] cloudpickle = ["cloudpickle"] graph = ["matplotlib"] -plot = ["altair >= 5.3.0"] +plot = ["altair >= 5.4.0"] style = ["great-tables >= 0.8.0"] timezone = ["backports.zoneinfo; python_version < '3.9'", "tzdata; platform_system == 'Windows'"] From c6e7d6b49ea5477397417fec83c1925ead36adee Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:05:53 +0100 Subject: [PATCH 07/42] update user guide --- .../python/user-guide/misc/visualization.py | 110 +++++++++++------- docs/user-guide/misc/visualization.md | 55 ++++++--- 2 files changed, 107 insertions(+), 58 deletions(-) diff --git a/docs/src/python/user-guide/misc/visualization.py b/docs/src/python/user-guide/misc/visualization.py index f04288cb7812..d6ab6e8f1f66 100644 --- a/docs/src/python/user-guide/misc/visualization.py +++ b/docs/src/python/user-guide/misc/visualization.py @@ -3,30 +3,33 @@ path = "docs/data/iris.csv" -df = pl.scan_csv(path).group_by("species").agg(pl.col("petal_length").mean()).collect() +df = pl.read_csv(path) print(df) # --8<-- [end:dataframe] """ # --8<-- [start:hvplot_show_plot] -df.plot.bar( - x="species", - y="petal_length", +import hvplot.polars +df.hvplot.scatter( + x="sepal_width", + y="sepal_length", + by="species", width=650, ) # --8<-- [end:hvplot_show_plot] """ # --8<-- [start:hvplot_make_plot] -import hvplot +import hvplot.polars -plot = df.plot.bar( - x="species", - y="petal_length", +plot = df.hvplot.scatter( + x="sepal_width", + y="sepal_length", + by="species", width=650, ) -hvplot.save(plot, "docs/images/hvplot_bar.html") -with open("docs/images/hvplot_bar.html", "r") as f: +hvplot.save(plot, "docs/images/hvplot_scatter.html") +with open("docs/images/hvplot_scatter.html", "r") as f: chart_html = f.read() print(f"{chart_html}") # --8<-- [end:hvplot_make_plot] @@ -35,7 +38,12 @@ # --8<-- [start:matplotlib_show_plot] import matplotlib.pyplot as plt -plt.bar(x=df["species"], height=df["petal_length"]) +fig, ax = plt.subplots() +ax.scatter( + x=df["sepal_width"], + y=df["sepal_length"], + c=df["species"].cast(pl.Categorical).to_physical(), +) # --8<-- [end:matplotlib_show_plot] """ @@ -44,9 +52,14 @@ import matplotlib.pyplot as plt -plt.bar(x=df["species"], height=df["petal_length"]) -plt.savefig("docs/images/matplotlib_bar.png") -with open("docs/images/matplotlib_bar.png", "rb") as f: +fig, ax = plt.subplots() +ax.scatter( + x=df["sepal_width"], + y=df["sepal_length"], + c=df["species"].cast(pl.Categorical).to_physical(), +) +fig.savefig("docs/images/matplotlib_scatter.png") +with open("docs/images/matplotlib_scatter.png", "rb") as f: png = base64.b64encode(f.read()).decode() print(f'') # --8<-- [end:matplotlib_make_plot] @@ -54,24 +67,28 @@ """ # --8<-- [start:seaborn_show_plot] import seaborn as sns -sns.barplot( +sns.scatterplot( df, - x="species", - y="petal_length", + x="sepal_width", + y="sepal_length", + hue="species", ) # --8<-- [end:seaborn_show_plot] """ # --8<-- [start:seaborn_make_plot] import seaborn as sns +import matplotlib.pyplot as plt -sns.barplot( +fig, ax = plt.subplots() +ax = sns.scatterplot( df, - x="species", - y="petal_length", + x="sepal_width", + y="sepal_length", + hue="species", ) -plt.savefig("docs/images/seaborn_bar.png") -with open("docs/images/seaborn_bar.png", "rb") as f: +fig.savefig("docs/images/seaborn_scatter.png") +with open("docs/images/seaborn_scatter.png", "rb") as f: png = base64.b64encode(f.read()).decode() print(f'') # --8<-- [end:seaborn_make_plot] @@ -80,11 +97,12 @@ # --8<-- [start:plotly_show_plot] import plotly.express as px -px.bar( +px.scatter( df, - x="species", - y="petal_length", - width=400, + x="sepal_width", + y="sepal_length", + color="species", + width=650, ) # --8<-- [end:plotly_show_plot] """ @@ -92,39 +110,45 @@ # --8<-- [start:plotly_make_plot] import plotly.express as px -fig = px.bar( +fig = px.scatter( df, - x="species", - y="petal_length", + x="sepal_width", + y="sepal_length", + color="species", width=650, ) -fig.write_html("docs/images/plotly_bar.html", full_html=False, include_plotlyjs="cdn") -with open("docs/images/plotly_bar.html", "r") as f: +fig.write_html("docs/images/plotly_scatter.html", full_html=False, include_plotlyjs="cdn") +with open("docs/images/plotly_scatter.html", "r") as f: chart_html = f.read() print(f"{chart_html}") # --8<-- [end:plotly_make_plot] """ # --8<-- [start:altair_show_plot] -import altair as alt - -alt.Chart(df, width=700).mark_bar().encode(x="species:N", y="petal_length:Q") +( + df.plot.point( + x="sepal_length", + y="sepal_width", + color="species", + ) + .properties(width=500) + .configure_scale(zero=False) +) # --8<-- [end:altair_show_plot] """ # --8<-- [start:altair_make_plot] -import altair as alt - chart = ( - alt.Chart(df, width=600) - .mark_bar() - .encode( - x="species:N", - y="petal_length:Q", + df.plot.point( + x="sepal_length", + y="sepal_width", + color="species", ) + .properties(width=500) + .configure_scale(zero=False) ) -chart.save("docs/images/altair_bar.html") -with open("docs/images/altair_bar.html", "r") as f: +chart.save("docs/images/altair_scatter.html") +with open("docs/images/altair_scatter.html", "r") as f: chart_html = f.read() print(f"{chart_html}") # --8<-- [end:altair_make_plot] diff --git a/docs/user-guide/misc/visualization.md b/docs/user-guide/misc/visualization.md index 88dcd83a18a6..df7ff8a4e104 100644 --- a/docs/user-guide/misc/visualization.md +++ b/docs/user-guide/misc/visualization.md @@ -2,7 +2,8 @@ Data in a Polars `DataFrame` can be visualized using common visualization libraries. -We illustrate plotting capabilities using the Iris dataset. We scan a CSV and then do a group-by on the `species` column and get the mean of the `petal_length`. +We illustrate plotting capabilities using the Iris dataset. We read a CSV and then +plot one column against another, colored by a yet another column. {{code_block('user-guide/misc/visualization','dataframe',[])}} @@ -10,9 +11,39 @@ We illustrate plotting capabilities using the Iris dataset. We scan a CSV and th --8<-- "python/user-guide/misc/visualization.py:dataframe" ``` -## Built-in plotting with hvPlot +### Built-in plotting with Altair -Polars has a `plot` method to create interactive plots using [hvPlot](https://hvplot.holoviz.org/). +Polars has a `plot` method to create plots using [Altair](https://altair-viz.github.io/): + +{{code_block('user-guide/misc/visualization','altair_show_plot',[])}} + +```python exec="on" session="user-guide/misc/visualization" +--8<-- "python/user-guide/misc/visualization.py:altair_make_plot" +``` + +This is shorthand for: + +```python +import altair as alt + +( + alt.Chart(df).mark_point().encode( + x="sepal_length", + y="sepal_width", + color="species", + ) + .properties(width=500) + .configure_scale(zero=False) +) +``` + +and is only provided for convenience, and to signal that Altair is known to work well with +Polars. + +## hvPlot + +If you import `hvplot.polars`, then it registers a `hvplot` +method which you can use to create interactive plots using [hvPlot](https://hvplot.holoviz.org/). {{code_block('user-guide/misc/visualization','hvplot_show_plot',[])}} @@ -22,8 +53,9 @@ Polars has a `plot` method to create interactive plots using [hvPlot](https://hv ## Matplotlib -To create a bar chart we can pass columns of a `DataFrame` directly to Matplotlib as a `Series` for each column. Matplotlib does not have explicit support for Polars objects but Matplotlib can accept a Polars `Series` because it can convert each Series to a numpy array, which is zero-copy for numeric -data without null values. +To create a scatter plot we can pass columns of a `DataFrame` directly to Matplotlib as a `Series` for each column. +Matplotlib does not have explicit support for Polars objects but can accept a Polars `Series` by +converting it to a NumPy array (which is zero-copy for numeric data without null values). {{code_block('user-guide/misc/visualization','matplotlib_show_plot',[])}} @@ -31,9 +63,10 @@ data without null values. --8<-- "python/user-guide/misc/visualization.py:matplotlib_make_plot" ``` -## Seaborn, Plotly & Altair +## Seaborn and Plotly -[Seaborn](https://seaborn.pydata.org/), [Plotly](https://plotly.com/) & [Altair](https://altair-viz.github.io/) can accept a Polars `DataFrame` by leveraging the [dataframe interchange protocol](https://data-apis.org/dataframe-api/), which offers zero-copy conversion where possible. +[Seaborn](https://seaborn.pydata.org/) and [Plotly](https://plotly.com/) can accept a Polars `DataFrame` by leveraging the [dataframe interchange protocol](https://data-apis.org/dataframe-api/), which offers zero-copy conversion where possible. Note +that the protocol does not support all Polars data types (e.g. `List`) so your mileage may vary here. ### Seaborn @@ -50,11 +83,3 @@ data without null values. ```python exec="on" session="user-guide/misc/visualization" --8<-- "python/user-guide/misc/visualization.py:plotly_make_plot" ``` - -### Altair - -{{code_block('user-guide/misc/visualization','altair_show_plot',[])}} - -```python exec="on" session="user-guide/misc/visualization" ---8<-- "python/user-guide/misc/visualization.py:altair_make_plot" -``` From 9a92211406235cc63fb985a58c30d700d0189ad9 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:08:09 +0100 Subject: [PATCH 08/42] formatting --- docs/src/python/user-guide/misc/visualization.py | 4 +++- docs/user-guide/misc/visualization.md | 2 +- py-polars/polars/dataframe/plotting.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/src/python/user-guide/misc/visualization.py b/docs/src/python/user-guide/misc/visualization.py index d6ab6e8f1f66..cd256127f1dd 100644 --- a/docs/src/python/user-guide/misc/visualization.py +++ b/docs/src/python/user-guide/misc/visualization.py @@ -117,7 +117,9 @@ color="species", width=650, ) -fig.write_html("docs/images/plotly_scatter.html", full_html=False, include_plotlyjs="cdn") +fig.write_html( + "docs/images/plotly_scatter.html", full_html=False, include_plotlyjs="cdn" +) with open("docs/images/plotly_scatter.html", "r") as f: chart_html = f.read() print(f"{chart_html}") diff --git a/docs/user-guide/misc/visualization.md b/docs/user-guide/misc/visualization.md index df7ff8a4e104..146bba2406ba 100644 --- a/docs/user-guide/misc/visualization.md +++ b/docs/user-guide/misc/visualization.md @@ -11,7 +11,7 @@ plot one column against another, colored by a yet another column. --8<-- "python/user-guide/misc/visualization.py:dataframe" ``` -### Built-in plotting with Altair +## Built-in plotting with Altair Polars has a `plot` method to create plots using [Altair](https://altair-viz.github.io/): diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index e15d4e179e09..024809c4899c 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Mapping, TypeAlias, Union, Unpack +from typing import TYPE_CHECKING, Any, Callable, Mapping, Union, Unpack if TYPE_CHECKING: import sys From c59780e8170ff617a20ab2a981d4c55ecf6f9860 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:15:21 +0100 Subject: [PATCH 09/42] cross-version compat --- py-polars/polars/dataframe/plotting.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 024809c4899c..042bd80f8caa 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Mapping, Union, Unpack +from typing import TYPE_CHECKING, Any, Callable, Mapping, Union if TYPE_CHECKING: import sys @@ -22,6 +22,10 @@ from typing import TypeAlias else: from typing_extensions import TypeAlias + if sys.version_info >= (3, 11): + from typing import Unpack + else: + from typing_extensions import Unpack ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] From 541361b8bf250085fb696288756ff741ef01370e Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:21:35 +0100 Subject: [PATCH 10/42] py38 typing compat --- py-polars/polars/dataframe/plotting.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 042bd80f8caa..7bbc6b8f65de 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -31,12 +31,9 @@ Encodings: TypeAlias = dict[ str, - ChannelX - | ChannelY - | ChannelColor - | ChannelOrder - | ChannelSize - | ChannelTooltip, + Union[ + ChannelX, ChannelY, ChannelColor, ChannelOrder, ChannelSize, ChannelTooltip + ], ] From 7f511189275afb033e46d8e2660e56f5a01d1612 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:24:21 +0100 Subject: [PATCH 11/42] py38 typing compat --- py-polars/polars/dataframe/plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 7bbc6b8f65de..fd293df6dd01 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Mapping, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Union if TYPE_CHECKING: import sys @@ -29,7 +29,7 @@ ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] - Encodings: TypeAlias = dict[ + Encodings: TypeAlias = Dict[ str, Union[ ChannelX, ChannelY, ChannelColor, ChannelOrder, ChannelSize, ChannelTooltip From bb6116cfc1879dbc1dd39593011ba0b357e9d3a5 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:40:50 +0100 Subject: [PATCH 12/42] fix minimum version --- py-polars/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py-polars/requirements-dev.txt b/py-polars/requirements-dev.txt index 7e08a7f8bff8..af96cf2bbe02 100644 --- a/py-polars/requirements-dev.txt +++ b/py-polars/requirements-dev.txt @@ -47,7 +47,7 @@ deltalake>=0.15.0 # Csv zstandard # Plotting -altair>=5.3.0 +altair>=5.4.0 # Styling great-tables>=0.8.0; python_version >= '3.9' # Async From f4b42b1583c5c62475d063262faed6221d42f3a8 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 16:50:12 +0100 Subject: [PATCH 13/42] try setting typing extensions minimum --- py-polars/requirements-dev.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py-polars/requirements-dev.txt b/py-polars/requirements-dev.txt index af96cf2bbe02..bebf8d038d1f 100644 --- a/py-polars/requirements-dev.txt +++ b/py-polars/requirements-dev.txt @@ -74,3 +74,6 @@ flask-cors # Stub files pandas-stubs boto3-stubs + +# Typing +typing-extensions>=4.10.0 From 91a19f82f57c61a28cbbae81d8aa38131358ca79 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:46:08 +0100 Subject: [PATCH 14/42] regular pip install to debug :sunglasses: --- .github/workflows/test-python.yml | 2 +- py-polars/requirements-dev.txt | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index ca717ef191f2..d41317f8cf72 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,7 +66,7 @@ jobs: - name: Install Python dependencies run: | pip install uv - uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + pip install -r requirements-dev.txt -r requirements-ci.txt - name: Set up Rust run: rustup show diff --git a/py-polars/requirements-dev.txt b/py-polars/requirements-dev.txt index bebf8d038d1f..af96cf2bbe02 100644 --- a/py-polars/requirements-dev.txt +++ b/py-polars/requirements-dev.txt @@ -74,6 +74,3 @@ flask-cors # Stub files pandas-stubs boto3-stubs - -# Typing -typing-extensions>=4.10.0 From 5c619821fbecaf1da9bbaeb0fc2ee3c0645fc4bb Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:53:38 +0100 Subject: [PATCH 15/42] that worked...what if we put uv back but without compile-bytecode? --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-coverage.yml | 2 +- .github/workflows/test-python.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 335d0a32b754..071cd6589efd 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -48,7 +48,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + run: uv pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index ce642bbec306..03d27c779d2e 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -103,7 +103,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + run: uv pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index d41317f8cf72..262872dda185 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,7 +66,7 @@ jobs: - name: Install Python dependencies run: | pip install uv - pip install -r requirements-dev.txt -r requirements-ci.txt + uv pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show From 8a11760a97bcc7c30fd6bab52d48f1df5fb09264 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:56:28 +0100 Subject: [PATCH 16/42] maybe not --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-coverage.yml | 2 +- .github/workflows/test-python.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 071cd6589efd..a74dcba05274 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -48,7 +48,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 03d27c779d2e..03dbc9cfbb8f 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -103,7 +103,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 262872dda185..dc04f71f7adc 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,7 +66,7 @@ jobs: - name: Install Python dependencies run: | pip install uv - uv pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show From f27326d81ec1b480411477607f41608c57f5712d Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:33:18 +0100 Subject: [PATCH 17/42] try putting torch and extra-index-url on the same line --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-coverage.yml | 2 +- .github/workflows/test-python.yml | 2 +- py-polars/requirements-ci.txt | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a74dcba05274..335d0a32b754 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -48,7 +48,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 03dbc9cfbb8f..ce642bbec306 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -103,7 +103,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index dc04f71f7adc..ca717ef191f2 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,7 +66,7 @@ jobs: - name: Install Python dependencies run: | pip install uv - pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/py-polars/requirements-ci.txt b/py-polars/requirements-ci.txt index 9bf4d2a58e7e..050e4e259084 100644 --- a/py-polars/requirements-ci.txt +++ b/py-polars/requirements-ci.txt @@ -2,7 +2,6 @@ # Packages that we require for unit tests that run on CI # (installable via `make requirements-all`) # ------------------------------------------------------- ---extra-index-url https://download.pytorch.org/whl/cpu -torch +--extra-index-url https://download.pytorch.org/whl/cpu torch jax[cpu] pyiceberg>=0.5.0 From f294a1e969ed664a003c198b6d3b212d3f23e649 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 09:36:38 +0100 Subject: [PATCH 18/42] inline torch install --- .github/workflows/benchmark.yml | 2 +- .github/workflows/test-coverage.yml | 2 +- .github/workflows/test-python.yml | 2 +- py-polars/requirements-ci.txt | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 335d0a32b754..e7095e35820f 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -48,7 +48,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + run: uv pip install --compile-bytecode -r requirements-dev.txt --extra-index-url https://download.pytorch.org/whl/cpu torch -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index ce642bbec306..c2c2d26bfc41 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -103,7 +103,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + run: uv pip install --compile-bytecode -r requirements-dev.txt --extra-index-url https://download.pytorch.org/whl/cpu torch -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index ca717ef191f2..fd4a31774342 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,7 +66,7 @@ jobs: - name: Install Python dependencies run: | pip install uv - uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + uv pip install --compile-bytecode -r requirements-dev.txt --extra-index-url https://download.pytorch.org/whl/cpu torch -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/py-polars/requirements-ci.txt b/py-polars/requirements-ci.txt index 050e4e259084..fc6fc8d8b511 100644 --- a/py-polars/requirements-ci.txt +++ b/py-polars/requirements-ci.txt @@ -2,6 +2,5 @@ # Packages that we require for unit tests that run on CI # (installable via `make requirements-all`) # ------------------------------------------------------- ---extra-index-url https://download.pytorch.org/whl/cpu torch jax[cpu] pyiceberg>=0.5.0 From 2a1cba0fa313b077ef5b67c3eba7f9d4957afef2 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:40:24 +0100 Subject: [PATCH 19/42] UV_INDEX_STRATEGY --- .github/workflows/benchmark.yml | 3 ++- .github/workflows/test-coverage.yml | 3 ++- .github/workflows/test-python.yml | 3 ++- .github/workflows/test-rust.yml | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index e7095e35820f..d63e6b68e388 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -24,6 +24,7 @@ env: SCCACHE_GHA_ENABLED: 'true' RUSTC_WRAPPER: sccache RUST_BACKTRACE: 1 + UV_INDEX_STRATEGY: unsafe-best-match jobs: main: @@ -48,7 +49,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt --extra-index-url https://download.pytorch.org/whl/cpu torch -r requirements-ci.txt --verbose + run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index c2c2d26bfc41..120c67a935e9 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -29,6 +29,7 @@ env: CARGO_LLVM_COV: 1 CARGO_LLVM_COV_SHOW_ENV: 1 CARGO_LLVM_COV_TARGET_DIR: ${{ github.workspace }}/target + UV_INDEX_STRATEGY: unsafe-best-match jobs: coverage-rust: @@ -103,7 +104,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt --extra-index-url https://download.pytorch.org/whl/cpu torch -r requirements-ci.txt --verbose + run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index fd4a31774342..2826832068d5 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,6 +26,7 @@ env: RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down RUST_BACKTRACE: 1 PYTHONUTF8: 1 + UV_INDEX_STRATEGY: unsafe-best-match defaults: run: @@ -66,7 +67,7 @@ jobs: - name: Install Python dependencies run: | pip install uv - uv pip install --compile-bytecode -r requirements-dev.txt --extra-index-url https://download.pytorch.org/whl/cpu torch -r requirements-ci.txt --verbose + uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-rust.yml b/.github/workflows/test-rust.yml index 4e54ca0cf8e9..5111b77dea1b 100644 --- a/.github/workflows/test-rust.yml +++ b/.github/workflows/test-rust.yml @@ -23,6 +23,7 @@ concurrency: env: RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down RUST_BACKTRACE: 1 + UV_INDEX_STRATEGY: unsafe-best-match jobs: test: From b029aed255446bf51f22188586453728c788aae1 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:50:11 +0100 Subject: [PATCH 20/42] revert requirements-ci.txt change --- py-polars/requirements-ci.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/py-polars/requirements-ci.txt b/py-polars/requirements-ci.txt index fc6fc8d8b511..9bf4d2a58e7e 100644 --- a/py-polars/requirements-ci.txt +++ b/py-polars/requirements-ci.txt @@ -2,5 +2,7 @@ # Packages that we require for unit tests that run on CI # (installable via `make requirements-all`) # ------------------------------------------------------- +--extra-index-url https://download.pytorch.org/whl/cpu +torch jax[cpu] pyiceberg>=0.5.0 From e19a0b46a3427a4bdc60c65f71cf1b22874af1e4 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 13:59:20 +0100 Subject: [PATCH 21/42] need both altair and hvplot in user guide docs --- docs/requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index db32e0f1cd39..258288d07e16 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,10 +1,11 @@ +altair pandas pyarrow graphviz +hvplot matplotlib seaborn plotly -altair numba numpy From db6b59f11949101aff56caacfc4cf5ed0cd2722f Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:27:48 +0100 Subject: [PATCH 22/42] another strategy --- .github/workflows/benchmark.yml | 6 ++++-- .github/workflows/test-coverage.yml | 5 +++-- .github/workflows/test-python.yml | 4 ++-- .github/workflows/test-rust.yml | 1 - Makefile | 2 +- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index d63e6b68e388..47a5caf936a6 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -24,7 +24,6 @@ env: SCCACHE_GHA_ENABLED: 'true' RUSTC_WRAPPER: sccache RUST_BACKTRACE: 1 - UV_INDEX_STRATEGY: unsafe-best-match jobs: main: @@ -49,7 +48,10 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + run: | + uv pip install --compile-bytecode -r requirements-dev.txt + uv pip install --compile-bytecode -r requirements-ci.txt --verbose --index-strategy unsafe-best-match + - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 120c67a935e9..24bec88416e2 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -29,7 +29,6 @@ env: CARGO_LLVM_COV: 1 CARGO_LLVM_COV_SHOW_ENV: 1 CARGO_LLVM_COV_TARGET_DIR: ${{ github.workspace }}/target - UV_INDEX_STRATEGY: unsafe-best-match jobs: coverage-rust: @@ -104,7 +103,9 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + run: | + uv pip install --compile-bytecode -r requirements-dev.txt + uv pip install --compile-bytecode -r requirements-ci.txt --verbose --index-strategy unsafe-best-match - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 2826832068d5..59a433018eb3 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -26,7 +26,6 @@ env: RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down RUST_BACKTRACE: 1 PYTHONUTF8: 1 - UV_INDEX_STRATEGY: unsafe-best-match defaults: run: @@ -67,7 +66,8 @@ jobs: - name: Install Python dependencies run: | pip install uv - uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose + uv pip install --compile-bytecode -r requirements-dev.txt + uv pip install --compile-bytecode -r requirements-ci.txt --verbose --index-strategy unsafe-best-match - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-rust.yml b/.github/workflows/test-rust.yml index 5111b77dea1b..4e54ca0cf8e9 100644 --- a/.github/workflows/test-rust.yml +++ b/.github/workflows/test-rust.yml @@ -23,7 +23,6 @@ concurrency: env: RUSTFLAGS: -C debuginfo=0 # Do not produce debug symbols to keep memory usage down RUST_BACKTRACE: 1 - UV_INDEX_STRATEGY: unsafe-best-match jobs: test: diff --git a/Makefile b/Makefile index 524e04eddc2c..5e0789efcf33 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ requirements: .venv ## Install/refresh Python project requirements .PHONY: requirements-all requirements-all: .venv ## Install/refresh all Python requirements (including those needed for CI tests) $(MAKE) requirements - $(VENV_BIN)/uv pip install --upgrade --compile-bytecode -r py-polars/requirements-ci.txt + $(VENV_BIN)/uv pip install --upgrade --compile-bytecode -r py-polars/requirements-ci.txt --index-strategy unsafe-best-match .PHONY: build build: .venv ## Compile and install Python Polars for development From e22550b3f1c002494c478fc208ca64d9ca93b994 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:42:00 +0100 Subject: [PATCH 23/42] maybe a bit of separation was all we needed --- .github/workflows/benchmark.yml | 4 ++-- .github/workflows/test-coverage.yml | 4 ++-- .github/workflows/test-python.yml | 4 ++-- Makefile | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 47a5caf936a6..f00f966bee41 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -49,8 +49,8 @@ jobs: - name: Install Python dependencies working-directory: py-polars run: | - uv pip install --compile-bytecode -r requirements-dev.txt - uv pip install --compile-bytecode -r requirements-ci.txt --verbose --index-strategy unsafe-best-match + uv pip install --compile-bytecode -r requirements-dev.txt --verbose + uv pip install --compile-bytecode -r requirements-ci.txt --verbose - name: Set up Rust diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 24bec88416e2..b6d8ee3e2ee8 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -104,8 +104,8 @@ jobs: - name: Install Python dependencies working-directory: py-polars run: | - uv pip install --compile-bytecode -r requirements-dev.txt - uv pip install --compile-bytecode -r requirements-ci.txt --verbose --index-strategy unsafe-best-match + uv pip install --compile-bytecode -r requirements-dev.txt --verbose + uv pip install --compile-bytecode -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 59a433018eb3..80b818b61da2 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,8 +66,8 @@ jobs: - name: Install Python dependencies run: | pip install uv - uv pip install --compile-bytecode -r requirements-dev.txt - uv pip install --compile-bytecode -r requirements-ci.txt --verbose --index-strategy unsafe-best-match + uv pip install --compile-bytecode -r requirements-dev.txt --verbose + uv pip install --compile-bytecode -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/Makefile b/Makefile index 5e0789efcf33..524e04eddc2c 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ requirements: .venv ## Install/refresh Python project requirements .PHONY: requirements-all requirements-all: .venv ## Install/refresh all Python requirements (including those needed for CI tests) $(MAKE) requirements - $(VENV_BIN)/uv pip install --upgrade --compile-bytecode -r py-polars/requirements-ci.txt --index-strategy unsafe-best-match + $(VENV_BIN)/uv pip install --upgrade --compile-bytecode -r py-polars/requirements-ci.txt .PHONY: build build: .venv ## Compile and install Python Polars for development From 6ff8e99a8d6553e5627914b68b355abcea5daf68 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 18:16:36 +0100 Subject: [PATCH 24/42] what if we install cython --- py-polars/requirements-ci.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/py-polars/requirements-ci.txt b/py-polars/requirements-ci.txt index 9bf4d2a58e7e..9025182c0ac6 100644 --- a/py-polars/requirements-ci.txt +++ b/py-polars/requirements-ci.txt @@ -3,6 +3,7 @@ # (installable via `make requirements-all`) # ------------------------------------------------------- --extra-index-url https://download.pytorch.org/whl/cpu +Cython torch jax[cpu] pyiceberg>=0.5.0 From a9861a027fefeeb19566487bdd0260d25a3c1889 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:13:02 +0100 Subject: [PATCH 25/42] only use extra index url on linux? --- .github/workflows/test-python.yml | 5 ++++- py-polars/requirements-ci.txt | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 80b818b61da2..6980c65e5b16 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -67,7 +67,10 @@ jobs: run: | pip install uv uv pip install --compile-bytecode -r requirements-dev.txt --verbose - uv pip install --compile-bytecode -r requirements-ci.txt --verbose + if [ "$(uname)" = "Linux" ]; then + uv pip install --compile-bytecode --extra-index-url https://download.pytorch.org/whl/cpu -r requirements-ci.txt --verbose + else + uv pip install --compile-bytecode -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/py-polars/requirements-ci.txt b/py-polars/requirements-ci.txt index 9025182c0ac6..57fd2ea333d4 100644 --- a/py-polars/requirements-ci.txt +++ b/py-polars/requirements-ci.txt @@ -2,8 +2,6 @@ # Packages that we require for unit tests that run on CI # (installable via `make requirements-all`) # ------------------------------------------------------- ---extra-index-url https://download.pytorch.org/whl/cpu -Cython torch jax[cpu] pyiceberg>=0.5.0 From 8d329e4d63192e22dac5abdeeb1c18cb79b112a0 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 19:22:50 +0100 Subject: [PATCH 26/42] include fi --- .github/workflows/test-python.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 6980c65e5b16..ccbf064e9603 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -71,6 +71,7 @@ jobs: uv pip install --compile-bytecode --extra-index-url https://download.pytorch.org/whl/cpu -r requirements-ci.txt --verbose else uv pip install --compile-bytecode -r requirements-ci.txt --verbose + fi - name: Set up Rust run: rustup show From c7a31f0d16c8a47db9e7618baa395f947ede9aaf Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 21:19:45 +0100 Subject: [PATCH 27/42] regular old-fashioned pip install --- .github/workflows/benchmark.yml | 5 +---- .github/workflows/test-coverage.yml | 4 +--- .github/workflows/test-python.yml | 7 +------ 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index f00f966bee41..a74dcba05274 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -48,10 +48,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: | - uv pip install --compile-bytecode -r requirements-dev.txt --verbose - uv pip install --compile-bytecode -r requirements-ci.txt --verbose - + run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index b6d8ee3e2ee8..03dbc9cfbb8f 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -103,9 +103,7 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: | - uv pip install --compile-bytecode -r requirements-dev.txt --verbose - uv pip install --compile-bytecode -r requirements-ci.txt --verbose + run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index ccbf064e9603..dc04f71f7adc 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,12 +66,7 @@ jobs: - name: Install Python dependencies run: | pip install uv - uv pip install --compile-bytecode -r requirements-dev.txt --verbose - if [ "$(uname)" = "Linux" ]; then - uv pip install --compile-bytecode --extra-index-url https://download.pytorch.org/whl/cpu -r requirements-ci.txt --verbose - else - uv pip install --compile-bytecode -r requirements-ci.txt --verbose - fi + pip install -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show From f3186b512b3a3c7affa044a415525ca7c6825cfa Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 12 Aug 2024 21:20:40 +0100 Subject: [PATCH 28/42] revert requirements-ci.txt change --- py-polars/requirements-ci.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/py-polars/requirements-ci.txt b/py-polars/requirements-ci.txt index 57fd2ea333d4..9bf4d2a58e7e 100644 --- a/py-polars/requirements-ci.txt +++ b/py-polars/requirements-ci.txt @@ -2,6 +2,7 @@ # Packages that we require for unit tests that run on CI # (installable via `make requirements-all`) # ------------------------------------------------------- +--extra-index-url https://download.pytorch.org/whl/cpu torch jax[cpu] pyiceberg>=0.5.0 From 0f5e8039fef6eb1248ebc319dba930608b71eaaa Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Tue, 13 Aug 2024 22:57:48 +0100 Subject: [PATCH 29/42] install typing-extensions _before_ the other requirements --- .github/workflows/benchmark.yml | 4 +++- .github/workflows/test-coverage.yml | 4 +++- .github/workflows/test-python.yml | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index a74dcba05274..23bbf4364578 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -48,7 +48,9 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + run: | + uv pip install -U typing-extensions + uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index 03dbc9cfbb8f..bde306e87d44 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -103,7 +103,9 @@ jobs: - name: Install Python dependencies working-directory: py-polars - run: pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + run: | + uv pip install -U typing-extensions + uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup component add llvm-tools-preview diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index dc04f71f7adc..346eff3b959d 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,7 +66,8 @@ jobs: - name: Install Python dependencies run: | pip install uv - pip install -r requirements-dev.txt -r requirements-ci.txt --verbose + uv pip install -U typing-extensions + uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose - name: Set up Rust run: rustup show From df98a2ea8a378bd1f8e34b7d46a9bb68205fa835 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:01:32 +0100 Subject: [PATCH 30/42] minor updates --- docs/user-guide/misc/visualization.md | 3 +++ py-polars/polars/dataframe/plotting.py | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/docs/user-guide/misc/visualization.md b/docs/user-guide/misc/visualization.md index 146bba2406ba..3f7574c07a2e 100644 --- a/docs/user-guide/misc/visualization.md +++ b/docs/user-guide/misc/visualization.md @@ -57,6 +57,9 @@ To create a scatter plot we can pass columns of a `DataFrame` directly to Matplo Matplotlib does not have explicit support for Polars objects but can accept a Polars `Series` by converting it to a NumPy array (which is zero-copy for numeric data without null values). +Note that because the column `'species'` isn't numeric, we need to first convert it to numeric values so that +it can be passed as an argument to `c`. + {{code_block('user-guide/misc/visualization','matplotlib_show_plot',[])}} ```python exec="on" session="user-guide/misc/visualization" diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index fd293df6dd01..5af80884c491 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -80,7 +80,7 @@ def bar( color Column to color bars by. tooltip - Columns to show values of when hovering over points with pointer. + Columns to show values of when hovering over bars with pointer. *args, **kwargs Additional arguments and keyword arguments passed to Altair. @@ -94,7 +94,7 @@ def bar( ... "stock": ["a", "a", "a", "b", "b", "b"], ... } ... ) - >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP + >>> df.plot.bar(x="price", y="count()") # doctest: +SKIP """ encodings: Encodings = {} if x is not None: @@ -145,7 +145,7 @@ def line( order Column to use for order of data points in lines. tooltip - Columns to show values of when hovering over points with pointer. + Columns to show values of when hovering over lines with pointer. *args, **kwargs Additional arguments and keyword arguments passed to Altair. From 8d786e116c5e0b202407b62e349ffd8f03f8327b Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 14 Aug 2024 09:07:41 +0100 Subject: [PATCH 31/42] extra comment --- .github/workflows/benchmark.yml | 3 +++ .github/workflows/test-coverage.yml | 3 +++ .github/workflows/test-python.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml index 23bbf4364578..ce06b799f909 100644 --- a/.github/workflows/benchmark.yml +++ b/.github/workflows/benchmark.yml @@ -49,6 +49,9 @@ jobs: - name: Install Python dependencies working-directory: py-polars run: | + # Install typing-extensions separately whilst the `--extra-index-url` in `requirements-ci.txt` + # doesn't have an up-to-date typing-extensions, see + # https://github.com/astral-sh/uv/issues/6028#issuecomment-2287232150 uv pip install -U typing-extensions uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose diff --git a/.github/workflows/test-coverage.yml b/.github/workflows/test-coverage.yml index bde306e87d44..c774bdf864c9 100644 --- a/.github/workflows/test-coverage.yml +++ b/.github/workflows/test-coverage.yml @@ -104,6 +104,9 @@ jobs: - name: Install Python dependencies working-directory: py-polars run: | + # Install typing-extensions separately whilst the `--extra-index-url` in `requirements-ci.txt` + # doesn't have an up-to-date typing-extensions, see + # https://github.com/astral-sh/uv/issues/6028#issuecomment-2287232150 uv pip install -U typing-extensions uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose diff --git a/.github/workflows/test-python.yml b/.github/workflows/test-python.yml index 346eff3b959d..089ccb9f553a 100644 --- a/.github/workflows/test-python.yml +++ b/.github/workflows/test-python.yml @@ -66,6 +66,9 @@ jobs: - name: Install Python dependencies run: | pip install uv + # Install typing-extensions separately whilst the `--extra-index-url` in `requirements-ci.txt` + # doesn't have an up-to-date typing-extensions, see + # https://github.com/astral-sh/uv/issues/6028#issuecomment-2287232150 uv pip install -U typing-extensions uv pip install --compile-bytecode -r requirements-dev.txt -r requirements-ci.txt --verbose From 4491d83102fdc446f1fb948e76e02d61dd547900 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:11:10 +0100 Subject: [PATCH 32/42] remove unused type alias --- py-polars/polars/dataframe/plotting.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 5af80884c491..ff22e5033f82 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -27,8 +27,6 @@ else: from typing_extensions import Unpack - ChannelType: TypeAlias = Union[str, Mapping[str, Any], Any] - Encodings: TypeAlias = Dict[ str, Union[ From 9266adb9d9d696c0a549e2970a0dced802d50341 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 14 Aug 2024 10:14:55 +0100 Subject: [PATCH 33/42] lint --- py-polars/polars/dataframe/plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index ff22e5033f82..b60c7086d26f 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Union +from typing import TYPE_CHECKING, Any, Callable, Dict, Union if TYPE_CHECKING: import sys From 6f4d85a0f14ad760edad2d48a5dad9fcf23468b8 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Wed, 14 Aug 2024 19:09:52 +0100 Subject: [PATCH 34/42] :truck: 1.5.0 => 1.6.0 --- py-polars/polars/dataframe/frame.py | 2 +- py-polars/polars/dataframe/plotting.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index 059b9220f2ff..cbd9c65cc38c 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -611,7 +611,7 @@ def plot(self) -> Plot: This functionality is currently considered **unstable**. It may be changed at any point without it being considered a breaking change. - .. versionchanged:: 1.5.0 + .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index b60c7086d26f..1b86b3b74103 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -63,7 +63,7 @@ def bar( as is intended for convenience - for full customisatibility, use a plotting library directly. - .. versionchanged:: 1.5.0 + .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace @@ -126,7 +126,7 @@ def line( as is intended for convenience - for full customisatibility, use a plotting library directly. - .. versionchanged:: 1.5.0 + .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace @@ -195,7 +195,7 @@ def point( as is intended for convenience - for full customisatibility, use a plotting library directly. - .. versionchanged:: 1.5.0 + .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do add `import hvplot.polars` at the top of your script and replace From 4bc052fada0d4c1d5f2fef318d37ec72ab5f07bc Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sat, 17 Aug 2024 22:30:39 +0100 Subject: [PATCH 35/42] wip --- py-polars/polars/dataframe/frame.py | 5 --- py-polars/polars/dataframe/plotting.py | 2 +- py-polars/polars/series/series.py | 48 ++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index f6d128bd02f3..e00be8cc5333 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -652,11 +652,6 @@ def plot(self) -> Plot: ... } ... ) >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP - - Histogram: - - >>> df = pl.DataFrame({"rating": [1, 3, 3, 3, 2, 3, 3, 2, 1, 4]}) - >>> df.plot.bar(x="rating", y="count()") # doctest: +SKIP """ if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 4, 0): msg = "altair>=5.4.0 is required for `.plot`" diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 1b86b3b74103..b43ba8c320d8 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -247,7 +247,7 @@ def point( def __getattr__( self, attr: str, *args: EncodeKwds, **kwargs: EncodeKwds ) -> Callable[..., alt.Chart]: - method = self.chart.getattr(f"mark_{attr}", None) + method = self.chart.__getattr__(f"mark_{attr}", None) if method is None: msg = "Altair has no method 'mark_{attr}'" raise AttributeError(msg) diff --git a/py-polars/polars/series/series.py b/py-polars/polars/series/series.py index 96a5c6cec6c4..87ad673867e0 100644 --- a/py-polars/polars/series/series.py +++ b/py-polars/polars/series/series.py @@ -1,4 +1,5 @@ from __future__ import annotations +from polars.series.plotting import Plot import contextlib import math @@ -86,11 +87,13 @@ ) from polars.datatypes._utils import dtype_to_init_repr from polars.dependencies import ( + _ALTAIR_AVAILABLE, _PYARROW_AVAILABLE, _check_for_numpy, _check_for_pandas, _check_for_pyarrow, import_optional, + altair, ) from polars.dependencies import numpy as np from polars.dependencies import pandas as pd @@ -7354,6 +7357,51 @@ def struct(self) -> StructNameSpace: """Create an object namespace of all struct related methods.""" return StructNameSpace(self) + @property + @unstable() + def plot(self) -> Plot: + """ + Create a plot namespace. + + .. warning:: + This functionality is currently considered **unstable**. It may be + changed at any point without it being considered a breaking change. + + .. versionchanged:: 1.6.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Polars does not implement plotting logic itself, but instead defers to + Altair: + + - `s.plot.hist(*args, **kwargs)` + is shorthand for + `alt.Chart(s.to_frame()).mark_bar().encode(x=s.name, y='count()', *args, **kwargs).interactive()` + - `s.plot.kde(*args, **kwargs)` + is shorthand for + `alt.Chart(s.to_frame()).transform_density(s.name, as_=[s.name, 'density']).mark_area().encode(x=s.name, y='density', *args, **kwargs).interactive()` + + For anything else, please call `s.to_frame()` and then use one of the + methods in :meth:`DataFrame.plot`. + + Examples + -------- + Histogram: + + >>> s = pl.Series([1, 1, 2, 3]) + >>> s.plot.hist() # doctest: +SKIP + + KDE plot: + + >>> s.plot.kde() # doctest: +SKIP + """ + if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 4, 0): + msg = "altair>=5.4.0 is required for `.plot`" + raise ModuleUpgradeRequiredError(msg) + return Plot(self) + def _resolve_temporal_dtype( dtype: PolarsDataType | None, From e043a359bbae83b07272b50b80bc03dfeeae6ed8 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sat, 17 Aug 2024 23:42:24 +0100 Subject: [PATCH 36/42] add Series.plot --- py-polars/polars/series/plotting.py | 239 ++++++++++++++++++++++++++++ 1 file changed, 239 insertions(+) create mode 100644 py-polars/polars/series/plotting.py diff --git a/py-polars/polars/series/plotting.py b/py-polars/polars/series/plotting.py new file mode 100644 index 000000000000..25e84e338437 --- /dev/null +++ b/py-polars/polars/series/plotting.py @@ -0,0 +1,239 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Any, Dict, Union + +if TYPE_CHECKING: + import sys + + import altair as alt + from altair.typing import ( + ChannelColor, + ChannelOrder, + ChannelSize, + ChannelTooltip, + ChannelX, + ChannelY, + EncodeKwds, + ) + + + if sys.version_info >= (3, 10): + from typing import TypeAlias + else: + from typing_extensions import TypeAlias + if sys.version_info >= (3, 11): + from typing import Unpack + else: + from typing_extensions import Unpack + + Encodings: TypeAlias = Dict[ + str, + Union[ + ChannelX, ChannelY, ChannelColor, ChannelOrder, ChannelSize, ChannelTooltip + ], + ] + + +class Plot: + """DataFrame.plot namespace.""" + + chart: alt.Chart + + def __init__(self, s: Series) -> None: + import altair as alt + + self.chart = alt.Chart(s.to_frame()) + self._series_name = s.name + + def bar( + self, + x: ChannelX | None = None, + y: ChannelY | None = None, + color: ChannelColor | None = None, + tooltip: ChannelTooltip | None = None, + /, + **kwargs: Unpack[EncodeKwds], + ) -> alt.Chart: + """ + Draw bar plot. + + Polars does not implement plotting logic itself but instead defers to Altair. + `df.plot.bar(*args, **kwargs)` is shorthand for + `alt.Chart(df).mark_bar().encode(*args, **kwargs).interactive()`, + as is intended for convenience - for full customisatibility, use a plotting + library directly. + + .. versionchanged:: 1.6.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Parameters + ---------- + x + Column with x-coordinates of bars. + y + Column with y-coordinates of bars. + color + Column to color bars by. + tooltip + Columns to show values of when hovering over bars with pointer. + *args, **kwargs + Additional arguments and keyword arguments passed to Altair. + + Examples + -------- + >>> from datetime import date + >>> df = pl.DataFrame( + ... { + ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, + ... "price": [1, 4, 6, 1, 5, 2], + ... "stock": ["a", "a", "a", "b", "b", "b"], + ... } + ... ) + >>> df.plot.bar(x="price", y="count()") # doctest: +SKIP + """ + encodings: Encodings = {} + if x is not None: + encodings["x"] = x + if y is not None: + encodings["y"] = y + if color is not None: + encodings["color"] = color + if tooltip is not None: + encodings["tooltip"] = tooltip + return ( + self.chart.mark_bar().encode(**{**encodings, **kwargs}).interactive() # type: ignore[arg-type] + ) + + def kde( + self, + /, + **kwargs: Unpack[EncodeKwds], + ) -> alt.Chart: + """ + Draw line plot. + + Polars does not implement plotting logic itself but instead defers to Altair. + `df.plot.line(*args, **kwargs)` is shorthand for + `alt.Chart(df).mark_line().encode(*args, **kwargs).interactive()`, + as is intended for convenience - for full customisatibility, use a plotting + library directly. + + .. versionchanged:: 1.6.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Parameters + ---------- + x + Column with x-coordinates of lines. + y + Column with y-coordinates of lines. + color + Column to color lines by. + order + Column to use for order of data points in lines. + tooltip + Columns to show values of when hovering over lines with pointer. + *args, **kwargs + Additional arguments and keyword arguments passed to Altair. + + Examples + -------- + >>> from datetime import date + >>> df = pl.DataFrame( + ... { + ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, + ... "price": [1, 4, 6, 1, 5, 2], + ... "stock": ["a", "a", "a", "b", "b", "b"], + ... } + ... ) + >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP + """ + return ( + self.chart.transform_density(self._series_name, as_=[f'{self._series_name}', 'density']).mark_area() + .encode(x=f'{self._series_name}', y='density:Q', **kwargs) # type: ignore[arg-type] + .interactive() + ) + alt.Chart(df).transform_density('price', as_=['price', 'density']).mark_area().encode(x='price', y='density:Q') + + def point( + self, + x: ChannelX | None = None, + y: ChannelY | None = None, + color: ChannelColor | None = None, + size: ChannelSize | None = None, + tooltip: ChannelTooltip | None = None, + *args: Any, + **kwargs: Any, + ) -> alt.Chart: + """ + Draw scatter plot. + + Polars does not implement plotting logic itself but instead defers to Altair. + `df.plot.point(*args, **kwargs)` is shorthand for + `alt.Chart(df).mark_point().encode(*args, **kwargs).interactive()`, + as is intended for convenience - for full customisatibility, use a plotting + library directly. + + .. versionchanged:: 1.6.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Parameters + ---------- + x + Column with x-coordinates of points. + y + Column with y-coordinates of points. + color + Column to color points by. + size + Column which determines points' sizes. + tooltip + Columns to show values of when hovering over points with pointer. + *args, **kwargs + Additional arguments and keyword arguments passed to Altair. + + Examples + -------- + >>> df = pl.DataFrame( + ... { + ... "length": [1, 4, 6], + ... "width": [4, 5, 6], + ... "species": ["setosa", "setosa", "versicolor"], + ... } + ... ) + >>> df.plot.point(x="length", y="width", color="species") # doctest: +SKIP + """ + encodings: Encodings = {} + if x is not None: + encodings["x"] = x + if y is not None: + encodings["y"] = y + if color is not None: + encodings["color"] = color + if size is not None: + encodings["size"] = size + if tooltip is not None: + encodings["tooltip"] = tooltip + return ( + self.chart.mark_point() + .encode(*args, **{**encodings, **kwargs}) + .interactive() + ) + + # def __getattr__( + # self, attr: str, *args: EncodeKwds, **kwargs: EncodeKwds + # ) -> Callable[..., alt.Chart]: + # method = self.chart.__getattr__(f"mark_{attr}", None) + # if method is None: + # msg = "Altair has no method 'mark_{attr}'" + # raise AttributeError(msg) + # return method().encode(*args, **kwargs).interactive() From ec57fb0e0a117203b8249d7462d1a3370df46b84 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 18 Aug 2024 14:12:56 +0100 Subject: [PATCH 37/42] add Series.plot --- .../docs/source/reference/series/index.rst | 1 + py-polars/polars/dataframe/frame.py | 30 ++- py-polars/polars/dataframe/plotting.py | 49 +++-- py-polars/polars/series/plotting.py | 193 ++++++------------ py-polars/polars/series/series.py | 26 ++- .../unit/operations/namespaces/test_plot.py | 36 ++-- 6 files changed, 141 insertions(+), 194 deletions(-) diff --git a/py-polars/docs/source/reference/series/index.rst b/py-polars/docs/source/reference/series/index.rst index 6090e43ddd15..a8476da64b97 100644 --- a/py-polars/docs/source/reference/series/index.rst +++ b/py-polars/docs/source/reference/series/index.rst @@ -20,6 +20,7 @@ This page gives an overview of all public Series methods. list modify_select miscellaneous + plot string struct temporal diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index e00be8cc5333..d40506d58d7d 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -618,15 +618,20 @@ def plot(self) -> Plot: `df.plot` with `df.hvplot`. Polars does not implement plotting logic itself, but instead defers to - Altair: + `Altair `_: - - `df.plot.line(*args, **kwargs)` + - `df.plot.line(**kwargs)` is shorthand for - `alt.Chart(df).mark_line().encode(*args, **kwargs).interactive()` - - `df.plot.point(*args, **kwargs)` + `alt.Chart(df).mark_line().encode(**kwargs).interactive()` + - `df.plot.point(**kwargs)` is shorthand for - `alt.Chart(df).mark_point().encode(*args, **kwargs).interactive()` - - ... (likewise, for any other attribute, e.g. `df.plot.bar`) + `alt.Chart(df).mark_point().encode(**kwargs).interactive()` + - `df.plot.bar(**kwargs)` + is shorthand for + `alt.Chart(df).mark_bar().encode(**kwargs).interactive()` + - for any other attribute `attr`, `df.plot.attr(**kwargs)` + is shorthand for + `alt.Chart(df).mark_attr().encode(**kwargs).interactive()` Examples -------- @@ -652,6 +657,19 @@ def plot(self) -> Plot: ... } ... ) >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP + + Bar plot: + + >>> df = pl.DataFrame( + ... { + ... "day": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] * 2, + ... "group": ["a"] * 7 + ["b"] * 7, + ... "value": [1, 3, 2, 4, 5, 6, 1, 1, 3, 2, 4, 5, 1, 2], + ... } + ... ) + >>> df.plot.bar( + ... x="day", y="value", color="day", column="group" + ... ) # doctest: +SKIP """ if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 4, 0): msg = "altair>=5.4.0 is required for `.plot`" diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index b43ba8c320d8..2fdd35cdc4d2 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -57,9 +57,11 @@ def bar( """ Draw bar plot. - Polars does not implement plotting logic itself but instead defers to Altair. - `df.plot.bar(*args, **kwargs)` is shorthand for - `alt.Chart(df).mark_bar().encode(*args, **kwargs).interactive()`, + Polars does not implement plotting logic itself but instead defers to + `Altair `_. + + `df.plot.bar(**kwargs)` is shorthand for + `alt.Chart(df).mark_bar().encode(**kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. @@ -79,8 +81,8 @@ def bar( Column to color bars by. tooltip Columns to show values of when hovering over bars with pointer. - *args, **kwargs - Additional arguments and keyword arguments passed to Altair. + **kwargs + Additional keyword arguments passed to Altair. Examples -------- @@ -120,9 +122,10 @@ def line( """ Draw line plot. - Polars does not implement plotting logic itself but instead defers to Altair. - `df.plot.line(*args, **kwargs)` is shorthand for - `alt.Chart(df).mark_line().encode(*args, **kwargs).interactive()`, + Polars does not implement plotting logic itself but instead defers to + `Altair `_. + + `alt.Chart(df).mark_line().encode(**kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. @@ -144,8 +147,8 @@ def line( Column to use for order of data points in lines. tooltip Columns to show values of when hovering over lines with pointer. - *args, **kwargs - Additional arguments and keyword arguments passed to Altair. + **kwargs + Additional keyword arguments passed to Altair. Examples -------- @@ -183,15 +186,16 @@ def point( color: ChannelColor | None = None, size: ChannelSize | None = None, tooltip: ChannelTooltip | None = None, - *args: Any, **kwargs: Any, ) -> alt.Chart: """ Draw scatter plot. - Polars does not implement plotting logic itself but instead defers to Altair. - `df.plot.point(*args, **kwargs)` is shorthand for - `alt.Chart(df).mark_point().encode(*args, **kwargs).interactive()`, + Polars does not implement plotting logic itself but instead defers to + `Altair `_. + + `df.plot.point(**kwargs)` is shorthand for + `alt.Chart(df).mark_point().encode(**kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. @@ -213,8 +217,8 @@ def point( Column which determines points' sizes. tooltip Columns to show values of when hovering over points with pointer. - *args, **kwargs - Additional arguments and keyword arguments passed to Altair. + **kwargs + Additional keyword arguments passed to Altair. Examples -------- @@ -240,15 +244,16 @@ def point( encodings["tooltip"] = tooltip return ( self.chart.mark_point() - .encode(*args, **{**encodings, **kwargs}) + .encode( + **encodings, # type: ignore[arg-type] + **kwargs, + ) .interactive() ) - def __getattr__( - self, attr: str, *args: EncodeKwds, **kwargs: EncodeKwds - ) -> Callable[..., alt.Chart]: - method = self.chart.__getattr__(f"mark_{attr}", None) + def __getattr__(self, attr: str) -> Callable[..., alt.Chart]: + method = getattr(self.chart, f"mark_{attr}", None) if method is None: msg = "Altair has no method 'mark_{attr}'" raise AttributeError(msg) - return method().encode(*args, **kwargs).interactive() + return lambda **kwargs: method().encode(**kwargs).interactive() diff --git a/py-polars/polars/series/plotting.py b/py-polars/polars/series/plotting.py index 25e84e338437..3321830e68a6 100644 --- a/py-polars/polars/series/plotting.py +++ b/py-polars/polars/series/plotting.py @@ -1,11 +1,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Dict, Union +from typing import TYPE_CHECKING, Callable, Dict, Union + +from polars.dependencies import altair as alt if TYPE_CHECKING: import sys - import altair as alt from altair.typing import ( ChannelColor, ChannelOrder, @@ -16,7 +17,6 @@ EncodeKwds, ) - if sys.version_info >= (3, 10): from typing import TypeAlias else: @@ -26,6 +26,8 @@ else: from typing_extensions import Unpack + from polars import Series + Encodings: TypeAlias = Dict[ str, Union[ @@ -35,31 +37,30 @@ class Plot: - """DataFrame.plot namespace.""" + """Series.plot namespace.""" + + _accessor = "plot" chart: alt.Chart def __init__(self, s: Series) -> None: - import altair as alt - - self.chart = alt.Chart(s.to_frame()) - self._series_name = s.name + name = s.name or "value" + self._df = s.to_frame(name) + self._series_name = name - def bar( + def hist( self, - x: ChannelX | None = None, - y: ChannelY | None = None, - color: ChannelColor | None = None, - tooltip: ChannelTooltip | None = None, /, **kwargs: Unpack[EncodeKwds], ) -> alt.Chart: """ - Draw bar plot. + Draw histogram. + + Polars does not implement plotting logic itself but instead defers to + `Altair `_. - Polars does not implement plotting logic itself but instead defers to Altair. - `df.plot.bar(*args, **kwargs)` is shorthand for - `alt.Chart(df).mark_bar().encode(*args, **kwargs).interactive()`, + `s.plot.hist(**kwargs)` is shorthand for + `alt.Chart(s.to_frame()).mark_bar().encode(x=alt.X(f'{s.name}:Q', bin=True), y='count()', **kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. @@ -71,40 +72,22 @@ def bar( Parameters ---------- - x - Column with x-coordinates of bars. - y - Column with y-coordinates of bars. - color - Column to color bars by. - tooltip - Columns to show values of when hovering over bars with pointer. - *args, **kwargs + **kwargs Additional arguments and keyword arguments passed to Altair. Examples -------- - >>> from datetime import date - >>> df = pl.DataFrame( - ... { - ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, - ... "price": [1, 4, 6, 1, 5, 2], - ... "stock": ["a", "a", "a", "b", "b", "b"], - ... } - ... ) - >>> df.plot.bar(x="price", y="count()") # doctest: +SKIP - """ - encodings: Encodings = {} - if x is not None: - encodings["x"] = x - if y is not None: - encodings["y"] = y - if color is not None: - encodings["color"] = color - if tooltip is not None: - encodings["tooltip"] = tooltip + >>> s = pl.Series("price", [1, 3, 3, 3, 5, 2, 6, 5, 5, 5, 7]) + >>> s.plot.hist() # doctest: +SKIP + """ # noqa: W505 + if self._series_name == "count()": + msg = "Cannot use `plot.hist` when Series name is `'count()'`" + raise ValueError(msg) return ( - self.chart.mark_bar().encode(**{**encodings, **kwargs}).interactive() # type: ignore[arg-type] + alt.Chart(self._df) + .mark_bar() + .encode(x=alt.X(f"{self._series_name}:Q", bin=True), y="count()", **kwargs) # type: ignore[misc] + .interactive() ) def kde( @@ -113,11 +96,13 @@ def kde( **kwargs: Unpack[EncodeKwds], ) -> alt.Chart: """ - Draw line plot. + Draw kernel dentity estimate plot. + + Polars does not implement plotting logic itself but instead defers to + `Altair `_. - Polars does not implement plotting logic itself but instead defers to Altair. - `df.plot.line(*args, **kwargs)` is shorthand for - `alt.Chart(df).mark_line().encode(*args, **kwargs).interactive()`, + `s.plot.kde(**kwargs)` is shorthand for + `alt.Chart(s.to_frame()).transform_density(s.name, as_=[s.name, 'density']).mark_area().encode(x=s.name, y='density:Q', **kwargs).interactive()`, as is intended for convenience - for full customisatibility, use a plotting library directly. @@ -144,96 +129,32 @@ def kde( Examples -------- - >>> from datetime import date - >>> df = pl.DataFrame( - ... { - ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, - ... "price": [1, 4, 6, 1, 5, 2], - ... "stock": ["a", "a", "a", "b", "b", "b"], - ... } - ... ) - >>> df.plot.line(x="date", y="price", color="stock") # doctest: +SKIP - """ + >>> s = pl.Series("price", [1, 3, 3, 3, 5, 2, 6, 5, 5, 5, 7]) + >>> s.plot.kde() # doctest: +SKIP + """ # noqa: W505 + if self._series_name == "density": + msg = "Cannot use `plot.kde` when Series name is `'density'`" + raise ValueError(msg) return ( - self.chart.transform_density(self._series_name, as_=[f'{self._series_name}', 'density']).mark_area() - .encode(x=f'{self._series_name}', y='density:Q', **kwargs) # type: ignore[arg-type] + alt.Chart(self._df) + .transform_density(self._series_name, as_=[self._series_name, "density"]) + .mark_area() + .encode(x=self._series_name, y="density:Q", **kwargs) # type: ignore[misc] .interactive() ) - alt.Chart(df).transform_density('price', as_=['price', 'density']).mark_area().encode(x='price', y='density:Q') - - def point( - self, - x: ChannelX | None = None, - y: ChannelY | None = None, - color: ChannelColor | None = None, - size: ChannelSize | None = None, - tooltip: ChannelTooltip | None = None, - *args: Any, - **kwargs: Any, - ) -> alt.Chart: - """ - Draw scatter plot. - - Polars does not implement plotting logic itself but instead defers to Altair. - `df.plot.point(*args, **kwargs)` is shorthand for - `alt.Chart(df).mark_point().encode(*args, **kwargs).interactive()`, - as is intended for convenience - for full customisatibility, use a plotting - library directly. - .. versionchanged:: 1.6.0 - In prior versions of Polars, HvPlot was the plotting backend. If you would - like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace - `df.plot` with `df.hvplot`. - - Parameters - ---------- - x - Column with x-coordinates of points. - y - Column with y-coordinates of points. - color - Column to color points by. - size - Column which determines points' sizes. - tooltip - Columns to show values of when hovering over points with pointer. - *args, **kwargs - Additional arguments and keyword arguments passed to Altair. - - Examples - -------- - >>> df = pl.DataFrame( - ... { - ... "length": [1, 4, 6], - ... "width": [4, 5, 6], - ... "species": ["setosa", "setosa", "versicolor"], - ... } - ... ) - >>> df.plot.point(x="length", y="width", color="species") # doctest: +SKIP - """ - encodings: Encodings = {} - if x is not None: - encodings["x"] = x - if y is not None: - encodings["y"] = y - if color is not None: - encodings["color"] = color - if size is not None: - encodings["size"] = size - if tooltip is not None: - encodings["tooltip"] = tooltip + def __getattr__(self, attr: str) -> Callable[..., alt.Chart]: + if "index" in self._df.columns: + msg = "Cannot call `plot.{attr}` when Series name is 'index'" + raise ValueError(msg) + method = getattr( + alt.Chart(self._df.with_row_index("index")), f"mark_{attr}", None + ) + if method is None: + msg = "Altair has no method 'mark_{attr}'" + raise AttributeError(msg) return ( - self.chart.mark_point() - .encode(*args, **{**encodings, **kwargs}) + lambda **kwargs: method() + .encode(x="index", y=self._series_name, **kwargs) .interactive() ) - - # def __getattr__( - # self, attr: str, *args: EncodeKwds, **kwargs: EncodeKwds - # ) -> Callable[..., alt.Chart]: - # method = self.chart.__getattr__(f"mark_{attr}", None) - # if method is None: - # msg = "Altair has no method 'mark_{attr}'" - # raise AttributeError(msg) - # return method().encode(*args, **kwargs).interactive() diff --git a/py-polars/polars/series/series.py b/py-polars/polars/series/series.py index 87ad673867e0..c1b3020a5b8a 100644 --- a/py-polars/polars/series/series.py +++ b/py-polars/polars/series/series.py @@ -1,5 +1,4 @@ from __future__ import annotations -from polars.series.plotting import Plot import contextlib import math @@ -92,8 +91,8 @@ _check_for_numpy, _check_for_pandas, _check_for_pyarrow, - import_optional, altair, + import_optional, ) from polars.dependencies import numpy as np from polars.dependencies import pandas as pd @@ -105,6 +104,7 @@ from polars.series.categorical import CatNameSpace from polars.series.datetime import DateTimeNameSpace from polars.series.list import ListNameSpace +from polars.series.plotting import Plot from polars.series.string import StringNameSpace from polars.series.struct import StructNameSpace from polars.series.utils import expr_dispatch, get_ffi_func @@ -7376,27 +7376,31 @@ def plot(self) -> Plot: Polars does not implement plotting logic itself, but instead defers to Altair: - - `s.plot.hist(*args, **kwargs)` + - `s.plot.hist(**kwargs)` + is shorthand for + `alt.Chart(s.to_frame()).mark_bar().encode(x=alt.X(f'{s.name}:Q', bin=True), y='count()', **kwargs).interactive()` + - `s.plot.kde(**kwargs)` is shorthand for - `alt.Chart(s.to_frame()).mark_bar().encode(x=s.name, y='count()', *args, **kwargs).interactive()` - - `s.plot.kde(*args, **kwargs)` + `alt.Chart(s.to_frame()).transform_density(s.name, as_=[s.name, 'density']).mark_area().encode(x=s.name, y='density:Q', **kwargs).interactive()` + - for any other attribute `attr`, `s.plot.attr(**kwargs)` is shorthand for - `alt.Chart(s.to_frame()).transform_density(s.name, as_=[s.name, 'density']).mark_area().encode(x=s.name, y='density', *args, **kwargs).interactive()` - - For anything else, please call `s.to_frame()` and then use one of the - methods in :meth:`DataFrame.plot`. + `alt.Chart(s.to_frame().with_row_index()).mark_attr().encode(x=s.name, y='index', **kwargs).interactive()` Examples -------- Histogram: - >>> s = pl.Series([1, 1, 2, 3]) + >>> s = pl.Series([1, 4, 4, 6, 2, 4, 3, 5, 5, 7, 1]) >>> s.plot.hist() # doctest: +SKIP KDE plot: >>> s.plot.kde() # doctest: +SKIP - """ + + Line plot: + + >>> s.plot.line() # doctest: +SKIP + """ # noqa: W505 if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 4, 0): msg = "altair>=5.4.0 is required for `.plot`" raise ModuleUpgradeRequiredError(msg) diff --git a/py-polars/tests/unit/operations/namespaces/test_plot.py b/py-polars/tests/unit/operations/namespaces/test_plot.py index f8f6710095e0..e1cd8f8f1edb 100644 --- a/py-polars/tests/unit/operations/namespaces/test_plot.py +++ b/py-polars/tests/unit/operations/namespaces/test_plot.py @@ -1,15 +1,8 @@ -from datetime import date - -import pytest - import polars as pl -# Calling `plot` the first time is slow -# https://github.com/pola-rs/polars/issues/13500 -pytestmark = pytest.mark.slow - -def test_dataframe_scatter() -> None: +def test_dataframe_plot() -> None: + # dry-run, check nothing errors df = pl.DataFrame( { "length": [1, 4, 6], @@ -17,19 +10,24 @@ def test_dataframe_scatter() -> None: "species": ["setosa", "setosa", "versicolor"], } ) - df.plot.point(x="length", y="width", color="species") + df.plot.line(x="length", y="width", color="species").to_json() + df.plot.point(x="length", y="width", size="species").to_json() + df.plot.bar(x="length", y="width", color="species").to_json() + df.plot.area(x="length", y="width", color="species").to_json() -def test_dataframe_line() -> None: - df = pl.DataFrame( - { - "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, - "price": [1, 4, 6, 1, 5, 2], - "stock": ["a", "a", "a", "b", "b", "b"], - } - ) - df.plot.line(x="date", y="price", color="stock") +def test_series_plot() -> None: + # dry-run, check nothing errors + s = pl.Series("a", [1, 4, 4, 4, 7, 2, 5, 3, 6]) + s.plot.kde().to_json() + s.plot.hist().to_json() + s.plot.line().to_json() + s.plot.point().to_json() def test_empty_dataframe() -> None: pl.DataFrame({"a": [], "b": []}).plot.point(x="a", y="b") + + +def test_nameless_series() -> None: + pl.Series([1, 2, 3]).plot.kde().to_json() From 28ac59665f84a7df166a19a23f3dffe107575670 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 18 Aug 2024 15:33:14 +0100 Subject: [PATCH 38/42] add missing page, add `scatter` as alias --- py-polars/docs/source/reference/series/plot.rst | 7 +++++++ py-polars/polars/dataframe/frame.py | 3 ++- py-polars/polars/dataframe/plotting.py | 3 +++ py-polars/tests/unit/operations/namespaces/test_plot.py | 1 + 4 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 py-polars/docs/source/reference/series/plot.rst diff --git a/py-polars/docs/source/reference/series/plot.rst b/py-polars/docs/source/reference/series/plot.rst new file mode 100644 index 000000000000..f7f719b8472e --- /dev/null +++ b/py-polars/docs/source/reference/series/plot.rst @@ -0,0 +1,7 @@ +==== +Plot +==== + +.. currentmodule:: polars + +.. autoproperty:: Series.plot \ No newline at end of file diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index d40506d58d7d..77d8bbe2ad1f 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -625,7 +625,8 @@ def plot(self) -> Plot: `alt.Chart(df).mark_line().encode(**kwargs).interactive()` - `df.plot.point(**kwargs)` is shorthand for - `alt.Chart(df).mark_point().encode(**kwargs).interactive()` + `alt.Chart(df).mark_point().encode(**kwargs).interactive()` (and `plot.scatter` is + provided as an alias) - `df.plot.bar(**kwargs)` is shorthand for `alt.Chart(df).mark_bar().encode(**kwargs).interactive()` diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 2fdd35cdc4d2..28d8144d8902 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -251,6 +251,9 @@ def point( .interactive() ) + # Alias to `point` because of how common it is. + scatter = point + def __getattr__(self, attr: str) -> Callable[..., alt.Chart]: method = getattr(self.chart, f"mark_{attr}", None) if method is None: diff --git a/py-polars/tests/unit/operations/namespaces/test_plot.py b/py-polars/tests/unit/operations/namespaces/test_plot.py index e1cd8f8f1edb..a2f60c932651 100644 --- a/py-polars/tests/unit/operations/namespaces/test_plot.py +++ b/py-polars/tests/unit/operations/namespaces/test_plot.py @@ -23,6 +23,7 @@ def test_series_plot() -> None: s.plot.hist().to_json() s.plot.line().to_json() s.plot.point().to_json() + s.plot.scatter().to_json() def test_empty_dataframe() -> None: From d5167f14bff464a44e7a03569844af277f806fd0 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 18 Aug 2024 15:33:57 +0100 Subject: [PATCH 39/42] lint --- py-polars/polars/dataframe/frame.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index 77d8bbe2ad1f..6634f5afbf10 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -625,8 +625,8 @@ def plot(self) -> Plot: `alt.Chart(df).mark_line().encode(**kwargs).interactive()` - `df.plot.point(**kwargs)` is shorthand for - `alt.Chart(df).mark_point().encode(**kwargs).interactive()` (and `plot.scatter` is - provided as an alias) + `alt.Chart(df).mark_point().encode(**kwargs).interactive()` (and + `plot.scatter` is provided as an alias) - `df.plot.bar(**kwargs)` is shorthand for `alt.Chart(df).mark_bar().encode(**kwargs).interactive()` From efed5c9599d0c187cb9288a1404fcb1081202f65 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 18 Aug 2024 15:41:46 +0100 Subject: [PATCH 40/42] rename, better bar plot example, simplify --- py-polars/polars/dataframe/frame.py | 6 +++--- py-polars/polars/dataframe/plotting.py | 23 +++++++++-------------- py-polars/polars/series/plotting.py | 2 +- py-polars/polars/series/series.py | 6 +++--- 4 files changed, 16 insertions(+), 21 deletions(-) diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index 6634f5afbf10..4bb953126f58 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -66,7 +66,7 @@ from polars._utils.wrap import wrap_expr, wrap_ldf, wrap_s from polars.dataframe._html import NotebookFormatter from polars.dataframe.group_by import DynamicGroupBy, GroupBy, RollingGroupBy -from polars.dataframe.plotting import Plot +from polars.dataframe.plotting import DataFramePlot from polars.datatypes import ( N_INFER_DEFAULT, Boolean, @@ -603,7 +603,7 @@ def _replace(self, column: str, new_column: Series) -> DataFrame: @property @unstable() - def plot(self) -> Plot: + def plot(self) -> DataFramePlot: """ Create a plot namespace. @@ -675,7 +675,7 @@ def plot(self) -> Plot: if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 4, 0): msg = "altair>=5.4.0 is required for `.plot`" raise ModuleUpgradeRequiredError(msg) - return Plot(self) + return DataFramePlot(self) @property @unstable() diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 28d8144d8902..058cad91d435 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -35,7 +35,7 @@ ] -class Plot: +class DataFramePlot: """DataFrame.plot namespace.""" chart: alt.Chart @@ -86,15 +86,16 @@ def bar( Examples -------- - >>> from datetime import date >>> df = pl.DataFrame( ... { - ... "date": [date(2020, 1, 2), date(2020, 1, 3), date(2020, 1, 4)] * 2, - ... "price": [1, 4, 6, 1, 5, 2], - ... "stock": ["a", "a", "a", "b", "b", "b"], + ... "day": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] * 2, + ... "group": ["a"] * 7 + ["b"] * 7, + ... "value": [1, 3, 2, 4, 5, 6, 1, 1, 3, 2, 4, 5, 1, 2], ... } ... ) - >>> df.plot.bar(x="price", y="count()") # doctest: +SKIP + >>> df.plot.bar( + ... x="day", y="value", color="day", column="group" + ... ) # doctest: +SKIP """ encodings: Encodings = {} if x is not None: @@ -105,9 +106,7 @@ def bar( encodings["color"] = color if tooltip is not None: encodings["tooltip"] = tooltip - return ( - self.chart.mark_bar().encode(**{**encodings, **kwargs}).interactive() # type: ignore[arg-type] - ) + return self.chart.mark_bar().encode(**encodings, **kwargs).interactive() def line( self, @@ -173,11 +172,7 @@ def line( encodings["order"] = order if tooltip is not None: encodings["tooltip"] = tooltip - return ( - self.chart.mark_line() - .encode(**{**encodings, **kwargs}) # type: ignore[arg-type] - .interactive() - ) + return self.chart.mark_line().encode(**encodings, **kwargs).interactive() def point( self, diff --git a/py-polars/polars/series/plotting.py b/py-polars/polars/series/plotting.py index 3321830e68a6..6165521d95f5 100644 --- a/py-polars/polars/series/plotting.py +++ b/py-polars/polars/series/plotting.py @@ -36,7 +36,7 @@ ] -class Plot: +class SeriesPlot: """Series.plot namespace.""" _accessor = "plot" diff --git a/py-polars/polars/series/series.py b/py-polars/polars/series/series.py index c1b3020a5b8a..8cf154090d61 100644 --- a/py-polars/polars/series/series.py +++ b/py-polars/polars/series/series.py @@ -104,7 +104,7 @@ from polars.series.categorical import CatNameSpace from polars.series.datetime import DateTimeNameSpace from polars.series.list import ListNameSpace -from polars.series.plotting import Plot +from polars.series.plotting import SeriesPlot from polars.series.string import StringNameSpace from polars.series.struct import StructNameSpace from polars.series.utils import expr_dispatch, get_ffi_func @@ -7359,7 +7359,7 @@ def struct(self) -> StructNameSpace: @property @unstable() - def plot(self) -> Plot: + def plot(self) -> SeriesPlot: """ Create a plot namespace. @@ -7404,7 +7404,7 @@ def plot(self) -> Plot: if not _ALTAIR_AVAILABLE or parse_version(altair.__version__) < (5, 4, 0): msg = "altair>=5.4.0 is required for `.plot`" raise ModuleUpgradeRequiredError(msg) - return Plot(self) + return SeriesPlot(self) def _resolve_temporal_dtype( From 381d48171c9a900e8cf367b7b14fe97dc366a9d0 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:51:08 +0100 Subject: [PATCH 41/42] assorted improvements --- py-polars/polars/dataframe/plotting.py | 6 +- py-polars/polars/series/plotting.py | 69 ++++++++++++++----- .../unit/operations/namespaces/test_plot.py | 2 +- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index 058cad91d435..fe207d4da156 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -62,7 +62,7 @@ def bar( `df.plot.bar(**kwargs)` is shorthand for `alt.Chart(df).mark_bar().encode(**kwargs).interactive()`, - as is intended for convenience - for full customisatibility, use a plotting + and is provided for convenience - for full customisatibility, use a plotting library directly. .. versionchanged:: 1.6.0 @@ -125,7 +125,7 @@ def line( `Altair `_. `alt.Chart(df).mark_line().encode(**kwargs).interactive()`, - as is intended for convenience - for full customisatibility, use a plotting + and is provided for convenience - for full customisatibility, use a plotting library directly. .. versionchanged:: 1.6.0 @@ -191,7 +191,7 @@ def point( `df.plot.point(**kwargs)` is shorthand for `alt.Chart(df).mark_point().encode(**kwargs).interactive()`, - as is intended for convenience - for full customisatibility, use a plotting + and is provided for convenience - for full customisatibility, use a plotting library directly. .. versionchanged:: 1.6.0 diff --git a/py-polars/polars/series/plotting.py b/py-polars/polars/series/plotting.py index 6165521d95f5..06b0379ee0ad 100644 --- a/py-polars/polars/series/plotting.py +++ b/py-polars/polars/series/plotting.py @@ -61,7 +61,7 @@ def hist( `s.plot.hist(**kwargs)` is shorthand for `alt.Chart(s.to_frame()).mark_bar().encode(x=alt.X(f'{s.name}:Q', bin=True), y='count()', **kwargs).interactive()`, - as is intended for convenience - for full customisatibility, use a plotting + and is provided for convenience - for full customisatibility, use a plotting library directly. .. versionchanged:: 1.6.0 @@ -103,7 +103,7 @@ def kde( `s.plot.kde(**kwargs)` is shorthand for `alt.Chart(s.to_frame()).transform_density(s.name, as_=[s.name, 'density']).mark_area().encode(x=s.name, y='density:Q', **kwargs).interactive()`, - as is intended for convenience - for full customisatibility, use a plotting + and is provided for convenience - for full customisatibility, use a plotting library directly. .. versionchanged:: 1.6.0 @@ -114,18 +114,8 @@ def kde( Parameters ---------- - x - Column with x-coordinates of lines. - y - Column with y-coordinates of lines. - color - Column to color lines by. - order - Column to use for order of data points in lines. - tooltip - Columns to show values of when hovering over lines with pointer. - *args, **kwargs - Additional arguments and keyword arguments passed to Altair. + **kwargs + Additional keyword arguments passed to Altair. Examples -------- @@ -143,13 +133,56 @@ def kde( .interactive() ) + def line( + self, + /, + **kwargs: Unpack[EncodeKwds], + ) -> alt.Chart: + """ + Draw line plot. + + Polars does not implement plotting logic itself but instead defers to + `Altair `_. + + `s.plot.line(**kwargs)` is shorthand for + `alt.Chart(s.to_frame().with_row_index()).mark_line().encode(x='index', y=s.name, **kwargs).interactive()`, + and is provided for convenience - for full customisatibility, use a plotting + library directly. + + .. versionchanged:: 1.6.0 + In prior versions of Polars, HvPlot was the plotting backend. If you would + like to restore the previous plotting functionality, all you need to do + add `import hvplot.polars` at the top of your script and replace + `df.plot` with `df.hvplot`. + + Parameters + ---------- + **kwargs + Additional keyword arguments passed to Altair. + + Examples + -------- + >>> s = pl.Series("price", [1, 3, 3, 3, 5, 2, 6, 5, 5, 5, 7]) + >>> s.plot.kde() # doctest: +SKIP + """ # noqa: W505 + if self._series_name == "index": + msg = "Cannot call `plot.line` when Series name is 'index'" + raise ValueError(msg) + return ( + alt.Chart(self._df.with_row_index()) + .mark_line() + .encode(x="index", y=self._series_name, **kwargs) # type: ignore[misc] + .interactive() + ) + def __getattr__(self, attr: str) -> Callable[..., alt.Chart]: - if "index" in self._df.columns: + if self._series_name == "index": msg = "Cannot call `plot.{attr}` when Series name is 'index'" raise ValueError(msg) - method = getattr( - alt.Chart(self._df.with_row_index("index")), f"mark_{attr}", None - ) + if attr == "scatter": + # alias `scatter` to `point` because of how common it is + attr = "point" + method = getattr(alt.Chart(self._df.with_row_index()), f"mark_{attr}", None) if method is None: msg = "Altair has no method 'mark_{attr}'" raise AttributeError(msg) diff --git a/py-polars/tests/unit/operations/namespaces/test_plot.py b/py-polars/tests/unit/operations/namespaces/test_plot.py index a2f60c932651..fc2fbc02648a 100644 --- a/py-polars/tests/unit/operations/namespaces/test_plot.py +++ b/py-polars/tests/unit/operations/namespaces/test_plot.py @@ -12,6 +12,7 @@ def test_dataframe_plot() -> None: ) df.plot.line(x="length", y="width", color="species").to_json() df.plot.point(x="length", y="width", size="species").to_json() + df.plot.scatter(x="length", y="width", size="species").to_json() df.plot.bar(x="length", y="width", color="species").to_json() df.plot.area(x="length", y="width", color="species").to_json() @@ -23,7 +24,6 @@ def test_series_plot() -> None: s.plot.hist().to_json() s.plot.line().to_json() s.plot.point().to_json() - s.plot.scatter().to_json() def test_empty_dataframe() -> None: From ea018b50f5dc8c281b752011c006cf700e2d115b Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Mon, 19 Aug 2024 09:27:13 +0100 Subject: [PATCH 42/42] assorted docs and typing improvements --- py-polars/polars/dataframe/frame.py | 2 +- py-polars/polars/dataframe/plotting.py | 25 ++++++++++--------- py-polars/polars/series/plotting.py | 33 +++++--------------------- py-polars/polars/series/series.py | 4 ++-- 4 files changed, 21 insertions(+), 43 deletions(-) diff --git a/py-polars/polars/dataframe/frame.py b/py-polars/polars/dataframe/frame.py index 4bb953126f58..427ac0031d56 100644 --- a/py-polars/polars/dataframe/frame.py +++ b/py-polars/polars/dataframe/frame.py @@ -614,7 +614,7 @@ def plot(self) -> DataFramePlot: .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Polars does not implement plotting logic itself, but instead defers to diff --git a/py-polars/polars/dataframe/plotting.py b/py-polars/polars/dataframe/plotting.py index fe207d4da156..ed118e504656 100644 --- a/py-polars/polars/dataframe/plotting.py +++ b/py-polars/polars/dataframe/plotting.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Any, Callable, Dict, Union +from typing import TYPE_CHECKING, Callable, Dict, Union if TYPE_CHECKING: import sys @@ -38,12 +38,10 @@ class DataFramePlot: """DataFrame.plot namespace.""" - chart: alt.Chart - def __init__(self, df: DataFrame) -> None: import altair as alt - self.chart = alt.Chart(df) + self._chart = alt.Chart(df) def bar( self, @@ -68,7 +66,7 @@ def bar( .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Parameters @@ -106,7 +104,7 @@ def bar( encodings["color"] = color if tooltip is not None: encodings["tooltip"] = tooltip - return self.chart.mark_bar().encode(**encodings, **kwargs).interactive() + return self._chart.mark_bar().encode(**encodings, **kwargs).interactive() def line( self, @@ -131,7 +129,7 @@ def line( .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Parameters @@ -172,7 +170,7 @@ def line( encodings["order"] = order if tooltip is not None: encodings["tooltip"] = tooltip - return self.chart.mark_line().encode(**encodings, **kwargs).interactive() + return self._chart.mark_line().encode(**encodings, **kwargs).interactive() def point( self, @@ -181,7 +179,8 @@ def point( color: ChannelColor | None = None, size: ChannelSize | None = None, tooltip: ChannelTooltip | None = None, - **kwargs: Any, + /, + **kwargs: Unpack[EncodeKwds], ) -> alt.Chart: """ Draw scatter plot. @@ -197,7 +196,7 @@ def point( .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Parameters @@ -238,9 +237,9 @@ def point( if tooltip is not None: encodings["tooltip"] = tooltip return ( - self.chart.mark_point() + self._chart.mark_point() .encode( - **encodings, # type: ignore[arg-type] + **encodings, **kwargs, ) .interactive() @@ -250,7 +249,7 @@ def point( scatter = point def __getattr__(self, attr: str) -> Callable[..., alt.Chart]: - method = getattr(self.chart, f"mark_{attr}", None) + method = getattr(self._chart, f"mark_{attr}", None) if method is None: msg = "Altair has no method 'mark_{attr}'" raise AttributeError(msg) diff --git a/py-polars/polars/series/plotting.py b/py-polars/polars/series/plotting.py index 06b0379ee0ad..c666c5a9b177 100644 --- a/py-polars/polars/series/plotting.py +++ b/py-polars/polars/series/plotting.py @@ -1,26 +1,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Dict, Union +from typing import TYPE_CHECKING, Callable from polars.dependencies import altair as alt if TYPE_CHECKING: import sys - from altair.typing import ( - ChannelColor, - ChannelOrder, - ChannelSize, - ChannelTooltip, - ChannelX, - ChannelY, - EncodeKwds, - ) - - if sys.version_info >= (3, 10): - from typing import TypeAlias - else: - from typing_extensions import TypeAlias + from altair.typing import EncodeKwds + if sys.version_info >= (3, 11): from typing import Unpack else: @@ -28,21 +16,12 @@ from polars import Series - Encodings: TypeAlias = Dict[ - str, - Union[ - ChannelX, ChannelY, ChannelColor, ChannelOrder, ChannelSize, ChannelTooltip - ], - ] - class SeriesPlot: """Series.plot namespace.""" _accessor = "plot" - chart: alt.Chart - def __init__(self, s: Series) -> None: name = s.name or "value" self._df = s.to_frame(name) @@ -67,7 +46,7 @@ def hist( .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Parameters @@ -109,7 +88,7 @@ def kde( .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Parameters @@ -152,7 +131,7 @@ def line( .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Parameters diff --git a/py-polars/polars/series/series.py b/py-polars/polars/series/series.py index 8cf154090d61..b61198fb9deb 100644 --- a/py-polars/polars/series/series.py +++ b/py-polars/polars/series/series.py @@ -7370,7 +7370,7 @@ def plot(self) -> SeriesPlot: .. versionchanged:: 1.6.0 In prior versions of Polars, HvPlot was the plotting backend. If you would like to restore the previous plotting functionality, all you need to do - add `import hvplot.polars` at the top of your script and replace + is add `import hvplot.polars` at the top of your script and replace `df.plot` with `df.hvplot`. Polars does not implement plotting logic itself, but instead defers to @@ -7384,7 +7384,7 @@ def plot(self) -> SeriesPlot: `alt.Chart(s.to_frame()).transform_density(s.name, as_=[s.name, 'density']).mark_area().encode(x=s.name, y='density:Q', **kwargs).interactive()` - for any other attribute `attr`, `s.plot.attr(**kwargs)` is shorthand for - `alt.Chart(s.to_frame().with_row_index()).mark_attr().encode(x=s.name, y='index', **kwargs).interactive()` + `alt.Chart(s.to_frame().with_row_index()).mark_attr().encode(x='index', y=s.name, **kwargs).interactive()` Examples --------