《Python数据科学手册》读书笔记
数组的计算:广播
另外一种向量化操作的方法是利用 NumPy 的广播
功能。
广播的介绍
对于同样大小的数组, 二进制操作是对相应元素逐个计
算:
import numpy as np
a = np.array([0, 1, 2])
b = np.array([5, 5, 5])
a + b
array([5, 6, 7])
广播允许这些二进制操作可以用于不同大小的数组。 例如, 可以简单地
将一个标量(可以认为是一个零维的数组) 和一个数组相加:
a + 5
array([5, 6, 7])
我们可以认为这个操作是将数值 5 扩展或重复至数组 [5, 5, 5], 然后
执行加法。 NumPy 广播功能的好处是, 这种对值的重复实际上并没有
发生, 但是这是一种很好用的理解广播的模型。
M = np.ones((3, 3))
M
array([[1., 1., 1.],
[1., 1., 1.],
[1., 1., 1.]])
M + a
array([[1., 2., 3.],
[1., 2., 3.],
[1., 2., 3.]])
这里这个一维数组就被扩展或者广播了。 它沿着第二个维度扩展, 扩展
到匹配 M 数组的形状。
以上的这些例子理解起来都相对容易, 更复杂的情况会涉及对两个数组
的同时广播:
# 两个数组同时广播
a = np.arange(3)
b = np.arange(3)[:, np.newaxis]
print(a)
print(b)
[0 1 2]
[[0]
[1]
[2]]
a + b
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
广播的规则
- 如果两个数组的维度数不同,那么小维度数组的形状将会在最左边补1
- 如果两个数组的形状在任何一个维度都不匹配,那么数组的形状将会沿着维度为1的维度扩展以匹配另外一个数组的形状
- 如果两个数组的形状在任何一个维度都不匹配并且没有任何一个维度等于1,那么会发生异常
- example 1
M = np.ones((2, 3))
a = np.arange(3)
- M.shape = (2, 3)
- a.shape = (3,)
根据规则1
- M.shape -> (2, 3)
- a.shape -> (1, 3)
根据规则2
- M.shape -> (2, 3)
- a.shape -> (2, 3)
M + a
array([[1., 2., 3.],
[1., 2., 3.]])
- example 2
a = np.arange(3).reshape((3, 1))
b = np.arange(3)
- M.shape = (3, 1)
- a.shape = (3,)
根据规则1
- M.shape -> (3, 1)
- a.shape -> (1, 3)
根据规则2
- M.shape -> (3, 3)
- a.shape -> (3, 3)
a + b
array([[0, 1, 2],
[1, 2, 3],
[2, 3, 4]])
- example 3
M = np.ones((3, 2))
a = np.arange(3)
- M.shape = (3, 2)
- a.shape = (3,)
根据规则1
- M.shape -> (3, 2)
- a.shape -> (1, 3)
根据规则2
- M.shape -> (3, 2)
- a.shape -> (3, 3)
根据规则3
最终形状不匹配,这两个数组不兼容
M + a
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-43-8cac1d547906> in <module>()
----> 1 M + a
ValueError: operands could not be broadcast together with shapes (3,2) (3,)
# 如果希望实现右边补齐,可以通过变形数组来实现
a[:, np.newaxis].shape
(3, 1)
M + a[:, np.newaxis]
array([[1., 1.],
[2., 2.],
[3., 3.]])
另外也需要注意, 这里仅用到了 + 运算符, 而这些广播规则对于任
意二进制通用函数都是适用的。 例如这里的 logaddexp(a, b) 函
数, 比起简单的方法, 该函数计算 log(exp(a) + exp(b)) 更准
确:
np.logaddexp(M, a[:, np.newaxis])
array([[1.31326169, 1.31326169],
[1.69314718, 1.69314718],
[2.31326169, 2.31326169]])
广播的实际应用
- 数组的归一化
X = np.random.random((10, 3))
# 计算每一列的平均值
Xmean = X.mean(0)
Xmean
array([0.47874092, 0.54918989, 0.48897961])
# 实现归一化
X_centered = X - Xmean
# 检验每一列的均值是否为0
X_centered.mean(0)
array([ 1.66533454e-17, -8.88178420e-17, 0.00000000e+00])
- 画一个二维函数
# x and y have 50 steps from 0 to 5
x = np.linspace(0, 5, 50)
y = np.linspace(0, 5, 50)[:, np.newaxis]
z = np.sin(x) ** 10 + np.cos(10 + y * x) * np.cos(x)
%matplotlib inline
import matplotlib.pyplot as plt
plt.imshow(z, origin='lower', extent=[0, 5, 0, 5],
cmap='viridis')
plt.colorbar();
比较,掩码和布尔逻辑
比较操作
x = np.array([1, 2, 3, 4, 5])
x < 3
array([ True, True, False, False, False])
x > 3
array([False, False, False, True, True])
x <= 3
array([ True, True, True, False, False])
x >= 3
array([False, False, True, True, True])
x != 3
array([ True, True, False, True, True])
x == 3
array([False, False, True, False, False])
# 利用复合表达式对两个数组进行逐元素比较
(2 * x) == (x ** 2)
array([False, True, False, False, False])
运算符 | 对应通用函数 |
---|---|
== | np.equal |
< | np.less |
> | np.greater |
!= | np.not_equal |
<= | np.less_equal |
>= | np.greater_equal |
# 比较运算通用函数可以用于任何形状大小的数组
rng = np.random.RandomState(0)# 确保每次产生的随机数组是一样的
x = rng.randint(10, size=(3, 4))
x
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 6]])
x < 6
array([[ True, True, True, True],
[False, False, True, True],
[ True, True, False, False]])
这样每次计算的结果都是布尔数组了。 NumPy 提供了一些简明的模式
来操作这些布尔结果。
操作布尔数组
给定一个布尔数组, 你可以实现很多有用的操作。 首先打印出此前生成
的二维数组 x:
print(x)
[[5 0 3 3]
[7 9 3 5]
[2 4 7 6]]
- 统计个数
# 有多少个值小于6
np.count_nonzero(x < 6)
8
# 也可使用sum来进行计数,这个例子中F被解释成0,T被解释成1
np.sum(x < 6)
8
# 每行有多少个值小于6
np.sum(x < 6, axis=1)
array([4, 2, 2])
# 有没有值大于8
np.any(x > 8)
True
# 有没有值小于0
np.any(x < 0)
False
# 是否所有值小于10
np.all(x < 10)
True
# 是否所有值等于6
np.all(x == 6)
False
# 是否每行的所有值都小于8
np.all(x < 8, axis=1)
array([ True, False, True])
- 布尔运算符
-
&:逻辑与
-
| :逻辑或
-
~:逻辑取反
运算符 | 对应通用函数 |
---|---|
& | np.bitwise_and |
np.bitwise_or | |
~ | np.bitwise_not |
将布尔数组作为掩码
一种
更强大的模式是使用布尔数组作为掩码, 通过该掩码选择数据的子数据
集。
x
array([[5, 0, 3, 3],
[7, 9, 3, 5],
[2, 4, 7, 6]])
x < 5
array([[False, True, True, True],
[False, False, True, False],
[ True, True, False, False]])
现在为了将这些值从数组中选出, 可以进行简单的索引, 即掩码操作:
# 将小于5的值从数组中筛选出来
x[x < 5]
array([0, 3, 3, 3, 2, 4])
and和or对整个对象执行单个布尔运算,而&和|对一个对象的内容执行多个布尔运算,对于Numpy布尔数组,后者是最常用的操作