3.11 向量化字符串操作
使用 Python 的一個優勢就是字符串處理起來比較容易。在此基礎上創建的 Pandas 同樣提供了一系列向量化字符串操作(vectorized string operation),它們都是在處理(清洗)現實工作中的數據時不可或缺的功能。在這一節中,我們將介紹 Pandas 的字符串操作,學習如何用它們對一個從網絡採集來的雜亂無章的數據集進行局部清理。
3.11.1 Pandas字符串操作簡介
前面的章節已經介紹過如何用 NumPy 和 Pandas 進行一般的運算操作,因此我們也能簡便快速地對多個數組元素執行同樣的操作,例如:
import numpy as np x = np.array([2, 3, 5, 7, 11, 13]) x * 2
array([ 4, 6, 10, 14, 22, 26])
向量化操作簡化了純數值的數組操作語法——我們不需要再擔心數組的長度或維度,只需要關心需要的操作。然而,由於 NumPy 並沒有爲字符串數組提供簡單的接口,因此需要通過繁瑣的 for 循環來解決問題:
data = ['peter', 'Paul', 'MARY', 'gUIDO'] [s.capitalize() for s in data]
['Peter', 'Paul', 'Mary', 'Guido']
雖然這麼做對於某些數據可能是有效的,但是假如數據中出現了缺失值,那麼這樣做就會引起異常,例如:
data = ['peter', 'Paul', None, 'MARY', 'gUIDO'] [s.capitalize() for s in data]
--------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-3-3b0264c38d59> in <module>() 1 data = ['peter', 'Paul', None, 'MARY', 'gUIDO'] ----> 2 [s.capitalize() for s in data] <ipython-input-3-3b0264c38d59> in <listcomp>(.0) 1 data = ['peter', 'Paul', None, 'MARY', 'gUIDO'] ----> 2 [s.capitalize() for s in data] AttributeError: 'NoneType' object has no attribute 'capitalize'
Pandas 爲包含字符串的 Series 和 Index 對象提供的 str 屬性堪稱兩全其美的方法,它既可以滿足向量化字符串操作的需求,又可以正確地處理缺失值。例如,我們用前面的數據 data 創建了一個 Pandas 的 Series:
import pandas as pd names = pd.Series(data) names
0 peter 1 Paul 2 None 3 MARY 4 gUIDO dtype: object
現在就可以直接調用轉換大寫方法 capitalize() 將所有的字符串變成大寫形式,缺失值會被跳過:
names.str.capitalize()
0 Peter 1 Paul 2 None 3 Mary 4 Guido dtype: object
在 str 屬性後面用 Tab 鍵,可以看到 Pandas 支持的所有向量化字符串方法。
3.11.2 Pandas字符串方法列表
如果你熟悉 Python 的字符串方法的話,就會發現 Pandas 絕大多數的字符串語法都很直觀,甚至可以列成一個表格。在深入論述後面的內容之前,讓我們先從這一步開始。這一節的示例將採用一些人名來演示:
monte = pd.Series(['Graham Chapman', 'John Cleese', 'Terry Gilliam', 'Eric Idle', 'Terry Jones', 'Michael Palin'])
與Python字符串方法相似的方法
幾乎所有 Python 內置的字符串方法都被複制到 Pandas 的向量化字符串方法中。下面的表格列舉了 Pandas 的 str 方法借鑑 Python 字符串方法的內容:
方法 | 方法 | 方法 | 方法 |
---|---|---|---|
len() |
lower() |
translate() |
islower() |
ljust() |
upper() |
startswith() |
isupper() |
rjust() |
find() |
endswith() |
isnumeric() |
center() |
rfind() |
isalnum() |
isdecimal() |
zfill() |
index() |
isalpha() |
split() |
strip() |
rindex() |
isdigit() |
rsplit() |
rstrip() |
capitalize() |
isspace() |
partition() |
lstrip() |
swapcase() |
istitle() |
rpartition() |
需要注意的是,這些方法的返回值不同,例如 lower() 方法返回一個字符串 Series:
monte.str.lower()
0 graham chapman 1 john cleese 2 terry gilliam 3 eric idle 4 terry jones 5 michael palin dtype: object
但是有些方法返回數值:
monte.str.len()
0 14 1 11 2 13 3 9 4 11 5 13 dtype: int64
有些方法返回布爾值:
monte.str.startswith('T')
0 False 1 False 2 True 3 False 4 True 5 False dtype: bool
還有些方法返回列表或其他複合值:
monte.str.split()
0 [Graham, Chapman] 1 [John, Cleese] 2 [Terry, Gilliam] 3 [Eric, Idle] 4 [Terry, Jones] 5 [Michael, Palin] dtype: object
在接下來的內容中,我們將進一步學習這類由列表元素構成的 Series(series-of-lists)對象。
使用正則表達式的方法
還有一些支持正則表達式的方法可以用來處理每個字符串元素。表中的內容是 Pandas 向量化字符串方法根據 Python 標準庫的 re 模塊函數實現的 API。
方法 | 描述 |
---|---|
match() | 對每個元素調用 re.match(),返回布爾類型值 |
extract() | 對每個元素調用 re.match(),返回匹配的字符串組(groups) |
findall() | 對每個元素調用 re.findall() |
replace() | 用正則模式替換字符串 |
contains() | 對每個元素調用 re.search(),返回布爾類型值 |
count() | 計算符合正則模式的字符串的數量 |
split() | 等價於 str.split(),支持正則表達式 |
rsplit() | 等價於 str.rsplit(),支持正則表達式 |
通過這些方法,你就可以實現各種有趣的操作了。例如,可以提取元素前面的連續字母作爲每個人的名字(first name):
monte.str.extract('([A-Za-z]+)', expand=False)
0 Graham 1 John 2 Terry 3 Eric 4 Terry 5 Michael dtype: object
我們還能實現更復雜的操作,例如找出所有開頭和結尾都是輔音字母的名字——這可以用正則表達式中的開始符號(^)與結尾符號($)來實現:
monte.str.findall(r'^[^AEIOU].*[^aeiou]$')
0 [Graham Chapman] 1 [] 2 [Terry Gilliam] 3 [] 4 [Terry Jones] 5 [Michael Palin] dtype: object
能將正則表達式應用到 Series 與 DataFrame 之中的話,就有可能實現更多的數據分析與清洗方法。
其他字符串方法
還有其他一些方法也可以實現方便的操作(如表所示)。
方法 | 描述 |
---|---|
get() | 獲取元素索引位置上的值,索引從 0 開始 |
slice() | 對元素進行切片取值 |
slice_replace() | 對元素進行切片替換 |
cat() | 連接字符串(此功能比較複雜,建議閱讀文檔) |
repeat() | 重複元素 |
normalize() | 將字符串轉換爲 Unicode 規範形式 |
pad() | 在字符串的左邊、右邊或兩邊增加空格 |
wrap() | 將字符串按照指定的寬度換行 |
join() | 用分隔符連接 Series 的每個元素 |
get_dummies() | 按照分隔符提取每個元素的 dummy 變量, |
.. | 轉換爲獨熱(one-hot)編碼的 DataFrame |
(1) 向量化字符串的取值與切片操作。這裏需要特別指出的是,get() 與 slice() 操作可以從每個字符串數組中獲取向量化元素。例如,我們可以通過 str.slice(0, 3) 獲取每個字符串數組的前三個字符。通過 Python 的標準取值方法也可以取得同樣的效果,例如 df.str.slice(0, 3) 等價於 df.str[0:3]:
monte.str[0:3]
0 Gra 1 Joh 2 Ter 3 Eri 4 Ter 5 Mic dtype: object
df.str.get(i) 與 df.str[i] 的按索引取值效果類似。
get() 與 slice() 操作還可以在 split() 操作之後使用。例如,要獲取每個姓名的姓(last name),可以結合使用 split() 與 get():
monte.str.split().str.get(-1)
0 Chapman 1 Cleese 2 Gilliam 3 Idle 4 Jones 5 Palin dtype: object
(2) 指標變量。另一個需要多花點兒時間解釋的是 get_dummies() 方法。當你的數據有一列包含了若干已被編碼的指標(coded indicator)時,這個方法就能派上用場了。例如,假設有一個包含了某種編碼信息的數據集,如 A= 出生在美國、B= 出生在英國、C= 喜歡奶酪、D= 喜歡午餐肉:
full_monte = pd.DataFrame({'name': monte, 'info': ['B|C|D', 'B|D', 'A|C', 'B|D', 'B|C', 'B|C|D']}) full_monte
name | info | |
---|---|---|
0 | Graham Chapman | B|C|D |
1 | John Cleese | B|D |
2 | Terry Gilliam | A|C |
3 | Eric Idle | B|D |
4 | Terry Jones | B|C |
5 | Michael Palin | B|C|D |
get_dummies() 方法可以讓你快速將這些指標變量分割成一個獨熱編碼的 DataFrame(每個元素都是 0 或 1):
full_monte['info'].str.get_dummies('|')
A | B | C | D | |
---|---|---|---|---|
0 | 0 | 1 | 1 | 1 |
1 | 0 | 1 | 0 | 1 |
2 | 1 | 0 | 1 | 0 |
3 | 0 | 1 | 0 | 1 |
4 | 0 | 1 | 1 | 0 |
5 | 0 | 1 | 1 | 1 |
通過 Pandas 自帶的這些字符串操作方法,你就可以建立一個功能無比強大的字符串處理程序來清洗自己的數據了。
雖然本書將不再繼續介紹這些方法,但是希望你仔細閱讀 Pandas 在線文檔中“Working with Text Data”(http://pandas.pydata.org/pandas-docs/stable/text.html)節,或者閱讀 3.14 節的相關資源。
3.11.3 案例:食譜數據庫
略