当前位置: 首页 > news >正文

NumPy 比较、掩码与布尔逻辑

文章目录

  • 比较、掩码与布尔逻辑
    • 示例:统计下雨天数
    • 作为通用函数(Ufuncs)的比较运算符
    • 使用布尔数组
      • 计数条目
      • 布尔运算符
    • 布尔数组作为掩码
    • 使用关键字 and/or 与运算符 &/| 的区别

比较、掩码与布尔逻辑

本文介绍如何使用布尔掩码来检查和操作 NumPy 数组中的值。
当你想根据某些条件提取、修改、计数或以其他方式处理数组中的值时,就会用到掩码:例如,你可能希望统计所有大于某个值的元素,或者移除所有高于某个阈值的异常值。
在 NumPy 中,布尔掩码通常是完成此类任务最高效的方法。

示例:统计下雨天数

假设你有一组数据,表示某个城市一年中每天的降水量。
例如,这里我们将使用 Pandas 加载 2015 年西雅图的每日降雨统计数据:

import numpy as np
from vega_datasets import data# Use DataFrame operations to extract rainfall as a NumPy array
rainfall_mm = np.array(data.seattle_weather().set_index('date')['precipitation']['2015'])
len(rainfall_mm)
365

该数组包含 365 个数值,表示 2015 年 1 月 1 日至 12 月 31 日每天的降雨量(单位:毫米)。

作为初步的可视化,我们可以通过下图(使用 Matplotlib 生成)查看雨天的直方图:

%matplotlib inline
import matplotlib.pyplot as plt
plt.style.use('seaborn-v0_8-whitegrid')
plt.hist(rainfall_mm, 40);

直方图

这个直方图让我们对数据有了一个大致的了解:尽管这座城市以多雨著称,但实际上 2015 年西雅图大多数日子的降雨量几乎为零。
不过,这并不能很好地展示我们想要了解的信息:比如,这一年中有多少个下雨天?下雨天的平均降水量是多少?有多少天的降水量超过了 10 毫米?

一种方法是手动回答这些问题:我们可以遍历数据,每当遇到落在某个范围内的数值时就递增计数器。
但正如本章多次讨论的那样,从编写代码和计算结果的效率来看,这种方法都非常低效。
我们在NumPy 数组上的计算:通用函数中看到,NumPy 的 ufunc 可以用来替代循环,对数组进行快速的逐元素算术运算;同样地,我们也可以使用其他 ufunc 对数组进行逐元素比较,然后操作结果来回答我们关心的问题。
我们暂且把数据放在一边,先来讨论 NumPy 中的一些通用工具,看看如何利用掩码快速回答这类问题。

作为通用函数(Ufuncs)的比较运算符

NumPy 数组上的计算:通用函数 介绍了通用函数(ufuncs),并重点讲解了算术运算符。我们看到,对数组使用 +-*/ 等运算符会进行逐元素操作。
NumPy 还实现了诸如 <(小于)和 >(大于)这样的比较运算符,并将它们作为逐元素的通用函数。
这些比较运算符的结果总是一个布尔类型的数据数组。
所有六种标准的比较操作都可供使用:

x = np.array([1, 2, 3, 4, 5])
x < 3   # less than 小于
array([ True,  True, False, False, False])
x > 3  # greater than 大于
array([False, False, False,  True,  True])
x <= 3  # less than or equal to 小于等于
array([ True,  True,  True, False, False])
x >= 3  # greater than or equal to 大于等于
array([False, False,  True,  True,  True])
x != 3  # not equal 不等于
array([ True,  True, False,  True,  True])
x == 3  # equal 等于
array([False, False,  True, False, False])

也可以对两个数组进行逐元素比较,并包含复合表达式:

(2 * x) == (x ** 2)  # 等式判断
array([False,  True, False, False, False])

如同算术运算符一样,NumPy 中的比较运算符也是以通用函数(ufuncs)的形式实现的;例如,当你写下 x < 3 时,NumPy 在内部实际上调用的是 np.less(x, 3)
下表总结了常用的比较运算符及其对应的 ufunc:

运算符对应的 ufunc运算符对应的 ufunc
==np.equal!=np.not_equal
<np.less<=np.less_equal
>np.greater>=np.greater_equal

就像算术通用函数(ufuncs)一样,这些比较运算符同样适用于任意大小和形状的数组。
下面是一个二维数组的示例:

rng = np.random.default_rng(seed=1701)
x = rng.integers(10, size=(3, 4))
x
array([[9, 4, 0, 3],[8, 6, 3, 1],[3, 7, 4, 0]])
x < 6
array([[False,  True,  True,  True],[False, False,  True,  True],[ True, False,  True,  True]])

在每种情况下,结果都是一个布尔数组,NumPy 提供了许多直接的方法来处理这些布尔结果。

使用布尔数组

给定一个布尔数组,你可以进行许多有用的操作。
我们将使用前面创建的二维数组 x 进行演示:

print(x)
[[9 4 0 3][8 6 3 1][3 7 4 0]]

计数条目

要统计布尔数组中 True 条目的数量,可以使用 np.count_nonzero

# 小于6的条目数
np.count_nonzero(x < 6)
8

我们可以看到,有八个数组元素小于 6。
另一种获取此信息的方法是使用 np.sum;在这种情况下,False 会被当作 0True 会被当作 1

np.sum(x < 6)
np.int64(8)

np.sum 的好处在于,和其他 NumPy 聚合函数一样,这种求和操作也可以沿着行或列进行:

# 每一行小于6的条目数
np.sum(x < 6, axis=1)
array([3, 2, 3])

这将统计矩阵每一行中小于 6 的值的数量。

如果我们想快速检查某一行是否有任意值为 True,或者所有值都为 True,可以使用(你猜对了)np.anynp.all

# 是否有大于8的值?
np.any(x > 8)
np.True_
# 是否有小于0的值?
np.any(x < 0)
np.False_
# 所有值都小于10吗?
np.all(x < 10)
np.True_
# 所有值都等于6?
np.all(x == 6)
np.False_

np.allnp.any 也可以沿特定轴进行操作。例如:

# 是否每行的值都小于8?
np.all(x < 8, axis=1)
array([False, False,  True])

这里,第三行的所有元素都小于 8,而其他行则不是如此。

最后需要提醒一点:正如在 聚合:最小值、最大值以及所有其他内容 中提到的,Python 内置的 sumanyall 函数与 NumPy 版本的语法不同,尤其是在多维数组上使用时,可能会出错或产生意外结果。在这些示例中,请确保使用的是 np.sumnp.anynp.all

布尔运算符

我们已经看到如何统计降雨量小于 20 毫米的天数,或者降雨量大于 10 毫米的天数。 但如果我们想知道降雨量大于 10 毫米且小于 20 毫米的天数有多少天呢?这时可以用 Python 的按位逻辑运算符 &|^~ 来实现。 与标准算术运算符类似,NumPy 也重载了这些运算符,使其可以对(通常是布尔型)数组进行逐元素操作。

例如,我们可以这样解决这类复合问题:

np.sum((rainfall_mm > 10) & (rainfall_mm < 20))
np.int64(16)

这告诉我们有 16 天的降雨量在 10 到 20 毫米之间。

这里的括号非常重要。由于运算符优先级规则,如果去掉括号,这个表达式会被如下方式计算,这将导致错误:

rainfall_mm > (10 & rainfall_mm) < 20

Let’s demonstrate a more complicated expression. Using De Morgan’s laws, we can compute the same result in a different manner:
让我们展示一个更复杂的表达方式——利用德摩根定律,用另一种方式得出相同的结果:

np.sum(~( (rainfall_mm <= 10) | (rainfall_mm >= 20) ))
np.int64(16)

将比较运算符与布尔运算符结合应用于数组,可以实现高效且多样的逻辑操作。

下表总结了按位布尔运算符及其对应的通用函数 ufunc:

运算符对应的 ufunc运算符对应的 ufunc
&np.bitwise_and|np.bitwise_or
^np.bitwise_xor~np.bitwise_not

利用这些工具,我们可以开始回答关于天气数据的许多问题。
以下是结合掩码与聚合操作时可以计算的一些结果示例:

print("Number days without rain:  ", np.sum(rainfall_mm == 0))
print("Number days with rain:     ", np.sum(rainfall_mm != 0))
print("Days with more than 10 mm: ", np.sum(rainfall_mm > 10))
print("Rainy days with < 5 mm:    ", np.sum((rainfall_mm > 0) &(rainfall_mm < 5)))
Number days without rain:   221
Number days with rain:      144
Days with more than 10 mm:  34
Rainy days with < 5 mm:     83

布尔数组作为掩码

在前一节中,我们介绍了如何直接对布尔数组进行聚合计算。
更强大的用法是将布尔数组用作掩码,从而选择数据中的特定子集。让我们回到之前的 x 数组:

x
array([[9, 4, 0, 3],[8, 6, 3, 1],[3, 7, 4, 0]])

假设我们想要获取数组中所有小于 5 的值组成的数组。正如我们之前看到的,我们可以很容易地为这个条件获得一个布尔数组:

x < 5
array([[False,  True,  True,  True],[False, False,  True,  True],[ True, False,  True,  True]])

现在,要从数组中选取这些值,我们只需用这个布尔数组进行索引;这被称为掩码操作(masking operation):

x[x < 5]
array([4, 0, 3, 3, 1, 3, 4, 0])

返回的是一个一维数组,包含所有满足该条件的值;换句话说,就是所有在掩码数组中对应位置为 True 的值。

随后,我们可以对这些值进行任意操作。 例如,我们可以对西雅图降雨数据计算一些相关统计量:

# construct a mask of all rainy days
rainy = (rainfall_mm > 0)# construct a mask of all summer days (June 21st is the 172nd day)
days = np.arange(365)
summer = (days > 172) & (days < 262)print("Median precip on rainy days in 2015 (mm):   ",np.median(rainfall_mm[rainy]))
print("Median precip on summer days in 2015 (mm):  ",np.median(rainfall_mm[summer]))
print("Maximum precip on summer days in 2015 (mm): ",np.max(rainfall_mm[summer]))
print("Median precip on non-summer rainy days (mm):",np.median(rainfall_mm[rainy & ~summer]))
Median precip on rainy days in 2015 (mm):    3.8
Median precip on summer days in 2015 (mm):   0.0
Maximum precip on summer days in 2015 (mm):  32.5
Median precip on non-summer rainy days (mm): 4.1

通过结合布尔运算、掩码操作和聚合操作,我们可以非常快速地回答关于数据集的这类问题。

使用关键字 and/or 与运算符 &/| 的区别

一个常见的困惑点是关键字 andor 与运算符 &| 之间的区别。
什么时候应该用前者,什么时候用后者?

区别在于:andor 作用于整个对象,而 &| 作用于对象中的每个元素。

当你使用 andor 时,相当于让 Python 把整个对象当作一个布尔实体来处理。
在 Python 中,所有非零整数都会被判断为 True。因此:

bool(42), bool(0)
(True, False)
bool(42 and 0)
False
bool(42 or 0)
True

当你对整数使用 &| 时,表达式会作用于元素的位表示,对组成该数字的每一位分别执行按位与按位或操作:

bin(42)
'0b101010'
bin(59)
'0b111011'
bin(42 & 59)
'0b101010'
bin(42 | 59)
'0b111011'

注意,二进制表示的对应位会逐一比较,从而得出结果。

当你在 NumPy 中拥有一个布尔值数组时,可以将其视为一串比特,其中 1 = True0 = False,而 &| 的操作方式与前面的示例类似:

A = np.array([1, 0, 1, 0, 1, 0], dtype=bool)
B = np.array([1, 1, 1, 0, 1, 1], dtype=bool)
A | B
array([ True,  True,  True, False,  True,  True])

但是如果你对这些数组使用 or,它会尝试判断整个数组对象的真值,这并不是一个明确定义的值:

A or B
---------------------------------------------------------------------------ValueError                                Traceback (most recent call last)Cell In[38], line 1
----> 1 A or BValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

类似地,当你对一个数组进行布尔表达式运算时,应使用 |&,而不是 orand

x = np.arange(10)
(x > 4) & (x < 8)
array([False, False, False, False, False,  True,  True,  True, False,False])

尝试对整个数组求真或求假会导致和之前一样的 ValueError 错误:

(x > 4) and (x < 8)
---------------------------------------------------------------------------ValueError                                Traceback (most recent call last)Cell In[40], line 1
----> 1 (x > 4) and (x < 8)ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

相关文章:

  • UDP:简洁高效的报文结构解析与关键注意事项
  • 45、web实验-抽取公共页面
  • 电商实践 基于token防止订单重复创建
  • Python基于方差-协方差方法实现投资组合风险管理的VaR与ES模型项目实战
  • Spring Boot 项目集成 Redis 问题:RedisTemplate 多余空格问题
  • 论文笔记——相干体技术在裂缝预测中的应用研究
  • 智慧停车设备选型指南:何时应优先考虑免布线视频桩方案?
  • 口语考试准备part1(西电)
  • Linux共享内存原理及系统调用分析
  • Linux 内核队列调度相关内核选项详解
  • 用ApiFox MCP一键生成接口文档,做接口测试
  • 十八、【用户认证篇】安全第一步:基于 JWT 的前后端分离认证方案
  • 浅谈控制器
  • Redis:介绍和认识,通用命令,数据类型和内部编码,单线程模型
  • Hive中ORC存储格式的优化方法
  • 11 - ArcGIS For JavaScript -- 高程分析
  • day38 6月5号
  • golang 如何定义一种能够与自身类型值进行比较的Interface
  • ignore文件不生效的问题
  • JVM垃圾回收器-ZGC
  • 北京网站建设与维护/seo课程哪个好
  • 网站销售怎么做的/北京计算机培训机构哪个最好
  • 个人兼职做建设网站/百度推广销售员好做吗
  • wordpress维护模式/百度优化排名软件
  • 江苏优化网站/江苏网站推广公司
  • 网站推广的方法枫子/百度网盟推广