NumPy 入门指南:快速了解与使用 – wiki基地


NumPy 入门指南:快速了解与使用

导言:为什么需要 NumPy?

在 Python 的世界里,处理数值数据是家常便饭。无论是进行科学计算、数据分析、机器学习,还是图像处理,高效地操作大量的数字是核心需求。Python 标准库中的列表(list)非常灵活,可以存储不同类型的数据,但在处理大型同质数值数据集时,它们存在明显的性能瓶颈:

  1. 速度慢: Python 列表是对象的集合,每个对象都需要单独存储和管理。进行数值运算时,需要通过 Python 的循环逐个处理元素,这比编译型语言(如 C 或 Fortran)慢得多。
  2. 内存效率低: 每个元素不仅存储数据本身,还需要存储类型信息和引用计数等额外信息,导致内存占用较高。
  3. 功能受限: 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): 创建指定形状和数据类型,元素值是未初始化的(随机值)。这通常比 zerosones 稍快,但要注意数组内容的不确定性。

“`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): 在 startstop 之间均匀地生成 num 个点(包含 startstop)。

“`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. 如果两个数组的维度不同,维度较小的数组会在其前面填充一维,直到它们的维度相等。
  2. 沿着任一维度,如果两个数组在该维度上的大小相等,或者其中一个数组的大小为 1,则它们是兼容的。
  3. 如果两个数组在任一维度上大小不相等且都不为 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 为什么快。主要原因包括:

  1. 底层实现: NumPy 的核心部分是用 C 或 Fortran 编写的,这些编译型语言的执行速度远高于 Python 解释器。
  2. 连续内存分配: ndarray 的元素存储在连续的内存块中,这使得 CPU 可以更有效地访问数据,也更利于利用 CPU 的向量化指令(SIMD)。Python 列表的元素引用可以指向内存中分散的对象。
  3. 向量化运算: 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 是进行数据科学和科学计算的坚实基础。接下来,你可以尝试:

  1. 多练习: 尝试本文中所有的代码示例,并修改它们来观察结果。解决一些简单的 NumPy 练习题。
  2. 阅读文档: 查阅 NumPy 的官方文档 (numpy.org/doc/),它提供了更详细的信息和更多函数的用法。
  3. 学习其他库: 在掌握 NumPy 后,可以进一步学习 Pandas(数据分析)、Matplotlib/Seaborn(数据可视化)、SciPy(科学计算)、scikit-learn(机器学习)等基于 NumPy 的库。

NumPy 是你数据科学之旅中必不可少的工具。勤加练习,你会越来越熟练地使用它来处理和分析数值数据。祝你学习愉快!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部