Python 金融编程第二版(四)(3)https://developer.aliyun.com/article/1559419
CSV 文件中的数据
交换金融数据最广泛使用的格式之一是 CSV
格式。尽管它并没有真正标准化,但它可以被任何平台处理,并且绝大多数与数据和金融分析有关的应用程序都可以处理。前一节展示了如何使用标准 Python 功能将数据写入 CSV
文件并从 CSV
文件中读取数据(参见“读写文本文件”)。pandas
使得整个过程更加方便,代码更加简洁,并且总体执行更快(还可以参见图 9-3):
In [114]: %time data.to_csv(filename + '.csv') # ① 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') # ② 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') # ① 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') # ② 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 # ① import datetime as dt
①
包名是PyTables
,导入名称是tables
。
与表格一起工作
PyTables
提供了一种基于文件的数据库格式,类似于SQLite3
。⁵。以下是打开数据库文件并创建表格的示例:
In [124]: filename = path + 'pytab.h5' In [125]: h5 = tb.open_file(filename, 'w') # ① In [126]: row_des = { 'Date': tb.StringCol(26, pos=1), # ② 'No1': tb.IntCol(pos=2), # ③ 'No2': tb.IntCol(pos=3), # ③ 'No3': tb.Float64Col(pos=4), # ④ 'No4': tb.Float64Col(pos=5) # ④ } In [127]: rows = 2000000 In [128]: filters = tb.Filters(complevel=0) # ⑤ In [129]: tab = h5.create_table('/', 'ints_floats', # ⑥ row_des, # ⑦ title='Integers and Floats', # ⑧ expectedrows=rows, # ⑨ filters=filters) # ⑩ 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 # ① In [133]: ran_int = np.random.randint(0, 10000, size=(rows, 2)) # ② In [134]: ran_flo = np.random.standard_normal((rows, 2)).round(4) # ③ In [135]: %%time for i in range(rows): pointer['Date'] = dt.datetime.now() # ④ pointer['No1'] = ran_int[i, 0] # ④ pointer['No2'] = ran_int[i, 1] # ④ pointer['No3'] = ran_flo[i, 0] # ④ pointer['No4'] = ran_flo[i, 1] # ④ pointer.append() # ⑤ tab.flush() # ⑥ CPU times: user 8.36 s, sys: 136 ms, total: 8.49 s Wall time: 8.92 s In [136]: tab # ⑦ 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')]) # ① In [139]: sarray = np.zeros(len(ran_int), dtype=dty) # ② In [140]: sarray[:4] # ③ 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() # ④ sarray['No1'] = ran_int[:, 0] # ④ sarray['No2'] = ran_int[:, 1] # ④ sarray['No3'] = ran_flo[:, 0] # ④ sarray['No4'] = ran_flo[:, 1] # ④ 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) # ⑤ 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 # ① 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') # ②
①
带有两个 Table
对象的 File
对象的描述。
②
这会删除具有冗余数据的第二个 Table
对象。
Table
对象在大多数情况下的行为与 NumPy
结构化的 ndarray
对象非常相似(另见 图 9-5):
In [146]: tab[:3] # ① 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'] # ② Out[147]: array([ 0.2468, 0.3932, 0.2579, -0.5582]) In [148]: %time np.sum(tab[:]['No3']) # ③ 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'])) # ③ 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); # ④ 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))' # ① In [152]: iterator = tab.where(query) # ② In [153]: %time res = [(row['No3'], row['No4']) for row in iterator] # ③ CPU times: user 487 ms, sys: 128 ms, total: 615 ms Wall time: 637 ms In [154]: res = np.array(res) # ④ 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, # ① complib='blosc') # ② 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) # ③ In [167]: %time res = [(row['No3'], row['No4']) for row in iteratorc] # ④ 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() # ① 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() # ② 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* # ③ -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() # ④
①
从未压缩的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) # ① arr_flo = h5.create_array('/', 'floats', ran_flo) # ② CPU times: user 3.24 ms, sys: 33.1 ms, total: 36.3 ms Wall time: 41.6 ms In [178]: h5 # ③ 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
对象的方法更快。
Python 金融编程第二版(四)(5)https://developer.aliyun.com/article/1559424