Skip to main content

SecretPlots: publication-ready scientific figures in a few readable lines of Python

SecretPlots

Publication-ready scientific figures in a few readable lines of Python.
Journal presets · statistics built in · colour-blind-safe palettes · fonts embedded

PyPI Python versions CI Documentation MIT license

Documentation · Gallery · Journal-ready figures · Changelog · Report a bug


Same calls, better figure

If you can write matplotlib, you can write SecretPlots. Same data, same figure size, no styling code on either side:

matplotlib SecretPlots
import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 3.7),
                       layout="constrained")
for arm, g in tumours.groupby("arm", sort=False):
    ax.plot(g["day"], g["volume"],
            marker="o", label=arm)
ax.set(xlabel="Day",
       ylabel="Tumour volume (mm³)",
       title="Tumour growth")
ax.legend()
import SecretPlots as sp

(
    sp.plot(tumours, x="day", y="volume",
            hue="arm")
    .markers()
    .labels(x="Day",
            y="Tumour volume (mm³)",
            title="Tumour growth")
)
Tumour growth curves with matplotlib defaults The same curves with SecretPlots defaults

You get a colour-blind-safe palette, a quiet grid, a titled legend outside the data and readable type without touching rcParams. From there, one more step labels the lines directly (.endlabels()), one argument summarises replicate animals with confidence bands (error="ci"), and one line makes it a journal figure.

From table to journal figure

import SecretPlots as sp

sp.use(preset="nature")                         # 89 mm column, 5–7 pt text, 300 dpi

(
    sp.box(cells, x="treatment", y="area")
    .points()                                   # show every cell
    .compare(ref="Control", correction="holm")  # significance brackets
    .colors({"Control": "#BBBBBB", "siRNA": "#EE6677", "siRNA + rescue": "#4477AA"})
    .labels(x="", y="Cell area (µm²)")
    .save("figure1.pdf")                        # fonts embedded, ready to upload
)

Box plots of cell area with every cell and Holm-corrected P values

Why SecretPlots?

  • Journal presets. Nature, Science, Cell, PNAS, AHA, Elsevier, Springer, IEEE, ACM and NeurIPS presets set column widths, font limits, dpi and file formats from each publisher's author guidelines.
  • Statistics where you need them. Significance brackets with Holm or Bonferroni correction, confidence bands, percentile intervals of the observations, linear fits with R², KDEs and hierarchical clustering. Most plots return their computed numbers as tables (.source_data(), .test_results()).
  • Every observation visible. Points over boxes, individual trajectories under means, rainclouds and rugs, the patterns reviewers ask for with small samples.
  • Colour-blind safe. Palettes and colormaps come from SecretColors. Unsafe maps are flagged, and a print-bw theme renders the figure in greys.
  • Readable, chainable API. One constructor plus small steps. plot.arguments() lists what the constructor takes, plot.options() every step, and plot.options("legend") explains one.
  • Honest defaults. Missing data raises instead of being silently dropped. Font and palette fallbacks are reported, and plot.explain("font_size") says where any style value came from.

Installation

pip install SecretPlots          # or: uv add SecretPlots

Requires Python 3.12+. Depends only on matplotlib, numpy, pandas and SecretColors.

Version 3 is a ground-up rewrite and is not compatible with the 0.1.x releases; pin SecretPlots<1 if you depend on the old API.

Every image below is unretouched SecretPlots output at Nature single-column size. The gallery has the complete, runnable code for each.

Compare groups and conditions

Grouped bar chart of qPCR fold change with SD error bars Raincloud plot of reaction times
sp.bar(qpcr, x="gene", y="fold", hue="genotype", error="sd").refline(1, linestyle=":").ticklabels(italic=True)
sp.raincloud(stroop, x="condition", y="rt").compare(ref="Congruent", format="stars")

Follow change over time

Tumour growth curves with individual animals Pharmacokinetic curves on a log axis
sp.line(tumours, x="day", y="volume", hue="arm", units="mouse", error="ci").endlabels().log("y")
sp.line(pk, x="hours", y="conc", hue="dose", error="sd").errorbars("bars").markers().log("y")

Find the pattern in a matrix

Clustered expression heatmap with pathway groups Lower-triangle correlation matrix
sp.heatmap(expression).scale("row").groups(rows="pathway").cluster(cols=False).tracks(cols={"Condition": condition})
sp.heatmap(cohort.corr(), square=True).mask("upper").cmap("tol_bu_rd", center=0).values(".2f")

Relationships and distributions

Volcano plot Scatter plot with linear fits and R²
Overlaid density histograms with KDE Split violin plots
sp.scatter(genes, x="lfc", y="log_p", hue="call").refline(-1, 1, axis="x").refline(2, axis="y")
sp.scatter(biopsies, x="bmi", y="lipid", hue="tissue").fit("linear", stats=True)
sp.histogram(serum, x="level", hue="cohort", stat="density").kde().show_n()
sp.violin(behaviour, x="genotype", y="score", hue="sex", split=True, inner="quartiles")

Clinical results

Subgroup forest plot with an events table and interaction p-values Kaplan-Meier curves with a number-at-risk table and log-rank p-value
sp.forest(trial, label="subgroup", estimate="hr", lower="lower", upper="upper", group="variable").summary("All patients").table(left={"Drug": "drug"})
sp.survival(patients, time="months", event="died", hue="arm").risk_table().median().pvalue().hazard_ratio()

One figure, any journal

Presets describe where a figure is going; themes describe how it looks. Switch either one without touching your plotting code:

sp.use(preset="nature")                    # for the whole session
plot.preset("ieee")                        # or for one plot
plot.size(width="double", height="60mm")   # the journal's named widths, or any length
plot.theme("print-bw").encode_groups()     # greyscale with dashes and markers
Kind Presets
Journals nature · science · cell · pnas · aha · elsevier · springer · ieee · acm · neurips
Everyday screen (default) · web · a4 · letter · slide-16x9 · poster
Newsroom bbc · ft-like · economist-like · nyt-like · wapo-like

Journal presets cite the guidelines they were built from. If you set a value outside a journal's limits, you get a LimitExceededWarning instead of a silent change. Newsroom presets ending in -like are inspired by publicly visible chart conventions; they are not affiliated with those publishers and use open fallback fonts. You can write your own preset in a few lines of TOML.

Plot types

sp.bar Means, sums or values per category; error bars, groups, stacks, value labels
sp.scatter Colour, size and marker encodings; linear and LOWESS fits
sp.line Means with CI/SD/SEM/PI bands, fan charts, individual units, end labels
sp.area, sp.fill_between Stacked and 100% areas, envelopes, split by sign, gradients
sp.histogram Counts, percent or density; KDE, rug, cumulative, layered, stacked or dodged
sp.heatmap Colormaps, z-scoring, clustering, groups, annotation tracks, masks
sp.forest Subgroup and meta-analysis forest plots with tables, sections, summary diamonds, weights and side-by-side panels
sp.survival Kaplan-Meier, cumulative incidence and cumulative hazard; risk tables, censor ticks, medians, log-rank tests and hazard ratios
sp.box Points, means, sample sizes, significance brackets
sp.violin, sp.raincloud Split violins, inner box/quartiles, half-violin rainclouds

Find any option without leaving Python

No need to open the docs or call help(). Every plot type describes itself as a table you can read or search:

sp.BarPlot.arguments()          # what sp.bar(...) accepts: allowed values, defaults
sp.BarPlot.arguments("error")   # one argument
plot.options()                  # every chainable step for this plot
plot.options("legend")          # one step's parameters, allowed values and defaults
sp.theme_options()              # every style field accepted by .theme(...)
plot.explain("font_size")       # where a style value came from

Development

uv sync --group dev --group docs
uv run pre-commit install
uv run pytest --mpl                       # tests, including image comparisons
uv run python scripts/render_docs.py      # regenerate documentation figures
uv run mkdocs serve                       # preview the documentation

See CONTRIBUTING.md for adding presets and documentation. Bug reports, preset requests and pull requests are very welcome.

License

MIT © Rohit Suratekar and contributors. Colour palettes and colormaps by SecretColors.

Metadata

Release files for SecretPlots 3.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for SecretPlots 3.0.0
File Size Uploaded Provenance Actions
secretplots-3.0.0.tar.gz 651.5 kB Without provenance Details for secretplots-3.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for SecretPlots 3.0.0
File Interpreter ABI Platform Actions
secretplots-3.0.0-py3-none-any.whl Python 3 none any Details for secretplots-3.0.0-py3-none-any.whl

Total release size: 853.2 kB

Release files / secretplots-3.0.0.tar.gz

Download URL secretplots-3.0.0.tar.gz
Size 651.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f2b9fde97a7fdedf61754e5d78738273f7324ceec9e6ee4c30e8338a87dee979
BLAKE2b-256 checksum
How to use checksums
53398671e55723b5b4d65e2c2e03abdc8d71f8f8f83d37bacf81cdd232d6fb2f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / secretplots-3.0.0-py3-none-any.whl

Download URL secretplots-3.0.0-py3-none-any.whl
Size 201.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
364d36a0bf76aab39e9b545f282bf986ea7ca1cb93110b8d44c68886a78a9551
BLAKE2b-256 checksum
How to use checksums
b6ef4c382b3cf926d5dccbc05dba1b51c196ee4a3374ecb0844eedea42860dbb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Security for SecretPlots 3.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page