Histogramming, grouping, and binning#
Overview#
Histogramming (see sc.hist), grouping (using sc.groupby), and binning (see Binned data) all serve similar but slightly different purposes. Picking the optimal one of the three for a particular application may yield more natural code and better performance. Let us start by an example. Consider a table of scattered measurements:
[1]:
import numpy as np
import scipp as sc
N = 5000
values = 10 * np.random.rand(N)
table = sc.DataArray(
data=sc.array(
dims=['position'], unit=sc.units.counts, values=values, variances=values
),
coords={
'x': sc.array(dims=['position'], unit='m', values=np.random.rand(N)),
'y': sc.array(dims=['position'], unit='m', values=np.random.rand(N)),
},
)
table.values *= 1.0 / np.exp(5.0 * table.coords['x'].values)
sc.table(table['position', :5])
[1]:
Coordinates | Data | |
---|---|---|
x [m] | y [m] | [counts] |
0.873 | 0.247 | 0.101±2.823 |
0.109 | 0.792 | 3.006±2.279 |
0.054 | 0.834 | 4.577±2.450 |
0.395 | 0.909 | 0.617±2.112 |
0.750 | 0.439 | 0.157±2.582 |
We may now be interested in the total intensity (counts) as a function of 'x'
. There are three ways to do this:
[2]:
xbins = sc.linspace('x', 0, 1, num=40, unit='m')
ds = sc.Dataset(
{
'histogram': table.hist(x=xbins),
'groupby': table.groupby('x', bins=xbins).sum('position'),
'bin': table.bin(x=xbins).bins.sum(),
}
)
ds.plot()
[2]:
In the above plot we can only see a single line, since the three solutions yield exactly the same result (neglecting floating-point rounding errors):
hist
sorts data points into ‘x’ bins, summing immediately.groupby
groups by ‘x’ and then sums (on-the-fly) all data points falling in the same ‘x’ bin.bin
sorts data points into ‘x’ bins. Summing all rows in a bin yields the same result as grouping and summing directly.
So in this case we get equivalent results, but the application areas differ, as described in more detail in the following sections.
Histogramming#
scipp.hist directly sums the data and is efficient. Limitations are:
When histogramming in more than one dimension, the implementation uses
sc.bin
internally, which may be less efficient and uses more memory.Can only apply “sum” or “nansum” to accumulate into a bin. scipp.nanhist is currently implemented differently and uses
sc.bin
internally. It therefore uses more memory and may be less efficient.
We can also histogram binned data (since binning preserves the 'y'
coord), to create 2-D (or N-D) histograms:
[3]:
binned = table.bin(x=xbins)
hist = binned.hist(y=30)
hist.plot()
[3]:
[4]:
hist
[4]:
- x: 39
- y: 30
- x(x [bin-edge])float64m0.0, 0.026, ..., 0.974, 1.0
Values:
array([0. , 0.02564103, 0.05128205, 0.07692308, 0.1025641 , 0.12820513, 0.15384615, 0.17948718, 0.20512821, 0.23076923, 0.25641026, 0.28205128, 0.30769231, 0.33333333, 0.35897436, 0.38461538, 0.41025641, 0.43589744, 0.46153846, 0.48717949, 0.51282051, 0.53846154, 0.56410256, 0.58974359, 0.61538462, 0.64102564, 0.66666667, 0.69230769, 0.71794872, 0.74358974, 0.76923077, 0.79487179, 0.82051282, 0.84615385, 0.87179487, 0.8974359 , 0.92307692, 0.94871795, 0.97435897, 1. ]) - y(y [bin-edge])float64m0.001, 0.034, ..., 0.967, 1.000
Values:
array([6.06004058e-04, 3.39184081e-02, 6.72308121e-02, 1.00543216e-01, 1.33855620e-01, 1.67168024e-01, 2.00480428e-01, 2.33792832e-01, 2.67105236e-01, 3.00417640e-01, 3.33730045e-01, 3.67042449e-01, 4.00354853e-01, 4.33667257e-01, 4.66979661e-01, 5.00292065e-01, 5.33604469e-01, 5.66916873e-01, 6.00229277e-01, 6.33541681e-01, 6.66854085e-01, 7.00166489e-01, 7.33478893e-01, 7.66791297e-01, 8.00103701e-01, 8.33416105e-01, 8.66728509e-01, 9.00040913e-01, 9.33353317e-01, 9.66665721e-01, 9.99978125e-01])
- (x, y)float64counts11.543, 12.004, ..., 0.173, 0.184σ = 3.503, 3.594, ..., 4.829, 5.104
Values:
array([[1.15425936e+01, 1.20036873e+01, 2.65119958e+01, ..., 3.55322427e+01, 2.86596889e+01, 1.64545172e+01], [1.51048737e+01, 1.17587308e+01, 1.59542835e+01, ..., 1.56323445e+01, 1.71096955e+01, 2.14240240e+01], [1.51860697e+01, 1.91154263e+01, 1.40073122e+01, ..., 1.50181730e+00, 2.13457187e+01, 1.33396470e+01], ..., [1.86119419e-01, 2.70130055e-01, 2.56578066e-01, ..., 3.48401397e-01, 2.05931092e-01, 1.20652787e-01], [2.66116125e-02, 9.59008241e-02, 2.42074573e-01, ..., 2.56943303e-01, 1.68540789e-01, 1.52756593e-01], [1.16641244e-01, 2.04345102e-01, 1.80672694e-01, ..., 8.47414396e-02, 1.72689477e-01, 1.83647923e-01]])
Variances (σ²):
array([[12.26914818, 12.91825147, 27.94586173, ..., 38.66683113, 30.11059852, 17.39351218], [17.74427355, 14.28100728, 19.45239352, ..., 18.79020555, 20.76761409, 26.40602212], [21.14097296, 26.35503441, 18.50757458, ..., 1.94467103, 29.63084007, 19.11635377], ..., [19.94200467, 29.9512705 , 28.09939694, ..., 37.90221902, 22.35121893, 13.34101211], [ 3.36900383, 12.08484288, 28.88191493, ..., 31.17581055, 20.85328706, 18.19329346], [16.95991685, 28.29955625, 25.476225 , ..., 11.45182507, 23.32379799, 26.05357255]])
Another capability of hist
is to histogram a dimension that has previously been binned with a different or higher resolution, i.e. different bin edges. Compare to the plot of the initial example:
[5]:
binned = table.bin(x=xbins)
binned.hist(x=100).plot()
[5]:
Grouping#
groupby
is more flexible in terms of operations than can be applied and may be the go-to solution when a quick one-liner is required. Limitations are:
Can only group along a single dimension.
Works best for small to medium-sized data, or if data is already mostly sorted along the grouping dimension. Slow if millions of small input slices contribute to each group.
groupby
can also operate on binned data, combining bin contents by concatenation:
[6]:
binned = table.bin(x=xbins)
binned.coords['param'] = sc.array(
dims=['x'], values=(np.random.random(39) * 4).astype(np.int32)
)
grouped = binned.groupby('param').concat('x')
grouped
[6]:
- param: 4
- param(param)int32𝟙0, 1, 2, 3
Values:
array([0, 1, 2, 3], dtype=int32)
- (param)DataArrayViewbinned data [len=1011, len=1781, len=1671, len=537]
dim='position', content=DataArray( dims=(position: 5000), data=float64[counts], coords={'x':float64[m], 'y':float64[m]})
Each output bin is a combination of multiple input bins:
[7]:
grouped.values[0]
[7]:
- position: 1011
- x(position)float64m0.318, 0.321, ..., 0.931, 0.935
Values:
array([0.31839655, 0.32081917, 0.32136288, ..., 0.94838361, 0.93085796, 0.93462191]) - y(position)float64m0.021, 0.222, ..., 0.291, 0.399
Values:
array([0.02070678, 0.222265 , 0.0320649 , ..., 0.14080949, 0.29126196, 0.39896608])
- (position)float64counts1.007, 1.946, ..., 0.023, 0.053σ = 2.225, 3.111, ..., 1.538, 2.380
Values:
array([1.00725715, 1.94579746, 0.8918958 , ..., 0.03837605, 0.02252423, 0.0529419 ])
Variances (σ²):
array([4.9491393 , 9.67715288, 4.44779483, ..., 4.39996405, 2.36582364, 5.66637339])
Binning#
scipp.bin actually reorders data and meta data such that all data contributing to a bin is in a contiguous block. Binning along multiple dimensions is supported. Of the three options it is the only solution that supports modifying data in the grouped/binned layout. A variety of operations on such binned data is available. Limitations are:
Requires copying and reordering the input data and can thus become expensive.
In the above example the 'y'
information is dropped by hist
and groupby
, but bin
preserves it:
[8]:
binned = table.bin(x=xbins)
binned.values[0]
[8]:
- position: 124
- x(position)float64m0.017, 0.025, ..., 0.009, 0.021
Values:
array([0.01685874, 0.02518787, 0.0142156 , 0.01339243, 0.01646339, 0.02278218, 0.02352378, 0.00034037, 0.00702477, 0.02068409, 0.00944961, 0.02535764, 0.0118218 , 0.01939319, 0.00135103, 0.00196343, 0.02416495, 0.01153743, 0.02332407, 0.01938602, 0.02188819, 0.01924007, 0.01107418, 0.0239808 , 0.00014081, 0.01628176, 0.01211211, 0.00975945, 0.00929899, 0.01518851, 0.01055941, 0.00296156, 0.01131279, 0.0099928 , 0.00751283, 0.00992096, 0.01108614, 0.01962396, 0.01368003, 0.02551869, 0.00485827, 0.02023673, 0.02072553, 0.00423266, 0.0158078 , 0.01642123, 0.01832343, 0.01400257, 0.02073934, 0.00025806, 0.00062263, 0.00146954, 0.01293559, 0.00573 , 0.02125158, 0.02382963, 0.01591199, 0.0102639 , 0.00367393, 0.00818832, 0.01624389, 0.00362538, 0.01262956, 0.01151696, 0.00501104, 0.0034119 , 0.00660773, 0.00507033, 0.02486328, 0.0060391 , 0.01276854, 0.01448795, 0.01583236, 0.01414278, 0.0186912 , 0.01846625, 0.01068264, 0.0018909 , 0.00721084, 0.00906161, 0.007343 , 0.0158449 , 0.00375504, 0.01578966, 0.01049775, 0.01334171, 0.01873013, 0.02495505, 0.02181458, 0.01801281, 0.02005287, 0.00461474, 0.01144381, 0.01098383, 0.01213924, 0.02285467, 0.02057179, 0.01189919, 0.02001411, 0.00513284, 0.02376527, 0.0220869 , 0.01272964, 0.02088673, 0.01825329, 0.00144907, 0.02110048, 0.00250538, 0.02314298, 0.01709832, 0.00093577, 0.01177759, 0.02443336, 0.00108809, 0.02205678, 0.00469862, 0.00403082, 0.01951408, 0.02503366, 0.011557 , 0.01509656, 0.01717497, 0.00855665, 0.0207516 ]) - y(position)float64m0.039, 0.608, ..., 0.674, 0.247
Values:
array([0.03939187, 0.60759428, 0.1431645 , 0.32255379, 0.43032628, 0.82762678, 0.92050399, 0.81909886, 0.16113273, 0.48792606, 0.26053788, 0.52171054, 0.27952296, 0.89183853, 0.87886013, 0.51675003, 0.36270442, 0.06634036, 0.32298394, 0.33842464, 0.08425561, 0.82429236, 0.30740864, 0.4097557 , 0.61417598, 0.91940697, 0.92792365, 0.35289827, 0.12530755, 0.38783187, 0.85019858, 0.62386857, 0.40240113, 0.60773365, 0.81983159, 0.09269512, 0.62426749, 0.12639219, 0.9540675 , 0.64152926, 0.1112241 , 0.90767077, 0.85282529, 0.30030032, 0.89294405, 0.96292919, 0.15605446, 0.0727232 , 0.26881438, 0.49029699, 0.93406269, 0.08398393, 0.01754255, 0.57216576, 0.37881253, 0.10015858, 0.52686579, 0.48633203, 0.27357235, 0.99673981, 0.94219257, 0.18323562, 0.762249 , 0.57306824, 0.29401157, 0.5952864 , 0.96082257, 0.9798561 , 0.95644155, 0.92011357, 0.52223288, 0.82616568, 0.33787517, 0.55689919, 0.81187543, 0.29870328, 0.82884775, 0.85931846, 0.53992326, 0.59960261, 0.33821603, 0.95390557, 0.60870662, 0.39153391, 0.7214963 , 0.90914273, 0.58320066, 0.76854124, 0.12650784, 0.97737454, 0.82271707, 0.74369727, 0.08138393, 0.13403912, 0.49989943, 0.5760724 , 0.11203416, 0.01104358, 0.89755993, 0.52091672, 0.90147947, 0.43993612, 0.7922016 , 0.86970608, 0.43088532, 0.67722273, 0.78871325, 0.84274385, 0.6169371 , 0.7238037 , 0.82635718, 0.13868085, 0.658678 , 0.63317278, 0.79439298, 0.17229635, 0.40769058, 0.24088587, 0.15663371, 0.68427942, 0.67708117, 0.32914431, 0.67434995, 0.24733759])
- (position)float64counts7.063, 3.595, ..., 4.668, 0.824σ = 2.772, 2.019, ..., 2.207, 0.956
Values:
array([7.06263746, 3.59476608, 3.50493368, 7.40519859, 2.04507014, 4.18318196, 7.77628006, 0.31240218, 1.59761756, 3.31323672, 3.2115173 , 8.11964977, 6.63496207, 3.46682588, 0.31546875, 5.21773342, 2.63774815, 4.94104989, 1.15106494, 0.7151765 , 2.27369897, 0.69930863, 0.98980664, 2.70097326, 7.49553867, 7.09899566, 6.77207667, 9.02862281, 0.63462996, 4.95136879, 1.3322997 , 0.56540872, 7.59745964, 8.88523672, 9.218362 , 7.09653215, 5.26325844, 6.38330684, 2.39152323, 1.28208288, 1.71980099, 5.38448371, 2.50078536, 9.7080004 , 4.26368122, 6.85782532, 2.43643722, 0.67615535, 1.06784956, 0.66535689, 7.38330411, 8.41658524, 3.44157398, 3.78595562, 6.10558612, 3.32463591, 4.36067784, 5.43635594, 1.65474851, 4.97257322, 0.04961509, 5.02032868, 2.61938294, 4.43865634, 4.33534554, 4.99207422, 9.12949127, 5.11784652, 2.52542901, 1.29381063, 0.54713721, 1.06716031, 4.73763888, 2.84830381, 3.96383228, 1.94837098, 0.94334361, 9.42114834, 5.23006853, 0.0340602 , 1.80238975, 0.32250091, 4.06870272, 4.98706642, 6.96641003, 6.70025052, 8.99475463, 7.05079766, 4.97490376, 6.36409745, 7.78119748, 5.75883076, 4.72438818, 3.01593471, 1.21707274, 7.12565381, 7.08028098, 8.10101958, 6.79235056, 4.87829423, 0.50634542, 8.84960446, 2.84427877, 5.43279809, 5.12872884, 3.34753342, 5.23741069, 9.72210755, 5.12946666, 3.16193409, 6.15572848, 8.3664804 , 5.88891785, 2.69343965, 5.23998072, 6.82638467, 6.80728176, 8.47393426, 2.95613441, 8.7544471 , 3.65482496, 4.68934005, 4.66792603, 0.82375317])
Variances (σ²):
array([7.68378477, 4.07723182, 3.76312453, 7.91804573, 2.220537 , 4.68789228, 8.74687947, 0.31293428, 1.65472918, 3.67423894, 3.36689705, 9.21723627, 7.03897077, 3.81982796, 0.31760701, 5.26920901, 2.97650666, 5.2344667 , 1.29344331, 0.78796958, 2.53666174, 0.76992454, 1.04615886, 3.04504653, 7.5008176 , 7.70109181, 7.19487056, 9.48012133, 0.66483378, 5.34203483, 1.40453122, 0.57384348, 8.03958832, 9.34045633, 9.57122804, 7.45743154, 5.56324178, 7.04139351, 2.56082796, 1.45656285, 1.76208878, 5.95782263, 2.77383951, 9.91564309, 4.61435414, 7.44465666, 2.67020152, 0.72519145, 1.18452706, 0.66621596, 7.40632511, 8.47865548, 3.67152414, 3.89599203, 6.79007501, 3.74532473, 4.72178756, 5.72262993, 1.68542657, 5.18038343, 0.05381297, 5.11216141, 2.79012544, 4.70175804, 4.44534074, 5.07796702, 9.43615525, 5.24925098, 2.85973033, 1.33347367, 0.58320707, 1.14733398, 5.12792284, 3.05701076, 4.35213835, 2.1368331 , 0.99510055, 9.51064311, 5.42207511, 0.03563889, 1.86979427, 0.34909024, 4.1458151 , 5.39674591, 7.34183491, 7.16245978, 9.87782402, 7.98780487, 5.54822983, 6.96387777, 8.60182674, 5.89325318, 5.00259679, 3.18619989, 1.29323237, 7.98827493, 7.84732377, 8.59762404, 7.50723796, 5.00511207, 0.57023322, 9.88291234, 3.03119734, 6.03084987, 5.61883462, 3.37187563, 5.82017102, 9.84466149, 5.7587277 , 3.44414426, 6.18459769, 8.87396081, 6.65413908, 2.70813305, 5.85093652, 6.98865638, 6.94586812, 9.34241991, 3.35030288, 9.27522426, 3.94138035, 5.10983302, 4.8719688 , 0.9138157 ])
If we omit the call to bins.sum
in the original example, we can subsequently apply another histogramming or binning operation to the data:
[9]:
binned = binned.bin(y=100)
binned
[9]:
- x: 39
- y: 100
- x(x [bin-edge])float64m0.0, 0.026, ..., 0.974, 1.0
Values:
array([0. , 0.02564103, 0.05128205, 0.07692308, 0.1025641 , 0.12820513, 0.15384615, 0.17948718, 0.20512821, 0.23076923, 0.25641026, 0.28205128, 0.30769231, 0.33333333, 0.35897436, 0.38461538, 0.41025641, 0.43589744, 0.46153846, 0.48717949, 0.51282051, 0.53846154, 0.56410256, 0.58974359, 0.61538462, 0.64102564, 0.66666667, 0.69230769, 0.71794872, 0.74358974, 0.76923077, 0.79487179, 0.82051282, 0.84615385, 0.87179487, 0.8974359 , 0.92307692, 0.94871795, 0.97435897, 1. ]) - y(y [bin-edge])float64m0.001, 0.011, ..., 0.990, 1.000
Values:
array([6.06004058e-04, 1.05997253e-02, 2.05934465e-02, 3.05871677e-02, 4.05808889e-02, 5.05746101e-02, 6.05683313e-02, 7.05620526e-02, 8.05557738e-02, 9.05494950e-02, 1.00543216e-01, 1.10536937e-01, 1.20530659e-01, 1.30524380e-01, 1.40518101e-01, 1.50511822e-01, 1.60505543e-01, 1.70499265e-01, 1.80492986e-01, 1.90486707e-01, 2.00480428e-01, 2.10474150e-01, 2.20467871e-01, 2.30461592e-01, 2.40455313e-01, 2.50449034e-01, 2.60442756e-01, 2.70436477e-01, 2.80430198e-01, 2.90423919e-01, 3.00417640e-01, 3.10411362e-01, 3.20405083e-01, 3.30398804e-01, 3.40392525e-01, 3.50386247e-01, 3.60379968e-01, 3.70373689e-01, 3.80367410e-01, 3.90361131e-01, 4.00354853e-01, 4.10348574e-01, 4.20342295e-01, 4.30336016e-01, 4.40329737e-01, 4.50323459e-01, 4.60317180e-01, 4.70310901e-01, 4.80304622e-01, 4.90298344e-01, 5.00292065e-01, 5.10285786e-01, 5.20279507e-01, 5.30273228e-01, 5.40266950e-01, 5.50260671e-01, 5.60254392e-01, 5.70248113e-01, 5.80241834e-01, 5.90235556e-01, 6.00229277e-01, 6.10222998e-01, 6.20216719e-01, 6.30210441e-01, 6.40204162e-01, 6.50197883e-01, 6.60191604e-01, 6.70185325e-01, 6.80179047e-01, 6.90172768e-01, 7.00166489e-01, 7.10160210e-01, 7.20153931e-01, 7.30147653e-01, 7.40141374e-01, 7.50135095e-01, 7.60128816e-01, 7.70122538e-01, 7.80116259e-01, 7.90109980e-01, 8.00103701e-01, 8.10097422e-01, 8.20091144e-01, 8.30084865e-01, 8.40078586e-01, 8.50072307e-01, 8.60066028e-01, 8.70059750e-01, 8.80053471e-01, 8.90047192e-01, 9.00040913e-01, 9.10034634e-01, 9.20028356e-01, 9.30022077e-01, 9.40015798e-01, 9.50009519e-01, 9.60003241e-01, 9.69996962e-01, 9.79990683e-01, 9.89984404e-01, 9.99978125e-01])
- (x, y)DataArrayViewbinned data [len=0, len=2, ..., len=1, len=1]
dim='position', content=DataArray( dims=(position: 5000), data=float64[counts], coords={'x':float64[m], 'y':float64[m]})
As in the 1-D example above, summing the bins is equivalent to histogramming binned data:
[10]:
binned.bins.sum().plot()
[10]: