目录
- 布尔索引
- 花式索引 (Fancy Indexing)
- 二者的联系?
申明:本文中提到的数组就是特指numpy的数据结构ndarray,同理,一维数组或者N维数组,也是指一维活着N维ndarray。
参考资料:
(https://docs.scipy.org/doc/numpy/reference/arrays.indexing.html#indexing)
Python for Data Analysis 2nd Edition
布尔索引
我们可以通过一个布尔数组来索引目标数组,以此找出与布尔数组中值为True的对应的目标数组中的数据(后面通过实例可清晰的观察)。需要注意的是,布尔数组的长度必须与目标数组对应的轴的长度一致。下面通过几个例子来说明。
一维数组的索引
布尔数组中,下标为0,3,4的位置是True,因此将会取出目标数组中对应位置的元素。
In [24]: arr = np.arange(7)
In [25]: booling1 = np.array([True,False,False,True,True,False,False])
In [26]: arr[booling1]
Out[26]: array([0, 3, 4])
二维数组的索引
布尔数组中,下标为0,3,4的位置是True,因此将会取出目标数组中第0,3,4行。
In [27]: arr = np.arange(28).reshape((7,4))
In [28]: arr
Out[28]:
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]])
In [29]: booling1 = np.array([True,False,False,True,True,False,False])
In [30]: arr[booling1]
Out[30]:
array([[ 0, 1, 2, 3],
[12, 13, 14, 15],
[16, 17, 18, 19]])
我们还可以通过数组的逻辑运算来作为索引(实际上数组的逻辑运算的结果,也就是一个布尔数组)。假设我们有一个长度为7的字符串数组,然后对这个字符串数组进行逻辑运算,进而把元素的结果(布尔数组)作为索引的条件传递给目标数组(本质上,和上面那个例子是类似的)。例如,还是上面例子中的数组arr,现在就胡乱想象成每一行数据是一个人的一个月其中四天赚的钱。这7行中,可能有某些行属于特定的人,那就想象成不同月份赚到的钱。
In [35]: names = np.array(['Ben','Tom','Ben','Jeremy','Jason','Michael','Ben'])
In [36]: names == 'Ben'
Out[36]: array([ True, False, True, False, False, False, True], dtype=bool)
# 找出Ben赚的钱的明细
In [37]: arr[names == 'Ben']
Out[37]:
array([[ 0, 1, 2, 3],
[ 8, 9, 10, 11],
[24, 25, 26, 27]])
# 在此基础上,我们还可以添加常规的索引和切片操作
In [38]: arr[names == 'Ben',3]
Out[38]: array([ 3, 11, 27])
In [39]: arr[names == 'Ben',1:4]
Out[39]:
array([[ 1, 2, 3],
[ 9, 10, 11],
[25, 26, 27]])
上面的例子,通过逻辑运算把属于Ben的明细找了出来。那如果我们想查找不属于Ben的明细,则可以通过!=或者~运算。这种逻辑运算,还可以用来做数据清洗,后面会介绍到。
In [40]: arr[names!='Ben']
Out[40]:
array([[ 4, 5, 6, 7],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
In [41]: arr[~(names=='Ben')]
Out[41]:
array([[ 4, 5, 6, 7],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
多个逻辑运算的与和或也是支持的。例如,找出Tom或者Jason的明细,并且想对他们残忍点,把他们的钱都清零。
In [44]: arr[(names == 'Jason') | (names == 'Tom')]
Out[44]:
array([[ 4, 5, 6, 7],
[16, 17, 18, 19]])
In [45]: arr[(names == 'Jason') | (names == 'Tom')] = 0
In [46]: arr
Out[46]:
array([[ 0, 1, 2, 3],
[ 0, 0, 0, 0],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[ 0, 0, 0, 0],
[20, 21, 22, 23],
[24, 25, 26, 27]])
除此之外,我们也可以目标数组上做逻辑运算。实际上,这种逻辑运算的到的结果是和目标数组维度和长度都一样的布尔数组。例如,找出arr中大于15的元素,与上面的例子不一样,这个例子返回的结果是一个一维数组。
In [51]: arr[arr>15]
Out[51]: array([16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27])
借上面的例子引申一下,假设现在想把上述数组中,小于或者等于15的数归零,类似于数据清洗,那么可以通过下面的方式。
In [139]: arr
Out[139]:
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]])
In [140]: arr[arr<=15]=0
In [141]: arr
Out[141]:
array([[ 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 0, 0, 0, 0, 0],
[ 0, 0, 16, 17, 18, 19, 20],
[21, 22, 23, 24, 25, 26, 27]])
花式索引 (Fancy Indexing)
花式索引是NumPy用来描述使用整型数组(这里的数组,可以是NumPy的数组,也可以是python自带的list)作为索引的术语,其意义是根据索引数组的值作为目标数组的某个轴的下标来取值。对于使用一维整型数组作为索引,如果目标是一维数组,那么索引的结果就是对应位置的元素;如果目标是二维数组,那么就是对应下标的行。
In [69]: arr = np.array(['zero','one','two','three','four'])
In [70]: arr[[1,4]]
Out[70]:
array(['one', 'four'],
dtype='<U5')
In [71]: arr = np.empty((8,4),dtype=np.int)
In [72]: for i in range(8):
...: arr[i] = i
...:
In [73]: arr
Out[73]:
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]])
In [75]: arr[[4,3,0,6]]
Out[75]:
array([[4, 4, 4, 4],
[3, 3, 3, 3],
[0, 0, 0, 0],
[6, 6, 6, 6]])
In [76]: arr[[-3,-5,-7]]
Out[76]:
array([[5, 5, 5, 5],
[3, 3, 3, 3],
[1, 1, 1, 1]])
对于使用两个整型数组作为索引的时候,那么结果是按照顺序取出对应轴的对应下标的值。特别注意,这两个整型数组的shape应该一致,或者其中一个数组应该是长度为1的一维数组(与NumPy的Broadcasting机制于关系)。例如,以[1,3,5],[2,4,6]这两个整型数组作为索引,那么对于二维数组,则取出(1,2),(3,4),(5,6)这些坐标对应的元素。
In [77]: arr = np.arange(42).reshape(6,7)
In [78]: arr
Out[78]:
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, 32, 33, 34],
[35, 36, 37, 38, 39, 40, 41]])
In [79]: arr[[1,3,5],[2,4,6]]
Out[79]: array([ 9, 25, 41])
二者的联系?
个人的猜想是,布尔索引和花式索引之间,是有联系的。窃以为布尔索引是通过花式索引来实现的(虽然不100%确定,但是我个人感觉可以这么理解。各位请自行判断)。为什么这么说?先来看看官方的文档对布尔索引的说明:
Boolean array indexing
A single boolean index array is practically identical to x[obj.nonzero()]
上面的意思,不就是说x[obj]是等价x[obj.nonzero()](这里obj是一个布尔数组)。我们来验证一下:
In [112]: arr = np.arange(12).reshape(3,4)
In [113]: i = np.array([True,False,True])
In [114]: i.nonzero()
Out[114]: (array([0, 2]),)
In [115]: arr[i]
Out[115]:
array([[ 0, 1, 2, 3],
[ 8, 9, 10, 11]])
In [116]: arr[i.nonzero()]
Out[116]:
array([[ 0, 1, 2, 3],
[ 8, 9, 10, 11]])
通过以上例子可知大概的关系:arr[i] <=> arr[i.nonzero()] => arr[(array([0,2]),)] => arr[array([0,2]),] => arr[[0,2]](到此为止就是花式索引了)。在来看一个复杂点的例子:
In [131]: i
Out[131]: array([ True, False, True], dtype=bool)
In [132]: j
Out[132]: array([ True, True, False, False], dtype=bool)
In [133]: i.nonzero()
Out[133]: (array([0, 2]),)
In [134]: j.nonzero()
Out[134]: (array([0, 1]),)
In [135]: arr[i,j]
Out[135]: array([0, 9])
# 这里不大清楚,为什么结果是一个二维数组
In [136]: arr[i.nonzero(),j.nonzero()]
Out[136]: array([[0, 9]])
In [137]: arr[[0,2],[0,1]]
Out[137]: array([0, 9])