除了基本的切片和整数索引,NumPy还提供了高级索引(也称为花式索引)功能,允许使用整数数组或布尔数组进行复杂的数据选择。高级索引总是返回原数组的副本(而非视图),这为数据处理提供了极大的灵活性。
整数数组索引允许使用整数数组作为索引,选择任意顺序的元素,甚至可以重复选取。
import numpy as np
arr = np.array([10, 20, 30, 40, 50])
idx = np.array([0, 2, 4])
print("arr[idx] =", arr[idx]) # 选择索引0,2,4的元素
# 可以重复索引
idx2 = [3, 1, 3, 0]
print("arr[idx2] =", arr[idx2]) # [40 20 40 10]
输出:
arr[idx] = [10 30 50]
arr[idx2] = [40 20 40 10]
对于多维数组,可以为每个维度提供索引数组,它们会相互对应形成元素对。
arr2d = np.arange(12).reshape(3, 4)
print("原数组:\n", arr2d)
# 选择特定行(第0行和第2行)
rows = arr2d[[0, 2]]
print("选择行0和2:\n", rows)
# 选择特定列(第1列和第3列)
cols = arr2d[:, [1, 3]]
print("选择列1和3:\n", cols)
# 组合索引:选择(0,1)和(2,3)两个点
points = arr2d[[0, 2], [1, 3]] # 分别对应 (0,1) 和 (2,3)
print("选中的点:", points) # [1 11]
输出:
原数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
选择行0和2:
[[0 1 2 3]
[8 9 10 11]]
选择列1和3:
[[ 1 3]
[ 5 7]
[ 9 11]]
选中的点: [1 11]
注意:当行索引和列索引形状相同时,它们会配对产生一维结果。如果想要选择矩形区域,应使用 np.ix_ 函数(见下文)。
布尔索引使用布尔数组(通常由条件表达式生成)选择满足条件的元素。返回的是一维数组(按行优先顺序包含所有满足条件的元素)。
arr = np.array([5, 10, 15, 20, 25])
bool_idx = arr > 15
print("arr > 15:", bool_idx)
print("arr[bool_idx] =", arr[bool_idx]) # 选择大于15的元素
# 结合赋值:将小于10的元素替换为0
arr[arr < 10] = 0
print("修改后:", arr)
# 二维布尔索引
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print("大于5的元素:\n", arr2d[arr2d > 5]) # 一维数组 [6 7 8 9]
# 布尔索引也可以与切片混合(见组合索引)
输出:
arr > 15: [False False False True True]
arr[bool_idx] = [20 25]
修改后: [ 0 0 15 20 25]
大于5的元素:
[6 7 8 9]
高级索引可以与基本切片混合使用,但需要注意:当花式索引或布尔索引与切片混合时,它们返回的总是副本,且结果维度可能发生变化。
arr2d = np.arange(12).reshape(3, 4)
print("原数组:\n", arr2d)
# 选择第0行和第2行的第1到第3列
result = arr2d[[0, 2], 1:3]
print("结果:\n", result) # shape (2,2)
输出:
原数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
结果:
[[1 2]
[9 10]]
# 选择所有行,但只选择元素值大于5的列(返回一维数组)
mask = arr2d > 5
print("元素大于5的一维结果:", arr2d[mask])
# 选择行0和行1,以及列索引满足某个条件的列
# 例如,选择列索引为偶数的列
col_mask = np.array([True, False, True, False])
result = arr2d[:2, col_mask]
print("前两行,偶数列:\n", result)
输出:
元素大于5的一维结果: [ 6 7 8 9 10 11]
前两行,偶数列:
[[0 2]
[4 6]]
np.ix_ 函数:构造开放网格索引
当我们需要选择不同维度的所有组合(例如行集合与列集合的笛卡尔积)时,可以使用 np.ix_ 函数。它接受多个一维整数数组,并返回一个广播索引元组,用于选择矩形区域。
rows = [0, 2]
cols = [1, 3]
ix = np.ix_(rows, cols)
print("np.ix_ 结果:\n", ix) # 实际上是两个数组,分别用于广播
result = arr2d[ix]
print("使用 np.ix_ 选择的行列交叉区域:\n", result) # shape (2,2)
输出:
np.ix_ 结果:
(array([[0],
[2]]), array([[1, 3]]))
使用 np.ix_ 选择的行列交叉区域:
[[ 1 3]
[ 9 11]]
这等价于 arr2d[[0,2]][:,[1,3]],但更高效且可读性更好。
np.take 和 np.put 函数
np.take 和 np.put 提供了类似花式索引的功能,但允许指定轴,并提供边界处理模式。
arr = np.array([10, 20, 30, 40, 50])
indices = [0, 2, 4]
# np.take 沿指定轴取元素(默认拉平后取)
print("np.take(arr, indices):", np.take(arr, indices))
# 对于二维数组,可以指定轴
arr2d = np.arange(12).reshape(3, 4)
print("np.take(arr2d, [0,2], axis=1):\n", np.take(arr2d, [0, 2], axis=1)) # 取第0列和第2列
# np.put 对指定索引赋值(就地修改)
arr_put = np.array([1, 2, 3, 4])
np.put(arr_put, [0, 2], [99, 100])
print("np.put 后:", arr_put) # [99 2 100 4]
输出:
np.take(arr, indices): [10 30 50]
np.take(arr2d, [0,2], axis=1):
[[ 0 2]
[ 4 6]
[ 8 10]]
np.put 后: [ 99 2 100 4]
np.put 还支持 mode 参数来处理索引越界(raise/wrap/clip)。
与基本切片不同,高级索引总是返回数组的副本。这意味着通过高级索引得到的结果与原数组不共享内存,修改它不会影响原数组。
arr = np.array([1, 2, 3, 4])
fancy = arr[[0, 2]]
fancy[0] = 999
print("修改花式索引结果后原数组:", arr) # 原数组不变
bool_idx = arr > 2
bool_result = arr[bool_idx]
bool_result[0] = 888
print("修改布尔索引结果后原数组:", arr) # 原数组不变
输出:
修改花式索引结果后原数组: [1 2 3 4]
修改布尔索引结果后原数组: [1 2 3 4]
当多个索引数组用于不同维度时,它们会按照广播规则组合。例如,使用 np.ix_ 时,行索引和列索引会广播成二维形状。
# 自定义广播索引
rows = np.array([0, 2]).reshape(-1, 1) # shape (2,1)
cols = np.array([1, 3]) # shape (2,)
result = arr2d[rows, cols] # 广播后 rows(2,1) 和 cols(1,2) -> (2,2)
print("手动广播索引结果:\n", result) # 等价于 np.ix_([0,2],[1,3])
输出:
手动广播索引结果:
[[1 3]
[9 11]]
高级索引(花式索引和布尔索引)为NumPy数组提供了强大的数据选择能力,允许根据任意规则抽取元素。与基本切片不同,高级索引返回的是副本,这保证了数据的安全性。结合 np.ix_、np.take 和 np.put 等工具,可以灵活地处理各种复杂的数据提取和修改任务。下一章我们将学习NumPy的线性代数模块。