GroupBy#

“Group by” refers to an implementation of the “split-apply-combine” approach known from pandas and xarray. Scipp currently supports only a limited number of operations that can be applied.

Grouping based on label values#

Suppose we have measured data for a number of parameter values, potentially repeating measurements with the same parameter multiple times:

[1]:
import numpy as np
import scipp as sc

np.random.seed(0)
[2]:
param = sc.Variable(dims=['x'], values=[1, 3, 1, 1, 5, 3])
values = sc.Variable(dims=['x', 'y'], values=np.random.rand(6, 16))
values += 1.0 + param

If we store this data as a data array we obtain the following plot:

[3]:
data = sc.DataArray(
    values,
    coords={
        'x': sc.Variable(dims=['x'], values=np.arange(6)),
        'y': sc.Variable(dims=['y'], values=np.arange(16)),
    },
)
sc.plot(data)
[3]:
../_images/user-guide_groupby_4_0.svg

Note that we chose the “measured” values such that the three distinct values of the underlying parameter are visible. We can now use the split-apply-combine mechanism to transform our data into a more useful representation. We start by storing the parameter values (or any value to be used for grouping) as a non-dimension coordinate:

[4]:
data.coords['param'] = param

Next, we call scipp.groupby to split the data and call mean on each of the groups:

[5]:
grouped = sc.groupby(data, group='param').mean('x')
sc.plot(grouped)
[5]:
../_images/user-guide_groupby_8_0.svg

Apart from mean, groupby also supports sum, concat, and more. See GroupByDataArray and GroupByDataset for a full list.

Grouping based on binned label values#

Grouping based on non-dimension coordinate values (also known as labels) is most useful when labels are strings or integers. If labels are floating-point values or cover a wide range, it is more convenient to group values into bins, i.e., all values within certain bounds are mapped into the same group. We modify the above example to use a contiuously-valued parameter:

[6]:
param = sc.Variable(dims=['x'], values=np.random.rand(16))
values = sc.Variable(dims=['x', 'y'], values=np.random.rand(16, 16))
values += 1.0 + 5.0 * param
[7]:
data = sc.DataArray(
    values,
    coords={
        'x': sc.Variable(dims=['x'], values=np.arange(16)),
        'y': sc.Variable(dims=['y'], values=np.arange(16)),
    },
)
sc.plot(data)
[7]:
../_images/user-guide_groupby_11_0.svg

We create a variable defining the desired binning:

[8]:
bins = sc.Variable(dims=["z"], values=np.linspace(0.0, 1.0, 10))

As before, we can now use groupby and mean to transform the data:

[9]:
data.coords['param'] = param
grouped = sc.groupby(data, group='param', bins=bins).mean('x')
sc.plot(grouped)
[9]:
../_images/user-guide_groupby_15_0.svg

The values in the white rows are NaN. This is the result of empty bins, which do not have a meaningful mean value.

Alternatively, grouping can be done based on groups defined as Variables rather than strings. This, however, requires bins to be specified, since bins define the new dimension label.

[10]:
grouped = sc.groupby(data, group=param, bins=bins).mean(
    'x'
)  # note the lack of quotes around param!
sc.plot(grouped)
[10]:
../_images/user-guide_groupby_18_0.svg