将Numpy切片操作向量化

7

假设我有一个Numpy向量:

A = zeros(100)

我将其通过一个断点列表,这个列表是索引到A中的位置来将其分成子向量,例如,

breaks = linspace(0, 100, 11, dtype=int)

所以第i个子向量将位于索引breaks [i](包括)和breaks [i + 1](不包括)之间。 断点不一定等距,这只是一个示例。但是,它们始终严格递增。
现在我想对这些子向量进行操作。例如,如果我想将第i个子向量的所有元素设置为i,我可以执行以下操作:
for i in range(len(breaks) - 1):
    A[breaks[i] : breaks[i+1]] = i

或者我想计算子向量的平均值:

b = empty(len(breaks) - 1)
for i in range(len(breaks) - 1):
    b = A[breaks[i] : breaks[i+1]].mean()

等等,还有其他的问题。

如何避免使用for循环而是将这些操作向量化?


breaks 是否预先排序? - Divakar
@Divakar:是的,它们严格递增。 - cfh
此外,中断的限制是否涵盖整个 A,即在此操作后可能存在一些不会更改的 A 元素? - Divakar
@Divakar:是的,他们会涵盖所有内容。 - cfh
3个回答

7
你可以使用简单的 np.cumsum 函数。
import numpy as np

# Form zeros array of same size as input array and 
# place ones at positions where intervals change
A1 = np.zeros_like(A)
A1[breaks[1:-1]] = 1

# Perform cumsum along it to create a staircase like array, as the final output
out = A1.cumsum()

样例运行 -

In [115]: A
Out[115]: array([3, 8, 0, 4, 6, 4, 8, 0, 2, 7, 4, 9, 3, 7, 3, 8, 6, 7, 1, 6])

In [116]: breaks
Out[116]: array([ 0,  4,  9, 11, 18, 20])

In [142]: out
Out[142]: array([0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 3, 3, 3, 3, 3, 3, 3, 4, 4]..)

如果您想要从A中获取这些子向量的均值,可以使用np.bincount函数 -

mean_vals = np.bincount(out, weights=A)/np.bincount(out)

如果您想扩展此功能并使用自定义函数,您可能需要查看MATLAB的accumarray等效项Python/Numpynumpy_groupies,其源代码在这里可用。


我喜欢你的方法,它比我的快。你也可以使用 A1 = np.zeros(breaks[-1]) - unutbu
这解决了将每个子向量设置为常数的简单用例(这只是一个示例)。例如,如果我想计算每个子向量的平均值怎么办? - cfh
你的第一个方法就是 np.unique 带有可选参数 return_inverse 来完成它的工作的。太棒了! - Jaime
@Jaime 是的,我想对于已排序的数组来说它是等价的。在MATLAB中,我们有一个类似的东西unique,它有第三个输出参数。看到所有这些相似之处真是太好了! - Divakar

6

对于你的问题,真正的答案并非唯一,而是有几种可以用作基本构建块的技术。另外一个可能会有帮助的技巧是:

所有的NumPy通用函数(ufuncs)都有一个.reduceat方法,你可以利用它来进行一些计算:

>>> a = np.arange(100)
>>> breaks = np.linspace(0, 100, 11, dtype=np.intp)
>>> counts = np.diff(breaks)
>>> counts
array([10, 10, 10, 10, 10, 10, 10, 10, 10, 10])
>>> sums = np.add.reduceat(a, breaks[:-1], dtype=np.float)
>>> sums
array([  45.,  145.,  245.,  345.,  445.,  545.,  645.,  745.,  845.,  945.])
>>> sums / counts  # i.e. the mean
array([  4.5,  14.5,  24.5,  34.5,  44.5,  54.5,  64.5,  74.5,  84.5,  94.5])

这是离我需要的最接近的内容,但实际上我只想要 reduceat 使用的切片 - 我并不想要减少这些切片!就此而言,我甚至没有一个ufunc。有没有什么东西可以简单地返回 reduceat 计算的切片,并以向量化的方式执行?在我的情况下,所有的切片长度都是相同的。 - Mackie Messer

3
您可以使用 np.repeat 来实现:
In [35]: np.repeat(np.arange(0, len(breaks)-1), np.diff(breaks))
Out[35]: 
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2,
       2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4,
       4, 4, 4, 4, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6, 6, 6, 6,
       6, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 9, 9,
       9, 9, 9, 9, 9, 9, 9, 9])

要计算任意分箱统计信息,您可以使用scipy.stats.binned_statistic函数:

import numpy as np
import scipy.stats as stats

breaks = np.linspace(0, 100, 11, dtype=int)
A = np.random.random(100)

means, bin_edges, binnumber = stats.binned_statistic(
    x=np.arange(len(A)), values=A, statistic='mean', bins=breaks)

stats.binned_statistic 可以计算均值、中位数、计数、总和;或者,为了每个 bin 计算任意统计量,您可以将可调用对象传递给 statistic 参数:

def func(values):
    return values.mean()

funcmeans, bin_edges, binnumber = stats.binned_statistic(
    x=np.arange(len(A)), values=A, statistic=func, bins=breaks)

assert np.allclose(means, funcmeans)

但是我现在如何设置第 i 部分为 i 而避免使用 for 循环呢? - cfh

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接