Skip to content

Commit 8d8f346

Browse files
feat: Add ratio plot support (#60)
* Update minimum required version of hist to v2.3.0 - c.f. scikit-hep/hist#161 * Add stack_ratio_plot to heputils.plot API - Will be revised in future release for better API * Update dev-example notebook with examples of ratio plots
1 parent 5f8aee4 commit 8d8f346

File tree

3 files changed

+153
-2
lines changed

3 files changed

+153
-2
lines changed

examples/dev-example.ipynb

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,13 @@
77
"# Development examples"
88
]
99
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"## Style setup"
15+
]
16+
},
1017
{
1118
"cell_type": "code",
1219
"execution_count": null,
@@ -28,6 +35,13 @@
2835
") # Default values"
2936
]
3037
},
38+
{
39+
"cell_type": "markdown",
40+
"metadata": {},
41+
"source": [
42+
"## Stacked histogram plots"
43+
]
44+
},
3145
{
3246
"cell_type": "code",
3347
"execution_count": null,
@@ -59,13 +73,15 @@
5973
"metadata": {},
6074
"outputs": [],
6175
"source": [
76+
"fig, ax = plt.subplots()\n",
6277
"ax = heputils.plot.stack_hist(\n",
6378
" simulation_hists,\n",
6479
" labels=labels,\n",
6580
" color=colormap,\n",
6681
" xlabel=r\"$X$ Mass [GeV]\",\n",
6782
" ylabel=\"Count\",\n",
6883
" scale_factors=scale_factors,\n",
84+
" ax=ax,\n",
6985
")\n",
7086
"ax = heputils.plot.data_hist(data_hist, ax=ax);"
7187
]
@@ -76,7 +92,7 @@
7692
"metadata": {},
7793
"outputs": [],
7894
"source": [
79-
"ax.figure.savefig(\"example_stack.png\")"
95+
"fig.savefig(\"example_stack.png\")"
8096
]
8197
},
8298
{
@@ -141,6 +157,13 @@
141157
")"
142158
]
143159
},
160+
{
161+
"cell_type": "markdown",
162+
"metadata": {},
163+
"source": [
164+
"## Shape plots"
165+
]
166+
},
144167
{
145168
"cell_type": "code",
146169
"execution_count": null,
@@ -212,6 +235,68 @@
212235
")"
213236
]
214237
},
238+
{
239+
"cell_type": "markdown",
240+
"metadata": {},
241+
"source": [
242+
"## Ratio plots"
243+
]
244+
},
245+
{
246+
"cell_type": "code",
247+
"execution_count": null,
248+
"metadata": {},
249+
"outputs": [],
250+
"source": [
251+
"fig = plt.figure()\n",
252+
"axs = heputils.plot.stack_ratio_plot(\n",
253+
" simulation_hists,\n",
254+
" data_hist=data_hist,\n",
255+
" ratio_numerator=\"data\", # \"data\", \"simulation\", or \"mc\"\n",
256+
" labels=labels,\n",
257+
" color=colormap,\n",
258+
" xlabel=r\"$X$ Mass [GeV]\",\n",
259+
" ylabel=\"Count\",\n",
260+
" rp_uncert_draw_type=\"line\", # \"line\" or \"bar\"\n",
261+
")"
262+
]
263+
},
264+
{
265+
"cell_type": "code",
266+
"execution_count": null,
267+
"metadata": {},
268+
"outputs": [],
269+
"source": [
270+
"fig.savefig(\"example_stack_ratio.png\")"
271+
]
272+
},
273+
{
274+
"cell_type": "code",
275+
"execution_count": null,
276+
"metadata": {},
277+
"outputs": [],
278+
"source": [
279+
"fig = plt.figure()\n",
280+
"axs = heputils.plot.stack_ratio_plot(\n",
281+
" simulation_hists,\n",
282+
" data_hist=data_hist,\n",
283+
" labels=labels,\n",
284+
" color=colormap,\n",
285+
" rp_ylim=[-1, 12],\n",
286+
" xlabel=r\"$X$ Mass [GeV]\",\n",
287+
" ylabel=\"Count\",\n",
288+
" logy=False,\n",
289+
" rp_uncert_draw_type=\"bar\",\n",
290+
")"
291+
]
292+
},
293+
{
294+
"cell_type": "markdown",
295+
"metadata": {},
296+
"source": [
297+
"## Jupyter repr"
298+
]
299+
},
215300
{
216301
"cell_type": "code",
217302
"execution_count": null,

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ install_requires =
3939
click>=6.0
4040
awkward>=1.0
4141
uproot>=4.0
42-
hist[plot]>=2.2.0
42+
hist[plot]>=2.3.0
4343
mplhep>=0.2.18
4444
uproot3>=3.14 # Needed until writing added in uproot4
4545

src/heputils/plot.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -495,3 +495,69 @@ def stack_hist(hists, ax=None, **kwargs):
495495
ax = draw_experiment_label(ax, max_height=max_hist, **kwargs)
496496

497497
return _plot_ax_kwargs(ax, **kwargs)
498+
499+
500+
def stack_ratio_plot(hists, **kwargs):
501+
"""
502+
Stack plot on top, ratio plot on bottom
503+
"""
504+
fig = kwargs.pop("fig", plt.gcf())
505+
506+
# Scale figure height to deal with ratio subplot being added
507+
fig_height_scale = kwargs.pop("fig_height_scale", 1.25)
508+
_fig_width, _fig_height = get_style()["figure.figsize"]
509+
fig.set_size_inches(_fig_width, _fig_height * fig_height_scale, forward=True)
510+
511+
# Setup figure subplot grid and axis dict for hist.plot_ratio
512+
grid = fig.add_gridspec(2, 1, hspace=0, height_ratios=[3, 1])
513+
main_ax = fig.add_subplot(grid[0])
514+
subplot_ax = fig.add_subplot(grid[1], sharex=main_ax)
515+
ax_dict = {"main_ax": main_ax, "ratio_ax": subplot_ax}
516+
517+
labels = kwargs.pop("labels", None)
518+
color = kwargs.pop("color", None)
519+
if color is not None and len(color) != len(hists):
520+
color = color[: len(hists)]
521+
alpha = kwargs.pop("alpha", None)
522+
semilogy = kwargs.pop("logy", True)
523+
_data_hist = kwargs.pop("data_hist", None)
524+
525+
# Setup and plot the ratio plot
526+
ratio_plot_numerator = kwargs.pop("ratio_numerator", "data")
527+
ratio_plot_kwargs = {
528+
"ax_dict": ax_dict,
529+
"rp_ylim": kwargs.pop("rp_ylim", None),
530+
"rp_uncertainty_type": kwargs.pop("rp_uncertainty_type", "poisson"),
531+
"rp_uncert_draw_type": kwargs.pop("rp_uncert_draw_type", "line"),
532+
}
533+
534+
num_hists = utils.sum_hists(hists)
535+
if ratio_plot_numerator.lower() in ["simulation", "sim", "mc"]:
536+
ratio_plot_kwargs["rp_ylabel"] = kwargs.pop("rp_ylabel", "MC/Data")
537+
num_hists.plot_ratio(_data_hist, **ratio_plot_kwargs)
538+
else:
539+
ratio_plot_kwargs["rp_ylabel"] = kwargs.pop("rp_ylabel", "Data/MC")
540+
_data_hist.plot_ratio(num_hists, **ratio_plot_kwargs)
541+
subplot_ax.set_xlabel(kwargs.get("xlabel", None))
542+
543+
# FIXME: Ugly hack to overwrite ratio plot main axis
544+
main_ax.clear()
545+
main_ax = stack_hist(
546+
hists,
547+
labels=labels,
548+
color=color,
549+
alpha=alpha,
550+
logy=semilogy,
551+
data_hist=_data_hist,
552+
ax=main_ax,
553+
**kwargs,
554+
)
555+
556+
# Hide tick marks of main_ax
557+
plt.setp(main_ax.get_xticklabels(), visible=False)
558+
# Trying to get things looking okay
559+
if semilogy and fig_height_scale < 1.25:
560+
# Ensure enough space for legend
561+
main_ax.set_ylim(top=main_ax.get_ylim()[-1] * 100)
562+
563+
return main_ax, subplot_ax

0 commit comments

Comments
 (0)