读书笔记四、基本的索引和切片
一维数组的切片:
arr=np.arange(10) arr Out[29]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) arr[5] Out[30]: 5 arr[5:8] Out[31]: array([5, 6, 7]) arr[5:8]=12 arr Out[33]: array([ 0, 1, 2, 3, 4, 12, 12, 12, 8, 9])
arr_slice=arr[5:8] arr_slice Out[35]: array([12, 12, 12]) arr_slice[1]=12345 arr Out[37]: array([ 0, 1, 2, 3, 4, 12, 12345, 12, 8, 9]) arr_slice[:]=64 arr Out[39]: array([ 0, 1, 2, 3, 4, 64, 64, 64, 8, 9])
对于高维数组,能做的事情更多。在一个二维数组中,各索引位置上的元素不再是标量而是一维数组:
arr2d=np.array([[1,2,3],[4,5,6],[7,8,9]]) arr2d Out[41]: array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) arr2d[2] Out[42]: array([7, 8, 9])
因此,可以对各个元素进行递归访问。可以传入一个以逗号隔开的索引列表来选取单个元素。下面2种方式等价:
arr2d[0][2] Out[43]: 3 arr2d[0,2] Out[44]: 3
在多维数组中,如果省略了后面的索引,则返回对象会是一个维度第一点的ndarray:
arr3d=np.array([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]]) arr3d Out[46]: array([[[ 1, 2, 3], [ 4, 5, 6]], [[ 7, 8, 9], [10, 11, 12]]])
arr3d[0]是一个2×3数组:
arr3d[0] Out[47]: array([[1, 2, 3], [4, 5, 6]])
标量值和数组都可以被赋值给arr3d[0]:
old_values=arr3d[0].copy() #将标量值赋给arr3d[0] arr3d[0]=42 arr3d Out[50]: array([[[42, 42, 42], [42, 42, 42]], [[ 7, 8, 9], [10, 11, 12]]]) #将数组赋给arr3d[0] arr3d[0]=old_values arr3d Out[52]: array([[[ 1, 2, 3], [ 4, 5, 6]], [[ 7, 8, 9], [10, 11, 12]]])
以此类推,arr3d[1,0]可以访问索引以(1,0)开头的那些值:
arr3d[1,0]
Out[53]: array([7, 8, 9])
注意,在上面这些所有选取数组子集的例子中,返回的数组都是视图:
arr3d[1,0]=8 arr3d Out[55]: array([[[ 1, 2, 3], [ 4, 5, 6]], [[ 8, 8, 8], [10, 11, 12]]])
import numpy as np arr=np.arange(10) arr[1:6] Out[3]: array([1, 2, 3, 4, 5])
高维度对象的花样更多,可以在一个或者多个轴上切片,也可以跟整数索引混合使用。对于上面那个二维数组arr2d,其切片方式稍显不同:
arr2d=np.array([[1,2,3],[4,5,6],[7,8,9]]) arr2d Out[5]: array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]) arr2d[:2] Out[6]: array([[1, 2, 3], [4, 5, 6]])
可以看出,它是沿着第0轴(即第一个轴)切片的。也就是说,切片是沿着一个轴向选取元素的。可以一次传入多个切片,像传入多个索引那样:
arr2d[:2,:1] Out[7]: array([[1], [4]])
这样进行切片时,只能得到相同维数的数组视图。通过将整数索引和切片混合,可得到低纬度的切片:
arr2d[1,:2] Out[8]: array([4, 5]) arr2d[2,:1] Out[9]: array([7]) #“只有冒号”表示选取整个轴: arr2d[:,:1] Out[10]: array([[1], [4], [7]])
对切片表达式的赋值操作也会被扩散到整个选区:
arr2d[:2,1:]=0 arr2d Out[12]: array([[1, 0, 0], [4, 0, 0], [7, 8, 9]])
names=np.array(['Bob','Joe','Will','Bob','Will','Joe','Joe']) data=np.random.randn(7,4) names Out[15]: array(['Bob', 'Joe', 'Will', 'Bob', 'Will', 'Joe', 'Joe'], dtype='<U4') data Out[16]: array([[-1.91071805, 0.12663092, -1.53889879, 0.87588762], [-0.39801907, -1.51346505, 1.31478407, 0.76033884], [ 0.5674499 , 0.11646338, 0.66960379, 0.33064397], [-0.14963993, -0.15050284, 1.37466165, -2.25447743], [ 0.06028391, 0.06226708, 0.08291613, -0.19584573], [-0.32005106, 1.13986837, -0.33484255, -0.53863811], [ 0.46754402, 0.2592858 , -0.76507756, 0.45506666]])
假设每个名字都对应data数组中的一行,选出对应于名字“Bob”的所有行。和算术运算一样,数组的比较运算也是矢量化的。因此,对names和字符串“Bob”的比较运算将会产生一个布尔型数组:
names=='Bob' Out[17]: array([ True, False, False, True, False, False, False]) #这个布尔型数组可用于数组索引: data[names=='Bob'] Out[18]: array([[-1.91071805, 0.12663092, -1.53889879, 0.87588762], [-0.14963993, -0.15050284, 1.37466165, -2.25447743]])
布尔型数组的长度必须跟被索引的轴长度一致。此外,还可以将布尔型数组跟切片、整数混合使用:
data[names=='Bob',2:] Out[20]: array([[-1.53889879, 0.87588762], [ 1.37466165, -2.25447743]]) data[names=='Bob',3] Out[21]: array([ 0.87588762, -2.25447743])
要选择除“Bob”以外的其他值,既可以使用不等于符号(!=),也可以通过取反符号(~)对条件进行否定:
names!='Bob' Out[22]: array([False, True, True, False, True, True, True]) data[~(names=='Bob')] Out[25]: array([[-0.39801907, -1.51346505, 1.31478407, 0.76033884], [ 0.5674499 , 0.11646338, 0.66960379, 0.33064397], [ 0.06028391, 0.06226708, 0.08291613, -0.19584573], [-0.32005106, 1.13986837, -0.33484255, -0.53863811], [ 0.46754402, 0.2592858 , -0.76507756, 0.45506666]])
选取三个名字中的两个需要组合应用多个布尔条件,使用&(和)、|(或)之类的布尔运算符即可:
mask=(names=='Bob')|(names=='Will') mask Out[27]: array([ True, False, True, True, True, False, False]) data[mask] Out[28]: array([[-1.91071805, 0.12663092, -1.53889879, 0.87588762], [ 0.5674499 , 0.11646338, 0.66960379, 0.33064397], [-0.14963993, -0.15050284, 1.37466165, -2.25447743], [ 0.06028391, 0.06226708, 0.08291613, -0.19584573]])
data[data<0]=0 data Out[31]: array([[0. , 0.12663092, 0. , 0.87588762], [0. , 0. , 1.31478407, 0.76033884], [0.5674499 , 0.11646338, 0.66960379, 0.33064397], [0. , 0. , 1.37466165, 0. ], [0.06028391, 0.06226708, 0.08291613, 0. ], [0. , 1.13986837, 0. , 0. ], [0.46754402, 0.2592858 , 0. , 0.45506666]])
通过一位布尔数组设置整行或者整列的值:
data[names!='Joe']=7 data Out[33]: array([[7. , 7. , 7. , 7. ], [0. , 0. , 1.31478407, 0.76033884], [7. , 7. , 7. , 7. ], [7. , 7. , 7. , 7. ], [7. , 7. , 7. , 7. ], [0. , 1.13986837, 0. , 0. ], [0.46754402, 0.2592858 , 0. , 0.45506666]])
arr=np.empty((8,4)) for i in range(8): arr[i]=i arr Out[36]: array([[0., 0., 0., 0.], [1., 1., 1., 1.], [2., 2., 2., 2.], [3., 3., 3., 3.], [4., 4., 4., 4.], [5., 5., 5., 5.], [6., 6., 6., 6.], [7., 7., 7., 7.]])
为了以特定顺序选取行子集,只需传入一个用于指定顺序的整数列表或ndarray:
arr[[4,3,0,6]] Out[37]: array([[4., 4., 4., 4.], [3., 3., 3., 3.], [0., 0., 0., 0.], [6., 6., 6., 6.]]) #使用负整数索引将会从末尾开始选取行: arr[[-3,-5,-7]] Out[38]: array([[5., 5., 5., 5.], [3., 3., 3., 3.], [1., 1., 1., 1.]])
一次传入多个索引数组会有点特别。返回的是一个一维数组,其中的元素对应各个索引元组:
arr=np.arange(32).reshape((8,4)) arr Out[40]: 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]]) arr[[1,5,7,2],[0,3,1,2]] Out[42]: array([ 4, 23, 29, 10])
arr[[1,5,7,2]][:,[0,3,1,2]] Out[43]: array([[ 4, 7, 5, 6], [20, 23, 21, 22], [28, 31, 29, 30], [ 8, 11, 9, 10]])
另一个办法是使用np.ix_函数,它将两个一维整数数组转换为一个用于选取方形区域的索引器:
arr[np.ix_([1,5,7,2],[0,3,1,2])] Out[45]: array([[ 4, 7, 5, 6], [20, 23, 21, 22], [28, 31, 29, 30], [ 8, 11, 9, 10]])
记住,花式索引和切片不一样,总是将数据复制到新数组中。