加载数值数据
什么是数值数据?
在“数值数据”框架下有许多不同的数据格式(并且本质上这些数据格式不一定是数值). 数值数据与普通数据的主要区别是数据需要的 Wolfram 语言格式将由实数、整数、有理数或者复数,而不是字符串或者符号表示.
加载数据的函数与策略
Import | 对用户友好,并支持多种格式 |
ReadList | 快速灵活地控制类型,支持数据流 |
BinaryReadList | 比前两种函数更好的性能 |
Get(DumpSave) | 最有效的加载操作,防止数组封装 |
Import | 高内存花销,有限的性能 |
ReadList | 对用户不友好,最适用于普通文本格式 |
BinaryReadList | 通常是最不用户友好的,可能涉及大量数据处理 |
Get(DumpSave) | 文件不可转移,需要先由其他机制加载 |
对于小型文件,并且当第一次作用于新数据时,通常使用 Import 来加载数据是最简单的. Import 提供了最友好的用户界面来加载数据,并且,通常在这些情况下,额外的花销不是主要问题. 但是,当部署一个应用程序或者使用特别大的数据集时,使用函数例如 ReadList 或者 BinaryReadList 能够更好地提高数据加载的性能.
ReadList 的局限性
作为一个普遍适用的规则,ReadList 在速度和内存开销上比 Import 操作更优. 部分原因是受 ReadList 中使用的第二个参数的类型限制,这使得 Wolfram 语言可以跳过 Import 中使用的各种解析操作(这确保解释为 strings、reals 和 integers 的字符串项目正确用于数值). 这通常产生对用户更加友好的 Import,代价是需要额外的时间和内存来做与 ReadList 相同的操作.
如果您想要看看在内存使用量上的区别,请尝试应用 MemoryConstrained[…,4*1024^2](把操作限制于 4 MB 的 RAM):
ReadList 在大型数据文件具有单个类型(比如实数或者整数组成的行和列)的情况下易于使用,但是当使用多个类型使,需要更多操作(比如当字符串和实数混合时,或者在每个列的第一行中具有实数值的数据文件中).
Import 自动转化时间序列数据的日期字符串:
使用转换器导入
不是所有 Import 操作都由 Wolfram 语言直接处理,但是依赖于额外的转换器来处理导入操作. 在许多情况下,这些二进制文件在文件大小上有自身的局限性,或者可能具有某些与它们相关联的开销.
XLS 文件(Microsoft Excel 文档),例如,使用 XLS 转换器来处理 XLS 文档,并且将它们读取到 Wolfram 语言中. 该过程需要额外的内存,由于不单是 Wolfram 语言而且还有转换器也需要内存中的一份文件来处理,因此必须把数据通过 Wolfram Symbolic Transfer Protocol (WSTP) 传给 Wolfram 语言. 与导入这些文件格式相关联的内存开销很大是不常见的.
对 BigData 友好的格式
Comma-Separated Values | .csv | 使用逗号和换行分隔记录的普通文本格式 |
Tab-Separated Values | .tsv | 使用制表符和换行分隔记录的普通文本格式 |
Table | .dat | 使用制表符和换行分隔记录的普通文本文件 |
Plain Text | .txt | 普通文本文件可以使用任意记录分隔符存储数据 |
Binary | * | 二进制数据格式;可以具有各种不同的文件扩展名,或者什么都不用 |
测量
AbsoluteTiming vs. Timing
Timing 测量在内核进行计算所需的时间,并且没有算上等待时间.
当读进数据时,查看 Timing 的结果会是有用的,但是在大多数情况下,经过时间(因此使用 AbsoluteTiming)是速度的有效测量方式,由于所花的实际时间很可能影响应用. 下列示例显示了在范例数据集上 Timing 和 AbsoluteTiming 的不同.
ClearSystemCache 用于每次计算之前,以确保时间值不受缓存结果影响:
测量时间的注意事项
当测量时间时,通常在特定操作上设置时间限制是个好办法. TimeConstrained 可以加在目标函数上,并且指定一个时间限制(以秒为单位).
MemoryInUse 与 MaxMemoryUsed
测量内存的注意事项
很重要的一点是,注意,尤其当使用大型数据文件时,Wolfram 语言保持一份过去计算的历史数据,这由环境变量$HistoryLength 管理.
ByteCount 的注意事项
ByteCount 结果可以与由 MemoryInUse 返回的结果不同,因为 ByteCount 分别计算每个表达式和子表达式,但是事实上,子表达式可以是相同的,因此可以共享.
下面的例子说明这一点.
这些结果是不同的,因为一个 1,000,000 元素的表达式(比如 x)使用 8,000,000 个字节,与什么元素无关. (坐在使用 64 位机器,其中一个指针是 8 字节,因此一百万指针组成的数组是 8 百万字节.)所有这些指针指向相同的表达式,String 的内容是 "Sample long string...".
ByteCount 使用简单的方法来计算表达式的大小:它把每个分量的大小和容器的大小加起来.
下面是例子.
返回 8,000,000 个字节,加上 String 表达式的一百万个备份,产生一个 152 百万的估计字节数.
局限性
在 Wolfram 语言第 9 版以及更高的版本中,您能够导入的数据量的限制是系统上可用的内存量,或者更具体一些是您的操作系统允许对单个进程使用多少内存. 换句话说,当加载数据时,存在大量因素您需要考虑,比如它们可能限制您加载大型数据集的能力.
数据维度与文件大小
值得注意的是,内存不仅受表达式中的内容影响,也受数据维度影响. 例如,下面的两个加载数据集在相同的机器上使用 ReadList 和 Import,每个都使用新的内核,并且具有相同的元素数目,但是需要很不同的内存来加载和存储表达式.
数据由位于 0 和 10 之间的 Real 数,以及单个列中的 4.68 百万个元素组成:
注意,封装的表达式都具有相同的大小(正如每个数据文件的 Import 和 ReadList 的未封装大小),但是在基于文件维度需要加载和存储表达式的内存量上存在显著区别(在某一维度上是长的表达式通常需要更多内存).
改善局限
ReadList 与 Import 对比
二进制文件
BinaryReadList 是加载数据最节省内存最节省时间的函数,基本上1个文件大小,只需1个单位内存的消耗.
正如前面的章节所示,当操作二进制格式的数据时,通常 BinaryReadList 比 ReadList 操作运行得更快. 但是,可能有些情况下您的数据已经是以普通文本/ASCII 格式存储,这时可能仍然可以使用 BinaryReadList.
编程帮助函数
在开发过程中,您可能在文件上使用 Import 以获取数据,并且让 Wolfram 语言来处理格式化和数据解释.
该函数比 Import 快 5 倍,并且使用要求的部分内存,因为它根据数据格式自动调整,而不是例如 Import 的普通函数(导入普通文本文件通常是一个很鲁棒的,普通 ReadList 函数来处理大量数据格式,另外进行一些后续处理).
读取文件的一部分
大型数据文件,尤其是那些通过脚本或者记录系统合成的文件,有时候可以消耗大量空间,致使它们无法一次加载到系统内存中. 但是,这并不意味着数据是 Wolfram 语言无法访问的,并且存在大量方法,可用于从相当大的文件中读取部分数据.
ReadList 可直接在大型文件上使用,以读取数据子集,如果数据位于文件顶端.
重新加载数据
有些应用可能要求您每次使用时重新加载数据,或者可能要求访问数据文件的特定列表. 在这些情况下,数据加载可以通过使用 Wolfram 语言的 DumpSave 函数得到大幅度优化.
Wolfram 语言含有两个内置方法,可以在数据文件中存储变量,除了正常的 Write/Export 函数. Save 和 DumpSave 可以用于在文件系统中存储变量值,允许它们将来使用 Get 被重新加载.
在 Save 和 DumpSave 之间有一些重要区别. 关于数据存储的最大不同是 Save 使用普通文本格式来存储信息,而 DumpSave 使用二进制数据格式(它使用较少的空间,并且可以更快地读取). DumpSave 也保存封装数组,这可以同时提高速度和内存开销.
DumpSave 的主要局限性是它的编码系统要求数据在与编码使用的相同机器架构上加载,因此不同不能在平台之间转移.