Skip to content

Commit 2d58005

Browse files
committed
Add hvPlot (Bokeh) as a comparison package
1 parent 331bac9 commit 2d58005

6 files changed

Lines changed: 635 additions & 1 deletion

File tree

Examples.ipynb

Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,72 @@
9090
"\n",
9191
"get_ipython().display_formatter.formatters[\"image/png\"].for_type(\n",
9292
" lp.plot.core.PlotSpec, _lets_plot_png\n",
93+
")\n",
94+
"\n",
95+
"import atexit\n",
96+
"import io\n",
97+
"import logging\n",
98+
"\n",
99+
"import holoviews as hv\n",
100+
"import hvplot.pandas # registers the .hvplot accessor on pandas objects\n",
101+
"from bokeh.io.export import get_screenshot_as_png\n",
102+
"from bokeh.models import Plot\n",
103+
"\n",
104+
"hv.extension(\"bokeh\")\n",
105+
"logging.getLogger(\"bokeh.io.export\").setLevel(logging.ERROR)\n",
106+
"hv.plotting.bokeh.ElementPlot.toolbar = None\n",
107+
"hv.plotting.bokeh.ElementPlot.fontscale = 1.6\n",
108+
"\n",
109+
"_bokeh_driver = None\n",
110+
"\n",
111+
"\n",
112+
"def _bokeh_webdriver():\n",
113+
" \"\"\"Headless Chrome for Bokeh's PNG export.\n",
114+
"\n",
115+
" Bokeh screenshots plots through Selenium. Reuse the Chrome for Testing\n",
116+
" build Kaleido already downloads; Selenium Manager supplies the matching\n",
117+
" chromedriver.\n",
118+
" \"\"\"\n",
119+
" global _bokeh_driver\n",
120+
" if _bokeh_driver is None:\n",
121+
" from selenium.webdriver.chrome.options import Options\n",
122+
" from selenium.webdriver.chrome.webdriver import WebDriver as Chrome\n",
123+
"\n",
124+
" options = Options()\n",
125+
" options.add_argument(\"--headless=new\")\n",
126+
" options.add_argument(\"--hide-scrollbars\")\n",
127+
" options.add_argument(\"--force-device-scale-factor=1\")\n",
128+
" options.add_argument(\"--force-color-profile=srgb\")\n",
129+
" options.add_argument(\"--no-sandbox\")\n",
130+
" try:\n",
131+
" from choreographer.browsers.chromium import Chromium\n",
132+
"\n",
133+
" options.binary_location = str(Chromium.find_browser(skip_local=False))\n",
134+
" except Exception: # fall back to whatever Chrome is on the system\n",
135+
" pass\n",
136+
" _bokeh_driver = Chrome(options=options)\n",
137+
" atexit.register(_bokeh_driver.quit)\n",
138+
" return _bokeh_driver\n",
139+
"\n",
140+
"\n",
141+
"def _hvplot_png(obj):\n",
142+
" \"\"\"Render an hvPlot/HoloViews Bokeh figure to PNG for the cell output.\"\"\"\n",
143+
" figure = hv.render(obj, backend=\"bokeh\")\n",
144+
" models = figure.references()\n",
145+
" for model in models:\n",
146+
" if \"toolbar_location\" in model.properties():\n",
147+
" model.toolbar_location = None\n",
148+
" panes = [m for m in models if isinstance(m, Plot)]\n",
149+
" if len(panes) == 1: # leave faceted grids at their per-panel size\n",
150+
" panes[0].width, panes[0].height = 750, 750\n",
151+
" image = get_screenshot_as_png(figure, driver=_bokeh_webdriver())\n",
152+
" buffer = io.BytesIO()\n",
153+
" image.save(buffer, format=\"png\")\n",
154+
" return buffer.getvalue()\n",
155+
"\n",
156+
"\n",
157+
"get_ipython().display_formatter.formatters[\"image/png\"].for_type(\n",
158+
" hv.core.dimension.Dimensioned, _hvplot_png\n",
93159
")"
94160
]
95161
},
@@ -397,6 +463,27 @@
397463
" .label(title=\"Number of Cars by Make\"))"
398464
]
399465
},
466+
{
467+
"cell_type": "code",
468+
"execution_count": null,
469+
"metadata": {
470+
"tags": [
471+
"ex",
472+
"name:bar-counts",
473+
"package:hvplot"
474+
]
475+
},
476+
"outputs": [],
477+
"source": [
478+
"\"\"\"hvPlot exposes Bokeh through a `.hvplot`\n",
479+
"accessor that mirrors pandas' own `.plot`.\n",
480+
"\"\"\"\n",
481+
"(mpg[\"manufacturer\"]\n",
482+
" .value_counts(sort=False)\n",
483+
" .hvplot.barh(\n",
484+
" title=\"Number of Cars by Make\"))"
485+
]
486+
},
400487
{
401488
"cell_type": "markdown",
402489
"metadata": {},
@@ -545,6 +632,21 @@
545632
" .add(so.Bars(), so.Hist(binwidth=2)))"
546633
]
547634
},
635+
{
636+
"cell_type": "code",
637+
"execution_count": null,
638+
"metadata": {
639+
"tags": [
640+
"ex",
641+
"name:simple-histogram",
642+
"package:hvplot"
643+
]
644+
},
645+
"outputs": [],
646+
"source": [
647+
"mpg.hvplot.hist(\"cty\", bins=12)"
648+
]
649+
},
548650
{
549651
"cell_type": "markdown",
550652
"metadata": {},
@@ -700,6 +802,26 @@
700802
" title=\"Engine Displacement in Liters vs Highway MPG\"))"
701803
]
702804
},
805+
{
806+
"cell_type": "code",
807+
"execution_count": null,
808+
"metadata": {
809+
"tags": [
810+
"ex",
811+
"name:scatter-plot",
812+
"package:hvplot"
813+
]
814+
},
815+
"outputs": [],
816+
"source": [
817+
"mpg.hvplot.scatter(\n",
818+
" x=\"displ\", y=\"hwy\",\n",
819+
" xlabel=\"Engine Displacement in Liters\",\n",
820+
" ylabel=\"Highway MPG\",\n",
821+
" title=\"Engine Displacement in Liters \"\n",
822+
" \"vs Highway MPG\")"
823+
]
824+
},
703825
{
704826
"cell_type": "markdown",
705827
"metadata": {},
@@ -1043,6 +1165,22 @@
10431165
")"
10441166
]
10451167
},
1168+
{
1169+
"cell_type": "code",
1170+
"execution_count": null,
1171+
"metadata": {
1172+
"tags": [
1173+
"ex",
1174+
"name:scatter-plot-with-colors",
1175+
"package:hvplot"
1176+
]
1177+
},
1178+
"outputs": [],
1179+
"source": [
1180+
"mpg.hvplot.scatter(\n",
1181+
" x=\"displ\", y=\"hwy\", by=\"class\")"
1182+
]
1183+
},
10461184
{
10471185
"cell_type": "markdown",
10481186
"metadata": {},
@@ -1199,6 +1337,23 @@
11991337
" .label(x=\"City MPG\", y=\"Highway MPG\"))"
12001338
]
12011339
},
1340+
{
1341+
"cell_type": "code",
1342+
"execution_count": null,
1343+
"metadata": {
1344+
"tags": [
1345+
"ex",
1346+
"name:scatter-plot-with-size",
1347+
"package:hvplot"
1348+
]
1349+
},
1350+
"outputs": [],
1351+
"source": [
1352+
"mpg.hvplot.scatter(\n",
1353+
" x=\"cty\", y=\"hwy\",\n",
1354+
" s=\"cyl\", scale=4, alpha=0.5)"
1355+
]
1356+
},
12021357
{
12031358
"cell_type": "markdown",
12041359
"metadata": {},
@@ -1338,6 +1493,25 @@
13381493
")"
13391494
]
13401495
},
1496+
{
1497+
"cell_type": "code",
1498+
"execution_count": null,
1499+
"metadata": {
1500+
"tags": [
1501+
"ex",
1502+
"name:scatter-plot-with-facet",
1503+
"package:hvplot"
1504+
]
1505+
},
1506+
"outputs": [],
1507+
"source": [
1508+
"(mpg.hvplot.scatter(\n",
1509+
" x=\"displ\", y=\"hwy\", by=\"class\",\n",
1510+
" subplots=True, fontscale=0.65,\n",
1511+
" width=185, height=185)\n",
1512+
" .cols(4))"
1513+
]
1514+
},
13411515
{
13421516
"cell_type": "markdown",
13431517
"metadata": {},
@@ -1482,6 +1656,27 @@
14821656
")"
14831657
]
14841658
},
1659+
{
1660+
"cell_type": "code",
1661+
"execution_count": null,
1662+
"metadata": {
1663+
"tags": [
1664+
"ex",
1665+
"name:scatter-plot-with-facets",
1666+
"package:hvplot"
1667+
]
1668+
},
1669+
"outputs": [],
1670+
"source": [
1671+
"\"\"\"`row` and `col` build a HoloViews `GridSpace`,\n",
1672+
"which sorts its panels by the facet values.\n",
1673+
"\"\"\"\n",
1674+
"mpg.hvplot.scatter(\n",
1675+
" x=\"displ\", y=\"hwy\",\n",
1676+
" row=\"cyl\", col=\"drv\", subplots=True,\n",
1677+
" fontscale=0.8, width=230, height=180)"
1678+
]
1679+
},
14851680
{
14861681
"cell_type": "markdown",
14871682
"metadata": {},
@@ -1778,6 +1973,26 @@
17781973
" .add(so.Bar(), so.Count(), so.Stack()))"
17791974
]
17801975
},
1976+
{
1977+
"cell_type": "code",
1978+
"execution_count": null,
1979+
"metadata": {
1980+
"tags": [
1981+
"ex",
1982+
"name:stacked-bar-chart",
1983+
"package:hvplot"
1984+
]
1985+
},
1986+
"outputs": [],
1987+
"source": [
1988+
"(diamonds\n",
1989+
" .groupby([\"cut\", \"clarity\"])\n",
1990+
" .size()\n",
1991+
" .unstack()\n",
1992+
" .hvplot.bar(stacked=True, rot=45,\n",
1993+
" legend=\"top_left\"))"
1994+
]
1995+
},
17811996
{
17821997
"cell_type": "code",
17831998
"execution_count": null,
@@ -1907,6 +2122,26 @@
19072122
" .add(so.Bar(), so.Count(), so.Dodge()))"
19082123
]
19092124
},
2125+
{
2126+
"cell_type": "code",
2127+
"execution_count": null,
2128+
"metadata": {
2129+
"tags": [
2130+
"ex",
2131+
"name:dodged-bar-chart",
2132+
"package:hvplot"
2133+
]
2134+
},
2135+
"outputs": [],
2136+
"source": [
2137+
"(diamonds\n",
2138+
" .groupby([\"cut\", \"clarity\"])\n",
2139+
" .size()\n",
2140+
" .unstack()\n",
2141+
" .hvplot.bar(stacked=False, rot=45,\n",
2142+
" legend=\"top_left\"))"
2143+
]
2144+
},
19102145
{
19112146
"cell_type": "code",
19122147
"execution_count": null,
@@ -2072,6 +2307,27 @@
20722307
"Image(fig.to_image(format=\"png\", width=750, height=750))"
20732308
]
20742309
},
2310+
{
2311+
"cell_type": "code",
2312+
"execution_count": null,
2313+
"metadata": {
2314+
"tags": [
2315+
"ex",
2316+
"name:stacked-kde",
2317+
"package:hvplot"
2318+
]
2319+
},
2320+
"outputs": [],
2321+
"source": [
2322+
"\"\"\"`xlim` only clips the axis. Unlike ggplot2's\n",
2323+
"`xlim()` it doesn't drop rows before the\n",
2324+
"densities are estimated.\n",
2325+
"\"\"\"\n",
2326+
"diamonds.hvplot.kde(\n",
2327+
" y=\"depth\", by=\"cut\",\n",
2328+
" alpha=0.1, xlim=(55, 70))"
2329+
]
2330+
},
20752331
{
20762332
"cell_type": "code",
20772333
"execution_count": null,
@@ -2186,6 +2442,21 @@
21862442
"(so.Plot(ts, x=\"date\", y=\"value\")\n",
21872443
" .add(so.Line()))"
21882444
]
2445+
},
2446+
{
2447+
"cell_type": "code",
2448+
"execution_count": null,
2449+
"metadata": {
2450+
"tags": [
2451+
"ex",
2452+
"name:timeseries",
2453+
"package:hvplot"
2454+
]
2455+
},
2456+
"outputs": [],
2457+
"source": [
2458+
"ts.hvplot.line(x=\"date\", y=\"value\")"
2459+
]
21892460
}
21902461
],
21912462
"metadata": {

INTRO.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ There are several tools that can make the kinds of plots described here. At pres
3333

3434
"[Lets-Plot](https://lets-plot.org/ "Lets-Plot: an open-source plotting library for statistical data") is an open-source plotting library for statistical data," written by JetBrains and modeled on the grammar of graphics. Its Python API tracks ggplot2 closely enough that most of the examples below translate line for line. I provide Lets-Plot examples rendered as static images.
3535

36-
"[Bokeh](http://bokeh.pydata.org/en/latest/ "Python interactive visualization library") is a Python interactive visualization library that targets modern web browsers for presentation."
36+
"[Bokeh](http://bokeh.pydata.org/en/latest/ "Python interactive visualization library") is a Python interactive visualization library that targets modern web browsers for presentation." The Bokeh examples below go through [hvPlot](https://hvplot.holoviz.org/), which adds an `.hvplot` accessor to data frames that deliberately echoes the pandas `.plot` API, so most of these plots are one call with a few keyword arguments. hvPlot has no regression line or loess smoother, so those two examples are missing. I provide hvPlot examples rendered as static images.
3737

3838
"[bqplot](https://github.com/bloomberg/bqplot) is a Grammar of Graphics-based interactive plotting framework for the Jupyter notebook."
3939

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,12 @@ dependencies = [
2222
"plotly>=5.24",
2323
"altair>=5.0",
2424
"vl-convert-python>=1.0", # Required for Altair PNG export
25+
"hvplot>=0.12", # Bokeh via the pandas .hvplot accessor
2526

2627
# Image rendering
2728
"kaleido>=1.0", # v1+ requires Chrome
2829
"pillow>=10.0",
30+
"selenium>=4.11", # Required for Bokeh PNG export
2931

3032
# Statistical tools
3133
"statsmodels>=0.14",

render.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"plotnine": "plotnine",
2525
"lets-plot": "lets-plot",
2626
"plotly": "plotly",
27+
"hvplot": "hvplot (Bokeh)",
2728
"altair": "Altair",
2829
"ggplot": "ggplot2 (R)",
2930
}

0 commit comments

Comments
 (0)