在數據分析相關項目工作時,我通常使用Jupyter筆記本和pandas庫來處理和傳遞我的數據。對于中等大小的數據集來說,這是一個非常直接的過程,你甚至可以將其存儲為純文本文件而沒有太多的開銷。
然而,當你的數據集中的觀測數據數量較多時,保存和加載數據回內存的過程就會變慢,現在程序的重新啟動都會迫使你等待數據重新加載。所以最終,CSV文件或任何其他純文本格式都會失去吸引力。
我們可以做得更好。有很多二進制格式可以用來將數據存儲到磁盤上,其中有很多格式pandas都支持。我們怎么能知道哪一種更適合我們的目的呢?
來吧,我們嘗試其中的幾個,然后進行對比!這就是我決定在這篇文章中要做的:通過幾種方法將 pandas.DataFrame 保存到磁盤上,看看哪一種在I/O速度、內存消耗和磁盤空間方面做的更好。
在這篇文章中,我將展示我的測試結果。
1.要比較的格式
我們將考慮采用以下格式來存儲我們的數據:
- CSV -- 數據科學家的一個好朋友
- Pickle -- 一種Python的方式來序列化事物
- MessagePack -- 它就像JSON,但又快又小
- HDF5 -- 一種設計用于存儲和組織大量數據的文件格式
- Feather -- 一種快速、輕量級、易于使用的二進制文件格式,用于存儲數據框架
- Parquet -- Apache Hadoop的柱狀存儲格式
所有這些格式都是被廣泛使用的,而且(也許除了MessagePack)在你做一些數據分析的事情時非常經常遇到。
為了追求找到最好的緩沖格式來存儲程序會話之間的數據,我選擇了以下指標進行比較。
- size_mb - 文件大小(Mb)。
- save_time - 將數據幀保存到磁盤上所需的時間量。
- load_time - 將之前轉儲的數據幀加載到內存中所需要的時間量。
- save_ram_delta_mb - 數據幀保存過程中最大的內存消耗增長量。
- load_ram_delta_mb - 數據幀加載過程中的最大內存消耗增長量。
請注意,當我們使用高效壓縮的二進制數據格式,如 Parquet 時,最后兩個指標變得非常重要。它們可以幫助我們估計加載序列化數據所需的內存量,此外還有數據大小本身。我們將在接下來的章節中更詳細地討論這個問題。
2.測試及結果
我決定使用一個合成數據集進行測試,以便更好地控制序列化的數據結構和屬性。
另外,我在我的基準中使用了兩種不同的方法:
(a) 將生成的分類變量保留為字符串。
(b) 在執行任何I/O之前將它們轉換為 pandas.Categorical 數據類型。
函數generate_dataset顯示了我在基準中是如何生成數據集的:
def generate_dataset(n_rows, num_count, cat_count, max_nan=0.1, max_cat_size=100):
"""
隨機生成具有數字和分類特征的數據集。
數字特征取自正態分布X ~ N(0, 1)。
分類特征則被生成為隨機的uuid4字符串。
此外,數字和分類特征的max_nan比例被替換為NaN值。
"""
dataset, types = {}, {}
def generate_categories():
from uuid import uuid4
category_size = np.random.randint(2, max_cat_size)
return [str(uuid4()) for _ in range(category_size)]
for col in range(num_count):
name = f'n{col}'
values = np.random.normal(0, 1, n_rows)
nan_cnt = np.random.randint(1, int(max_nan*n_rows))
index = np.random.choice(n_rows, nan_cnt, replace=False)
values[index] = np.nan
dataset[name] = values
types[name] = 'float32'
for col in range(cat_count):
name = f'c{col}'
cats = generate_categories()
values = np.array(np.random.choice(cats, n_rows, replace=True), dtype=object)
nan_cnt = np.random.randint(1, int(max_nan*n_rows))
index = np.random.choice(n_rows, nan_cnt, replace=False)
values[index] = np.nan
dataset[name] = values
types[name] = 'object'
return pd.DataFrame(dataset), types
我們將CSV文件的保存和加載性能作為一個基準。
五個隨機生成的具有一百萬個觀測值的數據集被轉儲到CSV中,并讀回內存以獲得平均指標。
每種二進制格式都針對20個隨機生成的具有相同行數的數據集進行測試。
這些數據集包括15個數字特征和15個分類特征。你可以在這個資源庫中找到帶有基準測試功能和所需的完整源代碼:
https://github.com/devforfu/pandas-formats-benchmark
或在Python實用寶典后臺回復 **Pandas IO對比 **,下載完整代碼。
(a) 數據為字符串特征時的性能
下圖顯示了每種數據格式的平均I/O時間。一個有趣的觀察是,hdf顯示出比csv更慢的加載速度,而其他二進制格式的表現明顯更好。其中最令人印象深刻的是feather和parquet。
在保存數據和從磁盤上讀取數據時,內存開銷如何?
下一張圖片告訴我們,hdf 的表現就不是那么好了。可以肯定的是,csv在保存/加載純文本字符串時不需要太多的額外內存,而Feather和parquet則相當接近:
最后,讓我們看看文件的大小。這次parquet顯示了一個令人印象深刻的結果,考慮到這種格式是為有效存儲大量數據而開發的,這并不令人驚訝。
(b) 字符串特征轉換為數字時的性能
在上一節中,我們沒有嘗試有效地存儲我們的分類特征而是使用普通的字符串。讓我們來彌補這個遺漏吧! 這一次我們使用一個專門的 pandas.Categorical 類型,轉字符串特征為數字特征。
看看現在與純文本的csv相比,它看起來如何!
現在所有的二進制格式都顯示出它們的真正力量。Csv的基準結果已經遠遠落后了,所以讓我們把它去掉,以便更清楚地看到各種二進制格式之間的差異:
Feather 和 Pickle 顯示了最好的 I/O 速度,而 hdf 仍然顯示了明顯的性能開銷。
現在是時候比較數據進程加載時的內存消耗了。下面的柱狀圖顯示了我們之前提到的關于parquet格式的一個重要事實。
可以看到 parquet 讀寫時的內存空間差距有多大,你有可能你無法將比較大的 parquet 文件加載到內存中。
最后的圖顯示了各格式的文件大小。所有的格式都顯示出良好的效果,除了hdf仍然需要比其他格式多得多的空間:
3.結論
正如我們的測試所顯示的,似乎 feather 格式是存儲Python會話數據的理想候選者。它顯示了很快的I/O速度,在磁盤上不占用太多內存,并且在加載回RAM時不需要消耗太大的內存。
當然,這種比較并不意味著你應該在每個可能的情況下使用這種格式。例如,feather格式一般不會被用作長期文件存儲的格式。
另外,某些特定情況下也無法使用 feather,這由你的整個程序架構決定。然而,就如本帖開頭所述的目的,它在不被任何特殊事項限制的情況下是一個很好的選擇。
-
數據
+關注
關注
8文章
6898瀏覽量
88836 -
存儲
+關注
關注
13文章
4265瀏覽量
85675 -
磁盤
+關注
關注
1文章
367瀏覽量
25177
發布評論請先 登錄
相關推薦
評論