【導讀】本文為大家帶來了算法工程師必備技能——Pandas的進階教程,主要分享了作者應用Pandas解決實際任務的過程中,所積累到的寶貴經驗。
這篇博文主要關於Pandas工具包的一些最佳實踐,其目標讀者是那些正在使用Pandas的人,即使你從未使用過Pandas,現在開始動手也不遲,不是嗎?
本文主要內容:
原文連結:
https://medium.com/unit8-machine-learning-publication/from-pandas-wan-to-pandas-master-4860cf0ce442
閱讀這篇文章時,建議讀者先瀏覽下Pandas文檔,以熟悉每個函數的大致內容。
Pandas是一個開源且受BSD許可約束的工具庫,為Python開發者提供高性能、易用的數據結構和數據分析工具。其中Pandas提供一種被稱為 DataFrame 的數據抽象結構。簡單的來說,它通過管理數據索引以達到快速訪問數據、執行分析、轉換操作、繪製圖形的目的。
撰寫這篇文章時,Pandas最新版本是v.0.24.2。
沒錯,Pandas正在努力升級到1.0版本,在此期間,每個版本升級可能都會帶來使用方式的不同。
讓我們開始吧!首先選擇的數據集是「從1985到 2016之間每個國家的自殺率」(來自Kaggle),這是一個小型數據集。簡單但足以讓你上手Pandas。
在寫代碼之前,如果你想重現實驗結果,這裡有小型的數據樣本,以防止弄錯列名和數據類型。
提示:如果你讀取的是一個大文件,在read_csv()中使用參數 chunksize=N,它會返回一個迭代器輸出數據對象。
下面是數據集的部分數據:
數據包括了從1985年到2016年間,101個國家、兩種性別、六個年代和六個不同的年齡的數據信息。我們可以使用一些簡單的方法來得到這些信息。
例如,使用unique() 和 nunique() 函數在表內獲取不重複的值(或者不重複值的數量)
使用describe()對每一個數字行輸出不同的統計數字(例如min,max,mean,count),並且,如果指定include='all'-會顯示每個列對象的不重複元素數和頂部元素(即出現最頻繁的元素數)
使用 head() 和tail() 顯示小部分數據集等。
在處理數據之前,很重要的是理解數據,並為數據集的列對象選擇正確的類型。
在Pandas內部,每種不同類型的數據,將存儲為不同的numpy數組(例如一個float64矩陣、一個int32矩陣)。
這裡有兩種方法可以大大降低您的內存消耗。
Pandas自帶了一個方法memory_usage(),用於分析DataFrame的內存消耗。在上面的代碼中,指定deep=true以確保考慮實際系統使用情況。
理解行數據類型是重要的。它可以通過以下兩個簡單的方法為您節省高達90%的內存使用量:1. 了解你的DataFrame使用的是什麼數據類型;2. 了解DataFrame中可以使用哪種數據類型,來減少內存使用(例如,如果在價格列中使用float64,則它可能會產生不必要的開銷)
除了降低自己使用的數字類型的大小之外,(int32代替int64),pandas還帶有一個categorical 類型。
如果你是一個R語言開發者,你可以把它看成是一個factor類型。
categorical 類型允許用索引替換重複的值,並將實際值存儲在其他地方。例如,針對國家類型,與其將同一個字符串「瑞士」或「波蘭」存儲多次,不如簡單地用0和1替換它們,然後存儲於一個字典中。
categorical_dict = {0: 'Switzerland', 1: 'Poland'}回到我們的convert_df()方法上,當列類型不統一時,這個方法會自動的將列類型轉換成大多數的樣子。
通過我們「聰明」的轉換,DataFrame幾乎減少了10倍(準確是7.34倍)內存。
Pandas很強大,但需要一定的開銷。加載DataFrame時,它會創建索引並將數據存儲在numpy數組中。意思是說,一旦內存加載了DataFrame,如果用戶可以正確地管理索引,就可以非常快地訪問數據。
訪問數據的方法主要有兩種,即索引和查詢。根據情況,你可以選擇其中一個。然而,在大多數情況下,索引(和多索引)是最佳選擇。讓我們以下面的例子為例
整個過程加速了20倍!
那麼,創建這個多索引需要多少時間(如上圖)?可以看出,創建索引的時間開銷,是執行查詢的1.5倍。如果只想檢索一次數據(很少是這樣),那麼查詢是正確的方法。否則,一定要採用索引的方式,你的CPU會感謝你的。
.set_index(drop=False) //這種設置,將不允許刪除索引列。.loc[] / .iloc[] 方法在想要只讀DataFrame時性能非常好。如果需要手工構造(例如使用循環),那麼可以考慮另一個數據結構(例如字典、列表),然後在準備好所有數據後,一次性創建DataFrame。否則,對於DataFrame中的每一個新行,pandas都會更新索引,這不是一個簡單的hashmap,會非常耗時。
通過將方法與 DataFrame 連結組合,可以形成 DataFrame 的處理鏈條。在當前版本的Pandas中,使用方法鏈的原因,是可以避免存儲中間變量,並防止以下情況:
方法連結器的工具箱,由不同的方法(例如apply、assign、loc、query、pipe、groupby、agg)組成,這些方法輸出DataFrame或序列對象(或DataFrameGroupby)。
為了找出「Generation X」所關聯的三個年齡組,這個工作我們可以通過方法鏈條來實現。第一步是對年齡組進行分組。此方法返回一個DataframeGroupBy對象,其中每個組通過唯一標籤進行聚合,在聚合過程中,方法可以接收一個lambda函數。
在最新版本(v0.25)中,Pandas引入了一種使用agg的新方法。
圖:使用sort_values和head函數展示每個國家和年份最多自殺數
圖:使用nlargest函數展示每個國家和年份最多自殺數
在上述示例中,輸出是相同的:一個包括二級多索引(國家和年份)的DataFrame,以及一個包含10個排序最大值的新列suicides_su。
nlargest(10) 比sort_values(ascending=False).head(10)還要有效。
另一個有趣的方法是unstack,它允許轉變索引級別。
「age」是索引,「succides_no」和「population」有一個二級列「sex」
下一種方法,pipe,是非常通用的方法之一。允許程序進行管道操作(如shell腳本中的操作),並使用鏈式方法完成更多操作。
管道的一個強大的用法是用來記錄不同的信息
例如,如果與列year相比,我們希望驗證列country_year是否正確。
圖:在「country_year」欄中驗證」year」的管道
這條管道的輸出是一個DataFrame,同時也將在標準輸出(console/repl)中列印出來,同理,你也可以在一個鏈中放入不同的管道操作
除了輸出到控制臺之外,pipe還可以直接在DataFrame上進行函數操作
通過深入研究代碼,我們可以看出,norm_df() 將對輸入的DataFrame和列數組使用MinMaxScaling方法進行縮放。
以下tips非常有用,但並不適用於前面的任何部分。
1、itertuples()在DataFrame的行中迭代效率更高。
2、使用merge()來替代join()。
3、在Jupyter notebook中,以「%%time」開頭的單元格,可以更加高效的完成時間操作。
4、uint8 數據類型可以支持整數的NaN值。
5、請記住,任何密集的I/O操作,(例如一個大csv文件轉儲)都可以在較低級別的方法(儘可能多地使用python核心函數)中表現得更好。
除了本文提到的方法之外,還有一些有用的方法/數據結構沒有被本文所涵蓋,同樣值得大家花時間去理解。
感謝閱讀這篇短文,也希望大家能夠更好地了解Pandns在幕後的工作方式,以及pandas工具庫目前的發展狀況。希望通過本文的介紹,令讀者對索引和查詢內容有了一個清晰的認識。最後,建議大家針對Pandas的各類函數,多多實踐,不斷提升對工具的熟練程度。
實踐會帶來改變,所以請繼續努力提高自己的技能,幫助我們建立一個更好的世界。
PS:有時純numpy實現更快(Julien Herzen)