《Python数据分析基础教程:NumPy学习指南(第2版)》笔记11:第五章 矩阵和通用函数1

本章我们将学习矩阵和通用函数(universal functions,即ufuncs)的相关内容。矩阵作为一种重要的数学概念,在NumPy中也有专门的表示方法。通用函数可以逐个处理数组中的元素,也可以直接处理标量。通用函数的输入是一组标量,输出也是一组标量,它们通常可以对应于基本数学运算,如加、减、乘、除等。我们还将介绍三角函数、位运算函数和比较函数。

第五章 矩阵和通用函数

5.1 矩阵

NumPy中,矩阵是ndarray的子类,可以由专用的字符串格式来创建。与数学概念中的矩阵一样, NumPy中的矩阵也是二维的。如你所料,矩阵的乘法运算和NumPy中的普通乘法运算不同。幂运算当然也不一样。我们可以使用matmatrix以及bmat函数来创建矩阵。

5.2 动手实践:创建矩阵

mat函数创建矩阵时,若输入已为matrixndarray对象,则不会为它们创建副本。 因此,调用mat函数和调用matrix(data, copy=False)等价。 我们还将展示矩阵转置和矩阵求逆的方法。

  • (1) 在创建矩阵的专用字符串中,矩阵的行与行之间用分号隔开,行内的元素之间用空格隔开。使用如下的字符串调用mat函数创建矩阵:
import numpy as np

A = np.mat('1 2 3; 4 5 6; 7 8 9')
print("Creation from string", A)

输出的矩阵如下:

Creation from string [[1 2 3]
[4 5 6]
[7 8 9]]
  • (2) 用T属性获取转置矩阵:
print("transpose A", A.T)

转置矩阵如下:

transpose A [[1 4 7]
[2 5 8]
[3 6 9]]
  • (3) 除了使用字符串创建矩阵以外,我们还可以使用NumPy数组进行创建:
print("Creation from array", np.mat(np.arange(9).reshape(3, 3)))

创建的矩阵如下:

Creation from array [[0 1 2]
 [3 4 5]
 [6 7 8]]

5.3 从已有矩阵创建新矩阵

有些时候,我们希望利用一些已有的较小的矩阵来创建一个新的大矩阵。这可以用bmat函数来实现。这里的b表示“分块”, bmat即分块矩阵(block matrix)。

5.4 动手实践:从已有矩阵创建新矩阵

我们将利用两个较小的矩阵创建一个新的矩阵,步骤如下。

  • (1) 首先,创建一个2×2的单位矩阵:
import numpy as np

A = np.eye(2)
print( "A", A)

该单位矩阵如下所示:

A [[ 1. 0.]
[ 0. 1.]]

创建另一个与A同型的矩阵,并乘以2

B = 2 * A
print( "B", B)

第二个矩阵如下所示:

B [[ 2. 0.]
[ 0. 2.]]
  • (2) 使用字符串创建复合矩阵,该字符串的格式与mat函数中一致,只是在这里你可以用矩
    阵变量名代替数字:
print( "Compound matrix\n", np.bmat("A B; A B"))

创建的复合矩阵如下所示:

Compound matrix
[[ 1. 0. 2. 0.]
[ 0. 1. 0. 2.]
[ 1. 0. 2. 0.]
[ 0. 1. 0. 2.]]

小结

我们使用bmat函数,从两个小矩阵创建了一个分块复合矩阵。我们用矩阵变量名替代了数
字,并将字符串传给bmat函数。

在使用matbmat函数创建矩阵时,需要输入字符串(使用;作为矩阵的行分隔符)来定义矩阵。

案例完整代码如下:

import numpy as np

A = np.eye(2)
print( "A", A)
B = 2 * A
print( "B", B)
print( "Compound matrix\n", np.bmat("A B; A B"))

5.5 通用函数

通用函数的输入是一组标量,输出也是一组标量,它们通常可以对应于基本数学运算,如加、减、乘、除等。

5.6 动手实践:创建通用函数

我们可以使用NumPy中的frompyfunc函数, 通过一个Python函数来创建通用函数,步骤如下。

  • (1) 定义一个回答宇宙、生命及万物的终极问题的Python函数(问题和答案来源于《银河系漫游指南》,如果你没看过,可以忽略):
    def ultimate_answer(a):
    到这里为止还没有什么特别的。我们将这个函数命名为ultimate_answer,并为之定义了一个参数a
  • (2) 使用zeros_like函数创建一个和a形状相同,并且元素全部为0的数组result
    result = np.zeros_like(a)
    -(3) 现在,我们将刚刚生成的数组中的所有元素设置为“终极答案”其值为42,并返回这个结果。完整的函数代码如下所示。 flat属性为我们提供了一个扁平迭代器,可以逐个设置数组元素的值:
def ultimate_answer(a):
    result = np.zeros_like(a)
    result.flat = 42

    return result
  • (4) 使用frompyfunc创建通用函数。指定输入参数的个数为1,随后的1为输出参数的个数:
ufunc = np.frompyfunc(ultimate_answer, 1, 1) 
print("The answer", ufunc(np.arange(4)))

输出结果如下所示:

The answer array([[42, 42, 42, 42]])

我们可以对二维数组进行完全一样的操作,代码如下:

print("The answer", ufunc(np.arange(4).reshape(2, 2)))

输出结果如下所示:

The answer array([[42, 42],
       [42, 42]])

小结

我们定义了一个Python函数。其中,我们使用zeros_like函数根据输入参数的形状初始化一个全为0的数组,然后利用ndarray对象的flat属性将所有的数组元素设置为“终极答案”其值为42

案例完整代码如下:

import numpy as np

def ultimate_answer(a):
    result = np.zeros_like(a)
    result.flat = 42

    return result

ufunc = np.frompyfunc(ultimate_answer, 1, 1) 
print("The answer", ufunc(np.arange(4)))

print("The answer", ufunc(np.arange(4).reshape(2, 2)))

5.7 通用函数的方法

函数竟然也可以拥有方法?如前所述,**其实通用函数并非真正的函数,而是能够表示函数的对象。通用函数有四个方法,不过这些方法只对输入两个参数、输出一个参数的ufunc对象有效,例如add函数。**其他不符合条件的ufunc对象调用这些方法时将抛出ValueError异常。因此只能在二元通用函数上调用这些方法。以下将逐一介绍这4个方法:

  • reduce
  • accumulate
  • reduceat
  • outer

5.8 动手实践:在 add 上调用通用函数的方法

我们将在add函数上分别调用4个方法。

  • (1) 沿着指定的轴,在连续的数组元素之间递归调用通用函数,即可得到输入数组的规约(reduce)计算结果。对于add函数,其对数组的reduce计算结果等价于对数组元素求和。调用reduce方法:
import numpy as np

a=np.arange(9)
print("Reduce", np.add.reduce(a))

计算结果如下:

Reduce 36
  • (2) accumulate方法同样可以递归作用于输入数组。但是与reduce方法不同的是,它将存储运算的中间结果并返回。因此在add函数上调用accumulate方法,等价于直接调用cumsum函数。在add函数上调用accumulate方法:
print( "Accumulate", np.add.accumulate(a))

计算结果如下:

Accumulate [ 0 1 3 6 10 15 21 28 36]

(3) reduceat方法解释起来有点复杂,我们先运行一次,再一步一步来看它的算法。
reduceat方法需要输入一个数组以及一个索引值列表作为参数。

print( "Reduceat", np.add.reduceat(a, [0, 5, 2, 7]))

运行结果如下:

Reduceat [10 5 20 15]

第一步用到索引值列表中的0和5,实际上就是对数组中索引值在0到5之间的元素进行reduce操作。

print( "Reduceat step I", np.add.reduce(a[0:5]))

第一步的输出如下:

Reduceat step I 10

第二步用到索引值5和2。由于2比5小,所以直接返回索引值为5的元素:

print( "Reduceat step II", a[5])

第二步的结果如下:

Reduceat step II 5

第三步用到索引值2和7。这一步是对索引值在2到7之间的数组元素进行reduce操作:

print( "Reduceat step III", np.add.reduce(a[2:7]))

第三步的结果如下:

Reduceat step III 20

第四步用到索引值7。这一步是对索引值从7开始直到数组末端的元素进行reduce操作:

print( "Reduceat step IV", np.add.reduce(a[7:]))

第四步的结果如下:

Reduceat step IV 15

(4) outer方法返回一个数组,它的秩(rank)等于两个输入数组的秩的和。它会作用于两个输入数组之间存在的所有元素对。在add函数上调用outer方法:

print( "Outer", np.add.outer(np.arange(3), a))

输出结果如下:

Outer [[ 0 1 2 3 4 5 6 7 8]
[ 1 2 3 4 5 6 7 8 9]
[ 2 3 4 5 6 7 8 9 10]]

5.9 算术运算

NumPy中,基本算术运算符+-*隐式关联着通用函数addsubtractmultiply。也就是说,当你对NumPy数组使用这些算术运算符时,对应的通用函数将自动被调用。除法包含的过程则较为复杂 ,在数组的除法运算中涉及三个通用函数 dividetrue_divide
floor_division,以及两个对应的运算符///

5.10 动手实践:数组的除法运算

让我们在实践中了解数组的除法运算。

  • (1) divide函数在整数和浮点数除法中均浮点数结果而不作截断:
import numpy as np

a = np.array([2, 6, 5])
b = np.array([1, 2, 3])

print( "Divide", np.divide(a, b), np.divide(b, a))

divide函数的运算结果如下:

Divide [2.         3.         1.66666667] [0.5        0.33333333 0.6       ]
  • (2) true_divide函数与divide函数的功能完全一致(Python3除法默认即为真除法):
print( "True Divide", np.true_divide(a, b), np.true_divide(b, a))

true_divide函数的运算结果如下:

True Divide [2.         3.         1.66666667] [0.5        0.33333333 0.6       ]
  • (3) floor_divide函数总是返回整数结果,相当于先调用divide函数再调用floor函数。
    floor函数将对浮点数进行向下取整并返回整数:
print( "Floor Divide", np.floor_divide(a, b), np.floor_divide(b, a))
c = 3.14 * b
print( "Floor Divide 2", np.floor_divide(c, b), np.floor_divide(b, c))

floor_divide函数的运算结果如下:

Floor Divide [2 3 1] [0 0 0]
Floor Divide 2 [3. 3. 3.] [0. 0. 0.]
  • (4) 默认情况下,使用/运算符相当于调用divide函数:
print( "/ operator", a/b, b/a)

计算结果如下:

/ operator [ 2. 3. 1.66666667] [ 0.5 0.33333333 0.6 ]
  • (5) 运算符//对应于floor_divide函数。例如下面的代码:
print( "// operator", a//b, b//a)
print( "// operator 2", c//b, b//c)

计算结果如下:

// operator [2 3 1] [0 0 0]
// operator 2 [ 3. 3. 3.] [ 0. 0. 0.]

5.11 模运算

计算模数或者余数,可以使用NumPy中的modremainderfmod函数。当然,也可以使用%运算符。这些函数的主要差异在于处理负数的方式。 fmod函数在这方面异于其他函数。

5.12 动手实践:模运算

我们将逐一调用前面提到的函数。

  • (1) remainder函数逐个返回两个数组中元素相除后的余数。如果第二个数字为0,则直接返回0
import numpy as np

a = np.arange(-4, 4)

print( "Remainder", np.remainder(a, 2))

计算结果如下:

Remainder [0 1 0 1 0 1 0 1]
  • (2) mod函数与remainder函数的功能完全一致
print( "Mod", np.mod(a, 2))

计算结果如下:

Mod [0 1 0 1 0 1 0 1]
  • (3) %操作符仅仅是remainder函数的简写
print( "% operator", a % 2)

计算结果如下:

% operator [0 1 0 1 0 1 0 1]
  • (4) fmod函数处理负数的方式与remaindermod%不同。所得余数的正负由被除数决定,
与除数的正负无关:
print( "Fmod", np.fmod(a, 2))

计算结果如下:

Fmod [ 0 -1 0 -1 0 1 0 1]

你可能感兴趣的:(numpy,通用函数,矩阵,算术运算)