數據分析篇 | PyCon 大咖親傳 pandas 25 式,長文建議收藏
- 2019 年 11 月 27 日
- 筆記
Kevin Markham,數據科學講師,2002 年,畢業於范德堡大學,電腦工程學士,2014 年,創建了 Data School,在線教授 Python 數據科學課程,他的課程主要包括 Pandas、Scikit-learn、Kaggle 競賽數據科學、機器學習、自然語言處理等內容,迄今為止,瀏覽量在YouTube上已經超過 500 萬次。
Kevin 還是 PyCon 培訓講師,主要培訓課程如下:
- PyCon 2016,用 Scikit-learn 機器學習技術處理文本
- PyCon 2018,如何用 Pandas 更好(或更糟)地實現數據科學
- PyCon 2019,Pandas 數據科學最佳實踐
本文基於 Kevin 於 2019 年 7 月推出的最新影片教程,匯總了他 5 年來最喜歡的 25 個 pandas 操作技巧,希望大家喜歡。
目錄
- 查看 pandas 及其支援項的版本
- 創建 DataFrame
- 重命名列
- 反轉行序
- 反轉列序
- 按數據類型選擇列
- 把字元串轉換為數值
- 優化 DataFrame 大小
- 用多個文件建立 DataFrame ~ 按行
- 用多個文件建立 DataFrame ~ 按列
- 從剪貼板創建 DataFrame
- 把 DataFrame 分割為兩個隨機子集
- 根據多個類別篩選 DataFrame
- 根據最大的類別篩選 DataFrame
- 操控缺失值
- 把字元串分割為多列
- 把 Series 里的列錶轉換為 DataFrame
- 用多個函數聚合
- 用一個 DataFrame 合併聚合的輸出結果
- 選擇行與列
- 重塑多重索引 Series
- 創建透視表
- 把連續型數據轉換為類別型數據
- 改變顯示選項
- 設置 DataFrame 樣式
- 彩蛋:預覽 DataFrame
文末有 Jupyter Notebook 下載,正文先上圖。
0. 使用的數據集
原文的數據集是 bit.ly 短網址的,我這裡在讀取時出問題,不穩定,就幫大家下載下來,統一放到了 data 目錄里。
drinks = pd.read_csv('data/drinks.csv') movies = pd.read_csv('data/imdb_1000.csv') orders = pd.read_csv('data/chipotle.tsv', sep='t') orders['item_price'] = orders.item_price.str.replace('$', '').astype('float') stocks = pd.read_csv('data/stocks.csv', parse_dates=['Date']) titanic = pd.read_csv('data/titanic_train.csv') ufo = pd.read_csv('data/ufo.csv', parse_dates=['Time'])
本文中採用讓數據集主要為常見的酒水飲料、IMDB 電影、泰坦尼克號、飛碟目擊等數據集。
這裡需要注意的是:
1) pd.read_csv('data/chipotle.tsv', sep='t')
里的 chipotle.tsv,是用 tab 作為分隔符的,所以要增加參數 sep=t
;
2) orders.item_price.str.replace('$', '').astype('float')
,item_price 列是帶 $ 的文本,要用 .str.replace('$', '').astype('float')
去掉 $,再把該列數據類型改為 float
;
3)ufo.csv
里的 Time 列,要用 parse_dates=['Time'])
,解析日期。
1. 查看 pandas 及其支援項的版本
使用 pd.__version__
查看 pandas 的版本。
查看所有 pandas 的支援項版本,使用 show_versions
函數。比如,查看 Python、pandas、Numpy、matplotlib 等支援項的版本。
2. 創建 DataFrame
創建 DataFrame 的方式有很多,比如,可以把字典傳遞給 DataFrame 構建器,字典的 Key 是列名,字典的 Value 為列表,是 DataFrame 的列的值。
如果 DataFrame 的數據較多,用字典的方式就不合適了,需要輸入的東西太多。這時,可以用 Numpy 的 random.rand()
函數,設定行數與列數,然後把值傳遞給 DataFrame 構建器。
這樣就可以生成 DataFrame 了,但如果要用非數字形式的列名,需要強制把字元串轉換為列表, 再把這個列表傳給 columns
參數。
這裡要注意的是,字元串里的字元數量必須與 DataFrame 的列數一致。
3. 重命名列
用點(.
)選擇 pandas 里的列寫起來比較容易,但列名里有空格,就沒法這樣操作了。
rename()
方法改列名是最靈活的方式,它的參數是字典,字典的 Key 是原列名,值是新列名,還可以指定軸向(axis)。
這種方式的優點是可以重命名任意數量的列,一列、多列、所有列都可以。
還有一種簡單的方式可以一次性重命名所有列,即,直接為列的屬性賦值。
只想替換列名里的空格,還有更簡單的操作,直接用 str.replace
方法,不必把所有的列名都敲一遍。
以上這三種方式都可以更改列名。
用 add_prefix
與 add_suffix
函數可以為所有列名添加前綴或後綴。
4. 反轉列序
反轉 drinks
表的順序。
這個數據集按國家列出了酒水平均消耗量,如果想反轉列序該怎麼辦?
最直接的方式是把 ::-1
傳遞給 loc
訪問器,與 Python 里反轉列表的切片法一樣。
如果想讓索引從 0 到 1,用 reset_index()
方法,並用 drop
關鍵字去掉原有索引。
這樣,行序就已經反轉過來了,索引也重置為默認索引。
5. 反轉列序
與反轉行序類似,還可以用 loc
從左到右反轉列序。
逗號前面的分號表示選擇所有行,逗號後面的 ::-1
表示反轉列,這樣一來,country 列就跑到最右邊去了。
6. 按數據類型選擇列
首先,查看一下 drinks 的數據類型:
選擇所有數值型的列,用 selec_dtypes()
方法。
同樣的方法,還可以選擇所有字元型的列。
同理,還可以用 datetime
選擇日期型的列。
傳遞列表即可選擇多種類型的列。
還可以使用 exclude
關鍵字排除指定的數據類型。
7. 把字元串轉換為數值
再創建一個新的 DataFrame 示例。
這個 DataFrame 里的數字其實是以字元串形式保存的,因此,列類型是 object
。
要想執行數學計算,要先把這些列的數據類型轉換為數值型,下面的程式碼用 astype()
方法把前兩列的數據類型轉化為 float
。
用這種方式轉換第三列會出錯,因為這列里包含一個代表 0 的下劃線,pandas 無法自動判斷這個下劃線。
為了解決這個問題,可以使用 to_numeric()
函數來處理第三列,讓 pandas 把任意無效輸入轉為 NaN
。
NaN
代表的是 0,可以用 fillna()
方法填充。
一行程式碼就可以解決這個問題,現在所有列的值都轉成 float
了。
8. 優化 DataFrame 對記憶體的佔用
pandas 的 DataFrame 設計的目標是把數據存到記憶體里,有時要縮減 DataFrame 的大小,減少對記憶體的佔用。
下面顯示了 drinks 佔用的記憶體。
這裡顯示 drinks 使用了 30.5 KB 記憶體。
大型 DataFrame 會影響計算性能,甚至導致 DataFrame 讀入記憶體失敗,下面介紹簡單幾步,即可在讀取 DataFrame 時減少記憶體佔用。
第一步是只讀取切實所需的列,這裡需要指定 usecols
參數。
只選擇兩列以後,DataFrame 對記憶體的佔用減少到 13.7 KB。
第二步是把包含類別型數據的 object 列轉換為 Category 數據類型,通過指定 dtype
參數實現。
把 continent 列改為 category 數據類型後,DataFrame 對記憶體的佔用進一步縮減到 2.4 KB。
注意:類別數量相對於行數較少時,category 數據類型對對記憶體佔用的減少會比較有限。
9. 用多個文件建立 DataFrame ~ 按行
本段介紹怎樣把分散於多個文件的數據集讀取為一個 DataFrame。
比如,有多個 stock 文件,每個 CSV 文件里只存儲一天的數據。
下面是三天的股票數據:
把每個 CSV 文件讀取成 DataFrame,合併後,再刪除導入的原始 DataFrame,但這種方式佔用記憶體太多,而且要寫很多程式碼。
使用 Python 內置的 glob
更方便。
把文件名規則傳遞給 glob()
,這裡包括通配符,即可返回包含所有合規文件名的列表。
本例里,glob
會查找 data 子目錄里所有以 stocks 開頭的 CSV 文件。
glob
返回的是無序文件名,要用 Python 內置的 sorted()
函數排序列表。
調用 read_csv()
函數讀取生成器表達式里的每個文件,把讀取結果傳遞給 concat()
函數,然後合併為一個 DataFrame。
註:原文里用的是 stock_files = sorted(glob('data/stocks*.csv'))
,譯文里沒用 stocks*
,用的是 stocks?
,這是因為 data 目錄里還有一個叫 stocks.csv 的文件,如果用 *,會讀取出 4 個文件,而不是原文中的 3 個文件。
生成的 DataFrame 索引有重複值,見 「0、1、2」。為避免這種情況,要在 concat()
函數里用忽略舊索引、重置新索引的參數,ignore_index = True
。
10. 用多個文件建立 DataFrame ~ 按列
上個技巧按行合併數據集,但是如果多個文件包含不同的列,該怎麼辦?
本例將 drinks 數據集分為了兩個 CSV 文件,每個文件都包含 3 列。
與上例一樣,還是使用 glob()
。
這裡要讓 concat()
函數按列合併,axis='columns
。
現在 drinks 有 6 列啦!
11. 從剪貼板創建 DataFrame
想快速把 Excel 或別的表格軟體里存儲的數據讀取為 DataFrame,用 read_clipboard()
函數。
打開要複製的 Excel 文件,選取內容,複製。
與 read_csv()
函數類似,
read_clipboard()
會自動檢測列名與每列的數據類型。
真不錯!pandas 自動把第一列當設置成索引了。
注意:因為不能復用、重現,不推薦在正式程式碼里使用 read_clipboard()
函數。
12. 把 DataFrame 分割為兩個隨機子集
把 DataFrame 分為兩個隨機子集,一個占 75% 的數據量,另一個是剩下的 25%。
以 Movies 為例,該數據有 979 條記錄。
使用 sample()
方法隨機選擇 75% 的記錄,並將之賦值給 moives_1。
使用 drop()
方法刪掉 movies 里所有 movies_1,並將之賦值給 movies_2。
兩個 DataFrame 的行數之和與 movies 一致。
movies_1 與 movies_2 里的每個索引值都來自於 movies,而且互不重複。
注意:如果索引值有重複、不唯一,這種方式會失效。
13. 根據多個類別篩選 DataFrame
預覽 movies。
查看 genre(電影類型)列。
要是想篩選 Action(動作片)、Drama(劇情片)、Western(西部片),可以用 or
的操作符實現多條件篩選。
不過,用 isin()
方法篩選會更清晰,只要傳遞電影類型的列表就可以了。
如果想反選,可在條件前添加一個波浪符(tilde ~)。
14. 根據最大的類別篩選 DataFrame
篩選電影類別里(genre)數量最多的三類電影。
先用 value_counts()
統計各類電影的數量,把統計結果賦值給 counts
,這個結果是 Series。
使用 Series 的 nlargest
方法,可以輕鬆選出 Series 里最大的三個值。
這裡所需的只是這個 Series 的 index。
把這個 index 傳遞給 isin()
。
最終,這個 DataFrame 里就只剩下了劇情片、喜劇片與動作片。
15. 處理缺失值
本例使用目擊 UFO 數據集。
可以看到,這個數據集里有缺失值。
要查看每列有多少缺失值,可以使用 isna()
方法,然後使用 sum()
函數。
isna()
生成一個由 True
與 False
構成的 DataFrame,sum()
把 True
轉換為 1, 把 False
轉換為 0。
還可以用 mean()
函數,計算缺失值佔比。
用 dropna()
刪除列里的所有缺失值。
只想刪除列中缺失值高於 10% 的缺失值,可以設置 dropna()
里的閾值,即 threshold
.
16. 把字元串分割為多列
創建一個 DataFrame 示例。
把姓名列分為姓與名兩列,用 str.split()
方法,按空格分割,並用 expand
關鍵字,生成一個新的 DataFrame。
通過賦值語句,把這兩列添加到原 DataFrame。
如果想分割字元串,但只想保留分割結果的一列,該怎麼操作?
要是只想保留城市列,可以選擇只把城市加到 DataFrame 里。
17. 把 Series 里的列錶轉換為 DataFrame
創建一個 DataFrame 示例。
這裡包含了兩列,第二列包含的是 Python 整數列表。
要把第二列轉為 DataFrame,在第二列上使用 apply()
方法,並把結果傳遞給 Series 構建器。
用 concat()
函數,把原 DataFrame 與新 DataFrame 組合在一起。
18. 用多個函數聚合
先看一下 Chipotle 連鎖餐館的 DataFrame。
每個訂單都有訂單號(order_id),每個訂單有多行。要統計每個訂單的金額,需要先根據每個 order_id 匯總每個訂單里各個產品(item_price)的金額。下面的例子列出了訂單號為 1 的總價。
計算每單的總價,要按 order_id
進行 groupby()
分組,再按 item_price
計算每組的總價。
有時,要用多個聚合函數,不一定只是 sum()
一個函數。這時,要用 agg()
方法,把多個聚合函數的列表作為該方法的參數。
上列就算出了每個訂單的總價與訂單里的產品數量。
19. 用一個 DataFrame 合併聚合的輸出結果
本例用的還是 orders。
如果想新增一列,為每行列出訂單的總價,要怎麼操作?上面介紹過用 sum()
計算總價。
sum()
是聚合函數,該函數返回結果的行數(1834行)比原始數據的行數(4622行)少。
要解決這個問題得用 transform()
方法,這個方法執行同樣的計算,但返回與原始數據行數一樣的輸出結果,本例中為 4622 行。
接下來,為 DataFrame 新增一列,total_price
。
如上所示,每一行都列出了對應的訂單總價。
這樣一來,計算每行產品占訂單總價的百分比就易如反掌了。
20. 選擇行與列
本例使用大家都看膩了的泰坦尼克數據集。
這個數據集包括了泰坦尼克乘客的基本資訊以及是否逃生的數據。
用 describe()
方法,可以得到該數據集的基本統計數據。
這個結果集顯示的數據很多,但不一定都是你需要的,可能只需要其中幾行。
還可以只選擇部分列。
21. 重塑多重索引 Series
泰坦尼克數據集里有一列標註了倖存(Survived)狀態,值用 0、1 代表。計算該列的平均值可以計算整體倖存率。
按性別(Sex)統計男女的倖存率,需要使用 groupby()
。
要按性別與艙型(Pclass)統計倖存率,就要按性別與艙型進行 groupby()
。
上面顯示了不同性別,不同艙型的倖存率,輸出結果是一個多重索引的序列(Series),這種形式與實際數據相比多了多重索引。
這種表現形式不利於閱讀,也不方便實現數據交互,用 unstack()
把多重索引轉換為 DataFrame 更方便。
這個 DataFrame 包含的數據與多重索引序列一模一樣,只是可以用大家更熟悉的 DataFrame 方法進行操控。
22. 創建透視表
經常輸出類似上例的 DataFrame,pivot_table()
方法更方便。
使用透視表,可以直接指定索引、數據列、值與聚合函數。
設置 margins=True
,即可為透視表添加行與列的匯總。
此表顯示了整體倖存率,及按性別與艙型劃分的倖存率。
把聚合函數 mean
改為 count
,就可以生成交叉表。
這裡顯示了每個類別的記錄數。
23. 把連續型數據轉換為類型數據
下面看一下泰坦尼克數據集的年齡(Age)列。
這一列是連續型數據,如果想把它轉換為類別型數據怎麼辦?
這裡可以用 cut
函數把年齡劃分為兒童、青年、成人三個年齡段。
這段程式碼為不同分箱提供了標籤,年齡在 0-18 歲的為兒童,18-25 歲的為青年,25-99 歲的為成人。
注意:現在數據已經是類別型了,類別型數據會自動排序。
24. 改變顯示選項
接下來還是看泰坦尼克數據集。
年齡列有 1 位小數,票價列有 4 位小數,如何將這兩列顯示的小數位數標準化?
用以下程式碼讓這兩列只顯示 2 位小數。
第一個參數是要設置的選項名稱,第二個參數是 Python 的字元串格式。
現在年齡與票價列為 2 位小數了。
注意:這種操作不改變底層數據,只改變數據的顯示形式。
還可以用以下程式碼重置數據顯示選項。
pd.reset_option('display.float_format')
注意:使用同樣的方式,還可以設置更多選項。
25. 設置 DataFrame 樣式
上面的技巧適用於調整整個 Jupyter Notebook 的顯示內容。
不過,要想為某個 DataFrame 設定指定的樣式,pandas 還提供了更靈活的方式。
下面看一下 stocks。
創建樣式字元字典,指定每列使用的格式。
把這個字典傳遞給 DataFrame 的 style.format()
方法。
注意:日期是月-日-年的格式,閉市價有美元符,交易量有千分號。
接下來用鏈式方法實現更多樣式。
可以看到,這個表隱藏了索引,閉市價最小值用紅色顯示,最大值用淺綠色顯示。
再看一下背景色漸變的樣式。
交易量(Volume)列現在按不同深淺的藍色顯示,一眼就能看出來數據的大小。
下面看最後一個例子。
本例的 DataFrame 加上了標題,交易量列使用了迷你條形圖。
注意:Pandas 還支援更多 DataFrame 樣式選項,詳見 pandas 官方文檔。
彩蛋:預覽 DataFrame
假如剛拿到一個數據集,想快速了解該數據集,又不想費勁折騰怎麼辦?這裡介紹一個獨立的支援庫,pandas_profiling,可以快速預覽數據集。
第一步,安裝, pip install pandas-profiling
第二步,導入,import pandas_profiling
本例簡單介紹一下 ProfileReport()
函數,這個函數支援任意 DataFrame,並生成互動式 HTML 數據報告:
- 第一部分是縱覽數據集,還會列出數據一些可能存在的問題;
- 第二部分匯總每列數據,點擊 toggle details 查看更多資訊;
- 第三部分顯示列之間的關聯熱力圖;
- 第四部分顯示數據集的前幾條數據。
英文版 Jupyter Notebook 鏈接: https://nbviewer.jupyter.org/github/justmarkham/pandas-videos/blob/master/top_25_pandas_tricks.ipynb
中文版 Jupyter Notebook 鏈接: https://github.com/jaystone776/pandas_answered/blob/master/25_Pandas_Tips_by_PyCon_Master.ipynb
數據集下載: https://github.com/jaystone776/pandas_answered/blob/master/data/25_Pandas_Tips_by_PyCon_Master_data.zip