本章我们将学习矩阵和通用函数(universal functions,即ufuncs)的相关内容。矩阵作为一种重要的数学概念,在NumPy中也有专门的表示方法。通用函数可以逐个处理数组中的元素,也可以直接处理标量。通用函数的输入是一组标量,输出也是一组标量,它们通常可以对应于基本数学运算,如加、减、乘、除等。我们还将介绍三角函数、位运算函数和比较函数。
第五章 矩阵和通用函数
5.1 矩阵
在NumPy
中,矩阵是ndarray
的子类,可以由专用的字符串格式来创建。与数学概念中的矩阵一样, NumPy中的矩阵也是二维的。如你所料,矩阵的乘法运算和NumPy
中的普通乘法运算不同。幂运算当然也不一样。我们可以使用mat
、 matrix
以及bmat
函数来创建矩阵。
5.2 动手实践:创建矩阵
mat
函数创建矩阵时,若输入已为matrix
或ndarray
对象,则不会为它们创建副本。 因此,调用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
函数。
在使用mat
和bmat
函数创建矩阵时,需要输入字符串(使用;
作为矩阵的行分隔符)来定义矩阵。
案例完整代码如下:
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
中,基本算术运算符+
、-
和*
隐式关联着通用函数add
、 subtract
和multiply
。也就是说,当你对NumPy数组使用这些算术运算符时,对应的通用函数将自动被调用。除法包含的过程则较为复杂 ,在数组的除法运算中涉及三个通用函数 divide
、true_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
中的mod
、 remainder
和fmod
函数。当然,也可以使用%
运算符。这些函数的主要差异在于处理负数的方式。 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
函数处理负数的方式与remainder
、mod
和%
不同。所得余数的正负由被除数决定,
与除数的正负无关:
print( "Fmod", np.fmod(a, 2))
计算结果如下:
Fmod [ 0 -1 0 -1 0 1 0 1]