【问题标题】:Adding Numpy arrays like Counters添加像 Counters 这样的 Numpy 数组
【发布时间】:2017-07-10 20:33:50
【问题描述】:

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

基本上,我需要能够接受这样的数组:

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

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

然后“添加”它们,使它们看起来像这样:

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

如果没有快速有效地做到这一点的好方法,我愿意接受任何其他可以让我做类似事情的想法,并且我愿意接受 Numpy 以外的模块。

谢谢!

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

collections.Counter 结果: 2.131000, 2.125000, 2.125000

Divakar 的 union1d + 掩蔽结果: 1.641000, 1.633000, 1.625000

Divakar 的 union1d + 索引结果: 0.625000, 0.625000, 0.641000

直方图结果: 1.844000, 1.938000, 1.858000

熊猫结果: 16.659000, 16.686000, 16.885000

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

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

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

【问题讨论】:

  • 为什么结果有4行?
  • 因为结果的行数等于数组所有第一索引联合中唯一第一索引的数量。由于在顶部数组中,只有顶部的数组有“2”,只有中间的数组有“3”,所以底部的数组同时有 2 和 3。
  • 你知道第一列的最大值是多少吗?
  • 是的,假设我知道数字范围。对于我正在做的事情,这些数字将是一种 ID。
  • 出于好奇,你到底在用collections.Counter做什么?

标签: python arrays numpy multidimensional-array counter


【解决方案1】:

这是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.]])

【讨论】:

  • @juanpa.arrivillaga 如果 OP 发布该版本,我也愿意这样做。
【解决方案2】:

如果您知道字段数,请使用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])

【讨论】:

  • 我的最后一个危险地接近你的第二个。想我会回滚到以前的版本。 range 是个好主意!
  • 认为您需要修改您的解决方案,因为 OP 说他/她 might have gaps 并且他们希望在输出中获得 "union" version
【解决方案3】:

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

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]

【讨论】:

    【解决方案4】:

    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
    

    【讨论】:

      【解决方案5】:

      这是一个典型的分组问题,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 数组会更干净;在标记事物时,浮点数很挑剔,正零和负零,打印值不传递所有二进制状态。最好使用整数。

      【讨论】:

      • 你的 numpy_indexed 是否与 pyinstaller 兼容?编辑:它是用 Python 实现的还是有 C 扩展?
      • 没有c扩展,所以原则上你可以将源代码复制到你的项目并运行它;虽然我强烈推荐 conda 用于 python 包管理
      • 我不知道还能在哪里问:当我尝试使用 pip 下载 numpy_indexed 以及尝试使用 pip 下载 Conda 时出现错误。使用 numpy_indexed,我的错误是:“命令“python setup.py egg_info”失败,错误代码为 1”,它给出的路径结尾是“appdata\local\temp\pip-build-u6xyi9\numpy-indexed\”。尝试下载 Conda 时,我得到“找不到满足 menuinst 要求的版本(来自 conda)(来自版本:)”并在“没有找到 menuinst 的匹配发行版(来自 conda)”下。请帮忙?我想测试一下它的速度。
      • 使用 pip,尝试在 numpy_indexed 之前先安装 pyyaml。我不熟悉的 conda 错误。你试过从他们的网站下载 miniconda3 吗? conda.io/miniconda.html
      猜你喜欢
      • 2021-04-18
      • 2013-03-24
      • 1970-01-01
      • 2017-07-28
      • 1970-01-01
      • 2022-01-24
      • 1970-01-01
      • 2021-06-17
      • 2014-01-24
      相关资源
      最近更新 更多