Python NumPy 入门:核心功能详解
引言
在当今数据爆炸的时代,数据科学、机器学习、人工智能以及科学计算领域飞速发展。Python 作为一门易学且功能强大的语言,在这些领域占据了核心地位。然而,标准的 Python 列表在处理大量数值计算时效率不高,这是因为 Python 列表可以存储不同类型的数据,且其底层实现并非为高速数值操作优化。
为了解决这个问题,NumPy (Numerical Python) 应运而生。NumPy 是 Python 生态系统中最核心的科学计算库,它提供了强大的 N 维数组对象以及用于处理这些数组的各种工具。几乎所有基于 Python 的科学计算和数据分析库,如 SciPy、Pandas、Matplotlib、Scikit-learn 等,都严重依赖于 NumPy。
本文旨在为初学者详细介绍 NumPy 的核心功能,帮助你快速掌握使用 NumPy 进行高效数值计算的基础。我们将从 NumPy 的优势讲起,深入探讨其核心数据结构 ndarray
,学习如何创建、索引、切片、操作数组,以及理解广播、通用函数等重要概念。
第一章:初识 NumPy
1.1 什么是 NumPy?
NumPy 是一个开源的 Python 库,专门用于处理大型多维数组和矩阵,并提供了一套庞大的数学函数库来操作这些数组。它的核心是 ndarray
(n-dimensional array) 对象。
与 Python 列表不同,NumPy 的 ndarray
具有以下关键特性:
- 同构性 (Homogeneous):
ndarray
中的所有元素必须是相同的数据类型。这使得存储更紧凑,操作更高效。 - 固定大小 (Fixed Size): 一旦创建,
ndarray
的大小是固定的。虽然可以通过一些操作改变形状,但元素数量通常保持不变(除非进行拼接或分割)。 - 基于 C 实现的高性能: NumPy 的许多底层操作都是用 C 或 Fortran 实现的,这使得它在处理大型数组时比纯 Python 快得多。
- 支持多维: 可以轻松创建和操作一维、二维(矩阵)、三维甚至更高维度的数组。
1.2 为什么使用 NumPy?优势何在?
理解 NumPy 的优势是学习它的关键驱动力。对比标准的 Python 列表,NumPy 的主要优势体现在:
- 性能 (Performance): 这是 NumPy 最显著的优势。
- 向量化操作 (Vectorization): NumPy 鼓励使用向量化操作,即直接对整个数组进行数学运算,而不是使用 Python 循环遍历每个元素。这些向量化操作在底层被编译好的 C 代码高效执行,大大减少了 Python 解释器的开销。例如,两个 NumPy 数组相加
a + b
比使用循环将两个 Python 列表逐元素相加快几个数量级。 - 内存效率 (Memory Efficiency):
ndarray
中的元素类型统一,且存储在连续的内存块中(通常)。这比 Python 列表更节省内存,同时也更利于 CPU 缓存的利用,进一步提升了性能。Python 列表存储的是对象的引用,这些对象可能分散在内存中,且每个元素都带有额外的类型信息和引用计数等开销。
- 向量化操作 (Vectorization): NumPy 鼓励使用向量化操作,即直接对整个数组进行数学运算,而不是使用 Python 循环遍历每个元素。这些向量化操作在底层被编译好的 C 代码高效执行,大大减少了 Python 解释器的开销。例如,两个 NumPy 数组相加
- 功能丰富 (Rich Functionality): NumPy 提供了大量的数学、统计、线性代数、傅里叶变换等函数,这些函数都经过高度优化,可以直接应用于
ndarray
。无需从零开始实现复杂的数值算法。 - 互操作性 (Interoperability): NumPy
ndarray
是事实上的标准数据交换格式,许多其他科学计算库都能轻松地与 NumPy 集成。 - 语法简洁 (Concise Syntax): NumPy 提供的许多操作(如广播)可以用非常简洁的代码实现复杂的数据操作,提高了代码的可读性和开发效率。
示例:对比 Python 列表与 NumPy 数组的加法
让我们通过一个简单的例子来直观感受性能差异。
“`python
import numpy as np
import time
使用 Python 列表
list1 = list(range(1000000))
list2 = list(range(1000000))
start_time = time.time()
result_list = [x + y for x, y in zip(list1, list2)]
end_time = time.time()
print(f”Python 列表加法耗时: {end_time – start_time:.6f} 秒”)
使用 NumPy 数组
np_array1 = np.arange(1000000)
np_array2 = np.arange(1000000)
start_time = time.time()
result_np = np_array1 + np_array2
end_time = time.time()
print(f”NumPy 数组加法耗时: {end_time – start_time:.6f} 秒”)
“`
运行这段代码,你会发现 NumPy 版本要快得多,特别是当数据量更大时,性能差距会更明显。这就是向量化和底层优化的力量。
1.3 安装 NumPy
安装 NumPy 非常简单,只需要使用 pip 包管理器:
bash
pip install numpy
1.4 导入 NumPy
按照惯例,NumPy 通常被导入并赋予别名 np
:
python
import numpy as np
在接下来的所有示例中,我们都假设已经执行了 import numpy as np
。
第二章:NumPy 的核心:ndarray 对象
ndarray
是 NumPy 的核心数据结构。它是一个用于存储同类型元素的固定大小的多维容器。数组中的元素类型由 dtype
对象指定。
2.1 ndarray 的属性
每个 ndarray
对象都有一些重要的属性,用于描述数组的结构和内容:
shape
: 一个元组,表示数组在每个维度上的大小。例如,一个 2 行 3 列的矩阵的shape
是(2, 3)
。ndim
: 一个整数,表示数组的维度数量 (number of dimensions)。例如,一个向量的ndim
是 1,一个矩阵的ndim
是 2。size
: 一个整数,表示数组中元素的总数。等于shape
中所有元素的乘积。dtype
: 一个对象,表示数组中元素的类型(如int32
,float64
等)。所有元素必须具有相同的dtype
。itemsize
: 一个整数,表示数组中每个元素占用的字节数。nbytes
: 一个整数,表示整个数组占用的总字节数。等于size * itemsize
。
示例:查看 ndarray 的属性
“`python
import numpy as np
创建一个二维数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(f”数组:\n{arr}”)
print(f”形状 (shape): {arr.shape}”)
print(f”维度数 (ndim): {arr.ndim}”)
print(f”元素总数 (size): {arr.size}”)
print(f”数据类型 (dtype): {arr.dtype}”)
print(f”单个元素字节数 (itemsize): {arr.itemsize}”)
print(f”总字节数 (nbytes): {arr.nbytes}”)
创建一个三维数组
arr_3d = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
print(f”\n数组 (3D):\n{arr_3d}”)
print(f”形状 (shape, 3D): {arr_3d.shape}”)
print(f”维度数 (ndim, 3D): {arr_3d.ndim}”)
print(f”元素总数 (size, 3D): {arr_3d.size}”)
“`
2.2 对比 Python 列表与 ndarray
特性 | Python List | NumPy ndarray |
---|---|---|
数据类型 | 异构 (可包含不同类型元素) | 同构 (所有元素类型相同) |
大小 | 可变 (动态调整) | 固定 (创建后元素数量固定) |
性能 | 较慢 (适用于通用数据存储) | 快速 (适用于数值计算) |
内存 | 较低效 (存储对象引用) | 高效 (存储同类型元素,通常连续) |
功能 | 通用序列操作 (增删改查) | 大量数学、统计、线性代数函数 |
维度支持 | 主要是一维概念,多维需嵌套列表 | 原生支持多维 |
算术运算 | 需通过循环或列表推导实现元素级运算 | 直接支持元素级运算 (向量化) |
对于需要进行大量数值计算、矩阵运算等任务,NumPy 的 ndarray
显然是更优的选择。
第三章:创建 ndarray 数组
创建数组是使用 NumPy 的第一步。NumPy 提供了多种创建数组的方法。
3.1 从 Python 列表或元组创建
使用 np.array()
函数可以将 Python 列表或元组转换为 NumPy 数组。
“`python
从列表创建
list_data = [1, 2, 3, 4, 5]
arr1 = np.array(list_data)
print(f”从列表创建: {arr1}”)
print(f”类型: {type(arr1)}”)
print(f”数据类型: {arr1.dtype}”) # 默认推断为 int64 (或 int32,取决于系统)
从嵌套列表创建多维数组
list_of_lists = [[1, 2, 3], [4, 5, 6]]
arr2 = np.array(list_of_lists)
print(f”\n从嵌套列表创建:\n{arr2}”)
print(f”形状: {arr2.shape}”)
从元组创建
tuple_data = (7, 8, 9)
arr3 = np.array(tuple_data)
print(f”\n从元组创建: {arr3}”)
指定数据类型
arr4 = np.array([1.1, 2.2, 3.3], dtype=np.float32)
print(f”\n指定 float32 类型: {arr4}”)
print(f”数据类型: {arr4.dtype}”)
arr5 = np.array([1, 2, 3], dtype=np.complex64)
print(f”指定 complex64 类型: {arr5}”)
print(f”数据类型: {arr5.dtype}”)
注意:列表中的元素必须是同构的,否则 dtype 会被推断为 object (效率低下)
arr_hetero = np.array([1, ‘hello’, 3.14])
print(f”\n包含不同类型元素的数组: {arr_hetero}”)
print(f”数据类型: {arr_hetero.dtype}”) # 输出 object
“`
3.2 创建特定内容的数组
NumPy 提供了一系列便捷函数来创建具有特定初始值的数组:
np.zeros(shape[, dtype])
: 创建一个指定形状和类型的全零数组。np.ones(shape[, dtype])
: 创建一个指定形状和类型的全一数组。np.empty(shape[, dtype])
: 创建一个指定形状和类型的数组,其元素值未初始化(可能是内存中的任意值)。np.full(shape, fill_value[, dtype])
: 创建一个指定形状和类型,并用fill_value
填充的数组。
“`python
全零数组
zeros_arr = np.zeros((2, 3)) # 2行3列
print(f”全零数组:\n{zeros_arr}”)
全一数组
ones_arr = np.ones((3, 2), dtype=np.int32) # 3行2列, int32类型
print(f”\n全一数组:\n{ones_arr}”)
空数组 (注意元素值)
empty_arr = np.empty((2, 2))
print(f”\n空数组:\n{empty_arr}”)
全特定值数组
full_arr = np.full((2, 2), 7.7)
print(f”\n全特定值数组:\n{full_arr}”)
“`
3.3 创建序列数组
np.arange([start,] stop[, step][, dtype])
: 创建一个在指定区间内的等差序列数组。类似于 Python 的range()
,但返回的是 ndarray。np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None)
: 创建一个在指定区间内,包含num
个元素的等间隔序列数组。
“`python
使用 arange
range_arr = np.arange(10) # 0到9
print(f”arange(10): {range_arr}”)
range_arr2 = np.arange(2, 10, 2) # 从2到10(不含),步长为2
print(f”arange(2, 10, 2): {range_arr2}”)
使用 linspace
linspace_arr = np.linspace(0, 1, 5) # 在0到1之间生成5个等间隔的点,包含0和1
print(f”\nlinspace(0, 1, 5): {linspace_arr}”)
linspace_arr2 = np.linspace(0, 1, 5, endpoint=False) # 不包含1
print(f”linspace(0, 1, 5, endpoint=False): {linspace_arr2}”)
“`
3.4 创建随机数组
np.random
模块提供了丰富的函数来创建包含随机数的数组。
np.random.rand(d0, d1, ..., dn)
: 创建指定形状的数组,元素值在 [0, 1) 的均匀分布中。np.random.randn(d0, d1, ..., dn)
: 创建指定形状的数组,元素值来自标准正态分布(均值为 0,方差为 1)。np.random.randint(low, high=None, size=None, dtype='int')
: 创建指定形状的整数数组,元素值在[low, high)
区间内。
“`python
均匀分布
rand_arr = np.random.rand(2, 3) # 2行3列
print(f”\nrand(2, 3):\n{rand_arr}”)
标准正态分布
randn_arr = np.random.randn(2, 3)
print(f”\nrandn(2, 3):\n{randn_arr}”)
整数随机数
randint_arr = np.random.randint(0, 10, size=(2, 3)) # 0到10(不含), 2行3列
print(f”\nrandint(0, 10, size=(2, 3)):\n{randint_arr}”)
“`
3.5 创建特殊矩阵
np.eye(N[, M, k, dtype])
: 创建一个 N x M 的单位矩阵(对角线为 1,其余为 0)。k
参数指定对角线的位置(0 为主对角线)。np.identity(n[, dtype])
: 创建一个 n x n 的方单位矩阵。
“`python
单位矩阵
eye_matrix = np.eye(3) # 3×3单位矩阵
print(f”\neye(3):\n{eye_matrix}”)
eye_matrix2 = np.eye(3, 4, k=1) # 3×4矩阵,第1条对角线为1
print(f”\neye(3, 4, k=1):\n{eye_matrix2}”)
方单位矩阵 (与 eye(n) 相同)
identity_matrix = np.identity(3)
print(f”\nidentity(3):\n{identity_matrix}”)
“`
第四章:索引和切片 (Indexing and Slicing)
NumPy 数组的索引和切片功能非常强大和灵活,它允许你访问数组的单个元素、行、列或子数组。NumPy 的索引语法与 Python 列表类似,但扩展到了多维。
4.1 一维数组的索引和切片
与 Python 列表完全一样。
“`python
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(f”原数组: {arr}”)
索引单个元素
print(f”第一个元素: {arr[0]}”) # 0
print(f”最后一个元素: {arr[-1]}”) # 9
切片
print(f”前五个元素: {arr[:5]}”) # [0 1 2 3 4]
print(f”从索引5开始到结束: {arr[5:]}”) # [5 6 7 8 9]
print(f”从索引2到索引7(不含): {arr[2:7]}”) # [2 3 4 5 6]
print(f”带步长的切片: {arr[::2]}”) # [0 2 4 6 8]
print(f”反转数组: {arr[::-1]}”) # [9 8 7 6 5 4 3 2 1 0]
“`
重要提示: NumPy 数组切片返回的是视图 (view),而不是副本 (copy)。这意味着对切片进行修改会直接影响原始数组。
“`python
arr = np.arange(10)
slice_arr = arr[5:8]
print(f”原数组: {arr}”) # [0 1 2 3 4 5 6 7 8 9]
print(f”切片: {slice_arr}”) # [5 6 7]
修改切片
slice_arr[0] = 99
print(f”修改切片后: {slice_arr}”) # [99 6 7]
print(f”原始数组的变化: {arr}”) # [0 1 2 3 4 99 6 7 8 9]
如果你需要一个副本,使用 .copy() 方法
arr_copy = arr[5:8].copy()
arr_copy[0] = 100
print(f”\n原始数组 (copy 后): {arr}”) # [0 1 2 3 4 99 6 7 8 9] (未变化)
print(f”副本的变化: {arr_copy}”) # [100 6 7]
“`
4.2 多维数组的索引和切片
多维数组的索引使用逗号 ,
分隔每个维度的索引。语法通常是 arr[dim1_index, dim2_index, ..., dimN_index]
。
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(f”二维数组:\n{arr2d}”)
索引单个元素 (第一行第二列,索引从0开始)
print(f”元素 [0, 1]: {arr2d[0, 1]}”) # 2
print(f”元素 [2, 2]: {arr2d[2, 2]}”) # 9
索引整行 (索引第一行)
print(f”第一行: {arr2d[0]}”) # [1 2 3]
也可以显式指定所有列
print(f”第一行 (显式所有列): {arr2d[0, :]}”) # [1 2 3]
索引整列 (索引第二列)
print(f”第二列: {arr2d[:, 1]}”) # [2 5 8]
切片行和列
提取前两行
print(f”前两行:\n{arr2d[:2]}”)
[[1 2 3]
[4 5 6]]
提取所有行,但只取后两列
print(f”所有行,后两列:\n{arr2d[:, 1:]}”)
[[2 3]
[5 6]
[8 9]]
提取子数组 (例如,第一行到第二行,第二列到第三列)
print(f”子数组 arr2d[0:2, 1:3]:\n{arr2d[0:2, 1:3]}”)
[[2 3]
[5 6]]
使用单个索引获取降维的子数组
arr2d[0] 返回一个一维数组 [1 2 3]
arr2d[0, :] 也返回一个一维数组 [1 2 3]
如果想保留维度,可以使用切片
print(f”第一行 (保留维度): {arr2d[0:1, :]}”)
[[1 2 3]]
可以混合使用索引和切片
提取第一行,第二列的元素 (这与 arr2d[0, 1] 相同)
print(f”元素 arr2d[0, 1:2]: {arr2d[0, 1:2]}”) # [2] (注意返回的是一个包含一个元素的一维数组)
print(f”元素 arr2d[0:1, 1:2]: {arr2d[0:1, 1:2]}”) # [[2]] (注意返回的是一个二维数组)
“`
4.3 布尔索引 (Boolean Indexing)
布尔索引(也称为掩码索引)是一种非常强大的索引方式,它使用一个与原数组形状相同(或可广播)的布尔数组来选择元素。布尔数组中为 True
的位置对应的原数组元素会被选中。
“`python
arr = np.arange(10)
print(f”原数组: {arr}”)
创建一个布尔数组 (例如,选择大于5的元素)
bool_mask = arr > 5
print(f”布尔掩码: {bool_mask}”) # [False False False False False False True True True True]
使用布尔掩码进行索引
selected_elements = arr[bool_mask]
print(f”大于5的元素: {selected_elements}”) # [6 7 8 9]
可以在一行内完成
print(f”偶数元素: {arr[arr % 2 == 0]}”) # [0 2 4 6 8]
布尔索引在多维数组中也很有效
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(f”\n二维数组:\n{arr2d}”)
选择大于5的元素
print(f”二维数组中大于5的元素: {arr2d[arr2d > 5]}”) # [6 7 8 9] (注意返回的是一个一维数组)
使用布尔索引进行赋值
arr2d[arr2d > 5] = 99
print(f”将大于5的元素赋值为99:\n{arr2d}”)
[[ 1 2 3]
[ 4 5 99]
[99 99 99]]
“`
4.4 花式索引 (Fancy Indexing)
花式索引是指定一个整数数组来一次性选择多个非连续的元素或行/列。
“`python
arr = np.arange(10) * 2 # [ 0 2 4 6 8 10 12 14 16 18]
print(f”原数组: {arr}”)
使用整数列表或数组进行索引
indices = [1, 3, 5]
print(f”使用索引列表 [1, 3, 5]: {arr[indices]}”) # [2 6 10]
indices_arr = np.array([2, 8, 0, 4])
print(f”使用索引数组 [2, 8, 0, 4]: {arr[indices_arr]}”) # [ 4 16 0 8]
花式索引在多维数组中
arr2d = np.arange(16).reshape(4, 4)
print(f”\n二维数组:\n{arr2d}”)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
选择指定的行 (例如,第0行、第2行、第1行)
rows_indices = [0, 2, 1]
print(f”选择指定行:\n{arr2d[rows_indices]}”)
[[ 0 1 2 3]
[ 8 9 10 11]
[ 4 5 6 7]]
选择指定的列 (例如,第0列、第3列、第1列)
cols_indices = [0, 3, 1]
print(f”选择指定列:\n{arr2d[:, cols_indices]}”)
[[ 0 3 1]
[ 4 7 5]
[ 8 11 9]
[12 15 13]]
结合行索引和列索引 (需要注意的是,这种方式是选择 (row_idx[i], col_idx[i]) 对应的元素)
row_indices = np.array([0, 2, 3])
col_indices = np.array([1, 3, 0])
print(f”选择指定位置的元素 (0,1), (2,3), (3,0): {arr2d[row_indices, col_indices]}”) # [ 1 11 12]
如果想要选择 (0,1), (0,3), (2,1), (2,3), (3,1), (3,3) 这样的组合,
可以使用 np.ix_() 或者通过广播的方式来实现
rows = np.array([0, 2, 3])
cols = np.array([1, 3])
使用 np.ix_ 构建一个用于索引的网格
grid = np.ix_(rows, cols)
print(f”使用 np.ix_ 选择子网格:\n{arr2d[grid]}”)
[[ 1 3]
[ 9 11]
[13 15]]
或者通过广播实现 (后面会详细讲广播)
print(f”使用广播选择子网格:\n{arr2d[rows[:, np.newaxis], cols]}”)
[[ 1 3]
[ 9 11]
[13 15]]
花式索引返回的是副本 (copy),而不是视图 (view)
arr_fancy_slice = arr2d[rows_indices]
arr_fancy_slice[0, 0] = 999
print(f”\n修改花式索引结果后:\n{arr_fancy_slice}”)
print(f”原数组 (花式索引后未变化):\n{arr2d}”)
“`
花式索引是提取数组中特定元素的强大方式,特别是当这些元素在数组中不形成一个规则的矩形切片时。
第五章:数据类型 (Data Types – dtype)
数据类型(dtype
)是 NumPy 的一个重要概念,它决定了数组中元素的类型以及在内存中占用的空间。由于 ndarray
是同构的,所有元素都共享同一个 dtype
。
5.1 常用的 NumPy 数据类型
NumPy 支持比 Python 内置类型更广泛的数值类型,以满足不同计算需求和精度要求:
- 整数类型:
int8
,int16
,int32
,int64
(带符号整数);uint8
,uint16
,uint32
,uint64
(无符号整数)。后面的数字表示位数。 - 浮点数类型:
float16
,float32
,float64
(标准的双精度浮点数),float128
。 - 复数类型:
complex64
,complex128
(标准的双精度复数),complex256
。 - 布尔类型:
bool
(存储 True/False)。 - 字符串类型:
string_
或S<n>
(固定长度字节串),unicode_
或U<n>
(固定长度 Unicode 串)。通常在处理文本时使用,但效率低于数值类型。 - 对象类型:
object
。用于存储 Python 对象。当数组包含异构数据时,NumPy 会尝试使用object
类型,但这会丧失 NumPy 的大部分性能优势,应尽量避免。
5.2 指定数据类型
在创建数组时,可以使用 dtype
参数指定数据类型:
“`python
arr_int = np.array([1, 2, 3], dtype=np.int16)
print(f”int16 数组: {arr_int}, dtype: {arr_int.dtype}”)
arr_float = np.array([1.0, 2.5, 3.1], dtype=np.float32)
print(f”float32 数组: {arr_float}, dtype: {arr_float.dtype}”)
arr_bool = np.array([0, 1, 0, 1], dtype=np.bool_)
print(f”bool 数组: {arr_bool}, dtype: {arr_bool.dtype}”) # [False True False True]
arr_str = np.array([‘hello’, ‘world’], dtype=np.string_)
print(f”string_ 数组: {arr_str}, dtype: {arr_str.dtype}”) # [b’hello’ b’world’]
arr_unicode = np.array([‘你好’, ‘世界’], dtype=np.unicode_)
print(f”unicode_ 数组: {arr_unicode}, dtype: {arr_unicode.dtype}”) # [‘你好’ ‘世界’]
“`
5.3 转换数据类型
可以使用 astype()
方法将数组的数据类型转换为另一种。该方法会返回一个新的数组(副本)。
“`python
arr = np.array([1.5, 2.6, 3.1, 4.9])
print(f”原数组: {arr}, dtype: {arr.dtype}”) # float64
转换为整数
arr_int_converted = arr.astype(np.int64)
print(f”转换为 int64: {arr_int_converted}, dtype: {arr_int_converted.dtype}”) # [1 2 3 4] (小数部分被截断)
转换为布尔值 (非零为 True)
arr_bool_converted = arr.astype(np.bool_)
print(f”转换为 bool: {arr_bool_converted}, dtype: {arr_bool_converted.dtype}”) # [True True True True]
整数转换为浮点数
arr_int = np.array([1, 2, 3])
arr_float_converted = arr_int.astype(np.float64)
print(f”整数转换为 float64: {arr_float_converted}, dtype: {arr_float_converted.dtype}”) # [1. 2. 3.]
浮点数转换为字符串
arr_str_converted = arr.astype(np.string_)
print(f”浮点数转换为 string_: {arr_str_converted}, dtype: {arr_str_converted.dtype}”)
“`
选择合适的数据类型对于内存使用和计算性能都很重要。例如,如果知道所有数值都在 0-255 之间,使用 uint8
将比 int64
节省大量内存。
第六章:通用函数 (Universal Functions – ufuncs)
通用函数(ufuncs)是 NumPy 中对 ndarray
进行快速元素级操作的核心。它们是封装了针对 ndarray
元素进行操作的函数,通常执行的是一对一的映射(输入一个或多个数组,输出一个或多个数组)。
ufuncs 的关键特性是它们能够对整个数组进行操作,而无需编写显式的 Python 循环。这是 NumPy 实现向量化和高性能的关键。
6.1 什么是 ufunc?
ufuncs 是在编译好的 C 代码中实现的,能够接收 ndarray
作为输入,并对数组中的每个元素或多个数组中对应位置的元素执行相同的操作。
例如,对一个 NumPy 数组取平方根:
python
arr = np.array([1, 4, 9, 16])
sqrt_arr = np.sqrt(arr) # 直接对数组进行操作
print(f"原数组: {arr}")
print(f"平方根: {sqrt_arr}") # [1. 2. 3. 4.]
对比 Python 列表:
“`python
import math
list_data = [1, 4, 9, 16]
sqrt_list = [math.sqrt(x) for x in list_data] # 必须使用循环或列表推导
“`
np.sqrt()
就是一个 ufunc。其他常见的 ufuncs 包括各种数学函数、比较函数、逻辑函数等。
6.2 常见的 ufuncs
NumPy 提供了大量的 ufuncs,可以大致分为几类:
- 算术运算 (Arithmetic Operations):
+
,-
,*
,/
,**
,%
(或者对应的函数np.add
,np.subtract
,np.multiply
,np.divide
,np.power
,np.mod
等)np.floor()
,np.ceil()
,np.round()
,np.abs()
,np.sign()
等。
- 比较运算 (Comparison Operations):
>
,<
,>=
,<=
,==
,!=
(或者对应的函数np.greater
,np.less
,np.greater_equal
,np.less_equal
,np.equal
,np.not_equal
). 这些操作返回布尔数组。
- 三角函数 (Trigonometric Functions):
np.sin()
,np.cos()
,np.tan()
,np.arcsin()
,np.arccos()
,np.arctan()
等。 - 指数和对数函数 (Exponential and Logarithmic Functions):
np.exp()
,np.log()
,np.log10()
,np.log2()
等。 - 逻辑运算 (Logical Operations):
np.logical_and()
,np.logical_or()
,np.logical_not()
,np.where()
等。
示例:ufuncs 的使用
“`python
arr1 = np.array([1, 2, 3, 4])
arr2 = np.array([5, 6, 7, 8])
算术运算
print(f”arr1 + arr2: {arr1 + arr2}”) # [6 8 10 12]
print(f”arr1 * arr2: {arr1 * arr2}”) # [ 5 12 21 32]
print(f”np.power(arr1, 2): {np.power(arr1, 2)}”) # [ 1 4 9 16]
比较运算 (返回布尔数组)
print(f”arr1 > 2: {arr1 > 2}”) # [False False True True]
print(f”arr1 == arr2: {arr1 == arr2}”) # [False False False False]
三角函数
angles = np.array([0, np.pi/2, np.pi])
print(f”\n角度 (弧度): {angles}”)
print(f”sin(angles): {np.sin(angles)}”) # [0.000e+00 1.000e+00 1.225e-16] (注意浮点数精度问题)
指数和对数
exp_arr = np.exp(arr1)
print(f”\nexp(arr1): {exp_arr}”) # [ 2.71828183 7.3890561 20.08553692 54.59815003]
逻辑运算结合布尔索引
arr = np.arange(-5, 5) # [-5 -4 -3 -2 -1 0 1 2 3 4]
print(f”\n原数组: {arr}”)
选择大于0且小于3的元素
print(f”大于0且小于3的元素: {arr[np.logical_and(arr > 0, arr < 3)]}”) # [1 2]
或者更简洁地使用 & 运算符 (NumPy 已经重载了逻辑运算符)
print(f”大于0且小于3的元素 (使用 &): {arr[(arr > 0) & (arr < 3)]}”) # [1 2]
注意: 不要使用 Python 内置的 and
, or
, not
,它们不适用于 NumPy 数组的元素级逻辑运算。
np.where() 条件选择
np.where(condition, x, y) – 当 condition 为 True 时选择 x 中的元素,否则选择 y 中的元素
print(f”np.where(arr > 0, ‘positive’, ‘non-positive’): {np.where(arr > 0, ‘positive’, ‘non-positive’)}”)
[‘non-positive’ ‘non-positive’ … ‘non-positive’ ‘positive’ … ‘positive’]
可以用于根据条件替换元素
arr2 = np.arange(10)
result = np.where(arr2 % 2 == 0, arr2 * 10, arr2 + 100)
print(f”根据条件替换元素: {result}”)
[ 0 101 20 103 40 105 60 107 80 109]
“`
ufuncs 是 NumPy 向量化编程风格的核心。通过使用 ufuncs,你可以避免编写缓慢的 Python 循环,让代码更简洁、更高效。
第七章:广播 (Broadcasting)
广播是 NumPy 中处理具有不同形状的数组之间算术运算的一套规则。在执行算术运算(如加、减、乘、除)时,如果两个数组的形状不完全相同,NumPy 会尝试通过广播机制来使它们的形状“兼容”,以便进行元素级运算。
广播使得在数组和标量之间,或者在不同维度的数组之间进行操作成为可能,而无需显式地复制数据,这大大提高了效率。
7.1 广播的规则
当 NumPy 对两个数组进行操作时,它会比较它们的形状(从最后一个维度开始向前比较)。如果满足以下条件,两个数组就是“可广播”的:
- 两个维度相等。
- 其中一个维度为 1。
- 其中一个数组的维度较少,可以认为其在前面添加了大小为 1 的新维度,直到两个数组的维度数相等。
如果以上条件都不满足,会抛出 ValueError: operands could not be broadcast together with shapes ...
错误。
7.2 广播示例
-
标量与数组的广播:
一个标量可以被广播到任意形状的数组上。“`python
arr = np.arange(5) # [0 1 2 3 4]
print(f”原数组: {arr}”)
print(f”数组 + 5: {arr + 5}”) # [ 5 6 7 8 9]标量 5 被广播成形状为 (5,) 的数组 [5 5 5 5 5],然后进行元素级加法
“`
-
一维数组与二维数组的广播:
形状为(N,)
的一维数组可以广播到形状为(M, N)
的二维数组上。“`python
arr2d = np.arange(6).reshape(2, 3)[[0 1 2]
[3 4 5]]
arr1d = np.array([10, 20, 30]) # 形状 (3,)
print(f”二维数组:\n{arr2d}”)
print(f”一维数组: {arr1d}”)print(f”\narr2d + arr1d:\n{arr2d + arr1d}”)
输出:
[[10 21 32]
[13 24 35]]
解释: arr1d (shape (3,)) 被广播到 arr2d (shape (2, 3)) 的每一行。
广播过程想象为 arr1d 变成了形状 (2, 3) 的数组
[[10 20 30]
[10 20 30]]
然后进行元素级加法。
``
(M,)
注意:如果一维数组的形状是,二维数组的形状是
(M, N),则需要对一维数组进行形状变换或者使用
np.newaxis使其形状变为
(M, 1)才能与二维数组
(M, N)` 按列广播。“`python
arr2d = np.arange(6).reshape(2, 3) # shape (2, 3)[[0 1 2]
[3 4 5]]
arr1d_col = np.array([[10], [20]]) # shape (2, 1)
print(f”\narr2d + arr1d_col (shape (2,1)):\n{arr2d + arr1d_col}”)
输出:
[[10 11 12]
[23 24 25]]
解释: arr1d_col (shape (2, 1)) 被广播到 arr2d (shape (2, 3)) 的每一列。
广播过程想象为 arr1d_col 变成了形状 (2, 3) 的数组
[[10 10 10]
[20 20 20]]
然后进行元素级加法。
使用 np.newaxis 实现列广播
arr1d_row_needs_transform = np.array([10, 20]) # shape (2,)
如果想让它按列广播,需要变成 shape (2, 1)
print(f”\narr2d + arr1d_row_needs_transform[:, np.newaxis]:\n{arr2d + arr1d_row_needs_transform[:, np.newaxis]}”)
输出与 arr2d + arr1d_col 相同
“`
-
两个二维数组的广播:
“`python
arr_A = np.ones((2, 3)) # shape (2, 3)[[1. 1. 1.]
[1. 1. 1.]]
arr_B = np.arange(3) # shape (3,)
[0 1 2]
arr_B 可以广播到 arr_A 的每一行 (如上面的一维/二维例子)
arr_C = np.arange(2).reshape(2, 1) # shape (2, 1)
[[0]
[1]]
arr_C 可以广播到 arr_A 的每一列 (如上面的列广播例子)
print(f”\narr_A (2,3) + arr_B (3,):\n{arr_A + arr_B}”)
print(f”\narr_A (2,3) + arr_C (2,1):\n{arr_A + arr_C}”)arr_B (3,) 与 arr_C (2,1) 可以广播吗?
比较形状: (3,) vs (2, 1)
从右向左比较:
维度 1: 3 vs 1 -> 满足规则 2 (其中一个为 1)
维度 0: 没有对应维度 vs 2 -> arr_B 增加维度 1 -> (1, 3) vs (2, 1)
比较形状: (1, 3) vs (2, 1)
从右向左比较:
维度 1: 3 vs 1 -> 满足规则 2
维度 0: 1 vs 2 -> 满足规则 2
可广播! 最终广播后的形状取两者维度上的最大值: max(1, 2) x max(3, 1) = (2, 3)
print(f”\narr_B (3,) 与 arr_C (2,1) 的广播:\n{arr_B + arr_C}”)
输出:
[[0 1 2] + [[0] 广播为 [[0 0 0] + [[0 0 0] = [[0 1 2]
[0 1 2]] [1]] [1 1 1]] [1 1 1]] [1 2 3]]
[[0 1 2]
[1 2 3]]
“`
理解广播规则对于高效使用 NumPy 至关重要,它可以帮助你避免不必要的数据复制和形状操作。
第八章:数组形状操作 (Array Shape Manipulation)
NumPy 提供了多种函数来改变数组的形状、合并数组或分割数组。
8.1 改变形状
reshape(shape)
: 返回一个具有新形状的数组,但数据不变。新形状必须与原数组的元素总数兼容。可以使用 -1 让 NumPy 自动计算维度大小。ravel()
: 返回一个扁平化(一维化)的数组的视图。flatten()
: 返回一个扁平化(一维化)的数组的副本。
“`python
arr = np.arange(12) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
print(f”原数组: {arr}”)
改变形状为 3×4 矩阵
reshaped_arr = arr.reshape(3, 4)
print(f”\nreshape(3, 4):\n{reshaped_arr}”)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
使用 -1 自动计算维度
reshaped_arr2 = arr.reshape(2, -1) # 2行,列数自动计算 (12 / 2 = 6)
print(f”\nreshape(2, -1):\n{reshaped_arr2}”)
[[ 0 1 2 3 4 5]
[ 6 7 8 9 10 11]]
扁平化
ravelled_arr = reshaped_arr.ravel()
print(f”\nravel(): {ravelled_arr}”) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
flattened_arr = reshaped_arr.flatten()
print(f”flatten(): {flattened_arr}”) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
注意 ravel() 和 flatten() 的区别
ravelled_arr[0] = 999
print(f”修改 ravelled 数组后,原数组 reshaped_arr 的变化:\n{reshaped_arr}”) # [[999 1 2 3] …] (原数组也变了)
flattened_arr[0] = 888
print(f”修改 flattened 数组后,原数组 reshaped_arr 的变化:\n{reshaped_arr}”) # [[999 1 2 3] …] (原数组未变)
“`
8.2 转置
T
属性 或transpose()
方法: 返回数组的转置。对于二维数组,就是交换行和列。对于更高维度,transpose
可以接受参数指定轴的顺序。
“`python
arr2d = np.arange(6).reshape(2, 3)
print(f”原数组:\n{arr2d}”)
[[0 1 2]
[3 4 5]]
转置
transposed_arr = arr2d.T
print(f”\n转置:\n{transposed_arr}”)
[[0 3]
[1 4]
[2 5]]
转置是视图
transposed_arr[0, 0] = 99
print(f”修改转置后原数组:\n{arr2d}”)
[[99 1 2]
[ 3 4 5]]
“`
8.3 添加/移除维度
np.newaxis
: 使用np.newaxis
可以增加数组的维度,通常用于广播。squeeze()
: 从数组的形状中移除大小为 1 的维度。
“`python
arr = np.array([1, 2, 3]) # shape (3,)
print(f”原数组: {arr}, shape: {arr.shape}”)
在前面添加一个维度 (变成行向量)
row_vector = arr[np.newaxis, :] # 或者 arr[None, :]
print(f”添加维度 (行向量): {row_vector}, shape: {row_vector.shape}”) # [[1 2 3]], shape (1, 3)
在后面添加一个维度 (变成列向量)
col_vector = arr[:, np.newaxis] # 或者 arr[:, None]
print(f”添加维度 (列向量): {col_vector}, shape: {col_vector.shape}”) # [[1]\n [2]\n [3]], shape (3, 1)
squeeze
arr_with_extra_dim = np.array([[[1, 2, 3]]]) # shape (1, 1, 3)
print(f”\n带额外维度的数组: {arr_with_extra_dim}, shape: {arr_with_extra_dim.shape}”)
squeezed_arr = arr_with_extra_dim.squeeze()
print(f”squeeze() 后: {squeezed_arr}, shape: {squeezed_arr.shape}”) # [1 2 3], shape (3,)
“`
8.4 合并数组
np.concatenate((arr1, arr2, ...), axis=0)
: 沿指定轴连接一系列数组。np.vstack((arr1, arr2, ...))
: 垂直堆叠(按行堆叠)数组。等价于np.concatenate(..., axis=0)
,要求数组列数相同。np.hstack((arr1, arr2, ...))
: 水平堆叠(按列堆叠)数组。等价于np.concatenate(..., axis=1)
,要求数组行数相同。
“`python
arr1 = np.array([[1, 2], [3, 4]]) # shape (2, 2)
arr2 = np.array([[5, 6]]) # shape (1, 2)
垂直合并 (axis=0)
concat_v = np.concatenate((arr1, arr2), axis=0)
print(f”\n垂直合并 (concatenate axis=0):\n{concat_v}”)
[[1 2]
[3 4]
[5 6]]
vstack_result = np.vstack((arr1, arr2))
print(f”垂直堆叠 (vstack):\n{vstack_result}”) # 与上面相同
arr3 = np.array([[7], [8]]) # shape (2, 1)
水平合并 (axis=1)
concat_h = np.concatenate((arr1, arr3), axis=1)
print(f”\n水平合并 (concatenate axis=1):\n{concat_h}”)
[[1 2 7]
[3 4 8]]
hstack_result = np.hstack((arr1, arr3))
print(f”水平堆叠 (hstack):\n{hstack_result}”) # 与上面相同
注意: 合并的数组在指定轴上的大小可以不同,但在其他轴上的大小必须相同(除非通过广播兼容)
例如,arr1 (2,2) 与 arr2 (1,2) 可以在 axis=0 合并 (列数都是2)
arr1 (2,2) 与 arr3 (2,1) 可以在 axis=1 合并 (行数都是2)
arr1 (2,2) 与 arr2 (1,2) 不能在 axis=1 合并 (行数不同 2 vs 1)
np.concatenate((arr1, arr2), axis=1) 会报错
“`
8.5 分割数组
np.split(arr, indices_or_sections, axis=0)
: 沿指定轴将数组分割成多个子数组。indices_or_sections
可以是一个整数(表示等分数)或一个索引列表(表示分割发生的位置)。np.vsplit(arr, indices_or_sections)
: 垂直分割(按行分割)。等价于np.split(..., axis=0)
。np.hsplit(arr, indices_or_sections)
: 水平分割(按列分割)。等价于np.split(..., axis=1)
。
“`python
arr = np.arange(16).reshape(4, 4)
print(f”原数组:\n{arr}”)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
垂直分割成2等份
vsplit_arrs_equal = np.vsplit(arr, 2)
print(f”\n垂直分割成2等份:\n{vsplit_arrs_equal}”)
[array([[0, 1, 2, 3], [4, 5, 6, 7]]),
array([[ 8, 9, 10, 11], [12, 13, 14, 15]])]
水平分割,指定分割位置 (在列索引1和列索引3之前分割)
hsplit_arrs_at_indices = np.hsplit(arr, [1, 3])
print(f”\n水平分割,指定分割位置 [1, 3]:\n{hsplit_arrs_at_indices}”)
[array([[ 0], [ 4], [ 8], [12]]), # 0:1
array([[ 1, 2], [ 5, 6], [ 9, 10], [13, 14]]), # 1:3
array([[ 3], [ 7], [11], [15]])] # 3:end
split 函数的通用用法
split_arrs_v = np.split(arr, [1, 3], axis=0) # 在行索引1和3之前分割
print(f”\nsplit (axis=0) 指定分割位置 [1, 3]:\n{split_arrs_v}”)
[array([[0, 1, 2, 3]]),
array([[ 4, 5, 6, 7], [ 8, 9, 10, 11]]),
array([[12, 13, 14, 15]])]
“`
第九章:统计计算和排序 (Statistical Computation and Sorting)
NumPy 提供了许多用于计算数组统计量和对数组进行排序的方法。
9.1 统计方法
许多统计计算可以通过数组的方法或 NumPy 的顶级函数来实现。这些方法通常有一个 axis
参数,用于指定计算发生的轴。
sum()
: 计算数组元素的总和。mean()
: 计算数组元素的平均值。std()
: 计算数组元素的标准差。var()
: 计算数组元素的方差。min()
,max()
: 计算数组元素的最小值和最大值。argmin()
,argmax()
: 计算数组中最小值和最大值对应的索引。cumsum()
: 计算元素的累积和。cumprod()
: 计算元素的累积积。
“`python
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(f”原数组:\n{arr}”)
整个数组的统计量
print(f”总和: {arr.sum()}”) # 21
print(f”平均值: {arr.mean()}”) # 3.5
print(f”最小值: {arr.min()}”) # 1
print(f”最大值: {arr.max()}”) # 6
沿轴计算统计量
axis=0: 沿着列方向操作,结果减少一行,形状变成 (1, num_cols) 或 (num_cols,)
print(f”\n沿 axis=0 (列) 求和: {arr.sum(axis=0)}”) # [5 7 9] (1+4, 2+5, 3+6)
print(f”沿 axis=0 (列) 求平均值: {arr.mean(axis=0)}”) # [2.5 3.5 4.5]
print(f”沿 axis=0 (列) 求最小值: {arr.min(axis=0)}”) # [1 2 3]
print(f”沿 axis=0 (列) 求最大值: {arr.max(axis=0)}”) # [4 5 6]
axis=1: 沿着行方向操作,结果减少一列,形状变成 (num_rows, 1) 或 (num_rows,)
print(f”\n沿 axis=1 (行) 求和: {arr.sum(axis=1)}”) # [ 6 15] (1+2+3, 4+5+6)
print(f”沿 axis=1 (行) 求平均值: {arr.mean(axis=1)}”) # [2. 5.]
print(f”沿 axis=1 (行) 求最小值: {arr.min(axis=1)}”) # [1 4]
print(f”沿 axis=1 (行) 求最大值: {arr.max(axis=1)}”) # [3 6]
argmin/argmax
print(f”\n整个数组最小值的索引 (展平后): {arr.argmin()}”) # 0 (元素1的索引)
print(f”整个数组最大值的索引 (展平后): {arr.argmax()}”) # 5 (元素6的索引)
print(f”沿 axis=0 (列) 最小值的索引: {arr.argmin(axis=0)}”) # [0 0 0] (每列最小元素都在第0行)
print(f”沿 axis=1 (行) 最大值的索引: {arr.argmax(axis=1)}”) # [2 2] (每行最大元素都在第2列)
累积计算
arr1d = np.array([1, 2, 3, 4])
print(f”\n一维数组: {arr1d}”)
print(f”累积和: {arr1d.cumsum()}”) # [ 1 3 6 10]
print(f”累积积: {arr1d.cumprod()}”) # [ 1 2 6 24]
“`
理解 axis
参数对于在多维数组上进行聚合操作至关重要。axis=0
表示跨行操作(结果是每列的聚合),axis=1
表示跨列操作(结果是每行的聚合)。
9.2 排序
sort()
方法: 对数组的副本进行排序,返回一个新数组。可以指定axis
参数沿特定轴进行排序。argsort()
方法: 返回一个包含原数组按排序顺序排列的索引的数组。
“`python
arr = np.array([3, 1, 4, 1, 5, 9, 2, 6])
print(f”原数组: {arr}”)
排序 (返回副本)
sorted_arr = arr.sort() # 注意:数组的 sort() 方法会就地修改数组并返回 None
如果想获取排序后的副本,使用 np.sort() 函数
sorted_arr_copy = np.sort(arr)
print(f”排序后的副本: {sorted_arr_copy}”) # [1 1 2 3 4 5 6 9]
print(f”原数组 (就地排序): {arr}”) # [1 1 2 3 4 5 6 9] (原数组已经被 sort() 方法修改)
argsort
arr_orig = np.array([3, 1, 4, 1, 5, 9, 2, 6])
sorted_indices = arr_orig.argsort()
print(f”排序后的索引: {sorted_indices}”) # [1 3 6 0 2 4 7 5] (元素1在原数组索引1和3,元素2在索引6,以此类推)
print(f”使用 argsort 结果索引原数组: {arr_orig[sorted_indices]}”) # [1 1 2 3 4 5 6 9]
多维数组排序
arr2d = np.array([[0, 5, 2],
[3, 1, 4]])
print(f”\n二维数组:\n{arr2d}”)
沿 axis=0 (列) 排序
sorted_arr2d_axis0 = np.sort(arr2d, axis=0)
print(f”沿 axis=0 排序:\n{sorted_arr2d_axis0}”)
[[0 1 2]
[3 5 4]] # (第一列 [0,3] -> [0,3],第二列 [5,1] -> [1,5],第三列 [2,4] -> [2,4])
沿 axis=1 (行) 排序
sorted_arr2d_axis1 = np.sort(arr2d, axis=1)
print(f”沿 axis=1 排序:\n{sorted_arr2d_axis1}”)
[[0 2 5] # (第一行 [0,5,2] -> [0,2,5])
[1 3 4]] # (第二行 [3,1,4] -> [1,3,4])
“`
第十章:线性代数基础 (Basic Linear Algebra)
NumPy 的 linalg
子模块提供了进行基本线性代数运算的功能。
10.1 点积 (Dot Product) 和矩阵乘法 (Matrix Multiplication)
np.dot(a, b)
: 计算两个数组的点积。如果输入是二维数组,执行矩阵乘法。@
运算符 (Python 3.5+): 专用于矩阵乘法。
“`python
arr1 = np.array([1, 2])
arr2 = np.array([3, 4])
向量点积
dot_product_vec = np.dot(arr1, arr2)
print(f”向量点积: {dot_product_vec}”) # 13 + 24 = 11
matrix1 = np.array([[1, 2], [3, 4]]) # 2×2 矩阵
matrix2 = np.array([[5, 6], [7, 8]]) # 2×2 矩阵
矩阵乘法
matrix_product_dot = np.dot(matrix1, matrix2)
print(f”\n矩阵乘法 (np.dot):\n{matrix_product_dot}”)
[[15 + 27, 16 + 28],
[35 + 47, 36 + 48]]
[[19 22]
[43 50]]
使用 @ 运算符
matrix_product_at = matrix1 @ matrix2
print(f”矩阵乘法 (@ 运算符):\n{matrix_product_at}”) # 与上面相同
矩阵与向量相乘
vec = np.array([1, 0]) # 1×2 向量
matrix_vec_product = matrix1 @ vec # (2×2) @ (2,) -> 结果形状 (2,)
print(f”\n矩阵与向量相乘:\n{matrix_vec_product}”) # [11 + 20, 31 + 40] = [1, 3]
“`
10.2 常用线性代数函数
np.linalg.inv(a)
: 计算矩阵的逆。np.linalg.det(a)
: 计算矩阵的行列式。np.linalg.eig(a)
: 计算方阵的特征值和特征向量。np.linalg.solve(a, b)
: 求解线性方程组 $Ax = b$,其中 A 是方阵。
“`python
matrix = np.array([[1, 2], [3, 4]])
计算逆矩阵
try:
inv_matrix = np.linalg.inv(matrix)
print(f”矩阵的逆:\n{inv_matrix}”)
# 验证: 矩阵 * 逆矩阵 约等于 单位矩阵
print(f”矩阵 * 逆矩阵:\n{matrix @ inv_matrix}”) # 应接近 [[1. 0.], [0. 1.]]
except np.linalg.LinAlgError:
print(“矩阵不可逆”)
计算行列式
det_matrix = np.linalg.det(matrix)
print(f”\n矩阵的行列式: {det_matrix}”) # 14 – 23 = -2.0
求解线性方程组 Ax = b, 例如 1x + 2y = 5, 3x + 4y = 6
A = np.array([[1, 2], [3, 4]])
b = np.array([5, 6])
x = np.linalg.solve(A, b)
print(f”\n方程组 Ax=b 的解 x: {x}”) # [x, y] = [-4. 4.5]
验证: A @ x = [[1(-4) + 2(4.5)], [3(-4) + 4(4.5)]] = [[-4 + 9], [-12 + 18]] = [[5], [6]]
print(f”验证 A @ x:\n{A @ x}”)
“`
第十一章:文件 I/O (File Input/Output)
NumPy 提供了方便的函数来保存和加载 NumPy 数组到文件。
11.1 保存和加载二进制文件 (.npy)
NumPy 特有的 .npy
格式是一种存储数组的二进制格式,它保留了数组的形状、dtype 等信息,并且读写速度非常快。
np.save('filename.npy', arr)
: 将单个数组保存到.npy
文件。np.load('filename.npy')
: 从.npy
文件加载数组。
“`python
arr = np.arange(100).reshape(10, 10)
保存数组
np.save(‘my_array.npy’, arr)
print(“数组已保存到 my_array.npy”)
加载数组
loaded_arr = np.load(‘my_array.npy’)
print(f”\n从 my_array.npy 加载的数组:\n{loaded_arr}”)
print(f”加载的数组形状: {loaded_arr.shape}”)
print(f”加载的数组数据类型: {loaded_arr.dtype}”)
删除文件 (可选)
import os
os.remove(‘my_array.npy’)
“`
11.2 保存和加载文本文件
np.savetxt('filename.txt', arr[, fmt, delimiter])
: 将数组保存到文本文件,通常是 CSV 格式。fmt
参数指定输出格式(例如'%.2f'
表示保留两位小数),delimiter
指定分隔符。np.loadtxt('filename.txt'[, dtype, delimiter])
: 从文本文件加载数据到数组。
“`python
arr = np.array([[1.1, 2.2], [3.3, 4.4]])
保存为文本文件 (CSV)
np.savetxt(‘my_array.txt’, arr, fmt=’%.2f’, delimiter=’,’)
print(“\n数组已保存到 my_array.txt (CSV 格式)”)
加载文本文件
loaded_arr_txt = np.loadtxt(‘my_array.txt’, delimiter=’,’)
print(f”从 my_array.txt 加载的数组:\n{loaded_arr_txt}”)
删除文件 (可选)
os.remove(‘my_array.txt’)
“`
.npy
格式是 NumPy 推荐的数组存储方式,因为它效率高且能完整保留数组信息。文本格式(如 CSV)可读性好,方便与其他工具或软件交换数据。
结论
NumPy 是 Python 进行科学计算和数据分析的基石。通过本文,我们详细探讨了 NumPy 的核心功能,包括:
- 认识
ndarray
对象及其重要属性。 - 掌握多种创建数组的方法,从列表、序列到随机数和特殊矩阵。
- 学习灵活强大的数组索引、切片、布尔索引和花式索引技术。
- 理解数据类型
dtype
的作用和转换。 - 利用通用函数 (ufuncs) 实现高效的元素级数组操作。
- 理解广播机制如何简化不同形状数组的运算。
- 进行数组形状操作,如改变形状、转置、添加/移除维度、合并和分割。
- 执行基本的统计计算和数组排序。
- 进行基础的线性代数运算。
- 学会将数组保存和加载到文件。
掌握这些核心功能,你就已经具备了使用 NumPy 进行高效数值计算的基础能力。NumPy 的强大之处远不止于此,它还有信号处理、图像处理等更高级的功能,这些通常是在 SciPy 等基于 NumPy 的库中实现或扩展。
NumPy 的向量化和广播机制是其高性能的关键。在编写代码时,尽量利用这些特性,避免使用低效的 Python 循环。
现在,你可以开始尝试在实际问题中运用 NumPy。实践是最好的学习方式。继续探索 NumPy 文档,并将其与其他库(如 Pandas 用于数据处理,Matplotlib 用于可视化)结合使用,你将在数据科学和数值计算的道路上走得更远。
祝你 NumPy 学习愉快!
这篇文章包含了对 NumPy 核心功能的详细解释和代码示例,字数应已达到或接近 3000 字的要求。结构上分为引言、核心概念(ndarray)、创建、索引、dtype、ufuncs、广播、形状操作、统计排序、线性代数基础以及文件I/O,最后是结论,基本覆盖了入门所需的所有重要知识点。