适用于 MATLAB 用户的 NumPy#

简介#

MATLAB® 和 NumPy 有很多共同点,但 NumPy 是为与 Python 协作而创建的,而不是 MATLAB 的克隆。本指南将帮助 MATLAB 用户开始使用 NumPy。

一些主要区别#

在 MATLAB 中,基本类型(即使是标量)也是多维数组。MATLAB 中的数组赋值通常存储为双精度浮点数的二维数组,除非您指定维度数量和类型。对这些数组的二维实例的操作是基于线性代数中的矩阵操作进行建模的。

在 NumPy 中,基本类型是多维 array。NumPy 中的数组赋值通常存储为 n 维数组,其类型为按顺序存储对象所需的最小类型,除非您指定维度数量和类型。NumPy 执行元素级操作,因此使用 * 乘以二维数组不是矩阵乘法,而是元素级乘法。(从 Python 3.5 起,@ 运算符可用于传统的矩阵乘法。)

MATLAB 的索引从 1 开始;a(1) 是第一个元素。参见注释 INDEXING

NumPy 和 Python 一样,索引从 0 开始;a[0] 是第一个元素。

MATLAB 的脚本语言是为线性代数而创建的,因此某些数组操作的语法比 NumPy 更紧凑。另一方面,用于添加 GUI 和创建完整应用程序的 API 或多或少是后来才考虑的。

NumPy 基于 Python,这是一种通用语言。NumPy 的优势在于可以访问包括 SciPyMatplotlibPandasOpenCV 等在内的 Python 库。此外,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

参见注释 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 循环和 range 打印数字 1、2 和 3

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 中的布尔对象TrueFalse,而 MATLAB 的逻辑类型是 10

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).epsnp.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 的索引,参见注释 INDEXING

[ 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]])

从块 abcd 构造一个矩阵

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 的第一行到第三行以及第五列到第九列。

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 结果是布尔值 FalseTrue 的数组。

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]

创建一个递增向量(参见注释 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))

填充 64 位浮点零的 3x4 二维数组

zeros(3,4,5)

np.zeros((3, 4, 5))

填充 64 位浮点零的 3x4x5 三维数组

ones(3,4)

np.ones((3, 4))

填充 64 位浮点 1 的 3x4 二维数组

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))

使用默认随机数生成器和 seed = 42 生成一个 3x4 随机数组

linspace(1,3,4)

np.linspace(1,3,4)

1 到 3 之间(含 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]

连接 ab 的列

[a; b]

np.concatenate((a,b))np.vstack((a,b))np.r_[a,b]

连接 ab 的行

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)

逐元素比较 ab,并返回每对中的最大值

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)

逐元素 OR 运算符(NumPy ufunc) 参见注释 LOGICOPS

bitand(a,b)

a & b

位运算符 AND(Python 内置和 NumPy ufunc)

bitor(a,b)

a | b

位运算符 OR(Python 内置和 NumPy ufunc)

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 中 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\),其中 \(\mathbf{a} v = \lambda v\)

[V,D]=eig(a,b)

D,V = linalg.eig(a, b)

ab 的特征值 \(\lambda\) 和特征向量 \(v\),其中 \(\mathbf{a} v = \lambda \mathbf{b} v\)

[V,D]=eigs(a,3)

D,V = eigs(a, k=3)

查找二维数组 ak=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)

执行 \(\mathbf{Zx}=\mathbf{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 命令通常会列出函数所在的文件名。Python 也有一个 inspect 模块(执行 import inspect),它提供一个通常有效的 getfile 函数。

索引:MATLAB 使用基于 1 的索引,因此序列的第一个元素的索引是 1。Python 使用基于 0 的索引,因此序列的第一个元素的索引是 0。混淆和争论的产生是因为两者各有优缺点。基于 1 的索引与人类语言的常见用法一致,即序列的“第一个”元素的索引是 1。基于 0 的索引简化了索引操作。另请参阅 Edsger W. Dijkstra 教授的一篇文章

范围:在 MATLAB 中,0:5 可以用作范围字面量和“切片”索引(在括号内);然而,在 Python 中,像 0:5 这样的构造只能用作切片索引(在方括号内)。因此,创建了有些古怪的 r_ 对象,以使 NumPy 具有类似简洁的范围构造机制。请注意,r_ 不像函数或构造函数那样被调用,而是通过方括号进行索引,这允许在参数中使用 Python 的切片语法。

逻辑运算符:NumPy 中的 &| 是按位 AND/OR,而 MATLAB 中的 &| 是逻辑 AND/OR。两者看起来可能工作相同,但存在重要差异。如果您使用 MATLAB 的 &| 运算符,则应使用 NumPy 的 ufunc logical_and/logical_or。MATLAB 和 NumPy 的 &| 运算符之间的显著差异是

  • 非逻辑 {0,1} 输入:NumPy 的输出是输入的按位 AND。MATLAB 将任何非零值视为 1 并返回逻辑 AND。例如,NumPy 中的 (3 & 4)0,而在 MATLAB 中,34 都被认为是逻辑真,(3 & 4) 返回 1

  • 优先级:NumPy 的 & 运算符的优先级高于 <> 等逻辑运算符;MATLAB 的则相反。

如果您确定使用布尔参数,则可以使用 NumPy 的按位运算符,但请注意括号,例如:z = (x > 1) & (x < 2)。NumPy 缺少 logical_andlogical_or 的运算符形式是 Python 设计的一个不幸结果。

RESHAPE 和线性索引:MATLAB 总是允许使用标量或线性索引访问多维数组,而 NumPy 不允许。线性索引在 MATLAB 程序中很常见,例如,对矩阵执行 find() 会返回它们,而 NumPy 的 find 行为不同。转换 MATLAB 代码时,可能需要先将矩阵重塑为线性序列,执行一些索引操作,然后再重塑回去。由于 reshape(通常)会生成相同存储的视图,因此应该可以相当高效地完成此操作。请注意,NumPy 中 reshape 使用的扫描顺序默认为“C”顺序,而 MATLAB 使用的 Fortran 顺序。如果您只是将其转换为线性序列并再转换回来,则无关紧要。但是,如果您正在转换依赖于扫描顺序的 MATLAB 代码中的 reshape,那么 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()

    • 对于 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 @ vv 视为列向量,而 v @ Av 视为行向量。这可以省去您输入大量转置操作。

    • :) array 是 NumPy 的“默认”类型,因此它得到了最多的测试,并且是使用 NumPy 的第三方代码最有可能返回的类型。

    • :) 擅长处理任何维数的数据。

    • :) 如果您熟悉张量代数,它在语义上更接近张量代数。

    • :) 所有操作(*, /, +, - 等)都是元素级的。

    • :( scipy.sparse 中的稀疏矩阵与数组的交互性不佳。

  • matrix

    • :\\ 行为更像 MATLAB 矩阵。

    • <:( 最大二维。要保存三维数据,您需要 array 或者可能是包含 matrix 的 Python 列表。

    • <:( 最小二维。您不能有向量。它们必须被转换为单列或单行矩阵。

    • <:( 由于 array 是 NumPy 中的默认设置,即使您向某些函数传递 matrix 作为参数,它们也可能返回 array。NumPy 函数不应发生这种情况(如果发生,那是错误),但基于 NumPy 的第三方代码可能无法像 NumPy 那样保持类型。

    • :) A*B 是矩阵乘法,所以它看起来就像您在线性代数中写的那样(对于 Python >= 3.5,普通数组使用 @ 运算符也具有同样的便利性)。

    • <:( 元素级乘法需要调用函数 multiply(A,B)

    • <:( 运算符重载的使用有点不合逻辑:* 不进行元素级操作,但 / 可以。

    • scipy.sparse 的交互更清晰一些。

因此,array 更值得推荐使用。事实上,我们最终打算弃用 matrix

自定义环境#

在 MATLAB 中,用于自定义环境的主要工具是修改搜索路径,以包含您喜欢的函数的位置。您可以将这些自定义放入 MATLAB 启动时运行的启动脚本中。

NumPy,或者更确切地说是 Python,也具有类似的功能。

  • 要修改您的 Python 搜索路径以包含您自己的模块的位置,请定义 PYTHONPATH 环境变量。

  • 要在交互式 Python 解释器启动时执行特定的脚本文件,请定义 PYTHONSTARTUP 环境变量,使其包含您的启动脚本的名称。

与 MATLAB 不同,在 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