plothist 1.4.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 +5 -5
- plothist/_version.py +2 -2
- plothist/comparison.py +170 -120
- plothist/examples/1d_hist/1d_comparison_asymmetry.py +37 -0
- plothist/examples/1d_hist/1d_comparison_difference.py +40 -0
- plothist/examples/1d_hist/1d_comparison_efficiency.py +37 -0
- plothist/examples/1d_hist/1d_comparison_only_efficiency.py +33 -0
- plothist/examples/1d_hist/1d_comparison_pull.py +37 -0
- plothist/examples/1d_hist/1d_comparison_ratio.py +37 -0
- plothist/examples/1d_hist/1d_comparison_relative_difference.py +37 -0
- plothist/examples/1d_hist/1d_comparison_split_ratio.py +37 -0
- plothist/examples/1d_hist/1d_elt1.py +38 -0
- plothist/examples/1d_hist/1d_elt1_stacked.py +45 -0
- plothist/examples/1d_hist/1d_elt2.py +33 -0
- plothist/examples/1d_hist/1d_hist_simple.py +28 -0
- plothist/examples/1d_hist/1d_int_category.py +41 -0
- plothist/examples/1d_hist/1d_profile.py +33 -0
- plothist/examples/1d_hist/1d_side_by_side.py +58 -0
- plothist/examples/1d_hist/1d_str_category.py +41 -0
- plothist/examples/1d_hist/README.rst +4 -0
- plothist/examples/2d_hist/2d_hist_correlations.py +65 -0
- plothist/examples/2d_hist/2d_hist_simple.py +28 -0
- plothist/examples/2d_hist/2d_hist_simple_discrete_colormap.py +42 -0
- plothist/examples/2d_hist/2d_hist_uneven.py +28 -0
- plothist/examples/2d_hist/2d_hist_with_projections.py +36 -0
- plothist/examples/2d_hist/README.rst +4 -0
- plothist/examples/README.rst +7 -0
- plothist/examples/advanced/1d_comparison_advanced.py +87 -0
- plothist/examples/advanced/1d_side_by_side_with_numbers.py +81 -0
- plothist/examples/advanced/README.rst +4 -0
- plothist/examples/advanced/asymmetry_comparison_advanced.py +133 -0
- plothist/examples/advanced/model_examples_flatten2D.py +86 -0
- plothist/examples/func_1d/README.rst +4 -0
- plothist/examples/func_1d/fct_1d.py +27 -0
- plothist/examples/func_1d/fct_1d_stacked.py +42 -0
- plothist/examples/model_ex/README.rst +4 -0
- plothist/examples/model_ex/model_all_comparisons.py +103 -0
- plothist/examples/model_ex/model_all_comparisons_no_model_unc.py +115 -0
- plothist/examples/model_ex/model_examples_pull.py +56 -0
- plothist/examples/model_ex/model_examples_pull_no_model_unc.py +59 -0
- plothist/examples/model_ex/model_examples_stacked.py +74 -0
- plothist/examples/model_ex/model_examples_stacked_unstacked.py +60 -0
- plothist/examples/model_ex/model_examples_unstacked.py +57 -0
- plothist/examples/model_ex/model_with_stacked_and_unstacked_function_components.py +50 -0
- plothist/examples/model_ex/model_with_stacked_and_unstacked_histograms_components.py +69 -0
- plothist/examples/model_ex/ratio_data_vs_model_with_stacked_and_unstacked_function_components.py +61 -0
- plothist/examples/utility/README.rst +4 -0
- plothist/examples/utility/add_text_example.py +39 -0
- plothist/examples/utility/color_palette_hists.py +94 -0
- plothist/examples/utility/color_palette_squares.py +100 -0
- plothist/examples/utility/matplotlib_vs_plothist_style.py +63 -0
- plothist/examples/utility/uncertainty_types.py +120 -0
- plothist/histogramming.py +60 -39
- plothist/plothist_style.py +56 -59
- plothist/plotters.py +210 -195
- plothist/test_helpers.py +43 -0
- plothist/variable_registry.py +46 -30
- {plothist-1.4.0.dist-info → plothist-1.6.0.dist-info}/METADATA +1 -1
- plothist-1.6.0.dist-info/RECORD +64 -0
- plothist/scripts/__init__.py +0 -3
- plothist/scripts/make_examples.py +0 -209
- plothist-1.4.0.dist-info/RECORD +0 -17
- plothist-1.4.0.dist-info/entry_points.txt +0 -2
- {plothist-1.4.0.dist-info → plothist-1.6.0.dist-info}/WHEEL +0 -0
- {plothist-1.4.0.dist-info → plothist-1.6.0.dist-info}/licenses/AUTHORS.md +0 -0
- {plothist-1.4.0.dist-info → plothist-1.6.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Color palettes in stacked histograms
|
|
3
|
+
====================================
|
|
4
|
+
|
|
5
|
+
Examples of color palettes in stacked histograms.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from plothist_utils import get_dummy_data
|
|
9
|
+
|
|
10
|
+
df = get_dummy_data()
|
|
11
|
+
|
|
12
|
+
import matplotlib.pyplot as plt
|
|
13
|
+
|
|
14
|
+
from plothist import add_text, get_color_palette, make_hist, plot_error_hist, plot_model
|
|
15
|
+
|
|
16
|
+
# Define the histograms
|
|
17
|
+
key = "variable_1"
|
|
18
|
+
xrange = (-8, 10)
|
|
19
|
+
category = "category"
|
|
20
|
+
|
|
21
|
+
# Define masks
|
|
22
|
+
signal_mask = df[category] == 7
|
|
23
|
+
data_mask = df[category] == 8
|
|
24
|
+
|
|
25
|
+
background_categories = [0, 1, 2, 3, 4, 5]
|
|
26
|
+
background_categories_labels = [f"c{i}" for i in background_categories]
|
|
27
|
+
background_masks = [df[category] == p for p in background_categories]
|
|
28
|
+
|
|
29
|
+
# Make histograms
|
|
30
|
+
data_hist = make_hist(df[key][data_mask], bins=50, range=xrange, weights=1)
|
|
31
|
+
background_hists = [
|
|
32
|
+
make_hist(df[key][mask], bins=50, range=xrange, weights=1)
|
|
33
|
+
for mask in background_masks
|
|
34
|
+
]
|
|
35
|
+
signal_hist = make_hist(df[key][signal_mask], bins=50, range=xrange, weights=1)
|
|
36
|
+
|
|
37
|
+
# Optional: scale to data
|
|
38
|
+
background_scaling_factor = data_hist.sum().value / sum(background_hists).sum().value
|
|
39
|
+
background_hists = [background_scaling_factor * h for h in background_hists]
|
|
40
|
+
|
|
41
|
+
signal_scaling_factor = data_hist.sum().value / signal_hist.sum().value
|
|
42
|
+
signal_hist *= signal_scaling_factor
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Plotting section
|
|
46
|
+
nrows, ncols = 2, 2
|
|
47
|
+
|
|
48
|
+
fig, axes = plt.subplots(
|
|
49
|
+
nrows=nrows,
|
|
50
|
+
ncols=ncols,
|
|
51
|
+
figsize=(12, 10),
|
|
52
|
+
)
|
|
53
|
+
fig.subplots_adjust(hspace=0.25)
|
|
54
|
+
|
|
55
|
+
cmap_list = ["viridis", "ggplot", "coolwarm", "YlGnBu_r"]
|
|
56
|
+
ax_coords = [(x, y) for x in range(nrows) for y in range(ncols)]
|
|
57
|
+
|
|
58
|
+
for k, cmap_name in enumerate(cmap_list):
|
|
59
|
+
background_categories_colors = get_color_palette(
|
|
60
|
+
cmap_name, len(background_categories)
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
ax = axes[ax_coords[k]]
|
|
64
|
+
|
|
65
|
+
plot_model(
|
|
66
|
+
stacked_components=background_hists,
|
|
67
|
+
stacked_labels=background_categories_labels,
|
|
68
|
+
stacked_colors=background_categories_colors,
|
|
69
|
+
xlabel=key,
|
|
70
|
+
ylabel="Entries",
|
|
71
|
+
model_uncertainty=False,
|
|
72
|
+
fig=fig,
|
|
73
|
+
ax=ax,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
plot_error_hist(
|
|
77
|
+
data_hist,
|
|
78
|
+
color="black",
|
|
79
|
+
label="Data",
|
|
80
|
+
ax=ax,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
ax.set_xlim(xrange)
|
|
84
|
+
ax.legend()
|
|
85
|
+
|
|
86
|
+
cmap_name = cmap_name.replace("_", r"\_")
|
|
87
|
+
add_text(
|
|
88
|
+
rf"$\mathrm{{\mathbf{{cmap = {cmap_name}}}}}$", x="right", fontsize=12, ax=ax
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
fig.savefig(
|
|
92
|
+
"color_palette_hists.svg",
|
|
93
|
+
bbox_inches="tight",
|
|
94
|
+
)
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Color palettes
|
|
3
|
+
==============
|
|
4
|
+
|
|
5
|
+
Examples of color palettes.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import matplotlib.colors as mcolors
|
|
9
|
+
import matplotlib.pyplot as plt
|
|
10
|
+
import numpy as np
|
|
11
|
+
from matplotlib import patches
|
|
12
|
+
|
|
13
|
+
from plothist import get_color_palette
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def create_palette_plot(colors, fig_name, add_text=False, add_black_border=False):
|
|
17
|
+
ncolors = len(colors)
|
|
18
|
+
|
|
19
|
+
# Create a figure and axis
|
|
20
|
+
fig, ax = plt.subplots(figsize=(ncolors, 1))
|
|
21
|
+
|
|
22
|
+
# Plot the colored squares with small spacing
|
|
23
|
+
square_size = 1
|
|
24
|
+
spacing = 0.1
|
|
25
|
+
x = 0
|
|
26
|
+
|
|
27
|
+
for color in colors:
|
|
28
|
+
rect = patches.Rectangle((x, 0), square_size, square_size, color=color)
|
|
29
|
+
ax.add_patch(rect)
|
|
30
|
+
x += square_size + spacing
|
|
31
|
+
|
|
32
|
+
if add_text:
|
|
33
|
+
# Add text displaying the color value
|
|
34
|
+
ax.text(
|
|
35
|
+
x - (square_size + spacing) / 1.81,
|
|
36
|
+
-0.18,
|
|
37
|
+
mcolors.rgb2hex(color).upper(),
|
|
38
|
+
ha="center",
|
|
39
|
+
fontsize=10,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# Set the x-axis limits and show the ticks
|
|
43
|
+
ax.set_xlim(-0.5, x - spacing)
|
|
44
|
+
ax.set_xticks(np.arange(0, x, square_size + spacing))
|
|
45
|
+
ax.set_xticklabels(np.arange(1, ncolors + 1), fontsize=8)
|
|
46
|
+
ax.set_xticklabels([]) # Remove the x-tick labels
|
|
47
|
+
|
|
48
|
+
# Set the y-axis ticks and labels
|
|
49
|
+
ax.set_yticks([])
|
|
50
|
+
ax.set_yticklabels([])
|
|
51
|
+
|
|
52
|
+
# Remove the borders around the plot
|
|
53
|
+
ax.spines["top"].set_visible(False)
|
|
54
|
+
ax.spines["bottom"].set_visible(False)
|
|
55
|
+
ax.spines["left"].set_visible(False)
|
|
56
|
+
ax.spines["right"].set_visible(False)
|
|
57
|
+
|
|
58
|
+
# Remove the x-label and y-label
|
|
59
|
+
ax.set_xlabel("")
|
|
60
|
+
ax.set_ylabel("")
|
|
61
|
+
|
|
62
|
+
if add_black_border:
|
|
63
|
+
# Add a black border rectangle
|
|
64
|
+
border_rect = patches.Rectangle(
|
|
65
|
+
(0, 0),
|
|
66
|
+
x - spacing,
|
|
67
|
+
square_size,
|
|
68
|
+
edgecolor="black",
|
|
69
|
+
facecolor="none",
|
|
70
|
+
linewidth=1,
|
|
71
|
+
)
|
|
72
|
+
ax.add_patch(border_rect)
|
|
73
|
+
|
|
74
|
+
# Adjust the padding and remove extra whitespace
|
|
75
|
+
plt.margins(0)
|
|
76
|
+
plt.gca().set_axis_off()
|
|
77
|
+
plt.subplots_adjust(left=0.05, right=0.95)
|
|
78
|
+
|
|
79
|
+
plt.savefig(fig_name, bbox_inches="tight")
|
|
80
|
+
|
|
81
|
+
return fig
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
figs = []
|
|
85
|
+
|
|
86
|
+
ncolors = 7
|
|
87
|
+
|
|
88
|
+
ncolors_ggplot = 7 if ncolors > 7 else ncolors
|
|
89
|
+
colors = get_color_palette("ggplot", ncolors_ggplot)
|
|
90
|
+
figs.append(
|
|
91
|
+
create_palette_plot(colors, fig_name="usage_style_cycle.svg", add_text=True)
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
colors = get_color_palette("cubehelix", ncolors)
|
|
95
|
+
figs.append(create_palette_plot(colors, fig_name="usage_cubehelix.svg"))
|
|
96
|
+
|
|
97
|
+
cmap_list = ["viridis", "coolwarm", "YlGnBu_r"]
|
|
98
|
+
for cmap_name in cmap_list:
|
|
99
|
+
colors = get_color_palette(cmap_name, ncolors)
|
|
100
|
+
figs.append(create_palette_plot(colors, fig_name=f"usage_{cmap_name}_palette.svg"))
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Default style: matplotlib vs plothist
|
|
3
|
+
=====================================
|
|
4
|
+
|
|
5
|
+
Illustration of the difference between matplotlib and plothist default styles.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import numpy as np
|
|
10
|
+
from plothist_utils import get_dummy_data
|
|
11
|
+
|
|
12
|
+
df = get_dummy_data()
|
|
13
|
+
|
|
14
|
+
figs = []
|
|
15
|
+
|
|
16
|
+
for style in ["matplotlib", "plothist"]:
|
|
17
|
+
if style == "matplotlib":
|
|
18
|
+
plt.style.use("default")
|
|
19
|
+
plt.rcParams["font.family"] = "DejaVu Sans"
|
|
20
|
+
else:
|
|
21
|
+
# No need to set the style if we use plothist, just importing it is enough
|
|
22
|
+
# Here we set the style because the matplotlib style was set before
|
|
23
|
+
from plothist import set_style
|
|
24
|
+
|
|
25
|
+
set_style("default")
|
|
26
|
+
|
|
27
|
+
# Create a figure with subplots
|
|
28
|
+
fig, (ax1, ax2) = plt.subplots(
|
|
29
|
+
2, 1, figsize=(6, 5.4), sharex=True, gridspec_kw={"height_ratios": [4, 1]}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Plot histograms in the first subplot (ax1)
|
|
33
|
+
hist_0, bins, _ = ax1.hist(
|
|
34
|
+
df["variable_0"], bins=20, histtype="step", linewidth=1.2, label="h1"
|
|
35
|
+
)
|
|
36
|
+
h1 = ax1.hist(
|
|
37
|
+
df["variable_1"], bins=bins, histtype="step", linewidth=1.2, label="h2"
|
|
38
|
+
)
|
|
39
|
+
ax1.set_ylabel("Entries")
|
|
40
|
+
ax1.legend()
|
|
41
|
+
|
|
42
|
+
# Calculate the ratio of histogram values and plot in the second subplot (ax2)
|
|
43
|
+
with np.errstate(divide="ignore", invalid="ignore"):
|
|
44
|
+
ratio = hist_0 / h1[0] # Divide bin values of variable_0 by variable_1
|
|
45
|
+
bin_centers = 0.5 * (bins[:-1] + bins[1:]) # Calculate bin centers
|
|
46
|
+
|
|
47
|
+
# Create fake error bars for the ratio
|
|
48
|
+
ax2.plot(bin_centers, ratio, marker="|", linestyle="", markersize=15, color="black")
|
|
49
|
+
ax2.plot(bin_centers, ratio, marker="o", linestyle="", markersize=4, color="black")
|
|
50
|
+
|
|
51
|
+
ax2.axhline(y=1, color="black", linestyle="--", linewidth=0.8)
|
|
52
|
+
ax2.set_xlabel("Variable")
|
|
53
|
+
ax2.set_ylabel("Ratio")
|
|
54
|
+
|
|
55
|
+
ax1.set_xlim(-10, 10)
|
|
56
|
+
ax2.set_xlim(-10, 10)
|
|
57
|
+
ax2.set_ylim(0, 2)
|
|
58
|
+
|
|
59
|
+
fig.subplots_adjust(hspace=0.15)
|
|
60
|
+
|
|
61
|
+
fig.savefig(f"{style}_example.svg", bbox_inches="tight")
|
|
62
|
+
|
|
63
|
+
figs.append(fig)
|
|
@@ -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")
|
plothist/histogramming.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import warnings
|
|
4
|
+
from collections.abc import Sequence
|
|
5
|
+
from typing import Callable
|
|
4
6
|
|
|
5
7
|
import boost_histogram as bh
|
|
6
8
|
import numpy as np
|
|
@@ -15,17 +17,23 @@ class RangeWarning(Warning):
|
|
|
15
17
|
warnings.filterwarnings("always", category=RangeWarning)
|
|
16
18
|
|
|
17
19
|
|
|
18
|
-
def create_axis(
|
|
20
|
+
def create_axis(
|
|
21
|
+
bins: int | list[float] | np.ndarray,
|
|
22
|
+
range: tuple[float | str, float | str] | None = None,
|
|
23
|
+
data: list[float] | np.ndarray | None = None,
|
|
24
|
+
overflow: bool = False,
|
|
25
|
+
underflow: bool = False,
|
|
26
|
+
) -> bh.axis.Regular | bh.axis.Variable:
|
|
19
27
|
"""
|
|
20
28
|
Create an axis object for histogram binning based on the input data and parameters.
|
|
21
29
|
|
|
22
30
|
Parameters
|
|
23
31
|
----------
|
|
24
|
-
bins : int or
|
|
32
|
+
bins : int or list[float]
|
|
25
33
|
The number of bins or bin edges for the axis.
|
|
26
|
-
range : None or tuple, optional
|
|
27
|
-
The range of the axis. If None, it will be determined based on the data.
|
|
28
|
-
data :
|
|
34
|
+
range : None or tuple[float | str, float | str], optional
|
|
35
|
+
The range of the axis. If None, it will be determined based on the data. Default is None.
|
|
36
|
+
data : list[float] or np.ndarray, optional
|
|
29
37
|
The input data for determining the axis range. Default is None.
|
|
30
38
|
overflow : bool, optional
|
|
31
39
|
Whether to include an overflow bin. If False, the upper edge of the last bin is inclusive. Default is False.
|
|
@@ -52,19 +60,16 @@ def create_axis(bins, range=None, data=None, overflow=False, underflow=False):
|
|
|
52
60
|
if data is None:
|
|
53
61
|
data = np.array([])
|
|
54
62
|
|
|
55
|
-
|
|
56
|
-
N = len(bins)
|
|
57
|
-
except TypeError:
|
|
58
|
-
N = 1
|
|
63
|
+
is_variable_bins = isinstance(bins, (list, np.ndarray))
|
|
59
64
|
|
|
60
|
-
if
|
|
65
|
+
if is_variable_bins:
|
|
61
66
|
if range is not None:
|
|
62
67
|
warnings.warn(
|
|
63
68
|
f"Custom binning -> ignore supplied range ({range}).", stacklevel=2
|
|
64
69
|
)
|
|
65
70
|
return bh.axis.Variable(bins, underflow=underflow, overflow=overflow)
|
|
66
71
|
|
|
67
|
-
if bins <= 0:
|
|
72
|
+
if isinstance(bins, int) and bins <= 0:
|
|
68
73
|
raise ValueError(f"Number of bins must be positive, but got {bins}.")
|
|
69
74
|
|
|
70
75
|
# Inspired from np.histograms
|
|
@@ -74,8 +79,8 @@ def create_axis(bins, range=None, data=None, overflow=False, underflow=False):
|
|
|
74
79
|
"Cannot use 'min'/'max' range values with empty data. "
|
|
75
80
|
"Please supply a range or provide data."
|
|
76
81
|
)
|
|
77
|
-
x_min = min(data) if range[0] == "min" else range[0]
|
|
78
|
-
x_max = max(data) if range[1] == "max" else range[1]
|
|
82
|
+
x_min = min(data) if range[0] == "min" else float(range[0])
|
|
83
|
+
x_max = max(data) if range[1] == "max" else float(range[1])
|
|
79
84
|
if x_min > x_max:
|
|
80
85
|
raise ValueError(
|
|
81
86
|
f"Range of [{x_min}, {x_max}] is not valid. Max must be larger than min."
|
|
@@ -84,10 +89,10 @@ def create_axis(bins, range=None, data=None, overflow=False, underflow=False):
|
|
|
84
89
|
raise ValueError(f"Range of [{x_min}, {x_max}] is not finite.")
|
|
85
90
|
elif len(data) == 0:
|
|
86
91
|
# handle empty arrays. Can't determine range, so use 0-1.
|
|
87
|
-
x_min, x_max = 0, 1
|
|
92
|
+
x_min, x_max = 0.0, 1.0
|
|
88
93
|
else:
|
|
89
|
-
x_min = min(data)
|
|
90
|
-
x_max = max(data)
|
|
94
|
+
x_min = float(min(data))
|
|
95
|
+
x_max = float(max(data))
|
|
91
96
|
if not (np.isfinite(x_min) and np.isfinite(x_max)):
|
|
92
97
|
raise ValueError(f"Autodetected range of [{x_min}, {x_max}] is not finite.")
|
|
93
98
|
|
|
@@ -99,30 +104,35 @@ def create_axis(bins, range=None, data=None, overflow=False, underflow=False):
|
|
|
99
104
|
return bh.axis.Regular(bins, x_min, x_max, underflow=underflow, overflow=overflow)
|
|
100
105
|
|
|
101
106
|
|
|
102
|
-
def make_hist(
|
|
107
|
+
def make_hist(
|
|
108
|
+
data: list[float] | np.ndarray | None = None,
|
|
109
|
+
bins: int | list[float] | np.ndarray = 50,
|
|
110
|
+
range: tuple[float | str, float | str] | None = None,
|
|
111
|
+
weights: float | list[float] | np.ndarray = 1,
|
|
112
|
+
) -> bh.Histogram:
|
|
103
113
|
"""
|
|
104
114
|
Create a histogram object and fill it with the provided data.
|
|
105
115
|
|
|
106
116
|
Parameters
|
|
107
117
|
----------
|
|
108
|
-
data :
|
|
118
|
+
data : list[float] or np.ndarray, optional
|
|
109
119
|
1D array-like data used to fill the histogram (default is None).
|
|
110
120
|
If None is provided, an empty histogram is returned.
|
|
111
|
-
bins : int or
|
|
121
|
+
bins : int or list[float], optional
|
|
112
122
|
Binning specification for the histogram (default is 50).
|
|
113
123
|
If an integer, it represents the number of bins.
|
|
114
|
-
If a
|
|
115
|
-
range : tuple, optional
|
|
124
|
+
If a list, it should be the explicit list of all bin edges.
|
|
125
|
+
range : tuple[float | str, float | str], optional
|
|
116
126
|
The range of values to consider for the histogram bins (default is None).
|
|
117
127
|
If None, the range is determined from the data.
|
|
118
|
-
weights : float or
|
|
128
|
+
weights : float or list[float] or np.ndarray, optional
|
|
119
129
|
Weight(s) to apply to the data points (default is 1).
|
|
120
130
|
If a float, a single weight is applied to all data points.
|
|
121
131
|
If an array-like, weights are applied element-wise.
|
|
122
132
|
|
|
123
133
|
Returns
|
|
124
134
|
-------
|
|
125
|
-
histogram :
|
|
135
|
+
histogram : bh.Histogram
|
|
126
136
|
The filled histogram object.
|
|
127
137
|
|
|
128
138
|
Warns
|
|
@@ -160,31 +170,38 @@ def make_hist(data=None, bins=50, range=None, weights=1):
|
|
|
160
170
|
return h
|
|
161
171
|
|
|
162
172
|
|
|
163
|
-
def make_2d_hist(
|
|
173
|
+
def make_2d_hist(
|
|
174
|
+
data: list[np.ndarray] | np.ndarray | None = None,
|
|
175
|
+
bins: Sequence[int | Sequence[float]] | None = None,
|
|
176
|
+
range: tuple[
|
|
177
|
+
tuple[float | str, float | str] | None, tuple[float | str, float | str] | None
|
|
178
|
+
] = (None, None),
|
|
179
|
+
weights: float | list[float] | np.ndarray = 1,
|
|
180
|
+
) -> bh.Histogram:
|
|
164
181
|
"""
|
|
165
182
|
Create a 2D histogram object and fill it with the provided data.
|
|
166
183
|
|
|
167
184
|
Parameters
|
|
168
185
|
----------
|
|
169
|
-
data :
|
|
186
|
+
data : list[np.ndarray] or np.ndarray, optional
|
|
170
187
|
2D array-like data used to fill the histogram (default is None).
|
|
171
188
|
If None is provided, an empty histogram is returned.
|
|
172
|
-
bins :
|
|
173
|
-
Binning specification for each dimension of the histogram (
|
|
189
|
+
bins : Sequence[int | Sequence[float]], optional
|
|
190
|
+
Binning specification for each dimension of the histogram (if None, it will be set to [50, 50]).
|
|
174
191
|
Each element of the tuple represents the number of bins for the corresponding dimension.
|
|
175
192
|
Also support explicit bin edges specification (for non-constant bin size).
|
|
176
|
-
range : tuple, optional
|
|
193
|
+
range : tuple[tuple[float | str, float | str] | None, tuple[float | str, float | str] | None], optional
|
|
177
194
|
The range of values to consider for each dimension of the histogram (default is (None, None)).
|
|
178
195
|
If None, the range is determined from the data for that dimension.
|
|
179
196
|
The tuple should have the same length as the data.
|
|
180
|
-
weights : float or
|
|
197
|
+
weights : float or list[float] or np.ndarray, optional
|
|
181
198
|
Weight(s) to apply to the data points (default is 1).
|
|
182
199
|
If a float, a single weight is applied to all data points.
|
|
183
200
|
If an array-like, weights are applied element-wise.
|
|
184
201
|
|
|
185
202
|
Returns
|
|
186
203
|
-------
|
|
187
|
-
histogram :
|
|
204
|
+
histogram : bh.Histogram
|
|
188
205
|
The filled 2D histogram object.
|
|
189
206
|
|
|
190
207
|
Raises
|
|
@@ -203,6 +220,8 @@ def make_2d_hist(data=None, bins=(10, 10), range=(None, None), weights=1):
|
|
|
203
220
|
raise ValueError("data should have two components, x and y")
|
|
204
221
|
if len(data[0]) != len(data[1]):
|
|
205
222
|
raise ValueError("x and y must have the same length.")
|
|
223
|
+
if bins is None:
|
|
224
|
+
bins = [50, 50]
|
|
206
225
|
|
|
207
226
|
x_axis = create_axis(bins[0], range[0], data[0])
|
|
208
227
|
y_axis = create_axis(bins[1], range[1], data[1])
|
|
@@ -236,13 +255,13 @@ def make_2d_hist(data=None, bins=(10, 10), range=(None, None), weights=1):
|
|
|
236
255
|
return h
|
|
237
256
|
|
|
238
257
|
|
|
239
|
-
def _check_counting_histogram(hist):
|
|
258
|
+
def _check_counting_histogram(hist: bh.Histogram) -> None:
|
|
240
259
|
"""
|
|
241
260
|
Check that the histogram is a counting histogram.
|
|
242
261
|
|
|
243
262
|
Parameters
|
|
244
263
|
----------
|
|
245
|
-
hist :
|
|
264
|
+
hist : bh.Histogram
|
|
246
265
|
The histogram to check.
|
|
247
266
|
|
|
248
267
|
Raise
|
|
@@ -257,7 +276,9 @@ def _check_counting_histogram(hist):
|
|
|
257
276
|
)
|
|
258
277
|
|
|
259
278
|
|
|
260
|
-
def _make_hist_from_function(
|
|
279
|
+
def _make_hist_from_function(
|
|
280
|
+
func: Callable[[np.ndarray], np.ndarray], ref_hist: bh.Histogram
|
|
281
|
+
) -> bh.Histogram:
|
|
261
282
|
"""
|
|
262
283
|
Create a histogram from a function and a reference histogram.
|
|
263
284
|
The returned histogram has the same binning as the reference histogram and
|
|
@@ -265,14 +286,14 @@ def _make_hist_from_function(func, ref_hist):
|
|
|
265
286
|
|
|
266
287
|
Parameters
|
|
267
288
|
----------
|
|
268
|
-
func :
|
|
289
|
+
func : Callable[[np.ndarray], np.ndarray]
|
|
269
290
|
1D function. The function should support vectorization (i.e. accept a numpy array as input).
|
|
270
|
-
ref_hist :
|
|
291
|
+
ref_hist : bh.Histogram
|
|
271
292
|
The reference 1D histogram to use for the binning.
|
|
272
293
|
|
|
273
294
|
Returns
|
|
274
295
|
-------
|
|
275
|
-
hist :
|
|
296
|
+
hist : bh.Histogram
|
|
276
297
|
The histogram filled with the function.
|
|
277
298
|
|
|
278
299
|
Raises
|
|
@@ -290,18 +311,18 @@ def _make_hist_from_function(func, ref_hist):
|
|
|
290
311
|
return hist
|
|
291
312
|
|
|
292
313
|
|
|
293
|
-
def flatten_2d_hist(hist):
|
|
314
|
+
def flatten_2d_hist(hist: bh.Histogram) -> bh.Histogram:
|
|
294
315
|
"""
|
|
295
316
|
Flatten a 2D histogram into a 1D histogram.
|
|
296
317
|
|
|
297
318
|
Parameters
|
|
298
319
|
----------
|
|
299
|
-
hist : Histogram
|
|
320
|
+
hist : bh.Histogram
|
|
300
321
|
The 2D histogram to be flattened.
|
|
301
322
|
|
|
302
323
|
Returns
|
|
303
324
|
-------
|
|
304
|
-
Histogram
|
|
325
|
+
bh.Histogram
|
|
305
326
|
The flattened 1D histogram.
|
|
306
327
|
|
|
307
328
|
Raises
|