跳到主要内容

NumPy 数组索引和切片

数组索引和切片是 NumPy 中最常用的操作之一。本章将详细介绍各种索引和切片方法。

基本索引

一维数组索引

一维数组的索引方式与 Python 列表类似:

import numpy as np

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

# 正向索引(从0开始)
print(f"arr[0]: {arr[0]}") # 第一个元素: 0
print(f"arr[3]: {arr[3]}") # 第四个元素: 3

# 负向索引(从-1开始)
print(f"arr[-1]: {arr[-1]}") # 最后一个元素: 9
print(f"arr[-3]: {arr[-3]}") # 倒数第三个元素: 7

# 索引越界会报错
# print(arr[10]) # IndexError

多维数组索引

多维数组使用逗号分隔的索引元组:

import numpy as np

# 创建二维数组
arr = np.array([[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]])

print(f"数组:\n{arr}")

# 使用逗号分隔各维度索引
print(f"arr[0, 0]: {arr[0, 0]}") # 第一行第一列: 1
print(f"arr[1, 2]: {arr[1, 2]}") # 第二行第三列: 7
print(f"arr[-1, -1]: {arr[-1, -1]}") # 最后一行最后一列: 12

# 只指定部分索引
print(f"arr[0]: {arr[0]}") # 整行,返回一维数组
print(f"arr[:, 0]: {arr[:, 0]}") # 整列,返回一维数组

# 三维数组
arr_3d = np.array([[[1, 2], [3, 4]],
[[5, 6], [7, 8]]])
print(f"三维数组形状: {arr_3d.shape}") # (2, 2, 2)
print(f"arr_3d[0]:\n{arr_3d[0]}") # 第一"页"
print(f"arr_3d[0, 1]: {arr_3d[0, 1]}") # 第一页第二行
print(f"arr_3d[0, 1, 0]: {arr_3d[0, 1, 0]}") # 3

切片操作

一维数组切片

切片语法为 start:stop:step,与 Python 列表完全一致:

import numpy as np

arr = np.array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])

# 基本切片
print(f"arr[2:5]: {arr[2:5]}") # [2, 3, 4] - 索引2到4(不含5)
print(f"arr[:5]: {arr[:5]}") # [0, 1, 2, 3, 4] - 从开头到4
print(f"arr[5:]: {arr[5:]}") # [5, 6, 7, 8, 9] - 从5到结尾
print(f"arr[:]: {arr[:]}") # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - 全部

# 负索引切片
print(f"arr[-3:]: {arr[-3:]}") # [7, 8, 9] - 最后三个
print(f"arr[:-3]: {arr[:-3]}") # [0, 1, 2, 3, 4, 5, 6] - 除了最后三个

# 步长切片
print(f"arr[::2]: {arr[::2]}") # [0, 2, 4, 6, 8] - 每隔一个
print(f"arr[1::2]: {arr[1::2]}") # [1, 3, 5, 7, 9] - 从1开始
print(f"arr[::-1]: {arr[::-1]}") # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] - 反转
print(f"arr[5:2:-1]: {arr[5:2:-1]}") # [5, 4, 3] - 反向切片

# 省略参数
print(f"arr[::]: {arr[::]}") # 全部(等同于 arr[:])
print(f"arr[::3]: {arr[::3]}") # [0, 3, 6, 9] - 步长3

多维数组切片

多维数组的切片是每个维度独立切片的组合:

import numpy as np

arr = np.array([[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11, 12, 13, 14, 15],
[16, 17, 18, 19, 20]])

print(f"数组 (4x5):\n{arr}")

# 各维度独立切片
print(f"arr[1:3, 2:4]:\n{arr[1:3, 2:4]}")
# 行1-2,列2-3

# 省略某些维度
print(f"arr[:, 0]: {arr[:, 0]}") # 所有行,第0列
print(f"arr[0, :]: {arr[0, :]}") # 第0行,所有列
print(f"arr[:, :]:\n{arr[:, :]}") # 全部

# 步长切片
print(f"arr[::2, ::2]:\n{arr[::2, ::2]}")
# 行间隔2,列间隔2

# 负索引切片
print(f"arr[-2:, -3:]:\n{arr[-2:, -3:]}")
# 最后2行,后3列

切片是视图而非副本

这是 NumPy 与 Python 列表的重要区别:切片返回的是视图,而不是副本

import numpy as np

arr = np.array([1, 2, 3, 4, 5])
sliced = arr[1:4]

print(f"原数组: {arr}")
print(f"切片: {sliced}")

# 修改切片会影响原数组
sliced[0] = 99
print(f"修改切片后,原数组: {arr}") # [1, 99, 3, 4, 5]

# 如果需要副本,使用 copy()
arr2 = np.array([1, 2, 3, 4, 5])
sliced_copy = arr2[1:4].copy()
sliced_copy[0] = 99
print(f"修改副本后,原数组: {arr2}") # [1, 2, 3, 4, 5] - 不变

高级索引

整数数组索引

使用整数数组作为索引,可以自由选择任意元素:

import numpy as np

arr = np.array([10, 20, 30, 40, 50, 60])

# 一维整数数组索引
indices = [0, 2, 4]
print(f"arr[[0, 2, 4]]: {arr[indices]}") # [10, 30, 50]

# 重复索引
indices2 = [0, 0, 1, 1, 2, 2]
print(f"重复索引: {arr[indices2]}") # [10, 10, 20, 20, 30, 30]

# 二维索引
arr_2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# 分别指定行索引和列索引
rows = [0, 1, 2]
cols = [2, 1, 0]
print(f"arr[rows, cols]: {arr_2d[rows, cols]}") # [3, 5, 7]

# 选择特定位置的元素
print(f"arr[[0, 0], [0, 2]]: {arr_2d[[0, 0], [0, 2]]}") # [1, 3]

布尔索引

使用布尔数组作为索引,选择符合条件的元素:

import numpy as np

arr = np.array([10, 20, 30, 40, 50, 60, 70, 80])

# 创建布尔条件
condition = arr > 40
print(f"条件: {condition}") # [False False False False False True True True]

# 使用布尔数组索引
print(f"arr[condition]: {arr[condition]}") # [50, 60, 70, 80]

# 常用写法:直接使用条件表达式
print(f"arr[arr > 40]: {arr[arr > 40]}") # [50, 60, 70, 80]
print(f"arr[(arr > 20) & (arr < 60)]: {arr[(arr > 20) & (arr < 60)]}") # [30, 40, 50]

# 二维数组布尔索引
arr_2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# 选择大于5的元素(返回一维数组)
print(f"arr_2d[arr_2d > 5]: {arr_2d[arr_2d > 5]}") # [6, 7, 8, 9]

# 按行条件选择
print(f"arr_2d[arr_2d[:, 0] > 3]:\n{arr_2d[arr_2d[:, 0] > 3]}")
# 选择第一列大于3的行

np.where 函数

np.where 可以根据条件返回值或索引:

import numpy as np

arr = np.array([10, 20, 30, 40, 50, 60])

# 基础用法:条件索引
indices = np.where(arr > 30)
print(f"大于30的索引: {indices}") # (array([3, 4, 5]),)
print(f"大于30的元素: {arr[indices]}") # [40, 50, 60]

# 三参数用法:类似三元表达式
result = np.where(arr > 30, arr * 2, arr)
print(f"三参数 where: {result}") # [10, 20, 30, 80, 100, 120]

# 二维数组
arr_2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])

# 返回满足条件的索引
rows, cols = np.where(arr_2d > 5)
print(f"大于5的位置: 行{rows}, 列{cols}")

# 使用 where 创建掩码
mask = arr_2d > 5
print(f"掩码:\n{mask}")

花式索引

花式索引(Fancy Indexing)使用整数数组或布尔数组进行索引:

import numpy as np

arr = np.arange(20).reshape(4, 5)
print(f"原数组:\n{arr}")

# 整数数组索引 - 选择特定行
rows = [0, 2, 3]
print(f"选择行 [0, 2, 3]:\n{arr[rows]}")

# 整数数组索引 - 选择特定列
cols = [1, 3, 4]
print(f"选择列 [1, 3, 4]:\n{arr[:, cols]}")

# 组合使用
print(f"arr[rows][cols]:\n{arr[rows][:, cols]}")

# 更高效的方式:arr[rows, cols[:, np.newaxis]]
print(f"arr[rows, cols]:\n{arr[np.ix_(rows, cols)]}")

ix_ 函数

np.ix_ 用于创建网格索引,使得花式索引更清晰:

import numpy as np

arr = np.arange(12).reshape(3, 4)
print(f"原数组:\n{arr}")

# 选择特定行和列的交叉点
rows = [0, 2]
cols = [1, 3]
print(f"交叉点:\n{arr[np.ix_(rows, cols)]}")

# ix_ 确保生成正确的广播索引
# 等价于:
# arr[[0, 2]][:, [1, 3]]

索引和切片的选择

场景推荐方法说明
选择连续区域切片 arr[1:3, 2:4]返回视图,性能好
选择特定元素整数索引 arr[[0, 2], [1, 3]]返回副本
按条件选择布尔索引 arr[arr > 0]灵活方便
复杂选择np.where + 整数索引组合使用
import numpy as np

arr = np.arange(12).reshape(3, 4)
print(f"数组:\n{arr}")

# 选择连续区域(推荐切片)
region = arr[1:, 2:]
print(f"切片区域:\n{region}")

# 选择分散位置(使用花式索引)
disperse = arr[[0, 2], [0, 3]]
print(f"分散位置: {disperse}")

# 复杂条件(使用布尔索引)
complex_condition = arr[(arr > 3) & (arr < 10)]
print(f"复杂条件: {complex_condition}")

小结

本章介绍了 NumPy 的各种索引和切片方法:

  1. 基本索引:使用整数索引访问单个元素
  2. 切片操作:使用 start:stop:step 语法,切片返回视图
  3. 高级索引
    • 整数数组索引:自由选择元素,返回副本
    • 布尔索引:按条件筛选
  4. np.where:根据条件获取索引或值
  5. 花式索引:使用整数数组进行复杂选择

理解这些索引方法的区别(视图 vs 副本)对于正确操作 NumPy 数组非常重要。