NumPy 数组 索引与切片

索引和切片是访问和修改数组元素的核心操作。NumPy提供了丰富的索引方式,包括基本索引、切片、布尔索引和花式索引,灵活运用它们可以高效地操作数组数据。

1. 一维数组的索引与切片

一维NumPy数组的索引和切片与Python列表非常相似。

import numpy as np

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
print("原数组:", arr)

# 单个索引
print("arr[0] =", arr[0])    # 第一个元素
print("arr[-1] =", arr[-1])  # 最后一个元素

# 切片 [start:stop:step]
print("arr[2:5] =", arr[2:5])      # 索引2到4
print("arr[:5] =", arr[:5])        # 开头到索引4
print("arr[5:] =", arr[5:])        # 索引5到最后
print("arr[::2] =", arr[::2])      # 步长为2
print("arr[::-1] =", arr[::-1])    # 反转数组

输出:

原数组: [0 1 2 3 4 5 6 7 8 9]
arr[0] = 0
arr[-1] = 9
arr[2:5] = [2 3 4]
arr[:5] = [0 1 2 3 4]
arr[5:] = [5 6 7 8 9]
arr[::2] = [0 2 4 6 8]
arr[::-1] = [9 8 7 6 5 4 3 2 1 0]

2. 二维数组的索引与切片

对于二维数组,索引和切片使用逗号分隔各个维度,格式为 arr[行, 列]

arr2d = np.array([[1, 2, 3, 4],
                   [5, 6, 7, 8],
                   [9, 10, 11, 12]])
print("二维数组:\n", arr2d)

# 访问单个元素
print("arr2d[1, 2] =", arr2d[1, 2])   # 第2行第3列

# 获取整行
print("第一行 arr2d[0] =", arr2d[0])   # 等价于 arr2d[0, :]
print("第二行 arr2d[1, :] =", arr2d[1, :])

# 获取整列
print("第二列 arr2d[:, 1] =", arr2d[:, 1])

# 切片:前两行,后两列
print("前两行,后两列:\n", arr2d[:2, -2:])

# 步长切片
print("每隔一行,每隔一列:\n", arr2d[::2, ::2])

输出:

二维数组:
 [[ 1  2  3  4]
 [ 5  6  7  8]
 [ 9 10 11 12]]
arr2d[1, 2] = 7
第一行 arr2d[0] = [1 2 3 4]
第二行 arr2d[1, :] = [5 6 7 8]
第二列 arr2d[:, 1] = [ 2  6 10]
前两行,后两列:
 [[3 4]
 [7 8]]
每隔一行,每隔一列:
 [[ 1  3]
 [ 9 11]]

3. 多维数组的索引(三维及以上)

更高维数组的索引同理,每个维度用逗号分隔。可以使用 ...(省略号)代表多个连续的冒号切片。

arr3d = np.arange(24).reshape(2, 3, 4)
print("三维数组 shape:", arr3d.shape)
print("arr3d:\n", arr3d)

# 访问单个元素
print("arr3d[1, 2, 3] =", arr3d[1, 2, 3])  # 第2个块的第三行第四列

# 切片:获取所有块的第一行
print("所有块的第一行:\n", arr3d[:, 0, :])

# 使用省略号:获取所有块的最后一列
print("所有块的最后一列 (arr3d[..., -1]):\n", arr3d[..., -1])

输出:

三维数组 shape: (2, 3, 4)
arr3d:
 [[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
arr3d[1, 2, 3] = 23
所有块的第一行:
 [[ 0  1  2  3]
 [12 13 14 15]]
所有块的最后一列 (arr3d[..., -1]):
 [[ 3  7 11]
 [15 19 23]]

4. 视图与副本:切片返回视图

NumPy的切片操作返回原数组的视图,意味着修改切片会影响原数组(反之亦然)。这与Python列表的切片不同(列表返回副本)。

arr = np.array([1, 2, 3, 4, 5])
slice_view = arr[1:4]      # 视图
print("原数组:", arr)
print("切片视图:", slice_view)

# 修改视图
slice_view[0] = 99
print("修改视图后原数组:", arr)   # 原数组也被修改

# 检查是否共享内存
print("共享内存?", np.may_share_memory(arr, slice_view))  # True

输出:

原数组: [1 2 3 4 5]
切片视图: [2 3 4]
修改视图后原数组: [ 1 99  3  4  5]
共享内存? True
重要: 如果需要复制切片数据(独立副本),可以使用 copy() 方法:slice_copy = arr[1:4].copy()

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

花式索引使用整数数组或列表作为索引,可以选择任意顺序的元素,并返回副本(不是视图)。

arr = np.array([10, 20, 30, 40, 50])
idx = [0, 2, 4]
print("arr[idx] =", arr[idx])   # 选择索引0,2,4的元素

# 二维花式索引
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,2)两个点
points = arr2d[[0, 2], [1, 2]]  # 分别对应 (0,1) 和 (2,2)
print("选中的点:", points)

输出:

arr[idx] = [10 30 50]
原二维数组:
 [[ 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 10]

6. 布尔索引

布尔索引使用布尔数组(条件表达式的结果)选择满足条件的元素,返回副本

arr = np.array([5, 10, 15, 20, 25])
bool_idx = arr > 15
print("arr > 15:", bool_idx)
print("arr[arr > 15] =", 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])   # 返回一维数组
print("偶数行且偶数列:\n", arr2d[arr2d % 2 == 0])

输出:

arr > 15: [False False False  True  True]
arr[arr > 15] = [20 25]
修改后: [ 0  0 15 20 25]
大于5的元素:
 [6 7 8 9]
偶数行且偶数列:
 [2 4 6 8]

7. 组合索引(混合切片和花式/布尔索引)

可以在不同维度混合使用切片、花式索引和布尔索引,但需要注意结果的维度。

arr2d = np.arange(12).reshape(3, 4)
print("原数组:\n", arr2d)

# 切片与花式索引:选择第0行和第2行的第1到第3列
result = arr2d[[0, 2], 1:3]
print("选择指定行的部分列:\n", result)

# 切片与布尔索引:选择所有行,且元素值大于5的列(列条件作用于整个数组,返回布尔掩码)
mask = arr2d > 5
print("元素大于5的掩码:\n", mask)
# 注意:arr2d[mask] 返回所有满足条件的一维数组
print("arr2d[mask] =", arr2d[mask])

输出:

原数组:
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
选择指定行的部分列:
 [[1 2]
 [9 10]]
元素大于5的掩码:
 [[False False False False]
 [False False  True  True]
 [ True  True  True  True]]
arr2d[mask] = [ 6  7  8  9 10 11]

8. 省略号 (...) 的使用

对于高维数组,可以使用省略号 ... 代表一个或多个完整的切片(即 :),简化代码。

arr3d = np.arange(24).reshape(2, 3, 4)
# 以下两种写法等价
print("arr3d[0, ...] 相当于 arr3d[0, :, :]:\n", arr3d[0, ...])
print("arr3d[..., -1] 相当于 arr3d[:, :, -1]:\n", arr3d[..., -1])

9. 使用 take()put() 进行索引和赋值

NumPy还提供了 take()put() 方法,与花式索引类似,但可能更适合某些性能场景。

arr = np.array([10, 20, 30, 40])
indices = [1, 3]
print("arr.take(indices) =", arr.take(indices))
arr.put(indices, [99, 100])
print("arr.put后:", arr)

输出:

arr.take(indices) = [20 40]
arr.put后: [ 10  99  30 100]
总结:
  • 基本索引和切片返回视图,修改会影响原数组。
  • 花式索引和布尔索引返回副本,修改不会影响原数组。
  • 掌握索引和切片是高效使用NumPy的关键,可以避免显式循环,提升代码性能。