Skip to main content

rootfig

Publication-quality figures straight from ROOT trees and histograms, without ROOT.

Documentation · Gallery · Quick start

Documentation DOI CI Key4hep codecov PyPI Python License

Go from a ROOT file to a styled figure in one call. Choose a variable, add a selection, and plot:

import rootfig as rf

rf.plot("events.root", "Muon_pt", tree="events", selection="Muon_pt > 20", bins=50)

Start with a single distribution; add samples, weights, stacks and ratio panels as your analysis grows. Every plot gives you a matplotlib figure to customise and save.

Logarithmic axes with log-spaced bins    Two-dimensional histogram

Broken x axis with a ratio panel    FCC-ee stack scaled to luminosity with a significance panel

Explore the gallery → See each figure alongside the code that makes it, from simple overlays to stacked data/MC comparisons, broken axes and 2D histograms, in the neutral style or in that of ATLAS, CMS, LHCb, ALICE or DUNE.

Installation

pip install rootfig
# or
uv add rootfig

Python 3.12 or newer. No ROOT installation is needed; TTree and RNTuple files and stored TH1/TH2 histograms are all supported.

Compare samples in one call

import rootfig as rf

# Overlay two samples, each normalised to unity, with a Signal / Background panel.
rf.plot(
    {"Signal": "signal.root", "Background": "background.root"},
    "Muon_pt",
    tree="events",
    selection="abs(Muon_eta) < 2.5",
    weight="event_weight",
    bins=(50, 0, 200),
    normalize=True,
    panel="ratio",
    reference="Background",
)

Build up to a full analysis

Define samples, variables, cuts and styles once, then reuse them across plots:

import rootfig as rf

signal = rf.Sample("sig_*.root", tree="events", label="Signal", weight="mc_weight")
background = rf.Sample("bkg.root", tree="events", label="Background", weight="mc_weight")
data = rf.Sample("data.root", tree="events", label="Data", is_data=True)

pt = rf.Variable("Muon_pt", bins=(50, 0, 200), label=r"$p_T^{\mu}$", unit="GeV")
baseline = rf.Cut("nMuon >= 1") & "abs(Muon_eta) < 2.5"
style = rf.Style(experiment="ATLAS", status="Internal", lumi=140, com=13.6)

p = rf.plot(
    [background, signal],
    pt,
    observed=data,
    selection=baseline,
    stack=True,
    panel="ratio",
    data_errors="auto",  # Poisson intervals for unweighted data
    logy=True,
    style=style,
)
p.ax.set_ylim(top=1e5)  # it is a normal matplotlib Axes
p.save("muon_pt.pdf")

Everything you get back is a standard object: p.fig and p.ax are matplotlib Figure/Axes, p.hists are hist.Hist objects, and rf.load(...) returns Awkward arrays.

For histogram files, Group sums processes and a Variable crops and merges their bins just as it bins a tree. PlotBook draws the variants from one preparation; rf.ALL discovers every shared histogram for an overview.

# Histogram files: one per process, already scaled to 5 ab^-1
ww = rf.Sample("outputs/p8_ee_WW_ecm240.root", label="WW")
zz = rf.Sample("outputs/p8_ee_ZZ_ecm240.root", label="ZZ")
zh = rf.Sample("outputs/p8_ee_ZH_ecm240.root", label="ZH")
fcc = rf.Style(experiment="FCC-ee", status="Simulation", com="240 GeV", lumi="5 ab^-1")

book = rf.PlotBook(
    [rf.Group([ww, zz], label="VV"), zh],
    [rf.Variable("zmumu_recoil_m", bins=(200, 120, 140), label="Recoil mass", unit="GeV")],
    variants={"stack": {"stack": True}, "nostack": {"stack": ["VV"]}},
    plot_kwargs={"style": fcc},
)
book.save_pdf("zh.pdf")  # rf.ALL in place of the list plots every histogram the files share

What you can do

  • Select events and objects with readable expressions. Write cuts such as count(Jet_pt) >= 2 or Muon_pt > 20, calling functions of your own where the built-in ones end (functions={"delta_r": delta_r}); event and object selections have explicit rules, and event weights carry through to each selected object.
  • Compare samples with a few keywords. Overlays, stacks, data points (√N as ROOT draws them, or Poisson intervals) and a lower panel (ratio, difference, relative difference, asymmetry, pull or significance, against a reference you name) share binning and propagate histogram uncertainties; ROOT's χ² and Kolmogorov tests quote the agreement (goodness_of_fit=True). Bin edges and (n, low, high) are used as given, while a range inferred from the data ignores far outliers, so -999 sentinels do not set the axis. Normalise to unity, density, bin width or luminosity; stack some samples and overlay the rest. Draw several samples as one histogram with rf.Group, each keeping its own weights, cross section and systematics.
  • Show systematic uncertainties. Attach weight, branch, file or normalisation variations to a sample; stacks and lower panels draw the combined statistical and systematic band, and every component stays accessible.
  • Style figures for your analysis. Add experiment labels, units, log axes and broken axes, then refine the result with matplotlib.
  • Produce whole sets of plots. rf.PlotBook runs one rf.plot call over variables × selections × variants, lazily, and saves each under a deterministic name or all of them as one multipage PDF; rf.ALL discovers the variables from the files, and select() filters the book down while iterating on a plot.
  • Go beyond 1D plots. Draw 2D histograms, correlations, efficiencies, profiles, resolutions and significance panels; produce cut flows and summary statistics from the same inputs.
  • Work directly with your files. Read TTree and RNTuple data, combine files with globs, limit entry ranges for quick checks, and use EDM4hep split collections. Only the branches your expressions need are read.

Documentation

Read the docs or browse the gallery for examples with figures and code.

Relation to the ecosystem

rootfig brings a TTree::Draw-like workflow to the Scientific Python HEP stack, building on familiar libraries:

Task Library What rootfig adds
Reading ROOT files uproot file globs, tree auto-detection, reading only the required branches
Jagged arrays Awkward Array the per-event/per-object rules for cuts and weights
Histograms hist / boost-histogram shared binning, robust automatic ranges, normalisation, ratios
Drawing mplhep + matplotlib overlays, stacks, ratio panels, labels and legends with good defaults
Intervals SciPy ROOT's Poisson and efficiency intervals, kept through scaling and normalisation

If your histograms already exist, rf.plot draws them too: name a TH1 stored in the file instead of a branch (rf.plot("zh_histo.root", "m_recoil")), or pass hist.Hist objects directly. If you want the arrays, rf.load returns them. See the ecosystem guide for details.

Development

git clone https://github.com/jbeirer/rootfig
cd rootfig
uv sync --all-groups
uv run pytest
uv run ruff check . && uv run ruff format --check .
uv run mypy

See CONTRIBUTING.md for details.

Citation

If rootfig is useful in your research, please cite it:

@software{rootfig,
  author = {Beirer, Joshua Falco},
  doi = {10.5281/zenodo.22726311},
  license = {MIT},
  title = {{rootfig}},
  url = {https://github.com/jbeirer/rootfig},
  year = {2026}
}

License

MIT. See LICENSE.

Metadata

Release files for rootfig 0.11.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 rootfig 0.11.0
File Size Uploaded Provenance Actions
rootfig-0.11.0.tar.gz 21.5 MB With provenance Details for rootfig-0.11.0.tar.gz

Built distribution (wheel)

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

Total release size: 21.8 MB

Release files / rootfig-0.11.0.tar.gz

Download URL rootfig-0.11.0.tar.gz
Size 21.5 MB
Tags Source
SHA-256 checksum
How to use checksums
92a13f46ddb319ad524c5ca3209cf67faf264192c5578d43b2389e9cc5873900
BLAKE2b-256 checksum
How to use checksums
b44e838384d189c9b87098b71dbe179937bf260978f6baa3d3437830329a63a1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

Release files / rootfig-0.11.0-py3-none-any.whl

Download URL rootfig-0.11.0-py3-none-any.whl
Size 279.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1808f3b9e1373edf3065f5bd8737aa3f7c6f21a9f81634fd84afa654f688edcf
BLAKE2b-256 checksum
How to use checksums
6ebfec13fbaa7f4603487c2eb1e329869240f0bf8196f41569a0885fa391c2df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

Security for rootfig 0.11.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.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for rootfig-0.11.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 31, 2026.

Transparency log

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