本系列是《NumPy 速通》的配套章节。

数据处理的日常:把满足条件的元素挑出来。NumPy 用布尔数组做索引,非常优雅,而且返回副本(不会污染原数据)。本章讲透:基本筛选、组合条件、二维行筛选、np.where、布尔统计。

基本筛选

import numpy as np

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

arr[arr > 3]   # [4 5]  取出所有大于 3 的元素

拆开看,它分两步:

mask = arr > 3         # [False False False  True  True]  布尔数组
arr[mask]              # [4 5]  只取 True 位置的元素

💡 返回的是副本:修改筛选结果不会影响原数组(和第 3 章花式索引一致)。比切片安全得多。

组合条件:& | ~

用 &(与)、|(或)、~(非),每个条件都要加括号:

arr[(arr > 2) & (arr < 5)]   # [3 4]    大于 2 且小于 5
arr[(arr < 2) | (arr > 4)]   # [1 5]    小于 2 或大于 4
arr[~(arr == 3)]             # [1 2 4 5]  不等于 3

⚠️ 灵魂警告:

  1. 不能用 Python 的 and / or(它们只认单个布尔值,不认数组)——必须用 & / | / ~(逐元素位运算)
  2. 每个条件必须加括号:(arr > 2) & (arr < 5)。写成 arr > 2 & arr < 5 会报错或算错(运算符优先级问题)
  3. 常见报错 The truth value of an array with more than one element is ambiguous 就是忘了这些导致的

二维数组:筛选行 / 列

data = np.array([[1, 2, 3],
                 [4, 5, 6],
                 [7, 8, 9]])

# 按某列条件筛行:第一列大于 3 的行
rows = data[data[:, 0] > 3]
# [[4 5 6]
#  [7 8 9]]

# 按行条件筛列:每行第一个元素大于 2 的列?不,是取列
data[:, data[0] > 2]   # 第一行大于 2 的列 → 第 2、3 列
# [[2 3]
#  [5 6]
#  [8 9]]

口诀:data[行条件, :] 筛行;data[:, 列条件] 筛列。条件是布尔数组时,它充当对应维度的"开关列表"。

实战:沿用成绩表

scores = np.array([[90, 80, 85],   # 语 数 英
                   [60, 70, 65],
                   [95, 88, 92],
                   [72, 65, 78],
                   [55, 60, 70]])

# 语文 >= 90 的同学
scores[scores[:, 0] >= 90]
# [[90 80 85]
#  [95 88 92]]

# 平均分 >= 70 的同学
means = scores.mean(axis=1)          # [85 65 91.67 71.67 61.67]
scores[means >= 70]
# [[90 80 85]
#  [95 88 92]
#  [72 65 78]]

先算每行统计量,再用布尔索引筛行——两步组合是数据分析最常用的套路。

np.where —— 条件替换

想"满足条件的地方换成 A,否则换成 B":

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

np.where(arr > 3, 100, 0)    # [0 0 0 100 100]  大于 3 的换成 100
np.where(arr > 3, arr, -1)   # [-1 -1 -1 4 5]   满足的保留原值

np.where(条件, 满足时, 不满足时),三个参数。两个分支都可以是数组(广播),因此极灵活:

# 把不及格的成绩替换成 0
np.where(scores < 60, 0, scores)
# [[90 80 85]
#  [60 70 65]
#  [95 88 92]
#  [72 65 78]
#  [55 60 70]]  → 55 变 0(其实这里没有 <60 的)

# 布尔数组当"开关"
np.where(np.isnan(data), 0, data)   # 把缺失值替换成 0

💡 np.where 只传一个参数 np.where(cond) 时,等价于 cond.nonzero(),返回满足条件的位置索引。二维时返回 (行索引数组, 列索引数组)。

布尔统计:True 当 1 算

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

(arr > 3).sum()     # 2   有多少个元素大于 3
np.mean(arr > 3)    # 0.4 占比 40%

这是统计"符合条件比例"的经典写法,不用写任何循环:

# 及格率
np.mean(scores >= 60)          # 总及格率 100%?不对,仔细看全是 >=60
np.mean(scores >= 90)          # 优秀率:90 分以上占比
# 每列及格率
np.mean(scores >= 60, axis=0)  # [1. 1. 1.]

其它实用筛选函数

np.nonzero(arr > 3)    # (array([3, 4]),)  满足条件的位置
np.extract(arr > 3, arr)  # [4 5]  等价 arr[arr > 3]
arr[arr > 3]           # 最常用,直接布尔索引

小结

  1. arr[mask] 返回副本,安全
  2. 组合条件用 & | ~,每个条件加括号,别用 and/or
  3. data[行条件] 筛行、data[:, 列条件] 筛列
  4. np.where(条件, A, B) 条件替换/处理缺失值
  5. (cond).sum() / np.mean(cond) 统计数量与占比

下一章:把多个数组合并、或把一个数组拆开。


上一篇:第 8 章 广播机制 | 下一篇:第 10 章 数组拼接与分割


上一篇:第 8 章 广播机制 | 下一篇:第 10 章 数组拼接与分割