【问题标题】:Merge sum two numpy arrays, one as key, another as value合并两个numpy数组,一个作为键,另一个作为值
【发布时间】:2020-09-05 03:28:03
【问题描述】:

是否有任何函数可以对 numpy 数组进行分组求和?

可能与this重复

x = np.array([[1.2, 10],
              [2.3, 20],
              [1.2, 30],
              [2.3, 7]
            ])

想要的输出:

x = np.array([[1.2, 40],
              [2.3, 27]            
            ])

更新:

实际上,我的数据的第一列总是四舍五入到小数点后两位。所以x可以写成:

x = np.array([[120, 10],
              [230, 20],
              [120, 30],
              [230, 7]
            ])

【问题讨论】:

  • 你试过什么?
  • 请注意,来自真实数据的值很少彼此相等,因此实际上极不可能有重复的浮点键。根据它的用途,您可能希望使用更像直方图的东西。

标签: python numpy vectorization


【解决方案1】:

我不会说这是重复的,但您提到的相关问题是一个很好的起点。您的链接的大多数答案需要对数组进行排序,提取组开始的索引,然后在其上调用np.split。这里不是这种情况,因为它会返回大小不平衡的组列表。

相反,您可以使用np.bincount 方法。它计算每个加权值的出现次数,这实际上与 groupby sum 相同,只是输出中不存在组键。

def group_by_sum(x):
    u, idx = np.unique(x[:,0], return_inverse=True)
    s = np.bincount(idx, weights = x[:,1])
    return np.c_[u, s]

奖金。它实际上是numpy_indexed 包中的单行器:

np.transpose(npi.group_by(x[:, 0]).sum(x[:, 1]))

基准测试

import numpy as np
import perfplot
import matplotlib.pyplot as plt

def bincount(x):
    u, idx = np.unique(x[:,0], return_inverse=True)
    s = np.bincount(idx, weights = x[:,1])
    return np.c_[u, s]

def reduceat(x):
    x = x[np.argsort(x[:, 0])]
    i = np.flatnonzero(np.diff(x[:, 0]))
    i = np.r_[0, i + 1]
    s = np.add.reduceat(x[:, 1], i)
    return np.stack((x[i, 0], s), axis=-1)

def setup(N, s):
    x = np.linspace(0,1,N+1)[np.random.randint(N, size = s)]
    return np.c_[x, (x**2)%1]

def build_args(k):
    return {'setup': lambda x: setup(k, x),
            'kernels': [bincount, reduceat],
            'n_range': [2**k for k in range(1, 20)],
            'title': f'Testing for x samples in [0, 1] with no more than {k} groups',
            'show_progress': True,
            'equality_check': False}

outs = [perfplot.bench(**build_args(n)) for n in (10, 100, 1000, 10000)]
fig = plt.figure(figsize=(20, 20))
for i in range(len(outs)):
    ax = fig.add_subplot(2, 2, i + 1)
    ax.grid(True, which="both")
    outs[i].plot()
plt.show()

【讨论】:

  • 不客气。虽然不知道这种方法在内部是如何工作的,但np.add.reduceat 也是一个不错的功能。
  • unique 在内部通过排序工作,因此两种方法具有相同的复杂性。您是否有资源将它们相互进行基准测试?
  • @疯狂物理学家。接得好。这对我来说也很有趣,但我目前正忙于解决另一个问题,所以我会在接下来的一天对其进行基准测试并更新答案。
  • 它们几乎一样。
【解决方案2】:

Numpy 提供了无需显式循环即可执行此操作的工具。

首先对行进行排序:

a = a[np.argsort(a[:, 0])]

然后找到值变化的索引:

i = np.flatnonzero(np.diff(a[:, 0]))
i = np.r_[0, i + 1]

然后将元素相加:

s = np.add.reduceat(a[:, 1], i)

索引只是a每次运行的第一个元素,所以结果是

result = np.stack((a[i, 0], s), axis=-1)

【讨论】:

  • 这个问题需要排序吗?我认为它比它需要的成本更高。
  • @Ehsan。是的。这就是unique 在内部工作的方式。否则你将不知道哪些元素是相同的。它比哈希表更复杂,但对于大多数实际大小的应用程序,我怀疑它比 python set 更快。
  • 谢谢。有必要知道哪些元素是相同的吗?计数和唯一值还不够吗(就像我在帖子中使用的那样)?
【解决方案3】:

这是一个使用唯一值来计算每个元素的重复次数并将其乘以它的值以计算 groupby 总和的解决方案(您可以通过实现仅计算重复次数和唯一值的 O(n) 哈希图更快地实现它) :

编辑,因为原始问题已编辑:

keys2, idx, count = np.unique(x[:,0], return_counts=True, return_index=True)
values2 = x[:,1][idx]*count

另一种方法是使用 pandas groupby:

df = pd.DataFrame({'keys':x[:,0], 'values':x[:,1]})
df2 = df.groupby(keys)['values'].agg('sum')
keys2, values2 = df2.index.to_numpy(), df2.values

输出:

[1.2 2.3] 
[20 30]

【讨论】:

  • @MadPhysicist 你的意思是return_index?这就说得通了。谢谢你。编辑了帖子。
  • @MadPhysicist 我明白了。谢谢你纠正我。然而,对于这个特定的例子,我们仍然可以使用带有O(n) 的哈希图完成任务,而无需知道哪些元素是相同的。只需要计数和值。
【解决方案4】:

这是一个方法

d = {}
for k,v in x:
    d[k] = d.get(k,0) + v

x = np.array(list(d.items()))

请记住,这是对浮点相等性的测试……您可能不应该这样做

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-05-30
    • 1970-01-01
    • 2011-06-08
    相关资源
    最近更新 更多