数组索引指的是通过索引信息来获取数组中元素的一种方法,通常在[]
中给出目标元素的索引信息,在NumPy
中,索引规则是非常强大和灵活的。
单元素索引
对一维数组来说,索引的规则和原生Python
中的索引规则相同,以0
作为起始位置,如果索引值为负数,则从数组末尾开始查询。
import numpy as np
v = np.arange(4) # array([0, 1, 2, 3])
print(v[0], v[-1]) # 0 3
与list
等序列类型不同,NumPy
数组可以将每个维度的索引值写在一起对多维数组进行索引操作,而不是将各个维度的索引值写在多个[]
中。如果提供的索引值数量小于数组的维度,则会生成一个子数组。
import numpy as np
v = np.array([[1, 2], [3, 4]])
x_0 = v[0, 0] # 1
x_1 = v[0, 1] # 2
sub_v = v[1] # array([3, 4])
对上面的数组v
,现在有两种方式可以索引到其中的一个元素,一种是直接提供各维度的索引信息:v[0, 0]
,另外一种是通过子数组二次索引v[0][0]
。这两种索引方式中,第二种效率会差一点,因为中间生成了一次子数组。通过下面这个例子可以对比一下:
import numpy as np
import time
v = np.ones((2, 3, 4, 2), dtype=np.int8) # 生成一个四维数组,维度越高,差别越明显
sum = 0
start = 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 np
v = 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 np
v = np.arange(6)
t = v[::]
print(id(v), id(t)) # 140670553636016 140670346333120
[::]
是在Python
中对序列类型进行拷贝的一种方法,生成原始数据的一个备份。结合上面的操作,我们看到,v
,t
两个数组的id
值并不相同,这意味这它们在内存中已经不是同一个对象,但并不能认为他们之间毫无关联了,现在对v
和t
中的元素进行修改:
v[0], t[-1] = 666, 999
print(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] = 666
print(repr(v)) # array([0, 1, 2])
print(repr(t)) # array([666, 1, 2])
数组切片之后可以直接用常数赋值,这一点和Python
的序列类型有些不同。
"""
被切出来的元素都会被赋值
"""
import numpy as np
a = np.arange(6) # array([0, 1, 2, 3, 4, 5])
a[1:3] = 666
print(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 iterable
li[1:3] = [666] # [0, 666, 3, 4]
索引数组
可以用一个数组的索引值构成一个新的数组(索引数组)[也可以是列表],用这个索引数组来对原数组进行索引操作,返回每个索引值对应元素构成的新数组,听起来有点绕……直接看代码把:
import numpy as np
v = np.arange(1, 10)
index_v = np.array([0, 0, 3, 3, -1, -2]) # 由索引值构成的新数组
t = v[index_v] # 用索引数组进行索引操作
"""
v[0] ---> 1 v[3] ---> 4
v[-1] ---> 9 v[-2] ---> 8
"""
print(repr(t)) # array([1, 1, 4, 4, 9, 8])
mod_index = [1, 2, 3]
v[mod_index] = 666
print(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] ---> 6
v[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 np
v = 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 > 3
v[mask] = 0
print(v)
# [[0 1 2]
# [2 3 0]
# [3 0 0]]