NumPy 入门指南:快速了解与使用
导言:为什么需要 NumPy?
在 Python 的世界里,处理数值数据是家常便饭。无论是进行科学计算、数据分析、机器学习,还是图像处理,高效地操作大量的数字是核心需求。Python 标准库中的列表(list)非常灵活,可以存储不同类型的数据,但在处理大型同质数值数据集时,它们存在明显的性能瓶颈:
- 速度慢: Python 列表是对象的集合,每个对象都需要单独存储和管理。进行数值运算时,需要通过 Python 的循环逐个处理元素,这比编译型语言(如 C 或 Fortran)慢得多。
- 内存效率低: 每个元素不仅存储数据本身,还需要存储类型信息和引用计数等额外信息,导致内存占用较高。
- 功能受限: Python 列表没有内置的对多维数组进行数学运算的直接支持(例如矩阵乘法)。
为了解决这些问题,NumPy(Numerical Python) 应运而生。NumPy 是 Python 生态系统中用于数值计算的基础库,它提供了一个高性能的多维数组对象 ndarray
(n-dimensional array),以及大量用于处理这些数组的函数。可以说,没有 NumPy,就没有今天 Python 在科学计算和数据分析领域的统治地位。
NumPy 是许多著名 Python 库(如 Pandas、SciPy、Matplotlib、scikit-learn、TensorFlow、PyTorch 等)的基础,它们都使用 NumPy 数组作为底层数据结构。学习 NumPy 不仅能让你更高效地进行数值计算,也是深入学习其他科学计算库的必经之路。
本文将带你快速了解 NumPy 的核心概念和常用操作,帮助你迈出 NumPy 的第一步。
1. 安装 NumPy
安装 NumPy 非常简单。如果你已经安装了 Python 和包管理器 pip
,只需要在终端或命令行中运行:
bash
pip install numpy
如果你使用的是 Anaconda 或 Miniconda,NumPy 通常已经预装好了。如果没有,可以通过 conda 进行安装:
bash
conda install numpy
安装完成后,你可以在 Python 解释器中导入 NumPy 来验证是否成功:
python
import numpy as np
print(np.__version__) # 打印 NumPy 版本号
我们通常约定俗成地使用 np
作为 NumPy 的别名。
2. NumPy 的核心:ndarray
对象
NumPy 的核心是 ndarray
对象。ndarray
是一个由相同类型的元素组成的多维数组。与 Python 列表不同,ndarray
中的所有元素必须是相同的数据类型(homogeneous data type),这使得 NumPy 可以高效地存储和操作数据。
ndarray
数组具有以下几个重要的属性:
ndim
: 数组的维度(轴数)。例如,一个向量的维度是 1,一个矩阵的维度是 2。shape
: 一个元组,表示数组在每个维度上的大小。例如,一个 2×3 的矩阵的 shape 是(2, 3)
。size
: 数组中元素的总数,等于 shape 中各元素的乘积。dtype
: 数组中元素的数据类型。NumPy 支持多种数据类型,如int64
(64位整数),float64
(64位浮点数),bool
(布尔值),complex128
(128位复数) 等。itemsize
: 数组中每个元素占用的字节数。例如,float64
的 itemsize 是 8。nbytes
: 整个数组占用的总字节数,等于size * itemsize
。
理解这些属性对于有效地使用 NumPy 数组至关重要。
3. 创建 NumPy 数组
创建 ndarray
有多种方式,最常用的是通过 Python 列表或元组:
3.1 从 Python 列表或元组创建
使用 np.array()
函数可以将 Python 的列表或元组转换为 NumPy 数组。
“`python
import numpy as np
创建一维数组 (向量)
list1 = [1, 2, 3, 4, 5]
arr1d = np.array(list1)
print(“一维数组:”, arr1d)
print(“维度:”, arr1d.ndim)
print(“形状:”, arr1d.shape)
print(“数据类型:”, arr1d.dtype)
print(“-” * 20)
创建二维数组 (矩阵)
list2d = [[1, 2, 3], [4, 5, 6]]
arr2d = np.array(list2d)
print(“二维数组:\n”, arr2d)
print(“维度:”, arr2d.ndim)
print(“形状:”, arr2d.shape)
print(“数据类型:”, arr2d.dtype) # 如果元素都是整数,默认为int64
print(“-” * 20)
创建三维数组
list3d = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
arr3d = np.array(list3d)
print(“三维数组:\n”, arr3d)
print(“维度:”, arr3d.ndim)
print(“形状:”, arr3d.shape)
print(“数据类型:”, arr3d.dtype)
print(“-” * 20)
指定数据类型
arr_float = np.array([1, 2, 3], dtype=np.float64)
print(“指定 float64 类型的数组:”, arr_float)
print(“数据类型:”, arr_float.dtype)
print(“-” * 20)
强制转换数据类型
arr_int = arr_float.astype(np.int32)
print(“强制转换为 int32 的数组:”, arr_int)
print(“数据类型:”, arr_int.dtype)
“`
注意: np.array()
会尝试推断最佳的数据类型。如果输入列表中混合了整数和浮点数,NumPy 会自动选择可以容纳所有数据的类型(通常是浮点数)。如果需要特定的数据类型,可以使用 dtype
参数。
3.2 创建特定内容的数组
NumPy 提供了一些函数来快速创建具有特定初始值的数组:
np.zeros(shape, dtype)
: 创建指定形状和数据类型,元素全为 0 的数组。np.ones(shape, dtype)
: 创建指定形状和数据类型,元素全为 1 的数组。np.full(shape, fill_value, dtype)
: 创建指定形状和数据类型,元素全部为fill_value
的数组。np.empty(shape, dtype)
: 创建指定形状和数据类型,元素值是未初始化的(随机值)。这通常比zeros
或ones
稍快,但要注意数组内容的不确定性。
“`python
全零数组
zeros_arr = np.zeros((2, 3)) # 2行3列
print(“全零数组:\n”, zeros_arr)
print(“-” * 20)
全一数组
ones_arr = np.ones((4, 2), dtype=np.int16) # 4行2列,int16类型
print(“全一数组:\n”, ones_arr)
print(“-” * 20)
全指定值数组
full_arr = np.full((3, 3), 7.5) # 3×3,元素全为7.5
print(“全指定值数组:\n”, full_arr)
print(“-” * 20)
未初始化数组
empty_arr = np.empty((2, 2))
print(“未初始化数组:\n”, empty_arr) # 值不确定
print(“-” * 20)
“`
3.3 创建等差或等比数列
np.arange(start, stop, step)
: 类似于 Python 的range()
,返回一个数组。注意,stop
值不包含在结果中。np.linspace(start, stop, num)
: 在start
和stop
之间均匀地生成num
个点(包含start
和stop
)。
“`python
等差数列 (类似 range)
arange_arr = np.arange(0, 10, 2) # 从0开始,步长为2,到10之前
print(“arange 数组:”, arange_arr)
print(“-” * 20)
等差数列 (指定点数)
linspace_arr = np.linspace(0, 1, 5) # 在0到1之间均匀生成5个点
print(“linspace 数组:”, linspace_arr)
print(“-” * 20)
“`
3.4 创建单位矩阵或对角矩阵
np.eye(N)
: 创建一个 N阶单位矩阵。np.diag(v)
: 创建一个对角线元素为 v 的对角矩阵。
“`python
单位矩阵
eye_matrix = np.eye(3) # 3×3 单位矩阵
print(“单位矩阵:\n”, eye_matrix)
print(“-” * 20)
对角矩阵
diag_matrix = np.diag([1, 2, 3, 4]) # 对角线为 [1, 2, 3, 4]
print(“对角矩阵:\n”, diag_matrix)
print(“-” * 20)
“`
4. 数组的索引与切片
NumPy 数组的索引和切片功能非常强大,类似于 Python 列表,但也扩展到了多维。
4.1 一维数组的索引与切片
与 Python 列表完全相同。
“`python
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
索引
print(“第一个元素:”, arr[0])
print(“最后一个元素:”, arr[-1])
print(“-” * 20)
切片
print(“前三个元素:”, arr[:3]) # [0 1 2]
print(“从索引2到5的元素:”, arr[2:6]) # [2 3 4 5]
print(“从索引5开始到结尾:”, arr[5:]) # [5 6 7 8 9]
print(“带步长的切片 (隔一个取一个):”, arr[::2]) # [0 2 4 6 8]
print(“反转数组:”, arr[::-1]) # [9 8 7 6 5 4 3 2 1 0]
print(“-” * 20)
“`
4.2 多维数组的索引与切片
多维数组的索引使用逗号分隔的索引元组。
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
索引单个元素: arr[行索引, 列索引]
print(“第一行第二列的元素:”, arr2d[0, 1]) # 2
print(“第二行第三列的元素:”, arr2d[1, 2]) # 6
print(“最后一行最后一列的元素:”, arr2d[-1, -1]) # 9
print(“-” * 20)
切片操作
获取第一行
print(“获取第一行:”, arr2d[0, :]) # 或者 arr2d[0]
print(“-” * 20)
获取第二列
print(“获取第二列:”, arr2d[:, 1])
print(“-” * 20)
获取前两行
print(“获取前两行:\n”, arr2d[:2, :]) # 或者 arr2d[:2]
print(“-” * 20)
获取前两行和后两列
print(“获取前两行后两列:\n”, arr2d[:2, 1:])
结果是 [[2, 3],
[5, 6]]
print(“-” * 20)
获取特定行 (例如,第一行和第三行) – 使用整数数组索引
print(“获取第一行和第三行:\n”, arr2d[[0, 2], :])
结果是 [[1, 2, 3],
[7, 8, 9]]
print(“-” * 20)
获取特定元素 (例如,(0,0), (1,1), (2,2) – 对角线)
print(“获取对角线元素:”, arr2d[[0, 1, 2], [0, 1, 2]]) # 使用两个索引数组,它们必须形状相同
结果是 [1 5 9]
print(“-” * 20)
获取特定元素 (例如,(0,1), (1,2) )
print(“获取 (0,1) 和 (1,2) 元素:”, arr2d[[0, 1], [1, 2]])
结果是 [2 6]
print(“-” * 20)
“`
4.3 布尔索引 (Boolean Indexing)
布尔索引是 NumPy 中非常强大的功能,可以根据条件选择数组中的元素。当你对一个数组应用一个布尔数组(与原数组形状相同,元素为 True 或 False)时,结果将是一个一维数组,包含原数组中布尔数组对应位置为 True 的所有元素。
“`python
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
创建一个布尔数组 (例如,筛选出大于5的元素)
mask = arr > 5 # [False False False False False False True True True True]
print(“布尔掩码:”, mask)
使用布尔数组进行索引
print(“大于5的元素:”, arr[mask]) # [6 7 8 9]
通常更简洁的写法是直接将条件作为索引
print(“大于5的元素 (直接写法):”, arr[arr > 5]) # [6 7 8 9]
print(“-” * 20)
多维数组的布尔索引
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
筛选出所有偶数
print(“所有偶数:”, arr2d[arr2d % 2 == 0]) # [2 4 6 8]
print(“-” * 20)
筛选出大于3且小于8的元素
print(“大于3且小于8的元素:”, arr2d[(arr2d > 3) & (arr2d < 8)]) # 注意使用 & (and), | (or), ~ (not)
结果是 [4 5 6 7]
“`
重要提示: NumPy 切片返回的是原数组的视图(view),而不是副本(copy)。这意味着如果你修改了切片,原数组也会被修改。如果你需要一个独立的副本,应该使用 .copy()
方法。
“`python
arr = np.arange(5) # [0 1 2 3 4]
slice_arr = arr[:3] # 切片,是视图
print(“原始数组:”, arr)
print(“切片视图:”, slice_arr)
修改切片视图
slice_arr[0] = 99
print(“修改切片视图后:”)
print(“原始数组:”, arr) # 原始数组也被修改了! [99 1 2 3 4]
print(“切片视图:”, slice_arr) # [99 1 2]
print(“-” * 20)
如果想要副本
arr = np.arange(5) # [0 1 2 3 4]
copy_arr = arr[:3].copy() # 获取副本
print(“原始数组:”, arr)
print(“副本:”, copy_arr)
修改副本
copy_arr[0] = 88
print(“修改副本后:”)
print(“原始数组:”, arr) # 原始数组不受影响 [0 1 2 3 4]
print(“副本:”, copy_arr) # [88 1 2]
“`
5. 数组的基本运算
NumPy 数组的一大优势在于其高效的元素级运算和广播(Broadcasting)机制。
5.1 元素级运算 (Element-wise Operations)
NumPy 的算术运算符(+, -, , /, *)默认执行元素级运算。这意味着两个形状相同的数组进行运算时,对应位置的元素会进行相应的运算。
“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
加法
print(“加法:”, arr1 + arr2) # [5 7 9]
减法
print(“减法:”, arr1 – arr2) # [-3 -3 -3]
乘法 (不是矩阵乘法,是元素乘法)
print(“乘法:”, arr1 * arr2) # [4 10 18]
除法
print(“除法:”, arr2 / arr1) # [4. 2.5 2. ]
幂运算
print(“幂运算:”, arr1 ** 2) # [1 4 9]
print(“-” * 20)
对多维数组同样适用
arr_a = np.array([[1, 2], [3, 4]])
arr_b = np.array([[5, 6], [7, 8]])
print(“多维数组加法:\n”, arr_a + arr_b)
结果是 [[ 6 8]
[10 12]]
print(“-” * 20)
“`
5.2 NumPy 的通用函数 (Universal Functions / Ufuncs)
NumPy 提供了大量的通用函数(ufuncs),它们对 ndarray
进行元素级运算,速度非常快。常见的 ufuncs 包括:
- 数学函数:
np.sin()
,np.cos()
,np.tan()
,np.arcsin()
,np.arccos()
,np.arctan()
,np.sqrt()
,np.exp()
,np.log()
,np.log10()
,np.abs()
,np.ceil()
,np.floor()
,np.round()
等。 - 比较函数:
np.maximum()
,np.minimum()
等。 - 逻辑函数:
np.logical_and()
,np.logical_or()
,np.logical_not()
等。
这些函数可以直接应用于整个数组,而无需编写循环。
“`python
arr = np.array([0, np.pi/2, np.pi]) # pi是NumPy内置的常数
print(“sin 函数:”, np.sin(arr)) # [0. 1. 0. ] (浮点精度问题)
print(“-” * 20)
arr = np.array([1.2, 2.7, 3.5, 4.0])
print(“向上取整 (ceil):”, np.ceil(arr)) # [2. 3. 4. 4.]
print(“向下取整 (floor):”, np.floor(arr)) # [1. 2. 3. 4.]
print(“四舍五入 (round):”, np.round(arr)) # [1. 3. 4. 4.]
print(“-” * 20)
arr_comp1 = np.array([1, 5, 3])
arr_comp2 = np.array([4, 2, 6])
print(“元素级最大值:”, np.maximum(arr_comp1, arr_comp2)) # [4 5 6]
“`
5.3 聚合函数 (Aggregation Functions)
NumPy 提供了多种函数来计算数组的聚合值(例如,总和、平均值、最大值、最小值等)。
arr.sum()
: 计算所有元素的总和。arr.mean()
: 计算所有元素的平均值。arr.max()
,arr.min()
: 计算最大值和最小值。arr.std()
: 计算标准差。arr.var()
: 计算方差。arr.argmax()
,arr.argmin()
: 计算最大值和最小值的索引。
这些聚合函数有一个重要的参数 axis
,用于指定沿着哪个轴进行计算。
axis=0
: 沿着列方向计算(压缩行)。axis=1
: 沿着行方向计算(压缩列)。axis=None
(默认): 计算所有元素的聚合值。
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(“所有元素的总和:”, arr2d.sum()) # 45
print(“所有元素的平均值:”, arr2d.mean()) # 5.0
print(“所有元素的最大值:”, arr2d.max()) # 9
print(“所有元素的最小值:”, arr2d.min()) # 1
print(“-” * 20)
按列求和 (axis=0)
print(“按列求和:”, arr2d.sum(axis=0)) # [1+4+7, 2+5+8, 3+6+9] = [12 15 18]
print(“-” * 20)
按行求和 (axis=1)
print(“按行求和:”, arr2d.sum(axis=1)) # [1+2+3, 4+5+6, 7+8+9] = [ 6 15 24]
print(“-” * 20)
按列求平均值
print(“按列求平均值:”, arr2d.mean(axis=0)) # [4. 5. 6.]
print(“-” * 20)
按行求最大值
print(“按行求最大值:”, arr2d.max(axis=1)) # [3 6 9]
print(“-” * 20)
“`
理解 axis
参数对于处理多维数据(如表格数据或图像数据)至关重要。
5.4 广播 (Broadcasting)
广播是 NumPy 处理具有不同形状的数组之间的运算的强大机制。在某些条件下,较小的数组形状会自动“广播”到较大数组的形状,使得它们兼容进行元素级运算。
广播遵循一些规则:
- 如果两个数组的维度不同,维度较小的数组会在其前面填充一维,直到它们的维度相等。
- 沿着任一维度,如果两个数组在该维度上的大小相等,或者其中一个数组的大小为 1,则它们是兼容的。
- 如果两个数组在任一维度上大小不相等且都不为 1,则会引发错误。
如果上述规则通过,较小数组的大小为 1 的维度会被拉伸以匹配较大数组的大小。
最简单的广播例子是数组与标量的运算:
“`python
arr = np.array([1, 2, 3])
scalar = 2
print(“数组与标量相加:”, arr + scalar) # [3 4 5] (标量2被广播成 [2 2 2])
print(“数组与标量相乘:”, arr * scalar) # [2 4 6] (标量2被广播成 [2 2 2])
print(“-” * 20)
“`
更复杂的例子:
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6]]) # 形状 (2, 3)
arr1d = np.array([10, 20, 30]) # 形状 (3,)
arr1d 的形状 (3,) 在前面填充一维变成 (1, 3),然后拉伸第一维到 2,变成 (2, 3)
print(“arr2d + arr1d:\n”, arr2d + arr1d)
结果是 [[1+10 2+20 3+30]
[4+10 5+20 6+30]]
[[11 22 33]
[14 25 36]]
print(“-” * 20)
arr2d_other = np.array([[10],
[20]]) # 形状 (2, 1)
arr2d_other 形状 (2, 1) 的第二维被拉伸到 3,变成 (2, 3)
print(“arr2d + arr2d_other:\n”, arr2d + arr2d_other)
结果是 [[1+10 2+10 3+10]
[4+20 5+20 6+20]]
[[11 12 13]
[24 25 26]]
print(“-” * 20)
不兼容的广播例子
arr_invalid = np.array([1, 2]) # 形状 (2,)
print(arr2d + arr_invalid) # 形状 (2, 3) 与 (2,) 不兼容,会报错 ValueError
“`
广播是 NumPy 提高效率的重要手段,它避免了创建大型的中间数组副本。理解广播规则需要一些练习。
6. 数组的形状操作
修改数组的形状是常见需求。NumPy 提供了一些函数来完成这些操作。
6.1 重塑 (Reshape)
reshape()
方法可以在不改变数组数据和元素总数的前提下,改变数组的形状。
“`python
arr = np.arange(12) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
重塑为 3×4 矩阵
arr_reshaped = arr.reshape((3, 4))
print(“重塑为 3×4:\n”, arr_reshaped)
print(“-” * 20)
重塑为 4×3 矩阵
arr_reshaped2 = arr.reshape(4, 3) # shape 元组可以不加括号
print(“重塑为 4×3:\n”, arr_reshaped2)
print(“-” * 20)
使用 -1 让 NumPy 自动推断某个维度的大小
arr_reshaped3 = arr.reshape(2, -1) # 2行,列数自动计算 (12 / 2 = 6)
print(“重塑为 2行 (-1):\n”, arr_reshaped3)
print(“-” * 20)
arr_reshaped4 = arr.reshape(-1, 3) # 3列,行数自动计算 (12 / 3 = 4)
print(“重塑为 (-1)行 3列:\n”, arr_reshaped4)
print(“-” * 20)
“`
注意: reshape
返回的是一个视图(如果可能),而不是副本。如果新形状与原数组的内存布局兼容,会返回视图;否则会返回副本。通常情况下,建议假设它返回的是视图,并使用 .copy()
来强制创建副本,以避免意外修改。
6.2 转置 (Transpose)
对于二维数组(矩阵),转置是行和列的互换。NumPy 提供了 .T
属性或 np.transpose()
函数。
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6]]) # 形状 (2, 3)
print(“原始数组:\n”, arr2d)
print(“-” * 20)
转置 (使用 .T 属性)
print(“转置后的数组:\n”, arr2d.T) # 形状 (3, 2)
结果是 [[1 4]
[2 5]
[3 6]]
print(“-” * 20)
对于更高维度的数组,transpose 允许指定轴的顺序
arr3d = np.arange(24).reshape((2, 3, 4)) # 形状 (2, 3, 4)
print(“原始三维数组形状:”, arr3d.shape)
print(“交换轴 0 和 1:\n”, arr3d.transpose((1, 0, 2)).shape) # 形状变为 (3, 2, 4)
print(“交换轴 1 和 2:\n”, arr3d.transpose((0, 2, 1)).shape) # 形状变为 (2, 4, 3)
“`
6.3 展平 (Flatten)
将多维数组展平(或称为拉伸)成一维数组。可以使用 flatten()
方法或 ravel()
方法。
“`python
arr2d = np.array([[1, 2], [3, 4]])
print(“原始数组:\n”, arr2d)
使用 flatten() 方法
arr_flattened = arr2d.flatten()
print(“flatten 展平后的数组:”, arr_flattened) # [1 2 3 4]
print(“-” * 20)
使用 ravel() 方法
arr_raveled = arr2d.ravel()
print(“ravel 展平后的数组:”, arr_raveled) # [1 2 3 4]
print(“-” * 20)
flatten() 总是返回一个副本,而 ravel() 返回一个视图(如果可能)
arr2d_copy = arr2d.copy() # 确保从一个副本开始,避免影响其他测试
arr2d_view = arr2d # 创建一个视图
flattened_copy = arr2d_copy.flatten()
raveled_view = arr2d_view.ravel()
print(“修改 flattened_copy[0] = 99”)
flattened_copy[0] = 99
print(“原始数组 (arr2d_copy):\n”, arr2d_copy) # 不变
print(“修改 raveled_view[0] = 88”)
raveled_view[0] = 88
print(“原始数组 (arr2d_view):\n”, arr2d_view) # 变了
print(“-” * 20)
“`
出于安全考虑(避免意外修改原数组),flatten()
可能是更稳妥的选择,尽管 ravel()
在某些情况下可能更快。
6.4 拼接 (Concatenate/Stack) 与 分割 (Split)
np.concatenate((arr1, arr2, ...), axis)
: 沿指定轴连接一系列数组。要求除了连接轴之外的其他轴的大小都相同。np.vstack((arr1, arr2, ...))
: 垂直堆叠数组(沿着 axis 0)。相当于np.concatenate((arr1, arr2, ...), axis=0)
。np.hstack((arr1, arr2, ...))
: 水平堆叠数组(沿着 axis 1)。相当于np.concatenate((arr1, arr2, ...), axis=1)
。np.stack((arr1, arr2, ...), axis)
: 沿指定轴堆叠一系列数组。与concatenate
的区别在于,stack
会创建一个新轴,而concatenate
是在现有轴上拼接。例如,堆叠两个一维数组会得到一个二维数组。
“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
沿 axis=0 拼接 (一维数组只有 axis 0)
concat_1d = np.concatenate((arr1, arr2))
print(“一维数组拼接:”, concat_1d) # [1 2 3 4 5 6]
print(“-” * 20)
arr_a = np.array([[1, 2],
[3, 4]]) # 形状 (2, 2)
arr_b = np.array([[5, 6],
[7, 8]]) # 形状 (2, 2)
垂直堆叠 (axis=0)
vstack_arr = np.vstack((arr_a, arr_b))
print(“垂直堆叠 (vstack):\n”, vstack_arr)
结果是 [[1 2]
[3 4]
[5 6]
[7 8]]
print(“-” * 20)
水平堆叠 (axis=1)
hstack_arr = np.hstack((arr_a, arr_b))
print(“水平堆叠 (hstack):\n”, hstack_arr)
结果是 [[1 2 5 6]
[3 4 7 8]]
print(“-” * 20)
堆叠 (stack) – 创建新轴
arr1d_x = np.array([1, 2, 3])
arr1d_y = np.array([4, 5, 6])
stack_arr = np.stack((arr1d_x, arr1d_y), axis=0) # 在新轴 0 上堆叠
print(“stack (axis=0):\n”, stack_arr) # 结果是 [[1 2 3], [4 5 6]], 形状 (2, 3)
stack_arr2 = np.stack((arr1d_x, arr1d_y), axis=1) # 在新轴 1 上堆叠
print(“stack (axis=1):\n”, stack_arr2) # 结果是 [[1 4], [2 5], [3 6]], 形状 (3, 2)
print(“-” * 20)
“`
np.split(arr, indices_or_sections, axis)
: 沿指定轴将数组分割成多个子数组。indices_or_sections
可以是一个整数(表示等分成几份)或一个索引列表(表示在哪些位置进行分割)。np.vsplit(arr, indices_or_sections)
: 垂直分割数组(沿 axis 0)。np.hsplit(arr, indices_or_sections)
: 水平分割数组(沿 axis 1)。
“`python
arr = np.arange(12).reshape(3, 4)
print(“原始数组:\n”, arr)
print(“-” * 20)
垂直分割成 3 块 (等分)
vsplit_arrs = np.vsplit(arr, 3)
print(“垂直分割成 3 块:\n”, vsplit_arrs) # 结果是一个列表,包含 3 个 (1, 4) 的数组
[[[ 0 1 2 3]],
[[ 4 5 6 7]],
[[ 8 9 10 11]]]
print(“-” * 20)
水平分割成 2 块 (等分)
hsplit_arrs = np.hsplit(arr, 2)
print(“水平分割成 2 块:\n”, hsplit_arrs) # 结果是一个列表,包含 2 个 (3, 2) 的数组
[[[ 0 1],
[ 4 5],
[ 8 9]],
[[ 2 3],
[ 6 7],
[10 11]]]
print(“-” * 20)
沿 axis=1 在索引 1 和 3 处分割 (不是等分)
split_arrs = np.split(arr, [1, 3], axis=1)
print(“沿 axis=1 在索引 [1, 3] 处分割:\n”, split_arrs) # 结果是 3 个数组
索引 0 到 1 (不含): arr[:, 0:1] -> [[ 0], [ 4], [ 8]]
索引 1 到 3 (不含): arr[:, 1:3] -> [[ 1 2], [ 5 6], [ 9 10]]
索引 3 到 4 (含): arr[:, 3:] -> [[ 3], [ 7], [11]]
print(“-” * 20)
“`
7. 线性代数运算 (基本)
NumPy 的 numpy.linalg
模块提供了一些基本的线性代数功能。最常用的是矩阵乘法。
7.1 矩阵乘法
在 Python 3.5+ 中,推荐使用 @
运算符进行矩阵乘法。或者使用 np.dot()
函数。
“`python
matrix_a = np.array([[1, 2],
[3, 4]]) # 形状 (2, 2)
matrix_b = np.array([[5, 6],
[7, 8]]) # 形状 (2, 2)
矩阵乘法 (使用 @ 运算符)
matrix_prod = matrix_a @ matrix_b
print(“矩阵 A @ 矩阵 B:\n”, matrix_prod)
结果是 [[15+27, 16+28],
[35+47, 36+48]]
[[ 5+14, 6+16],
[15+28, 18+32]]
[[19, 22],
[43, 50]]
print(“-” * 20)
矩阵乘法 (使用 np.dot())
matrix_prod_dot = np.dot(matrix_a, matrix_b)
print(“np.dot(A, B):\n”, matrix_prod_dot)
print(“-” * 20)
矩阵与向量相乘
matrix_c = np.array([[1, 2, 3],
[4, 5, 6]]) # 形状 (2, 3)
vector_v = np.array([7, 8, 9]) # 形状 (3,)
矩阵 C @ 向量 v
matrix_vector_prod = matrix_c @ vector_v # (2, 3) @ (3,) -> (2,)
print(“矩阵 C @ 向量 v:”, matrix_vector_prod)
结果是 [17+28+39, 47+58+69]
[7+16+27, 28+40+54]
[50, 122]
print(“-” * 20)
向量内积 (点乘)
vector1 = np.array([1, 2, 3])
vector2 = np.array([4, 5, 6])
dot_product = np.dot(vector1, vector2) # 14 + 25 + 3*6 = 4 + 10 + 18 = 32
print(“向量内积:”, dot_product)
print(“-” * 20)
“`
注意:*
运算符是元素级乘法,而 @
或 np.dot()
是矩阵乘法(对于二维数组)或点积(对于一维数组)。这是初学者常犯的错误。
7.2 其他线性代数函数
numpy.linalg
中还有许多其他有用的函数,例如:
np.linalg.inv(A)
: 计算矩阵 A 的逆。np.linalg.det(A)
: 计算矩阵 A 的行列式。np.linalg.eig(A)
: 计算矩阵 A 的特征值和特征向量。np.linalg.solve(A, b)
: 解线性方程组 Ax = b。
这些函数在更高级的数值计算中非常重要。
8. 随机数生成
NumPy 的 numpy.random
模块用于生成各种概率分布的随机数。
“`python
生成 0 到 1 之间的均匀分布随机数
rand_arr = np.random.rand(3, 2) # 形状为 3×2 的数组
print(“rand (0-1 均匀分布):\n”, rand_arr)
print(“-” * 20)
生成标准正态分布 (均值0,方差1) 随机数
randn_arr = np.random.randn(2, 4) # 形状为 2×4 的数组
print(“randn (标准正态分布):\n”, randn_arr)
print(“-” * 20)
生成指定范围内的随机整数
randint_arr = np.random.randint(1, 10, size=(3, 3)) # 生成 1 到 9 (不包含 10) 的随机整数,形状 3×3
print(“randint (随机整数):\n”, randint_arr)
print(“-” * 20)
从给定序列中随机选择元素
choices = [1, 2, 3, 4, 5]
choice_arr = np.random.choice(choices, size=5, replace=False) # 不重复选择 5 个元素
print(“choice (不重复选择):”, choice_arr)
choice_arr_replace = np.random.choice(choices, size=10, replace=True) # 重复选择 10 个元素
print(“choice (重复选择):”, choice_arr_replace)
print(“-” * 20)
设置随机种子 (为了结果的可重复性)
np.random.seed(42) # 任何整数都可以作为种子
rand1 = np.random.rand(3)
print(“第一次随机生成 (种子42):”, rand1)
np.random.seed(42) # 再次设置相同的种子
rand2 = np.random.rand(3)
print(“第二次随机生成 (种子42):”, rand2) # 会生成和 rand1 相同的序列
np.random.seed(100) # 设置不同的种子
rand3 = np.random.rand(3)
print(“第三次随机生成 (种子100):”, rand3) # 会生成不同的序列
print(“-” * 20)
“`
numpy.random
模块还有许多其他函数,可以生成各种特定分布的随机数。
9. 保存与加载数组
NumPy 提供了方便的函数来将数组保存到文件或从文件加载。
np.save('filename.npy', arr)
: 将单个数组保存到.npy
文件中(NumPy 专用的二进制格式),保存和加载速度快,能够保留数组的形状和数据类型。np.savez('filename.npz', arr1=arr1, arr2=arr2)
: 将多个数组保存到.npz
文件中(压缩格式),通过关键字参数指定数组名。np.load('filename.npy')
: 从.npy
文件加载数组。np.load('filename.npz')
: 从.npz
文件加载多个数组,返回一个类似于字典的对象,可以通过数组名访问。np.savetxt('filename.txt', arr, delimiter=',')
: 将数组保存到文本文件中,通常用于保存二维数组。可以指定分隔符。np.loadtxt('filename.txt', delimiter=',')
: 从文本文件中加载数组。
“`python
arr_to_save = np.arange(10).reshape(2, 5)
保存到 .npy 文件
np.save(‘my_array.npy’, arr_to_save)
print(“数组已保存到 my_array.npy”)
从 .npy 文件加载
loaded_arr = np.load(‘my_array.npy’)
print(“从 my_array.npy 加载的数组:\n”, loaded_arr)
print(“-” * 20)
保存多个数组到 .npz 文件
arr1 = np.array([1, 2, 3])
arr2 = np.array([[4, 5], [6, 7]])
np.savez(‘multiple_arrays.npz’, array_a=arr1, array_b=arr2)
print(“多个数组已保存到 multiple_arrays.npz”)
从 .npz 文件加载
loaded_npz = np.load(‘multiple_arrays.npz’)
print(“从 multiple_arrays.npz 加载的对象类型:”, type(loaded_npz))
print(“访问 array_a:”, loaded_npz[‘array_a’])
print(“访问 array_b:\n”, loaded_npz[‘array_b’])
加载完成后通常需要关闭文件对象
loaded_npz.close()
print(“-” * 20)
保存到文本文件
arr_text = np.array([[1.1, 2.2], [3.3, 4.4]])
np.savetxt(‘my_array.txt’, arr_text, delimiter=’,’)
print(“数组已保存到 my_array.txt”)
从文本文件加载
loaded_text_arr = np.loadtxt(‘my_array.txt’, delimiter=’,’)
print(“从 my_array.txt 加载的数组:\n”, loaded_text_arr)
print(“-” * 20)
注意: savetxt/loadtxt 主要适用于简单的数据格式,对于复杂结构或包含多种数据类型的数组,推荐使用 .npy/.npz 格式。
“`
10. NumPy 的性能优势
再强调一下 NumPy 为什么快。主要原因包括:
- 底层实现: NumPy 的核心部分是用 C 或 Fortran 编写的,这些编译型语言的执行速度远高于 Python 解释器。
- 连续内存分配:
ndarray
的元素存储在连续的内存块中,这使得 CPU 可以更有效地访问数据,也更利于利用 CPU 的向量化指令(SIMD)。Python 列表的元素引用可以指向内存中分散的对象。 - 向量化运算: NumPy 的 ufuncs 和广播机制允许直接在整个数组上执行操作,避免了显式的 Python 循环。这减少了 Python 解释器的开销。
例如,计算两个大列表相加与两个大 NumPy 数组相加的速度对比:
“`python
import time
list1 = list(range(1000000))
list2 = list(range(1000000))
start_time = time.time()
list_sum = [x + y for x, y in zip(list1, list2)] # Python 列表求和
end_time = time.time()
print(f”Python 列表相加耗时: {end_time – start_time:.6f} 秒”)
arr1 = np.arange(1000000)
arr2 = np.arange(1000000)
start_time = time.time()
numpy_sum = arr1 + arr2 # NumPy 数组相加
end_time = time.time()
print(f”NumPy 数组相加耗时: {end_time – start_time:.6f} 秒”)
通常情况下,NumPy 的速度会快很多倍
“`
这简单的例子就体现了 NumPy 在处理大型数值数据时的巨大优势。
总结与展望
通过本文,你应该对 NumPy 有了基本的认识,包括:
- NumPy 的重要性以及它如何解决 Python 列表在数值计算上的不足。
- 核心的
ndarray
对象及其重要属性(ndim
,shape
,size
,dtype
)。 - 创建
ndarray
的多种方法(从列表、使用内置函数)。 - 多维数组的灵活索引和切片,特别是布尔索引的强大功能。
- NumPy 数组的基本运算、通用函数 (ufuncs) 和聚合函数 (以及
axis
参数)。 - 广播机制的工作原理。
- 数组的形状操作(
reshape
,T
,flatten
,ravel
,concatenate
,stack
,split
)。 - 基本的线性代数运算(矩阵乘法
@
)。 - 随机数生成。
- 数组的保存与加载。
- NumPy 性能优势的原因。
这只是 NumPy 功能的冰山一角。作为一个强大的数值计算库,NumPy 还有更多高级功能等待你去探索,例如:
- 更完整的线性代数模块 (
numpy.linalg
) - 傅里叶变换 (
numpy.fft
) - 数学和统计函数 (
numpy.math
,numpy.statistics
) - 排序、查找和计数操作
- 结构化数组
- 内存映射文件 (
numpy.memmap
)
掌握 NumPy 是进行数据科学和科学计算的坚实基础。接下来,你可以尝试:
- 多练习: 尝试本文中所有的代码示例,并修改它们来观察结果。解决一些简单的 NumPy 练习题。
- 阅读文档: 查阅 NumPy 的官方文档 (numpy.org/doc/),它提供了更详细的信息和更多函数的用法。
- 学习其他库: 在掌握 NumPy 后,可以进一步学习 Pandas(数据分析)、Matplotlib/Seaborn(数据可视化)、SciPy(科学计算)、scikit-learn(机器学习)等基于 NumPy 的库。
NumPy 是你数据科学之旅中必不可少的工具。勤加练习,你会越来越熟练地使用它来处理和分析数值数据。祝你学习愉快!