第 8 章 布尔掩码与条件选择
学习目标
- 理解布尔掩码索引的机制
- 掌握用布尔数组过滤、统计
- 掌握
np.where的条件选择 - 掌握逻辑运算
&、|、~与any/all - 会用掩码做数据清洗
8.1 布尔掩码
布尔掩码(boolean mask)是与数组同形状的布尔数组,用 数组[掩码] 取出 True 对应的元素,False 对应的被过滤掉。
第 4 章学过,比较运算产生布尔数组;现在把它当索引用:
import numpy as np
scores = np.array([85, 92, 58, 90, 76, 95, 63])
mask = scores >= 90
print(mask)
print(scores[mask])输出:
[False True False True False True False]
[92 90 95]mask 里 True 的位置正好是 92、90、95 的位置,scores[mask] 只留下它们。
可以一步写成 scores[scores >= 90],不用中间变量。
8.2 统计满足条件的数量与比例
布尔数组可以直接求和、求平均:True 计为 1,False 计为 0。所以:
(条件).sum()是满足条件的个数(条件).mean()是满足条件的比例
import numpy as np
scores = np.array([85, 92, 58, 90, 76, 95, 63])
print("90 分以上人数:", (scores >= 90).sum())
print("90 分以上比例:", (scores >= 90).mean())
print("不及格(<60)人数:", (scores < 60).sum())输出:
90 分以上人数: 3
90 分以上比例: 0.42857142857142855
不及格(<60)人数: 18.3 np.where:条件选择
np.where(条件, 满足时的值, 不满足时的值) 逐元素二选一,返回与条件同形状的数组:
import numpy as np
a = np.array([10, 20, 30, 40])
print(np.where(a > 20, "高", "低"))
print(np.where(a > 20, 1, 0))输出:
['低' '低' '高' '高']
[0 0 1 1]两个值可以是标量,也可以是数组(此时按广播规则工作)。
只传一个参数时,np.where(条件) 返回满足条件的下标:
import numpy as np
a = np.array([10, 20, 30, 40])
print(np.where(a > 20))输出:
(array([2, 3]),)返回的是元组,里面每个元素对应一个轴的坐标。一维数组时取 np.where(a > 20)[0] 即可得到下标数组。
8.4 逻辑运算:& | ~
多个条件组合要用按位逻辑运算符(不是 Python 的 and/or):
&:与(两个都满足)|:或(至少一个满足)~:非(取反)
import numpy as np
x = np.array([5, 12, 18, 25, 30])
print((x >= 10) & (x < 20)) # 10 ≤ x < 20
print((x < 10) | (x >= 25)) # 小于 10 或大于等于 25
print(~(x >= 10)) # 取反:小于 10输出:
[False True True False False]
[ True False False True True]
[ True False False False False]注意两个易错点:
- 不能用
and/or。它们会尝试把整个数组当作一个真值,报错:>>> x >= 10 and x < 20 Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all() &比>=优先级低,所以复合条件必须加括号:(x >= 10) & (x < 20),漏括号会语法错误或逻辑错误。
8.5 any 与 all
把布尔数组整体压缩成一个真值:
(条件).any():只要有一个True就是True(条件).all():全部为True才是True
import numpy as np
x = np.array([5, 12, 18, 25, 30])
print((x > 100).any()) # 有大于 100 的吗?没有
print((x > 0).all()) # 全部大于 0 吗?是输出:
False
True8.6 二维数组的掩码
二维数组用掩码过滤后变成一维——只留下 True 对应的元素,形状信息丢失:
import numpy as np
m = np.array([[1, 5, 3],
[9, 2, 7]])
print(m[m > 4])
print(m[m > 4].mean()) # 大于 4 的元素平均值输出:
[5 9 7]
7.08.7 应用:数据清洗
数据清洗是把脏数据(异常值、缺失值)找出来并处理的过程。两个经典操作:
操作一:把异常值替换为 nan。 温度数据里混入 99.9、200.0 这类错误读数:
import numpy as np
temps = np.array([36.5, 37.0, 99.9, 36.8, 200.0, 36.6])
clean = np.where((temps > 40) | (temps < 35), np.nan, temps)
print(clean)输出:
[36.5 37. nan 36.8 nan 36.6]操作二:排除缺失值后统计。 用 np.isnan 找出 nan 并过滤:
import numpy as np
data = np.array([1.0, -999.0, 2.5, -999.0, 3.1]) # -999 表示缺失
data[data == -999.0] = np.nan # 统一标成 nan
print(data)
print("有效值平均:", data[~np.isnan(data)].mean())输出:
[ 1. nan 2.5 nan 3.1]
有效值平均: 2.1999999999999997data == -999.0 是掩码,data[掩码] = np.nan 是「给掩码位置赋值」——第 3 章切片赋值的推广。
操作三:批量替换满足条件的元素。 把数组里所有偶数改成 0:
import numpy as np
m = np.array([[1, 2],
[3, 4],
[5, 6]])
m[m % 2 == 0] = 0
print(m)输出:
[[1 0]
[3 0]
[5 0]]动手实践
- 统计
np.array([85, 92, 58, 90, 76, 95, 63])中 80~90 分(含)的人数与比例(提示:两个条件用&连接)。 - 用
np.where把np.array([10, 20, 30, 40])中大于 25 的标记为"大",其余为"小"。 - 造一组含
-999缺失值的数组,替换为 nan 后求有效值平均。 - 生成 20 个随机整数(第 11 章预告:
rng.integers(0, 100, 20)),用掩码取出所有偶数并求和。
常见错误
| 错误写法 | 现象 | 原因 |
|---|---|---|
(x >= 10) and (x < 20) | ValueError: The truth value of an array ... is ambiguous | 布尔数组不能整体判断真值;用 & |
x >= 10 & x < 20(漏括号) | 语法错误或错误结果 | & 优先级低于比较运算,必须加括号 |
m[m > 4].shape 想保持二维 | 得到一维 | 掩码过滤后形状会被压平,这是设计行为 |
np.where(条件) 当作返回下标数组直接用 | 返回元组 | 单参数形式返回各轴下标元组,取 [0] 得到一维下标 |
用 a[~np.isnan(a)] 时忘了 ~ | 留下的是 nan | ~ 取反;直接 a[np.isnan(a)] 是留下缺失值 |
用 data[data == -999] = nan 对浮点数组 | 正常 | 注意:对 nan 做 == 比较永远 False,所以清洗时要先替换成 nan 再判断 |
章末练习
基础
- 用掩码从
np.array([3, 8, 15, 22, 7])取出大于 10 的元素。 - 统计
np.array([1, 2, 3, 4, 5, 6])中偶数的个数(提示:% 2 == 0)。 - 用
np.where把np.array([-2, 5, -8, 9])中负数改成 0,正数保持不变。
提高
- 统计
np.array([72, 85, 58, 90, 66, 95, 63, 88])中 60~85 分(含)的人数,并打印这些分数。 - 对一组含异常值的数据(自行设计,如身高数据混入
200),用掩码把异常值替换为 nan,再求有效值平均与标准差。
挑战
- 用
np.where实现分段函数:当x < 0时取 0,0 ≤ x < 10时取 1,x ≥ 10时取 2(提示:可以嵌套np.where或多次赋值)。 - 给定
np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),不用循环找出所有大于列平均的元素,并统计个数(提示:先算列平均,再广播比较)。
章末自测
每题选择一个最佳答案。本书不附答案:完成后交由老师或 AI 老师批改讲解。
a = np.array([1, 2, 3, 4]); a[a > 2]的结果是?- A.
[3, 4] - B.
[2, 3, 4] - C.
[1, 2] - D. 报错
- A.
(a > 2).sum()表示?- A. 大于 2 的元素个数
- B. 大于 2 的元素之和
- C. 所有元素之和
- D. 数组长度
- 组合两个条件「大于 10 且小于 20」的正确写法是?
- A.
a > 10 and a < 20 - B.
(a > 10) & (a < 20) - C.
a > 10 & a < 20 - D.
and(a > 10, a < 20)
- A.
np.where(a > 2, "高", "低")对[1, 3, 2]的结果是?- A.
['低', '高', '高'] - B.
['低', '高', '低'] - C.
['高', '低', '高'] - D.
[1, 3, 2]
- A.
(a > 100).any()对[1, 50, 200]的结果是?- A.
True - B.
False - C.
[True] - D. 报错
- A.
- 布尔掩码索引
m[m > 4](m 是二维)的结果是?- A. 二维数组
- B. 一维数组
- C. 布尔数组
- D. 报错
- 统计满足条件的比例,应使用?
- A.
(条件).sum() - B.
(条件).mean() - C.
(条件).max() - D.
(条件).size
- A.
- 取反操作符是?
- A.
! - B.
~ - C.
not - D.
-
- A.
- 把缺失值标记为 nan 后,排除它统计的写法是?
- A.
data[~np.isnan(data)] - B.
data[np.isnan(data)] - C.
data[data == np.nan] - D.
np.filter(data)
- A.
np.where(a > 2)(只有一个参数)返回的是?- A. 满足条件的元素
- B. 满足条件的下标(元组)
- C. 布尔数组
- D. 报错
