[英]Is there any numpy group by function?
numpy 中是否有任何 function 将这个数组按第一列分组?
我在互联网上找不到任何好的答案..
>>> a
array([[ 1, 275],
[ 1, 441],
[ 1, 494],
[ 1, 593],
[ 2, 679],
[ 2, 533],
[ 2, 686],
[ 3, 559],
[ 3, 219],
[ 3, 455],
[ 4, 605],
[ 4, 468],
[ 4, 692],
[ 4, 613]])
想要 output:
array([[[275, 441, 494, 593]],
[[679, 533, 686]],
[[559, 219, 455]],
[[605, 468, 692, 613]]], dtype=object)
受Eelco Hoogendoorn 的 library 的启发,但没有他的 library,并使用数组的第一列总是增加的事实(如果不是,请先使用a = a[a[:, 0].argsort()]
)
>>> np.split(a[:,1], np.unique(a[:, 0], return_index=True)[1][1:])
[array([275, 441, 494, 593]),
array([679, 533, 686]),
array([559, 219, 455]),
array([605, 468, 692, 613])]
我没有“timeit”,但这可能是解决问题的更快方法:
[编辑] 由于ns63sr 的回答和Behzad Shayegh (参见评论),我改进了答案
numpy_indexed包(免责声明:我是它的作者)旨在填补 numpy 中的这一空白。 numpy-indexed 中的所有操作都是完全矢量化的,并且在该库的制作过程中没有损坏 O(n^2) 算法。
import numpy_indexed as npi
npi.group_by(a[:, 0]).split(a[:, 1])
请注意,直接计算此类组的相关属性通常更有效(即 group_by(keys).mean(values)),而不是首先拆分为列表/锯齿状数组。
Numpy 在这里不是很方便,因为所需的输出不是整数数组(它是一个列表对象数组)。
我建议使用纯 Python 方式...
from collections import defaultdict
%%timeit
d = defaultdict(list)
for key, val in a:
d[key].append(val)
10.7 µs ± 156 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
# result:
defaultdict(list,
{1: [275, 441, 494, 593],
2: [679, 533, 686],
3: [559, 219, 455],
4: [605, 468, 692, 613]})
...或熊猫方式:
import pandas as pd
%%timeit
df = pd.DataFrame(a, columns=["key", "val"])
df.groupby("key").val.apply(pd.Series.tolist)
979 µs ± 3.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
# result:
key
1 [275, 441, 494, 593]
2 [679, 533, 686]
3 [559, 219, 455]
4 [605, 468, 692, 613]
Name: val, dtype: object
n = np.unique(a[:,0])
np.array( [ list(a[a[:,0]==i,1]) for i in n] )
输出:
array([[275, 441, 494, 593], [679, 533, 686], [559, 219, 455],
[605, 468, 692, 613]], dtype=object)
我使用 np.unique() 后跟 np.extract()
unique = np.unique(a[:, 0:1])
answer = []
for element in unique:
present = a[:,0]==element
answer.append(np.extract(present,a[:,-1]))
print (answer)
[array([275, 441, 494, 593]), array([679, 533, 686]), array([559, 219, 455]), array([605, 468, 692, 613])]
给定 X 作为要分组的项目数组,将 y (一维数组)作为相应的组,以下函数使用numpy进行分组:
def groupby(X, y):
y = np.asarray(y)
X = np.asarray(X)
y_uniques = np.unique(y)
return [X[y==yi] for yi in y_uniques]
所以, groupby(a[:,1], a[:,0])
返回[array([275, 441, 494, 593]), array([679, 533, 686]), array([559, 219, 455]), array([605, 468, 692, 613])]
我们可能还会发现生成dict
很有用:
def groupby(X):
X = np.asarray(X)
x_uniques = np.unique(X)
return {xi:X[X==xi] for xi in x_uniques}
让我们试试看:
X=[1,1,2,2,3,3,3,3,4,5,6,7,7,8,9,9,1,1,1]
groupby(X)
Out[9]:
{1: array([1, 1, 1, 1, 1]),
2: array([2, 2]),
3: array([3, 3, 3, 3]),
4: array([4]),
5: array([5]),
6: array([6]),
7: array([7, 7]),
8: array([8]),
9: array([9, 9])}
请注意,这本身并不是非常引人注目 - 但如果我们将X
namedtuple
一个object
或namedtuple
,然后提供一个groupby
函数,它会变得更有趣。 稍后会放上来。
聚会迟到了,但无论如何。 如果您不仅打算对数组进行分组,而且还想对它们进行 sum、mean 等操作,并且考虑到速度,那么您可能还需要考虑numpy_groupies 。 所有这些组操作都使用 numba 进行了优化和抖动。 它们很容易胜过其他提到的解决方案。
from numpy_groupies.aggregate_numpy import aggregate
aggregate(a[:,0], a[:,1], "array", fill_value=[])
>>> array([array([], dtype=int64), array([275, 441, 494, 593]),
array([679, 533, 686]), array([559, 219, 455]),
array([605, 468, 692, 613])], dtype=object)
aggregate(a[:,0], a[:,1], "sum")
>>> array([ 0, 1803, 1898, 1233, 2378])
很明显a = a[a[:, 0].argsort()]
是所有竞争分组算法的瓶颈,非常感谢Vincent J澄清这一点。 超过 80% 的处理时间都被这种argsort
方法占用了,并且没有简单的方法来替换或优化它。 numba
包允许加速许多算法,希望argsort
将在未来吸引任何努力。 假设第一列的索引很小,分组的其余部分可以显着改进。
大多数分组方法的其余部分包含np.unique
方法,在组值较小的情况下,该方法非常缓慢且过多。 用np.bincount
替换它更有效,稍后可以在numba
改进。 有一些关于如何改进剩余部分的结果:
def _custom_return(unique_id, a, split_idx, return_groups):
'''Choose if you want to also return unique ids'''
if return_groups:
return unique_id, np.split(a[:,1], split_idx)
else:
return np.split(a[:,1], split_idx)
def numpy_groupby_index(a, return_groups=False):
'''Code refactor of method of Vincent J'''
u, idx = np.unique(a[:,0], return_index=True)
return _custom_return(u, a, idx[1:], return_groups)
def numpy_groupby_counts(a, return_groups=False):
'''Use cumsum of counts instead of index'''
u, counts = np.unique(a[:,0], return_counts=True)
idx = np.cumsum(counts)
return _custom_return(u, a, idx[:-1], return_groups)
def numpy_groupby_diff(a, return_groups=False):
'''No use of any np.unique options'''
u = np.unique(a[:,0])
idx = np.flatnonzero(np.diff(a[:,0])) + 1
return _custom_return(u, a, idx, return_groups)
def numpy_groupby_bins(a, return_groups=False):
'''Replace np.unique by np.bincount'''
bins = np.bincount(a[:,0])
nonzero_bins_idx = bins != 0
nonzero_bins = bins[nonzero_bins_idx]
idx = np.cumsum(nonzero_bins[:-1])
return _custom_return(np.flatnonzero(nonzero_bins_idx), a, idx, return_groups)
def numba_groupby_bins(a, return_groups=False):
'''Replace np.bincount by numba_bincount'''
bins = numba_bincount(a[:,0])
nonzero_bins_idx = bins != 0
nonzero_bins = bins[nonzero_bins_idx]
idx = np.cumsum(nonzero_bins[:-1])
return _custom_return(np.flatnonzero(nonzero_bins_idx), a, idx, return_groups)
所以numba_bincount
工作方式与np.bincount
相同,它的定义如下:
from numba import njit
@njit
def _numba_bincount(a, counts, m):
for i in range(m):
counts[a[i]] += 1
def numba_bincount(arr): #just a refactor of Python count
M = np.max(arr)
counts = np.zeros(M + 1, dtype=int)
_numba_bincount(arr, counts, len(arr))
return counts
a = np.array([[1,275],[1,441],[1,494],[1,593],[2,679],[2,533],[2,686],[3,559],[3,219],[3,455],[4,605],[4,468],[4,692],[4,613]])
a = a[a[:, 0].argsort()]
>>> numpy_groupby_index(a, return_groups=False)
[array([275, 441, 494, 593]),
array([679, 533, 686]),
array([559, 219, 455]),
array([605, 468, 692, 613])]
>>> numpy_groupby_index(a, return_groups=True)
(array([1, 2, 3, 4]),
[array([275, 441, 494, 593]),
array([679, 533, 686]),
array([559, 219, 455]),
array([605, 468, 692, 613])])
在我的计算机上对 100M 项目(有 10 个不同的 ID)进行排序需要大约 30 秒。 让我们测试一下剩余部分的方法需要多少时间来运行:
%matplotlib inline
benchit.setparams(rep=3)
sizes = [3*10**(i//2) if i%2 else 10**(i//2) for i in range(17)]
N = sizes[-1]
x1 = np.random.randint(0,10, size=N)
x2 = np.random.normal(loc=500, scale=200, size=N).astype(int)
a = np.transpose([x1, x2])
arr = a[a[:, 0].argsort()]
fns = [numpy_groupby_index, numpy_groupby_counts, numpy_groupby_diff, numpy_groupby_bins, numba_groupby_bins]
in_ = {s/1000000: (arr[:s], ) for s in sizes}
t = benchit.timings(fns, in_, multivar=True, input_name='Millions of events')
t.plot(logx=True, figsize=(12, 6), fontsize=14)
毫无疑问, numba
驱动的 bincount 是包含小 ID 的数据集的新赢家。 它有助于将排序数据的分组减少约 5 次,即总运行时间的约 10%。
Ashwini Chaudhary建议的另一种方法可能就是您正在寻找的。 把它放在一个简单的 function
def np_groupby(x, index):
return np.split(x, np.where(np.diff(x[:,index]))[0]+1)
x = numpy 阵列
索引 = 列索引
[0] + 1 根据 Ashwini, ...任何非零的东西意味着它旁边的项目不同,我们可以使用numpy.where
找到非零项目的索引,然后将其加 1,因为该项目的实际索引比返回的索引大一; ...numpy.diff 用于找出项目实际更改的位置。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.