6.6 Publication-Quality Figures
"Above all else show the data."— Edward Tufte, Data Visualization Pioneer
From code to publication: creating professional figures that meet publication standards
Section Objectives
After completing this section, you will be able to:
- Set figure size and resolution to meet journal requirements
- Configure professional fonts and labels
- Create multi-panel layouts
- Export high-quality images (PNG, PDF, SVG)
- Understand figure specifications of major journals
Figure Size and Resolution
Common Journal Requirements
| Journal | Width Requirement | DPI | Format |
|---|---|---|---|
| Nature | Single column: 89mm, Double column: 183mm | 300+ | TIFF, EPS |
| Science | Single column: 9cm, Double column: 18cm | 300+ | PDF, EPS |
| PNAS | Single column: 8.7cm, Double column: 17.8cm | 300+ | TIFF, PDF |
| AER | Single column: 3.5in, Double column: 7in | 300+ | EPS, PDF |
Setting Figure Size
python
import matplotlib.pyplot as plt
import numpy as np
# Method 1: Specify when creating figure
fig, ax = plt.subplots(figsize=(7, 5)) # inches
ax.plot([1, 2, 3], [1, 4, 9])
plt.tight_layout()
# Method 2: Global settings using rcParams
plt.rcParams['figure.figsize'] = (7, 5)
plt.rcParams['figure.dpi'] = 300 # Screen display DPI
plt.rcParams['savefig.dpi'] = 600 # Save DPI
# Common sizes (inches)
SIZES = {
'nature_single': (3.5, 2.625), # 89mm ≈ 3.5in, 4:3 ratio
'nature_double': (7.2, 5.4), # 183mm ≈ 7.2in
'science_single': (3.54, 2.66),
'science_double': (7.08, 5.31),
'pnas_single': (3.43, 2.57),
'pnas_double': (7.01, 5.26)
}
# Use preset sizes
fig, ax = plt.subplots(figsize=SIZES['nature_single'])Fonts and Styles
Setting Professional Fonts
python
# Method 1: Using rcParams
plt.rcParams.update({
'font.family': 'sans-serif',
'font.sans-serif': ['Arial', 'Helvetica'],
'font.size': 10,
'axes.labelsize': 10,
'axes.titlesize': 12,
'xtick.labelsize': 9,
'ytick.labelsize': 9,
'legend.fontsize': 9,
'figure.titlesize': 12
})
# Method 2: Using LaTeX rendering (more professional)
plt.rcParams.update({
'text.usetex': True,
'font.family': 'serif',
'font.serif': ['Times New Roman'],
'font.size': 10
})
# Example
fig, ax = plt.subplots(figsize=(7, 5))
ax.plot([1, 2, 3], [1, 4, 9], linewidth=2)
ax.set_xlabel(r'$x$ (unit)', fontsize=12)
ax.set_ylabel(r'$y = x^2$', fontsize=12)
ax.set_title('Professional Figure', fontsize=14, fontweight='bold')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()Removing Chart Clutter
python
import seaborn as sns
# Set seaborn style (paper-friendly)
sns.set_style("whitegrid")
sns.set_context("paper", font_scale=1.2)
# Or manually set minimal style
plt.rcParams.update({
'axes.spines.top': False,
'axes.spines.right': False,
'axes.grid': True,
'axes.grid.axis': 'y',
'grid.alpha': 0.3,
'legend.frameon': False
})Multi-Panel Layouts
Method 1: GridSpec (Recommended)
python
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize=(12, 8))
gs = GridSpec(3, 3, figure=fig, hspace=0.3, wspace=0.3)
# Different sized subplots
ax1 = fig.add_subplot(gs[0, :]) # First row, full width
ax2 = fig.add_subplot(gs[1, :2]) # Second row, first two columns
ax3 = fig.add_subplot(gs[1, 2]) # Second row, third column
ax4 = fig.add_subplot(gs[2, 0]) # Third row, first column
ax5 = fig.add_subplot(gs[2, 1:]) # Third row, last two columns
# Draw content...
for ax, label in zip([ax1, ax2, ax3, ax4, ax5], ['A', 'B', 'C', 'D', 'E']):
ax.text(0.05, 0.95, label, transform=ax.transAxes, fontsize=16,
fontweight='bold', va='top')
plt.suptitle('Multi-Panel Figure', fontsize=16, fontweight='bold')
plt.show()Method 2: Subplot Labels (A, B, C...)
python
fig, axes = plt.subplots(2, 2, figsize=(10, 8))
axes = axes.ravel()
for i, ax in enumerate(axes):
# Draw content
ax.plot(np.random.randn(100).cumsum())
# Add panel label
ax.text(-0.1, 1.05, chr(65+i), # A, B, C, D
transform=ax.transAxes,
fontsize=16, fontweight='bold', va='top')
plt.tight_layout()
plt.show()Exporting High-Quality Images
Basic Export
python
# PNG (suitable for online viewing)
plt.savefig('figure1.png', dpi=300, bbox_inches='tight')
# PDF (suitable for paper submission, vector format)
plt.savefig('figure1.pdf', bbox_inches='tight')
# SVG (suitable for post-editing)
plt.savefig('figure1.svg', bbox_inches='tight')
# EPS (required by some journals)
plt.savefig('figure1.eps', bbox_inches='tight')Advanced Export Options
python
# Full parameters
plt.savefig(
'figure1.pdf',
dpi=600, # High resolution
bbox_inches='tight', # Auto-crop white space
pad_inches=0.1, # Keep margins
transparent=True, # Transparent background
facecolor='white', # Background color
edgecolor='none', # No border
format='pdf', # Format
metadata={ # Metadata
'Creator': 'Python matplotlib',
'Author': 'Your Name',
'Title': 'Figure 1'
}
)Complete Case: Publication-Quality Figure
python
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import statsmodels.api as sm
# Global settings
plt.rcParams.update({
'font.family': 'sans-serif',
'font.sans-serif': ['Arial'],
'font.size': 10,
'axes.labelsize': 11,
'axes.titlesize': 12,
'xtick.labelsize': 10,
'ytick.labelsize': 10,
'legend.fontsize': 9,
'axes.spines.top': False,
'axes.spines.right': False,
'axes.grid': True,
'axes.grid.axis': 'y',
'grid.alpha': 0.3,
'legend.frameon': False
})
# Generate data
np.random.seed(42)
n = 500
education = np.random.normal(13, 3, n)
experience = np.random.uniform(0, 30, n)
female = np.random.binomial(1, 0.5, n)
log_wage = (1.5 + 0.08*education + 0.03*experience - 0.0005*experience**2
- 0.15*female + np.random.normal(0, 0.3, n))
wage = np.exp(log_wage)
df = pd.DataFrame({
'wage': wage,
'log_wage': log_wage,
'education': education,
'experience': experience,
'female': female,
'gender': ['Female' if f else 'Male' for f in female]
})
# Create publication-quality multi-panel figure
fig = plt.figure(figsize=(12, 10))
gs = GridSpec(3, 2, figure=fig, hspace=0.35, wspace=0.3)
# Panel A: Distribution
ax1 = fig.add_subplot(gs[0, 0])
ax1.hist(df['wage'], bins=30, edgecolor='black', alpha=0.7, color='steelblue')
ax1.set_xlabel('Wage (thousand yuan/month)')
ax1.set_ylabel('Frequency')
ax1.set_title('Distribution of Wages')
ax1.text(-0.15, 1.05, 'A', transform=ax1.transAxes, fontsize=14, fontweight='bold')
# Panel B: Scatter plot with regression
ax2 = fig.add_subplot(gs[0, 1])
for gender, color, marker in [('Male', 'blue', 'o'), ('Female', 'red', 's')]:
mask = df['gender'] == gender
ax2.scatter(df.loc[mask, 'education'], df.loc[mask, 'log_wage'],
alpha=0.4, s=30, color=color, marker=marker, label=gender)
ax2.set_xlabel('Education (years)')
ax2.set_ylabel('log(Wage)')
ax2.set_title('Education-Wage Relationship by Gender')
ax2.legend(loc='upper left')
ax2.text(-0.15, 1.05, 'B', transform=ax2.transAxes, fontsize=14, fontweight='bold')
# Panel C: Box plot
ax3 = fig.add_subplot(gs[1, 0])
sns.boxplot(x='gender', y='wage', data=df, ax=ax3, palette=['blue', 'red'])
ax3.set_xlabel('Gender')
ax3.set_ylabel('Wage (thousand yuan/month)')
ax3.set_title('Wage Distribution by Gender')
ax3.text(-0.15, 1.05, 'C', transform=ax3.transAxes, fontsize=14, fontweight='bold')
# Panel D: Regression diagnostics
ax4 = fig.add_subplot(gs[1, 1])
X = sm.add_constant(df[['education', 'experience', 'female']])
model = sm.OLS(df['log_wage'], X).fit()
ax4.scatter(model.fittedvalues, model.resid, alpha=0.5, s=30)
ax4.axhline(y=0, color='r', linestyle='--', linewidth=2)
ax4.set_xlabel('Fitted values')
ax4.set_ylabel('Residuals')
ax4.set_title('Residual Plot')
ax4.text(-0.15, 1.05, 'D', transform=ax4.transAxes, fontsize=14, fontweight='bold')
# Panel E: Coefficient plot
ax5 = fig.add_subplot(gs[2, :])
coefs = model.params[1:] # Exclude constant
ci = model.conf_int()[1:]
y_pos = np.arange(len(coefs))
ax5.errorbar(coefs, y_pos, xerr=[coefs - ci[0], ci[1] - coefs],
fmt='o', markersize=6, capsize=4, capthick=1.5, linewidth=1.5)
ax5.axvline(x=0, color='red', linestyle='--', linewidth=1.5, alpha=0.7)
ax5.set_yticks(y_pos)
ax5.set_yticklabels(['Education', 'Experience', 'Female'])
ax5.set_xlabel('Coefficient estimate')
ax5.set_title('Regression Coefficients with 95% CI')
ax5.text(-0.08, 1.05, 'E', transform=ax5.transAxes, fontsize=14, fontweight='bold')
# Add figure caption
fig.text(0.5, 0.01, 'Figure 1. Wage Determination Analysis. '
'Panel A shows the distribution of wages. Panel B displays education-wage relationships by gender. '
'Panel C compares wage distributions. Panel D shows regression residuals. '
'Panel E presents coefficient estimates. N=500.',
ha='center', fontsize=9, wrap=True)
plt.savefig('figure1_publication.pdf', dpi=600, bbox_inches='tight')
plt.savefig('figure1_publication.png', dpi=300, bbox_inches='tight')
plt.show()
print("Figures saved: figure1_publication.pdf and figure1_publication.png")Section Summary
Publication Figure Checklist
File Requirements:
- [ ] Resolution ≥ 300 DPI
- [ ] Use vector format (PDF/EPS/SVG)
- [ ] File size reasonable (< 10 MB)
Visual Requirements:
- [ ] Clear, readable fonts (≥ 9pt)
- [ ] Appropriate line thickness (≥ 1pt)
- [ ] Colorblind-friendly colors
- [ ] Remove unnecessary elements
Content Requirements:
- [ ] Concise, clear titles
- [ ] Axes with units
- [ ] Complete legends
- [ ] Multi-panel labels (A, B, C...)
- [ ] Detailed figure caption
Common Mistakes
Avoid:
- Low image resolution
- Using RGB colors (print requires CMYK)
- Fonts too small (< 8pt)
- Overly complex charts (one chart, multiple meanings)
- Color schemes indistinguishable in black-and-white printing
Recommended:
- Use grayscale + shapes to distinguish data
- Simple layouts
- High contrast
- Clear annotations
Further Resources
Visualization Guides
- Nature Figure Guidelines: https://www.nature.com/nature/for-authors/final-submission
- Ten Simple Rules for Better Figures (Rougier et al., 2014)
- Matplotlib Cheatsheets: https://github.com/matplotlib/cheatsheets
Python Tools
python
# Recommended visualization packages
pip install matplotlib seaborn plotly
pip install adjustText # Auto-adjust label positions
pip install scienceplots # Scientific style themesCongratulations! Module 6 Complete!
You have now mastered:
- Univariate and bivariate visualization
- Regression analysis visualization
- Distribution comparison methods
- Creating publication-quality figures
Next Steps: Apply these skills to your research and let data tell compelling stories!
Continue exploring other modules in StatsPai!