如何利用pandas處理大資料
翻譯自這篇文章
當我們需要處理大資料時,如果不對資料做任何處理,可能會帶來記憶體佔用過大和執行過慢的風險。
當然對於處理大資料集,類似spark之類的專業處理工具是大家的首選,但是pandas優秀的特性和簡單明瞭的語法能極大提升資料分析的效率,因此我需要考慮如何對資料優化,使得我們能在pandas上完成更大資料量的資料分析工作。
在用pandas進行資料分析時,減少記憶體佔用簡單來說就是選擇合適的資料型別。
我們用棒球比賽日誌資料來作為我們的例子。
import pandas as pd
gl = pd.read_csv("game_logs.csv")#讀資料
gl.info(memory_usage = 'deep' )#資料集的一些資訊和記憶體佔用
我們先了解下pandas內部儲存Dataframe的機制。
pandas將列聚合成一個個的blocks,block內都是相同型別的資料。blocks不會儲存列名,只會儲存dataframe列中的實際的值。內部的BlockManager class會將row和column的indexes與它們的值一一對映起來。當我們選擇,編輯,刪除值的時候,dataframe類會和BlockManager類互動,將我們的請求轉換成相應的函式和方法呼叫。
pandas中,有一個叫pandas.core.internals模組,這裡有許多的class,每個資料型別都對應了一個特殊的class。例如Pandas用ObjectBlock類來表示包含了儲存string型別資料的列的block。對於包含儲存數值資料比如說整型和浮點型資料的block,pandas將他們合併在一起,並用Numpy ndarray來儲存。
由於每個資料型別在pandas中的都是分別儲存的。我們可以看下每種資料型別平均需要用多少的記憶體空間。
for dtype in ['float', 'int', 'object]:
selected_dtype = gl.select_dtypes(include = [dtypes])
mean_usage_b = selected.dtype.memory_usage(deep = True)
mean_usage_mb = mean_usage_b / 1024 ** 2
print("Average memory usage for {} columns: {:03.2f} MB" .format(dtype, mean_usage_mb))
正如我們之前提到的,pandas用Numpy ndarrays來表示數值型資料,並且將他們存放在連續的記憶體單元中。這樣的儲存模型能消耗更小的空間資源,並且能使我們的訪問更加迅速。
許多的pandas中的類有許多的子類。例如float型別有flloat16,float32和float64這些子類。使用的記憶體依次上升。
我們可以用numy.iinfo類來觀察每個子類的最大值和最小值。例如
import numpy as np
int_types = ['uint8', 'int8', 'int16']
for it in int_types:
print(np.iinfo(it))
這裡uint代表了無符號整型,int代表了帶符號整型。兩種子類的儲存空間都是相同的。
我們可以再看下資料型別轉換後的記憶體變化情況。
def mem_usage(pandas_obj):
if isinstance(pandas_obj, pd.DataFrame):
usage_b = pandas_obj.memory_usage(deep = True).sum()
else:
usage_b = pandas_obj.memory_usage(deep = True)
usage_mb = usage_b / 1024 ** 2
return "{:03.2f} MB".format(usage_mb)
gl_int = gl.select_dtypes(include = ['int'])
converted_int = gl_int.apply(pd.to_numeric, downcast = 'unsigned')
print(mem_usage(go_int))
print(mem_usage(converted_int))
compare_ints = pd.concat([gl_int.dtypes, converted_int.dtypes], axis = 1)
compare_ints.columnns = ['before', 'after']
compare_ints.apply(pd.Series.value_counts)
我們對float型別的資料也做同樣的處理。最後看下整個資料集記憶體減少了多少。
optimized_gl = gl.copy()
optimized_gl[converted_int.columns] = converted_int
optimized_gl[converted_float.columns] = converted_float
print(mem_usage(gl))
print(mem_usage(optimized_gl))
我們發現,僅僅對數值型資料做型別變換不能大幅度的減少整個資料集的記憶體佔用情況。因此我們需要對最耗記憶體的,也就是object型別的資料進行優化。
pandas中的object型別的資料表示資料是Python string型別的,部分原因是因為在Numpy中缺少對缺失string值的支援機制,因為Python語言的原因,它沒有一個好的對值的記憶體管理的機制。
這樣的限制導致了strings被儲存在非連續的記憶體單元當中,每一個元素實際上是一個包含了值的實際記憶體地址的指標。
from sys import getsizeof
s1 = "working out"
s2 = "memory usage for"
s3 = "strings in python is fun!"
s4 = "strings in python is fun!"
for s in [s1, s2, s3, s4]:
print(getsizeof(s))
我們用categgory型別來對object型別的資料做優化。
dow = gl_ob.day_of_week
print(dow.head())
dow_cat = dow.astype("category")
print(dow_cat.head())
如果執行,我們將會看到這樣的操作的確節省了非常多的記憶體。