Python NumPy 基础教程:从入门到精通
1. 引言:为什么选择 NumPy?
在 Python 的数据科学生态系统中,NumPy (Numerical Python) 是一个不可或缺的核心库。它提供了强大的 N 维数组对象,以及用于处理这些数组的各种工具。虽然 Python 内建的列表 (list) 可以存储数据,但 NumPy 数组在以下几个方面具有显著优势:
- 性能优化:NumPy 数组是同类型数据的集合,底层用 C/C++ 实现,运算速度远超 Python 列表。在处理大量数据时,性能优势尤为明显。
- 内存效率:NumPy 数组存储的是连续的内存块,数据类型统一,相比 Python 列表存储的是对象的引用,NumPy 更节省内存。
- 强大的数学功能:NumPy 提供了大量用于数组操作的数学函数,包括线性代数、傅里叶变换、随机数生成等,是科学计算的基础。
- 互操作性:NumPy 数组是许多其他科学计算库(如 SciPy, scikit-learn, Pandas, Matplotlib)的基础数据结构。
本教程将带您从 NumPy 的基础概念入手,逐步深入到高级用法,助您从入门走向精通。
2. 安装 NumPy
安装 NumPy 非常简单,通常使用 pip 工具:
bash
pip install numpy
安装完成后,在 Python 代码中导入 NumPy,习惯上使用 np 作为别名:
python
import numpy as np
3. NumPy 数组 (ndarray) 的创建
NumPy 的核心是 ndarray 对象,它是一个多维数组。
3.1 从 Python 列表创建
最常见的方法是从 Python 列表或元组创建数组。
“`python
一维数组
arr1d = np.array([1, 2, 3, 4, 5])
print(“一维数组:”, arr1d)
print(“类型:”, type(arr1d))
print(“数据类型:”, arr1d.dtype) # 默认根据元素推断类型
二维数组 (矩阵)
arr2d = np.array([[1, 2, 3], [4, 5, 6]])
print(“\n二维数组:\n”, arr2d)
指定数据类型
arr_float = np.array([1, 2, 3], dtype=np.float64)
print(“\n指定数据类型的数组:”, arr_float)
print(“指定数据类型:”, arr_float.dtype)
“`
3.2 使用内置函数创建特殊数组
NumPy 提供了一系列函数用于快速创建特定类型的数组。
np.zeros(shape, dtype): 创建全零数组。np.ones(shape, dtype): 创建全一数组。np.empty(shape, dtype): 创建未初始化数组 (值是随机的)。np.arange(start, stop, step): 创建等差数列数组 (类似 Python 的range())。np.linspace(start, stop, num): 创建指定数量的等间隔数组。np.eye(N): 创建 N x N 的单位矩阵。np.diag(v): 创建对角线为 v 的矩阵。
“`python
print(“全零数组:\n”, np.zeros((2, 3))) # 2行3列
print(“全一数组:\n”, np.ones((3, 2))) # 3行2列
print(“未初始化数组 (随机值):\n”, np.empty((2, 2)))
print(“等差数列 (0到9):\n”, np.arange(10))
print(“等差数列 (1到10, 步长2):\n”, np.arange(1, 11, 2))
print(“等间隔数组 (0到10, 共5个点):\n”, np.linspace(0, 10, 5))
print(“单位矩阵:\n”, np.eye(3)) # 3×3 单位矩阵
“`
3.3 随机数数组
numpy.random 模块提供了强大的随机数生成功能。
np.random.rand(d0, d1, ..., dn): 生成 (0, 1) 区间内的浮点数。np.random.randn(d0, d1, ..., dn): 生成标准正态分布 (均值0,方差1) 的浮点数。np.random.randint(low, high, size): 生成指定范围内的整数。
“`python
print(“随机浮点数 (0-1):\n”, np.random.rand(2, 3))
print(“标准正态分布随机数:\n”, np.random.randn(2, 2))
print(“随机整数 (0-9, 2×3矩阵):\n”, np.random.randint(0, 10, size=(2, 3)))
“`
4. 数组属性
NumPy 数组有一些重要的属性,用于描述其结构。
ndim: 数组的维度 (轴的数量)。shape: 数组的形状,一个元组,表示每个维度的大小。size: 数组中元素的总数量。dtype: 数组中元素的类型。itemsize: 数组中每个元素占用的字节数。nbytes: 整个数组占用的总字节数。
python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print("数组:\n", arr)
print("维度 (ndim):", arr.ndim)
print("形状 (shape):", arr.shape) # (行数, 列数)
print("大小 (size):", arr.size)
print("数据类型 (dtype):", arr.dtype)
print("每个元素字节数 (itemsize):", arr.itemsize)
print("总字节数 (nbytes):", arr.nbytes)
5. 数组的索引和切片
NumPy 数组的索引和切片功能非常强大,与 Python 列表类似但更灵活。
5.1 基本索引
- 一维数组:
arr[index] - 多维数组:
arr[row_index, col_index, ...]
“`python
arr1d = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(“arr1d[3]:”, arr1d[3]) # 3
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(“arr2d:\n”, arr2d)
print(“arr2d[1, 2]:”, arr2d[1, 2]) # 第二行第三列的元素 (5)
print(“arr2d[0]:”, arr2d[0]) # 第一行 [1 2 3]
“`
5.2 切片
使用 : 进行切片,语法与 Python 列表相同:start:stop:step。
“`python
arr1d = np.arange(10)
print(“arr1d[2:7]:”, arr1d[2:7]) # 从索引2到6 [2 3 4 5 6]
print(“arr1d[:5]:”, arr1d[:5]) # 前5个元素 [0 1 2 3 4]
print(“arr1d[5:]:”, arr1d[5:]) # 索引5及以后的元素 [5 6 7 8 9]
print(“arr1d[::2]:”, arr1d[::2]) # 每隔一个取一个 [0 2 4 6 8]
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(“\n二维数组切片:”)
print(“arr2d[0:2, 1:3]:\n”, arr2d[0:2, 1:3]) # 前两行,第二列到第三列
[[2 3]
[5 6]]
print(“arr2d[:, 0]:\n”, arr2d[:, 0]) # 所有行的第一列 [1 4 7]
print(“arr2d[1, :]:\n”, arr2d[1, :]) # 第二行的所有元素 [4 5 6]
print(“arr2d[-1, -1]:”, arr2d[-1, -1]) # 最后一个元素 9
“`
重要提示:NumPy 数组的切片是原数组的视图 (view),而不是副本 (copy)。修改切片会同时修改原数组。如果需要副本,请使用 .copy() 方法。
python
arr_slice = arr2d[0:2, 1:3]
arr_slice[0, 0] = 99
print("\n修改切片后原数组:\n", arr2d) # arr2d 也被修改了
5.3 布尔索引 (花式索引)
使用布尔数组来选择元素。
“`python
arr = np.array([1, 2, 3, 4, 5, 6])
mask = (arr > 3)
print(“布尔掩码:”, mask) # [False False False True True True]
print(“大于3的元素:”, arr[mask]) # [4 5 6]
arr2d = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(“\narr2d 中偶数元素:\n”, arr2d[arr2d % 2 == 0]) # [2 4 6 8]
“`
5.4 整数数组索引
使用整数数组来选择任意行或列。
“`python
arr = np.arange(10, 20).reshape(2, 5)
print(“数组:\n”, arr)
[[10 11 12 13 14]
[15 16 17 18 19]]
选择特定行和列的元素
print(“选择 (0,1), (1,3) 位置的元素:”, arr[[0, 1], [1, 3]]) # [11 18]
选择特定行
print(“选择第0行和第1行:\n”, arr[[0, 1]])
“`
6. 数组形状操作
6.1 重塑 (Reshape)
reshape() 函数可以改变数组的形状,但不会改变其数据。要求新旧形状的元素总数必须相同。
“`python
arr = np.arange(12)
print(“原数组:\n”, arr)
arr_reshaped = arr.reshape(3, 4) # 3行4列
print(“重塑为3×4:\n”, arr_reshaped)
arr_reshaped2 = arr.reshape(2, 2, 3) # 2个2×3的矩阵
print(“重塑为2x2x3:\n”, arr_reshaped2)
-1 表示自动计算该维度的大小
arr_reshaped3 = arr.reshape(3, -1)
print(“自动计算列数:\n”, arr_reshaped3)
展平数组 (一维化)
print(“展平数组:”, arr_reshaped.flatten())
print(“展平数组:”, arr_reshaped.ravel()) # ravel 也是视图
“`
6.2 拼接 (Concatenation)
使用 np.concatenate()、np.vstack() (垂直堆叠) 和 np.hstack() (水平堆叠) 拼接数组。
“`python
a = np.array([[1, 2], [3, 4]])
b = np.array([[5, 6], [7, 8]])
print(“数组 a:\n”, a)
print(“数组 b:\n”, b)
沿轴0 (垂直) 拼接
print(“垂直拼接 (axis=0):\n”, np.concatenate((a, b), axis=0))
[[1 2]
[3 4]
[5 6]
[7 8]]
沿轴1 (水平) 拼接
print(“水平拼接 (axis=1):\n”, np.concatenate((a, b), axis=1))
[[1 2 5 6]
[3 4 7 8]]
np.vstack 等价于 np.concatenate(…, axis=0)
print(“垂直堆叠 (vstack):\n”, np.vstack((a, b)))
np.hstack 等价于 np.concatenate(…, axis=1)
print(“水平堆叠 (hstack):\n”, np.hstack((a, b)))
“`
6.3 分割 (Splitting)
使用 np.split()、np.vsplit() (垂直分割) 和 np.hsplit() (水平分割) 分割数组。
“`python
arr = np.arange(16).reshape(4, 4)
print(“原数组:\n”, arr)
垂直分割成2个数组
arr_vsplit = np.vsplit(arr, 2)
print(“垂直分割结果 (2个数组):\n”, arr_vsplit[0], “\n”, arr_vsplit[1])
水平分割成4个数组
arr_hsplit = np.hsplit(arr, 4)
print(“水平分割结果 (第一个数组):\n”, arr_hsplit[0])
“`
7. 数学运算
NumPy 数组的强大之处在于其支持快速、高效的元素级 (element-wise) 运算和矩阵运算。
7.1 元素级运算
数组与标量、数组与数组之间可以直接进行加减乘除、幂运算等,这些操作会自动应用到数组的每个元素上。
“`python
a = np.array([1, 2, 3])
b = np.array([4, 5, 6])
print(“a + b:”, a + b) # [5 7 9]
print(“a * 2:”, a * 2) # [2 4 6]
print(“b – a:”, b – a) # [3 3 3]
print(“a ** 2:”, a ** 2) # [1 4 9]
print(“b / a:”, b / a) # [4. 2.5 2. ] (浮点数除法)
比较运算
print(“a > 2:”, a > 2) # [False False True]
“`
7.2 通用函数 (Universal Functions, ufuncs)
NumPy 提供了大量的通用函数 (ufuncs),它们对数组的每个元素进行操作。
python
arr = np.array([-2, -1, 0, 1, 2])
print("绝对值 (np.abs):", np.abs(arr))
print("平方根 (np.sqrt):", np.sqrt(np.array([1, 4, 9])))
print("指数 (np.exp):", np.exp(arr))
print("对数 (np.log):", np.log(np.array([1, np.e, np.e**2])))
print("正弦 (np.sin):", np.sin(np.array([0, np.pi/2, np.pi])))
7.3 聚合函数
用于计算数组的统计量。
sum(): 求和。min(): 最小值。max(): 最大值。mean(): 平均值。std(): 标准差。var(): 方差。argmin(): 最小值索引。argmax(): 最大值索引。
可以指定 axis 参数在特定轴上进行计算。
“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(“数组:\n”, arr)
print(“所有元素和:”, arr.sum())
print(“行最大值:”, arr.max(axis=1)) # [3 6]
print(“列平均值:”, arr.mean(axis=0)) # [2.5 3.5 4.5]
print(“所有元素最小值索引:”, arr.argmin()) # 0 (对应元素1)
“`
7.4 线性代数
NumPy 提供了强大的线性代数功能。
np.dot(): 点积、矩阵乘法。@运算符 (Python 3.5+): 矩阵乘法。np.linalg.inv(): 矩阵求逆。np.linalg.det(): 矩阵行列式。np.linalg.eig(): 特征值和特征向量。
“`python
A = np.array([[1, 2], [3, 4]])
B = np.array([[5, 6], [7, 8]])
print(“矩阵 A:\n”, A)
print(“矩阵 B:\n”, B)
矩阵乘法
print(“A @ B (矩阵乘法):\n”, A @ B)
或 print(“np.dot(A, B):\n”, np.dot(A, B))
矩阵求逆
inv_A = np.linalg.inv(A)
print(“A 的逆矩阵:\n”, inv_A)
print(“A @ inv_A (接近单位矩阵):\n”, A @ inv_A)
行列式
print(“A 的行列式:”, np.linalg.det(A))
“`
8. 广播 (Broadcasting)
广播是 NumPy 的一个强大功能,它描述了 NumPy 如何在算术运算期间处理具有不同形状的数组。在不需要复制数据的情况下,广播机制可以有效地执行数组运算。
广播规则:
当对两个数组进行操作时,NumPy 会逐元素比较它们的形状 (从末尾维度开始)。
1. 如果两个维度相等,则它们兼容。
2. 如果其中一个维度为 1,则另一个维度兼容。
3. 如果以上两条规则都不满足,则维度不兼容,会引发 ValueError。
形状较小的数组的维度会被“广播”到较大数组的形状。
“`python
a = np.array([1, 2, 3]) # 形状 (3,)
b = 2 # 标量
print(“a + b (标量广播):”, a + b) # [3 4 5]
A = np.array([[1, 2, 3], [4, 5, 6]]) # 形状 (2, 3)
b = np.array([10, 20, 30]) # 形状 (3,)
print(“\nA:\n”, A)
print(“b:”, b)
b 被广播到 A 的每一行
print(“A + b (维度广播):\n”, A + b)
[[11 22 33]
[14 25 36]]
另一个例子:增加一列
C = np.array([[1, 2], [3, 4]]) # 形状 (2, 2)
D = np.array([[10], [20]]) # 形状 (2, 1)
print(“\nC:\n”, C)
print(“D:\n”, D)
D 的列被广播到 C 的所有列
print(“C + D:\n”, C + D)
[[11 12]
[23 24]]
“`
9. 文件的输入/输出
NumPy 可以方便地保存和加载数组。
np.save('filename.npy', arr): 将单个数组以.npy格式保存。np.load('filename.npy'): 从.npy文件加载数组。np.savetxt('filename.txt', arr, delimiter=','): 将数组保存为文本文件。np.loadtxt('filename.txt', delimiter=','): 从文本文件加载数组。
“`python
data = np.arange(10).reshape(2, 5)
np.save(‘my_array.npy’, data)
loaded_data = np.load(‘my_array.npy’)
print(“加载的 .npy 数据:\n”, loaded_data)
np.savetxt(‘my_data.csv’, data, delimiter=’,’)
可以用 Excel 或文本编辑器打开 my_data.csv
加载 csv 文件
loaded_csv_data = np.loadtxt(‘my_data.csv’, delimiter=’,’)
print(“加载的 .csv 数据:\n”, loaded_csv_data)
“`
(注:这里的文件操作会在当前执行脚本的目录下创建文件,请注意清理。)
10. 结论
NumPy 是 Python 进行科学计算的基石。掌握其 N 维数组对象、索引切片、形状操作、强大的数学函数以及广播机制,将极大地提升您处理数值数据的效率和能力。从基础的数组创建到复杂的线性代数运算,NumPy 为数据科学家和工程师提供了不可或缺的工具。
这仅仅是 NumPy 功能的冰山一角。要成为真正的专家,建议您继续探索以下高级主题:
- 结构化数组 (Structured Arrays):存储混合数据类型的数组。
- 内存视图和复制的深层理解:避免不必要的内存开销。
- ufuncs 的更多细节:了解其工作原理和如何创建自定义 ufuncs。
- 高级线性代数模块
numpy.linalg:更多矩阵分解、求解线性方程等功能。 - 与 Pandas、Matplotlib 和 SciPy 的集成:NumPy 如何作为这些库的后端。
通过不断实践和深入学习,您将能够充分利用 NumPy 的强大功能,解决各种复杂的数值计算问题。