Visualization with plotnine in Python

Visualization is a key part of statistical analyses, especially in systems engineering. In this tutorial, we'll learn to visualize data with plotnine (the Python port of ggplot2).

Please follow along using the code below!

Getting Started

Loading Packages

import pandas as p
from plotnine import *
from gapminder import gapminder as gapminder

# diamonds bundled with plotnine
from plotnine.data import diamonds

Gapminder data

# View it in the console
gapminder
# Glimpse-like summary
gapminder.dtypes, gapminder.shape

Your first scatterplot

# make the folder we save figures into
import os; os.makedirs("plotnine_figures", exist_ok=True)
p1 = ggplot(data=gapminder, mapping=aes(x='gdpPercap', y='lifeExp'))
p1.save("plotnine_figures/01_scatter_base.png", dpi=200, width=6, height=4)
Blank scatterplot base layer with GDP per capita mapped to x and life expectancy mapped to y, before any points are drawn

Add points with + geom_point().

p2 = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp')) + geom_point())
p2.save("plotnine_figures/01_scatter_points.png", dpi=200, width=6, height=4)
Scatterplot of GDP per capita versus life expectancy with points plotted for every country-year
LC 01
What kind of relationship does this graph show? Why might it matter to policymakers?

Transparency (alpha)

p3 = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp')) + geom_point(alpha=0.2))
p3.save("plotnine_figures/01_scatter_alpha02.png", dpi=200, width=6, height=4)
p4 = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp')) + geom_point(alpha=0.5))
p4.save("plotnine_figures/01_scatter_alpha05.png", dpi=200, width=6, height=4)
p5 = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp')) + geom_point(alpha=1))
p5.save("plotnine_figures/01_scatter_alpha1.png", dpi=200, width=6, height=4)
Scatterplot of GDP per capita versus life expectancy with point transparency alpha set to 0.2 Scatterplot of GDP per capita versus life expectancy with point transparency alpha set to 0.5 Scatterplot of GDP per capita versus life expectancy with point transparency alpha set to 1 (fully opaque)
LC 02
What happens when you change alpha across the three visuals above?

Color: constant vs mapped

# Single color
p6 = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp')) +
  geom_point(alpha=0.5, color='steelblue'))
p6.save("plotnine_figures/01_scatter_single_color.png", dpi=200, width=6, height=4)

# Color mapped by continent
p7 = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp', color='continent')) +
  geom_point(alpha=0.5))
p7.save("plotnine_figures/01_scatter_mapped_color.png", dpi=200, width=6, height=4)
Scatterplot of GDP per capita versus life expectancy with all points set to a single steelblue color Scatterplot of GDP per capita versus life expectancy with point color mapped to continent
LC 03
Where do you place color for a single color vs. multiple colors based on a variable?

Improving our visualizations

p8 = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp', color='continent')) +
  geom_point(alpha=0.5) +
  labs(x='GDP per capita (USD)',
       y='Life Expectancy (years)',
       color='Continent',
       title='Does Wealth affect Health?',
       subtitle='Global Health Trends by Continent',
       caption='Points display individual country-year observations.'))
p8.save("plotnine_figures/01_scatter_improved.png", dpi=200, width=8, height=6)
Scatterplot of GDP per capita versus life expectancy colored by continent with title, subtitle, axis labels, and caption added

You can save visuals as objects to reuse them.

myviz = (ggplot(gapminder, aes(x='gdpPercap', y='lifeExp', color='continent')) +
  geom_point(alpha=0.5) +
  labs(x='GDP per capita (USD)', y='Life Expectancy (years)', color='Continent',
       title='Does Wealth affect Health?', subtitle='Global Health Trends by Continent',
       caption='Points display individual country-year observations.'))

myviz.save("plotnine_figures/01_myviz_default.png", dpi=200, width=8, height=6)
(myviz + theme_bw()).save("plotnine_figures/01_myviz_bw.png", dpi=200, width=8, height=6)
(myviz + theme_dark()).save("plotnine_figures/01_myviz_dark.png", dpi=200, width=8, height=6)
(myviz + theme_classic()).save("plotnine_figures/01_myviz_classic.png", dpi=200, width=8, height=6)
The saved myviz scatterplot rendered with plotnine's default theme The saved myviz scatterplot rendered with theme_bw() The saved myviz scatterplot rendered with theme_dark() The saved myviz scatterplot rendered with theme_classic()

Visualizing diamonds data

diamonds.head(3)
diamonds.dtypes, diamonds.shape

Boxplots by cut

p9 = (ggplot(diamonds, aes(x='cut', y='price', group='cut')) + geom_boxplot())
p9.save("plotnine_figures/01_boxplot_default.png", dpi=200, width=6, height=4)
Boxplot of diamond price by cut with plotnine's default boxplot fill
p10 = (ggplot(diamonds, aes(x='cut', y='price', group='cut')) +
  geom_boxplot(fill='steelblue'))
p10.save("plotnine_figures/01_boxplot_fill.png", dpi=200, width=6, height=4)
Boxplot of diamond price by cut with every box filled a single steelblue color
p11 = (ggplot(diamonds, aes(x='cut', y='price', group='cut', fill='cut')) +
  geom_boxplot())
p11.save("plotnine_figures/01_boxplot_mapped_fill.png", dpi=200, width=6, height=4)
Boxplot of diamond price by cut with fill color mapped to the cut variable
LC 04
Why do the two boxplot versions look different? What changed in the code to create those effects?

Visualizing Distributions

p12 = (ggplot(diamonds, aes(x='price', fill='cut')) +
  geom_histogram(color='white') +
  labs(x='Price (USD)', y='Frequency', title='US Diamond Sales'))
p12.save("plotnine_figures/01_histogram_default.png", dpi=200, width=8, height=6)
Stacked histogram of diamond price filled by cut, default binwidth

Try adjusting binwidth and theme

p13 = (ggplot(diamonds, aes(x='price', fill='cut')) +
  geom_histogram(color='white', binwidth=500) +
  theme_classic() +
  labs(x='Price (USD)', y='Frequency', title='US Diamond Sales (binwidth=500)'))
p13.save("plotnine_figures/01_histogram_binwidth.png", dpi=200, width=8, height=6)
Stacked histogram of diamond price filled by cut with binwidth set to 500 and theme_classic() applied
LC 05
Make a histogram of price with a narrower binwidth and apply a different theme. Which choices improve readability?

Conclusion

You learned how to build scatterplots, boxplots, and histograms with plotnine and how to control transparency, color mapping, labels, and themes.