向量化 Numpy 切片操作
Vectorizing a Numpy slice operation
假设我有一个 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
循环,而是将这些操作矢量化?
你可以使用 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)
您可以使用简单的 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
equivalent for Python/Numpy
: accum
whose source code is available here.
您的问题确实没有单一的答案,但您可以使用多种技术作为构建基块。另一个你可能会觉得有用的:
所有 numpy ufunc 都有一个 .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])
假设我有一个 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
循环,而是将这些操作矢量化?
你可以使用 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)
您可以使用简单的 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
equivalent for Python/Numpy
: accum
whose source code is available here.
您的问题确实没有单一的答案,但您可以使用多种技术作为构建基块。另一个你可能会觉得有用的:
所有 numpy ufunc 都有一个 .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])