通常np.vectorize 用于将标量(Python,非numpy)函数应用于数组或数组集的所有元素。有一个注释经常被忽略:
vectorize 函数主要是为了方便,而不是为了
表现。该实现本质上是一个 for 循环。
In [278]: m = np.array([[1,2,3],[4,5,6]])
In [279]: np.vectorize(lambda x:2*x)(m)
Out[279]:
array([[ 2, 4, 6],
[ 8, 10, 12]])
这会将m 的每个元素乘以 2,为我们处理循环文书工作。
更好的是,当给定几个数组时,它会广播(“外积”的概括)。
In [280]: np.vectorize(lambda x,y:2*x+y)(np.arange(3), np.arange(2)[:,None])
Out[280]:
array([[0, 2, 4],
[1, 3, 5]])
这会将(x,y) 标量元组提供给 lambda,用于针对 (2,1) 数组广播的 (3,) 数组的所有组合,从而生成 (2,3) 数组。可以看成是map的广播扩展。
np.vectorize(np.rot90) 的问题在于 rot90 采用二维数组,但 vectorize 将提供标量。
但是我在文档中看到v1.12 他们添加了一个签名参数。这是我第一次使用它。
您的问题 - 将 np.rot90 应用于 3d 数组的 2d 元素:
In [266]: m = np.array([[1,2,3],[4,5,6]])
In [267]: a = np.stack([m,m])
In [268]: a
Out[268]:
array([[[1, 2, 3],
[4, 5, 6]],
[[1, 2, 3],
[4, 5, 6]]])
虽然您可以将此 a 描述为 2d 数组的数组,但最好将其视为 3d 整数数组。这就是np.vectorize(myfun)(a) 看到它的方式,给myfun 每个数字。
应用于二维m:
In [269]: np.rot90(m)
Out[269]:
array([[3, 6],
[2, 5],
[1, 4]])
借助 Python 工作马,列表推导:
In [270]: [np.rot90(i) for i in a]
Out[270]:
[array([[3, 6],
[2, 5],
[1, 4]]), array([[3, 6],
[2, 5],
[1, 4]])]
结果是一个列表,但我们可以将其包装在 np.array 中。
Python map 做同样的事情。
In [271]: list(map(np.rot90, a))
Out[271]:
[array([[3, 6],
[2, 5],
[1, 4]]), array([[3, 6],
[2, 5],
[1, 4]])]
理解和映射都在 a 的第一个维度上进行迭代,对生成的 2d 元素执行操作。
vectorize 与signature:
In [272]: f = np.vectorize(np.rot90, signature='(n,m)->(k,l)')
In [273]: f(a)
Out[273]:
array([[[3, 6],
[2, 5],
[1, 4]],
[[3, 6],
[2, 5],
[1, 4]]])
signature 告诉它传递一个二维数组并期望返回一个二维数组。 (我应该探索signature 如何使用otypes 参数。)
一些快速的时间比较:
In [287]: timeit np.array([np.rot90(i) for i in a])
10000 loops, best of 3: 40 µs per loop
In [288]: timeit np.array(list(map(np.rot90, a)))
10000 loops, best of 3: 41.1 µs per loop
In [289]: timeit np.vectorize(np.rot90, signature='(n,m)->(k,l)')(a)
1000 loops, best of 3: 234 µs per loop
In [290]: %%timeit f=np.vectorize(np.rot90, signature='(n,m)->(k,l)')
...: f(a)
...:
1000 loops, best of 3: 196 µs per loop
所以对于一个小数组,Python 列表方法要快很多。有时,numpy 方法在更大的数组上效果更好,尽管我对此表示怀疑。
rot90 带有轴参数更好,并且可以很好地处理更大的数组:
In [292]: timeit np.rot90(a,axes=(1,2))
100000 loops, best of 3: 15.7 µs per loop
查看np.rot90 代码,我发现它只是在执行np.flip(反向)和np.transpose,根据k 进行各种组合。在这种情况下,它正在执行以下操作:
In [295]: a.transpose(0,2,1)[:,::-1,:]
Out[295]:
array([[[3, 6],
[2, 5],
[1, 4]],
[[3, 6],
[2, 5],
[1, 4]]])
(这比rot90 还要快。)
我怀疑 vectorize 和 signature 正在做类似的事情:
In [301]: b = np.zeros(2,dtype=object)
In [302]: b[...] = [m,m]
In [303]: f = np.frompyfunc(np.rot90, 1,1)
In [304]: f(b)
Out[304]:
array([array([[3, 6],
[2, 5],
[1, 4]]),
array([[3, 6],
[2, 5],
[1, 4]])], dtype=object)
np.stack(f(b)) 会像其他代码一样将对象数组转换为 3d 数组。
frompyfunc 是vectorize 的底层函数,并返回一个对象数组。在这里,我创建了一个类似于您的 a 的数组,但它是 1d 的,包含多个 m 数组。它是一个数组数组,而不是 3d 数组。