How to make publication-quality Matplotlib plots

This guide is intended for Matplotlib ≥2.0; if you are using an older version, please go here.

Before the days of matplotlib, astronomers often used Supermongo to generate their figures, which I find always look really great (notwithstanding the torturous path it takes to get there). For example, here is a very basic SM macro that I wrote to plot this data, and below is the resulting figure:

Alas, the same cannot be said for figures generated with Matplotlib. Although the release of Matplotlib 2.0, came with many improvements to the default aesthetics, it still leaves a lot to be desired. Here is the same figure made using this python script and the matplotlibrc defaults:

It's certainly an improvement upon this monstrosity, but a number of problems remain, including: the text size is too small compared to the other elements in the figure, there are no minor tickmarks on the y-axis, and the tickmarks are too small.

Almost everything that I found needed adjustment can be changed from one's matplotlibrc file. This is what mine looks like:


    # Set the backend, otherwise the figure won't show up. Note that this will 
    # depend on your system setup; to see which backend is the default,
    # run "matplotlib.get_backend()" in the Python interpreter.
    backend                 :  GTK3Agg    

    # Increase the default DPI, and change the file type from png to pdf 
    savefig.dpi             :   300
    savefig.format          :   pdf

    # Instead of individually increasing font sizes, point sizes, and line 
    # thicknesses, I found it easier to just decrease the figure size so
    # that the line weights of various components still agree 
    figure.figsize          :   4,4

    # Turn on minor ticks, top and right axis ticks, and change the direction to "in" 
    xtick.top               :   True
    ytick.right             :   True
    xtick.minor.visible     :   True
    ytick.minor.visible     :   True
    ytick.direction         :   in
    xtick.direction         :   in
            
    # Increase the major and minor tick-mark lengths 
    xtick.major.size        :   6  # default 3.5
    ytick.major.size        :   6  # default 3.5
    xtick.minor.size        :   3  # default 2 
    ytick.minor.size        :   3  # default 2 

    # Change the tick-mark and axes widths, as well as the widths of plotted lines,
    # to be consistent with the font weight 
    xtick.major.width       :   1  # default 0.8
    ytick.major.width       :   1  # default 0.8
    xtick.minor.width       :   1  # default 0.6
    ytick.minor.width       :   1  # default 0.6
    axes.linewidth          :   1  # default 0.8 
    lines.linewidth         :   1  # default 1.5 

    # Increase the padding between the ticklabels and the axes, to prevent 
    # overlap in the lower lefthand corner
    xtick.major.pad         :   4  # default 3.5
    ytick.major.pad         :   4  # default 3.5 
    xtick.minor.pad         :   4  # default 3.5 
    ytick.minor.pad         :   4  # default 3.5 
 
    # Turn off the legend frame and reduce the space between the point and the label  
    legend.frameon          :   False
    legend.handletextpad    :   0.0
    

The final touch is to set the axis limits, in order to obtain the full range of ticklabels. Since this will be specific to individual figures, it should be done in the source code, and I've added it below:

    import matplotlib.pyplot as plt
    import numpy as np

    x, y = np.loadtxt("data.txt", skiprows=1, unpack=True)
    x = 10 ** x

    fig, ax = plt.subplots()
    ax.semilogx(x, y, 'o', mec='k', label="data")
    ax.legend(loc=2, title="Legend")
    ax.set_xlabel(r"Normal text vs. ${\rm math\, text}$")
    ax.set_ylabel(r"A B C $\alpha$ $\beta$ $\gamma$") 

    #
    # Set axis limits 
    #
    ax.set_ylim(0, 10)
    ax.set_xlim(10, 1E5)

    fig.savefig("plot.pdf")                                                                   
    plt.close(fig)
    

Here is the result:

A bit better! If you're feeling brave, take a dive down the font rabbit hole with me. The default font Bitstream Vera / DejaVu Sans suffers from an unfortunate case of ugly 1's, a number that shows up a lot when you are plotting logarithmic quantities.

While the beautiful vector fonts of SM are out of reach, a great alternative is Computer Modern Bright. Install it on your system, make sure it works in LaTeX, and then make the following changes / additions to the above matplotlibrc:

    # Computer Modern Bright has a much lower font weight, so the tick mark and 
    # line widths should be adjusted accordingly. 
    xtick.major.width       :   0.6  # default 0.8
    ytick.major.width       :   0.6  # default 0.8
    axes.linewidth          :   0.6  # default 0.8 
    lines.linewidth         :   0.6  # default 1.5 
    lines.markeredgewidth   :   0.6  # default 1

    # The magic sauce
    text.usetex             :  True     
    text.latex.preamble     :  \usepackage[T1]{fontenc}, \usepackage{cmbright}
    

And here is what comes out:

Finally, a huge thanks to Gabriel-Dominique Marleau who made many helpful suggestions that improved this page.