前言
ndarray对象的内容可以通过索引或切片来访问和修改,与 Python 中 list 的切片操作一样。
1.整数索引
【例1】通过整数索引访问数组
>>> a = np.random.randint(0,10,5)
>>> print(a,a[2])
[2 7 3 6 6] 3
>>> b = np.random.randint(0,10,(5,5))
>>> print(b,b[3][1],b[3,1])
[[3 7 0 7 9]
[1 6 9 3 1]
[2 7 5 0 7]
[6 2 6 1 0]
[5 5 6 8 1]] 2 2
>>> c = np.random.randint(0,10,(3,3,3))
>>> print(c,c[0][0][0],c[0,0,0])
[[[6 1 5]
[9 5 9]
[2 3 5]]
[[1 1 5]
[2 1 5]
[1 5 4]]
[[3 5 9]
[8 9 6]
[7 3 3]]] 6 6
这里用了np.random.randint()方法生成了随机ndarray数组,然后利用整数索引进行访问。
注:[x][y][z] 与 [x,y,z]的效果相同
顾名思义,我们可以把切片理解成一系列切蛋糕的行为,第一个参数是第一刀,第二个参数是最后一刀后面的位置,第三个参数是刀与刀之间的距离,那么整数索引在二维数组就相当于与横纵都仅切1刀,且刀宽为1。
2.切片索引
我们在上一章知道,python切片获得原来数据的副本(深拷贝),而ndarray数据类型切片获得原数据的视图。
ndarray 数组可以基于 0 - n 的下标进行索引,切片对象可以通过内置的 slice 函数,并设置 start, stop 及 step 参数进行,从原数组中切割出一个新数组,也可以通过冒号分隔切片参数 start:stop:step 来进行切片操作。
【例2-1】单维度切片
#一维数组
>>> a = np.random.randint(0,10,5)
>>> print(a)
>>> print(a[:])
>>> print(a[:-2])
>>> print(a[1:])
>>> print(a[::2])
[8 6 4 8 6]
[8 6 4 8 6]
[8 6 4]
[6 4 8 6]
[8 4 6]
#二维数组
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[1 7 5 8 3]
[1 7 8 7 3]
[2 1 7 3 7]
[3 3 4 1 4]
[6 4 3 8 6]]
#打印倒数前2行之前的数组(不包括倒数第二行)
>>> print(a[:-2])
[[1 7 5 8 3]
[1 7 8 7 3]
[2 1 7 3 7]]
#步长为2打印整个数组
>>> print(a[::2])
[[1 7 5 8 3]
[2 1 7 3 7]
[6 4 3 8 6]]
#打印倒数前2列之前的数组(不包括倒数第二列)
>>> print(a[...,:-2])
[[1 7 5]
[1 7 8]
[2 1 7]
[3 3 4]
[6 4 3]]
#打印倒数前2行之前的数组(不包括倒数第二行)
>>> print(a[:-2,...])
[[1 7 5 8 3]
[1 7 8 7 3]
[2 1 7 3 7]]
二维数组单维度切片示意图如下:
上面的案例仅以一维、二维数组为例进行单维度切片(在二维数组中要么是行,要么是列),上升到多维数组后,行列就要换成第几维去描述。
我们注意到a[:-2]
其实是和a[:-2,...]
是等价的,那么就此引出dots:
NumPy 允许使用
...
表示足够多的冒号来构建完整的索引列表。
那么在上述案例中,:::
其实就和...
是等价。对于其他维度的数组,...
可以起到一个补全的效果,我们接下来会在多维度切片中进行演示:
【例2-2】多维度切片
#二维数组
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[7 7 9 8 0]
[7 9 9 9 9]
[2 1 4 7 7]
[4 9 7 3 8]
[2 1 8 2 8]]
#第2行与第3、4列交汇的数据
>>> print(a[1,2:4])
[9 9]
#第2列与第3、4行交汇的数据
>>> print(a[2:4,1])
[1 9]
#第3、4行与第3、4列交汇的数据
>>> print(a[2:4,2:4])
[[4 7]
[7 3]]
#从第一行开始以2为步长的行与从第一行开始以2为步长的列交汇的数据
>>> print(a[::2,::2])
[[7 9 0]
[2 4 7]
[2 8 8]]
#去掉最后一列
>>> print(a[...,:-1])
[[7 7 9 8]
[7 9 9 9]
[2 1 4 7]
[4 9 7 3]
[2 1 8 2]]
二维数组多维度切片示意图:
复杂版:
在这里比较好奇,如果是一行一列,返回的结果是怎样的呢?
>>> print(a[0,0])
7
是一个整数,切片切多了,忘了[0,0]其实就是整数索引 —_-!
3.整数数组索引
先简单看一个案例:
【例3-1】单维度整数数组索引
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[0 3 2 8 0]
[2 6 8 7 9]
[4 0 5 0 5]
[8 5 9 0 0]
[1 9 8 8 5]]
>>> s = [0,2,4]
#取a的第1,3,5行
>>> print(a[s])
[[0 3 2 8 0]
[4 0 5 0 5]
[1 9 8 8 5]]
整数数组解决的是不连续切片且间隔是随机非等差的问题,上面的案例是操作行,我们也可以同时操作行和列:
【例3-2】多维度整数数组索引
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[6 2 9 7 2]
[8 5 4 5 9]
[1 4 3 3 9
[9 8 0 8 8]
[7 1 6 5 2]]
>>> s = [0,2,3]
>>> p = [1,2,4]
>>> q = [1,2]
>>> print(a[s,p])
[2 3 8]
>>> print(a[s,q])
IndexError: shape mismatch: indexing arrays could not be broadcast together with shapes (3,) (2,)
最后一句代码出现问题,原因是传入的两个index数组的数量必须相等,怎样理解,就是将整数索引重复n遍,那么两个index数组当然要相等。
二维数组多维度整数数组切片示意图:
假设a是一个ndarray的二维数组,p是一个一维list,那么a[p]的结果是一个ndarray二维数组。
若p也是一个ndarray数组呢?请看下面的例子:
【例3-3】索引标志为ndarray的整数数组索引(单维度)
#单维度情况
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[5 0 1 7 4]
[3 9 2 5 1]
[3 5 8 3 2]
[5 4 4 6 5]
[5 7 7 5 6]]
>>> p = np.array([[0,2],[2,3]])
>>> print(a[p])
[[[5 0 1 7 4]
[3 5 8 3 2]]
[[3 5 8 3 2]
[5 4 4 6 5]]]
你会发现其实索引标志为ndarray时,其实就是多次整数数组索引,然后再将结果整合到一个ndarray中,在这种单维度的情况下:
目标数组二维+索引标志一维 = 结果数组二维
目标数组二维+索引标志二维 = 结果数组三维
…
显然,结果数组维数 = 目标数组维数 + 索引标志维数 - 1
【例3-4】索引标志为ndarray的整数数组索引(多维度)
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[0 9 8 2 4]
[7 8 7 2 0]
[8 7 3 1 5]
[9 8 4 8 2]
[0 9 7 9 4]]
>>> p = np.array([[0,2],[2,3]])
>>> q = np.array([[0,4],[0,4]])
>>> print(a[p,q])
[[0 5]
[8 2]]
其实可以把这种索引标志为ndarray的切片分解成多个子切片行为,即[a[p1,q1]+a[p2+q2]]
,如下图所示,求解过程可化解为2个普通多维度整数数组索引,如例3-2图所示。
易知,条件允许的情况下:
- 目标数组维数 + 索引标志维数 - 2
- 且多个索引标志维数必须相等,不然会报错
我们再来介绍一下take()函数:
numpy.take(a,indices,axis = None,out = None,mode =‘raise’ )
其中,当axis不是None时,此函数与使用数组索引数组的功能相同,但是若未指定axis时,会默认把目标数组维度打成一维,数据扁平化:
【例3-5】ndarray.take()的使用
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[8 4 0 3 2]
[0 0 1 0 8]
[6 7 4 7 2]
[0 7 9 6 2]
[4 0 9 1 9]]
>>> print(np.take(a,[0,1,2],axis=0))
[[8 4 0 3 2]
[0 0 1 0 8]
[6 7 4 7 2]]
>>> print(np.take(a,[0,1,2],axis=1))
[[8 4 0]
[0 0 1]
[6 7 4]
[0 7 9]
[4 0 9]]
>>> print(np.take(a,[0,1,2]))
[8 4 0]
如上述案例所示,当axis=0时,取的是1-3行;当axis=1时,取的是1-3列;当axis未赋值时,数据被扁平化,取的是扁平化后的前三个数组元素。
4.布尔索引
与其称为布尔索引,不如理解成条件索引:
【例4-1】控制最小值及类型的布尔索引
#控制最小值
>>> a = np.random.randint(0,10,(5,5))
>>> print(a)
[[5 9 6 6 1]
[0 0 4 0 0]
[0 8 2 5 7]
[8 0 2 0 0]
[3 4 0 1 6]]
>>> b = a > 5
>>> print(b)
[[False True True True False]
[False False False False False]
[False True False False True]
[ True False False False False]
[False False False False True]]
>>> print(a[b])
[9 6 6 8 7 8 6]
#控制类型,找到不是nan的数据
>>> c = np.array([np.nan,9,8,7,np.nan,6,5])
>>> d = np.logical_not(np.isnan(c))
>>> print(d)
[False True True True False True True]
>>> print(c[d])
[9. 8. 7. 6. 5.]
我们其实可以看出,最终带入的就是一个布尔数组b,然后利用a[b]去生成满足条件的数组。
【例4-2】布尔索引在matplotlib上的应用
>>> import matplotlib.pyplot as plt
#y = sinx函数图像 x在[0,2pi]之间,最小刻度为pi/20
>>> x = np.linspace(0, 2 * np.pi, 50)
>>> y = np.sin(x)
>>> print(len(x))
>>> plt.plot(x, y)
#mask1
#mask1 = y >= 0
>>> mask1 = np.logical_and(y >= 0, x >= 0)
>>> plt.plot(x[mask1],y[mask1],'ro')
#mask2
>>> mask2 = np.logical_and(y <= 0, x >= 3*np.pi/2 )
>>> plt.plot(x[mask2],y[mask2],'yo')
解释一下np.logical_and
的含义,这个需要同时满足参数条件,比如 np.logical_and(y >= 0, x >= 0)
代表x,y需要同时大于0。
可以把mask理解成一个范式,把它放入x,y中会根据范式规则返回需要的数据。