Skip to content

Commit a34c675

Browse files
committed
feat: to_numpy matching uproot, with mode
1 parent 146150a commit a34c675

File tree

3 files changed

+52
-7
lines changed

3 files changed

+52
-7
lines changed

src/boost_histogram/_internal/hist.py

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -552,18 +552,25 @@ def _compute_commonindex(self, index):
552552

553553
return indexes
554554

555-
@inject_signature("self, flow=False, *, dd=False")
555+
@inject_signature("self, flow=False, *, dd=False, mode='numpy'")
556556
def to_numpy(self, flow=False, **kwargs):
557557
"""
558-
Convert to a Numpy style tuple of return arrays.
558+
Convert to a Numpy style tuple of return arrays. Edges are converted
559+
to exactly match NumPy standards, with upper edge inclusive, unlike
560+
boost-histogram, where upper edge is exclusive.
559561
560562
Parameters
561563
----------
562-
563564
flow : bool = False
564565
Include the flow bins.
565566
dd : bool = False
566-
Use the histogramdd return syntax, where the edges are in a tuple
567+
Use the histogramdd return syntax, where the edges are in a tuple.
568+
Otherwise, this is the histogram/histogram2d return style.
569+
mode : Literal["numpy", "view"] = "numpy"
570+
The behavior for the return value. "numpy" will return the NumPy
571+
array of the values only regardless of the storage (which is all
572+
NumPy's histogram function can do). "view" will leave the
573+
boost-histogram view of the storage untouched.
567574
568575
Return
569576
------
@@ -575,13 +582,23 @@ def to_numpy(self, flow=False, **kwargs):
575582

576583
with KWArgs(kwargs) as kw:
577584
dd = kw.optional("dd", False)
585+
mode = kw.optional("mode", "numpy")
578586

587+
# Python 3+ would be simpler
579588
return_tuple = self._hist.to_numpy(flow)
589+
hist = return_tuple[0]
590+
591+
if mode == "numpy":
592+
hist = self.values(flow=flow)
593+
elif mode == "view":
594+
hist = self.view(flow=flow)
595+
else:
596+
raise KeyError("Invalid mode")
580597

581598
if dd:
582-
return return_tuple[0], return_tuple[1:]
599+
return hist, return_tuple[1:]
583600
else:
584-
return return_tuple
601+
return (hist,) + return_tuple[1:]
585602

586603
@inject_signature("self, *, deep=True")
587604
def copy(self, **kwargs):

src/boost_histogram/numpy.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,10 @@ def histogramdd(
7979
density = hist.view() / hist.sum() / areas
8080
return (density, hist.to_numpy()[1:])
8181

82-
return hist if bh_cls is not None else hist.to_numpy(dd=True)
82+
# Note: this is mode="view" since users have to ask explicilty for special
83+
# storages, so mode="numpy" would throw away part of what they are asking
84+
# for. Users can use a histogram return type if they need mode="numpy".
85+
return hist if bh_cls is not None else hist.to_numpy(dd=True, mode="view")
8386

8487

8588
@_inject_signature(

tests/test_internal_histogram.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,31 @@ def test_numpy_dd():
135135
assert_array_equal(x1, x2)
136136
assert_array_equal(y1, y2)
137137

138+
def test_numpy_weights():
139+
h = bh.Histogram(
140+
bh.axis.Regular(10, 0, 1), bh.axis.Regular(5, 0, 1), storage=bh.storage.Weight()
141+
)
142+
143+
for i in range(10):
144+
for j in range(5):
145+
x, y = h.axes[0].centers[i], h.axes[1].centers[j]
146+
v = i + j * 10 + 1
147+
h.fill([x] * v, [y] * v)
148+
149+
h2, x2, y2 = h.to_numpy(mode="numpy")
150+
h1, (x1, y1) = h.to_numpy(dd=True, mode="numpy")
151+
152+
assert_array_equal(h1, h2)
153+
assert_array_equal(x1, x2)
154+
assert_array_equal(y1, y2)
155+
156+
h1, (x1, y1) = h.to_numpy(dd=True, mode="numpy")
157+
h2, x2, y2 = h.to_numpy(mode="view")
158+
159+
assert_array_equal(h1, h2.value)
160+
assert_array_equal(x1, x2)
161+
assert_array_equal(y1, y2)
162+
138163

139164
def test_numpy_flow():
140165
h = bh.Histogram(

0 commit comments

Comments
 (0)