繁体   English   中英

function 是否有任何 numpy 组?

[英]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”,但这可能是解决问题的更快方法:

  • 没有python本机循环
  • 结果列表是 numpy 数组,如果您需要对它们进行其他 numpy 操作,则不需要新的转换
  • 复杂度像 O(n)

[编辑] 由于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)

简化Vincent J答案并考虑到 HS-nebula 的评论,可以使用return_index = True而不是return_counts = True并摆脱cumsum

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])]

我使用 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一个objectnamedtuple ,然后提供一个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将在未来吸引任何努力。 假设第一列的索引很小,分组的其余部分可以显着改进。

TL; 博士

大多数分组方法的其余部分包含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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM