羁瘾 發表於 2021-8-14 21:45:00

别再用CSV了,更高效的Python文件存储方案

<p>CSV无可厚非的是一种良好的通用文件存储方式,几乎任何一款工具或者编程语言都能对其进行读写,但是当文件特别大的时候,CSV这种存储方式就会变得十分缓慢且低效。本文将介绍几种在Python中能够代替CSV这种格式的其他文件格式,并对比每种文件存储的时间与大小。</p>
<p>先说结论,<code>parquet</code>是最好的文件存储格式,具体对比见下文。</p>
<h2 id="生成随机数据">生成随机数据</h2>
<h3 id="导入依赖">导入依赖</h3>
<pre><code class="language-python">import random
import string
import pickle
# 以下需要自行安装
import numpy as np
import pandas as pd
import tables
import pyarrow as pa
import pyarrow.feather as feather
import pyarrow.parquet as pq
import fastparquet as fp
</code></pre>
<h3 id="生成随机数据-1">生成随机数据</h3>
<p>这里使用pandas的dataframe来存储数据</p>
<pre><code class="language-python"># 变量定义
row_num = int(1e7)
col_num = 5
str_len = 4
str_nunique = 10 # 字符串组合数量
# 生成随机数
int_matrix = np.random.randint(0, 100, size=(row_num, col_num))
df = pd.DataFrame(int_matrix, columns=['int_%d' % i for i in range(col_num)])
float_matrix = np.random.rand(row_num, col_num)
df = pd.concat(
    (df, pd.DataFrame(float_matrix, columns=['float_%d' % i for i in range(col_num)])), axis=1)
str_list = [''.join(random.sample(string.ascii_letters, str_len))
            for _ in range(str_nunique)]
for i in range(col_num):
    sr = pd.Series(str_list*(row_num//str_nunique)
                   ).sample(frac=1, random_state=i)
    df['str_%d' % i] = sr

print(df.info())
</code></pre>
<p>生成100w行数据,其中整型,浮点型和字符串各5列,数据大小在内存里大概为1GB+</p>
<pre><code class="language-python">&lt;class 'pandas.core.frame.DataFrame'&gt;
RangeIndex: 10000000 entries, 0 to 9999999
Data columns (total 15 columns):
#   Column   Dtype
---------   -----
0   int_0    int64
1   int_1    int64
2   int_2    int64
3   int_3    int64
4   int_4    int64
5   float_0float64
6   float_1float64
7   float_2float64
8   float_3float64
9   float_4float64
10str_0    object
11str_1    object
12str_2    object
13str_3    object
14str_4    object
dtypes: float64(5), int64(5), object(5)
memory usage: 1.1+ GB
</code></pre>
<h2 id="保存文件">保存文件</h2>
<h3 id="csv">csv</h3>
<p>CSV的保存方式很简单,直接使用pandas自带的<code>to_csv()</code> 方法即可</p>
<pre><code class="language-python"># 写入
df.to_csv('./df_csv.csv', index=False)
# 读取
df = pd.read_csv('./df_csv.csv')
</code></pre>
<p>写入时间花费:78 s</p>
<p>读取时间花费:11.8 s</p>
<p>所需存储空间:1.3GB</p>
<h3 id="csv--gz">csv + gz</h3>
<p>基于自带的压缩方法,对保存的csv文件使用gzip算法进行压缩</p>
<pre><code class="language-python"># 写入
df.to_csv('./df_csv.csv.gz', index=False, compression='gzip')
# 读取
df = pd.read_csv('./df_csv.csv.gz', compression='gzip')
</code></pre>
<p>写入时间花费:259 s</p>
<p>读取时间花费:22.8 s</p>
<p>所需存储空间:508M</p>
<h3 id="csv--zip">csv + zip</h3>
<p>基于自带的压缩方法,对保存的csv文件使用zip算法进行压缩</p>
<pre><code class="language-python"># 写入
df.to_csv('./df_csv.csv.zip', index=False, compression='zip')
# 读取
df = pd.read_csv('./df_csv.csv.zip', compression='zip')
</code></pre>
<p>写入时间花费:177 s</p>
<p>读取时间花费:20.7 s</p>
<p>所需存储空间:511M</p>
<h3 id="pkl">pkl</h3>
<p>pkl文件需要用到<code>built-in</code>的<code>pickle</code>包</p>
<pre><code class="language-python"># 写入
with open('./df_pkl.pkl', 'wb') as f:
    pickle.dump(df, f)
# 读取
with open('./df_pkl.pkl', 'rb') as f:
    df = pickle.load(f)
</code></pre>
<p>写入时间花费:2.89 s</p>
<p>读取时间花费:2.61 s</p>
<p>所需存储空间:858M</p>
<h3 id="npy">npy</h3>
<p>npy是numpy自带的一种保存格式,唯一的缺点是只能保存numpy的格式,所以需要将pandas先转成numpy才行,为了公平,这里我们会算上转换的时间</p>
<pre><code class="language-python"># 写入
with open('./df_npy.npy', "wb") as f:
    np.save(f, arr=df.values)
# 读取
with open('./df_npy.npy', "rb") as f:
    df_array = np.load(f, allow_pickle=True)
df = pd.DataFrame(df_array)
</code></pre>
<p>写入时间花费:21 s</p>
<p>读取时间花费:14.8 s</p>
<p>所需存储空间:620M</p>
<h3 id="hdf">hdf</h3>
<blockquote>
<p>层次数据格式(HDF)是自描述的,允许应用程序在没有外部信息的情况下解释文件的结构和内容。一个HDF文件可以包含一系列相关对象,这些对象可以作为一个组或单个对象进行访问。</p>
</blockquote>
<p>这里将使用pandas自带的<code>to_hdf()</code>方法,该方法默认是用的<code>HDF5</code>格式</p>
<pre><code class="language-python"># 写入
df.to_hdf('df_hdf.h5', key='df')
# 读取
df = pd.read_hdf('df_hdf.h5', key='df')
</code></pre>
<p>写入时间花费:3.96 s</p>
<p>读取时间花费:4.13 s</p>
<p>所需存储空间:1.5G</p>
<h3 id="已废弃-msgpack"><s>已废弃 msgpack</s></h3>
<p><s>pandas支持msgpack格式的对象序列化。他是一种轻量级可移植的二进制格式,同二进制的JSON类似,具有高效的空间利用率以及不错的写入(序列化)和读取(反序列化)性能。</s></p>
<blockquote>
<p>从0.25版本开始,不推荐使用msgpack格式,并且之后的版本也将删除它。推荐使用pyarrow对pandas对象进行在线的转换。</p>
</blockquote>
<blockquote>
<p>read_msgpack() (opens new window)仅在pandas的0.20.3版本及以下版本兼容。</p>
</blockquote>
<h3 id="parquet">parquet</h3>
<blockquote>
<p>Apache Parquet为数据帧提供了分区的二进制柱状序列化。它的设计目的是使数据帧的读写效率,并使数据共享跨数据分析语言容易。Parquet可以使用多种压缩技术来尽可能地缩小文件大小,同时仍然保持良好的读取性能。</p>
</blockquote>
<p>这里需要使用到<code>pyarrow</code>里面的方法来进行操作</p>
<pre><code class="language-python"># 写入
pq.write_table(pa.Table.from_pandas(df), 'df_parquet.parquet')
# 读取
df = pq.read_table('df_parquet.parquet').to_pandas()
</code></pre>
<p>写入时间花费:3.47 s</p>
<p>读取时间花费:1.85 s</p>
<p>所需存储空间:426M</p>
<h3 id="feature">feature</h3>
<blockquote>
<p>Feather是一种可移植的文件格式,用于存储内部使用Arrow IPC格式的Arrow表或数据帧(来自Python或R等语言)。Feather是在Arrow项目早期创建的,作为Python和R的快速、语言无关的数据帧存储概念的证明。</p>
</blockquote>
<p>这里需要使用到<code>pyarrow</code>里面的方法来进行操作</p>
<pre><code class="language-python"># 写入
feather.write_feather(df, 'df_feather.feather')
# 读取
df = feather.read_feather('df_feather.feather')
</code></pre>
<p>写入时间花费:1.9 s</p>
<p>读取时间花费:1.52 s</p>
<p>所需存储空间:715M</p>
<h2 id="总结">总结</h2>
<p>对比表格</p>
<table>
<thead>
<tr>
<th style="text-align: center">文件类型</th>
<th style="text-align: center">写入时间(s)</th>
<th style="text-align: center"><strong>读取时间(s)</strong></th>
<th style="text-align: center"><strong>存储空间(MB)</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center"><strong>csv</strong></td>
<td style="text-align: center">78.00</td>
<td style="text-align: center">11.80</td>
<td style="text-align: center">1,300</td>
</tr>
<tr>
<td style="text-align: center"><strong>csv+gz</strong></td>
<td style="text-align: center">259.00</td>
<td style="text-align: center">22.80</td>
<td style="text-align: center">508</td>
</tr>
<tr>
<td style="text-align: center"><strong>csv+zip</strong></td>
<td style="text-align: center">177.00</td>
<td style="text-align: center">20.70</td>
<td style="text-align: center">511</td>
</tr>
<tr>
<td style="text-align: center"><strong>pickle</strong></td>
<td style="text-align: center">2.89</td>
<td style="text-align: center">2.61</td>
<td style="text-align: center">858</td>
</tr>
<tr>
<td style="text-align: center"><strong>npy</strong></td>
<td style="text-align: center">21.00</td>
<td style="text-align: center">14.80</td>
<td style="text-align: center">620</td>
</tr>
<tr>
<td style="text-align: center"><strong>hdf</strong></td>
<td style="text-align: center">3.96</td>
<td style="text-align: center">4.13</td>
<td style="text-align: center">1,500</td>
</tr>
<tr>
<td style="text-align: center"><strong>parquet</strong></td>
<td style="text-align: center">3.47</td>
<td style="text-align: center">1.85</td>
<td style="text-align: center">426</td>
</tr>
<tr>
<td style="text-align: center"><strong>feature</strong></td>
<td style="text-align: center">1.90</td>
<td style="text-align: center">1.52</td>
<td style="text-align: center">715</td>
</tr>
</tbody>
</table>
<p>时间对比</p>
<p><img src="https://i.loli.net/2021/08/17/xX9tUnZSFu8cmhw.jpg" alt="读写时间对比.jpg" loading="lazy"></p>
<p>空间对比</p>
<img src="https://i.loli.net/2021/08/17/vE8S9fizkIAxNnp.jpg" alt="存储空间对比.jpg" style="zoom: 50%">
<p>可以看出<code>parquet</code>会是一个保存文件的最好选择,虽然时间上比<code>feature</code>略慢一点,但空间上有着更大的优势。</p><br><br>
来源:https://www.cnblogs.com/harrylyx/p/15141986.html
頁: [1]
查看完整版本: 别再用CSV了,更高效的Python文件存储方案