技术背景
本文所使用的Numpy版本为:Version: 1.20.3。基于Python和C++开发的Numpy一般被认为是Python中最好的Matlab替代品,其中最常见的就是各种Numpy矩阵类型的运算。对于矩阵的运算而言,取对轴和元素是至关重要的,这里我们来看看一些常见的Numpy下标取法和标记。
二维矩阵的取法
这里我们定义一个4*4的矩阵用于取下标,为了方便理解,这个矩阵中所有的元素都是不一样的:
In [1]: import numpy as np In [2]: x = np.arange(16).reshape((4,4)) In [3]: x Out[3]: array([[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15]])
取单行和单个元素
比如我们想取第一行的所有元素,那么就是x[0],如果想取第一行的第一列的元素,那么就是x[0][0],而在numpy中为了简化,可以讲x[0][0]写成x[0,0]的形式:
In [4]: id = 0 In [5]: x[id] Out[5]: array([0, 1, 2, 3]) In [6]: x[id][id] Out[6]: 0 In [7]: x[id,id] Out[7]: 0
下标的list和tuple格式区分
在上一个章节中我们提到的取单个元素x[0,0]的方法,其实本质上等同于x[(0,0)],也就是一个tuple的格式,但是如果把这里的tuple格式换成list,所表示的含义和得到的结果是完全不一样的:
In [8]: id = [1,1] In [9]: x[id] Out[9]: array([[4, 5, 6, 7], [4, 5, 6, 7]]) In [10]: x[id,id] Out[10]: array([5, 5]) In [11]: id = (1,1) In [12]: x[id] Out[12]: 5
这里list格式的id,代表的意思是分别取第二行和第二行的内容,再放到一个完整的矩阵中。如果id设置为[1,2]的话,就是分别取第二行和第三行,而不是取第二行的第二个元素。如果需要取第二行的第二列的元素,那么还是需要用tuple的格式来取下标。有一个比较有意思的点是,如果把刚才的下标重复输入两次,也就是x[[1,2],[1,2]]的话,所表示的含义是分别取x[1][1]和x[2][2],再放到同一个矩阵中,也是一种比较常用的分离式取下标的方法。
冒号的使用
在Numpy的下标中,冒号和后置逗号同时出现,表示轴向全取,比如x[0,:]表示取x的第一行的所有数据,x[:,0]表示取第一列的所有数据:
In [14]: id = 1 In [15]: x[id,:] Out[15]: array([4, 5, 6, 7]) In [16]: x[:,id] Out[16]: array([ 1, 5, 9, 13])
现存的list与numpy.array不相兼容的取法
虽然上文我们提到,如果下标被定义成一个list格式的话,就表示分别取。但是目前Numpy的实现中还有这样的一个遗留问题,就是使用多维的list格式取下标,会自动将最外层转化成tuple的格式,采用tuple的取法。虽然计算时会给出告警,但是目前来说也需要引起一定的注意。
In [17]: id = [[1],[1]] In [18]: x[id]:1: FutureWarning: Using a non-tuple sequence for multidimensional indexing is deprecated; use `arr[tuple(seq)]` instead of `arr[seq]`. In the future this will be interpreted as an array index, `arr[np.array(seq)]`, which will result either in an error or a different result. x[id] Out[18]: array([5]) In [19]: id = np.array([[1],[1]]) In [20]: x[id] Out[20]: array([[[4, 5, 6, 7]], [[4, 5, 6, 7]]])
两个冒号的组合用法
在Numpy中冒号不与后置逗号同时出现时,表示的含义是从冒号前的元素取值到冒号后的元素,比如x[0:3]所表示的元素是[x[0],x[1],x[2]]。如果是两个冒号连用中间没有逗号的话,比如x[0:3:2],表示的是每隔2个元素取一个,最后得到的应该是[x[0],x[2]]。还有一种非常常见的操作是取[::-1]这样的下标,所表示的含义就是对当前轴进行倒序。
In [31]: x[::-1] Out[31]: array([[12, 13, 14, 15], [ 8, 9, 10, 11], [ 4, 5, 6, 7], [ 0, 1, 2, 3]]) In [32]: x[::-1,::-1] Out[32]: array([[15, 14, 13, 12], [11, 10, 9, 8], [ 7, 6, 5, 4], [ 3, 2, 1, 0]])
用None作扩维
虽然在Numpy中有broadcast和expand_dim之类的函数可以对矩阵进行扩维或者是广播,但是更方便的操作是对需要扩展的维度取一个None的下标,比如要把一个(4,4)大小的矩阵扩展成(1,4,4),那么就对下标取[None,:]或者[None,:,:]即可。而如果需要把(4,4)变成(4,1,4),那就需要把None换个位置为[:,None,:]就可以实现:
In [33]: x[None,:] Out[33]: array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15]]]) In [34]: x[:,None,:] Out[34]: array([[[ 0, 1, 2, 3]], [[ 4, 5, 6, 7]], [[ 8, 9, 10, 11]], [[12, 13, 14, 15]]]) In [35]: x[:,:,None] Out[35]: array([[[ 0], [ 1], [ 2], [ 3]], [[ 4], [ 5], [ 6], [ 7]], [[ 8], [ 9], [10], [11]], [[12], [13], [14], [15]]])
高维矩阵的取法
在高维矩阵中,因为没有了行和列这样的概念,因此需要从轴上去理解相关操作,我们先定义一个简单的三维张量:
In [49]: y = np.arange(32).reshape((2,4,4)) In [50]: y Out[50]: array([[[ 0, 1, 2, 3], [ 4, 5, 6, 7], [ 8, 9, 10, 11], [12, 13, 14, 15]], [[16, 17, 18, 19], [20, 21, 22, 23], [24, 25, 26, 27], [28, 29, 30, 31]]])
常规的操作其实都跟前面章节中介绍二维张量一致,这里我们考虑一种比较特殊的场景。就是如果同样用二维矩阵的取法去取,只是第一条轴每个元素取一个id,比如取第0条轴的[0,1]元素和第1条轴的[2,3]元素,那么其实最简单的方案就是在第一个下标的位置加上一个位置元素,这个位置元素用下标id的第一个轴的长度去定义即可:
In [58]: id = np.array([[0,1],[2,3]]) In [59]: y[np.arange(id.shape[0]),id[:,0],id[:,1]] Out[59]: array([ 1, 27])
总结概要
这篇文章的主要内容是梳理在Numpy中经常用到的各种取下标的操作,包括但不限于取指定轴的所有元素、取指定位置的单个元素、取指定位置的多个元素、扩维以及取未显式给定位置的多个元素等等。比较重要的是在Numpy中tuple的取法和list的取法是代表不一样的含义,并且由于历史原因,Numpy中存在一些list取法和numpy.array的取法表示不一致的地方,在本文中进行了总结。
到此这篇关于Numpy的各种下标操作的示例代码的文章就介绍到这了,更多相关Numpy 下标 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!