零基础学习NumPy:数据科学的基石,快速入门指南
欢迎踏上Python数据科学之旅!如果你希望在数据分析、机器学习、科学计算等领域深耕,那么NumPy是你必须掌握的第一个重要工具。它提供了强大的多维数组对象以及丰富的函数库,极大地提升了处理数值数据的效率和便捷性。
本篇文章将带领完全没有NumPy基础的你,一步步揭开NumPy的神秘面纱。我们将从最基本的概念开始,通过大量的代码示例,让你快速上手并理解其核心功能。无需担心,我们将尽量使用通俗易懂的语言来解释每一个概念。
目录
- 什么是NumPy?为什么需要它?
- Python列表的局限性
- NumPy的核心优势
- 安装NumPy
- NumPy的核心:ndarray(N维数组)
- 理解ndarray
- ndarray vs Python列表
- ndarray的属性(shape, dtype, ndim, size)
- 创建ndarray
- 从Python列表/元组创建
- 使用内置函数创建(zeros, ones, full, empty)
- 使用序列生成函数(arange, linspace, logspace)
- 创建随机数组
- 访问和操作数组元素(索引与切片)
- 一维数组的索引与切片
- 多维数组的索引与切片
- 布尔索引(Fancy Indexing)
- 整数数组索引(Advanced Indexing)
- 数组的形状操作
- 改变数组形状(reshape, flatten, ravel)
- 转置(transpose)
- 堆叠与拆分(concatenate, stack, split)
- NumPy的通用函数(ufunc)与数学运算
- 元素级运算
- 聚合函数(sum, mean, min, max, std等)
- 常用的数学函数(sin, cos, sqrt, exp等)
- 广播(Broadcasting)机制
- 理解广播
- 广播规则
- 广播示例
- 线性代数操作
- 点积与矩阵乘法
- 文件I/O:保存与加载数组
- 使用
np.save
和np.load
- 使用
np.savetxt
和np.loadtxt
- 使用
- NumPy的性能优势
- 总结与下一步
1. 什么是NumPy?为什么需要它?
NumPy(Numerical Python的缩写)是Python中用于科学计算的基础库。它提供了一个高性能的多维数组对象,以及处理这些数组的各种工具。
Python列表的局限性
在学习Python基础时,我们经常使用列表(list)来存储数据。列表非常灵活,可以存储不同类型的数据,并且大小可变。然而,当我们需要进行大量的数值计算,特别是处理大型数据集时,Python列表就显得力不从心了:
- 性能低下: Python列表不是为数值计算优化的。当你对两个列表进行加法或其他数学运算时,需要通过循环逐个元素进行操作,效率非常低。
- 功能有限: Python列表没有内置的数学函数(如求平均值、标准差、矩阵乘法等),你需要自己编写函数或循环来实现这些功能,增加了代码量和复杂性。
- 内存占用: Python列表中的元素可以是不连续存储在内存中的不同Python对象,这导致了额外的内存开销和访问效率的降低。
考虑一个简单的例子:计算两个包含一百万个数字的列表的和。使用Python列表,你需要一个for
循环;而使用NumPy,只需要一行代码,并且速度快得多。
NumPy的核心优势
NumPy通过引入ndarray
(N维数组)对象,彻底解决了Python列表在数值计算方面的不足:
- 高性能:
ndarray
中的元素类型相同,且连续存储在内存中。NumPy的操作是在底层C语言实现的,避免了Python层面的循环,实现了向量化运算,效率极高。 - 功能丰富: NumPy提供了大量的数学、统计、线性代数等函数,可以直接应用于整个数组,无需编写循环。
- 内存效率:
ndarray
存储效率更高,尤其对于大型数据集。 - 易用性: NumPy语法简洁直观,许多操作可以直接通过运算符完成。
因此,无论你是在进行数据分析(Pandas、Matplotlib都构建在NumPy之上)、开发机器学习算法(Scikit-learn、TensorFlow、PyTorch等都大量使用NumPy)、进行科学模拟还是工程计算,NumPy都是不可或缺的工具。
2. 安装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,这是社区约定俗成的习惯。
3. NumPy的核心:ndarray(N维数组)
NumPy最核心的对象是ndarray
,它代表了一个多维度的、同质的、固定大小的元素集合。
理解ndarray
- 多维度(N维): ndarray可以表示标量(0维)、向量(1维)、矩阵(2维)、张量(3维或更高维)等任何维度的数组。
- 同质的(Homogeneous): 数组中的所有元素必须是相同的数据类型(dtype),比如都是整数,或者都是浮点数。这与Python列表形成鲜明对比,列表可以包含不同类型的对象。同质性是NumPy高性能的关键之一。
- 固定大小: 一旦创建,ndarray的大小是固定的。虽然可以通过一些操作改变数组的形状,但这通常会返回一个新的数组,而不是修改原数组。
ndarray vs Python列表
特性 | Python列表 | NumPy ndarray |
---|---|---|
类型 | 可变,可存储不同类型的对象 | 固定,所有元素类型相同 |
维度 | 主要用于一维序列,难以表示高维 | 原生支持多维(张量) |
性能 | 循环操作,效率较低 | 向量化操作,效率高,基于C实现 |
内存 | 对象引用,内存开销大,不连续 | 元素连续存储,内存效率高 |
功能 | 基础序列操作 | 大量内置的数学、统计、线性代数函数 |
ndarray的属性
每个ndarray
对象都有几个重要的属性,可以帮助我们了解数组的结构:
shape
: 一个元组,表示数组在每个维度上的大小。例如,一个 2×3 的矩阵,其 shape 是(2, 3)
。dtype
: 数组中元素的数据类型。NumPy支持多种数据类型(如int64
,float64
,bool
,str
等)。ndim
: 数组的维数(轴数)。shape 元组的长度。size
: 数组中元素的总个数。等于 shape 元组中所有元素的乘积。
“`python
import numpy as np
创建一个2×3的二维数组
arr = np.array([[1, 2, 3], [4, 5, 6]])
print(“数组本身:\n”, arr)
print(“数组的形状 (shape):”, arr.shape) # (2, 3) – 2行3列
print(“数组的维数 (ndim):”, arr.ndim) # 2 – 二维
print(“数组元素的总个数 (size):”, arr.size) # 6 (2 * 3)
print(“数组元素的数据类型 (dtype):”, arr.dtype) # 可能是 int64 或其他整数类型,取决于系统
“`
4. 创建ndarray
NumPy提供了多种创建数组的方法,以满足不同的需求。
从Python列表/元组创建
最常用的方法是使用 np.array()
函数,将Python的列表或元组转换为ndarray。
“`python
从列表创建一维数组
arr1d = np.array([1, 2, 3, 4, 5])
print(“一维数组:\n”, arr1d)
print(“形状:”, arr1d.shape) # (5,)
从列表的列表创建二维数组
arr2d = np.array([[10, 11, 12], [13, 14, 15]])
print(“\n二维数组:\n”, arr2d)
print(“形状:”, arr2d.shape) # (2, 3)
可以指定数据类型
arr_float = np.array([1, 2, 3], dtype=float)
print(“\n指定float类型的数组:”, arr_float)
print(“数据类型:”, arr_float.dtype) # float64
“`
使用内置函数创建
NumPy提供了创建特定初始值数组的便捷函数:
np.zeros(shape)
: 创建指定形状的全零数组。np.ones(shape)
: 创建指定形状的全一数组。np.full(shape, fill_value)
: 创建指定形状,所有元素都是fill_value
的数组。np.empty(shape)
: 创建指定形状的数组,元素值是随机的(取决于内存中的内容),但速度最快。
“`python
创建一个3×4的全零数组
zeros_arr = np.zeros((3, 4))
print(“\n全零数组:\n”, zeros_arr)
创建一个2×2的全一数组,指定整数类型
ones_arr = np.ones((2, 2), dtype=int)
print(“\n全一整数数组:\n”, ones_arr)
创建一个2×3的全7数组
full_arr = np.full((2, 3), 7)
print(“\n全7数组:\n”, full_arr)
创建一个2×2的空数组 (值不确定)
empty_arr = np.empty((2, 2))
print(“\n空数组 (值不确定):\n”, empty_arr)
“`
使用序列生成函数
np.arange(start, stop, step)
: 类似于Python的range()
,但不包含stop
值,并返回ndarray。np.linspace(start, stop, num)
: 在指定的区间[start, stop]
内均匀生成num
个点。np.logspace(start, stop, num, base)
: 在指定的区间[base**start, base**stop]
内按对数尺度均匀生成num
个点。
“`python
生成从0到9的一维数组
arange_arr = np.arange(10)
print(“\narange(10):\n”, arange_arr)
生成从1到10,步长为2的数组
arange_step = np.arange(1, 11, 2)
print(“\narange(1, 11, 2):\n”, arange_step)
在0到1之间均匀生成5个点 (包含0和1)
linspace_arr = np.linspace(0, 1, 5)
print(“\nlinspace(0, 1, 5):\n”, linspace_arr) # [0. 0.25 0.5 0.75 1. ]
“`
创建随机数组
NumPy的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)
: 创建指定形状的整数数组,元素在 [low, high) 之间随机取整。
“`python
创建一个2×3的随机浮点数数组 [0, 1)
random_uniform = np.random.rand(2, 3)
print(“\n均匀分布随机数组:\n”, random_uniform)
创建一个2×3的标准正态分布随机数数组
random_normal = np.random.randn(2, 3)
print(“\n标准正态分布随机数组:\n”, random_normal)
创建一个3×3的随机整数数组 [0, 10)
random_int = np.random.randint(0, 10, size=(3, 3))
print(“\n随机整数数组 [0, 10):\n”, random_int)
“`
5. 访问和操作数组元素(索引与切片)
访问和操作NumPy数组的元素与Python列表类似,但扩展到了多维。
一维数组的索引与切片
和Python列表一样,使用方括号 []
进行索引和切片。索引从0开始。
“`python
arr1d = np.arange(10) # [0 1 2 3 4 5 6 7 8 9]
索引单个元素
print(“第一个元素:”, arr1d[0]) # 0
print(“第五个元素:”, arr1d[4]) # 4
print(“最后一个元素:”, arr1d[-1]) # 9
切片 (start:stop:step)
print(“前三个元素:”, arr1d[:3]) # [0 1 2] (不包含stop)
print(“从索引5到末尾:”, arr1d[5:]) # [5 6 7 8 9]
print(“从索引2到7 (不包含7):”, arr1d[2:7]) # [2 3 4 5 6]
print(“每隔一个元素:”, arr1d[::2]) # [0 2 4 6 8]
print(“反转数组:”, arr1d[::-1]) # [9 8 7 6 5 4 3 2 1 0]
“`
多维数组的索引与切片
对于多维数组,使用逗号 ,
分隔不同维度的索引或切片。格式通常是 [row_index, column_index, ...]
.
“`python
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]]) # 3×3 矩阵
访问单个元素 (第一行,第二列,注意索引从0开始)
print(“\n二维数组:\n”, arr2d)
print(“第一行第二列元素:”, arr2d[0, 1]) # 2
print(“第二行第三列元素:”, arr2d[1, 2]) # 6
访问整行
print(“第一行:”, arr2d[0, :]) # [1 2 3] 或简写为 arr2d[0]
print(“第二行:”, arr2d[1]) # [4 5 6]
访问整列
print(“第二列:”, arr2d[:, 1]) # [2 5 8]
切片 sub-matrix (子矩阵)
访问前两行,后两列
print(“前两行后两列的子矩阵:\n”, arr2d[:2, 1:])
[[2 3]
[5 6]]
访问所有行,从第二列开始,步长为2
print(“所有行,第二列开始,步长为2:\n”, arr2d[:, 1::2])
[[2]
[5]
[8]]
“`
布尔索引(Boolean Indexing)
布尔索引使用一个与原数组形状相同或可广播的布尔数组来选择元素。布尔数组中为 True
的位置对应的原数组元素会被选中,最终返回一个一维数组。
“`python
arr = np.array([10, 5, 2, 8, 15, 3])
找到所有大于5的元素
bool_mask = (arr > 5) # 生成布尔数组 [ True False False True True False]
print(“\n布尔掩码:”, bool_mask)
print(“大于5的元素:”, arr[bool_mask]) # [10 8 15]
简写形式
print(“大于8的元素:”, arr[arr > 8]) # [10 15]
结合条件
print(“大于5且小于10的元素:”, arr[(arr > 5) & (arr < 10)]) # [8] (注意位运算符 & | ~ )
在多维数组中使用布尔索引
arr2d = np.arange(1, 10).reshape(3, 3)
print(“\n二维数组:\n”, arr2d)
print(“二维数组中大于5的元素:”, arr2d[arr2d > 5]) # [6 7 8 9] (返回一维数组)
“`
整数数组索引(Advanced Indexing / Fancy Indexing)
使用整数数组或列表来指定要选择的元素的索引。这使得你可以按照任意顺序选择元素,或者多次选择同一个元素。
“`python
arr1d = np.arange(10, 20) # [10 11 12 13 14 15 16 17 18 19]
按照指定的索引列表选择元素
indices = [0, 3, 7]
print(“\n根据索引列表选择元素:”, arr1d[indices]) # [10 13 17]
多维数组的整数数组索引
arr2d = np.array([[1, 2, 3],
[4, 5, 6],
[7, 8, 9]])
选择指定的行 (索引为 0 和 2 的行)
print(“\n选择指定的行:\n”, arr2d[[0, 2]])
[[1 2 3]
[7 8 9]]
选择指定的行和列 – 这会根据 (行索引数组[i], 列索引数组[i]) 的组合选择元素
rows = [0, 1, 2]
cols = [1, 2, 0]
print(“选择指定的行和列组合 ([0,1], [1,2], [2,0]):”, arr2d[rows, cols]) # [2 6 7]
“`
6. 数组的形状操作
NumPy提供了改变数组形状、组合或拆分数组的功能。
改变数组形状
arr.reshape(new_shape)
: 返回一个新数组,形状改变为new_shape
。元素的总数必须与原数组相同。可以使用-1
代表该维度的大小由其他维度推断。arr.flatten()
: 将多维数组展平为一维数组,总是返回一个副本。arr.ravel()
: 将多维数组展平为一维数组,返回一个视图或副本,取决于原数组的内存布局。通常ravel更推荐,因为它不一定强制复制。
“`python
arr = np.arange(12) # [0 1 2 … 11]
将一维数组重塑为 3×4 的二维数组
arr_reshaped = arr.reshape((3, 4))
print(“\n重塑为 3×4:\n”, arr_reshaped)
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]]
使用 -1 推断维度 (这里是 4×3)
arr_reshaped_auto = arr.reshape((4, -1))
print(“使用-1自动推断形状 (4×3):\n”, arr_reshaped_auto)
展平数组 (副本)
arr_flattened = arr_reshaped.flatten()
print(“展平 (flatten):\n”, arr_flattened) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
展平数组 (视图或副本)
arr_raveled = arr_reshaped.ravel()
print(“展平 (ravel):\n”, arr_raveled) # [ 0 1 2 3 4 5 6 7 8 9 10 11]
“`
转置(Transpose)
arr.T
或 np.transpose(arr)
可以交换数组的维度。对于二维数组,就是行变列,列变行。
“`python
arr = np.arange(1, 7).reshape(2, 3)
print(“\n原始数组:\n”, arr)
[[1 2 3]
[4 5 6]]
print(“转置后 (.T):\n”, arr.T)
[[1 4]
[2 5]
[3 6]]
“`
堆叠与拆分
np.concatenate((arr1, arr2, ...), axis=0)
: 沿着指定的轴连接一系列数组。默认轴是0(垂直方向,增加行)。np.vstack((arr1, arr2, ...))
或np.concatenate((arr1, arr2), axis=0)
: 垂直堆叠数组。np.hstack((arr1, arr2, ...))
或np.concatenate((arr1, arr2), axis=1)
: 水平堆叠数组。np.split(arr, indices_or_sections, axis=0)
: 沿着指定的轴将数组拆分为多个子数组。indices_or_sections
可以是整数(等分为N份)或索引列表。
“`python
arr1 = np.array([[1, 2], [3, 4]])
arr2 = np.array([[5, 6], [7, 8]])
垂直堆叠 (沿着轴0)
arr_vstack = np.vstack((arr1, arr2))
print(“\n垂直堆叠 (vstack):\n”, arr_vstack)
[[1 2]
[3 4]
[5 6]
[7 8]]
水平堆叠 (沿着轴1)
arr_hstack = np.hstack((arr1, arr2))
print(“\n水平堆叠 (hstack):\n”, arr_hstack)
[[1 2 5 6]
[3 4 7 8]]
拆分数组
arr = np.arange(16).reshape(4, 4)
print(“\n待拆分数组:\n”, arr)
沿着轴0 (行) 等分为两份
split_rows = np.split(arr, 2, axis=0)
print(“按行拆分 (axis=0):\n”, split_rows)
[array([[0, 1, 2, 3], [4, 5, 6, 7]]),
array([[ 8, 9, 10, 11], [12, 13, 14, 15]])]
沿着轴1 (列) 在索引1和3处拆分
split_cols = np.split(arr, [1, 3], axis=1)
print(“按列拆分 (axis=1, 在索引1和3):\n”, split_cols)
[array([[ 0], [ 4], [ 8], [12]]),
array([[ 1, 2], [ 5, 6], [ 9, 10], [13, 14]]),
array([[ 3], [ 7], [11], [15]])]
“`
7. NumPy的通用函数(ufunc)与数学运算
NumPy的通用函数(ufunc,universal function)是对ndarray进行元素级操作的函数。它们通常比Python内置函数或循环快得多。
元素级运算
基本的算术运算符(+,-,,/,*)在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(“大于:”, arr1 > 2) # [False False True]
“`
聚合函数
NumPy提供了计算数组统计量或聚合值的函数,例如求和、平均值、最小值、最大值、标准差等。这些函数可以作为ndarray的方法调用,也可以作为np的函数调用。
“`python
arr = np.arange(1, 7).reshape(2, 3)
print(“\n数组:\n”, arr)
[[1 2 3]
[4 5 6]]
print(“所有元素的和:”, arr.sum()) # 21 (1+2+3+4+5+6)
print(“所有元素的平均值:”, arr.mean()) # 3.5
print(“所有元素的最小值:”, arr.min()) # 1
print(“所有元素的最大值:”, arr.max()) # 6
可以指定轴 (axis) 进行聚合
axis=0: 沿着列进行计算 (对每一列求和/平均等)
print(“沿着列求和 (axis=0):”, arr.sum(axis=0)) # [5 7 9] (1+4, 2+5, 3+6)
print(“沿着列求平均 (axis=0):”, arr.mean(axis=0)) # [2.5 3.5 4.5]
axis=1: 沿着行进行计算 (对每一行求和/平均等)
print(“沿着行求和 (axis=1):”, arr.sum(axis=1)) # [ 6 15] (1+2+3, 4+5+6)
print(“沿着行求平均 (axis=1):”, arr.mean(axis=1)) # [2. 5.]
找到最大/最小元素的索引
print(“最小元素索引:”, arr.argmin()) # 0 (展平后的索引)
print(“最大元素索引:”, arr.argmax()) # 5 (展平后的索引)
print(“沿着列的最小元素索引 (axis=0):”, arr.argmin(axis=0)) # [0 0 0] (第一列最小值在索引0行,第二列最小值在索引0行…)
“`
常用的数学函数
NumPy包含大量的数学函数,如三角函数、指数函数、对数函数、平方根等。
“`python
arr = np.array([0, np.pi/2, np.pi]) # 使用np.pi获取圆周率
print(“\nsin:”, np.sin(arr)) # [0. 1. 0. ] (近似值)
print(“cos:”, np.cos(arr)) # [ 1.0000000e+00 6.1232340e-17 -1.0000000e+00] (接近1, 0, -1)
arr_pos = np.array([1, 4, 9])
print(“平方根:”, np.sqrt(arr_pos)) # [1. 2. 3.]
arr_exp = np.array([0, 1, 2])
print(“指数函数 (e^x):”, np.exp(arr_exp)) # [1. 2.71828183 7.3890561 ]
print(“对数函数 (ln):”, np.log(np.exp(arr_exp))) # [0. 1. 2.] (np.log是自然对数)
“`
8. 广播(Broadcasting)机制
广播是NumPy中一个非常强大且重要的概念。它描述了NumPy如何处理形状不同的数组之间的算术运算。当执行一个通用函数(如加法、乘法等)时,NumPy会尝试在较低维度的数组上“广播”操作,使其能够与较高维度的数组兼容。
理解广播
想象你有一个二维数组(矩阵)和一维数组(向量)。如果你想将向量加到矩阵的每一行上,传统的做法可能需要循环。广播允许NumPy在不实际复制向量的情况下,像向量被“拉伸”或“复制”到与矩阵形状兼容一样进行计算。
广播规则
NumPy遵循一套严格的广播规则:
- 检查维数: 从末尾维度开始比较两个数组的形状。
- 比较维度大小: 如果两个维度大小相同,或其中一个维度大小为1,则它们是兼容的。
- 不兼容: 如果两个维度大小不同且都不为1,则会引发错误。
- 维度较少的数组: 维度较少的数组会在其左侧(前面)填充大小为1的维度,直到它们的维数相同。
例如:
* (2, 3) 形状的数组与 (3,) 形状的数组相加:
* 比较末尾维度:3 和 3,兼容。
* 比较倒数第二维度:2 和 1 (因为 (3,) 广播后变成 (1, 3),左侧填充1),兼容。
* 广播成功。向量 (3,) 会被广播成 (2, 3) 的形状(概念上复制了两次)。
* (2, 3) 形状的数组与 (2,) 形状的数组相加:
* 比较末尾维度:3 和 2,不相同且都不为1,广播失败,引发错误。
* (1, 3) 形状的数组与 (3,) 形状的数组相加:
* 比较末尾维度:3 和 3,兼容。
* 比较倒数第二维度:1 和 1 (因为 (3,) 广播后变成 (1, 3)),兼容。
* 广播成功。(1, 3) 与 (1, 3) 形状相同。
广播示例
“`python
arr2d = np.arange(6).reshape(2, 3)
print(“二维数组 (2×3):\n”, arr2d)
[[0 1 2]
[3 4 5]]
标量与数组广播 (scalar + array)
标量 5 会被广播到数组中的每一个元素
print(“\n数组 + 标量 5:\n”, arr2d + 5)
[[ 5 6 7]
[ 8 9 10]]
一维数组与二维数组广播 (row-wise)
(2, 3) + (3,)
arr1d_row = np.array([10, 20, 30]) # shape (3,)
print(“\n一维数组 (shape (3,)):\n”, arr1d_row)
print(“二维数组 + 一维数组 (广播到行):\n”, arr2d + arr1d_row)
[[10 21 32] (0+10, 1+20, 2+30)
[13 24 35]] (3+10, 4+20, 5+30)
如果想广播到列,需要调整一维数组的形状
(2, 3) + (2, 1)
arr1d_col = np.array([[100], [200]]) # shape (2, 1)
print(“\n一维数组 (shape (2, 1)):\n”, arr1d_col)
print(“二维数组 + 一维数组 (广播到列):\n”, arr2d + arr1d_col)
[[100 101 102] (0+100, 1+100, 2+100)
[300 204 205]] (3+200, 4+200, 5+200)
“`
理解广播是高效使用NumPy的关键,它可以让你写出更简洁、更快速的代码,避免显式的循环。
9. 线性代数操作
NumPy的linalg
模块提供了一些基本的线性代数功能。最常见的是点积和矩阵乘法。
np.dot(a, b)
: 计算两个数组的点积。对于二维数组,就是矩阵乘法。a @ b
: Python 3.5+ 引入的@
运算符,是矩阵乘法的专用符号,等同于np.matmul(a, b)
。
“`python
arr1 = np.array([[1, 2], [3, 4]]) # 2×2
arr2 = np.array([[5, 6], [7, 8]]) # 2×2
arr3 = np.array([1, 2]) # 1×2
print(“\n矩阵1:\n”, arr1)
print(“矩阵2:\n”, arr2)
print(“向量:\n”, arr3)
矩阵乘法 (2×2 * 2×2 -> 2×2)
matrix_mult = arr1 @ arr2
print(“\n矩阵乘法 (arr1 @ arr2):\n”, matrix_mult)
[[15 + 27, 16 + 28],
[35 + 47, 36 + 48]]
[[19 22]
[43 50]]
矩阵与向量乘法 (2×2 * 2 -> 2)
matrix_vector_mult = arr1 @ arr3
print(“矩阵与向量乘法 (arr1 @ arr3):\n”, matrix_vector_mult)
[[11 + 22],
[31 + 42]]
[5 11]
向量点积 (1×2 * 2 -> 标量)
vector_dot = arr3 @ arr3
print(“向量点积 (arr3 @ arr3):”, vector_dot) # 11 + 22 = 5
“`
NumPy的linalg
模块还包含求解线性方程组、计算行列式、逆矩阵、特征值等功能,这些都是进行科学计算时非常有用的工具。
10. 文件I/O:保存与加载数组
NumPy提供了方便的函数来保存和加载数组到文件。
np.save('filename.npy', arr)
: 将数组保存到二进制.npy
文件中。这是NumPy特有的格式,非常高效,能保留数组的形状和dtype信息。np.load('filename.npy')
: 从.npy
文件加载数组。np.savetxt('filename.txt', arr, delimiter=' ')
: 将数组保存为纯文本文件,如.txt
或.csv
。可以指定分隔符。np.loadtxt('filename.txt', delimiter=' ')
: 从纯文本文件加载数组。
“`python
arr_to_save = np.arange(20).reshape(4, 5)
print(“\n待保存的数组:\n”, arr_to_save)
保存为 .npy 文件
np.save(‘my_array.npy’, arr_to_save)
print(“数组已保存到 my_array.npy”)
从 .npy 文件加载
loaded_arr_npy = np.load(‘my_array.npy’)
print(“从 my_array.npy 加载的数组:\n”, loaded_arr_npy)
保存为 .txt 文件 (用逗号分隔)
np.savetxt(‘my_array.csv’, arr_to_save, delimiter=’,’)
print(“\n数组已保存到 my_array.csv”)
从 .csv 文件加载
注意:loadtxt默认加载为浮点数,如果需要整数,可能需要指定dtype
loaded_arr_txt = np.loadtxt(‘my_array.csv’, delimiter=’,’)
print(“从 my_array.csv 加载的数组:\n”, loaded_arr_txt)
或者指定dtype=int
loaded_arr_txt_int = np.loadtxt(‘my_array.csv’, delimiter=’,’, dtype=int)
print(“从 my_array.csv 加载的整数数组:\n”, loaded_arr_txt_int)
``
my_array.npy
当你运行这段代码后,会在当前目录下看到和
my_array.csv这两个文件。
.npy文件是二进制的,不能直接阅读;
.csv` 文件是文本格式,可以用文本编辑器打开查看。
11. NumPy的性能优势
为什么NumPy比纯Python列表和循环快这么多?主要原因在于:
- 向量化 (Vectorization): NumPy的操作(ufunc、聚合函数、广播等)是针对整个数组进行的,而不是单个元素。NumPy在底层使用了优化的、通常是编译好的代码(很多是用C或Fortran编写的)来执行这些操作,避免了Python解释器的开销。
- 内存连续性与类型统一: ndarray的元素在内存中是连续存储的,并且所有元素类型相同。这使得NumPy可以高效地访问和操作数据块,利用现代CPU的缓存优化和SIMD(单指令多数据)指令集。Python列表由于其灵活性,元素可以存储在内存的任何地方,且类型不统一,访问效率较低。
我们来做一个简单的计时对比:
“`python
import time
size = 1000000
使用Python列表
list1 = list(range(size))
list2 = list(range(size))
start_time = time.time()
list_sum = [x + y for x, y in zip(list1, list2)]
end_time = time.time()
print(f”\nPython列表相加耗时: {end_time – start_time:.6f} 秒”)
使用NumPy数组
numpy1 = np.arange(size)
numpy2 = np.arange(size)
start_time = time.time()
numpy_sum = numpy1 + numpy2 # 向量化操作
end_time = time.time()
print(f”NumPy数组相加耗时: {end_time – start_time:.6f} 秒”)
结果会显示NumPy快几个数量级!
“`
这个例子清晰地展示了NumPy在处理大规模数值计算时的巨大性能优势。因此,在进行任何数值计算时,尽量利用NumPy的向量化操作,而不是编写显式的Python循环。
12. 总结与下一步
恭喜你!你已经完成了NumPy的快速入门。我们学习了:
- NumPy的核心:
ndarray
多维数组。 - 如何创建不同类型的数组。
- 如何使用索引、切片、布尔索引和整数数组索引访问和操作数组元素。
- 如何改变数组的形状和组合拆分数组。
- 如何使用NumPy的通用函数进行元素级和聚合计算。
- 强大的广播机制。
- 基本的线性代数操作。
- 数组的保存和加载。
- NumPy相对于Python列表的性能优势。
这只是NumPy的冰山一角。NumPy库非常庞大,还包括了更多模块,如:
- 随机数生成的高级功能(指定种子、各种分布)。
- 更丰富的线性代数(
np.linalg
)。 - 傅里叶变换(
np.fft
)。 - 数学和统计函数大全。
- 处理缺失值(NaN)。
下一步的学习建议:
- 实践! 尝试用NumPy解决一些简单的问题,比如计算一组数据的平均值、标准差,实现一个简单的矩阵乘法,或者处理一些小型数据集。
- 深入了解文档: NumPy官方文档非常详细,是学习和查找功能的最佳资源。
- 学习Pandas: Pandas是构建在NumPy之上的另一个重要库,提供了DataFrame结构,非常适合处理结构化数据(表格数据)。掌握NumPy是学习Pandas的坚实基础。
- 学习Matplotlib: 用于绘制图表,通常与NumPy和Pandas结合使用。
- 探索SciPy: SciPy是科学计算库,提供了更多高级功能,如优化、积分、插值、信号处理等,也严重依赖于NumPy。
NumPy是Python数据科学生态系统的基石。花时间深入学习它,将为你未来在数据分析、机器学习和科学研究道路上打下坚实的基础。
祝你学习顺利!不断练习,你将很快掌握NumPy的强大力量。