Skip to content

Commit 1cbd74a

Browse files
authored
Fixing category indexing (#298)
1 parent e3d241d commit 1cbd74a

File tree

3 files changed

+84
-5
lines changed

3 files changed

+84
-5
lines changed

CHANGELOG.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ Documentation update. Now using development branch of Boost.Histogram again.
44

55
#### Bug fixes
66

7-
* Allow slicing on flowless axes [#288][]
8-
* Sum repr fixed [#293][]
7+
* Fix sum over category axes in indexing [#298][]
8+
* Allow single category item selection [#298][]
9+
* Allow slicing on axes without flow bins [#288][]
10+
* Sum repr no longer throws error [#293][]
911

1012
#### Developer changes
1113

@@ -14,6 +16,7 @@ Documentation update. Now using development branch of Boost.Histogram again.
1416
[#288]: https://github.com/scikit-hep/boost-histogram/pull/288
1517
[#292]: https://github.com/scikit-hep/boost-histogram/pull/292
1618
[#293]: https://github.com/scikit-hep/boost-histogram/pull/293
19+
[#298]: https://github.com/scikit-hep/boost-histogram/pull/298
1720

1821
### Version 0.6.1
1922

boost_histogram/_internal/hist.py

+35-3
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ def __getitem__(self, index):
484484
slices = []
485485
zeroes_start = []
486486
zeroes_stop = []
487+
pick = dict()
487488

488489
# We could use python's sum here, but for now, a private sum is used
489490
class ext_sum:
@@ -492,7 +493,8 @@ class ext_sum:
492493
# Compute needed slices and projections
493494
for i, ind in iterator:
494495
if hasattr(ind, "__index__"):
495-
ind = slice(ind.__index__(), ind.__index__() + 1, ext_sum())
496+
pick[i] = ind.__index__()
497+
ind = slice(None)
496498

497499
elif not isinstance(ind, slice):
498500
raise IndexError(
@@ -508,6 +510,8 @@ class ext_sum:
508510
zeroes_start.append(i)
509511
if ind.stop is not None:
510512
zeroes_stop.append(i)
513+
if ind.stop is None and ind.start is None:
514+
continue
511515
elif hasattr(ind.step, "factor"):
512516
merge = ind.step.factor
513517
else:
@@ -530,11 +534,17 @@ class ext_sum:
530534
slices.append(_core.algorithm.slice_and_rebin(i, begin, end, merge))
531535

532536
reduced = self._reduce(*slices)
537+
533538
if not integrations:
534-
return self.__class__(reduced)
539+
result = self.__class__(reduced)
535540
else:
536541
projections = [i for i in range(self.rank) if i not in integrations]
537542

543+
# For each axes projected out, we need to lower "pick" since it
544+
# operates after the projection
545+
reduce_ax = lambda ax: sum(i in projections for i in range(ax))
546+
pick = {reduce_ax(ax): pick[ax] for ax in pick}
547+
538548
# Replacement for crop missing in BH
539549
for i in zeroes_start:
540550
if self.axes[i].options.underflow:
@@ -543,12 +553,34 @@ class ext_sum:
543553
if self.axes[i].options.underflow:
544554
reduced._hist._reset_row(i, reduced.axes[i].size)
545555

546-
return (
556+
result = (
547557
self.__class__(reduced.project(*projections))
548558
if projections
549559
else reduced.sum(flow=True)
550560
)
551561

562+
# Allow user to "pick" out values when mixed with slices
563+
if pick:
564+
# Sum out the axes. We will replace the contents, so "drop" would be
565+
# just as good if there was one.
566+
sresult = result[{ax: slice(None, None, ext_sum()) for ax in pick}]
567+
568+
# Make an array of selections, starting with [:,:,...,:]
569+
bins_indexes = [slice(None)] * result.rank
570+
# Now make the axis selections, remembering we will be in flow view
571+
for ax in pick:
572+
bins_indexes[ax] = pick[ax] + result._axis(ax).options.underflow
573+
574+
# If the result is a single value, we need to avoid [...]
575+
if hasattr(sresult, "rank"):
576+
sresult[...] = result.view(flow=True)[tuple(bins_indexes)]
577+
else:
578+
sresult = result.view(flow=True)[tuple(bins_indexes)]
579+
580+
result = sresult
581+
582+
return result
583+
552584
def __setitem__(self, index, value):
553585
"""
554586
There are several supported possibilities:

tests/test_histogram_indexing.py

+44
Original file line numberDiff line numberDiff line change
@@ -218,3 +218,47 @@ def test_noflow_slicing():
218218

219219
assert_array_equal(h[:, :, True].view(), vals)
220220
assert_array_equal(h[:, :, False].view(), 0)
221+
222+
223+
def test_pick_str_category():
224+
noflow = dict(underflow=False, overflow=False)
225+
226+
h = bh.Histogram(
227+
bh.axis.Regular(10, 0, 10),
228+
bh.axis.Regular(10, 0, 10, **noflow),
229+
bh.axis.StrCategory(["on", "off", "maybe"]),
230+
)
231+
232+
vals = np.arange(100).reshape(10, 10)
233+
h[:, :, bh.loc("on")] = vals
234+
235+
assert h[0, 1, bh.loc("on")] == 1
236+
assert h[1, 0, bh.loc("on")] == 10
237+
assert h[1, 1, bh.loc("on")] == 11
238+
assert h[3, 4, bh.loc("maybe")] == 0
239+
240+
assert_array_equal(h[:, :, bh.loc("on")].view(), vals)
241+
assert_array_equal(h[:, :, bh.loc("off")].view(), 0)
242+
243+
244+
def test_pick_int_category():
245+
noflow = dict(underflow=False, overflow=False)
246+
247+
h = bh.Histogram(
248+
bh.axis.Regular(10, 0, 10),
249+
bh.axis.Regular(10, 0, 10, **noflow),
250+
bh.axis.IntCategory([3, 5, 7]),
251+
)
252+
253+
vals = np.arange(100).reshape(10, 10)
254+
h[:, :, bh.loc(3)] = vals
255+
h[:, :, bh.loc(5)] = vals + 1
256+
257+
assert h[0, 1, bh.loc(3)] == 1
258+
assert h[1, 0, bh.loc(5)] == 10 + 1
259+
assert h[1, 1, bh.loc(5)] == 11 + 1
260+
assert h[3, 4, bh.loc(7)] == 0
261+
262+
assert_array_equal(h[:, :, bh.loc(3)].view(), vals)
263+
assert_array_equal(h[:, :, bh.loc(5)].view(), vals + 1)
264+
assert_array_equal(h[:, :, bh.loc(7)].view(), 0)

0 commit comments

Comments
 (0)