比较、掩码和布尔逻辑
当想基于某些准则来抽取,修改,计数或对一个数组中的值进行其他操作时,掩码就派上用场了
例如,统计数组中有多少值大于某个给定值,或者删除所有超过某些门限值的异常点
在numpy中,布尔掩码通常是完成此类任务的最高效方式
比较操作
Numpy实现了如<小于和>大于的逐元素比较的通用函数,这些比较运算的结果是一个布尔数据类型的数组
共有6中不同的比较操作:
1 import numpy as np 2 3 x = np.array([1,2,3,4,5]) 4 5 x < 3 # 小于 6 Out[3]: array([ True, True, False, False, False]) 7 8 x > 3 # 大于 9 Out[4]: array([False, False, False, True, True]) 10 11 x <= 3 # 小于等于 12 Out[5]: array([ True, True, True, False, False]) 13 14 x >= 3 # 大于等于 15 Out[6]: array([False, False, True, True, True]) 16 17 x != 3 # 不等于 18 Out[7]: array([ True, True, False, True, True]) 19 20 x == 3 # 等于 21 Out[8]: array([False, False, True, False, False])
利用复合表达式实现数组的逐元素比较也是可行的
1 (2 ** x) == (x ** 2) 2 Out[9]: array([False, True, False, True, False])
比较操作符也是借助通用函数来实现的,例如写 x < 3,Numpy内部会使用np.less(x, 3)
和算术运算符一样,这些比较运算通用函数也是可以用于任意形状,大小的数组
1 rng = np.random.RandomState(0) 2 3 x = rng.randint(10, size=(3, 4)) 4 5 x 6 Out[12]: 7 array([[5, 0, 3, 3], 8 [7, 9, 3, 5], 9 [2, 4, 7, 6]]) 10 11 x < 6 12 Out[13]: 13 array([[ True, True, True, True], 14 [False, False, True, True], 15 [ True, True, False, False]])
操作布尔数组
给定一个布尔数组,可以实现很多操作
1 print(x) 2 [[5 0 3 3] 3 [7 9 3 5] 4 [2 4 7 6]]
1.统计记录的个数
统计布尔数组中的True记录的个数,可以使用np.count_nonzero函数
1 # 有多少值小于6? 2 np.count_nonzero(x < 6) 3 Out[15]: 8
另一种实现方式是利用np.sum,在这个例子里面,False会被解释为0,True会被解释为1:
1 np.sum(x < 6) 2 Out[16]: 8
sum的好处是,与其他Numpy的聚合函数一样,这个求和也可以沿着行或列进行:
1 np.sum(x < 6, axis=1) 2 Out[17]: array([4, 2, 2]) #每一行小于6的个数
如果要快速检查人意或者所有这些值是否为True,可以使用np.any()或np.all()
1 # 有没有值大于8 2 np.any(x >8) 3 Out[18]: True 4 # 有没有值小于0 5 np.any(x < 0) 6 Out[19]: False 7 # 是否所有值都小于10 8 np.all(x < 10) 9 Out[20]: True 10 # 是否所有值都等于6 11 np.all(x == 6) 12 Out[21]: False 13 14 ###np.all和np.any也可以用于沿着特定的坐标轴 15 16 np.all(x < 8, axis=1) 17 Out[22]: array([ True, False, True])
这里的第一行和第三行的所有元素都小于8,而第二行不是所有的元素都小于8
2.布尔运算符
现在已经知道了如何统计小于一个数或大于一个数的情况,如果想统计小于一个数且大于另外一个数的情况,可以通过Python的逐位逻辑运算符 & ,|,^, ~来实现的
同标准的算术运算符一样,Numpy用通用函数重载了这些运算符,这样可实现数组的逐位运算(通常是布尔运算)
1 import numpy as np 2 3 x = np.arange(9).reshape((3,3)) 4 5 np.sum((x > 2) & (x < 5)) 6 Out[3]: 2 7 8 np.sum((x > 2) & (x < 8)) 9 Out[4]: 5
利用A AND B 和 NOT (NOT A OR B)的等价原理
np.sum(~( (x > 2) | (x < 4)) )
Out[9]: 1
将比较运算符和布尔运算符用在数组上,可以实现更多有效的逻辑运算操作
以下总结了逐位的布尔运算符和其对应的通用函数:
将布尔数组作为掩码
通过掩码选择数据的子数据集
以前面x数组为例:假设我们希望抽取数组中所有小于5的元素
1 x = np.random.randint(10, size=(3,4)) 2 3 x 4 Out[13]: 5 array([[6, 0, 7, 1], 6 [2, 2, 8, 7], 7 [5, 1, 2, 6]]) 8 9 x < 5 # 得到一个布尔数组 10 Out[14]: 11 array([[False, True, False, True], 12 [ True, True, False, False], 13 [False, True, True, False]])
为了将这些值选出,可以进行简单的索引,即掩码操作
1 x[x<5] 2 Out[15]: array([0, 1, 2, 2, 1, 2])
返回的是一维数组,包含了所有满足条件的值,所有这些值是掩码数组对应位置为True的值
花哨的索引
花哨的索引和前面那些简单的索引非常类似,但是传递的是索引数组,而不是单个标量
花哨的索引让我们能够快速获得并修改复杂的数组值的子数据集
探索花哨的索引
传递一个索引数组来一次性获得多个数组元素
假设希望获得三个不同的元素,可以这样实现:
另一种方法是通过传递索引的单个列表或数组来获取相同的结果:
利用花哨的索引,结果的形状与索引数组的形状一致,而不是与被索引数组的形状一样
花哨的索引也对多个维度适用
和标准的索引一样,第一个索引指的是行,第二个索引指的是列:
结果的第一个值是x[0, 2],第二个值是x[1, 1],第三个值是x[2, 3]
当我们将一个列向量和一个行向量组合在一个索引中时,会得到一个二维结果
这里的每一行的值都与每一列的向量匹配,正如我们看到的广播的算术运算:
需要注意的是,花哨索引返回的值反映的是广播后的索引数组的形状,而不是被索引的数组的形状
组合索引
花哨的索引可以与其他索引结合起来形成更强大的索引操作:
可以将花哨索引和简单的索引结合使用:
也可以将花哨的索引和切片操作组合使用:
更可以将花哨索引和掩码组合使用
用花哨的索引修改值
正如花哨的索引可以获取部分数组,也可以用来修改部分数组
设置数组中对应的值:
可以用任何的赋值操作来实现:
需要注意的是,操作重复的索引会导致一些出乎意料的结果产生,如下:
4去了哪里,x[0] = 4,然后x[0] = 6,覆盖地掉了,算是合理的解释,但是:
你可能期望的是x[3] = 2, x[4] = 3,因为这些索引值重复的次数,但为什么结果跟我们想的不一样呢
???????
如果希望累加,可以借助通用函数的at()方法来实现:
at()函数在这里对给定的操作,给定的索引以及给定的值执行的是就地操作,另一个实现该功能的类似方法是通用函数中的reduceat()函数