数组索引指的是通过索引信息来获取数组中元素的一种方法,通常在[]中给出目标元素的索引信息,在NumPy中,索引规则是非常强大和灵活的。
单元素索引
对一维数组来说,索引的规则和原生Python中的索引规则相同,以0作为起始位置,如果索引值为负数,则从数组末尾开始查询。
import numpy as npv = np.arange(4) # array([0, 1, 2, 3])print(v[0], v[-1]) # 0 3
与list等序列类型不同,NumPy数组可以将每个维度的索引值写在一起对多维数组进行索引操作,而不是将各个维度的索引值写在多个[]中。如果提供的索引值数量小于数组的维度,则会生成一个子数组。
import numpy as npv = np.array([[1, 2], [3, 4]])x_0 = v[0, 0] # 1x_1 = v[0, 1] # 2sub_v = v[1] # array([3, 4])
对上面的数组v,现在有两种方式可以索引到其中的一个元素,一种是直接提供各维度的索引信息:v[0, 0],另外一种是通过子数组二次索引v[0][0]。这两种索引方式中,第二种效率会差一点,因为中间生成了一次子数组。通过下面这个例子可以对比一下:
import numpy as npimport timev = np.ones((2, 3, 4, 2), dtype=np.int8) # 生成一个四维数组,维度越高,差别越明显sum = 0start = time.clock()for i in range(1000000):sum += v[1][1][1][1] # 加法运算,消耗下时间stop = time.clock()for i in range(1000000):sum -= v[1, 1, 1, 1] # 和上面的差别主要来自索引取值end = time.clock()print("Time for v[1][1][1][1]: %.2f" % (stop - start))print("Time for v[1, 1, 1, 1]: %.2f" % (end - stop))# Time for v[1][1][1][1]: 0.73s# Time for v[1, 1, 1, 1]: 0.34s
多元素索引(切片)
这里和原生Python中对序列类型进行切片的规则是一样的,可以从整个数组中按规则取出一部分构成新的数组。
import numpy as npv = np.arange(6)print(repr(v[1:3])) # array([1, 2])print(repr(v[::2])) # array([0, 2, 4])v = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9])print(repr(v[:2, :2]))# array([[1, 2],# [4, 5]])
但是,对NumPy数组的切片,并不仅仅是生成了对应数据的一份拷贝,而是建立了和原始数据相关联的视图。看下面这个例子:
import numpy as npv = np.arange(6)t = v[::]print(id(v), id(t)) # 140670553636016 140670346333120
[::]是在Python中对序列类型进行拷贝的一种方法,生成原始数据的一个备份。结合上面的操作,我们看到,v,t两个数组的id值并不相同,这意味这它们在内存中已经不是同一个对象,但并不能认为他们之间毫无关联了,现在对v和t中的元素进行修改:
v[0], t[-1] = 666, 999print(repr(v)) # array([666, 1, 2, 3, 4, 999])print(repr(t)) # array([666, 1, 2, 3, 4, 999])
可以看到,不管修改哪个数组,都会对另一个造成影响。如果确实想得到拷贝形式的结果,z在执行切片的操作后,还要显示地调用copy()方法:
v = np.arange(3)t = v[::].copy()t[0] = 666print(repr(v)) # array([0, 1, 2])print(repr(t)) # array([666, 1, 2])
数组切片之后可以直接用常数赋值,这一点和Python的序列类型有些不同。
"""被切出来的元素都会被赋值"""import numpy as npa = np.arange(6) # array([0, 1, 2, 3, 4, 5])a[1:3] = 666print(a) # [ 0 666 666 666 4 5]b = a.reshape(2, 3)b[:, :2] = 666"""[:] == [::] == [0:2] 表示第一维度维度元素全选"""print(b)# [[666 666 2]# [666 666 5]]li = [0, 1, 2, 3, 4]li[1:3] = 666 # TypeError: can only assign an iterableli[1:3] = [666] # [0, 666, 3, 4]
索引数组
可以用一个数组的索引值构成一个新的数组(索引数组)[也可以是列表],用这个索引数组来对原数组进行索引操作,返回每个索引值对应元素构成的新数组,听起来有点绕……直接看代码把:
import numpy as npv = np.arange(1, 10)index_v = np.array([0, 0, 3, 3, -1, -2]) # 由索引值构成的新数组t = v[index_v] # 用索引数组进行索引操作"""v[0] ---> 1 v[3] ---> 4v[-1] ---> 9 v[-2] ---> 8"""print(repr(t)) # array([1, 1, 4, 4, 9, 8])mod_index = [1, 2, 3]v[mod_index] = 666print(repr(v)) # array([ 1, 666, 666, 666, 5, 6, 7, 8, 9])
更好的理解应该是对应元素去取代索引数组中的索引值,从而产生新的数组。
index_v = np.array([[0, 0], [3, 3], [-1, -2]])t = v[index_v]print(repr(t))# array([[1, 1],# [4, 4],# [9, 8]])
对于多维数组,从形式上来看,不是特别容易理解:要将不同维度上的索引值分别构成数组。
v = np.array([[1, 2, 3, 4],[5, 6, 7, 8],[9, 10, 11, 12]])index_x, index_y = [0, 1, 2], [2, 1, 0]t = v[index_x, index_y]"""v[0, 2] ---> 3 v[1, 1] ---> 6v[2, 0] ---> 9"""print(repr(t)) # array([3, 6, 9])"""下面这么写应该比较容易理解,返回数组的「尺寸」是和索引数组「尺寸」对应的"""index_x = [[0, 1],[1, 2]]index_y = [[0, 1],[1, 2]]t = v[index_x, index_y] # 这其实就是单元素索引的写法 array[x, y], array[x, y, z]..."""便于理解:将两个维度的索引值进行叠加,可以得到高维的直接索引值[[(0, 0), (1, 1)],[(1, 1), (2, 2)]]"""print(repr(t))# array([[ 1, 6],# [ 6, 11]])
掩码索引
掩码索引(boolean index),是由boolean类型元素构成的数组,用这样的数组,可以对原数组进行数据筛选工作。
import numpy as npv = np.array([[1, 2, 3, 4],[2, 4, 6, 8],[3, 6, 9, 12],[999, 8, 12, 16]])mask = v > 5 # 生成掩码数组print(repr(mask))# array([[False, False, False, False],# [False, False, True, True],# [False, True, True, True],# [ True, True, True, True]])t = v[mask] # 通过掩码筛选出所有符合条件的元素print(repr(t)) # array([ 6, 8, 6, 9, 12, 999, 8, 12, 16])"""掩码数组维度和数组维度相同,筛选出来的是元素掩码数组维度比数组维度小,筛选出来的是子数组"""sub_mask = [False, True, False, False] # 一维,表示保留第二个元素sub_v = v[sub_mask] # array([[2, 4, 6, 8]]) # 子数组[2, 4, 6, 8]是v的第二个元素"""将数组中大于 3 的元素全部置零"""v = np.array([[0, 1, 2], [2, 3, 4], [3, 4, 5]])mask = v > 3v[mask] = 0print(v)# [[0 1 2]# [2 3 0]# [3 0 0]]
