Skip to content

Commit a17e92f

Browse files
authored
Keyword numpy (#143)
* Add bhistogram*, remove debug printout * Use keyword arguments instead
1 parent a9dc4a5 commit a17e92f

File tree

7 files changed

+85
-42
lines changed

7 files changed

+85
-42
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Version 0.5
2+
3+
First beta release and beginning of the changelog.
4+
5+
#### Known issues:
6+
7+
* Unlimited storage does not support pickling or classic multiprocessing
8+
* Some non-simple storages do not support some forms of access, like `.view`

README.md

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,11 @@ For the moment, you need to uninstall and reinstall to ensure you have the lates
2626

2727
## Usage
2828

29-
This is a suggested example of usage.
3029

3130
```python
3231
import boost_histogram as bh
3332

34-
# Compose axis however you like
33+
# Compose axis however you like; this is a 2D histogram
3534
hist = bh.histogram(bh.axis.regular(2, 0, 1),
3635
bh.axis.regular(4, 0.0, 1.0))
3736

@@ -53,21 +52,25 @@ counts = hist.view()
5352
* `bh.axis.regular_pow(n, start, stop, power)`: Regularly spaced value to some `power`
5453
* `bh.axis.integer(start, stop, underflow=True, overflow=True, growth=False)`: Special high-speed version of `regular` for evenly spaced bins of width 1
5554
* `bh.axis.variable([start, edge1, edge2, ..., stop], underflow=True, overflow=True)`: Uneven bin spacing
56-
* `bh.axis.category([...], growth=False)`: Integer or (WIP) string categories
55+
* `bh.axis.category([...], growth=False)`: Integer or string categories
5756
* Axis features:
57+
* `.index(values)`: The index at a point (or points) on the axis
58+
* `.value(indexes)`: The value for a fractional bin in the axis
59+
* `.bin(i)`: The bin given an integer index
60+
* `.options`: The options the axis was created with
61+
* `.metadata`: Anything a user wants to store
62+
* `.size`: The number of bins (not including under/overflow)
63+
* `.extent`: The number of bins (including under/overflow)
5864
* `.bin(i)`: The bin or a bin view for continuous axis types
5965
* `.lower()`: The lower value
6066
* `.upper()`: The upper value
6167
* `.center()`: The center value
6268
* `.width()`: The bin width
63-
* `.bins()`: A list of bins or bin views
64-
* `.size()`: The number of bins (not including under/overflow)
65-
* `.size(flow=True)`: The number of bins (including under/overflow)
6669
* `.options()`: The options set on the axis (`bh.axis.options` bitfields)
67-
* `.edges(flow=False)`: The N+1 bin edges (if continuous)
68-
* `.centers(flow=False)`: The N bin centers (if continuous)
69-
* `.index(values)`: The index at a point (or points) on the axis
70-
* `.value(index)`: The value for a fractional bin in the axis
70+
* `.edges`: The N+1 bin edges (if continuous)
71+
* `.centers`: The N bin centers (if continuous)
72+
* `.widths`: The N bin widths
73+
7174
* Many storage types
7275
* `bh.storage.int`: 64 bit unsigned integers for high performance and useful view access
7376
* `bh.storage.double`: Doubles for weighted values
@@ -82,30 +85,29 @@ counts = hist.view()
8285
* `bh.accumulator.sum`: High accuracy sum (Neumaier)
8386
* `bh.accumulator.mean`: Running count, mean, and variance (Welfords's incremental algorithm)
8487
* Histogram operations
85-
* `.fill(arr, ..., weight=...)` Fill with N arrays or single values
86-
* `(a, b, ...)`: Fill with arrays or single values
87-
* `+`: Add two histograms
88-
* `.rank()`: The number of dimensions
89-
* `.size()`: The number of bins (include under/overflow bins)
88+
* `h.fill(arr, ..., weight=...)` Fill with N arrays or single values
89+
* `h.rank`: The number of dimensions
90+
* `h.size or len(h)`: The number of bins
9091
* `.reset()`: Set counters to 0
92+
* `+`: Add two histograms
9193
* `*=`: Multiply by a scaler (not all storages) (`hist * scalar` and `scalar * hist` supported too)
9294
* `/=`: Divide by a scaler (not all storages) (`hist / scalar` supported too)
9395
* `.to_numpy(flow=False)`: Convert to a numpy style tuple (with or without under/overflow bins)
9496
* `.view(flow=False)`: Get a view on the bin contents (with or without under/overflow bins)
95-
* `np.asarray(...)`: Get a view on the bin contents with under/overflow bins
9697
* `.axis(i)`: Get the `i`th axis
97-
* `.at(i, j, ...)`: Get the bin contents as a location
98-
* `.sum()`: The total count of all bins
98+
* `.sum(flow=False)`: The total count of all bins
9999
* `.project(ax1, ax2, ...)`: Project down to listed axis (numbers)
100100
* `.reduce(ax, reduce_option, ...)`: shrink, rebin, or slice, or any combination
101+
<!--
101102
* `.indexed(flow=False)`: Iterate over the bins with a special "indexed" iterator
102103
* `ind.content`: The contents of a bin (set or get)
103104
* `ind.bins()`: A list of bins
104105
* `ind.centers()`: The centers of each bin
105106
* `ind.indices()`: A list of indices
107+
-->
108+
* Indexing - Supports the Unified Histogram Indexing (UHI) proposal
106109
* Details
107110
* Use `bh.histogram(..., storage=...)` to make a histogram (there are several different types)
108-
* Several common combinations are optimized, such as regular axes + int storage
109111

110112

111113
## Supported platforms

boost_histogram/__init__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
from ._hist import histogram
44

5-
from . import axis, storage, accumulators, algorithm
6-
7-
# The numpy module is not imported yet - waiting until it is stable
5+
from . import axis, storage, accumulators, algorithm, numpy
86

97
from .utils import loc, rebin, project, indexed, underflow, overflow
108

boost_histogram/_hist.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,23 @@ def _make_histogram(*args, **kwargs):
2727
Make a histogram with an optional storage (keyword only).
2828
"""
2929

30+
# Keyword only trick (change when Python2 is dropped)
3031
with KWArgs(kwargs) as k:
3132
storage = k.optional("storage", _core.storage.double())
3233

3334
# Initialize storage if user has not
3435
if isinstance(storage, type):
3536
storage = storage()
3637

38+
# Allow a tuple to represent a regular axis
3739
args = [_arg_shortcut(arg) for arg in args]
3840

3941
if len(args) > _core.hist._axes_limit:
4042
raise IndexError(
4143
"Too many axes, must be less than {}".format(_core.hist._axes_limit)
4244
)
4345

46+
# Check all available histograms, and if the storage matches, return that one
4447
for h in _histograms:
4548
if isinstance(storage, h._storage_type):
4649
return h(args, storage)

boost_histogram/numpy.py

Lines changed: 50 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,26 @@
11
from __future__ import absolute_import, division, print_function
22

3+
del absolute_import, division, print_function # hides these from IPython
4+
35
from . import axis as _axis
46
from . import _hist as _hist
57
from . import core as _core
68

7-
import warnings
9+
from .utils import KWArgs as _KWArgs
10+
11+
import numpy as _np
12+
13+
__all__ = ("histogram", "histogram2d", "histogramdd")
814

9-
warnings.warn(
10-
"The boost_histogram.numpy module is provisional and may change in future releases",
11-
FutureWarning,
12-
)
1315

16+
def histogramdd(
17+
a, bins=10, range=None, normed=None, weights=None, density=None, **kwargs
18+
):
19+
np = _np # Hidden to keep module clean
1420

15-
def histogramdd(a, bins=10, range=None, normed=None, weights=None, density=None):
16-
import numpy as np
21+
with _KWArgs(kwargs) as k:
22+
boost = k.optional("bh", False)
23+
storage = k.optional("bh_storage", _core.storage.double)
1724

1825
if normed is not None:
1926
raise KeyError(
@@ -24,7 +31,7 @@ def histogramdd(a, bins=10, range=None, normed=None, weights=None, density=None)
2431
"boost-histogram does not support the density keyword at the moment"
2532
)
2633

27-
# Odd design here. Oh well.
34+
# Odd numpy design here. Oh well.
2835
if isinstance(a, np.ndarray):
2936
a = a.T
3037

@@ -53,23 +60,49 @@ def histogramdd(a, bins=10, range=None, normed=None, weights=None, density=None)
5360
axs.append(_axis.variable(b))
5461

5562
if weights is None:
56-
return _hist.histogram(*axs).fill(*a)
63+
hist = _hist.histogram(*axs).fill(*a)
5764
else:
58-
return _hist.histogram(*axis).fill(*a, weight=weights)
65+
hist = _hist.histogram(*axis).fill(*a, weight=weights)
66+
67+
return hist if boost else hist.to_numpy()
68+
5969

70+
def histogram2d(
71+
x, y, bins=10, range=None, normed=None, weights=None, density=None, **kwargs
72+
):
73+
return histogramdd((x, y), bins, range, normed, weights, density, **kwargs)
6074

61-
def histogram2d(x, y, bins=10, range=None, normed=None, weights=None, density=None):
62-
return histogramdd((x, y), bins, range, normed, weights, density)
6375

76+
def histogram(
77+
a, bins=10, range=None, normed=None, weights=None, density=None, **kwargs
78+
):
79+
np = _np
6480

65-
def histogram(x, bins=10, range=None, normed=None, weights=None, density=None):
66-
import numpy as np
81+
# numpy 1d histogram returns integers in some cases
82+
if "bh_storage" not in kwargs and not (weights or normed or density):
83+
kwargs["bh_storage"] = _core.storage.int
6784

6885
if isinstance(bins, str):
6986
if tuple(int(x) for x in np.__version__.split(".")[:2]) < (1, 13):
7087
raise KeyError(
7188
"Upgrade numpy to 1.13+ to use string arguments to boost-histogram's histogram function"
7289
)
73-
bins = np.histogram_bin_edges(x, bins, range, weights)
74-
return histogramdd((x,), (bins,), (range,), normed, weights, density)
75-
# TODO: this also supports a few more tricks in Numpy, those can be added for numpy 1.13+
90+
bins = np.histogram_bin_edges(a, bins, range, weights)
91+
return histogramdd((a,), (bins,), (range,), normed, weights, density, **kwargs)
92+
93+
94+
# Process docstrings
95+
for f, n in zip(
96+
(histogram, histogram2d, histogramdd),
97+
(_np.histogram, _np.histogram2d, _np.histogramdd),
98+
):
99+
100+
H = """\
101+
Return a boost-histogram object using the same arguments as numpy's {}.
102+
This does not support density/normed yet. Two extra arguments are added: bh=True
103+
will enable object based output, and bh_storage=... lets you set the storage used.
104+
"""
105+
106+
f.__doc__ = H.format(n.__name__) + n.__doc__
107+
108+
del f, n, H

include/boost/histogram/python/regular_numpy.hpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ class regular_numpy : public bh::axis::regular<double, bh::use_default, metadata
3030
, stop_(0){};
3131

3232
boost::histogram::axis::index_type index(value_type v) const {
33-
std::cout << "stop: " << stop_ << std::endl;
3433
return v <= stop_ ? std::min(regular::index(v), size() - 1) : regular::index(v);
3534
}
3635

tests/test_numpy.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
def test_histogram1d(a, opt):
4747
v = np.array(a)
4848
h1, e1 = np.histogram(v, **opt)
49-
h2, e2 = bhnp.histogram(v, **opt).to_numpy()
49+
h2, e2 = bhnp.histogram(v, **opt)
5050

5151
np.testing.assert_array_almost_equal(e1, e2)
5252
np.testing.assert_array_equal(h1, h2)
@@ -57,7 +57,7 @@ def test_histogram2d():
5757
y = np.array([0.4, 0.5, 0.22, 0.65, 0.32, 0.01, 0.23, 1.98])
5858

5959
h1, e1x, e1y = np.histogram2d(x, y)
60-
h2, e2x, e2y = bhnp.histogram2d(x, y).to_numpy()
60+
h2, e2x, e2y = bhnp.histogram2d(x, y)
6161

6262
np.testing.assert_array_almost_equal(e1x, e2x)
6363
np.testing.assert_array_almost_equal(e1y, e2y)

0 commit comments

Comments
 (0)