NumPy 矩阵运算

import numpy as np

NumPy 矩阵运算的基本机制

NumPy 中,numpy 模块的方法和 ndarray 对象的方法几乎是一一对应的。绝大多数方法都具有两种调用方式,并且它们是等价的。例如:

A = np.arange(6).reshape(2, 3)

ndarray.func(*args, **kwargs) 形式:

A.transpose([1, 0])
array([[0, 3],
       [1, 4],
       [2, 5]])

numpy.func(ndarray, *args, **kwargs) 形式:

np.transpose(A, [1, 0])
array([[0, 3],
       [1, 4],
       [2, 5]])

NumPy 中,一般支持这两种调用方式的方法都具有如下特点:

  • 不会修改调用方法的 ndarray 对象本身
  • 返回的计算结果是副本

NumPy 数学运算

一元运算

取绝对值

A = np.linspace(-1, 1, num=5)

np.abs(A)
array([1. , 0.5, 0. , 0.5, 1. ])

取倒数

A = np.arange(1, 6).astype(np.float)

np.reciprocal(A)
array([1.        , 0.5       , 0.33333333, 0.25      , 0.2       ])

平方

A = np.arange(1, 6)

np.square(A)
array([ 1,  4,  9, 16, 25])

开方

A = np.arange(1, 6)
A = A * A

np.sqrt(A)
array([1., 2., 3., 4., 5.])

指数函数

A = np.linspace(-1, 1, num=5)

np.exp(A)
array([0.36787944, 0.60653066, 1.        , 1.64872127, 2.71828183])

对数函数

A = np.array([1, 2, 2.71, 10])

np.log(A), np.log2(A), np.log10(A)
(array([0.        , 0.69314718, 0.99694863, 2.30258509]),
 array([0.        , 1.        , 1.43829285, 3.32192809]),
 array([0.        , 0.30103   , 0.43296929, 1.        ]))

三角函数

A = np.linspace(0, 2*np.pi, num=12)

np.sin(A), np.cos(A), np.tan(A)
(array([ 0.00000000e+00,  5.40640817e-01,  9.09631995e-01,  9.89821442e-01,
         7.55749574e-01,  2.81732557e-01, -2.81732557e-01, -7.55749574e-01,
        -9.89821442e-01, -9.09631995e-01, -5.40640817e-01, -2.44929360e-16]),
 array([ 1.        ,  0.84125353,  0.41541501, -0.14231484, -0.65486073,
        -0.95949297, -0.95949297, -0.65486073, -0.14231484,  0.41541501,
         0.84125353,  1.        ]),
 array([ 0.00000000e+00,  6.42660977e-01,  2.18969456e+00, -6.95515277e+00,
        -1.15406152e+00, -2.93626493e-01,  2.93626493e-01,  1.15406152e+00,
         6.95515277e+00, -2.18969456e+00, -6.42660977e-01, -2.44929360e-16]))

反三角函数

A = np.linspace(-1, 1, num=5)

np.arcsin(A), np.arccos(A), np.arctan(A)
(array([-1.57079633, -0.52359878,  0.        ,  0.52359878,  1.57079633]),
 array([3.14159265, 2.0943951 , 1.57079633, 1.04719755, 0.        ]),
 array([-0.78539816, -0.46364761,  0.        ,  0.46364761,  0.78539816]))

A = np.random.randn(2, 5)
A
array([[ 0.50865384, -0.22082557, -0.38529808,  2.17211963,  0.32188451],
       [-1.97660967, -0.08196995, -0.10862636, -0.69184639,  1.67270543]])

符号函数

np.sign(A)
array([[ 1., -1., -1.,  1.,  1.],
       [-1., -1., -1., -1.,  1.]])

向上取整(天花板函数)

np.ceil(A)
array([[ 1., -0., -0.,  3.,  1.],
       [-1., -0., -0., -0.,  2.]])

向下取整(地板函数)

np.floor(A)
array([[ 0., -1., -1.,  2.,  0.],
       [-2., -1., -1., -1.,  1.]])

四舍五入到整数

np.rint(A)
array([[ 1., -0., -0.,  2.,  0.],
       [-2., -0., -0., -1.,  2.]])

舍入函数,其中参数 decimals 表示小数的位数。

np.around(A, decimals=2)
array([[ 0.51, -0.22, -0.39,  2.17,  0.32],
       [-1.98, -0.08, -0.11, -0.69,  1.67]])

整数部分和小数部分分离,返回两个 ndarray 数组:

np.modf(A)
(array([[ 0.50865384, -0.22082557, -0.38529808,  0.17211963,  0.32188451],
        [-0.97660967, -0.08196995, -0.10862636, -0.69184639,  0.67270543]]),
 array([[ 0., -0., -0.,  2.,  0.],
        [-1., -0., -0., -0.,  1.]]))

二元运算

A = np.random.randn(4)
B = np.random.randn(4)
A, B
(array([-1.39684605,  1.13610876,  0.37116582,  0.11624161]),
 array([ 1.41286043, -1.52254143,  0.41928741,  1.09645406]))

取最大值

np.maximum(A, B)
array([1.41286043, 1.13610876, 0.41928741, 1.09645406])

取最小值

np.minimum(A, B)
array([-1.39684605, -1.52254143,  0.37116582,  0.11624161])

下面的函数和上面等价:

np.fmax(A, B), np.fmin(A, B)
(array([1.41286043, 1.13610876, 0.41928741, 1.09645406]),
 array([-1.39684605, -1.52254143,  0.37116582,  0.11624161]))

A = np.arange(6).reshape(2, 3)
B = np.full((2, 3), 2)
A, B
(array([[0, 1, 2],
        [3, 4, 5]]),
 array([[2, 2, 2],
        [2, 2, 2]]))

四则运算

np.add(A, B), np.subtract(A, B), np.multiply(A, B), np.divide(A, B)
(array([[2, 3, 4],
        [5, 6, 7]]),
 array([[-2, -1,  0],
        [ 1,  2,  3]]),
 array([[ 0,  2,  4],
        [ 6,  8, 10]]),
 array([[0. , 0.5, 1. ],
        [1.5, 2. , 2.5]]))

等价的重载运算符的写法:

A + B, A - B, A * B, A / B
(array([[2, 3, 4],
        [5, 6, 7]]),
 array([[-2, -1,  0],
        [ 1,  2,  3]]),
 array([[ 0,  2,  4],
        [ 6,  8, 10]]),
 array([[0. , 0.5, 1. ],
        [1.5, 2. , 2.5]]))

乘方(幂)

np.power(A, B)
array([[ 0,  1,  4],
       [ 9, 16, 25]])

等价的重载运算符的写法:

A ** B
array([[ 0,  1,  4],
       [ 9, 16, 25]])

取模

np.mod(A, B)
array([[0, 1, 0],
       [1, 0, 1]])

等价的重载运算符的写法:

A % B
array([[0, 1, 0],
       [1, 0, 1]])

将 $B$ 中各元素的符号赋值给 $A$ 中对应元素:

np.copysign(A, B)
array([[0., 1., 2.],
       [3., 4., 5.]])

比较运算,返回布尔矩阵:

A > B, A < B, A >= B, A <= B, A == B, A != B
(array([[False, False, False],
        [ True,  True,  True]]),
 array([[ True,  True, False],
        [False, False, False]]),
 array([[False, False,  True],
        [ True,  True,  True]]),
 array([[ True,  True,  True],
        [False, False, False]]),
 array([[False, False,  True],
        [False, False, False]]),
 array([[ True,  True, False],
        [ True,  True,  True]]))

应用:利用布尔矩阵索引机制,将矩阵 $A$ 小于矩阵 $B$ 的元素置 $1$,将矩阵 $A$ 大于矩阵 $B$ 的元素置 $0$:

a = A < B
b = A > B
A[a] = 1
A[b] = 0
A
array([[1, 1, 2],
       [0, 0, 0]])

将 $x$ 轴和 $y$ 轴分别往纵向和横向扩展成 2D 数组:

x = np.array(np.arange(10))
y = np.array(np.arange(10))

xx, yy = np.meshgrid(x, y)
xx, yy
(array([[0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
        [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]]),
 array([[0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
        [1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
        [2, 2, 2, 2, 2, 2, 2, 2, 2, 2],
        [3, 3, 3, 3, 3, 3, 3, 3, 3, 3],
        [4, 4, 4, 4, 4, 4, 4, 4, 4, 4],
        [5, 5, 5, 5, 5, 5, 5, 5, 5, 5],
        [6, 6, 6, 6, 6, 6, 6, 6, 6, 6],
        [7, 7, 7, 7, 7, 7, 7, 7, 7, 7],
        [8, 8, 8, 8, 8, 8, 8, 8, 8, 8],
        [9, 9, 9, 9, 9, 9, 9, 9, 9, 9]]))

$(xx, yy)$ 组成了平面直角坐标系:

for i in range(10):
    for j in range(10):
        point = (xx[i,j], yy[i,j])
        print(point, end=' ')
    print()
(0, 0) (1, 0) (2, 0) (3, 0) (4, 0) (5, 0) (6, 0) (7, 0) (8, 0) (9, 0) 
(0, 1) (1, 1) (2, 1) (3, 1) (4, 1) (5, 1) (6, 1) (7, 1) (8, 1) (9, 1) 
(0, 2) (1, 2) (2, 2) (3, 2) (4, 2) (5, 2) (6, 2) (7, 2) (8, 2) (9, 2) 
(0, 3) (1, 3) (2, 3) (3, 3) (4, 3) (5, 3) (6, 3) (7, 3) (8, 3) (9, 3) 
(0, 4) (1, 4) (2, 4) (3, 4) (4, 4) (5, 4) (6, 4) (7, 4) (8, 4) (9, 4) 
(0, 5) (1, 5) (2, 5) (3, 5) (4, 5) (5, 5) (6, 5) (7, 5) (8, 5) (9, 5) 
(0, 6) (1, 6) (2, 6) (3, 6) (4, 6) (5, 6) (6, 6) (7, 6) (8, 6) (9, 6) 
(0, 7) (1, 7) (2, 7) (3, 7) (4, 7) (5, 7) (6, 7) (7, 7) (8, 7) (9, 7) 
(0, 8) (1, 8) (2, 8) (3, 8) (4, 8) (5, 8) (6, 8) (7, 8) (8, 8) (9, 8) 
(0, 9) (1, 9) (2, 9) (3, 9) (4, 9) (5, 9) (6, 9) (7, 9) (8, 9) (9, 9) 

NumPy 变维运算

通过 ndarray 的属性和方法实现

ndarray 数组的 reshape 方法可以将普通的一维数组变成多维数组。

np.arange(24).reshape(2, 3, 4)
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]]])

resize 方法会直接改变原矩阵的 shape,不返回任何值。

A = np.arange(24)
A.resize(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]]])

如果我们已经有了一个多维数组,我们想要改变其 shape,可以直接设置其 shape 属性。

A = np.arange(24).reshape(2, 3, 4)
A.shape = (4, 3, 2)
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]]])

轴的置换

我们可以用广义的多维矩阵转置的方法实现变维。其中参数 axes 表示新的轴顺序。

A = np.arange(24).reshape(2, 3, 4)
np.transpose(A, axes=[1, 2, 0])
array([[[ 0, 12],
        [ 1, 13],
        [ 2, 14],
        [ 3, 15]],

       [[ 4, 16],
        [ 5, 17],
        [ 6, 18],
        [ 7, 19]],

       [[ 8, 20],
        [ 9, 21],
        [10, 22],
        [11, 23]]])

如果是普通的二维矩阵,可以直接用 ndarrayT 属性来得到转置的结果。

A = np.arange(12).reshape(3, 4)
A.T
array([[ 0,  4,  8],
       [ 1,  5,  9],
       [ 2,  6, 10],
       [ 3,  7, 11]])

我们还可以通过多维矩阵维度交换的方法实现变维。传入的参数 02 同样表示轴。

A = np.arange(24).reshape(2, 3, 4)
np.swapaxes(A, 0, 2)
array([[[ 0, 12],
        [ 4, 16],
        [ 8, 20]],

       [[ 1, 13],
        [ 5, 17],
        [ 9, 21]],

       [[ 2, 14],
        [ 6, 18],
        [10, 22]],

       [[ 3, 15],
        [ 7, 19],
        [11, 23]]])

降维与拉直

A = np.arange(6).reshape(2, 1, 3)
A
array([[[0, 1, 2]],

       [[3, 4, 5]]])

squeeze 方法将 shape 为 $1$ 的部分去除。

np.squeeze(A)
array([[0, 1, 2],
       [3, 4, 5]])

如果我们希望将任意多维数组都化为一维,可以通过下面的方法。

ravel 方法可以“拉直”多维数组,它返回的是数组的视图 view

A.ravel()
array([0, 1, 2, 3, 4, 5])

flatten 方法同样可以“拉直”多维数组,但是它返回的是数组的副本。

A.flatten()
array([0, 1, 2, 3, 4, 5])

在“拉直”的一维数组和多维数组之间我们常常需要进行下标和索引的计算,下面的方法根据对应的 shape 将一维数组下的整型下标转化为多维数组下对应的下标:

np.unravel_index(9, shape=(3, 4))
(2, 1)

NumPy 数组排序

NumPy 提供了 sort 方法来实现对多维矩阵的排序。

A = np.random.randn(2, 5)
A
array([[ 0.64501916,  0.27037382, -0.19212451,  1.64902317,  0.25770498],
       [-1.42529996,  1.78813871, -1.13524005, -1.42806611,  1.1828902 ]])

默认情况下,排序算法为快速排序 quicksort。平均情况下,该排序算法是最快的。不过我们也可以指定排序算法,即设置 kind 参数。可选的排序算法有 ‘mergesort’ 归并排序和 ‘heapsort’ 堆排序。

np.sort(A)
array([[-0.19212451,  0.25770498,  0.27037382,  0.64501916,  1.64902317],
       [-1.42806611, -1.42529996, -1.13524005,  1.1828902 ,  1.78813871]])

对于多维数组,我们建议指定其 axis 参数。

np.sort(A, axis=0)
array([[-1.42529996,  0.27037382, -1.13524005, -1.42806611,  0.25770498],
       [ 0.64501916,  1.78813871, -0.19212451,  1.64902317,  1.1828902 ]])
np.sort(A, axis=1)
array([[-0.19212451,  0.25770498,  0.27037382,  0.64501916,  1.64902317],
       [-1.42806611, -1.42529996, -1.13524005,  1.1828902 ,  1.78813871]])

argsort 则返回排序后的索引值:

np.argsort(A)
array([[2, 4, 1, 0, 3],
       [3, 0, 2, 4, 1]])
np.argsort(A, axis=0)
array([[1, 0, 1, 1, 0],
       [0, 1, 0, 0, 1]])
np.argsort(A, axis=1)
array([[2, 4, 1, 0, 3],
       [3, 0, 2, 4, 1]])

NumPy 适用于纯数据的排序,并且能够方便地得到各个数据的“名次”。然而,对于较为复杂的多关键字排序,我们更建议使用数据分析包 Pandas

Previous
Next