別再用CSV了,更高效的Python文件存儲方案

CSV無可厚非的是一種良好的通用文件存儲方式,幾乎任何一款工具或者程式語言都能對其進行讀寫,但是當文件特別大的時候,CSV這種存儲方式就會變得十分緩慢且低效。本文將介紹幾種在Python中能夠代替CSV這種格式的其他文件格式,並對比每種文件存儲的時間與大小。

先說結論,parquet是最好的文件存儲格式,具體對比見下文。

生成隨機數據

導入依賴

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

生成隨機數據

這裡使用pandas的dataframe來存儲數據

# 變數定義
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())

生成100w行數據,其中整型,浮點型和字元串各5列,數據大小在記憶體里大概為1GB+

<class 'pandas.core.frame.DataFrame'>
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_0  float64
 6   float_1  float64
 7   float_2  float64
 8   float_3  float64
 9   float_4  float64
 10  str_0    object 
 11  str_1    object 
 12  str_2    object 
 13  str_3    object 
 14  str_4    object 
dtypes: float64(5), int64(5), object(5)
memory usage: 1.1+ GB

保存文件

csv

CSV的保存方式很簡單,直接使用pandas自帶的to_csv() 方法即可

# 寫入
df.to_csv('./df_csv.csv', index=False)
# 讀取
df = pd.read_csv('./df_csv.csv')

寫入時間花費:78 s

讀取時間花費:11.8 s

所需存儲空間:1.3GB

pkl

pkl文件需要用到built-inpickle

# 寫入
with open('./df_pkl.pkl', 'wb') as f:
    pickle.dump(df, f)
# 讀取
with open('./df_pkl.pkl', 'rb') as f:
    df = pickle.load(f)

寫入時間花費:2.89 s

讀取時間花費:2.61 s

所需存儲空間:858M

npy

npy是numpy自帶的一種保存格式,唯一的缺點是只能保存numpy的格式,所以需要將pandas先轉成numpy才行,為了公平,這裡我們會算上轉換的時間

# 寫入
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)

寫入時間花費:21 s

讀取時間花費:14.8 s

所需存儲空間:620M

hdf

層次數據格式(HDF)是自描述的,允許應用程式在沒有外部資訊的情況下解釋文件的結構和內容。一個HDF文件可以包含一系列相關對象,這些對象可以作為一個組或單個對象進行訪問。

這裡將使用pandas自帶的to_hdf()方法,該方法默認是用的HDF5格式

# 寫入
df.to_hdf('df_hdf.h5', key='df')
# 讀取
df = pd.read_hdf('df_hdf.h5', key='df')

寫入時間花費:3.96 s

讀取時間花費:4.13 s

所需存儲空間:1.5G

已廢棄 msgpack

pandas支援msgpack格式的對象序列化。他是一種輕量級可移植的二進位格式,同二進位的JSON類似,具有高效的空間利用率以及不錯的寫入(序列化)和讀取(反序列化)性能。

從0.25版本開始,不推薦使用msgpack格式,並且之後的版本也將刪除它。推薦使用pyarrow對pandas對象進行在線的轉換。

read_msgpack() (opens new window)僅在pandas的0.20.3版本及以下版本兼容。

parquet

Apache Parquet為數據幀提供了分區的二進位柱狀序列化。它的設計目的是使數據幀的讀寫效率,並使數據共享跨數據分析語言容易。Parquet可以使用多種壓縮技術來儘可能地縮小文件大小,同時仍然保持良好的讀取性能。

這裡需要使用到pyarrow裡面的方法來進行操作

# 寫入
pq.write_table(pa.Table.from_pandas(df), 'df_parquet.parquet')
# 讀取
df = pq.read_table('df_parquet.parquet').to_pandas()

寫入時間花費:3.47 s

讀取時間花費:1.85 s

所需存儲空間:426M

feature

Feather是一種可移植的文件格式,用於存儲內部使用Arrow IPC格式的Arrow表或數據幀(來自Python或R等語言)。Feather是在Arrow項目早期創建的,作為Python和R的快速、語言無關的數據幀存儲概念的證明。

這裡需要使用到pyarrow裡面的方法來進行操作

# 寫入
feather.write_feather(df, 'df_feather.feather')
# 讀取

寫入時間花費:1.9 s

讀取時間花費:1.52 s

所需存儲空間:715M

總結

對比表格

文件類型 讀取時間(s) 寫入時間(s) 存儲空間(MB)
csv 78.00 11.80 1,300
pickle 2.89 2.61 858
npy 21.00 14.80 620
hdf 3.96 4.13 1,500
parquet 3.47 1.85 426
feature 1.90 1.52 715

時間對比

讀寫時間對比.png

空間對比

存儲空間對比.png

可以看出parquet會是一個保存文件的最好選擇,雖然時間上比feature略慢一點,但空間上有著更大的優勢。