Skip to content

API reference

Top level

rootfig.plot

plot(
    data: Any,
    variable: str | Variable | None = None,
    *,
    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,
    normalize_uncertainty: NormalizeUncertainty = "scale",
    stack: StackSpec = False,
    panel: ComparisonKind | None = None,
    reference: str | None = None,
    panel_ylim: tuple[float, float] | None = None,
    panel_label: str | None = None,
    panel_uncertainty: UncertaintyMode | None = None,
    goodness_of_fit: bool | GoodnessOfFitTest = False,
    goodness_of_fit_format: Literal[
        "quotient", "fraction"
    ] = "quotient",
    logx: bool | None = None,
    logy: bool = False,
    flow: FlowSpec = "hint",
    histtype: HistType | None = None,
    errorbars: bool | None = None,
    data_errors: DataErrors | 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,
    variances_from_contents: bool = False,
    functions: Mapping[str, Callable[..., Any]]
    | 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.

The same call draws histograms that already exist. A variable that is a bare name and addresses a TH1 stored in the files (a histogram written by an analysis framework) is read instead of filled, summed over each sample's files and scaled like a filled one; an explicit tree= always means a branch. Histogram objects (hist.Hist or :class:~rootfig.histograms.Histogram, one or a list) passed as data are drawn as they are, with label naming them and variable optional. Options that need event data, such as selection and weight, raise for both. Range inference ("auto", "robust") is a no-op for them; an explicit range crops their axis, moving the rest into flow bins.

Parameters:

Name Type Description Default
data Any

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

required
variable str | Variable | None

Branch name or expression (see :mod:rootfig.expressions), the name of a histogram stored in the files, or a :class:~rootfig.model.Variable carrying binning and labels. Optional for histogram objects, where it supplies the labels, unit and log flag and, through bins and range, a binning to crop and merge them to.

None
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.Regular/Variable axis. Overrides the Variable's binning. A histogram that already exists (stored or object) keeps its binning unless asked to crop or merge: an int must divide its count, and explicit edges must coincide with its own (a Variable written for the tree describes the histogram filled from it) and merge the bins between them. Content outside the requested edges joins the flow bins.

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. For existing histograms, an explicit range without bins crops to its ends, which must be existing edges, and keeps the bins between them.

None
label str | Sequence[str] | None

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

None
observed Any

A sample or group of observed data (or the file(s) for one; histogram objects when data are) drawn as points, excluded from stacks and, without reference=, compared with the prediction in a ratio, relative difference, difference or pull panel.

None
xlabel str | None

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

None
ylabel str | None

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

None
unit str | None

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

None
title str | None

Axis labels; defaults come from the variable (or the stored axis title), 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. With any stacked histograms, only None, False and "width" are allowed: normalising each component separately would not give a normalised total.

None
normalize_uncertainty NormalizeUncertainty

What normalising to a histogram's own total (True, "unity", "density", a number) does to its statistical uncertainty: "scale" keeps every bin's relative uncertainty, as TH1::Scale; "shape" lets the total fluctuate with the bins (the uncertainty of a shape: a binomial fraction, Clopper-Pearson for counts; see :data:~rootfig.histograms.normalize.NormalizeUncertainty). "shape" cannot be combined with flow="sum", whose flow bins lie outside the total.

'scale'
stack StackSpec

True stacks every non-data histogram, False or [] overlays all. A legend label or sequence of labels stacks every histogram carrying those labels, with the rest overlaid. A group is selected by its own label. Unknown labels and data-only labels raise ValueError. Stacks are filled in input order, first at the bottom, followed by the band, overlays in input order, then data. A histogram keeps its colour whether it is stacked or overlaid; explicit colours do not use up entries in the colour cycle.

False
panel ComparisonKind | None

What a lower panel shows: "ratio", "relative_difference", "difference", "pull", "asymmetry", "s/sqrt(b)" or "s/sqrt(s+b)", each defined in the plotting guide ("Lower panel"); None draws none. A point beyond the panel's range is marked at its edge.

None
reference str | None

The label of the one histogram the panel compares with, the background of a significance: every other histogram, data included, is compared with it (every other non-data histogram, as a signal, for a significance). By default data is compared with the stack total, or with the first non-data histogram without a stack; with a stack but no data, every overlaid histogram with the total, and with neither (or with observed data alone), every histogram after the first with the first. A significance takes the overlaid non-data histograms as signals over the stack, or otherwise the last non-data histogram over the sum of the others.

None
panel_ylim tuple[float, float] | None

Lower panel range, y label, and uncertainty treatment of a ratio, relative difference or difference ("propagate", or "numerator" with the reference's uncertainty as a band; by default each numerator uses "numerator" for data over simulation and "propagate" otherwise, so shared systematic sources cancel). "poisson-ratio" takes the exact one-sigma interval of the ratio of two Poisson means for a ratio of counts, as ROOT's TGraphAsymmErrors::Divide(..., "pois"): every compared histogram must hold known counts (see :meth:~rootfig.histograms.Histogram.counts). The label is shrunk, and if needed wrapped onto two lines, to fit the short panel; pass a shorter panel_label ("Ratio") to keep it at full size.

None
panel_label tuple[float, float] | None

Lower panel range, y label, and uncertainty treatment of a ratio, relative difference or difference ("propagate", or "numerator" with the reference's uncertainty as a band; by default each numerator uses "numerator" for data over simulation and "propagate" otherwise, so shared systematic sources cancel). "poisson-ratio" takes the exact one-sigma interval of the ratio of two Poisson means for a ratio of counts, as ROOT's TGraphAsymmErrors::Divide(..., "pois"): every compared histogram must hold known counts (see :meth:~rootfig.histograms.Histogram.counts). The label is shrunk, and if needed wrapped onto two lines, to fit the short panel; pass a shorter panel_label ("Ratio") to keep it at full size.

None
panel_uncertainty tuple[float, float] | None

Lower panel range, y label, and uncertainty treatment of a ratio, relative difference or difference ("propagate", or "numerator" with the reference's uncertainty as a band; by default each numerator uses "numerator" for data over simulation and "propagate" otherwise, so shared systematic sources cancel). "poisson-ratio" takes the exact one-sigma interval of the ratio of two Poisson means for a ratio of counts, as ROOT's TGraphAsymmErrors::Divide(..., "pois"): every compared histogram must hold known counts (see :meth:~rootfig.histograms.Histogram.counts). The label is shrunk, and if needed wrapped onto two lines, to fit the short panel; pass a shorter panel_label ("Ratio") to keep it at full size.

None
goodness_of_fit bool | GoodnessOfFitTest

Test every histogram a ratio panel would compare with its reference (data with the stack total by default; reference= names another, with or without a panel) and write the result below the experiment label, after the text lines: True or "chi2" is ROOT's TH1::Chi2Test of the shapes, "chi2-absolute" a chi-square of the difference with systematic uncertainties, "ks" ROOT's TH1::KolmogorovTest (see :func:~rootfig.goodness_of_fit). The histograms are tested before normalize, in the bins drawn: flow bins enter when flow shows or sums them, xlim and xbreak restrict nothing.

False
goodness_of_fit_format Literal['quotient', 'fraction']

How the chi-square per degree of freedom is written: "quotient" (1.15) or "fraction" (33.5/29).

'quotient'
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 overlaid samples: "step" (default), "fill", "errorbar" or "band". A sample's or group's own histtype takes precedence. Stacked histograms are always filled.

None
errorbars bool | None

Draw statistical error bars on overlaid histograms; ignored for stacked histograms. None draws them only for "errorbar" histtypes.

None
data_errors DataErrors | None

The statistical uncertainty of observed data, in the main and the lower panel alike. None (default) keeps each histogram's own: ROOT's TH1 default, sqrt(sum of squared weights) (sqrt(N) for counts), unless it carries a Poisson interval or errors of its own (a histogram object with poisson= or stat_errors=, a stored TH1 saved with kPoisson/kPoisson2). "auto" keeps such a model too and otherwise draws the Poisson interval for unit-weight counts and sqrt(sum of squared weights) for anything else. The explicit choices replace any model: "sumw2" forces sqrt(sum of squared weights); "poisson" is ROOT's TH1::kPoisson, the Garwood 68 % interval of the counts, asymmetric and with an upper error for an empty bin; a confidence level such as 0.95 (ROOT's TH1::kPoisson2) is the Garwood interval at that level. Both raise for data not known to hold counts (weighted data, see :meth:~rootfig.histograms.Histogram.counts). "auto" is the usual choice for published data/simulation plots.

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 a lower panel; 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. Needs the unbinned statistics collected while filling, which stored histograms and histogram objects lack.

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 a (main, panel) pair with panel=).

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 lower panel includes them in its band and error bars (in a pull, in its denominator; a significance is statistical only). A name is one source: sources of the same name are fully correlated across samples (a plot-level source is shared by all of them), different names are independent and added in quadrature; Plot.uncertainty() returns the components. Histogram objects carry theirs in :attr:~rootfig.histograms.Histogram.variations.

None
variances_from_contents bool

Accept a histogram (stored or object) with a plain count storage that was filled with weights or rescaled, so hist reports no variances: the absolute bin contents are used instead (a warning says so). Fill with hist.storage.Weight() to keep the real uncertainties.

False
functions Mapping[str, Callable[..., Any]] | None

Functions of your own that the variable, selection and weights may call besides the built-in ones, {name: callable} (see :mod:rootfig.expressions). They are called with the branch arrays of a chunk of events at a time, possibly in several threads at once, so each event's result must depend on that event alone.

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 comparisons.

rootfig.plot2d

plot2d(
    data: Any,
    x: str | Variable | None = None,
    y: str | Variable | None = None,
    *,
    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",
    variances_from_contents: bool = False,
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
    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.

Like :func:plot, it also draws histograms that already exist: x alone may name a TH2 stored in the file (its label, unit and log flag then describe the x axis; the y axis keeps the stored title, and shows its axis name when the file has none), and data may be a 2D hist.Hist or :class:~rootfig.histograms.Histogram. For such an object x and y are optional and describe its axes as variable does in :func:plot: label, unit, log flag, a name for the axis and bins/range to crop and merge to; the object itself is left untouched. For both, bins crops and merges bins per axis as in :func:plot: an integer count, or edges that coincide with the existing ones. A range without bins keeps the bins between its ends, which must be existing edges too; cropped content moves into the flow bins. variances_from_contents accepts such a histogram without variances, as in :func:plot. functions ({name: callable}) are functions of your own the expressions may call, as in :func:plot.

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,
    normalize_uncertainty: NormalizeUncertainty = "scale",
    nonfinite: NonFinitePolicy = "drop",
    variances_from_contents: bool = False,
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
) -> Hist

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

See :func:plot for the arguments. data is one sample or one :class:~rootfig.model.Group, whose samples are summed. An integer bins without a range infers one robustly (range="auto" for the full finite minimum and maximum). A variable naming a histogram stored in the file returns that histogram (summed over the files, in Weight storage).

Examples:

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

rootfig.histograms

histograms(
    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,
    normalize: NormalizeSpec = None,
    normalize_uncertainty: NormalizeUncertainty = "scale",
    nonfinite: NonFinitePolicy = "drop",
    systematics: Mapping[str, SystematicLike] | None = None,
    variances_from_contents: bool = False,
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
) -> list[Histogram]

Fill one :class:~rootfig.histograms.Histogram per sample or group with shared binning.

See :func:plot for the meaning of the arguments; this function stops before drawing. An integer bins without a range infers one robustly (range="auto" for the full finite minimum and maximum). Systematic variations are in :attr:~rootfig.histograms.Histogram.variations and summarised by :func:~rootfig.histograms.uncertainty. A variable naming a histogram stored in the files is read instead of filled, as in :func:plot.

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,
    functions: Mapping[str, Callable[..., Any]]
    | 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
functions Mapping[str, Callable[..., Any]] | None

Functions of your own the expressions and the selection may call, {name: callable}, as in :func:plot.

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",
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
) -> SummaryTable

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

The options work as in :func:plot, functions ({name: callable}, functions of your own the expressions may call) included.

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",
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
    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. functions ({name: callable}) are functions of your own the expressions may call, as in :func:plot.

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",
    interval: EfficiencyInterval = "auto",
    cl: float = ONE_SIGMA,
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
) -> 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, nonfinite and functions work as in :func:plot: events with a nan/inf weight are excluded from all steps with a warning, or raise for nonfinite="error". Yields carry sqrt(sum w^2); efficiencies carry the confidence interval interval names (:attr:~rootfig.Cutflow.efficiency_errors): "auto" is ROOT's TEfficiency default for each efficiency, Clopper-Pearson when the event weights of both steps (before the sample's scale and luminosity factor) are unweighted as ROOT decides (their sum equals the sum of their squares: weights of 1, or 0 and 1) and the normal approximation otherwise; the other frequentist methods of :func:efficiency can be named, at confidence level cl. Systematics are not propagated.

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
>>> table.get("Signal").efficiency_errors  # (down, up)

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,
    panel: ComparisonKind | None = None,
    reference: str | None = None,
    panel_ylim: tuple[float, float] | None = None,
    panel_label: str | 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,
    cl: float = ONE_SIGMA,
    interval: EfficiencyInterval = "auto",
    show_empty: bool = False,
    nonfinite: NonFinitePolicy = "drop",
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
    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 confidence intervals of confidence level cl (one standard deviation, 68.27 %, by default, as ROOT's). interval="auto" gives what ROOT's TEfficiency gives: Clopper-Pearson for unweighted entries, the normal approximation for weighted ones. The sample's scale and luminosity factor cancel in an efficiency and are left out, so an unweighted sample stays unweighted. interval names another method, as ROOT means it: "clopper-pearson", "normal", "wilson" and "agresti-coull" (all but the normal approximation for unweighted entries only), the Bayesian "jeffreys" and "uniform" or any rf.Bayesian(alpha, beta, mode=, shortest=) prior (weighted entries too; the efficiency is then the posterior's mean or mode), and "wilson-effective", the Wilson interval of the effective entries for weighted samples (see :data:~rootfig.histograms.binomial.EfficiencyInterval). A binomial interval (any but the normal approximation) is not drawn, with a warning, for a bin that an entry with a negative weight falls into, which keeps its efficiency. Empty bins are left out; show_empty=True draws them as ROOT's "e0" does (0 in [0, 1], or the prior for a Bayesian interval; for weighted samples 0 without a width, or no point for a Bayesian interval), while a bin whose signed weights cancel holds entries and stays undefined. 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).

panel compares the efficiencies in a lower panel ("ratio" for a scale factor, "relative_difference", "difference", "pull", "asymmetry"): data over the first simulated sample, or every further sample over the first, or every other one over the sample reference names; the intervals are propagated as independent, keeping their asymmetry. panel_ylim and panel_label set its range and y label, and Plot.comparisons holds the :class:~rootfig.histograms.Comparison objects.

functions ({name: callable}) are functions of your own the variable, selection and passed may call, as in :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,
    panel: ComparisonKind | None = None,
    reference: str | None = None,
    panel_ylim: tuple[float, float] | None = None,
    panel_label: str | 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",
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
    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. panel, reference, panel_ylim and panel_label add a lower panel comparing the profiles, as for :func:efficiency (panel="difference" compares the response or resolution of two configurations). functions ({name: callable}) are functions of your own the expressions may call, as in :func:plot.

Examples:

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

rootfig.evaluate

evaluate(
    expression: ExpressionLike,
    arrays: Mapping[str, Any] | Array,
    *,
    length: int | None = None,
    functions: Mapping[str, Callable[..., Any]]
    | 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. functions ({name: callable}) may be called besides the built-in ones; like any rootfig call, this one sees no others.

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]
>>> evaluate("twice(count(pt))", arrays, functions={"twice": lambda x: 2 * x}).tolist()
[4, 0, 2]

rootfig.compare

compare(
    numerator: Hist | Histogram | Efficiency | Profile,
    reference: Hist | Histogram | Efficiency | Profile,
    *,
    kind: ComparisonKind = "ratio",
    uncertainty: UncertaintyMode = "propagate",
) -> Comparison

Compare numerator with reference bin by bin, with uncertainties.

With n and d the contents and vn and vd the variances:

  • "ratio" is n / d, "relative_difference" n / d - 1 (same uncertainties), "difference" n - d;
  • "asymmetry" is (n - d) / (n + d), both sides propagated;
  • "pull" is (n - d) / sqrt(vn + vd + syst**2), where syst is the systematic uncertainty of n - d on the side facing the reference (the lower one where n > d, the upper one elsewhere): a Gaussian diagnostic of the error bars, not a likelihood (deviance) residual;
  • "s/sqrt(b)" and "s/sqrt(s+b)" are per-bin significances of the numerator as signal over the reference as background, statistical only.

Statistical uncertainties of the two sides are uncorrelated and propagated to first order from each side's (down, up) errors (:meth:Histogram.errors() <rootfig.histograms.Histogram.errors>; sqrt of the variances for a plain hist.Hist): the lower error of the result takes each side's lower or upper error, whichever lowers it, so asymmetric errors stay asymmetric, and a pull divides by the errors facing the other side. With symmetric errors these are the formulas above. Histogram inputs with systematic variations enter source by source through the varied comparison itself: a source present on both sides varies both together (a shared luminosity uncertainty cancels in a ratio), 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 uncertainty="numerator" only the numerator's sources enter the error bars; the reference's are the band. uncertainty="poisson-ratio" takes the exact interval of the ratio of two Poisson means instead of the propagated one, for two histograms of counts (see :data:UncertaintyMode). In a ratio or a relative difference, a variation that empties a reference bin leaves that bin's systematic uncertainty nan, with a :class:~rootfig.errors.RootfigWarning.

Two :class:~rootfig.histograms.Efficiency or two :class:~rootfig.histograms.Profile objects compare their values the same way, their intervals propagated as independent, so the asymmetric intervals of an efficiency stay asymmetric. Significances, which count events, and "numerator", which needs a band, and "poisson-ratio", which needs counts, are refused for them.

Raises:

Type Description
BinningError

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

TypeError

For a histogram compared with an efficiency or a profile, or an efficiency with a profile.

ValueError

For an unknown kind or uncertainty, uncertainty="numerator" with a pull, an asymmetry or a significance, which have no band, and for efficiencies and profiles as described above, or profiles of different statistics.

rootfig.goodness_of_fit

goodness_of_fit(
    a: Hist | Histogram,
    b: Hist | Histogram,
    *,
    test: GoodnessOfFitTest = "chi2",
) -> GoodnessOfFit

Test whether two one-dimensional histograms agree, from their own uncertainties.

  • "chi2" is ROOT's TH1::Chi2Test, a test of homogeneity: whether both are drawn from one shape, whatever their normalisations (ndf is the number of bins less one). Histograms known to hold counts (:meth:Histogram.counts() <rootfig.histograms.Histogram.counts>, scaled counts included, as ROOT's "UU NORM") enter as Poisson counts, every other one as weighted, so a weighted histogram is never taken for counts: counts with counts is ROOT's "UU", counts with a weighted histogram "UW" (in either order) and two weighted histograms "WW". Statistical only. As in ROOT, an empty bin of the weighted histogram of "UW" takes the variance sum(w^2) / sum(w), which does not scale like a variance, so there the result depends on that histogram's normalisation. A histogram with stat_errors is weighted and enters with their squares, whatever counts it holds. Counts whose factor varies from bin to bin (divided by unequal bin widths, say) enter as counts only with counts of the same factors, and otherwise as weighted, with their contents.
  • "chi2-absolute" also tests the normalisation: r C^-1 r with r = a - b and C the statistical variances of r, each side's error taken towards the other histogram as in a pull, plus one matrix per systematic source, fully correlated across bins (a source shared by both histograms moves them together, as in :func:compare); ndf is the number of bins. A Gaussian approximation, poor for bins with few entries.
  • "ks" is ROOT's TH1::KolmogorovTest: the largest distance between the cumulative shapes, its probability from the effective entries of both. A histogram without uncertainties is compared as a function. For binned data the p-value is biased high, the less so the finer the binning relative to the features compared (ROOT's NOTE 3). Categories have no order to accumulate along, so it refuses a category axis.

"chi2" and "ks" need one variance per bin: the sum of squared weights, or the square of symmetric stat_errors; asymmetric stat_errors are refused, as no variance describes them.

Bins empty on both histograms do not enter the chi-squares; flow bins do not enter at all. For "chi2-absolute" a bin is empty only without an uncertainty too: one whose weights cancel to zero still enters, with its sum of squared weights.

Every test takes the bins' uncertainties as independent, so histograms normalised with uncertainty="shape", whose bins are correlated (see :func:~rootfig.histograms.shape_covariance), are refused: test them before normalising.

Raises:

Type Description
BinningError

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

ValueError

For an unknown test; for a histogram normalised with uncertainty="shape"; for "chi2" and "ks", for a histogram whose contents sum to zero or with asymmetric stat_errors, or a bin they cannot test (no uncertainty on either side); for "ks", for a category axis; for "chi2-absolute", for a singular covariance (bins without statistical uncertainty that the systematic sources do not make up for).

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, NumPy .npz file(s) (one array per branch), 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 by default compared with the prediction in a ratio, relative difference, difference or pull panel (never a signal of a significance).

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 "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.Group dataclass

Group(
    components: Sequence[Sample | Group],
    *,
    label: str,
    color: str | None = None,
    histtype: HistType | None = None,
)

Several samples drawn as one histogram.

The components are filled independently, each with its own source, selection, weight, scale, cross section and systematics, and their histograms are summed afterwards; same-named systematic sources stay correlated (see :func:~rootfig.histograms.sum_histograms). Normalisation applies to the sum. The summed histogram carries the group's label and drawing hints, no sample and no unbinned statistics.

Parameters:

Name Type Description Default
components Sequence[Sample | Group]

:class:Sample objects and nested groups, in drawing order. All observed data or all simulation. Raw file paths are not accepted; wrap them in a Sample.

required
label str

Legend label of the summed histogram.

required
color str | None

Drawing hints for the summed histogram, None for the style defaults. The components' own are not used.

None
histtype str | None

Drawing hints for the summed histogram, None for the style defaults. The components' own are not used.

None

Examples:

>>> import rootfig as rf
>>> ww = rf.Sample("ww.root", label="WW", xsec="16.4 pb", ngen="eventsProcessed")
>>> zz = rf.Sample("zz.root", label="ZZ", xsec="1.4 pb", ngen="eventsProcessed")
>>> vv = rf.Group([ww, zz], label="VV", color="C0")

samples property

samples: tuple[Sample, ...]

The leaf samples, nested groups expanded, left to right.

is_data property

is_data: bool

Whether the group is observed data (all of its samples are).

replace

replace(**changes: Any) -> Group

Return a copy with the given fields changed, validated like the constructor.

rootfig.Variable dataclass

Variable(
    expression: str,
    bins: Bins | None = None,
    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). Its syntax is checked here; the functions it calls are looked up when it is used, among the call's functions=.

required
bins Bins | None

Binning specification, see :data:Bins. None (the default) means no preference: :data:DEFAULT_BINS bins over a range inferred from the data when filling from a tree, and the stored binning when the variable names a histogram stored in a file. An integer asks for that many bins; a stored histogram is then merged down to that count. Explicit edges (also (n, low, high) or an integer with a (low, high) range) must coincide with a stored histogram's edges, crop its axis and merge the bins between them, with the rest in flow bins (:func:~rootfig.model.binning.merge_target), so one variable describes the tree and the histograms filled from it alike.

None
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. For existing histograms, an explicit range without bins crops to its ends, which must be edges of the histogram, and keeps the bins between them.

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, :class:~rootfig.PlotBook). Defaults to :func:~rootfig.model.safe_file_stem of the expression. An explicit name must be a file name component on every platform (:func:~rootfig.model.check_file_stem): no slash, control character or <>:"|?*, no trailing dot or space, and not a Windows device name such as CON.

None

safe_name property

safe_name: str

The file name component of this variable: name, else one made from the expression.

Both pass :func:~rootfig.model.check_file_stem, so :meth:Plot.save with a directory and :class:~rootfig.PlotBook use it as it is.

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). Its syntax is checked here; the functions it calls are looked up when it is used, among the call's functions=.

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 lower 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.

rootfig.Bayesian dataclass

Bayesian(
    alpha: float = 1.0,
    beta: float = 1.0,
    mode: bool = False,
    shortest: bool | None = None,
)

The Bayesian interval of an efficiency with a Beta(alpha, beta) prior.

passed of total entries make the posterior Beta(passed + alpha, total - passed + beta); with weights, the sums are scaled to the effective entries of the total first (sum w / sum w^2), as ROOT's TEfficiency does. The efficiency is the posterior mean, or its mode with mode=True, and the interval the central one, or the shortest with shortest=True (ROOT's kPosteriorMode and kShortestInterval). shortest follows mode unless given, as TEfficiency::SetPosteriorMode also sets the shortest interval. interval="jeffreys" is Bayesian(0.5, 0.5), "uniform" is Bayesian(1, 1).

Raises:

Type Description
ValueError

Unless alpha and beta are positive finite numbers.

rootfig.ONE_SIGMA module-attribute

ONE_SIGMA = math.erf(1.0 / math.sqrt(2.0))

The confidence level of one standard deviation, 68.27 %: ROOT's default for intervals.

Batch plotting

rootfig.PlotBook dataclass

PlotBook(
    data: Any,
    variables: str
    | Variable
    | Sequence[str | Variable]
    | _AllVariables,
    *,
    selections: Mapping[str, CutLike | None] | None = None,
    variants: Mapping[str, Mapping[str, Any]] | None = None,
    plot_kwargs: Mapping[str, Any] | None = None,
    include: str | Sequence[str] | None = None,
    exclude: str | Sequence[str] | None = None,
)

Many :func:~rootfig.plot calls described once: variables, selections and variants.

A book holds what every plot shares (data and plot_kwargs) and the three axes that vary. :meth:tasks lists the Cartesian product in a fixed order (variables, then selections, then variants, each in insertion order), :meth:plots runs it one plot at a time, :meth:save writes one file per task under deterministic names and :meth:save_pdf one multipage PDF with several plots per page. Each plot is what the corresponding :func:~rootfig.plot call returns, so samples, groups, stored histograms, arrays and histogram objects behave exactly as they do there; data is passed on as given, never copied. The book only reads the files less often: once per batch of variables, for every selection and variant (see :meth:plots). The configuration mappings are copied, the values inside them (a systematics= mapping, a Style) are shared. Names of selections and variants are file name components and must be usable on every platform (:func:~rootfig.model.check_file_stem). Two tasks whose file names differ only in case or Unicode normalisation are one file on many file systems and are rejected as a collision.

The variables are listed explicitly, in which case nothing is inspected when the book is built, or discovered with variables=rf.ALL, which inspects the metadata of the inputs (branch types, object classes, array types) and never their contents; the result is an ordinary :attr:variables tuple.

Parameters:

Name Type Description Default
data Any

What to plot, exactly as :func:~rootfig.plot takes it.

required
variables str | Variable | Sequence[str | Variable] | _AllVariables

Variables (names, expressions or :class:~rootfig.model.Variable objects); one may be given bare. :attr:~rootfig.model.Variable.safe_name identifies each and names its files, so two variables must not share one. :data:ALL (rf.ALL) discovers them instead: every branch of the trees whose values are numbers or booleans (lists and fixed-size arrays of them included, strings and records left out) and every TH1 stored in files read without a tree, kept when every sample, observed= included, provides it the same way under every variant, sorted by name. Stored histograms are left out when a selection, a weight, nonfinite="error" or a systematic varying event data rules them out. A name that is not an identifier is addressed in backticks. Histogram objects as data carry no names to discover and need an explicit variable.

required
selections Mapping[str, CutLike | None] | None

Named selections, {name: cut} with cut a string, a :class:~rootfig.model.Cut or None. None (the default) plots without a selection and without a selection component in the file names.

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

Named drawing variants, {name: {keyword: value}}. A variant's keywords override plot_kwargs for its tasks. None (the default) draws every plot once, without a variant component in the file names.

None
plot_kwargs Mapping[str, Any] | None

Keywords passed to every :func:~rootfig.plot call (stack=, observed=, style=, ...). data, variable, selection, save and ax belong to the book and are rejected here and in variants.

None
include str | Sequence[str] | None

With variables=rf.ALL only: shell patterns (one, or a sequence; "Muon_*", ["*_cov", "*Index"]), matched case-sensitively against the source name (jet1_b-tag, sel/mz, not the file name component). A variable is kept when it matches one include pattern (all do when include is not given) and no exclude pattern.

None
exclude str | Sequence[str] | None

With variables=rf.ALL only: shell patterns (one, or a sequence; "Muon_*", ["*_cov", "*Index"]), matched case-sensitively against the source name (jet1_b-tag, sel/mz, not the file name component). A variable is kept when it matches one include pattern (all do when include is not given) and no exclude pattern.

None

Raises:

Type Description
ValueError

No variable, an empty selections= or variants=, a reserved plot keyword, a name that cannot be a file name component, two tasks whose output files would collide, include=/exclude= with an explicit variable list, or patterns that leave no variable.

TypeError

selections=, variants=, plot_kwargs= or one variant's overrides is not a mapping, a name is not a string, a keyword is not one :func:~rootfig.plot takes (the message names the closest one), a pattern is not a string, or rf.ALL meets histogram objects.

SourceError

With rf.ALL, when the inputs offer nothing every task can plot, or a file cannot be surveyed (several trees and no tree=, a missing tree).

Examples:

>>> book = rf.PlotBook(
...     [background, signal],
...     [mass, pt],
...     selections={"baseline": "nMuon >= 2", "sr": "(nMuon >= 2) & (mass > 120)"},
...     variants={"lin": {}, "log": {"logy": True}},
...     plot_kwargs={"stack": True, "style": style},
... )
>>> book.save("plots", formats=["pdf", "png"])
>>> rf.PlotBook(
...     [background, signal], rf.ALL, include=["Muon_*", "MET*"], exclude="*Index"
... ).variables
(Variable('MET'), Variable('Muon_eta'), Variable('Muon_pt'), ...)

tasks

tasks() -> tuple[PlotTask, ...]

All plots of the book, variables outermost and variants innermost.

Pure: nothing is read or drawn. An implicit axis contributes one task with the name None.

plots

plots() -> Iterator[tuple[PlotTask, Plot]]

Run the tasks lazily, yielding (task, plot) one figure at a time.

Each result is what plot(data, task.variable, selection=task.selection, **task.kwargs) returns, produced in batches: the variables are taken a few dozen at a time, the branches they and every selection need are read once per sample for the batch, and variants that only change the drawing (logy, normalize, panel, style, ...) are drawn from one set of prepared histograms, each from its own copy. A variant that changes how the histograms are prepared (bins, weight, observed, systematics, ...) is prepared on its own. The tasks themselves are built one at a time and nothing is read before the first one runs. The caller owns the figures that are yielded and closes them (:meth:Plot.close); the figure a failing call had already created is closed here. An error keeps its type and gains a note naming the task (variable=..., selection=..., variant=...); one raised while reading a batch names the batch's first task.

save

save(
    directory: str | PathLike[str],
    *,
    formats: str | Sequence[str] = DEFAULT_FORMATS,
    **savefig_kwargs: Any,
) -> list[Path]

Draw every task and write it to directory as <stem>.<format>.

The directory is created if needed. Every figure is closed once it has been written, and also when drawing or writing it fails, so memory stays bounded; use :meth:plots to keep or customise figures. Writing goes through :meth:Plot.save.

Parameters:

Name Type Description Default
directory str | PathLike[str]

Where to write. It is created if it does not exist; an existing path that is not a directory raises :class:NotADirectoryError.

required
formats str | Sequence[str]

One format or several ("png", ["pdf", "png"]), each written for every task. A leading dot is accepted and duplicates are dropped. Anything matplotlib cannot write raises :class:ValueError before the first figure is drawn.

DEFAULT_FORMATS
**savefig_kwargs Any

Forwarded to :meth:Plot.save. format and fname are rejected, since the file names come from formats and from the task.

{}

Returns:

Type Description
list[Path]

The written files, in task order and then format order.

save_pdf

save_pdf(
    path: str | PathLike[str],
    *,
    layout: LayoutSpec = "auto",
    figsize: tuple[float, float] | None = None,
    **savefig_kwargs: Any,
) -> Path

Draw every task into one multipage PDF, several plots per page.

The plots follow :meth:tasks order, filling each page row by row, and each is drawn as :func:~rootfig.plot would draw it on a figure of its own (its panels, broken axis, style and labels), inside a cell of the page. Pages are written one after another and each figure is closed before the next page begins; the histograms are prepared as for :meth:plots, with the same batched reads. The file is written next to its final name and renamed onto it once complete, so an existing file survives a failure.

Parameters:

Name Type Description Default
path str | PathLike[str]

The PDF to write. .pdf is appended to a name without a suffix ("overview" writes overview.pdf); another suffix raises :class:ValueError. Parent directories are created. An existing directory raises :class:IsADirectoryError: the method writes one file and does not invent its name.

required
layout LayoutSpec

"auto" (the default) chooses the grid of each page from the plots it holds: up to 2 x 3 plots without a lower panel or broken axis, up to 2 x 2 when one of them has one, fewer on the last page so that the remaining plots are not crammed into a corner (seven plain plots make a 2 x 3 page and a 1 x 1 one). A (rows, columns) pair is the grid of every page, the last included, whose unused cells stay empty.

'auto'
figsize tuple[float, float] | None

Size of a whole page in inches. By default a page is as large as the grid of figures the plots would have on their own, so a cell shows its plot at the usual size (a lower panel makes the cells of its page taller, a :class:~rootfig.model.Style with a figsize sets theirs). A task's own figsize cannot apply inside a shared page and raises.

None
**savefig_kwargs Any

Forwarded to :meth:matplotlib.backends.backend_pdf.PdfPages.savefig for every page (dpi= for rasterised parts, ...). The background is the page's own unless facecolor is given, as for :meth:Plot.save. metadata= is the exception: it is the document information dictionary ({"Title": ..., "Author": ...}), which belongs to the document rather than to a page, and is applied to the whole PDF. format, fname, figure and backend are rejected, since the method writes PDF pages of its own figures.

{}

Returns:

Type Description
Path

The written file.

Raises:

Type Description
ValueError

A layout that is not "auto" or a pair of positive integers, a figsize that is not a pair of positive finite numbers, a path with a suffix other than .pdf, a reserved savefig keyword, a figsize in plot_kwargs or a variant, or plots sharing a page whose styles ask for different page backgrounds; all before anything is read or drawn.

IsADirectoryError

path is an existing directory.

Examples:

>>> book.save_pdf("overview.pdf")
PosixPath('overview.pdf')
>>> book.save_pdf("overview", layout=(2, 3), figsize=(16, 10))
PosixPath('overview.pdf')

select

select(
    *,
    variables: str | Sequence[str] | None = None,
    selections: str | Sequence[str] | None = None,
    variants: str | Sequence[str] | None = None,
) -> PlotBook

Return a new book restricted to the named variables, selections and variants.

Names are :attr:~rootfig.model.Variable.safe_name values and the keys of the selections and variants mappings; None leaves an axis as it is and the book's order is kept. An unknown name, or a filter that names nothing, raises and lists the choices. An implicit axis (selections=None or variants=None) has the single choice "all" or "default" and stays implicit.

rootfig.PlotTask dataclass

PlotTask(
    variable: Variable,
    selection_name: str | None,
    selection: Cut | None,
    variant_name: str | None,
    kwargs: Mapping[str, Any],
)

One plot of a :class:PlotBook: a variable, a selection and the keywords to draw it with.

Tasks compare and hash by their :attr:stem, which is unique within a book, so they can be collected in a set or used as dictionary keys. The keyword values are not compared: valid ones such as bins=np.array(...) have no scalar equality.

Attributes:

Name Type Description
variable Variable

The :class:~rootfig.model.Variable to histogram.

selection_name str | None

The key of the selection in the book's selections mapping, or None when the book has no selection axis (selections=None).

selection Cut | None

The :class:~rootfig.model.Cut passed as selection=, or None.

variant_name str | None

The key of the variant in the book's variants mapping, or None when the book has no variant axis (variants=None).

kwargs Mapping[str, Any]

The keywords passed to :func:~rootfig.plot: the book's common plot_kwargs with the variant's overrides on top.

selection_id property

selection_id: str

The selection name, or "all" for the implicit no-selection choice (display only).

variant_id property

variant_id: str

The variant name, or "default" for the implicit single variant (display only).

stem property

stem: str

The output file name without suffix: <variable>[__<selection>][__<variant>].

Only axes the book was given explicitly appear, so a book without selections= writes mass.pdf while selections={"all": ...} writes mass__all.pdf: an explicit name is never dropped, whatever it is.

describe

describe() -> str

Name the task for messages: variable='mass', selection='sr', variant='log'.

rootfig.ALL module-attribute

ALL: Final = _AllVariables()

Discover the variables of a :class:PlotBook from its inputs: PlotBook(data, rf.ALL).

Every branch of the trees (TTree or RNTuple) holding numbers or booleans, lists and fixed-size arrays of them included, and every 1D histogram stored in files read without a tree, when every sample provides it under every variant; include= and exclude= narrow the set with shell patterns. Only metadata is read. A distinct object rather than a string, so that a branch or histogram named "all" stays an ordinary variable name.

rootfig.discover_variables

discover_variables(
    data: Any,
    *,
    selections: Mapping[str, CutLike | None] | None = None,
    variants: Mapping[str, Mapping[str, Any]] | None = None,
    plot_kwargs: Mapping[str, Any] | None = None,
    include: str | Sequence[str] | None = None,
    exclude: str | Sequence[str] | None = None,
) -> tuple[Variable, ...]

Return the variables PlotBook(data, rf.ALL, ...) would plot, without building the book.

The same discovery from metadata alone, under the same keywords (see :class:PlotBook): every branch or stored TH1 that every sample, observed= included, provides the same way under every variant and that nothing in the configuration is bound to refuse, filtered by include and exclude and sorted by source name. Unlike the book it does not check that the output file names are distinct: two variables that sanitise to one name (a-b and a_b) are both returned, to be told apart with :meth:~rootfig.model.Variable.replace before they are given to a book.

Raises:

Type Description
(SourceError, TypeError, ValueError)

As :class:PlotBook with variables=rf.ALL.

Examples:

>>> variables = rf.discover_variables(samples, include="jet*")
>>> renamed = [
...     v.replace(name="jet1_btag") if v.expression == "`jet1_b-tag`" else v for v in variables
... ]
>>> book = rf.PlotBook(samples, renamed)

rootfig.model.check_file_stem

check_file_stem(value: str, *, what: str) -> str

Return value if it can be a file name component on every platform, else raise.

Checked once, when a :class:~rootfig.model.Variable name or a :class:~rootfig.PlotBook selection or variant name is given, rather than when save() reaches a file system that refuses it. what names the value in the message, which also offers :func:safe_file_stem of the value when that leaves anything: a spelling that passes this check.

rootfig.model.safe_file_stem

safe_file_stem(text: str) -> str

Reduce text to a component that passes :func:check_file_stem, or to "".

Runs of anything but ASCII letters, digits and _ become one _, and a stem Windows reserves (CON, COM1) gets a trailing _. "" means nothing usable was left, as for "?". :attr:~rootfig.model.Variable.safe_name builds the automatic name of a variable from its expression this way.

Results

rootfig.Plot dataclass

Plot(
    fig: Figure,
    ax: Axes,
    panel_ax: Axes | None = None,
    ax_right: Axes | None = None,
    panel_ax_right: Axes | None = None,
    histograms: list[Histogram] = list(),
    comparisons: list[Comparison] = list(),
    variable: Variable | None = None,
    matrix: FloatArray | None = None,
    efficiencies: list[Efficiency] = list(),
    profiles: list[Profile] = list(),
    stack: Histogram | None = None,
    goodness_of_fit: list[GoodnessOfFit] = 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.

panel_ax Axes | None

The lower panel's axes (panel=), or None.

ax_right, panel_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).

stack Histogram | None

The summed stacked histograms, labelled "Total", including variations, or None. The stack's band uses this histogram, and so does a lower panel comparing with the stack total (not one with reference=, nor a significance of a fully stacked signal, whose background excludes it).

comparisons list[Comparison]

The :class:~rootfig.histograms.Comparison objects drawn in the lower panel, one per numerator.

goodness_of_fit list[GoodnessOfFit]

The :class:~rootfig.histograms.GoodnessOfFit of each histogram tested against its reference (plot(goodness_of_fit=...)).

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 the lower panel (left, right).

hists property

hists: list[Any]

The underlying hist.Hist objects, in input order.

uncertainty

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

Statistical and systematic uncertainties of the stack or one histogram.

Parameters:

Name Type Description Default
label str | None

The label of one histogram. None selects the stack total, with same-named systematic sources added linearly, or the sole non-data histogram when there is no stack.

None

Raises:

Type Description
KeyError

If no histogram has label.

ValueError

If label is None and there is no stack and either zero or several non-data histograms.

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,
    per_object: bool | None = None,
    poisson: bool | float = False,
    _provenance: Provenance | None = None,
    *,
    stat_errors: tuple[ArrayLike, ArrayLike] | 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.

per_object bool

Whether an entry is an object rather than an event (a per-object variable), which words the Entries/Events y label. Follows stats unless given.

poisson bool | float

Whether the statistical uncertainty is the Poisson (Garwood) interval of the counts rather than sqrt(variances) (see :meth:errors): True at one standard deviation, ROOT's TH1::kPoisson, or a confidence level such as 0.95, ROOT's TH1::kPoisson2. Needs contents known to be counts (see :meth:counts); anything else raises ValueError, since sums of weights cannot say whether they are counts. Scaling, normalising or rebinning such a histogram keeps the interval, scaled like the contents (ROOT falls back to sqrt(variances) once a histogram is scaled), so counts scaled by c are Histogram(counts, poisson=True).scaled(c). plot(data_errors=...) sets it for observed data, and a stored TH1 saved with kPoisson or kPoisson2 brings it.

Statistical errors of any other origin (a fit, a bootstrap, an asymmetric
graph) are given as ``stat_errors=(down, up)`` arrays with a value per bin,
or per cell with the flow bins. They go through scaling, normalising and
moving cells with the contents; where bins are merged they add in
quadrature side by side, an approximation for asymmetric errors.
``errors()`` returns them, and ``replace(hist=...)`` drops them with the old
contents.
Whether the contents are counts is kept apart from the error model it
survives ``replace(poisson=False)`` and ``stat_errors``, so the Poisson
interval can be asked for again later.

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, or that sums several (a group), 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
) -> tuple[FloatArray, FloatArray]

Statistical uncertainty below and above the contents, (down, up).

Both are sqrt(variances), the uncertainty of a sum of weights; the errors given as stat_errors; or, with :attr:poisson, the distances to the Garwood interval of the counts at its confidence level, scaled like the contents (see :func:~rootfig.histograms.intervals.poisson_errors). The factor of each bin comes from a record of one count per bin that goes through every scaling, normalisation and rebinning with the contents, never from the contents themselves, so an empty histogram scaled by 3 gets 0 +5.52 and an empty bin divided by its width its own share. The pair is matplotlib's yerr order; drawing, comparisons and :func:~rootfig.histograms.uncertainty take it from here.

counts

counts(
    *, flow: bool = False
) -> tuple[FloatArray, FloatArray]

Return the whole counts behind the contents and each bin's factor: (counts, factor).

values = counts * factor. Known for unit-weight counts (whole numbers equal to their variances, not filled with weights: factor 1) and for what rootfig made of them since: scaled, normalised, rebinned or summed with counts of the same factors, whatever the error model.

Raises:

Type Description
ValueError

For anything else, since sums of weights cannot say whether they are counts, and for counts scaled by a negative factor.

replace

replace(**changes: Any) -> Histogram

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

A new hist brings its own counts: whether they are counts (see :meth:counts) is judged from it afresh, and errors given for the old contents (stat_errors) are dropped; pass new ones as stat_errors. Use :meth:map_hists to transform the contents and keep both. Switching the error model (poisson) keeps what is known about the counts.

map_hists

map_hists(
    transform: Callable[[Hist], Hist],
    *,
    linear: bool = False,
) -> Histogram

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

The result has sqrt(variances) errors and holds no known counts (:attr:poisson off, errors given as stat_errors dropped) unless linear=True says that transform acts on the cells linearly with non-negative coefficients, as cropping, rebinning, moving flow cells and scaling by a positive factor do (:meth:scaled handles a negative one, which turns intervals over). The record of counts and the given errors then go through it too, which holds only while every cell stays counts times factors that do not depend on the contents: merging counts of different factors, e.g. bins divided by unequal widths, drops the counts (and raises with :attr:poisson). Errors correlated across bins stay correlated through any transform.

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. A negative factor turns an interval over, so errors given as stat_errors swap their sides.

rebinned

rebinned(factor: int | Sequence[int]) -> Histogram

Return a copy with every factor adjacent bins merged (per axis for a sequence).

Contents and variances add up; the flow bins are kept. Variations are rebinned alike, so ratios and uncertainty bands stay consistent. Rebin before normalising: heights per unit width or per unit area do not add.

Raises:

Type Description
BinningError

If the histogram is normalised, a factor is not a positive integer dividing the axis' bin count, an axis is categorical, or the sequence does not have one entry per axis.

rebinned_to

rebinned_to(
    bins: Bins | Sequence[Bins | None] | None,
    range: RangeSpec | Sequence[RangeSpec] = None,
) -> Histogram

Crop and merge axes using the same bins and range as a Variable.

For 1D, give one binning and range specification. For multiple axes, give one of each per axis; a single None or integer bin count, and a single range (None, a string or (low, high)), apply to every axis. A range without bins keeps the existing bins between its ends. Requested edges, range ends included, must coincide with existing edges. Cropped values and variances join the flow bins, and variations follow the same transformation. Uniform merges preserve Regular axes and their transforms; uneven merges produce Variable axes. Unbinned statistics describe the entries and are kept. Asking for the existing bins returns self.

Raises:

Type Description
BinningError

If edges do not coincide, a count does not divide the axis size, specifications do not match the dimensionality, a categorical axis is merged, a cropped side lacks its flow bin, or a histogram is normalised. Crop and rebin before normalising.

rootfig.Comparison dataclass

Comparison(
    kind: ComparisonKind,
    label: str,
    reference: str,
    values: FloatArray,
    errors: _Errors,
    edges: FloatArray,
    band: _Errors | None = None,
    syst_errors: tuple[FloatArray, FloatArray]
    | None = None,
    syst_band: tuple[FloatArray, FloatArray] | None = None,
    uncertainty: UncertaintyMode = "propagate",
    reference_hist: Hist | None = None,
    reference_points: Efficiency | Profile | None = None,
)

A bin-by-bin comparison of a numerator with a reference.

Attributes:

Name Type Description
kind ComparisonKind

The :data:ComparisonKind computed.

label, reference

The labels of the numerator and the reference; "" for a plain hist.Hist.

values FloatArray

The comparison per bin; nan where it is undefined (an empty reference for a ratio, a zero uncertainty for a pull).

errors _Errors

Statistical uncertainty on values as (down, up) (matplotlib's yerr order; see :data:UncertaintyMode), nan where values is; 1 on both sides for a pull, whose uncertainty is its unit.

edges FloatArray

Bin edges shared by both histograms.

band _Errors | None

The reference's statistical uncertainty as (down, up), drawn around the baseline: relative for a ratio and a relative difference, absolute for a difference; None for a pull, an asymmetry and a significance.

syst_errors tuple[FloatArray, FloatArray] | None

Systematic uncertainty on values as (down, up) (matplotlib's yerr order), following :data:UncertaintyMode; None without systematic variations, and for a pull, which divides by them.

syst_band tuple[FloatArray, FloatArray] | None

The reference's systematic uncertainty, in the units of band, as (down, up); None if the reference has no variations.

uncertainty UncertaintyMode

The :data:UncertaintyMode of the error bars: with "numerator" the reference's uncertainty is only in the band, which is then drawn.

reference_hist Hist | None

The histogram compared with, which a panel drawing several comparisons checks they share; None for efficiencies and profiles and for a :class:Comparison built by hand.

reference_points Efficiency | Profile | None

The efficiency or profile compared with, checked like reference_hist; None for histograms and for a :class:Comparison built by hand.

centers property

centers: FloatArray

Bin centres.

half_widths property

half_widths: FloatArray

Half bin widths (for horizontal error bars).

total_errors

total_errors() -> _Errors

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

total_band

total_band() -> _Errors | None

Return the reference's total uncertainty, (down, up); None without a band.

rootfig.GoodnessOfFit dataclass

GoodnessOfFit(
    test: GoodnessOfFitTest,
    method: str,
    statistic: float,
    ndf: int | None,
    p_value: float,
    label: str,
    reference: str,
    bins: ndarray,
    notes: tuple[str, ...] = (),
    systematics: tuple[str, ...] = (),
)

How well two histograms agree: the result of :func:goodness_of_fit.

Attributes:

Name Type Description
test GoodnessOfFitTest

The :data:GoodnessOfFitTest run.

method str

"UU", "UW" or "WW" for "chi2" (ROOT's names for comparing unweighted counts and weighted histograms), "absolute" for "chi2-absolute" and "ks" for "ks".

statistic float

The chi-square, or for "ks" the largest distance between the two cumulative shapes.

ndf int | None

The degrees of freedom; None for "ks".

p_value float

The probability of a statistic at least as large for compatible histograms; nan without degrees of freedom.

label, reference

The labels of the two histograms, in the order given.

bins ndarray

Which bins entered the test (bins empty on both sides do not).

notes tuple[str, ...]

Conditions that make the p-value unreliable, as ROOT reports them: bins with less than one event or fewer than ten effective entries.

systematics tuple[str, ...]

The systematic sources in the covariance of "chi2-absolute".

chi2_ndf property

chi2_ndf: float | None

The chi-square per degree of freedom; None for "ks", nan without any.

rootfig.Uncertainty dataclass

Uncertainty(
    edges: FloatArray,
    nominal: FloatArray,
    stat_down: FloatArray,
    stat_up: 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_down, stat_up

Statistical uncertainty below and above the contents (:meth:Histogram.errors() <rootfig.histograms.Histogram.errors>).

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.

The usual convention for an uncertainty band: with a Poisson interval, which is no Gaussian standard deviation, the sum has no exact coverage.

total_down property

total_down: FloatArray

Statistical and systematic uncertainty below the nominal (see :attr:total_up).

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, ...],
    interval: EfficiencyInterval = "auto",
    cl: float = ONE_SIGMA,
)

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

interval is the confidence interval of the efficiency errors (see :data:~rootfig.histograms.binomial.EfficiencyInterval). "auto" is decided for each efficiency apart, as TEfficiency decides for the two histograms of one: Clopper-Pearson when both steps' sums of weights equal their sums of squared weights (unweighted events; weights of 0 and 1 are unweighted), else the normal approximation. A cut that removes every weighted event therefore makes the later relative efficiencies Clopper-Pearson, while those measured against the first step stay normal. cl is the confidence level of the intervals. Bayesian intervals are refused: they report the posterior's mean or mode as the efficiency, where a cut flow reports the ratio of its yields.

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 the summed event weights, the yields without the sample's scale and luminosity factor, which cancel: nan where the previous sum is zero. With signed (NLO) weights a sum 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).

efficiency_errors property

efficiency_errors: tuple[ndarray, ndarray]

(down, up) statistical errors of :attr:efficiencies, 0 for the first step.

A step keeps a subset of the previous step's events, so its efficiency is that of a pass fraction, never a ratio of two independent yields: the :attr:interval at confidence level :attr:cl of the summed event weights, as TEfficiency computes it from the histograms' contents (for unweighted events the number of events of weight 1, so events of weight 0 are no trials). nan where the efficiency is undefined or lies outside [0, 1], and for a binomial interval after a step holding a negative weight (:attr:CutflowStep.negative_weights).

absolute_efficiency_errors property

absolute_efficiency_errors: tuple[ndarray, ndarray]

(down, up) errors of :attr:absolute_efficiencies, as :attr:efficiency_errors.

Every step keeps a subset of the first step's events, the denominator.

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 confidence interval.

Attributes:

Name Type Description
values FloatArray

The efficiency passed / total, or the posterior's mean or mode for a Bayesian interval (as ROOT's TEfficiency); nan where the total weight is zero (an empty bin, or weights that cancel), except for empty bins that are shown (see :func:efficiency).

lower, upper

Bounds of the confidence interval (see :func:efficiency for the method and confidence level). It is nan where none is defined (see :func:efficiency) 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.

Never negative: a posterior's mode can lie outside its central interval, which then reaches only one way.

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, ...).

annotate

annotate(note: str) -> Iterator[None]

Attach note to any :class:RootfigError raised inside the block.

The note is shown below the message (PEP 678). rootfig uses it to name the systematic variation an error was raised for.

Lower layers

These are the layers rf.plot is built from, usable on their own; names a module does not list here are internal. The descriptions in rootfig.model are documented above.

rootfig.io

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

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 leaves only, a nested field by its dotted path (Muon.pt): the branches a TTree lists, the fields of an RNTuple's schema. The result is cached on the instance; the file is opened only once.

branch_forms

branch_forms() -> dict[str, Any]

Return the Awkward form of every branch of :meth:branches, as :meth:arrays reads it.

The forms come from the tree's metadata alone (the interpretation of each TTree branch, the field schema of an RNTuple), so no entry is read; they tell the type and structure of a branch, which :mod:rootfig.io.schema classifies. A TTree branch uproot has no interpretation for, or whose interpretation has no Awkward form, is left out: :meth:arrays could not read it either. Cached on the instance like :meth:branches.

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. The result is cached on the instance, like :meth:branches, so scaling many histograms to a luminosity opens the files once.

objects

objects() -> dict[str, str]

Return {name: class} for every object in the first file (cycle numbers stripped).

Objects inside directories are listed as "dir/name". The result is cached on the instance.

trees

trees() -> list[str]

Return the names of the TTree/RNTuple objects in the first file.

histograms

histograms(ndim: int | None = None) -> list[str]

Return the names of the histograms (TH1*, TH2*) in the first file, sorted.

ndim keeps the 1D or the 2D ones only; a histogram inside a directory is listed as "dir/name".

read_histogram

read_histogram(
    name: str, *, variances_from_contents: bool = False
) -> Hist

Read the histogram stored as name in every file and return their sum.

The result has Weight storage; see :func:rootfig.io.objects.read_histograms for how uncertainties are treated and what variances_from_contents accepts. name may address an object inside a directory ("dir/name").

read_histograms

read_histograms(
    names: Sequence[str],
    *,
    variances_from_contents: bool = False,
) -> dict[str, Hist]

Read the histograms stored as names in every file and return their sums, by name.

Each file is opened once for all of them; otherwise as :meth:read_histogram.

arrays

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

Read branches from all files and concatenate them.

iterate

iterate(
    branches: Sequence[str], chunk_bytes: int | None = None
) -> Iterator[dict[str, ak.Array]]

Read branches a chunk of entries at a time, in order.

A chunk holds about chunk_bytes of arrays, :data:CHUNK_BYTES unless given; an RNTuple cluster larger than that, which is read whole, makes a chunk alone.

The chunks, concatenated, are what :meth:arrays returns, entry range included, while only one of them is held here at a time. Small pieces (under an eighth of a chunk: the end of a file, small files, small RNTuple clusters) are joined with what follows as long as together they fit in a chunk; a larger piece makes a chunk of its own, since joining copies it. At least one chunk is yielded, an empty one when no entry is in range, so the types of the branches are always known.

describe

describe() -> str

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

NpzSource dataclass

NpzSource(
    files: FilesLike,
    *,
    entry_start: int | None = None,
    entry_stop: int | None = None,
)

Branch arrays read from one or more .npz files, one array per branch.

Every array with at least one dimension is a branch with one entry per event: a flat array is per-event, an (events, n) array per-object, like any fixed-size branch. Arrays of no dimension (a scalar saved beside the events) are not branches. Object arrays (ragged lists saved by NumPy) are refused, since NumPy can only load them by unpickling, which runs code from the file.

Parameters:

Name Type Description Default
files FilesLike

A local path, glob pattern, or a sequence of them; see :data:FilesLike. The files are joined in the order given, the matches of a glob sorted, and must hold the same branches.

required
entry_start int | None

Keep only this range of entries, counted across all files (Python slice semantics). Only the files the range overlaps are read.

None
entry_stop int | None

Keep only this range of entries, counted across all files (Python slice semantics). Only the files the range overlaps are read.

None

default_label property

default_label: str

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

branches

branches() -> list[str]

Return the names of the arrays in the first file, scalars left out.

branch_forms

branch_forms() -> dict[str, Any]

Return the Awkward form of every branch, read from the array headers, not the values.

arrays

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

Read branches within the entry range, joined in file order.

Each file's arrays are cut to its part of the range before they are joined, so a short range of many large files holds only that part.

num_entries

num_entries() -> int

Return the number of events within the entry range, from the array headers.

describe

describe() -> str

Return e.g. 'events.npz' or '3 files (run1.npz, ...)'.

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.

branch_forms

branch_forms() -> dict[str, Any]

Return the Awkward form of every column: its type, read off the array, not its values.

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.

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.

ReadCache

ReadCache()

Branch arrays and stored histograms read once, served to every histogram built from them.

Keyed by a :class:FileSource's value (files, tree, entry range), so two samples over the same files, or the fresh source a systematic variation builds over them, share one read. :meth:arrays reads only the names it does not hold yet and keeps them, so the cache can be warmed with the union of what a batch will need (:func:rootfig.histograms.prefetch) or simply fill as histograms are built. :meth:source hands out one instance per value, so what a source learns about its files is learnt once too. In-memory and third-party sources are not cached; callers read those directly. Drop the cache to release what it holds.

source

source(source: FileSource) -> FileSource

Return the instance held for source's value, adopting source if it is the first.

A :class:FileSource keeps what it learns about its files (branches, objects, tree, entry count, scalars) on the instance. Sources rebuilt from the same paths for every histogram, such as raw paths given as data or the files of a Systematic.samples variation, read through the first instance and so learn it once. Reading through the cache applies this (:func:~rootfig.histograms.sources.shared_source), so callers only need it themselves for what they read from a source directly.

arrays

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

Return branches of source, reading the ones not held yet.

A fresh mapping holding exactly the requested names in their order, as :meth:FileSource.arrays returns them.

histogram

histogram(
    source: FileSource,
    name: str,
    *,
    variances_from_contents: bool = False,
) -> Hist

Return the stored histogram name of source, summed over its files, read once.

histograms

histograms(
    source: FileSource,
    names: Sequence[str],
    *,
    variances_from_contents: bool = False,
) -> None

Read the stored histograms names of source not held yet, one open per file.

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, or an :class:~rootfig.io.NpzSource when every file they resolve to ends in .npz (tree does not apply to those, as to in-memory arrays); 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.

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.
  • Functions of the caller's own, given as functions={name: callable} to the rootfig call (or to :func:parse).
  • Constants pi, e, inf, nan, True, False.

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

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

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.

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.

Expression dataclass

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

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, built-in or given with functions=. The functions themselves are bound when it is parsed, and two expressions are equal when their text and these functions are.

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.

parse

parse(
    expression: ExpressionLike,
    *,
    functions: Mapping[str, Callable[..., Any]]
    | None = None,
) -> Expression

Parse and validate an expression string.

Calls may name the built-in functions (:data:FUNCTIONS) and those in functions, {name: callable}. Without functions, they are the ones given to the rootfig call this runs in (none outside one). The expression keeps the functions it was parsed with, so an :class:Expression is returned as it is, and refused together with functions.

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, names beginning with __rootfig_ outside backticks, ...). 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.

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,
    unit_weights: 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).

unit_weights bool

True if the weights are all 0 or 1, judged on every event before the selection (so an empty selection still says what its weights were): every entry then counts once or not at all, as without weights.

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).

weighted property

weighted: bool

True unless every entry counts once or not at all (no weights, or 0 and 1 only).

A histogram filled from unweighted columns holds counts, as ROOT treats a TH1 whose sum of weights equals its sum of squared weights.

effective_weights

effective_weights() -> np.ndarray

Weights as an array, 1.0 everywhere when unweighted.

concatenate classmethod

concatenate(parts: Sequence[Columns]) -> Columns

Join the columns of consecutive chunks of events into those of all of them, in order.

What :func:prepare returns for the concatenated arrays: the values in the same order, the event and entry counts added up.

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,
    report: bool = True,
) -> 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
report bool

False drops and counts non-finite values without the warning or error nonfinite asks for, leaving it to the caller, e.g. once for several chunks of events (:func:report_nonfinite).

True

Returns:

Type Description
Columns

Flat aligned values and weights.

depth_of

depth_of(array: Array) -> int

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

rootfig.histograms

Histogram construction, normalisation, comparisons and statistics.

build_histograms

build_histograms(
    items: Sequence[Sample | Group],
    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,
    variances_from_contents: bool = False,
    cache: ReadCache | None = None,
) -> list[Histogram]

Fill one 1D histogram per sample or group, 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. A :class:~rootfig.model.Group is filled through its samples, which share the binning like any other, and gets the sum of their histograms (:func:~rootfig.histograms.groups.group_histogram).

A bare variable name that addresses a histogram stored in the samples' files (see :func:~rootfig.histograms.stored_mode) is read instead of filled; variances_from_contents then accepts stored histograms without a sum of squared weights.

A cache (:class:~rootfig.io.ReadCache) serves the branch arrays and stored histograms it holds and reads the rest, so several histograms built from the same files read them once; the result does not depend on it.

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.

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; it counts objects (per_object) if any input does, and is observed data (is_data) if every input is. Counts of one factor add up to counts of it, so the sum holds known counts (:meth:~rootfig.histograms.Histogram.counts) when every input does with the same factor per count in every bin, however the inputs reached their binning, and keeps the Poisson interval (:attr:~rootfig.histograms.Histogram.poisson) when every input also has it at one confidence level, as TH1::Add keeps kPoisson for unweighted histograms. Otherwise, if an input has errors of its own (stat_errors), each input's (down, up) errors add in quadrature side by side, an approximation for asymmetric errors; else the sum has sqrt(sum w^2), as ROOT falls back to it.

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.

poisson_interval

poisson_interval(
    counts: ArrayLike, cl: float = ONE_SIGMA
) -> tuple[FloatArray, FloatArray]

Garwood's central interval of a Poisson mean for whole counts: (lower, upper).

The bounds cover the confidence level cl (68.27 % by default, ROOT's TH1::kPoisson; 0.95 is TH1::kPoisson2), half the remainder on each side: P(N >= n | lower) and P(N <= n | upper) are both (1 - cl) / 2. The lower bound of n = 0 is 0, its upper bound -log((1 - cl) / 2) (1.84 at one standard deviation). Both are gamma quantiles (SciPy's inverse incomplete gamma functions), as in ROOT.

Raises:

Type Description
ValueError

If a count is not a non-negative whole number or cl is not a confidence level between 0 and 1.

shape_covariance

shape_covariance(
    histogram: Histogram | Hist, spec: NormalizeSpec = True
) -> FloatArray

Return the covariance matrix of the visible bins of histogram normalised by spec.

The histogram's own total fluctuates with its bins, so they are anti-correlated (see :data:NormalizeUncertainty): the first-order covariance J V J^T of normalize(histogram, spec), with V the diagonal of the sums of squared weights and the bins flattened in C order. Its diagonal holds the variances that normalize(..., uncertainty="shape") gives a histogram of summed weights; for counts with a Poisson interval that gives the exact marginal interval of each bin instead, which no covariance matrix holds.

Raises:

Type Description
ValueError

Unless spec normalises to the histogram's own total, if that total is zero or not finite (no shape to normalise to), or for a histogram with stat_errors, whose asymmetric errors no covariance matrix describes.

summarize

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

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

read_stored

read_stored(
    samples: Sequence[Sample],
    variables: Sequence[Variable],
    *,
    selection: CutLike | None = None,
    weight: str | None = None,
    lumi: float | str | None = None,
    nonfinite: NonFinitePolicy = "drop",
    systematics: Mapping[str, SystematicLike] | None = None,
    variances_from_contents: bool = False,
    include_systematics: bool = True,
    cache: ReadCache | None = None,
) -> list[Histogram]

Read the histogram named by variables from every sample's files.

One :class:~rootfig.histograms.Histogram per sample, in Weight storage, scaled by the sample's scale and luminosity factor. A TH1 saved with kPoisson or kPoisson2 brings its Poisson interval (see :func:with_saved_errors) unless a factor other than 1 made it weighted. A variable's bins and range crop and merge the stored bins. An integer count merges the whole axis; explicit edges must coincide with stored ones, and a range without bins keeps the stored bins between its ends, which must be stored edges too. Cropped content joins the flow bins (see :func:~rootfig.model.binning.merge_target). label and unit replace the stored axis title. Systematics of kind "norm" scale the histogram and Systematic.samples reads the same name from other files, which are checked like the nominal ones; the other kinds need event data. include_systematics=False reads the nominal histograms only and leaves the samples' systematics unexamined, for callers that draw no variations (2D plots); the samples themselves are kept on the result as given. A cache (:class:~rootfig.io.ReadCache) serves the stored histograms it holds and reads the rest.

Raises:

Type Description
SelectionError

For a selection, weight or nonfinite="error" request: those act on event data, which a stored histogram does not contain.

BinningError

If requested edges do not coincide with stored edges, a count does not divide the stored bin count, or a cropped side lacks a flow bin.

SystematicError

For weight or branch-replacement systematics, or a variation whose sample addresses a tree or in-memory data instead of stored histograms.

SourceError

If the stored histogram has another dimensionality than variables, its variances are unusable (see variances_from_contents), or a file (of a sample or of a variation) lacks it.

compatible_binning

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

Return True if both histograms are one-dimensional and bin the same way.

Numeric axes must have the same edges up to round-off (a millionth of the smallest bin width, so bins shifted by a whole width at large coordinates are rejected); category axes the same categories in the same order. Flow bins and axis names or labels do not matter here: this decides whether two histograms can be stacked, summed or divided bin by bin.

normalize

normalize(
    histogram: Histogram,
    spec: NormalizeSpec,
    *,
    uncertainty: NormalizeUncertainty = "scale",
) -> Histogram

Return a normalised copy of a :class:Histogram, recording the mode.

uncertainty says what normalising to the histogram's own total does to its statistical uncertainty (see :data:NormalizeUncertainty): "scale" keeps every bin's relative uncertainty, as TH1::Scale; "shape" lets the total fluctuate with the bins, and raises ValueError for a histogram with stat_errors.

normalization is set only when the normalisation was actually applied; a histogram that could not be normalised (see :func:normalize_hist) keeps None so labels do not claim a scaling that did not happen.

Systematic variations are normalised the same way as the nominal histogram, each by its own total, so a normalised plot shows how a variation changes the shape; a pure normalisation uncertainty drops out of the rescaling modes (unity, density and numeric targets). width retains it. If the nominal cannot be normalised, all variations stay unchanged too. A variation that cannot be normalised when the nominal can raises :class:~rootfig.errors.SystematicError, avoiding a mixture of raw and normalised contents in the uncertainty.

rootfig.plotting

Rendering histograms with matplotlib and mplhep.

Layout dataclass

Layout(
    fig: Figure,
    main: Axes,
    panel: Axes | None = None,
    main_right: Axes | None = None,
    panel_right: Axes | None = None,
)

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

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

main_axes property

main_axes: tuple[Axes, ...]

The main-panel axes, left to right.

panel_axes property

panel_axes: tuple[Axes, ...]

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

axes property

axes: tuple[Axes, ...]

Every axes of the layout: main (left, right), then the lower panel (left, right).

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.

draw_histograms

draw_histograms(
    histograms: Sequence[Histogram],
    ax: Axes,
    *,
    style: Style,
    stack: StackSpec = False,
    histtype: HistType | None = None,
    errorbars: bool | None = None,
    flow: FlowSpec = "hint",
    stack_uncertainty: bool = True,
    alpha: float | None = None,
    view: Sequence[tuple[float, 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 input order.

required
ax Axes

Target axes.

required
style Style

Style providing the colour cycle.

required
stack StackSpec

True stacks every non-data histogram; a label or sequence of labels stacks those histograms. False or an empty sequence overlays all. Stacks keep input order, first at the bottom, followed by their band, overlays in input order, then data. Colours are assigned over all non-data histograms before splitting; explicit colours do not consume cycle entries. Stacked histograms are always filled.

False
histtype HistType | None

Default drawing type for overlaid histograms without their own histtype; None means "step". Ignored for stacked histograms, as is a histogram's own histtype.

None
errorbars bool | None

Draw statistical error bars on overlaid histograms. None draws them only for "errorbar" histtypes. Ignored for stacked histograms.

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.45 for overlays).

None
view Sequence[tuple[float, float]] | None

The x windows whose bins set ymin/ymax/ymin_positive.

None

draw_panel

draw_panel(
    comparisons: Sequence[Comparison],
    ax: Axes,
    *,
    colors: Sequence[str] | None = None,
    observed: Sequence[bool] | None = None,
    ylim: tuple[float, float] | None = None,
    ylabel: str | None = None,
    band: bool | None = None,
    view: Sequence[tuple[float, float]] | None = None,
) -> None

Draw comparisons of one kind into the lower panel ax.

Ratios, relative differences, differences and asymmetries are points with error bars around their baseline (1 or 0, a dashed line), over the reference's uncertainty band where there is one; pulls are filled bars from 0, without error bars; a significance is points with error bars and no baseline. A point beyond the y range is marked by a triangle at the edge it left through, in its colour; the markers follow later changes of the range.

Parameters:

Name Type Description Default
comparisons Sequence[Comparison]

What :func:~rootfig.histograms.compare returned, one per numerator, all of one kind.

required
ax Axes

The panel's axes.

required
colors Sequence[str] | None

One colour per comparison; defaults to the foreground colour for observed data and the style's cycle for the rest.

None
observed Sequence[bool] | None

Which comparisons have observed data as numerator; their markers are drawn larger. Defaults to none.

None
ylim tuple[float, float] | None

Vertical range; defaults to :func:panel_ylim.

None
ylabel str | None

Label; defaults to :func:comparison_label for the first comparison's reference.

None
band bool | None

Draw the reference's uncertainty band of the first comparison that has one. Defaults to drawing it when a comparison's error bars leave the reference out (uncertainty="numerator"); with "propagate" they already include it.

None
view Sequence[tuple[float, float]] | None

The x windows whose bins set the automatic y range.

None

Raises:

Type Description
ValueError

Without comparisons, or with comparisons of different kinds, references or binnings: one panel shows one kind against one reference, whose label and uncertainty band it draws, so references whose bands differ are refused too.

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_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

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.

split_stack

split_stack(
    histograms: Sequence[Histogram], stack: StackSpec
) -> tuple[
    list[Histogram], list[Histogram], list[Histogram]
]

Return stacked, overlaid and observed histograms, each in input order.

Labels select every non-data histogram carrying them. Unknown labels and labels belonging only to observed data raise ValueError; selectors other than a bool, a string or a sequence of strings raise TypeError.

make_figure

make_figure(
    style: Style,
    *,
    panel: bool,
    ax: AxesLike = None,
    figsize: tuple[float, float] | None = None,
    break_widths: tuple[float, float] | None = None,
    cell: SubplotSpec | 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
panel bool

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

required
ax AxesLike

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

None
figsize tuple[float, float] | None

Figure size in inches; defaults to the style's, enlarged for a lower panel. Not supported together with cell, whose figure exists already.

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
cell SubplotSpec | None

A cell of a grid on an existing figure to build the panels in, so that several plots share one page (:class:~rootfig.PlotBook). The panels are laid out inside it exactly as they are on a figure of the cell's size.

None

figure_size

figure_size(
    style: Style,
    *,
    panel: bool,
    figsize: tuple[float, float] | None = None,
) -> tuple[float, float]

Return the size in inches of a figure drawn on its own under style.

figsize wins, then the style's own size; otherwise the active figure.figsize, made taller for a lower panel so the main panel keeps its shape. Read inside the style context, since the rcParams are the style's.

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: Sequence[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. colors supplies one colour per input histogram; a length mismatch raises ValueError.

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.

style_context

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

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