添加像计数器这样的 Numpy 数组

Adding Numpy arrays like Counters

由于 collections.Counter 太慢了,我在 Python 2.7 中寻求一种更快的求和映射值的方法。这似乎是一个简单的概念,我对内置的 Counter 方法有点失望。

基本上,我需要能够采用这样的数组:

array([[ 0.,  2.],
       [ 2.,  2.],
       [ 3.,  1.]])

array([[ 0.,  3.],
       [ 1.,  1.],
       [ 2.,  5.]])

然后 "add" 他们看起来像这样:

array([[ 0.,  5.],
       [ 1.,  1.],
       [ 2.,  7.],
       [ 3.,  1.]])

如果没有快速有效地执行此操作的好方法,我愿意接受任何其他允许我做类似事情的想法,并且我愿意接受 Numpy 以外的模块。

谢谢!

编辑:准备好进行一些速度测试了吗? 英特尔赢得 64 位机器。以下所有值均以秒为单位; 20000 个循环。

collections.Counter results: 2.131000, 2.125000, 2.125000

Divakar's union1d + masking results: 1.641000, 1.633000, 1.625000

Divakar's union1d + indexing results: 0.625000, 0.625000, 0.641000

Histogram results: 1.844000, 1.938000, 1.858000

Pandas results: 16.659000, 16.686000, 16.885000

结论:union1d + 索引获胜,数组大小太小以至于 Pandas 无效,直方图方法的简单性让我大吃一惊,但我猜创建它需要太多开销.不过,我收到的所有回复都非常好。 This is what I used to get the numbers.再次感谢!

编辑:应该提到的是,尽管做了同样的事情(65.671000 秒),但使用 Counter1.update(Counter2.elements()) 还是很糟糕。

稍后编辑:我一直在思考这个问题,我开始意识到,使用 Numpy,用零填充每个数组可能更有效 所以甚至不需要第一列,因为我们可以只使用索引,这也将使将多个数组加在一起以及执行其他功能变得更加容易。此外,Pandas 比 Numpy 更有意义,因为不需要填充 0,而且对于大型数据集肯定会更有效(但是,Numpy 具有在更多平台上兼容的优势,例如 GAE ,如果这很重要的话)。最后,我检查的答案绝对是我提出的确切问题的最佳答案——按照我展示的方式添加两个数组——但我认为我需要的是改变视角。

这是 np.union1dmasking -

的一种方法
def app1(a,b):
    c0 = np.union1d(a[:,0],b[:,0])

    out = np.zeros((len(c0),2))
    out[:,0] = c0

    mask1 = np.in1d(c0,a[:,0])
    out[mask1,1] = a[:,1]

    mask2 = np.in1d(c0,b[:,0])
    out[mask2,1] += b[:,1]
    return out

样本运行-

In [174]: a
Out[174]: 
array([[  0.,   2.],
       [ 12.,   2.],
       [ 23.,   1.]])

In [175]: b
Out[175]: 
array([[  0.,   3.],
       [  1.,   1.],
       [ 12.,   5.]])

In [176]: app1(a,b)
Out[176]: 
array([[  0.,   5.],
       [  1.,   1.],
       [ 12.,   7.],
       [ 23.,   1.]])

这是另一个 np.union1dindexing -

def app2(a,b):
    n = np.maximum(a[:,0].max(), b[:,0].max())+1
    c0 = np.union1d(a[:,0],b[:,0])
    out0 = np.zeros((int(n), 2))
    out0[a[:,0].astype(int),1] = a[:,1]

    out0[b[:,0].astype(int),1] += b[:,1]

    out = out0[c0.astype(int)]
    out[:,0] = c0
    return out

对于ab中的第一列值覆盖所有索引的情况-

def app2_specific(a,b):
    c0 = np.union1d(a[:,0],b[:,0])
    n = c0[-1]+1
    out0 = np.zeros((int(n), 2))
    out0[a[:,0].astype(int),1] = a[:,1]        
    out0[b[:,0].astype(int),1] += b[:,1]
    out0[:,0] = c0
    return out0

样本运行-

In [234]: a
Out[234]: 
array([[ 0.,  2.],
       [ 2.,  2.],
       [ 3.,  1.]])

In [235]: b
Out[235]: 
array([[ 0.,  3.],
       [ 1.,  1.],
       [ 2.,  5.]])

In [236]: app2_specific(a,b)
Out[236]: 
array([[ 0.,  5.],
       [ 1.,  1.],
       [ 2.,  7.],
       [ 3.,  1.]])

Pandas 有一些功能完全符合您的意图

import pandas as pd
pda = pd.DataFrame(a).set_index(0)
pdb = pd.DataFrame(b).set_index(0)
result = pd.concat([pda, pdb], axis=1).fillna(0).sum(axis=1)

编辑:如果您确实需要以 numpy 格式返回数据,只需执行

array_res = result.reset_index(name=1).values

如果知道字段数,请使用np.bincount

c = np.vstack([a, b])
counts = np.bincount(c[:, 0], weights = c[:, 1], minlength = numFields)
out = np.vstack([np.arange(numFields), counts]).T

如果您一次获取所有数据,此方法有效。列出您的数组并 vstack 它们。如果您按顺序获取数据块,则可以使用 np.add.at 来做同样的事情。

out = np.zeros(2, numFields)
out[:, 0] = np.arange(numFields)
np.add.at(out[:, 1], a[:, 0], a[:, 1])
np.add.at(out[:, 1], b[:, 0], b[:, 1])

这是一个典型的分组问题,numpy_indexed(免责声明:我是它的作者)是为了优雅高效地解决问题而创建的:

import numpy_indexed as npi
C = np.concatenate([A, B], axis=0)
labels, sums = npi.group_by(C[:, 0]).sum(C[:, 1])

注意:将标签数组维护为单独的 int 数组会更干净;浮点数在标记事物时很挑剔,带有正零和负零,并且打印的值不中继所有二进制状态。最好为此使用整数。

您可以使用基本直方图,。如果需要,您可以过滤掉零计数条目。

import numpy as np

x = np.array([[ 0.,  2.],
              [ 2.,  2.],
              [ 3.,  1.]])

y = np.array([[ 0.,  3.],
              [ 1.,  1.],
              [ 2.,  5.],
              [ 5.,  3.]])

c, w = np.vstack((x,y)).T
h, b = np.histogram(c, weights=w, 
                    bins=np.arange(c.min(),c.max()+2))
r = np.vstack((b[:-1], h)).T
print(r)
# [[ 0.  5.]
#  [ 1.  1.]
#  [ 2.  7.]
#  [ 3.  1.]
#  [ 4.  0.]
#  [ 5.  3.]]
r_nonzero = r[r[:,1]!=0]