NumPy: 布尔与花式索引

1. 你将学到


2. 故事

Alice 手里有 100,000 条气温记录,需要找出所有超过 40°C 的异常高温。她写下 temps[temps > 40],瞬间得到结果。Bob 看后惊叹:"如果用 Python 循环加 if 判断,至少要写 5 行,NumPy 一行搞定,还快了将近 100 倍!"这就是 NumPy 高级索引的威力——用简洁的表达式完成复杂的数据筛选。


3. 布尔掩码

(1) 什么是布尔掩码

布尔掩码是一个与原数组形状相同的 True/False 数组。NumPy 比较运算符作用于整个数组时,会逐元素比较并返回布尔数组。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 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。每个条件需用括号包裹。

PYTHON
arr = np.array([1, 5, 8, 3, 9, 2])
mask = (arr > 3) & (arr < 8)   # 3 < arr < 8
print(arr[mask])  # [5]
TEXT 📖 仅展示
> **输出:** 在本地 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

PYTHON
arr = np.array([-3, 5, -1, 8, 0])
result = np.where(arr > 0, arr, 0)
print(result)  # [0 5 0 8 0]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) np.where 单参数形式

只传 condition 时,np.where 返回满足条件的元素索引(元组形式,每个维度一个数组)。

PYTHON
arr = np.array([10, 20, 30, 40])
indices = np.where(arr > 25)
print(indices)       # (array([2, 3]),)
print(arr[indices])  # [30 40]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(3) np.select 多条件多分支

当有多个条件对应不同值时,用 np.select 替代嵌套 np.where

PYTHON
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
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(4) np.choose 从选项中选

np.choose 根据索引数组从多个选项中选取元素。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

5. 花式索引

(1) 一维花式索引

用整数数组按任意顺序选取元素,索引可重复、可不连续

PYTHON
arr = np.array([10, 20, 30, 40, 50])
print(arr[[4, 1, 3]])  # [50 20 40]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) 二维花式索引

对二维数组,传入两个索引数组分别指定行和列,结果为对应位置配对的元素。

PYTHON
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)
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(3) np.take 与 np.put

np.take 沿指定轴按索引取值,np.put 按索引写入值。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

6. 组合索引

(1) 基本索引与高级索引混合

可以同时使用切片(基本索引)和整数数组(高级索引)。

PYTHON
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]]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

(2) 高级索引返回副本

这是高级索引与基本索引最关键的区别:

特性 基本索引(切片) 高级索引(布尔/花式)
返回 视图 副本
修改影响原数组
内存共享
PYTHON
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
TEXT 📖 仅展示
> **输出:** 在本地 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. 流程图

100%
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&#91;mask&#93;]
    G --> H
    H --> I[Returns Copy]
    D --> J[Integer Array<br/>arr&#91;&#91;i1, i2, i3&#93;&#93;]
    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
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

9. 示例

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:布尔掩码筛选正数(难度⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
import numpy as np

arr = np.array([-3, 5, -1, 8, 0, 2])
positives = arr[arr > 0]
print(positives)  # [5 8 2]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:多条件组合筛选(难度⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:np.where 替换负数(难度⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:花式索引重排与选取(难度⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:高级索引返回副本验证(难度⭐⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

▶ 示例

TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

:成绩数组条件筛选(综合,难度⭐⭐⭐)

⚠️ 注意: 以下代码需在本地 Python 环境中运行。

PYTHON
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]
TEXT 📖 仅展示
> **输出:** 在本地 Python 环境运行 NumPy 2.x,输出 ndarray 数组内容。Piston 服务器未预装 NumPy,请在本机安装(`pip install numpy`)后实操对照。实际数值可能因 NumPy 版本、随机种子略有差异。

❓ 常见问题

Q 布尔索引为什么返回副本而不是视图?
A 布尔掩码选取的元素在原数组中不连续,无法用一个步长描述,因此 NumPy 必须将它们复制到一块新内存中。
Q 布尔条件组合时 &and 有什么区别?
A and 是 Python 关键字,对整个对象做布尔判断,不逐元素操作;& 是位运算符重载,对数组逐元素求逻辑与。布尔索引必须用 &|~
Q 花式索引能赋值吗?
A 可以。arr[[1, 3]] = [100, 200] 会将值写入原数组对应位置。虽然读取返回副本,但赋值会直接修改原数组。
Q np.where 单参数形式返回什么?
A 返回一个元组,每个元素是对应维度上满足条件的索引数组。对于一维数组,等价于 np.nonzero
Q 如何同时用花式索引选多行多列的子矩阵?
A arr[[0,2], :][:, [1,3]]——先用花式索引选行(返回副本),再选列。不能直接 arr[[0,2], [1,3]],那样会配对取 (0,1) 和 (2,3) 两个点。

📖 小节


📝 作业

  1. 基础题(难度⭐):创建数组 arr = np.arange(1, 21),用布尔索引筛选出所有奇数。

  2. 进阶题(难度⭐⭐):给定 arr = np.array([-5, 3, -2, 7, -1, 8]),用 np.where 将所有负数替换为 0,正数保持不变。

  3. 挑战题(难度⭐⭐⭐):给定 arr = np.array([10, 20, 30, 40, 50]),用花式索引将数组重排为 [50, 30, 10, 40, 20] 的顺序,并验证结果与原数组不共享内存。

Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏