Skip to content

Commit

Permalink
feat(python): add nicer default plot configuration, link to Altair Chart
Browse files Browse the repository at this point in the history
Configuration docs
  • Loading branch information
MarcoGorelli committed Sep 8, 2024
1 parent 98788b2 commit 57106a7
Show file tree
Hide file tree
Showing 6 changed files with 233 additions and 100 deletions.
12 changes: 10 additions & 2 deletions docs/src/python/user-guide/misc/visualization.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
y="sepal_length",
by="species",
width=650,
title="Irises",
)
# --8<-- [end:hvplot_show_plot]
"""
Expand All @@ -27,6 +28,7 @@
y="sepal_length",
by="species",
width=650,
title="Irises",
)
hvplot.save(plot, "docs/images/hvplot_scatter.html")
with open("docs/images/hvplot_scatter.html", "r") as f:
Expand All @@ -44,6 +46,7 @@
y=df["sepal_length"],
c=df["species"].cast(pl.Categorical).to_physical(),
)
ax.set_title('Irises')
# --8<-- [end:matplotlib_show_plot]
"""

Expand All @@ -58,6 +61,7 @@
y=df["sepal_length"],
c=df["species"].cast(pl.Categorical).to_physical(),
)
ax.set_title("Irises")
fig.savefig("docs/images/matplotlib_scatter.png")
with open("docs/images/matplotlib_scatter.png", "rb") as f:
png = base64.b64encode(f.read()).decode()
Expand All @@ -72,7 +76,7 @@
x="sepal_width",
y="sepal_length",
hue="species",
)
).set_title('Irises')
# --8<-- [end:seaborn_show_plot]
"""

Expand All @@ -86,7 +90,7 @@
x="sepal_width",
y="sepal_length",
hue="species",
)
).set_title("Irises")
fig.savefig("docs/images/seaborn_scatter.png")
with open("docs/images/seaborn_scatter.png", "rb") as f:
png = base64.b64encode(f.read()).decode()
Expand All @@ -103,6 +107,7 @@
y="sepal_length",
color="species",
width=650,
title="Irises",
)
# --8<-- [end:plotly_show_plot]
"""
Expand All @@ -116,6 +121,7 @@
y="sepal_length",
color="species",
width=650,
title="Irises",
)
fig.write_html(
"docs/images/plotly_scatter.html", full_html=False, include_plotlyjs="cdn"
Expand All @@ -132,6 +138,7 @@
x="sepal_length",
y="sepal_width",
color="species",
title="Irises",
)
.properties(width=500)
.configure_scale(zero=False)
Expand All @@ -145,6 +152,7 @@
x="sepal_length",
y="sepal_width",
color="species",
title="Irises",
)
.properties(width=500)
.configure_scale(zero=False)
Expand Down
7 changes: 5 additions & 2 deletions docs/user-guide/misc/visualization.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@ import altair as alt
y="sepal_width",
color="species",
)
.properties(width=500)
.properties(width=500, title="Irises")
.configure_scale(zero=False)
)
```

and is only provided for convenience, and to signal that Altair is known to work well with
(with some extra configuration) and is only provided for convenience, and to signal that Altair is known to work well with
Polars.

For configuration, we suggest reading [Chart Configuration](https://altair-viz.github.io/altair-tutorial/notebooks/08-Configuration.html). For example, you can change the x-axis label rotation by appending
`.configure_axisX(rotation=30)` to your call.

## hvPlot

If you import `hvplot.polars`, then it registers a `hvplot`
Expand Down
20 changes: 4 additions & 16 deletions py-polars/polars/dataframe/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -618,22 +618,10 @@ def plot(self) -> DataFramePlot:
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
`Altair <https://altair-viz.github.io/>`_:
- `df.plot.line(**kwargs)`
is shorthand for
`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)
- `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()`
Polars defers to `Altair <https://altair-viz.github.io/>`_ for plotting, and
this functionality is only provided for convenience.
For configuration, we suggest reading `Chart Configuration
<https://altair-viz.github.io/altair-tutorial/notebooks/08-Configuration.html>`_.
Examples
--------
Expand Down
167 changes: 138 additions & 29 deletions py-polars/polars/dataframe/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,73 @@
]


def configure_chart(
chart: alt.Chart,
*,
title: str | None,
x_axis_title: str | None,
y_axis_title: str | None,
) -> alt.Chart:
"""
A nice-looking default configuration, produced by Altair maintainer.
Source: https://gist.github.com/binste/b4042fa76a89d72d45cbbb9355ec6906.
"""
properties = {}
if title is not None:
properties["title"] = title
if x_axis_title is not None:
chart.encoding.x.title = x_axis_title
if y_axis_title is not None:
chart.encoding.y.title = y_axis_title
return (
chart.properties(**properties)
.configure_axis(
labelFontSize=16,
titleFontSize=16,
titleFontWeight="normal",
gridColor="lightGray",
labelAngle=0,
labelFlush=False,
labelPadding=5,
)
.configure_axisY(
domain=False,
ticks=False,
labelPadding=10,
titleAngle=0,
titleY=-20,
titleAlign="left",
titlePadding=0,
)
.configure_axisTemporal(grid=False)
.configure_axisDiscrete(ticks=False, labelPadding=10, grid=False)
.configure_scale(barBandPaddingInner=0.2)
.configure_header(labelFontSize=16, titleFontSize=16)
.configure_legend(labelFontSize=16, titleFontSize=16, titleFontWeight="normal")
.configure_title(
fontSize=20,
fontStyle="normal",
align="left",
anchor="start",
orient="top",
fontWeight=600,
offset=10,
subtitlePadding=3,
subtitleFontSize=16,
)
.configure_view(
strokeWidth=0, continuousHeight=350, continuousWidth=600, step=50
)
.configure_line(strokeWidth=3.5)
.configure_text(fontSize=16)
.configure_circle(size=60)
.configure_point(size=60)
.configure_square(size=60)
.interactive()
)


class DataFramePlot:
"""DataFrame.plot namespace."""

Expand All @@ -50,18 +117,18 @@ def bar(
color: ChannelColor | None = None,
tooltip: ChannelTooltip | None = None,
/,
title: str | None = None,
x_axis_title: str | None = None,
y_axis_title: str | None = None,
**kwargs: Unpack[EncodeKwds],
) -> alt.Chart:
"""
Draw bar plot.
Polars does not implement plotting logic itself but instead defers to
`Altair <https://altair-viz.github.io/>`_.
`df.plot.bar(**kwargs)` is shorthand for
`alt.Chart(df).mark_bar().encode(**kwargs).interactive()`,
and is provided for convenience - for full customisatibility, use a plotting
library directly.
Polars defers to `Altair <https://altair-viz.github.io/>`_ for plotting, and
this functionality is only provided for convenience.
For configuration, we suggest reading `Chart Configuration
<https://altair-viz.github.io/altair-tutorial/notebooks/08-Configuration.html>`_.
.. versionchanged:: 1.6.0
In prior versions of Polars, HvPlot was the plotting backend. If you would
Expand All @@ -79,6 +146,12 @@ def bar(
Column to color bars by.
tooltip
Columns to show values of when hovering over bars with pointer.
title
Plot title.
x_axis_title
Title of x-axis.
y_axis_title
Title of y-axis.
**kwargs
Additional keyword arguments passed to Altair.
Expand All @@ -104,7 +177,12 @@ def bar(
encodings["color"] = color
if tooltip is not None:
encodings["tooltip"] = tooltip
return self._chart.mark_bar().encode(**encodings, **kwargs).interactive()
return configure_chart(
self._chart.mark_bar().encode(**encodings, **kwargs),
title=title,
x_axis_title=x_axis_title,
y_axis_title=y_axis_title,
)

def line(
self,
Expand All @@ -114,17 +192,18 @@ def line(
order: ChannelOrder | None = None,
tooltip: ChannelTooltip | None = None,
/,
title: str | None = None,
x_axis_title: str | None = None,
y_axis_title: str | None = None,
**kwargs: Unpack[EncodeKwds],
) -> alt.Chart:
"""
Draw line plot.
Polars does not implement plotting logic itself but instead defers to
`Altair <https://altair-viz.github.io/>`_.
`alt.Chart(df).mark_line().encode(**kwargs).interactive()`,
and is provided for convenience - for full customisatibility, use a plotting
library directly.
Polars defers to `Altair <https://altair-viz.github.io/>`_ for plotting, and
this functionality is only provided for convenience.
For configuration, we suggest reading `Chart Configuration
<https://altair-viz.github.io/altair-tutorial/notebooks/08-Configuration.html>`_.
.. versionchanged:: 1.6.0
In prior versions of Polars, HvPlot was the plotting backend. If you would
Expand All @@ -144,6 +223,12 @@ def line(
Column to use for order of data points in lines.
tooltip
Columns to show values of when hovering over lines with pointer.
title
Plot title.
x_axis_title
Title of x-axis.
y_axis_title
Title of y-axis.
**kwargs
Additional keyword arguments passed to Altair.
Expand All @@ -170,7 +255,12 @@ def line(
encodings["order"] = order
if tooltip is not None:
encodings["tooltip"] = tooltip
return self._chart.mark_line().encode(**encodings, **kwargs).interactive()
return configure_chart(
self._chart.mark_line().encode(**encodings, **kwargs),
title=title,
x_axis_title=x_axis_title,
y_axis_title=y_axis_title,
)

def point(
self,
Expand All @@ -180,18 +270,18 @@ def point(
size: ChannelSize | None = None,
tooltip: ChannelTooltip | None = None,
/,
title: str | None = None,
x_axis_title: str | None = None,
y_axis_title: str | None = None,
**kwargs: Unpack[EncodeKwds],
) -> alt.Chart:
"""
Draw scatter plot.
Polars does not implement plotting logic itself but instead defers to
`Altair <https://altair-viz.github.io/>`_.
`df.plot.point(**kwargs)` is shorthand for
`alt.Chart(df).mark_point().encode(**kwargs).interactive()`,
and is provided for convenience - for full customisatibility, use a plotting
library directly.
Polars defers to `Altair <https://altair-viz.github.io/>`_ for plotting, and
this functionality is only provided for convenience.
For configuration, we suggest reading `Chart Configuration
<https://altair-viz.github.io/altair-tutorial/notebooks/08-Configuration.html>`_.
.. versionchanged:: 1.6.0
In prior versions of Polars, HvPlot was the plotting backend. If you would
Expand All @@ -211,6 +301,12 @@ def point(
Column which determines points' sizes.
tooltip
Columns to show values of when hovering over points with pointer.
title
Plot title.
x_axis_title
Title of x-axis.
y_axis_title
Title of y-axis.
**kwargs
Additional keyword arguments passed to Altair.
Expand All @@ -236,21 +332,34 @@ def point(
encodings["size"] = size
if tooltip is not None:
encodings["tooltip"] = tooltip
return (
self._chart.mark_point()
.encode(
return configure_chart(
self._chart.mark_point().encode(
**encodings,
**kwargs,
)
.interactive()
),
title=title,
x_axis_title=x_axis_title,
y_axis_title=y_axis_title,
)

# Alias to `point` because of how common it is.
scatter = point

def __getattr__(self, attr: str) -> Callable[..., alt.Chart]:
def __getattr__(
self,
attr: str,
*,
title: str | None = None,
x_axis_title: str | None = None,
y_axis_title: str | None = None,
) -> 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 lambda **kwargs: method().encode(**kwargs).interactive()
return lambda **kwargs: configure_chart(
method().encode(**kwargs),
title=title,
x_axis_title=x_axis_title,
y_axis_title=y_axis_title,
)
Loading

0 comments on commit 57106a7

Please sign in to comment.