序列和索引包含一些列的字符操作方法,这可以使我们轻易操作数组中的各个元素。最重要的是,这些方法可以自动跳过 缺失/NA 值。这些方法可以在str
属性中访问到,并且基本上和python内建的(标量)字符串方法同名:
In [1]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])
In [2]: s.str.lower()
Out[2]:
0 a
1 b
2 c
3 aaba
4 baca
5 NaN
6 caba
7 dog
8 cat
dtype: object
In [3]: s.str.upper()
Out[3]:
0 A
1 B
2 C
3 AABA
4 BACA
5 NaN
6 CABA
7 DOG
8 CAT
dtype: object
In [4]: s.str.len()
Out[4]:
0 1.0
1 1.0
2 1.0
3 4.0
4 4.0
5 NaN
6 4.0
7 3.0
8 3.0
dtype: float64
In [5]: idx = pd.Index([' jack', 'jill ', ' jesse ', 'frank'])
In [6]: idx.str.strip()
Out[6]: Index(['jack', 'jill', 'jesse', 'frank'], dtype='object')
In [7]: idx.str.lstrip()
Out[7]: Index(['jack', 'jill ', 'jesse ', 'frank'], dtype='object')
In [8]: idx.str.rstrip()
Out[8]: Index([' jack', 'jill', ' jesse', 'frank'], dtype='object')
索引的字符串方法在清理或者转换数据表列的时候非常有用。例如,你的列中或许会包含首位的白空格:
In [9]: df = pd.DataFrame(np.random.randn(3, 2),
...: columns=[' Column A ', ' Column B '], index=range(3))
...:
In [10]: df
Out[10]:
Column A Column B
0 0.469112 -0.282863
1 -1.509059 -1.135632
2 1.212112 -0.173215
Since df.columns
is an Index object, we can use the .str
accessor
In [11]: df.columns.str.strip()
Out[11]: Index(['Column A', 'Column B'], dtype='object')
In [12]: df.columns.str.lower()
Out[12]: Index([' column a ', ' column b '], dtype='object')
这些字符串方法可以被用来清理需要的列。这里,我们想清理开头和结尾的白空格,将所有的名称都换为小写,并且将其余的空格都替换为下划线:
In [13]: df.columns = df.columns.str.strip().str.lower().str.replace(' ', '_')
In [14]: df
Out[14]:
column_a column_b
0 0.469112 -0.282863
1 -1.509059 -1.135632
2 1.212112 -0.173215
小贴士
如果你有一个序列,里面有很多重复的值 (即,序列中唯一元素的数量远小于序列
的长度),将原有序列转换为一种分类类型,然后使用.str.
或者 .dt.
方法,则会获得更快的速度。 速度的差异来源于,在分类类型
的序列
中,字符操作只是在categories
中完成的,而不是针对序列
中的每一个元素。
请注意,相比于字符串类型的序列
,带.categories
类型的 分类
类别的 序列
有一些限制(例如,你不能像其中的元素追加其他的字串:s + " " + s
将不能正确工作,如果s是一个分类
类型的序列。并且,.str
中,那些可以对 列表(list)
类型的元素进行操作的方法,在分类序列中也无法使用。
警告
v.0.25.0版以前, .str
访问器只会进行最基本的类型检查。 从v.0.25.0起,序列的类型会被自动推断出来,并且会更为激进地使用恰当的类型。
一般来说 .str
访问器只倾向于针对字符串类型工作。只有在个别的情况下,才能对非字符串类型工作,但是这也将会在未来的版本中被逐步修正
拆分和替换字符串
类似split
的方法返回一个列表类型的序列:
In [15]: s2 = pd.Series(['a_b_c', 'c_d_e', np.nan, 'f_g_h'])
In [16]: s2.str.split('_')
Out[16]:
0 [a, b, c]
1 [c, d, e]
2 NaN
3 [f, g, h]
dtype: object
切分后的列表中的元素可以通过 get
方法或者 []
方法进行读取:
In [17]: s2.str.split('_').str.get(1)
Out[17]:
0 b
1 d
2 NaN
3 g
dtype: object
In [18]: s2.str.split('_').str[1]
Out[18]:
0 b
1 d
2 NaN
3 g
dtype: object
使用expand
方法可以轻易地将这种返回展开为一个数据表.
In [19]: s2.str.split('_', expand=True)
Out[19]:
0 1 2
0 a b c
1 c d e
2 NaN NaN NaN
3 f g h
同样,我们也可以限制切分的次数:
In [20]: s2.str.split('_', expand=True, n=1)
Out[20]:
0 1
0 a b_c
1 c d_e
2 NaN NaN
3 f g_h
rsplit
与split
相似,不同的是,这个切分的方向是反的。即,从字串的尾端向首段切分:
In [21]: s2.str.rsplit('_', expand=True, n=1)
Out[21]:
0 1
0 a_b c
1 c_d e
2 NaN NaN
3 f_g h
replace
方法默认使用 正则表达式 (opens new window):
In [22]: s3 = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca',
....: '', np.nan, 'CABA', 'dog', 'cat'])
....:
In [23]: s3
Out[23]:
0 A
1 B
2 C
3 Aaba
4 Baca
5
6 NaN
7 CABA
8 dog
9 cat
dtype: object
In [24]: s3.str.replace('^.a|dog', 'XX-XX ', case=False)
Out[24]:
0 A
1 B
2 C
3 XX-XX ba
4 XX-XX ca
5
6 NaN
7 XX-XX BA
8 XX-XX
9 XX-XX t
dtype: object
一定要时时记得,是正则表达式,因此要格外小心。例如,因为正则表达式中的*$*符号,下列代码将会导致一些麻烦:
# Consider the following badly formatted financial data
In [25]: dollars = pd.Series(['12', '-$10', '$10,000'])
# This does what you'd naively expect:
In [26]: dollars.str.replace('$', '')
Out[26]:
0 12
1 -10
2 10,000
dtype: object
# But this doesn't:
In [27]: dollars.str.replace('-$', '-')
Out[27]:
0 12
1 -$10
2 $10,000
dtype: object
# We need to escape the special character (for >1 len patterns)
In [28]: dollars.str.replace(r'-\$', '-')
Out[28]:
0 12
1 -10
2 $10,000
dtype: object
v0.23.0. 新加入
如果你只是向单纯地替换字符 (等价于python中的 str.replace()
(opens new window)),你可以将可选参数 regex
设置为 False
,而不是傻傻地转义所有符号。这种情况下,pat
和 repl
就都将作为普通字符对待:
# These lines are equivalent
In [29]: dollars.str.replace(r'-\$', '-')
Out[29]:
0 12
1 -10
2 $10,000
dtype: object
In [30]: dollars.str.replace('-$', '-', regex=False)
Out[30]:
0 12
1 -10
2 $10,000
dtype: object
v0.20.0. 新加入
replace
方法也可以传入一个可调用对象作为替换值。它针对每一个 pat
通过re.sub()
(opens new window)来调用。可调用对象应只具有一个形参(一个正则表达式对象)并且返回一个字符串。
# Reverse every lowercase alphabetic word
In [31]: pat = r'[a-z]+'
In [32]: def repl(m):
....: return m.group(0)[::-1]
....:
In [33]: pd.Series(['foo 123', 'bar baz', np.nan]).str.replace(pat, repl)
Out[33]:
0 oof 123
1 rab zab
2 NaN
dtype: object
# Using regex groups
In [34]: pat = r"(?P<one>\w+) (?P<two>\w+) (?P<three>\w+)"
In [35]: def repl(m):
....: return m.group('two').swapcase()
....:
In [36]: pd.Series(['Foo Bar Baz', np.nan]).str.replace(pat, repl)
Out[36]:
0 bAR
1 NaN
dtype: object
v0.20.0. 新加入
replace
方法也可以接受一个来自re.compile()
(opens new window) 编译过的正则表达式对象,来做为表达式
。所有的标记都应该被包含在这个已经编译好的正则表达式对象中。
In [37]: import re
In [38]: regex_pat = re.compile(r'^.a|dog', flags=re.IGNORECASE)
In [39]: s3.str.replace(regex_pat, 'XX-XX ')
Out[39]:
0 A
1 B
2 C
3 XX-XX ba
4 XX-XX ca
5
6 NaN
7 XX-XX BA
8 XX-XX
9 XX-XX t
dtype: object
如果在已经使用编译的正则对象中继续传入flags
参数,并进行替换,将会导致ValueError
。
In [40]: s3.str.replace(regex_pat, 'XX-XX ', flags=re.IGNORECASE)
---------------------------------------------------------------------------
ValueError: case and flags cannot be set when pat is a compiled regex
拼接
Pandas提供了不同的方法将序列
或索引
与他们自己或者其他的对象进行拼接,所有的方法都是基于各自的cat()
(opens new window), resp. Index.str.cat
.
将单个序列拼接为一个完整字符串
序列
或索引
的内容可以进行拼接:
In [41]: s = pd.Series(['a', 'b', 'c', 'd'])
In [42]: s.str.cat(sep=',')
Out[42]: 'a,b,c,d'
如果没有额外声明,sep
即分隔符默认为空字串,即sep=''
:
In [43]: s.str.cat()
Out[43]: 'abcd'
默认情况下,缺失值会被忽略。使用na_rep
参数,可以对缺失值进行赋值:
In [44]: t = pd.Series(['a', 'b', np.nan, 'd'])
In [45]: t.str.cat(sep=',')
Out[45]: 'a,b,d'
In [46]: t.str.cat(sep=',', na_rep='-')
Out[46]: 'a,b,-,d'
拼接序列和其他类列表型对象为新的序列
cat()
(opens new window) 的第一个参数为类列表对象,但必须要确保长度与序列
或索引
相同.
In [47]: s.str.cat(['A', 'B', 'C', 'D'])
Out[47]:
0 aA
1 bB
2 cC
3 dD
dtype: object
任何一端的缺失值都会导致之中结果为缺失值,除非使用na_rep
:
In [48]: s.str.cat(t)
Out[48]:
0 aa
1 bb
2 NaN
3 dd
dtype: object
In [49]: s.str.cat(t, na_rep='-')
Out[49]:
0 aa
1 bb
2 c-
3 dd
dtype: object
拼接序列与类数组对象为新的序列
v0.23.0. 新加入
others
参数可以是二维的。此时,行数需要与序列
或索引
的长度相同。
In [50]: d = pd.concat([t, s], axis=1)
In [51]: s
Out[51]:
0 a
1 b
2 c
3 d
dtype: object
In [52]: d
Out[52]:
0 1
0 a a
1 b b
2 NaN c
3 d d
In [53]: s.str.cat(d, na_rep='-')
Out[53]:
0 aaa
1 bbb
2 c-c
3 ddd
dtype: object
对齐拼接序列与带索引的对象成为新的序列
v0.23.0.新加入
对于拼接序列
或者数据表
,我们可以使用 join
关键字来对齐索引。
In [54]: u = pd.Series(['b', 'd', 'a', 'c'], index=[1, 3, 0, 2])
In [55]: s
Out[55]:
0 a
1 b
2 c
3 d
dtype: object
In [56]: u
Out[56]:
1 b
3 d
0 a
2 c
dtype: object
In [57]: s.str.cat(u)
Out[57]:
0 ab
1 bd
2 ca
3 dc
dtype: object
In [58]: s.str.cat(u, join='left')
Out[58]:
0 aa
1 bb
2 cc
3 dd
dtype: object
警告
如果不使用join
关键字, cat()
(opens new window) 方法将会滚回到0.23.0版之前,即(无对齐)模式。但如果任何的索引不一致时,将会抛出一个 FutureWarning
警告,因为在未来的版本中,默认行为将改为join='left' 。
join
的选项为('left'
, 'outer'
, 'inner'
, 'right'
)中的一个。 特别的,对齐操作使得两个对象可以是不同的长度。
In [59]: v = pd.Series(['z', 'a', 'b', 'd', 'e'], index=[-1, 0, 1, 3, 4])
In [60]: s
Out[60]:
0 a
1 b
2 c
3 d
dtype: object
In [61]: v
Out[61]:
-1 z
0 a
1 b
3 d
4 e
dtype: object
In [62]: s.str.cat(v, join='left', na_rep='-')
Out[62]:
0 aa
1 bb
2 c-
3 dd
dtype: object
In [63]: s.str.cat(v, join='outer', na_rep='-')
Out[63]:
-1 -z
0 aa
1 bb
2 c-
3 dd
4 -e
dtype: object
当others
是一个数据表
时,也可以执行相同的对齐操作:
In [64]: f = d.loc[[3, 2, 1, 0], :]
In [65]: s
Out[65]:
0 a
1 b
2 c
3 d
dtype: object
In [66]: f
Out[66]:
0 1
3 d d
2 NaN c
1 b b
0 a a
In [67]: s.str.cat(f, join='left', na_rep='-')
Out[67]:
0 aaa
1 bbb
2 c-c
3 ddd
dtype: object
将一个序列与多个对象拼接为一个新的序列
所有的一维,类列表对象都可以任意组合进一个类列表的容器(包括迭代器,dict-视图等):
In [68]: s
Out[68]:
0 a
1 b
2 c
3 d
dtype: object
In [69]: u
Out[69]:
1 b
3 d
0 a
2 c
dtype: object
In [70]: s.str.cat([u, u.to_numpy()], join='left')
Out[70]:
0 aab
1 bbd
2 cca
3 ddc
dtype: object
除了那些有索引的,所有传入没有索引的元素(如np.ndarray
)必须与序列
或索引
有相同的长度。但是,只要禁用对齐join=None
,那么序列
或索引
就可以是任意长度。
In [71]: v
Out[71]:
-1 z
0 a
1 b
3 d
4 e
dtype: object
In [72]: s.str.cat([v, u, u.to_numpy()], join='outer', na_rep='-')
Out[72]:
-1 -z--
0 aaab
1 bbbd
2 c-ca
3 dddc
4 -e--
dtype: object
如果在一个包含不同的索引的others
列表上使用join='right'
,所有索引的并集将会被作为最终拼接的基础:
In [73]: u.loc[[3]]
Out[73]:
3 d
dtype: object
In [74]: v.loc[[-1, 0]]
Out[74]:
-1 z
0 a
dtype: object
In [75]: s.str.cat([u.loc[[3]], v.loc[[-1, 0]]], join='right', na_rep='-')
Out[75]:
-1 --z
0 a-a
3 dd-
dtype: object
使用.str进行索引
你可以使用 []
方法来直接索引定位。如果你的索引超过了字符串的结尾,将返回NaN
。
In [76]: s = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan,
....: 'CABA', 'dog', 'cat'])
....:
In [77]: s.str[0]
Out[77]:
0 A
1 B
2 C
3 A
4 B
5 NaN
6 C
7 d
8 c
dtype: object
In [78]: s.str[1]
Out[78]:
0 NaN
1 NaN
2 NaN
3 a
4 a
5 NaN
6 A
7 o
8 a
dtype: object
提取子字符串
提取第一个匹配的对象 (extract)
警告
在 0.18.0中,extract
拥有了 expand
参数。当 expand=False
时, 将返回一个序列,索引或者数据表, 这取决于原对象和正则表达式(之前的版本也是如此)。当 expand=True
时,它则总是返回一个DataFrame
,这样可以更加一致,并且减少用户的混淆。 Expand=True
从0.23.0版本之后成为默认值。
extract
方法接受一个至少含有一个捕获组的 正则表达式 (opens new window)。
使用超过一个捕获组的正则表达式则会提取并返回一个数据表,每一列为一个捕获组。
In [79]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'([ab])(\d)', expand=False)
Out[79]:
0 1
0 a 1
1 b 2
2 NaN NaN
没有成功匹配的元素将会返回一行NaN
。因此,一个序列的混乱的字符串可以被‘转换’为一个类似索引的序列或数据表。返回的内容会更为清爽,而且不需要使用get()
方法来访问元组中的成员或者re.match
对象。返回的类型将总是object
类,即使匹配失败,返回的全是NaN
。
有名称的捕获组,如:
In [80]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'(?P<letter>[ab])(?P<digit>\d)',
....: expand=False)
....:
Out[80]:
letter digit
0 a 1
1 b 2
2 NaN NaN
可选组类似,如:
In [81]: pd.Series(['a1', 'b2', '3']).str.extract(r'([ab])?(\d)', expand=False)
Out[81]:
0 1
0 a 1
1 b 2
2 NaN 3
也可以被使用。注意,任何有名称的捕获组,其名称都会被用做列名,否则将会直接使用数字。
如果仅使用正则表达式捕获一个组,而expand=True
,那么仍然将返回一个数据表
。
In [82]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'[ab](\d)', expand=True)
Out[82]:
0
0 1
1 2
2 NaN
如果expand=False
,则会返回一个序列
。
In [83]: pd.Series(['a1', 'b2', 'c3']).str.extract(r'[ab](\d)', expand=False)
Out[83]:
0 1
1 2
2 NaN
dtype: object
在索引
上使用正则表达式,并且仅捕获一个组时,将会返回一个数据表
,如果expand=True
。
In [84]: s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"])
In [85]: s
Out[85]:
A11 a1
B22 b2
C33 c3
dtype: object
In [86]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=True)
Out[86]:
letter
0 A
1 B
2 C
如果expand=False
,则返回一个Index
。
In [87]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=False)
Out[87]: Index(['A', 'B', 'C'], dtype='object', name='letter')
如果在索引
上使用正则并捕获多个组,则返回一个数据表
,如果expand=True
。
In [88]: s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=True)
Out[88]:
letter 1
0 A 11
1 B 22
2 C 33
如果 expand=False
,则抛出ValueError
。
>>> s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=False)
ValueError: only one regex group is supported with Index
下面的表格总结了extract (expand=False)
时的行为(输入对象在第一列,捕获组的数量在第一行)
1 group | >1 group |
---|---|
Index | Index |
Series | Series |
提取所有的匹配 (extractall)
v0.18.0. 新加入
不同于 extract
(只返回第一个匹配),
In [89]: s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"])
In [90]: s
Out[90]:
A a1a2
B b1
C c1
dtype: object
In [91]: two_groups = '(?P<letter>[a-z])(?P<digit>[0-9])'
In [92]: s.str.extract(two_groups, expand=True)
Out[92]:
letter digit
A a 1
B b 1
C c 1
extractall
方法返回所有的匹配。extractall
总是返回一个带有行多重索引
的数据表
,最后一级多重索引
被命名为match
,它指出匹配的顺序
In [93]: s.str.extractall(two_groups)
Out[93]:
letter digit
match
A 0 a 1
1 a 2
B 0 b 1
C 0 c 1
当所有的对象字串都只有一个匹配时,
In [94]: s = pd.Series(['a3', 'b3', 'c2'])
In [95]: s
Out[95]:
0 a3
1 b3
2 c2
dtype: object
extractall(pat).xs(0, level='match')
的返回与extract(pat)
相同。
In [96]: extract_result = s.str.extract(two_groups, expand=True)
In [97]: extract_result
Out[97]:
letter digit
0 a 3
1 b 3
2 c 2
In [98]: extractall_result = s.str.extractall(two_groups)
In [99]: extractall_result
Out[99]:
letter digit
match
0 0 a 3
1 0 b 3
2 0 c 2
In [100]: extractall_result.xs(0, level="match")
Out[100]:
letter digit
0 a 3
1 b 3
2 c 2
索引
也支持.str.extractall
。 它返回一个数据表
,其中包含与Series.str.estractall
相同的结果,使用默认索引(从0开始)
v0.19.0. 新加入
In [101]: pd.Index(["a1a2", "b1", "c1"]).str.extractall(two_groups)
Out[101]:
letter digit
match
0 0 a 1
1 a 2
1 0 b 1
2 0 c 1
In [102]: pd.Series(["a1a2", "b1", "c1"]).str.extractall(two_groups)
Out[102]:
letter digit
match
0 0 a 1
1 a 2
1 0 b 1
2 0 c 1
测试匹配或包含模式的字符串
你可以检查是否一个元素包含一个可以匹配到的正则表达式:
In [103]: pattern = r'[0-9][a-z]'
In [104]: pd.Series(['1', '2', '3a', '3b', '03c']).str.contains(pattern)
Out[104]:
0 False
1 False
2 True
3 True
4 True
dtype: bool
或者是否元素完整匹配一个正则表达式
In [105]: pd.Series(['1', '2', '3a', '3b', '03c']).str.match(pattern)
Out[105]:
0 False
1 False
2 True
3 True
4 False
dtype: bool
match
和contains
的区别是是否严格匹配。match
严格基于re.match
,而contains
基于re.search
。
类似match
, contains
, startswith
和 endswith
可以传入一个额外的na
参数,因此,因此缺失值在匹配时可以被认为是True
或者False
:
In [106]: s4 = pd.Series(['A', 'B', 'C', 'Aaba', 'Baca', np.nan, 'CABA', 'dog', 'cat'])
In [107]: s4.str.contains('A', na=False)
Out[107]:
0 True
1 False
2 False
3 True
4 False
5 False
6 True
7 False
8 False
dtype: bool
建立一个指示变量
你从字符串列可以抽出一个哑变量。例如,是否他们由|
分割:
In [108]: s = pd.Series(['a', 'a|b', np.nan, 'a|c'])
In [109]: s.str.get_dummies(sep='|')
Out[109]:
a b c
0 1 0 0
1 1 1 0
2 0 0 0
3 1 0 1
索引也支持get_dummies
,它返回一个多重索引:
v0.18.1. 新加入
In [110]: idx = pd.Index(['a', 'a|b', np.nan, 'a|c'])
In [111]: idx.str.get_dummies(sep='|')
Out[111]:
MultiIndex([(1, 0, 0),
(1, 1, 0),
(0, 0, 0),
(1, 0, 1)],
names=['a', 'b', 'c'])
参见 get_dummies()
(opens new window).
讨论区