| name | mplhep |
| description | Use when creating ATLAS publication-quality plots with matplotlib using mplhep: applying the ATLAS experiment style, placing ATLAS labels (Internal, Preliminary, Simulation), plotting 1D or 2D histograms from hist/uproot/numpy, making data/MC comparison plots with ratio panels, using mplhep comparison functions (ratio, pull, split_ratio), or customizing ATLAS label positions and luminosity text on matplotlib figures. |
mplhep -- Matplotlib for HEP
Overview
mplhep is the scikit-hep matplotlib wrapper for publication-quality HEP plots.
It provides experiment-specific styles (ATLAS, CMS, LHCb, ALICE, DUNE),
histogram plotting functions that accept hist.Hist, uproot.TH1, and numpy
arrays via the Unified Histogram Interface (UHI), and dedicated comparison
plotters for data/MC ratio panels. For ATLAS work, mplhep replaces the ROOT
atlasrootstyle macros with a pure-Python matplotlib workflow.
When to Use
- Creating ATLAS publication-quality plots in matplotlib (not ROOT)
- Applying the ATLAS experiment style (
mplhep.style.use("ATLAS"))
- Placing ATLAS labels (Internal, Preliminary, Simulation) on matplotlib figures
- Plotting prebinned histograms from
hist.Hist, uproot.TH1, or numpy arrays
- Making data/MC comparison plots with ratio, pull, or split-ratio panels
- Customizing ATLAS label positions, luminosity text, and center-of-mass energy
- Using
mplhep.comp.data_model() for stacked background + signal + data plots
Key Concepts
| Function / Module | Purpose |
|---|
mh.style.use("ATLAS") | Apply the ATLAS matplotlib style globally |
mh.atlas.label() | Place official ATLAS label with status, lumi, com |
mh.histplot() | Plot 1D histograms (step, fill, errorbar, band, bar) |
mh.hist2dplot() | Plot 2D histograms with optional colorbar |
mh.comp.hists() | Compare two histograms with a ratio/pull panel |
mh.comp.data_model() | Data vs stacked MC with comparison panel |
mh.comp.comparison() | Standalone comparison panel (no main plot) |
mh.mpl_magic() | Auto-fit y-axis for labels and legends |
mh.add_text() | Place arbitrary text at named locations |
mh.savelabels() | Generate plot variations (Preliminary, Final, WIP) |
mh.subplots() | Multi-panel figure with automatic sizing |
ATLAS Label Categories
Label text follows ATLAS publication policy (identical to the ROOT
atlasrootstyle macros covered by the astyle skill):
| Status text | Usage |
|---|
"" | Approved results in published papers |
"Preliminary" | Conference results not yet in a paper |
"Internal" | Results not for public distribution |
"Simulation" | Simulation-only results (no real data) |
"Work in Progress" | Early-stage results, not for approval |
Supported Input Formats
mh.histplot() accepts any UHI-compatible histogram:
hist.Hist -- error bars shown automatically when Weight() storage is
used (has .variances())
uproot.TH1 -- ROOT histograms read via uproot, plotted directly
numpy.histogram tuple -- (values, bin_edges) from np.histogram()
- Array-like -- plain list or array of bin heights (bins auto-generated)
Canonical Patterns
Minimal ATLAS Histogram
import matplotlib.pyplot as plt
import mplhep as mh
import numpy as np
mh.style.use("ATLAS")
fig, ax = plt.subplots()
mh.histplot(*np.histogram(np.random.normal(0, 1, 1000), bins=40), ax=ax)
mh.atlas.label("Internal", data=False, lumi=150, com=13)
mh.mpl_magic(soft_fail=True)
fig.savefig("plot.pdf")
Plotting a hist.Hist Object
import matplotlib.pyplot as plt
import hist
import mplhep as mh
mh.style.use("ATLAS")
h = hist.Hist(hist.axis.Regular(50, 0, 500, name="pt", label=r"$p_T$ [GeV]"))
h.fill(pt=jet_pts_gev, weight=event_weights)
fig, ax = plt.subplots()
mh.histplot(h, ax=ax, histtype="errorbar", label="Data")
mh.atlas.label("Internal", data=True, lumi=150, com=13)
mh.mpl_magic(soft_fail=True)
Stacked Backgrounds with Data Overlay
mh.style.use("ATLAS")
fig, ax = plt.subplots()
mh.histplot(
[h_bkg1, h_bkg2, h_signal],
histtype="fill",
stack=True,
label=["Bkg 1", "Bkg 2", "Signal"],
ax=ax,
)
mh.histplot(h_data, histtype="errorbar", label="Data", ax=ax, color="black")
ax.legend(loc="upper right")
mh.atlas.label("Internal", data=True, lumi=150, com=13)
mh.mpl_magic(soft_fail=True)
Data/MC Comparison with Ratio Panel
mh.style.use("ATLAS")
fig, ax_main, ax_comp = mh.comp.data_model(
data_hist=h_data,
stacked_components=[h_bkg1, h_bkg2, h_signal],
stacked_labels=["Bkg 1", "Bkg 2", "Signal"],
xlabel=r"$m_{jj}$ [GeV]",
ylabel="Events",
)
mh.atlas.label("Internal", data=True, lumi=150, ax=ax_main)
mh.mpl_magic(soft_fail=True)
The default comparison is split_ratio. Other options: ratio, pull,
difference, relative_difference, asymmetry, efficiency.
Two-Histogram Comparison
fig, ax_main, ax_comp = mh.comp.hists(
h1, h2,
comparison="ratio",
xlabel=r"$m_{jj}$ [GeV]",
ylabel="Events",
h1_label="Data",
h2_label="MC",
)
mh.atlas.label("Internal", data=True, ax=ax_main)
mh.mpl_magic(soft_fail=True)
Blinding a Signal Region
mh.histplot(h_data, ax=ax, blind=(-1.0, 1.0), label="Data")
fig, ax_main, ax_comp = mh.comp.data_model(
data_hist=h_data,
stacked_components=[h_bkg1, h_bkg2],
stacked_labels=["Bkg 1", "Bkg 2"],
blind=(120, 130),
)
Gotchas
- Always call
mh.mpl_magic(soft_fail=True) after mh.atlas.label(): The
ATLAS style requires this to auto-fit the y-axis around labels and legends.
Other experiments (CMS, DUNE) do not need it.
- Apply style at script top, not per-function:
mh.style.use("ATLAS") sets
global rcParams. Call it once before creating any figures.
plt.style.context() is unreliable: Due to matplotlib font-handling
limitations, context managers do not work reliably with mplhep styles. Use
mh.style.use() globally.
- ATLAS luminosity format defaults to integer:
lumi_format="{0:.0f}", so
lumi=140 renders as "140 fb^{-1}". Override with lumi_format="{0:.1f}" if
decimal precision is needed.
- Automatic error bars with
hist.Hist: When a histogram has .variances()
(i.e. Weight() storage), histplot shows error bars automatically. Numpy
histogram tuples do not carry variance information.
mh.hist() does not return UHI objects: Unlike mh.histplot(), the
convenience wrapper mh.hist() runs np.histogram internally and cannot be
used with other mplhep histogram functions.
- All ATLAS energy/momentum values are in MeV in the detector/analysis
framework, but plot axis labels conventionally show GeV or TeV. Divide by 1000
before filling histograms.
- Style is a dict: Access as
mh.style.ATLAS to inspect or extend rcParams.
Customize via mh.style.use([mh.style.ATLAS, {"font.size": 14}]).
Interop
- astyle (ROOT): The astyle skill covers the ROOT
atlasrootstyle macros
(SetAtlasStyle, ATLASLabel, myText). Use mplhep for matplotlib-based
ATLAS plots and atlasrootstyle for ROOT/PyROOT-based plots. The label
categories and publication policy rules are identical across both tools.
- hist:
mh.histplot() accepts hist.Hist objects natively. Use hist for
histogram creation, slicing, and rebinning; pass directly to mplhep for
plotting.
- uproot:
mh.histplot() accepts uproot.TH1 objects directly -- read ROOT
files with uproot and plot without conversion.
- coffea: Coffea processors accumulate
hist.Hist objects which plot
directly with mplhep.
- boost-histogram:
mh.histplot() accepts bh.Histogram via UHI.
- Other experiment styles: mplhep also supports CMS (
mh.style.use("CMS")),
LHCb (mh.style.use("LHCb2")), ALICE, and DUNE styles with matching label
functions (mh.cms.label(), mh.lhcb.label(), etc.).
Docs
https://mplhep.readthedocs.io/
For detailed reference beyond the upstream docs, consult the bundled files:
references/atlas-label-options.md -- Full ATLAS label parameter
reference, label positions, text placement, and savelabels workflow
references/comparison-plots.md -- All comparison types with formulas,
data/MC patterns, blinding syntax, and custom multi-panel layouts