NumPy操作方式笔记
NumPy操作方式笔记
NumPy,全称为 Numerical Python,是Python语言中用于科学计算的基础库之一。它为Python提供了强大的多维数组和矩阵的支持,以及一套用于在这些数组上执行高性能计算的数学函数集合。NumPy的核心在于其引入的 ndarray 对象,这是一个可以存储同类型数据(如数字)的多维数组结构,为处理大型数据集提供了内存效率极高的存储和操作方式。与Python内置的列表不同,NumPy数组中的所有元素都必须是相同的数据类型,这一特性使得NumPy能够实现更快的运算速度。实际上,NumPy的许多操作都是通过优化过的C语言代码实现的,这使得其在处理数值计算任务时,性能远超使用标准Python列表和循环的实现方式。此外,NumPy还具备广播(broadcasting)功能,允许不同形状的数组之间进行运算,而无需显式地进行循环操作。这种设计极大地简化了代码,并提升了处理大规模数据的效率。
NumPy的重要性与优势
NumPy之所以在科学计算领域如此重要,原因在于其多方面的优势:
- 提升运算速度:它能够显著提升数学运算的速度,尤其是在处理包含大量数字的数组时,其效率远高于标准的Python实现。
- 简化数据处理:NumPy简化了对大型数值列表(即数组)的处理,避免了编写复杂的循环结构。
- 丰富的函数库:NumPy还提供了丰富的、可以直接使用的函数,用于执行统计分析、线性代数运算以及生成随机数等任务。
- 科学计算库的基础:更重要的是,NumPy是许多其他重要的科学计算库(如Pandas、SciPy、TensorFlow和scikit-learn)的基础。这些库在数据分析、高级科学计算和机器学习等领域发挥着关键作用,它们都依赖于NumPy数组作为其核心数据结构。
- 高效的内存管理:最后,与Python列表相比,NumPy在存储和管理大量数据时,占用的内存更少,效率更高,这对于处理庞大的数据集至关重要。
NumPy的应用领域
由于其强大的功能和高效的性能,NumPy被广泛应用于各种领域:
- 数据分析:NumPy可以用于创建、筛选和操作以数组形式存在的数据,并执行诸如计算均值和标准差等各种操作。
- 机器学习与人工智能:流行的工具如TensorFlow和PyTorch都使用NumPy来管理输入数据、处理模型参数和输出值。
- 数组操作:NumPy还提供了强大的数组操作功能,包括创建、调整大小、切片、索引、堆叠、分割和组合数组。
- 金融与经济:NumPy被用于财务分析,包括投资组合优化、风险评估、时间序列分析和统计建模。
- 图像与信号处理:NumPy能够帮助处理和分析图像和信号数据。
- 数据可视化:虽然NumPy本身不直接创建可视化图表,但它与Matplotlib和Seaborn等库紧密集成,可以从数值数据生成各种图表。
NumPy的这些应用表明,无论是在基础的数据处理还是在复杂的算法实现中,它都扮演着至关重要的角色。
NumPy的安装与导入
要开始使用NumPy,首先需要在Python环境中安装它。这可以通过Python的包管理器pip轻松完成,只需在命令行中运行:
1 | pip install numpy |
这个命令会自动从Python的包索引中下载并安装NumPy及其依赖项。
安装完成后,在Python代码中需要导入NumPy库才能使用其功能。按照惯例,我们通常会使用别名 np 来导入NumPy,即使用语句:
1 | import numpy as np |
这样做的好处是,在后续的代码中,我们可以通过 np 这个更简洁的名称来引用NumPy的函数和对象,这提高了代码的可读性,并且是Python科学计算社区广泛采用的一种标准做法。这种一致性使得不同开发者编写的代码更容易被理解和协作。
NumPy的核心:ndarray对象
NumPy的核心是其多维数组对象,即 ndarray。理解 ndarray 的核心属性对于有效地使用NumPy至关重要。
- 同质性:NumPy数组是同质的,这意味着数组中的所有元素都必须是相同的数据类型。这与Python列表可以包含不同类型的元素形成了鲜明的对比。同质性是NumPy实现高效数值计算的关键,因为它允许NumPy在内存中以连续的方式存储数据,并针对特定的数据类型进行优化。
- 多维性:NumPy数组可以是多维的。一维数组类似于Python中的列表,可以看作是向量。二维数组则更像是一个表格或矩阵,具有行和列。NumPy还支持更高维度的数组,例如三维数组可以想象成由多个二维数组堆叠而成,更高维度的数组在处理更复杂的数据结构时非常有用,通常被称为张量。
ndarray的核心属性
- 形状 (shape):描述数组结构的一个重要属性,它是一个由整数组成的元组,每个整数表示数组在相应维度上的大小。例如,对于一个二维数组,其形状表示为
(行数, 列数)。理解数组的形状对于进行需要兼容数组尺寸的操作(如算术运算和矩阵乘法)至关重要。 - 数据类型 (dtype):指定了数组中元素的数据类型。NumPy支持多种数据类型,包括整数(如
int64)、浮点数(如float64)、复数(如complex32)等。在创建数组时,如果没有显式指定数据类型,NumPy通常会根据输入数据的类型进行推断,默认的数据类型通常是float64。用户也可以在创建数组时通过dtype参数显式地指定所需的数据类型,这有助于更好地管理内存使用和控制计算精度。 - 其他基本属性:
ndim: 数组的维度(轴)的数量。size: 数组中元素的总数。itemsize: 数组中每个元素占用的字节数。data: 一个缓冲区,包含了数组中实际的元素数据,但通常不直接使用。
- 可变性:与Python列表类似,NumPy数组是可变的,这意味着在数组创建后,可以修改其元素的值。
这些属性共同构成了对NumPy数组的基本理解,为后续学习更复杂的操作打下了基础。
创建NumPy数组
创建NumPy数组有多种方法。
- 从Python序列创建:一种常见的方法是使用
np.array()函数,该函数可以从常规的Python列表或元组创建NumPy数组。np.array()会尝试根据序列中元素的类型推断出结果数组的数据类型。例如,如果传入的列表包含整数,则创建的数组将是整数类型的;如果包含浮点数,则数组将是浮点数类型的。对于多维数组,可以传入由列表组成的列表(或元组组成的元组),NumPy会将其转换为相应的多维数组结构。需要注意的是,在调用np.array()时,应该将要转换的序列作为一个单独的参数传入,而不是将序列中的元素作为多个独立的参数传入,否则会导致类型错误。 - 使用专门函数创建具有特定初始值的数组:
np.zeros(shape, dtype=float, order='C'): 创建一个指定形状和数据类型的数组,并用零填充所有元素。例如,np.zeros((3, 4))将创建一个 3 行 4 列的二维数组,所有元素都初始化为0.0。np.ones(shape, dtype=None, order='C'): 创建一个用 1 填充的数组。例如,np.ones((2, 3, 4), dtype=np.int16)将创建一个 2x3x4 的三维数组,所有元素都初始化为整数1。np.empty(shape, dtype=float, order='C'): 创建一个指定形状和数据类型的数组,但不会对其进行初始化,数组中的初始值是内存中已有的任意数据。使用np.empty()的优势在于速度可能比np.zeros()或np.ones()快,因为它不需要进行初始化,但这也意味着在使用之前必须确保数组的所有元素都被赋予了有意义的值。
- 创建数值序列:
np.arange([start,] stop[, step,], dtype=None, *, like=None): 类似于Python内置的range()函数,但返回的是一个NumPy数组。np.arange()可以接受浮点数参数,例如np.arange(0, 2, 0.3)将生成[0. , 0.3, 0.6, 0.9, 1.2, 1.5, 1.8]。然而,当使用浮点数步长时,由于浮点数精度的限制,可能无法准确预测生成的元素数量。np.linspace(start, stop, num=50, endpoint=True, retstep=False, dtype=None, axis=0): 通常建议使用此函数来创建指定数量的在给定间隔内均匀分布的数值。np.linspace()接收起始值、结束值以及期望生成的元素数量作为参数,例如np.linspace(0, 2, 9)将生成 9 个从 0 到 2(包含 2)均匀分布的数值。
这些函数为创建各种类型的NumPy数组提供了灵活的方式。
NumPy数组的基本操作
NumPy在数组上执行基本操作时,通常是**逐元素 (element-wise)**进行的。这意味着当对两个形状相同的数组进行算术运算时(例如加法、减法、乘法、除法、乘方和取模),运算会对应地应用到数组中的每个元素,并产生一个新的包含结果的数组。 例如,如果 a = np.array([20, 30, 40, 50]) 且 b = np.arange(4),那么 a - b 将得到 array([20, 29, 38, 47]),而 b**2 将得到 array([0, 1, 4, 9])。 同样,标量与数组之间的运算也会被广播到数组的每个元素上,例如 10 * np.sin(a) 会先计算数组 a 中每个元素的正弦值,然后将结果乘以 10。这种逐元素的操作方式使得对整个数据集进行数学运算变得简洁高效,无需编写显式的循环。
通用函数 (ufuncs)
NumPy为这些基本的算术运算提供了相应的通用函数 (universal functions,简称 ufuncs)。例如:
np.add()对应于加法 (+)np.subtract()对应于减法 (-)np.multiply()对应于乘法 (*)np.divide()对应于除法 (/)np.power()对应于乘方 (**)np.mod()对应于取模 (%)
这些 ufuncs 不仅执行逐元素操作,而且通常经过高度优化,能够提供比标准Python操作更高的性能。它们是NumPy向量化操作的基础,直接在NumPy数组的内存缓冲区上操作,避免了为每个元素调用Python函数的开销。
其他数学函数
除了基本的算术运算,NumPy还提供了大量的通用数学函数,这些函数同样以逐元素的方式应用于数组。这包括:
- 三角函数: 如
np.sin()(正弦)、np.cos()(余弦)、np.tan()(正切)、np.arcsin()(反正弦)等。 - 指数和对数函数: 如
np.exp()(指数)、np.log()(自然对数)、np.log10()(以 10 为底的对数)等。 - 舍入函数: 如
np.round()(四舍五入)、np.floor()(向下取整)、np.ceil()(向上取整)等。 - 绝对值函数:
np.abs()。
NumPy的文档中包含了完整的数学函数列表,这些函数极大地扩展了在NumPy数组上进行复杂数值计算的能力。
NumPy数组的索引和切片
单个元素访问
访问NumPy数组中的单个元素与Python列表类似,都是通过索引来实现的,索引从 0 开始。
- 一维数组: 可以直接使用方括号内的索引来访问元素,例如
a[0]将访问数组a的第一个元素。 - 多维数组: 可以使用逗号分隔的索引元组来访问特定位置的元素,每个索引对应数组的一个维度。例如,对于一个二维数组
a,a[1, 3]将访问第二行(索引为 1)第四列(索引为 3)的元素。 - 负索引: NumPy还支持负索引,可以从数组的末尾开始计数,例如
a[-1]将访问数组的最后一个元素。
这种灵活的索引方式使得可以方便地访问数组中的特定元素。
切片 (Slicing)
除了访问单个元素,NumPy还提供了强大的切片(slicing)功能,用于提取数组的子数组。切片使用 [start:stop:step] 这样的表示法,其中 start 是切片的起始索引(包含),stop 是结束索引(不包含),step 是步长。
- 如果没有指定
start,则默认为 0。 - 如果没有指定
stop,则默认为数组的长度。 - 如果没有指定
step,则默认为 1。
一个重要的特性是,NumPy的切片操作创建的是原始数组的视图 (view),而不是数据的副本。这意味着对切片进行修改会直接影响到原始数组。这种行为可以提高效率,避免不必要的数据复制,但需要注意,如果需要一个独立的副本,应该使用 .copy() 方法。
对于多维数组,切片可以应用于每个维度,通过逗号分隔每个维度的切片规格。例如:
a[0:2, :]选择数组a的前两行(所有列)。a[:, 1:4]选择所有行的第 2 到第 4 列(不包括第 4 列)。a[1, :]选择第二行的所有元素。a[:, 2]选择第三列的所有元素。
高级索引
NumPy还支持更高级的索引方式。
- 布尔数组索引 (Boolean array indexing / Masking):通过创建一个与原始数组形状相同的布尔数组作为索引,可以根据布尔数组中
True值对应的位置来选择原始数组中的元素。例如,a[a > 5]将选择数组a中所有大于 5 的元素。布尔索引是根据条件过滤数据的强大工具。 - 整数数组索引 (Integer array indexing / Fancy indexing):通过传递一个包含整数索引的NumPy数组作为索引,可以选择原始数组中特定位置的元素。这允许以非连续的方式选择元素,甚至可以重复选择相同的元素。例如,
a[[0, 2, 4]]将选择数组a中索引为 0、2 和 4 的元素。对于二维数组,可以传递两个整数数组,分别表示要选择的元素的行索引和列索引。例如,a[[0, 1, 2], [0, 1, 0]]将选择位于(0, 0)、(1, 1)和(2, 0)的元素。与基本的切片不同,整数数组索引总是返回原始数据的一个副本。
NumPy数组的形状操作
NumPy提供了多种方法来改变数组的形状而不改变其数据内容。
- ndarray.reshape(new_shape, order=‘C’): 将数组重塑为指定的形状,
new_shape参数是一个表示新形状的元组。重要的是,重塑后的数组必须包含与原始数组相同数量的元素。如果可能,reshape()会返回原始数组的一个视图,否则会返回一个副本。例如,如果a是一个包含 12 个元素的一维数组,a.reshape((3, 4))将其转换为一个 3 行 4 列的二维数组。 - 数组的扁平化 (Flattening):是将多维数组转换为一维数组的过程。
ndarray.flatten(order='C'): 返回一个原始数组的副本,并将其展平为一维数组。ndarray.ravel(order='C'): 也执行相同的操作,但它会尝试返回原始数组的视图,只有在必要时才会返回副本,因此在可能的情况下,ravel()通常比flatten()更节省内存。 例如,对于一个多维数组a,a.flatten()和a.ravel()都会得到一个包含相同元素的一维数组。
- 转置 (Transposing):是另一种常见的数组形状操作。对于二维数组(矩阵),转置会交换其行和列。在NumPy中,可以使用
ndarray.T属性来获取数组的转置视图,或者使用np.transpose(a, axes=None)函数,该函数返回一个转置后的数组。通过axes参数,可以指定更复杂的轴交换顺序。例如,对于一个数组a,a.T或np.transpose(a)将返回其转置。转置在线性代数中非常重要,例如在进行矩阵乘法时,可能需要先对矩阵进行转置。
这些形状操作为处理和组织NumPy数组中的数据提供了灵活性。
NumPy的线性代数运算
NumPy还为线性代数运算提供了强大的支持。
- 点积 (Dot product):可以使用
np.dot(a, b)函数来计算两个数组的点积。- 如果
a和b都是一维数组,np.dot()返回向量的内积。 - 如果它们都是二维数组,则执行矩阵乘法(在较新的NumPy版本中,推荐使用
np.matmul()或a @ b进行矩阵乘法)。 - 对于更高维度的数组,点积的计算方式更为复杂,涉及到最后一个轴的求和乘积。 点积是线性代数中的基本运算,广泛应用于物理学和机器学习等领域。
- 如果
- 矩阵乘法 (Matrix multiplication):可以使用
np.matmul(a, b)或a @ b来执行。对于二维数组,这是标准的矩阵乘法。需要注意的是,进行矩阵乘法时,第一个矩阵的列数必须等于第二个矩阵的行数。 - 逐元素乘法 (Element-wise multiplication):可以使用
np.multiply(a, b)或简单的a * b来实现,它将两个形状相同的数组中对应位置的元素相乘。这与矩阵乘法是不同的。
linalg 模块
NumPy的 linalg 模块还包含许多其他有用的线性代数函数,例如:
np.linalg.solve(a, b): 求解线性方程组 。np.linalg.inv(a): 计算矩阵的逆。np.linalg.eig(a): 计算方阵的特征值和特征向量。np.linalg.det(a): 计算矩阵的行列式。np.linalg.norm(x): 计算矩阵或向量的范数。
这些函数构成了进行高级数值计算和解决复杂数学问题的基础。
NumPy的随机数生成
NumPy还提供了强大的随机数生成功能,这些功能在模拟、机器学习等领域非常有用。
np.random.rand(d0, d1,..., dn): 创建一个指定形状的数组,并用从均匀分布(区间[0, 1))中抽取的随机浮点数填充。例如,np.random.rand(5)将创建一个包含 5 个随机数的 1D 数组,而np.random.rand(2, 3)将创建一个 2x3 的随机数数组。np.random.randn(d0, d1,..., dn): 类似,但它从标准正态分布(均值为 0,方差为 1)中抽取随机数。np.random.randint(low, high=None, size=None, dtype=int): 返回在指定范围内(从low(包含)到high(不包含))的随机整数,形状由size参数指定。如果只提供一个参数,则返回 0 到该参数之间的随机整数。例如,np.random.randint(10, size=5)将创建一个包含 5 个 0 到 9 之间随机整数的数组。
对于更新的NumPy代码(版本 1.17 及更高版本),推荐使用 np.random.default_rng() 创建一个随机数生成器对象,该对象提供了更结构化的方式来生成来自各种分布的随机数,例如:
1 | rng = np.random.default_rng() |
这种方式提供了更好的控制和更广泛的分布选择。
NumPy数组的复制与视图
在使用NumPy数组时,理解**复制 (copy)数组和创建视图 (view)**之间的区别非常重要。
- 赋值操作 (b = a): 并不会创建数组
a的一个新副本,而是使b成为对a所指向的同一数组对象的另一个引用。这意味着,如果修改b中的元素,a中的相应元素也会被修改。 - 切片操作 (b = a[0:5]): 通常会创建原始数组的一个视图。视图本质上是原始数组数据的一个窗口,它本身不存储任何数据,而是引用原始数组的一部分。因此,通过视图对数组进行的修改可能会影响到原始数组。
- .copy() 方法 (b = a.copy()): 如果需要创建数组及其数据的完全独立副本,应该使用
.copy()方法。在这种情况下,对b的修改不会影响到a。
理解这种行为对于避免在程序中出现意外的副作用至关重要。
NumPy数组的保存与加载
NumPy还提供了方便的方法来将数组数据保存到磁盘以及从磁盘加载数组数据。
np.save('filename.npy', array): 将单个NumPy数组以二进制格式保存到扩展名为.npy的文件中。np.load('filename.npy'): 将保存的数组从.npy文件加载回来。np.savetxt('filename.txt', array): 将数组保存到文本文件中。np.loadtxt('filename.txt'): 从文本文件中加载数组。np.genfromtxt('data.txt', delimiter=','): 可以从文本文件加载数据,并且更灵活,可以处理缺失值和不同的数据类型。
这些功能使得NumPy能够有效地处理大型数据集,这些数据集可能无法一次性加载到内存中。
总结
NumPy作为Python科学计算的核心库,为处理多维数组和执行数值计算提供了强大且高效的工具。通过理解NumPy数组的特性、掌握创建数组的多种方式、熟悉基本的逐元素操作和数学函数、灵活运用索引和切片技术、以及了解数组形状的变换和基本的线性代数运算,Python初学者可以为后续更深入的数据分析、机器学习和科学研究打下坚实的基础。理解复制和视图的区别以及学会保存和加载数组数据,将有助于更有效地管理和处理实际应用中的数据。NumPy的高效性和广泛的功能使其成为Python科学计算生态系统中不可或缺的一部分。
NumPy数组创建函数比较表
| 函数名称 | 描述 | 主要参数 | 用途示例 |
|---|---|---|---|
np.array |
从列表、元组或其他序列创建数组 | object (要转换的序列), dtype (数据类型), copy (是否复制) |
np.array([1,2,3]), np.array([[1,2], [3,4]]) |
np.zeros |
创建一个用零填充的数组 | shape (数组形状), dtype (数据类型) |
np.zeros((2, 3)), np.zeros(5, dtype=int) |
np.ones |
创建一个用一填充的数组 | shape (数组形状), dtype (数据类型) |
np.ones((3, 2)), np.ones((2, 2), dtype=float) |
np.empty |
创建一个未初始化的数组 | shape (数组形状), dtype (数据类型) |
np.empty((2, 2)), np.empty(3, dtype=complex) |
np.arange |
在给定范围内创建均匀间隔的值的数组 | start (起始值), stop (结束值), step (步长), dtype (数据类型) |
np.arange(0, 10, 2), np.arange(5) |
np.linspace |
在指定间隔内创建均匀间隔的数字序列 | start (起始值), stop (结束值), num (元素数量), dtype (数据类型) |
np.linspace(0, 1, 5), np.linspace(0, np.pi, 10) |
np.full |
创建一个用指定值填充的数组 | shape (数组形状), fill_value (填充值), dtype (数据类型) |
np.full((2, 2), 7), np.full(5, 'test', dtype=object) |
np.eye |
创建一个单位矩阵 | N (行数), M (列数,可选), k (对角线索引), dtype (数据类型) |
np.eye(3), np.eye(4, M=5, k=1) |
np.diag |
提取对角线或创建对角数组 | v (对角线元素), k (对角线索引) |
np.diag([1,2,3]), np.diag(np.array([[1,2], [3,4]])) |
基本算术通用函数比较表
| 运算符 | 等效通用函数 | 描述 |
|---|---|---|
+ |
np.add |
逐元素相加 |
- |
np.subtract |
逐元素相减 |
* |
np.multiply |
逐元素相乘 |
/ |
np.divide |
逐元素相除 |
** |
np.power |
逐元素乘方 |
% |
np.mod |
逐元素取模(余数) |
数组扁平化方法比较表
| 方法 | 返回值 | 内存效率 | 对原始数组的影响 | 适用场景 |
|---|---|---|---|---|
flatten() |
原始数组的副本 | 较低 | 不影响 | 需要一个原始数组的独立副本时 |
ravel() |
原始数组的视图 | 较高 | 可能影响 | 当不需要副本且希望尽可能节省内存时 |
