Excel Python:飞速搞定数据分析与处理

在这里插入图片描述

第二部分 pandas 入门

第四章 NumPy 基础

NumPy 是 Python 科学计算的关键包,为数组运算线性代数运算提供了支持。因为 pandas 是在 NumPy 之上建立起来的,所以本章会先介绍 NumPy 的基础知识。在解释了什么是 NumPy 数组之后,我们会学习向量化广播这两个重要概念。利用向量化和广播,我们可以写出简洁的数学运算代码,并且它们在 pandas 中也有广泛运用。之后,介绍为什么 NumPy 会提供叫作“全局函数”的特殊函数。最后,通过解释 NumPy 视图和副本之间的区别,学习如何存取 NumPy 数组的值。

4.1 NumPy入门

介绍一维二维的 NumPy 数组,以及向量化、广播和通用函数的背景知识

4.1.1 NumPy数组

对嵌套列表进行数组运算,可以使用循环来完成。要为嵌套列表中的每一个元素都加上 1,可以使用下面的嵌套列表推导式:

In [1]: matrix = [[1, 2, 3],
 				  [4, 5, 6],
				  [7, 8, 9]]
In [2]: [[i + 1 for i in row] for row in matrix]
Out[2]: [[2, 3, 4], [5, 6, 7], [8, 9, 10]]

但是这样的代码可读性很低。更关键的是,在面对更大的数组时,遍历整个数组会非常。如果你的用例和数组大小合适的话,那么使用 NumPy 数组进行运算会比 Python 列表快上几百倍。为了达到如此高的性能,NumPy 利用了用 C 和 Fortran(它们都是编译型语言,比 Python 要快得多)编写的代码。NumPy 数组是保存同构数据(homogenous data)的 N 维数组。“同构”意味着数组中的所有数据都必须是相同类型。最常见的情况就是处理一维和二维的浮点数数组。

在这里插入图片描述

下面来创建一个一维数组和一个二维数组:

In [3]: # 首先导入NumPy
 		import numpy as np
In [4]: # 使用列表构造一个一维数组
 		array1 = np.array([10, 100, 1000.])
In [5]: # 使用嵌套列表构造一个二维数组
 		array2 = np.array([[1., 2., 3.],
 						   [4., 5., 6.]])

数组维度:要注意一维数组和二维数组之间的区别。一维数组只有一个轴,因此不区分行数组和列数组。

即使 array1 除了最后一个元素(浮点数)之外全是整数,但由于 NumPy 对同构的要求,这个数组的数据类型依然是 float64,这个类型足以容纳所有的元素。要想了解一个数组的数据类型,可以访问它的 dtype 属性:

In [6]: array1.dtype
Out[6]: dtype('float64')

如果需要显式地将 NumPy 数据类型转换成 Python 的基本数据类型,只需使用对应的构造器即可:

In [7]: float(array1[0])
Out[7]: 10.0
4.1.2 向量化和广播

如果你对一个标量和 NumPy 数组求和,那么 NumPy 会执行按元素的操作。也就是说,你不用亲自遍历每一个元素。NumPy 社区称之为向量化(vectorization)。向量化可以让代码更简洁,更接近于数学记法。

In [8]: array2 + 1
Out[8]: array([[2., 3., 4.],
 			   [5., 6., 7.]])

标量:标量(scalar)指的是某种 Python 基本数据类型,比如浮点型字符串。这是为了将其和列表及字典一类的多元素数据结构,以及一维和二维的 NumPy 数组区分开来。

在处理两个数组时也是同样的道理,NumPy 会执行按元素的运算:

In [9]: array2 * array2
Out[9]: array([[ 1., 4., 9.],
			   [16., 25., 36.]])

如果你在算术运算中使用了两个形状不同的数组,那么 NumPy 在可能的情况下会自动将较小的数组扩展成较大的数组的形状。这就是广播(broadcasting):

In [10]: array2 * array1
Out[10]: array([[ 10., 200., 3000.],
			    [ 40., 500., 6000.]])

要求矩阵的点积,需要使用 @ 运算符:

In [11]: array2 @ array2.T # array2.T是array2.transpose()的缩写形式
Out[11]: array([[14., 32.], 
 				[32., 77.]])

拓展:矩阵的点积,也称为内积标量积,在数学中是一种重要的矩阵运算。它涉及两个矩阵中对应元素的乘积和随后的求和,结果是一个单一的标量值。对于两个向量 ab,它们的点积是这样计算的:ab = a1b1 + a2b2 + … + anbn,其中 a1, a2, …, an 和 b1, b2, …, bn 是向量 ab 的对应元素。如果将这个概念扩展到矩阵,矩阵的点积就是两个矩阵对应元素乘积的总和

4.1.3 通用函数

通用函数(universal function,简称 ufunc)会对 NumPy 数组中的每个元素执行操作。如果在 NumPy 数组中使用 Python 标准库 math 模块中的开平方函数,那么你会得到一个错误:

In [12]: import math
In [13]: math.sqrt(array2) # 这里会发生错误
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-13-5c37e8f41094> in <module>
----> 1 math.sqrt(array2) # 这里会发生错误

TypeError: only size-1 arrays can be converted to Python scalars

可以写一个嵌套循环来计算每个元素的平方根,然后再把结果构造成一个 NumPy 数组:

In [14]: np.array([[math.sqrt(i) for i in row] for row in array2])
Out[14]: array([[1. , 1.41421356, 1.73205081],
 			    [2. , 2.23606798, 2.44948974]])

NumPy 有这样一个 ufunc,直接用它,除了更容易输入和阅读,在处理大型数组时 ufunc 会快得多:

In [15]: np.sqrt(array2)
Out[15]: array([[1. , 1.41421356, 1.73205081],
 				[2. , 2.23606798, 2.44948974]])

NumPy 的一些 ufunc 也可以用作数组的方法。以 sum 为例,如果你想求出每一列的总和,那么可以像下面这样做:

In [16]: array2.sum(axis=0) # 返回一维数组
Out[16]: array([5., 7., 9.])

参数 axis=0 表示以行为轴,参数 axis=1 表示以列为轴。省略 axis 参数会将整个数组加起来:

In [17]: array2.sum()
Out[17]: 21.0

4.2 创建和操作数组

4.2.1 存取元素

在处理本章开头例子中的 matrix 这类嵌套列表时,可以使用链式索引(chained indexing):matrix[0] [0]会得到第一行的第一个元素。不过在 NumPy 数组中,你要在一对方括号中同时提供两个维度的索引和切片参数:

numpy_array[row_selection, column_selection]

对于一维数组,上述代码简化成了 numpy_array[selection]。在选取单个元素时,你会得到一个标量,否则得到的就是一维或二维的数组。对二维数组的行或列进行切片,得到的是一个一维数组,而不是二维列向量或行向量。

在这里插入图片描述

In [18]: array1[2] # 返回标量
Out[18]: 1000.0
In [19]: array2[0, 0] # 返回标量
Out[19]: 1.0
In [20]: array2[:, 1:] # 返回二维数组
Out[20]: array([[2., 3.],
 				[5., 6.]])
In [21]: array2[:, 1] # 返回一维数组
Out[21]: array([2., 5.])
In [22]: array2[1, :2] # 返回一维数组
Out[22]: array([4., 5.])
4.2.2 方便的数组构造器

通过 arange 和 reshape,可以快速生成指定维度的数组:

In [23]: np.arange(2 * 5).reshape(2, 5) # 2行,5列
Out[23]: array([[0, 1, 2, 3, 4],
 				[5, 6, 7, 8, 9]])

以蒙特卡罗模拟为例,一个常见需求是生成服从正态分布的伪随机数数组。NumPy 可以轻松做到:

In [24]: np.random.randn(2, 3) # 2行,3列
Out[24]: array([[-0.30047275, -1.19614685, -0.13652283],
 				[ 1.05769357, 0.03347978, -1.2153504 ]])

还有一些方便的构造器值得去发掘,比如 np.onesnp.zeros,它们分别可以创建全是 10 的数组。np.eye 可以创建单位矩阵

4.2.3 视图和副本

在对 NumPy 数组切片时,其返回值视图(view)。这就意味着你是在操作原数组的一个子集,而没有发生数据的复制。因而设置视图的值也会改变原数组中的值

In [25]: array2
Out[25]: array([[1., 2., 3.],
 				[4., 5., 6.]])
In [26]: subset = array2[:, :2]
 		 subset
Out[26]: array([[1., 2.],
 				[4., 5.]])
In [27]: subset[0, 0] = 1000
In [28]: subset
Out[28]: array([[1000., 2.],
 				[ 4., 5.]]) 
In [29]: array2
Out[29]: array([[1000., 2., 3.],
 				[ 4., 5., 6.]])

如果不想要这样的结果,那么可以把 In [26] 的代码改成下面这样:

subset = array2[:, :2].copy()

对副本进行操作不会影响原数组。

更多推荐