NumPy 高级索引

除了基本的切片和整数索引,NumPy还提供了高级索引(也称为花式索引)功能,允许使用整数数组或布尔数组进行复杂的数据选择。高级索引总是返回原数组的副本(而非视图),这为数据处理提供了极大的灵活性。

1. 整数数组索引(花式索引)

整数数组索引允许使用整数数组作为索引,选择任意顺序的元素,甚至可以重复选取。

1.1 一维数组的花式索引

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]

1.2 二维数组的花式索引

对于多维数组,可以为每个维度提供索引数组,它们会相互对应形成元素对。

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_ 函数(见下文)。

2. 布尔索引

布尔索引使用布尔数组(通常由条件表达式生成)选择满足条件的元素。返回的是一维数组(按行优先顺序包含所有满足条件的元素)。

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]

3. 组合索引(混合切片、花式索引和布尔索引)

高级索引可以与基本切片混合使用,但需要注意:当花式索引或布尔索引与切片混合时,它们返回的总是副本,且结果维度可能发生变化。

3.1 切片与花式索引

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]]

3.2 切片与布尔索引

# 选择所有行,但只选择元素值大于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]]

4. 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]],但更高效且可读性更好。

5. np.takenp.put 函数

np.takenp.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)。

6. 高级索引与视图/副本

与基本切片不同,高级索引总是返回数组的副本。这意味着通过高级索引得到的结果与原数组不共享内存,修改它不会影响原数组。

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]

7. 高级索引与广播

当多个索引数组用于不同维度时,它们会按照广播规则组合。例如,使用 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.takenp.put 等工具,可以灵活地处理各种复杂的数据提取和修改任务。下一章我们将学习NumPy的线性代数模块。