publication-figures
Publication-ready figure conventions for empirical finance and economics. Covers matplotlib styling, color palettes, export settings, and common figure types (time series, decile bars, coefficient plots, event studies). Auto-apply when creating any figure, plot, or visualization.
/plugin install ai-asset-pricingdetails
Publication-Ready Figures
For this repo, the production plotting toolkit is fintools.figures. Read
docs/ai/figures.md first, then choose the plotting path deliberately:
- Use native
fintools.figureshelpers for repo work, Word proof packs, validation checks, and dataframe-to-figure suites. - Use
style="fins"for the house publication style andstyle="ft"for FT-style output. - Use the legacy skill-local
finance.mplstyle/figutils.pyassets only when the user explicitly wants the older standalone helper style or needs a portable snippet outside the package workflow.
To recreate the FT validation gallery:
python tools/figure_examples.py --style ft --docx --output results/figures
To recreate the house-style gallery:
python tools/figure_examples.py --style fins --docx --output results/figures
Generated PNG/PDF/DOCX/caption files belong under ignored results/figures/
paths. Do not commit maintainer proof packs or local gallery outputs.
Apply these conventions whenever creating figures. The goal: every figure Claude produces is publication-ready by default — no manual cleanup needed.
Legacy Helper Quick Start
Prefer fintools.figures for this repo. The helper assets below remain
available for standalone or explicitly requested legacy publication-style
plots.
Copy finance.mplstyle from this skill directory into the project, then:
import matplotlib.pyplot as plt
plt.style.use('path/to/finance.mplstyle')
Or apply inline (no file needed):
import matplotlib.pyplot as plt
plt.rcParams.update({
'font.family': 'serif',
'font.serif': ['Times New Roman', 'STIXGeneral', 'DejaVu Serif'],
'mathtext.fontset': 'stix',
'font.size': 9,
'axes.labelsize': 9,
'xtick.labelsize': 8,
'ytick.labelsize': 8,
'legend.fontsize': 8,
'axes.linewidth': 0.6,
'axes.spines.top': False,
'axes.spines.right': False,
'lines.linewidth': 1.2,
'xtick.direction': 'out',
'ytick.direction': 'out',
'legend.frameon': False,
'figure.dpi': 150,
'savefig.dpi': 600,
'savefig.format': 'pdf',
'pdf.fonttype': 42,
})
Default Aesthetic
- Fonts: Times New Roman / STIX (serif, no LaTeX dependency)
- Spines: Bottom + left only (no top/right)
- Grid: Off by default
- Ticks: Outward, 8pt labels
- Colors: Okabe-Ito colorblind-safe palette (blue first)
- Export: PDF vector, 600 DPI, fonts embedded (type 42)
Color Palettes
Default cycle (Okabe-Ito, colorblind-safe):
PALETTE = ['#377EB8', '#E41A1C', '#4DAF4A', '#984EA3',
'#FF7F00', '#A65628', '#F781BF', '#999999']
Two-series (long vs short, treatment vs control):
BLUE_RED = ['#377EB8', '#E41A1C']
Grayscale-safe (for guaranteed print clarity):
GRAYSCALE = ['#000000', '#555555', '#999999', '#CCCCCC']
# Combine with linestyles: '-', '--', ':', '-.'
Sequential/diverging colormaps: Use viridis or cividis (colorblind-safe) for heatmaps. Use RdBu_r for diverging (correlation matrices).
Figure Sizing
| Context | Width (inches) | Use |
|---|---|---|
| Single column | 3.5 | Most journal figures |
| 1.5 column | 5.25 | Medium panels |
| Double column / full width | 7.0 | Wide multi-panel figures |
| Slide / presentation | 10.0 | Beamer, PowerPoint |
Aspect ratio: Default to golden ratio (width / 1.618). Use square for heatmaps, wide (width / 2.0) for time series.
def set_size(width='single', ratio='golden'):
widths = {'single': 3.5, 'onehalf': 5.25, 'double': 7.0, 'slide': 10.0}
ratios = {'golden': 1.618, 'square': 1.0, 'wide': 2.0}
w = widths.get(width, width)
r = ratios.get(ratio, ratio)
return (w, w / r)
Common Figure Types in Empirical Finance
Time Series
fig, ax = plt.subplots(figsize=set_size('double', 'wide'))
ax.plot(dates, values)
ax.set_xlabel(''); ax.set_ylabel('Return (%)')
- Use
ax.axhline(0, color='grey', linewidth=0.5, zorder=0)for zero reference - Add NBER recession bands with
ax.axvspan(start, end, alpha=0.1, color='grey')
Cumulative Return / Wealth Paths
cumret = (1 + returns).cumprod()
ax.plot(cumret.index, cumret.values)
ax.set_ylabel('Growth of $1')
- Start at 1.0 (or 100 for percentage scale)
- Log scale optional for long horizons:
ax.set_yscale('log')
Decile Portfolio Bar Chart with Newey-West CIs
# returns_df: DataFrame with columns 0..9 (portfolio return time series)
plot_portfolio_bars(ax, returns_df, show_ls=True, ls_label='10-1')
# Computes means, Newey-West SEs (lag = floor(T^0.25)), 95% CI error bars
# Includes long-short bar with t-stat annotation
- Use
plot_portfolio_barsfor any portfolio sort figure — it handles NW SEs automatically - Short leg (decile 1) colored red, long leg (decile 10) green, L-S bar purple
- t-stat annotated on the L-S bar
For simple bars without CIs (pre-computed means):
plot_decile_bars(ax, means, highlight_extremes=True, spread_label=True)
Coefficient Plot (Forest Plot)
ax.errorbar(coefs, range(len(coefs)), xerr=[coefs-ci_lo, ci_hi-coefs],
fmt='o', color='#377EB8', capsize=3, markersize=4)
ax.axvline(0, color='grey', linewidth=0.5, linestyle='--')
ax.set_yticks(range(len(names))); ax.set_yticklabels(names)
Event Study (CAR Plot)
days = range(event_window[0], event_window[1] + 1)
ax.plot(days, car, color='#377EB8')
ax.fill_between(days, ci_lo, ci_hi, alpha=0.2, color='#377EB8')
ax.axvline(0, color='grey', linewidth=0.5, linestyle='--')
ax.axhline(0, color='grey', linewidth=0.5)
ax.set_xlabel('Days Relative to Event'); ax.set_ylabel('CAR (%)')
Correlation Heatmap
import seaborn as sns
mask = np.triu(np.ones_like(corr, dtype=bool), k=1)
sns.heatmap(corr, mask=mask, cmap='RdBu_r', center=0, vmin=-1, vmax=1,
annot=True, fmt='.2f', linewidths=0.5, ax=ax,
cbar_kws={'shrink': 0.8})
Multi-Panel Figures
fig, axes = plt.subplots(1, 3, figsize=set_size('double', 'wide'))
# Label panels
for i, ax in enumerate(axes):
ax.text(-0.1, 1.05, f'({chr(97+i)})', transform=ax.transAxes,
fontsize=10, fontweight='bold', va='top')
Export Checklist
Before saving any figure:
- Format: PDF (vector) for papers; PNG (300+ DPI) for slides/web
- Font embedding:
pdf.fonttype = 42(already in style) - Bbox:
bbox_inches='tight'to avoid clipped labels - DPI: 600 for publication, 150 for screen preview
- Size: Match target journal column width — don't rescale in LaTeX/Word
fig.savefig('figure.pdf', bbox_inches='tight', dpi=600)
# Also save PNG for quick preview:
fig.savefig('figure.png', bbox_inches='tight', dpi=150)
Journal-Specific Overrides
| Journal | Override |
|---|---|
| RFS | Export as TIF at 300 DPI (photos) or 600 DPI (line art). Fonts: Arial, Courier, Times, Helvetica, Symbol only. |
| AER | No shading, no gridlines, no background color. Vector PDF/EPS preferred. Max 9 columns wide including row headings for tables. |
| JF | Color figures OK online (free). Color in print costs $500/page. Design for grayscale print compatibility. |
| Nature | Sans-serif fonts required (Helvetica/Arial). Override: plt.rcParams['font.family'] = 'sans-serif' |
Do NOT
- Use rainbow colormaps (
jet,hsv) — not perceptually uniform, not colorblind-safe - Use 3D plots unless the data genuinely requires a third dimension
- Add chartjunk: unnecessary gridlines, borders, background colors
- Scale figures in LaTeX/Word — set correct size in matplotlib, include at 100%
- Use different fonts/sizes across figures in the same paper
- Use LaTeX escapes (
\&,\%,\_) in matplotlib text (titles, labels, annotations) — matplotlib's default text engine renders backslashes literally. Write plainS&P 500, notS\&P 500. LaTeX escapes only work whenplt.rcParams['text.usetex'] = True, which requires a full LaTeX installation and is NOT enabled by default in our style.
technical
- github
- Alexander-M-Dickerson/ai-asset-pricing
- stars
- 49
- license
- MIT
- contributors
- 1
- last commit
- 2026-04-19T07:58:01Z
- file
- .claude/skills/publication-figures/SKILL.md