NumPy for MATLAB 用户#

介绍#

MATLAB® 和 NumPy 有很多共同点,但 NumPy 是为了与 Python 一起工作而创建的,并不是为了成为 MATLAB 的克隆.本指南将帮助 MATLAB 用户开始使用 NumPy.

一些关键差异#

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

在 NumPy 中,基本类型是多维 array.NumPy 中的数组赋值通常存储为 n 维数组,其类型为能容纳序列中对象的最小类型,除非你指定维数和类型.NumPy 执行的操作是逐元素进行的,因此使用 * 乘以 2D 数组不是矩阵乘法——而是逐元素乘法.(自 Python 3.5 起可用的 @ 运算符可用于传统的矩阵乘法.)

MATLAB 的索引从 1 开始;``a(1)`` 是第一个元素.:ref:见注释 INDEXING

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

MATLAB 的脚本语言是为线性代数创建的,因此某些数组操作的语法比 NumPy 的更紧凑.另一方面,用于添加图形用户界面和创建成熟的应用程序的 API 或多或少是事后才想到的.

NumPy 基于 Python,一种通用编程语言.NumPy 的优势在于可以访问包括:SciPyMatplotlibPandas`OpenCV <https://opencv.org/>>`_ 等在内的 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

查看帮助注释

找出 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 b

短路逻辑与运算符 (Python 原生运算符);仅限标量参数

a || b

a b

短路逻辑或运算符 (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 时,请使用最新的二进制格式.:func:scipy.io.loadmat 将创建一个包含保存的数组和进一步信息的字典.)

ode45

integrate.solve_ivp(f)

使用 Runge-Kutta 4,5 积分一个常微分方程

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

从块 a, b, cd 构建一个矩阵

a(end)

a[-1]

在 MATLAB 向量(1xn 或 nx1)或 1D NumPy 数组 ``a``(长度 n)中访问最后一个元素

a(2,5)

a[1, 4]

访问二维数组 a 中第二行、第五列的元素

a(2,:)

a[1]a[1, :]

2D 数组 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]]

提取 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

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

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

zeros(3,4,5)

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

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

ones(3,4)

np.ones((3, 4))

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

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

生成一个随机 3x4 数组,使用默认随机数生成器且种子 = 42

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

按位与运算符(Python 原生和 NumPy ufunc)

bitor(a,b)

a | b

按位或运算符(Python 原生和 NumPy ufunc)

inv(a)

linalg.inv(a)

平方二维数组 a 的逆

pinv(a)

linalg.pinv(a)

2D 数组 a 的伪逆

rank(a)

np.linalg.matrix_rank(a)

二维数组 a 的矩阵秩

a\b

linalg.solve(a, b) 如果 a 是方阵;否则为 linalg.lstsq(a, b)

求解 x 使得 a x = b

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)

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

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

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

特征值 \(\lambda\) 和特征向量 \(v\)a, b 其中 \(\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) == 转置(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_ 命令的索引列表来完成.例如,对于 2D 数组 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的索引`简化了索引 <https://groups.google.com/group/comp.lang.python/msg/1bf4d925dfbf368?q=g:thl3498076713d&hl=en>`__.另见 Edsger W. Dijkstra 教授的一篇文章.

范围: 在 MATLAB 中,``0:5`` 既可以用作范围字面量,也可以用作 ‘切片’ 索引(在括号内);然而,在 Python 中,像 0:5 这样的构造只能用作切片索引(在方括号内).因此,创建了有点奇特的 r_ 对象,以允许 NumPy 拥有一个类似简洁的范围构造机制.注意,``r_`` 不是像函数或构造器那样调用,而是使用方括号进行 索引,这允许在参数中使用 Python 的切片语法.

LOGICOPS: 在 NumPy 中,``&`` 或 | 是按位与/或,而在 MATLAB 中 & 和 | 是逻辑与/或.两者看起来可能工作方式相同,但存在重要差异.如果你使用了 MATLAB 的 &| 运算符,你应该使用 NumPy 的 ufuncs logical_and/logical_or.MATLAB 和 NumPy 的 &| 运算符之间显著的差异是:

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

  • 优先级:NumPy 的 & 运算符比 <> 这样的逻辑运算符优先级更高;MATLAB 的则相反.

如果你知道你有布尔参数,你可以使用 NumPy 的按位运算符,但要小心括号,像这样:z = (x > 1) & (x < 2).NumPy 缺少 logical_andlogical_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().

‘数组’ 还是 ‘矩阵’?我应该使用哪一个?#

从历史上看,NumPy 提供了一种特殊的矩阵类型,`np.matrix`,它是 ndarray 的子类,使得二元操作成为线性代数操作.你可能会在一些现有代码中看到它的使用,而不是 np.array.那么,应该使用哪一个呢?

简答#

Use arrays.

  • 它们支持在 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 @ vv 视为列向量,而 v @ Av 视为行向量.这可以节省你输入大量转置的时间.

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

    • :) 在处理任意数量的数据维度时非常得心应手.

    • :) 在语义上更接近张量代数,如果你熟悉的话.

    • :) 所有 操作(*,``/,``+,``-`` 等)都是按元素进行的.

    • :( 来自 scipy.sparse 的稀疏矩阵与数组的交互不是很好.

  • matrix

    • :\ 行为更像 MATLAB 矩阵.

    • <:( 最多二维.要保存三维数据,你需要 array 或者可能是一个 matrix 的 Python 列表.

    • <:( 至少是二维的.你不能有向量.它们必须被转换为单列或单行矩阵.

    • <:( 由于 array 是 NumPy 中的默认类型,某些函数可能会返回一个 array ,即使你将 matrix 作为参数传递.这不应发生在 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