Skip to content

Histogram projections and UHI fail on histograms with axes having infinite upper edges. #20077

@aapo-kossi

Description

@aapo-kossi

Check duplicate issues.

  • Checked for duplicates

Description

Infinite bin edges are useful for example in event categorizations, where for example events above a final threshold in a variable are placed in a specific category that is not accurately described as an overflow bin. However, when creating projections from histograms that include axes with an infinite upper edge, the contents of the highest bins are not filled in to the projected histogram. This behaviour is undocumented. The below reproducer uses a TH2D to demonstrate the problem, but the same applies at least to the Projection3D method of TH3 histograms.

The new ROOT UHI implementation also fails to operate on dimensions with infinite bin edges so it cannot be used as a workaround in pyROOT. The behaviour here is more clearly incorrect though, as all resulting bins end up empty instead of only the bin that extends to infinity, as is the case when using traditional ROOT methods.

I don't imagine a fix for this should take too much effort, but I am unfortunately too busy with other work to contribute a PR for this in the near future. Of course a large value could be used in place of infinity as a placeholder, but magic numbers are not nice.

Reproducer

# file repro.py
import numpy as np
import ROOT
import ROOT.uhi as uhi

def inspect(proj, full, y):
    expected = full.values()[:, y-1].sum()
    print(f"proj(y={y}) = {proj.GetBinContent(y)}, expected {expected}")

def test_ops(h):
    proj = h.ProjectionY()
    print("Original histogram")
    print(h.values())
    print("ProjectionY")
    print(proj.values())
    for y in range(1, h.GetNbinsY() + 1):
        inspect(proj, h, y)

    uhi_projection = h[uhi.sum,:]
    print("uhi projection")
    print(uhi_projection.values())

    uhi_slice = h[1,:]
    print("uhi slice")
    print(uhi_slice.values())

nx = 2
ny = 3
h = ROOT.TH2D(
    "h", "h;X;Y;Z",
    nx, np.arange(nx + 1, dtype=float),
    ny, np.arange(ny + 1, dtype=float),
)

for ix in range(1, h.GetNbinsX()+1):
    for iy in range(1, h.GetNbinsY()+1):
            val = 10*ix + iy
            h.SetBinContent(ix, iy, val)
print("Testing standard histogram")
test_ops(h)

h_inf = ROOT.TH2D(
    "h_inf", "h;X;Y;Z",
    nx, np.arange(nx + 1, dtype=float),
    ny, np.concatenate((np.arange(ny , dtype=float), np.array([np.inf]))),
)

for ix in range(1, h_inf.GetNbinsX()+1):
    for iy in range(1, h_inf.GetNbinsY()+1):
            val = 10*ix + iy
            h_inf.SetBinContent(ix, iy, val)
print("Testing histogram with infinite high bin")
test_ops(h_inf)

Example output from python repro.py:

Testing standard histogram
Original histogram
[[11. 12. 13.]
 [21. 22. 23.]]
ProjectionY
[32. 34. 36.]
proj(y=1) = 32.0, expected 32.0
proj(y=2) = 34.0, expected 34.0
proj(y=3) = 36.0, expected 36.0
uhi projection
[32. 34. 36.]
uhi slice
[21. 22. 23.]
Testing histogram with infinite high bin
Original histogram
[[11. 12. 13.]
 [21. 22. 23.]]
ProjectionY
[32. 34.  0.]
proj(y=1) = 32.0, expected 32.0
proj(y=2) = 34.0, expected 34.0
proj(y=3) = 0.0, expected 36.0
uhi projection
[0. 0. 0.]
uhi slice
[0. 0. 0.]

ROOT version

ROOT v6.36.04
Built for linuxx8664gcc on Sep 24 2025, 16:10:48
From tags/6-36-04@6-36-04
With c++ (GCC) 15.2.1 20250813
Binary directory: /usr/bin

Installation method

pre-built binaries from Arch Linux repositories and pre-built binaries from conda-forge

Operating system

Linux

Additional context

No response

Metadata

Metadata

Assignees

Type

No type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions