1 文本数据类型
在 pandas
中,存储文本主要有两种方式
object
类型StringDtype
扩展类型
但一般建议使用 StringDtype
类型存储文本数据。都是由于各种原因,现在字符串数据的默认存储类型还是 object
。
In [1]: pd.Series(["a", "b", "c"])
Out[1]:
0 a
1 b
2 c
dtype: object
要存储为 string
类型,需要显式的设置 dtype
参数
In [2]: pd.Series(["a", "b", "c"], dtype="string")
Out[2]:
0 a
1 b
2 c
dtype: string
In [3]: pd.Series(["a", "b", "c"], dtype=pd.StringDtype())
Out[3]:
0 a
1 b
2 c
dtype: string
或者在创建 Series
或 DataFrame
之后,使用 astype
转换类型
In [4]: s = pd.Series(["a", "b", "c"])
In [5]: s
Out[5]:
0 a
1 b
2 c
dtype: object
In [6]: s.astype("string")
Out[6]:
0 a
1 b
2 c
dtype: string
也可以使用 StringDtype/"string"
转换其他非字符串类型的数据
In [7]: s = pd.Series(["a", 2, np.nan], dtype="string")
In [8]: s
Out[8]:
0 a
1 2
2 <NA>
dtype: string
In [9]: type(s[1])
Out[9]: str
转换现有数据的类型
In [10]: s1 = pd.Series([1, 2, np.nan], dtype="Int64")
In [11]: s1
Out[11]:
0 1
1 2
2 <NA>
dtype: Int64
In [12]: s2 = s1.astype("string")
In [13]: s2
Out[13]:
0 1
1 2
2 <NA>
dtype: string
In [14]: type(s2[0])
Out[14]: str
1.1 行为差异
StringDtype
类型对象与 object
类型之间存在一些差异
- 对于
StringDtype
,对于返回数值型输出字符串方法将始终返回非空的integer
类型。而不是int
或float
类型。对于布尔型输出方法,返回可空的布尔类型
In [15]: s = pd.Series(["a", None, "b"], dtype="string")
In [16]: s
Out[16]:
0 a
1 <NA>
2 b
dtype: string
In [17]: s.str.count("a")
Out[17]:
0 1
1 <NA>
2 0
dtype: Int64
In [18]: s.dropna().str.count("a")
Out[18]:
0 1
2 0
dtype: Int64
两个结果的输出都是 Int64
类型。将其与 object
类型比较
In [19]: s2 = pd.Series(["a", None, "b"], dtype="object")
In [20]: s2.str.count("a")
Out[20]:
0 1.0
1 NaN
2 0.0
dtype: float64
In [21]: s2.dropna().str.count("a")
Out[21]:
0 1
2 0
dtype: int64
当存在 NA
值时,输出为 float64
。类似地,对于返回布尔值的方法
In [22]: s.str.isdigit()
Out[22]:
0 False
1 <NA>
2 False
dtype: boolean
In [23]: s.str.match("a")
Out[23]:
0 True
1 <NA>
2 False
dtype: boolean
-
一些字符串方法,如
Series.str.decode()
在StringArray
上是不可用的。因为StringArray
只保存字符串,而不是字节 -
在比较操作中,
arrays.StringArray
和StringArray
支持的Series
将返回一个具有BooleanDtype
的对象,而不是一个bool
对象。StringArray
中的缺失值会在比较操作中传播,而不是像numpy.nan
那样总是比较不等
2 字符串方法
Series
和 Index
有一套字符串处理方法,可以方便地对数组的每个元素进行操作,最重要的是,这些方法会自动忽略缺失值。
这些方法可以通过 str
属性访问,通常具有与内置字符串方法相匹配的名称
In [24]: s = pd.Series(
....: ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
....: )
....:
In [25]: s.str.lower()
Out[25]:
0 a
1 b
2 c
3 aaba
4 baca
5 <NA>
6 caba
7 dog
8 cat
dtype: string
In [26]: s.str.upper()
Out[26]:
0 A
1 B
2 C
3 AABA
4 BACA
5 <NA>
6 CABA
7 DOG
8 CAT
dtype: string
In [27]: s.str.len()
Out[27]:
0 1
1 1
2 1
3 4
4 4
5 <NA>
6 4
7 3
8 3
dtype: Int64
In [28]: idx = pd.Index([" jack", "jill ", " jesse ", "frank"])
In [29]: idx.str.strip()
Out[29]: Index(['jack', 'jill', 'jesse', 'frank'], dtype='object')
In [30]: idx.str.lstrip()
Out[30]: Index(['jack', 'jill ', 'jesse ', 'frank'], dtype='object')
In [31]: idx.str.rstrip()
Out[31]: Index([' jack', 'jill', ' jesse', 'frank'], dtype='object')
Index
上的字符串方法对于清理或转换 DataFrame
的列特别有用。
例如,您可能有带有前导或后置空格的列
In [32]: df = pd.DataFrame(
....: np.random.randn(3, 2), columns=[" Column A ", " Column B "], index=range(3)
....: )
....:
In [33]: df
Out[33]:
Column A Column B
0 0.469112 -0.282863
1 -1.509059 -1.135632
2 1.212112 -0.173215
因为 df.columns
是一个 Index
对象,所以我们可以使用 .str
访问器
In [34]: df.columns.str.strip()
Out[34]: Index(['Column A', 'Column B'], dtype='object')
In [35]: df.columns.str.lower()
Out[35]: Index([' column a ', ' column b '], dtype='object')
我们可以根据需要对列名进行处理,然后重新设置列名。
例如,我们删除列名的前后空格,并将其改为小写字母,同时用 _
替换剩余的空格
In [36]: df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")
In [37]: df
Out[37]:
column_a column_b
0 0.469112 -0.282863
1 -1.509059 -1.135632
2 1.212112 -0.173215
3 切割和替换字符串
split
方法会返回一个值为 list
的 Series
In [38]: s2 = pd.Series(["a_b_c", "c_d_e", np.nan, "f_g_h"], dtype="string")
In [39]: s2.str.split("_")
Out[39]:
0 [a, b, c]
1 [c, d, e]
2 <NA>
3 [f, g, h]
dtype: object
可以使用 get
或 []
访问拆分后的列表中的元素
In [40]: s2.str.split("_").str.get(1)
Out[40]:
0 b
1 d
2 <NA>
3 g
dtype: object
In [41]: s2.str.split("_").str[1]
Out[41]:
0 b
1 d
2 <NA>
3 g
dtype: object
更简单的方法是设置 expand
参数,返回一个 DataFrame
In [42]: s2.str.split("_", expand=True)
Out[42]:
0 1 2
0 a b c
1 c d e
2 <NA> <NA> <NA>
3 f g h
当原来的 Series
包含 StringDtype
类型的数据时,输出列也将全部为 StringDtype
当然,也可以设置切割次数
In [43]: s2.str.split("_", expand=True, n=1)
Out[43]:
0 1
0 a b_c
1 c d_e
2 <NA> <NA>
3 f g_h
它还有个对应的 rsplit
方法,从右边起始对字符串进行拆分
In [44]: s2.str.rsplit("_", expand=True, n=1)
Out[44]:
0 1
0 a_b c
1 c_d e
2 <NA> <NA>
3 f_g h
replace
参数支持使用正则表达式,前两个参数是 pat
(匹配模式) 和 repl
(替换字符串)
In [45]: s3 = pd.Series(
....: ["A", "B", "C", "Aaba", "Baca", "", np.nan, "CABA", "dog", "cat"],
....: dtype="string",
....: )
....:
In [46]: s3
Out[46]:
0 A
1 B
2 C
3 Aaba
4 Baca
5
6 <NA>
7 CABA
8 dog
9 cat
dtype: string
In [47]: s3.str.replace("^.a|dog", "XX-XX ", case=False, regex=True)
Out[47]:
0 A
1 B
2 C
3 XX-XX ba
4 XX-XX ca
5
6 <NA>
7 XX-XX BA
8 XX-XX
9 XX-XX t
dtype: string
如果只是想要替换字符串字面值,可以将 regex
参数设置为 False
,而不需要对每个特殊字符进行转义。此时 pat
和 repl
参数必须是字符串
In [48]: dollars = pd.Series(["12", "-$10", "$10,000"], dtype="string")
# These lines are equivalent
In [49]: dollars.str.replace(r"-\$", "-", regex=True)
Out[49]:
0 12
1 -10
2 $10,000
dtype: string
In [50]: dollars.str.replace("-$", "-", regex=False)
Out[50]:
0 12
1 -10
2 $10,000
dtype: string
此外,replace
方法还接受一个可调用的替换函数,会使用 re.sub()
方法在每个匹配的模式上调用该函数
该函数需要传入一个正则对象作为位置参数,并返回一个字符串。例如
# 反转每个小写单词的顺序
In [51]: pat = r"[a-z]+"
In [52]: def repl(m):
....: return m.group(0)[::-1]
....:
In [53]: pd.Series(["foo 123", "bar baz", np.nan], dtype="string").str.replace(
....: pat, repl, regex=True
....: )
....:
Out[53]:
0 oof 123
1 rab zab
2 <NA>
dtype: string
# 使用 regex 捕获分组
In [54]: pat = r"(?P<one>\w+) (?P<two>\w+) (?P<three>\w+)"
In [55]: def repl(m):
....: return m.group("two").swapcase()
....:
In [56]: pd.Series(["Foo Bar Baz", np.nan], dtype="string").str.replace(
....: pat, repl, regex=True
....: )
....:
Out[56]:
0 bAR
1 <NA>
dtype: string
replace
方法的 pat
参数还接受 re.compile()
编译的正则表达式对象。所有的 flags
需要在编译正则对象时设置
In [57]: import re
In [58]: regex_pat = re.compile(r"^.a|dog", flags=re.IGNORECASE)
In [59]: s3.str.replace(regex_pat, "XX-XX ", regex=True)
Out[59]:
0 A
1 B
2 C
3 XX-XX ba
4 XX-XX ca
5
6 <NA>
7 XX-XX BA
8 XX-XX
9 XX-XX t
dtype: string
如果在 replace
中设置 flags
参数,则会抛出异常
In [60]: s3.str.replace(regex_pat, 'XX-XX ', flags=re.IGNORECASE)
---------------------------------------------------------------------------
ValueError: case and flags cannot be set when pat is a compiled regex
4 连接
有几种方法可以将一个 Series
或 Index
与自己或其他的 Series
或 Index
相连接,所有这些方法都是基于 cat()
方法
4.1 将单个 Series 对象连接成字符串
可以连接一个 Series
或 Index
的内容
In [61]: s = pd.Series(["a", "b", "c", "d"], dtype="string")
In [62]: s.str.cat(sep=",")
Out[62]: 'a,b,c,d'
如果未指定 sep
参数,则默认为空字符串
In [63]: s.str.cat()
Out[63]: 'abcd'
默认会跳过缺失值,也可以使用 na_rep
指定缺失值的表示方式
In [64]: t = pd.Series(["a", "b", np.nan, "d"], dtype="string")
In [65]: t.str.cat(sep=",")
Out[65]: 'a,b,d'
In [66]: t.str.cat(sep=",", na_rep="-")
Out[66]: 'a,b,-,d'
4.2 连接 Series 与一个类似列表的对象
cat()
的第一个参数 others
可以是类似列表的对象,但是其长度需要和调用对象一致
In [67]: s.str.cat(["A", "B", "C", "D"])
Out[67]:
0 aA
1 bB
2 cC
3 dD
dtype: string
只要两个对象中存在缺失值,对应的结果中也是缺失值,除非指定了 na_rep
In [68]: s.str.cat(t)
Out[68]:
0 aa
1 bb
2 <NA>
3 dd
dtype: string
In [69]: s.str.cat(t, na_rep="-")
Out[69]:
0 aa
1 bb
2 c-
3 dd
dtype: string
4.3 连接 Series 与多个类似列表的对象
others
参数也可以是二维的,但是得保证其行数必须与调用的对象一致
In [70]: d = pd.concat([t, s], axis=1)
In [71]: s
Out[71]:
0 a
1 b
2 c
3 d
dtype: string
In [72]: d
Out[72]:
0 1
0 a a
1 b b
2 <NA> c
3 d d
In [73]: s.str.cat(d, na_rep="-")
Out[73]:
0 aaa
1 bbb
2 c-c
3 ddd
dtype: string
4.4 连接一个 Series 和一个带索引的对象
对于 Series
或 DataFrame
的连接,可以通过设置 join
参数指定对齐方式
In [74]: u = pd.Series(["b", "d", "a", "c"], index=[1, 3, 0, 2], dtype="string")
In [75]: s
Out[75]:
0 a
1 b
2 c
3 d
dtype: string
In [76]: u
Out[76]:
1 b
3 d
0 a
2 c
dtype: string
In [77]: s.str.cat(u)
Out[77]:
0 aa
1 bb
2 cc
3 dd
dtype: string
In [78]: s.str.cat(u, join="left")
Out[78]:
0 aa
1 bb
2 cc
3 dd
dtype: string
通常 join
可选范围为: 'left'
, 'outer'
, 'inner'
, 'right'
。此时,不再要求两个对象长度一致
In [79]: v = pd.Series(["z", "a", "b", "d", "e"], index=[-1, 0, 1, 3, 4], dtype="string")
In [80]: s
Out[80]:
0 a
1 b
2 c
3 d
dtype: string
In [81]: v
Out[81]:
-1 z
0 a
1 b
3 d
4 e
dtype: string
In [82]: s.str.cat(v, join="left", na_rep="-")
Out[82]:
0 aa
1 bb
2 c-
3 dd
dtype: string
In [83]: s.str.cat(v, join="outer", na_rep="-")
Out[83]:
-1 -z
0 aa
1 bb
2 c-
3 dd
4 -e
dtype: string
当 others
参数是 DataFrame
时,也可以使用
In [84]: f = d.loc[[3, 2, 1, 0], :]
In [85]: s
Out[85]:
0 a
1 b
2 c
3 d
dtype: string
In [86]: f
Out[86]:
0 1
3 d d
2 <NA> c
1 b b
0 a a
In [87]: s.str.cat(f, join="left", na_rep="-")
Out[87]:
0 aaa
1 bbb
2 c-c
3 ddd
dtype: string
4.5 连接 Series 和多个对象
可以将一些类似数组的对象(如 Series
,Index
等)放在一个类似列表的容器中,然后传递给 cat
In [88]: s
Out[88]:
0 a
1 b
2 c
3 d
dtype: string
In [89]: u
Out[89]:
1 b
3 d
0 a
2 c
dtype: string
In [90]: s.str.cat([u, u.to_numpy()], join="left")
Out[90]:
0 aab
1 bbd
2 cca
3 ddc
dtype: string
对于没有索引的对象,其长度必须与调用 cat
的对象相同。但是 Series
和 Index
可以是任意的,除非设置了 json=None
。
In [91]: v
Out[91]:
-1 z
0 a
1 b
3 d
4 e
dtype: string
In [92]: s.str.cat([v, u, u.to_numpy()], join="outer", na_rep="-")
Out[92]:
-1 -z--
0 aaab
1 bbbd
2 c-ca
3 dddc
4 -e--
dtype: string
如果在 others
参数上包含不同索引的对象,且设置了 join='right'
,则最后的结果将会是这些索引的并集
In [93]: u.loc[[3]]
Out[93]:
3 d
dtype: string
In [94]: v.loc[[-1, 0]]
Out[94]:
-1 z
0 a
dtype: string
In [95]: s.str.cat([u.loc[[3]], v.loc[[-1, 0]]], join="right", na_rep="-")
Out[95]:
-1 --z
0 a-a
3 dd-
dtype: string
5 使用 .str 索引
可以使用 []
符号直接按位置进行索引,如果索引超过字符串的长度,结果将是 NaN
In [96]: s = pd.Series(
....: ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
....: )
....:
In [97]: s.str[0]
Out[97]:
0 A
1 B
2 C
3 A
4 B
5 <NA>
6 C
7 d
8 c
dtype: string
In [98]: s.str[1]
Out[98]:
0 <NA>
1 <NA>
2 <NA>
3 a
4 a
5 <NA>
6 A
7 o
8 a
dtype: string
6 提取子字符串
6.1 提取第一个匹配项(extract)
在 0.23
版本之前,extract
方法的参数 expand
默认为 False
。当 expand=False
时,expand
会根据正则表达式模式返回一个 Series
、Index
或 DataFrame
当 expand=True
时,它总是返回一个 DataFrame
,这种方式更加符合用户的需求,从 0.23.0
版本开始就是默认的
extract
方法接受一个至少包含一个捕获组的正则表达式
如果是包含多个组的正则表达式将返回一个 DataFrame
,每个捕获组是一列
In [99]: pd.Series(
....: ["a1", "b2", "c3"],
....: dtype="string",
....: ).str.extract(r"([ab])(\d)", expand=False)
....:
Out[99]:
0 1
0 a 1
1 b 2
2 <NA> <NA>
未匹配的行会填充 NaN
,可以从混乱的字符串序列中提取出有规则的信息。
对于命名分组
In [100]: pd.Series(["a1", "b2", "c3"], dtype="string").str.extract(
.....: r"(?P<letter>[ab])(?P<digit>\d)", expand=False
.....: )
.....:
Out[100]:
letter digit
0 a 1
1 b 2
2 <NA> <NA>
对于可选的分组
In [101]: pd.Series(
.....: ["a1", "b2", "3"],
.....: dtype="string",
.....: ).str.extract(r"([ab])?(\d)", expand=False)
.....:
Out[101]:
0 1
0 a 1
1 b 2
2 <NA> 3
注意:正则表达式中的任何捕获组名称都将用作列名,否则将使用捕获组号
如果 expand=True
,则返回一个 DataFrame
In [102]: pd.Series(["a1", "b2", "c3"], dtype="string").str.extract(r"[ab](\d)", expand=True)
Out[102]:
0
0 1
1 2
2 <NA>
如果 expand=False
,则返回一个 Series
In [103]: pd.Series(["a1", "b2", "c3"], dtype="string").str.extract(r"[ab](\d)", expand=False)
Out[103]:
0 1
1 2
2 <NA>
dtype: string
对于索引,如果 expand=True
,且只有一个捕获组则返回一个只有一列的 DataFrame
In [104]: s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"], dtype="string")
In [105]: s
Out[105]:
A11 a1
B22 b2
C33 c3
dtype: string
In [106]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=True)
Out[106]:
letter
0 A
1 B
2 C
此时,如果 expand=False
将会返回一个 Index
In [107]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=False)
Out[107]: Index(['A', 'B', 'C'], dtype='object', name='letter')
对于索引,正则表达式设置多个分组将返回 DataFrame
In [108]: s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=True)
Out[108]:
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
6.2 提取所有匹配(extractall)
对于 extract
只返回第一个匹配项
In [109]: s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"], dtype="string")
In [110]: s
Out[110]:
A a1a2
B b1
C c1
dtype: string
In [111]: two_groups = "(?P<letter>[a-z])(?P<digit>[0-9])"
In [112]: s.str.extract(two_groups, expand=True)
Out[112]:
letter digit
A a 1
B b 1
C c 1
与 extract
不同,extractall
方法返回每个匹配项,其结果始终是具有 MultiIndex
的 DataFrame
。
MultiIndex
的最后一级名为 match
,标示的是匹配的顺序
In [113]: s.str.extractall(two_groups)
Out[113]:
letter digit
match
A 0 a 1
1 a 2
B 0 b 1
C 0 c 1
对于只有一个匹配的 Series
In [114]: s = pd.Series(["a3", "b3", "c2"], dtype="string")
In [115]: s
Out[115]:
0 a3
1 b3
2 c2
dtype: string
extractall(pat).xs(0, level='match')
与 extract(pat)
的结果一致
In [116]: extract_result = s.str.extract(two_groups, expand=True)
In [117]: extract_result
Out[117]:
letter digit
0 a 3
1 b 3
2 c 2
In [118]: extractall_result = s.str.extractall(two_groups)
In [119]: extractall_result
Out[119]:
letter digit
match
0 0 a 3
1 0 b 3
2 0 c 2
In [120]: extractall_result.xs(0, level="match")
Out[120]:
letter digit
0 a 3
1 b 3
2 c 2
Index
也支持 .str.extractall
,它返回一个 DataFrame
,其结果与 Series.str
相同。
In [121]: pd.Index(["a1a2", "b1", "c1"]).str.extractall(two_groups)
Out[121]:
letter digit
match
0 0 a 1
1 a 2
1 0 b 1
2 0 c 1
In [122]: pd.Series(["a1a2", "b1", "c1"], dtype="string").str.extractall(two_groups)
Out[122]:
letter digit
match
0 0 a 1
1 a 2
1 0 b 1
2 0 c 1
7 测试字符串匹配与包含
您可以检查字符串元素中是否包含正则匹配模式
In [123]: pattern = r"[0-9][a-z]"
In [124]: pd.Series(
.....: ["1", "2", "3a", "3b", "03c", "4dx"],
.....: dtype="string",
.....: ).str.contains(pattern)
.....:
Out[124]:
0 False
1 False
2 True
3 True
4 True
5 True
dtype: boolean
或者字符串元素是否与模式匹配
In [125]: pd.Series(
.....: ["1", "2", "3a", "3b", "03c", "4dx"],
.....: dtype="string",
.....: ).str.match(pattern)
.....:
Out[125]:
0 False
1 False
2 True
3 True
4 False
5 True
dtype: boolean
而在 1.1.0
版本中
In [126]: pd.Series(
.....: ["1", "2", "3a", "3b", "03c", "4dx"],
.....: dtype="string",
.....: ).str.fullmatch(pattern)
.....:
Out[126]:
0 False
1 False
2 True
3 True
4 False
5 False
dtype: boolean
注意:
match
、fullmatch
和 contains
之间的区别是:
fullmatch
:测试整个字符串是否与正则表达式完全匹配match
:测试字符串的开头是否与正则表达式匹配contains
:测试字符串中的任何位置是否存在正则表达式的匹配
这三个函数于 re
模块的 re.fullmatch
、re.match
和 re.search
对应
像 match
, fullmatch
, contains
, startswith
和 endswith
有一个额外的 na
参数,用于将缺失值替换为 True
或 False
In [127]: s4 = pd.Series(
.....: ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="string"
.....: )
.....:
In [128]: s4.str.contains("A", na=False)
Out[128]:
0 True
1 False
2 False
3 True
4 False
5 False
6 True
7 False
8 False
dtype: boolean
8 创建指标变量
您可以从字符串列中提取指标变量。例如,如果使用 '|'
分隔的字符串
In [129]: s = pd.Series(["a", "a|b", np.nan, "a|c"], dtype="string")
In [130]: s.str.get_dummies(sep="|")
Out[130]:
a b c
0 1 0 0
1 1 1 0
2 0 0 0
3 1 0 1
字符串 Index
也支持 get_dummies
,它返回一个 MultiIndex
In [131]: idx = pd.Index(["a", "a|b", np.nan, "a|c"])
In [132]: idx.str.get_dummies(sep="|")
Out[132]:
MultiIndex([(1, 0, 0),
(1, 1, 0),
(0, 0, 0),
(1, 0, 1)],
names=['a', 'b', 'c'])