Customizing styles#

There are typically three questions to ask yourself around considerations to make when you’re creating a plot:

  1. Are my data correctly represented and readable?

  2. Is the plot sufficiently labeled and/or annotated to make it understandable?

  3. If the plot is for more than personal consumption or exploration, is the content presented in such a way that it is sufficiently professional?

This third piece is what we focus on in this section: how to customize plots in matplotlib in ways that make the presentation of the plot professional and stand out amongst a sea of mediocre visualizations.

Before we start, let’s take a subplot plot with two subplots containing a line/scatter plot and a bar plot. In its initial state, it is a perfectly acceptable plot where all of our data are correctly represented in a form that suites them and all the content in our plot is fully labeled to be able to facilitate interpretation (satisfying questions 1 and 2 above with a resounding “yes”). The only style that was added was to change the color of the scattered data to distinguish it from the plot data (otherwise they are plotted in the same color).

You’ll notice that we’re making this into a function so we can run this again without having to rewrite the code for the plot since we won’t be changing that; we’ll only be changing the style.

%config InlineBackend.figure_format = 'retina'

import matplotlib.pyplot as plt

x = [1, 2, 3, 4, 5]
y1 = [1.5, 3.5, 2.5, 4.5, 3]
y2 = [1, 2, 3, 4, 5]
y3 = [1.5, 2.5, 3.5, 4.5, 5.5]

y_bar = [1.5, 3.5, 2.5, 4.5, 3]
x_bar = ["A", "B", "C", "D", "E"]


def myplot():
    fig, [ax_line, ax_bar] = plt.subplots(1, 2, figsize=(8, 4))

    ax_line.plot(x, y2, label="Model 1")
    ax_line.plot(x, y3, label="Model 2")
    ax_line.scatter(x, y1, label="Sample Data", color="darkgrey")
    ax_line.legend()
    ax_line.set_xlabel("x label")
    ax_line.set_ylabel("y label")
    ax_line.set_title("Title of my line plot")

    ax_bar.bar(x_bar, y_bar)
    ax_bar.set_xlabel("x label")
    ax_bar.set_ylabel("y label")
    ax_bar.set_title("Title of my bar plot")


myplot()
../../../_images/2ae7d5b8b1301e96c3b68937c147cdf128cae8f510a5cb50886c579ebcd3c182.png

Built in matplotlib styles#

Matplotlib comes with several predetermined styles that you can choose from and are listed in the documentation. A few of these are listed as:

  • ggplot

  • seaborn-v8_0

  • fivethirtyeight

Let’s see what happens when we use each of these and apply them to the plot above. We can do this using the style.use() method of the matplotlib.pyplot module and feeding in a name of a default style that’s been composed into a stylesheet:

plt.style.use("seaborn-v0_8")
myplot()
../../../_images/4bbd188e49c41dd625e70188408f2792402387c3a3802dde72a85af43ec32b75.png
plt.style.use("ggplot")
myplot()
../../../_images/2de8c5fc2f2bb35900415a4315f3956e7fe0eafaac99da55863f13af55fa2a00.png
plt.style.use("fivethirtyeight")
myplot()
../../../_images/6275d1a085ecf2c7e932117021accf936b569487947453dd0381759eadcced57.png

Once you set one of these styles, your plots will continue to follow that style while your current session is active or until you change the style. This doesn’t permanently change the default plotting style, but just for your active session.

Each of these meet certain aesthetic goals and you likely have your personal preferences as to which of the four styles above are most aesthetically appealing.

Customizing your own styles#

While you can select from any of the predetermined styles in matplotlib, you’re also welcome to customize your own. This is typically done by setting what are known as runtime configuration parameters or rcParams. Every parameter you could set is listed in the matplotlib documentation.

There are a few high-level groupings of these parameters and the most common to change are:

  • axes

  • figure

  • font

  • legend

  • lines

  • text

  • xtick

  • ytick

Let’s modify a few of these to understand how these changes can be made and how they’re reflected in our plots.

Let’s start again with our default style and begin modifying it:

plt.style.use("default")  # This resets the style to the default so we can modify it
myplot()
../../../_images/2ae7d5b8b1301e96c3b68937c147cdf128cae8f510a5cb50886c579ebcd3c182.png

First, let’s adjust the spines of the axes so that they’re only present on the bottom and left, not the top and right. To modify an individual rcParam, we directly set those params by setting them through plt.rcParams['param_name'] = param_value.

# Edit the style
plt.rcParams["axes.spines.bottom"] = True
plt.rcParams["axes.spines.top"] = False
plt.rcParams["axes.spines.left"] = True
plt.rcParams["axes.spines.right"] = False

myplot()
../../../_images/2b7d68fdfcbf2c77e8725b1ef3a3707a813db03d7474dee1829d53d0b47e44ea.png

Let’s also change the default positions of the titles and axes labels. Let’s make the title left-justified and the axes labels at the top and right for the y- and x-axis, respectively:

# Edit the style
plt.rcParams["axes.titlelocation"] = "left"
plt.rcParams["xaxis.labellocation"] = "right"
plt.rcParams["yaxis.labellocation"] = "top"

myplot()
../../../_images/8a094320e3d26817c568130e2a1a2c073d12cb6557999aff5e25a0f700b48ee3.png

Another way of making a plot a bit less cluttered is to remove the box from around the legend. This can be easily done by setting the legend.frameon parameter to False.

plt.rcParams["legend.frameon"] = False

myplot()
../../../_images/ba40ba07209e967aa891f6f5990d62ad6b607dcca20bfb9c97b5b833c3224a59.png

Next, let’s set the default font and fontsize. In matplotlib, the default font is set by selecting a font family, which has a list of options of individual fonts within that family (in case a particular font is not available on the machine you’re using). The default font is DejaVu Sans, and is always good to have as a backup in case your preferred font is not available. For clean, company plots, a good option is Arial Narrow. Let’s set that as the default font within the san-serif font family (sans serif fonts don’t have serifs which are the little end strokes present in serif fonts like Times New Roman).

# Set the font
plt.rcParams["font.sans-serif"] = ["Arial Narrow", "DejaVu Sans"]
plt.rcParams["font.family"] = "sans-serif"

# Adjust font sizes and weights
plt.rcParams["font.size"] = 10
plt.rcParams["axes.titlesize"] = 15  # This overrides the font size just for the title
plt.rcParams["axes.titleweight"] = "bold"

myplot()
../../../_images/454573d3681c072538be2b80b836bf2d1a0a9f73849f98a2d37dbb638021b667.png

“axes”: { “bottom”: true, “top”: false, “left”: true, “right”: false, “xmargin”: 0.02, “ymargin”: 0.02 }, “axis_labels”: { “pad”: -1, “size”: “large” }, “ticks”: { “direction”: “in”, “draw_minor”: true, “width_major”: 2, “width_minor”: 1, “size_major”: 10, “size_minor”: 6 }, “tick_labels”: { “size”: “large” }, “title”: { “location”: “center”, “pad”: -2, “size”: “x-large” }, “grid”: { “draw”: true, “axis”: “both”, “ticks”: “major”, “alpha”: 0.5, “width”: 0.5 }, “fonts”: { “family”: “sans-serif”, “sans-serif”: [“Arial”] }, “colors”: { “figure_background_color”: “white”, “plot_background_color”: “white”, “text_color”: “black”, “line_color”: “#333”, “axes_color”: “#555”, “grid_color”: “#555”, “tick_color”: “#555”, “tick_label_color”: “black”, “palette”: [ “#56B4DF”, “#D55E00”, “#009E73”, “#4EB67F”, “#E69F00”, “#CC79A7” ]

Next, let’s make the spines and ticks a light grey color. We want to keep the actual tick labels (the numbers along the axes) black, so we’ll need to set the x- and ytick.color parameters first, and then separately set the x- and ytick.labelcolor parameters so that the labels remain black.

# Set the colors of axis elements
plt.rcParams["axes.edgecolor"] = "lightgrey"
plt.rcParams["xtick.color"] = "lightgrey"
plt.rcParams["ytick.color"] = "lightgrey"

# Since x and ytick.color over rides the label color, we need to reset the tick labels to still be black
plt.rcParams["xtick.labelcolor"] = "black"
plt.rcParams["ytick.labelcolor"] = "black"

myplot()
../../../_images/48f3314ad0d44c55aea4ea828cdc44ee34572e7958d662138c9be8229d52b25e.png

It’s a bit difficult to read exactly where the points are without a grid, so let’s add in a grid, but just for the y-values. Let’s also make the color of the grid light grey and make the linewidth very small, let’s say 0.5 points so it’s just a hairline. We’ll need to turn on the grid and we’ll also set the grid to appear BELOW the plot instead of being drawn over the plot elements.

plt.rcParams["axes.linewidth"] = 0.5
plt.rcParams["grid.linewidth"] = 0.5
plt.rcParams["axes.grid"] = True
plt.rcParams["axes.grid.axis"] = "y"
plt.rcParams["grid.color"] = "lightgrey"
plt.rcParams["axes.axisbelow"] = True

myplot()
../../../_images/98e3b9ddf873cde10ea68cbc62957c53c45881c0a51a2c2aa14a362451855fab.png

Changing the default colors is also relatively simple. How matplotlib colors work is that are are a list of standard colors that matplotlib cycles through as new elements are placed on a plot of the same type. If you plot 3 lines, each will be in a different color, for example. That list is not infinite, so once you hit the maximum number of colors, the next line to be plotted cylces back around and is plotted with the first color on the list. You can enter your list of colors using the plt.cycle(color=[list_of_colors]) syntax as shown below:

plt.rcParams["axes.prop_cycle"] = plt.cycler(
    color=["#55b748", "#000000", "#ec008b", "#fdbf11", "#d2d2d2", "#000000"]
)  # Enter a list of colors to cycle through

myplot()
../../../_images/3297d7bd05333836e424f3a2ca30e458230fd856349e3ac05349c99eceb96ab2.png

Finally, notice the empty space between the x-axis and the first set of y-values at 1. Let’s remove the margin of space between those to make the plot tighter. We’ll also remove the y-axis spine since it’s redundant now.

plt.rcParams["axes.ymargin"] = 0
plt.rcParams["axes.spines.left"] = False

myplot()
../../../_images/2cd0d9ef6087f45869bf4e2217f7d5821093e6b2da5da848386891c6cafb9f18.png

Whether or not you love or hate the style choices made above, hopefully what you can see is that matplotlib provides extensive flexibility in customizing the look and feel of your plots, only limited by your creativity and imagination. Go wild and try making some changes that you find interesting and visually appealing!

Collecting your customizations into a stylesheet file#

We made many changes above and while you can certainly include all the plt.rcParams[param_to_change] = param_value statements into a header of each plot, you can alternatively collect them into a style sheet that you can simply activate with one line in each file of plt.style.use(path_to_style_file). Let’s do this for all the changes above. In this case, our file would be structure as follows, with each parameter being followed by a colon and the parameter value we want to set it to.

One thing to note for these files, no quotes are used around any entries

Contents of mystyle.mplstyle:

# Turn off all spines except for the bottom spine
axes.spines.bottom : True
axes.spines.top : False
axes.spines.left : False
axes.spines.right : False

# Adjust label alignments
axes.titlelocation : left
xaxis.labellocation : right
yaxis.labellocation : top

# Turn off the frame legend
legend.frameon : False

# Set the fonts, sizes, and weights
font.sans-serif : Arial Narrow, DejaVu Sans
font.family : sans-serif
font.size : 10
axes.titlesize : 15
axes.titleweight : bold

# Set the colors of axis elements
axes.edgecolor : lightgrey
xtick.color : lightgrey
ytick.color : lightgrey

# Since x and ytick.color over rides the label color, we need to reset the tick labels to still be black
xtick.labelcolor : black
ytick.labelcolor : black

# Turn on the grid and show the grid on the y-axis only
axes.grid : True
axes.grid.axis : y

# Set the default plotting colors
axes.prop_cycle : cycler('color', ['55b748','000000', 'ec008b', 'fdbf11', 'd2d2d2', '000000'])

# Make the grid and axes lines as thin as possible and lightgrey
axes.linewidth : 0.5
grid.linewidth : 0.5
grid.color : lightgrey

# Set the axis elements, including the grid, to be below the data that's plotted
axes.axisbelow : True

# Eliminate the vertical empty space between the x-axis and the first plotted y-values
axes.ymargin : 0
plt.style.use("default")  # Resets to the original style
plt.style.use(
    "mystyle.mplstyle"
)  # If this file is not in your current directory, you'll need include the path to the file
myplot()
../../../_images/2cd0d9ef6087f45869bf4e2217f7d5821093e6b2da5da848386891c6cafb9f18.png

What if I’m not an artist?#

Most of us aren’t artists and being able to determine what will look professional is not always intuitive. For that reason, following style guides that have been developed by others who consider these challenges is usually a good idea. For example, the style developed above closely follows many of the principles and aesthetic choices of the Urban Institute’s data visualization style guide.

One vital word of caution: good style does not imply effective communication. The clarity of communication of a message and the accuracy of the implied interpretation of the data in a visualization should be the top concerns.

As we’ll reference later, there are numerous collections of best practices when it comes to data visualization, and below are an assortment of resources for learning more about evidence-driven best practices for effective data visualization.

Rougier, N.P., Droettboom, M. and Bourne, P.E., 2014. Ten simple rules for better figures. PLoS computational biology, 10(9), p.e1003833.

Ajani, K., Lee, E., Xiong, C., Knaflic, C.N., Kemper, W. and Franconeri, S., 2021. Declutter and focus: Empirically evaluating design guidelines for effective data communication. IEEE Transactions on Visualization and Computer Graphics, 28(10), pp.3351-3364.

Franconeri, S.L., Padilla, L.M., Shah, P., Zacks, J.M. and Hullman, J., 2021. The science of visual data communication: What works. Psychological Science in the public interest, 22(3), pp.110-161.