Python 金融编程第二版(二)(2)

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

Python 金融编程第二版(二)(1)https://developer.aliyun.com/article/1559402


布尔数组

比较和逻辑操作通常在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数据类型。

结构化数组

NumPy提供了除了常规数组之外,还提供了结构化(记录)数组,允许描述和处理类似表格的数据结构,每个(命名的)列具有各种不同的数据类型。它们将SQL表格类似的数据结构带到了Python中,大部分具备常规ndarray对象的优点(语法、方法、性能)。

代码的向量化

代码的矢量化是一种获得更紧凑代码并可能更快执行的策略。其基本思想是对复杂对象进行“一次性”操作或应用函数,而不是通过循环遍历对象的单个元素。在Python中,函数式编程工具,如mapfilter,提供了一些基本的矢量化手段。然而,NumPy在其核心深处内置了矢量化。

基本矢量化

正如我们在上一节中学到的,简单的数学运算,如计算所有元素的总和,可以直接在ndarray对象上实现(通过方法或通用函数)。还可以进行更一般的矢量化操作。例如,我们可以按元素将两个NumPy数组相加如下:

In [117]: np.random.seed(100)
          r = np.arange(12).reshape((4, 3))  # ①
          s = np.arange(12).reshape((4, 3)) * 0.5  # ②
In [118]: r  # ①
Out[118]: array([[ 0,  1,  2],
                 [ 3,  4,  5],
                 [ 6,  7,  8],
                 [ 9, 10, 11]])
In [119]: s  # ②
Out[119]: array([[ 0. ,  0.5,  1. ],
                 [ 1.5,  2. ,  2.5],
                 [ 3. ,  3.5,  4. ],
                 [ 4.5,  5. ,  5.5]])
In [120]: r + s  # ③
Out[120]: array([[  0. ,   1.5,   3. ],
                 [  4.5,   6. ,   7.5],
                 [  9. ,  10.5,  12. ],
                 [ 13.5,  15. ,  16.5]])

具有随机数的第一个ndarray对象。

具有随机数的第二个ndarray对象。

逐元素加法作为矢量化操作(无循环)。

NumPy还支持所谓的广播。这允许在单个操作中组合不同形状的对象。我们之前已经使用过这个功能。考虑以下示例:

In [121]: r + 3  # ①
Out[121]: array([[ 3,  4,  5],
                 [ 6,  7,  8],
                 [ 9, 10, 11],
                 [12, 13, 14]])
In [122]: 2 * r  # ②
Out[122]: array([[ 0,  2,  4],
                 [ 6,  8, 10],
                 [12, 14, 16],
                 [18, 20, 22]])
In [123]: 2 * r + 3  # ③
Out[123]: array([[ 3,  5,  7],
                 [ 9, 11, 13],
                 [15, 17, 19],
                 [21, 23, 25]])

在标量加法期间,标量被广播并添加到每个元素。

在标量乘法期间,标量也广播并与每个元素相乘。

此线性变换结合了两个操作。

这些操作也适用于不同形状的ndarray对象,直到某个特定点为止:

In [124]: r
Out[124]: array([[ 0,  1,  2],
                 [ 3,  4,  5],
                 [ 6,  7,  8],
                 [ 9, 10, 11]])
In [125]: r.shape
Out[125]: (4, 3)
In [126]: s = np.arange(0, 12, 4)  # ①
          s  # ①
Out[126]: array([0, 4, 8])
In [127]: r + s  # ②
Out[127]: array([[ 0,  5, 10],
                 [ 3,  8, 13],
                 [ 6, 11, 16],
                 [ 9, 14, 19]])
In [128]: s = np.arange(0, 12, 3)  # ③
          s  # ③
Out[128]: array([0, 3, 6, 9])
In [129]: # r + s # ④
In [130]: r.transpose() + s  # ⑤
Out[130]: array([[ 0,  6, 12, 18],
                 [ 1,  7, 13, 19],
                 [ 2,  8, 14, 20]])
In [131]: sr = s.reshape(-1, 1)  # ⑥
          sr
Out[131]: array([[0],
                 [3],
                 [6],
                 [9]])
In [132]: sr.shape  # ⑥
Out[132]: (4, 1)
In [133]: r + s.reshape(-1, 1)  # ⑥
Out[133]: array([[ 0,  1,  2],
                 [ 6,  7,  8],
                 [12, 13, 14],
                 [18, 19, 20]])

长度为 3 的新一维ndarray对象。

r(矩阵)和s(向量)对象可以直接相加。

另一个长度为 4 的一维ndarray对象。

s(向量)对象的长度现在与r对象的第二维长度不同。

再次转置r对象允许进行矢量化加法。

或者,s的形状可以更改为(4, 1)以使加法起作用(但结果不同)。

通常情况下,自定义的Python函数也适用于numpy.ndarray。如果实现允许,数组可以像intfloat对象一样与函数一起使用。考虑以下函数:

In [134]: def f(x):
              return 3 * x + 5  # ①
In [135]: f(0.5)  # ②
Out[135]: 6.5
In [136]: f(r)  # ③
Out[136]: array([[ 5,  8, 11],
                 [14, 17, 20],
                 [23, 26, 29],
                 [32, 35, 38]])

实现对参数x进行线性变换的简单 Python 函数。

函数f应用于 Python 的float对象。

同一函数应用于ndarray对象,导致函数的向量化和逐个元素的评估。

NumPy所做的是简单地将函数f逐个元素地应用于对象。在这种意义上,通过使用这种操作,我们并避免循环;我们只是在Python级别上避免了它们,并将循环委托给了NumPy。在NumPy级别上,对ndarray对象进行循环处理是由高度优化的代码来完成的,其中大部分代码都是用C编写的,因此通常比纯Python快得多。这解释了在基于数组的用例中使用NumPy带来性能优势的“秘密”。

内存布局

当我们首次使用np.zero初始化numpy.ndarray对象时,我们提供了一个可选参数用于内存布局。这个参数大致指定了数组的哪些元素会被连续地存储在内存中。当处理小数组时,这几乎不会对数组操作的性能产生任何可测量的影响。然而,当数组变大并且取决于要在其上实现的(财务)算法时,情况可能会有所不同。这就是内存布局发挥作用的时候(参见,例如多维数组的内存布局)。

要说明数组的内存布局在科学和金融中的潜在重要性,考虑以下构建多维ndarray对象的情况:

In [137]: x = np.random.standard_normal((1000000, 5))  # ①
In [138]: y = 2 * x + 3  # ②
In [139]: C = np.array((x, y), order='C')  # ③
In [140]: F = np.array((x, y), order='F')  # ④
In [141]: x = 0.0; y = 0.0  # ⑤
In [142]: C[:2].round(2)  # ⑥
Out[142]: array([[[-1.75,  0.34,  1.15, -0.25,  0.98],
                  [ 0.51,  0.22, -1.07, -0.19,  0.26],
                  [-0.46,  0.44, -0.58,  0.82,  0.67],
                  ...,
                  [-0.05,  0.14,  0.17,  0.33,  1.39],
                  [ 1.02,  0.3 , -1.23, -0.68, -0.87],
                  [ 0.83, -0.73,  1.03,  0.34, -0.46]],
                 [[-0.5 ,  3.69,  5.31,  2.5 ,  4.96],
                  [ 4.03,  3.44,  0.86,  2.62,  3.51],
                  [ 2.08,  3.87,  1.83,  4.63,  4.35],
                  ...,
                  [ 2.9 ,  3.28,  3.33,  3.67,  5.78],
                  [ 5.04,  3.6 ,  0.54,  1.65,  1.26],
                  [ 4.67,  1.54,  5.06,  3.69,  2.07]]])

一个在两个维度上具有较大不对称性的ndarray对象。

对原始对象数据进行线性变换。

这将创建一个二维ndarray对象,其顺序为C(行优先)。

这将创建一个二维ndarray对象,其顺序为F(列优先)。

内存被释放(取决于垃圾收集)。

C对象中获取一些数字。

让我们看一些关于两种类型的ndarray对象的基本示例和用例,并考虑它们在不同内存布局下执行的速度:

In [143]: %timeit C.sum()  # ①
          4.65 ms ± 73.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [144]: %timeit F.sum()  # ①
          4.56 ms ± 105 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [145]: %timeit C.sum(axis=0)  # ②
          20.9 ms ± 358 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [146]: %timeit C.sum(axis=1)  # ③
          38.5 ms ± 1.1 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [147]: %timeit F.sum(axis=0)  # ②
          87.5 ms ± 1.37 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [148]: %timeit F.sum(axis=1)  # ③
          81.6 ms ± 1.66 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)
In [149]: F = 0.0; C = 0.0

计算所有元素的总和。

每行计算和(“许多”)。

计算每列的总和(“少”)。

我们可以总结性能结果如下:

  • 当计算所有元素的总和时,内存布局实际上并不重要。
  • C-ordered ndarray 对象的求和在行和列上都更快(绝对速度优势)。
  • 使用 C-ordered(行优先)ndarray 对象,对行求和相对比对列求和更快。
  • 使用 F-ordered(列优先)ndarray 对象,对列求和相对比对行求和更快。

结论

NumPy 是 Python 中数值计算的首选包。ndarray 类是专门设计用于处理(大)数值数据的高效方便的类。强大的方法和 NumPy 的通用函数允许进行向量化的代码,大部分避免了在 Python 层上的慢循环。本章介绍的许多方法也适用于 pandas 及其 DataFrame 类(见 第五章)

更多资源

有用的资源提供在:

优秀的 NumPy 介绍书籍包括:

  • McKinney, Wes(2017):Python 数据分析。第 2 版,O’Reilly,北京等。
  • VanderPlas, Jake(2016):Python 数据科学手册。O’Reilly,北京等。

第五章:数据分析与 pandas

数据!数据!数据!没有数据,我无法制造砖头!

夏洛克·福尔摩斯

简介

本章讨论的是pandas,这是一个专注于表格数据的数据分析库。pandas在最近几年已经成为一个强大的工具,不仅提供了强大的类和功能,还很好地封装了来自其他软件包的现有功能。结果是一个用户界面,使得数据分析,特别是金融分析,成为一项便捷和高效的任务。

pandas的核心和本章中的是DataFrame,一个有效处理表格形式数据的类,即以列为组织的数据。为此,DataFrame类提供了列标签以及对数据集的行(记录)进行灵活索引的能力,类似于关系数据库中的表或 Excel 电子表格。

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

对象类型 意义 用途/模型为
DataFrame 带有索引的二维数据对象 表格数据以列组织
Series 带有索引的一维数据对象 单一(时间)数据系列

本章组织如下:

“DataFrame 类”

本章从使用简单且小的数据集探索pandasDataFrame类的基本特征和能力开始;然后通过使用NumPyndarray对象并将其转换为DataFrame对象来进行处理。

“基本分析” 和 “基本可视化”

本章还展示了基本的分析和可视化能力,尽管后面的章节在这方面更深入。

“Series 类”

本节简要介绍了pandasSeries类,它在某种程度上代表了DataFrame类的一个特殊情况,只包含单列数据。

“GroupBy 操作”

DataFrame类的一大优势在于根据单个或多个列对数据进行分组。

“复杂选择”

使用(复杂)条件允许从DataFrame对象中轻松选择数据。

“串联、连接和合并”

将不同数据集合并为一个是数据分析中的重要操作。pandas提供了多种选项来完成这样的任务。

“性能方面”

与 Python 一般一样,pandas在一般情况下提供了多种选项来完成相同的目标。本节简要讨论潜在的性能差异。


Python 金融编程第二版(二)(3)https://developer.aliyun.com/article/1559404

相关文章
|
2天前
|
存储 分布式计算 数据可视化
Python 金融编程第二版(四)(2)
Python 金融编程第二版(四)
10 0
|
2天前
|
存储 SQL 数据可视化
Python 金融编程第二版(四)(1)
Python 金融编程第二版(四)
9 0
|
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天前
|
存储 SQL 数据可视化
Python 金融编程第二版(二)(4)
Python 金融编程第二版(二)
10 1
|
2天前
|
数据挖掘 索引 Python
Python 金融编程第二版(二)(5)
Python 金融编程第二版(二)
7 0
|
2天前
|
存储 索引 Python
Python 金融编程第二版(二)(1)
Python 金融编程第二版(二)
9 2