Skip to content

API reference

Top level

rootfig.plot

plot(
    data: Any,
    variable: str | Variable,
    *,
    tree: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    bins: Bins | None = None,
    range: RangeSpec = None,
    label: str | Sequence[str] | None = None,
    observed: Any = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
    unit: str | None = None,
    title: str | None = None,
    normalize: NormalizeSpec = None,
    stack: bool = False,
    ratio: RatioSpec = False,
    ratio_ylim: tuple[float, float] | None = None,
    ratio_label: str | None = None,
    ratio_uncertainty: RatioUncertainty | None = None,
    logx: bool | None = None,
    logy: bool = False,
    flow: FlowSpec = "hint",
    histtype: HistType | None = None,
    errorbars: bool | None = None,
    xlim: tuple[float, float] | None = None,
    ylim: tuple[float | None, float | None] | None = None,
    xbreak: tuple[float, float] | None = None,
    legend: bool | str | None = None,
    stats: bool | str = False,
    text: str | Sequence[str] | None = None,
    style: StyleLike = None,
    figsize: tuple[float, float] | None = None,
    ax: AxesLike = None,
    nonfinite: NonFinitePolicy = "drop",
    systematics: Mapping[str, SystematicLike] | None = None,
    save: str | None = None,
) -> Plot

Histogram a variable from one or more samples and draw it.

This is the TTree::Draw of rootfig: read only the branches needed, evaluate the expressions, apply the selection with the documented per-event/per-object semantics, fill hist.Hist objects with a binning shared by all samples, and render them with mplhep.

Parameters:

Name Type Description Default
data Any

What to plot: a file path or glob, "path:tree", a list of those (one sample each), a {label: files} mapping, one or more :class:~rootfig.model.Sample objects, or in-memory arrays (a mapping of arrays or an Awkward record array).

required
variable str | Variable

Branch name or expression (see :mod:rootfig.expressions), or a :class:~rootfig.model.Variable carrying binning and labels.

required
tree str | None

Tree name for file inputs; auto-detected when a file holds one tree.

None
selection CutLike | None

Boolean expression or :class:~rootfig.model.Cut applied before filling. Per-object selections mask objects of jagged variables; per-event selections drop events.

None
weight str | None

Weight expression, e.g. "mc_weight * sf"; per-event weights are broadcast onto objects.

None
lumi float | str | None

Integrated luminosity to scale simulated samples to, in fb^-1 or as a string with a unit ("10.8 ab^-1"). Applies to samples with a cross section (Sample(xsec=..., ngen=...)): their weights are multiplied by xsec * lumi / ngen. Also written into the label unless the style already has a luminosity.

None
bins Bins | None

Binning: an int (range inferred from the data), (n, low, high), bin edges, or a hist axis. Overrides the Variable's binning.

None
range RangeSpec

Range for integer bins: (low, high), "robust" (the default) or "auto". "robust" ignores values far from the bulk of the data, so sentinels such as -999 do not set the axis, and cuts the thin end of a tail so a long one does not leave the rest of the distribution in a corner of the axis. Nothing is dropped: those values land in the under/overflow, which flow shows. Use "auto" for the full finite minimum and maximum.

None
label str | Sequence[str] | None

Legend label(s) for samples given as plain files.

None
observed Any

A sample of observed data (or the file(s) for one) drawn as points, excluded from stacks and used as numerator of the ratio.

None
xlabel str | None

Axis labels; defaults come from the variable, the normalisation and the bin width (Events / 2 GeV).

None
ylabel str | None

Axis labels; defaults come from the variable, the normalisation and the bin width (Events / 2 GeV).

None
unit str | None

Axis labels; defaults come from the variable, the normalisation and the bin width (Events / 2 GeV).

None
title str | None

Axis labels; defaults come from the variable, the normalisation and the bin width (Events / 2 GeV).

None
normalize NormalizeSpec

True/"unity" (sum to one), "density", "width" (divide by bin width) or a number to normalise to.

None
stack bool

Stack the non-data samples.

False
ratio RatioSpec

True for a ratio panel: data / total MC for a stack (needs observed=), data / the first non-data sample when data is overlaid, otherwise every further sample over the first; a sample label to use as the reference (all other histograms, data included, are divided by it); or a significance panel: "significance" (S/sqrt(B)), "s/sqrt(b)" or "s/sqrt(s+b)", where the signal is the last non-data sample (the top of a stack) and the background the sum of the others; ("s/sqrt(b)", "Signal") names the signal sample.

False
ratio_ylim tuple[float, float] | None

Ratio panel range, y label, and uncertainty treatment ("propagate" or "numerator" with a reference band; by default data uses "numerator" and simulation "propagate", so systematic sources shared with the reference cancel). The label is shrunk, and if needed wrapped onto two lines, to fit the short panel; pass a shorter ratio_label ("Ratio") to keep it at full size.

None
ratio_label tuple[float, float] | None

Ratio panel range, y label, and uncertainty treatment ("propagate" or "numerator" with a reference band; by default data uses "numerator" and simulation "propagate", so systematic sources shared with the reference cancel). The label is shrunk, and if needed wrapped onto two lines, to fit the short panel; pass a shorter ratio_label ("Ratio") to keep it at full size.

None
ratio_uncertainty tuple[float, float] | None

Ratio panel range, y label, and uncertainty treatment ("propagate" or "numerator" with a reference band; by default data uses "numerator" and simulation "propagate", so systematic sources shared with the reference cancel). The label is shrunk, and if needed wrapped onto two lines, to fit the short panel; pass a shorter ratio_label ("Ratio") to keep it at full size.

None
logx bool | None

Logarithmic axes. logx=None (default) follows the Variable's log flag; True/False override it.

None
logy bool | None

Logarithmic axes. logx=None (default) follows the Variable's log flag; True/False override it.

None
flow FlowSpec

Under/overflow display: "hint" (arrows), "show" (extra bins), "sum" (added to the edge bins, also for ratios and limits), "none".

'hint'
histtype HistType | None

Default drawing style for non-data samples: "step", "fill", "errorbar" or "band".

None
errorbars bool | None

Draw statistical error bars on non-data histograms.

None
xlim tuple[float, float] | None

Axis limits; ylim entries may be None to keep the automatic value.

None
ylim tuple[float, float] | None

Axis limits; ylim entries may be None to keep the automatic value.

None
xbreak tuple[float, float] | None

(a, b) to cut the x axis: the range between a and b is removed and the two remaining segments are drawn side by side with a break mark (e.g. a peak and a far tail, or a sentinel region). Works with ratio panels; not with ax=.

None
legend bool | str | None

False to suppress, or a matplotlib location string.

None
stats bool | str

Add a box with entries, mean and standard deviation per sample; True or a location string.

False
text str | Sequence[str] | None

Extra text line(s) drawn with the experiment label.

None
style StyleLike

:class:~rootfig.model.Style, an experiment name ("ATLAS", ...), or None for the neutral rootfig style.

None
figsize tuple[float, float] | None

Figure size in inches.

None
ax AxesLike

Draw into existing axes (Axes or (main, ratio) pair).

None
nonfinite NonFinitePolicy

"drop" (warn) or "error" for nan/inf values.

'drop'
systematics Mapping[str, SystematicLike] | None

Sources of systematic uncertainty applied to every simulated sample, in the forms Sample(systematics=...) accepts (a sample's own source of the same name takes precedence), e.g. {"lumi": 0.017}. Stacks draw the statistical and systematic uncertainty as one band, overlaid samples with variations a light band in their colour, and the ratio panel includes them in its band and error bars. Sources of the same name are fully correlated across samples, different ones added in quadrature; Plot.uncertainty() returns the components.

None
save str | None

Path to save the figure to (also returned in the :class:Plot).

None

Returns:

Type Description
Plot

The figure, axes, histograms and ratios.

rootfig.plot_histograms

plot_histograms(
    hists: Sequence[Histogram | Hist],
    *,
    variable: Variable | None = None,
    labels: Sequence[str] | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
    title: str | None = None,
    normalize: NormalizeSpec = None,
    stack: bool = False,
    ratio: RatioSpec = False,
    ratio_ylim: tuple[float, float] | None = None,
    ratio_label: str | None = None,
    ratio_uncertainty: RatioUncertainty | None = None,
    logx: bool | None = None,
    logy: bool = False,
    flow: FlowSpec = "hint",
    histtype: HistType | None = None,
    errorbars: bool | None = None,
    xlim: tuple[float, float] | None = None,
    ylim: tuple[float | None, float | None] | None = None,
    xbreak: tuple[float, float] | None = None,
    legend: bool | str | None = None,
    stats: bool | str = False,
    style: StyleLike = None,
    figsize: tuple[float, float] | None = None,
    ax: AxesLike = None,
    assume_poisson: bool = False,
    save: str | None = None,
) -> Plot

Draw already-filled histograms (hist.Hist or :class:~rootfig.histograms.Histogram).

Accepts the same drawing options as :func:plot. Plain hist.Hist objects are labelled from labels (or numbered) and converted to Weight storage if they have a plain count storage; mark data by passing :class:~rootfig.histograms.Histogram objects with is_data=True. Overlaid histograms may have different binnings; stacks, ratio panels and flow="show" need identical bin edges.

A count-storage histogram that was filled with weights (or rescaled) has lost its sum of squared weights and is rejected with a ValueError; assume_poisson=True draws it anyway with the absolute bin contents as variances (a warning is issued). Fill with hist.storage.Weight() to keep the real uncertainties.

Systematic uncertainties come from the histograms' variations (Histogram(h, label=..., variations={"jes": (h_up, h_down)})) and are drawn as in :func:plot.

rootfig.plot2d

plot2d(
    data: Any,
    x: str | Variable,
    y: str | Variable,
    *,
    tree: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    bins: Bins | tuple[Bins, Bins] | None = None,
    normalize: NormalizeSpec = None,
    logz: bool = False,
    logx: bool | None = None,
    logy: bool | None = None,
    cmap: str | Any = "viridis",
    colorbar: bool = True,
    zlabel: str | None = None,
    title: str | None = None,
    text: str | Sequence[str] | None = None,
    style: StyleLike = None,
    figsize: tuple[float, float] | None = None,
    ax: AxesLike = None,
    nonfinite: NonFinitePolicy = "drop",
    save: str | None = None,
) -> Plot

Draw a two-dimensional histogram of y versus x for one sample.

x and y must have the same structure (both per-event, or both per-object from the same collection). bins applies to both axes unless it is a pair of binning specifications, one per axis (so (40, 20) is two bin counts, never a range; a range needs (n, low, high)); per-axis ranges, labels and logarithmic scales (logx/logy default to the variables' log flags) are best given through :class:~rootfig.model.Variable objects. The two axes infer their ranges robustly and independently, and unlike the 1D plots there is no flow indicator, so give every axis that needs its full extent its own range="auto": rf.Variable(x, range="auto") leaves the y axis inferring robustly.

rootfig.histogram

histogram(
    data: Any,
    variable: str | Variable,
    *,
    tree: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    bins: Bins | None = None,
    range: RangeSpec = None,
    normalize: NormalizeSpec = None,
    nonfinite: NonFinitePolicy = "drop",
) -> Hist

Fill a single histogram and return it as a plain hist.Hist.

See :func:plot for the arguments. An integer bins without a range infers one robustly (range="auto" for the full finite minimum and maximum).

Examples:

>>> h = rf.histogram(
...     "events.root", "Muon_pt", tree="events", selection="Muon_pt > 20", bins=(50, 0, 200)
... )
>>> h.values().sum()

rootfig.histograms

Histogram construction, normalisation, ratios and statistics.

ProfileStatistic module-attribute

ProfileStatistic: TypeAlias = Literal['mean', 'std']

What a profile shows per bin: the weighted mean of y or its standard deviation.

NormalizeSpec module-attribute

NormalizeSpec: TypeAlias = (
    bool
    | Literal["unity", "density", "width"]
    | float
    | int
    | None
)

How to normalise a histogram.

  • False/None - raw sums of weights.
  • True or "unity" - scale so the visible bins sum to one.
  • "density" - scale so the integral over the visible range is one (contents divided by bin width and total).
  • "width" - divide each bin by its width (Events / GeV), no rescaling.
  • a number - scale so the visible bins sum to that number.

SIGNIFICANCE_KINDS module-attribute

SIGNIFICANCE_KINDS: tuple[str, ...] = (
    "significance",
    "s/sqrt(b)",
    "s/sqrt(s+b)",
)

Strings accepted by ratio= for a significance panel ("significance" means S/sqrt(B)).

SignificanceKind module-attribute

SignificanceKind: TypeAlias = Literal[
    "s/sqrt(b)", "s/sqrt(s+b)"
]

Per-bin significance estimators: S / sqrt(B) or S / sqrt(S + B).

Histogram dataclass

Histogram(
    hist: Hist,
    label: str,
    sample: Sample | None = None,
    stats: Summary | None = None,
    is_data: bool = False,
    color: str | None = None,
    histtype: HistType | None = None,
    normalization: str | None = None,
    variations: Mapping[str, tuple[Hist, Hist | None]]
    | None = None,
)

A filled histogram together with its provenance and drawing hints.

The underlying :class:Hist (Weight storage, so bin variances are the sums of squared weights) is available as :attr:hist; everything else is metadata used for legends, ratios and statistics.

Attributes:

Name Type Description
hist Hist

The histogram itself (1D or 2D).

label str

Legend label.

sample Sample | None

The :class:~rootfig.model.Sample this was filled from, if any.

stats Summary | None

Unbinned :class:~rootfig.histograms.Summary statistics of the filled values, if available.

is_data bool

Whether this represents observed data.

color, histtype

Drawing hints, None for style defaults.

normalization str | None

Description of the normalisation applied (None for raw counts).

variations Mapping[str, tuple[Hist, Hist]]

Systematic variations, {name: (up, down)} histograms with the binning of :attr:hist. A down given as None is filled in by mirroring the up shift around the nominal contents. Summarised by :func:~rootfig.histograms.uncertainty. The mapping is copied and made read-only; every stored pair contains two histograms. Use histogram.replace(variations=...) to replace it. The underlying hist.Hist objects remain mutable. Observed data (is_data) cannot carry variations, as for :class:~rootfig.model.Sample.

ndim property

ndim: int

Number of axes.

axis property

axis: Any

The first axis.

edges property

edges: FloatArray

Bin edges of the first axis.

centers property

centers: FloatArray

Bin centres of the first axis.

widths property

widths: FloatArray

Bin widths of the first axis.

integral property

integral: float

Sum of the visible bin contents (flow bins excluded).

sum_weights property

sum_weights: float

Sum of all bin contents including the flow bins.

underflow property

underflow: float

Content of the underflow bin (first axis, 1D only; 0 if the axis has none).

overflow property

overflow: float

Content of the overflow bin (first axis, 1D only; 0 if the axis has none).

underflow_variance property

underflow_variance: float

Variance of the underflow bin (first axis, 1D only; 0 if the axis has none).

overflow_variance property

overflow_variance: float

Variance of the overflow bin (first axis, 1D only; 0 if the axis has none).

entries property

entries: int | None

Number of filled entries from the unbinned statistics, or None if unknown.

A histogram that was not filled by rootfig carries no entry count: its bin contents are sums of weights, which only equal the number of fills for unweighted, unscaled histograms (see :attr:sum_weights).

values

values(*, flow: bool = False) -> FloatArray

Bin contents (sum of weights).

variances

variances(*, flow: bool = False) -> FloatArray

Bin variances (sum of squared weights).

errors

errors(*, flow: bool = False) -> FloatArray

Bin uncertainties, sqrt(variances).

replace

replace(**changes: Any) -> Histogram

Return a copy with the given fields changed, e.g. h.replace(label="B").

map_hists

map_hists(transform: Callable[[Hist], Hist]) -> Histogram

Return a copy with transform applied to the nominal histogram and every variation.

scaled

scaled(factor: float) -> Histogram

Return a copy multiplied by factor.

Variances scale with factor**2. The statistics' sum of weights (and sum of squared weights) scale along; the moments, entry count and effective entries are unchanged by a uniform rescaling.

Cutflow dataclass

Cutflow(sample: str, steps: tuple[CutflowStep, ...])

The cut flow of one sample: a sequence of :class:CutflowStep.

labels property

labels: list[str]

Step labels.

yields property

yields: ndarray

Weighted yield per step.

events property

events: ndarray

Raw event count per step.

efficiencies property

efficiencies: ndarray

Weighted efficiency of each step relative to the previous one (1 for the first).

The plain ratio of yields: nan where the previous yield is zero. With signed (NLO) weights a yield can be negative, and the ratio may then lie outside [0, 1]; it is still reported.

absolute_efficiencies property

absolute_efficiencies: ndarray

Weighted efficiency of each step relative to the first (see :attr:efficiencies).

CutflowStep dataclass

CutflowStep(
    label: str,
    expression: str,
    events: int,
    yield_: float,
    error: float,
)

Yields after one more cut has been applied.

Attributes:

Name Type Description
label str

The cut's label (or expression). For the first step: the label (or expression) of the sample's own selection, "All" if it has none.

expression str

The cut expression, empty for the first step.

events int

Raw number of events passing all cuts so far.

yield_ float

Weighted yield (sum of weights, including the sample scale and luminosity).

error float

Statistical uncertainty on yield_, sqrt(sum w^2).

CutflowTable dataclass

CutflowTable(rows: tuple[Cutflow, ...])

Cut flows of several samples with the same cuts; str(table) is an aligned text table.

samples property

samples: list[str]

Sample labels in order.

labels property

labels: list[str]

Step labels (identical for all samples).

get

get(sample: str) -> Cutflow

Return the cut flow of the sample labelled sample.

Efficiency dataclass

Efficiency(
    values: FloatArray,
    lower: FloatArray,
    upper: FloatArray,
    edges: FloatArray,
    label: str = "",
)

Bin-by-bin efficiency passed / total with a binomial confidence interval.

Attributes:

Name Type Description
values FloatArray

The efficiency passed / total; nan where the total weight is zero (an empty bin, or weights that cancel).

lower, upper

Bounds of the Wilson score interval (z standard deviations; z = 1 is the usual 68 % band), computed with the effective number of entries so weighted samples get sensible intervals. The interval always contains the value; it is nan where the total weight is negative or the value is outside [0, 1] (negative weights), since a binomial interval is undefined there while the ratio itself is still reported.

edges FloatArray

Bin edges.

label str

Legend label.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths.

errors property

errors: tuple[FloatArray, FloatArray]

(values - lower, upper - values), ready for yerr.

Profile dataclass

Profile(
    values: FloatArray,
    errors: FloatArray,
    counts: FloatArray,
    edges: FloatArray,
    statistic: ProfileStatistic = "mean",
    label: str = "",
)

A statistic of y in bins of x.

Attributes:

Name Type Description
values FloatArray

The weighted mean (statistic="mean") or standard deviation ("std") of y per bin; nan for empty bins (zero total weight). The standard deviation (and hence the error) is also nan where negative weights make the total weight negative or the weighted variance negative; the mean is still reported there.

errors FloatArray

Standard error: std / sqrt(n_eff) for the mean, std / sqrt(2 n_eff) for the standard deviation, with the effective entries n_eff.

counts FloatArray

Sum of weights per bin.

edges FloatArray

Bin edges.

statistic, label

What is shown and the legend label.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths.

Ratio dataclass

Ratio(
    values: FloatArray,
    errors: FloatArray,
    band: FloatArray,
    edges: FloatArray,
    syst_errors: tuple[FloatArray, FloatArray]
    | None = None,
    syst_band: tuple[FloatArray, FloatArray] | None = None,
)

Bin-by-bin ratio of two histograms.

Attributes:

Name Type Description
values FloatArray

numerator / denominator; nan where the denominator is zero.

errors FloatArray

Uncertainty on values (see :data:RatioUncertainty).

band FloatArray

Relative statistical uncertainty of the denominator, sqrt(var_den) / den; use as a band around one. nan where the denominator is zero.

edges FloatArray

Bin edges shared by both histograms.

syst_errors tuple[FloatArray, FloatArray] | None

Systematic uncertainty on values as (down, up) (matplotlib's yerr order), following :data:RatioUncertainty; None without systematic variations.

syst_band tuple[FloatArray, FloatArray] | None

Relative systematic uncertainty of the denominator below and above one, (down, up); None if the denominator has no variations.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths (for horizontal error bars).

total_errors

total_errors() -> tuple[FloatArray, FloatArray]

Statistical and systematic uncertainty on values in quadrature, (down, up).

total_band

total_band() -> tuple[FloatArray, FloatArray]

Relative statistical and systematic denominator uncertainty, (down, up).

Summary dataclass

Summary(
    entries: int,
    sum_weights: float,
    mean: float,
    std: float,
    sem: float,
    skewness: float,
    minimum: float,
    maximum: float,
    n_events: int = 0,
    n_selected_events: int = 0,
    n_missing: int = 0,
    n_nonfinite: int = 0,
    per_object: bool = False,
    _sum_w2: float = 0.0,
)

Weighted summary statistics of one column of values.

All moments use the weights as frequency weights. sem is the standard error of the mean, std / sqrt(n_eff) with the Kish effective sample size n_eff = (sum w)^2 / sum w^2.

With negative weights (e.g. NLO simulation) the weighted second moment can be negative; the standard deviation, its error and the skewness are then nan (the histogram itself is unaffected).

rms property

rms: float

Alias for :attr:std (ROOT calls the standard deviation "RMS").

effective_entries property

effective_entries: float

Kish effective sample size, equal to entries for unit weights.

format

format(
    precision: int = 4, *, include_entries: bool = True
) -> str

Return a compact multi-line text, e.g. for a statistics box on a plot.

Uses matplotlib math text for the symbols so it renders with the plot fonts ($\mu$, $\sigma$).

Uncertainty dataclass

Uncertainty(
    edges: FloatArray,
    nominal: FloatArray,
    stat: FloatArray,
    components: Mapping[
        str, tuple[FloatArray, FloatArray]
    ] = dict(),
)

Per-bin statistical and systematic uncertainties of a one-dimensional histogram.

All arrays cover the visible bins. Uncertainties are non-negative magnitudes below (down) and above (up) the nominal contents.

Attributes:

Name Type Description
edges FloatArray

Bin edges.

nominal FloatArray

Nominal bin contents.

stat FloatArray

Statistical uncertainty, sqrt(variances).

components Mapping[str, tuple[FloatArray, FloatArray]]

Signed shifts of every source, {name: (up - nominal, down - nominal)}.

syst_up property

syst_up: FloatArray

Systematic uncertainty above the nominal contents.

syst_down property

syst_down: FloatArray

Systematic uncertainty below the nominal contents.

total_up property

total_up: FloatArray

Statistical and systematic uncertainty above the nominal, in quadrature.

total_down property

total_down: FloatArray

Statistical and systematic uncertainty below the nominal, in quadrature.

has_systematics property

has_systematics: bool

True if at least one systematic source contributes.

as_weight_storage

as_weight_storage(
    histogram: Hist, *, assume_poisson: bool = False
) -> Hist

Return histogram with Weight storage (a copy if it had another storage).

Plain count storages (Double, Int64, ...) carry no sum of squared weights; their variances are taken as hist reports them, i.e. the counts (Poisson) for unweighted fills. After a weighted fill or arithmetic on such a storage hist reports no variances at all: the sum of squared weights is lost and cannot be reconstructed. That is an error unless assume_poisson=True, which uses the absolute bin contents as variances (the Poisson guess; a :class:~rootfig.errors.RootfigWarning says so).

Raises:

Type Description
TypeError

If the storage is not a count or Weight storage (Mean, ...).

ValueError

If the histogram reports no variances and assume_poisson is False.

fill

fill(axes: Sequence[Axis], columns: Columns) -> Hist

Create a Hist with Weight storage and fill it from columns.

One axis is expected per column in columns.arrays. Unweighted data is filled without weights, so bin variances equal bin counts.

profile

profile(
    x: FloatArray,
    y: FloatArray,
    edges: FloatArray,
    *,
    weights: FloatArray | None = None,
    statistic: ProfileStatistic = "mean",
    label: str = "",
) -> Profile

Bin x with edges and compute the weighted mean or standard deviation of y.

The variance is computed from the deviations from the bin mean (two passes), so a narrow spread at a large offset, e.g. std([1e9, 1e9 + 1]) = 0.5, is not lost to cancellation.

normalization_label

normalization_label(spec: NormalizeSpec) -> str | None

Short description of a normalisation, used as the y-axis label.

normalize_hist

normalize_hist(
    histogram: Hist, spec: NormalizeSpec
) -> Hist

Return a normalised copy of histogram with Weight storage.

Flow bins are scaled by the same factor as the visible bins for the rescaling modes, and divided by the neighbouring visible bin size for the per-width modes so they stay comparable when drawn. Histograms with a plain count storage are converted (see :func:~rootfig.histograms.as_weight_storage).

The rescaling modes divide by the signed sum of the visible bins, so the bins sum to the target even when negative weights dominate (the shape then flips sign, with a warning). A histogram whose visible bins sum to zero, because it is empty or because positive and negative weights cancel, is returned unchanged with a warning.

build_histograms

build_histograms(
    samples: Sequence[Sample],
    variable: Variable | str,
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
    systematics: Mapping[str, SystematicLike] | None = None,
) -> list[Histogram]

Fill one 1D histogram per sample with a binning shared by all of them.

Systematic variations (the sample's own and systematics, which apply to every non-data sample; a sample's own source of the same name wins) are filled into :attr:~rootfig.histograms.Histogram.variations with the binning chosen from the nominal values.

build_histograms_2d

build_histograms_2d(
    samples: Sequence[Sample],
    x: Variable | str,
    y: Variable | str,
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> list[Histogram]

Fill one 2D histogram per sample; x and y must share their structure.

combined_selection

combined_selection(
    sample: Sample, selection: CutLike | None
) -> Cut | None

Combine a sample's own selection with a plot-level selection using &.

combined_weight

combined_weight(
    sample: Sample, weight: str | None
) -> str | None

Combine a sample's own weight with a plot-level weight multiplicatively.

load_columns

load_columns(
    sample: Sample,
    variables: Sequence[Variable | str],
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> Columns

Read the required branches of sample and prepare flat columns.

The selection and weight given here are combined with those defined on the sample itself (see :func:combined_selection and :func:combined_weight). lumi scales samples that carry a cross section (see :meth:~rootfig.model.Sample.lumi_scale).

load_columns_each

load_columns_each(
    sample: Sample,
    variables: Sequence[Variable | str],
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> list[Columns]

Like :func:load_columns once per variable, reading the source only once.

The union of the branches needed by all variables, the selection and the weight is read in a single pass; each variable is then prepared on its own, so variables of different structure (per-event and per-object) may be mixed and each keeps the semantics it would have alone.

read_arrays

read_arrays(
    sample: Sample, expressions: Sequence[Any]
) -> tuple[dict[str, Any], int]

Read the branches expressions need from sample and return them with the event count.

Only the union of the required branches is read. The number of events is taken from the arrays, or from the source when nothing had to be read (all expressions constant), so "1" or "True" still know how many events there are.

source_length

source_length(source: Any) -> int

Return the number of events in source (num_entries(), else a branch length).

compatible_binning

compatible_binning(a: Hist, b: Hist) -> bool

Return True if both histograms are one-dimensional with identical edges.

Edges may differ by round-off only: the tolerance is a millionth of the smallest bin width, so bins shifted by a whole width at large coordinates (where NumPy's default relative tolerance would accept them) are rejected.

significance

significance(
    signal: Hist,
    background: Hist,
    *,
    kind: SignificanceKind = "s/sqrt(b)",
) -> Ratio

Per-bin significance of signal over background with propagated uncertainties.

Returned as a :class:Ratio (values, errors, edges; band is nan) so it can be drawn like a ratio panel. Bins with zero background (or zero total for "s/sqrt(s+b)") are nan.

correlation_matrix

correlation_matrix(columns: Columns) -> FloatArray

Return the (weighted) Pearson correlation matrix of all columns in columns.

Raises:

Type Description
SelectionError

If fewer than two columns or fewer than two entries (with non-zero weight) are available, or weights are negative.

describe_table

describe_table(
    summaries: Sequence[tuple[str, Summary]],
    precision: int = 4,
) -> str

Format (label, summary) pairs as an aligned plain-text table.

summarize

summarize(columns: Columns, index: int = 0) -> Summary

Compute :class:Summary statistics for column index of columns.

sum_histograms

sum_histograms(
    histograms: Sequence[Histogram], *, label: str = "Total"
) -> Histogram

Add one-dimensional histograms bin by bin, keeping their systematic variations.

Variations are matched by name and added linearly (fully correlated); a histogram without a source contributes its nominal contents to it. The sum keeps the inputs' normalization when they all share it and has none otherwise, so it never claims a scaling one of its parts lacks.

Raises:

Type Description
BinningError

If there are no histograms, an input is not one-dimensional, or the bin edges or flow-bin traits differ. Axis names and labels may differ.

uncertainty

uncertainty(histogram: Histogram) -> Uncertainty

Summarise the statistical and systematic uncertainties of a 1D histogram.

Raises:

Type Description
BinningError

If the histogram is not one-dimensional.

rootfig.load

load(
    data: Any,
    expressions: str
    | Sequence[str]
    | Mapping[str, str]
    | None = None,
    *,
    tree: str | None = None,
    selection: CutLike | None = None,
    entry_start: int | None = None,
    entry_stop: int | None = None,
) -> ak.Array

Read branches (or evaluate expressions) into an Awkward record array.

Parameters:

Name Type Description Default
data Any

File path(s), glob, "path:tree", a :class:~rootfig.model.Sample, or in-memory arrays.

required
expressions str | Sequence[str] | Mapping[str, str] | None

Branch names or expressions to evaluate. A mapping gives the output field names explicitly ({"pt": "Muon_pt / 1000"}). None reads every branch.

None
tree str | None

Tree name when data is a file specification.

None
selection CutLike | None

An event-level boolean expression; events failing it are dropped. A per-object selection raises :class:~rootfig.errors.SelectionError (apply object cuts inside the expressions instead, e.g. "Muon_pt[Muon_pt > 20]"), and so does a numeric one (an integer flag would otherwise be taken as an index array; write "flag != 0").

None
entry_start int | None

Entry range to read (ignored for a Sample, which carries its own).

None
entry_stop int | None

Entry range to read (ignored for a Sample, which carries its own).

None

Returns:

Type Description
Array

A record array with one field per expression.

rootfig.summarize

summarize(
    data: Any,
    variables: str | Variable | Sequence[str | Variable],
    *,
    tree: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    label: str | Sequence[str] | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> SummaryTable

Compute entries, mean, standard deviation, skewness, ... for variables and samples.

Examples:

>>> table = rf.summarize(
...     "events.root", ["MET", "Muon_pt"], tree="events", selection="nMuon > 0"
... )
>>> print(table)
>>> table.get("MET").mean

rootfig.correlation

correlation(
    data: Any,
    variables: Sequence[str | Variable],
    *,
    tree: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    labels: Sequence[str] | None = None,
    percent: bool = False,
    cmap: str | Any = "RdBu_r",
    annotate: bool = True,
    title: str | None = None,
    style: StyleLike = None,
    figsize: tuple[float, float] | None = None,
    ax: AxesLike = None,
    nonfinite: NonFinitePolicy = "drop",
    save: str | None = None,
) -> Plot

Draw the correlation matrix of several variables for one sample.

All variables must share the same structure (all per-event, or all per-object from one collection). The matrix is available as Plot.matrix. The matrix is titled "<sample>: correlation"; a style with an experiment draws that experiment's label above the matrix instead, and an explicit title is always shown.

rootfig.cutflow

cutflow(
    data: Any,
    cuts: Sequence[CutLike],
    *,
    tree: str | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    label: str | Sequence[str] | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> CutflowTable

Count events and weighted yields after each successive cut, per sample.

The first row holds all events (after the sample's own selection, if any); every further row applies one more cut. Per-object cuts pass an event when any object passes. weight, lumi and nonfinite work as in :func:plot: events with a nan/inf weight are excluded from all steps with a warning, or raise for nonfinite="error".

Examples:

>>> table = rf.cutflow(
...     [sig, bkg], ["nMuon >= 2", rf.Cut("MET > 50", label="MET"), "any(Jet_btag > 0.8)"]
... )
>>> print(table)
>>> table.get("Signal").efficiencies

rootfig.efficiency

efficiency(
    data: Any,
    variable: str | Variable,
    *,
    passed: CutLike,
    tree: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    bins: Bins | None = None,
    range: RangeSpec = None,
    label: str | Sequence[str] | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
    unit: str | None = None,
    title: str | None = None,
    logx: bool | None = None,
    xlim: tuple[float, float] | None = None,
    ylim: tuple[float | None, float | None] | None = None,
    legend: bool | str | None = None,
    text: str | Sequence[str] | None = None,
    style: StyleLike = None,
    figsize: tuple[float, float] | None = None,
    ax: AxesLike = None,
    z: float = 1.0,
    nonfinite: NonFinitePolicy = "drop",
    save: str | None = None,
) -> Plot

Plot the fraction of entries passing passed as a function of variable.

For every sample two histograms are filled with the same binning, all entries satisfying selection (the denominator) and those also satisfying passed (the numerator); the ratio is drawn as points with Wilson score intervals (z standard deviations, effective entries for weighted samples; see :func:rootfig.histograms.efficiency for the treatment of negative weights). The :class:~rootfig.histograms.Efficiency objects are returned in Plot.efficiencies. An integer bins without a range infers one robustly, shared by numerator and denominator (see :func:plot).

Examples:

>>> rf.efficiency(
...     "reco.root", "TrueMuon_pt", passed="TrueMuon_matched", bins=(20, 0, 100)
... )

rootfig.profile

profile(
    data: Any,
    x: str | Variable,
    y: str | Variable,
    *,
    statistic: ProfileStatistic = "mean",
    tree: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    bins: Bins | None = None,
    range: RangeSpec = None,
    label: str | Sequence[str] | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
    unit: str | None = None,
    title: str | None = None,
    logx: bool | None = None,
    logy: bool | None = None,
    xlim: tuple[float, float] | None = None,
    ylim: tuple[float | None, float | None] | None = None,
    legend: bool | str | None = None,
    text: str | Sequence[str] | None = None,
    style: StyleLike = None,
    figsize: tuple[float, float] | None = None,
    ax: AxesLike = None,
    nonfinite: NonFinitePolicy = "drop",
    save: str | None = None,
) -> Plot

Plot the mean (or standard deviation) of y in bins of x, per sample.

statistic="mean" draws the weighted mean with its standard error, the profile histogram of ROOT; "std" draws the standard deviation with its error, the usual resolution-versus-variable plot (make y the residual, e.g. "(reco_pt - true_pt) / true_pt"). x and y must have the same structure (both per-event or both per-object of one collection). xlabel and unit describe the x axis; logx/logy default to the variables' log flags. With negative weights a bin whose total weight is negative keeps its mean but has no error, and a bin whose weighted variance is negative has no standard deviation (nan). An integer bins without a range infers the x range robustly (see :func:plot). The :class:~rootfig.histograms.Profile objects are returned in Plot.profiles.

Examples:

>>> rf.profile(
...     "reco.root",
...     "true_pt",
...     "(reco_pt - true_pt) / true_pt",
...     statistic="std",
...     bins=(20, 0, 100),
...     unit="GeV",
... )

rootfig.significance

significance(
    signal: Hist,
    background: Hist,
    *,
    kind: SignificanceKind = "s/sqrt(b)",
) -> Ratio

Per-bin significance of signal over background with propagated uncertainties.

Returned as a :class:Ratio (values, errors, edges; band is nan) so it can be drawn like a ratio panel. Bins with zero background (or zero total for "s/sqrt(s+b)") are nan.

rootfig.evaluate

evaluate(
    expression: ExpressionLike,
    arrays: Mapping[str, Any] | Array,
    *,
    length: int | None = None,
) -> ak.Array

Parse (if needed) and evaluate expression on arrays.

This is a convenience wrapper around :func:parse and :meth:Expression.evaluate; length sizes constant expressions when arrays is empty.

Examples:

>>> import awkward as ak
>>> arrays = {"pt": ak.Array([[10.0, 30.0], [], [50.0]])}
>>> evaluate("pt > 20", arrays).tolist()
[[False, True], [], [True]]
>>> evaluate("count(pt)", arrays).tolist()
[2, 0, 1]

rootfig.ratio

Ratios of histograms with uncertainty propagation.

SignificanceKind module-attribute

SignificanceKind: TypeAlias = Literal[
    "s/sqrt(b)", "s/sqrt(s+b)"
]

Per-bin significance estimators: S / sqrt(B) or S / sqrt(S + B).

SIGNIFICANCE_KINDS module-attribute

SIGNIFICANCE_KINDS: tuple[str, ...] = (
    "significance",
    "s/sqrt(b)",
    "s/sqrt(s+b)",
)

Strings accepted by ratio= for a significance panel ("significance" means S/sqrt(B)).

Ratio dataclass

Ratio(
    values: FloatArray,
    errors: FloatArray,
    band: FloatArray,
    edges: FloatArray,
    syst_errors: tuple[FloatArray, FloatArray]
    | None = None,
    syst_band: tuple[FloatArray, FloatArray] | None = None,
)

Bin-by-bin ratio of two histograms.

Attributes:

Name Type Description
values FloatArray

numerator / denominator; nan where the denominator is zero.

errors FloatArray

Uncertainty on values (see :data:RatioUncertainty).

band FloatArray

Relative statistical uncertainty of the denominator, sqrt(var_den) / den; use as a band around one. nan where the denominator is zero.

edges FloatArray

Bin edges shared by both histograms.

syst_errors tuple[FloatArray, FloatArray] | None

Systematic uncertainty on values as (down, up) (matplotlib's yerr order), following :data:RatioUncertainty; None without systematic variations.

syst_band tuple[FloatArray, FloatArray] | None

Relative systematic uncertainty of the denominator below and above one, (down, up); None if the denominator has no variations.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths (for horizontal error bars).

total_errors

total_errors() -> tuple[FloatArray, FloatArray]

Statistical and systematic uncertainty on values in quadrature, (down, up).

total_band

total_band() -> tuple[FloatArray, FloatArray]

Relative statistical and systematic denominator uncertainty, (down, up).

compatible_binning

compatible_binning(a: Hist, b: Hist) -> bool

Return True if both histograms are one-dimensional with identical edges.

Edges may differ by round-off only: the tolerance is a millionth of the smallest bin width, so bins shifted by a whole width at large coordinates (where NumPy's default relative tolerance would accept them) are rejected.

ratio

ratio(
    numerator: Hist | Histogram,
    denominator: Hist | Histogram,
    *,
    uncertainty: RatioUncertainty = "propagate",
) -> Ratio

Compute numerator / denominator bin by bin with uncertainties.

Statistical uncertainties of numerator and denominator are uncorrelated. Histogram inputs with systematic variations also give :attr:Ratio.syst_errors and :attr:Ratio.syst_band, propagated source by source through the varied ratio itself: a source present on both sides varies numerator and denominator together (a shared luminosity uncertainty cancels), a source on one side only varies that side against the other's nominal contents. The shifts of different sources then combine like those of one histogram (see :mod:rootfig.histograms.systematics). With "numerator" only the numerator's sources enter the error bars; the denominator's are the band. A variation that empties a denominator bin leaves that bin's systematic uncertainty nan, with a :class:~rootfig.errors.RootfigWarning.

Raises:

Type Description
BinningError

If the histograms do not share the same one-dimensional binning.

significance

significance(
    signal: Hist,
    background: Hist,
    *,
    kind: SignificanceKind = "s/sqrt(b)",
) -> Ratio

Per-bin significance of signal over background with propagated uncertainties.

Returned as a :class:Ratio (values, errors, edges; band is nan) so it can be drawn like a ratio panel. Bins with zero background (or zero total for "s/sqrt(s+b)") are nan.

rootfig.log_bins

log_bins(n: int, low: float, high: float) -> np.ndarray

Return n + 1 logarithmically spaced bin edges between low and high.

Examples:

>>> log_bins(3, 1, 1000).tolist()
[1.0, 10.0, 100.0, 1000.0]

rootfig.use_style

use_style(style: StyleLike = None) -> Style

Apply style globally (until :func:matplotlib.pyplot.rcdefaults), return it.

This is the one function in rootfig that changes global matplotlib state; everything else uses :func:style_context.

rootfig.dark_theme

dark_theme() -> Iterator[None]

Draw everything made inside the block for a dark page.

The block runs under :data:DARK_THEME, so figures, axes and artists made with plain matplotlib (plt.subplots() for ax=, p.ax.text(...)) match. rootfig figures apply it again after their style sheet, so it also holds for experiment styles that fix a white background (ATLAS, LHCb, ALICE, DUNE); Style.rc still wins. The background is transparent, and :meth:Plot.save <rootfig.Plot.save> keeps it so, so one image suits any dark page.

Descriptions

rootfig.Sample dataclass

Sample(
    data: Any,
    *,
    tree: str | None = None,
    label: str | None = None,
    selection: CutLike | None = None,
    weight: str | None = None,
    is_data: bool = False,
    color: str | None = None,
    histtype: HistType | None = None,
    scale: float = 1.0,
    xsec: float | str | None = None,
    ngen: float | str | None = None,
    entry_start: int | None = None,
    entry_stop: int | None = None,
    systematics: Mapping[str, SystematicLike] | None = None,
)

A dataset: where it comes from, how it is labelled, and how it is drawn.

Parameters:

Name Type Description Default
data Any

File path(s), glob pattern(s), "path:tree" strings, a mapping of arrays, an Awkward record array, or an existing :class:~rootfig.io.Source.

required
tree str | None

Tree name for file inputs. Auto-detected when the file holds exactly one TTree/RNTuple.

None
label str | None

Legend label. Defaults to the file stem.

None
selection CutLike | None

A selection applied to this sample only, combined with the selection given to :func:rootfig.plot with &.

None
weight str | None

Expression for per-event (or per-object) weights, e.g. "mc_weight" or "mc_weight * pileup_sf". Combined multiplicatively with the weight given to :func:rootfig.plot.

None
is_data bool

Mark as observed data: drawn as points with error bars, excluded from stacks, and used as the numerator of data/MC ratios.

False
color str | None

Matplotlib colour. Defaults to the style's colour cycle.

None
histtype HistType | None

How to draw this sample; see :data:HistType. Defaults depend on the plot type.

None
scale float

Constant multiplied into every weight.

1.0
xsec float | str | None

Cross section of the process, in pb or as a string with a unit ("1.2 fb"). With a luminosity given to :func:rootfig.plot (lumi=), every weight is multiplied by xsec * lumi / ngen so that the histogram is the expected yield.

None
ngen float | str | None

Number of generated events the cross section refers to: a number, the name of an object in the file(s) holding it (a sum-of-weights histogram or a TParameter such as FCCAnalyses' "eventsProcessed"), or None to use the number of entries in the source.

None
entry_start int | None

Read only this range of entries (counted across files).

None
entry_stop int | None

Read only this range of entries (counted across files).

None
systematics Mapping[str, SystematicLike] | None

Sources of systematic uncertainty, {name: variation}: a weight expression or (up, down) pair of them replacing weight, a relative normalisation uncertainty (0.05), (up, down) normalisation factors, a mapping of branches to their (up, down) replacements ({"Jet_pt": ("Jet_pt_up", "Jet_pt_down")}), or Systematic.samples(...) for varied files. Sources with the same name in several samples are fully correlated; different names are independent. Not allowed together with is_data=True. The mapping is copied and made read-only; use sample.replace(systematics=...) to change it.

None

Examples:

>>> import rootfig as rf
>>> sig = rf.Sample("sig.root", tree="events", label="Signal", weight="mc_w")
>>> bkg = rf.Sample(["bkg_*.root"], tree="events", label="Background", color="gray")
>>> data = rf.Sample("data.root", tree="events", label="Data", is_data=True)
>>> mc = rf.Sample(
...     "mc.root", weight="w", systematics={"pileup": ("w_pu_up", "w_pu_down"), "xsec": 0.05}
... )

files property

files: tuple[str, ...]

The resolved input files, or an empty tuple for in-memory sources.

generated_events

generated_events() -> float

Return the number of generated events ngen refers to (see the class docstring).

lumi_scale

lumi_scale(lumi: float | str | None) -> float

Factor turning weights into expected yields at lumi; 1 without a cross section.

xsec (pb) times lumi (fb^-1, or a string with a unit) divided by :meth:generated_events.

replace

replace(**changes: Any) -> Sample

Return a copy with the given fields changed, e.g. sample.replace(label="B").

New values are validated and normalised exactly as by the constructor (selection accepts a string, source anything :func:~rootfig.io.as_source accepts, scale must be finite, ...).

rootfig.Variable dataclass

Variable(
    expression: str,
    bins: Bins = 50,
    range: RangeSpec = DEFAULT_RANGE,
    label: str | None = None,
    unit: str | None = None,
    log: bool = False,
    name: str | None = None,
)

What to histogram and how to present it.

Parameters:

Name Type Description Default
expression str

A rootfig expression (a branch name or a formula, see :mod:rootfig.expressions).

required
bins Bins

Binning specification, see :data:Bins. Default 50 bins over a range inferred from the data.

50
range RangeSpec

Range used when bins is an integer, see :data:RangeSpec. Defaults to :data:DEFAULT_RANGE ("robust"), which ignores far outliers such as -999 sentinels and cuts the thin end of a tail; "auto" uses the full finite minimum and maximum instead.

DEFAULT_RANGE
label str | None

Axis label; may contain matplotlib math text. Defaults to the expression.

None
unit str | None

Physical unit appended to the axis label as [unit] and used in the automatic y-axis label (Events / 2 GeV).

None
log bool

Draw the x axis with a logarithmic scale.

False
name str | None

Short identifier used for file names (:meth:Plot.save with a directory). Defaults to a sanitised version of the expression. An explicit name must be a plain file stem: it cannot contain path separators or be "."/"..".

None

safe_name property

safe_name: str

A file-system friendly identifier for this variable.

axis_label property

axis_label: str

The x-axis label including the unit, e.g. '$p_T$ [GeV]'.

parsed

parsed() -> Expression

Return the parsed :class:~rootfig.expressions.Expression.

replace

replace(**changes: Any) -> Variable

Return a copy with the given fields changed, e.g. var.replace(bins=20).

rootfig.Cut dataclass

Cut(expression: str, label: str | None = None)

A selection expression that composes with &, | and ~.

Plain strings are accepted wherever a Cut is; the class exists so selections can be built up and named in analysis scripts::

base = rf.Cut("nMuon >= 2", label="2 muons")
signal_region = base & "abs(Muon_eta) < 2.4" & ~rf.Cut("has_bjet")

Parameters:

Name Type Description Default
expression str

A rootfig expression producing booleans (see :mod:rootfig.expressions).

required
label str | None

Optional short description for legends or logging.

None

parsed

parsed() -> Expression

Return the parsed :class:~rootfig.expressions.Expression.

rootfig.Style dataclass

Style(
    experiment: str | None = None,
    status: str | None = None,
    text: str | Sequence[str] | None = None,
    lumi: float | str | None = None,
    com: float | str | None = None,
    lumi_unit: str = "fb^{-1}",
    com_unit: str = "TeV",
    simulation: bool | None = None,
    label_loc: int | None = None,
    base: str | Mapping[str, Any] | None = None,
    rc: Mapping[str, Any] = dict(),
    figsize: tuple[float, float] | None = None,
    colors: Sequence[str] | None = None,
    legend: bool | str = True,
    legend_kwargs: Mapping[str, Any] = dict(),
)

Appearance settings applied while a figure is drawn.

All fields are optional. Passing Style() (or nothing) gives the neutral rootfig look; setting experiment switches to that experiment's mplhep style and adds its label.

Parameters:

Name Type Description Default
experiment str | None

Experiment name written in the label, e.g. "ATLAS". When it matches one of :data:EXPERIMENT_STYLES the corresponding mplhep style sheet is used as base unless base is set explicitly.

None
status str | None

Text after the experiment name: "Internal", "Preliminary", "Work in Progress", "Simulation Preliminary", ...

None
text str | Sequence[str] | None

Extra line(s) below the label, e.g. r"$E_\\gamma = 65$ GeV" or a list of strings.

None
lumi float | str | None

Integrated luminosity shown in the label: a number in lumi_unit (default fb^-1) or a string carrying its own unit, "10.8 ab^-1".

None
com float | str | None

Centre-of-mass energy shown in the label: a number in com_unit (default TeV) or a string carrying its own unit, "240 GeV".

None
lumi_unit str

Units for numeric lumi and com ("fb^{-1}", "ab^{-1}", "TeV", "GeV", ...). Lepton-collider analyses typically set lumi_unit="ab^{-1}", com_unit="GeV".

'fb^{-1}'
com_unit str

Units for numeric lumi and com ("fb^{-1}", "ab^{-1}", "TeV", "GeV", ...). Lepton-collider analyses typically set lumi_unit="ab^{-1}", com_unit="GeV".

'fb^{-1}'
simulation bool | None

Whether to add "Simulation" to the label. None (default) adds it when the plot contains no data sample.

None
label_loc int | None

Placement of the experiment label (mplhep loc, 0-4). None uses the experiment's own convention (e.g. inside the frame for ATLAS, above it for CMS).

None
base str | Mapping[str, Any] | None

Name of an mplhep style ("ATLAS", "CMS", "LHCb2", "ALICE", "DUNE", "ROOT"), a matplotlib style name, or a mapping of rcParams. None selects the neutral rootfig defaults.

None
rc Mapping[str, Any]

Additional rcParams overrides applied on top of base.

dict()
figsize tuple[float, float] | None

Figure size in inches. Defaults to the style's figure.figsize, enlarged vertically when a ratio panel is present.

None
colors Sequence[str] | None

Colour cycle for samples without an explicit colour.

None
legend bool | str

Draw a legend (True), suppress it (False), or pass a matplotlib legend location string such as "upper left".

True
legend_kwargs Mapping[str, Any]

Extra keyword arguments forwarded to :meth:matplotlib.axes.Axes.legend.

dict()

has_label property

has_label: bool

True if an experiment label (or free text) should be drawn.

lumi_parts property

lumi_parts: tuple[str, str] | None

(value, unit) of the luminosity, e.g. ("10.8", "ab^{-1}"), or None.

com_parts property

com_parts: tuple[str, str] | None

(value, unit) of the centre-of-mass energy, e.g. ("240", "GeV"), or None.

text_lines property

text_lines: tuple[str, ...]

text normalised to a tuple of lines.

replace

replace(**changes: Any) -> Style

Return a copy with the given fields changed, e.g. style.replace(lumi=140).

rootfig.Systematic dataclass

Systematic(kind: SystematicKind, up: Any, down: Any = None)

One source of systematic uncertainty, as an up and an optional down variation.

Sample(systematics=...) and rf.plot(systematics=...) take a mapping from source name to one of these forms; only variations from other data need a Systematic written out (:meth:samples):

===================================== ============================================= form variation ===================================== ============================================= "w_up", ("w_up", "w_down") weight expression(s) replacing Sample.weight 0.05 the nominal histogram scaled by 1 +- 0.05; 0 <= u < 1 (1.10, 0.95) the nominal histogram scaled by these factors {"pt": ("pt_up", "pt_down")} branches replaced in variable, selection and weight Systematic.samples(up, down) other files or arrays (see :meth:samples) ===================================== =============================================

Every pair is (up, down). A variation without a down direction is symmetrised: the down variation shifts every bin by the opposite of the up shift. The plot-level weight=, the sample's scale and the luminosity scaling multiply every weight variation.

Attributes:

Name Type Description
kind SystematicKind

See :data:SystematicKind.

up, down

The up and down variation: a weight expression ("weight"), a mapping of branch names ("replace"), a dataset ("samples") or a factor ("norm"). down is None for a symmetrised variation. Branch replacement mappings are copied and made read-only.

symmetric property

symmetric: bool

True if the down variation is derived by mirroring the up variation.

samples classmethod

samples(up: Any, down: Any = None) -> Systematic

Take the variation from other data: file path(s), in-memory arrays or a Sample.

Anything that is not a Sample replaces only the nominal sample's data; selection, weight, cross section, tree name and entry range are kept. A Sample is used exactly as given.

Results

rootfig.Plot dataclass

Plot(
    fig: Figure,
    ax: Axes,
    ratio_ax: Axes | None = None,
    ax_right: Axes | None = None,
    ratio_ax_right: Axes | None = None,
    histograms: list[Histogram] = list(),
    ratios: list[Ratio] = list(),
    variable: Variable | None = None,
    matrix: FloatArray | None = None,
    efficiencies: list[Efficiency] = list(),
    profiles: list[Profile] = list(),
)

A finished figure plus the objects it was built from.

Everything is a standard matplotlib or hist object, so further customisation is ordinary matplotlib code::

p = rf.plot(...)
p.ax.set_ylim(top=500)
p.ax.axvline(91.2, color="gray", ls="--")
p.save("z_mass.pdf")

Attributes:

Name Type Description
fig Figure

The :class:matplotlib.figure.Figure.

ax Axes

The main :class:matplotlib.axes.Axes.

ratio_ax Axes | None

The ratio panel axes, or None.

ax_right, ratio_ax_right

The right-hand segments when the x axis is broken (xbreak), else None.

histograms list[Histogram]

The :class:~rootfig.histograms.Histogram objects drawn (each wraps a hist.Hist).

ratios list[Ratio]

The :class:~rootfig.histograms.Ratio objects drawn in the ratio panel.

variable Variable | None

The :class:~rootfig.model.Variable (x axis) if known; used for default file names.

matrix FloatArray | None

For correlation plots, the correlation matrix.

efficiencies, profiles

For :func:rootfig.efficiency and :func:rootfig.profile, the computed :class:~rootfig.histograms.Efficiency / :class:~rootfig.histograms.Profile objects.

axes property

axes: tuple[Axes, ...]

All axes in reading order: main (left, right), then ratio (left, right).

hists property

hists: list[Any]

The underlying hist.Hist objects, in drawing order.

uncertainty

uncertainty(label: str | None = None) -> Uncertainty

Statistical and systematic uncertainties of a histogram of the plot, or of their sum.

Parameters:

Name Type Description Default
label str | None

The label of one histogram. None sums all non-data histograms, with same-named systematic sources added linearly: the stack total of a stacked plot, the one simulated histogram if there is only one, and for an overlay a total that is not drawn, which needs every histogram to share its binning.

None

Raises:

Type Description
KeyError

If no histogram has label.

ValueError

If label is None and there are no non-data histograms.

BinningError

If label is None and the non-data histograms have different binnings (an overlay); pass a label instead.

save

save(
    path: str | PathLike[str],
    *,
    formats: Sequence[str] | None = None,
    **kwargs: Any,
) -> list[Path]

Save the figure and return the written paths.

Parameters:

Name Type Description Default
path str | PathLike[str]

Output file. If it is an existing directory (or ends with a separator), the file name is derived from the variable (<dir>/<variable>.pdf).

required
formats Sequence[str] | None

Optional list of formats (["pdf", "png"]). Each replaces the suffix of path; only the listed formats are written.

None
**kwargs Any

Forwarded to :meth:matplotlib.figure.Figure.savefig. Figures made by rootfig use constrained layout and are saved at exactly their figsize. For figures drawn into user axes without a layout engine, bbox_inches="tight" is used unless given. The background is the figure's own (facecolor="auto") unless given: saving runs after the style has been undone, so the global savefig.facecolor would otherwise paint over it (a :func:~rootfig.dark_theme figure would lose its transparency).

{}

show

show() -> None

Display the figure (plt.show()).

close

close() -> None

Close the figure to free memory.

rootfig.Histogram dataclass

Histogram(
    hist: Hist,
    label: str,
    sample: Sample | None = None,
    stats: Summary | None = None,
    is_data: bool = False,
    color: str | None = None,
    histtype: HistType | None = None,
    normalization: str | None = None,
    variations: Mapping[str, tuple[Hist, Hist | None]]
    | None = None,
)

A filled histogram together with its provenance and drawing hints.

The underlying :class:Hist (Weight storage, so bin variances are the sums of squared weights) is available as :attr:hist; everything else is metadata used for legends, ratios and statistics.

Attributes:

Name Type Description
hist Hist

The histogram itself (1D or 2D).

label str

Legend label.

sample Sample | None

The :class:~rootfig.model.Sample this was filled from, if any.

stats Summary | None

Unbinned :class:~rootfig.histograms.Summary statistics of the filled values, if available.

is_data bool

Whether this represents observed data.

color, histtype

Drawing hints, None for style defaults.

normalization str | None

Description of the normalisation applied (None for raw counts).

variations Mapping[str, tuple[Hist, Hist]]

Systematic variations, {name: (up, down)} histograms with the binning of :attr:hist. A down given as None is filled in by mirroring the up shift around the nominal contents. Summarised by :func:~rootfig.histograms.uncertainty. The mapping is copied and made read-only; every stored pair contains two histograms. Use histogram.replace(variations=...) to replace it. The underlying hist.Hist objects remain mutable. Observed data (is_data) cannot carry variations, as for :class:~rootfig.model.Sample.

ndim property

ndim: int

Number of axes.

axis property

axis: Any

The first axis.

edges property

edges: FloatArray

Bin edges of the first axis.

centers property

centers: FloatArray

Bin centres of the first axis.

widths property

widths: FloatArray

Bin widths of the first axis.

integral property

integral: float

Sum of the visible bin contents (flow bins excluded).

sum_weights property

sum_weights: float

Sum of all bin contents including the flow bins.

underflow property

underflow: float

Content of the underflow bin (first axis, 1D only; 0 if the axis has none).

overflow property

overflow: float

Content of the overflow bin (first axis, 1D only; 0 if the axis has none).

underflow_variance property

underflow_variance: float

Variance of the underflow bin (first axis, 1D only; 0 if the axis has none).

overflow_variance property

overflow_variance: float

Variance of the overflow bin (first axis, 1D only; 0 if the axis has none).

entries property

entries: int | None

Number of filled entries from the unbinned statistics, or None if unknown.

A histogram that was not filled by rootfig carries no entry count: its bin contents are sums of weights, which only equal the number of fills for unweighted, unscaled histograms (see :attr:sum_weights).

values

values(*, flow: bool = False) -> FloatArray

Bin contents (sum of weights).

variances

variances(*, flow: bool = False) -> FloatArray

Bin variances (sum of squared weights).

errors

errors(*, flow: bool = False) -> FloatArray

Bin uncertainties, sqrt(variances).

replace

replace(**changes: Any) -> Histogram

Return a copy with the given fields changed, e.g. h.replace(label="B").

map_hists

map_hists(transform: Callable[[Hist], Hist]) -> Histogram

Return a copy with transform applied to the nominal histogram and every variation.

scaled

scaled(factor: float) -> Histogram

Return a copy multiplied by factor.

Variances scale with factor**2. The statistics' sum of weights (and sum of squared weights) scale along; the moments, entry count and effective entries are unchanged by a uniform rescaling.

rootfig.Ratio dataclass

Ratio(
    values: FloatArray,
    errors: FloatArray,
    band: FloatArray,
    edges: FloatArray,
    syst_errors: tuple[FloatArray, FloatArray]
    | None = None,
    syst_band: tuple[FloatArray, FloatArray] | None = None,
)

Bin-by-bin ratio of two histograms.

Attributes:

Name Type Description
values FloatArray

numerator / denominator; nan where the denominator is zero.

errors FloatArray

Uncertainty on values (see :data:RatioUncertainty).

band FloatArray

Relative statistical uncertainty of the denominator, sqrt(var_den) / den; use as a band around one. nan where the denominator is zero.

edges FloatArray

Bin edges shared by both histograms.

syst_errors tuple[FloatArray, FloatArray] | None

Systematic uncertainty on values as (down, up) (matplotlib's yerr order), following :data:RatioUncertainty; None without systematic variations.

syst_band tuple[FloatArray, FloatArray] | None

Relative systematic uncertainty of the denominator below and above one, (down, up); None if the denominator has no variations.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths (for horizontal error bars).

total_errors

total_errors() -> tuple[FloatArray, FloatArray]

Statistical and systematic uncertainty on values in quadrature, (down, up).

total_band

total_band() -> tuple[FloatArray, FloatArray]

Relative statistical and systematic denominator uncertainty, (down, up).

rootfig.Uncertainty dataclass

Uncertainty(
    edges: FloatArray,
    nominal: FloatArray,
    stat: FloatArray,
    components: Mapping[
        str, tuple[FloatArray, FloatArray]
    ] = dict(),
)

Per-bin statistical and systematic uncertainties of a one-dimensional histogram.

All arrays cover the visible bins. Uncertainties are non-negative magnitudes below (down) and above (up) the nominal contents.

Attributes:

Name Type Description
edges FloatArray

Bin edges.

nominal FloatArray

Nominal bin contents.

stat FloatArray

Statistical uncertainty, sqrt(variances).

components Mapping[str, tuple[FloatArray, FloatArray]]

Signed shifts of every source, {name: (up - nominal, down - nominal)}.

syst_up property

syst_up: FloatArray

Systematic uncertainty above the nominal contents.

syst_down property

syst_down: FloatArray

Systematic uncertainty below the nominal contents.

total_up property

total_up: FloatArray

Statistical and systematic uncertainty above the nominal, in quadrature.

total_down property

total_down: FloatArray

Statistical and systematic uncertainty below the nominal, in quadrature.

has_systematics property

has_systematics: bool

True if at least one systematic source contributes.

rootfig.Summary dataclass

Summary(
    entries: int,
    sum_weights: float,
    mean: float,
    std: float,
    sem: float,
    skewness: float,
    minimum: float,
    maximum: float,
    n_events: int = 0,
    n_selected_events: int = 0,
    n_missing: int = 0,
    n_nonfinite: int = 0,
    per_object: bool = False,
    _sum_w2: float = 0.0,
)

Weighted summary statistics of one column of values.

All moments use the weights as frequency weights. sem is the standard error of the mean, std / sqrt(n_eff) with the Kish effective sample size n_eff = (sum w)^2 / sum w^2.

With negative weights (e.g. NLO simulation) the weighted second moment can be negative; the standard deviation, its error and the skewness are then nan (the histogram itself is unaffected).

rms property

rms: float

Alias for :attr:std (ROOT calls the standard deviation "RMS").

effective_entries property

effective_entries: float

Kish effective sample size, equal to entries for unit weights.

format

format(
    precision: int = 4, *, include_entries: bool = True
) -> str

Return a compact multi-line text, e.g. for a statistics box on a plot.

Uses matplotlib math text for the symbols so it renders with the plot fonts ($\mu$, $\sigma$).

rootfig.SummaryTable dataclass

SummaryTable(rows: tuple[tuple[str, str, Summary], ...])

Summary statistics for several variables and samples.

str(table) gives an aligned text table; :meth:get returns a single :class:~rootfig.histograms.Summary.

rows instance-attribute

rows: tuple[tuple[str, str, Summary], ...]

(sample label, variable expression, summary) triples.

samples property

samples: list[str]

Distinct sample labels in order of appearance.

variables property

variables: list[str]

Distinct variable expressions in order of appearance.

get

get(variable: str, sample: str | None = None) -> Summary

Return the summary for variable (and sample, if several).

rootfig.Cutflow dataclass

Cutflow(sample: str, steps: tuple[CutflowStep, ...])

The cut flow of one sample: a sequence of :class:CutflowStep.

labels property

labels: list[str]

Step labels.

yields property

yields: ndarray

Weighted yield per step.

events property

events: ndarray

Raw event count per step.

efficiencies property

efficiencies: ndarray

Weighted efficiency of each step relative to the previous one (1 for the first).

The plain ratio of yields: nan where the previous yield is zero. With signed (NLO) weights a yield can be negative, and the ratio may then lie outside [0, 1]; it is still reported.

absolute_efficiencies property

absolute_efficiencies: ndarray

Weighted efficiency of each step relative to the first (see :attr:efficiencies).

rootfig.CutflowTable dataclass

CutflowTable(rows: tuple[Cutflow, ...])

Cut flows of several samples with the same cuts; str(table) is an aligned text table.

samples property

samples: list[str]

Sample labels in order.

labels property

labels: list[str]

Step labels (identical for all samples).

get

get(sample: str) -> Cutflow

Return the cut flow of the sample labelled sample.

rootfig.Efficiency dataclass

Efficiency(
    values: FloatArray,
    lower: FloatArray,
    upper: FloatArray,
    edges: FloatArray,
    label: str = "",
)

Bin-by-bin efficiency passed / total with a binomial confidence interval.

Attributes:

Name Type Description
values FloatArray

The efficiency passed / total; nan where the total weight is zero (an empty bin, or weights that cancel).

lower, upper

Bounds of the Wilson score interval (z standard deviations; z = 1 is the usual 68 % band), computed with the effective number of entries so weighted samples get sensible intervals. The interval always contains the value; it is nan where the total weight is negative or the value is outside [0, 1] (negative weights), since a binomial interval is undefined there while the ratio itself is still reported.

edges FloatArray

Bin edges.

label str

Legend label.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths.

errors property

errors: tuple[FloatArray, FloatArray]

(values - lower, upper - values), ready for yerr.

rootfig.Profile dataclass

Profile(
    values: FloatArray,
    errors: FloatArray,
    counts: FloatArray,
    edges: FloatArray,
    statistic: ProfileStatistic = "mean",
    label: str = "",
)

A statistic of y in bins of x.

Attributes:

Name Type Description
values FloatArray

The weighted mean (statistic="mean") or standard deviation ("std") of y per bin; nan for empty bins (zero total weight). The standard deviation (and hence the error) is also nan where negative weights make the total weight negative or the weighted variance negative; the mean is still reported there.

errors FloatArray

Standard error: std / sqrt(n_eff) for the mean, std / sqrt(2 n_eff) for the standard deviation, with the effective entries n_eff.

counts FloatArray

Sum of weights per bin.

edges FloatArray

Bin edges.

statistic, label

What is shown and the legend label.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths.

Errors and warnings

rootfig.errors

Exception and warning types raised by rootfig.

All exceptions derive from :class:RootfigError so callers can catch everything from the library with a single except clause. The more specific subclasses also inherit from the matching built-in exception (ValueError, KeyError-like LookupError) so generic handlers keep working.

RootfigError

Bases: Exception

Base class for all errors raised by rootfig.

ExpressionError

Bases: RootfigError, ValueError

An expression string could not be parsed, is not allowed, or failed to evaluate.

MissingBranchError

MissingBranchError(
    name: str,
    *,
    available: Sequence[str] = (),
    suggestions: Sequence[str] = (),
    context: str | None = None,
)

Bases: ExpressionError, LookupError

An expression refers to a name that is not a branch, alias, function, or constant.

SelectionError

Bases: RootfigError, ValueError

Variable, selection, and weight arrays have incompatible structures.

IncompatibleWeightError

Bases: SelectionError

A weight array cannot be broadcast to the variable being histogrammed.

BinningError

Bases: RootfigError, ValueError

A binning or range specification is invalid or cannot be inferred.

SourceError

Bases: RootfigError

A file, tree, or in-memory data source cannot be opened or interpreted.

LuminosityError

Bases: RootfigError, ValueError

A sample cannot be scaled to a luminosity (no luminosity, cross section or event count).

SystematicError

Bases: RootfigError, ValueError

A systematic variation is malformed or cannot be applied to a sample or histogram.

RootfigWarning

Bases: UserWarning

Base class for warnings emitted by rootfig (dropped values, empty selections, ...).

Lower layers

rootfig.io

Reading branch arrays from ROOT files (uproot) or from memory.

FilesLike module-attribute

FilesLike = (
    str | PathLike[str] | Sequence[str | PathLike[str]]
)

A path, glob pattern, URL, "path:tree" string, or a sequence of those.

ArraySource dataclass

ArraySource(
    data: Mapping[str, Any] | Array | ndarray,
    *,
    entry_start: int | None = None,
    entry_stop: int | None = None,
)

Branch arrays already in memory.

Parameters:

Name Type Description Default
data Mapping[str, Any] | Array | ndarray

A mapping from branch name to array-like, an Awkward record array, or a NumPy structured array. All columns must have the same length.

required
entry_start int | None

Keep only this range of entries (Python slice semantics).

None
entry_stop int | None

Keep only this range of entries (Python slice semantics).

None

default_label property

default_label: str

A generic legend label for in-memory data.

branches

branches() -> list[str]

Return the column names.

arrays

arrays(branches: Sequence[str]) -> dict[str, ak.Array]

Return the requested columns.

num_entries

num_entries() -> int

Return the number of events (length of the arrays).

describe

describe() -> str

Return a short description of the in-memory data.

FileSource dataclass

FileSource(
    files: FilesLike,
    tree: str | None = None,
    *,
    entry_start: int | None = None,
    entry_stop: int | None = None,
)

Branch arrays read from one or more ROOT files with uproot.

Parameters:

Name Type Description Default
files FilesLike

See :data:FilesLike. Globs are expanded and sorted at construction.

required
tree str | None

Name of the TTree/RNTuple. If omitted, the files are inspected and the tree is auto-detected when there is exactly one.

None
entry_start int | None

Restrict reading to a range of entries counted across all files, in the same way as :func:uproot.concatenate. Useful for quick looks at large inputs.

None
entry_stop int | None

Restrict reading to a range of entries counted across all files, in the same way as :func:uproot.concatenate. Useful for quick looks at large inputs.

None

default_label property

default_label: str

A legend label derived from the first file's stem.

resolved_tree

resolved_tree() -> str

Return the tree name, auto-detecting it from the first file if needed.

branches

branches() -> list[str]

Return the branch (or RNTuple field) names of the tree in the first file.

The result is cached on the instance; the file is opened only once.

num_entries

num_entries() -> int

Return the number of entries (all files, honouring the entry range).

read_scalar

read_scalar(key: str) -> float

Sum a number stored under key in every file.

Accepts histograms (their total content including flow bins, e.g. a sum-of-weights histogram) and TParameter objects (their value). Typical use: the number of generated events written by a framework.

arrays

arrays(branches: Sequence[str]) -> dict[str, ak.Array]

Read branches from all files and concatenate them.

describe

describe() -> str

Return e.g. 'events.root' or '3 files (run*.root)'.

Source

Bases: Protocol

Anything that can list branch names and return branch arrays.

Implementations must be cheap to construct; opening files or reading data should happen inside :meth:branches and :meth:arrays.

branches

branches() -> list[str]

Return the names of all available branches.

arrays

arrays(branches: Sequence[str]) -> dict[str, ak.Array]

Read the given branches and return them as Awkward arrays of equal length.

describe

describe() -> str

Return a short human-readable description used in labels and messages.

as_source

as_source(
    data: Any,
    *,
    tree: str | None = None,
    entry_start: int | None = None,
    entry_stop: int | None = None,
) -> Source

Turn user input into a :class:Source.

Strings, paths, and sequences of them become a :class:FileSource; mappings, Awkward record arrays and NumPy structured arrays become an :class:ArraySource; existing sources are returned unchanged. An entry range (or, for a :class:FileSource, a different tree) cannot be applied to an existing source afterwards and raises :class:SourceError; give them to the source when constructing it.

resolve_files

resolve_files(
    files: FilesLike,
) -> tuple[tuple[str, ...], str | None]

Expand files into a sorted tuple of concrete paths and an optional tree name.

Parameters:

Name Type Description Default
files FilesLike

A path, glob pattern, remote URL, "path:tree" string, or a sequence of any of these. Local glob patterns are expanded and sorted so results are deterministic regardless of filesystem order.

required

Returns:

Type Description
tuple

(paths, tree) where tree is the tree name found in "path:tree" strings (all must agree) or None.

Raises:

Type Description
SourceError

If a pattern matches nothing, a local file does not exist, or the embedded tree names disagree.

rootfig.expressions

Expression strings over branches.

rootfig expressions use ordinary Python syntax and are evaluated with NumPy and Awkward Array semantics, so Muon_pt > 20 produces a boolean array with the same (possibly jagged) structure as Muon_pt.

Supported syntax
  • Names refer to branches (Muon_pt). Branch names that are not valid Python identifiers are written in backticks: `jet1_b-tag` > 0.5.
  • Arithmetic + - * / // % ** and unary -.
  • Comparisons == != < <= > >= including chained comparisons (20 < Muon_pt < 100), which are rewritten element-wise.
  • Boolean combinations with &, |, ~ or with and, or, not; the keyword forms are rewritten to the element-wise operators.
  • Indexing and slicing (Muon_pt[:, 0]).
  • A fixed set of functions (see :data:FUNCTIONS): element-wise NumPy functions such as abs, sqrt, log, where, and per-event reductions over jagged branches: count, sum, min, max, mean, any, all, first.
  • Constants pi, e, inf, nan, True, False.

Everything else (attribute access, lambdas, comprehensions, string literals, calls to unknown functions) is rejected at parse time with :class:~rootfig.errors.ExpressionError.

The public entry points are :func:parse, :class:Expression, and :func:evaluate.

CONSTANTS module-attribute

CONSTANTS: Final[Mapping[str, float]] = MappingProxyType(
    {
        "pi": math.pi,
        "e": math.e,
        "inf": math.inf,
        "nan": math.nan,
    }
)

Named constants available in expressions when no branch of that name exists.

FUNCTIONS module-attribute

FUNCTIONS: Final[Mapping[str, Callable[..., Any]]] = (
    MappingProxyType(
        {
            "abs": np.abs,
            "sqrt": np.sqrt,
            "cbrt": np.cbrt,
            "exp": np.exp,
            "expm1": np.expm1,
            "log": np.log,
            "log10": np.log10,
            "log2": np.log2,
            "log1p": np.log1p,
            "power": np.power,
            "hypot": np.hypot,
            "sin": np.sin,
            "cos": np.cos,
            "tan": np.tan,
            "arcsin": np.arcsin,
            "arccos": np.arccos,
            "arctan": np.arctan,
            "arctan2": np.arctan2,
            "sinh": np.sinh,
            "cosh": np.cosh,
            "tanh": np.tanh,
            "arcsinh": np.arcsinh,
            "arccosh": np.arccosh,
            "arctanh": np.arctanh,
            "deg2rad": np.deg2rad,
            "rad2deg": np.rad2deg,
            "floor": np.floor,
            "ceil": np.ceil,
            "round": np.rint,
            "trunc": np.trunc,
            "sign": np.sign,
            "minimum": np.minimum,
            "maximum": np.maximum,
            "clip": _clip,
            "isnan": np.isnan,
            "isinf": np.isinf,
            "isfinite": np.isfinite,
            "where": ak.where,
            "count": _reduction("count", ak.num),
            "len": _reduction("len", ak.num),
            "sum": _reduction("sum", ak.sum),
            "prod": _reduction("prod", ak.prod),
            "min": _reduction("min", ak.min),
            "max": _reduction("max", ak.max),
            "mean": _reduction("mean", ak.mean),
            "std": _reduction("std", ak.std),
            "any": _reduction("any", ak.any),
            "all": _reduction("all", ak.all),
            "argmin": _reduction("argmin", ak.argmin),
            "argmax": _reduction("argmax", ak.argmax),
            "first": _first,
            "pt": _pt,
            "p": _p,
            "theta": _theta,
            "costheta": _costheta,
            "eta": _eta,
            "phi": _phi,
            "mass": _mass,
        }
    )
)

Functions callable from expressions, keyed by the name used in the expression.

ExpressionLike module-attribute

ExpressionLike = str | Expression

Anything accepted where an expression is expected.

Expression dataclass

Expression(
    text: str,
    names: tuple[str, ...],
    functions: tuple[str, ...],
    _code: CodeType,
    _backticks: Mapping[str, str] = dict(),
)

A parsed, validated expression ready to be evaluated on arrays.

Create instances with :func:parse (or pass strings anywhere rootfig accepts an expression; they are parsed on the fly).

Attributes:

Name Type Description
text str

The original expression string.

names tuple[str, ...]

Names referenced by the expression in order of first appearance. These are candidate branch names; a name that is not a branch may still resolve to a constant (pi, e, inf, nan).

functions tuple[str, ...]

Names of the functions called by the expression.

is_trivial property

is_trivial: bool

True if the expression is a bare name (a single branch, no computation).

required_branches

required_branches(available: Collection[str]) -> list[str]

Return the referenced names that must be read from available branches.

Names that are not available but match a constant are skipped. Any other unknown name raises :class:~rootfig.errors.MissingBranchError with close-match suggestions.

evaluate

evaluate(
    arrays: Mapping[str, Any] | Array,
    *,
    length: int | None = None,
) -> ak.Array

Evaluate the expression using arrays to resolve branch names.

Parameters:

Name Type Description Default
arrays Mapping[str, Any] | Array

Either a mapping from branch name to array, or an Awkward record array whose fields are the branches.

required
length int | None

Number of events a constant expression ("1", "True") is broadcast to when arrays holds nothing to take the length from.

None

Returns:

Type Description
Array

The result, converted to an Awkward array if the expression produced a NumPy array or a scalar.

evaluate

evaluate(
    expression: ExpressionLike,
    arrays: Mapping[str, Any] | Array,
    *,
    length: int | None = None,
) -> ak.Array

Parse (if needed) and evaluate expression on arrays.

This is a convenience wrapper around :func:parse and :meth:Expression.evaluate; length sizes constant expressions when arrays is empty.

Examples:

>>> import awkward as ak
>>> arrays = {"pt": ak.Array([[10.0, 30.0], [], [50.0]])}
>>> evaluate("pt > 20", arrays).tolist()
[[False, True], [], [True]]
>>> evaluate("count(pt)", arrays).tolist()
[2, 0, 1]

parse

parse(expression: ExpressionLike) -> Expression

Parse and validate an expression string.

Raises:

Type Description
ExpressionError

If the text is not a single Python expression or uses a disallowed construct (unknown functions, string literals, lambdas, comprehensions, conditional expressions, ...). Dotted names such as Collection.field.sub are read as one branch name (podio/EDM4hep files), not as attribute access; attributes of anything else are rejected.

rootfig.selection

Selection semantics: combine variable, cut and weight arrays into flat columns.

NonFinitePolicy module-attribute

NonFinitePolicy = Literal['drop', 'error']

What to do with nan/inf values: silently drop them (with a warning) or raise.

Columns dataclass

Columns(
    arrays: tuple[ndarray, ...],
    weights: ndarray | None,
    n_events: int,
    n_selected_events: int,
    n_missing: int = 0,
    n_nonfinite: int = 0,
    per_object: bool = False,
)

Flat, finite, aligned columns ready to fill a histogram.

Attributes:

Name Type Description
arrays tuple[ndarray, ...]

One flat float64 array per requested variable, all the same length.

weights ndarray | None

Weights aligned with arrays, or None for unweighted data.

n_events int

Number of events in the input arrays.

n_selected_events int

Number of events contributing at least one entry after selection.

n_missing int

Entries dropped because a value (or weight) was None, including the objects of events whose collection or event weight was missing.

n_nonfinite int

Entries dropped because a value (or weight) was nan or inf (entries that were also missing count as missing only).

per_object bool

True if the variable was jagged (one entry per object rather than per event).

values property

values: ndarray

The first (usually only) variable column.

n_entries property

n_entries: int

Number of entries that will be histogrammed.

sum_weights property

sum_weights: float

Sum of weights (or number of entries when unweighted).

effective_weights

effective_weights() -> np.ndarray

Weights as an array, 1.0 everywhere when unweighted.

boolean_mask

boolean_mask(
    selection: ExpressionLike,
    arrays: Mapping[str, Any] | Array,
    *,
    length: int | None = None,
) -> ak.Array

Evaluate selection and check that it is boolean; missing values become False.

Fixed-size dimensions are turned into lists (see :mod:rootfig.selection).

Raises:

Type Description
SelectionError

If the selection evaluates to numbers rather than booleans, so an integer flag is never mistaken for an index array.

depth_of

depth_of(array: Array) -> int

Return the list depth of array: 1 for flat, 2 for jagged, and so on.

event_mask

event_mask(
    selection: ExpressionLike,
    arrays: Mapping[str, Any] | Array,
    *,
    length: int | None = None,
) -> np.ndarray

Evaluate selection to one boolean per event.

Per-object selections count an event as passing when any of its objects passes (the cut-flow convention); missing values count as False. length sizes constant selections when arrays is empty.

event_weights

event_weights(
    weight: ExpressionLike | None,
    arrays: Mapping[str, Any] | Array,
    n_events: int,
    *,
    nonfinite: NonFinitePolicy = "drop",
    context: str = "",
) -> np.ndarray

Evaluate a per-event weight expression (None gives unit weights).

Missing (None) and non-finite weights are returned as nan so the caller can exclude those events; non-finite ones are reported following nonfinite (a :class:~rootfig.errors.RootfigWarning, or a :class:~rootfig.errors.SelectionError for "error").

prepare

prepare(
    arrays: Mapping[str, Any] | Array,
    variables: ExpressionLike | Sequence[ExpressionLike],
    *,
    selection: ExpressionLike | None = None,
    weight: ExpressionLike | None = None,
    scale: float = 1.0,
    nonfinite: NonFinitePolicy = "drop",
    context: str = "",
    n_events: int | None = None,
) -> Columns

Evaluate expressions and apply the selection/weight rules.

Parameters:

Name Type Description Default
arrays Mapping[str, Any] | Array

Branch arrays (mapping or record array) covering every name used.

required
variables ExpressionLike | Sequence[ExpressionLike]

One expression, or several that must share the same structure (used for 2D histograms and correlations).

required
selection ExpressionLike | None

Boolean expression, or None to keep everything.

None
weight ExpressionLike | None

Weight expression, or None for unit weights.

None
scale float

Constant factor multiplied into the weights.

1.0
nonfinite NonFinitePolicy

"drop" (default) removes nan/inf entries with a warning; "error" raises :class:~rootfig.errors.SelectionError instead.

'drop'
context str

Short text (e.g. the sample label) prepended to warnings.

''
n_events int | None

Number of events, needed only when every expression is a constant ("1") so that nothing in arrays gives the length.

None

Returns:

Type Description
Columns

Flat aligned values and weights.

same_structure

same_structure(a: Array, b: Array) -> bool

Return True if a and b have identical list lengths at every level.

rootfig.histograms

Histogram construction, normalisation, ratios and statistics.

ProfileStatistic module-attribute

ProfileStatistic: TypeAlias = Literal['mean', 'std']

What a profile shows per bin: the weighted mean of y or its standard deviation.

NormalizeSpec module-attribute

NormalizeSpec: TypeAlias = (
    bool
    | Literal["unity", "density", "width"]
    | float
    | int
    | None
)

How to normalise a histogram.

  • False/None - raw sums of weights.
  • True or "unity" - scale so the visible bins sum to one.
  • "density" - scale so the integral over the visible range is one (contents divided by bin width and total).
  • "width" - divide each bin by its width (Events / GeV), no rescaling.
  • a number - scale so the visible bins sum to that number.

SIGNIFICANCE_KINDS module-attribute

SIGNIFICANCE_KINDS: tuple[str, ...] = (
    "significance",
    "s/sqrt(b)",
    "s/sqrt(s+b)",
)

Strings accepted by ratio= for a significance panel ("significance" means S/sqrt(B)).

SignificanceKind module-attribute

SignificanceKind: TypeAlias = Literal[
    "s/sqrt(b)", "s/sqrt(s+b)"
]

Per-bin significance estimators: S / sqrt(B) or S / sqrt(S + B).

Histogram dataclass

Histogram(
    hist: Hist,
    label: str,
    sample: Sample | None = None,
    stats: Summary | None = None,
    is_data: bool = False,
    color: str | None = None,
    histtype: HistType | None = None,
    normalization: str | None = None,
    variations: Mapping[str, tuple[Hist, Hist | None]]
    | None = None,
)

A filled histogram together with its provenance and drawing hints.

The underlying :class:Hist (Weight storage, so bin variances are the sums of squared weights) is available as :attr:hist; everything else is metadata used for legends, ratios and statistics.

Attributes:

Name Type Description
hist Hist

The histogram itself (1D or 2D).

label str

Legend label.

sample Sample | None

The :class:~rootfig.model.Sample this was filled from, if any.

stats Summary | None

Unbinned :class:~rootfig.histograms.Summary statistics of the filled values, if available.

is_data bool

Whether this represents observed data.

color, histtype

Drawing hints, None for style defaults.

normalization str | None

Description of the normalisation applied (None for raw counts).

variations Mapping[str, tuple[Hist, Hist]]

Systematic variations, {name: (up, down)} histograms with the binning of :attr:hist. A down given as None is filled in by mirroring the up shift around the nominal contents. Summarised by :func:~rootfig.histograms.uncertainty. The mapping is copied and made read-only; every stored pair contains two histograms. Use histogram.replace(variations=...) to replace it. The underlying hist.Hist objects remain mutable. Observed data (is_data) cannot carry variations, as for :class:~rootfig.model.Sample.

ndim property

ndim: int

Number of axes.

axis property

axis: Any

The first axis.

edges property

edges: FloatArray

Bin edges of the first axis.

centers property

centers: FloatArray

Bin centres of the first axis.

widths property

widths: FloatArray

Bin widths of the first axis.

integral property

integral: float

Sum of the visible bin contents (flow bins excluded).

sum_weights property

sum_weights: float

Sum of all bin contents including the flow bins.

underflow property

underflow: float

Content of the underflow bin (first axis, 1D only; 0 if the axis has none).

overflow property

overflow: float

Content of the overflow bin (first axis, 1D only; 0 if the axis has none).

underflow_variance property

underflow_variance: float

Variance of the underflow bin (first axis, 1D only; 0 if the axis has none).

overflow_variance property

overflow_variance: float

Variance of the overflow bin (first axis, 1D only; 0 if the axis has none).

entries property

entries: int | None

Number of filled entries from the unbinned statistics, or None if unknown.

A histogram that was not filled by rootfig carries no entry count: its bin contents are sums of weights, which only equal the number of fills for unweighted, unscaled histograms (see :attr:sum_weights).

values

values(*, flow: bool = False) -> FloatArray

Bin contents (sum of weights).

variances

variances(*, flow: bool = False) -> FloatArray

Bin variances (sum of squared weights).

errors

errors(*, flow: bool = False) -> FloatArray

Bin uncertainties, sqrt(variances).

replace

replace(**changes: Any) -> Histogram

Return a copy with the given fields changed, e.g. h.replace(label="B").

map_hists

map_hists(transform: Callable[[Hist], Hist]) -> Histogram

Return a copy with transform applied to the nominal histogram and every variation.

scaled

scaled(factor: float) -> Histogram

Return a copy multiplied by factor.

Variances scale with factor**2. The statistics' sum of weights (and sum of squared weights) scale along; the moments, entry count and effective entries are unchanged by a uniform rescaling.

Cutflow dataclass

Cutflow(sample: str, steps: tuple[CutflowStep, ...])

The cut flow of one sample: a sequence of :class:CutflowStep.

labels property

labels: list[str]

Step labels.

yields property

yields: ndarray

Weighted yield per step.

events property

events: ndarray

Raw event count per step.

efficiencies property

efficiencies: ndarray

Weighted efficiency of each step relative to the previous one (1 for the first).

The plain ratio of yields: nan where the previous yield is zero. With signed (NLO) weights a yield can be negative, and the ratio may then lie outside [0, 1]; it is still reported.

absolute_efficiencies property

absolute_efficiencies: ndarray

Weighted efficiency of each step relative to the first (see :attr:efficiencies).

CutflowStep dataclass

CutflowStep(
    label: str,
    expression: str,
    events: int,
    yield_: float,
    error: float,
)

Yields after one more cut has been applied.

Attributes:

Name Type Description
label str

The cut's label (or expression). For the first step: the label (or expression) of the sample's own selection, "All" if it has none.

expression str

The cut expression, empty for the first step.

events int

Raw number of events passing all cuts so far.

yield_ float

Weighted yield (sum of weights, including the sample scale and luminosity).

error float

Statistical uncertainty on yield_, sqrt(sum w^2).

CutflowTable dataclass

CutflowTable(rows: tuple[Cutflow, ...])

Cut flows of several samples with the same cuts; str(table) is an aligned text table.

samples property

samples: list[str]

Sample labels in order.

labels property

labels: list[str]

Step labels (identical for all samples).

get

get(sample: str) -> Cutflow

Return the cut flow of the sample labelled sample.

Efficiency dataclass

Efficiency(
    values: FloatArray,
    lower: FloatArray,
    upper: FloatArray,
    edges: FloatArray,
    label: str = "",
)

Bin-by-bin efficiency passed / total with a binomial confidence interval.

Attributes:

Name Type Description
values FloatArray

The efficiency passed / total; nan where the total weight is zero (an empty bin, or weights that cancel).

lower, upper

Bounds of the Wilson score interval (z standard deviations; z = 1 is the usual 68 % band), computed with the effective number of entries so weighted samples get sensible intervals. The interval always contains the value; it is nan where the total weight is negative or the value is outside [0, 1] (negative weights), since a binomial interval is undefined there while the ratio itself is still reported.

edges FloatArray

Bin edges.

label str

Legend label.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths.

errors property

errors: tuple[FloatArray, FloatArray]

(values - lower, upper - values), ready for yerr.

Profile dataclass

Profile(
    values: FloatArray,
    errors: FloatArray,
    counts: FloatArray,
    edges: FloatArray,
    statistic: ProfileStatistic = "mean",
    label: str = "",
)

A statistic of y in bins of x.

Attributes:

Name Type Description
values FloatArray

The weighted mean (statistic="mean") or standard deviation ("std") of y per bin; nan for empty bins (zero total weight). The standard deviation (and hence the error) is also nan where negative weights make the total weight negative or the weighted variance negative; the mean is still reported there.

errors FloatArray

Standard error: std / sqrt(n_eff) for the mean, std / sqrt(2 n_eff) for the standard deviation, with the effective entries n_eff.

counts FloatArray

Sum of weights per bin.

edges FloatArray

Bin edges.

statistic, label

What is shown and the legend label.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths.

Ratio dataclass

Ratio(
    values: FloatArray,
    errors: FloatArray,
    band: FloatArray,
    edges: FloatArray,
    syst_errors: tuple[FloatArray, FloatArray]
    | None = None,
    syst_band: tuple[FloatArray, FloatArray] | None = None,
)

Bin-by-bin ratio of two histograms.

Attributes:

Name Type Description
values FloatArray

numerator / denominator; nan where the denominator is zero.

errors FloatArray

Uncertainty on values (see :data:RatioUncertainty).

band FloatArray

Relative statistical uncertainty of the denominator, sqrt(var_den) / den; use as a band around one. nan where the denominator is zero.

edges FloatArray

Bin edges shared by both histograms.

syst_errors tuple[FloatArray, FloatArray] | None

Systematic uncertainty on values as (down, up) (matplotlib's yerr order), following :data:RatioUncertainty; None without systematic variations.

syst_band tuple[FloatArray, FloatArray] | None

Relative systematic uncertainty of the denominator below and above one, (down, up); None if the denominator has no variations.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths (for horizontal error bars).

total_errors

total_errors() -> tuple[FloatArray, FloatArray]

Statistical and systematic uncertainty on values in quadrature, (down, up).

total_band

total_band() -> tuple[FloatArray, FloatArray]

Relative statistical and systematic denominator uncertainty, (down, up).

Summary dataclass

Summary(
    entries: int,
    sum_weights: float,
    mean: float,
    std: float,
    sem: float,
    skewness: float,
    minimum: float,
    maximum: float,
    n_events: int = 0,
    n_selected_events: int = 0,
    n_missing: int = 0,
    n_nonfinite: int = 0,
    per_object: bool = False,
    _sum_w2: float = 0.0,
)

Weighted summary statistics of one column of values.

All moments use the weights as frequency weights. sem is the standard error of the mean, std / sqrt(n_eff) with the Kish effective sample size n_eff = (sum w)^2 / sum w^2.

With negative weights (e.g. NLO simulation) the weighted second moment can be negative; the standard deviation, its error and the skewness are then nan (the histogram itself is unaffected).

rms property

rms: float

Alias for :attr:std (ROOT calls the standard deviation "RMS").

effective_entries property

effective_entries: float

Kish effective sample size, equal to entries for unit weights.

format

format(
    precision: int = 4, *, include_entries: bool = True
) -> str

Return a compact multi-line text, e.g. for a statistics box on a plot.

Uses matplotlib math text for the symbols so it renders with the plot fonts ($\mu$, $\sigma$).

Uncertainty dataclass

Uncertainty(
    edges: FloatArray,
    nominal: FloatArray,
    stat: FloatArray,
    components: Mapping[
        str, tuple[FloatArray, FloatArray]
    ] = dict(),
)

Per-bin statistical and systematic uncertainties of a one-dimensional histogram.

All arrays cover the visible bins. Uncertainties are non-negative magnitudes below (down) and above (up) the nominal contents.

Attributes:

Name Type Description
edges FloatArray

Bin edges.

nominal FloatArray

Nominal bin contents.

stat FloatArray

Statistical uncertainty, sqrt(variances).

components Mapping[str, tuple[FloatArray, FloatArray]]

Signed shifts of every source, {name: (up - nominal, down - nominal)}.

syst_up property

syst_up: FloatArray

Systematic uncertainty above the nominal contents.

syst_down property

syst_down: FloatArray

Systematic uncertainty below the nominal contents.

total_up property

total_up: FloatArray

Statistical and systematic uncertainty above the nominal, in quadrature.

total_down property

total_down: FloatArray

Statistical and systematic uncertainty below the nominal, in quadrature.

has_systematics property

has_systematics: bool

True if at least one systematic source contributes.

as_weight_storage

as_weight_storage(
    histogram: Hist, *, assume_poisson: bool = False
) -> Hist

Return histogram with Weight storage (a copy if it had another storage).

Plain count storages (Double, Int64, ...) carry no sum of squared weights; their variances are taken as hist reports them, i.e. the counts (Poisson) for unweighted fills. After a weighted fill or arithmetic on such a storage hist reports no variances at all: the sum of squared weights is lost and cannot be reconstructed. That is an error unless assume_poisson=True, which uses the absolute bin contents as variances (the Poisson guess; a :class:~rootfig.errors.RootfigWarning says so).

Raises:

Type Description
TypeError

If the storage is not a count or Weight storage (Mean, ...).

ValueError

If the histogram reports no variances and assume_poisson is False.

fill

fill(axes: Sequence[Axis], columns: Columns) -> Hist

Create a Hist with Weight storage and fill it from columns.

One axis is expected per column in columns.arrays. Unweighted data is filled without weights, so bin variances equal bin counts.

profile

profile(
    x: FloatArray,
    y: FloatArray,
    edges: FloatArray,
    *,
    weights: FloatArray | None = None,
    statistic: ProfileStatistic = "mean",
    label: str = "",
) -> Profile

Bin x with edges and compute the weighted mean or standard deviation of y.

The variance is computed from the deviations from the bin mean (two passes), so a narrow spread at a large offset, e.g. std([1e9, 1e9 + 1]) = 0.5, is not lost to cancellation.

normalization_label

normalization_label(spec: NormalizeSpec) -> str | None

Short description of a normalisation, used as the y-axis label.

normalize_hist

normalize_hist(
    histogram: Hist, spec: NormalizeSpec
) -> Hist

Return a normalised copy of histogram with Weight storage.

Flow bins are scaled by the same factor as the visible bins for the rescaling modes, and divided by the neighbouring visible bin size for the per-width modes so they stay comparable when drawn. Histograms with a plain count storage are converted (see :func:~rootfig.histograms.as_weight_storage).

The rescaling modes divide by the signed sum of the visible bins, so the bins sum to the target even when negative weights dominate (the shape then flips sign, with a warning). A histogram whose visible bins sum to zero, because it is empty or because positive and negative weights cancel, is returned unchanged with a warning.

build_histograms

build_histograms(
    samples: Sequence[Sample],
    variable: Variable | str,
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
    systematics: Mapping[str, SystematicLike] | None = None,
) -> list[Histogram]

Fill one 1D histogram per sample with a binning shared by all of them.

Systematic variations (the sample's own and systematics, which apply to every non-data sample; a sample's own source of the same name wins) are filled into :attr:~rootfig.histograms.Histogram.variations with the binning chosen from the nominal values.

build_histograms_2d

build_histograms_2d(
    samples: Sequence[Sample],
    x: Variable | str,
    y: Variable | str,
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> list[Histogram]

Fill one 2D histogram per sample; x and y must share their structure.

combined_selection

combined_selection(
    sample: Sample, selection: CutLike | None
) -> Cut | None

Combine a sample's own selection with a plot-level selection using &.

combined_weight

combined_weight(
    sample: Sample, weight: str | None
) -> str | None

Combine a sample's own weight with a plot-level weight multiplicatively.

load_columns

load_columns(
    sample: Sample,
    variables: Sequence[Variable | str],
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> Columns

Read the required branches of sample and prepare flat columns.

The selection and weight given here are combined with those defined on the sample itself (see :func:combined_selection and :func:combined_weight). lumi scales samples that carry a cross section (see :meth:~rootfig.model.Sample.lumi_scale).

load_columns_each

load_columns_each(
    sample: Sample,
    variables: Sequence[Variable | str],
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
) -> list[Columns]

Like :func:load_columns once per variable, reading the source only once.

The union of the branches needed by all variables, the selection and the weight is read in a single pass; each variable is then prepared on its own, so variables of different structure (per-event and per-object) may be mixed and each keeps the semantics it would have alone.

read_arrays

read_arrays(
    sample: Sample, expressions: Sequence[Any]
) -> tuple[dict[str, Any], int]

Read the branches expressions need from sample and return them with the event count.

Only the union of the required branches is read. The number of events is taken from the arrays, or from the source when nothing had to be read (all expressions constant), so "1" or "True" still know how many events there are.

source_length

source_length(source: Any) -> int

Return the number of events in source (num_entries(), else a branch length).

compatible_binning

compatible_binning(a: Hist, b: Hist) -> bool

Return True if both histograms are one-dimensional with identical edges.

Edges may differ by round-off only: the tolerance is a millionth of the smallest bin width, so bins shifted by a whole width at large coordinates (where NumPy's default relative tolerance would accept them) are rejected.

significance

significance(
    signal: Hist,
    background: Hist,
    *,
    kind: SignificanceKind = "s/sqrt(b)",
) -> Ratio

Per-bin significance of signal over background with propagated uncertainties.

Returned as a :class:Ratio (values, errors, edges; band is nan) so it can be drawn like a ratio panel. Bins with zero background (or zero total for "s/sqrt(s+b)") are nan.

correlation_matrix

correlation_matrix(columns: Columns) -> FloatArray

Return the (weighted) Pearson correlation matrix of all columns in columns.

Raises:

Type Description
SelectionError

If fewer than two columns or fewer than two entries (with non-zero weight) are available, or weights are negative.

describe_table

describe_table(
    summaries: Sequence[tuple[str, Summary]],
    precision: int = 4,
) -> str

Format (label, summary) pairs as an aligned plain-text table.

summarize

summarize(columns: Columns, index: int = 0) -> Summary

Compute :class:Summary statistics for column index of columns.

sum_histograms

sum_histograms(
    histograms: Sequence[Histogram], *, label: str = "Total"
) -> Histogram

Add one-dimensional histograms bin by bin, keeping their systematic variations.

Variations are matched by name and added linearly (fully correlated); a histogram without a source contributes its nominal contents to it. The sum keeps the inputs' normalization when they all share it and has none otherwise, so it never claims a scaling one of its parts lacks.

Raises:

Type Description
BinningError

If there are no histograms, an input is not one-dimensional, or the bin edges or flow-bin traits differ. Axis names and labels may differ.

uncertainty

uncertainty(histogram: Histogram) -> Uncertainty

Summarise the statistical and systematic uncertainties of a 1D histogram.

Raises:

Type Description
BinningError

If the histogram is not one-dimensional.

rootfig.plotting

Rendering histograms with matplotlib and mplhep.

AxesLike module-attribute

AxesLike: TypeAlias = (
    Axes | tuple[Axes, Axes] | Sequence[Axes] | None
)

Where to draw: nothing (new figure), one axes, or (main, ratio) axes.

DATA_STYLE module-attribute

DATA_STYLE: dict[str, Any] = {
    "marker": "o",
    "markersize": 5,
    "capsize": 0,
}

Default appearance of data points, drawn in the style's ink colour unless a sample sets one.

FlowSpec module-attribute

FlowSpec: TypeAlias = Literal["hint", "show", "sum", "none"]

How under/overflow is shown (mplhep flow): small arrows hinting at flow content ("hint"), extra bins ("show"), added to the edge bins ("sum"), or ignored.

DARK_THEME module-attribute

DARK_THEME: Mapping[str, Any] = {
    "figure.facecolor": "none",
    "axes.facecolor": "none",
    "text.color": _DARK_INK,
    "axes.labelcolor": _DARK_INK,
    "axes.edgecolor": _DARK_INK,
    "xtick.color": _DARK_INK,
    "ytick.color": _DARK_INK,
    "patch.edgecolor": _DARK_INK,
    "hatch.color": _DARK_INK,
    "grid.color": "#30363d",
    "savefig.facecolor": "auto",
    "savefig.edgecolor": "auto",
}

Colours applied by :func:dark_theme: light ink on a transparent background.

DEFAULT_COLORS module-attribute

DEFAULT_COLORS: tuple[str, ...] = (
    *_TAB10,
    *_TAB20_LIGHT,
    *_TAB20B,
    *_TAB20C,
)

Default colour cycle: matplotlib's tab10, extended to 40 colours.

The first ten are the familiar matplotlib defaults, followed by their lighter tab20 companions and ten colours each from tab20b and tab20c, so plots with many samples get distinct colours instead of a repeating cycle (the scheme used by k4Bench).

ROOTFIG_STYLE module-attribute

ROOTFIG_STYLE: Mapping[str, Any] = {
    "figure.figsize": (7.0, 5.6),
    "figure.dpi": 100,
    "savefig.dpi": 200,
    "figure.subplot.left": 0.13,
    "figure.subplot.right": 0.97,
    "figure.subplot.top": 0.95,
    "figure.subplot.bottom": 0.12,
    "font.size": 13,
    "font.family": "sans-serif",
    "font.sans-serif": [
        "DejaVu Sans",
        "Arial",
        "Helvetica",
        "Liberation Sans",
    ],
    "mathtext.fontset": "dejavusans",
    "axes.labelsize": 15,
    "axes.titlesize": 14,
    "axes.linewidth": 1.1,
    "axes.labelpad": 6,
    "axes.formatter.use_mathtext": True,
    "axes.formatter.limits": (-3, 4),
    "axes.prop_cycle": cycler(color=list(DEFAULT_COLORS)),
    "xtick.direction": "in",
    "ytick.direction": "in",
    "xtick.top": True,
    "ytick.right": True,
    "xtick.minor.visible": True,
    "ytick.minor.visible": True,
    "xtick.major.size": 7,
    "ytick.major.size": 7,
    "xtick.minor.size": 3.5,
    "ytick.minor.size": 3.5,
    "xtick.major.width": 1.0,
    "ytick.major.width": 1.0,
    "xtick.labelsize": 13,
    "ytick.labelsize": 13,
    "legend.frameon": False,
    "legend.fontsize": 12,
    "legend.handlelength": 1.4,
    "legend.borderaxespad": 0.8,
    "lines.linewidth": 1.8,
    "lines.markersize": 5,
    "errorbar.capsize": 0,
    "hatch.linewidth": 0.8,
}

Experiment-neutral defaults used when a :class:Style has no base.

Layout dataclass

Layout(
    fig: Figure,
    main: Axes,
    ratio: Axes | None = None,
    main_right: Axes | None = None,
    ratio_right: Axes | None = None,
)

The axes of a figure: main panel, optional ratio panel, optional right segments.

With a broken x axis every panel exists twice: main/main_right and ratio/ratio_right show the left and right segments.

main_axes property

main_axes: tuple[Axes, ...]

The main-panel axes, left to right.

ratio_axes property

ratio_axes: tuple[Axes, ...]

The ratio-panel axes, left to right (empty without a ratio panel).

is_broken property

is_broken: bool

True if the x axis is split into two segments.

legend_axes property

legend_axes: Axes

Where the legend and statistics box go: the right-most main axes.

xlabel_axes property

xlabel_axes: Axes

Where the x label goes: the bottom right axes.

Drawn dataclass

Drawn(
    artists: list[Artist],
    labels: list[str],
    ymin: float,
    ymax: float,
    ymin_positive: float,
    colors: dict[str, str],
    histogram_colors: list[str],
)

What :func:draw_histograms produced.

colors maps legend labels to colours (for the statistics box); histogram_colors holds the colour of every input histogram in order, so callers can identify histograms with duplicate labels.

Plot dataclass

Plot(
    fig: Figure,
    ax: Axes,
    ratio_ax: Axes | None = None,
    ax_right: Axes | None = None,
    ratio_ax_right: Axes | None = None,
    histograms: list[Histogram] = list(),
    ratios: list[Ratio] = list(),
    variable: Variable | None = None,
    matrix: FloatArray | None = None,
    efficiencies: list[Efficiency] = list(),
    profiles: list[Profile] = list(),
)

A finished figure plus the objects it was built from.

Everything is a standard matplotlib or hist object, so further customisation is ordinary matplotlib code::

p = rf.plot(...)
p.ax.set_ylim(top=500)
p.ax.axvline(91.2, color="gray", ls="--")
p.save("z_mass.pdf")

Attributes:

Name Type Description
fig Figure

The :class:matplotlib.figure.Figure.

ax Axes

The main :class:matplotlib.axes.Axes.

ratio_ax Axes | None

The ratio panel axes, or None.

ax_right, ratio_ax_right

The right-hand segments when the x axis is broken (xbreak), else None.

histograms list[Histogram]

The :class:~rootfig.histograms.Histogram objects drawn (each wraps a hist.Hist).

ratios list[Ratio]

The :class:~rootfig.histograms.Ratio objects drawn in the ratio panel.

variable Variable | None

The :class:~rootfig.model.Variable (x axis) if known; used for default file names.

matrix FloatArray | None

For correlation plots, the correlation matrix.

efficiencies, profiles

For :func:rootfig.efficiency and :func:rootfig.profile, the computed :class:~rootfig.histograms.Efficiency / :class:~rootfig.histograms.Profile objects.

axes property

axes: tuple[Axes, ...]

All axes in reading order: main (left, right), then ratio (left, right).

hists property

hists: list[Any]

The underlying hist.Hist objects, in drawing order.

uncertainty

uncertainty(label: str | None = None) -> Uncertainty

Statistical and systematic uncertainties of a histogram of the plot, or of their sum.

Parameters:

Name Type Description Default
label str | None

The label of one histogram. None sums all non-data histograms, with same-named systematic sources added linearly: the stack total of a stacked plot, the one simulated histogram if there is only one, and for an overlay a total that is not drawn, which needs every histogram to share its binning.

None

Raises:

Type Description
KeyError

If no histogram has label.

ValueError

If label is None and there are no non-data histograms.

BinningError

If label is None and the non-data histograms have different binnings (an overlay); pass a label instead.

save

save(
    path: str | PathLike[str],
    *,
    formats: Sequence[str] | None = None,
    **kwargs: Any,
) -> list[Path]

Save the figure and return the written paths.

Parameters:

Name Type Description Default
path str | PathLike[str]

Output file. If it is an existing directory (or ends with a separator), the file name is derived from the variable (<dir>/<variable>.pdf).

required
formats Sequence[str] | None

Optional list of formats (["pdf", "png"]). Each replaces the suffix of path; only the listed formats are written.

None
**kwargs Any

Forwarded to :meth:matplotlib.figure.Figure.savefig. Figures made by rootfig use constrained layout and are saved at exactly their figsize. For figures drawn into user axes without a layout engine, bbox_inches="tight" is used unless given. The background is the figure's own (facecolor="auto") unless given: saving runs after the style has been undone, so the global savefig.facecolor would otherwise paint over it (a :func:~rootfig.dark_theme figure would lose its transparency).

{}

show

show() -> None

Display the figure (plt.show()).

close

close() -> None

Close the figure to free memory.

add_legend

add_legend(
    ax: Axes, style: Style, *, loc: str | None = None
) -> Legend | None

Add a legend with data entries first, then the other histograms in drawing order.

mplhep already registers stacked histograms top-of-stack first, so the legend order matches what the eye sees. Returns None when the style disables legends or nothing is labelled.

add_stats_box

add_stats_box(
    ax: Axes,
    histograms: Sequence[Histogram],
    *,
    loc: str = "auto",
    precision: int = 4,
    include_entries: bool = True,
    colors: Mapping[str, str] | None = None,
    legend: Legend | None = None,
) -> list[Text]

Add N, mean and standard deviation for each histogram with statistics.

With loc="auto" the box sits in the upper right corner, directly below the legend if there is one there (the classic ROOT layout). Any matplotlib legend location string places it in that corner instead. One block of text is drawn per histogram, coloured like the histogram.

add_text

add_text(
    ax: Axes,
    text: str | Sequence[str],
    *,
    loc: str = "upper left",
    **kwargs: Any,
) -> AnchoredText

Add free text (one string or several lines) anchored inside ax.

correlation_figsize

correlation_figsize(
    n_variables: int,
) -> tuple[float, float]

Figure size for a matrix of n_variables, near-square cells included.

The matrix grows with the number of variables, in proportion to the active style's figure width: experiment styles pair larger fonts with larger figures, and the cell annotations and tick labels need that room too.

draw_correlation

draw_correlation(
    matrix: FloatArray,
    labels: Sequence[str],
    ax: Axes,
    *,
    cmap: str | Any = "RdBu_r",
    annotate: bool = True,
    fmt: str | None = None,
    colorbar: bool = True,
    percent: bool = False,
) -> Any

Draw matrix (values in [-1, 1]) as an annotated heat map.

Parameters:

Name Type Description Default
matrix FloatArray

Square correlation matrix.

required
labels Sequence[str]

Tick labels, one per row/column.

required
ax Axes

Target axes.

required
cmap str | Any

Diverging colour map.

'RdBu_r'
annotate bool

Write the coefficient into each cell.

True
fmt str | None

Format for the annotations.

None
colorbar bool

Add a colour bar.

True
percent bool

Show coefficients in percent instead of fractions.

False

apply_xbreak

apply_xbreak(
    left: Axes,
    right: Axes,
    left_range: tuple[float, float],
    right_range: tuple[float, float],
    *,
    mark_size: float = 0.012,
) -> None

Style a pair of axes as the two segments of one broken x axis.

Sets the x limits, removes the facing spines and ticks, and draws diagonal cut marks at the break. The right axes keeps no y tick labels (it shares y with the left one).

break_segments

break_segments(
    outer: tuple[float, float],
    xbreak: tuple[float, float],
    *,
    logx: bool = False,
) -> tuple[
    tuple[float, float],
    tuple[float, float],
    tuple[float, float],
]

Split outer at xbreak into two x ranges and their relative widths.

Returns (left_range, right_range, (left_width, right_width)). Widths are proportional to the spans (in log space for logx).

Raises:

Type Description
ValueError

If the break does not satisfy outer[0] < a < b < outer[1].

finish_axes

finish_axes(
    ax: Axes,
    *,
    data_range: tuple[float, float],
    xlabel: str | None,
    ylabel: str | None,
    xlim: tuple[float, float] | None,
    ylim: tuple[float | None, float | None] | None,
    logx: bool,
    logy: bool,
    headroom: float = 1.2,
    log_headroom: float = 12.0,
    ymin_linear: Literal["zero", "auto"] = "zero",
) -> None

Apply labels, scales and limits, leaving headroom for legends and labels.

headroom is a small fixed margin above the tallest bin: enough to keep the histogram off the top spine, not enough to reserve space for artists that may not be there. Room for a legend, an experiment label, a statistics box or a text line is made afterwards by :func:raise_ylim_above, which measures the drawn artists and raises the limit only as far as they actually need.

Parameters:

Name Type Description Default
data_range tuple[float, float]

(min, max) of the drawn values (including error bars). For log axes the minimum should be the smallest positive value.

required

make_figure

make_figure(
    style: Style,
    *,
    ratio: bool,
    ax: AxesLike = None,
    figsize: tuple[float, float] | None = None,
    break_widths: tuple[float, float] | None = None,
) -> Layout

Create (or reuse) the figure and axes for a plot.

Parameters:

Name Type Description Default
style Style

Provides the default figure size.

required
ratio bool

Add a ratio panel below the main panel, sharing the x axis.

required
ax AxesLike

Existing axes to draw into: one Axes, or (main, ratio). Not supported together with break_widths.

None
figsize tuple[float, float] | None

Figure size in inches; defaults to the style's, enlarged for a ratio panel.

None
break_widths tuple[float, float] | None

Relative widths of the left and right segments of a broken x axis. None for an ordinary single x axis.

None

overlay_artists

overlay_artists(
    ax: Axes, legend: Artist | None
) -> list[Artist]

Return the legend, anchored text boxes and axes-relative texts drawn on ax.

These are the things that must not cover the histograms: the legend, the experiment label, the statistics box and free text lines.

raise_ylim_above

raise_ylim_above(
    axes: Sequence[Axes],
    obstacles: Sequence[Artist],
    *,
    edges: ndarray,
    heights: ndarray,
    logy: bool,
    floating: Sequence[Artist] = (),
    margin: float = 1.08,
) -> None

Raise the upper y limit of axes until obstacles clear the histograms.

heights is the tallest drawn value per bin (see :func:~rootfig.plotting.hist1d.envelope). obstacles are anchored in axes coordinates (a legend with a fixed location, the experiment label, text boxes): they keep their place while the data shrinks beneath them, so for each one the histogram maximum under its horizontal extent is compared with its lower edge and, if it would cover the histogram, the shared y range is stretched until the edge sits margin above the histogram. floating artists (a legend with loc="best") are relocated by matplotlib at draw time; for those only the size matters and room is made for them in the upper left or upper right corner, whichever needs less. Nothing happens if the backend cannot measure artists.

ylabel_for

ylabel_for(
    *,
    normalization: str | None,
    unit: str | None,
    widths: ndarray | None,
    per_object: bool,
) -> str

Default y-axis label: Events, Entries / 2 GeV, Normalised to unity, ...

draw_histograms

draw_histograms(
    histograms: Sequence[Histogram],
    ax: Axes,
    *,
    style: Style,
    stack: bool = False,
    histtype: HistType | None = None,
    errorbars: bool | None = None,
    flow: FlowSpec = "hint",
    stack_uncertainty: bool = True,
    alpha: float | None = None,
) -> Drawn

Draw histograms on ax.

Histograms flagged is_data are always drawn as points with error bars on top; the others are overlaid (default) or stacked.

Parameters:

Name Type Description Default
histograms Sequence[Histogram]

Histograms to draw, in legend order.

required
ax Axes

Target axes.

required
style Style

Style providing the colour cycle.

required
stack bool

Stack the non-data histograms (filled).

False
histtype HistType | None

Default drawing type for non-data histograms without their own histtype; None means "fill" for stacks and "step" otherwise.

None
errorbars bool | None

Draw statistical error bars on non-data histograms. None draws them only for "errorbar" histtypes.

None
flow FlowSpec

Under/overflow display, see :data:FlowSpec.

'hint'
stack_uncertainty bool

Draw a hatched band for the uncertainty of the stack total: statistical, and systematic where the histograms carry variations. Overlaid histograms with variations get a light band in their own colour.

True
alpha float | None

Opacity for filled histograms (default 1 for stacks, 0.4 for overlays).

None

envelope

envelope(
    histograms: Sequence[Histogram], *, stack: bool
) -> tuple[np.ndarray, np.ndarray]

Bin edges and the highest drawn value (content plus uncertainty) per bin.

Used to keep legends and labels clear of the histograms. The uncertainty includes systematic variations, which are drawn as bands. For stacks the stack total counts; otherwise the maximum over all histograms. Overlaid histograms may have different binnings: the envelope is then evaluated on the union of all edges.

fold_flow_bins

fold_flow_bins(
    histograms: Sequence[Histogram],
) -> list[Histogram]

Add the under/overflow of each histogram to its first/last visible bin.

This is mplhep's flow="sum" done once, up front, so that ratios, significances, stack totals, statistical bands and axis limits are all computed from the bins that are drawn. Systematic variations are folded the same way. The flow bins of the returned histograms are empty (values and variances).

label_flow_bins

label_flow_bins(
    ax: Axes, edges: ndarray, *, under: bool, over: bool
) -> None

Label the flow bins made by :func:show_flow_bins <low and >high on ax.

show_flow_bins

show_flow_bins(
    histograms: Sequence[Histogram],
) -> tuple[list[Histogram], tuple[bool, bool]]

Turn under/overflow into extra visible bins, consistently for all histograms.

mplhep's flow="show" adds a flow bin to each histogram only when that histogram has content there, so a stack or overlay mixing histograms with and without overflow ends up with mismatched bins (a crash for stacks, misaligned axes otherwise). rootfig therefore materialises the flow bins itself: a bin is added on a side when any histogram (or systematic variation) has content there, with mplhep's width convention (the larger of 5 % of the range and the mean bin width), and every histogram gets the same edges. The flow bins of the returned histograms are empty.

Returns:

Type Description
(histograms, (underflow_shown, overflow_shown))

draw_hist2d

draw_hist2d(
    histogram: Histogram,
    ax: Axes,
    *,
    logz: bool = False,
    cmap: str | Any = "viridis",
    colorbar: bool = True,
    zlabel: str | None = None,
    flow: str = "none",
    **kwargs: Any,
) -> Any

Draw a 2D histogram as a colour mesh with mplhep.

Empty bins (no content and no variance, i.e. nothing was ever filled there) are left blank so the colour scale is driven by the populated region; bins with negative content stay visible on a linear scale. With logz only positive bins can be shown. Returns the mplhep artists.

draw_efficiencies

draw_efficiencies(
    efficiencies: Sequence[Efficiency],
    ax: Axes,
    *,
    style: Style,
    colors: Sequence[str] | None = None,
) -> tuple[float, float]

Draw each efficiency as points with asymmetric error bars; return the y data range.

draw_profiles

draw_profiles(
    profiles: Sequence[Profile],
    ax: Axes,
    *,
    style: Style,
    colors: Sequence[str] | None = None,
) -> tuple[float, float]

Draw each profile as points with error bars; return the y data range.

draw_ratio_panel

draw_ratio_panel(
    numerators: Sequence[Histogram],
    reference: Histogram,
    ax: Axes,
    *,
    style: Style,
    uncertainty: RatioUncertainty
    | Sequence[RatioUncertainty],
    colors: Sequence[str] | None = None,
    ylim: tuple[float, float] | None = None,
    ylabel: str | None = None,
    band: bool | None = None,
) -> list[Ratio]

Draw numerator / reference for every numerator and return the ratios.

Parameters:

Name Type Description Default
numerators Sequence[Histogram]

Histograms to divide by reference.

required
reference Histogram

The denominator.

required
ax Axes

The ratio axes.

required
style Style

Style providing colours.

required
uncertainty RatioUncertainty | Sequence[RatioUncertainty]

"propagate" (error bars carry both uncertainties) or "numerator" (error bars carry the numerator's; the reference uncertainty is a band), for all numerators or one per numerator. Systematic variations of the histograms are included in error bars and band alike (see :func:~rootfig.histograms.ratio).

required
colors Sequence[str] | None

One colour per numerator; defaults to the numerator's own colour or the style cycle (text.color for data).

None
ylim tuple[float, float] | None

Vertical range; defaults to :data:DEFAULT_RATIO_YLIM expanded to cover the points.

None
ylabel str | None

Label; defaults to "Ratio to <reference>" or "Data / MC".

None
band bool | None

Draw the reference uncertainty band. Defaults to True when any numerator uses uncertainty="numerator".

None

draw_significance_panel

draw_significance_panel(
    result: Ratio,
    ax: Axes,
    *,
    kind: SignificanceKind = "s/sqrt(b)",
    color: str | None = None,
    ylim: tuple[float, float] | None = None,
    ylabel: str | None = None,
) -> None

Draw a per-bin significance (from :func:~rootfig.histograms.significance) as points.

ratio_ylim

ratio_ylim(
    ratios: Sequence[Ratio],
    *,
    band: tuple[ndarray, ndarray] | None = None,
) -> tuple[float, float]

Choose a ratio range: at least (0.5, 1.5), widened to cover the bulk of what is drawn.

The bulk is the 5th to 95th percentile, padded by 10 percent, of the finite ratio values, of the systematic extent of the points and of the edges of the reference band when one is drawn. Each is judged on its own, so it can widen the range but never narrow it. Statistical error bars do not count: a few low-statistics bins with huge uncertainties would otherwise squash the panel. The result is clipped to [0, 3], or to [-3, 3] with negative ratios (signed weights), whose lowest value then stays in view.

add_experiment_label

add_experiment_label(
    ax: Axes,
    style: Style,
    *,
    has_data: bool,
    above: bool = False,
    right: Axes | None = None,
) -> None

Draw the experiment label and/or free text described by style on ax.

above=True puts an experiment label above the frame unless style.label_loc is set, for plots whose data fill the frame (2D histograms, matrices) and so leave no room for a label inside it. right is the right segment of a broken x axis: the luminosity text that belongs above the right end of the frame is placed above that segment's end.

align_experiment_label

align_experiment_label(
    ax: Axes, *, right: Axes | None = None
) -> None

Anchor mplhep's experiment label to the finished figure.

mplhep positions the status word ("Simulation", "Internal", ...) as an axes fraction computed when the label is drawn, and shifts a label above the axes right by the width of the y axis' scientific-notation offset text measured at that moment. Both go stale once the axes are resized (constrained layout) or the y scale changes (a log axis has no offset text). This puts a label above the axes flush with the frame unless an offset text is really shown, fits the line above the frame to its width (see :func:_fit_label_line), lifts the title above the label texts over the frame (see :func:_clear_title), and places the status word a fixed gap after the experiment name with a point-based offset, so it stays put at any axes size.

Call it on the finished figure once its style context has ended, as rootfig's plotting functions do: tick labels and colour bars are laid out when the figure is drawn, from the rcParams active then, so only that layout is the one the figure is shown with. Fitting the label changes the layout in turn, so the figure is drawn and measured again, up to :data:LABEL_FIT_PASSES times, until nothing changes. Sizes follow from the texts' original sizes, so aligning again leaves the label as it is. right is the right segment of a broken x axis, which carries the luminosity text (see :func:add_experiment_label). Does nothing when ax carries no mplhep label.

color_cycle

color_cycle(
    n: int, style: Style | None = None
) -> list[str]

Return n colours from the style's cycle (repeating if necessary).

dark_theme

dark_theme() -> Iterator[None]

Draw everything made inside the block for a dark page.

The block runs under :data:DARK_THEME, so figures, axes and artists made with plain matplotlib (plt.subplots() for ax=, p.ax.text(...)) match. rootfig figures apply it again after their style sheet, so it also holds for experiment styles that fix a white background (ATLAS, LHCb, ALICE, DUNE); Style.rc still wins. The background is transparent, and :meth:Plot.save <rootfig.Plot.save> keeps it so, so one image suits any dark page.

finalize_figure

finalize_figure(
    fig: Figure, *, panels: Sequence[Axes] = ()
) -> None

Fix what the figure's look depends on while its style context is still active.

Call as the last drawing step inside the style context: pins the fonts (see :func:pin_fonts) and fits the y label of each lower panel in panels (see :func:~rootfig.plotting.figure.fit_ylabel), which measures text and so must follow the font pinning that decides which font is drawn. The experiment label is anchored afterwards, outside the context (see :func:align_experiment_label).

foreground

foreground() -> str

Return the ink colour of the active style (text.color) for points and outlines.

A colour name or hex string is returned as set; an RGB(A) tuple, which the rcParam also accepts, is converted to hex so it stays usable as a string.

legend_location

legend_location(style: Style) -> str | None

Return the legend location string for style or None to skip the legend.

resolve_rc

resolve_rc(style: Style) -> list[Mapping[str, Any] | str]

Return the list of style sheets to apply for style (later entries win).

style_context

style_context(style: StyleLike = None) -> Iterator[Style]

Temporarily apply style's rcParams; yields the resolved :class:Style.

use_style

use_style(style: StyleLike = None) -> Style

Apply style globally (until :func:matplotlib.pyplot.rcdefaults), return it.

This is the one function in rootfig that changes global matplotlib state; everything else uses :func:style_context.