NumPy 索引入门

NumPyBeginner
立即练习

介绍

欢迎回来!既然你已经了解了如何创建 NumPy 数组,现在是时候学习如何访问和操作其中的数据了。在本实验中,你将探索 **索引 (indexing)**——这是访问和修改 NumPy 数组中数据的首要方法。

在已有知识基础上学习

在之前的实验中,你学习了:

  • 如何使用各种 NumPy 函数创建数组
  • 数组与 Python 列表之间的区别
  • 数组的维度(1D、2D、3D)和数据类型

现在你将学习如何:

  • 访问单个元素或元素组
  • 从数组中提取特定的行、列或区域
  • 使用高级选择技术进行复杂数据过滤
  • 通过索引修改数组数据

为什么索引很重要

索引是数据操作的基础,因为它允许你:

  • 提取数据子集 以供分析
  • 修改大型数据集中的特定值
  • 根据条件过滤数据
  • 对选定的元素执行向量化操作 (vectorized operations)

这些技能对于 Python 中的任何数据分析或科学计算任务都至关重要。

一维数组的基本索引和切片

既然你已经知道如何创建数组,现在让我们学习如何访问它们的内容。本实验中的所有工作都将在 indexing_practice.py 文件中进行。

理解数组索引

基于零的索引 (Zero-Based Indexing)

与 Python 列表一样,NumPy 数组也使用 基于零的索引。这意味着:

  • 第一个元素位于索引 0
  • 第二个元素位于索引 1
  • 以此类推...

可视化数组索引

对于数组 x = [10, 20, 30, 40, 50]

索引:  0   1   2   3   4
值:   10  20  30  40  50

所以 x[0] 返回 10x[2] 返回 30,依此类推。

切片语法 (Slicing Syntax)

切片允许你使用 start:stop:step 语法选择一个元素范围:

  • start: 切片开始的索引(包含)
  • stop: 切片结束的索引(不包含)
  • step: 要跳过的元素数量(可选,默认为 1)

常见的切片模式:

  • x[1:4]: 索引为 1、2、3 的元素
  • x[:3]: 前 3 个元素(索引 0、1、2)
  • x[2:]: 从索引 2 到末尾
  • x[::2]: 从索引 0 开始,每隔一个元素取一个
  • x[::-1]: 反转整个数组

首先,在编辑器中打开 indexing_practice.py 文件。然后,用以下代码替换其全部内容。此代码创建一个一维数组,并演示了如何访问单个元素和一组元素(切片)。

import numpy as np

## 创建一个包含从 0 到 9 的数字的一维数组
x = np.arange(10)
print("Original array:", x)

## 访问索引为 2 的单个元素
element = x[2]
print("Element at index 2:", element)

## 对数组进行切片,从索引 1 开始到索引 7 结束(不包含 7),步长为 2
a_slice = x[1:7:2]
print("Slice from 1 to 7 with step 2:", a_slice)

将代码添加到 indexing_practice.py 后,保存文件。现在,通过执行以下命令从终端运行脚本:

python indexing_practice.py

你应该会看到以下输出,其中显示了原始数组、指定索引处的元素以及生成的切片。

Original array: [0 1 2 3 4 5 6 7 8 9]
Element at index 2: 2
Slice from 1 to 7 with step 2: [1 3 5]

多维数组的索引

现在让我们来处理包含多个维度的数组。这正是 NumPy 相较于 Python 列表的强大之处!

多维思维

二维数组如同表格

二维数组就像一个电子表格或表格:

  • 行 (Rows) 是第一个维度(水平方向)
  • 列 (Columns) 是第二个维度(垂直方向)
  • 你需要同时指定行和列的索引:array[row, column]

可视化二维索引

对于一个二维数组:

array = [[10, 20, 30],
         [40, 50, 60],
         [70, 80, 90]]

索引:     0,0  0,1  0,2
             1,0  1,1  1,2
             2,0  2,1  2,2
  • array[0, 0] → 10 (第一行,第一列)
  • array[1, 2] → 60 (第二行,第三列)
  • array[2, 1] → 80 (第三行,第二列)

选择整行或整列

  • array[0]array[0, :] → 整个第一行 [10, 20, 30]
  • array[:, 1] → 整个第二列 [20, 50, 80]
  • 这比嵌套的 Python 列表方便得多!

让我们通过一个二维 (2D) 数组来练习这一点。使用以下代码更新你的 indexing_practice.py 文件。此脚本创建一个 3x4 的数组,并展示了如何访问单个元素和整行。

import numpy as np

## 创建一个二维数组(3 行,4 列)
x = np.arange(12).reshape(3, 4)
print("Original 2D array:\n", x)

## 访问第 1 行、第 2 列的元素
element = x[1, 2]
print("\nElement at (1, 2):", element)

## 访问整个第一行(行索引为 0)
first_row = x[0]
print("\nFirst row:", first_row)

保存文件并再次从终端运行它:

python indexing_practice.py

输出将显示二维数组以及你选择的特定部分。

Original 2D array:
 [[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]

Element at (1, 2): 6

First row: [0 1 2 3]

高级索引

基础切片对于选择连续区域效果很好,但有时你需要更复杂的选择。NumPy 提供了两种强大的高级索引技术:

整数数组索引 (Integer Array Indexing)

通过提供一个索引数组来选择任意元素。这就像使用位置来从列表中挑选特定的项。

实际示例: 你有考试分数,想查看第 3、7 和第 12 位学生的得分:

scores = np.array([85, 92, 78, 95, 88, 76, 91, 89, 84, 93, 87, 90, 82])
student_positions = [3, 7, 12]  ## 你感兴趣的学生位置
selected_scores = scores[student_positions]  ## [95, 89, 82]

布尔数组索引 (Boolean Array Indexing) / 掩码 (Masking)

根据条件选择元素。创建一个 True/False 值的“掩码”,然后用它来过滤数组。

实际示例: 从班级中筛选出及格成绩(≥ 80):

scores = np.array([85, 92, 78, 95, 88, 76, 91, 89, 84, 93])
passing_mask = scores >= 80  ## [True, True, False, True, True, False, True, True, True, True]
passing_scores = scores[passing_mask]  ## [85, 92, 95, 88, 91, 89, 84, 93]

重要性

  • 整数索引: 非常适合采样特定的数据点
  • 布尔索引: 非常适合数据过滤和条件选择
  • 两者都会创建 副本(而不是视图),因此修改不会影响原始数组

让我们来尝试一下。用以下代码替换 indexing_practice.py 的内容:

import numpy as np

## --- 整数数组索引 ---
x = np.arange(10, 0, -1)
print("Array for integer indexing:", x)

## 选择索引为 3、3、1 和 8 的元素
selected_elements = x[np.array([3, 3, 1, 8])]
print("Selected elements with integer array:", selected_elements)


## --- 布尔数组索引 ---
y = np.array([1., -1., -2., 3.])
print("\nArray for boolean indexing:", y)

## 创建一个用于负数的布尔掩码
mask = y < 0
print("Boolean mask (y < 0):", mask)

## 选择条件为 True 的元素
negative_elements = y[mask]
print("Elements where y < 0:", negative_elements)

保存文件并运行脚本:

python indexing_practice.py

你的输出应该展示整数索引和布尔索引如何从你的数组中选择特定数据。

Array for integer indexing: [10  9  8  7  6  5  4  3  2  1]
Selected elements with integer array: [7 7 9 2]

Array for boolean indexing: [ 1. -1. -2.  3.]
Boolean mask (y < 0): [False  True  True False]
Elements where y < 0: [-1. -2.]

为索引数组赋值

索引不仅用于读取数据,它在 修改 数据方面也非常强大。你可以在赋值运算符 (=) 的左侧使用任何索引方法来更改特定元素。

广播 (Broadcasting):使形状兼容

在向索引数组赋值时,NumPy 使用 广播 (broadcasting) 来使形状兼容。这是 NumPy 最强大的功能之一!

广播规则

NumPy 可以在赋值过程中自动扩展较小的数组以匹配较大的数组,遵循以下规则:

  1. 单个值赋给多个元素: 一个值可以赋给多个位置
  2. 小型数组赋给较大的选择: 只要维度兼容即可

赋值中广播的示例

## 将单个值赋给一个切片
arr = np.array([1, 2, 3, 4, 5])
arr[1:4] = 99  ## [1, 99, 99, 99, 5]

## 将数组赋给匹配的切片
arr = np.array([1, 2, 3, 4, 5])
arr[1:4] = [10, 20, 30]  ## [1, 10, 20, 30, 5]

## 使用布尔索引进行广播
arr = np.array([1, 2, 3, 4, 5])
arr[arr % 2 == 0] = -1  ## 将所有偶数替换为 -1

重要提示

  • 广播仅在形状兼容时才有效
  • 要赋的值的形状必须能够“适应”被索引的选择区域
  • 这比手动循环遍历元素要高效得多

更新你的 indexing_practice.py 文件,使用以下代码来实际体验这一点。

import numpy as np

## --- 将单个值赋给一个切片 ---
x = np.arange(10)
print("Original array:", x)

## 将值 99 赋给从索引 2 到 4 的元素
x[2:5] = 99
print("After assigning 99 to slice [2:5]:", x)


## --- 根据布尔条件赋值 ---
y = np.arange(10)
print("\nOriginal array:", y)

## 将值 -1 赋给所有偶数
y[y % 2 == 0] = -1
print("After assigning -1 to even numbers:", y)

保存文件并从终端运行它:

python indexing_practice.py

输出将显示修改前后的数组,展示此功能在数据操作方面的强大之处。

Original array: [0 1 2 3 4 5 6 7 8 9]
After assigning 99 to slice [2:5]: [ 0  1 99 99 99  5  6  7  8  9]

Original array: [0 1 2 3 4 5 6 7 8 9]
After assigning -1 to even numbers: [-1  1 -1  3 -1  5 -1  7 -1  9]

总结

在本实验中,你学习了索引 NumPy 数组的基本技术。你从一维数组中的基本单元素访问和切片开始,这与 Python 列表类似。然后,你进阶学习了索引多维数组以选择特定元素和子数组。最后,你探索了使用整数和布尔数组的高级索引,并学会了如何使用这些强大的选择方法来修改数组中的数据。这些技能对于使用 NumPy 在 Python 中进行有效的数据操作和分析至关重要。