Matplotlib: Place Things Where You Want#
I have recently done a couple of seminars on matplotlib. Among these seminars I demonstrate how to conceptually approach matplotlib
: its 2 apis, convenience layers vs essential layers, dichotomous artist types, and coordinate systems/transforms.
Once you understand these ideas, the entire utility of matplotlib
begins to snap into place. This week, I want to highlight one of these concepts: coordinate systems & transforms. The first step to making an aesthetically appealing graphic is to have confidence in placing Artists
where you want them. Their existance (or lack thereof) on your Figure
should not be a surprise, and by understanding matplotlib
s coordinate systems we gain more power over the aesthetic of our plots.
Coordinate Systems#
Matplotlib has many coordinate systems, but in my experience the three most important ones are the “data”, “axes” coordinate systems. These both refer to coordinate systems that exist within an Axes
.
In matplotlib
, the “axes” systems are represents a proportional coordinate systems within the Axes
. This space ranges from 0-1 on its horizontal and vertical dimensions, where in the x-dimension 0 is the left-hand side of the Axes
and 1 is the right-hand side. Then in the vertical dimension 0 represents the bottom of the Axes
, and 1 represents the top
. This means that a coordinate of (.3, .8)
represents a coordinate that exists 30% away from the left side of the Axes
and 80%
away from the bottom.
Contrarily, the “data” coordinate system exists embedded within the axes coordinates. This coordinate system is bound by the x
and y
axis of a given Axes
instance.
Transforms#
These coordinate systems are important because we can use them to place Artists
on our figures and axes through their transform
parameter. All matplotlib Artists
have a transform parameter that we can use to instruct matplotlib how to interpret the supplied coordinates. In general the usecases for these transforms as follows:
coordinate |
transform |
display purpose |
---|---|---|
“axes” |
ax.transAxes |
display an artist on an |
“data” |
ax.transData |
display an artist on an |
We typically think of Axes
as existing in a data-space. When we run the following code, we create a plot that has a line who exists in data-space. The points we feed into the line as x and y coordinates are drawn according to the x and y-axis of our Axes
from matplotlib.pyplot import subplots, rcParams
from numpy import arange
# force solid white background on figure
xs = arange(1, 10)
fig, ax = subplots()
fig.set_facecolor('white')
ax.plot(xs, xs ** 2, label="line");
But what if we wanted to draw something on these axes that ignored data space. Like a Text
that always appears in the upper left hand corner of the plot? To insert Artists
in a static position on the Axes
we need to use the axes proportional coordinate system, not the axes data coordinate system.
from matplotlib.pyplot import subplots
from numpy import arange
# Aesthetics
rcParams['font.size'] = 12
for side in ['left', 'right', 'top', 'bottom']:
rcParams[f'axes.spines.{side}'] = True
fig, axes = subplots(1, 2, figsize=(12, 6))
fig.set_facecolor('white')
for ax, max_x in zip(axes.flat, [10, 20]):
xs = arange(1, max_x)
ax.plot(xs, xs ** 2, label="line")
ax.text(
x=.05, y=.95,
s='proportional coords (.05, .95)',
transform=ax.transAxes
)
# ax.text defaults to using transform=ax.transData
ax.text(
x=5, y=20,
s='data coords (5, 20)',
transform=ax.transData
)
On the plots above, you can see there is a text in the upper left-hand corner. By inputting x=.05, y=.95, ... transform=ax.transAxes
I was able to align the left hand side of that text to 5% acrpss the x-axis, and 95% up the y-axis.
Contrarily, when I specified x=5, y=20, transform=ax.transData
I was able to insert text at the location of (5, 20) on the x and y axis. This text label seemingly shifted places from the left hand Axes
to the right hand Axes
. This is because the scale of our x and y axis changed which shifts the overall placement of the text.
We can use this idea of transforms and coordinate systems to create very powerful visualizations. In fact, I used this idea to replicate a plot I encountered on Twitter where I created multiple nested Axes
on a blended coordinate system to draw histograms that overlap with bars from a barplot as a way to highlight the average value and spread of a couple datasets:
from matplotlib.pyplot import subplots, show, rcParams
from numpy.random import default_rng
from pandas import DataFrame
# Aesthetics: increase font size & remove spines
rcParams['figure.facecolor'] = (1, 1, 1, 1)
rcParams['font.size'] = 16
for side in ['left', 'right', 'top', 'bottom']:
rcParams[f'axes.spines.{side}'] = False
# Generate some data
rng = default_rng(0)
df = DataFrame({
'$\mu=40,\sigma=3$': rng.normal(40, 3, size=300),
'$\mu=65,\sigma=5$': rng.normal(65, 5, size=300),
'$\mu=50,\sigma=8$': rng.normal(50, 10, size=300),
})
agg_df = df.mean()
fig, ax = subplots(figsize=(8,5))
fig.set_facecolor('white')
bars = ax.bar(agg_df.index, agg_df)
for rect, label in zip(bars, df.columns):
x_data_pos = rect.get_x()
# ax.get_xaxis_transform() returns a "blended transform"
# this means that I can specify my x-coordinates
# in data units, and my y-coordinate
# in proportional coordinates
hist_ax = ax.inset_axes(
(x_data_pos, 0, rect.get_width(), 1),
transform=ax.get_xaxis_transform(),
sharey=ax # share the y-axis with the parent `Axes`
)
# draw a histogram from the dataset onto this new Axes
hist_ax.hist(
df[label], orientation='horizontal',
ec='gray', fc='white',
alpha=.6, rwidth=.7
)
# make the background of our child axes invisible (or else it hides the bar)
hist_ax.patch.set_alpha(0)
hist_ax.xaxis.set_visible(False)
hist_ax.yaxis.set_visible(False)
ax.patch.set_color('#f4f4f4')
ax.yaxis.grid(color='lightgray')
ax.yaxis.set_tick_params(width=0)
ax.set_axisbelow(True)
ax.set_title('Insetting a histogram on each bar\nof a bar chart');
And there you have it- leveraging matplotlib
’s coordinate systems through transform
! By understanding this concept, you will have the ability to place anything anywhere on your plots. This opens the door to create powerful visualizations by having complete control of the placement of your annotations, legends, and other Artists
. This matplotlib binge has been a lot of fun, and I can’t wait to share with you what visualizations I’ll come up with next.