简介
MATLAB®和NumPy有很多共同之处,但NumPy是为Python开发的,而不是MATLAB的克隆版本。本指南将帮助MATLAB用户入门NumPy。
一些关键区别
在MATLAB中,即使对于标量,基本类型也是多维数组。在MATLAB中,数组赋值存储为2D数组,其元素为双精度浮点数,除非您指定维数和类型。对这些数组的2D实例的操作是基于线性代数中的矩阵操作建模的。 | 在NumPy中,基本类型是多维array 。在NumPy中,数组赋值通常存储为n维数组,其类型是能够容纳序列中的对象所需的最小类型,除非您指定维数和类型。NumPy执行逐元素操作,因此使用* 乘法运算符进行的2D数组相乘不是矩阵乘法,而是逐元素乘法。(自Python 3.5起提供的@ 运算符可用于传统的矩阵乘法。) |
---|---|
MATLAB从1开始编号索引;a(1) 是第一个元素。请参阅索引注释 | NumPy和Python一样,从0开始编号索引;a[0] 是第一个元素。 |
MATLAB的脚本语言是为线性代数而创建的,因此某些数组操作的语法比NumPy的更简洁。另一方面,用于添加GUI和创建完整应用程序的API或多或少是事后想法。 | NumPy基于Python,是一种通用语言。NumPy的优势在于可以访问Python库,包括:SciPy,Matplotlib,Pandas,OpenCV等。此外,Python通常被嵌入为脚本语言在其他软件中,也可以在那里使用NumPy。 |
MATLAB数组切片使用传值语义,并采用延迟写时复制方案,以防止在需要之前创建副本。切片操作会复制数组的部分。 | NumPy数组切片使用传引用,不会复制参数。切片操作是对数组的视图。 |
大致等价物
下表给出了一些常见MATLAB表达式的大致等价物。这些是类似的表达式,而不是完全等价的。有关详细信息,请参阅文档。
在下表中,假设您已在Python中执行了以下命令:
import numpy as np
from scipy import io, integrate, linalg, signal
from scipy.sparse.linalg import cg, eigs
还假设下面的说明中如果提到“矩阵”,则参数是二维实体。
通用等价物
MATLAB | NumPy | 说明 |
---|---|---|
help func | info(func) 或help(func) 或func? (在IPython中) | 获取有关函数func的帮助信息 |
which func | 请参阅帮助注释 | 查找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) | 使用range 的for循环打印数字1、2和3 |
a && b | a and b | 短路逻辑与运算符(Python原生运算符);仅适用于标量参数 |
`a | b` | |
>> 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的2D数组 |
[ a b; c d ] | np.block([[a, b], [c, d]]) | 从块a 、b 、c 和d 构造矩阵 |
a(end) | a[-1] | 访问MATLAB向量(1xn或nx1)或1D NumPy数组a (长度为n)的最后一个元素 |
a(2,5) | a[1, 4] | 访问2D数组a 中第二行第五列的元素 |
a(2,:) | a[1] 或a[1, :] | 2D数组a 的整个第二行 |
a(1:5,:) | a[0:5] 或a[:5] 或a[0:5, :] | 2D数组a 的前5行 |
a(end-4:end,:) | a[-5:] | 2D数组a 的最后5行 |
a(1:3,5:9) | a[0:3, 4:9] | 2D数组a 的第1到第3行和第5到第9列 |
a([2,4,5],[1,3]) | a[np.ix_([1, 3, 4], [0, 2])] | 2D数组a 的第2、4和5行以及第1和第3列。这样可以修改矩阵,并且不需要常规切片。 |
a(3:2:21,:) | a[2:21: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 个元素是否大于 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]] | 提取 a 中向量 v > 0.5 的列 |
a(:,find(v>0.5)) | a[:, v.T > 0.5] | 提取 a 中列向量 v > 0.5 的列 |
a(a<0.5)=0 | a[a < 0.5]=0 | 将小于 0.5 的元素替换为 0 |
a .* (a>0.5) | a * (a > 0.5) | 将小于 0.5 的元素替换为 0 |
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] | 创建一个递增的向量(参见注释RANGES) |
0:9 | np.arange(10.) 或 np.r_[:10.] 或 np.r_[:9:10j] | 创建一个递增的向量(参见注释RANGES) |
[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 行 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,如果存在 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 ufunc)参见注释 LOGICOPS |
`a | b` | np.logical_or(a,b) |
bitand(a,b) | a & b | 按位 AND 运算符(Python 原生和 NumPy ufunc) |
bitor(a,b) | `a | b` |
inv(a) | linalg.inv(a) | 二维方阵 a 的逆矩阵 |
pinv(a) | linalg.pinv(a) | 二维数组 a 的伪逆矩阵 |
rank(a) | np.linalg.matrix_rank(a) | 二维数组 a 的矩阵秩 |
a\b | 如果 a 是方阵,则 linalg.solve(a, b) ;否则为 linalg.lstsq(a, b) | 解方程 a x = b,其中 a 是方阵;否则为最小二乘解 |
b/a | 解方程 a.T x.T = b.T | 解方程 x a = b |
[U,S,V]=svd(a) | U, S, Vh = linalg.svd(a); V = Vh.T | 二维数组 a 的奇异值分解 |
chol(a) | linalg.cholesky(a) | 二维数组 a 的 Cholesky 分解 |
[V,D]=eig(a) | D,V = linalg.eig(a) | 二维数组 a 的特征值和特征向量 |
[V,D]=eig(a,b) | D,V = linalg.eig(a, b) | 二维数组 a 和 b 的特征值和特征向量 |
[V,D]=eigs(a,3) | D,V = eigs(a, k=3) | 二维数组 a 的前 k=3 个最大特征值和特征向量 |
[Q,R]=qr(a,0) | Q,R = linalg.qr(a) | 二维数组 a 的 QR 分解 |
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) | 执行形如 Z\y 的线性回归 |
decimate(x, q) | signal.resample(x, np.ceil(len(x)/q)) | 通过低通滤波进行下采样 |
unique(a) | np.unique(a) | 返回数组 a 中的唯一值向量 |
squeeze(a) | a.squeeze() | 去除数组 a 的单维度。请注意,MATLAB 总是返回二维或更高维的数组,而 NumPy 返回零维或更高维的数组 |
注意事项
子矩阵:可以使用 ix_
命令和索引列表对子矩阵进行赋值。例如,对于二维数组 a
,可以执行以下操作:ind=[1, 3]; a[np.ix_(ind, ind)] +=100
。
帮助:没有直接等价于 MATLAB 的 which
命令,但是命令 help
和 numpy.source
通常会列出函数所在的文件名。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 中的 &
和 |
是逻辑与/或运算符。两者看起来可能工作相同,但存在重要的区别。如果您使用的是 MATLAB 的 &
或 |
运算符,则应使用 NumPy 的 ufunc logical_and
/logical_or
。MATLAB 的 &
和 |
运算符之间的显着区别包括:
- 非逻辑 {0,1} 输入:NumPy 的输出是输入的按位与。MATLAB 将任何非零值视为 1,并返回逻辑与。例如,在 NumPy 中,
(3 & 4)
是0
,而在 MATLAB 中,3
和4
都被视为逻辑 true,(3 & 4)
返回1
。 - 优先级:NumPy 的
&
运算符优先级高于<
和>
等逻辑运算符;MATLAB 的优先级相反。
如果您知道参数是布尔值,可以使用 NumPy 的按位运算符,但是要小心使用括号,例如:z = (x > 1) & (x < 2)
。NumPy 中缺少 logical_and
和 logical_or
的运算符形式是 Python 设计的一个不幸后果。
重塑和线性索引:MATLAB 总是允许使用标量或线性索引访问多维数组,而 NumPy 不允许。线性索引在 MATLAB 程序中很常见,例如,在矩阵上执行 find()
将返回它们,而 NumPy 的 find()
表现不同。在转换 MATLAB 代码时,可能需要首先将矩阵重塑为线性序列,执行一些索引操作,然后再重塑回来。由于重塑(通常)产生对相同存储的视图,因此应该可以以相当高效的方式执行此操作。请注意,NumPy 中的重塑的扫描顺序默认为“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
将v
视为行向量。这可以节省大量转置的输入。:)
array
是默认的 NumPy 类型,因此它得到了最多的测试,并且是最有可能由使用 NumPy 的第三方代码返回的类型。:)
可以很好地处理任意维度的数据。:)
与张量代数更接近,如果您熟悉的话。:)
所有操作(*
、/
、+
、-
等)都是逐元素的。:(
来自scipy.sparse
的稀疏矩阵与数组的互动效果不佳。
matrix
:\\
行为更像 MATLAB 矩阵。<:(
二维的最大。要保存三维数据,您需要使用array
或可能是matrix
的 Python 列表。<:(
二维的最小。您不能有向量。它们必须被转换为单列或单行矩阵。<:(
由于array
是 NumPy 中的默认类型,因此一些函数可能会返回一个array
,即使您将matrix
作为参数传递给它们。NumPy 函数不应该发生这种情况(如果发生了,那是一个错误),但是基于 NumPy 的第三方代码可能不像 NumPy 那样保留类型。
<:(
逐元素相乘需要调用函数multiply(A,B)
。<:(
运算符重载有点不太合理:*
不是逐元素操作,但/
是。- 与
scipy.sparse
的交互更加清晰。
因此,更建议使用 array
。实际上,我们最终打算弃用 matrix
。
自定义您的环境
在 MATLAB 中,用于自定义环境的主要工具是通过修改搜索路径来指定您喜爱函数的位置。您可以将这些自定义内容放入启动脚本中,MATLAB 将在启动时运行该脚本。
NumPy,或者说 Python,也有类似的功能。
- 要修改 Python 的搜索路径以包含自己模块的位置,请定义
PYTHONPATH
环境变量。 - 要在交互式 Python 解释器启动时执行特定脚本文件,请定义
PYTHONSTARTUP
环境变量,其中包含您启动脚本的名称。
与 MATLAB 不同,Python 中需要先执行 ‘import’ 语句才能让特定文件中的函数可用。
例如,您可以创建一个类似以下的启动脚本(注意:这只是一个示例,不是“最佳实践”的陈述):
# 通过更短的 'np' 前缀使所有 numpy 可用
import numpy as np
#
# 使 SciPy 线性代数函数可用,如 linalg.func()
# 例如 linalg.lu, linalg.eig(用于一般 l*B@u==A@u 解法)
from scipy import linalg
#
# 定义一个共轭函数
def hermitian(A, **kwargs):
return np.conj(A,**kwargs).T
# 为 hermitian 定义一个快捷方式:
# hermitian(A) --> H(A)
H = hermitian
要使用已弃用的 matrix 和其他 matlib 函数:
# 通过 M.func() 使所有 matlib 函数在顶层可用
import numpy.matlib as M
# 通过从 numpy.matlib 导入 matrix,rand,zeros,ones,empty,eye,使一些 matlib 函数直接在顶层可用,例如 rand(3,3)
from numpy.matlib import matrix,rand,zeros,ones,empty,eye
链接
可以在 http://mathesaurus.sf.net/ 找到另一个有些过时的 MATLAB/NumPy 交叉参考。
可以在 topical software page 找到一个关于科学工作使用 Python 的工具的广泛列表。
请参阅 List of Python software: scripting 查看使用 Python 作为脚本语言的软件列表。
MATLAB® 和 SimuLink® 是 The MathWorks, Inc. 的注册商标。