5·基础入门入门

NumPy 基础:数组与向量化运算

numpypython

第 5 课:NumPy 基础:数组与向量化运算

学习目标

完成本课学习后,你将能够:

  1. 理解 NumPy 的核心数据结构 ndarray,并能创建基本数组。
  2. 掌握向量化运算的概念,并利用 NumPy 进行高效的元素级数学运算。
  3. 熟练运用数组的索引、切片和形状操作来访问与修改数据。
  4. 认识并使用一些常用的 NumPy 数学与统计函数。

核心概念

1. 为什么需要 NumPy?

Python 原生的列表(list)虽然灵活,但在处理大量数值数据时效率极低。NumPy(Numerical Python)是 Python 科学计算的基础包,它提供了高性能的多维数组对象 ndarray 和用于操作这些数组的工具。在机器学习中,几乎所有数据都会被表示为数组(向量、矩阵、张量),NumPy 就是处理这些数据的“基石”。

2. ndarray:N维数组对象

ndarray 是 NumPy 的核心。它是一个由相同类型元素组成的多维网格。你可以把它想象成一个超级加强版的“列表”,但内部数据在内存中是连续存储的,并且支持高效的数学运算。

3. 向量化(Vectorization)

这是 NumPy 的魔法所在。向量化意味着用数组表达式代替显式的循环。它使得代码更简洁、更易读,并且速度快得多,因为底层运算由高度优化的 C 代码执行。

# 普通Python循环(慢)
result = []
for i in range(1000000):
    result.append(i * 2)

# NumPy向量化(快)
import numpy as np
result = np.arange(1000000) * 2  # 一行代码,速度提升数十甚至数百倍

4. 数组的形状(Shape)与维度

数组的形状是一个元组,描述了每个维度的大小。一个一维数组 (5,) 有5个元素;一个二维数组 (3, 4) 有3行4列。理解形状对于正确使用索引和广播运算至关重要。

代码示例

1. 创建数组

import numpy as np  # 约定俗成的导入方式

# 从Python列表创建
arr1 = np.array([1, 2, 3, 4, 5])
print("一维数组:", arr1)
print("类型:", type(arr1))  # <class 'numpy.ndarray'>
print("元素类型:", arr1.dtype)  # int64 (根据系统可能不同)

# 创建二维数组
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print("二维数组:\n", arr2d)
print("形状:", arr2d.shape)  # (2, 3) 表示 2行3列

# 使用内置函数快速创建
zeros = np.zeros((3, 4))  # 3行4列的全0数组
ones = np.ones((2, 2))    # 2行2列的全1数组
eye = np.eye(3)           # 3x3的单位矩阵
range_arr = np.arange(0, 10, 2)  # 类似range,但生成数组 [0, 2, 4, 6, 8]
linspace_arr = np.linspace(0, 1, 5)  # 在0到1之间均匀取5个点 [0., 0.25, 0.5, 0.75, 1.]

print("单位矩阵:\n", eye)
print("arange数组:", range_arr)
print("linspace数组:", linspace_arr)

2. 向量化运算

a = np.array([1, 2, 3, 4])
b = np.array([10, 20, 30, 40])

# 元素级运算(数组形状必须相同,或满足广播规则)
print("加法:", a + b)        # [11 22 33 44]
print("减法:", b - a)        # [9 18 27 36]
print("乘法:", a * b)        # [10 40 90 160]
print("除法:", b / a)        # [10. 10. 10. 10.]
print("乘方:", a ** 2)       # [1 4 9 16]
print("比较:", a > 2)        # [False False True True] -> 布尔数组

# 与标量运算(广播)
print("所有元素加5:", a + 5)  # [6 7 8 9]
print("所有元素乘0.5:", a * 0.5)  # [0.5 1. 1.5 2.]

# 函数也是向量化的
print("平方根:", np.sqrt(a))  # [1.         1.41421356 1.73205081 2.        ]
print("指数:", np.exp(a))     # [ 2.71828183  7.3890561  20.08553692 54.59815003]

3. 索引与切片

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

# 一维索引与切片(和Python列表几乎一样)
print("第三个元素:", arr[2])        # 2 (索引从0开始)
print("切片[2:5]:", arr[2:5])       # [2 3 4]
print("带步长的切片:", arr[1:8:2])   # [1 3 5 7]

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

print("第二行第三列元素:", matrix[1, 2])  # 6 (行索引, 列索引)
print("第一行:", matrix[0, :])           # [1 2 3]
print("第二列:", matrix[:, 1])           # [2 5 8]
print("子矩阵:\n", matrix[:2, 1:])      # 取前2行,第2列及之后 [[2 3] [5 6]]

# **重要概念:视图(View)而非复制**
slice_view = arr[3:6]  # 创建一个视图
slice_view[0] = 100    # 修改视图会影响原数组!
print("修改后的原数组:", arr)  # [  0   1   2 100   4   5   6   7   8   9]
# 如果需要副本,使用.copy()
slice_copy = arr[3:6].copy()
slice_copy[0] = 200    # 不影响原数组

4. 常用数学与统计函数

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

# 聚合函数(沿轴计算)
print("所有元素的和:", np.sum(data))          # 45
print("每列的和 (axis=0):", np.sum(data, axis=0))  # [12 15 18]
print("每行的和 (axis=1):", np.sum(data, axis=1))  # [ 6 15 24]
print("最大值:", np.max(data))                # 9
print("平均值:", np.mean(data))               # 5.0
print("标准差:", np.std(data))                # 2.58198889747...

# 通用函数
print("最大值的索引:", np.argmax(data))  # 8 (展平后的索引)
print("每列最大值的索引:", np.argmax(data, axis=0))  # [2 2 2]

实践练习

练习1:基础数组操作

要求:

  1. 创建一个包含 1 到 20 的整数的一维数组。
  2. 将该数组重塑为一个 4 行 5 列的二维数组。
  3. 打印出该数组的形状、元素总数和所有元素的数据类型。 预期输出示例:
形状: (4, 5)
元素总数: 20
数据类型: int64

练习2:向量化统计

要求: 给定学生成绩二维数组 scores = np.array([[85, 90, 78], [92, 88, 95], [76, 82, 89]]),其中每一行代表一个学生,每一列代表一门课程(语文、数学、英语)。

  1. 计算每个学生三门课程的总分。
  2. 计算每门课程所有学生的平均分。
  3. 找出成绩最高的学生及其对应的总分。 预期输出示例:
学生总分: [253 275 247]
课程平均分: [84.33333333 86.66666667 87.33333333]
最高总分: 275

练习3:索引与切片

要求: 使用 np.arange(1, 26).reshape(5, 5) 创建一个 5x5 的矩阵。

  1. 提取矩阵的中心 3x3 子矩阵。
  2. 将该子矩阵的所有元素都设置为 0。
  3. 打印修改后的整个 5x5 矩阵。 预期输出示例:
[[ 1  2  3  4  5]
 [ 6  0  0  0 10]
 [11  0  0  0 15]
 [16  0  0  0 20]
 [21 22 23 24 25]]

常见错误

  1. 索引混淆:NumPy 数组索引从 0 开始,不是 1。访问 arr[1] 是第二个元素。
  2. 形状不匹配进行运算:尝试对形状 (3,)(4,) 的数组直接相加会引发 ValueError。运算前务必检查或调整形状。
  3. 整数除法陷阱:在 Python 3 中,a / b 对整数数组得到的是浮点结果(如 np.array([5]) / 2 -> [2.5])。如果需要整数结果,用 //.astype(int) 转换。
  4. 混淆视图和复制:切片操作(如 arr[2:5])返回的是原数组的视图,修改视图会影响原数据。如果你需要独立的副本,务必使用 .copy() 方法。
  5. 忘记轴(axis)参数:聚合函数(如 sum, mean)如果不指定 axis,会计算整个数组的结果。axis=0 表示沿垂直方向(跨行)计算,得到每列的结果;axis=1 表示沿水平方向(跨列)计算,得到每行的结果。

小结

本节课我们学习了 NumPy 最基础也最重要的概念:

  • ndarray 是 NumPy 的核心,它是同质、高效的多维数组。
  • 向量化 使我们能用简洁的数组表达式替代低效的循环,是高性能计算的关键。
  • 索引与切片 用于访问和修改数组的子集,需要注意是视图操作。
  • 常用函数(如 sum, mean, max, reshape)提供了强大的数据操作和统计能力。

掌握 NumPy 数组是进入机器学习和数据科学领域必须迈出的第一步。下一课,我们将学习基于 NumPy 构建的、更专注于数据分析和处理的 Pandas 库。

练习编辑器

rust
Loading...

继续学习

完成本课后,建议继续学习下一课「Pandas 基础:DataFrame 与数据操作」 以巩固所学知识。