【问题标题】:How to sum up for each distinct value c in array X all elements Y[i] where X[i] = k efficiently?如何有效地总结数组 X 中所有元素 Y[i] 中的每个不同值 c,其中 X[i] = k?
【发布时间】:2019-04-18 14:02:00
【问题描述】:

在 Numpy 中给定一个长度为 n 的一维数组 Xk 不同的值,我想在另一个具有相同长度的一维数组 Y 中总结每个不同的值 c,所有这些元素 Y[idx] 其中X[idx] == c 以最有效的方式。

例子:

X = [1, 3, 2, 1, 2]Y = [0.1, 0.2, 0.5, 2.0, 0.3]n 的长度是 5,我们在 X 中有 k=3 不同的值。这意味着我们操作的结果是Xk=3 不同元素[1, 3, 2] 的向量以及来自Y 元素的相应总和,即[2.1, 0.2, 0.8]。如果对不同的元素进行排序也很好。因此[1, 2, 3][2.1, 0.8, 0.2] 也是一个解决方案。

我已经在 Numpy 中查找了各种函数,最接近我想要的是 np.unique(X, return_counts=True),但它返回的是计数而不是 Y 中的总和。

当然,可以用一个讨厌的循环来解决整个问题,例如:

import numpy as np

X = np.array([1, 3, 2, 1, 2])
Y = np.array([0.1, 0.2, 0.5, 2.0, 0.3])

def unique_sums(x, y):
    distinct_x = np.unique(x)
    y_sums = np.empty(distinct_x.shape)
    for idx, val in enumerate(distinct_x):
        y_sums[idx] = np.sum(y[x == val])
    return distinct_x, y_sums

unique_sums(X, Y)

导致有序结果:

(array([1, 2, 3]), array([2.1, 0.8, 0.2]))

在 Numpy 或任何其他常见的 Python 库中是否有这样的矢量化操作?如果没有,Cython 中最有效的实现是什么?

【问题讨论】:

  • 您能否在某处发布已发布的方法如何在您暗示的 ~10k 到 ~100k 大小的数据集上公平?我猜性能,即运行时是你的首要任务,所以很高兴看看这些是如何叠加的。

标签: python numpy


【解决方案1】:

给你:

In [21]: u, inv = np.unique(X, return_inverse=True)                                                                                                            

In [22]: sums = np.zeros(len(u), dtype=Y.dtype)                                                                                                                               

In [23]: np.add.at(sums, inv, Y)                                                                                                                               

In [24]: sums                                                                                                                                                  
Out[24]: array([2.1, 0.8, 0.2])

这会将您的for-loop 替换为漂亮的方法numpy.add.at

注意np.unique 排序X,所以这个方法是O(n*log(n))。这不是这个问题的最佳时间复杂度。

【讨论】:

  • 我真的很感动!正是我想要的。我不知道 Numpy 中有这些 ufunc.at 函数。
【解决方案2】:

我们可以在这里使用scipy.sparse.csr_matrix 以获得更有效的解决方案


设置

X = np.array([1, 3, 2, 1, 2])
Y = np.array([0.1, 0.2, 0.5, 2.0, 0.3])

from scipy import sparse

res = sparse.csr_matrix(
    (Y, X, np.arange(Y.shape[0]+1)),
    (Y.shape[0], X.max()+1)
).sum(0).A1

array([0. , 2.1, 0.8, 0.2])

这是来自0 -> k 的总和列表,其中kX 数组的最大值。在X 中不存在密钥的任何条目显然是0。要获得更好的映射,您可以使用np.unique 和一些索引:

u = np.unique(X)
np.column_stack((u, res[u]))

array([[1. , 2.1],
       [2. , 0.8],
       [3. , 0.2]])

时间

X = np.random.randint(0, 100, 100_000)
Y = np.random.rand(100_000)

In [11]: %%timeit
    ...: sparse.csr_matrix(
    ...:     (Y, X, np.arange(Y.shape[0]+1)),
    ...:     (Y.shape[0], X.max()+1)
    ...: ).sum(0).A1
    ...:
1.15 ms ± 17.3 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [13]: %%timeit
    ...: u, inv = np.unique(X, return_inverse=True)
    ...: sums = np.zeros(len(u), dtype=Y.dtype)
    ...: np.add.at(sums, inv, Y)
    ...:
16.5 ms ± 161 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [16]: %timeit unique_sums(X, Y)
16.6 ms ± 169 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

【讨论】:

  • 谢谢,好主意。我是否还会在您的解决方案中获得相应的不同值列表?
  • 不,你没有。如果你需要这些,你必须使用np.unique。但是,根据我的时间安排,使用np.unique 不会增加足够的开销来使任何其他解决方案都比这个更快。例如,使用n=100_000,总时间增加了大约 3 毫秒,仍然比其他解决方案快 3 倍。
  • 好的,知道了。您的解决方案的唯一缺点是我只能处理 X 中的整数,对吗?如果我将其应用于大多数矩阵 X 是浮动的 Scikit-Learn 上下文中,这可能是个问题。
  • 没错。尽管这会弄乱这里的所有解决方案。我在答案中添加了一个将唯一值链接回结果的示例
  • 谢谢,很好。我刚刚尝试了@WarrenWeckesser 在 X 中使用浮点值的解决方案,它仍然有效。为什么会崩溃?
【解决方案3】:

我们将尝试利用pandas.factorize 有效地获得基于int 的唯一ID,然后使用numpy.bincount 获得基于ID 的求和。所以,解决方案看起来像这样 -

import pandas as pd

def unique_sums_factorize_bincount(X, Y):
    ids,unq = pd.factorize(X)
    return unq, np.bincount(ids,Y)

示例运行 -

In [24]: X = np.array([ 1,   3,   2,   1,   2]).astype(float)
    ...: Y = np.array([0.1, 0.2, 0.5, 2.0, 0.3])

In [25]: unique_sums_factorize_bincount(X,Y)
Out[25]: (array([1., 3., 2.]), array([2.1, 0.2, 0.8]))

【讨论】:

    【解决方案4】:

    我想你会想要使用哈希表。对于小型数据集,Python 的 dict 将足够高效。您当然必须为此推出自己的算法。

    def unique_sums(x, y):
        xd = {}
        for i, number in enumerate(y):
            xd[x[i]] = xd.get(x[i], 0) + number
        return xd.keys(), xd.values()
    

    我认为您的解决方案是 O(n^2),因为 np.sum(y[x == val]) 但我上面的解决方案是 O(n)。

    【讨论】:

    • 感谢您的建议,我应该提到我的用例。它将是由样本权重加权的分类朴素贝叶斯的一部分。因此,我处理相当大的数组,例如 ~10k 到 ~100k 甚至更多。
    • 这还不错,我认为 dict 应该适合你。你至少不会是 O(len(Y)),如果 dict 对你来说不够快,你应该能够用 C 中的自定义哈希表来实现这个算法。
    • 虽然理论上,字典是 O(1) 我认为由于散列、非连续内存等原因有很多开销。Cython 中的循环应该通过 X 和 Y 完成任务我猜每次只有一次。我只是想知道 Cython 是否是唯一的解决方案,或者是否有 np.wherenp.choose 的智能应用程序或我不知道的某些功能。
    • 您要求“最有效的实施”。您没有指定运行时或内存,所以我假设运行时。最有效的是 O(n),这是一个解决方案。如果你不想使用散列,你应该编辑你的问题以规定“最有效的实现没有散列”。
    • @fwilhelm,鉴于您将拥有 10k 到 100k 个元素,可能值得花一些时间测试这种大小的数组的性能。对于小型数组,使用np.unique 可能会获胜,但随着数组大小的增加,最终时间复杂度较低的方法会获胜。测试会让你知道你是否达到了那个点。
    猜你喜欢
    • 2012-11-23
    • 1970-01-01
    • 2016-02-07
    • 1970-01-01
    • 2020-03-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多