plothist 1.5.0__py3-none-any.whl → 1.6.0__py3-none-any.whl

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.
plothist/__init__.py CHANGED
@@ -85,18 +85,19 @@ __all__ = [
85
85
  ]
86
86
 
87
87
 
88
- # Get style file and use it
88
+ from importlib import resources
89
89
  from importlib.resources import files
90
90
 
91
+ import boost_histogram as bh
92
+ import matplotlib.font_manager as fm
91
93
  import matplotlib.pyplot as plt
92
94
 
95
+ # Get style file and use it
96
+
93
97
  style_file = files("plothist").joinpath("default_style.mplstyle")
94
98
  plt.style.use(style_file)
95
99
 
96
100
  # Install fonts
97
- from importlib import resources
98
-
99
- import matplotlib.font_manager as fm
100
101
 
101
102
  with resources.as_file(resources.files("plothist_utils") / "fonts") as font_path:
102
103
  font_files = fm.findSystemFonts(fontpaths=[str(font_path)])
@@ -104,7 +105,6 @@ with resources.as_file(resources.files("plothist_utils") / "fonts") as font_path
104
105
  fm.fontManager.addfont(font)
105
106
 
106
107
  # Check version of boost_histogram
107
- import boost_histogram as bh
108
108
 
109
109
  if tuple(int(part) for part in bh.__version__.split(".")) < (1, 4, 0):
110
110
  raise ImportError(
plothist/_version.py CHANGED
@@ -17,5 +17,5 @@ __version__: str
17
17
  __version_tuple__: VERSION_TUPLE
18
18
  version_tuple: VERSION_TUPLE
19
19
 
20
- __version__ = version = '1.5.0'
21
- __version_tuple__ = version_tuple = (1, 5, 0)
20
+ __version__ = version = '1.6.0'
21
+ __version_tuple__ = version_tuple = (1, 6, 0)
plothist/comparison.py CHANGED
@@ -22,9 +22,16 @@ def _check_uncertainty_type(uncertainty_type: str) -> None:
22
22
  If the uncertainty type is not valid.
23
23
 
24
24
  """
25
- if uncertainty_type not in ["symmetrical", "asymmetrical"]:
25
+ _valid_uncertainty_types = [
26
+ "symmetrical",
27
+ "asymmetrical",
28
+ "asymmetrical_double_sided_zeros",
29
+ "asymmetrical_one_sided_zeros",
30
+ ]
31
+
32
+ if uncertainty_type not in _valid_uncertainty_types:
26
33
  raise ValueError(
27
- f"Uncertainty type {uncertainty_type} not valid. Must be 'symmetrical' or 'asymmetrical'."
34
+ f"Uncertainty type {uncertainty_type} not valid. Must be in {_valid_uncertainty_types}."
28
35
  )
29
36
 
30
37
 
@@ -42,11 +49,12 @@ def _is_unweighted(hist: bh.Histogram) -> bool:
42
49
  bool
43
50
  True if the histogram is unweighted, False otherwise.
44
51
  """
45
- return np.allclose(hist.variances(), hist.values())
52
+ return np.allclose(hist.variances(), hist.values(), equal_nan=True)
46
53
 
47
54
 
48
55
  def get_asymmetrical_uncertainties(
49
56
  hist: bh.Histogram,
57
+ uncertainty_type: str = "asymmetrical",
50
58
  ) -> tuple[np.ndarray, np.ndarray]:
51
59
  """
52
60
  Get Poisson asymmetrical uncertainties for a histogram via a frequentist approach based on a confidence-interval computation.
@@ -57,6 +65,8 @@ def get_asymmetrical_uncertainties(
57
65
  ----------
58
66
  hist : bh.Histogram
59
67
  The histogram.
68
+ uncertainty_type : str, optional
69
+ The type of uncertainty to compute for bins with 0 entry. Default is "asymmetrical" (= "asymmetrical_one_sided_zeros"). Use "asymmetrical_double_sided_zeros" to have the double-sided definition. More information in :ref:`documentation-statistics-label`.
60
70
 
61
71
  Returns
62
72
  -------
@@ -72,16 +82,44 @@ def get_asymmetrical_uncertainties(
72
82
 
73
83
  """
74
84
  _check_counting_histogram(hist)
85
+ _check_uncertainty_type(uncertainty_type)
75
86
 
76
87
  if not _is_unweighted(hist):
77
88
  raise ValueError(
78
89
  "Asymmetrical uncertainties can only be computed for an unweighted histogram."
79
90
  )
80
- conf_level = 0.682689492
81
- alpha = 1.0 - conf_level
91
+
92
+ alpha = 1.0 - 0.682689492
93
+ tail_probability = alpha / 2
94
+
82
95
  n = hist.values()
83
- uncertainties_low = n - stats.gamma.ppf(alpha / 2, n, scale=1)
84
- uncertainties_high = stats.gamma.ppf(1 - alpha / 2, n + 1, scale=1) - n
96
+
97
+ lower_bound = np.zeros_like(n, dtype=float)
98
+ upper_bound = np.zeros_like(n, dtype=float)
99
+
100
+ # Two-sided Garwood intervals for n > 0
101
+ lower_bound[n > 0] = stats.gamma.ppf(q=tail_probability, a=n[n > 0], scale=1)
102
+ upper_bound[n > 0] = stats.gamma.ppf(
103
+ q=1 - tail_probability, a=n[n > 0] + 1, scale=1
104
+ )
105
+
106
+ if uncertainty_type == "asymmetrical_double_sided_zeros":
107
+ # Two-sided Garwood intervals for n == 0
108
+ upper_bound[n == 0] = stats.gamma.ppf(q=1 - tail_probability, a=1, scale=1)
109
+ elif uncertainty_type in ["asymmetrical_one_sided_zeros", "asymmetrical"]:
110
+ # One-sided upper limit for n == 0
111
+ upper_bound[n == 0] = stats.gamma.ppf(q=1 - 2 * tail_probability, a=1, scale=1)
112
+ else:
113
+ raise ValueError(
114
+ f"Invalid uncertainty type '{uncertainty_type}' for asymmetrical uncertainties."
115
+ )
116
+
117
+ # Compute asymmetric uncertainties
118
+ uncertainties_low = n - lower_bound
119
+ uncertainties_high = upper_bound - n
120
+
121
+ uncertainties_low = np.nan_to_num(uncertainties_low, nan=0.0)
122
+ uncertainties_high = np.nan_to_num(uncertainties_high, nan=0.0)
85
123
 
86
124
  return uncertainties_low, uncertainties_high
87
125
 
@@ -174,8 +212,10 @@ def get_pull(
174
212
  _check_counting_histogram(h1)
175
213
  _check_counting_histogram(h2)
176
214
 
177
- if h1_uncertainty_type == "asymmetrical":
178
- uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(h1)
215
+ if "asymmetrical" in h1_uncertainty_type:
216
+ uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(
217
+ h1, h1_uncertainty_type
218
+ )
179
219
  h1_variances = np.where(
180
220
  h1.values() >= h2.values(),
181
221
  uncertainties_low**2,
@@ -232,8 +272,10 @@ def get_difference(
232
272
 
233
273
  difference_values = h1.values() - h2.values()
234
274
 
235
- if h1_uncertainty_type == "asymmetrical":
236
- uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(h1)
275
+ if "asymmetrical" in h1_uncertainty_type:
276
+ uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(
277
+ h1, h1_uncertainty_type
278
+ )
237
279
 
238
280
  difference_uncertainties_low = np.sqrt(uncertainties_low**2 + h2.variances())
239
281
  difference_uncertainties_high = np.sqrt(uncertainties_high**2 + h2.variances())
@@ -390,11 +432,13 @@ def get_ratio(
390
432
 
391
433
  ratio_values = np.where(h2.values() != 0, h1.values() / h2.values(), np.nan)
392
434
 
393
- if h1_uncertainty_type == "asymmetrical":
394
- uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(h1)
435
+ if "asymmetrical" in h1_uncertainty_type:
436
+ uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(
437
+ h1, h1_uncertainty_type
438
+ )
395
439
 
396
440
  if ratio_uncertainty_type == "uncorrelated":
397
- if h1_uncertainty_type == "asymmetrical":
441
+ if "asymmetrical" in h1_uncertainty_type:
398
442
  h1_high = h1.copy()
399
443
  h1_high[:] = np.c_[h1_high.values(), uncertainties_high**2]
400
444
  h1_low = h1.copy()
@@ -405,7 +449,7 @@ def get_ratio(
405
449
  ratio_uncertainties_low = np.sqrt(get_ratio_variances(h1, h2))
406
450
  ratio_uncertainties_high = ratio_uncertainties_low
407
451
  elif ratio_uncertainty_type == "split":
408
- if h1_uncertainty_type == "asymmetrical":
452
+ if "asymmetrical" in h1_uncertainty_type:
409
453
  ratio_uncertainties_low = uncertainties_low / h2.values()
410
454
  ratio_uncertainties_high = uncertainties_high / h2.values()
411
455
  else:
@@ -493,7 +537,7 @@ def get_comparison(
493
537
  h1, h2, h1_uncertainty_type
494
538
  )
495
539
  elif comparison == "asymmetry":
496
- if h1_uncertainty_type == "asymmetrical":
540
+ if "asymmetrical" in h1_uncertainty_type:
497
541
  raise ValueError(
498
542
  "Asymmetrical uncertainties are not supported for the asymmetry comparison."
499
543
  )
@@ -501,7 +545,7 @@ def get_comparison(
501
545
  lower_uncertainties = uncertainties
502
546
  upper_uncertainties = uncertainties
503
547
  elif comparison == "efficiency":
504
- if h1_uncertainty_type == "asymmetrical":
548
+ if "asymmetrical" in h1_uncertainty_type:
505
549
  raise ValueError(
506
550
  "Asymmetrical uncertainties are not supported in an efficiency computation."
507
551
  )
@@ -0,0 +1,120 @@
1
+ """
2
+ Representation of different uncertainty types.
3
+ ==============================================
4
+
5
+ This example demonstrates how to use the `plot_error_hist` function with different uncertainty types
6
+ """
7
+
8
+ import boost_histogram as bh
9
+ import matplotlib.pyplot as plt
10
+ import numpy as np
11
+
12
+ from plothist import add_text, plot_error_hist
13
+
14
+
15
+ def shifted_array(values, offset: int, size: int = 15) -> np.ndarray:
16
+ """
17
+ Create an array of a given size with NaNs and fill it with values at specified offsets.
18
+ The values are inserted at every offset starting from the given offset.
19
+ """
20
+
21
+ arr = np.full(size, np.nan)
22
+ indices = range(offset, size, len(values))
23
+
24
+ for i, idx in enumerate(indices):
25
+ if i >= len(values):
26
+ break
27
+ arr[idx] = values[i]
28
+
29
+ return arr
30
+
31
+
32
+ def make_grouped_edges(
33
+ n_groups, group_size=4, inner_spacing=0.1, inter_spacing=0.2
34
+ ) -> np.ndarray:
35
+ """
36
+ Create a set of edges for a histogram with grouped categories.
37
+ """
38
+
39
+ edges = [
40
+ group_start + i * inner_spacing
41
+ for group in range(n_groups)
42
+ for i in range(group_size)
43
+ for group_start in [
44
+ group * (group_size * inner_spacing + inter_spacing - inner_spacing)
45
+ ]
46
+ ]
47
+ return np.array(edges)
48
+
49
+
50
+ # Create a category axis with explicit locations
51
+ edges = make_grouped_edges(4)
52
+ axis = bh.axis.Variable(edges)
53
+
54
+ hists = []
55
+ entries = [0, 0.5, 3, 500]
56
+
57
+ for k in range(3):
58
+ hist = bh.Histogram(axis, storage=bh.storage.Weight())
59
+ values = shifted_array(entries, k)
60
+ hist[:] = np.c_[values, values]
61
+ hists.append(hist)
62
+
63
+
64
+ fig, (ax_top, ax_bot) = plt.subplots(
65
+ 2, 1, gridspec_kw={"height_ratios": [1, 3], "hspace": 0.05}
66
+ )
67
+
68
+ for ax in (ax_top, ax_bot):
69
+ plot_error_hist(
70
+ hists[0], ax=ax, label="symmetrical", uncertainty_type="symmetrical"
71
+ )
72
+ plot_error_hist(
73
+ hists[1], ax=ax, label="asymmetrical", uncertainty_type="asymmetrical"
74
+ )
75
+ plot_error_hist(
76
+ hists[2],
77
+ ax=ax,
78
+ label="asymmetrical_double_sided_zeros",
79
+ uncertainty_type="asymmetrical_double_sided_zeros",
80
+ )
81
+
82
+ add_text("plot_error_hist() with different uncertainty type", ax=ax_top, x="right")
83
+
84
+ # Set axis limits
85
+ ax_top.set_ylim(465, 530)
86
+ ax_bot.set_ylim(-0.5, 6.9)
87
+ ax_bot.set_xlim(xmin=-0.05)
88
+
89
+ # Format bottom ticks and labels
90
+ ax_bot.set_xticks(edges[1::4] + 0.05)
91
+ ax_bot.set_xlabel("Entry category")
92
+ ax_bot.set_xticklabels(entries)
93
+ ax_bot.set_ylabel("Entries")
94
+ ax_bot.yaxis.label.set_horizontalalignment("left")
95
+ ax_bot.spines.top.set_visible(False)
96
+ ax_bot.xaxis.set_minor_locator(plt.NullLocator()) # Hide x-axis minor ticks
97
+
98
+ # Format top ticks and labels
99
+ ax_top.xaxis.tick_top()
100
+ ax_top.spines.bottom.set_visible(False)
101
+ ax_top.set_xticklabels([])
102
+ ax_top.set_xticks([])
103
+
104
+ # Draw break marks
105
+ d = 0.5 # proportion of vertical to horizontal extent of the slanted line
106
+ kwargs = {
107
+ "marker": [(-1, -d), (1, d)],
108
+ "markersize": 12,
109
+ "linestyle": "none",
110
+ "color": "k",
111
+ "mec": "k",
112
+ "mew": 1,
113
+ "clip_on": False,
114
+ }
115
+ ax_top.plot([0, 1], [0, 0], transform=ax_top.transAxes, **kwargs)
116
+ ax_bot.plot([0, 1], [1, 1], transform=ax_bot.transAxes, **kwargs)
117
+
118
+ ax_top.legend(loc="upper left")
119
+
120
+ fig.savefig("uncertainty_types.svg", bbox_inches="tight")
@@ -112,7 +112,7 @@ def cubehelix_palette(
112
112
  pal = cmap(x)[:, :3].tolist()
113
113
  if reverse:
114
114
  pal = pal[::-1]
115
- return pal
115
+ return [tuple(c) for c in pal]
116
116
 
117
117
 
118
118
  def get_color_palette(
@@ -170,7 +170,7 @@ def get_color_palette(
170
170
  )
171
171
 
172
172
  plt_cmap = plt.get_cmap(cmap)
173
- return plt_cmap(np.linspace(0, 1, N))
173
+ return [tuple(k) for k in plt_cmap(np.linspace(0, 1, N))]
174
174
 
175
175
 
176
176
  def set_fitting_ylabel_fontsize(ax: plt.Axes) -> float:
plothist/plotters.py CHANGED
@@ -406,7 +406,9 @@ def plot_error_hist(
406
406
  if uncertainty_type == "symmetrical":
407
407
  kwargs.setdefault("yerr", np.sqrt(hist.variances()))
408
408
  else:
409
- uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(hist)
409
+ uncertainties_low, uncertainties_high = get_asymmetrical_uncertainties(
410
+ hist, uncertainty_type
411
+ )
410
412
  kwargs.setdefault("yerr", [uncertainties_low, uncertainties_high])
411
413
 
412
414
  kwargs.setdefault("fmt", ".")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plothist
3
- Version: 1.5.0
3
+ Version: 1.6.0
4
4
  Summary: Plot histograms in a scalable way and a beautiful style.
5
5
  Project-URL: Homepage, https://github.com/cyrraz/plothist
6
6
  Project-URL: Documentation, https://plothist.readthedocs.io/
@@ -1,11 +1,11 @@
1
- plothist/__init__.py,sha256=00yQZBIlyvZxdimTgPMHK_ZH5pNVadyi-fu6ezuXRQs,2760
2
- plothist/_version.py,sha256=qEW4HoWHYDkBguijNs9nZzHd38qlKSeRTDG2QQbYrGY,511
1
+ plothist/__init__.py,sha256=ikcYOZqMs4WKAJ2rM-3BnHu_CQxLlQCuc-iFo_a-Mns,2760
2
+ plothist/_version.py,sha256=5FGJNp9Lkk9uOxeCjXpoCGBF79Ar6LGPOR7-atBqb_4,511
3
3
  plothist/_version.pyi,sha256=o7uNL6MhuJoiqpEnriU7rBT6TmkJZA-i2qMoNz9YcgQ,82
4
- plothist/comparison.py,sha256=Lk5bTMHe93C_0eOfUJQaY8lHxykJIQBWMDDt-iOmR-E,17935
4
+ plothist/comparison.py,sha256=jkONOiCp8RNBk8V_VvwACdfJJqo89rK9NQW71XxJscc,19623
5
5
  plothist/default_style.mplstyle,sha256=7MmB2uiXmD_DSqFHeH1xxC-lTctBD_EASxMdSOsPep0,1574
6
6
  plothist/histogramming.py,sha256=o0RLdoEhxHA7Ws0bvK3DewsbQEu-QOtJNw5Md5AckHM,11474
7
- plothist/plothist_style.py,sha256=oPGt61GT6Hs_m6jRNlNDGCH1-5AJuMMUi7guw1vTGVo,13215
8
- plothist/plotters.py,sha256=hjoDKVTYfRXI1m0GxlkfPc2LnpQWH1aYC8GnsYEak9k,46296
7
+ plothist/plothist_style.py,sha256=bbYFc-qsrTfdbacCBAAzmiHJoRIh3toBomFfxtTRNMs,13255
8
+ plothist/plotters.py,sha256=ncPZa34Fo1mbFxv0guFc2zEciX_JXGKJxeIfcUDaP6w,46336
9
9
  plothist/test_helpers.py,sha256=JXhxUdqMszkawxkU8cDPqipkSXNHfsKSfe3K-4frDrM,1379
10
10
  plothist/variable_registry.py,sha256=usVLoDLbIMzjeRDazIH0OIjQIUsh9RSJ9Ncnhn1V9qw,10783
11
11
  plothist/examples/README.rst,sha256=PVzkOQzVnNeWORxEv3NNv5XoSN2Y71D7SYzRkCqirfA,151
@@ -56,8 +56,9 @@ plothist/examples/utility/add_text_example.py,sha256=EB7ZUStt2E8GawjWNmVbSV34NIN
56
56
  plothist/examples/utility/color_palette_hists.py,sha256=uIc6TrmjTj8EUif1Yj1wq4nXoY1sgJpS15yPe3-FpTw,2383
57
57
  plothist/examples/utility/color_palette_squares.py,sha256=pNasgvZZApu-sCqhi5FTJAH3l6Px2cgB5RwJcQ1eF1U,2689
58
58
  plothist/examples/utility/matplotlib_vs_plothist_style.py,sha256=iEIfbFKmAN-PcDCzw_sBuhrfpes4I2wUVfC0r1vUHEw,2006
59
- plothist-1.5.0.dist-info/METADATA,sha256=0km7Qp4BxeY3aBXgC7EdkIShgSlNVo6yOnR6ehC16a0,4737
60
- plothist-1.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
61
- plothist-1.5.0.dist-info/licenses/AUTHORS.md,sha256=02x3_8PNyTsXcRs0IlJeCTOmpGNRqymcJ71-2QtR37E,111
62
- plothist-1.5.0.dist-info/licenses/LICENSE,sha256=bfaEdGehofQDaw-zDdVMHNUKo1FrOm6oGUEF-ltrp6w,1523
63
- plothist-1.5.0.dist-info/RECORD,,
59
+ plothist/examples/utility/uncertainty_types.py,sha256=r1yxzEvxn5Clv0ZIok9JjVh3RB-YQ_usWhFnpvl1els,3259
60
+ plothist-1.6.0.dist-info/METADATA,sha256=WsoMGQrcmUasAx0UxEP9aGEw7LAk1eHQCrjUz6228fY,4737
61
+ plothist-1.6.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
62
+ plothist-1.6.0.dist-info/licenses/AUTHORS.md,sha256=02x3_8PNyTsXcRs0IlJeCTOmpGNRqymcJ71-2QtR37E,111
63
+ plothist-1.6.0.dist-info/licenses/LICENSE,sha256=bfaEdGehofQDaw-zDdVMHNUKo1FrOm6oGUEF-ltrp6w,1523
64
+ plothist-1.6.0.dist-info/RECORD,,