NumPy: 布尔与花式索引
1. 你将学到
- ❶ 布尔掩码——用条件表达式生成 True/False 数组,按掩码筛选元素
- ❷
np.where——条件选择与元素替换的三元操作 - ❸ 花式索引——用整数数组按任意顺序选取元素
- ❹ 组合索引——基本索引与高级索引混合使用
- ❺ 高级索引返回副本——理解视图与副本的关键区别
2. 故事
Alice 手里有 100,000 条气温记录,需要找出所有超过 40°C 的异常高温。她写下 temps[temps > 40],瞬间得到结果。Bob 看后惊叹:"如果用 Python 循环加 if 判断,至少要写 5 行,NumPy 一行搞定,还快了将近 100 倍!"这就是 NumPy 高级索引的威力——用简洁的表达式完成复杂的数据筛选。
3. 布尔掩码
(1) 什么是布尔掩码
布尔掩码是一个与原数组形状相同的 True/False 数组。NumPy 比较运算符作用于整个数组时,会逐元素比较并返回布尔数组。
import numpy as np
arr = np.array([3, -1, 4, -2, 5])
mask = arr > 0
print(mask) # [ True False True False True]
print(arr[mask]) # [3 4 5]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(2) 比较运算符
NumPy 支持以下逐元素比较运算符:
| 运算符 | 含义 | 等价函数 |
|---|---|---|
> |
大于 | np.greater |
>= |
大于等于 | np.greater_equal |
< |
小于 | np.less |
<= |
小于等于 | np.less_equal |
== |
等于 | np.equal |
!= |
不等于 | np.not_equal |
(3) 逻辑组合
多个条件可用逻辑运算符组合,必须使用 &(与)、|(或)、~(非),而非 Python 的 and/or/not。每个条件需用括号包裹。
arr = np.array([1, 5, 8, 3, 9, 2])
mask = (arr > 3) & (arr < 8) # 3 < arr < 8
print(arr[mask]) # [5]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
| 逻辑运算符 | 含义 | 等价函数 |
|---|---|---|
& |
逻辑与 | np.logical_and |
| |
逻辑或 | np.logical_or |
~ |
逻辑非 | np.logical_not |
4. np.where 与条件选择
(1) np.where 三参数形式
np.where(condition, x, y) 相当于逐元素的三元表达式:满足条件取 x,否则取 y。
arr = np.array([-3, 5, -1, 8, 0])
result = np.where(arr > 0, arr, 0)
print(result) # [0 5 0 8 0]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(2) np.where 单参数形式
只传 condition 时,np.where 返回满足条件的元素索引(元组形式,每个维度一个数组)。
arr = np.array([10, 20, 30, 40])
indices = np.where(arr > 25)
print(indices) # (array([2, 3]),)
print(arr[indices]) # [30 40]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(3) np.select 多条件多分支
当有多个条件对应不同值时,用 np.select 替代嵌套 np.where。
scores = np.array([55, 85, 92, 40, 73])
conditions = [scores < 60, scores >= 90, (scores >= 60) & (scores < 90)]
choices = np.array(["fail", "excellent", "pass", "unknown"], dtype=object)
result = np.select(conditions, choices[:3], default=choices[3])
print(result) # NumPy 2.x: object dtype
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(4) np.choose 从选项中选
np.choose 根据索引数组从多个选项中选取元素。
idx = np.array([0, 2, 1, 0])
options = [np.array([10, 20, 30, 40]),
np.array([100, 200, 300, 400]),
np.array([1000, 2000, 3000, 4000])]
print(np.choose(idx, options)) # [ 10 3000 200 40]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
5. 花式索引
(1) 一维花式索引
用整数数组按任意顺序选取元素,索引可重复、可不连续。
arr = np.array([10, 20, 30, 40, 50])
print(arr[[4, 1, 3]]) # [50 20 40]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(2) 二维花式索引
对二维数组,传入两个索引数组分别指定行和列,结果为对应位置配对的元素。
arr = np.arange(12).reshape(3, 4)
print(arr)
# [[ 0 1 2 3]
# [ 4 5 6 7]
# [ 8 9 10 11]]
print(arr[[0, 2], [1, 3]]) # [1 11] -> (0,1) and (2,3)
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(3) np.take 与 np.put
np.take 沿指定轴按索引取值,np.put 按索引写入值。
arr = np.arange(6)
np.take(arr, [5, 2, 0]) # array([5, 2, 0])
np.put(arr, [0, 3], [99, 88])
print(arr) # [99 1 2 88 4 5]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
6. 组合索引
(1) 基本索引与高级索引混合
可以同时使用切片(基本索引)和整数数组(高级索引)。
arr = np.arange(20).reshape(4, 5)
# rows [1,3] via fancy index, columns 1:4 via slice
print(arr[[1, 3], 1:4])
# [[ 6 7 8]
# [16 17 18]]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
(2) 高级索引返回副本
这是高级索引与基本索引最关键的区别:
| 特性 | 基本索引(切片) | 高级索引(布尔/花式) |
|---|---|---|
| 返回 | 视图 | 副本 |
| 修改影响原数组 | 是 | 否 |
| 内存共享 | 是 | 否 |
arr = np.arange(6)
view = arr[2:5]
view[:] = 99
print(arr) # [ 0 1 99 99 99 0] -- view modifies original
copy = arr[[0, 2, 4]]
copy[:] = 0
print(arr) # [ 0 1 99 99 99 0] -- copy does NOT modify original
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
7. 对比表格
(1) 基本 vs 高级索引
| 维度 | 基本索引 | 高级索引 |
|---|---|---|
| 语法 | 切片 a[1:3]、单整数 a[2] |
布尔数组、整数数组 |
| 返回 | 视图(共享内存) | 副本(独立内存) |
| 赋值修改 | 影响原数组 | 不影响原数组 |
| 索引顺序 | 连续或步长 | 任意、可重复 |
(2) 布尔 vs 花式索引
| 维度 | 布尔索引 | 花式索引 |
|---|---|---|
| 输入 | True/False 数组 | 整数数组 |
| 长度要求 | 需与被索引维度等长 | 无限制 |
| 适用场景 | 条件筛选 | 任意位置选取、重排 |
| 结果顺序 | 按元素位置顺序 | 按索引数组顺序 |
(3) where vs select vs choose
| 函数 | 输入 | 典型用途 |
|---|---|---|
np.where(cond, x, y) |
1 个条件 + 2 个值 | 二选一 |
np.select(conds, choices) |
N 个条件 + N 个值 | 多条件多分支 |
np.choose(idx, options) |
索引数组 + N 个选项 | 按索引选选项 |
(4) 条件筛选方式
| 方式 | 语法 | 返回 |
|---|---|---|
| 布尔索引 | arr[mask] |
满足条件的元素 |
np.where 单参数 |
np.where(cond) |
索引元组 |
np.where 三参数 |
np.where(cond, x, y) |
条件替换后的数组 |
np.extract |
np.extract(cond, arr) |
满足条件的元素 |
8. 流程图
graph TB
A[Start: Data Array] --> B{Index Type?}
B -->|Condition Filter| C[Boolean Mask]
B -->|Position Select| D[Fancy Index]
C --> E[Comparison Operator<br/>> < >= <= == !=]
E --> F{Multiple Conditions?}
F -->|Yes| G[Combine with & | ~]
F -->|No| H[arr[mask]]
G --> H
H --> I[Returns Copy]
D --> J[Integer Array<br/>arr[[i1, i2, i3]]]
J --> I
I --> K{Need Conditional Replace?}
K -->|Binary| L[np.where cond, x, y]
K -->|Multi-branch| M[np.select]
K -->|No| N[Done]
L --> N
M --> N
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
9. 示例
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:布尔掩码筛选正数(难度⭐)
import numpy as np
arr = np.array([-3, 5, -1, 8, 0, 2])
positives = arr[arr > 0]
print(positives) # [5 8 2]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:多条件组合筛选(难度⭐⭐)
import numpy as np
arr = np.array([1, 5, 8, 3, 9, 2, 7])
# Select elements: 2 < arr < 8
mask = (arr > 2) & (arr < 8)
print(arr[mask]) # [5 3 7]
# Select elements: arr <= 1 OR arr >= 9
mask2 = (arr <= 1) | (arr >= 9)
print(arr[mask2]) # [1 9]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:np.where 替换负数(难度⭐⭐)
import numpy as np
arr = np.array([-3, 5, -1, 8, 0])
# Replace negatives with 0 (ReLU-like)
result = np.where(arr < 0, 0, arr)
print(result) # [0 5 0 8 0]
# Single-argument form: get indices of positives
indices = np.where(arr > 0)
print(indices) # (array([1, 3]),)
print(arr[indices]) # [5 8]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:花式索引重排与选取(难度⭐⭐)
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
# Reverse order
print(arr[[4, 3, 2, 1, 0]]) # [50 40 30 20 10]
# Duplicate index
print(arr[[0, 0, 4, 4]]) # [10 10 50 50]
# 2D fancy index
mat = np.arange(12).reshape(3, 4)
rows = [0, 2]
cols = [1, 3]
print(mat[rows, cols]) # [1 11]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:高级索引返回副本验证(难度⭐⭐⭐)
import numpy as np
arr = np.arange(6) # [0 1 2 3 4 5]
# Basic index -> view
view = arr[2:5]
view[:] = 99
print(arr) # [ 0 1 99 99 99 5] -- original changed
# Reset
arr[:] = np.arange(6)
# Advanced index -> copy
copy = arr[[0, 2, 4]]
copy[:] = 100
print(arr) # [0 1 2 3 4 5] -- original NOT changed
print(copy) # [100 100 100]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
▶ 示例
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
:成绩数组条件筛选(综合,难度⭐⭐⭐)
import numpy as np
scores = np.array([45, 92, 58, 73, 31, 88, 65, 99])
# Boolean mask: fail (< 60) and excellent (>= 90)
fail = scores[scores < 60]
excellent = scores[scores >= 90]
print("Fail:", fail) # Fail: [45 58 31]
print("Excellent:", excellent) # Excellent: [92 88 99]
# np.where: classify pass/fail
labels = np.where(scores >= 60, "pass", "fail")
print(labels) # ['fail' 'pass' 'fail' 'pass' 'fail' 'pass' 'pass' 'pass']
# np.select: three-tier classification
conditions = [scores < 60, (scores >= 60) & (scores < 90), scores >= 90]
choices = np.array(["fail", "pass", "excellent", "unknown"], dtype=object)
result = np.select(conditions, choices[:3], default=choices[3])
print(result) # NumPy 2.x: dtype=object
# Fancy index: reorder by custom order
order = [7, 1, 5, 3, 6, 2, 0, 4] # excellent first, then descending
print(scores[order])
# [99 92 88 73 65 58 45 31]
# Boolean index assignment: remedial exam for fail scores
scores_copy = scores.copy()
scores_copy[scores_copy < 60] = -1 # mark for retake
print(scores_copy) # [-1 92 -1 73 -1 88 65 99]
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。
❓ 常见问题
& 和 and 有什么区别?and 是 Python 关键字,对整个对象做布尔判断,不逐元素操作;& 是位运算符重载,对数组逐元素求逻辑与。布尔索引必须用 &、|、~。arr[[1, 3]] = [100, 200] 会将值写入原数组对应位置。虽然读取返回副本,但赋值会直接修改原数组。np.where 单参数形式返回什么?np.nonzero。arr[[0,2], :][:, [1,3]]——先用花式索引选行(返回副本),再选列。不能直接 arr[[0,2], [1,3]],那样会配对取 (0,1) 和 (2,3) 两个点。📖 小节
- 布尔掩码通过比较运算符生成 True/False 数组,用于条件筛选
- 多条件组合必须用
&、|、~,每个条件加括号 np.where(cond, x, y)实现条件替换;单参数形式返回索引np.select处理多条件多分支;np.choose按索引选选项- 花式索引用整数数组按任意顺序选取元素,可重复
- 高级索引(布尔/花式)返回副本,基本索引(切片)返回视图
- 花式索引读取返回副本,但赋值操作会修改原数组
📝 作业
-
基础题(难度⭐):创建数组
arr = np.arange(1, 21),用布尔索引筛选出所有奇数。 -
进阶题(难度⭐⭐):给定
arr = np.array([-5, 3, -2, 7, -1, 8]),用np.where将所有负数替换为 0,正数保持不变。 -
挑战题(难度⭐⭐⭐):给定
arr = np.array([10, 20, 30, 40, 50]),用花式索引将数组重排为[50, 30, 10, 40, 20]的顺序,并验证结果与原数组不共享内存。