数据科学 IPython 笔记本 9.8 比较,掩码和布尔逻辑

9.8 比较,掩码和布尔逻辑

本节是《Python 数据科学手册》(Python Data Science Handbook)的摘录。

译者:飞龙

协议:CC BY-NC-SA 4.0

本节介绍如何使用布尔掩码,来检查和操作 NumPy 数组中的值。当你想要根据某些标准,提取,修改,计算或以其他方式操纵数组中的值时,掩码会有所帮助:例如,你可能希望计算大于某个值的所有值,或者可能删除高于某些阈值的所有异常值。

在 NumPy 中,布尔掩码通常是完成这些类型任务的最有效方法。

示例:统计雨天

想象一下,你有一系列数据表示某一城市一年中每天的降水量。例如,在这里我们将使用 Pandas 加载 2014 年西雅图市的每日降雨量统计数据(在第三章中有更详细的介绍):

import numpy as np
import pandas as pd

# 使用 pandas 将降雨量英寸提取为 NumPy 数组
rainfall = pd.read_csv('data/Seattle2014.csv')['PRCP'].values
inches = rainfall / 254  # 1/10mm -> 英寸
inches.shape

# (365,)

该数组包含 365 个值,提供了 2014 年 1 月 1 日至 12 月 31 日的每日降雨量,单位为英寸。

作为第一个简单的可视化,让我们看一下使用 Matplotlib 生成的雨天的直方图(我们将在第四章中更全面地探索这个工具):

%matplotlib inline
import matplotlib.pyplot as plt
import seaborn; seaborn.set()  # 设置绘图风格

plt.hist(inches, 40);
png

这个直方图让我们对数据的概况有了一个大概的了解:尽管它的声誉很高,但 2014 年西雅图的绝大多数日子的测得的降雨量几乎为零。但这并没有很好地传达我们希望看到的一些信息:例如,一年中有多少雨天?那些下雨天的平均降雨量是多少? 有多少天有超过半英寸的降雨?

挖掘数据

一种方法是手动回答这些问题:遍历数据,每当我们看到某个所需范围内的值时,递增计数器。由于本章讨论的原因,从编写代码的时间和计算结果的时间的角度来看,这种方法效率非常低。

我们在“NumPy 上的数组计算:通用函数”中看到,NumPy 的ufuncs可用于代替循环,对数组进行快速的逐元素算术运算;以同样的方式,我们可以使用其他ufunc对数组进行逐元素比较,然后我们可以操纵结果来回答我们的问题。

我们现在暂时搁置数据,并讨论 NumPy 中的一些常用工具,使用掩码快速回答这类的问题。

作为ufunc的比较运算

在“NumPy 上的数组计算:通用函数”中,我们介绍了ufunc,专注于算术运算符。 我们看到,在数组上使用+-*/和其他,产生了逐元素操作。

NumPy 还将比较运算符,例如<(小于)和>(大于),实现为逐元素的ufunc。这些比较运算符的结果始终是布尔数据类型的数组。所有六种标准比较操作都可用:

x = np.array([1, 2, 3, 4, 5])

x < 3  # 小于

# array([ True,  True, False, False, False], dtype=bool)

x > 3  # 大于

# array([False, False, False,  True,  True], dtype=bool)

x <= 3  # 小于等于

# array([ True,  True,  True, False, False], dtype=bool)

x >= 3  # 大于等于

# array([False, False,  True,  True,  True], dtype=bool)

x != 3  # 不等于

# array([ True,  True, False,  True,  True], dtype=bool)

x == 3  # 等于

# array([False, False,  True, False, False], dtype=bool)

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

(2 * x) == (x ** 2)

# array([False,  True, False, False, False], dtype=bool)

与算术运算符的情况一样,比较运算符在 NumPy 中实现为ufunc;例如,当你编写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

就像算术ufunc的情况一样,这些适用于任何大小和形状的数组。这是一个二维的例子:

rng = np.random.RandomState(0)
x = rng.randint(10, size=(3, 4))
x

'''
array([[5, 0, 3, 3],
       [7, 9, 3, 5],
       [2, 4, 7, 6]])
'''

x < 6
'''
array([[ True,  True,  True,  True],
       [False, False,  True,  True],
       [ True,  True, False, False]], dtype=bool)
'''

在每种情况下,结果都是一个布尔数组,NumPy 提供了许多简单的模式来处理这些布尔结果。

使用布尔数组

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

print(x)

'''
[[5 0 3 3]
 [7 9 3 5]
 [2 4 7 6]]
'''

对元素计数

要计算布尔数组中True元素的数量,np.count_nonzero很有用:

# 多少个值小于 6
np.count_nonzero(x < 6)

# 8

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

np.sum(x < 6)

# 8

`sum()``的好处就是和其他NumPy聚合函数一样,这个求和也可以沿着行或列来完成:

# 每一行有多少个值小于 6
np.sum(x < 6, axis=1)

# array([4, 2, 2])

这计算了矩阵每行中小于 6 的值的数量。

如果我们有兴趣快速检查,是否任何或所有值都是真的,我们可以使用(你猜对了)np.anynp.all

# 存在大于 8 的值吗?
np.any(x > 8)

# True

# 存在小于 0 的值吗?
np.any(x < 0)

# False

# 所有值都小于 10 吗?
np.all(x < 10)

# True

# 所有值都等于 6 吗?
np.all(x == 6)

# False

np.allnp.any也可用于特定的轴。例如:

# 每一行的所有值都小于 4 吗?
np.all(x < 8, axis=1)

# array([ True, False,  True], dtype=bool)

这里第一行和第三行中的所有元素都小于 8,而第二行则不是这种情况。

最后,一个简单的警告:如“聚合:最小、最大和之间的任何东西”中所述,Python 内置了sum()any()all()函数。 它们的语法与 NumPy 版本不同,特别是在多维数组上使用时会失败或产生意外结果。对于这些情况,请确保使用np.sum()np.any()np.all(()

布尔运算符

我们已经看到了我们如何计算,比如降雨量小于 4 英寸的所有日子,或降雨量大于 2 英寸的所有日子。但是如果我们想了解降雨量小于 4 英寸且大于 1 英寸的所有日子呢?

这是通过 Python 的按位逻辑运算符,&|^~来实现的。与标准算术运算符一样,NumPy 将这些重载为ufunc,这些ufunc在(通常是布尔)数组上逐元素工作。例如,我们可以像这样解决这种复合问题:

np.sum((inches > 0.5) & (inches < 1))

# 29

所以我们看到有 29 天的降雨量在 0.5 到 1.0 英寸之间。请注意,此处的括号很重要 - 由于运算符优先级规则,删除了括号,此表达式将按如下方式计算,这会导致错误:

inches > (0.5 & inches) < 1

使用A AND BNOT (NOT A OR NOT B)的等价性(如果你已经参加了逻辑入门课程,你可能还记得),我们可以用不同的方式计算相同的结果:

np.sum(~( (inches <= 0.5) | (inches >= 1) ))

# 29

在数组上组合比较运算符和布尔运算符。可以实现广泛的高效逻辑运算。下表总结了按位布尔运算符及其等效的ufunc

运算符 等价 ufunc 运算符 等价 ufunc
& np.bitwise_and <code>|</code> np.bitwise_or
^ np.bitwise_xor ~ np.bitwise_not

使用这些工具,我们可以开始回答有关天气数据的问题。以下是将掩码聚合结合使用时,可以计算的一些结果示例:

print("Number days without rain:      ", np.sum(inches == 0))
print("Number days with rain:         ", np.sum(inches != 0))
print("Days with more than 0.5 inches:", np.sum(inches > 0.5))
print("Rainy days with < 0.2 inches  :", np.sum((inches > 0) &
                                                (inches < 0.2)))
                                                
'''
Number days without rain:       215
Number days with rain:          150
Days with more than 0.5 inches: 37
Rainy days with < 0.2 inches  : 75
'''

作为掩码的布尔数组

在上一节中,我们研究了直接在布尔数组上计算的聚合。更强大的模式是将布尔数组用作掩码,来选择数据本身的特定子集。回到之前的x数组,假设我们想要所有值小于 5 的数组:

x

'''
array([[5, 0, 3, 3],
       [7, 9, 3, 5],
       [2, 4, 7, 6]])
'''

我们可以很容易地获得这样的布尔数组,正如我们已经看到的:

x < 5

'''
array([[False,  True,  True,  True],
       [False, False,  True, False],
       [ True,  True, False, False]], dtype=bool)
'''

现在为了从数组中选择这些值,我们可以简单地用这个布尔数组来索引;这被称为掩码操作:

x[x < 5]

# array([0, 3, 3, 3, 2, 4])

返回的是一维数组,包含满足此条件的所有值;换句话说,掩码数组为True的位置的所有值。然后我们可以按照我们的意愿,自由操作这些值。例如,我们可以计算西雅图降雨量数据的一些相关统计数据:

# 为所有雨天构造掩码
rainy = (inches > 0)

# 为所有夏天构造掩码(6 月 21 日是第 172 天)
days = np.arange(365)
summer = (days > 172) & (days < 262)

print("Median precip on rainy days in 2014 (inches):   ",
      np.median(inches[rainy]))
print("Median precip on summer days in 2014 (inches):  ",
      np.median(inches[summer]))
print("Maximum precip on summer days in 2014 (inches): ",
      np.max(inches[summer]))
print("Median precip on non-summer rainy days (inches):",
      np.median(inches[rainy & ~summer]))
      
'''
Median precip on rainy days in 2014 (inches):    0.194881889764
Median precip on summer days in 2014 (inches):   0.0
Maximum precip on summer days in 2014 (inches):  0.850393700787
Median precip on non-summer rainy days (inches): 0.200787401575
'''

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

注:使用关键字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], dtype=bool)

在这些数组上使用andor,将尝试求解整个数组对象的真实性或错误性,这不是一个明确定义的值:

A or B

'''
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-38-5d8e4f2e21c0> in <module>()
----> 1 A or B


ValueError: 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], dtype=bool)

试图求解整个数组的真实性或错误性,将给出我们之前看到的相同的ValueError

(x > 4) and (x < 8)

'''
---------------------------------------------------------------------------

ValueError                                Traceback (most recent call last)

<ipython-input-40-3d24f1ffd63d> in <module>()
----> 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()
'''

所以记住这一点:andor对整个对象执行单个布尔求值,而&|对对象的内容(单个位或字节)执行多次布尔求值。对于布尔 NumPy 数组,后者几乎总是所需的操作。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354

推荐阅读更多精彩内容