使用numpy.vectorize()旋转NumPy数组的所有元素

时间:2017-06-25 12:56:09

标签: python numpy vectorization

我正处于学习NumPy的开始阶段。我有一个3x3矩阵的Numpy数组。我想创建一个新的数组,其中每个矩阵旋转90度。我已经研究了answer,但我仍然无法弄清楚我做错了什么。

import numpy as np

# 3x3
m = np.array([[1,2,3], [4,5,6], [7,8,9]])

# array of 3x3
a = np.array([m,m,m,m])

# rotate a single matrix counter-clockwise
def rotate90(x):
    return np.rot90(x)

# function that can be called on all elements of an np.array
# Note: I've tried different values for otypes= without success
f = np.vectorize(rotate90)

result = f(a)
# ValueError: Axes=(0, 1) out of range for array of ndim=0.
# The error occurs in NumPy's rot90() function.

注意:我意识到我可以执行以下操作,但我想了解矢量化选项。

t = np.array([ np.rot90(x, k=-1) for x in a])

2 个答案:

答案 0 :(得分:5)

无需单独进行轮换:numpy具有内置numpy.rot90(m, k=1, axes=(0, 1))功能。默认情况下,矩阵在第一维和第二维上旋转。

如果你想要更深一级旋转,你只需设置旋转发生的轴,更深一层(如果你想在不同的方向旋转,也可以选择交换它们)。或者如文档所述:

  

axes: (2,) array_like

     

阵列在由。定义的平面中旋转   轴。轴必须不同。

所以我们旋转 y z 平面(如果我们标注尺寸 x y z )因此我们指定(2,1)(1,2)

当您想要旋转到右/左时,您只需正确设置axes

np.rot90(a,axes=(2,1)) # right
np.rot90(a,axes=(1,2)) # left

这将旋转所有矩阵,例如:

>>> np.rot90(a,axes=(2,1))
array([[[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]],

       [[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]],

       [[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]],

       [[7, 4, 1],
        [8, 5, 2],
        [9, 6, 3]]])

或者,如果您想要向左旋转

>>> np.rot90(a,axes=(1,2))
array([[[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]],

       [[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]],

       [[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]],

       [[3, 6, 9],
        [2, 5, 8],
        [1, 4, 7]]])

请注意,您只能从 numpy 1.12和(可能)未来版本指定axes

答案 1 :(得分:1)

通常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

应用于2d 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元素进行动作。

vectorizesignature

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告诉它传递一个2d数组并期望返回一个2d数组。 (我应该探讨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方法对于较大的数组会做得更好,但我怀疑在这种情况下。

使用axes参数的

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数组,就像其他代码一样。

frompyfuncvectorize的基础函数,并返回一个对象数组。在这里,我创建了一个类似a的数组,除了它是1d,包含多个m数组。它是一个数组数组,而不是3d数组。