使用文本数据#

版本 3.0 中已更改: 字符串的推断和行为在 pandas 3.0 中发生了重大变化。请参阅 新字符串数据类型迁移指南(pandas 3.0)

文本数据类型#

pandas 中存储文本数据有两种方法

  1. StringDtype 扩展类型。

  2. NumPy object 数据类型。

我们建议使用 StringDtype 来存储文本数据,使用别名 dtype="str"(字符串数据类型推断时的默认值),有关更多详细信息,请参阅下文。

在 pandas 1.0 之前,object 数据类型是唯一的选择。这由于多种原因而不幸

  1. 您可能会不小心将字符串和非字符串的混合体存储在 object 数据类型数组中。最好有一个专门的数据类型。

  2. object 数据类型会破坏特定数据类型操作,例如 DataFrame.select_dtypes()。没有明确的方法可以选择文本而排除非文本但仍然是 object 数据类型的列。

  3. 在阅读代码时,object 数据类型数组的内容不如 'string' 清晰。

当使用 StringDtype 并以 PyArrow 作为存储时(见下文),用户将看到与 object 数据类型数组相比,内存使用和某些操作的时间方面都有很大的性能提升。当不使用 PyArrow 作为存储时,StringDtype 的性能与 object 大致相同。我们预计未来的增强功能将显著提高 StringDtype 在此情况下的性能并降低内存开销。

版本 3.0 中已更改: 当 pandas 推断一组字符串的数据类型时,默认使用 dtype='str'。这将使用 np.nan 作为其 NA 值,并在安装了 PyArrow 时由 PyArrow 字符串数组支持,或者在未安装 PyArrow 时由 NumPy object 数组支持。

In [1]: pd.Series(["a", "b", "c"])
Out[1]: 
0    a
1    b
2    c
dtype: str

显式指定 StringDtype#

当希望显式指定数据类型时,如果您希望将 np.nan 作为 NA 值,我们通常建议使用别名 dtype="str";如果您希望将 pd.NA 作为 NA 值,则建议使用别名 dtype="string"

In [2]: pd.Series(["a", "b", None], dtype="str")
Out[2]: 
0      a
1      b
2    NaN
dtype: str

In [3]: pd.Series(["a", "b", None], dtype="string")
Out[3]: 
0       a
1       b
2    <NA>
dtype: string

指定任一别名还将把非字符串数据转换为字符串

In [4]: s = pd.Series(["a", 2, np.nan], dtype="str")

In [5]: s
Out[5]: 
0      a
1      2
2    NaN
dtype: str

In [6]: type(s[1])
Out[6]: str

或从现有 pandas 数据进行转换

In [7]: s1 = pd.Series([1, 2, pd.NA], dtype="Int64")

In [8]: s1
Out[8]: 
0       1
1       2
2    <NA>
dtype: Int64

In [9]: s2 = s1.astype("string")

In [10]: s2
Out[10]: 
0       1
1       2
2    <NA>
dtype: string

In [11]: type(s2[0])
Out[11]: str

但是,有四种不同的 StringDtype 变体可供使用。有关详细信息,请参阅下文的 四种 StringDtype 变体 部分。

字符串方法#

Series 和 Index 装备了一组字符串处理方法,可以轻松地对数组的每个元素进行操作。最重要的是,这些方法会自动排除缺失/NA 值。这些方法通过 str 属性访问,通常名称与等效的(标量)内置字符串方法匹配。

In [12]: s = pd.Series(
   ....:     ["A", "B", "C", "Aaba", np.nan, "dog", "cat"],
   ....:     dtype="str",
   ....: )
   ....: 

In [13]: s.str.lower()
Out[13]: 
0       a
1       b
2       c
3    aaba
4     NaN
5     dog
6     cat
dtype: str

In [14]: s.str.upper()
Out[14]: 
0       A
1       B
2       C
3    AABA
4     NaN
5     DOG
6     CAT
dtype: str

In [15]: s.str.len()
Out[15]: 
0    1.0
1    1.0
2    1.0
3    4.0
4    NaN
5    3.0
6    3.0
dtype: float64
In [16]: idx = pd.Index([" jack", "jill ", " jesse ", "frank"])

In [17]: idx.str.strip()
Out[17]: Index(['jack', 'jill', 'jesse', 'frank'], dtype='str')

In [18]: idx.str.lstrip()
Out[18]: Index(['jack', 'jill ', 'jesse ', 'frank'], dtype='str')

In [19]: idx.str.rstrip()
Out[19]: Index([' jack', 'jill', ' jesse', 'frank'], dtype='str')

Index 上的字符串方法对于清理或转换 DataFrame 列特别有用。例如,您可能有一些列带有前导或尾随空格

In [20]: df = pd.DataFrame(
   ....:     np.random.randn(3, 2),
   ....:     columns=[" Column A ", " Column B "],
   ....:     index=range(3),
   ....: )
   ....: 

In [21]: df
Out[21]: 
   Column A   Column B 
0   0.469112  -0.282863
1  -1.509059  -1.135632
2   1.212112  -0.173215

由于 df.columns 是一个 Index 对象,我们可以使用 .str 访问器

In [22]: df.columns.str.strip()
Out[22]: Index(['Column A', 'Column B'], dtype='str')

In [23]: df.columns.str.lower()
Out[23]: Index([' column a ', ' column b '], dtype='str')

然后可以使用这些字符串方法按需清理列。在这里,我们正在删除前导和尾随空格,将所有名称转换为小写,并将任何剩余的空格替换为下划线。

In [24]: df.columns = df.columns.str.strip().str.lower().str.replace(" ", "_")

In [25]: df
Out[25]: 
   column_a  column_b
0  0.469112 -0.282863
1 -1.509059 -1.135632
2  1.212112 -0.173215

注意

如果您有一个 Series,其中许多元素是重复的(即,Series 中唯一元素的数量远小于 Series 的长度),那么最好先将原始 Series 转换为 category 类型,然后在其上使用 .str.<method>.dt.<property>。性能差异来自于这样一个事实:对于 category 类型的 Series,字符串操作是在 .categories 上执行的,而不是在 Series 的每个元素上执行的。

请注意,具有字符串 .categoriescategory 类型 Series 与字符串类型的 Series 相比存在一些限制(例如,您无法将字符串相加:如果 scategory 类型的 Series,则 s + " " + s 将不起作用)。此外,在这样的 Series 上,无法使用作用于 list 类型元素的 .str 方法。

警告

Series 的类型被推断,并且是允许的类型之一(即字符串)。

总的来说,.str 访问器旨在仅用于字符串。除极少数例外,其他用法不受支持,并可能在以后禁用。

拆分和替换字符串#

split 这样的方法返回一个列表的 Series

In [26]: s2 = pd.Series(["a_b_c", "c_d_e", np.nan, "f_g_h"], dtype="str")

In [27]: s2.str.split("_")
Out[27]: 
0    [a, b, c]
1    [c, d, e]
2          NaN
3    [f, g, h]
dtype: object

可以使用 get[] 符号访问拆分列表中的元素。

In [28]: s2.str.split("_").str.get(1)
Out[28]: 
0      b
1      d
2    NaN
3      g
dtype: object

In [29]: s2.str.split("_").str[1]
Out[29]: 
0      b
1      d
2    NaN
3      g
dtype: object

使用 expand,可以轻松地将其扩展为返回 DataFrame。

In [30]: s2.str.split("_", expand=True)
Out[30]: 
     0    1    2
0    a    b    c
1    c    d    e
2  NaN  NaN  NaN
3    f    g    h

当原始 Series 具有 StringDtype 时,输出列也将全部为 StringDtype

也可以限制拆分的数量

In [31]: s2.str.split("_", expand=True, n=1)
Out[31]: 
     0    1
0    a  b_c
1    c  d_e
2  NaN  NaN
3    f  g_h

rsplit 类似于 split,只是它以相反的方向工作,即从字符串的末尾到字符串的开头。

In [32]: s2.str.rsplit("_", expand=True, n=1)
Out[32]: 
     0    1
0  a_b    c
1  c_d    e
2  NaN  NaN
3  f_g    h

replace 可选使用 正则表达式

In [33]: s3 = pd.Series(
   ....:     ["A", "B", "C", "Aaba", "Baca", "", np.nan, "CABA", "dog", "cat"],
   ....:     dtype="str",
   ....: )
   ....: 

In [34]: s3
Out[34]: 
0       A
1       B
2       C
3    Aaba
4    Baca
5        
6     NaN
7    CABA
8     dog
9     cat
dtype: str

In [35]: s3.str.replace("^.a|dog", "XX-XX ", case=False, regex=True)
Out[35]: 
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: str

版本 2.0 中已更改: 带有 regex=True 的单字符模式也将被视为正则表达式。

In [36]: s4 = pd.Series(["a.b", ".", "b", np.nan, ""], dtype="str")

In [37]: s4
Out[37]: 
0    a.b
1      .
2      b
3    NaN
4       
dtype: str

In [38]: s4.str.replace(".", "a", regex=True)
Out[38]: 
0    aaa
1      a
2      a
3    NaN
4       
dtype: str

如果您想进行字面字符串替换(等同于 str.replace()),您可以将可选的 regex 参数设置为 False,而不是转义每个字符。在这种情况下,patrepl 都必须是字符串。

In [39]: dollars = pd.Series(["12", "-$10", "$10,000"], dtype="str")

# These lines are equivalent
In [40]: dollars.str.replace(r"-\$", "-", regex=True)
Out[40]: 
0         12
1        -10
2    $10,000
dtype: str

In [41]: dollars.str.replace("-$", "-", regex=False)
Out[41]: 
0         12
1        -10
2    $10,000
dtype: str

replace 方法还可以将一个可调用对象作为替换。它将使用 re.sub() 对每个 pat 调用。该可调用对象应该接受一个位置参数(一个正则表达式对象)并返回一个字符串。

# Reverse every lowercase alphabetic word
In [42]: pat = r"[a-z]+"

In [43]: def repl(m):
   ....:     return m.group(0)[::-1]
   ....: 

In [44]: pd.Series(["foo 123", "bar baz", np.nan], dtype="str").str.replace(
   ....:     pat, repl, regex=True
   ....: )
   ....: 
Out[44]: 
0    oof 123
1    rab zab
2        NaN
dtype: str

# Using regex groups
In [45]: pat = r"(?P<one>\w+) (?P<two>\w+) (?P<three>\w+)"

In [46]: def repl(m):
   ....:     return m.group("two").swapcase()
   ....: 

In [47]: pd.Series(["Foo Bar Baz", np.nan], dtype="str").str.replace(
   ....:     pat, repl, regex=True
   ....: )
   ....: 
Out[47]: 
0    bAR
1    NaN
dtype: str

replace 方法还接受来自 re.compile() 的已编译正则表达式对象作为模式。所有标志都应包含在已编译的正则表达式对象中。

In [48]: import re

In [49]: regex_pat = re.compile(r"^.a|dog", flags=re.IGNORECASE)

In [50]: s3.str.replace(regex_pat, "XX-XX ", regex=True)
Out[50]: 
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: str

当使用已编译的正则表达式对象调用 replace 时包含 flags 参数将引发 ValueError

In [51]: s3.str.replace(regex_pat, 'XX-XX ', flags=re.IGNORECASE)
---------------------------------------------------------------------------
ValueError: case and flags cannot be set when pat is a compiled regex

removeprefixremovesuffix 的效果与 Python 3.9 中添加的 str.removeprefixstr.removesuffix 相同。

In [52]: s = pd.Series(["str_foo", "str_bar", "no_prefix"])

In [53]: s.str.removeprefix("str_")
Out[53]: 
0          foo
1          bar
2    no_prefix
dtype: str

In [54]: s = pd.Series(["foo_str", "bar_str", "no_suffix"])

In [55]: s.str.removesuffix("_str")
Out[55]: 
0          foo
1          bar
2    no_suffix
dtype: str

连接#

有几种方法可以连接一个 SeriesIndex(与自身或其他对象),这些方法都基于 cat()Index.str.cat

将单个 Series 连接成一个字符串#

可以连接 Series(或 Index)的内容

In [56]: s = pd.Series(["a", "b", "c", "d"], dtype="str")

In [57]: s.str.cat(sep=",")
Out[57]: 'a,b,c,d'

如果未指定分隔符的 sep 关键字参数,则默认为空字符串 sep=''

In [58]: s.str.cat()
Out[58]: 'abcd'

默认情况下,忽略缺失值。使用 na_rep,可以为它们指定一个表示。

In [59]: t = pd.Series(["a", "b", np.nan, "d"], dtype="str")

In [60]: t.str.cat(sep=",")
Out[60]: 'a,b,d'

In [61]: t.str.cat(sep=",", na_rep="-")
Out[61]: 'a,b,-,d'

将 Series 和类列表对象连接成 Series#

cat() 的第一个参数可以是一个类列表对象,前提是它与调用 Series(或 Index)的长度匹配。

In [62]: s.str.cat(["A", "B", "C", "D"])
Out[62]: 
0    aA
1    bB
2    cC
3    dD
dtype: str

任一侧的缺失值都将在结果中产生缺失值,除非指定了 na_rep

In [63]: s.str.cat(t)
Out[63]: 
0     aa
1     bb
2    NaN
3     dd
dtype: str

In [64]: s.str.cat(t, na_rep="-")
Out[64]: 
0    aa
1    bb
2    c-
3    dd
dtype: str

将 Series 和类数组对象连接成 Series#

参数 others 也可以是二维的。在这种情况下,行数必须与调用 Series(或 Index)的长度匹配。

In [65]: d = pd.concat([t, s], axis=1)

In [66]: s
Out[66]: 
0    a
1    b
2    c
3    d
dtype: str

In [67]: d
Out[67]: 
     0  1
0    a  a
1    b  b
2  NaN  c
3    d  d

In [68]: s.str.cat(d, na_rep="-")
Out[68]: 
0    aaa
1    bbb
2    c-c
3    ddd
dtype: str

将 Series 和带索引的对象连接成 Series,并进行对齐#

对于与 SeriesDataFrame 的连接,可以通过设置 join 关键字来在连接前对齐索引。

In [69]: u = pd.Series(["b", "d", "a", "c"], index=[1, 3, 0, 2], dtype="str")

In [70]: s
Out[70]: 
0    a
1    b
2    c
3    d
dtype: str

In [71]: u
Out[71]: 
1    b
3    d
0    a
2    c
dtype: str

In [72]: s.str.cat(u)
Out[72]: 
0    aa
1    bb
2    cc
3    dd
dtype: str

In [73]: s.str.cat(u, join="left")
Out[73]: 
0    aa
1    bb
2    cc
3    dd
dtype: str

join 的常用选项('left', 'outer', 'inner', 'right' 之一)可用。特别是,对齐也意味着不同长度不必再完全一致。

In [74]: v = pd.Series(["z", "a", "b", "d", "e"], index=[-1, 0, 1, 3, 4], dtype="str")

In [75]: s
Out[75]: 
0    a
1    b
2    c
3    d
dtype: str

In [76]: v
Out[76]: 
-1    z
 0    a
 1    b
 3    d
 4    e
dtype: str

In [77]: s.str.cat(v, join="left", na_rep="-")
Out[77]: 
0    aa
1    bb
2    c-
3    dd
dtype: str

In [78]: s.str.cat(v, join="outer", na_rep="-")
Out[78]: 
-1    -z
 0    aa
 1    bb
 2    c-
 3    dd
 4    -e
dtype: str

othersDataFrame 时,可以使用相同的对齐方式。

In [79]: f = d.loc[[3, 2, 1, 0], :]

In [80]: s
Out[80]: 
0    a
1    b
2    c
3    d
dtype: str

In [81]: f
Out[81]: 
     0  1
3    d  d
2  NaN  c
1    b  b
0    a  a

In [82]: s.str.cat(f, join="left", na_rep="-")
Out[82]: 
0    aaa
1    bbb
2    c-c
3    ddd
dtype: str

将 Series 和多个对象连接成 Series#

可以将多个类数组项(具体来说:SeriesIndexnp.ndarray 的一维变体)组合在一个类列表容器中(包括迭代器、dict 视图等)。

In [83]: s
Out[83]: 
0    a
1    b
2    c
3    d
dtype: str

In [84]: u
Out[84]: 
1    b
3    d
0    a
2    c
dtype: str

In [85]: s.str.cat([u, u.to_numpy()], join="left")
Out[85]: 
0    aab
1    bbd
2    cca
3    ddc
dtype: str

传递的列表类中的所有无索引元素(例如 np.ndarray)必须与调用 Series(或 Index)的长度匹配,但 SeriesIndex 可以具有任意长度(只要通过 join=None 禁用了对齐)。

In [86]: v
Out[86]: 
-1    z
 0    a
 1    b
 3    d
 4    e
dtype: str

In [87]: s.str.cat([v, u, u.to_numpy()], join="outer", na_rep="-")
Out[87]: 
-1    -z--
0     aaab
1     bbbd
2     c-ca
3     dddc
4     -e--
dtype: str

如果在具有不同索引的 others 列表类上使用 join='right',则这些索引的并集将用作最终连接的基础。

In [88]: u.loc[[3]]
Out[88]: 
3    d
dtype: str

In [89]: v.loc[[-1, 0]]
Out[89]: 
-1    z
 0    a
dtype: str

In [90]: s.str.cat([u.loc[[3]], v.loc[[-1, 0]]], join="right", na_rep="-")
Out[90]: 
 3    dd-
-1    --z
 0    a-a
dtype: str

使用 .str 进行索引#

您可以使用 [] 符号直接按位置索引。如果您索引超出字符串末尾,结果将是 NaN

In [91]: s = pd.Series(
   ....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="str"
   ....: )
   ....: 

In [92]: s.str[0]
Out[92]: 
0      A
1      B
2      C
3      A
4      B
5    NaN
6      C
7      d
8      c
dtype: str

In [93]: s.str[1]
Out[93]: 
0    NaN
1    NaN
2    NaN
3      a
4      a
5    NaN
6      A
7      o
8      a
dtype: str

提取子字符串#

提取每个主题中的第一个匹配项(extract)#

extract 方法接受一个至少有一个捕获组的 正则表达式

提取具有多个组的正则表达式会返回一个 DataFrame,其中每组一列。

In [94]: pd.Series(
   ....:     ["a1", "b2", "c3"],
   ....:     dtype="str",
   ....: ).str.extract(r"([ab])(\d)", expand=False)
   ....: 
Out[94]: 
     0    1
0    a    1
1    b    2
2  NaN  NaN

不匹配的元素将返回一个用 NaN 填充的行。因此,可以“转换”一堆混乱的字符串为具有相同索引的、已清理或更有用的字符串的 Series 或 DataFrame,而无需使用 get() 来访问元组或 re.match 对象。结果的数据类型始终是 object,即使没有找到匹配项并且结果仅包含 NaN

命名组,如

In [95]: pd.Series(["a1", "b2", "c3"], dtype="str").str.extract(
   ....:     r"(?P<letter>[ab])(?P<digit>\d)", expand=False
   ....: )
   ....: 
Out[95]: 
  letter digit
0      a     1
1      b     2
2    NaN   NaN

和可选组,如

In [96]: pd.Series(
   ....:     ["a1", "b2", "3"],
   ....:     dtype="str",
   ....: ).str.extract(r"([ab])?(\d)", expand=False)
   ....: 
Out[96]: 
     0  1
0    a  1
1    b  2
2  NaN  3

也可以使用。请注意,正则表达式中的任何捕获组名称都将用于列名;否则将使用捕获组编号。

提取具有一个组的正则表达式会在 expand=True 时返回一个单列的 DataFrame

In [97]: pd.Series(["a1", "b2", "c3"], dtype="str").str.extract(r"[ab](\d)", expand=True)
Out[97]: 
     0
0    1
1    2
2  NaN

expand=False 时,它返回一个 Series。

In [98]: pd.Series(["a1", "b2", "c3"], dtype="str").str.extract(r"[ab](\d)", expand=False)
Out[98]: 
0      1
1      2
2    NaN
dtype: str

使用具有正好一个捕获组的正则表达式调用 Index 时,如果 expand=True,则返回一个单列的 DataFrame

In [99]: s = pd.Series(["a1", "b2", "c3"], ["A11", "B22", "C33"], dtype="str")

In [100]: s
Out[100]: 
A11    a1
B22    b2
C33    c3
dtype: str

In [101]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=True)
Out[101]: 
  letter
0      A
1      B
2      C

expand=False 时,它返回一个 Index

In [102]: s.index.str.extract("(?P<letter>[a-zA-Z])", expand=False)
Out[102]: Index(['A', 'B', 'C'], dtype='str', name='letter')

使用具有多个捕获组的正则表达式调用 Index 时,如果 expand=True,则返回一个 DataFrame

In [103]: s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=True)
Out[103]: 
  letter   1
0      A  11
1      B  22
2      C  33

如果 expand=False,它将引发 ValueError

In [104]: s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=False)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
Cell In[104], line 1
----> 1 s.index.str.extract("(?P<letter>[a-zA-Z])([0-9]+)", expand=False)

File ~/work/pandas/pandas/pandas/core/strings/accessor.py:142, in forbid_nonstring_types.<locals>._forbid_nonstring_types.<locals>.wrapper(self, *args, **kwargs)
    137     msg = (
    138         f"Cannot use .str.{func_name} with values of "
    139         f"inferred dtype '{self._inferred_dtype}'."
    140     )
    141     raise TypeError(msg)
--> 142 return func(self, *args, **kwargs)

File ~/work/pandas/pandas/pandas/core/strings/accessor.py:3420, in StringMethods.extract(self, pat, flags, expand)
   3417     raise ValueError("pattern contains no capture groups")
   3419 if not expand and regex.groups > 1 and isinstance(self._data, ABCIndex):
-> 3420     raise ValueError("only one regex group is supported with Index")
   3422 obj = self._data
   3423 result_dtype = _result_dtype(obj)

ValueError: only one regex group is supported with Index

下表总结了 extract(expand=False) 的行为(第一列为输入主题,第一行为正则表达式的组数)。

1 组

>1 组

索引

索引

ValueError

Series

Series

DataFrame

提取每个主题中的所有匹配项(extractall)#

extract(只返回第一个匹配项)不同,

In [105]: s = pd.Series(["a1a2", "b1", "c1"], index=["A", "B", "C"], dtype="str")

In [106]: s
Out[106]: 
A    a1a2
B      b1
C      c1
dtype: str

In [107]: two_groups = "(?P<letter>[a-z])(?P<digit>[0-9])"

In [108]: s.str.extract(two_groups, expand=True)
Out[108]: 
  letter digit
A      a     1
B      b     1
C      c     1

extractall 方法返回所有匹配项。extractall 的结果始终是一个 DataFrame,其行上有一个 MultiIndexMultiIndex 的最后一个级别被命名为 match,表示在主题中的顺序。

In [109]: s.str.extractall(two_groups)
Out[109]: 
        letter digit
  match             
A 0          a     1
  1          a     2
B 0          b     1
C 0          c     1

当 Series 中的每个主题字符串只有一个匹配项时,

In [110]: s = pd.Series(["a3", "b3", "c2"], dtype="str")

In [111]: s
Out[111]: 
0    a3
1    b3
2    c2
dtype: str

那么 extractall(pat).xs(0, level='match') 的结果与 extract(pat) 相同。

In [112]: extract_result = s.str.extract(two_groups, expand=True)

In [113]: extract_result
Out[113]: 
  letter digit
0      a     3
1      b     3
2      c     2

In [114]: extractall_result = s.str.extractall(two_groups)

In [115]: extractall_result
Out[115]: 
        letter digit
  match             
0 0          a     3
1 0          b     3
2 0          c     2

In [116]: extractall_result.xs(0, level="match")
Out[116]: 
  letter digit
0      a     3
1      b     3
2      c     2

Index 也支持 .str.extractall。它返回一个 DataFrame,其结果与具有默认索引(从 0 开始)的 Series.str.extractall 相同。

In [117]: pd.Index(["a1a2", "b1", "c1"]).str.extractall(two_groups)
Out[117]: 
        letter digit
  match             
0 0          a     1
  1          a     2
1 0          b     1
2 0          c     1

In [118]: pd.Series(["a1a2", "b1", "c1"], dtype="str").str.extractall(two_groups)
Out[118]: 
        letter digit
  match             
0 0          a     1
  1          a     2
1 0          b     1
2 0          c     1

测试与模式匹配或包含字符串#

您可以检查元素是否包含模式

In [119]: pattern = r"[0-9][a-z]"

In [120]: pd.Series(
   .....:     ["1", "2", "3a", "3b", "03c", "4dx"],
   .....:     dtype="str",
   .....: ).str.contains(pattern)
   .....: 
Out[120]: 
0    False
1    False
2     True
3     True
4     True
5     True
dtype: bool

或者元素是否匹配模式

In [121]: pd.Series(
   .....:     ["1", "2", "3a", "3b", "03c", "4dx"],
   .....:     dtype="str",
   .....: ).str.match(pattern)
   .....: 
Out[121]: 
0    False
1    False
2     True
3     True
4    False
5     True
dtype: bool
In [122]: pd.Series(
   .....:     ["1", "2", "3a", "3b", "03c", "4dx"],
   .....:     dtype="str",
   .....: ).str.fullmatch(pattern)
   .....: 
Out[122]: 
0    False
1    False
2     True
3     True
4    False
5    False
dtype: bool

注意

matchfullmatchcontains 之间的区别在于严格性:fullmatch 测试整个字符串是否与正则表达式匹配;match 测试正则表达式是否有匹配项,该匹配项从字符串的第一个字符开始;而 contains 测试字符串中的任何位置是否有匹配项。

Python 的 re 包中这三种匹配模式的对应函数分别是 re.fullmatchre.matchre.search

matchfullmatchcontainsstartswithendswith 这样的方法接受一个额外的 na 参数,因此缺失值可以被视为 True 或 False。

In [123]: s4 = pd.Series(
   .....:     ["A", "B", "C", "Aaba", "Baca", np.nan, "CABA", "dog", "cat"], dtype="str"
   .....: )
   .....: 

In [124]: s4.str.contains("A", na=False)
Out[124]: 
0     True
1    False
2    False
3     True
4    False
5    False
6     True
7    False
8    False
dtype: bool

创建指示变量#

您可以从字符串列中提取虚拟变量。例如,如果它们由 '|' 分隔

In [125]: s = pd.Series(["a", "a|b", np.nan, "a|c"], dtype="str")

In [126]: s.str.get_dummies(sep="|")
Out[126]: 
   a  b  c
0  1  0  0
1  1  1  0
2  0  0  0
3  1  0  1

字符串 Index 也支持 get_dummies,它返回一个 MultiIndex

In [127]: idx = pd.Index(["a", "a|b", np.nan, "a|c"])

In [128]: idx.str.get_dummies(sep="|")
Out[128]: 
MultiIndex([(1, 0, 0),
            (1, 1, 0),
            (0, 0, 0),
            (1, 0, 1)],
           names=['a', 'b', 'c'])

另请参阅 get_dummies()

行为差异#

行为上的差异主要归因于 NA 值的种类。

StringDtype 搭配 np.nan NA 值#

  1. dtype="object" 类似,返回整数输出的 字符串访问器方法 将返回一个 NumPy 数组,该数组根据 NA 值的存在情况,可以是 int 或 float 数据类型。返回布尔值输出的方法将返回一个布尔值数据类型的 NumPy 数组,当遇到 NA 值时,其值为 False

    In [129]: s = pd.Series(["a", None, "b"], dtype="str")
    
    In [130]: s
    Out[130]: 
    0      a
    1    NaN
    2      b
    dtype: str
    
    In [131]: s.str.count("a")
    Out[131]: 
    0    1.0
    1    NaN
    2    0.0
    dtype: float64
    
    In [132]: s.dropna().str.count("a")
    Out[132]: 
    0    1
    2    0
    dtype: int64
    

    当存在 NA 值时,输出数据类型为 float64。但是布尔值输出的结果对于 NA 值是 False

    In [133]: s.str.isdigit()
    Out[133]: 
    0    False
    1    False
    2    False
    dtype: bool
    
    In [134]: s.str.match("a")
    Out[134]: 
    0     True
    1    False
    2    False
    dtype: bool
    
  2. 某些字符串方法,例如 Series.str.decode(),不可用,因为底层数组只能包含字符串,而不能包含字节。

  3. 比较运算将返回一个布尔值数据类型的 NumPy 数组。缺失值将始终比较为不等,就像 np.nan 一样。

StringDtype 搭配 pd.NA NA 值#

  1. 返回整数输出的 字符串访问器方法 将始终返回一个可为空的整数数据类型,而不是 int 或 float 数据类型(取决于 NA 值的存在情况)。返回布尔值输出的方法将返回一个可为空的布尔值数据类型。

    In [135]: s = pd.Series(["a", None, "b"], dtype="string")
    
    In [136]: s
    Out[136]: 
    0       a
    1    <NA>
    2       b
    dtype: string
    
    In [137]: s.str.count("a")
    Out[137]: 
    0       1
    1    <NA>
    2       0
    dtype: Int64
    
    In [138]: s.dropna().str.count("a")
    Out[138]: 
    0    1
    2    0
    dtype: Int64
    

    这两个输出都是 Int64 数据类型。返回布尔值的方法也是如此。

    In [139]: s.str.isdigit()
    Out[139]: 
    0    False
    1     <NA>
    2    False
    dtype: boolean
    
    In [140]: s.str.match("a")
    Out[140]: 
    0     True
    1     <NA>
    2    False
    dtype: boolean
    
  2. 由于底层数组只能包含字符串,而不能包含字节,因此像 Series.str.decode() 这样的某些字符串方法不可用。

  3. 比较运算将返回一个具有 BooleanDtype 的对象,而不是一个 bool 数据类型的对象。缺失值将在比较运算中传播,而不是像 numpy.nan 那样总是比较为不等。

重要提示

本文档其余部分后续内容同样适用于 'str''string'object 数据类型。

四种 StringDtype 变体#

用户可以使用四种 StringDtype 变体,这些变体由 StringDtypestoragena_value 参数控制。在运行时,可以通过 StringDtype.storageStringDtype.na_value 属性检查这些。

Python 存储搭配 np.nan#

注意

这与 dtype='str' 在未安装 PyArrow 时相同。

实现使用 NumPy 对象数组,它直接存储 Python 字符串对象,因此这里的存储被称为 'python'。此数组中的 NA 值被表示为 np.nan 并以此行为。

In [141]: pd.Series(
   .....:     ["a", "b", None, np.nan, pd.NA],
   .....:     dtype=pd.StringDtype(storage="python", na_value=np.nan)
   .....: )
   .....: 
Out[141]: 
0      a
1      b
2    NaN
3    NaN
4    NaN
dtype: str

请注意,最后三个值都被 pandas 推断为 NA 值,因此存储为 np.nan

PyArrow 存储搭配 np.nan#

注意

这与 dtype='str' 在安装了 PyArrow 时相同。

实现使用 PyArrow 数组,但是此数组中的 NA 值被表示为 np.nan 并以此行为。

In [142]: pd.Series(
   .....:     ["a", "b", None, np.nan, pd.NA],
   .....:     dtype=pd.StringDtype(storage="pyarrow", na_value=np.nan)
   .....: )
   .....: 
Out[142]: 
0      a
1      b
2    NaN
3    NaN
4    NaN
dtype: str

请注意,最后三个值都被 pandas 推断为 NA 值,因此存储为 np.nan

Python 存储搭配 pd.NA#

注意

这与 dtype='string' 在未安装 PyArrow 时相同。

实现使用 NumPy 对象数组,它直接存储 Python 字符串对象,因此这里的存储被称为 'python'。此数组中的 NA 值被表示为 np.nan 并以此行为。

In [143]: pd.Series(
   .....:     ["a", "b", None, np.nan, pd.NA],
   .....:     dtype=pd.StringDtype(storage="python", na_value=pd.NA)
   .....: )
   .....: 
Out[143]: 
0       a
1       b
2    <NA>
3    <NA>
4    <NA>
dtype: string

请注意,最后三个值都被 pandas 推断为 NA 值,因此存储为 pd.NA

PyArrow 存储搭配 pd.NA#

注意

这与 dtype='string' 在安装了 PyArrow 时相同。

实现使用 PyArrow 数组。此数组中的 NA 值被表示为 pd.NA 并以此行为。

In [144]: pd.Series(
   .....:     ["a", "b", None, np.nan, pd.NA],
   .....:     dtype=pd.StringDtype(storage="python", na_value=pd.NA)
   .....: )
   .....: 
Out[144]: 
0       a
1       b
2    <NA>
3    <NA>
4    <NA>
dtype: string

请注意,最后三个值都被 pandas 推断为 NA 值,因此存储为 pd.NA

方法摘要#

方法

描述

cat()

连接字符串

split()

按分隔符拆分字符串

rsplit()

按分隔符拆分字符串,从字符串末尾开始

get()

索引到每个元素(检索第 i 个元素)

join()

用指定的分隔符连接 Series 中每个元素的字符串

get_dummies()

按分隔符拆分字符串,返回虚拟变量 DataFrame

contains()

如果每个字符串包含模式/正则表达式,则返回布尔数组

replace()

用另一个字符串或给定出现的函数返回的字符串替换模式/正则表达式/字符串的出现

removeprefix()

从字符串中删除前缀,即仅当字符串以该前缀开头时才删除。

removesuffix()

从字符串中删除后缀,即仅当字符串以该后缀结尾时才删除。

repeat()

重复值 (s.str.repeat(3) 等同于 x * 3)

pad()

在字符串的两侧添加空格

center()

等同于 str.center

ljust()

等同于 str.ljust

rjust()

等同于 str.rjust

zfill()

等同于 str.zfill

wrap()

将长字符串拆分成长度小于给定宽度的行

slice()

切片 Series 中的每个字符串

slice_replace()

用提供的值替换每个字符串中的切片

count()

计算模式的出现次数

startswith()

对每个元素等同于 str.startswith(pat)

endswith()

对每个元素等同于 str.endswith(pat)

findall()

计算每个字符串中模式/正则表达式的所有匹配项列表

match()

对每个元素调用 re.match,返回匹配的组作为列表

extract()

对每个元素调用 re.search,返回一个 DataFrame,其中每个元素占一行,每个正则表达式捕获组占一列

extractall()

对每个元素调用 re.findall,返回一个 DataFrame,其中每个匹配项占一行,每个正则表达式捕获组占一列

len()

计算字符串长度

strip()

等同于 str.strip

rstrip()

等同于 str.rstrip

lstrip()

等同于 str.lstrip

partition()

等同于 str.partition

rpartition()

等同于 str.rpartition

lower()

等同于 str.lower

casefold()

等同于 str.casefold

upper()

等同于 str.upper

find()

等同于 str.find

rfind()

等同于 str.rfind

index()

等同于 str.index

rindex()

等同于 str.rindex

capitalize()

等同于 str.capitalize

swapcase()

等同于 str.swapcase

normalize()

返回 Unicode 规范形式。等同于 unicodedata.normalize

translate()

等同于 str.translate

isalnum()

等同于 str.isalnum

isalpha()

等同于 str.isalpha

isdigit()

等同于 str.isdigit

isspace()

等同于 str.isspace

islower()

等同于 str.islower

isupper()

等同于 str.isupper

istitle()

等同于 str.istitle

isnumeric()

等同于 str.isnumeric

isdecimal()

等同于 str.isdecimal