Pandas初學者代碼優化指南

原文:A Beginner’s Guide to Optimizing Pandas Code for Speed 
做者:Sofia Heisler 
翻譯:無阻我飛揚
html

摘要:Pandas 是Python Data Analysis Library的簡寫,它是爲了解決數據分析任務而建立的工具,本文介紹了五種由慢到快逐步優化其效率的方法 ,如下是譯文python

若是你用Python語言作過任何的數據分析,那麼可能會用到Pandas,一個由Wes McKinney寫的奇妙的分析庫。經過賦予Python數據幀以分析功能,Pandas已經有效地把Python和一些諸如R或者SAS這樣比較成熟的分析工具置於相同的地位。git

不幸的是,在早期,Pandas因「慢」而聲名狼藉。的確,Pandas代碼不可能達到如徹底優化的原始C語言代碼的計算速度。然而,好消息是,對於大多數應用程序來講,寫的好的Pandas代碼已足夠快;Pandas強大的功能和友好的用戶體驗彌補了其速度的缺點。github

在這篇文章中,咱們將回顧應用於Pandas DataFrame函數的幾種方法的效率,從最慢到最快:編程

1. 在用索引的DataFrame行上的Crude looping 
2. 用iterrows()循環 
3. 用 apply()循環 
4. Pandas Series矢量化 
5. NumPy數組矢量化數組

對於咱們的實例函數,將使用Haversine(半正矢)距離公式。函數取兩點的經緯度,調整球面的曲率,計算它們之間的直線距離。這個函數看起來像這樣:數據結構

這裏寫圖片描述

爲了在真實數據上測試函數,咱們用一個包含紐約全部酒店座標的數據集,該數據來自Expedia開發者網站。要計算每個酒店和一個樣本集座標之間的距離(這剛好屬於在紐約市名爲布魯克林超級英雄供應店的一個夢幻般的小商店)app

你們能夠下載數據集,Jupyter notebook(是一個交互式筆記本,支持運行 40 多種編程語言)包含了用於這篇博客的函數,請點擊這裏下載。編程語言

這篇文章基於個人PyCon訪談,你們能夠在這裏觀看。ide

Pandas中的Crude looping,或者你永遠不該該這麼作

首先,讓咱們快速回顧一下Pandas數據結構的基本原理。Pandas的基本結構有兩種形式:DataFrame和Series。一個DataFrame是一個二維數組標記軸,不少功能與R中的data.frame相似,能夠將DataFrame理解爲Series的容器。換句話說,一個DataFrame是一個有行和列的矩陣,列有列名標籤,行有索引標籤。在Pandas DataFrame中一個單獨的列或者行是一個Pandas Series—一個帶有軸標籤的一維數組。

幾乎每個與我合做過的Pandas初學者,都曾經試圖經過一次一個的遍歷DataFrame行去應用自定義函數。這種方法的優勢是,它是Python對象之間交互的一致方式;例如,一種能夠經過列表或數組循環的方式。反過來講,不利的一面是,在Pandas中,Crude loop是最慢的方法。與下面將要討論的方法不一樣,Pandas中的Crude loop沒有利用任何內置優化,經過比較,其效率極低(並且代碼一般不那麼具備可讀性)

例如,有人可能會寫像下面這樣的代碼:

這裏寫圖片描述

爲了瞭解執行上述函數所須要的時間,咱們用%timeit命令。%timeit是一個「神奇的」命令,專用於Jupyter notebook(全部的魔法命令都以%標識開始,若是%命令只應用於一行,那麼%%命令應用於整個Jupyter單元)。%timeit命令將屢次運行一個函數,並打印出得到的運行時間的平均值和標準差。固然,經過%timeit命令得到的運行時間,運行該函數的每一個系統都不盡相同。儘管如此,它能夠提供一個有用的基準測試工具,用於比較同一系統和數據集上不一樣函數的運行時間。

這裏寫圖片描述

經過分析,crude looping函數運行了大約645ms,標準差是31ms。這彷佛很快,但考慮到它僅須要處理大約1600行的代碼,所以它其實是很慢的。接下來看看如何改善這種很差的情況。

用iterrows()循環

若是循環是必須的,找一個更好的方式去遍歷行,好比用iterrows()方法。iterrows()是一個生成器,遍歷DataFrame的全部行並返回每一行的索引,除了包含行自身的對象。iterrows() 是用Pandas DataFrame優化,儘管它是運行大多數標準函數最不高效的方式(稍後再談),但相對於Crude looping,這是一個重大的改進。在咱們的案例中,iterrows()解決同一個問題,幾乎比手動遍歷行快四倍。

這裏寫圖片描述

使用apply()方法實現更好的循環

一個比iterrows()更好的選擇是用 apply() 方法,它應用一個函數,沿着DataFrame某一個特定的軸線(意思就是行或列)。雖然apply()也固有的經過行循環,但它經過採起一些內部優化比iterrows()更高效,例如在Cython中使用迭代器。咱們使用一個匿名的lambda函數,每一行都用Haversine函數,它容許指向每一行中的特定單元格做爲函數的輸入。爲了指定Pandas是否應該將函數應用於行(axis = 1)或列(axis = 0),Lambda函數包含最終的axis參數。

這裏寫圖片描述

iterrows()方法用apply()方法替代後,大體能夠將函數的運行時間減半。爲了更深刻地瞭解函數中的實際運行時間,能夠運行一個在線分析器工具(Jupyter中神奇的命令%lprun

這裏寫圖片描述

結果以下:

這裏寫圖片描述

咱們能夠從這個信息中獲得一些有用的看法。例如,進行三角計算的函數佔了總運行時間的近一半。所以,若是想優化函數的各個組件,能夠從這裏入手。如今,特別值得注意的是每一行都被循環了1631次—apply()遍歷每一行的結果。若是能夠減小重複的工做量,就能夠下降整個運行時間。矢量化提供了一種更有效的替代方案。

Pandas Series矢量化

要了解如何能夠減小函數所執行的迭代數量,就要記得Pandas的基本單位,DataFrame和Series,它們都基於數組。基本單元的固有結構轉換成內置的設計用於對整個數組進行操做的Pandas函數,而不是按各個值的順序(簡稱標量)。矢量化是對整個數組執行操做的過程。

Pandas包含一個整體的矢量化函數集合,從數學運算到聚合和字符串函數(可用函數的擴展列表,查看Pandas docs)。對Pandas Series和DataFrame的操做進行內置優化。結果,使用矢量Pandas函數幾乎老是會用自定義的循環實現相似的功能。

到目前爲止,咱們僅傳遞標量給Haversine函數。全部的函數都應用在Haversine函數中,也能夠在數組上操做。這使得距離矢量化函數的過程很是的簡單:不是傳遞個別標量值的緯度和經度給它,而是把它傳遞給整個series(列)。這使得Pandas受益於可用於矢量函數的全套優化,特別是包括同時執行整個數組的全部計算。

這裏寫圖片描述

經過使用apply()方法,要比用iterrows()方法改進50倍的效率,經過矢量化函數則改進了iterrows()方法100倍—除了改變輸入類型,什麼都不要作!

看一眼後臺,看看函數到底在作什麼:

這裏寫圖片描述

注意,鑑於 apply() 執行函數1631次,矢量化版本僅執行一次,由於它同時應用於整個數組,這就是主要的時間節省來源。

用NumPy數組矢量化

Pandas series矢量化能夠完成平常計算優化的絕大多數須要。然而,若是速度是最高優先級,那麼能夠以NumPy Python庫的形式調用援軍。

NumPy庫,將本身描述爲一個「Python科學計算的基本包」,在後臺執行優化操做,預編譯C語言代碼。跟Pandas同樣,NumPy操做數組對象(簡稱ndarrays);然而,它省去了Pandas series操做所帶來的大量資源開銷,如索引、數據類型檢查等。所以,NumPy數組的操做能夠明顯快於pandas series的操做。

當Pandas series提供的額外功能不是很關鍵的時候,NumPy數組能夠用於替代Pandas series。例如,Haversine函數矢量化實現不使用索引的經度和緯度系列,所以沒有那些索引,也不會致使函數中斷。經過比較,咱們所作的操做如DataFrame的鏈接,它須要按索引來引用值,可能須要堅持使用Pandas對象。

僅僅是使用Pandas series 的values的方法,把緯度和經度數組從Pandas series轉換到NumPy數組。就像series矢量化同樣,經過NumPy數組直接進入函數將可讓Pandas對整個矢量應用函數。

這裏寫圖片描述

NumPy數組操做運行取得了又一個四倍的改善。總之,經過looping改進了運行時間超過半秒,經過NumPy矢量化,運行時間改進到了三分之一毫秒級!


總結

下面的表格總結了相關結果。用NumPy數組矢量化將會帶來最快的運行時間,相對於Pandas series矢量化的效果而言,這是一個很小的改進,但對比最快的looping版本,NumPy數組矢量化帶來了56倍的改進。

這裏寫圖片描述

這給咱們帶來了一些關於優化Pandas代碼的基本結論:

  1. 避免循環;它們很慢,並且在大多數狀況下是沒必要要的。
  2. 若是必須使用循環,用 apply(),而不是迭代函數。
  3. 矢量化一般優於標量運算。在Pandas中的大部分常見操做均可以矢量化。
  4. NumPy數組矢量化操做比Pandas series更有效。

固然,以上並非Pandas全部可能優化的全面清單。更愛冒險的用戶或許能夠考慮進一步用Cython改寫函數,或者嘗試優化函數的各個組件。然而,這些話題超出了這篇文章的範圍。

關鍵的是,在開始一次宏大的優化冒險以前,要確保正在優化的函數其實是你但願在長期運行中使用的函數。引用XKCD不朽的名言:「過早優化是萬惡之源」。

相關文章
相關標籤/搜索