Numpy中的axis/axes详解

首先,在Numpy中,维数(dimensions)通过轴(axes)来扩展,轴的个数被称作rank。这里的rank不是线性代数中的rank(秩),它指代的依旧是维数(number of dimensions)

The number of axes (dimensions) of the array. In the Python world, the number of dimensions is referred to as rank.

 在数学或者物理的概念中,dimensions被认为是在空间中表示一个点所需要的最少坐标个数,但是在Numpy中,dimensions指代的是axes。也就是说,axes、dimensions、rank这几个概念是相通的。

下面举例子,比如我们在3-D空间中定义三个点,用array的形式表达:

>>> import numpy as np
>>> a = np.array([[1,2,3],[2,3,4],[3,4,9]])
>>> a
array([[1, 2, 3],
       [2, 3, 4],
       [3, 4, 9]])

按照以往的理解,我们可能会认为这个array是三维的,但实际上在Numpy中,这个array的dimensions,也就是rank的值是等于2的:

>>> np.ndim(a)
2

因为它只有两个axis,只不过这两个axis的长度(length)均为3:

>>> np.shape(a)
(3, 3)

同时,这个矩阵在线性代数中的rank(秩)依旧是3(这里要加以区分):

>>> np.linalg.matrix_rank(a)
3

也就是说,在Numpy中,任何vector都被看作具有不同长度(length)的一维array。


下面继续聊聊axis/axes,我们依旧先定义一个3-D的array:

>>> b = np.array([[[1,2,3,4],[1,3,4,5]],[[2,4,7,5],[8,4,3,5]],[[2,5,7,3],[1,5,3,7]]])
>>> b
array([[[1, 2, 3, 4],
        [1, 3, 4, 5]],

       [[2, 4, 7, 5],
        [8, 4, 3, 5]],

       [[2, 5, 7, 3],
        [1, 5, 3, 7]]])
>>> b.shape
(3, 2, 4)

我们定义了一个shape为(3, 2, 4)的array,这个shape(用tuple表示)可以理解为在每个轴(axis)上的size,也即占有的长度(length),三个值的含义:axis = 0对应轴上的元素length = 3,axis = 1对应轴上的元素length = 2,axis = 2对应轴上的元素length = 4,也就是说,当我们在array中选定某个元素时,可能用到的最大索引(index)范围,举例:

>>> b[1]
array([[2, 4, 7, 5],
       [8, 4, 3, 5]])
>>> b[1,:,:]             # Equivalent value
array([[2, 4, 7, 5],
       [8, 4, 3, 5]])

这里的b[1]和b[1, :, :]表示在axis = 0的轴上选取第一个元素,同时选取axis = 1和axis = 2上的全部元素。我们可以暂时把多个axes想象成多层layers,当然也可以想象成树结构,虽然这么说并不准确,但是为了方便想象可以暂时这么理解:

axis = 0表示第一层layer:在代码中看到的效果就是b从外向里数第一层 [ ],对应的元素(length = 3)就是第一层 [ ] 中用逗号分隔的全部元素(注意:不要关注第二层及以上 [ ] 里的逗号),很明显这一层的元素数目为3;同理,axis = 1表示第二层layer:第一层layer中用逗号分隔开的任意一个 [ ] 中,再由逗号分隔开的全部元素(length = 2);axis = 2同理,表示最后一层layer中的元素(length = 4)。这么说还是有些乱,我把刚才的array重新布置一下:

像这样用猿们写c/c++代码时候的风格排布下,黑色代表第一层layer,可以看到有三个元素,红色代表第二层layer,在第一层layer相同的前提下有两个元素,蓝色代表第三层layer,在第一层和第二层layer都相同的前提下有4个元素。这样也就对应上了b.shape = (3, 2, 4)的结果。

这时候如果我们想定位到元素8应该怎么索引?可以看到8在第一层layer的第二个元素内,同时在第二层layer的第二个元素内、第三层layer的第一个元素,也就是说:8是axis = 0轴上的第二个位置、axis = 1轴上的第二个位置,axis = 2的第一个位置共同retrieve到的元素。写成代码:

>>> b[1,1,0]

8

噔噔噔~就酱。同时也可以发现,array有几维,我们就可以用几个integer去索引到(index)它的元素

进一步看下,对于b这个array来说,最后一个元素对应的位置就是可以用到的最大index(从0开始):

>>> b[2,1,3]
7

任一axis上再大一点就会出现IndexError了:

>>> b[2,1,4]

Traceback (most recent call last):
  File "<pyshell#38>", line 1, in <module>
    b[2,1,4]
IndexError: index 4 is out of bounds for axis 2 with size 4

之所以要设置不同的axis,是因为对于数据我们可以进行不同维度的处理。例如现在我们收集了四个同学对苹果、榴莲、西瓜这三种水果的喜爱程度进行打分的数据(总分为10),每个同学都有三个特征:

>>> item = np.array([[1,4,8],[2,3,5],[2,5,1],[1,10,7]])
>>> item
array([[1, 4, 8],
       [2, 3, 5],
       [2, 5, 1],
       [1, 10, 7]])

每一行包含了同一个人的三个特征,如果我们想看看哪个同学最喜欢吃水果,那就可以用:

>>> item.sum(axis = 1)
array([13, 10,  8, 18])

可以大概看出来同学4最喜欢吃水果。

如果我们想看看哪种水果最受欢迎,那就可以用:

>>> item.sum(axis = 0)
array([ 6, 22, 21])

可以看出基本是榴莲最受欢迎~~~耶~~~咳、这就是axis存在的意义- -、为了方便地从不同角度对数据进行处理。


接下来我们将对更高维度的array进行计算,会用到提供axis这个parameter的一些函数对轴这个概念进一步说明:

依旧是求和:ndarray.sum(axis=None, dtype=None, out=None, keepdims=False),等价于numpy.sum(a, axis=None, dtype=None, out=None, keepdims=False):

>>> b.sum()
94
>>> b.sum(axis = 0)
array([[ 5, 11, 17, 12],
       [10, 12, 10, 17]])
>>> b.sum(axis = 1)
array([[ 2,  5,  7,  9],
       [10,  8, 10, 10],
       [ 3, 10, 10, 10]])
>>> b.sum(axis = 2)
array([[10, 13],
       [18, 20],
       [17, 16]])

不设置axis的时候,sum返回所有元素的加和;当指定axis时,意味着对某一轴内的所有元素对应求和,同时这一层layer就collapsed掉了,因为这个过程是一个reduction operation。例如求b.sum(axis = 1)时,我们还是回到这个图:

axis = 1,直观上看,意味着我们需要关注红色layer之间的元素,顺次对axis = 0、axis = 2这两个轴上相同位置的不同红框之间的元素求和(好拗口),也就是下图中连线的元素对应求和:

可以得到(非规范写法): 

array([
        [ 2,  5,  7,  9],
        [10,  8, 10, 10],
        [ 3, 10, 10, 10]
      ])

也就是说,假设求和后的array为b_sum,就有:

b_sum[0, 0] = b[0, 0, 0]+b[0, 1, 0]; b_sum[0, 1] = b[0, 0, 1]+b[0, 1, 1]; b_sum[0, 2] = b[0, 0, 2]+b[0, 1, 2]; b_sum[0, 3] = b[0, 0, 3]+b[0, 1, 3];

b_sum[1, 0] = b[1, 0, 0]+b[1, 1, 0]; b_sum[1, 1] = b[1, 0, 1]+b[1, 1, 1]; b_sum[1, 2] = b[1, 0, 2]+b[1, 1, 2]; b_sum[1, 3] = b[1, 0, 3]+b[1, 1, 3];

b_sum[2, 0] = b[2, 0, 0]+b[2, 1, 0]; b_sum[2, 1] = b[2, 0, 1]+b[2, 1, 1]; b_sum[2, 2] = b[2, 0, 2]+b[2, 1, 2]; b_sum[2, 3] = b[2, 0, 3]+b[2, 1, 3];

即在axis = 0、axis = 2相同的情况下,对axis = 1上的全部元素进行求和所得的结果。

同时,在对这个轴上的元素求和之后,这个axis就消失了,我们可以再检查一下求和后的shape:

>>> b.sum(axis = 1).shape
(3, 4)

可以看到,对axis = 1上的元素进行求和后的shape由原来的(3, 2, 4)坍塌到(3, 4),其他轴可以自行验证。


接下来再看排序函数:ndarray.sort(axis=-1, kind='quicksort', order=None),也可以用numpy.sort(a, axis=-1, kind='quicksort', order=None),但是前者会改变b后者不会改变b:

>>> np.sort(b)
array([[[1, 2, 3, 4],
        [1, 3, 4, 5]],

       [[2, 4, 5, 7],
        [3, 4, 5, 8]],

       [[2, 3, 5, 7],
        [1, 3, 5, 7]]])
>>> np.sort(b, axis = 0)
array([[[1, 2, 3, 3],
        [1, 3, 3, 5]],

       [[2, 4, 7, 4],
        [1, 4, 3, 5]],

       [[2, 5, 7, 5],
        [8, 5, 4, 7]]])
>>> np.sort(b, axis = 1)
array([[[1, 2, 3, 4],
        [1, 3, 4, 5]],

       [[2, 4, 3, 5],
        [8, 4, 7, 5]],

       [[1, 5, 3, 3],
        [2, 5, 7, 7]]])

第一种情况,sort默认的axis值为-1,对于我们这个case来说,相当于对axis = 2轴上的元素进行排序(也就是倒数第一个轴)。

还是拿其中一种情况举例子,对于axis = 0,先直观来看,我们需要关注黑色框之间对应的元素:

我只标注出来了在轴axis = 0上进行排序的过程中,第一组元素之间和最后一组元素之间的对应关系,其他组元素同理。

从索引的角度也可以表示:

对b[0, 0, 0]、b[1, 0, 0]、b[2, 0, 0]进行排序,并将结果由小到大重新排列;

对b[0, 0, 1]、b[1, 0, 1]、b[2, 0, 1]进行排序,并将结果由小到大重新排列;

对b[0, 0, 2]、b[1, 0, 2]、b[2, 0, 2]进行排序,并将结果由小到大重新排列;

对b[0, 0, 3]、b[1, 0, 3]、b[2, 0, 3]进行排序,并将结果由小到大重新排列;

对b[0, 1, 0]、b[1, 1, 0]、b[2, 1, 0]进行排序,并将结果由小到大重新排列;

其余同理……

嗯就先举这两个例子吧,其他带有参数axis的函数也可以从这两个角度去分析。

发布了130 篇原创文章 · 获赞 30 · 访问量 4万+

猜你喜欢

转载自blog.csdn.net/W_H_M_2018/article/details/105536728
今日推荐