飞速搞定数据分析与处理-day3-一篇入门NumPy

2023-08-07 13:11:34 浏览数 (1)

NumPy入门

NumPy数组

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

代码语言:javascript复制
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 维数组。“同构”意味着数组中的所有数据都必须是相同类型。

代码语言:javascript复制
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 属性

代码语言:javascript复制
In [6]: array1.dtype
Out[6]: dtype('float64')

dtype 返回的是 float64 而不是第 3 章中介绍过的 float。你可能已经猜到了,NumPy 使用的是它自己的数值数据类型,它们比 Python 的数据类型粒度要细。通常这都不是问题,因为大部分时候 Python 和 NumPy 中的不同数据类型可以自动转换。

向量化和广播

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

代码语言:javascript复制
In [8]: array2   1
Out[8]: array([[2., 3., 4.],
[5., 6., 7.]])

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

代码语言:javascript复制
In [9]: array2 * array2
Out[9]: array([[ 1., 4., 9.],
[16., 25., 36.]])

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

代码语言:javascript复制
In [10]: array2 * array1
Out[10]: array([[ 10., 200., 3000.],
[ 40., 500., 6000.]])

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

代码语言:javascript复制
In [11]: array2 @ array2.T # array2.T是array2.transpose()的缩写形式
Out[11]: array([[14., 32.],
[32., 77.]])

通用函数

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

代码语言:javascript复制
n [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数组

代码语言:javascript复制
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,并且数组足够小的话,这样写也可以。然而要是NumPy 有这样一个 ufunc,你就该直接用它。除了更容易输入和阅读,在处理大型数组时ufunc 会快得多

代码语言:javascript复制
In [15]: np.sqrt(array2)
Out[15]: array([[1.
[2.
, 1.41421356, 1.73205081],
, 2.23606798, 2.44948974]])

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

代码语言:javascript复制
In [16]: array2.sum(axis=0) # 返回一维数组
Out[16]: array([5., 7., 9.])

参数 axis=0 表示以行为轴,参数 axis=1 表示以列为轴,就像图 4-1 中那样。省略 axis 参数会将整个数组加起来

代码语言:javascript复制
In [17]: array2.sum()
Out[17]: 21.0

创建和操作数组

存取元素

image-20230704183428516

代码语言:javascript复制
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.])

记住,对二维数组的行或列进行切片,得到的是一个一维数组,而不是二维列向量或行向量。

方便的数组构造器

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

代码语言:javascript复制
In [23]: np.arange(2 * 5).reshape(2, 5)
Out[23]: array([[0, 1, 2, 3, 4],
[5, 6, 7, 8, 9]])

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

代码语言:javascript复制
In [24]: np.random.randn(2, 3) # 2行,3列
Out[24]: array([[-0.30047275, -1.19614685, -0.13652283],
[ 1.05769357, 0.03347978, -1.2153504 ]]

视图和副本

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

代码语言:javascript复制
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] 的代码改成下面这样,对副本进行操作不会影响原数组。

代码语言:javascript复制
subset = array2[:, :2].copy()

0 人点赞