<jupyter_start><jupyter_text>Data Storage 高效的存储可以极大地提高性能，尤其是需要反复在磁盘上操作时。解压文本和解析CSV文件很耗时。 对于中等数据，最有效的策略之一是使用二进制存储格式，如HDF5。 通常这样做带来的性能提升足以让你再次切换回使用Pandas，而不是使用`dask.dataframe`。在本节中，我们将学习如何有效地安排和存储你的数据集在磁盘上的二进制格式。 我们将使用以下内容。1.  [Pandas `HDFStore`](http://pandas.pydata.org/pandas-docs/stable/io.htmlio-hdf5) "HDF5"格式化2.  数字化存储文本数据的分类法**主要收获**：1.  存储格式对性能的影响相差一个数量级以上。2.  文本数据即使是HDF5这样的快速格式也会保持缓慢的速度。3.  二进制格式、列存储和分区数据的结合，将一秒的等待时间变成了80ms的等待时间。  创建数据<jupyter_code>%run prep.py -d accounts<jupyter_output><empty_output><jupyter_text>读CSV 首先我们像之前一样读取我们的csv数据。CSV等文本格式的文件是数据源的常见存储方式，因为它们需要最少的预处理，可以逐行写入，并且是人类可读的。由于Pandas的 "read_csv "优化得很好，CSV是一个合理的输入，但远未达到优化的程度，因为阅读需要大量的文本解析。<jupyter_code>import os
filename = os.path.join('data', 'accounts.*.csv')
filename
import dask.dataframe as dd
df_csv = dd.read_csv(filename)
df_csv.head()<jupyter_output><empty_output><jupyter_text>写HDF5 HDF5和netCDF是科学领域非常常用的二进制数组格式。Pandas中包含一个专门的HDF5格式，`HDFStore`。 `dd.DataFrame.to_hdf``方法的工作原理与`pd.DataFrame.to_hdf``方法完全相同。<jupyter_code>target = os.path.join('data', 'accounts.h5')
target
# 转换为二进制格式，前期需要一些时间。
%time df_csv.to_hdf(target, '/data')
# same data as before
df_hdf = dd.read_hdf(target, '/data')
df_hdf.head()<jupyter_output><empty_output><jupyter_text>比较CSV和HDF5的速度 我们做一个简单的计算，需要读取数据集的一列，并比较CSV文件和我们新创建的HDF5文件之间的性能。 你希望哪个更快？<jupyter_code>%time df_csv.amount.sum().compute()
%time df_hdf.amount.sum().compute()<jupyter_output><empty_output><jupyter_text>遗憾的是，它们的速度差不多，甚至可能更慢。这里的罪魁祸首是 "names "列，它是 "object "类型的，因此很难有效地存储。 这里有两个问题：1.  如何在磁盘上高效存储`names`这样的文本数据？2.  为什么我们要读取`names`列，而我们想要的是`amount`。  1.  使用分类数据有效地存储文本 我们可以使用Pandas的分类方法，用数字表示来代替我们的对象dtypes。 这在前期需要花费更多的时间，但会带来更好的性能。更多关于分类数据的内容，请参见[pandas docs](http://pandas.pydata.org/pandas-docs/stable/categorical.html)和[this blogpost](http://matthewrocklin.com/blog/work/2015/06/18/Categoricals)。<jupyter_code># 将数据分类，然后存储在HDFStore中。
%time df_hdf.categorize(columns=['names']).to_hdf(target, '/data2')
# 看起来是一样的
df_hdf = dd.read_hdf(target, '/data2')
df_hdf.head()
# 但加载速度更快
%time df_hdf.amount.sum().compute()<jupyter_output><empty_output><jupyter_text>现在的速度肯定比以前快。 这告诉我们，影响存储性能的不仅仅是我们使用的文件类型，还有我们如何表示我们的变量。读取的性能如何取决于我们使用的调度器？你可以用线程、进程和分布式来尝试。然而这样还是可以更好。 我们必须读取所有的列(`names`和`amount`)，以便计算一个列(`amount`)的和。 我们会用`parquet`这个磁盘上的列存储来进一步改进这一点。 不过首先我们要学习如何在dask.dataframe中设置索引。  练习 `fastparquet`是一个与parquet格式文件交互的库，parquet格式文件是大数据生态系统中非常常见的格式，为Hadoop、Spark和Impala等所使用。<jupyter_code>target = os.path.join('data', 'accounts.parquet')
df_csv.categorize(columns=['names']).to_parquet(target, storage_options={"has_nulls": True}, engine="fastparquet")<jupyter_output><empty_output><jupyter_text>调查新目录中的文件结构--你认为这些文件是用来干什么的？`to_parquet`有很多选项，比如压缩，是否显式写入NULLs信息（本例中不需要），以及如何对字符串进行编码。可以实验一下，看看它们对文件大小和处理时间有什么影响，如下：<jupyter_code>ls -l data/accounts.parquet/
df_p = dd.read_parquet(target)
# 注意，列名显示了值的类型--可以选择加载为分类列或不加载。
df_p.dtypes<jupyter_output><empty_output><jupyter_text>对这个版本的数据重新运行上面的和计算，并计算所需时间。你可能想多试几次--许多库在第一次调用时，通常会做各种设置工作。<jupyter_code>%time df_p.amount.sum().compute()<jupyter_output><empty_output><jupyter_text>在对数据进行归档时，通常会按具有唯一标识符的一列进行排序和分区，以方便以后快速查找。对于该数据，该列就是`id`。从原始的CSV中，从HDF5和parquet版本中，最后从应用`set_index('id')`后写的新parquet版本中，检索`id==100`对应的行需要多长时间。<jupyter_code># df_p.set_index('id').to_parquet(...)<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.14172023262932354, 'fraction_numerical': 0.010407101316192225, 'mean_word_length': 13.787330316742082, 'pattern_counts': {'":': 1, '<': 37, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '9', 'dir': 'jupyter-structured-clean-dedup', 'id': '391718', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 2098, 'n_tokens_neox': 2244, 'n_words': 99, 'path': '07_dataframe_storage.ipynb', 'repo_name': 'haharay/DaskTutor', 'size': '3267'}