字符串操作
Python有簡(jiǎn)單易用的字符串和文本處理功能,大部分文本運(yùn)算都直接做成了字符串對(duì)象的內(nèi)置方法。對(duì)于更為復(fù)雜的模式匹配和文本操作,則可能需要用到正則表達(dá)式。
字符串對(duì)象方法
以逗號(hào)分隔的字符串可以用split拆分成數(shù)段
In [4]: val='a,bc,c, gudio'
In [5]: val.split(',')
Out[5]: ['a', 'bc', 'c', ' gudio']
In [6]: val='a,bc ,c, gudio'
In [7]: val.split(',')
Out[7]: ['a', 'bc ', 'c', ' gudio']
split常常結(jié)合strip(用于修剪空白符(包括換行符))一起使用
In [8]: pieces=[x.strip() for x in val.split(',')]
In [9]: pieces
Out[9]: ['a', 'bc', 'c', 'gudio']
利用加法,可以將這些子字符串以雙冒號(hào)分隔符的形式連接起來
In [13]: first,second,third,four=pieces
In [14]: first +'::'+ second +'::'+ third +'::'+four
Out[14]: 'a::bc::c::gudio'
向字符串"::"的join方法傳入一個(gè)列表或元組
In [15]: '::'.join(pieces)
Out[15]: 'a::bc::c::gudio'
檢測(cè)子串的最佳方式是利用Python的in關(guān)鍵字(還可以使用index和find)
In [16]: 'guido' in val
Out[16]: False
In [17]: 'gudio' in val
Out[17]: True
In [18]: val.index(',')
Out[18]: 1
In [19]: val.find(':')
Out[19]: -1
In [20]: val.find('bc')
Out[20]: 2
In [21]: val.find('c')
Out[21]: 3
In [22]: val.index('c')
Out[22]: 3
注意find和index的區(qū)別:如果找不到字符串,index將會(huì)引發(fā)一個(gè)異常(而不是返回-1)
In [23]: val.index(':')
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
<ipython-input-23-280f8b2856ce> in <module>()
----> 1 val.index(':')
ValueError: substring not found
count函數(shù),它可以返回指定子串的出現(xiàn)次數(shù)
In [24]: val.count(',')
Out[24]: 3
replace用于將指定模式替換為另一個(gè)模式。它也常常用于刪除模式:傳入空字符串
In [25]: val.replace(',','::')
Out[25]: 'a::bc ::c:: gudio'
In [26]: val.replace(',','')
Out[26]: 'abc c gudio'
In [27]: val
Out[27]: 'a,bc ,c, gudio'
In [28]: val.replace('c','m')
Out[28]: 'a,bm ,m, gudio'
In [29]: val.replace('bc','abcd')
Out[29]: 'a,abcd ,c, gudio'
字母轉(zhuǎn)換為大寫
In [30]: val.upper()
Out[30]: 'A,BC ,C, GUDIO'
Python內(nèi)置的字符串方法如表7-3所示

正則表達(dá)式
正則表達(dá)式(通常稱作regex)提供了一種靈活的在文本中搜索或匹配字符串模式的方式。正則表達(dá)式是根據(jù)正則表達(dá)式語言編寫的字符串。Python內(nèi)置的re模塊負(fù)責(zé)對(duì)字符串應(yīng)用正則表達(dá)式。
re模塊的函數(shù)可以分為三個(gè)大類:模式匹配、替換以及拆分。當(dāng)然,它們之間是相輔相成的。一個(gè)regex描述了需要在文本中定位的一個(gè)模式,它可以用于許多目的。我們先來看一個(gè)簡(jiǎn)單的例子:假設(shè)我想要拆分一個(gè)字符串,分隔符為數(shù)量不定的一組空白符(制表符、空格、換行符等)。描述一個(gè)或多個(gè)空白符的regex是\s+:
In [35]: import re
In [36]: text="foo bar\t baz \tqux"
In [37]: re.split('\s+',text)
Out[37]: ['foo', 'bar', 'baz', 'qux']
調(diào)用re.split('\s+',text)時(shí),正則表達(dá)式會(huì)先被編譯,然后再在text上調(diào)用其split方法。你可以用re.compile自己編譯regex以得到一個(gè)可重用的regex對(duì)象
In [38]: regex=re.compile('\s+')
In [39]: regex.split(text)
Out[39]: ['foo', 'bar', 'baz', 'qux']
如果只希望得到匹配regex的所有模式,則可以使用findall方法
In [40]: regex.findall(text)
Out[40]: [' ', '\t ', ' \t']
注意: 如果想避免正則表達(dá)式中不需要的轉(zhuǎn)義(\),則可以使用原始字符串字面量如r'C:\x'(也可以編寫其等價(jià)式'C:\x')。
如果打算對(duì)許多字符串應(yīng)用同一條正則表達(dá)式,強(qiáng)烈建議通過re.compile創(chuàng)建regex對(duì)象。這樣將可以節(jié)省大量的CPU時(shí)間。
match和search跟findall功能類似。findall返回的是字符串中所有的匹配項(xiàng),而search則只返回第一個(gè)匹配項(xiàng)。match更加嚴(yán)格,它只匹配字符串的首部。來看一個(gè)小例子,假設(shè)我們有一段文本以及一條能夠識(shí)別大部分電子郵件地址的正則表達(dá)式:
In [41]: text = """Dave dave@google.com
...: Steve steve@gmail.com
...: Rob rob@gmail.com
...: Ryan ryan@yahoo.com"""
In [42]: pattern = r'[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}'
re.IGNORECASE的作用是使正則表達(dá)式對(duì)大小寫不敏感
In [43]: regex=re.compile(pattern,flags=re.IGNORECASE)
對(duì)text使用findall將得到一組電子郵件地址
In [44]: regex.findall(text)
Out[44]: ['dave@google.com', 'steve@gmail.com', 'rob@gmail.com', 'ryan@yahoo.com']
search返回的是文本中第一個(gè)電子郵件地址(以特殊的匹配項(xiàng)對(duì)象形式返回)。對(duì)于上面那個(gè)regex,匹配項(xiàng)對(duì)象只能告訴我們模式在原字符串中的起始和結(jié)束位置
In [45]: m=regex.search(text)
In [46]: m
Out[46]: <_sre.SRE_Match object; span=(5, 20), match='dave@google.com'>
regex.match則將返回None,因?yàn)樗黄ヅ涑霈F(xiàn)在字符串開頭的模式
In [47]: print(regex.match(text))
None
sub方法,它會(huì)將匹配到的模式替換為指定字符串,并返回所得到的新字符串
In [48]: print(regex.sub('REDACTED',text))
Dave REDACTED
Steve REDACTED
Rob REDACTED
Ryan REDACTED
假設(shè)你不僅想要找出電子郵件地址,還想將各個(gè)地址分成3個(gè)部分:用戶名、域名以及域后綴。要實(shí)現(xiàn)此功能,只需將待分段的模式的各部分用圓括號(hào)包起來即可
In [49]: pattern = r'([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\.([A-Z]{2,4})'
In [50]: regex = re.compile(pattern, flags=re.IGNORECASE)
由這種正則表達(dá)式所產(chǎn)生的匹配項(xiàng)對(duì)象,可以通過其groups方法返回一個(gè)由模式各段組成的元組
In [51]: m=regex.match('wesm@bright.net')
In [52]: m.groups()
Out[52]: ('wesm', 'bright', 'net')
對(duì)于帶有分組功能的模式,findall會(huì)返回一個(gè)元組列表
In [53]: regex.findall(text)
Out[53]:
[('dave', 'google', 'com'),
('steve', 'gmail', 'com'),
('rob', 'gmail', 'com'),
('ryan', 'yahoo', 'com')]
sub還能通過諸如\1、\2之類的特殊符號(hào)訪問各匹配項(xiàng)中的分組
In [54]: print(regex.sub(r'Username: \1, Domain: \2, Suffix: \3', text))
Dave Username: dave, Domain: google, Suffix: com
Steve Username: steve, Domain: gmail, Suffix: com
Rob Username: rob, Domain: gmail, Suffix: com
Ryan Username: ryan, Domain: yahoo, Suffix: com
對(duì)上面那個(gè)電子郵件正則表達(dá)式做一點(diǎn)小變動(dòng):為各個(gè)匹配分組加上一個(gè)名稱
In [55]: regex = re.compile(r"""(?P<username>[A-Z0-9._%+-]+)
...: @(?P<domain>[A-Z0-9.-]+)\.
...: (?P<suffix>[A-Z]{2,4})""",
...: flags=re.IGNORECASE|re.VERBOSE)
由這種正則表達(dá)式所產(chǎn)生的匹配項(xiàng)對(duì)象可以得到一個(gè)簡(jiǎn)單易用的帶有分組名稱的字典
In [56]: m = regex.match('wesm@bright.net')
In [57]: m
Out[57]: <_sre.SRE_Match object; span=(0, 15), match='wesm@bright.net'>
In [58]: m.groupdict()
Out[58]: {'domain': 'bright', 'suffix': 'net', 'username': 'wesm'}
正則表達(dá)式的方法與說明如表所示

pandas中矢量化的字符串函數(shù)
含有字符串的列有時(shí)還含有缺失數(shù)據(jù),清理待分析的散亂數(shù)據(jù)時(shí),常常需要做一些字符串規(guī)整化工作。
In [59]: data = {'Dave': 'dave@google.com', 'Steve': 'steve@gmail.com',
...: 'Rob': 'rob@gmail.com', 'Wes': np.nan}
In [60]: data=Series(data)
In [61]: data
Out[61]:
Dave dave@google.com
Rob rob@gmail.com
Steve steve@gmail.com
Wes NaN
dtype: object
In [62]: data.isnull()
Out[62]:
Dave False
Rob False
Steve False
Wes True
dtype: bool
通過data.map,所有字符串和正則表達(dá)式方法都能被應(yīng)用于(傳入lambda表達(dá)式或其他函數(shù))各個(gè)值,但是如果存在NA就會(huì)報(bào)錯(cuò)。為了解決這個(gè)問題,Series有一些能夠跳過NA值的字符串操作方法。通過Series的str屬性即可訪問這些方法。例如,我們可以通過str.contains檢查各個(gè)電子郵件地址是否含有"gmail"
In [63]: data.str.contains('gmail')
Out[63]:
Dave False
Rob True
Steve True
Wes NaN
dtype: object
可以使用正則表達(dá)式,還可以加上任意re選項(xiàng)(如IGNORECASE)
In [64]: pattern
Out[64]: '([A-Z0-9._%+-]+)@([A-Z0-9.-]+)\\.([A-Z]{2,4})'
In [65]: data.str.findall(pattern,flags=re.IGNORECASE)
Out[65]:
Dave [(dave, google, com)]
Rob [(rob, gmail, com)]
Steve [(steve, gmail, com)]
Wes NaN
dtype: object
有兩個(gè)辦法可以實(shí)現(xiàn)矢量化的元素獲取操作:要么使用str.get,要么在str屬性上使用索引
In [66]: matches=data.str.match(pattern,flags=re.IGNORECASE)
In [67]: matches
Out[67]:
Dave True
Rob True
Steve True
Wes NaN
dtype: object
以下的str.get()方法和str()方法結(jié)果和書上不同,可能是版本問題。
In [71]: matches.str.get(1)
Out[71]:
Dave NaN
Rob NaN
Steve NaN
Wes NaN
dtype: float64
In [72]: matches.str[0]
Out[72]:
Dave NaN
Rob NaN
Steve NaN
Wes NaN
dtype: float64
可以利用對(duì)字符串進(jìn)行子串截取
In [73]: data.str[:5]
Out[73]:
Dave dave@
Rob rob@g
Steve steve
Wes NaN
dtype: object
表7-5介紹了矢量化的字符串方法

完成本章的練習(xí)了,接下來繼續(xù)學(xué)習(xí)下章的可視化知識(shí)。