Boilerplate Code for Publication-quality Scientific Plotting

Draft – work in progress.
  1. Control figure dimensions, with sensible defaults (e.g. specify either height or weight and derive the other using the golden ratio)
  2. Command-line options for different contexts — for embedding in papers, on the web (e.g. in blog posts), in presentation slides or on academic posters.
  3. Further ability to fine-tune these defaults by modifying the DPI.
  4. Control other stylistic choices, color palettes and aesthetics (borders, background colors, grids etc.)
  5. Simultaneously save as more than one format (pdf or pgf for LaTeX, svg for web and png for general purpose)
  6. Background transparency.
fig, ax = plt.subplots()

# plotting code goes here...

plt.tight_layout()

for ext in extension:
    fig.savefig(output_path.joinpath(f"prior_{context}_{suffix}.{ext}"),
                dpi=dpi, transparent=transparent)

plt.show()

Styles

$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --style=darkgrid
$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --style=whitegrid
$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --style=white
$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --style=ticks

Contexts

$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --context=paper
$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --context=notebook
$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --context=talk
$ python plot_example.py gallery -w 5 --dpi 200 -e png -e pdf --context=poster

Full Example

import sys
import click

import numpy as np
import tensorflow_probability as tfp

import matplotlib.pyplot as plt
import seaborn as sns

from pathlib import Path
from utils import GOLDEN_RATIO, WIDTH, pt_to_in


@click.command()
@click.argument("name")
@click.argument("output_dir", default="figures/",
                type=click.Path(file_okay=False, dir_okay=True))
@click.option('--transparent', is_flag=True)
@click.option('--context', default="paper")
@click.option('--style', default="ticks")
@click.option('--palette', default="muted")
@click.option('--width', '-w', type=float, default=pt_to_in(WIDTH))
@click.option('--height', '-h', type=float)
@click.option('--aspect', '-a', type=float, default=GOLDEN_RATIO)
@click.option('--dpi', type=float, default=300)
@click.option('--extension', '-e', multiple=True, default=["png"])
def main(name, output_dir, transparent, context, style, palette, width, height,
         aspect, dpi, extension):

    # preamble
    if height is None:
        height = width / aspect
    # height *= num_iterations
    # figsize = size(width, aspect)
    figsize = (width, height)

    suffix = f"{width*dpi:.0f}x{height*dpi:.0f}"

    rc = {
        "figure.figsize": figsize,
        "font.serif": ["Times New Roman"],
        "text.usetex": True,
    }
    sns.set(context=context, style=style, palette=palette, font="serif", rc=rc)

    output_path = Path(output_dir).joinpath(name)
    output_path.mkdir(parents=True, exist_ok=True)
    # / preamble

    # shortcuts
    tfd = tfp.distributions
    kernels = tfp.math.psd_kernels

    # constants
    num_features = 1  # dimensionality
    num_index_points = 512  # nbr of index points
    num_samples = 8

    x_min, x_max = -5.0, 5.0
    X_grid = np.linspace(x_min, x_max, num_index_points).reshape(-1, num_features)

    seed = 23  # set random seed for reproducibility

    kernel = kernels.ExponentiatedQuadratic()

    gp = tfd.GaussianProcess(kernel=kernel, index_points=X_grid)
    samples = gp.sample(num_samples, seed=seed)

    fig, ax = plt.subplots()

    ax.plot(X_grid, samples.numpy().T)

    ax.set_xlabel(r'$x$')
    ax.set_ylabel(r'$f(x)$')
    ax.set_title(r'Draws of $f(x)$ from GP prior')

    plt.tight_layout()

    for ext in extension:
        fig.savefig(output_path.joinpath(f"prior_{context}_{suffix}.{ext}"),
                    dpi=dpi, transparent=transparent)

    plt.show()

    return 0


if __name__ == "__main__":
    sys.exit(main())  # pragma: no cover

Helper utilities

import numpy as np

GOLDEN_RATIO = 0.5 * (1 + np.sqrt(5))
WIDTH = 397.48499


def pt_to_in(x):
    pt_per_in = 72.27
    return x / pt_per_in


def size(width, aspect=GOLDEN_RATIO):
    width_in = pt_to_in(width)
    return (width_in, width_in / aspect)
Louis Tiao
Louis Tiao
PhD Candidate

Thanks for stopping by! Let’s connect – drop me a message or follow me:

comments powered by Disqus