Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/hist/basehist.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,8 +460,8 @@ def _loc_shortcut(self, x: Any, ax_id: str | int | None = None) -> Any:
return [self._loc_shortcut(each) for each in x]
if isinstance(x, slice):
return slice(
self._loc_shortcut(x.start),
self._loc_shortcut(x.stop),
self._loc_shortcut(x.start, ax_id),
self._loc_shortcut(x.stop, ax_id),
self._step_shortcut(x.step),
)
if isinstance(x, complex):
Expand All @@ -471,6 +471,16 @@ def _loc_shortcut(self, x: Any, ax_id: str | int | None = None) -> Any:
return bh.loc(x.imag, int(x.real))
if isinstance(x, str):
return bh.loc(x)
# Normalize plain negative integers relative to the axis length, like
# Python/NumPy indexing. Complex (by-value) indices, bools, and UHI tags
# are intentionally left untouched (#559).
if (
ax_id is not None
and isinstance(x, (int, np.integer))
and not isinstance(x, bool)
and x < 0
):
return int(x) + len(self.axes[ax_id])
return x

@staticmethod
Expand Down
31 changes: 31 additions & 0 deletions tests/test_general.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,37 @@ def test_general_index_access():
h[0:10:20j, 0:5:10j, "hello", False, 5]


def test_negative_index_access():
"""
Negative plain-int indices and slice bounds should be normalized relative
to the number of bins on the axis, like Python/NumPy semantics (#559).
"""

h = Hist(axis.Regular(10, 0, 10, name="x")).fill(np.linspace(0, 9, 10, dtype=float))

# Bare integer index
assert h[-1] == h[9]
assert h[-2] == h[8]

# Slice bounds (positional)
assert h[1:-2] == h[1:8]
assert h[-3:-1] == h[7:9]
assert h[-3:] == h[7:]
assert h[:-2] == h[:8]

# Named / dict indexing goes through the same path
assert h[{"x": -2}] == h[{"x": 8}]
assert h[{"x": slice(1, -2)}] == h[{"x": slice(1, 8)}]

# Two-axis case to confirm per-axis normalization uses the right length
h2 = Hist(
axis.Regular(10, 0, 10, name="x"),
axis.Regular(4, 0, 4, name="y"),
)
assert h2[1:-2, :].axes[0].size == h2[1:8, :].axes[0].size
assert h2[:, -1] == h2[:, 3]


class TestGeneralStorageProxy:
"""
Test general storage proxy suite -- whether Hist storage proxy \
Expand Down