Python 金融编程第二版(GPT 重译)(四)
原文:
annas-archive.org/md5/d2f94efd019a2e2cb5c4fa9f260d63c
译者:飞龙
第八章:金融时间序列
时间的唯一目的是让一切不是同时发生。
阿尔伯特·爱因斯坦
金融时间序列数据是金融领域最重要的数据类型之一。这是按日期和/或时间索引的数据。例如,随着时间的推移,股票价格代表金融时间序列数据。类似地,随时间变化的欧元/美元汇率代表金融时间序列;汇率在较短的时间间隔内报价,一系列这样的报价则是汇率的时间序列。
没有任何金融学科能够不考虑时间因素而存在。这与物理学和其他科学基本相同。在 Python 中处理时间序列数据的主要工具是pandas
。pandas
的原始和主要作者 Wes McKinney 在 AQR Capital Management,一家大型对冲基金的分析员时开始开发这个库。可以肯定地说pandas
是从头开始设计用于处理金融时间序列数据的。
本章主要基于两个以逗号分隔值(CSV)文件形式的金融时间序列数据集。它沿以下线路进行:
“金融数据”
这一部分介绍了使用pandas
处理金融时间序列数据的基础知识:数据导入,导出摘要统计信息,计算随时间变化的数据和重采样。
“滚动统计”
在金融分析中,滚动统计起着重要作用。这些统计数据通常在一个固定的时间间隔内进行计算,并在整个数据集上“滚动前进”。简单移动平均线(SMAs)是一个流行的例子。这一部分说明了pandas
如何支持计算这种统计数据。
“相关性分析”
这一部分提供了一个基于标普 500 股票指数和 VIX 波动率指数的金融时间序列数据的案例研究。它为两个指数都呈现负相关的模式提供了一些支持。
“高频数据”
高频数据或者说 tick 数据在金融领域已经变得司空见惯。这一部分处理 tick 数据。pandas
再次在处理这样的数据集时表现强大。
金融数据
这一部分处理的是一个以 CSV 文件形式存储的本地金融数据集。从技术上讲,这些文件只是由逗号分隔单个值的数据行结构的文本文件。在导入数据之前,首先进行一些软件包导入和自定义。
In [1]: import numpy as np
import pandas as pd
from pylab import mpl, plt
plt.style.use('seaborn')
mpl.rcParams['font.family'] = 'serif'
%matplotlib inline
数据导入
pandas
提供了许多不同的函数和DataFrame
方法来导入以不同格式存储的数据(CSV、SQL、Excel 等)以及将数据导出为不同的格式(详见第九章)。下面的代码使用pd.read_csv()
函数从 CSV 文件中导入时间序列数据集。¹
In [2]: filename = '../../source/tr_eikon_eod_data.csv' ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [3]: !head -5 $filename ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Date,AAPL.O,MSFT.O,INTC.O,AMZN.O,GS.N,SPY,.SPX,.VIX,EUR=,XAU=,GDX,GLD
2010-01-04,30.57282657,30.95,20.88,133.9,173.08,113.33,1132.99,20.04,1.4411,1120.0,47.71,109.8
2010-01-05,30.625683660000004,30.96,20.87,134.69,176.14,113.63,1136.52,19.35,1.4368,1118.65,48.17,109.7
2010-01-06,30.138541290000003,30.77,20.8,132.25,174.26,113.71,1137.14,19.16,1.4412,1138.5,49.34,111.51
2010-01-07,30.082827060000003,30.452,20.6,130.0,177.67,114.19,1141.69,19.06,1.4318,1131.9,49.1,110.82
In [4]: data = pd.read_csv(filename, ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
index_col=0, ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
parse_dates=True) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
In [5]: data.info() ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1972 entries, 2010-01-04 to 2017-10-31
Data columns (total 12 columns):
AAPL.O 1972 non-null float64
MSFT.O 1972 non-null float64
INTC.O 1972 non-null float64
AMZN.O 1972 non-null float64
GS.N 1972 non-null float64
SPY 1972 non-null float64
.SPX 1972 non-null float64
.VIX 1972 non-null float64
EUR= 1972 non-null float64
XAU= 1972 non-null float64
GDX 1972 non-null float64
GLD 1972 non-null float64
dtypes: float64(12)
memory usage: 200.3 KB
指定路径和文件名。
显示原始数据的前五行(Linux/Mac)。
传递给pd.read_csv()
函数的文件名。
这指定第一列将被处理为索引。
这另外指定索引值的类型为日期时间。
结果的DataFrame
对象。
在这个阶段,金融分析师可能会通过检查数据或将其可视化(参见图 8-1)来首次查看数据。
In [6]: data.head() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[6]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX \
Date
2010-01-04 30.572827 30.950 20.88 133.90 173.08 113.33 1132.99 20.04
2010-01-05 30.625684 30.960 20.87 134.69 176.14 113.63 1136.52 19.35
2010-01-06 30.138541 30.770 20.80 132.25 174.26 113.71 1137.14 19.16
2010-01-07 30.082827 30.452 20.60 130.00 177.67 114.19 1141.69 19.06
2010-01-08 30.282827 30.660 20.83 133.52 174.31 114.57 1144.98 18.13
EUR= XAU= GDX GLD
Date
2010-01-04 1.4411 1120.00 47.71 109.80
2010-01-05 1.4368 1118.65 48.17 109.70
2010-01-06 1.4412 1138.50 49.34 111.51
2010-01-07 1.4318 1131.90 49.10 110.82
2010-01-08 1.4412 1136.10 49.84 111.37
In [7]: data.tail() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[7]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX \
Date
2017-10-25 156.41 78.63 40.78 972.91 241.71 255.29 2557.15 11.23
2017-10-26 157.41 78.76 41.35 972.43 241.72 255.62 2560.40 11.30
2017-10-27 163.05 83.81 44.40 1100.95 241.71 257.71 2581.07 9.80
2017-10-30 166.72 83.89 44.37 1110.85 240.89 256.75 2572.83 10.50
2017-10-31 169.04 83.18 45.49 1105.28 242.48 257.15 2575.26 10.18
EUR= XAU= GDX GLD
Date
2017-10-25 1.1812 1277.01 22.83 121.35
2017-10-26 1.1650 1266.73 22.43 120.33
2017-10-27 1.1608 1272.60 22.57 120.90
2017-10-30 1.1649 1275.86 22.76 121.13
2017-10-31 1.1644 1271.20 22.48 120.67
In [8]: data.plot(figsize=(10, 12), subplots=True) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
# plt.savefig('../../images/ch08/fts_01.png');
前五行 …
… 最后五行显示。
这通过多个子图可视化完整数据集。
图 8-1. 金融时间序列数据的线图
使用的数据来自汤森路透(TR)Eikon 数据 API。在 TR 世界中,金融工具的符号称为“路透社仪器代码
”或RICs
。单个RICs
代表的金融工具是:
In [9]: instruments = ['Apple Stock', 'Microsoft Stock',
'Intel Stock', 'Amazon Stock', 'Goldman Sachs Stock',
'SPDR S&P 500 ETF Trust', 'S&P 500 Index',
'VIX Volatility Index', 'EUR/USD Exchange Rate',
'Gold Price', 'VanEck Vectors Gold Miners ETF',
'SPDR Gold Trust']
In [10]: for pari in zip(data.columns, instruments):
print('{:8s} | {}'.format(pari[0], pari[1]))
AAPL.O | Apple Stock
MSFT.O | Microsoft Stock
INTC.O | Intel Stock
AMZN.O | Amazon Stock
GS.N | Goldman Sachs Stock
SPY | SPDR S&P 500 ETF Trust
.SPX | S&P 500 Index
.VIX | VIX Volatility Index
EUR= | EUR/USD Exchange Rate
XAU= | Gold Price
GDX | VanEck Vectors Gold Miners ETF
GLD | SPDR Gold Trust
摘要统计
下一步,金融分析师可能会采取的步骤是查看数据集的不同摘要统计信息,以了解它的“感觉
”。
In [11]: data.info() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 1972 entries, 2010-01-04 to 2017-10-31
Data columns (total 12 columns):
AAPL.O 1972 non-null float64
MSFT.O 1972 non-null float64
INTC.O 1972 non-null float64
AMZN.O 1972 non-null float64
GS.N 1972 non-null float64
SPY 1972 non-null float64
.SPX 1972 non-null float64
.VIX 1972 non-null float64
EUR= 1972 non-null float64
XAU= 1972 non-null float64
GDX 1972 non-null float64
GLD 1972 non-null float64
dtypes: float64(12)
memory usage: 200.3 KB
In [12]: data.describe().round(2) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[12]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX \
count 1972.00 1972.00 1972.00 1972.00 1972.00 1972.00 1972.00 1972.00
mean 86.53 40.59 27.70 401.15 163.61 172.84 1727.54 17.21
std 34.04 14.39 5.95 257.12 37.17 42.33 424.35 5.92
min 27.44 23.01 17.66 108.61 87.70 102.20 1022.58 9.19
25% 57.57 28.12 22.23 202.66 144.23 132.64 1325.53 13.25
50% 84.63 36.54 26.41 306.42 162.09 178.80 1783.81 15.65
75% 111.87 50.08 33.74 559.45 184.11 208.01 2080.15 19.20
max 169.04 83.89 45.49 1110.85 252.89 257.71 2581.07 48.00
EUR= XAU= GDX GLD
count 1972.00 1972.00 1972.00 1972.00
mean 1.25 1352.47 34.50 130.60
std 0.12 195.38 15.44 19.46
min 1.04 1051.36 12.47 100.50
25% 1.13 1214.56 22.22 116.77
50% 1.29 1288.82 26.59 123.90
75% 1.35 1491.98 49.77 145.43
max 1.48 1897.10 66.63 184.59
.info()
提供有关DataFrame
对象的一些元信息。
.describe()
提供每列有用的标准统计数据。
提示
pandas
提供了许多方法来快速查看新导入的金融时间序列数据集的概述,例如.info()
和.describe()
。它们还允许快速检查导入过程是否按预期工作(例如,DataFrame
对象是否确实具有DatetimeIndex
作为索引)。
当然,也有选项来自定义要推导和显示的统计信息类型。
In [13]: data.mean() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[13]: AAPL.O 86.530152
MSFT.O 40.586752
INTC.O 27.701411
AMZN.O 401.154006
GS.N 163.614625
SPY 172.835399
.SPX 1727.538342
.VIX 17.209498
EUR= 1.252613
XAU= 1352.471593
GDX 34.499391
GLD 130.601856
dtype: float64
In [14]: data.aggregate(min, ![2 np.mean, ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
np.std, ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
np.median, ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
max] ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
).round(2)
Out[14]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX EUR= \
min 27.44 23.01 17.66 108.61 87.70 102.20 1022.58 9.19 1.04
mean 86.53 40.59 27.70 401.15 163.61 172.84 1727.54 17.21 1.25
std 34.04 14.39 5.95 257.12 37.17 42.33 424.35 5.92 0.12
median 84.63 36.54 26.41 306.42 162.09 178.80 1783.81 15.65 1.29
max 169.04 83.89 45.49 1110.85 252.89 257.71 2581.07 48.00 1.48
XAU= GDX GLD
min 1051.36 12.47 100.50
mean 1352.47 34.50 130.60
std 195.38 15.44 19.46
median 1288.82 26.59 123.90
max 1897.10 66.63 184.59
每列的均值。
每列的最小值。
每列的均值。
每列的标准偏差。
每列的最大值。
使用.aggregate()
方法还允许传递自定义函数。
随时间变化
大多数统计分析方法,例如,通常基于时间序列随时间的变化,而不是绝对值本身。有多种选项可以计算时间序列随时间的变化,包括:绝对差异、百分比变化和对数(对数)收益。
首先是绝对差异,对于这个pandas
提供了一个特殊的方法。
In [15]: data.diff().head() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[15]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX EUR= \
Date
2010-01-04 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2010-01-05 0.052857 0.010 -0.01 0.79 3.06 0.30 3.53 -0.69 -0.0043
2010-01-06 -0.487142 -0.190 -0.07 -2.44 -1.88 0.08 0.62 -0.19 0.0044
2010-01-07 -0.055714 -0.318 -0.20 -2.25 3.41 0.48 4.55 -0.10 -0.0094
2010-01-08 0.200000 0.208 0.23 3.52 -3.36 0.38 3.29 -0.93 0.0094
XAU= GDX GLD
Date
2010-01-04 NaN NaN NaN
2010-01-05 -1.35 0.46 -0.10
2010-01-06 19.85 1.17 1.81
2010-01-07 -6.60 -0.24 -0.69
2010-01-08 4.20 0.74 0.55
In [16]: data.diff().mean() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[16]: AAPL.O 0.070252
MSFT.O 0.026499
INTC.O 0.012486
AMZN.O 0.492836
GS.N 0.035211
SPY 0.072968
.SPX 0.731745
.VIX -0.005003
EUR= -0.000140
XAU= 0.076712
GDX -0.012801
GLD 0.005515
dtype: float64
.diff()
提供了两个索引值之间的绝对变化。
当然,还可以应用聚合操作。
从统计学的角度来看,绝对变化不是最佳选择,因为它们依赖于时间序列数据本身的比例。因此,通常更喜欢百分比变化。以下代码推导了金融背景下的百分比变化或百分比收益(也称为:简单收益),并可视化其每列的均值(参见 图 8-2)。
In [17]: data.pct_change().round(3).head() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[17]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX EUR= \
Date
2010-01-04 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2010-01-05 0.002 0.000 -0.000 0.006 0.018 0.003 0.003 -0.034 -0.003
2010-01-06 -0.016 -0.006 -0.003 -0.018 -0.011 0.001 0.001 -0.010 0.003
2010-01-07 -0.002 -0.010 -0.010 -0.017 0.020 0.004 0.004 -0.005 -0.007
2010-01-08 0.007 0.007 0.011 0.027 -0.019 0.003 0.003 -0.049 0.007
XAU= GDX GLD
Date
2010-01-04 NaN NaN NaN
2010-01-05 -0.001 0.010 -0.001
2010-01-06 0.018 0.024 0.016
2010-01-07 -0.006 -0.005 -0.006
2010-01-08 0.004 0.015 0.005
In [18]: data.pct_change().mean().plot(kind='bar', figsize=(10, 6)); ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
# plt.savefig('../../images/ch08/fts_02.png');
.pct_change()
计算两个索引值之间的百分比变化。
结果的均值作为条形图可视化。
图 8-2. 百分比变化的均值作为条形图
作为百分比收益的替代,可以使用对数收益。在某些情况下,它们更容易处理,因此在金融背景下通常更受欢迎。² 图 8-3 展示了单个金融时间序列的累积对数收益。这种类型的绘图导致某种形式的归一化。
In [19]: rets = np.log(data / data.shift(1)) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [20]: rets.head().round(3) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[20]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX EUR= \
Date
2010-01-04 NaN NaN NaN NaN NaN NaN NaN NaN NaN
2010-01-05 0.002 0.000 -0.000 0.006 0.018 0.003 0.003 -0.035 -0.003
2010-01-06 -0.016 -0.006 -0.003 -0.018 -0.011 0.001 0.001 -0.010 0.003
2010-01-07 -0.002 -0.010 -0.010 -0.017 0.019 0.004 0.004 -0.005 -0.007
2010-01-08 0.007 0.007 0.011 0.027 -0.019 0.003 0.003 -0.050 0.007
XAU= GDX GLD
Date
2010-01-04 NaN NaN NaN
2010-01-05 -0.001 0.010 -0.001
2010-01-06 0.018 0.024 0.016
2010-01-07 -0.006 -0.005 -0.006
2010-01-08 0.004 0.015 0.005
In [21]: rets.cumsum().apply(np.exp).plot(figsize=(10, 6)); ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
# plt.savefig('../../images/ch08/fts_03.png');
这以向量化方式计算对数收益。
结果的子集。
这绘制了随时间累积的对数收益;首先调用.cumsum()
方法,然后将np.exp()
应用于结果。
图 8-3. 随时间累积的对数收益
重采样
对于金融时间序列数据,重采样是一项重要的操作。通常,这采取上采样的形式,意味着例如,具有每日观测的时间序列被重采样为具有每周或每月观测的时间序列。这也可能意味着将金融 Tick 数据系列重采样为一分钟间隔(也称为:柱)。
In [22]: data.resample('1w', label='right').last().head() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[22]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX .VIX \
Date
2010-01-10 30.282827 30.66 20.83 133.52 174.31 114.57 1144.98 18.13
2010-01-17 29.418542 30.86 20.80 127.14 165.21 113.64 1136.03 17.91
2010-01-24 28.249972 28.96 19.91 121.43 154.12 109.21 1091.76 27.31
2010-01-31 27.437544 28.18 19.40 125.41 148.72 107.39 1073.87 24.62
2010-02-07 27.922829 28.02 19.47 117.39 154.16 106.66 1066.19 26.11
EUR= XAU= GDX GLD
Date
2010-01-10 1.4412 1136.10 49.84 111.37
2010-01-17 1.4382 1129.90 47.42 110.86
2010-01-24 1.4137 1092.60 43.79 107.17
2010-01-31 1.3862 1081.05 40.72 105.96
2010-02-07 1.3662 1064.95 42.41 104.68
In [23]: data.resample('1m', label='right').last().head() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[23]: AAPL.O MSFT.O INTC.O AMZN.O GS.N SPY .SPX \
Date
2010-01-31 27.437544 28.1800 19.40 125.41 148.72 107.3900 1073.87
2010-02-28 29.231399 28.6700 20.53 118.40 156.35 110.7400 1104.49
2010-03-31 33.571395 29.2875 22.29 135.77 170.63 117.0000 1169.43
2010-04-30 37.298534 30.5350 22.84 137.10 145.20 118.8125 1186.69
2010-05-31 36.697106 25.8000 21.42 125.46 144.26 109.3690 1089.41
.VIX EUR= XAU= GDX GLD
Date
2010-01-31 24.62 1.3862 1081.05 40.72 105.960
2010-02-28 19.50 1.3625 1116.10 43.89 109.430
2010-03-31 17.59 1.3510 1112.80 44.41 108.950
2010-04-30 22.05 1.3295 1178.25 50.51 115.360
2010-05-31 32.07 1.2267 1213.81 49.86 118.881
In [24]: rets.cumsum().resample('1m', label='right').last(
).plot(figsize=(10, 6)); ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
# plt.savefig('../../images/ch08/fts_04.png');
EOD 数据被重采样为
结果的子集。
这绘制了随时间累积的对数收益;首先调用.cumsum()
方法,然后将np.exp()
应用于结果。
图 8-4. 随时间重采样的累积对数收益(每月)
注意
在重新取样时,默认情况下,pandas
采用区间的左标签(或索引值)。为了保持金融一致性,请确保使用右标签(索引值)和通常是区间中最后一个可用数据点。否则,可能会在金融分析中引入预见偏差。^(3)
滚动统计
在金融传统中,通常称为滚动统计,也称为金融指标或金融研究。这样的滚动统计是金融图表分析师和技术交易员的基本工具,例如。本节仅处理单个金融时间序列。
In [25]: sym = 'AAPL.O'
In [26]: data = pd.DataFrame(data[sym])
In [27]: data.tail()
Out[27]: AAPL.O
Date
2017-10-25 156.41
2017-10-26 157.41
2017-10-27 163.05
2017-10-30 166.72
2017-10-31 169.04
概览
使用pandas
很容易得出标准滚动统计。
In [28]: window = 20 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [29]: data['min'] = data[sym].rolling(window=window).min() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [30]: data['mean'] = data[sym].rolling(window=window).mean() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [31]: data['std'] = data[sym].rolling(window=window).std() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [32]: data['median'] = data[sym].rolling(window=window).median() ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
In [33]: data['max'] = data[sym].rolling(window=window).max() ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
In [34]: data['ewma'] = data[sym].ewm(halflife=0.5, min_periods=window).mean() ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
定义窗口,即要包含的索引值的数量。
计算滚动最小值。
计算滚动均值。
计算滚动标准差。
计算滚动中位数。
计算滚动最大值。
这将计算指数加权移动平均值,衰减以半衰期0.5
来计算。
要推导出更专业的金融指标,通常需要使用其他软件包(例如,使用Cufflinks
进行金融图表,参见“交互式二维绘图”)。也可以通过.apply()
方法轻松地应用自定义指标。
下面的代码显示了部分结果,并可视化了计算的滚动统计的选择部分(参见图 8-5)。
In [35]: data.dropna().head()
Out[35]: AAPL.O min mean std median max \
Date
2010-02-01 27.818544 27.437544 29.580892 0.933650 29.821542 30.719969
2010-02-02 27.979972 27.437544 29.451249 0.968048 29.711113 30.719969
2010-02-03 28.461400 27.437544 29.343035 0.950665 29.685970 30.719969
2010-02-04 27.435687 27.435687 29.207892 1.021129 29.547113 30.719969
2010-02-05 27.922829 27.435687 29.099892 1.037811 29.419256 30.719969
ewma
Date
2010-02-01 27.805432
2010-02-02 27.936337
2010-02-03 28.330134
2010-02-04 27.659299
2010-02-05 27.856947
In [36]: ax = data[['min', 'mean', 'max']].iloc[-200:].plot(
figsize=(10, 6), style=['g--', 'r--', 'g--'], lw=0.8) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
data[sym].iloc[-200:].plot(ax=ax, lw=2.0); ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
# plt.savefig('../../images/ch08/fts_05.png');
绘制最后 200 个数据行的三个滚动统计。
将原始时间序列数据添加到图表中。
图 8-5。最小、平均、最大值的滚动统计
技术分析示例
与基本分析相比,滚动统计是所谓的技术分析中的主要工具,基本分析侧重于财务报告和被分析股票公司的战略位置等方面。
基于技术分析的几十年历史的交易策略基于两个简单移动平均线(SMAs)。这个想法是,当短期 SMA 高于长期 SMA 时,交易员应该持有一支股票(或者一般的金融工具),当情况相反时,应该空仓。这些概念可以通过pandas
和DataFrame
对象的功能来精确描述。
当给定了window
参数规范并且有足够的数据时,通常才会计算滚动统计。如图 8-6 所示,SMAs 时间序列仅从有足够数据的那天开始。
In [37]: data['SMA1'] = data[sym].rolling(window=42).mean() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [38]: data['SMA2'] = data[sym].rolling(window=252).mean() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [39]: data[[sym, 'SMA1', 'SMA2']].tail()
Out[39]: AAPL.O SMA1 SMA2
Date
2017-10-25 156.41 157.610952 139.862520
2017-10-26 157.41 157.514286 140.028472
2017-10-27 163.05 157.517619 140.221210
2017-10-30 166.72 157.597857 140.431528
2017-10-31 169.04 157.717857 140.651766
In [40]: data[[sym, 'SMA1', 'SMA2']].plot(figsize=(10, 6)); ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
# plt.savefig('../../images/ch08/fts_06.png');
计算短期 SMA 的值。
计算长期 SMA 的值。
可视化股价数据和两条 SMAs 时间序列。
图 8-6. 苹果股价和两条简单移动平均线
在这个背景下,简单移动平均线(SMAs)仅仅是实现目标的手段。它们被用来推导出实施交易策略的定位。图 8-7 通过数值1
来可视化多头头寸,数值-1
来可视化空头头寸。头寸的变化(在视觉上)由表示 SMAs 时间序列的两条线的交叉触发。
In [41]: data.dropna(inplace=True) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [42]: data['positions'] = np.where(data['SMA1'] > data['SMA2'], ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
1, ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
-1) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [43]: ax = data[[sym, 'SMA1', 'SMA2', 'positions']].plot(figsize=(10, 6),
secondary_y='positions')
ax.get_legend().set_bbox_to_anchor((0.25, 0.85));
# plt.savefig('../../images/ch08/fts_07.png');
仅保留完整的数据行。
如果短期 SMA 值大于长期 SMA 值…
…买入股票(放置1
)…
…否则卖空股票(放置-1
)。
图 8-7. 苹果股价,两条简单移动平均线和头寸
在这里隐含地推导出的交易策略本质上只导致了很少的交易:只有当头寸价值变化(即发生交叉)时,才会进行交易。包括开仓和平仓交易,在总计中这将只增加到六次交易。
相关性分析
作为如何使用pandas
和金融时间序列数据的进一步说明,考虑标准普尔 500 股指数和 VIX 波动率指数的情况。一个事实是,一般情况下,当标准普尔 500 上涨时,VIX 下降,反之亦然。这涉及相关性而不是因果关系。本节展示了如何为标准普尔 500 和 VIX 之间(高度)负相关的事实提供支持性统计证据。⁴
数据
数据集现在包含两个金融时间序列,都在图 8-8 中可视化。
In [44]: # EOD data from Thomson Reuters Eikon Data API
raw = pd.read_csv('../../source/tr_eikon_eod_data.csv',
index_col=0, parse_dates=True)
In [45]: data = raw[['.SPX', '.VIX']]
In [46]: data.tail()
Out[46]: .SPX .VIX
Date
2017-10-25 2557.15 11.23
2017-10-26 2560.40 11.30
2017-10-27 2581.07 9.80
2017-10-30 2572.83 10.50
2017-10-31 2575.26 10.18
In [47]: data.plot(subplots=True, figsize=(10, 6));
# plt.savefig('../../images/ch08/fts_08.png');
图 8-8. S&P 500 和 VIX 时间序列数据(不同的缩放)
当在单个图中绘制(部分)两个时间序列并进行调整缩放时,两个指数之间的负相关的事实通过简单的视觉检查就已经变得明显。
In [48]: data.loc[:'2012-12-31'].plot(secondary_y='.VIX', figsize=(10, 6));
# plt.savefig('../../images/ch08/fts_09.png');
图 8-9. S&P 500 和 VIX 时间序列数据(相同的缩放)
对数收益率
如上所述,一般的统计分析依赖于收益而不是绝对变化或甚至绝对值。因此,在进行任何进一步分析之前,首先计算对数收益。图 8-10 显示了随时间变化的对数收益的高变异性。对于两个指数都可以发现所谓的波动率集群。总的来说,股票指数波动率高的时期伴随着波动率指数的同样现象。
In [49]: rets = np.log(data / data.shift(1))
In [50]: rets.head()
Out[50]: .SPX .VIX
Date
2010-01-04 NaN NaN
2010-01-05 0.003111 -0.035038
2010-01-06 0.000545 -0.009868
2010-01-07 0.003993 -0.005233
2010-01-08 0.002878 -0.050024
In [51]: rets.dropna(inplace=True)
In [52]: rets.plot(subplots=True, figsize=(10, 6));
# plt.savefig('../../images/ch08/fts_10.png');
图 8-10。S&P 500 和 VIX 的对数收益随时间变化
在这种情况下,pandas
的scatter_matrix()
绘图函数非常方便用于可视化。它将两个系列的对数收益相互绘制,可以在对角线上添加直方图或核密度估计器(KDE)(请参见图 8-11)。
In [53]: pd.plotting.scatter_matrix(rets, ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
alpha=0.2, ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
diagonal='hist', ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
hist_kwds={'bins': 35}, ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
figsize=(10, 6));
# plt.savefig('../../images/ch08/fts_11.png');
要绘制的数据集。
点的不透明度参数为alpha
。
放置在对角线上的内容;这里是列数据的直方图。
这些是要传递给直方图绘图函数的关键字。
图 8-11。S&P 500 和 VIX 的对数收益作为散点矩阵
OLS 回归
通过所有这些准备,普通最小二乘(OLS)回归分析很方便实现。图 8-12 显示了对数收益的散点图和通过点云的线性回归线。斜率明显为负,支持了关于两个指数之间负相关的事实。
In [54]: reg = np.polyfit(rets['.SPX'], rets['.VIX'], deg=1) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [55]: ax = rets.plot(kind='scatter', x='.SPX', y='.VIX', figsize=(10, 6)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
ax.plot(rets['.SPX'], np.polyval(reg, rets['.SPX']), 'r', lw=2); ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
# plt.savefig('../../images/ch08/fts_12.png');
这实现了线性 OLS 回归。
这将对数收益绘制为散点图…
… 添加了线性回归线。
图 8-12。S&P 500 和 VIX 的对数收益作为散点矩阵
相关性
最后,直接考虑相关性度量。考虑到两种度量,一种是考虑完整数据集的静态度量,另一种是显示一定时间内相关性的滚动度量。图 8-13 说明了相关性确实随时间变化,但鉴于参数设置,始终为负。这确实强有力地支持了 S&P 500 和 VIX 指数之间甚至强烈的负相关的事实。
In [56]: rets.corr() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[56]: .SPX .VIX
.SPX 1.000000 -0.808372
.VIX -0.808372 1.000000
In [57]: ax = rets['.SPX'].rolling(window=252).corr(
rets['.VIX']).plot(figsize=(10, 6)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
ax.axhline(rets.corr().iloc[0, 1], c='r'); ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
# plt.savefig('../../images/ch08/fts_13.png');
整个DataFrame
的相关矩阵。
这会绘制随时间变化的滚动相关性……
… 并将静态值添加到绘图中作为水平线。
图 8-13. 标普 500 和 VIX 之间的相关性(静态和滚动)
高频数据
本章介绍了使用pandas
进行金融时间序列分析。金融时间序列的一个特殊情况是 tick 数据集。坦率地说,它们可以更多或更少地以与本章迄今为止使用的 EOD 数据集相同的方式处理。一般来说,使用pandas
导入这些数据集也是相当快的。所使用的数据集包含 17,352 行数据(另见图 8-14)。
In [58]: %%time
# data from FXCM Forex Capital Markets Ltd.
tick = pd.read_csv('../../source/fxcm_eur_usd_tick_data.csv',
index_col=0, parse_dates=True)
CPU times: user 23 ms, sys: 3.35 ms, total: 26.4 ms
Wall time: 25.1 ms
In [59]: tick.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 17352 entries, 2017-11-10 12:00:00.007000 to 2017-11-10 14:00:00.131000
Data columns (total 2 columns):
Bid 17352 non-null float64
Ask 17352 non-null float64
dtypes: float64(2)
memory usage: 406.7 KB
In [60]: tick['Mid'] = tick.mean(axis=1) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [61]: tick['Mid'].plot(figsize=(10, 6));
# plt.savefig('../../images/ch08/fts_14.png');
计算每一行数据的Mid
价格。
图 8-14. 欧元/美元汇率的 tick 数据
处理 tick 数据通常需要对金融时间序列数据进行重新采样。以下代码将 tick 数据重新采样为一分钟的条形数据。例如,这样的数据集(另见图 8-15)用于回测算法交易策略或实施技术分析。
In [62]: tick_resam = tick.resample(rule='1min', label='right').last()
In [63]: tick_resam.head()
Out[63]: Bid Ask Mid
2017-11-10 12:01:00 1.16406 1.16407 1.164065
2017-11-10 12:02:00 1.16396 1.16397 1.163965
2017-11-10 12:03:00 1.16416 1.16418 1.164170
2017-11-10 12:04:00 1.16417 1.16417 1.164170
2017-11-10 12:05:00 1.16425 1.16427 1.164260
In [64]: tick_resam['Mid'].plot(figsize=(10, 6));
# plt.savefig('../../images/ch08/fts_15.png');
图 8-15. 欧元/美元汇率的一分钟条形数据
结论
本章涉及金融时间序列,可能是金融领域最重要的数据类型。pandas
是处理这种数据集的强大工具包,不仅可以进行高效的数据分析,而且还可以轻松进行可视化。pandas
还有助于从不同来源读取此类数据集,并将此类数据集导出为不同的技术文件格式。这在随后的第九章第九章中进行了说明。
进一步阅读
本章涵盖的主题在书籍形式上有很好的参考资料:
-
McKinney, Wes (2017): Python for Data Analysis. 2nd ed., O’Reilly, 北京等地.
-
VanderPlas, Jake (2016): Python Data Science Handbook. O’Reilly, 北京等地。
¹ 该文件包含了从汤姆森路透 Eikon 数据 API 检索的不同金融工具的每日结束数据(EOD 数据)。
² 其中一个优点是随时间的可加性,这对于简单的百分比变化/回报并不成立。
³ 预见偏见 ——或者,在其最强形式下,完美预见 ——意味着在金融分析的某个点上使用的数据仅在以后才可用。结果可能是“太好了
”的结果,例如,在回测交易策略时。
⁴ 其背后的一个推理是,当股指下跌时——例如在危机期间——交易量会上升,因此波动性也会上升。当股指上涨时,投资者通常会保持冷静,并且不怎么有动力进行大量交易。特别是,仅持有多头头寸的投资者会试图进一步跟进趋势。
第九章:输入/输出操作
在有数据之前进行理论推断是一个大错误。
福尔摩斯
作为一般规则,无论是在金融环境中还是在其他任何应用程序领域,大多数数据都存储在硬盘驱动器(HDDs)或其他形式的永久存储设备上,如固态硬盘(SSDs)或混合硬盘驱动器。多年来,存储容量一直在稳步增长,而存储单元的成本(例如,每兆字节)一直在稳步下降。
与此同时,存储数据的容量增长速度远远快于即使是最大型机器中可用的典型随机访问内存(RAM)。这使得不仅需要将数据存储到磁盘上以进行永久存储,而且需要通过将数据从 RAM 交换到磁盘,然后再交换回来来弥补 RAM 不足的情况。
因此,在金融应用程序和数据密集型应用程序中,输入/输出(I/O)操作通常是重要的任务。通常,它们代表了性能关键计算的瓶颈,因为 I/O 操作通常无法将数据快速地从 RAM 移动到 RAM¹,然后再从 RAM 移动到磁盘。从某种意义上说,CPU 经常由于 I/O 操作慢而“饥饿
”。
尽管如今的大部分金融和企业分析工作都面临着大数据(例如,PB 级别),但单个分析任务通常使用的数据子集属于中等数据类别。微软研究的一项研究得出了结论:
我们的测量以及其他最近的工作显示,大多数现实世界的分析任务处理的输入量不超过 100GB,但流行的基础设施,如 Hadoop/MapReduce 最初是为 PB 级处理而设计的。
Appuswamy 等人(2013 年)
就频率而言,单个金融分析任务通常处理不超过几 GB 大小的数据,并且这是 Python 及其科学堆栈库(如NumPy
,pandas
和PyTables
)的甜蜜点。这样大小的数据集也可以在内存中进行分析,利用今天的 CPU 和 GPU 通常会获得较高的速度。但是,必须将数据读入 RAM 并将结果写入磁盘,同时确保满足今天的性能要求。
本章涉及以下主题:
“使用 Python 进行基本 I/O”
Python 具有内置函数,可以对任何对象进行序列化并将其存储到磁盘上,然后从磁盘中读取到 RAM 中;除此之外,在处理文本文件和SQL
数据库时,Python 也很强大。NumPy
还提供了专用函数,用于快速二进制存储和检索ndarray
对象。
“使用 pandas 进行 I/O”
pandas
库提供了丰富的便利函数和方法,用于读取存储在不同格式中的数据(例如,CSV
,JSON
)并将数据写入不同格式的文件。
“使用 PyTables 进行快速 I/O”
PyTables
使用具有分层数据库结构和二进制存储的 HDF5
标准来实现对大数据集的快速 I/O 操作;速度通常仅受使用的硬件限制。
“TsTables 的 I/O”
TsTables
是一个构建在 PyTables
之上的包,允许快速存储和检索时间序列数据。
Python 的基本 I/O
Python 本身具有多种 I/O 功能,有些针对性能进行了优化,而其他一些则更注重灵活性。然而,总的来说,它们既可以在交互式环境下使用,也可以在生产环境中轻松应用。
将对象写入磁盘
以供以后使用、文档化或与他人共享,有人可能想要将 Python 对象存储在磁盘上。一个选项是使用 pickle
模块。该模块可以序列化大多数 Python 对象。序列化 指的是将对象(层次结构)转换为字节流;反序列化 是相反的操作。
通常情况下,首先进行一些与绘图相关的导入和自定义:
In [1]: from pylab import plt, mpl
plt.style.use('seaborn')
mpl.rcParams['font.family'] = 'serif'
%matplotlib inline
接下来的示例使用(伪)随机数据,这次存储在 list
对象中:
In [2]: import pickle ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
import numpy as np
from random import gauss ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [3]: a = [gauss(1.5, 2) for i in range(1000000)] ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [4]: path = '/Users/yves/Documents/Temp/data/' ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [5]: pkl_file = open(path + 'data.pkl', 'wb') ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
从标准库导入 pickle
模块。
导入 gauss
以生成正态分布的随机数。
创建一个更大的 list
对象,并填充随机数。
指定存储数据文件的路径。
以二进制模式打开文件进行写入(wb
)。
用于序列化和反序列化 Python 对象的两个主要函数是 pickle.dump()
(用于写入对象)和 pickle.load()
(用于将对象加载到内存中):
In [6]: %time pickle.dump(a, pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 23.4 ms, sys: 10.1 ms, total: 33.5 ms
Wall time: 31.9 ms
In [7]: pkl_file.close() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [8]: ll $path* ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
-rw-r--r-- 1 yves staff 9002006 Jan 18 10:05 /Users/yves/Documents/Temp/data/data.pkl
-rw-r--r-- 1 yves staff 163328824 Jan 18 10:05 /Users/yves/Documents/Temp/data/tstb.h5
In [9]: pkl_file = open(path + 'data.pkl', 'rb') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [10]: %time b = pickle.load(pkl_file) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
CPU times: user 28.7 ms, sys: 15.2 ms, total: 43.9 ms
Wall time: 41.9 ms
In [11]: a[:3]
Out[11]: [3.0804166128701134, -0.6586387748854099, 3.3266248354210206]
In [12]: b[:3]
Out[12]: [3.0804166128701134, -0.6586387748854099, 3.3266248354210206]
In [13]: np.allclose(np.array(a), np.array(b)) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
Out[13]: True
序列化对象 a
并将其保存到文件中。
关闭文件。
显示磁盘上的文件及其大小(Mac/Linux)。
以二进制模式打开文件进行读取(rb
)。
从磁盘读取对象并进行反序列化。
将 a
和 b
转换为 ndarrary
对象,np.allclose()
验证两者包含相同的数据(数字)。
使用 pickle
存储和检索单个对象显然非常简单。那么两个对象呢?
In [14]: pkl_file = open(path + 'data.pkl', 'wb')
In [15]: %time pickle.dump(np.array(a), pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 26.6 ms, sys: 11.5 ms, total: 38.1 ms
Wall time: 36.3 ms
In [16]: %time pickle.dump(np.array(a) ** 2, pkl_file) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
CPU times: user 35.3 ms, sys: 12.7 ms, total: 48 ms
Wall time: 46.8 ms
In [17]: pkl_file.close()
In [18]: ll $path* ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
-rw-r--r-- 1 yves staff 16000322 Jan 18 10:05 /Users/yves/Documents/Temp/data/data.pkl
-rw-r--r-- 1 yves staff 163328824 Jan 18 10:05 /Users/yves/Documents/Temp/data/tstb.h5
序列化 a
的 ndarray
版本并保存。
序列化 a
的平方 ndarray
版本并保存。
文件现在的大小大约是之前的两倍。
那么如何将两个ndarray
对象读回内存?
In [19]: pkl_file = open(path + 'data.pkl', 'rb')
In [20]: x = pickle.load(pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
x[:4]
Out[20]: array([ 3.08041661, -0.65863877, 3.32662484, 0.77225328])
In [21]: y = pickle.load(pkl_file) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
y[:4]
Out[21]: array([ 9.48896651, 0.43380504, 11.0664328 , 0.59637513])
In [22]: pkl_file.close()
这检索到了存储的对象第一个。
这检索到了存储的对象第二个。
很明显,pickle
根据先进先出(FIFO)原则存储对象。但这存在一个主要问题:用户事先无法获得关于存储在pickle
文件中的内容的元信息。
有时一个有用的解决方法是不存储单个对象,而是存储包含所有其他对象的dict
对象:
In [23]: pkl_file = open(path + 'data.pkl', 'wb')
pickle.dump({'x': x, 'y': y}, pkl_file) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
pkl_file.close()
In [24]: pkl_file = open(path + 'data.pkl', 'rb')
data = pickle.load(pkl_file) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
pkl_file.close()
for key in data.keys():
print(key, data[key][:4])
x [ 3.08041661 -0.65863877 3.32662484 0.77225328]
y [ 9.48896651 0.43380504 11.0664328 0.59637513]
In [25]: !rm -f $path*
存储包含两个ndarray
对象的dict
对象。
检索dict
对象。
然而,这种方法要求一次性写入和读取所有对象。考虑到它带来的更高便利性,这可能是一个可以接受的折中方案。
读写文本文件
文本处理可以被视为 Python 的一个优势。事实上,许多公司和科学用户正是用 Python 来完成这项任务的。使用 Python,你有多种选择来处理str
对象,以及一般的文本文件。
假设有一个相当大的数据集要共享为逗号分隔值(CSV
)文件。尽管这些文件具有特殊的内部结构,但它们基本上是纯文本文件。以下代码创建一个虚拟数据集作为ndarray
对象,一个DatetimeIndex
对象,将两者组合并将数据存储为 CSV 文本文件。
In [26]: import pandas as pd
In [27]: rows = 5000 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
a = np.random.standard_normal((rows, 5)).round(4) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [28]: a ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[28]: array([[-0.9627, 0.1326, -2.012 , -0.299 , -1.4554],
[ 0.8918, 0.8904, -0.3396, -2.3485, 2.0913],
[-0.1899, -0.9574, 1.0258, 0.6206, -2.4693],
...,
[ 1.4688, -1.268 , -0.4778, 1.4315, -1.4689],
[ 1.1162, 0.152 , -0.9363, -0.7869, -0.1147],
[-0.699 , 0.3206, 0.3659, -1.0282, -0.4151]])
In [29]: t = pd.date_range(start='2019/1/1', periods=rows, freq='H') ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [30]: t ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[30]: DatetimeIndex(['2019-01-01 00:00:00', '2019-01-01 01:00:00',
'2019-01-01 02:00:00', '2019-01-01 03:00:00',
'2019-01-01 04:00:00', '2019-01-01 05:00:00',
'2019-01-01 06:00:00', '2019-01-01 07:00:00',
'2019-01-01 08:00:00', '2019-01-01 09:00:00',
...
'2019-07-27 22:00:00', '2019-07-27 23:00:00',
'2019-07-28 00:00:00', '2019-07-28 01:00:00',
'2019-07-28 02:00:00', '2019-07-28 03:00:00',
'2019-07-28 04:00:00', '2019-07-28 05:00:00',
'2019-07-28 06:00:00', '2019-07-28 07:00:00'],
dtype='datetime64[ns]', length=5000, freq='H')
In [31]: csv_file = open(path + 'data.csv', 'w') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [32]: header = 'date,no1,no2,no3,no4,no5\n' ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
In [33]: csv_file.write(header) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[33]: 25
In [34]: for t_, (no1, no2, no3, no4, no5) in zip(t, a): ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
s = '{},{},{},{},{},{}\n'.format(t_, no1, no2, no3, no4, no5) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
csv_file.write(s) ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)
In [35]: csv_file.close()
In [36]: ll $path*
-rw-r--r-- 1 yves staff 284621 Jan 18 10:05 /Users/yves/Documents/Temp/data/data.csv
定义数据集的行数。
创建具有随机数的ndarray
对象。
创建一个适当长度的DatetimeIndex
对象(每小时间隔)。
打开一个供写入的文件(w
)。
定义标题行(列标签)并将其写为第一行。
数据以行为单位组合…
…转换为str
对象…
…并逐行写入(追加到 CSV 文本文件中)。
另一种方法也类似。首先,打开现有的CSV
文件。其次,使用file
对象的.readline()
或.readlines()
方法逐行读取其内容:
In [37]: csv_file = open(path + 'data.csv', 'r') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [38]: for i in range(5):
print(csv_file.readline(), end='') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
date,no1,no2,no3,no4,no5
2019-01-01 00:00:00,-0.9627,0.1326,-2.012,-0.299,-1.4554
2019-01-01 01:00:00,0.8918,0.8904,-0.3396,-2.3485,2.0913
2019-01-01 02:00:00,-0.1899,-0.9574,1.0258,0.6206,-2.4693
2019-01-01 03:00:00,-0.0217,-0.7168,1.7875,1.6226,-0.4857
In [39]: csv_file.close()
In [40]: csv_file = open(path + 'data.csv', 'r') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [41]: content = csv_file.readlines() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [42]: content[:5] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[42]: ['date,no1,no2,no3,no4,no5\n',
'2019-01-01 00:00:00,-0.9627,0.1326,-2.012,-0.299,-1.4554\n',
'2019-01-01 01:00:00,0.8918,0.8904,-0.3396,-2.3485,2.0913\n',
'2019-01-01 02:00:00,-0.1899,-0.9574,1.0258,0.6206,-2.4693\n',
'2019-01-01 03:00:00,-0.0217,-0.7168,1.7875,1.6226,-0.4857\n']
In [43]: csv_file.close()
打开文件以供读取(r
)。
逐行读取文件内容并打印。
一次性读取文件内容…
… 其结果是一个包含所有行的list
对象,每行作为单独的str
对象。
CSV
文件如此重要且常见,以至于 Python 标准库中有一个csv
模块,简化了 CSV 文件的处理。csv
模块的两个有用的读取器(迭代器)对象都返回一个list
对象的list
对象,或者一个list
对象的dict
对象。
In [44]: import csv
In [45]: with open(path + 'data.csv', 'r') as f:
csv_reader = csv.reader(f) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
lines = [line for line in csv_reader]
In [46]: lines[:5] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[46]: [['date', 'no1', 'no2', 'no3', 'no4', 'no5'],
['2019-01-01 00:00:00', '-0.9627', '0.1326', '-2.012', '-0.299', '-1.4554'],
['2019-01-01 01:00:00', '0.8918', '0.8904', '-0.3396', '-2.3485', '2.0913'],
['2019-01-01 02:00:00', '-0.1899', '-0.9574', '1.0258', '0.6206', '-2.4693'],
['2019-01-01 03:00:00', '-0.0217', '-0.7168', '1.7875', '1.6226', '-0.4857']]
In [47]: with open(path + 'data.csv', 'r') as f:
csv_reader = csv.DictReader(f) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
lines = [line for line in csv_reader]
In [48]: lines[:3] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[48]: [OrderedDict([('date', '2019-01-01 00:00:00'),
('no1', '-0.9627'),
('no2', '0.1326'),
('no3', '-2.012'),
('no4', '-0.299'),
('no5', '-1.4554')]),
OrderedDict([('date', '2019-01-01 01:00:00'),
('no1', '0.8918'),
('no2', '0.8904'),
('no3', '-0.3396'),
('no4', '-2.3485'),
('no5', '2.0913')]),
OrderedDict([('date', '2019-01-01 02:00:00'),
('no1', '-0.1899'),
('no2', '-0.9574'),
('no3', '1.0258'),
('no4', '0.6206'),
('no5', '-2.4693')])]
In [49]: !rm -f $path*
csv.reader()
将每一行都返回为一个list
对象。
csv.DictReader()
将每一行都返回为OrderedDict
,它是dict
对象的一种特殊情况。
SQL 数据库
Python 可以与任何类型的SQL
数据库一起工作,并且通常也可以与任何类型的NoSQL
数据库一起工作。在这种情况下,SQL
代表结构化查询语言。Python 默认提供的一个SQL
或关系数据库是SQLite3
。借助它,可以轻松地说明 Python 对SQL
数据库的基本方法:²
In [50]: import sqlite3 as sq3
In [51]: con = sq3.connect(path + 'numbs.db') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [52]: query = 'CREATE TABLE numbs (Date date, No1 real, No2 real)' ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [53]: con.execute(query) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[53]: <sqlite3.Cursor at 0x1054efb20>
In [54]: con.commit() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [55]: q = con.execute ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
In [56]: q('SELECT * FROM sqlite_master').fetchall() ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
Out[56]: [('table',
'numbs',
'numbs',
2,
'CREATE TABLE numbs (Date date, No1 real, No2 real)')]
打开数据库连接;如果不存在,则创建一个文件。
这是一个创建包含三列的表的SQL
查询。³
执行查询…
… 并提交更改。
这为con.execute()
方法定义了一个简短的别名。
这获取关于数据库的元信息,将刚创建的表显示为单个对象。
现在有了一个带有表的数据库文件,可以使用数据填充该表。每行由一个datetime
对象和两个float
对象组成:
In [57]: import datetime
In [58]: now = datetime.datetime.now()
q('INSERT INTO numbs VALUES(?, ?, ?)', (now, 0.12, 7.3)) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[58]: <sqlite3.Cursor at 0x1054efc70>
In [59]: np.random.seed(100)
In [60]: data = np.random.standard_normal((10000, 2)).round(4) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [61]: %%time
for row in data: ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
now = datetime.datetime.now()
q('INSERT INTO numbs VALUES(?, ?, ?)', (now, row[0], row[1]))
con.commit()
CPU times: user 111 ms, sys: 3.22 ms, total: 115 ms
Wall time: 116 ms
In [62]: q('SELECT * FROM numbs').fetchmany(4) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[62]: [('2018-01-18 10:05:24.043286', 0.12, 7.3),
('2018-01-18 10:05:24.071921', -1.7498, 0.3427),
('2018-01-18 10:05:24.072110', 1.153, -0.2524),
('2018-01-18 10:05:24.072160', 0.9813, 0.5142)]
In [63]: q('SELECT * FROM numbs WHERE no1 > 0.5').fetchmany(4) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[63]: [('2018-01-18 10:05:24.072110', 1.153, -0.2524),
('2018-01-18 10:05:24.072160', 0.9813, 0.5142),
('2018-01-18 10:05:24.072257', 0.6727, -0.1044),
('2018-01-18 10:05:24.072319', 1.619, 1.5416)]
In [64]: pointer = q('SELECT * FROM numbs') ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
In [65]: for i in range(3):
print(pointer.fetchone()) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
('2018-01-18 10:05:24.043286', 0.12, 7.3)
('2018-01-18 10:05:24.071921', -1.7498, 0.3427)
('2018-01-18 10:05:24.072110', 1.153, -0.2524)
In [66]: rows = pointer.fetchall() ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)
rows[:3]
Out[66]: [('2018-01-18 10:05:24.072160', 0.9813, 0.5142),
('2018-01-18 10:05:24.072184', 0.2212, -1.07),
('2018-01-18 10:05:24.072202', -0.1895, 0.255)]
将单行(或记录)写入numbs
表。
创建一个较大的虚拟数据集作为ndarray
对象。
迭代ndarray
对象的行。
从表中检索多行。
相同但在no1
列的值上有条件。
定义一个指针对象…
…它的行为类似于生成器对象。
.fetchall()
检索所有剩余的行。
最后,如果不再需要,可能会想要删除数据库中的表对象。
In [67]: q('DROP TABLE IF EXISTS numbs') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[67]: <sqlite3.Cursor at 0x1054eff80>
In [68]: q('SELECT * FROM sqlite_master').fetchall() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[68]: []
In [69]: con.close() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [70]: !rm -f $path* ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
从数据库中删除表格。
此操作后不再存在表格对象。
关闭数据库连接。
从磁盘中删除数据库文件。
SQL
数据库是一个相当广泛的主题;事实上,在本章中无法对其进行任何重要的涵盖,因为它太广泛且复杂了。基本信息如下:
-
Python 与几乎所有的数据库技术都能很好地集成。
-
基本的
SQL
语法主要由所使用的数据库确定;其余部分如我们所说,都是Pythonic
的。
接下来会有几个基于SQLite3
的示例。
写入和读取 NumPy 数组
NumPy
本身有函数可以以方便和高效的方式写入和读取ndarray
对象。在某些情况下,这节省了很多工作,比如当你必须将NumPy
的dtype
对象转换为特定的数据库类型时(例如对于SQLite3
)。为了说明NumPy
有时可以有效替代基于SQL
的方法,以下代码复制了之前使用NumPy
的示例。
代码使用NumPy
的np.arange()
函数生成一个存储了datetime
对象的ndarray
对象,而不是使用pandas
:⁴
In [71]: dtimes = np.arange('2019-01-01 10:00:00', '2025-12-31 22:00:00',
dtype='datetime64[m]') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [72]: len(dtimes)
Out[72]: 3681360
In [73]: dty = np.dtype([('Date', 'datetime64[m]'),
('No1', 'f'), ('No2', 'f')]) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [74]: data = np.zeros(len(dtimes), dtype=dty) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [75]: data['Date'] = dtimes ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [76]: a = np.random.standard_normal((len(dtimes), 2)).round(4) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
In [77]: data['No1'] = a[:, 0] ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
data['No2'] = a[:, 1] ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
In [78]: data.nbytes ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
Out[78]: 58901760
创建一个带有datetime
作为dtype
的ndarray
对象。
用于记录数组的特殊dtype
对象。
用特殊dtype
实例化的ndarray
对象。
这将填充Date
列。
假数据集……
…这填充了No1
和No2
列。
记录数组的大小(以字节为单位)。
保存ndarray
对象是高度优化的,因此非常快速。大约 60 MB 的数据在磁盘上保存约 0.1 秒(这里使用 SSD)。大小为 480 MB 的较大ndarray
对象在磁盘上保存大约需要 1 秒钟。
In [79]: %time np.save(path + 'array', data) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 4.06 ms, sys: 99.3 ms, total: 103 ms
Wall time: 107 ms
In [80]: ll $path* ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
-rw-r--r-- 1 yves staff 58901888 Jan 18 10:05 /Users/yves/Documents/Temp/data/array.npy
In [81]: %time np.load(path + 'array.npy') ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 1.81 ms, sys: 47.4 ms, total: 49.2 ms
Wall time: 46.7 ms
Out[81]: array([('2019-01-01T10:00', 1.51310003, 0.69730002),
('2019-01-01T10:01', -1.722 , -0.4815 ),
('2019-01-01T10:02', 0.8251 , 0.3019 ), ...,
('2025-12-31T21:57', 1.37199998, 0.64459997),
('2025-12-31T21:58', -1.25419998, 0.1612 ),
('2025-12-31T21:59', -1.1997 , -1.097 )],
dtype=[('Date', '<M8[m]'), ('No1', '<f4'), ('No2', '<f4')])
In [82]: %time data = np.random.standard_normal((10000, 6000)).round(4) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
CPU times: user 2.81 s, sys: 354 ms, total: 3.17 s
Wall time: 3.23 s
In [83]: data.nbytes ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[83]: 480000000
In [84]: %time np.save(path + 'array', data) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
CPU times: user 23.9 ms, sys: 878 ms, total: 902 ms
Wall time: 964 ms
In [85]: ll $path* ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
-rw-r--r-- 1 yves staff 480000080 Jan 18 10:05 /Users/yves/Documents/Temp/data/array.npy
In [86]: %time np.load(path + 'array.npy') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
CPU times: user 1.95 ms, sys: 441 ms, total: 443 ms
Wall time: 441 ms
Out[86]: array([[ 0.3066, 0.5951, 0.5826, ..., 1.6773, 0.4294, -0.2216],
[ 0.8769, 0.7292, -0.9557, ..., 0.5084, 0.9635, -0.4443],
[-1.2202, -2.5509, -0.0575, ..., -1.6128, 0.4662, -1.3645],
...,
[-0.5598, 0.2393, -2.3716, ..., 1.7669, 0.2462, 1.035 ],
[ 0.273 , 0.8216, -0.0749, ..., -0.0552, -0.8396, 0.3077],
[-0.6305, 0.8331, 1.3702, ..., 0.3493, 0.1981, 0.2037]])
In [87]: !rm -f $path*
这将记录的ndarray
对象保存到磁盘上。
磁盘上的大小几乎与内存中的大小相同(由于二进制存储)。
这会从磁盘加载记录的ndarray
对象。
一个较大的普通ndarray
对象。
这些示例说明,在这种情况下,写入磁盘主要受硬件限制,因为 480 MB/s 大致代表了标准 SSD 在撰写本文时的宣传写入速度(512 MB/s)。
无论如何,可以预期,与使用标准 SQL
数据库或使用标准 pickle
库进行序列化相比,这种形式的数据存储和检索速度要快得多。有两个原因:首先,数据主要是数字;其次,NumPy
实现了二进制存储,几乎将开销降低到零。当然,使用这种方法不具备 SQL
数据库的功能,但是随后的部分将显示 PyTables
将在这方面提供帮助。
pandas
中的 I/O
pandas
的一个主要优势之一是它可以原生地读取和写入不同的数据格式,其中包括:
-
CSV
(逗号分隔值) -
SQL
(结构化查询语言) -
XLS/XSLX
(微软Excel
文件) -
JSON
(JavaScript
对象表示法
) -
HTML
(超文本标记语言)
表 9-1 列出了 pandas
和 DataFrame
类的支持格式以及相应的导入和导出函数/方法。导入函数所接受的参数在 [Link to Come] 中列出并描述(根据函数,可能适用其他约定)。
表 9-1. 导入导出函数和方法
格式 | 输入 | 输出 | 备注 |
---|---|---|---|
CSV |
pd.read_csv() |
.to_csv() |
文本文件 |
XLS/XLSX |
pd.read_excel() |
.to_excel() |
电子表格 |
HDF |
pd.read_hdf() |
.to_hdf() |
HDF5 数据库 |
SQL |
pd.read_sql() |
.to_sql() |
SQL 表 |
JSON |
pd.read_json() |
.to_json() |
JavaScript 对象表示法 |
MSGPACK |
pd.read_msgpack() |
.to_msgpack() |
可移植二进制格式 |
HTML |
pd.read_html() |
.to_html() |
HTML 代码 |
GBQ |
pd.read_gbq() |
.to_gbq() |
Google Big Query 格式 |
DTA |
pd.read_stata() |
.to_stata() |
格式 104, 105, 108, 113-115, 117 |
任何 | pd.read_clipboard() |
.to_clipboard() |
例如,从 HTML 页面 |
任何 | pd.read_pickle() |
.to_pickle() |
(结构化的)Python 对象 |
测试案例再次是一个较大的 float
对象集合:
In [88]: data = np.random.standard_normal((1000000, 5)).round(4)
In [89]: data[:3]
Out[89]: array([[ 0.4918, 1.3707, 0.137 , 0.3981, -1.0059],
[ 0.4516, 1.4445, 0.0555, -0.0397, 0.44 ],
[ 0.1629, -0.8473, -0.8223, -0.4621, -0.5137]])
为此,我们还将重新审视 SQLite3
并将其性能与使用 pandas
的替代格式进行比较。
SQL 数据库
至于 SQLite3
的一切,现在应该都很熟悉了。
In [90]: filename = path + 'numbers'
In [91]: con = sq3.Connection(filename + '.db')
In [92]: query = 'CREATE TABLE numbers (No1 real, No2 real,\
No3 real, No4 real, No5 real)' ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [93]: q = con.execute
qm = con.executemany
In [94]: q(query)
Out[94]: <sqlite3.Cursor at 0x1054e2260>
一张具有五列实数(float
对象)的表格。
这次,可以应用 .executemany()
方法,因为数据在一个单一的 ndarray
对象中可用。读取和处理数据与以前一样工作。查询结果也可以轻松可视化(参见 图 9-1)。
In [95]: %%time
qm('INSERT INTO numbers VALUES (?, ?, ?, ?, ?)', data) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
con.commit()
CPU times: user 7.16 s, sys: 147 ms, total: 7.3 s
Wall time: 7.39 s
In [96]: ll $path*
-rw-r--r-- 1 yves staff 52633600 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.db
In [97]: %%time
temp = q('SELECT * FROM numbers').fetchall() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
print(temp[:3])
[(0.4918, 1.3707, 0.137, 0.3981, -1.0059), (0.4516, 1.4445, 0.0555, -0.0397, 0.44), (0.1629, -0.8473, -0.8223, -0.4621, -0.5137)]
CPU times: user 1.86 s, sys: 138 ms, total: 2 s
Wall time: 2.07 s
In [98]: %%time
query = 'SELECT * FROM numbers WHERE No1 > 0 AND No2 < 0'
res = np.array(q(query).fetchall()).round(3) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 770 ms, sys: 73.9 ms, total: 844 ms
Wall time: 854 ms
In [99]: res = res[::100] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
plt.figure(figsize=(10, 6))
plt.plot(res[:, 0], res[:, 1], 'ro') ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
plt.savefig('../../images/ch09/io_01.png');
将整个数据集一次性插入表中。
以单步操作从表中检索所有行。
检索行的选择并将其转换为 ndarray
对象。
绘制查询结果的子集。
图 9-1. 查询结果的散点图(选择)
从 SQL 到 pandas
一个通常更高效的方法,然而,是使用 pandas
读取整个表或查询结果。当您能够将整个表读入内存时,分析查询通常可以比使用 SQL
基于磁盘的方法执行得快得多。
使用 pandas
读取整个表与将其读入 NumPy
ndarray
对象大致需要相同的时间。在这里和那里,瓶颈是 SQL
数据库。
In [100]: %time data = pd.read_sql('SELECT * FROM numbers', con) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 2.11 s, sys: 175 ms, total: 2.29 s
Wall time: 2.33 s
In [101]: data.head()
Out[101]: No1 No2 No3 No4 No5
0 0.4918 1.3707 0.1370 0.3981 -1.0059
1 0.4516 1.4445 0.0555 -0.0397 0.4400
2 0.1629 -0.8473 -0.8223 -0.4621 -0.5137
3 1.3064 0.9125 0.5142 -0.7868 -0.3398
4 -0.1148 -1.5215 -0.7045 -1.0042 -0.0600
将表的所有行读入名为 data
的 DataFrame
对象中。
数据现在在内存中。这样可以进行更快的分析。加速通常是一个数量级或更多。pandas
也可以处理更复杂的查询,尽管它既不意味着也不能替代 SQL
数据库,当涉及复杂的关系数据结构时。多个条件组合的查询结果显示在 图 9-2 中。
In [102]: %time data[(data['No1'] > 0) & (data['No2'] < 0)].head() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 19.4 ms, sys: 9.56 ms, total: 28.9 ms
Wall time: 27.5 ms
Out[102]: No1 No2 No3 No4 No5
2 0.1629 -0.8473 -0.8223 -0.4621 -0.5137
5 0.1893 -0.0207 -0.2104 0.9419 0.2551
8 1.4784 -0.3333 -0.7050 0.3586 -0.3937
10 0.8092 -0.9899 1.0364 -1.0453 0.0579
11 0.9065 -0.7757 -0.9267 0.7797 0.0863
In [103]: %%time
res = data[['No1', 'No2']][((data['No1'] > 0.5) | (data['No1'] < -0.5))
& ((data['No2'] < -1) | (data['No2'] > 1))] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
CPU times: user 20.6 ms, sys: 9.18 ms, total: 29.8 ms
Wall time: 28 ms
In [104]: plt.figure(figsize=(10, 6))
plt.plot(res['No1'], res['No2'], 'ro');
plt.savefig('../../images/ch09/io_02.png');
两个条件逻辑上组合。
逻辑上组合了四个条件。
图 9-2. 查询结果的散点图(选择)
预期地,使用 pandas
的内存分析能力会显著加速,只要 pandas
能够复制相应的 SQL
语句。
使用 pandas
的另一个优点不仅仅是这个,因为 pandas
与 PyTables
等紧密集成 — 后续部分的主题。在这里,知道它们的组合可以显著加速 I/O 操作就足够了。如下所示:
In [105]: h5s = pd.HDFStore(filename + '.h5s', 'w') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [106]: %time h5s['data'] = data ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
CPU times: user 33 ms, sys: 43.3 ms, total: 76.3 ms
Wall time: 85.8 ms
In [107]: h5s ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[107]: <class 'pandas.io.pytables.HDFStore'>
File path: /Users/yves/Documents/Temp/data/numbers.h5s
In [108]: h5s.close() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
打开 HDF5
数据库文件进行写入;在 pandas
中创建一个 HDFStore
对象。
完整的 DataFrame
对象通过二进制存储存储在数据库文件中。
HDFStore
对象的信息。
关闭数据库文件。
与使用 SQLite3
相比,整个来自原始 SQL
表的所有数据的 DataFrame
写入速度快得多。读取甚至更快:
In [109]: %%time
h5s = pd.HDFStore(filename + '.h5s', 'r') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
data_ = h5s['data'] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
h5s.close() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 8.24 ms, sys: 21.2 ms, total: 29.4 ms
Wall time: 28.5 ms
In [110]: data_ is data ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[110]: False
In [111]: (data_ == data).all() ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[111]: No1 True
No2 True
No3 True
No4 True
No5 True
dtype: bool
In [112]: np.allclose(data_, data) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[112]: True
In [113]: ll $path* ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
-rw-r--r-- 1 yves staff 52633600 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.db
-rw-r--r-- 1 yves staff 48007192 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.h5s
打开 HDF5
数据库文件进行读取。
DataFrame
被读取并存储在内存中作为 data_
。
关闭数据库文件。
这两个 DataFrame
对象不相同。
然而,它们现在包含相同的数据。
与 SQL
表相比,二进制存储通常具有更小的大小开销。
CSV 文件中的数据
交换金融数据最广泛使用的格式之一是 CSV
格式。尽管它并没有真正标准化,但它可以被任何平台处理,并且绝大多数与数据和金融分析有关的应用程序都可以处理。前一节展示了如何使用标准 Python 功能将数据写入 CSV
文件并从 CSV
文件中读取数据(参见“读写文本文件”)。pandas
使得整个过程更加方便,代码更加简洁,并且总体执行更快(还可以参见图 9-3):
In [114]: %time data.to_csv(filename + '.csv') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 6.82 s, sys: 277 ms, total: 7.1 s
Wall time: 7.54 s
In [115]: ll $path
total 282184
-rw-r--r-- 1 yves staff 43834157 Jan 18 10:05 numbers.csv
-rw-r--r-- 1 yves staff 52633600 Jan 18 10:05 numbers.db
-rw-r--r-- 1 yves staff 48007192 Jan 18 10:05 numbers.h5s
In [116]: %time df = pd.read_csv(filename + '.csv') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
CPU times: user 1.4 s, sys: 124 ms, total: 1.53 s
Wall time: 1.58 s
In [117]: df[['No1', 'No2', 'No3', 'No4']].hist(bins=20, figsize=(10, 6));
plt.savefig('../../images/ch09/io_03.png');
.to_csv()
方法将 DataFrame
数据以 CSV
格式写入磁盘。
然后 pd.read_csv()
以新的 DataFrame
对象的形式将其再次读入内存。
图 9-3. 选定列的直方图
Excel 文件中的数据
尽管处理 Excel
电子表格是本书的后续章节的主题,但以下代码简要地演示了 pandas
如何以 Excel
格式写入数据并从 Excel
电子表格中读取数据。在这种情况下,我们将数据集限制为 100,000 行:
In [118]: %time data[:100000].to_excel(filename + '.xlsx') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 23.2 s, sys: 498 ms, total: 23.7 s
Wall time: 23.9 s
In [119]: %time df = pd.read_excel(filename + '.xlsx', 'Sheet1') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
CPU times: user 5.47 s, sys: 74.7 ms, total: 5.54 s
Wall time: 5.57 s
In [120]: df.cumsum().plot(figsize=(10, 6));
plt.savefig('../../images/ch09/io_04.png');
In [121]: ll $path*
-rw-r--r-- 1 yves staff 43834157 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.csv
-rw-r--r-- 1 yves staff 52633600 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.db
-rw-r--r-- 1 yves staff 48007192 Jan 18 10:05 /Users/yves/Documents/Temp/data/numbers.h5s
-rw-r--r-- 1 yves staff 4032639 Jan 18 10:06 /Users/yves/Documents/Temp/data/numbers.xlsx
In [122]: rm -f $path*
.to_excel()
方法将 DataFrame
数据以 XLSX
格式写入磁盘。
然后 pd.read_excel()
以新的 DataFrame
对象的形式将其再次读入内存,同时指定要从中读取的工作表。
图 9-4. 所有列的线性图
生成包含较小数据子集的 Excel
电子表格文件需要相当长的时间。这说明了电子表格结构所带来的额外开销。
对生成的文件进行检查后发现,DataFrame
与 HDFStore
结合是最紧凑的选择(使用压缩,正如本章后面所述,进一步增加了优势)。与文本文件相比,作为 CSV
文件的相同数量的数据的大小要大一些。这是处理 CSV
文件时性能较慢的另一个原因,另一个原因是它们只是“普通”文本文件。
使用 PyTables 进行快速 I/O
PyTables
是HDF5
数据库标准的 Python 绑定(参见http://www.hdfgroup.org)。它专门设计用于优化 I/O 操作的性能,并充分利用可用的硬件。库的导入名称是tables
。与pandas
类似,当涉及到内存分析时,PyTables
既不能也不意味着是对SQL
数据库的完全替代。然而,它带来了一些进一步缩小差距的特性。例如,一个PyTables
数据库可以有很多表,它支持压缩和索引以及对表的非平凡查询。此外,它可以有效地存储NumPy
数组,并具有其自己的数组数据结构的风格。
首先,一些导入:
In [123]: import tables as tb ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
import datetime as dt
包名是PyTables
,导入名称是tables
。
与表格一起工作
PyTables
提供了一种基于文件的数据库格式,类似于SQLite3
。⁵。以下是打开数据库文件并创建表格的示例:
In [124]: filename = path + 'pytab.h5'
In [125]: h5 = tb.open_file(filename, 'w') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [126]: row_des = {
'Date': tb.StringCol(26, pos=1), ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
'No1': tb.IntCol(pos=2), ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
'No2': tb.IntCol(pos=3), ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
'No3': tb.Float64Col(pos=4), ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
'No4': tb.Float64Col(pos=5) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
}
In [127]: rows = 2000000
In [128]: filters = tb.Filters(complevel=0) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
In [129]: tab = h5.create_table('/', 'ints_floats', ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
row_des, ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
title='Integers and Floats', ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)
expectedrows=rows, ![9](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/9.png)
filters=filters) ![10](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/10.png)
In [130]: type(tab)
Out[130]: tables.table.Table
In [131]: tab
Out[131]: /ints_floats (Table(0,)) 'Integers and Floats'
description := {
"Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
"No1": Int32Col(shape=(), dflt=0, pos=1),
"No2": Int32Col(shape=(), dflt=0, pos=2),
"No3": Float64Col(shape=(), dflt=0.0, pos=3),
"No4": Float64Col(shape=(), dflt=0.0, pos=4)}
byteorder := 'little'
chunkshape := (2621,)
以HDF5
二进制存储格式打开数据库文件。
用于日期时间信息的date
列(作为str
对象)。
用于存储int
对象的两列。
用于存储float
对象的两列。
通过Filters
对象,可以指定压缩级别等。
表的节点(路径)和技术名称。
行数据结构的描述。
表的名称(标题)。
预期的行数;允许进行优化。
用于表格的Filters
对象。
为了用数字数据填充表格,生成两个具有随机数字的ndarray
对象。一个是随机整数,另一个是随机浮点数。通过一个简单的 Python 循环来填充表格。
In [132]: pointer = tab.row ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [133]: ran_int = np.random.randint(0, 10000, size=(rows, 2)) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [134]: ran_flo = np.random.standard_normal((rows, 2)).round(4) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [135]: %%time
for i in range(rows):
pointer['Date'] = dt.datetime.now() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
pointer['No1'] = ran_int[i, 0] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
pointer['No2'] = ran_int[i, 1] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
pointer['No3'] = ran_flo[i, 0] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
pointer['No4'] = ran_flo[i, 1] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
pointer.append() ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
tab.flush() ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
CPU times: user 8.36 s, sys: 136 ms, total: 8.49 s
Wall time: 8.92 s
In [136]: tab ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
Out[136]: /ints_floats (Table(2000000,)) 'Integers and Floats'
description := {
"Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
"No1": Int32Col(shape=(), dflt=0, pos=1),
"No2": Int32Col(shape=(), dflt=0, pos=2),
"No3": Float64Col(shape=(), dflt=0.0, pos=3),
"No4": Float64Col(shape=(), dflt=0.0, pos=4)}
byteorder := 'little'
chunkshape := (2621,)
In [137]: ll $path*
-rw-r--r-- 1 yves staff 100156248 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytab.h5
创建了一个指针对象。
具有随机int
对象的ndarray
对象。
具有随机float
对象的ndarray
对象。
datetime
对象,两个int
和两个float
对象被逐行写入。
新行被附加。
所有写入的行都会被刷新,即作为永久更改提交。
更改反映在 Table
对象描述中。
在这种情况下,Python 循环相当慢。 有一种更高效和 Pythonic 的方法可以实现相同的结果,即使用 NumPy
结构化数组。 使用存储在结构化数组中的完整数据集,表的创建归结为一行代码。 请注意,不再需要行描述; PyTables
使用结构化数组的 dtype
对象来推断数据类型:
In [138]: dty = np.dtype([('Date', 'S26'), ('No1', '<i4'), ('No2', '<i4'),
('No3', '<f8'), ('No4', '<f8')]) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [139]: sarray = np.zeros(len(ran_int), dtype=dty) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [140]: sarray[:4] ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[140]: array([(b'', 0, 0, 0., 0.), (b'', 0, 0, 0., 0.), (b'', 0, 0, 0., 0.),
(b'', 0, 0, 0., 0.)],
dtype=[('Date', 'S26'), ('No1', '<i4'), ('No2', '<i4'), ('No3', '<f8'), ('No4', '<f8')])
In [141]: %%time
sarray['Date'] = dt.datetime.now() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
sarray['No1'] = ran_int[:, 0] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
sarray['No2'] = ran_int[:, 1] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
sarray['No3'] = ran_flo[:, 0] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
sarray['No4'] = ran_flo[:, 1] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
CPU times: user 82.7 ms, sys: 37.9 ms, total: 121 ms
Wall time: 133 ms
In [142]: %%time
h5.create_table('/', 'ints_floats_from_array', sarray,
title='Integers and Floats',
expectedrows=rows, filters=filters) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
CPU times: user 39 ms, sys: 61 ms, total: 100 ms
Wall time: 123 ms
Out[142]: /ints_floats_from_array (Table(2000000,)) 'Integers and Floats'
description := {
"Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
"No1": Int32Col(shape=(), dflt=0, pos=1),
"No2": Int32Col(shape=(), dflt=0, pos=2),
"No3": Float64Col(shape=(), dflt=0.0, pos=3),
"No4": Float64Col(shape=(), dflt=0.0, pos=4)}
byteorder := 'little'
chunkshape := (2621,)
定义特殊的 dtype
对象。
使用零(和空字符串)创建结构化数组。
来自 ndarray
对象的几条记录。
ndarray
对象的列一次性填充。
这将创建 Table
对象,并用数据填充它。
这种方法快了一个数量级,代码更简洁,且实现了相同的结果。
In [143]: type(h5)
Out[143]: tables.file.File
In [144]: h5 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[144]: File(filename=/Users/yves/Documents/Temp/data/pytab.h5, title='', mode='w', root_uep='/', filters=Filters(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None))
/ (RootGroup) ''
/ints_floats (Table(2000000,)) 'Integers and Floats'
description := {
"Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
"No1": Int32Col(shape=(), dflt=0, pos=1),
"No2": Int32Col(shape=(), dflt=0, pos=2),
"No3": Float64Col(shape=(), dflt=0.0, pos=3),
"No4": Float64Col(shape=(), dflt=0.0, pos=4)}
byteorder := 'little'
chunkshape := (2621,)
/ints_floats_from_array (Table(2000000,)) 'Integers and Floats'
description := {
"Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
"No1": Int32Col(shape=(), dflt=0, pos=1),
"No2": Int32Col(shape=(), dflt=0, pos=2),
"No3": Float64Col(shape=(), dflt=0.0, pos=3),
"No4": Float64Col(shape=(), dflt=0.0, pos=4)}
byteorder := 'little'
chunkshape := (2621,)
In [145]: h5.remove_node('/', 'ints_floats_from_array') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
带有两个 Table
对象的 File
对象的描述。
这会删除具有冗余数据的第二个 Table
对象。
Table
对象在大多数情况下的行为与 NumPy
结构化的 ndarray
对象非常相似(另见 图 9-5):
In [146]: tab[:3] ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
Out[146]: array([(b'2018-01-18 10:06:28.516235', 8576, 5991, -0.0528, 0.2468),
(b'2018-01-18 10:06:28.516332', 2990, 9310, -0.0261, 0.3932),
(b'2018-01-18 10:06:28.516344', 4400, 4823, 0.9133, 0.2579)],
dtype=[('Date', 'S26'), ('No1', '<i4'), ('No2', '<i4'), ('No3', '<f8'), ('No4', '<f8')])
In [147]: tab[:4]['No4'] ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
Out[147]: array([ 0.2468, 0.3932, 0.2579, -0.5582])
In [148]: %time np.sum(tab[:]['No3']) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 64.5 ms, sys: 97.1 ms, total: 162 ms
Wall time: 165 ms
Out[148]: 88.854299999999697
In [149]: %time np.sum(np.sqrt(tab[:]['No1'])) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 59.3 ms, sys: 69.4 ms, total: 129 ms
Wall time: 130 ms
Out[149]: 133349920.36892509
In [150]: %%time
plt.figure(figsize=(10, 6))
plt.hist(tab[:]['No3'], bins=30); ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
plt.savefig('../../images/ch09/io_05.png');
CPU times: user 244 ms, sys: 67.6 ms, total: 312 ms
Wall time: 340 ms
通过索引选择行。
仅通过索引选择列值。
应用 NumPy
通用函数。
从 Table
对象绘制列。
图 9-5. 列数据的直方图
PyTables
还提供了通过典型的 SQL
-like 语句查询数据的灵活工具,如下例所示(其结果如 图 9-6 所示;与 图 9-2 相比,基于 pandas
查询):
In [151]: query = '((No3 < -0.5) | (No3 > 0.5)) & ((No4 < -1) | (No4 > 1))' ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [152]: iterator = tab.where(query) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [153]: %time res = [(row['No3'], row['No4']) for row in iterator] ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 487 ms, sys: 128 ms, total: 615 ms
Wall time: 637 ms
In [154]: res = np.array(res) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
res[:3]
Out[154]: array([[ 0.7694, 1.4866],
[ 0.9201, 1.3346],
[ 1.4701, 1.8776]])
In [155]: plt.figure(figsize=(10, 6))
plt.plot(res.T[0], res.T[1], 'ro');
plt.savefig('../../images/ch09/io_06.png');
查询作为 str
对象,由逻辑运算符组合的四个条件。
基于查询的迭代器对象。
通过列表推导收集查询结果的行…
… 并转换为 ndarray
对象。
图 9-6. 列数据的直方图
快速查询
pandas
和PyTables
都能够处理相对复杂的、类似SQL
的查询和选择。它们在执行此类操作时都进行了速度优化。但是,与关系型数据库相比,这些方法当然存在限制。但对于大多数数值和金融应用程序,它们通常并不决定性。
正如以下示例所示,使用存储在PyTables
中的数据作为Table
对象让您感觉就像是在NumPy
或pandas
中工作且是内存中的,从语法和性能方面都是如此:
In [156]: %%time
values = tab[:]['No3']
print('Max %18.3f' % values.max())
print('Ave %18.3f' % values.mean())
print('Min %18.3f' % values.min())
print('Std %18.3f' % values.std())
Max 5.224
Ave 0.000
Min -5.649
Std 1.000
CPU times: user 88.9 ms, sys: 70 ms, total: 159 ms
Wall time: 156 ms
In [157]: %%time
res = [(row['No1'], row['No2']) for row in
tab.where('((No1 > 9800) | (No1 < 200)) \
& ((No2 > 4500) & (No2 < 5500))')]
CPU times: user 78.4 ms, sys: 38.9 ms, total: 117 ms
Wall time: 80.9 ms
In [158]: for r in res[:4]:
print(r)
(91, 4870)
(9803, 5026)
(9846, 4859)
(9823, 5069)
In [159]: %%time
res = [(row['No1'], row['No2']) for row in
tab.where('(No1 == 1234) & (No2 > 9776)')]
CPU times: user 58.9 ms, sys: 40.1 ms, total: 99 ms
Wall time: 133 ms
In [160]: for r in res:
print(r)
(1234, 9841)
(1234, 9821)
(1234, 9867)
(1234, 9987)
(1234, 9849)
(1234, 9800)
使用压缩表
使用PyTables
的一个主要优势是它采用的压缩方法。它不仅使用压缩来节省磁盘空间,还利用了在某些硬件场景下改善 I/O 操作性能的压缩。这是如何实现的?当 I/O 成为瓶颈,而 CPU 能够快速(解)压缩数据时,压缩在速度方面的净效果可能是积极的。由于以下示例基于标准 SSD 的 I/O,因此观察不到压缩的速度优势。但是,使用压缩也几乎没有缺点:
In [161]: filename = path + 'pytabc.h5'
In [162]: h5c = tb.open_file(filename, 'w')
In [163]: filters = tb.Filters(complevel=5, ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
complib='blosc') ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [164]: tabc = h5c.create_table('/', 'ints_floats', sarray,
title='Integers and Floats',
expectedrows=rows, filters=filters)
In [165]: query = '((No3 < -0.5) | (No3 > 0.5)) & ((No4 < -1) | (No4 > 1))'
In [166]: iteratorc = tabc.where(query) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [167]: %time res = [(row['No3'], row['No4']) for row in iteratorc] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
CPU times: user 362 ms, sys: 55.3 ms, total: 418 ms
Wall time: 445 ms
In [168]: res = np.array(res)
res[:3]
Out[168]: array([[ 0.7694, 1.4866],
[ 0.9201, 1.3346],
[ 1.4701, 1.8776]])
压缩级别(complevel
)可以取 0(无压缩)到 9(最高压缩)的值。
使用了经过优化的Blosc
压缩引擎(Blosc),该引擎旨在提高性能。
给定前面查询的迭代器对象。
通过列表推导收集查询结果行。
使用原始数据生成压缩的Table
对象并对其进行分析比使用未压缩的Table
对象稍慢一些。那么将数据读入ndarray
对象呢?让我们来检查一下:
In [169]: %time arr_non = tab.read() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
CPU times: user 42.9 ms, sys: 69.9 ms, total: 113 ms
Wall time: 117 ms
In [170]: tab.size_on_disk
Out[170]: 100122200
In [171]: arr_non.nbytes
Out[171]: 100000000
In [172]: %time arr_com = tabc.read() ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
CPU times: user 123 ms, sys: 60.5 ms, total: 184 ms
Wall time: 191 ms
In [173]: tabc.size_on_disk
Out[173]: 40612465
In [174]: arr_com.nbytes
Out[174]: 100000000
In [175]: ll $path* ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
-rw-r--r-- 1 yves staff 200312336 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytab.h5
-rw-r--r-- 1 yves staff 40647761 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytabc.h5
In [176]: h5c.close() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
从未压缩的Table
对象tab
中读取。
从压缩的Table
对象tabc
中读取。
压缩表的大小显着减小了。
关闭数据库文件。
例子表明,与未压缩的Table
对象相比,使用压缩的Table
对象工作时几乎没有速度差异。但是,磁盘上的文件大小可能会根据数据的质量而显着减少,这有许多好处:
-
存储成本:存储成本降低了
-
备份成本:备份成本降低了
-
网络流量:网络流量减少了
-
网络速度:存储在远程服务器上并从中检索的速度更快
-
CPU 利用率:为了克服 I/O 瓶颈而增加了 CPU 利用率
使用数组
“Python 基本 I/O”演示了NumPy
对于ndarray
对象具有内置的快速写入和读取功能。当涉及到存储和检索ndarray
对象时,PyTables
也非常快速和高效。由于它基于分层数据库结构,因此提供了许多便利功能:
In [177]: %%time
arr_int = h5.create_array('/', 'integers', ran_int) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
arr_flo = h5.create_array('/', 'floats', ran_flo) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
CPU times: user 3.24 ms, sys: 33.1 ms, total: 36.3 ms
Wall time: 41.6 ms
In [178]: h5 ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[178]: File(filename=/Users/yves/Documents/Temp/data/pytab.h5, title='', mode='w', root_uep='/', filters=Filters(complevel=0, shuffle=False, bitshuffle=False, fletcher32=False, least_significant_digit=None))
/ (RootGroup) ''
/floats (Array(2000000, 2)) ''
atom := Float64Atom(shape=(), dflt=0.0)
maindim := 0
flavor := 'numpy'
byteorder := 'little'
chunkshape := None
/integers (Array(2000000, 2)) ''
atom := Int64Atom(shape=(), dflt=0)
maindim := 0
flavor := 'numpy'
byteorder := 'little'
chunkshape := None
/ints_floats (Table(2000000,)) 'Integers and Floats'
description := {
"Date": StringCol(itemsize=26, shape=(), dflt=b'', pos=0),
"No1": Int32Col(shape=(), dflt=0, pos=1),
"No2": Int32Col(shape=(), dflt=0, pos=2),
"No3": Float64Col(shape=(), dflt=0.0, pos=3),
"No4": Float64Col(shape=(), dflt=0.0, pos=4)}
byteorder := 'little'
chunkshape := (2621,)
In [179]: ll $path*
-rw-r--r-- 1 yves staff 262344490 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytab.h5
-rw-r--r-- 1 yves staff 40647761 Jan 18 10:06 /Users/yves/Documents/Temp/data/pytabc.h5
In [180]: h5.close()
In [181]: !rm -f $path*
存储ran_int
ndarray
对象。
存储ran_flo
ndarray
对象。
更改反映在对象描述中。
将这些对象直接写入HDF5
数据库比遍历对象并逐行将数据写入Table
对象或使用结构化ndarray
对象的方法更快。
基于 HDF5 的数据存储
当涉及到结构化的数值和金融数据时,HDF5
分层数据库(文件)格式是一个强大的替代方案,例如,关系数据库。无论是在直接使用PyTables
还是与pandas
的功能结合使用时,您都可以期望获得几乎达到可用硬件允许的最大 I/O 性能。
内存外计算
PyTables
支持内存外操作,这使得可以实现不适合内存的基于数组的计算。为此,请考虑以下基于EArray
类的代码。这种类型的对象允许在一维(按行)中扩展,而列数(每行的元素)需要固定。
In [182]: filename = path + 'earray.h5'
In [183]: h5 = tb.open_file(filename, 'w')
In [184]: n = 500 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [185]: ear = h5.create_earray('/', 'ear', ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
atom=tb.Float64Atom(), ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
shape=(0, n)) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [186]: type(ear)
Out[186]: tables.earray.EArray
In [187]: rand = np.random.standard_normal((n, n)) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
rand[:4, :4]
Out[187]: array([[-1.25983231, 1.11420699, 0.1667485 , 0.7345676 ],
[-0.13785424, 1.22232417, 1.36303097, 0.13521042],
[ 1.45487119, -1.47784078, 0.15027672, 0.86755989],
[-0.63519366, 0.1516327 , -0.64939447, -0.45010975]])
In [188]: %%time
for _ in range(750):
ear.append(rand) ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
ear.flush()
CPU times: user 728 ms, sys: 1.11 s, total: 1.84 s
Wall time: 2.03 s
In [189]: ear
Out[189]: /ear (EArray(375000, 500)) ''
atom := Float64Atom(shape=(), dflt=0.0)
maindim := 0
flavor := 'numpy'
byteorder := 'little'
chunkshape := (16, 500)
In [190]: ear.size_on_disk
Out[190]: 1500032000
这定义了固定的列数。
EArray
对象的路径和技术名称。
单个值的原子dtype
对象。
用于实例化的形状(没有行,n
列)。
具有随机数的ndarray
对象…
… 多次附加。
对于不会导致聚合的内存外计算,需要另一个相同形状(大小)的EArray
对象。 PyTables+有一个特殊模块可以高效处理数值表达式。它称为Expr
,基于数值表达式库numexpr
。接下来的代码使用Expr
计算之前整个EArray
对象中的方程式 9-1 的数学表达式。
方程式 9-1. 示例数学表达式
结果存储在out
EArray
对象中,表达式评估以块方式进行。
In [191]: out = h5.create_earray('/', 'out',
atom=tb.Float64Atom(),
shape=(0, n))
In [192]: out.size_on_disk
Out[192]: 0
In [193]: expr = tb.Expr('3 * sin(ear) + sqrt(abs(ear))') ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [194]: expr.set_output(out, append_mode=True) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [195]: %time expr.eval() ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 2.98 s, sys: 1.38 s, total: 4.36 s
Wall time: 3.28 s
Out[195]: /out (EArray(375000, 500)) ''
atom := Float64Atom(shape=(), dflt=0.0)
maindim := 0
flavor := 'numpy'
byteorder := 'little'
chunkshape := (16, 500)
In [196]: out.size_on_disk
Out[196]: 1500032000
In [197]: out[0, :10]
Out[197]: array([-1.73369462, 3.74824436, 0.90627898, 2.86786818, 1.75424957,
-0.91108973, -1.68313885, 1.29073295, -1.68665599, -1.71345309])
In [198]: %time out_ = out.read() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
CPU times: user 879 ms, sys: 1.11 s, total: 1.99 s
Wall time: 2.18 s
In [199]: out_[0, :10]
Out[199]: array([-1.73369462, 3.74824436, 0.90627898, 2.86786818, 1.75424957,
-0.91108973, -1.68313885, 1.29073295, -1.68665599, -1.71345309])
这将基于str
对象的表达式转换为Expr
对象。
这定义了输出为 out
EArray
对象。
这启动了表达式的评估。
这将整个 EArray
读入内存。
考虑到整个操作是在内存之外进行的,可以认为是相当快的,尤其是在标准硬件上执行。作为基准,可以考虑 numexpr
模块的内存性能(也见[Link to Come])。它更快,但并不是很大的优势:
In [200]: import numexpr as ne ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [201]: expr = '3 * sin(out_) + sqrt(abs(out_))' ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [202]: ne.set_num_threads(1) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
Out[202]: 4
In [203]: %time ne.evaluate(expr)[0, :10] ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
CPU times: user 1.72 s, sys: 529 ms, total: 2.25 s
Wall time: 2.38 s
Out[203]: array([-1.64358578, 0.22567882, 3.31363043, 2.50443549, 4.27413965,
-1.41600606, -1.68373023, 4.01921805, -1.68117412, -1.66053597])
In [204]: ne.set_num_threads(4) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
Out[204]: 1
In [205]: %time ne.evaluate(expr)[0, :10] ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
CPU times: user 2.29 s, sys: 804 ms, total: 3.09 s
Wall time: 1.56 s
Out[205]: array([-1.64358578, 0.22567882, 3.31363043, 2.50443549, 4.27413965,
-1.41600606, -1.68373023, 4.01921805, -1.68117412, -1.66053597])
In [206]: h5.close()
In [207]: !rm -f $path*
导入用于 内存中 评估数值表达式的模块。
数值表达式作为 str
对象。
将线程数设置为仅一个。
使用一个线程在内存中评估数值表达式。
将线程数设置为四。
使用四个线程在内存中评估数值表达式。
通过 TsTables 进行 I/O 操作。
TsTables
包使用 PyTables
构建了一个高性能的时间序列数据存储。主要的使用场景是“一次写入,多次检索
”。这是金融分析中的典型场景,因为数据是在市场上创建的,可能是实时或异步检索,并存储在磁盘上以供以后使用。这样的使用场景可能是一个较大的交易策略回测程序,需要反复使用历史金融时间序列的不同子集。因此,数据检索速度很重要。
示例数据
通常情况下,首先生成一些足够大的示例数据集,以说明 TsTables
的好处。以下代码基于几何布朗运动的模拟生成了三个相当长的金融时间序列(见[Link to Come])。
In [208]: no = 5000000 ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
co = 3 ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
interval = 1. / (12 * 30 * 24 * 60) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
vol = 0.2 ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [209]: %%time
rn = np.random.standard_normal((no, co)) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
rn[0] = 0.0 ![6](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/6.png)
paths = 100 * np.exp(np.cumsum(-0.5 * vol ** 2 * interval +
vol * np.sqrt(interval) * rn, axis=0)) ![7](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/7.png)
paths[0] = 100 ![8](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/8.png)
CPU times: user 932 ms, sys: 204 ms, total: 1.14 s
Wall time: 1.2 s
时间步数。
时间序列的数量。
年份间隔作为年分数。
波动率。
标准正态分布的随机数。
初始随机数设为 0。
基于 Euler 离散化的模拟。
将路径的初始值设为 100。
由于TsTables
与pandas
DataFrame
对象很好地配合,因此数据被转换为这样的对象(另见图 9-7)。
In [210]: dr = pd.date_range('2019-1-1', periods=no, freq='1s')
In [211]: dr[-6:]
Out[211]: DatetimeIndex(['2019-02-27 20:53:14', '2019-02-27 20:53:15',
'2019-02-27 20:53:16', '2019-02-27 20:53:17',
'2019-02-27 20:53:18', '2019-02-27 20:53:19'],
dtype='datetime64[ns]', freq='S')
In [212]: df = pd.DataFrame(paths, index=dr, columns=['ts1', 'ts2', 'ts3'])
In [213]: df.info()
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 5000000 entries, 2019-01-01 00:00:00 to 2019-02-27 20:53:19
Freq: S
Data columns (total 3 columns):
ts1 float64
ts2 float64
ts3 float64
dtypes: float64(3)
memory usage: 152.6 MB
In [214]: df.head()
Out[214]: ts1 ts2 ts3
2019-01-01 00:00:00 100.000000 100.000000 100.000000
2019-01-01 00:00:01 100.018443 99.966644 99.998255
2019-01-01 00:00:02 100.069023 100.004420 99.986646
2019-01-01 00:00:03 100.086757 100.000246 99.992042
2019-01-01 00:00:04 100.105448 100.036033 99.950618
In [215]: df[::100000].plot(figsize=(10, 6));
plt.savefig('../../images/ch09/io_07.png')
图 9-7. 金融时间序列的选定数据点
数据存储
TsTables
基于特定的基于块的结构存储金融时间序列数据,该结构允许根据某个时间间隔快速检索任意数据子集。为此,该软件包将create_ts()
函数添加到PyTables
中。以下代码使用了来自PyTables
的class
基于描述方法,基于tb.IsDescription
类。
In [216]: import tstables as tstab
In [217]: class ts_desc(tb.IsDescription):
timestamp = tb.Int64Col(pos=0) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
ts1 = tb.Float64Col(pos=1) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
ts2 = tb.Float64Col(pos=2) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
ts3 = tb.Float64Col(pos=3) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [218]: h5 = tb.open_file(path + 'tstab.h5', 'w') ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
In [219]: ts = h5.create_ts('/', 'ts', ts_desc) ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
In [220]: %time ts.append(df) ![5](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/5.png)
CPU times: user 692 ms, sys: 403 ms, total: 1.1 s
Wall time: 1.12 s
In [221]: type(ts)
Out[221]: tstables.tstable.TsTable
In [222]: ls -n $path
total 306720
-rw-r--r-- 1 501 20 157037368 Jan 18 10:07 tstab.h5
时间戳的列。
存储数字数据的列。
为写入(w
)打开HDF5
数据库文件。
基于ts_desc
对象创建TsTable
对象。
将DataFrame
对象中的数据附加到TsTable
对象。
数据检索
使用TsTables
编写数据显然非常快,即使与硬件有关。对数据的块的读取也是如此。方便的是,TaTables
返回一个DataFrame
对象(另见图 9-8)。
In [223]: read_start_dt = dt.datetime(2019, 2, 1, 0, 0) ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
read_end_dt = dt.datetime(2019, 2, 5, 23, 59) ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
In [224]: %time rows = ts.read_range(read_start_dt, read_end_dt) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
CPU times: user 80.5 ms, sys: 36.2 ms, total: 117 ms
Wall time: 116 ms
In [225]: rows.info() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 431941 entries, 2019-02-01 00:00:00 to 2019-02-05 23:59:00
Data columns (total 3 columns):
ts1 431941 non-null float64
ts2 431941 non-null float64
ts3 431941 non-null float64
dtypes: float64(3)
memory usage: 13.2 MB
In [226]: rows.head() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
Out[226]: ts1 ts2 ts3
2019-02-01 00:00:00 52.063640 40.474580 217.324713
2019-02-01 00:00:01 52.087455 40.471911 217.250070
2019-02-01 00:00:02 52.084808 40.458013 217.228712
2019-02-01 00:00:03 52.073536 40.451408 217.302912
2019-02-01 00:00:04 52.056133 40.450951 217.207481
In [227]: h5.close()
In [228]: (rows[::500] / rows.iloc[0]).plot(figsize=(10, 6));
plt.savefig('../../images/ch09/io_08.png')
时间间隔的开始时间。
时间间隔的结束时间。
函数ts.read_range()
返回时间间隔的DataFrame
对象。
DataFrame
对象有几十万行数据。
图 9-8. 金融时间序列的特定时间间隔(归一化)
为了更好地说明基于TsTables
的数据检索性能,考虑以下基准,该基准检索由三天的一秒钟柱状图组成的 100 个数据块。检索包含 345,600 行数据的DataFrame
仅需不到十分之一秒。
In [229]: import random
In [230]: h5 = tb.open_file(path + 'tstab.h5', 'r')
In [231]: ts = h5.root.ts._f_get_timeseries() ![1](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/1.png)
In [235]: %%time
for _ in range(100): ![2](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/2.png)
d = random.randint(1, 24) ![3](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/3.png)
read_start_dt = dt.datetime(2019, 2, d, 0, 0, 0)
read_end_dt = dt.datetime(2019, 2, d + 3, 23, 59, 59)
rows = ts.read_range(read_start_dt, read_end_dt)
CPU times: user 3.51 s, sys: 1.03 s, total: 4.55 s
Wall time: 4.62 s
In [233]: rows.info() ![4](https://gitee.com/OpenDocCN/ibooker-quant-zh/raw/master/docs/py-fin-2e/img/4.png)
<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 431941 entries, 2019-02-01 00:00:00 to 2019-02-05 23:59:00
Data columns (total 3 columns):
ts1 431941 non-null float64
ts2 431941 non-null float64
ts3 431941 non-null float64
dtypes: float64(3)
memory usage: 13.2 MB
In [234]: !rm $path/tstab.h5
连接到TsTable
对象。
数据检索重复多次。
起始日值被随机化。
最后检索到的DataFrame
对象。
结论
基于SQL
或关系数据库的方法在处理展示了许多单个对象/表之间关系的复杂数据结构时具有优势。在某些情况下,这可能会使它们在纯NumPy
ndarray
或pandas
DataFrame
方法上的性能劣势成为合理。
金融或一般科学中的许多应用领域可以通过主要基于数组的数据建模方法取得成功。在这些情况下,通过利用原生NumPy
的 I/O 功能、NumPy
和PyTables
功能的组合,或通过HDF5
-based 存储的pandas
方法,可以实现巨大的性能提升。当处理大型(金融)时间序列数据集时,尤其是在“一次写入,多次检索”的场景中,TsTables
特别有用。
虽然最近的一个趋势是使用基于商品硬件的大量计算节点组成的云解决方案,特别是在金融背景下,人们应该仔细考虑哪种硬件架构最适合分析需求。微软的一项研究对这个问题有所启发:
我们声称一个“扩展”服务器可以处理这些工作中的每一个,并且在性能、成本、功耗和服务器密度等方面与集群一样好,甚至更好。
Appuswamy 等人(2013 年)
从事数据分析的公司、研究机构等应该首先分析一般情况下必须完成的具体任务,然后根据以下方面的硬件/软件架构做出决策:
扩展
使用具有标准 CPU 和相对较低内存的许多商品节点的集群
扩展
使用一台或多台强大的服务器,配备多核 CPU,可能还有 GPU 甚至 TPU,当机器学习和深度学习发挥作用时,并拥有大量内存。
扩展硬件规模并应用适当的实现方法可能会显著影响性能。下一章将更多地涉及性能。
进一步阅读
本章开头引用的论文以及“结论”部分是一篇不错的文章,也是思考金融分析硬件架构的良好起点:
- Appuswamy,Raja 等人(2013 年):“
没有人因为购买集群而被解雇。
”微软研究,英格兰剑桥,http://research.microsoft.com/apps/pubs/default.aspx?id=179615。
通常情况下,网络提供了许多有关本章涵盖主题的宝贵资源:
-
对于使用
pickle
对 Python 对象进行序列化,请参阅文档:http://docs.python.org/3/library/pickle.html。 -
关于
NumPy
的 I/O 功能概述可在SciPy
网站上找到:http://docs.scipy.org/doc/numpy/reference/routines.io.html。 -
对于使用
pandas
进行 I/O,请参阅在线文档中的相应部分:http://pandas.pydata.org/pandas-docs/stable/io.html。 -
PyTables
首页提供了教程和详细文档:http://www.pytables.org。 -
TsTables
的 Github 页面位于https://github.com/afiedler/tstables。
¹ 这里,我们不区分不同级别的 RAM 和处理器缓存。当前内存架构的最佳使用是一个独立的主题。
² 要了解 Python 可用的数据库连接器的概述,请访问https://wiki.python.org/moin/DatabaseInterfaces。与直接使用关系型数据库不同,对象关系映射器,例如SQLAlchemy,通常非常有用。它们引入了一个抽象层,允许更加 Pythonic、面向对象的代码。它们还允许更容易地在后端将一个关系型数据库更换为另一个。
³ 请参阅https://www.sqlite.org/lang.html以了解 SQLite3
语言方言的概述。
⁴ 请参阅http://docs.scipy.org/doc/numpy/reference/arrays.datetime.html。
⁵ 许多其他数据库需要服务器-客户端架构。对于交互式数据和金融分析,基于文件的数据库在一般情况下会更加方便,也足够满足大多数目的。
标签:raw,Python,docs,--,重译,https,master,com,png From: https://www.cnblogs.com/apachecn/p/18239950