Python 金融编程第二版(GPT 重译)(二)(1)

简介: Python 金融编程第二版(GPT 重译)(二)

第四章:使用 NumPy 进行数值计算

计算机是无用的。它们只能给出答案。

巴勃罗·毕加索

介绍

本章介绍了 Python基本数据类型和数据结构。尽管 Python 解释器本身已经带来了丰富的数据结构,但 NumPy 和其他库以有价值的方式添加了这些数据结构。

本章组织如下:

数据数组

本节详细讨论了数组的概念,并说明了在 Python 中处理数据数组的基本选项。

NumPy 数据结构

本节致力于介绍 NumPy ndarray 类的特性和功能,并展示了该类对科学和金融应用的一些好处。

代码向量化

本节说明了,由于 NumPy 的数组类,向量化代码很容易实现,从而导致代码更紧凑,性能更好。

本章涵盖了以下数据结构:

对象类型 含义 用法/模型
ndarray(常规) n 维数组对象 大量数值数据的大数组
ndarray(记录) 二维数组对象 以列组织的表格数据

本章组织如下:

“数据数组”

本节讨论了使用纯 Python 代码处理数据数组的方法。

[待添加链接]

这是关于常规 NumPy ndarray 类的核心部分;它是几乎所有数据密集型 Python 使用案例中的主要工具。

[待添加链接]

这个简短的部分介绍了用于处理带有列的表格数据的结构化(或记录)ndarray 对象。

“代码的向量化”

在本节中,讨论了代码的向量化及其好处;该部分还讨论了在某些情况下内存布局的重要性。

数据数组

前一章表明 Python 提供了一些非常有用和灵活的通用数据结构。特别是,list  对象可以被认为是一个真正的工作马,具有许多方便的特性和应用领域。在一般情况下,使用这样一个灵活的(可变的)数据结构的代价在于相对较高的内存使用量,较慢的性能或两者兼有。然而,科学和金融应用通常需要对特殊数据结构进行高性能操作。在这方面最重要的数据结构之一是数组。数组通常以行和列的形式结构化其他(基本)相同数据类型的对象。

暂时假设我们仅使用数字,尽管这个概念也可以推广到其他类型的数据。在最简单的情况下,一维数组在数学上表示为向量,通常由float对象内部表示为实数的一行或一列元素组成。在更普遍的情况下,数组表示为i × j 矩阵的元素。这个概念在三维中也可以推广为i × j × k 立方体的元素以及形状为i × j × k × l × …的一般n维数组。

线性代数和向量空间理论等数学学科说明了这些数学结构在许多科学学科和领域中的重要性。因此,设计一个专门的数据结构类来方便和高效地处理数组可能是非常有益的。这就是PythonNumPy的作用所在,其ndarray类应运而生。在下一节介绍其强大的ndarray类之前,本节展示了两种处理数组的替代方法。

使用 Python 列表的数组

在转向NumPy之前,让我们首先用上一节介绍的内置数据结构构建数组。list对象特别适用于完成这项任务。一个简单的list已经可以被视为一维数组:

In [1]: v = [0.5, 0.75, 1.0, 1.5, 2.0]  # ①

list对象与数字。

由于list对象可以包含任意其他对象,它们也可以包含其他list对象。通过嵌套list对象,可以轻松构建二维和更高维的数组:

In [2]: m = [v, v, v]  # ①
        m  # ②
Out[2]: [[0.5, 0.75, 1.0, 1.5, 2.0],
         [0.5, 0.75, 1.0, 1.5, 2.0],
         [0.5, 0.75, 1.0, 1.5, 2.0]]

list对象与list对象…

… 得到一个数字矩阵。

我们还可以通过简单的索引选择行或通过双重索引选择单个元素(然而,选择整列并不那么容易):

In [3]: m[1]
Out[3]: [0.5, 0.75, 1.0, 1.5, 2.0]
In [4]: m[1][0]
Out[4]: 0.5

嵌套可以进一步推广到更一般的结构:

In [5]: v1 = [0.5, 1.5]
        v2 = [1, 2]
        m = [v1, v2]
        c = [m, m]  # ①
        c
Out[5]: [[[0.5, 1.5], [1, 2]], [[0.5, 1.5], [1, 2]]]
In [6]: c[1][1][0]
Out[6]: 1

立方数。

请注意,刚刚介绍的对象组合方式通常使用对原始对象的引用指针。这在实践中意味着什么?让我们看看以下操作:

In [7]: v = [0.5, 0.75, 1.0, 1.5, 2.0]
        m = [v, v, v]
        m
Out[7]: [[0.5, 0.75, 1.0, 1.5, 2.0],
         [0.5, 0.75, 1.0, 1.5, 2.0],
         [0.5, 0.75, 1.0, 1.5, 2.0]]

现在修改v对象的第一个元素的值,看看m对象会发生什么变化:

In [8]: v[0] = 'Python'
        m
Out[8]: [['Python', 0.75, 1.0, 1.5, 2.0],
         ['Python', 0.75, 1.0, 1.5, 2.0],
         ['Python', 0.75, 1.0, 1.5, 2.0]]

通过使用copy模块的deepcopy函数,可以避免这种情况:

In [9]: from copy import deepcopy
        v = [0.5, 0.75, 1.0, 1.5, 2.0]
        m = 3 * [deepcopy(v), ]  # ①
        m
Out[9]: [[0.5, 0.75, 1.0, 1.5, 2.0],
         [0.5, 0.75, 1.0, 1.5, 2.0],
         [0.5, 0.75, 1.0, 1.5, 2.0]]
In [10]: v[0] = 'Python'  # ②
         m  # ③
Out[10]: [[0.5, 0.75, 1.0, 1.5, 2.0],
          [0.5, 0.75, 1.0, 1.5, 2.0],
          [0.5, 0.75, 1.0, 1.5, 2.0]]

使用物理副本而不是引用指针。

因此,对原始对象的更改…

… 不再有任何影响。

Python 数组类

Python 中有一个专用的array模块可用。正如您可以在文档页面上阅读到的(参见https://docs.python.org/3/library/array.html):

该模块定义了一种对象类型,可以紧凑地表示基本值的数组:字符、整数、浮点数。数组是序列类型,并且行为非常像列表,只是存储在其中的对象类型受到限制。类型在对象创建时通过使用类型代码(一个单个字符)来指定。

考虑以下代码,将一个list对象实例化为一个array对象。

In [11]: v = [0.5, 0.75, 1.0, 1.5, 2.0]
In [12]: import array
In [13]: a = array.array('f', v)  # ①
         a
Out[13]: array('f', [0.5, 0.75, 1.0, 1.5, 2.0])
In [14]: a.append(0.5)  # ②
         a
Out[14]: array('f', [0.5, 0.75, 1.0, 1.5, 2.0, 0.5])
In [15]: a.extend([5.0, 6.75])  # ②
         a
Out[15]: array('f', [0.5, 0.75, 1.0, 1.5, 2.0, 0.5, 5.0, 6.75])
In [16]: 2 * a  # ③
Out[16]: array('f', [0.5, 0.75, 1.0, 1.5, 2.0, 0.5, 5.0, 6.75, 0.5, 0.75, 1.0, 1.5, 2.0, 0.5, 5.0, 6.75])

使用float作为类型代码实例化array对象。

主要方法的工作方式类似于list对象的方法。

虽然“标量乘法”原理上可行,但结果不是数学上预期的;而是元素被重复。

尝试附加与指定数据类型不同的对象会引发TypeError

In [17]: # a.append('string') # ①
In [18]: a.tolist()  # ②
Out[18]: [0.5, 0.75, 1.0, 1.5, 2.0, 0.5, 5.0, 6.75]

仅能附加float对象;其他数据类型/类型代码会引发错误。

然而,如果需要这样的灵活性,array对象可以轻松转换回list对象。

array类的一个优点是它具有内置的存储和检索功能。

In [19]: f = open('array.apy', 'wb')  # ①
         a.tofile(f)  # ②
         f.close()  # ③
In [20]: with open('array.apy', 'wb') as f:  # ④
             a.tofile(f)  # ④
In [21]: !ls -n arr*  # ⑤
         -rw-r--r--@ 1 503  20  32 29 Dez 17:08 array.apy

打开一个用于写入二进制数据的磁盘上的文件。

array数据写入文件。

关闭文件。

或者,可以使用with上下文执行相同的操作。

这显示了磁盘上写入的文件。

与以前一样,从磁盘读取数据时,array对象的数据类型很重要。

In [22]: b = array.array('f')  # ①
In [23]: with open('array.apy', 'rb') as f:  # ②
             b.fromfile(f, 5)  # ③
In [24]: b  # ④
Out[24]: array('f', [0.5, 0.75, 1.0, 1.5, 2.0])
In [25]: b = array.array('d')  # ⑤
In [26]: with open('array.apy', 'rb') as f:
             b.fromfile(f, 2)  # ⑥
In [27]: b  # ⑦
Out[27]: array('d', [0.0004882813645963324, 0.12500002956949174])

使用类型代码float创建一个新的array对象。

打开文件以读取二进制数据…

…并在b对象中读取五个元素。

使用类型代码double创建一个新的array对象。

从文件中读取两个元素。

类型代码的差异导致“错误”的数字。

常规 NumPy 数组

显然,使用list对象构成数组结构有些作用。但这并不是真正方便的方式,而且list类并没有为此特定目标而构建。它的范围更广泛,更一般。array类已经稍微更专业一些,提供了一些有用的特性来处理数据数组。然而,某种“高度”专业化的类因此可能真的对处理数组类型的结构非常有益。

基础知识

这样一个专门的类就是numpy.ndarray类,它的特定目标是方便且高效地处理n维数组,即以高性能的方式。这个类的基本处理最好通过示例来说明:

In [28]: import numpy as np  # ①
In [29]: a = np.array([0, 0.5, 1.0, 1.5, 2.0])  # ②
         a
Out[29]: array([ 0. ,  0.5,  1. ,  1.5,  2. ])
In [30]: type(a)  # ②
Out[30]: numpy.ndarray
In [31]: a = np.array(['a', 'b', 'c'])  # ③
         a
Out[31]: array(['a', 'b', 'c'],
               dtype='<U1')
In [32]: a = np.arange(2, 20, 2)  # ④
         a
Out[32]: array([ 2,  4,  6,  8, 10, 12, 14, 16, 18])
In [33]: a = np.arange(8, dtype=np.float)  # ⑤
         a
Out[33]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.])
In [34]: a[5:]  # ⑥
Out[34]: array([ 5.,  6.,  7.])
In [35]: a[:2]  # ⑥
Out[35]: array([ 0.,  1.])

导入numpy包。

通过list对象中的浮点数创建一个ndarray对象。

通过list对象中的字符串创建一个ndarray对象。

np.arange的工作方式类似于range

然而,它接受附加输入dtype参数。

对于一维的ndarray对象,索引的工作方式与平常一样。

ndarray类的一个重要特性是内置方法的多样性。例如:

In [36]: a.sum()  # ①
Out[36]: 28.0
In [37]: a.std()  # ②
Out[37]: 2.2912878474779199
In [38]: a.cumsum()  # ③
Out[38]: array([  0.,   1.,   3.,   6.,  10.,  15.,  21.,  28.])

所有元素的总和。

元素的标准偏差。

所有元素的累积和(从索引位置 0 开始)。

另一个重要特性是对ndarray对象定义的(向量化的)数学运算

In [39]: l = [0., 0.5, 1.5, 3., 5.]
         2 * l  # ①
Out[39]: [0.0, 0.5, 1.5, 3.0, 5.0, 0.0, 0.5, 1.5, 3.0, 5.0]
In [40]: a
Out[40]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.])
In [41]: 2 * a  # ②
Out[41]: array([  0.,   2.,   4.,   6.,   8.,  10.,  12.,  14.])
In [42]: a ** 2  # ③
Out[42]: array([  0.,   1.,   4.,   9.,  16.,  25.,  36.,  49.])
In [43]: 2 ** a  # ④
Out[43]: array([   1.,    2.,    4.,    8.,   16.,   32.,   64.,  128.])
In [44]: a ** a  # ⑤
Out[44]: array([  1.00000000e+00,   1.00000000e+00,   4.00000000e+00,
                  2.70000000e+01,   2.56000000e+02,   3.12500000e+03,
                  4.66560000e+04,   8.23543000e+05])

list对象的“标量乘法”导致元素的重复。

相比之下,使用ndarray对象实现了适当的标量乘法,例如。

这个计算每个元素的平方值。

这解释了ndarray的元素作为幂。

这个计算每个元素的自身的幂。

NumPy包的另一个重要功能是通用函数。它们在一般情况下对ndarray对象以及基本 Python 数据类型进行操作。然而,当将通用函数应用于 Python float对象时,需要注意与math模块中相同功能的性能降低。

In [45]: np.exp(a)  # ①
Out[45]: array([  1.00000000e+00,   2.71828183e+00,   7.38905610e+00,
                  2.00855369e+01,   5.45981500e+01,   1.48413159e+02,
                  4.03428793e+02,   1.09663316e+03])
In [46]: np.sqrt(a)  # ②
Out[46]: array([ 0.        ,  1.        ,  1.41421356,  1.73205081,  2.        ,
                 2.23606798,  2.44948974,  2.64575131])
In [47]: np.sqrt(2.5)  # ③
Out[47]: 1.5811388300841898
In [48]: import math  # ④
In [49]: math.sqrt(2.5)  # ④
Out[49]: 1.5811388300841898
In [50]: # math.sqrt(a) # ⑤
In [51]: %timeit np.sqrt(2.5)  # ⑥
         703 ns ± 17.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
In [52]: %timeit math.sqrt(2.5)  # ⑦
         107 ns ± 1.48 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)

逐个元素计算指数值。

计算每个元素的平方根。

计算 Python float对象的平方根。

相同的计算,这次使用math模块。

math.sqrt不能直接应用于ndarray对象。

将通用函数np.sqrt应用于 Python float对象……

……比使用math.sqrt函数的相同操作慢得多。

多维度

切换到多维度是无缝的,并且到目前为止呈现的所有特征都适用于更一般的情况。特别是,索引系统在所有维度上保持一致:

In [53]: b = np.array([a, a * 2])  # ①
         b
Out[53]: array([[  0.,   1.,   2.,   3.,   4.,   5.,   6.,   7.],
                [  0.,   2.,   4.,   6.,   8.,  10.,  12.,  14.]])
In [54]: b[0]  # ②
Out[54]: array([ 0.,  1.,  2.,  3.,  4.,  5.,  6.,  7.])
In [55]: b[0, 2]  # ③
Out[55]: 2.0
In [56]: b[:, 1]  # ④
Out[56]: array([ 1.,  2.])
In [57]: b.sum()  # ⑤
Out[57]: 84.0
In [58]: b.sum(axis=0)  # ⑥
Out[58]: array([  0.,   3.,   6.,   9.,  12.,  15.,  18.,  21.])
In [59]: b.sum(axis=1)  # ⑦
Out[59]: array([ 28.,  56.])

用一维数组构造二维ndarray对象。

选择第一行。

选择第一行的第三个元素;在括号内,索引由逗号分隔。

选择第二列。

计算所有值的总和。

沿第一个轴计算总和,即按列计算。

沿第二轴计算总和,即按行计算。

有多种方法可以初始化(实例化)ndarray对象。一种方法如前所述,通过np.array。然而,这假定数组的所有元素已经可用。相比之下,也许我们希望首先实例化ndarray对象,以便在执行代码期间生成的结果后来填充它们。为此,我们可以使用以下函数:

In [60]: c = np.zeros((2, 3), dtype='i', order='C')  # ①
         c
Out[60]: array([[0, 0, 0],
                [0, 0, 0]], dtype=int32)
In [61]: c = np.ones((2, 3, 4), dtype='i', order='C')  # ②
         c
Out[61]: array([[[1, 1, 1, 1],
                 [1, 1, 1, 1],
                 [1, 1, 1, 1]],
                [[1, 1, 1, 1],
                 [1, 1, 1, 1],
                 [1, 1, 1, 1]]], dtype=int32)
In [62]: d = np.zeros_like(c, dtype='f16', order='C')  # ③
         d
Out[62]: array([[[ 0.0,  0.0,  0.0,  0.0],
                 [ 0.0,  0.0,  0.0,  0.0],
                 [ 0.0,  0.0,  0.0,  0.0]],
                [[ 0.0,  0.0,  0.0,  0.0],
                 [ 0.0,  0.0,  0.0,  0.0],
                 [ 0.0,  0.0,  0.0,  0.0]]], dtype=float128)
In [63]: d = np.ones_like(c, dtype='f16', order='C')  # ③
         d
Out[63]: array([[[ 1.0,  1.0,  1.0,  1.0],
                 [ 1.0,  1.0,  1.0,  1.0],
                 [ 1.0,  1.0,  1.0,  1.0]],
                [[ 1.0,  1.0,  1.0,  1.0],
                 [ 1.0,  1.0,  1.0,  1.0],
                 [ 1.0,  1.0,  1.0,  1.0]]], dtype=float128)
In [64]: e = np.empty((2, 3, 2))  # ④
         e
Out[64]: array([[[  0.00000000e+000,  -4.34540174e-311],
                 [  2.96439388e-323,   0.00000000e+000],
                 [  0.00000000e+000,   1.16095484e-028]],
                [[  2.03147708e-110,   9.67661175e-144],
                 [  9.80058441e+252,   1.23971686e+224],
                 [  4.00695466e+252,   8.34404939e-309]]])
In [65]: f = np.empty_like(c)  # ④
         f
Out[65]: array([[[0, 0, 0, 0],
                 [9, 0, 0, 0],
                 [0, 0, 0, 0]],
                [[0, 0, 0, 0],
                 [0, 0, 0, 0],
                 [0, 0, 0, 0]]], dtype=int32)
In [66]: np.eye(5)  # ⑤
Out[66]: array([[ 1.,  0.,  0.,  0.,  0.],
                [ 0.,  1.,  0.,  0.,  0.],
                [ 0.,  0.,  1.,  0.,  0.],
                [ 0.,  0.,  0.,  1.,  0.],
                [ 0.,  0.,  0.,  0.,  1.]])
In [67]: g = np.linspace(5, 15, 15) # ⑥
         g
Out[67]: array([  5.        ,   5.71428571,   6.42857143,   7.14285714,
                  7.85714286,   8.57142857,   9.28571429,  10.        ,
                 10.71428571,  11.42857143,  12.14285714,  12.85714286,
                 13.57142857,  14.28571429,  15.        ])

用零预先填充的ndarray对象。

用 1 预先填充的ndarray对象。

相同,但采用另一个ndarray对象来推断形状。

ndarray对象不预先填充任何内容(数字取决于内存中存在的位)。

创建一个由 1 填充对角线的方阵作为ndarray对象。

创建一个一维ndarray对象,其中数字之间的间隔均匀分布;所使用的参数是startendnum(元素数量)。

使用所有这些函数,我们可以提供以下参数:

shape

要么是一个int,一个``int+s序列,或者是对另一个+numpy.ndarray的引用

dtype(可选)

一个dtype——这些是NumPy特定的numpy.ndarray对象的数据类型

order(可选)

存储元素在内存中的顺序:C表示C风格(即,逐行),或F表示Fortran风格(即,逐列)

在这里,NumPy如何通过ndarray类专门构建数组的方式,与基于list的方法进行比较变得明显:

  • ndarray对象具有内置的维度(轴)。
  • ndarray对象是不可变的,其形状是固定的。
  • 它仅允许单一数据类型numpy.dtype)用于整个数组。

相反,array类只共享允许唯一数据类型(类型代码,dtype)的特性。

order参数的作用在本章稍后讨论。表 4-1 提供了numpy.dtype对象的概述(即,NumPy允许的基本数据类型)。

表 4-1。NumPy dtype 对象

dtype 描述 示例
t 位域 t4 (4 位)
b 布尔 b(true 或 false)
i 整数 i8 (64 位)
u 无符号整数 u8 (64 位)
f 浮点数 f8 (64 位)
c 复数浮点数 c16 (128 位)
O 对象 0 (对象指针)
S, a 字符串 S24 (24 个字符)
U Unicode U24 (24 个 Unicode 字符)
V 其他 V12 (12 字节数据块)

元信息

每个ndarray对象都提供访问一些有用属性的功能。

In [68]: g.size  # ①
Out[68]: 15
In [69]: g.itemsize  # ②
Out[69]: 8
In [70]: g.ndim  # ③
Out[70]: 1
In [71]: g.shape  # ④
Out[71]: (15,)
In [72]: g.dtype  # ⑤
Out[72]: dtype('float64')
In [73]: g.nbytes  # ⑥
Out[73]: 120

元素的数量。

用于表示一个元素所使用的字节数。

维度的数量。

ndarray对象的形状。

元素的dtype

内存中使用的总字节数。

重塑和调整大小

虽然ndarray对象默认是不可变的,但有多种选项可以重塑和调整此类对象。一般情况下,第一个操作只是提供相同数据的另一个视图,而第二个操作一般会创建一个新的(临时)对象。

In [74]: g = np.arange(15)
In [75]: g
Out[75]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
In [76]: g.shape  # ①
Out[76]: (15,)
In [77]: np.shape(g) # ①
Out[77]: (15,)
In [78]: g.reshape((3, 5))  # ②
Out[78]: array([[ 0,  1,  2,  3,  4],
                [ 5,  6,  7,  8,  9],
                [10, 11, 12, 13, 14]])
In [79]: h = g.reshape((5, 3))  # ③
         h
Out[79]: array([[ 0,  1,  2],
                [ 3,  4,  5],
                [ 6,  7,  8],
                [ 9, 10, 11],
                [12, 13, 14]])
In [80]: h.T  # ④
Out[80]: array([[ 0,  3,  6,  9, 12],
                [ 1,  4,  7, 10, 13],
                [ 2,  5,  8, 11, 14]])
In [81]: h.transpose()  # ④
Out[81]: array([[ 0,  3,  6,  9, 12],
                [ 1,  4,  7, 10, 13],
                [ 2,  5,  8, 11, 14]])

原始ndarray对象的形状。

重塑为两个维度(内存视图)。

创建新对象。

ndarray对象的转置。

在重塑操作期间,ndarray对象中的元素总数保持不变。在调整大小操作期间,此数字会更改,即它要么减少(“向下调整”),要么增加(“向上调整”)。

In [82]: g
Out[82]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
In [83]: np.resize(g, (3, 1))  # ①
Out[83]: array([[0],
                [1],
                [2]])
In [84]: np.resize(g, (1, 5))  # ①
Out[84]: array([[0, 1, 2, 3, 4]])
In [85]: np.resize(g, (2, 5))  # ①
Out[85]: array([[0, 1, 2, 3, 4],
                [5, 6, 7, 8, 9]])
In [86]: n = np.resize(g, (5, 4))  # ②
         n
Out[86]: array([[ 0,  1,  2,  3],
                [ 4,  5,  6,  7],
                [ 8,  9, 10, 11],
                [12, 13, 14,  0],
                [ 1,  2,  3,  4]])

两个维度,向下调整。

两个维度,向上调整。

堆叠是一种特殊操作,允许水平或垂直组合两个ndarray对象。但是,“连接”维度的大小必须相同。

In [87]: h
Out[87]: array([[ 0,  1,  2],
                [ 3,  4,  5],
                [ 6,  7,  8],
                [ 9, 10, 11],
                [12, 13, 14]])
In [88]: np.hstack((h, 2 * h))  # ①
Out[88]: array([[ 0,  1,  2,  0,  2,  4],
                [ 3,  4,  5,  6,  8, 10],
                [ 6,  7,  8, 12, 14, 16],
                [ 9, 10, 11, 18, 20, 22],
                [12, 13, 14, 24, 26, 28]])
In [89]: np.vstack((h, 0.5 * h))  # ②
Out[89]: array([[  0. ,   1. ,   2. ],
                [  3. ,   4. ,   5. ],
                [  6. ,   7. ,   8. ],
                [  9. ,  10. ,  11. ],
                [ 12. ,  13. ,  14. ],
                [  0. ,   0.5,   1. ],
                [  1.5,   2. ,   2.5],
                [  3. ,   3.5,   4. ],
                [  4.5,   5. ,   5.5],
                [  6. ,   6.5,   7. ]])

水平堆叠两个ndarray对象。

垂直堆叠两个ndarray对象。

另一个特殊操作是将多维ndarray对象展平为一维对象。可以选择是按行(C顺序)还是按列(F顺序)进行展平。

In [90]: h
Out[90]: array([[ 0,  1,  2],
                [ 3,  4,  5],
                [ 6,  7,  8],
                [ 9, 10, 11],
                [12, 13, 14]])
In [91]: h.flatten()  # ①
Out[91]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
In [92]: h.flatten(order='C')  # ①
Out[92]: array([ 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14])
In [93]: h.flatten(order='F')  # ②
Out[93]: array([ 0,  3,  6,  9, 12,  1,  4,  7, 10, 13,  2,  5,  8, 11, 14])
In [94]: for i in h.flat:  # ③
             print(i, end=',')
         0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
In [95]: for i in h.ravel(order='C'):  # ④
             print(i, end=',')
         0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,
In [96]: for i in h.ravel(order='F'):  # ④
             print(i, end=',')
         0,3,6,9,12,1,4,7,10,13,2,5,8,11,14,

平铺的默认顺序是C

F顺序展平。

flat属性提供了一个平坦的迭代器(C顺序)。

ravel()方法是flatten()的另一种选择。

布尔数组

比较和逻辑操作通常在ndarray对象上像在标准 Python 数据类型上一样逐元素地进行。默认情况下,评估条件会产生一个布尔ndarray对象(dtypebool)。

In [164]: h
Out[164]: array([[ 0,  1,  2],
                 [ 3,  4,  5],
                 [ 6,  7,  8],
                 [ 9, 10, 11],
                 [12, 13, 14]])
In [150]: h > 8  # ①
Out[150]: array([[False, False, False],
                 [False, False, False],
                 [False, False, False],
                 [ True,  True,  True],
                 [ True,  True,  True]], dtype=bool)
In [151]: h <= 7  # ②
Out[151]: array([[ True,  True,  True],
                 [ True,  True,  True],
                 [ True,  True, False],
                 [False, False, False],
                 [False, False, False]], dtype=bool)
In [152]: h == 5  # ③
Out[152]: array([[False, False, False],
                 [False, False,  True],
                 [False, False, False],
                 [False, False, False],
                 [False, False, False]], dtype=bool)
In [158]: (h == 5).astype(int)  # ④
Out[158]: array([[0, 0, 0],
                 [0, 0, 1],
                 [0, 0, 0],
                 [0, 0, 0],
                 [0, 0, 0]])
In [165]: (h > 4) & (h <= 12)  # ⑤
Out[165]: array([[False, False, False],
                 [False, False,  True],
                 [ True,  True,  True],
                 [ True,  True,  True],
                 [ True, False, False]], dtype=bool)

值是否大于…?

值是否小于或等于…?

值是否等于…?

以整数值 0 和 1 表示TrueFalse

值是否大于…且小于或等于…?

此类布尔数组可用于索引和数据选择。注意以下操作会展平数据。

In [153]: h[h > 8]  # ①
Out[153]: array([ 9, 10, 11, 12, 13, 14])
In [155]: h[(h > 4) & (h <= 12)]  # ②
Out[155]: array([ 5,  6,  7,  8,  9, 10, 11, 12])
In [157]: h[(h < 4) | (h >= 12)]  # ③
Out[157]: array([ 0,  1,  2,  3, 12, 13, 14])

给我所有大于…的值。

给我所有大于… 小于或等于…的值。

给我所有大于… 小于或等于…的值。

在这方面的一个强大工具是np.where()函数,它允许根据条件是True还是False来定义操作/操作。应用np.where()的结果是一个与原始对象相同形状的新ndarray对象。

In [159]: np.where(h > 7, 1, 0)  # ①
Out[159]: array([[0, 0, 0],
                 [0, 0, 0],
                 [0, 0, 1],
                 [1, 1, 1],
                 [1, 1, 1]])
In [160]: np.where(h % 2 == 0, 'even', 'odd')  # ②
Out[160]: array([['even', 'odd', 'even'],
                 ['odd', 'even', 'odd'],
                 ['even', 'odd', 'even'],
                 ['odd', 'even', 'odd'],
                 ['even', 'odd', 'even']],
                dtype='<U4')
In [163]: np.where(h <= 7, h * 2, h / 2)  # ③
Out[163]: array([[  0. ,   2. ,   4. ],
                 [  6. ,   8. ,  10. ],
                 [ 12. ,  14. ,   4. ],
                 [  4.5,   5. ,   5.5],
                 [  6. ,   6.5,   7. ]])

在新对象中,如果为True,则设置为1,否则设置为0

在新对象中,如果为True,则设置为even,否则设置为odd

在新对象中,如果为True,则将h元素设置为两倍,否则将h元素设置为一半。

后续章节提供了关于ndarray对象上这些重要操作的更多示例。

速度比较

在转向具有NumPy的结构化数组之前,让我们暂时保持常规数组,并看看专业化在性能方面带来了什么。

以一个简单的例子为例,假设我们想要生成一个形状为 5,000 × 5,000 元素的矩阵/数组,填充了(伪)随机的标准正态分布的数字。然后我们想要计算所有元素的总和。首先,纯Python方法,我们使用list推导来实现:

In [97]: import random
         I = 5000
In [98]: %time mat = [[random.gauss(0, 1) for j in range(I)] \
                      for i in range(I)]  # ①
         CPU times: user 20.9 s, sys: 372 ms, total: 21.3 s
         Wall time: 21.3 s
In [99]: mat[0][:5]  # ②
Out[99]: [0.02023704728430644,
          -0.5773300286314157,
          -0.5034574089604074,
          -0.07769332062744054,
          -0.4264012594572326]
In [100]: %time sum([sum(l) for l in mat])  # ③
          CPU times: user 156 ms, sys: 1.93 ms, total: 158 ms
          Wall time: 158 ms
Out[100]: 681.9120404070142
In [101]: import sys
          sum([sys.getsizeof(l) for l in mat])  # ④
Out[101]: 215200000

通过嵌套的列表推导来创建矩阵。

从所绘制的数字中选择一些随机数。

首先在列表推导中计算单个list对象的总和;然后计算总和的总和。

添加所有list对象的内存使用量。

现在让我们转向NumPy,看看同样的问题是如何在那里解决的。为了方便,NumPy子库random提供了许多函数来实例化一个ndarray对象,并同时填充它(伪)随机数:

In [102]: %time mat = np.random.standard_normal((I, I))  # ①
          CPU times: user 1.14 s, sys: 170 ms, total: 1.31 s
          Wall time: 1.32 s
In [103]: %time mat.sum()  # ②
          CPU times: user 29.5 ms, sys: 1.32 ms, total: 30.8 ms
          Wall time: 29.7 ms
Out[103]: 2643.0006104377485
In [104]: mat.nbytes  # ③
Out[104]: 200000000
In [105]: sys.getsizeof(mat)  # ③
Out[105]: 200000112

使用标准正态分布的随机数字创建ndarray对象;速度约快 20 倍。

计算ndarray对象中所有值的总和;速度约快 6 倍。

NumPy方法也节省了一些内存,因为ndarray对象的内存开销与数据本身的大小相比微不足道。

我们观察到以下情况:

语法

尽管我们使用了几种方法来压缩纯Python代码,但NumPy版本更加紧凑和易读。

性能

生成ndarray对象的速度大约快了 20 倍,求和的计算速度大约快了 6 倍,比纯Python中的相应操作更快。

使用 NumPy 数组

使用NumPy进行基于数组的操作和算法通常会导致代码紧凑、易读,并且与纯Python代码相比具有显著的性能改进。

结构化 NumPy 数组

ndarray类的专业化显然带来了许多有价值的好处。然而,太窄的专业化可能对大多数基于数组的算法和应用程序来说是一个太大的负担。因此,NumPy提供了允许每列具有不同dtype结构化记录ndarray对象。什么是“每列”?考虑以下结构化数组对象的初始化:

In [106]: dt = np.dtype([('Name', 'S10'), ('Age', 'i4'),
                         ('Height', 'f'), ('Children/Pets', 'i4', 2)])  # ①
In [107]: dt  # ①
Out[107]: dtype([('Name', 'S10'), ('Age', '<i4'), ('Height', '<f4'), ('Children/Pets', '<i4', (2,))])
In [108]: dt = np.dtype({'names': ['Name', 'Age', 'Height', 'Children/Pets'],
                       'formats':'O int float int,int'.split()})  # ②
In [109]: dt  # ②
Out[109]: dtype([('Name', 'O'), ('Age', '<i8'), ('Height', '<f8'), ('Children/Pets', [('f0', '<i8'), ('f1', '<i8')])])
In [110]: s = np.array([('Smith', 45, 1.83, (0, 1)),
                        ('Jones', 53, 1.72, (2, 2))], dtype=dt)  # ③
In [111]: s  # ③
Out[111]: array([('Smith', 45,  1.83, (0, 1)), ('Jones', 53,  1.72, (2, 2))],
                dtype=[('Name', 'O'), ('Age', '<i8'), ('Height', '<f8'), ('Children/Pets', [('f0', '<i8'), ('f1', '<i8')])])
In [112]: type(s)  # ④
Out[112]: numpy.ndarray

复杂的dtype是由几部分组成的。

实现相同结果的替代语法。

结构化ndarray以两条记录实例化。

对象类型仍然是numpy.ndarray

从某种意义上说,这个构造与初始化SQL数据库中的表格的操作非常接近。我们有列名和列数据类型,可能还有一些附加信息(例如,每个string对象的最大字符数)。现在可以通过它们的名称轻松访问单个列,并通过它们的索引值访问行:

In [113]: s['Name']  # ①
Out[113]: array(['Smith', 'Jones'], dtype=object)
In [114]: s['Height'].mean()  # ②
Out[114]: 1.7749999999999999
In [115]: s[0]  # ③
Out[115]: ('Smith', 45,  1.83, (0, 1))
In [116]: s[1]['Age']  # ④
Out[116]: 53

通过名称选择一列。

在选定的列上调用方法。

选择一条记录。

选择记录中的一个字段。

总之,结构化数组是常规numpy.ndarray对象类型的泛化,因为数据类型只需在每列上保持相同,就像在SQL数据库表格上的上下文中一样。结构化数组的一个优点是,列的单个元素可以是另一个多维对象,不必符合基本的NumPy数据类型。


Python 金融编程第二版(GPT 重译)(二)(2)https://developer.aliyun.com/article/1559305

相关文章
|
2天前
|
数据可视化 Python
Python 金融编程第二版(三)(4)
Python 金融编程第二版(三)
12 2
|
2天前
|
存储 数据可视化 API
Python 金融编程第二版(三)(5)
Python 金融编程第二版(三)
8 1
|
2天前
|
存储 机器学习/深度学习 关系型数据库
Python 金融编程第二版(四)(5)
Python 金融编程第二版(四)
8 0
|
2天前
|
存储 SQL 数据库
Python 金融编程第二版(四)(4)
Python 金融编程第二版(四)
8 0
|
2天前
|
SQL 存储 数据库
Python 金融编程第二版(四)(3)
Python 金融编程第二版(四)
8 0
|
2天前
|
存储 分布式计算 数据可视化
Python 金融编程第二版(四)(2)
Python 金融编程第二版(四)
10 0
|
2天前
|
存储 SQL 数据可视化
Python 金融编程第二版(四)(1)
Python 金融编程第二版(四)
9 0
|
2天前
|
存储 索引 Python
Python 金融编程第二版(二)(1)
Python 金融编程第二版(二)
9 2
|
2天前
|
存储 算法 数据建模
Python 金融编程第二版(一)(5)
Python 金融编程第二版(一)
13 2
|
2天前
|
存储 数据可视化 索引
Python 金融编程第二版(三)(3)
Python 金融编程第二版(三)
11 1