gpt4 book ai didi

python - 按多个维度分组

转载 作者:太空宇宙 更新时间:2023-11-04 09:38:54 25 4
gpt4 key购买 nike

按单个维度分组适用于 xarray DataArrays:

d = xr.DataArray([1, 2, 3], coords={'a': ['x', 'x', 'y']}, dims=['a'])
d.groupby('a').mean()) # -> DataArray (a: 2) array([1.5, 3. ])`

但是,这仅支持单个维度,因此无法按多个维度分组:

d = DataAssembly([[1, 2, 3], [4, 5, 6]],
coords={'a': ('multi_dim', ['a', 'b']), 'c': ('multi_dim', ['c', 'c']), 'b': ['x', 'y', 'z']},
dims=['multi_dim', 'b'])
d.groupby(['a', 'b']) # TypeError: `group` must be an xarray.DataArray or the name of an xarray variable or dimension

我只有一个低效的解决方案,它手动执行 for 循环:

a, b = np.unique(d['a'].values), np.unique(d['b'].values)
result = xr.DataArray(np.zeros([len(a), len(b)]), coords={'a': a, 'b': b}, dims=['a', 'b'])
for a, b in itertools.product(a, b):
cells = d.sel(a=a, b=b)
merge = cells.mean()
result.loc[{'a': a, 'b': b}] = merge
# result = DataArray (a: 2, b: 2)> array([[2., 3.], [5., 6.]])
# Coordinates:
# * a (a) <U1 'x' 'y'
# * b (b) int64 0 1

然而,对于较大的阵列来说,这非常慢。是否有更高效/更直接的解决方法?

最佳答案

我构建了一个手动解决方案。为了提高效率,我丢弃了所有 xarray 并手动重建索引和值。使用更多 xarray 的任何更改(例如使用 sel,将单元格重新打包到 DataArray 中;另请参阅 https://github.com/pydata/xarray/issues/2452)会导致速度严重下降。

import itertools
from collections import defaultdict

import numpy as np
import xarray as xr
from xarray import DataArray

class DataAssembly(DataArray):
def multi_dim_groupby(self, groups, apply):
# align
groups = sorted(groups, key=lambda group: self.dims.index(self[group].dims[0]))
# build indices
groups = {group: np.unique(self[group]) for group in groups}
group_dims = {self[group].dims: group for group in groups}
indices = defaultdict(lambda: defaultdict(list))
result_indices = defaultdict(dict)
for group in groups:
for index, value in enumerate(self[group].values):
indices[group][value].append(index)
if value not in result_indices[group]: # if captured once, it will be "grouped away"
index = max(result_indices[group].values()) + 1 if len(result_indices[group]) > 0 else 0
result_indices[group][value] = index

coords = {coord: (dims, value) for coord, dims, value in walk_coords(self)}

def simplify(value):
return value.item() if value.size == 1 else value

def indexify(dict_indices):
return [(i,) if isinstance(i, int) else tuple(i) for i in dict_indices.values()]

# group and apply
# making this a DataArray right away and then inserting through .loc would slow things down
result = np.zeros([len(indices) for indices in result_indices.values()])
result_coords = {coord: (dims, [None] * len(result_indices[group_dims[dims]]))
for coord, (dims, value) in coords.items()}
for values in itertools.product(*groups.values()):
group_values = dict(zip(groups.keys(), values))
self_indices = {group: indices[group][value] for group, value in group_values.items()}
values_indices = indexify(self_indices)
cells = self.values[values_indices] # using DataArray would slow things down. thus we pass coords as kwargs
cells = simplify(cells)
cell_coords = {coord: (dims, value[self_indices[group_dims[dims]]])
for coord, (dims, value) in coords.items()}
cell_coords = {coord: (dims, simplify(np.unique(value))) for coord, (dims, value) in cell_coords.items()}

# ignore dims when passing to function
passed_coords = {coord: value for coord, (dims, value) in cell_coords.items()}
merge = apply(cells, **passed_coords)
result_idx = {group: result_indices[group][value] for group, value in group_values.items()}
result[indexify(result_idx)] = merge
for coord, (dims, value) in cell_coords.items():
if isinstance(value, np.ndarray): # multiple values for coord -> ignore
if coord in result_coords: # delete from result coords if not yet deleted
del result_coords[coord]
continue
assert dims == result_coords[coord][0]
coord_index = result_idx[group_dims[dims]]
result_coords[coord][1][coord_index] = value

# re-package
result = type(self)(result, coords=result_coords, dims=list(itertools.chain(*group_dims.keys())))
return result

def walk_coords(assembly):
"""
walks through coords and all levels, just like the `__repr__` function, yielding `(name, dims, values)`.
"""
coords = {}

for name, values in assembly.coords.items():
# partly borrowed from xarray.core.formatting#summarize_coord
is_index = name in assembly.dims
if is_index and values.variable.level_names:
for level in values.variable.level_names:
level_values = assembly.coords[level]
yield level, level_values.dims, level_values.values
else:
yield name, values.dims, values.values
return coords

multi_dim_groupby 方法一步完成分组和应用。传递的 apply 方法可以通过以坐标命名的参数接受组坐标(或者通过将 **_ 放在函数头中来忽略坐标)。

它不是特别漂亮,也没有涵盖所有可能的情况,但至少涵盖了以下测试用例:

import DataAssembly

class TestMultiDimGroupby:
def test_unique_values(self):
d = DataAssembly([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]],
coords={'a': ['a', 'b', 'c', 'd'],
'b': ['x', 'y', 'z']},
dims=['a', 'b'])
g = d.multi_dim_groupby(['a', 'b'], lambda x, **_: x)
assert g.equals(d)

def test_nonunique_singledim(self):
d = DataAssembly([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]],
coords={'a': ['a', 'a', 'b', 'b'],
'b': ['x', 'y', 'z']},
dims=['a', 'b'])
g = d.multi_dim_groupby(['a', 'b'], lambda x, **_: x.mean())
assert g.equals(DataAssembly([[2.5, 3.5, 4.5], [8.5, 9.5, 10.5]],
coords={'a': ['a', 'b'], 'b': ['x', 'y', 'z']},
dims=['a', 'b']))

def test_nonunique_adjacentcoord(self):
d = DataAssembly([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]],
coords={'a': ('adim', ['a', 'a', 'b', 'b']),
'aa': ('adim', ['a', 'b', 'a', 'b']),
'b': ['x', 'y', 'z']},
dims=['adim', 'b'])
g = d.multi_dim_groupby(['a', 'b'], lambda x, **_: x.mean())
assert g.equals(DataAssembly([[2.5, 3.5, 4.5], [8.5, 9.5, 10.5]],
coords={'adim': ['a', 'b'], 'b': ['x', 'y', 'z']},
dims=['adim', 'b'])), \
"adjacent coord aa should be discarded due to non-mappability"

def test_unique_values_swappeddims(self):
d = DataAssembly([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]],
coords={'a': ['a', 'b', 'c', 'd'],
'b': ['x', 'y', 'z']},
dims=['a', 'b'])
g = d.multi_dim_groupby(['b', 'a'], lambda x, **_: x)
assert g.equals(d)

关于python - 按多个维度分组,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52453426/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com