Skip to content

Repository files navigation

rootfig

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

Documentation · Gallery · Quick start

Documentation DOI CI 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 are both 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,
    ratio="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,
    ratio=True,
    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.

What you can do

  • Select events and objects with readable expressions. Write cuts such as count(Jet_pt) >= 2 or Muon_pt > 20; 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 and ratio panels share binning and propagate histogram uncertainties; 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.
  • Show systematic uncertainties. Attach weight, branch, file or normalisation variations to a sample; stacks and ratio 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.
  • 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

If you already have hist.Hist objects, rf.plot_histograms draws them with the same options. 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.

About

Publication-quality figures straight from ROOT trees, without ROOT

Resources

Contributing

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages