rootfig 0.2.1__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rootfig-0.3.0/PKG-INFO +214 -0
- rootfig-0.3.0/README.md +181 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/composable.md +7 -5
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/hooks/gallery.py +2 -1
- rootfig-0.3.0/docs/images/gallery/arrays.png +0 -0
- rootfig-0.3.0/docs/images/gallery/cms_density.png +0 -0
- rootfig-0.3.0/docs/images/gallery/correlation.png +0 -0
- rootfig-0.3.0/docs/images/gallery/expressions.png +0 -0
- rootfig-0.3.0/docs/images/gallery/fill_stats.png +0 -0
- rootfig-0.3.0/docs/images/gallery/hist2d.png +0 -0
- rootfig-0.3.0/docs/images/gallery/log_axes.png +0 -0
- rootfig-0.3.0/docs/images/gallery/luminosity.png +0 -0
- rootfig-0.3.0/docs/images/gallery/many_plots.png +0 -0
- rootfig-0.3.0/docs/images/gallery/object_vs_event.png +0 -0
- rootfig-0.3.0/docs/images/gallery/overlay_ratio.png +0 -0
- rootfig-0.3.0/docs/images/gallery/quick.png +0 -0
- rootfig-0.3.0/docs/images/gallery/ratio_reference.png +0 -0
- rootfig-0.3.0/docs/images/gallery/robust_range.png +0 -0
- rootfig-0.3.0/docs/images/gallery/stack_data.png +0 -0
- rootfig-0.3.0/docs/images/gallery/style_colors.png +0 -0
- rootfig-0.3.0/docs/images/gallery/variable_bins.png +0 -0
- rootfig-0.3.0/docs/images/gallery/xbreak_ratio.png +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/plotting.md +95 -6
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/quickstart.md +5 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/examples/gallery/__init__.py +24 -14
- {rootfig-0.2.1 → rootfig-0.3.0}/examples/gallery/registry.py +5 -2
- {rootfig-0.2.1 → rootfig-0.3.0}/pyproject.toml +1 -1
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/__init__.py +1 -1
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/api.py +31 -7
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/pipeline.py +11 -3
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/model/__init__.py +10 -1
- rootfig-0.3.0/src/rootfig/model/binning.py +465 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/model/variables.py +8 -5
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/figure.py +9 -3
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_api.py +93 -1
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_model.py +207 -5
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_plotting.py +7 -7
- rootfig-0.2.1/PKG-INFO +0 -201
- rootfig-0.2.1/README.md +0 -168
- rootfig-0.2.1/docs/images/gallery/arrays.png +0 -0
- rootfig-0.2.1/docs/images/gallery/cms_density.png +0 -0
- rootfig-0.2.1/docs/images/gallery/correlation.png +0 -0
- rootfig-0.2.1/docs/images/gallery/expressions.png +0 -0
- rootfig-0.2.1/docs/images/gallery/fill_stats.png +0 -0
- rootfig-0.2.1/docs/images/gallery/hist2d.png +0 -0
- rootfig-0.2.1/docs/images/gallery/log_axes.png +0 -0
- rootfig-0.2.1/docs/images/gallery/luminosity.png +0 -0
- rootfig-0.2.1/docs/images/gallery/many_plots.png +0 -0
- rootfig-0.2.1/docs/images/gallery/object_vs_event.png +0 -0
- rootfig-0.2.1/docs/images/gallery/overlay_ratio.png +0 -0
- rootfig-0.2.1/docs/images/gallery/quick.png +0 -0
- rootfig-0.2.1/docs/images/gallery/ratio_reference.png +0 -0
- rootfig-0.2.1/docs/images/gallery/robust_range.png +0 -0
- rootfig-0.2.1/docs/images/gallery/stack_data.png +0 -0
- rootfig-0.2.1/docs/images/gallery/style_colors.png +0 -0
- rootfig-0.2.1/docs/images/gallery/variable_bins.png +0 -0
- rootfig-0.2.1/docs/images/gallery/xbreak_ratio.png +0 -0
- rootfig-0.2.1/src/rootfig/model/binning.py +0 -242
- {rootfig-0.2.1 → rootfig-0.3.0}/.gitignore +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/CONTRIBUTING.md +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/LICENSE +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/api.md +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/ecosystem.md +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/expressions.md +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/gallery.md +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/images/gallery/efficiency.png +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/images/gallery/profile.png +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/docs/index.md +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/examples/gallery/__main__.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/examples/gallery/data.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/mkdocs.yml +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/_typing.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/errors.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/expressions/__init__.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/expressions/functions.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/expressions/parser.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/__init__.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/build.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/cutflow.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/efficiency.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/normalize.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/ratio.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/histograms/stats.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/io/__init__.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/io/sources.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/model/cuts.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/model/samples.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/model/style.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/model/units.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/__init__.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/annotations.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/correlation.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/hist1d.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/hist2d.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/points.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/ratio.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/result.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/plotting/style.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/py.typed +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/selection/__init__.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/src/rootfig/selection/columns.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/conftest.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/data/split_collection.root +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_expressions.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_gallery.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_histograms.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_io.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_selection.py +0 -0
- {rootfig-0.2.1 → rootfig-0.3.0}/tests/test_tutorials.py +0 -0
rootfig-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rootfig
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Publication-quality figures straight from ROOT trees, without ROOT: uproot + Awkward + hist + mplhep with a TTree::Draw-like API.
|
|
5
|
+
Project-URL: Homepage, https://github.com/jbeirer/rootfig
|
|
6
|
+
Project-URL: Documentation, https://jbeirer.github.io/rootfig/
|
|
7
|
+
Project-URL: Repository, https://github.com/jbeirer/rootfig
|
|
8
|
+
Project-URL: Issues, https://github.com/jbeirer/rootfig/issues
|
|
9
|
+
Author-email: Joshua Falco Beirer <jbeirer@cern.ch>
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: HEP,RNTuple,ROOT,TTree,awkward,histogram,matplotlib,mplhep,plotting,uproot
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Physics
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.12
|
|
26
|
+
Requires-Dist: awkward>=2.8
|
|
27
|
+
Requires-Dist: hist>=2.7.2
|
|
28
|
+
Requires-Dist: matplotlib>=3.11
|
|
29
|
+
Requires-Dist: mplhep>=1.3
|
|
30
|
+
Requires-Dist: numpy>=1.26
|
|
31
|
+
Requires-Dist: uproot>=5.7.4
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
<p align="center">
|
|
35
|
+
<picture>
|
|
36
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jbeirer/rootfig/main/.github/assets/rootfig-logo-dark.svg">
|
|
37
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/jbeirer/rootfig/main/.github/assets/rootfig-logo-light.svg">
|
|
38
|
+
<img src="https://raw.githubusercontent.com/jbeirer/rootfig/main/.github/assets/rootfig-logo-light.svg" alt="rootfig" width="560">
|
|
39
|
+
</picture>
|
|
40
|
+
</p>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<strong>Publication-quality figures straight from ROOT trees, without ROOT.</strong>
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<a href="https://jbeirer.github.io/rootfig/">Documentation</a> ·
|
|
48
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/">Gallery</a> ·
|
|
49
|
+
<a href="https://jbeirer.github.io/rootfig/quickstart/">Quick start</a>
|
|
50
|
+
</p>
|
|
51
|
+
|
|
52
|
+
<p align="center">
|
|
53
|
+
<a href="https://jbeirer.github.io/rootfig/"><img src="https://img.shields.io/badge/docs-online-blue" alt="Documentation"></a>
|
|
54
|
+
<a href="https://doi.org/10.5281/zenodo.22726311"><img src="https://zenodo.org/badge/1366702602.svg" alt="DOI"></a>
|
|
55
|
+
<a href="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml"><img src="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
56
|
+
<a href="https://codecov.io/gh/jbeirer/rootfig"><img src="https://codecov.io/gh/jbeirer/rootfig/branch/main/graph/badge.svg" alt="codecov"></a>
|
|
57
|
+
<a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/v/rootfig.svg" alt="PyPI"></a>
|
|
58
|
+
<a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/pyversions/rootfig.svg" alt="Python"></a>
|
|
59
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
60
|
+
</p>
|
|
61
|
+
|
|
62
|
+
Go from a ROOT file to a styled figure in one call. Choose a variable, add a
|
|
63
|
+
selection, and plot:
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import rootfig as rf
|
|
67
|
+
|
|
68
|
+
rf.plot("events.root", "Muon_pt", tree="events", selection="Muon_pt > 20", bins=50)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Start with a single distribution; add samples, weights, stacks and ratio
|
|
72
|
+
panels as your analysis grows. Every plot gives you a matplotlib figure to
|
|
73
|
+
customise and save.
|
|
74
|
+
|
|
75
|
+
<p align="center">
|
|
76
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#logarithmic-axes-with-log-spaced-bins"><img src="docs/images/gallery/log_axes.png" alt="Logarithmic axes with log-spaced bins" width="46%"></a>
|
|
77
|
+
|
|
78
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#a-broken-x-axis-peak-and-far-tail-without-the-empty-middle"><img src="docs/images/gallery/xbreak_ratio.png" alt="Broken x axis with a ratio panel" width="46%"></a>
|
|
79
|
+
</p>
|
|
80
|
+
<p align="center">
|
|
81
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#a-correlation-matrix"><img src="docs/images/gallery/correlation.png" alt="A correlation matrix" width="46%"></a>
|
|
82
|
+
|
|
83
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#a-two-dimensional-histogram"><img src="docs/images/gallery/hist2d.png" alt="Two-dimensional histogram" width="46%"></a>
|
|
84
|
+
</p>
|
|
85
|
+
|
|
86
|
+
**[Explore the gallery →](https://jbeirer.github.io/rootfig/gallery/)**
|
|
87
|
+
See each figure alongside the code that makes it, from simple overlays to
|
|
88
|
+
stacked data/MC comparisons, broken axes and 2D histograms.
|
|
89
|
+
|
|
90
|
+
## Installation
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install rootfig
|
|
94
|
+
# or
|
|
95
|
+
uv add rootfig
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Python 3.12 or newer. No ROOT installation is needed; `TTree` and `RNTuple`
|
|
99
|
+
files are both supported.
|
|
100
|
+
|
|
101
|
+
## Compare samples in one call
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
import rootfig as rf
|
|
105
|
+
|
|
106
|
+
# Overlay two samples, each normalised to unity, with a Signal / Background panel.
|
|
107
|
+
rf.plot(
|
|
108
|
+
{"Signal": "signal.root", "Background": "background.root"},
|
|
109
|
+
"Muon_pt",
|
|
110
|
+
tree="events",
|
|
111
|
+
selection="abs(Muon_eta) < 2.5",
|
|
112
|
+
weight="event_weight",
|
|
113
|
+
bins=(50, 0, 200),
|
|
114
|
+
normalize=True,
|
|
115
|
+
ratio="Background",
|
|
116
|
+
)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Build up to a full analysis
|
|
120
|
+
|
|
121
|
+
Define samples, variables, cuts and styles once, then reuse them across plots:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
import rootfig as rf
|
|
125
|
+
|
|
126
|
+
signal = rf.Sample("sig_*.root", tree="events", label="Signal", weight="mc_weight")
|
|
127
|
+
background = rf.Sample("bkg.root", tree="events", label="Background", weight="mc_weight")
|
|
128
|
+
data = rf.Sample("data.root", tree="events", label="Data", is_data=True)
|
|
129
|
+
|
|
130
|
+
pt = rf.Variable("Muon_pt", bins=(50, 0, 200), label=r"$p_T^{\mu}$", unit="GeV")
|
|
131
|
+
baseline = rf.Cut("nMuon >= 1") & "abs(Muon_eta) < 2.5"
|
|
132
|
+
style = rf.Style(experiment="ATLAS", status="Internal", lumi=140, com=13.6)
|
|
133
|
+
|
|
134
|
+
p = rf.plot(
|
|
135
|
+
[background, signal],
|
|
136
|
+
pt,
|
|
137
|
+
observed=data,
|
|
138
|
+
selection=baseline,
|
|
139
|
+
stack=True,
|
|
140
|
+
ratio=True,
|
|
141
|
+
logy=True,
|
|
142
|
+
style=style,
|
|
143
|
+
)
|
|
144
|
+
p.ax.set_ylim(top=1e5) # it is a normal matplotlib Axes
|
|
145
|
+
p.save("muon_pt.pdf")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Everything you get back is a standard object: `p.fig` and `p.ax` are
|
|
149
|
+
matplotlib `Figure`/`Axes`, `p.hists` are `hist.Hist` objects, and
|
|
150
|
+
`rf.load(...)` returns Awkward arrays.
|
|
151
|
+
|
|
152
|
+
## What you can do
|
|
153
|
+
|
|
154
|
+
- **Select events and objects with readable expressions.** Write cuts such as
|
|
155
|
+
`count(Jet_pt) >= 2` or `Muon_pt > 20`; event and object selections have
|
|
156
|
+
explicit rules, and event weights carry through to each selected object.
|
|
157
|
+
- **Compare samples with a few keywords.** Overlays, stacks, data points and
|
|
158
|
+
ratio panels share binning and propagate histogram uncertainties; bin edges
|
|
159
|
+
and `(n, low, high)` are used as given, while a range inferred from the data
|
|
160
|
+
ignores far outliers, so `-999` sentinels do not set the axis. Normalise to
|
|
161
|
+
unity, density, bin width or luminosity.
|
|
162
|
+
- **Style figures for your analysis.** Add experiment labels, units, log axes
|
|
163
|
+
and broken axes, then refine the result with matplotlib.
|
|
164
|
+
- **Go beyond 1D plots.** Draw 2D histograms, correlations, efficiencies,
|
|
165
|
+
profiles, resolutions and significance panels; produce cut flows and
|
|
166
|
+
summary statistics from the same inputs.
|
|
167
|
+
- **Work directly with your files.** Read `TTree` and `RNTuple` data, combine
|
|
168
|
+
files with globs, limit entry ranges for quick checks, and use EDM4hep
|
|
169
|
+
split collections. Only the branches your expressions need are read.
|
|
170
|
+
|
|
171
|
+
## Documentation
|
|
172
|
+
|
|
173
|
+
**[Read the docs](https://jbeirer.github.io/rootfig/)** or
|
|
174
|
+
**[browse the gallery](https://jbeirer.github.io/rootfig/gallery/)** for examples
|
|
175
|
+
with figures and code.
|
|
176
|
+
|
|
177
|
+
- [Quick start](https://jbeirer.github.io/rootfig/quickstart/): your first plot, selections and weights.
|
|
178
|
+
- [Expressions and selections](https://jbeirer.github.io/rootfig/expressions/): syntax and event/object rules.
|
|
179
|
+
- [Samples, variables, cuts and styles](https://jbeirer.github.io/rootfig/composable/): reusable analysis definitions.
|
|
180
|
+
- [Plotting options](https://jbeirer.github.io/rootfig/plotting/): binning, normalisation, panels and styling.
|
|
181
|
+
- [API reference](https://jbeirer.github.io/rootfig/api/): full signatures and options.
|
|
182
|
+
|
|
183
|
+
## Relation to the ecosystem
|
|
184
|
+
|
|
185
|
+
`rootfig` brings a `TTree::Draw`-like workflow to the Scientific Python HEP
|
|
186
|
+
stack, building on familiar libraries:
|
|
187
|
+
|
|
188
|
+
| Task | Library | What rootfig adds |
|
|
189
|
+
| --- | --- | --- |
|
|
190
|
+
| Reading ROOT files | [uproot](https://github.com/scikit-hep/uproot5) | file globs, tree auto-detection, reading only the required branches |
|
|
191
|
+
| Jagged arrays | [Awkward Array](https://github.com/scikit-hep/awkward) | the per-event/per-object rules for cuts and weights |
|
|
192
|
+
| Histograms | [hist](https://github.com/scikit-hep/hist) / boost-histogram | shared binning, robust automatic ranges, normalisation, ratios |
|
|
193
|
+
| Drawing | [mplhep](https://github.com/scikit-hep/mplhep) + matplotlib | overlays, stacks, ratio panels, labels and legends with good defaults |
|
|
194
|
+
|
|
195
|
+
If you already have `hist.Hist` objects, `rf.plot_histograms` draws them with
|
|
196
|
+
the same options. If you want the arrays, `rf.load` returns them. See
|
|
197
|
+
[the ecosystem guide](https://jbeirer.github.io/rootfig/ecosystem/) for details.
|
|
198
|
+
|
|
199
|
+
## Development
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
git clone https://github.com/jbeirer/rootfig
|
|
203
|
+
cd rootfig
|
|
204
|
+
uv sync --all-groups
|
|
205
|
+
uv run pytest
|
|
206
|
+
uv run ruff check . && uv run ruff format --check .
|
|
207
|
+
uv run mypy
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT. See [LICENSE](LICENSE).
|
rootfig-0.3.0/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/jbeirer/rootfig/main/.github/assets/rootfig-logo-dark.svg">
|
|
4
|
+
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/jbeirer/rootfig/main/.github/assets/rootfig-logo-light.svg">
|
|
5
|
+
<img src="https://raw.githubusercontent.com/jbeirer/rootfig/main/.github/assets/rootfig-logo-light.svg" alt="rootfig" width="560">
|
|
6
|
+
</picture>
|
|
7
|
+
</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<strong>Publication-quality figures straight from ROOT trees, without ROOT.</strong>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<a href="https://jbeirer.github.io/rootfig/">Documentation</a> ·
|
|
15
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/">Gallery</a> ·
|
|
16
|
+
<a href="https://jbeirer.github.io/rootfig/quickstart/">Quick start</a>
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<p align="center">
|
|
20
|
+
<a href="https://jbeirer.github.io/rootfig/"><img src="https://img.shields.io/badge/docs-online-blue" alt="Documentation"></a>
|
|
21
|
+
<a href="https://doi.org/10.5281/zenodo.22726311"><img src="https://zenodo.org/badge/1366702602.svg" alt="DOI"></a>
|
|
22
|
+
<a href="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml"><img src="https://github.com/jbeirer/rootfig/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
23
|
+
<a href="https://codecov.io/gh/jbeirer/rootfig"><img src="https://codecov.io/gh/jbeirer/rootfig/branch/main/graph/badge.svg" alt="codecov"></a>
|
|
24
|
+
<a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/v/rootfig.svg" alt="PyPI"></a>
|
|
25
|
+
<a href="https://pypi.org/project/rootfig/"><img src="https://img.shields.io/pypi/pyversions/rootfig.svg" alt="Python"></a>
|
|
26
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="License"></a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
Go from a ROOT file to a styled figure in one call. Choose a variable, add a
|
|
30
|
+
selection, and plot:
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
import rootfig as rf
|
|
34
|
+
|
|
35
|
+
rf.plot("events.root", "Muon_pt", tree="events", selection="Muon_pt > 20", bins=50)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Start with a single distribution; add samples, weights, stacks and ratio
|
|
39
|
+
panels as your analysis grows. Every plot gives you a matplotlib figure to
|
|
40
|
+
customise and save.
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#logarithmic-axes-with-log-spaced-bins"><img src="docs/images/gallery/log_axes.png" alt="Logarithmic axes with log-spaced bins" width="46%"></a>
|
|
44
|
+
|
|
45
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#a-broken-x-axis-peak-and-far-tail-without-the-empty-middle"><img src="docs/images/gallery/xbreak_ratio.png" alt="Broken x axis with a ratio panel" width="46%"></a>
|
|
46
|
+
</p>
|
|
47
|
+
<p align="center">
|
|
48
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#a-correlation-matrix"><img src="docs/images/gallery/correlation.png" alt="A correlation matrix" width="46%"></a>
|
|
49
|
+
|
|
50
|
+
<a href="https://jbeirer.github.io/rootfig/gallery/#a-two-dimensional-histogram"><img src="docs/images/gallery/hist2d.png" alt="Two-dimensional histogram" width="46%"></a>
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
**[Explore the gallery →](https://jbeirer.github.io/rootfig/gallery/)**
|
|
54
|
+
See each figure alongside the code that makes it, from simple overlays to
|
|
55
|
+
stacked data/MC comparisons, broken axes and 2D histograms.
|
|
56
|
+
|
|
57
|
+
## Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
pip install rootfig
|
|
61
|
+
# or
|
|
62
|
+
uv add rootfig
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Python 3.12 or newer. No ROOT installation is needed; `TTree` and `RNTuple`
|
|
66
|
+
files are both supported.
|
|
67
|
+
|
|
68
|
+
## Compare samples in one call
|
|
69
|
+
|
|
70
|
+
```python
|
|
71
|
+
import rootfig as rf
|
|
72
|
+
|
|
73
|
+
# Overlay two samples, each normalised to unity, with a Signal / Background panel.
|
|
74
|
+
rf.plot(
|
|
75
|
+
{"Signal": "signal.root", "Background": "background.root"},
|
|
76
|
+
"Muon_pt",
|
|
77
|
+
tree="events",
|
|
78
|
+
selection="abs(Muon_eta) < 2.5",
|
|
79
|
+
weight="event_weight",
|
|
80
|
+
bins=(50, 0, 200),
|
|
81
|
+
normalize=True,
|
|
82
|
+
ratio="Background",
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Build up to a full analysis
|
|
87
|
+
|
|
88
|
+
Define samples, variables, cuts and styles once, then reuse them across plots:
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import rootfig as rf
|
|
92
|
+
|
|
93
|
+
signal = rf.Sample("sig_*.root", tree="events", label="Signal", weight="mc_weight")
|
|
94
|
+
background = rf.Sample("bkg.root", tree="events", label="Background", weight="mc_weight")
|
|
95
|
+
data = rf.Sample("data.root", tree="events", label="Data", is_data=True)
|
|
96
|
+
|
|
97
|
+
pt = rf.Variable("Muon_pt", bins=(50, 0, 200), label=r"$p_T^{\mu}$", unit="GeV")
|
|
98
|
+
baseline = rf.Cut("nMuon >= 1") & "abs(Muon_eta) < 2.5"
|
|
99
|
+
style = rf.Style(experiment="ATLAS", status="Internal", lumi=140, com=13.6)
|
|
100
|
+
|
|
101
|
+
p = rf.plot(
|
|
102
|
+
[background, signal],
|
|
103
|
+
pt,
|
|
104
|
+
observed=data,
|
|
105
|
+
selection=baseline,
|
|
106
|
+
stack=True,
|
|
107
|
+
ratio=True,
|
|
108
|
+
logy=True,
|
|
109
|
+
style=style,
|
|
110
|
+
)
|
|
111
|
+
p.ax.set_ylim(top=1e5) # it is a normal matplotlib Axes
|
|
112
|
+
p.save("muon_pt.pdf")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Everything you get back is a standard object: `p.fig` and `p.ax` are
|
|
116
|
+
matplotlib `Figure`/`Axes`, `p.hists` are `hist.Hist` objects, and
|
|
117
|
+
`rf.load(...)` returns Awkward arrays.
|
|
118
|
+
|
|
119
|
+
## What you can do
|
|
120
|
+
|
|
121
|
+
- **Select events and objects with readable expressions.** Write cuts such as
|
|
122
|
+
`count(Jet_pt) >= 2` or `Muon_pt > 20`; event and object selections have
|
|
123
|
+
explicit rules, and event weights carry through to each selected object.
|
|
124
|
+
- **Compare samples with a few keywords.** Overlays, stacks, data points and
|
|
125
|
+
ratio panels share binning and propagate histogram uncertainties; bin edges
|
|
126
|
+
and `(n, low, high)` are used as given, while a range inferred from the data
|
|
127
|
+
ignores far outliers, so `-999` sentinels do not set the axis. Normalise to
|
|
128
|
+
unity, density, bin width or luminosity.
|
|
129
|
+
- **Style figures for your analysis.** Add experiment labels, units, log axes
|
|
130
|
+
and broken axes, then refine the result with matplotlib.
|
|
131
|
+
- **Go beyond 1D plots.** Draw 2D histograms, correlations, efficiencies,
|
|
132
|
+
profiles, resolutions and significance panels; produce cut flows and
|
|
133
|
+
summary statistics from the same inputs.
|
|
134
|
+
- **Work directly with your files.** Read `TTree` and `RNTuple` data, combine
|
|
135
|
+
files with globs, limit entry ranges for quick checks, and use EDM4hep
|
|
136
|
+
split collections. Only the branches your expressions need are read.
|
|
137
|
+
|
|
138
|
+
## Documentation
|
|
139
|
+
|
|
140
|
+
**[Read the docs](https://jbeirer.github.io/rootfig/)** or
|
|
141
|
+
**[browse the gallery](https://jbeirer.github.io/rootfig/gallery/)** for examples
|
|
142
|
+
with figures and code.
|
|
143
|
+
|
|
144
|
+
- [Quick start](https://jbeirer.github.io/rootfig/quickstart/): your first plot, selections and weights.
|
|
145
|
+
- [Expressions and selections](https://jbeirer.github.io/rootfig/expressions/): syntax and event/object rules.
|
|
146
|
+
- [Samples, variables, cuts and styles](https://jbeirer.github.io/rootfig/composable/): reusable analysis definitions.
|
|
147
|
+
- [Plotting options](https://jbeirer.github.io/rootfig/plotting/): binning, normalisation, panels and styling.
|
|
148
|
+
- [API reference](https://jbeirer.github.io/rootfig/api/): full signatures and options.
|
|
149
|
+
|
|
150
|
+
## Relation to the ecosystem
|
|
151
|
+
|
|
152
|
+
`rootfig` brings a `TTree::Draw`-like workflow to the Scientific Python HEP
|
|
153
|
+
stack, building on familiar libraries:
|
|
154
|
+
|
|
155
|
+
| Task | Library | What rootfig adds |
|
|
156
|
+
| --- | --- | --- |
|
|
157
|
+
| Reading ROOT files | [uproot](https://github.com/scikit-hep/uproot5) | file globs, tree auto-detection, reading only the required branches |
|
|
158
|
+
| Jagged arrays | [Awkward Array](https://github.com/scikit-hep/awkward) | the per-event/per-object rules for cuts and weights |
|
|
159
|
+
| Histograms | [hist](https://github.com/scikit-hep/hist) / boost-histogram | shared binning, robust automatic ranges, normalisation, ratios |
|
|
160
|
+
| Drawing | [mplhep](https://github.com/scikit-hep/mplhep) + matplotlib | overlays, stacks, ratio panels, labels and legends with good defaults |
|
|
161
|
+
|
|
162
|
+
If you already have `hist.Hist` objects, `rf.plot_histograms` draws them with
|
|
163
|
+
the same options. If you want the arrays, `rf.load` returns them. See
|
|
164
|
+
[the ecosystem guide](https://jbeirer.github.io/rootfig/ecosystem/) for details.
|
|
165
|
+
|
|
166
|
+
## Development
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
git clone https://github.com/jbeirer/rootfig
|
|
170
|
+
cd rootfig
|
|
171
|
+
uv sync --all-groups
|
|
172
|
+
uv run pytest
|
|
173
|
+
uv run ruff check . && uv run ruff format --check .
|
|
174
|
+
uv run mypy
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
|
|
178
|
+
|
|
179
|
+
## License
|
|
180
|
+
|
|
181
|
+
MIT. See [LICENSE](LICENSE).
|
|
@@ -61,14 +61,16 @@ What to histogram and how to present it.
|
|
|
61
61
|
```python
|
|
62
62
|
pt = rf.Variable("Muon_pt", bins=(50, 0, 200), label=r"$p_T^{\mu}$", unit="GeV")
|
|
63
63
|
met = rf.Variable(
|
|
64
|
-
"MET / 1000", bins=40, range="
|
|
64
|
+
"MET / 1000", bins=40, range="auto", label=r"$E_T^{miss}$", unit="TeV", log=True, name="met"
|
|
65
65
|
)
|
|
66
66
|
```
|
|
67
67
|
|
|
68
|
-
- `bins`: an `int` (range from the data), `(n, low, high)`, a sequence
|
|
69
|
-
edges (e.g. `rf.log_bins(30, 1, 1000)`), or a `hist.axis.Regular`/`Variable`.
|
|
70
|
-
- `range`: `(low, high)`, `"
|
|
71
|
-
`
|
|
68
|
+
- `bins`: an `int` (range inferred from the data), `(n, low, high)`, a sequence
|
|
69
|
+
of edges (e.g. `rf.log_bins(30, 1, 1000)`), or a `hist.axis.Regular`/`Variable`.
|
|
70
|
+
- `range`: `(low, high)`, `"robust"` (the default: ignores far outliers such as
|
|
71
|
+
`-999` sentinels and cuts a thin tail, both of which then land in the
|
|
72
|
+
under/overflow) or `"auto"` (the finite min/max over all samples). See
|
|
73
|
+
[Binning and range](plotting.md#binning-and-range).
|
|
72
74
|
- `label` and `unit` form the axis label `label [unit]`; the unit also appears
|
|
73
75
|
in the automatic y label (`Events / 4 GeV`).
|
|
74
76
|
- `name` is used for file names by `Plot.save(directory)`; it must be a plain
|
|
@@ -60,7 +60,8 @@ def render_section(gallery: ModuleType, example: Any) -> str:
|
|
|
60
60
|
return (
|
|
61
61
|
f"## {example.title}\n\n"
|
|
62
62
|
f"{example.description}\n\n"
|
|
63
|
-
f
|
|
63
|
+
f""
|
|
64
|
+
f'{{ width="{example.image_width}" }}\n\n'
|
|
64
65
|
f"```python\n{code}```\n"
|
|
65
66
|
)
|
|
66
67
|
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -86,12 +86,98 @@ shorter `ratio_label` such as `"Ratio"` to keep it at full size. The
|
|
|
86
86
|
computed values are returned in `Plot.ratios` as
|
|
87
87
|
[`Ratio`][rootfig.Ratio] objects (`values`, `errors`, `band`, `edges`).
|
|
88
88
|
|
|
89
|
+
## Binning and range
|
|
90
|
+
|
|
91
|
+
`bins` takes an `int`, a `(n, low, high)` triple, a sequence of edges or a
|
|
92
|
+
`hist` axis, and is shared by every sample of one plot (and by the numerator
|
|
93
|
+
and denominator of an [efficiency](#efficiencies)).
|
|
94
|
+
|
|
95
|
+
With an integer `bins` the range comes from `range`:
|
|
96
|
+
|
|
97
|
+
| `range` | Meaning |
|
|
98
|
+
| --- | --- |
|
|
99
|
+
| `(low, high)` | explicit |
|
|
100
|
+
| `"robust"` | **default**: the min/max of the data, ignoring values far from the bulk |
|
|
101
|
+
| `"auto"` | the full finite minimum and maximum over all samples |
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
rf.plot("events.root", "d0_significance", bins=50) # robust
|
|
105
|
+
rf.plot("events.root", "d0_significance", bins=50, range="auto") # full extent
|
|
106
|
+
rf.plot("events.root", "d0_significance", bins=50, range=(-5, 5)) # explicit
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
!!! note "Rejected values are not discarded"
|
|
110
|
+
|
|
111
|
+
A value outside the range is **not removed from the data**: it goes to the
|
|
112
|
+
under/overflow, shown by the flow arrows (`flow="show"` turns them into
|
|
113
|
+
visible bins, `flow="sum"` folds them into the edge bins). Statistics boxes
|
|
114
|
+
and `rf.summarize` are computed before binning, so means and entry counts
|
|
115
|
+
cover the full sample whichever range is used.
|
|
116
|
+
|
|
117
|
+
This happens in two steps. Outliers are rejected by their modified z-score
|
|
118
|
+
(`0.6745 * |x - median| / MAD`), with a threshold of 30, which removes sentinels
|
|
119
|
+
and anything else far from the bulk. That is done within each sample, and the
|
|
120
|
+
ranges they keep are unioned, so a sample is judged against its own median and
|
|
121
|
+
spread: a signal offset from a background is not an outlier merely because the
|
|
122
|
+
background outnumbers it, and a sample keeps the same values whether it is
|
|
123
|
+
plotted alone or in an overlay. The threshold is then tightened for as
|
|
124
|
+
long as each step costs no more than an *additional* 1 percent of any one
|
|
125
|
+
sample, by entries and by weight. Additional is meant literally: the budget is
|
|
126
|
+
measured against what the first step already moved out of the view, which may
|
|
127
|
+
be a good deal more than 1 percent. This second step cuts a tail that reaches
|
|
128
|
+
far but thins out smoothly, the kind a distance threshold keeps and that leaves
|
|
129
|
+
the interesting part of the distribution in a corner of the axis.
|
|
130
|
+
|
|
131
|
+
The budget is charged per sample rather than over the pooled entries, so a small
|
|
132
|
+
signal sitting far from a large background keeps its own place on the axis
|
|
133
|
+
instead of being cut as a rounding error, and it is charged against the weight a
|
|
134
|
+
cut would remove as well as the entries, so a handful of high-weight entries is
|
|
135
|
+
not treated as negligible. A sample with fewer than 20 distinct values is
|
|
136
|
+
categorical — counts, flags, multiplicities — has no tail to cut and gets no
|
|
137
|
+
budget at all, so the second step takes no value off its axis; this is decided
|
|
138
|
+
per sample too, and holds when it is overlaid with a continuous one. If MAD is zero, the mean absolute deviation from
|
|
139
|
+
the median is used instead. Every candidate is padded by 5 percent and clamped
|
|
140
|
+
to the `"auto"` range (whose upper edge is nudged above the maximum to include
|
|
141
|
+
it), so the range never reaches past the data. Degenerate ranges are widened
|
|
142
|
+
symmetrically. The threshold cannot distinguish sentinels from valid data.
|
|
143
|
+
Cases where you may want `range="auto"`:
|
|
144
|
+
|
|
145
|
+
- a distribution with a long tail (log-normal, Student-t, or an invariant mass
|
|
146
|
+
with a continuum) has valid tail entries pushed into the flow bins — this is
|
|
147
|
+
what the second step is for, so `"auto"` is the way to see the whole tail;
|
|
148
|
+
- a sparse discrete distribution can lose rare valid values from the visible
|
|
149
|
+
range, for example the ones in a binary sample with 999 zeros and one one;
|
|
150
|
+
- a lone value far from a bulk of near-identical ones looks exactly like a
|
|
151
|
+
sentinel and is rejected with them, however real it is;
|
|
152
|
+
- `xbreak=(a, b)` is validated against the inferred axis, so a break meant to
|
|
153
|
+
span a far tail needs `range="auto"` or an explicit range.
|
|
154
|
+
|
|
155
|
+
Inferring a robust range requires additional median and deviation calculations
|
|
156
|
+
over the combined samples, with additional time and memory costs. An explicit
|
|
157
|
+
range avoids range inference.
|
|
158
|
+
|
|
159
|
+
!!! tip "The plot is mostly empty space"
|
|
160
|
+
|
|
161
|
+
The inferred range cuts a thin tail, but only as far as its coverage
|
|
162
|
+
budget allows. A distribution whose tail carries more than that — a heavy
|
|
163
|
+
Student-t, a steeply falling spectrum over several decades — still spreads
|
|
164
|
+
the axis over bins holding a fraction of a percent of the peak. Three ways
|
|
165
|
+
out, in order of how often they are what you want:
|
|
166
|
+
|
|
167
|
+
- `range=(a, b)` around the core. Nothing is lost: entries outside go to
|
|
168
|
+
the flow bins, where `flow="hint"` (the default) marks them with arrows
|
|
169
|
+
and `flow="sum"` folds them into the edge bins.
|
|
170
|
+
- `logy=True`, which makes the tail visible instead of hiding it.
|
|
171
|
+
- `xbreak=(a, b)` to cut the empty middle out and keep both ends, with
|
|
172
|
+
`range="auto"` or an explicit range so the break lies inside the axis.
|
|
173
|
+
|
|
89
174
|
## Axes
|
|
90
175
|
|
|
91
176
|
- `logx`, `logy`: logarithmic scales. Log-spaced bins: `bins=rf.log_bins(n, low, high)`.
|
|
92
177
|
- `xlim`, `ylim`: limits; `ylim=(None, 1e4)` keeps the automatic lower value.
|
|
93
|
-
Automatic y limits
|
|
94
|
-
linear scale,
|
|
178
|
+
Automatic y limits add a small margin above the tallest bin (a factor 1.2 in
|
|
179
|
+
linear scale, 12 in log scale) and then raise it further as the drawn
|
|
180
|
+
legend, label, statistics box and text lines need.
|
|
95
181
|
- `xbreak=(a, b)`: cut the range between `a` and `b` out of the x axis and
|
|
96
182
|
draw the two remaining segments side by side with break marks, sharing the
|
|
97
183
|
y axis (and the ratio panel, if any). Useful for a peak plus a far tail or
|
|
@@ -99,7 +185,8 @@ computed values are returned in `Plot.ratios` as
|
|
|
99
185
|
(`Plot.ratio_ax_right`). Not available together with `ax=` or `flow="show"`.
|
|
100
186
|
|
|
101
187
|
{ width="60%" }
|
|
102
|
-
- `flow`: how under/overflow is shown
|
|
188
|
+
- `flow`: how under/overflow is shown (this is where entries outside an
|
|
189
|
+
inferred [range](#binning-and-range) end up), `"hint"` (small arrows, default),
|
|
103
190
|
`"show"` (extra bins labelled `<low` / `>high`, added on a side as soon as any
|
|
104
191
|
sample has content there, identical for all samples and the ratio panel),
|
|
105
192
|
`"sum"` (added to the edge bins before anything is computed, so ratios,
|
|
@@ -110,9 +197,11 @@ computed values are returned in `Plot.ratios` as
|
|
|
110
197
|
- `xlabel`, `ylabel`, `unit`, `title`. The title sits above the axes, where
|
|
111
198
|
the CMS-style label is also drawn; with such a style prefer `text=`.
|
|
112
199
|
- Automatic y limits leave room for the legend, the experiment label, the
|
|
113
|
-
statistics box and `text` lines:
|
|
114
|
-
|
|
115
|
-
|
|
200
|
+
statistics box and `text` lines: a small fixed margin is added above the
|
|
201
|
+
tallest bin, and the upper limit is then raised until none of them covers a
|
|
202
|
+
histogram (the legend picks a free upper corner). Room is only made for
|
|
203
|
+
what is actually drawn, so a plot without annotations keeps the margin.
|
|
204
|
+
A `ylim` with an explicit upper value switches this off.
|
|
116
205
|
|
|
117
206
|
## Legend, labels, text and statistics
|
|
118
207
|
|
|
@@ -19,6 +19,11 @@ the weight (nothing else), evaluates them, fills a `hist.Hist` and draws it.
|
|
|
19
19
|
If the file contains exactly one tree you can leave `tree` out. The file can
|
|
20
20
|
also be a glob (`"run_*.root"`), a list of files, or `"file.root:tree"`.
|
|
21
21
|
|
|
22
|
+
A bare `bins=50` infers the range from the data, ignoring far outliers so that
|
|
23
|
+
sentinel values such as `-999` do not set the axis; pass `range=(low, high)` to
|
|
24
|
+
be explicit or `range="auto"` for the full extent (see
|
|
25
|
+
[Binning and range](plotting.md#binning-and-range)).
|
|
26
|
+
|
|
22
27
|
The return value is a [`Plot`][rootfig.Plot] with `fig`, `ax`, `hists` and a
|
|
23
28
|
`save()` method:
|
|
24
29
|
|