一文搞懂 NumPy:入门指南
引言:为什么需要 NumPy?
在 Python 数据科学和数值计算领域,NumPy(Numerical Python 的简称)是一个基石般的存在。无论你是进行数据分析、机器学习、科学计算还是其他涉及大量数值运算的任务,几乎都会用到 NumPy。但对于初学者来说,可能还不清楚 NumPy 到底是什么,以及为什么它如此重要。
想象一下,你需要处理数百万甚至数十亿个数字。如果仅仅使用 Python 内置的列表(list)来存储和操作这些数字,你会发现效率非常低下。Python 列表非常灵活,可以存储不同类型的数据,而且大小可变。但这种灵活性带来了性能上的开销。列表中的元素可以分散在内存的各个地方,而且进行数值运算时,Python 需要不断地检查每个元素的类型,这极大地拖慢了速度。
NumPy 应运而生,正是为了解决这个问题。它提供了一个高性能的多维数组对象,称为 ndarray
(N-dimensional array),以及一套用于处理这些数组的函数。NumPy 数组与 Python 列表相比,具有以下显著优势:
- 高性能: NumPy 数组中的所有元素都是同一类型,且在内存中是连续存储的。这使得 NumPy 可以在底层利用优化的 C/Fortran 代码进行快速的数值运算。对于大规模数据,NumPy 的速度可以比纯 Python 快几个数量级。
- 内存效率: 由于元素类型统一且存储紧凑,NumPy 数组占用的内存通常比 Python 列表少得多。
- 丰富的函数库: NumPy 提供了大量的数学函数、线性代数函数、随机数生成函数等,可以直接作用于整个数组,无需编写显式的循环,这使得代码更简洁、更易读。
- 基石地位: 许多其他重要的数据科学库,如 Pandas、SciPy、Scikit-learn、Matplotlib 等,都构建在 NumPy 的基础上。理解 NumPy 是学习这些库的基础。
简单来说,如果你想在 Python 中高效地进行数值计算,NumPy 是你不可或缺的工具。
接下来,我们将一步步深入 NumPy 的世界,从安装到核心概念,再到各种常用操作,力求让你“一文搞懂” NumPy 的入门知识。
第一步:安装 NumPy
安装 NumPy 非常简单,通常推荐使用 pip 工具。如果你已经安装了 Python 和 pip,只需在终端或命令提示符中运行以下命令:
bash
pip install numpy
如果你使用的是 Anaconda 或 Miniconda 等数据科学发行版,NumPy 通常已经预装好了。如果没有,你可以使用 conda 进行安装:
bash
conda install numpy
安装完成后,你可以在 Python 交互环境中或脚本中导入 NumPy:
python
import numpy as np
约定俗成地,我们将 NumPy 导入后命名为 np
,这在 NumPy 社区非常普遍。
第二步:理解核心:ndarray
NumPy 的核心是 ndarray
对象。这是一个多维数组,所有元素必须是相同的 数据类型(dtype)。这与 Python 列表不同,后者可以包含任意类型的对象。
一个 ndarray
对象包含以下几个重要属性:
shape
: 数组的维度。例如,一个 2×3 的矩阵,其shape
就是(2, 3)
。dtype
: 数组中元素的数据类型。例如int64
(64位整数)、float64
(64位浮点数)等。数据类型的统一是 NumPy 高性能的关键。ndim
: 数组的维度数量,即shape
元组的长度。size
: 数组中元素的总个数。itemsize
: 数组中每个元素占用的字节数。
让我们通过一些例子来理解这些概念。
第三步:创建 ndarray
创建 NumPy 数组有很多种方法,这里介绍几种常用的:
3.1 从 Python 列表或元组创建
这是最常见的方法。可以使用 np.array()
函数将 Python 列表或元组转换为 NumPy 数组。
“`python
import numpy as np
从列表创建一维数组
list1 = [1, 2, 3, 4, 5]
arr1 = np.array(list1)
print(“一维数组:”, arr1)
print(“形状:”, arr1.shape)
print(“维度数:”, arr1.ndim)
print(“元素总数:”, arr1.size)
print(“元素类型:”, arr1.dtype)
print(“每个元素字节数:”, arr1.itemsize)
从列表的列表创建二维数组 (矩阵)
list2 = [[1, 2, 3], [4, 5, 6]]
arr2 = np.array(list2)
print(“\n二维数组:\n”, arr2)
print(“形状:”, arr2.shape)
print(“维度数:”, arr2.ndim)
print(“元素总数:”, arr2.size)
print(“元素类型:”, arr2.dtype) # 默认推断为整数类型
从元组创建
tuple1 = (10, 20, 30)
arr3 = np.array(tuple1)
print(“\n从元组创建:”, arr3)
“`
输出示例:
“`
一维数组: [1 2 3 4 5]
形状: (5,)
维度数: 1
元素总数: 5
元素类型: int64
每个元素字节数: 8
二维数组:
[[1 2 3]
[4 5 6]]
形状: (2, 3)
维度数: 2
元素总数: 6
元素类型: int64
每个元素字节数: 8
从元组创建: [10 20 30]
“`
注意:在创建数组时,NumPy 会尽量推断合适的数据类型。如果你需要指定数据类型,可以使用 dtype
参数:
“`python
arr_float = np.array([1, 2, 3], dtype=np.float64)
print(“\n指定float64类型:”, arr_float)
print(“元素类型:”, arr_float.dtype)
arr_bool = np.array([0, 1, 0], dtype=bool)
print(“\n指定bool类型:”, arr_bool)
print(“元素类型:”, arr_bool.dtype)
“`
输出示例:
“`
指定float64类型: [1. 2. 3.]
元素类型: float64
指定bool类型: [False True False]
元素类型: bool
“`
NumPy 支持多种数据类型,如 np.int32
, np.int64
, np.float32
, np.float64
, np.complex128
, np.bool_
, np.str_
等。
3.2 创建特定数值的数组
NumPy 提供了一些方便的函数来创建特定值的数组:
np.zeros(shape, dtype)
: 创建指定形状和类型的全零数组。np.ones(shape, dtype)
: 创建指定形状和类型的全一数组。np.full(shape, fill_value, dtype)
: 创建指定形状和类型,并填充指定值的数组。np.empty(shape, dtype)
: 创建指定形状和类型,但元素值未初始化的数组。这通常比zeros
或ones
稍快,但数组中的值是随机的(取决于内存中的现有内容),使用前需要填充。
“`python
创建一个2×3的全零浮点数组
zeros_arr = np.zeros((2, 3), dtype=np.float64)
print(“\n全零数组:\n”, zeros_arr)
创建一个3×3的全一整数数组
ones_arr = np.ones((3, 3), dtype=np.int32)
print(“\n全一数组:\n”, ones_arr)
创建一个2×2,填充值为7的数组
full_arr = np.full((2, 2), 7)
print(“\n填充特定值的数组:\n”, full_arr)
创建一个4元素的空数组 (值不确定)
empty_arr = np.empty(4)
print(“\n空数组 (值不确定):\n”, empty_arr)
“`
输出示例:
“`
全零数组:
[[0. 0. 0.]
[0. 0. 0.]]
全一数组:
[[1 1 1]
[1 1 1]
[1 1 1]]
填充特定值的数组:
[[7 7]
[7 7]]
空数组 (值不确定):
[0. 0. 0. 0.] # 注意:这里的输出可能是其他随机值
“`
3.3 创建序列或范围数组
np.arange(start, stop, step, dtype)
: 类似于 Python 的range()
,创建等差序列数组(不包含stop
值)。np.linspace(start, stop, num, endpoint, dtype)
: 创建指定数量的等间隔样本,在指定的闭区间[start, stop]
内生成数组。
“`python
创建从0到9的整数序列
range_arr = np.arange(10)
print(“\narange(10):”, range_arr)
创建从2到10,步长为2的序列
range_arr_step = np.arange(2, 11, 2)
print(“arange(2, 11, 2):”, range_arr_step)
创建在0到1之间,包含0和1,共5个点的等间隔数组
linspace_arr = np.linspace(0, 1, 5)
print(“\nlinspace(0, 1, 5):”, linspace_arr)
创建在0到1之间,不包含1,共5个点的等间隔数组
linspace_arr_no_endpoint = np.linspace(0, 1, 5, endpoint=False)
print(“linspace(0, 1, 5, endpoint=False):”, linspace_arr_no_endpoint)
“`
输出示例:
“`
arange(10): [0 1 2 3 4 5 6 7 8 9]
arange(2, 11, 2): [ 2 4 6 8 10]
linspace(0, 1, 5): [0. 0.25 0.5 0.75 1. ]
linspace(0, 1, 5, endpoint=False): [0. 0.2 0.4 0.6 0.8]
“`
3.4 创建对角矩阵和单位矩阵
np.eye(N, M, k, dtype)
: 创建一个对角线为1,其余为0的二维数组(单位矩阵或矩形单位矩阵)。N
是行数,M
是列数 (默认为N
),k
指定对角线的位置 (0为主对角线,正数向上偏移,负数向下偏移)。np.identity(n, dtype)
: 创建一个 n x n 的单位矩阵(主对角线为1)。
“`python
创建一个3×3的单位矩阵
identity_matrix = np.identity(3)
print(“\n3x3 单位矩阵:\n”, identity_matrix)
创建一个4×5的矩阵,主对角线为1
eye_matrix = np.eye(4, 5)
print(“\n4x5 eye 矩阵:\n”, eye_matrix)
创建一个3×3矩阵,对角线向下偏移1位
eye_matrix_offset = np.eye(3, k=-1)
print(“\n3x3 eye 矩阵 (k=-1):\n”, eye_matrix_offset)
“`
输出示例:
“`
3×3 单位矩阵:
[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
4×5 eye 矩阵:
[[1. 0. 0. 0. 0.]
[0. 1. 0. 0. 0.]
[0. 0. 1. 0. 0.]
[0. 0. 0. 1. 0.]]
3×3 eye 矩阵 (k=-1):
[[0. 0. 0.]
[1. 0. 0.]
[0. 1. 0.]]
“`
第四步:数组索引与切片
访问和操作 NumPy 数组中的元素是日常操作的核心。NumPy 的索引和切片语法与 Python 列表类似,但在处理多维数组时更加强大和灵活。
4.1 一维数组的索引与切片
这部分与 Python 列表非常相似:
“`python
arr = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
print(“原始数组:”, arr)
索引单个元素
print(“第一个元素:”, arr[0])
print(“最后一个元素:”, arr[-1])
切片
print(“前三个元素:”, arr[:3]) # 从开始到索引2 (不包含3)
print(“索引5及之后的元素:”, arr[5:]) # 从索引5到结束
print(“索引2到5之间的元素:”, arr[2:6]) # 从索引2到索引5 (不包含6)
print(“每隔两个元素取值:”, arr[::2]) # 从开始到结束,步长为2
print(“反转数组:”, arr[::-1])
“`
输出示例:
原始数组: [0 1 2 3 4 5 6 7 8 9]
第一个元素: 0
最后一个元素: 9
前三个元素: [0 1 2]
索引5及之后的元素: [5 6 7 8 9]
索引2到5之间的元素: [2 3 4 5]
每隔两个元素取值: [0 2 4 6 8]
反转数组: [9 8 7 6 5 4 3 2 1 0]
4.2 多维数组的索引与切片
对于多维数组,索引和切片通过逗号 ,
分隔不同维度。例如,对于一个二维数组 arr[row_index, column_index]
。
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(“原始二维数组:\n”, arr2d)
索引单个元素 (第一行第二列)
print(“元素 [0, 1]:”, arr2d[0, 1]) # 索引行0,索引列1
索引整行
print(“第一行:”, arr2d[0, :]) # 索引行0,所有列
print(“第二行:”, arr2d[1]) # 索引行1,默认所有列
索引整列
print(“第二列:”, arr2d[:, 1]) # 所有行,索引列1
切片 (提取子矩阵)
提取前两行,前两列
sub_arr = arr2d[:2, :2]
print(“\n提取前两行前两列子矩阵:\n”, sub_arr)
提取后两行,第二列及之后
sub_arr2 = arr2d[1:, 1:]
print(“\n提取后两行第二列及之后子矩阵:\n”, sub_arr2)
使用步长
print(“\n提取所有行,从第三列开始,步长为-1 (反向取列):\n”, arr2d[:, ::-1])
“`
输出示例:
“`
原始二维数组:
[[1 2 3]
[4 5 6]
[7 8 9]]
元素 [0, 1]: 2
第一行: [1 2 3]
第二行: [4 5 6]
第二列: [2 5 8]
提取前两行前两列子矩阵:
[[1 2]
[4 5]]
提取后两行第二列及之后子矩阵:
[[5 6]
[8 9]]
提取所有行,从第三列开始,步长为-1 (反向取列):
[[3 2 1]
[6 5 4]
[9 8 7]]
“`
重要提示: NumPy 数组的切片是视图(view),而不是副本(copy)。这意味着对切片进行修改会直接影响原数组。如果你需要一个独立的副本,可以使用 .copy()
方法。
“`python
arr_slice = arr2d[:2, :2]
print(“\n切片前 (视图):\n”, arr_slice)
arr_slice[0, 0] = 100 # 修改切片
print(“修改切片后 (原数组也改变):\n”, arr2d)
创建副本
arr_copy = arr2d[:2, :2].copy()
print(“\n副本:\n”, arr_copy)
arr_copy[0, 0] = 200 # 修改副本
print(“修改副本后 (原数组不变):\n”, arr2d)
print(“副本修改后的值:\n”, arr_copy)
“`
输出示例:
“`
切片前 (视图):
[[1 2]
[4 5]]
修改切片后 (原数组也改变):
[[100 2 3]
[ 4 5 6]
[ 7 8 9]]
副本:
[[100 2]
[ 4 5]]
修改副本后 (原数组不变):
[[100 2 3]
[ 4 5 6]
[ 7 8 9]]
副本修改后的值:
[[200 2]
[ 4 5]]
“`
4.3 布尔索引 (Boolean Indexing)
布尔索引是 NumPy 中一种非常强大的数据选择方法。你可以使用一个布尔数组(其中包含 True
或 False
)来索引另一个数组,只选择布尔数组中对应位置为 True
的元素。
“`python
arr = np.arange(10)
print(“原始数组:”, arr)
创建一个布尔数组 (例如,判断哪些元素大于5)
bool_arr = arr > 5
print(“布尔数组:”, bool_arr)
使用布尔数组进行索引
elements_greater_than_5 = arr[bool_arr]
print(“大于5的元素:”, elements_greater_than_5)
简化写法:直接使用条件表达式
elements_even = arr[arr % 2 == 0]
print(“偶数元素:”, elements_even)
在二维数组中使用布尔索引
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
print(“\n原始二维数组:\n”, arr2d)
提取所有大于5的元素 (结果是一个一维数组)
elements_greater_than_5_2d = arr2d[arr2d > 5]
print(“二维数组中大于5的元素:”, elements_greater_than_5_2d)
结合条件和索引
提取第一行中大于2的元素
print(“第一行中大于2的元素:”, arr2d[0, arr2d[0] > 2])
“`
输出示例:
“`
原始数组: [0 1 2 3 4 5 6 7 8 9]
布尔数组: [False False False False False False True True True True]
大于5的元素: [6 7 8 9]
偶数元素: [0 2 4 6 8]
原始二维数组:
[[1 2 3]
[4 5 6]
[7 8 9]]
二维数组中大于5的元素: [6 7 8 9]
第一行中大于2的元素: [3]
“`
4.4 花式索引 (Fancy Indexing)
花式索引是指使用一个整数数组来索引另一个数组。结果数组的形状将反映索引数组的形状。与切片不同,花式索引总是创建数据的副本。
“`python
arr = np.arange(10)
print(“原始数组:”, arr)
使用一个整数列表或数组进行索引
indices = [1, 5, 2, 8]
fancy_indexed = arr[indices]
print(“花式索引 [1, 5, 2, 8]:”, fancy_indexed)
使用一个不同形状的索引数组
indices2 = np.array([[0, 1], [3, 4]])
fancy_indexed2 = arr[indices2]
print(“花式索引 [[0, 1], [3, 4]]:\n”, fancy_indexed2) # 结果数组形状与 indices2 相同
在二维数组中使用花式索引
arr2d = np.arange(12).reshape(3, 4) # 创建一个3×4的数组并填充0-11
print(“\n原始二维数组:\n”, arr2d)
选择指定的行
row_indices = [0, 2, 1]
selected_rows = arr2d[row_indices]
print(“选择指定的行 [0, 2, 1]:\n”, selected_rows)
选择指定的元素 (对应位置)
例如,选择 (0, 0), (1, 3), (2, 1) 这三个元素
row_indices_elements = [0, 1, 2]
col_indices_elements = [0, 3, 1]
selected_elements = arr2d[row_indices_elements, col_indices_elements]
print(“选择特定位置的元素 ([0,0], [1,3], [2,1]):”, selected_elements)
结合切片和花式索引
选择所有行,但只取第0列和第2列
print(“选择所有行,第0列和第2列:\n”, arr2d[:, [0, 2]])
“`
输出示例:
“`
原始数组: [0 1 2 3 4 5 6 7 8 9]
花式索引 [1, 5, 2, 8]: [1 5 2 8]
花式索引 [[0, 1], [3, 4]]:
[[0 1]
[3 4]]
原始二维数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
选择指定的行 [0, 2, 1]:
[[ 0 1 2 3]
[ 8 9 10 11]
[ 4 5 6 7]]
选择特定位置的元素 ([0,0], [1,3], [2,1]): [ 0 7 9]
选择所有行,第0列和第2列:
[[ 0 2]
[ 4 6]
[ 8 10]]
“`
第五步:基本运算与广播
NumPy 数组的一大优势在于其强大的数值运算能力,特别是元素级的操作和广播机制。
5.1 元素级运算
NumPy 数组之间的基本算术运算(加、减、乘、除、幂等)默认是元素级的。这极大地简化了代码,避免了显式的循环。
“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
加法
print(“arr1 + arr2:”, arr1 + arr2)
减法
print(“arr1 – arr2:”, arr1 – arr2)
乘法 (不是矩阵乘法)
print(“arr1 * arr2:”, arr1 * arr2)
除法
print(“arr1 / arr2:”, arr1 / arr2) # 结果可能是浮点数
幂运算
print(“arr1 ** 2:”, arr1 ** 2) # 每个元素的平方
比较运算 (返回布尔数组)
print(“arr1 > arr2:”, arr1 > arr2)
“`
输出示例:
arr1 + arr2: [5 7 9]
arr1 - arr2: [-3 -3 -3]
arr1 * arr2: [ 4 10 18]
arr1 / arr2: [0.25 0.4 0.5 ]
arr1 ** 2: [1 4 9]
arr1 > arr2: [False False False]
这些操作同样适用于多维数组,只要它们的形状兼容(对于元素级运算,通常是形状相同)。
“`python
arr2d_a = np.array([[1, 2], [3, 4]])
arr2d_b = np.array([[5, 6], [7, 8]])
print(“\narr2d_a:\n”, arr2d_a)
print(“arr2d_b:\n”, arr2d_b)
print(“arr2d_a + arr2d_b:\n”, arr2d_a + arr2d_b)
print(“arr2d_a * arr2d_b:\n”, arr2d_a * arr2d_b)
“`
输出示例:
arr2d_a:
[[1 2]
[3 4]]
arr2d_b:
[[5 6]
[7 8]]
arr2d_a + arr2d_b:
[[ 6 8]
[10 12]]
arr2d_a * arr2d_b:
[[ 5 12]
[21 32]]
5.2 矩阵乘法
NumPy 提供了专门用于矩阵乘法的运算符 @
或函数 np.dot()
/ np.matmul()
。对于二维数组,这执行标准的矩阵乘法。
“`python
matrix_a = np.array([[1, 2], [3, 4]]) # 2×2 矩阵
matrix_b = np.array([[5, 6], [7, 8]]) # 2×2 矩阵
矩阵乘法 (使用 @ 运算符,Python 3.5+)
matrix_product_at = matrix_a @ matrix_b
print(“\n矩阵乘法 (a @ b):\n”, matrix_product_at)
矩阵乘法 (使用 np.dot())
matrix_product_dot = np.dot(matrix_a, matrix_b)
print(“矩阵乘法 (np.dot(a, b)):\n”, matrix_product_dot)
矩阵乘法 (使用 np.matmul())
matrix_product_matmul = np.matmul(matrix_a, matrix_b)
print(“矩阵乘法 (np.matmul(a, b)):\n”, matrix_product_matmul)
示例:非方阵乘法 (需要满足维度匹配规则)
matrix_c = np.array([[1, 2, 3], [4, 5, 6]]) # 2×3 矩阵
matrix_d = np.array([[7, 8], [9, 10], [11, 12]]) # 3×2 矩阵
2×3 乘以 3×2 得到 2×2 矩阵
matrix_product_cd = matrix_c @ matrix_d
print(“\n矩阵乘法 (c @ d):\n”, matrix_product_cd)
“`
输出示例:
“`
矩阵乘法 (a @ b):
[[19 22]
[43 50]]
矩阵乘法 (np.dot(a, b)):
[[19 22]
[43 50]]
矩阵乘法 (np.matmul(a, b)):
[[19 22]
[43 50]]
矩阵乘法 (c @ d):
[[ 58 64]
[139 154]]
“`
5.3 广播 (Broadcasting)
广播是 NumPy 中处理形状不同的数组之间运算的强大机制。当进行元素级运算时,如果两个数组的形状不同,NumPy 会尝试自动扩展其中一个或两个数组,使它们的形状兼容,然后再执行运算。这个过程就像是将较小的数组“广播”到较大数组的每个元素或行/列上。
广播遵循一套严格的规则:
- 维度数相同: 如果两个数组维度数不同,维度较小的数组会在其左侧填充一维,直到维度数相同。
- 维度大小比较: 从两个数组的末尾维度开始,向左比较对应维度的长度。它们必须满足以下条件之一:
- 两者相等。
- 其中一个为 1。
- 其中一个不存在(因为规则1填充了维度)。
如果这些规则不满足,广播将失败,并抛出 ValueError
。
广播的例子:
- 数组与标量相加: 标量会被广播到数组的每一个元素上。
python
arr = np.array([1, 2, 3])
print("arr + 5:", arr + 5) # 5 被广播到 [5, 5, 5] 然后进行元素相加
输出示例:
arr + 5: [6 7 8]
- 一维数组与二维数组相加:
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6]]) # 形状 (2, 3)
arr1d = np.array([10, 20, 30]) # 形状 (3,)
广播过程:arr1d (形状 (3,)) 会被扩展成形状 (1, 3),然后沿第一个维度复制,变成形状 (2, 3) 的数组
[[10, 20, 30],
[10, 20, 30]]
print(“\narr2d:\n”, arr2d)
print(“arr1d:”, arr1d)
print(“arr2d + arr1d:\n”, arr2d + arr1d)
“`
输出示例:
arr2d:
[[1 2 3]
[4 5 6]]
arr1d: [10 20 30]
arr2d + arr1d:
[[11 22 33]
[14 25 36]]
- 不同形状的二维数组相加:
“`python
matrix = np.ones((2, 3)) # 形状 (2, 3)
row_vector = np.arange(3) # 形状 (3,) -> 广播后 (1, 3) -> 广播后 (2, 3) [[0,1,2],[0,1,2]]
col_vector = np.arange(2).reshape((2, 1)) # 形状 (2, 1) -> 广播后 (2, 3) [[0,0,0],[1,1,1]]
print(“matrix:\n”, matrix)
print(“row_vector:”, row_vector)
print(“col_vector:\n”, col_vector)
print(“\nmatrix + row_vector:\n”, matrix + row_vector)
print(“\nmatrix + col_vector:\n”, matrix + col_vector)
“`
输出示例:
“`
matrix:
[[1. 1. 1.]
[1. 1. 1.]]
row_vector: [0 1 2]
col_vector:
[[0]
[1]]
matrix + row_vector:
[[1. 2. 3.]
[1. 2. 3.]]
matrix + col_vector:
[[1. 1. 1.]
[2. 2. 2.]]
“`
理解广播机制对于高效地编写 NumPy 代码至关重要,它可以避免显式的循环和不必要的数据复制。
第六步:通用函数 (Universal Functions – ufuncs)
通用函数(ufuncs)是 NumPy 提供的一类函数,它们对 ndarray
进行元素级的快速操作。大多数 ufuncs 是数学运算或元素级比较。它们非常高效,因为它们是用编译语言实现的。
常见的 ufuncs 包括:
- 数学函数:
np.abs()
,np.sin()
,np.cos()
,np.tan()
,np.sqrt()
,np.exp()
,np.log()
,np.log10()
,np.ceil()
,np.floor()
,np.round()
等。 - 算术运算:
np.add()
,np.subtract()
,np.multiply()
,np.divide()
,np.power()
等(这些通常直接使用运算符更方便)。 - 比较函数:
np.maximum()
,np.minimum()
,np.greater()
,np.less()
等。
“`python
arr = np.array([-1.5, 0, 2.3, -4.0])
print(“原始数组:”, arr)
print(“绝对值:”, np.abs(arr))
print(“平方根 (只对非负数有效):”, np.sqrt(np.array([0, 4, 9])))
print(“指数函数 (e^x):”, np.exp(arr))
print(“向下取整:”, np.floor(arr))
print(“向上取整:”, np.ceil(arr))
print(“四舍五入:”, np.round(arr))
arr_a = np.array([1, 5, 2])
arr_b = np.array([4, 2, 6])
print(“\narr_a:”, arr_a)
print(“arr_b:”, arr_b)
print(“元素级的最大值:”, np.maximum(arr_a, arr_b))
“`
输出示例:
“`
原始数组: [-1.5 0. 2.3 -4. ]
绝对值: [1.5 0. 2.3 4. ]
平方根 (只对非负数有效): [0. 2. 3.]
指数函数 (e^x): [0.22313035 1. 9.97418246 0.01831564]
向下取整: [-2. 0. 2. -4.]
向上取整: [-1. 0. 3. -4.]
四舍五入: [-1. 0. 2. -4.]
arr_a: [1 5 2]
arr_b: [4 2 6]
元素级的最大值: [4 5 6]
“`
Ufuncs 也支持广播。例如,np.maximum(arr, scalar)
会将标量广播到数组的每个元素上进行比较。
第七步:重塑数组 (Reshaping)
改变数组的形状是一个常见的操作,NumPy 提供了 reshape()
方法来实现这一点。reshape()
不会改变数组的数据,只是改变了数据的组织方式。
“`python
arr = np.arange(12) # 形状 (12,)
print(“原始数组:”, arr)
重塑为 3 行 4 列的二维数组
reshaped_arr = arr.reshape(3, 4)
print(“\n重塑为 3×4:\n”, reshaped_arr)
重塑为 4 行 3 列的二维数组
reshaped_arr2 = arr.reshape(4, 3)
print(“\n重塑为 4×3:\n”, reshaped_arr2)
重塑为 2x2x3 的三维数组
reshaped_arr3 = arr.reshape(2, 2, 3)
print(“\n重塑为 2x2x3:\n”, reshaped_arr3)
使用 -1 让 NumPy 自动计算某个维度的大小
重塑为 3 行,列数自动计算 (12 / 3 = 4)
reshaped_arr4 = arr.reshape(3, -1)
print(“\n重塑为 3x-1:\n”, reshaped_arr4)
重塑为未知行,4 列 (12 / 4 = 3)
reshaped_arr5 = arr.reshape(-1, 4)
print(“\n重塑为 -1×4:\n”, reshaped_arr5)
“`
输出示例:
“`
原始数组: [ 0 1 2 3 4 5 6 7 8 9 10 11]
重塑为 3×4:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
重塑为 4×3:
[[ 0 1 2]
[ 3 4 5]
[ 6 7 8]
[ 9 10 11]]
重塑为 2x2x3:
[[[ 0 1 2]
[ 3 4 5]]
[[ 6 7 8]
[ 9 10 11]]]
重塑为 3x-1:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
重塑为 -1×4:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
“`
注意:reshape()
返回的是原数组的视图(除非无法实现),所以修改重塑后的数组会影响原数组。
将多维数组展平(flatten)成一维数组可以使用 ravel()
或 flatten()
。flatten()
总是返回副本,而 ravel()
返回视图(如果可能)。
“`python
arr2d = np.arange(12).reshape(3, 4)
print(“\n原始二维数组:\n”, arr2d)
flattened_arr = arr2d.flatten()
print(“flatten() 展平:\n”, flattened_arr)
raveled_arr = arr2d.ravel()
print(“ravel() 展平:\n”, raveled_arr)
证明 flatten() 是副本,ravel() 可能是视图
raveled_arr[0] = 999
print(“修改 ravel() 结果后,原数组:\n”, arr2d) # 原数组改变
flattened_arr[0] = 888
print(“修改 flatten() 结果后,原数组:\n”, arr2d) # 原数组不变
“`
输出示例:
原始二维数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
flatten() 展平:
[ 0 1 2 3 4 5 6 7 8 9 10 11]
ravel() 展平:
[ 0 1 2 3 4 5 6 7 8 9 10 11]
修改 ravel() 结果后,原数组:
[[999 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
修改 flatten() 结果后,原数组:
[[999 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
有时候需要为数组添加一个新维度,通常是为了进行广播。可以使用 np.newaxis
或 None
来实现:
“`python
arr = np.arange(5) # 形状 (5,)
print(“\n原始一维数组:”, arr)
print(“形状:”, arr.shape)
将一维数组变成行向量 (形状 (1, 5))
row_vec = arr[np.newaxis, :]
print(“变成行向量:”, row_vec)
print(“形状:”, row_vec.shape)
将一维数组变成列向量 (形状 (5, 1))
col_vec = arr[:, np.newaxis]
print(“变成列向量:\n”, col_vec)
print(“形状:”, col_vec.shape)
也可以使用 None
row_vec_none = arr[None, :]
col_vec_none = arr[:, None]
print(“使用 None 变成行向量:”, row_vec_none.shape)
print(“使用 None 变成列向量:”, col_vec_none.shape)
“`
输出示例:
原始一维数组: [0 1 2 3 4]
形状: (5,)
变成行向量: [[0 1 2 3 4]]
形状: (1, 5)
变成列向量:
[[0]
[1]
[2]
[3]
[4]]
形状: (5, 1)
使用 None 变成行向量: (1, 5)
使用 None 变成列向量: (5, 1)
第八步:组合与分割数组
在实际应用中,我们经常需要将多个数组组合起来,或者将一个数组分割成多个小数组。NumPy 提供了相应的函数。
8.1 组合数组
np.concatenate((arr1, arr2, ...), axis)
: 沿指定轴连接一系列数组。数组除了连接轴外的其他轴的长度必须相同。np.vstack((arr1, arr2, ...))
: 垂直堆叠(按行堆叠)数组。相当于concatenate
沿axis=0
进行。数组除了第0轴外的其他轴的长度必须相同。np.hstack((arr1, arr2, ...))
: 水平堆叠(按列堆叠)数组。相当于concatenate
沿axis=1
进行(对于二维及以上数组)。数组除了第1轴外的其他轴的长度必须相同。np.stack((arr1, arr2, ...), axis)
: 沿新轴堆叠一系列数组。所有数组的形状必须相同。
“`python
arr1 = np.array([1, 2, 3])
arr2 = np.array([4, 5, 6])
一维数组的拼接 (默认 axis=0)
concat_1d = np.concatenate((arr1, arr2))
print(“一维数组拼接:”, concat_1d)
arr2d_a = np.array([[1, 2], [3, 4]])
arr2d_b = np.array([[5, 6], [7, 8]])
二维数组沿 axis=0 (垂直) 拼接
concat_2d_axis0 = np.concatenate((arr2d_a, arr2d_b), axis=0)
print(“\n二维数组沿 axis=0 拼接:\n”, concat_2d_axis0)
二维数组沿 axis=1 (水平) 拼接
concat_2d_axis1 = np.concatenate((arr2d_a, arr2d_b), axis=1)
print(“二维数组沿 axis=1 拼接:\n”, concat_2d_axis1)
vstack 和 hstack 是常用快捷方式
vstack_arr = np.vstack((arr2d_a, arr2d_b))
print(“\nvstack (垂直堆叠):\n”, vstack_arr)
hstack_arr = np.hstack((arr2d_a, arr2d_b))
print(“hstack (水平堆叠):\n”, hstack_arr)
stack 沿新轴堆叠 (形状必须一致)
arr_stack1 = np.array([1, 2])
arr_stack2 = np.array([3, 4])
stacked_arr = np.stack((arr_stack1, arr_stack2), axis=0) # 结果形状 (2, 2)
print(“\nstack (axis=0):\n”, stacked_arr)
stacked_arr_axis1 = np.stack((arr_stack1, arr_stack2), axis=1) # 结果形状 (2, 2)
print(“stack (axis=1):\n”, stacked_arr_axis1)
“`
输出示例:
“`
一维数组拼接: [1 2 3 4 5 6]
二维数组沿 axis=0 拼接:
[[1 2]
[3 4]
[5 6]
[7 8]]
二维数组沿 axis=1 拼接:
[[1 2 5 6]
[3 4 7 8]]
vstack (垂直堆叠):
[[1 2]
[3 4]
[5 6]
[7 8]]
hstack (水平堆叠):
[[1 2 5 6]
[3 4 7 8]]
stack (axis=0):
[[1 2]
[3 4]]
stack (axis=1):
[[1 3]
[2 4]]
“`
8.2 分割数组
np.split(arr, indices_or_sections, axis)
: 将一个数组沿指定轴分割成多个子数组。indices_or_sections
可以是一个整数(表示分割成多少等份),或者是一个表示分割位置索引的列表或数组。np.vsplit(arr, indices_or_sections)
: 垂直分割(按行分割)。相当于split
沿axis=0
。np.hsplit(arr, indices_or_sections)
: 水平分割(按列分割)。相当于split
沿axis=1
。
“`python
arr = np.arange(12).reshape(3, 4)
print(“原始数组:\n”, arr)
将数组沿 axis=0 平均分割成3份
split_arr_rows = np.split(arr, 3, axis=0)
print(“\n沿 axis=0 平均分割成3份:”)
for sub_arr in split_arr_rows:
print(sub_arr)
将数组沿 axis=1 按索引 [1, 3] 分割
分割位置在索引1之前和索引3之前
结果是 arr[:, :1], arr[:, 1:3], arr[:, 3:]
split_arr_cols = np.split(arr, [1, 3], axis=1)
print(“\n沿 axis=1 按索引 [1, 3] 分割:”)
for sub_arr in split_arr_cols:
print(sub_arr)
vsplit (垂直分割)
vsplit_arr = np.vsplit(arr, 3) # 等价于 np.split(arr, 3, axis=0)
print(“\nvsplit 平均分割成3份:”)
for sub_arr in vsplit_arr:
print(sub_arr)
hsplit (水平分割)
hsplit_arr = np.hsplit(arr, [1, 3]) # 等价于 np.split(arr, [1, 3], axis=1)
print(“\nhsplit 按索引 [1, 3] 分割:”)
for sub_arr in hsplit_arr:
print(sub_arr)
“`
输出示例:
“`
原始数组:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
沿 axis=0 平均分割成3份:
[[0 1 2 3]]
[[4 5 6 7]]
[[ 8 9 10 11]]
沿 axis=1 按索引 [1, 3] 分割:
[[ 0]
[ 4]
[ 8]]
[[ 1 2]
[ 5 6]
[ 9 10]]
[[ 3]
[ 7]
[11]]
vsplit 平均分割成3份:
[[0 1 2 3]]
[[4 5 6 7]]
[[ 8 9 10 11]]
hsplit 按索引 [1, 3] 分割:
[[ 0]
[ 4]
[ 8]]
[[ 1 2]
[ 5 6]
[ 9 10]]
[[ 3]
[ 7]
[11]]
“`
第九步:其他常用功能
9.1 聚合函数 (Aggregate Functions)
NumPy 提供了许多计算数组统计量或聚合值的函数,如求和、平均值、最小值、最大值、标准差等。这些函数可以作用于整个数组,也可以沿着指定的轴(axis)进行计算。
“`python
arr = np.arange(1, 10).reshape(3, 3)
print(“原始数组:\n”, arr)
print(“\n所有元素的和:”, np.sum(arr))
print(“所有元素的平均值:”, np.mean(arr))
print(“所有元素的最小值:”, np.min(arr))
print(“所有元素的最大值:”, np.max(arr))
print(“所有元素的标准差:”, np.std(arr))
沿 axis=0 (按列) 计算
print(“\n沿 axis=0 (按列) 求和:”, np.sum(arr, axis=0))
print(“沿 axis=0 (按列) 平均值:”, np.mean(arr, axis=0))
print(“沿 axis=0 (按列) 最小值:”, np.min(arr, axis=0))
沿 axis=1 (按行) 计算
print(“\n沿 axis=1 (按行) 求和:”, np.sum(arr, axis=1))
print(“沿 axis=1 (按行) 平均值:”, np.mean(arr, axis=1))
print(“沿 axis=1 (按行) 最大值:”, np.max(arr, axis=1))
argmin/argmax: 返回最小值/最大值的索引
print(“\n所有元素的最小值的索引:”, np.argmin(arr)) # 展平后的索引
print(“沿 axis=0 (按列) 最小值的索引:”, np.argmin(arr, axis=0))
print(“沿 axis=1 (按行) 最大值的索引:”, np.argmax(arr, axis=1))
“`
输出示例:
“`
原始数组:
[[1 2 3]
[4 5 6]
[7 8 9]]
所有元素的和: 45
所有元素的平均值: 5.0
所有元素的最小值: 1
所有元素的最大值: 9
所有元素的标准差: 2.5819890040099604
沿 axis=0 (按列) 求和: [12 15 18]
沿 axis=0 (按列) 平均值: [4. 5. 6.]
沿 axis=0 (按列) 最小值: [1 2 3]
沿 axis=1 (按行) 求和: [ 6 15 24]
沿 axis=1 (按行) 平均值: [2. 5. 8.]
沿 axis=1 (按行) 最大值: [3 6 9]
所有元素的最小值的索引: 0
沿 axis=0 (按列) 最小值的索引: [0 0 0]
沿 axis=1 (按行) 最大值的索引: [2 2 2]
“`
9.2 排序
np.sort(arr, axis)
返回一个排序后的数组副本。
“`python
arr = np.array([[3, 1, 2], [6, 5, 4]])
print(“原始数组:\n”, arr)
沿最后一个轴 (对于二维数组是按行) 排序
sorted_arr_rows = np.sort(arr)
print(“\n沿最后一个轴 (行) 排序:\n”, sorted_arr_rows)
沿 axis=0 (按列) 排序
sorted_arr_cols = np.sort(arr, axis=0)
print(“沿 axis=0 (列) 排序:\n”, sorted_arr_cols)
“`
输出示例:
“`
原始数组:
[[3 1 2]
[6 5 4]]
沿最后一个轴 (行) 排序:
[[1 2 3]
[4 5 6]]
沿 axis=0 (列) 排序:
[[3 1 2]
[6 5 4]]
“`
注意:np.sort()
返回副本。如果想原地排序,可以使用数组对象的 .sort()
方法。
9.3 查找唯一值
np.unique(arr)
返回数组中的唯一元素,并按排序顺序返回。
python
arr = np.array([1, 2, 1, 3, 2, 4, 5, 4])
print("原始数组:", arr)
unique_elements = np.unique(arr)
print("唯一元素:", unique_elements)
输出示例:
原始数组: [1 2 1 3 2 4 5 4]
唯一元素: [1 2 3 4 5]
9.4 随机数生成
np.random
模块提供了各种生成随机数的函数。
“`python
生成 [0.0, 1.0) 之间的随机浮点数
random_float = np.random.rand()
print(“\n单个随机浮点数:”, random_float)
生成指定形状的 [0.0, 1.0) 随机浮点数数组
random_arr_float = np.random.rand(2, 3)
print(“2×3 随机浮点数数组:\n”, random_arr_float)
生成符合标准正态分布 (均值0,标准差1) 的随机数
random_arr_normal = np.random.randn(2, 3)
print(“2×3 标准正态分布随机数数组:\n”, random_arr_normal)
生成指定范围内的随机整数 (低闭高开)
random_int = np.random.randint(0, 10) # 生成 [0, 9] 之间的随机整数
print(“单个 [0, 9) 随机整数:”, random_int)
生成指定范围和形状的随机整数数组
random_arr_int = np.random.randint(0, 10, size=(3, 3))
print(“3×3 [0, 10) 随机整数数组:\n”, random_arr_int)
从给定的一维数组或列表中随机选择元素
choices = [1, 2, 3, 4, 5]
random_choice = np.random.choice(choices, 2) # 随机选择2个元素 (可能重复)
print(“从列表中随机选择2个:”, random_choice)
random_choice_replace = np.random.choice(choices, 5, replace=False) # 随机选择5个元素 (不重复)
print(“从列表中不重复随机选择5个:”, random_choice_replace) # 相当于洗牌一部分
洗牌 (原地修改)
arr_to_shuffle = np.array([1, 2, 3, 4, 5])
np.random.shuffle(arr_to_shuffle)
print(“洗牌后的数组:”, arr_to_shuffle)
“`
输出示例 (随机,每次运行可能不同):
“`
单个随机浮点数: 0.123456789
2×3 随机浮点数数组:
[[0.987 0.654 0.321]
[0.111 0.222 0.333]]
2×3 标准正态分布随机数数组:
[[ 0.5 -1.2 0.1 ]
[ 1.0 0.0 -0.8 ]]
单个 [0, 9) 随机整数: 7
3×3 [0, 10) 随机整数数组:
[[5 2 8]
[1 9 3]
[6 4 7]]
从列表中随机选择2个: [3 1]
从列表中不重复随机选择5个: [4 1 5 2 3]
洗牌后的数组: [3 5 1 4 2]
“`
9.5 保存与加载数组
NumPy 提供了方便的函数来将数组保存到磁盘文件和从文件加载。
np.save(filename, arr)
: 将单个数组保存为.npy
格式的二进制文件。np.load(filename)
: 从.npy
文件加载数组。np.savez(filename, arr1=arr1, arr2=arr2, ...)
: 将多个数组保存为.npz
格式的压缩文件。可以通过关键字参数指定每个数组的名称。np.load(filename)
: 从.npz
文件加载数据。加载后会返回一个类似字典的对象,可以通过键访问各个数组。
“`python
arr_to_save = np.arange(10)
print(“\n要保存的数组:”, arr_to_save)
保存单个数组
np.save(‘my_array.npy’, arr_to_save)
print(“数组已保存到 my_array.npy”)
加载数组
loaded_arr = np.load(‘my_array.npy’)
print(“从 my_array.npy 加载的数组:”, loaded_arr)
保存多个数组
arr1 = np.array([1, 2, 3])
arr2 = np.array([[4, 5], [6, 7]])
np.savez(‘multiple_arrays.npz’, array1=arr1, array2=arr2)
print(“\n多个数组已保存到 multiple_arrays.npz”)
加载多个数组
loaded_data = np.load(‘multiple_arrays.npz’)
print(“从 multiple_arrays.npz 加载的数据 (类似字典):”, loaded_data)
print(“加载的 array1:”, loaded_data[‘array1’])
print(“加载的 array2:\n”, loaded_data[‘array2’])
清理文件 (可选)
import os
os.remove(‘my_array.npy’)
os.remove(‘multiple_arrays.npz’)
“`
输出示例:
“`
要保存的数组: [0 1 2 3 4 5 6 7 8 9]
数组已保存到 my_array.npy
从 my_array.npy 加载的数组: [0 1 2 3 4 5 6 7 8 9]
多个数组已保存到 multiple_arrays.npz
从 multiple_arrays.npz 加载的数据 (类似字典):
加载的 array1: [1 2 3]
加载的 array2:
[[4 5]
[6 7]]
“`
.npy
和 .npz
是 NumPy 自己特有的二进制格式,优点是读写速度快,且保留了数组的数据类型和形状信息。
第十步:NumPy 在生态系统中的地位
正如引言中所提到的,NumPy 是 Python 数据科学栈的基石。许多流行库都依赖于 NumPy 数组作为其主要数据结构。
- Pandas: 构建在 NumPy 之上,提供了 DataFrame 和 Series 等更高级的数据结构,用于结构化数据处理。Pandas 内部的数据存储和许多操作都使用了 NumPy 数组。
- SciPy: 提供了更广泛的科学和工程计算模块,包括优化、积分、插值、信号处理、线性代数(更高级的)、统计等。SciPy 中的函数通常接受 NumPy 数组作为输入和输出。
- Matplotlib: 最常用的 Python 绘图库。Matplotlib 可以直接接收 NumPy 数组作为输入来绘制图表。
- Scikit-learn: 流行的机器学习库。Scikit-learn 的模型通常接受 NumPy 数组或 Pandas DataFrame 作为输入数据。
学习和掌握 NumPy 不仅能让你更高效地进行数值计算,也是你深入学习这些高级库的必经之路。
结论
恭喜你!通过本文,你已经掌握了 NumPy 的核心概念和常用操作,包括:
- 理解 NumPy 的重要性以及与 Python 列表的区别
- 安装 NumPy
- 认识
ndarray
对象及其属性 - 创建各种类型的数组
- 掌握灵活的数组索引、切片、布尔索引和花式索引
- 理解元素级运算、矩阵乘法和强大的广播机制
- 使用通用函数进行高效的元素级操作
- 重塑、组合和分割数组
- 使用常用的聚合函数、排序和查找唯一值
- 生成随机数
- 保存和加载数组
- 了解 NumPy 在 Python 数据科学生态系统中的地位
NumPy 的功能远不止于此,还有更高级的线性代数、傅里叶变换、随机模拟等模块。但本文涵盖的知识点足以让你开始在日常数据处理和科学计算任务中有效地使用 NumPy。
下一步做什么?
最好的学习方法是实践!尝试用 NumPy 解决一些实际问题,例如:
- 创建一个大型随机数组,计算其平均值、标准差和最大值。
- 使用广播将一个二维数组的每一行减去该行的平均值。
- 生成一个包含重复元素的数组,然后找出其中的唯一元素及其出现的次数。
- 使用索引和切片提取二维数组的特定部分。
- 实现一个简单的矩阵乘法。
不断练习,动手编写代码,你会越来越熟悉 NumPy 的强大之处,并能够更自信地处理各种数值计算任务。
祝你在 NumPy 的学习和使用过程中一切顺利!