Right, let’s talk about saving your figures. This is where your beautiful, painstakingly crafted visualization goes from a fleeting moment in a Jupyter notebook to an actual, tangible artifact you can put in a paper, a presentation, or on a website. It’s the last step, and it’s shockingly easy to botch. I’ve seen more people than I can count—myself included—get a perfect plot on screen only to save it as a pixelated mess because they didn’t understand the mechanics. Let’s fix that.

The core command is deceptively simple: plt.savefig(). But its power is in the details—the keyword arguments you feed it.

import matplotlib.pyplot as plt
import numpy as np

# Let's make a simple figure to work with
x = np.linspace(0, 10, 100)
y = np.sin(x)

fig, ax = plt.subplots(figsize=(5, 3)) # We'll talk about this figsize in a second
ax.plot(x, y)
ax.set_title("A Very Important Sine Wave")

# The magic line. This saves the current figure.
plt.savefig('my_plot.png')

Boom. You now have a file called my_plot.png. But is it good? Probably not. Let’s make it good.

The Big Three: Raster, Vector, and The DPI Illusion

First, you need to choose a format. This isn’t just a file extension; it’s a fundamental choice between two types of graphics: raster and vector.

  • Raster (PNG, JPG): Think of these as a grid of pixels, like a digital photograph. You specify a fixed size (e.g., 1000x800 pixels), and that’s it. Zoom in far enough, and you’ll see the individual squares. PNG is lossless (perfect for plots) and handles transparency. JPG is lossy (bad for sharp lines and text) and does not; avoid it for plots.
  • Vector (PDF, SVG): Think of these as a set of mathematical instructions: “draw a line from here to here, render this text in this font at this size.” They are infinitely scalable. You can zoom in until the end of time and the lines will remain razor-sharp. This is almost always what you want for academic papers and presentations where resolution requirements can change.

So, why would anyone not use vector? Two reasons: complexity and compatibility. A vector file of a incredibly complex plot (e.g., millions of tiny points) can become huge and slow to render. And sometimes, a journal’s submission system might explicitly ask for a PNG. But as a default, start with PDF.

Now, here’s the part everyone gets wrong: DPI (Dots Per Inch).

DPI is a raster concept. It is utterly meaningless for vector formats like PDF and SVG. I’ll say it again: saving a figure as 'my_plot.pdf', dpi=300 is a no-op. The DPI setting is a ghost in the machine for vector files.

So what does control the size of a vector export? The physical dimensions of the figure itself, set by figsize in plt.subplots(). You designed your plot at a certain size on screen; that’s the size it will be saved as a vector. DPI only enters the picture when you save to a raster format like PNG. It determines how many pixels are used to represent each “inch” of your figure’s figsize.

# Good: Saving a vector for a paper
plt.savefig('figure_1.pdf')  # Clean, scalable, small file size. Perfect.

# Good: Saving a high-res raster for the web
plt.savefig('blog_post_plot.png', dpi=300, bbox_inches='tight')

# Confused: Doing the thing I just told you not to do
plt.savefig('figure_1.pdf', dpi=300)  # Does nothing. You're just typing more.

bbox_inches='tight': Your New Best Friend

Notice that argument above? This is Matplotlib’s “oh, you wanted the whole plot, right?” fix. By default, savefig() saves the entire figure canvas, including any white space around your axes. If your labels or titles are slightly outside this area, they get unceremoniously chopped off.

bbox_inches='tight' tells Matplotlib to calculate the smallest bounding box that can contain all the elements in the figure and trim the saved file to that. It’s an absolute lifesaver. The downside is that it makes the output size slightly inconsistent, which can be a pain if you’re trying to perfectly align multiple figures in a document. For 99% of use cases, just use it.

# Without - risk of cropped labels
plt.savefig('bad_save.png')

# With - almost always what you intend
plt.savefig('good_save.png', bbox_inches='tight')

Transparency and Facecolor

Ever saved a PNG, put it on a dark background, and gotten a lovely white box behind your plot? That’s the figure’s “facecolor” (background color) showing. To make it transparent, you need to set both the figure’s facecolor and tell savefig to use transparency.

fig, ax = plt.subplots(figsize=(5, 3))
ax.plot(x, y)
fig.patch.set_facecolor('white')  # Set figure background to white
ax.set_facecolor('white')         # Set axes background to white

# Now save with transparency enabled
plt.savefig('transparent_plot.png', transparent=True, bbox_inches='tight')

This replaces the white with transparency. If you set the figure facecolor to ’none’, the axes themselves will float on a transparent background, which can look very slick.

The Golden Rule: Save Before You Show

This is a classic “trap for young players.” In a script, the command plt.show() is a blocking function. Think of it as a giant pause button. Furthermore, after showing, the figure might be cleared from the internal state to free up memory. If you try to save after you show, you’ll often be saving a blank image or an empty figure.

Always call plt.savefig() before you call plt.show(). Make it a habit. Write it first if you have to.

# Correct Order
plt.plot(x, y)
plt.savefig('smart_plotter.png', dpi=150, bbox_inches='tight') # Save first
plt.show() # Then show

# Incorrect Order (often results in a blank image)
plt.plot(x, y)
plt.show() # Whoops, now the figure is "spent"
plt.savefig('sad_blank_image.png') # This makes me sad

So there you have it. The devil is in the details, but now you’re equipped to boss him around. Default to PDF, use bbox_inches='tight' religiously for PNGs, set a high DPI for raster, and for the love of all that is holy, save before you show. Now go make something worth saving.