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)
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)
As wealth per person (GDP per capita) increases, life expectancy rises quickly then tapers off. This shows a strong relationship between wealth and health.
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)
alpha across the three visuals above?alpha is a geom_point() argument that controls point opacity. Compare what happens to overlapping points as the value moves from near 0 toward 1.alpha controls transparency from 0 to 1. Higher values are more opaque; lower values are more transparent.
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)
color for a single color vs. multiple colors based on a variable?aes() or outside it.Single color: set color inside geom_point(color='...') (outside aes). Mapped colors: set color inside aes(color='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)
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)
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)
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)
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)
fill='steelblue' sits in the geom_boxplot() call versus where fill='cut' sits — one is a fixed argument to the geom, the other lives inside aes().Constant fill uses geom_boxplot(fill='steelblue'). Mapped fill uses aes(fill='cut') to color by variable.
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)
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)
price with a narrower binwidth and apply a different theme. Which choices improve readability?binwidth in geom_histogram() controls how wide each bar's price range is — smaller values show more detail. Try swapping in theme_bw(), theme_dark(), or theme_classic() to change the background and gridlines.p14 = (ggplot(diamonds, aes(x='price', fill='cut')) +
geom_histogram(color='white', binwidth=250) +
theme_bw() +
labs(x='Price (USD)', y='Frequency'))
p14.save("plotnine_figures/01_histogram_answer.png", dpi=200, width=8, height=6)
Conclusion
You learned how to build scatterplots, boxplots, and histograms with plotnine and how to control transparency, color mapping, labels, and themes.