numpy-索引 花式索引 切片 布尔值索引

Posted by Hilda on July 15, 2025

1.基本索引和切片

基本索引和切片是 NumPy 中访问数组元素的最常见方式,类似于 Python 列表的索引和切片,但功能更强大,尤其是在多维数组中。

(1)一维数组

对于一维数组,基本索引和切片与 Python 列表非常相似:

  • 索引单个元素: 使用方括号 [] 和一个整数索引来访问单个元素。索引从 0 开始。负数索引表示从数组末尾开始计数(-1 是最后一个元素)。
    • arr[i]:访问索引为 i 的元素。
    • arr[-i]:访问倒数第 i 个元素。
  • 切片(Slicing): 使用 [start:stop:step] 语法来获取数组的一个子序列。
    • start:切片的起始索引(包含)。如果省略,默认为 0
    • stop:切片的结束索引(不包含)。如果省略,默认为数组的长度。
    • step:步长,表示每隔多少个元素取一个。如果省略,默认为 1
    • arr[start:stop]:从 startstop-1 的元素。
    • arr[start:]:从 start 到末尾的元素。
    • arr[:stop]:从开头到 stop-1 的元素。
    • arr[::step]:以指定步长获取所有元素。
    • arr[::-1]:反转数组。
    • arr[::-step]:反转数组并以指定步长获取。

(2)多维数组

对于多维数组,索引和切片扩展到每个维度。可以使用逗号 , 分隔每个维度的索引或切片。

  • 索引单个元素:
    • arr2d[row_index, col_index]:访问二维数组中特定行和列的元素。这等价于 arr2d[row_index][col_index],但前者更高效和推荐。
    • 对于更高维数组,以此类推:arr3d[dim1_idx, dim2_idx, dim3_idx]
  • 切片多维数组:
    • arr2d[row_slice, col_slice]:同时对行和列进行切片。
    • : 表示选择该维度的所有元素。
    • 例如,arr2d[:2, 1:] 表示选择前两行(索引 0 和 1),以及从第二列(索引 1)到最后一列的所有列。
1
2
3
4
5
6
7
8
9
10
11
12
arr2d = np.arange(1, 10).reshape(3, 3)
print(arr2d)
# 第一行最后一列
print(arr2d[0, -1])
# 第三行第二列
print(arr2d[2, 1])
# 前两行后2列
print(arr2d[:2, 1:])
# 从第二行到末尾,前2列
print(arr2d[1:, :2])
# 所有行,第一列
print(arr2d[:, 0])

image-20250715200350468

(3)切片是视图

视图是浅拷贝的意思。

这是 NumPy 中一个非常重要的概念:NumPy 中的数组切片是原始数组的视图(view),而不是副本(copy)。 这意味着:

  • 切片操作不会复制数据,而是创建一个新的数组对象,这个新对象共享原始数组的底层数据内存。
  • 对切片(视图)的任何修改都会直接反映到原始数组上,因为它们操作的是同一块数据。
  • 这与 Python 列表的切片行为不同,Python 列表的切片会创建新的列表副本。
  • 这种设计是为了提高性能和内存效率,尤其是在处理大型数据集时。

切片赋值:您可以使用切片来修改数组的多个元素。当您对一个切片进行赋值时,NumPy 会将赋值的值广播(broadcast)到切片所对应的原始数组的元素上。

NumPy 数组的底层数据存储在连续的内存块中。数组对象本身包含元数据(如形状 shape、数据类型 dtype、步长 strides 和指向数据缓冲区的指针 data)。

  • 索引: 当您使用整数索引时,NumPy 会根据数组的 strides(每跨越一个元素在内存中需要跳过的字节数)和 offset(起始偏移量)来计算出目标元素在数据缓冲区中的精确内存地址,然后直接访问该地址的数据。
  • 切片: 当您创建切片时,NumPy 不会分配新的数据缓冲区。相反,它会创建一个新的数组对象。这个新数组对象:
    • 拥有自己的 shapestrides,这些 shapestrides 是根据切片规则计算出来的,以正确地“视图”原始数据。
    • 它的数据指针 data 仍然指向原始数组的数据缓冲区,但可能有一个新的起始偏移量。
    • 它的 base 属性会指向原始数组对象(如果它是视图)。
    • 它的 flags.owndata 属性会是 False

这种“视图”机制使得切片操作非常快速,因为它避免了昂贵的数据复制。

(4)选择题

  1. 给定以下 NumPy 代码:

    1
    2
    3
    4
    
    import numpy as np
    a = np.array([10, 20, 30, 40, 50])
    b = a[1:4]
    b[0] = 99
    

    执行上述代码后,a 数组的值是什么?

    A. [10, 20, 30, 40, 50] B. [10, 99, 30, 40, 50] C. [10, 99, 99, 99, 50] D. [99, 20, 30, 40, 50]

    答案:B

    b = a[1:4] 创建了一个视图,b 引用了 a[20, 30, 40] 部分。

    b[0] = 99 修改的是 b 的第一个元素,也就是 a 的第二个元素(20 变成了 99)。

  2. 对于二维 NumPy 数组 arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),表达式 arr2d[1:, :2] 的结果是什么? A. [[4, 5], [7, 8]] B. [[2, 3], [5, 6], [8, 9]] C. [[1, 2], [4, 5]] D. [[4, 5, 6], [7, 8, 9]]

    答案:A

    arr2d[1:, :2] 意味着:

    • 行:从索引 1 开始到末尾(即第 2 行和第 3 行)。
    • 列:从索引 02-1=1(即第 1 列和第 2 列)。

    所以它选择了 arr2d[1,0], [1,1][2,0], [2,1] 对应的元素。

(5)编程题

创建一个 5*5 的 NumPy 数组,其中所有元素都是 1 到 25 的整数(例如,使用 np.arange(1, 26).reshape(5, 5))。

使用基本切片操作:

  • 提取数组的中心 3*3 子数组。
  • 提取数组的偶数行和奇数列。
  • 将数组的第三行所有元素设置为 0。

打印每次操作后的数组。


1
2
3
4
5
6
7
8
9
10
11
12
arr = np.arange(1, 26).reshape(5, 5)
print(arr)
arr_center = arr[1:4, 1:4]
print(arr_center)
arr2 = arr[::2, [1,3]]
print(arr2)
# 或者
arr2_ = arr[::2, 1::2]
print(arr2_)
arr3 = arr.copy()
arr3[2] = 0
print(arr3)

image-20250715201308944

2.花式索引和索引技巧

花式索引(Fancy Indexing)是 NumPy 中一种强大的索引方式,它允许使用整数数组或布尔数组作为索引来选择非连续的元素。与基本切片不同,花式索引总是将数据复制到新数组中,这意味着它返回的是一个副本,而不是视图

(1)整数数组索引

当使用一个整数数组(或列表)作为索引时,NumPy 会根据这些索引值来选择对应的元素或行/列。

  • 一维数组:
    • arr[[idx1, idx2, ...]]:返回一个新数组,其中包含 idx1, idx2 等索引位置的元素。
    • 索引数组中的值可以重复,这将导致结果数组中出现重复的元素。
    • 结果数组的形状与索引数组的形状相同。
  • 多维数组:
    • 选择多行/多列: 当只对一个维度使用整数数组索引时,它会选择对应的行(对于第一维)或列(对于第二维)。
      • arr2d[[row_idx1, row_idx2]]:选择指定行。
      • arr2d[:, [col_idx1, col_idx2]]:选择指定列。
    • 选择特定位置的元素(配对索引): 当您为每个维度都提供一个相同长度的整数数组作为索引时,NumPy 会将这些索引数组中的对应元素配对,从而选择特定位置的元素。
      • arr2d[[row_indices], [col_indices]]:例如,arr2d[[1, 3], [2, 4]] 会选择 arr2d[1, 2]arr2d[3, 4] 两个元素。结果是一个一维数组。

【1】一维数组花式索引

1
2
3
4
5
6
7
8
arr = np.arange(1, 11)
print(arr)
arr1 = arr[[1, 1, 3, 3, 7, 8, 9]]
print(arr1)
# 花式索引返回的是副本,不是视图
print(arr1.base)  # None
print(arr1.flags.owndata) # True

image-20250715202045623

【2】二维数组花式索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
"""
[[41 97 39 81 85]
 [ 3 73 26  8 45]
 [ 1 69 12 48 18]
 [92 21 31 30 46]]
[[ 3 73 26  8 45]
 [92 21 31 30 46]]
[73 46]
"""

arr = np.random.randint(1, 100, (4, 5))
print(arr)
# 选择第2行和第四行
arr1 = arr[[1, 3]]
print(arr1)
# 选择第二行第二个,和 最后一行最后一个
arr3 = arr[[1, 3], [1, 4]]
print(arr3)

np.ix_() 函数

np.ix_() 函数是一个非常有用的工具,用于组合不同的向量(一维索引数组)来选择一个区域(网格)。它返回一个元组,其中包含用于构建 N 维网格索引的 N 个数组。

  • np.ix_([row_indices], [col_indices]):这会生成两个数组,当它们用于索引二维数组时,会选择由 row_indicescol_indices 定义的所有交叉点。
  • 结果等价于先用行索引进行花式索引,再对结果用列索引进行花式索引(或反之)。例如,arr2d[np.ix_([1,3],[2,4])] 等价于 arr2d[[1,3]][:,[2,4]]
  • np.ix_ 的主要优势在于它能够处理任意维度的交叉索引,并且在内部优化了索引数组的创建。

花式索引的工作原理与基本切片不同,因为它需要从原始数据中提取非连续的元素,并将它们组织成一个新的数组。

  • 数据复制: 当您使用整数数组进行花式索引时,NumPy 会遍历索引数组中的每一个索引值。对于每个索引,它会计算出原始数组中对应元素的内存地址,然后将该元素的值复制到一个新分配的内存区域。
  • 新数组创建: 最终,所有被选择的元素都被复制到一个全新的 NumPy 数组对象中。这个新数组拥有自己的数据缓冲区,因此对它的修改不会影响到原始数组。
  • baseflags.owndata 花式索引返回的数组的 base 属性将是 None,并且 flags.owndata 将是 True
  • 性能: 由于涉及数据复制,花式索引通常比基本切片(视图)的性能开销更大,尤其是在处理大型数组时。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
print(arr)
"""
[[41 97 39 81 85]
 [ 3 73 26  8 45]
 [ 1 69 12 48 18]
 [92 21 31 30 46]]
"""
row_index = [1, 3, 3, 3]
col_index = [2, 4, 4]
selected_data = arr[np.ix_(row_index, col_index)]
print(selected_data)
"""
[[26 45 45]
 [31 46 46]
 [31 46 46]
 [31 46 46]]
"""

(2)选择题

  1. 给定以下 NumPy 代码:

    1
    2
    3
    4
    
    import numpy as np
    data = np.array([10, 20, 30, 40, 50])
    result = data[[0, 2, 4]]
    result[0] = 100
    

    执行上述代码后,data 数组的值是什么?

    A. [100, 20, 30, 40, 50] B. [10, 20, 30, 40, 50] C. [100, 20, 100, 40, 100] D. [10, 20, 100, 40, 50]

    答案:B ,花式索引 data[[0, 2, 4]] 返回的是 data 的一个副本

    result 的修改不会影响到原始 data 数组。

  2. 对于二维 NumPy 数组 matrix = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]]),表达式 matrix[[0, 2], [1, 0]] 的结果是什么? A. [[2, 3], [7, 8]] B. [2, 7] C. [[1, 3], [7, 9]] D. [2, 8]

    答案:B,matrix[[0, 2], [1, 0]] 是配对索引。

    它会选择 matrix[0, 1](值为 2)和 matrix[2, 0](值为 7)这两个元素。

(3)编程题

  1. 创建一个 4*4 的 NumPy 数组,元素为 1 到 16。
  2. 使用花式索引:
    • 提取第 0 行、第 2 行和第 3 行。
    • 提取位于 (0, 0), (1, 2), (2, 1), (3, 3) 位置的元素。
    • 使用 np.ix_ 提取由行索引 [0, 2] 和列索引 [1, 3] 定义的子矩阵。
  3. 打印每次操作后的结果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
arr = np.arange(1, 17).reshape(4, 4)
arr
"""
array([[ 1,  2,  3,  4],
       [ 5,  6,  7,  8],
       [ 9, 10, 11, 12],
       [13, 14, 15, 16]])
"""
arr1 = arr[[0, 2, 3]]
arr1
"""
array([[ 1,  2,  3,  4],
       [ 9, 10, 11, 12],
       [13, 14, 15, 16]])
"""
arr2 = arr[[0, 1, 2, 3], [0, 2, 1, 3]]
arr2
"""
array([ 1,  7, 10, 16])
"""
row_index = [0, 2]
col_index = [1, 3]
arr3 = arr[np.ix_(row_index, col_index)]
arr3
"""
array([[ 2,  4],
       [10, 12]])
"""

3.布尔值索引

布尔值索引是 NumPy 中一种非常强大且直观的数据筛选方式。它允许您使用一个与数组形状相同的布尔数组来选择元素。布尔数组中值为 True 的位置对应的原始数组元素会被选中,而值为 False 的位置对应的元素则会被忽略。

创建布尔数组: 最常见的创建布尔数组的方式是使用比较运算符(如 ==, >, <, >=, <=, !=)对 NumPy 数组进行操作。这些操作会逐元素地进行比较,并返回一个与原始数组形状相同的布尔数组。

筛选元素: 将布尔数组作为索引传递给 NumPy 数组,即可筛选出所有对应位置为 True 的元素。

  • arr[boolean_array]:返回一个一维数组,其中包含所有满足条件的元素。即使原始数组是多维的,结果也会被展平为一维

结合逻辑运算符: 您可以使用 & (与), | (或), ~ (非) 等位运算符来组合多个布尔条件。注意: 在 NumPy 中,对于布尔数组的逻辑运算,必须使用 &, |, ~ 而不是 Python 的 and, or, not 关键字,因为后者会尝试对整个布尔数组进行真值判断,而不是逐元素操作。

修改元素: 布尔索引也可以用于修改数组中满足特定条件的元素

布尔索引的底层机制涉及对数据进行逐元素的条件检查和数据复制。

  • 条件评估: 当您执行 arr > 90 这样的比较操作时,NumPy 会在内部创建一个临时的布尔数组。这个布尔数组的每个元素都对应原始数组中相应位置的元素,如果条件满足则为 True,否则为 False
  • 数据选择与复制: 当您将这个布尔数组作为索引传递给原始数组时,NumPy 会遍历布尔数组。对于每一个 True 值,它会找到原始数组中对应位置的元素,并将其复制到一个新的、通常是一维的 NumPy 数组中。
  • 返回副本: 类似于花式索引,布尔索引操作也会返回一个新的数组副本。这是因为被选中的元素可能在原始数组中是非连续的,为了将它们组织成一个新的连续数组,需要进行数据复制。
  • 性能: 布尔索引需要额外的内存来存储布尔数组,并且涉及数据复制,因此在性能上可能不如直接的切片操作(视图),但它提供了极大的灵活性来筛选数据。

【1】一维数组的布尔索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
names = np.array(['softpo', 'Brandon', 'Will', 'Michael', 'Will', 'Ella', 'Daniel', 'softpo', 'Will', 'Brandon'])
names
"""
array(['softpo', 'Brandon', 'Will', 'Michael', 'Will', 'Ella', 'Daniel',
       'softpo', 'Will', 'Brandon'], dtype='<U7')
"""
arr1 = names == "Will"
arr1
"""
array([False, False,  True, False,  True, False, False, False,  True,
       False])
"""
name_Will = names[arr1]
name_Will
"""
array(['Will', 'Will', 'Will'], dtype='<U7')
"""

# 验证name_Will是一个副本
print(name_Will.base)  # None
name_Will.flags.owndata  # True

【2】二维数组的布尔索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
arr = np.random.randint(0, 100, (5, 4))
print(arr)
"""
[[ 0 58 35 49]
 [82 96 23 32]
 [84  9  1 77]
 [77 87 57 73]
 [32 88 62 43]]
"""
# 大于50
arr_bigger_50_cond = arr > 50
print(arr_bigger_50)
"""
[53 51 61 56 60 63 76 84 69 61 92 86]
"""
arr_bigger_50 = arr[arr_bigger_50_cond]
print(arr_bigger_50)   # 展平成1维
# 多个布尔条件   比如20-80
cond2 = (arr > 20) & (arr < 80)
arr_new = arr[cond2]
display(arr_new)
"""
array([58, 35, 49, 23, 32, 77, 77, 57, 73, 32, 62, 43])
"""
# 布尔索引赋值
arr1 = np.random.randint(0, 10, size=(3, 3))
display(arr1)
"""
array([[0, 1, 4],
       [3, 8, 3],
       [1, 4, 5]])
"""
# 将所有的偶数 设置为-1
arr1[arr1 % 2 == 0] = -1
arr1
"""
array([[-1,  1, -1],
       [ 3, -1,  3],
       [ 1, -1,  5]])
"""

(1)选择题

  1. 给定以下 NumPy 代码:

    1
    2
    3
    
    import numpy as np
    data = np.array([5, 12, 8, 20, 3])
    filtered_data = data[data > 10]
    

    filtered_data 的值是什么?

    A. [12, 20] B. [5, 8, 3] C. [True, False, False, True, False] D. [12, 8, 20]

    答案:A,data > 10 会生成布尔数组 [False, True, False, True, False]

    使用此布尔数组索引 data 会选择对应 True 位置的元素,即 1220

  2. 以下哪种逻辑运算符在 NumPy 布尔索引中是正确的?

    A. arr[cond1 and cond2] B. arr[cond1 & cond2] C. arr[cond1 or cond2] D. arr[not cond]

    答案:B,在 NumPy 中,对布尔数组进行逻辑运算必须使用逐位运算符 & (与), | (或), ~ (非)。

(2)编程题

  1. 创建一个 4*4 的 NumPy 数组,包含 0 到 15 的随机整数。
  2. 使用布尔索引:
    • 提取所有能被 3 整除的元素。
    • 将所有小于 5 的元素设置为 0。
    • 将所有大于 10 的元素设置为其自身的两倍。
  3. 打印每次操作后的数组。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
arr = np.random.randint(0, 16, (4, 4))
print(arr)
"""
[[ 5  1  5  6]
 [12 11  7  2]
 [ 7 10  1 15]
 [ 4  2  4  4]]
"""
con1 = arr % 3 == 0
arr1 = arr[con1]
print(arr1)
"""
[ 6 12 15]
"""

arr2 = arr.copy()
arr2[arr2 < 5] = 0
print(arr2)
"""
[[ 5  0  5  6]
 [12 11  7  0]
 [ 7 10  0 15]
 [ 0  0  0  0]]
"""

arr3 = arr.copy()
arr3[arr3 > 10] *= 2
print(arr3)
"""
[[ 5  1  5  6]
 [24 22  7  2]
 [ 7 10  1 30]
 [ 4  2  4  4]]
"""