Python中遍歷pandas資料的幾種方法介紹和效率對比說明
前言
Pandas是python的一個數據分析包,提供了大量的快速便捷處理資料的函式和方法。其中Pandas定義了Series 和 DataFrame兩種資料型別,這使資料操作變得更簡單。Series 是一種一維的資料結構,類似於將列表資料值與索引值相結合。DataFrame 是一種二維的資料結構,接近於電子表格或者mysql資料庫的形式。
圖片描述
在資料分析中不可避免的涉及到對資料的遍歷查詢和處理,比如我們需要將dataframe兩列資料兩兩相除,並將結果儲存於一個新的列表中。本文通過該例程介紹對pandas資料遍歷的幾種方法。
for…in迴圈迭代方式
for語句是Python內建的迭代器工具,用於從可迭代容器物件(如列表、元組、字典、集合、檔案等)中逐個讀取元素,直到容器中沒有更多元素為止,工具和物件之間只要遵循可迭代協議即可進行迭代操作。
具體的迭代的過程:可迭代物件通過__iter__方法返回迭代器,迭代器具有__next__方法,for迴圈不斷地呼叫__next__方法,每次按序返回迭代器中的一個值,直到迭代到最後,沒有更多元素時丟擲異常StopIteration(python自動處理異常)。迭代的優點是無需把所有元素一次載入到記憶體中,可以在呼叫next方法時逐個返回元素,避免出現記憶體空間不夠的情況。
>>> x = [1,2,3] >>> its = x.__iter__() #列表是可迭代物件,否則會提示不是迭代物件 >>> its <list_iterator object at 0x100f32198> >>> next(its) # its包含此方法,說明its是迭代器 1 >>> next(its) 2 >>>next(its) 3 >>> next(its) Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
實現程式碼如下:
def haversine_looping(df): disftance_list = [] for i in range(0,len(df)): disftance_list.append(df.iloc[i][‘high’]/df.iloc[i][‘open’]) return disftance_list 關於上述程式碼中range的實現方法,我們也可根據迭代器協議自實現相同功能的迭代器(自帶iter方法和next方法)應用在for迴圈中,程式碼如下: class MyRange: def __init__(self, num): self.i = 0 self.num = num def __iter__(self): return self def __next__(self): if self.i < self.num: i = self.i self.i += 1 return i else: raise StopIteration() for i in MyRange(10): print(i)
我們也可以通過列表解析的方式用更少的程式碼實現資料處理功能
disftance_list = [df.iloc[i][‘high’]/df.iloc[i][‘open’] for i in range(0,len(df))]
iterrows()生成器方式
iterrows是對dataframe行進行迭代的一個生成器,它返回每行的索引及包含行本身的物件。所謂生成器其實是一種特殊的迭代器,內部支援了迭代器協議。Python中提供生成器函式和生成器表示式兩種方式實現生成器,每次請求返回一個結果,不需要一次性構建一個結果列表,節省了記憶體空間。
生成器函式:編寫為常規的def語句,但是使用yield語句一次返回一個結果,在每個結果之間掛起和繼續它們的狀態。
def gensquares(N):
for i in range(N):
yield i**2
print gensquares(5)
for i in gensquares(5):
print(i)
<generator object gensquares at 0xb3d37fa4>
0
1
4
9
16
#生成器表示式:類似列表解析,按需產生結果的一個物件。
print (x**2 for x in range(5))
print list(x**2 for x in range(5))
<generator object <genexpr> at 0xb3d31fa4>
[0, 1, 4, 9, 16]
iterrows()實現程式碼如下:
def haversine_looping(df):
disftance_list = []
for index,row in df.iterrows():
disftance_list.append(row[‘high’]/row[‘open’])
return disftance_list
iterrows程式碼如下,yield語句掛起該函式並向呼叫者傳送回一組值:
def iterrows(self):
columns = self.columns
klass = self._constructor_sliced
for k, v in zip(self.index, self.values):
s = klass(v, index=columns, name=k)
yield k, s
apply()方法迴圈方式
apply()方法可將函式應用於dataframe特定行或列。函式由lambda方式在程式碼中內嵌實現,lambda函式的末尾包含axis引數,用來告知Pandas將函式運用於行(axis = 1)或者列(axis = 0)。
實現程式碼如下:
df.apply(lambda row: row[‘high’]/row[‘open’], axis =1)
Pandas series 的向量化方式
Pandas的DataFrame、series基礎單元資料結構基於連結串列,因此可將函式在整個連結串列上進行向量化操作,而不用按順序執行每個值。Pandas包括了非常豐富的向量化函式庫,我們可把整個series(列)作為引數傳遞,對整個連結串列進行計算。
實現程式碼如下:
dftest4['rate'] = dftest4['high']/dftest4['open']
Numpy arrays的向量化方式
由於函式的向量化實現中只使用了series的數值,因此可使用values 方法將連結串列從Pandas series轉換為NumPy arrays,把NumPy array作為引數傳遞,對整個連結串列進行計算。
實現程式碼如下:
dftest5['rate'] = dftest5['high'].values/dftest5['open'].values
總結
使用timeit方法對以上幾種遍歷方式進行執行時間測試,測試結果如下。可以看出迴圈執行的速度是最慢的,iterrows()針對Pandas的dataframe進行了優化,相比直接迴圈有顯著提升。apply()方法也是在行之間進行迴圈,但由於利用了類似Cython的迭代器的一系列全域性優化,其效率要比iterrows高很多。NumPy arrays的向量化執行速度最快,其次是Pandas series向量化。由於向量化是同時作用於整個序列的,可以節省更多的時間,相比使用標量操作更好,NumPy使用預編譯的C程式碼在底層進行優化,同時也避免了Pandas series操作過程中的很多開銷,例如索引、資料型別等等,因此,NumPy arrays的操作要比Pandas series快得多。
loop: 1.80301690102
iterrows: 0.724927186966
apply: 0.645957946777
pandas series: 0.333024024963
numpy array: 0.260366916656