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.04.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 在可能的情况下会自动将较小的数组扩展成较大的数组的形状。这就是广播broadcastingIn [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.]])拓展矩阵的点积也称为内积或标量积在数学中是一种重要的矩阵运算。它涉及两个矩阵中对应元素的乘积和随后的求和结果是一个单一的标量值。对于两个向量a和b它们的点积是这样计算的a⋅b a1b1 a2b2 … anbn其中 a1, a2, …, an 和 b1, b2, …, bn 是向量a和b的对应元素。如果将这个概念扩展到矩阵矩阵的点积就是两个矩阵对应元素乘积的总和。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(axis0) # 返回一维数组 Out[16]: array([5., 7., 9.])参数axis0表示以行为轴参数axis1表示以列为轴。省略 axis 参数会将整个数组加起来In [17]: array2.sum() Out[17]: 21.04.2 创建和操作数组4.2.1 存取元素在处理本章开头例子中的 matrix 这类嵌套列表时可以使用链式索引chained indexingmatrix[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.ones和np.zeros它们分别可以创建全是1和0的数组。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()对副本进行操作不会影响原数组。