文章目录
- NumPy 面向 MATLAB 用户的指南
- NumPy 实用指南
- 如何编写NumPy操作指南
- 文件读写操作
- 读取文本和[CSV](https://en.wikipedia.org/wiki/Comma-separated_values)文件
- 读取.npy或.npz格式文件
- 将数据写入文件供NumPy读取
- 读取任意格式的二进制文件("二进制数据块")
- 读写大型数组
- 为其他(非NumPy)工具编写可读取的文件
- 读写JSON文件
- 使用pickle文件保存/恢复
- 将 pandas DataFrame 转换为 NumPy 数组
- 使用 [`tofile`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.tofile.html#numpy.ndarray.tofile "numpy.ndarray.tofile") 和 [`fromfile`](https://numpy.org/doc/stable/reference/generated/numpy.fromfile.html#numpy.fromfile "numpy.fromfile") 进行保存/恢复
- 如何索引 [`ndarrays`](https://numpy.org/doc/stable/reference/generated/numpy.ndarray.html#numpy.ndarray "numpy.ndarray")
- 验证NumPy中的bug及其修复方案
- 如何创建等间距数值数组
NumPy 面向 MATLAB 用户的指南
https://numpy.org/doc/stable/user/numpy-for-matlab-users.html
简介
MATLAB® 和 NumPy 有许多共同点,但 NumPy 的诞生是为了与 Python 协同工作,而非作为 MATLAB 的克隆版本。本指南将帮助 MATLAB 用户快速上手 NumPy。
主要差异对比
在MATLAB中,基本数据类型(即使是标量)也是多维数组。MATLAB中的数组赋值默认存储为双精度浮点数的二维数组,除非显式指定维度和类型。对这些二维数组的操作基于线性代数中的矩阵运算建模。 | 在NumPy中,基本数据类型是多维array 。NumPy中的数组赋值通常存储为n维数组,采用能容纳序列对象的最小类型,除非显式指定维度和类型。NumPy执行逐元素操作,因此用 * 相乘二维数组不是矩阵乘法——而是逐元素相乘。(自Python 3.5起可用@ 运算符进行常规矩阵乘法。) |
---|---|
MATLAB索引从1开始;a(1) 表示第一个元素。详见索引说明 | NumPy与Python相同,索引从0开始;a[0] 表示第一个元素。 |
MATLAB的脚本语言专为线性代数设计,因此某些数组操作的语法比NumPy更简洁。但另一方面,其添加GUI和创建完整应用程序的API功能相对薄弱。 | NumPy基于通用编程语言Python,其优势在于可调用丰富的Python库,包括:SciPy、Matplotlib、Pandas、OpenCV等。此外,Python常被作为脚本语言嵌入其他软件,使得NumPy也能在这些环境中使用。 |
MATLAB数组切片采用传值语义,通过延迟写入时复制机制避免不必要的副本。切片操作会复制数组部分数据。 | NumPy数组切片采用传引用机制,不会复制参数。切片操作生成的是数组的视图。 |
近似对应关系
下表列出了一些常见 MATLAB 表达式的近似对应 Python 实现。请注意这些是相似表达式而非完全等效,具体细节请参阅参考文档。
在使用下表时,假设您已在 Python 中执行了以下命令:
import numpy as np
from scipy import io, integrate, linalg, signal
from scipy.sparse.linalg import cg, eigs
另外请注意,如果注释中提到"matrix",则表示参数是二维实体。
通用功能对照表
MATLAB | NumPy | 说明 |
---|---|---|
help func | info(func) 或 help(func) 或 func? (在 IPython 中) | 获取函数 func 的帮助信息 |
which func | 参见注释 HELP | 查找 func 的定义位置 |
type func | np.source(func) 或 func?? (在 IPython 中) | 打印 func 的源代码(若非内置函数) |
% comment | # comment | 用文本 comment 注释一行代码 |
for i=1:3 fprintf('%i\n',i) end | for i in range(1, 4): print(i) | 使用 for 循环打印数字 1、2 和 3,通过 range 实现 |
a && b | a and b | 短路逻辑 AND 运算符(Python 原生运算符);仅适用于标量参数 |
a || b | a or b | 短路逻辑 OR 运算符(Python 原生运算符);仅适用于标量参数 |
>> 4 == 4 ans = 1 >> 4 == 5 ans = 0 | >>> 4 == 4 True >>> 4 == 5 False | Python 中的布尔对象是 True 和 False ,而 MATLAB 的逻辑类型为 1 和 0 。 |
a=4 if a==4 fprintf('a = 4\n') elseif a==5 fprintf('a = 5\n') end | a = 4 if a == 4: print('a = 4') elif a == 5: print('a = 5') | 创建 if-else 语句检查 a 是否为 4 或 5,并打印结果 |
1*i , 1*j , 1i , 1j | 1j | 复数 |
eps | np.finfo(float).eps 或 np.spacing(1) | 双精度浮点数中 1 到下一个更大可表示实数的距离 |
load data.mat | io.loadmat('data.mat') | 加载保存到文件 data.mat 的 MATLAB 变量。(注意:在 MATLAB/Octave 中将数组保存到 data.mat 时,请使用最新的二进制格式。scipy.io.loadmat 将创建一个包含保存的数组和其他信息的字典。) |
ode45 | integrate.solve_ivp(f) | 使用 Runge-Kutta 4,5 方法积分 ODE |
ode15s | integrate.solve_ivp(f, method='BDF') | 使用 BDF 方法积分 ODE |
线性代数等价操作
MATLAB | NumPy | 说明 |
---|---|---|
ndims(a) | np.ndim(a) 或 a.ndim | 获取数组 a 的维度数 |
numel(a) | np.size(a) 或 a.size | 获取数组 a 的元素总数 |
size(a) | np.shape(a) 或 a.shape | 获取数组 a 的形状 |
size(a,n) | a.shape[n-1] | 获取数组 a 第 n 维的元素数(注意 MATLAB 使用 1 为起始索引,而 Python 使用 0 为起始索引,参见 索引说明) |
[ 1 2 3; 4 5 6 ] | np.array([[1., 2., 3.], [4., 5., 6.]]) | 定义一个 2x3 的二维数组 |
[ a b; c d ] | np.block([[a, b], [c, d]]) | 从块 a 、b 、c 和 d 构造矩阵 |
a(end) | a[-1] | 访问 MATLAB 向量(1xn 或 nx1)或一维 NumPy 数组 a (长度为 n)的最后一个元素 |
a(2,5) | a[1, 4] | 访问二维数组 a 中第二行第五列的元素 |
a(2,:) | a[1] 或 a[1, :] | 获取二维数组 a 的第二整行 |
a(1:5,:) | a[0:5] 或 a[:5] 或 a[0:5, :] | 获取二维数组 a 的前 5 行 |
a(end-4:end,:) | a[-5:] | 获取二维数组 a 的最后 5 行 |
a(1:3,5:9) | a[0:3, 4:9] | 获取二维数组 a 的第 1 到 3 行和第 5 到 9 列 |
a([2,4,5],[1,3]) | a[np.ix_([1, 3, 4], [0, 2])] | 获取第 2、4、5 行和第 1、3 列。这种方式允许修改矩阵,且不需要规则切片。 |
a(3:2:21,:) | a[2:21:2,:] | 从第三行开始到第二十一行,每隔一行获取 a 的行 |
a(1:2:end,:) | a[::2, :] | 从第一行开始,每隔一行获取 a 的行 |
a(end:-1:1,:) 或 flipud(a) | a[::-1,:] | 将 a 的行顺序反转 |
a([1:end 1],:) | a[np.r_[:len(a),0]] | 将 a 的第一行复制并追加到末尾 |
a.' | a.transpose() 或 a.T | a 的转置 |
a' | a.conj().transpose() 或 a.conj().T | a 的共轭转置 |
a * b | a @ b | 矩阵乘法 |
a .* b | a * b | 逐元素乘法 |
a./b | a/b | 逐元素除法 |
a.^3 | a**3 | 逐元素指数运算 |
(a > 0.5) | (a > 0.5) | 生成一个矩阵,其第 i,j 个元素为 (a_ij > 0.5)。MATLAB 结果为逻辑值 0 和 1 的数组,NumPy 结果为布尔值 False 和 True 的数组。 |
find(a > 0.5) | np.nonzero(a > 0.5) | 找到 (a > 0.5) 的索引 |
a(:,find(v > 0.5)) | a[:,np.nonzero(v > 0.5)[0]] | 提取向量 v > 0.5 对应的 a 的列 |
a(:,find(v>0.5)) | a[:, v.T > 0.5] | 提取列向量 v > 0.5 对应的 a 的列 |
a(a<0.5)=0 | a[a < 0.5]=0 | 将 a 中小于 0.5 的元素置零 |
a .* (a>0.5) | a * (a > 0.5) | 将 a 中小于 0.5 的元素置零 |
a(:) = 3 | a[:] = 3 | 将所有值设为相同的标量值 |
y=x | y = x.copy() | NumPy 默认按引用赋值 |
y=x(2,:) | y = x[1, :].copy() | NumPy 切片默认按引用 |
y=x(:) | y = x.flatten() | 将数组转换为向量(注意这会强制复制)。如需与 MATLAB 相同的数据顺序,使用 x.flatten('F') 。 |
1:10 | np.arange(1., 11.) 或 np.r_[1.:11.] 或 np.r_[1:10:10j] | 创建递增向量(参见 范围说明) |
0:9 | np.arange(10.) 或 np.r_[:10.] 或 np.r_[:9:10j] | 创建递增向量(参见 范围说明) |
[1:10]' | np.arange(1.,11.)[:, np.newaxis] | 创建列向量 |
zeros(3,4) | np.zeros((3, 4)) | 创建一个 3x4 的二维数组,填充 64 位浮点零 |
zeros(3,4,5) | np.zeros((3, 4, 5)) | 创建一个 3x4x5 的三维数组,填充 64 位浮点零 |
ones(3,4) | np.ones((3, 4)) | 创建一个 3x4 的二维数组,填充 64 位浮点一 |
eye(3) | np.eye(3) | 创建一个 3x3 的单位矩阵 |
diag(a) | np.diag(a) | 返回二维数组 a 的对角线元素向量 |
diag(v,0) | np.diag(v, 0) | 返回一个方阵对角线矩阵,其非零值为向量 v 的元素 |
rng(42,'twister') rand(3,4) | from numpy.random import default_rng rng = default_rng(42) rng.random((3, 4)) 或旧版本: random.rand((3, 4)) | 使用默认随机数生成器和种子 42 生成一个 3x4 的随机数组 |
linspace(1,3,4) | np.linspace(1,3,4) | 在 1 和 3 之间生成 4 个等间距的样本(包含端点) |
[x,y]=meshgrid(0:8,0:5) | np.mgrid[0:9.,0:6.] 或 np.meshgrid(r_[0:9.],r_[0:6.]) | 生成两个二维数组:一个为 x 值,另一个为 y 值 |
ogrid[0:9.,0:6.] 或 np.ix_(np.r_[0:9.],np.r_[0:6.] | 在网格上评估函数的最佳方式 | |
[x,y]=meshgrid([1,2,4],[2,4,5]) | np.meshgrid([1,2,4],[2,4,5]) | |
np.ix_([1,2,4],[2,4,5]) | 在网格上评估函数的最佳方式 | |
repmat(a, m, n) | np.tile(a, (m, n)) | 创建 a 的 m x n 个副本 |
[a b] | np.concatenate((a,b),1) 或 np.hstack((a,b)) 或 np.column_stack((a,b)) 或 np.c_[a,b] | 按列拼接 a 和 b |
[a; b] | np.concatenate((a,b)) 或 np.vstack((a,b)) 或 np.r_[a,b] | 按行拼接 a 和 b |
max(max(a)) | a.max() 或 np.nanmax(a) | 获取 a 的最大元素(MATLAB 要求 ndims(a)<=2,如果有 NaN,nanmax 会忽略并返回最大值) |
max(a) | a.max(0) | 获取数组 a 每列的最大元素 |
max(a,[],2) | a.max(1) | 获取数组 a 每行的最大元素 |
max(a,b) | np.maximum(a, b) | 逐元素比较 a 和 b ,返回每对中的最大值 |
norm(v) | np.sqrt(v @ v) 或 np.linalg.norm(v) | 向量 v 的 L2 范数 |
a & b | logical_and(a,b) | 逐元素 AND 运算符(NumPy 通用函数)参见逻辑运算说明 |
a | b | np.logical_or(a,b) | 逐元素 OR 运算符(NumPy 通用函数)参见逻辑运算说明 |
bitand(a,b) | a & b | 按位 AND 运算符(Python 原生及 NumPy 通用函数) |
bitor(a,b) | a | b | 按位 OR 运算符(Python 原生及 NumPy 通用函数) |
inv(a) | linalg.inv(a) | 二维方阵 a 的逆矩阵 |
pinv(a) | linalg.pinv(a) | 二维数组 a 的伪逆矩阵 |
rank(a) | np.linalg.matrix_rank(a) | 二维数组 a 的矩阵秩 |
a\b | linalg.solve(a, b) (若 a 为方阵);否则 linalg.lstsq(a, b) | 求解 a x = b 的 x |
b/a | 改为求解 a.T x.T = b.T | 求解 x a = b 的 x |
[U,S,V]=svd(a) | U, S, Vh = linalg.svd(a); V = Vh.T | a 的奇异值分解 |
chol(a) | linalg.cholesky(a) | 二维数组的 Cholesky 分解 |
[V,D]=eig(a) | D,V = linalg.eig(a) | a 的特征值
λ
\lambda
λ 和特征向量
v
v
v,满足
a
v
=
λ
v
av = \lambda v
av=λv |
[V,D]=eig(a,b) | D,V = linalg.eig(a, b) | a 和 b 的特征值
λ
\lambda
λ和特征向量
v
v
v,满足
a
v
=
λ
b
v
a v = \lambda b v
av=λbv |
[V,D]=eigs(a,3) | D,V = eigs(a, k=3) | 找到二维数组 a 的 k=3 个最大特征值和特征向量 |
[Q,R]=qr(a,0) | Q,R = linalg.qr(a) | QR 分解 |
[L,U,P]=lu(a) 其中 a==P'*L*U | P,L,U = linalg.lu(a) 其中 a == P@L@U | 带部分主元的 LU 分解(注意:P(MATLAB) == transpose(P(NumPy))) |
conjgrad | cg | 共轭梯度求解器 |
fft(a) | np.fft.fft(a) | a 的傅里叶变换 |
ifft(a) | np.fft.ifft(a) | a 的逆傅里叶变换 |
sort(a) | np.sort(a) 或 a.sort(axis=0) | 对二维数组 a 的每列排序 |
sort(a, 2) | np.sort(a, axis=1) 或 a.sort(axis=1) | 对二维数组 a 的每行排序 |
[b,I]=sortrows(a,1) | I = np.argsort(a[:, 0]); b = a[I,:] | 将数组 a 按第一列排序后保存为 b |
x = Z\y | x = linalg.lstsq(Z, y) | 执行线性回归 $ Zx = y$ |
decimate(x, q) | signal.resample(x, np.ceil(len(x)/q)) | 使用低通滤波进行下采样 |
unique(a) | np.unique(a) | 返回数组 a 中的唯一值向量 |
squeeze(a) | a.squeeze() | 移除数组 a 的单例维度。注意 MATLAB 始终返回 2D 或更高维数组,而 NumPy 可能返回 0D 或更高维数组 |
注意事项
子矩阵赋值:可以通过ix_
命令结合索引列表对子矩阵进行赋值。例如,对于二维数组a
,可以这样操作:ind=[1, 3]; a[np.ix_(ind, ind)] += 100
。
帮助命令:Python没有直接对应MATLAB的which
命令,但使用help
命令通常会显示函数所在的文件名。Python还提供了inspect
模块(需先执行import inspect
),其中的getfile
方法通常也能实现类似功能。
索引差异:
- MATLAB采用基于1的索引,序列的第一个元素索引为1
- Python采用基于0的索引,序列的第一个元素索引为0
这种差异常引发争论,因为各有优劣。基于1的索引更符合日常语言习惯("第一个"元素对应索引1),而基于0的索引能简化索引计算。更多讨论可参考Edsger W. Dijkstra教授的文章。
范围表示:
- 在MATLAB中,
0:5
既可作为范围字面量,也可作为切片索引(用在圆括号内) - 在Python中,
0:5
这类表达式只能作为切片索引(用在方括号内)
为此NumPy设计了特殊的r_
对象来实现简洁的范围构造机制。注意r_
不是通过函数调用,而是通过方括号索引来使用,这样可以在参数中使用Python的切片语法。
逻辑运算符:
- NumPy中的
&
和|
是位与/位或运算 - MATLAB中的
&
和|
是逻辑与/逻辑或运算
虽然表面相似但有重要区别:
1、非布尔值{0,1}输入时:
- NumPy输出按位运算结果(如
3 & 4
得0
) - MATLAB将任何非零值视为1进行逻辑运算(
3
和4
都视为真,3 & 4
得1
)
2、运算符优先级: - NumPy的
&
优先级高于比较运算符(如<
和>
) - MATLAB的优先级顺序相反
如果确定参数是布尔值,可以使用NumPy的位运算符,但要注意括号使用,如:z = (x > 1) & (x < 2)
。NumPy没有直接提供logical_and
和logical_or
的运算符形式是Python设计上的一个遗憾。
重塑与线性索引:
- MATLAB始终允许使用标量或线性索引访问多维数组
- NumPy不支持这种特性
MATLAB程序中常用线性索引(如矩阵的find()
会返回线性索引),而NumPy的find行为不同。转换MATLAB代码时可能需要:
1、先将矩阵重塑为线性序列
2、执行索引操作
3、再重塑回原形状
由于reshape通常生成视图而非副本,这个过程可以高效完成。需注意:
- NumPy的reshape默认使用’C’顺序
- MATLAB使用Fortran顺序
如果只是临时转换为线性序列再转回,顺序差异不影响结果。但如果转换依赖扫描顺序的MATLAB代码,例如MATLAB中的:
z = reshape(x,3,4);
在NumPy中应改为:
z = x.reshape(3,4,order='F').copy()
该用 ‘array’ 还是 ‘matrix’?
从历史沿革来看,NumPy 曾提供过特殊的矩阵类型 np.matrix,它是 ndarray 的子类,专门用于线性代数二元运算。在某些现有代码中,你可能会看到它被使用而非 np.array。那么究竟该选择哪一种呢?
简短回答
推荐使用数组。
- 它们支持MATLAB风格的多维数组运算
- 作为NumPy的标准向量/矩阵/张量类型,多数NumPy函数返回的是数组而非矩阵
- 能明确区分逐元素运算和线性代数运算
- 既支持标准向量,也可按需使用行向量/列向量
在Python 3.5之前,使用数组类型的唯一缺点是必须用dot
而非*
进行张量乘法运算(标量积、矩阵向量乘法等)。自Python 3.5起,可直接使用矩阵乘法运算符@
。
基于上述优势,我们计划最终弃用matrix
类型。
详细解答
NumPy 同时包含 array
类和 matrix
类。array
类旨在作为通用型 n 维数组,适用于多种数值计算场景,而 matrix
类则专门用于简化线性代数运算。实际上,两者之间仅存在几个关键区别:
- 运算符
*
和@
、函数dot()
与multiply()
:- 对于
array
,*
表示逐元素乘法,而@
表示矩阵乘法;对应的函数分别是multiply()
和dot()
(在 Python 3.5 之前,@
运算符不存在,必须使用dot()
进行矩阵乘法)。 - 对于
matrix
,*
表示矩阵乘法,进行逐元素乘法时需使用multiply()
函数。
- 对于
- 向量处理(一维数组):
- 对于
array
,形状为 1xN、Nx1 和 N 的向量是完全不同的对象。例如A[:,1]
会返回形状为 N 的一维数组,而非 Nx1 的二维数组。对一维array
进行转置操作不会产生任何变化。 - 对于
matrix
,一维数组总是被提升为 1xN 或 Nx1 矩阵(行向量或列向量)。A[:,1]
会返回形状为 Nx1 的二维矩阵。
- 对于
- 高维数组处理(ndim > 2):
array
对象支持维度数大于 2;matrix
对象始终严格保持二维。
- 便捷属性:
array
具有 .T 属性,返回数据的转置;matrix
还具有 .H、.I 和 .A 属性,分别返回矩阵的共轭转置、逆矩阵以及asarray()
转换结果。
- 便捷构造器:
array
构造器接受(嵌套的)Python 序列作为初始化参数,例如array([[1,2,3],[4,5,6]])
;matrix
构造器额外支持字符串初始化,例如matrix("[1 2 3; 4 5 6]")
。
两者各有优劣:
array
::)
逐元素乘法更直观:A*B
;:(
需记住矩阵乘法需使用专用运算符@
;:)
一维数组可灵活视为行向量或列向量。A @ v
将v
视作列向量,而v @ A
视作行向量,减少转置操作;:)
作为 NumPy 的"默认"类型,测试覆盖最全面,第三方代码也最可能返回此类型;:)
天然支持任意维度的数据处理;:)
语义上更接近张量代数(若熟悉该领域);:)
所有运算符(*
,/
,+
,-
等)均为逐元素操作;:(
与scipy.sparse
的稀疏矩阵交互性较差。
matrix
::\\
行为更接近 MATLAB 矩阵;<:(
仅支持二维。处理三维数据需改用array
或matrix
组成的 Python 列表;<:(
必须使用单列或单行矩阵表示向量;<:(
由于array
是默认类型,某些函数可能返回array
即使输入是matrix
(NumPy 原生函数应保持类型一致,但第三方代码可能不保证);:)
A*B
直接表示矩阵乘法,与线性代数书写习惯一致(Python >= 3.5 的普通数组通过@
运算符也可实现);<:(
逐元素乘法需调用函数multiply(A,B)
;<:(
运算符重载逻辑不一致:*
非逐元素操作但/
却是;- 与
scipy.sparse
的交互更简洁。
因此,强烈建议优先使用 array
。事实上,NumPy 官方计划最终弃用 matrix
类型。
环境自定义配置
在MATLAB中,自定义环境的主要方式是通过修改搜索路径来添加常用函数的位置。您可以将这些自定义设置放入MATLAB启动时自动运行的startup
脚本中。
NumPy(或者说Python)也提供了类似的功能:
- 若要在Python搜索路径中添加自定义模块的位置,需设置
PYTHONPATH
环境变量 - 若要在启动交互式Python解释器时自动执行特定脚本,需将
PYTHONSTARTUP
环境变量指向您的启动脚本名称
与MATLAB不同(路径上的函数可直接调用),Python需要先使用import
语句才能访问特定文件中的函数。
例如,您可以创建如下启动脚本(注意:以下仅为示例,不代表"最佳实践"):
# Make all numpy available via shorter 'np' prefix
import numpy as np
#
# Make the SciPy linear algebra functions available as linalg.func()
# e.g. linalg.lu, linalg.eig (for general l*B@u==A@u solution)
from scipy import linalg
#
# Define a Hermitian function
def hermitian(A, **kwargs):
return np.conj(A,**kwargs).T
# Make a shortcut for hermitian:
# hermitian(A) --> H(A)
H = hermitian
要使用已弃用的 matrix 和其他 matlib 函数:
# Make all matlib functions accessible at the top level via M.func()
import numpy.matlib as M
# Make some matlib functions accessible directly at the top level via, e.g. rand(3,3)
from numpy.matlib import matrix,rand,zeros,ones,empty,eye
链接
另一个稍显过时的 MATLAB/NumPy 交叉参考可以在 http://mathesaurus.sf.net/ 找到。
关于 Python 科学计算的丰富工具列表,请参阅 专题软件页面。
查看 Python 软件列表:脚本语言 了解使用 Python 作为脚本语言的软件列表。
MATLAB® 和 SimuLink® 是 The MathWorks, Inc. 的注册商标。
NumPy 实用指南
https://numpy.org/doc/stable/user/howtos_index.html
这些文档旨在提供使用 NumPy 完成常见任务的实用方法。如需查看包中包含的函数和类的详细参考文档,请参阅 API 参考。
如何编写NumPy操作指南
操作指南应直击要点——它们:
- 回答一个具体问题,或
- 将宽泛问题拆解为用户可选的具体子问题
一位陌生人向我问路…
“我需要给我的车加油。”
提供简洁明确的回答
- “前行三公里/英里,在干草籽路右转,目的地就在左侧。”
为新来者补充有用细节(即使三公里处只有干草籽路一个转弯点)。但避免无关信息:
- 不要同时提供从7号公路出发的路线
- 不要解释为什么镇上只有一个加油站
若有相关背景资料(教程、说明、参考或替代方案),通过链接引导用户获取(如"从7号公路出发的路线"、“为何加油站如此稀少?”)。
委托
- “前行三公里/英里,在干草籽路右转,跟随指示牌。”
如果信息已有文档记录且足够简洁,适合作为操作指南,只需直接链接到它(可以在简短的介绍后,如“前行三公里/英里,右转”)。
如果问题过于宽泛,请收窄并重定向
“我想游览景点。”
游览景点指南应链接到一组更具体的子指南:
- 寻找历史建筑
- 寻找观景台
- 寻找市中心
而这些子指南可能进一步链接到更细分的指南——例如市中心页面可能包含:
- 寻找法院
- 寻找市政厅
通过这种层级化的指南组织方式,你不仅为需要细化问题的用户展示了选项,同时也为那些直接提出具体问题的用户提供了答案(例如"我想看历史建筑"、“市政厅怎么走?”)。
如果步骤过多,请进行拆分
当操作指南包含多个步骤时:
- 考虑将某个步骤单独拆分为一个独立指南,并通过链接引用它。
- 使用子标题。这有助于读者快速了解后续内容,并能从上次中断处继续阅读。
既然有Stack Overflow、Reddit、Gitter等平台,为何还要编写操作指南?
- 我们提供权威解答
- 操作指南能降低非专业人士的使用门槛
- 操作指南能吸引用户访问网站,并帮助他们发现站内其他资源
- 编写操作指南让我们能以新视角审视NumPy的易用性
操作指南和教程是一回事吗?
人们经常混用"操作指南"和"教程"这两个术语,但我们遵循Daniele Procida的文档分类法,对两者进行了明确区分。
文档需要适应用户的实际需求。操作指南提供完成任务的信息——用户只需要可复制的步骤,而不一定需要理解NumPy的原理。教程则提供感性认知——用户希望获得对NumPy某些方面的直观感受(同样地,他们可能并不关心深层原理)。
我们将教程和操作指南与以下两类文档区分开来:解释说明旨在提供深入理解而非即时帮助;参考手册则提供NumPy具体部分(如API)的完整权威数据,但无需构建整体认知。
关于教程的更多信息,请参阅学习编写NumPy教程
这个页面是操作指南的示例吗?
是的——直到带有问号标题的部分为止;这些部分主要是解释而非提供操作指引。在操作指南中,这些内容通常会以链接形式呈现。
文件读写操作
https://numpy.org/devdocs/user/how-to-io.html
本页介绍常见应用场景;如需查看完整的I/O例程集合,请参阅输入输出章节。
读取文本和CSV文件
无缺失值情况
使用 numpy.loadtxt
。
处理缺失值
使用 numpy.genfromtxt
。
numpy.genfromtxt
会执行以下操作之一:
- 返回一个掩码数组,屏蔽缺失值(当
usemask=True
时) - 或者用指定值填充缺失值(通过
filling_values
参数指定,默认浮点型用np.nan
,整型用 -1)
使用非空白分隔符
>>> with open("csv.txt", "r") as f:
... print(f.read())
1, 2, 3
4,, 6
7, 8, 9
掩码数组输出
>>> np.genfromtxt("csv.txt", delimiter=",", usemask=True)
masked_array(
data=[[1.0, 2.0, 3.0],
[4.0, --, 6.0],
[7.0, 8.0, 9.0]], mask=[[False, False, False],
[False, True, False],
[False, False, False]], fill_value=1e+20)
数组输出
>>> np.genfromtxt("csv.txt", delimiter=",")
array([[ 1., 2., 3.],
[ 4., nan, 6.],
[ 7., 8., 9.]])
数组输出,指定填充值
>>> np.genfromtxt("csv.txt", delimiter=",", dtype=np.int8, filling_values=99)
array([[ 1, 2, 3],
[ 4, 99, 6],
[ 7, 8, 9]], dtype=int8)
以空格分隔
numpy.genfromtxt
也可以解析包含缺失值的空格分隔数据文件,前提是:
- 每个字段具有固定宽度:将宽度作为 delimiter 参数传入。
# File with width=4. The data does not have to be justified (for example,
# the 2 in row 1), the last column can be less than width (for example, the 6
# in row 2), and no delimiting character is required (for instance 8888 and 9
# in row 3)
>>> with open("fixedwidth.txt", "r") as f:
... data = (f.read())
>>> print(data)
1 2 3
44 6
7 88889
# Showing spaces as ^
>>> print(data.replace(" ","^"))
1^^^2^^^^^^3
44^^^^^^6
7^^^88889
>>> np.genfromtxt("fixedwidth.txt", delimiter=4)
array([[1.000e+00, 2.000e+00, 3.000e+00],
[4.400e+01, nan, 6.000e+00],
[7.000e+00, 8.888e+03, 9.000e+00]])
- 特殊值(如“x”)表示缺失字段:将其用作 missing_values 参数。
>>> with open("nan.txt", "r") as f:
... print(f.read())
1 2 3
44 x 6
7 8888 9
>>> np.genfromtxt("nan.txt", missing_values="x")
array([[1.000e+00, 2.000e+00, 3.000e+00],
[4.400e+01, nan, 6.000e+00],
[7.000e+00, 8.888e+03, 9.000e+00]])
- 您希望跳过包含缺失值的行:设置
invalid_raise=False
。
>>> with open("skip.txt", "r") as f:
... print(f.read())
1 2 3
44 6
7 888 9
>>> np.genfromtxt("skip.txt", invalid_raise=False)
__main__:1: ConversionWarning: Some errors were detected !
Line #2 (got 2 columns instead of 3)
array([[ 1., 2., 3.],
[ 7., 888., 9.]])
- 分隔符空白字符与表示缺失数据的空白字符不同。例如,如果列以
\t
分隔,那么当缺失数据由一个或多个空格组成时,系统会将其识别为缺失值。
>>> with open("tabs.txt", "r") as f:
... data = (f.read())
>>> print(data)
1 2 3
44 6
7 888 9
# Tabs vs. spaces
>>> print(data.replace("\t","^"))
1^2^3
44^ ^6
7^888^9
>>> np.genfromtxt("tabs.txt", delimiter="\t", missing_values=" +")
array([[ 1., 2., 3.],
[ 44., nan, 6.],
[ 7., 888., 9.]])
读取.npy或.npz格式文件
可选方法:
- 使用
numpy.load
。该函数可以读取由以下任一函数生成的文件:
numpy.save
、numpy.savez
或numpy.savez_compressed
。 - 使用内存映射方式。参见
numpy.lib.format.open_memmap
。
将数据写入文件供NumPy读取
二进制存储
使用 numpy.save
保存单个数组,或使用 numpy.savez
和 numpy.savez_compressed
保存多个数组。
出于安全性和可移植性考虑,除非数据类型包含必须通过 pickle 处理的 Python 对象,否则应将 allow_pickle=False
设为禁用状态。
注意:掩码数组 目前无法保存
,其他自定义数组子类同样不支持此功能。
人类可读格式
numpy.save
和 numpy.savez
会生成二进制文件。若要写入人类可读的文件,请使用 numpy.savetxt
。该数组必须是一维或二维的,且没有用于多个文件的 savetxtz
功能。
大型数组处理
请参阅如何读写大型数组。
读取任意格式的二进制文件(“二进制数据块”)
使用结构化数组。
示例:
.wav
文件头部是一个44字节的数据块,位于实际声音数据的data_size
字节之前:
chunk_id "RIFF"
chunk_size 4-byte unsigned little-endian integer
format "WAVE"
fmt_id "fmt "
fmt_size 4-byte unsigned little-endian integer
audio_fmt 2-byte unsigned little-endian integer
num_channels 2-byte unsigned little-endian integer
sample_rate 4-byte unsigned little-endian integer
byte_rate 4-byte unsigned little-endian integer
block_align 2-byte unsigned little-endian integer
bits_per_sample 2-byte unsigned little-endian integer
data_id "data"
data_size 4-byte unsigned little-endian integer
.wav
文件头作为 NumPy 结构化 dtype:
wav_header_dtype = np.dtype([
("chunk_id", (bytes, 4)), # flexible-sized scalar type, item size 4
("chunk_size", "<u4"), # little-endian unsigned 32-bit integer
("format", "S4"), # 4-byte string, alternate spelling of (bytes, 4)
("fmt_id", "S4"), ("fmt_size", "<u4"), ("audio_fmt", "<u2"), #
("num_channels", "<u2"), # .. more of the same ...
("sample_rate", "<u4"), #
("byte_rate", "<u4"), ("block_align", "<u2"), ("bits_per_sample", "<u2"), ("data_id", "S4"), ("data_size", "<u4"), #
# the sound data itself cannot be represented here:
# it does not have a fixed size
])
header = np.fromfile(f, dtype=wave_header_dtype, count=1)[0]
这个 .wav
示例仅用于演示;实际读取 .wav
文件时,请使用 Python 内置模块 wave
。
(改编自 Pauli Virtanen 的 Advanced NumPy,采用 CC BY 4.0 许可协议。)
读写大型数组
内存无法容纳的大型数组可以通过内存映射技术像普通内存数组一样处理。
- 使用
numpy.ndarray.tofile
或numpy.ndarray.tobytes
写入的原始数组数据,可以通过numpy.memmap
读取:
array = numpy.memmap("mydata/myarray.arr", mode="r", dtype=np.int16, shape=(1024, 1024))
使用 numpy.save
输出的文件(即采用 NumPy 格式存储的文件)可以通过 numpy.load
配合 mmap_mode
关键字参数进行读取。
large_array[some_slice] = np.load("path/to/small_array", mmap_mode="r")
内存映射缺乏数据分块和压缩等功能;可与NumPy配合使用的功能更全面的格式及库包括:
- HDF5: h5py 或 PyTables
- Zarr: 此处
- NetCDF:
scipy.io.netcdf_file
关于memmap、Zarr和HDF5之间的权衡比较,请参阅
pythonspeed.com。
为其他(非NumPy)工具编写可读取的文件
与其他工具交换数据的格式包括HDF5、Zarr和NetCDF(参见写入或读取大型数组)。
读写JSON文件
NumPy数组和大多数NumPy标量不能直接进行JSON序列化。建议使用自定义的json.JSONEncoder
来处理NumPy类型,您可以通过常用搜索引擎找到相关实现方案。
使用pickle文件保存/恢复
尽量避免使用;pickles无法防范错误或恶意构造的数据。
推荐使用numpy.save
和numpy.load
。除非数组dtype包含Python对象(此时必须使用pickle),否则应将allow_pickle
参数设为False。
numpy.load
和pickle
子模块还支持读取NumPy 1.26版本创建的pickle文件。
将 pandas DataFrame 转换为 NumPy 数组
使用 tofile
和 fromfile
进行保存/恢复
通常建议优先使用 numpy.save
和 numpy.load
。
numpy.ndarray.tofile
和 numpy.fromfile
会丢失字节序和精度信息,因此仅适用于临时存储场景。
如何索引 ndarrays
另请参阅:ndarrays的索引方法
本页介绍常见示例。如需深入了解索引机制,请参考ndarrays的索引方法。
访问特定/任意行和列
>>> a = np.arange(30).reshape(2, 3, 5)
>>> a
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]],
[[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]]])
>>> a[0, 2, :]
array([10, 11, 12, 13, 14])
>>> a[0, :, 3]
array([ 3, 8, 13])
请注意,索引操作的输出可能与原始对象的形状不同。若要在索引后保留原始维度,可以使用 newaxis
。如需使用其他类似工具,请参阅维度索引工具。
>>> a[0, :, 3].shape
(3,)
>>> a[0, :, 3, np.newaxis].shape
(3, 1)
>>> a[0, :, 3, np.newaxis, np.newaxis].shape
(3, 1, 1)
变量也可以用于索引:
>>> y = 0
>>> a[y, :, y+3]
array([ 3, 8, 13])
请参考处理程序中可变数量的索引了解如何在索引变量中使用
索引列
要为列创建索引,必须对最后一个轴进行索引操作。使用维度索引工具来获取所需的维度数量:
>>> a = np.arange(24).reshape(2, 3, 4)
>>> a
array([[[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]],
[[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]]])
>>> a[..., 3]
array([[ 3, 7, 11],
[15, 19, 23]])
要索引每列中的特定元素,请使用高级索引方法,如下所示:
>>> arr = np.arange(3*4).reshape(3, 4)
>>> arr
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
>>> column_indices = [[1, 3], [0, 2], [2, 2]]
>>> np.arange(arr.shape[0])
array([0, 1, 2])
>>> row_indices = np.arange(arr.shape[0])[:, np.newaxis]
>>> row_indices
array([[0],
[1],
[2]])
使用 row_indices
和 column_indices
进行高级索引:
>>> arr[row_indices, column_indices]
array([[ 1, 3],
[ 4, 6],
[10, 10]])
沿特定轴索引
使用 take
。另请参阅 take_along_axis
和 put_along_axis
。
>>> a = np.arange(30).reshape(2, 3, 5)
>>> a
array([[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14]],
[[15, 16, 17, 18, 19],
[20, 21, 22, 23, 24],
[25, 26, 27, 28, 29]]])
>>> np.take(a, [2, 3], axis=2)
array([[[ 2, 3],
[ 7, 8],
[12, 13]],
[[17, 18],
[22, 23],
[27, 28]]])
>>> np.take(a, [2], axis=1)
array([[[10, 11, 12, 13, 14]],
[[25, 26, 27, 28, 29]]])
创建大型矩阵的子集
使用切片与步进方法来访问大数组的数据块:
>>> a = np.arange(100).reshape(10, 10)
>>> a
array([[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
[20, 21, 22, 23, 24, 25, 26, 27, 28, 29],
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39],
[40, 41, 42, 43, 44, 45, 46, 47, 48, 49],
[50, 51, 52, 53, 54, 55, 56, 57, 58, 59],
[60, 61, 62, 63, 64, 65, 66, 67, 68, 69],
[70, 71, 72, 73, 74, 75, 76, 77, 78, 79],
[80, 81, 82, 83, 84, 85, 86, 87, 88, 89],
[90, 91, 92, 93, 94, 95, 96, 97, 98, 99]])
>>> a[2:5, 2:5]
array([[22, 23, 24],
[32, 33, 34],
[42, 43, 44]])
>>> a[2:5, 1:3]
array([[21, 22],
[31, 32],
[41, 42]])
>>> a[:5, :5]
array([[ 0, 1, 2, 3, 4],
[10, 11, 12, 13, 14],
[20, 21, 22, 23, 24],
[30, 31, 32, 33, 34],
[40, 41, 42, 43, 44]])
同样的操作可以通过稍微复杂些的高级索引方式实现。需要注意的是:
>>> a[np.arange(5)[:, None], np.arange(5)[None, :]]
array([[ 0, 1, 2, 3, 4],
[10, 11, 12, 13, 14],
[20, 21, 22, 23, 24],
[30, 31, 32, 33, 34],
[40, 41, 42, 43, 44]])
你也可以使用 mgrid
来生成索引:
>>> indices = np.mgrid[0:6:2]
>>> indices
array([0, 2, 4])
>>> a[:, indices]
array([[ 0, 2, 4],
[10, 12, 14],
[20, 22, 24],
[30, 32, 34],
[40, 42, 44],
[50, 52, 54],
[60, 62, 64],
[70, 72, 74],
[80, 82, 84],
[90, 92, 94]])
过滤值
非零元素
使用 nonzero
获取一个元组,其中包含对应每个维度的非零元素的数组索引:
>>> z = np.array([[1, 2, 3, 0], [0, 0, 5, 3], [4, 6, 0, 0]])
>>> z
array([[1, 2, 3, 0],
[0, 0, 5, 3],
[4, 6, 0, 0]])
>>> np.nonzero(z)
(array([0, 0, 0, 1, 1, 2, 2]), array([0, 1, 2, 2, 3, 0, 1]))
使用 flatnonzero
获取 ndarray 展平版本中非零元素的索引:
>>> np.flatnonzero(z)
array([0, 1, 2, 6, 7, 8, 9])
任意条件筛选
使用 where
根据条件生成索引,然后结合高级索引进行操作。
>>> a = np.arange(30).reshape(2, 3, 5)
>>> indices = np.where(a % 2 == 0)
>>> indices
(array([0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1]), array([0, 0, 0, 1, 1, 2, 2, 2, 0, 0, 1, 1, 1, 2, 2]), array([0, 2, 4, 1, 3, 0, 2, 4, 1, 3, 0, 2, 4, 1, 3]))
>>> a[indices]
array([ 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28])
或者,使用布尔数组索引:
>>> a > 14
array([[[False, False, False, False, False],
[False, False, False, False, False],
[False, False, False, False, False]],
[[ True, True, True, True, True],
[ True, True, True, True, True],
[ True, True, True, True, True]]])
>>> a[a > 14]
array([15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29])
过滤后替换值
通过带过滤条件的赋值操作来替换目标值:
>>> p = np.arange(-10, 10).reshape(2, 2, 5)
>>> p
array([[[-10, -9, -8, -7, -6],
[ -5, -4, -3, -2, -1]],
[[ 0, 1, 2, 3, 4],
[ 5, 6, 7, 8, 9]]])
>>> q = p < 0
>>> q
array([[[ True, True, True, True, True],
[ True, True, True, True, True]],
[[False, False, False, False, False],
[False, False, False, False, False]]])
>>> p[q] = 0
>>> p
array([[[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]],
[[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]]])
获取最大值/最小值的索引
>>> a = np.arange(30).reshape(2, 3, 5)
>>> np.argmax(a)
29
>>> np.argmin(a)
0
使用 axis
关键字获取沿特定轴的最大值和最小值的索引
>>> np.argmax(a, axis=0)
array([[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1],
[1, 1, 1, 1, 1]])
>>> np.argmax(a, axis=1)
array([[2, 2, 2, 2, 2],
[2, 2, 2, 2, 2]])
>>> np.argmax(a, axis=2)
array([[4, 4, 4],
[4, 4, 4]])
>>> np.argmin(a, axis=1)
array([[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0]])
>>> np.argmin(a, axis=2)
array([[0, 0, 0],
[0, 0, 0]])
将 keepdims
设置为 True
,可以在结果中保留被缩减的轴作为大小为1的维度:
>>> np.argmin(a, axis=2, keepdims=True)
array([[[0],
[0],
[0]],
[[0],
[0],
[0]]])
>>> np.argmax(a, axis=1, keepdims=True)
array([[[2, 2, 2, 2, 2]],
[[2, 2, 2, 2, 2]]])
要获取N维数组中每个(N-1)维数组的最大值或最小值索引,可以按照以下步骤操作:
1、使用reshape
将数组重塑为二维数组
2、沿axis=1
应用argmax
或argmin
3、通过unravel_index
恢复每个切片的原始索引值
>>> x = np.arange(2*2*3).reshape(2, 2, 3) % 7 # 3D example array
>>> x
array([[[0, 1, 2],
[3, 4, 5]],
[[6, 0, 1],
[2, 3, 4]]])
>>> x_2d = np.reshape(x, (x.shape[0], -1))
>>> indices_2d = np.argmax(x_2d, axis=1)
>>> indices_2d
array([5, 0])
>>> np.unravel_index(indices_2d, x.shape[1:])
(array([1, 0]), array([2, 0]))
返回的第一个数组包含原始数组中沿轴1的索引,第二个数组包含沿轴2的索引。因此,x[0]
中的最大值是x[0, 1, 2]
。
高效多次索引同一ndarray
需要牢记的是,基础索引会生成视图,而高级索引会生成副本,后者在计算效率上较低。因此,应当尽可能使用基础索引而非高级索引。
延伸阅读
Nicolas Rougier 的 100 道 NumPy 练习题 深入展示了索引如何与其他操作结合使用。以下练习题特别聚焦于索引操作:
第6题(创建大小为10的空向量,但第五个元素为1)、
第8题(反转向量)、
第10题(找出非零元素的索引)、
第15题(创建边界为1、内部为0的二维数组)、
第16题(为现有数组添加0值边框)、
第19题(创建8x8棋盘格矩阵)、
第20题(计算678形状数组中第100个元素的xyz索引)、
第45题(替换随机向量中的最大值)、
第59题(按第n列排序数组)、
第64题(通过索引向量实现条件加1)、
第65题(基于索引列表累加向量元素)、
第70题(构建插入连续零值的新向量)、
第71题(多维数组乘法)、
第72题(交换数组行)、
第76题(构建位移二维数组)、
第80题(提取固定形状的子数组)、
第81题(生成滑动窗口数组)、
第84题(提取连续3x3区块)、
第87题(计算4x4区块求和)、
第90题(构建笛卡尔积)、
第93题(查找包含特定元素的行)、
第94题(提取含不等值的行)。
验证NumPy中的bug及其修复方案
在本指南中,您将学习如何:
- 验证NumPy中是否存在某个bug
- 验证针对该bug的修复方案(如果有)
通过整个验证流程,您将掌握:
- 如何搭建Python虚拟环境(使用
virtualenv
) - 安装特定版本的NumPy:先复现bug现象,再验证修复效果
我们以Issue 16354为例进行说明。
该问题的具体表现为:
标题:当输入全零参数时,np.polymul返回类型错误地变为np.float64或np.complex128
当其中一个参数全零且两个参数类型为np.int64或np.float32时,np.polymul会返回np.float64类型的对象。类似地,全零的np.complex64参数会导致返回np.complex128类型的结果。
非零参数不会出现此问题,此时返回类型符合预期。
注意:np.convolve函数不存在此bug。
复现代码示例:
>>> import numpy as np
>>> np.__version__
'1.18.4'
>>> a = np.array([1,2,3])
>>> z = np.array([0,0,0])
>>> np.polymul(a.astype(np.int64), a.astype(np.int64)).dtype
dtype('int64')
>>> np.polymul(a.astype(np.int64), z.astype(np.int64)).dtype
dtype('float64')
>>> np.polymul(a.astype(np.float32), z.astype(np.float32)).dtype
dtype('float64')
>>> np.polymul(a.astype(np.complex64), z.astype(np.complex64)).dtype
dtype('complex128')
Numpy/Python version information:
>>> import sys, numpy; print(numpy.__version__, sys.version)
1.18.4 3.7.5 (default, Nov 7 2019, 10:50:52) [GCC 8.3.0]
1、设置虚拟环境
新建一个目录,进入该目录后,使用你偏好的方法创建虚拟环境。例如,在Linux或macOS系统上使用virtualenv
可以这样操作:
virtualenv venv_np_bug
source venv_np_bug/bin/activate
这样可以确保不会修改系统/全局/默认的 Python/NumPy 安装。
2、安装问题报告对应的NumPy版本
报告中提到的是NumPy 1.18.4版本,因此您需要安装这个特定版本。
由于该错误与发布版本相关而非特定提交,通过pip
在虚拟环境中安装预构建的wheel包即可满足需求:
pip install numpy==1.18.4
某些错误可能需要您构建问题报告中引用的NumPy版本。要了解如何操作,请参阅从源代码构建。
3、复现该错误
在 #16354 中报告的问题是:如果 numpy.polymul
方法的某个输入为零数组,则会返回错误的 dtype
。
要复现该错误,请启动 Python 终端,输入错误报告中显示的代码片段,并确保结果与问题中的描述一致:
>>> import numpy as np
>>> np.__version__
'...' # 1.18.4
>>> a = np.array([1,2,3])
>>> z = np.array([0,0,0])
>>> np.polymul(a.astype(np.int64), a.astype(np.int64)).dtype
dtype('int64')
>>> np.polymul(a.astype(np.int64), z.astype(np.int64)).dtype
dtype('...') # float64
>>> np.polymul(a.astype(np.float32), z.astype(np.float32)).dtype
dtype('...') # float64
>>> np.polymul(a.astype(np.complex64), z.astype(np.complex64)).dtype
dtype('...') # complex128
据报告,当零数组(如上例中的 z
)作为参数传递给 numpy.polymul
时,会返回错误的 dtype
。
4、检查最新版NumPy中的修复情况
如果您的错误问题报告尚未解决,则需要进一步提交操作或补丁。
但在本例中,该问题已通过
PR 17577得到解决并已关闭。因此
您可以尝试验证该修复。
验证修复的步骤如下:
1、卸载仍存在该bug的NumPy版本:
pip uninstall numpy
安装最新版本的NumPy:
pip install numpy
3、在您的Python终端中,运行之前用于验证该bug存在的代码片段,确认问题是否已解决:
>>> import numpy as np
>>> np.__version__
'...' # 1.18.4
>>> a = np.array([1,2,3])
>>> z = np.array([0,0,0])
>>> np.polymul(a.astype(np.int64), a.astype(np.int64)).dtype
dtype('int64')
>>> np.polymul(a.astype(np.int64), z.astype(np.int64)).dtype
dtype('int64')
>>> np.polymul(a.astype(np.float32), z.astype(np.float32)).dtype
dtype('float32')
>>> np.polymul(a.astype(np.complex64), z.astype(np.complex64)).dtype
dtype('complex64')
请注意,即使当零数组作为参数之一传递给 numpy.polymul
时,现在也能正确返回 dtype
。
5、通过验证和修复bug支持NumPy开发
前往NumPy GitHub问题页面,查看是否能确认尚未被确认的其他bug。特别是对于开发者来说,了解某个bug能否在新版NumPy中复现非常有价值。
验证bug存在的评论能提醒NumPy开发者:该问题可以被多个用户复现。
如何创建等间距数值数组
NumPy中有几个功能相似但结果略有差异的函数,如果不清楚何时以及如何使用它们,可能会引起混淆。以下指南旨在列出这些函数并说明它们的推荐用法。
涉及的函数包括:
一维域(区间)
linspace
与 arange
对比
numpy.linspace
和 numpy.arange
都提供了将区间(一维域)划分为等长子区间的方法。这些划分会根据选择的起点、终点以及步长(子区间的长度)而变化。
- 当需要整数步长时,使用
numpy.arange
。
numpy.arange
依赖于步长来确定返回数组中的元素数量,且不包含终点。这是通过 arange
的 step
参数来决定的。
示例:
>>> np.arange(0, 10, 2) # np.arange(start, stop, step)
array([0, 2, 4, 6, 8])
参数 start
和 stop
应为整数或实数,但不能是复数。numpy.arange
与 Python 内置的 range
类似。
浮点数精度问题可能导致 arange
在处理浮点数时产生令人困惑的结果。这种情况下,应改用 numpy.linspace
。
- 当需要结果包含终点值,或使用非整数步长时,请使用
numpy.linspace
。
numpy.linspace
可以包含终点值,并通过 num 参数(指定返回数组的元素数量)自动计算步长。
是否包含终点由可选布尔参数 endpoint
控制,默认值为 True
。注意:设置 endpoint=False
将改变步长计算方式,从而影响函数的输出结果。
示例:
>>> np.linspace(0.1, 0.2, num=5) # np.linspace(start, stop, num)
array([0.1 , 0.125, 0.15 , 0.175, 0.2 ])
>>> np.linspace(0.1, 0.2, num=5, endpoint=False)
array([0.1, 0.12, 0.14, 0.16, 0.18])
numpy.linspace
也可以用于复数参数:
>>> np.linspace(1+1.j, 4, 5, dtype=np.complex64)
array([1、 +1.j , 1.75+0.75j, 2.5 +0.5j , 3.25+0.25j, 4、 +0.j ],
dtype=complex64)
其他示例
1、如果在numpy.arange
中使用浮点数值作为step
参数,可能会出现意外结果。为避免这种情况,请确保所有浮点转换都在计算结果之后进行。例如,替换
>>> list(np.arange(0.1,0.4,0.1).round(1))
[0.1, 0.2, 0.3, 0.4] # endpoint should not be included!
为:
>>> list(np.arange(1, 4, 1) / 10.0)
[0.1, 0.2, 0.3] # expected result
2、请注意
>>> np.arange(0, 1.12, 0.04)
array([0、 , 0.04, 0.08, 0.12, 0.16, 0.2 , 0.24, 0.28, 0.32, 0.36, 0.4 , 0.44, 0.48, 0.52, 0.56, 0.6 , 0.64, 0.68, 0.72, 0.76, 0.8 , 0.84, 0.88, 0.92, 0.96, 1、 , 1.04, 1.08, 1.12])
和
>>> np.arange(0, 1.08, 0.04)
array([0、 , 0.04, 0.08, 0.12, 0.16, 0.2 , 0.24, 0.28, 0.32, 0.36, 0.4 , 0.44, 0.48, 0.52, 0.56, 0.6 , 0.64, 0.68, 0.72, 0.76, 0.8 , 0.84, 0.88, 0.92, 0.96, 1、 , 1.04])
这些差异源于数值噪声。当使用浮点数值时,可能会出现0 + 0.04 * 28 < 1.12
的情况,因此1.12
会被包含在区间内。实际上,这正是当前的情况:
>>> 1.12/0.04
28.000000000000004
但是 0 + 0.04 * 27 >= 1.08
因此 1.08 被排除在外:
>>> 1.08/0.04
27.0
或者,你可以使用 np.arange(0, 28)*0.04
,这样能始终精确控制终点值,因为它是整数形式的。
>>> np.arange(0, 28)*0.04
array([0、 , 0.04, 0.08, 0.12, 0.16, 0.2 , 0.24, 0.28, 0.32, 0.36, 0.4 , 0.44, 0.48, 0.52, 0.56, 0.6 , 0.64, 0.68, 0.72, 0.76, 0.8 , 0.84, 0.88, 0.92, 0.96, 1、 , 1.04, 1.08])
geomspace
与 logspace
numpy.geomspace
类似于 numpy.linspace
,但数值在 log 尺度上均匀分布(几何级数)。结果中包含端点。
示例:
>>> np.geomspace(2, 3, num=5)
array([2、 , 2.21336384, 2.44948974, 2.71080601, 3、 ])
numpy.logspace
与 numpy.geomspace
类似,但起始点和结束点是以对数形式指定的(默认以10为底):
>>> np.logspace(2, 3, num=5)
array([ 100、 , 177.827941 , 316.22776602, 562.34132519, 1000、 ])
在线性空间中,序列从 base ** start
(base
的 start
次方)开始,到 base ** stop
结束:
>>> np.logspace(2, 3, num=5, base=2)
array([4、 , 4.75682846, 5.65685425, 6.72717132, 8、 ])
N维域
N维域可以被划分为网格。这可以通过以下函数之一来实现。
meshgrid
numpy.meshgrid
的作用是通过一组一维坐标数组创建矩形网格。
给定数组:
>>> x = np.array([0, 1, 2, 3])
>>> y = np.array([0, 1, 2, 3, 4, 5])
meshgrid
将创建两个坐标数组,可用于生成确定该网格的坐标对。
>>> xx, yy = np.meshgrid(x, y)
>>> xx
array([[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3],
[0, 1, 2, 3]])
>>> yy
array([[0, 0, 0, 0],
[1, 1, 1, 1],
[2, 2, 2, 2],
[3, 3, 3, 3],
[4, 4, 4, 4],
[5, 5, 5, 5]])
>>> import matplotlib.pyplot as plt
>>> plt.plot(xx, yy, marker='.', color='k', linestyle='none')
mgrid
numpy.mgrid
可作为创建网格的快捷方式。它并非一个函数,但当被索引时,会返回一个多维网格。
>>> xx, yy = np.meshgrid(np.array([0, 1, 2, 3]), np.array([0, 1, 2, 3, 4, 5]))
>>> xx.T, yy.T
(array([[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2],
[3, 3, 3, 3, 3, 3]]), array([[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5]]))
>>> np.mgrid[0:4, 0:6]
array([[[0, 0, 0, 0, 0, 0],
[1, 1, 1, 1, 1, 1],
[2, 2, 2, 2, 2, 2],
[3, 3, 3, 3, 3, 3]],
[[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5],
[0, 1, 2, 3, 4, 5]]])
ogrid
与numpy.mgrid
类似,numpy.ogrid
返回一个开放的多维网格。这意味着当对其进行索引时,每个返回数组只有一个维度大于1。这样可以避免数据重复,从而节省内存空间,这通常是理想的效果。
这些稀疏坐标网格旨在与广播机制配合使用。当所有坐标都用于表达式时,广播仍会产生一个全维度的结果数组。
>>> np.ogrid[0:4, 0:6]
(array([[0],
[1],
[2],
[3]]), array([[0, 1, 2, 3, 4, 5]]))
这里描述的三种方法都可用于在网格上评估函数值。
>>> g = np.ogrid[0:4, 0:6]
>>> zg = np.sqrt(g[0]**2 + g[1]**2)
>>> g[0].shape, g[1].shape, zg.shape
((4, 1), (1, 6), (4, 6))
>>> m = np.mgrid[0:4, 0:6]
>>> zm = np.sqrt(m[0]**2 + m[1]**2)
>>> np.array_equal(zm, zg)
True
2025-05-10(六)