From d0a32ae7f2001b604987551a9c8c6ad6bf601e98 Mon Sep 17 00:00:00 2001 From: Tim Hopper Date: Tue, 4 Aug 2026 20:39:57 -0400 Subject: [PATCH 1/3] Register seaborn.objects as a comparison package --- render.py | 1 + tests/test_plots.py | 27 ++++++++++++++++++--------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/render.py b/render.py index 7173fc2..a3164ff 100644 --- a/render.py +++ b/render.py @@ -20,6 +20,7 @@ "pandas": "Pandas", "matplotlib": "Matplotlib", "seaborn": "Seaborn", + "seaborn-objects": "seaborn.objects", "plotnine": "plotnine", "lets-plot": "lets-plot", "plotly": "plotly", diff --git a/tests/test_plots.py b/tests/test_plots.py index 4bd99c4..3a4af40 100644 --- a/tests/test_plots.py +++ b/tests/test_plots.py @@ -3,17 +3,18 @@ defined_plots = { "bar-counts": [ - "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "pandas", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "dodged-bar-chart": [ - "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "pandas", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "scatter-plot": [ - "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "pandas", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "scatter-plot-with-colors": [ "matplotlib", "seaborn", + "seaborn-objects", "plotnine", "lets-plot", "ggplot", @@ -21,13 +22,13 @@ "altair", ], "scatter-plot-with-facet": [ - "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "seaborn", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "scatter-plot-with-facets": [ - "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "seaborn", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "scatter-plot-with-size": [ - "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "pandas", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "scatter-with-regression": [ "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", @@ -35,6 +36,7 @@ "simple-histogram": [ "pandas", "matplotlib", + "seaborn-objects", "plotnine", "lets-plot", "ggplot", @@ -42,16 +44,23 @@ "altair", ], "stacked-bar-chart": [ - "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "pandas", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "stacked-kde": [ - "pandas", "seaborn", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "pandas", + "seaborn", + "seaborn-objects", + "plotnine", + "lets-plot", + "ggplot", + "plotly", + "altair", ], "stacked-smooth-line-and-scatter": [ "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], "timeseries": [ - "pandas", "plotnine", "lets-plot", "ggplot", "plotly", "altair", + "pandas", "seaborn-objects", "plotnine", "lets-plot", "ggplot", "plotly", "altair", ], } From e68081c758ec9d0316d1568441e3063f46147a34 Mon Sep 17 00:00:00 2001 From: Tim Hopper Date: Tue, 4 Aug 2026 20:39:58 -0400 Subject: [PATCH 2/3] Add seaborn.objects examples for eleven plots --- Examples.ipynb | 287 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 286 insertions(+), 1 deletion(-) diff --git a/Examples.ipynb b/Examples.ipynb index 1e7db06..bc5e3e9 100644 --- a/Examples.ipynb +++ b/Examples.ipynb @@ -5,7 +5,93 @@ "execution_count": null, "metadata": {}, "outputs": [], - "source": "%matplotlib inline\n\nimport readline\nimport altair as alt\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib import pyplot\nfrom plotnine import *\nimport numpy as np\n\nfrom plotly import figure_factory\nfrom plotly import graph_objects\nimport plotly.express as px\nfrom IPython.core.magic import Magics, magics_class, cell_magic\n\nfrom IPython.display import Image\n\nfrom pylab import rcParams\n\nsize = 20\nparams = {\n \"legend.fontsize\": size,\n \"figure.figsize\": (15, 5),\n \"axes.labelsize\": size,\n \"axes.titlesize\": size,\n \"xtick.labelsize\": size,\n \"ytick.labelsize\": size,\n \"axes.titlesize\": 1.5 * size,\n \"figure.figsize\": (12, 12),\n}\nrcParams.update(params)\ntheme_update(\n figure_size=(9, 9),\n title=element_text(size=size),\n text=element_text(size=0.6 * size),\n) # for plotnine\n\n\nimport plotly.io as pio\npio.renderers.default = \"png\"\npio.renderers[\"png\"].width = 750\npio.renderers[\"png\"].height = 750\n\n# Render Altair charts as PNG via vl-convert\nalt.renderers.enable(\"png\", scale_factor=2.0)\n\nimport tempfile\n\nimport lets_plot as lp\nfrom lets_plot.export import ggsave\n\nlp.LetsPlot.setup_html()\nlp.LetsPlot.set_theme(\n lp.theme(text=lp.element_text(size=16), title=lp.element_text(size=20))\n)\n\n_LETS_PLOT_DIR = tempfile.mkdtemp(prefix=\"lets-plot-\")\n\n\ndef _lets_plot_png(plot):\n \"\"\"Render a Lets-Plot spec to PNG so the cell output carries an image.\"\"\"\n path = ggsave(plot + lp.ggsize(750, 750), \"plot.png\", path=_LETS_PLOT_DIR)\n with open(path, \"rb\") as f:\n return f.read()\n\n\nget_ipython().display_formatter.formatters[\"image/png\"].for_type(\n lp.plot.core.PlotSpec, _lets_plot_png\n)" + "source": [ + "%matplotlib inline\n", + "\n", + "import readline\n", + "import altair as alt\n", + "import pandas as pd\n", + "import seaborn as sns\n", + "from matplotlib import pyplot\n", + "from plotnine import *\n", + "import numpy as np\n", + "\n", + "from plotly import figure_factory\n", + "from plotly import graph_objects\n", + "import plotly.express as px\n", + "from IPython.core.magic import Magics, magics_class, cell_magic\n", + "\n", + "from IPython.display import Image\n", + "\n", + "from pylab import rcParams\n", + "\n", + "size = 20\n", + "params = {\n", + " \"legend.fontsize\": size,\n", + " \"figure.figsize\": (15, 5),\n", + " \"axes.labelsize\": size,\n", + " \"axes.titlesize\": size,\n", + " \"xtick.labelsize\": size,\n", + " \"ytick.labelsize\": size,\n", + " \"axes.titlesize\": 1.5 * size,\n", + " \"figure.figsize\": (12, 12),\n", + "}\n", + "rcParams.update(params)\n", + "theme_update(\n", + " figure_size=(9, 9),\n", + " title=element_text(size=size),\n", + " text=element_text(size=0.6 * size),\n", + ") # for plotnine\n", + "\n", + "\n", + "import seaborn.objects as so\n", + "\n", + "# Match the other libraries: ~750x750 px output with scaled-up text\n", + "so.Plot.config.display[\"scaling\"] = 1\n", + "so.Plot.config.theme.update(\n", + " {\n", + " \"figure.figsize\": (8, 8),\n", + " \"axes.labelsize\": size,\n", + " \"axes.titlesize\": 1.5 * size,\n", + " \"xtick.labelsize\": size,\n", + " \"ytick.labelsize\": size,\n", + " \"legend.fontsize\": size,\n", + " \"legend.title_fontsize\": size,\n", + " }\n", + ")\n", + "\n", + "import plotly.io as pio\n", + "pio.renderers.default = \"png\"\n", + "pio.renderers[\"png\"].width = 750\n", + "pio.renderers[\"png\"].height = 750\n", + "\n", + "# Render Altair charts as PNG via vl-convert\n", + "alt.renderers.enable(\"png\", scale_factor=2.0)\n", + "\n", + "import tempfile\n", + "\n", + "import lets_plot as lp\n", + "from lets_plot.export import ggsave\n", + "\n", + "lp.LetsPlot.setup_html()\n", + "lp.LetsPlot.set_theme(\n", + " lp.theme(text=lp.element_text(size=16), title=lp.element_text(size=20))\n", + ")\n", + "\n", + "_LETS_PLOT_DIR = tempfile.mkdtemp(prefix=\"lets-plot-\")\n", + "\n", + "\n", + "def _lets_plot_png(plot):\n", + " \"\"\"Render a Lets-Plot spec to PNG so the cell output carries an image.\"\"\"\n", + " path = ggsave(plot + lp.ggsize(750, 750), \"plot.png\", path=_LETS_PLOT_DIR)\n", + " with open(path, \"rb\") as f:\n", + " return f.read()\n", + "\n", + "\n", + "get_ipython().display_formatter.formatters[\"image/png\"].for_type(\n", + " lp.plot.core.PlotSpec, _lets_plot_png\n", + ")" + ] }, { "cell_type": "code", @@ -291,6 +377,26 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:bar-counts", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "\"\"\"seaborn.objects has no `coord_flip`, so the\n", + "categorical variable is mapped to `y` instead.\n", + "\"\"\"\n", + "(so.Plot(mpg, y=\"manufacturer\")\n", + " .add(so.Bar(), so.Count())\n", + " .label(title=\"Number of Cars by Make\"))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -423,6 +529,22 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:simple-histogram", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(mpg, x=\"cty\")\n", + " .add(so.Bars(), so.Hist(binwidth=2)))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -558,6 +680,26 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(mpg, x=\"displ\", y=\"hwy\")\n", + " .add(so.Dot())\n", + " .label(\n", + " x=\"Engine Displacement in Liters\",\n", + " y=\"Highway MPG\",\n", + " title=\"Engine Displacement in Liters vs Highway MPG\"))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -819,6 +961,27 @@ "));" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-colors", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(mpg, x=\"displ\", y=\"hwy\",\n", + " color=\"class\")\n", + " .add(so.Dot())\n", + " .label(\n", + " x=\"Engine Displacement in Liters\",\n", + " y=\"Highway MPG\",\n", + " title=\"Engine Displacement in Liters vs Highway MPG\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1017,6 +1180,25 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-size", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(mpg, x=\"cty\", y=\"hwy\",\n", + " pointsize=\"cyl\")\n", + " .add(so.Dots(alpha=.5))\n", + " .scale(pointsize=(4, 12))\n", + " .label(x=\"City MPG\", y=\"Highway MPG\"))" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -1085,6 +1267,23 @@ ");" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-facet", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(mpg, x=\"displ\", y=\"hwy\")\n", + " .add(so.Dot())\n", + " .facet(\"class\", wrap=4))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1211,6 +1410,23 @@ ");" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:scatter-plot-with-facets", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(mpg, x=\"displ\", y=\"hwy\")\n", + " .add(so.Dot())\n", + " .facet(col=\"cyl\", row=\"drv\"))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1546,6 +1762,22 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:stacked-bar-chart", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(diamonds, x=\"cut\", color=\"clarity\")\n", + " .add(so.Bar(), so.Count(), so.Stack()))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1659,6 +1891,22 @@ ")" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:dodged-bar-chart", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(diamonds, x=\"cut\", color=\"clarity\")\n", + " .add(so.Bar(), so.Count(), so.Dodge()))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1715,6 +1963,27 @@ "outputs": [], "source": "(sns\n .FacetGrid(diamonds, \n hue=\"cut\", \n height=10, \n xlim=(55, 70))\n .map(sns.kdeplot, 'depth', fill=True)\n .add_legend()\n);" }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:stacked-kde", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "\"\"\"`.limit()` only clips the axis; unlike ggplot2's\n", + "`xlim()` it does not drop rows before the density is\n", + "estimated.\n", + "\"\"\"\n", + "(so.Plot(diamonds, x=\"depth\", color=\"cut\")\n", + " .add(so.Area(alpha=.1), so.KDE(gridsize=500))\n", + " .limit(x=(55, 70)))" + ] + }, { "cell_type": "code", "execution_count": null, @@ -1901,6 +2170,22 @@ " x=\"date\", y=\"value\"\n", ")" ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "tags": [ + "ex", + "name:timeseries", + "package:seaborn-objects" + ] + }, + "outputs": [], + "source": [ + "(so.Plot(ts, x=\"date\", y=\"value\")\n", + " .add(so.Line()))" + ] } ], "metadata": { From 61d6de74fc8d8dfb0a872a00c21fda4717b02428 Mon Sep 17 00:00:00 2001 From: Tim Hopper Date: Tue, 4 Aug 2026 20:39:58 -0400 Subject: [PATCH 3/3] Describe seaborn.objects in the site intro --- INTRO.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/INTRO.md b/INTRO.md index 9398ad4..333bf38 100644 --- a/INTRO.md +++ b/INTRO.md @@ -20,6 +20,8 @@ Many excellent plotting tools are built on top of Matplotlib. "[Seaborn](https://seaborn.pydata.org/ "Seaborn: statistical data visualization") is a Python visualization library based on matplotlib. It provides a high-level interface for drawing attractive statistical graphics." Seaborn makes beautiful plots but is geared toward specific statistical plots, not general purpose plotting. It does have a powerful [faceting utility function](http://seaborn.pydata.org/tutorial/axis_grids.html) that I use regularly. +Seaborn 0.12 added [seaborn.objects](https://seaborn.pydata.org/tutorial/objects_interface.html), a second interface built on the grammar of graphics. It composes a plot from marks and statistical transforms instead of dispatching to a named plotting function, so it covers far more of the examples below than the classic interface does. It has no loess smoother and no regression confidence band, so those two examples are missing. + #### Interactive Plotting Libraries There are several tools that can make the kinds of plots described here. At present, I have little experience with them. If anyone would like to help add examples, please [get in touch](https://github.com/tdhopper/pythonplot.com).