How to Save Figures in Matplotlib
Saving matplotlib figures properly is a fundamental skill that separates hobbyist data scientists from professionals. Whether you're generating reports for stakeholders, creating publication-ready...
Key Insights
- Use
plt.savefig()with appropriate DPI settings: 72-96 for web, 300+ for print, and choose vector formats (PDF, SVG) for scalable graphics that will be resized - Always set
bbox_inches='tight'to eliminate whitespace around your plots, and callplt.close()after saving to prevent memory leaks in production workflows - Save figures before calling
plt.show()because the display process can clear figure data in some environments, leading to blank or corrupted saved files
Introduction
Saving matplotlib figures properly is a fundamental skill that separates hobbyist data scientists from professionals. Whether you’re generating reports for stakeholders, creating publication-ready graphics, or building automated dashboards, understanding the nuances of savefig() will save you hours of frustration and ensure your visualizations look professional across different media.
The default settings rarely produce optimal results. A figure that looks perfect in your Jupyter notebook might appear pixelated in a presentation, have awkward whitespace in a PDF report, or bloat your repository with unnecessarily large files. This article covers everything you need to know to save matplotlib figures correctly the first time.
Basic Figure Saving with savefig()
The savefig() method is your primary tool for exporting matplotlib figures. At its simplest, you provide a filename and matplotlib handles the rest:
import matplotlib.pyplot as plt
import numpy as np
# Create a simple plot
x = np.linspace(0, 10, 100)
y = np.sin(x)
plt.figure(figsize=(8, 6))
plt.plot(x, y, linewidth=2)
plt.xlabel('X axis')
plt.ylabel('Y axis')
plt.title('Simple Sine Wave')
# Save the figure
plt.savefig('sine_wave.png')
plt.close()
This creates a PNG file with matplotlib’s default settings (100 DPI, white background, standard margins). While functional, you’ll almost always want more control over the output.
Critical point: Always save before calling plt.show(). In some environments, particularly non-interactive backends, show() can clear the figure buffer, resulting in blank saved images.
File Format Options
Choosing the right file format depends on your use case. Matplotlib supports numerous formats, but these four cover 95% of scenarios:
PNG (Raster): Best for web use, presentations, and general sharing. Supports transparency. File sizes are reasonable with good compression.
JPEG (Raster): Smaller files than PNG but no transparency support and lossy compression. Use only when file size is critical and quality can be sacrificed.
PDF (Vector): Ideal for print publications and reports. Infinitely scalable without quality loss. Can be edited in vector graphics software.
SVG (Vector): Perfect for web graphics that need to scale responsively. Easily editable in tools like Inkscape or Adobe Illustrator.
Here’s how to save in different formats:
fig, ax = plt.subplots(figsize=(8, 6))
ax.plot(x, y, linewidth=2, color='#2E86AB')
ax.set_xlabel('Time (s)', fontsize=12)
ax.set_ylabel('Amplitude', fontsize=12)
ax.set_title('Signal Analysis', fontsize=14, fontweight='bold')
ax.grid(True, alpha=0.3)
# Save in multiple formats
fig.savefig('plot_web.png', dpi=96) # Web use
fig.savefig('plot_print.pdf') # Print/publication
fig.savefig('plot_vector.svg') # Web vector
fig.savefig('plot_compressed.jpg', quality=85) # Maximum compression
plt.close(fig)
Rule of thumb: Use vector formats (PDF, SVG) when the final size is unknown or the graphic will be resized. Use PNG for fixed-size web graphics and screenshots. Avoid JPEG for plots—the compression artifacts around text and lines look terrible.
Resolution and Quality Control
DPI (dots per inch) determines output resolution for raster formats. Higher DPI means sharper images but larger file sizes. Vector formats ignore DPI settings.
fig, ax = plt.subplots(figsize=(10, 6))
x = np.linspace(0, 4*np.pi, 1000)
y1 = np.sin(x)
y2 = np.cos(x)
ax.plot(x, y1, label='sin(x)', linewidth=2)
ax.plot(x, y2, label='cos(x)', linewidth=2)
ax.legend(fontsize=11)
ax.set_xlabel('Radians', fontsize=12)
ax.set_ylabel('Value', fontsize=12)
ax.grid(True, alpha=0.3)
# Different DPI for different purposes
fig.savefig('plot_screen.png', dpi=72) # Screen viewing - 500KB
fig.savefig('plot_web.png', dpi=96) # Web standard - 800KB
fig.savefig('plot_hires.png', dpi=150) # High-res web - 1.8MB
fig.savefig('plot_print.png', dpi=300) # Print quality - 5.2MB
plt.close(fig)
DPI guidelines:
- 72-96 DPI: Screen display, web dashboards, email attachments
- 150 DPI: High-quality web graphics, PowerPoint presentations
- 300 DPI: Print publications, posters, professional reports
- 600+ DPI: Large-format printing, billboards
Figure size (figsize) and DPI multiply to determine pixel dimensions. A (10, 6) inch figure at 100 DPI produces a 1000×600 pixel image. At 300 DPI, the same figure becomes 3000×1800 pixels.
Advanced savefig() Parameters
Several parameters dramatically improve output quality with minimal effort:
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
fig.suptitle('Quarterly Sales Analysis', fontsize=16, fontweight='bold')
# Generate some sample plots
for idx, ax in enumerate(axes.flat):
data = np.random.randn(100).cumsum()
ax.plot(data, linewidth=2)
ax.set_title(f'Quarter {idx+1}')
ax.grid(True, alpha=0.3)
# Standard save - notice the whitespace
fig.savefig('sales_standard.png', dpi=150)
# Optimized save
fig.savefig('sales_optimized.png',
dpi=150,
bbox_inches='tight', # Removes whitespace
pad_inches=0.1, # Adds small padding
facecolor='white', # Explicit background color
edgecolor='none') # No border
# Transparent background for presentations
fig.savefig('sales_transparent.png',
dpi=150,
bbox_inches='tight',
transparent=True, # Transparent background
pad_inches=0.1)
plt.close(fig)
Parameter breakdown:
bbox_inches='tight': Automatically crops whitespace. Use this on every figure unless you specifically need the padding.pad_inches: Adds controlled padding around the tight bounding box. Default is 0.1 inches.transparent=True: Makes the background transparent. Essential for overlaying plots on colored presentation slides.facecolorandedgecolor: Control background and border colors. Set explicitly to avoid surprises with different matplotlib styles.
Saving Multiple Figures and Subplots
Production workflows often require generating dozens or hundreds of figures. Proper automation prevents errors and saves time:
import os
from datetime import datetime
# Create output directory
output_dir = 'figures'
os.makedirs(output_dir, exist_ok=True)
# Generate timestamp for versioning
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Simulate multiple datasets
datasets = {
'temperature': np.random.randn(365).cumsum() + 20,
'humidity': np.random.randn(365).cumsum() + 60,
'pressure': np.random.randn(365).cumsum() + 1013
}
# Batch save figures
for name, data in datasets.items():
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(data, linewidth=1.5)
ax.set_title(f'{name.capitalize()} Over Time', fontsize=14)
ax.set_xlabel('Day of Year', fontsize=12)
ax.set_ylabel(name.capitalize(), fontsize=12)
ax.grid(True, alpha=0.3)
# Dynamic filename with timestamp
filename = f'{output_dir}/{name}_{timestamp}.png'
fig.savefig(filename,
dpi=150,
bbox_inches='tight',
facecolor='white')
print(f'Saved: {filename}')
plt.close(fig) # Critical for memory management
When working with multiple subplots in a single figure, save the entire figure object, not individual axes:
# Correct approach for subplots
fig, axes = plt.subplots(2, 3, figsize=(15, 10))
for idx, ax in enumerate(axes.flat):
ax.plot(np.random.randn(100).cumsum())
ax.set_title(f'Subplot {idx+1}')
# Save the entire figure
fig.savefig('all_subplots.png', dpi=150, bbox_inches='tight')
plt.close(fig)
Best Practices and Common Pitfalls
Here’s a production-ready template incorporating best practices:
import matplotlib.pyplot as plt
import numpy as np
from pathlib import Path
def save_plot_safely(fig, filename, formats=['png', 'pdf'], dpi=150):
"""
Save matplotlib figure with error handling and best practices.
Args:
fig: matplotlib figure object
filename: base filename without extension
formats: list of formats to save
dpi: resolution for raster formats
"""
output_path = Path('output')
output_path.mkdir(exist_ok=True)
for fmt in formats:
filepath = output_path / f'{filename}.{fmt}'
try:
fig.savefig(filepath,
dpi=dpi,
bbox_inches='tight',
pad_inches=0.1,
facecolor='white',
format=fmt)
print(f'✓ Saved: {filepath}')
except Exception as e:
print(f'✗ Failed to save {filepath}: {e}')
plt.close(fig) # Always close to free memory
# Usage example
fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(np.random.randn(1000).cumsum(), linewidth=2)
ax.set_title('Production-Ready Plot')
ax.grid(True, alpha=0.3)
save_plot_safely(fig, 'analysis_2024', formats=['png', 'pdf', 'svg'])
Critical pitfalls to avoid:
-
Memory leaks: Always call
plt.close()after saving, especially in loops. Matplotlib keeps figures in memory until explicitly closed. -
Overwriting files: Use timestamps or version numbers in filenames for important analyses.
-
Inconsistent backends: Set your backend explicitly at the start of scripts:
plt.switch_backend('Agg')for non-interactive environments. -
Ignoring file paths: Use
pathlib.Pathfor cross-platform compatibility instead of string concatenation. -
Wrong order of operations: The sequence matters: create plot → configure → save → close. Don’t call
show()beforesavefig().
Mastering these techniques transforms matplotlib from a frustrating tool into a reliable asset for production data visualization workflows. The difference between amateur and professional output often comes down to these details.