首页 / Pandas 入门教程 / 文本进阶:正则与新字符串类型

Pandas 入门教程

文本进阶:正则与新字符串类型

本教程共 54 篇 · 第 49 篇 · 更新于 2026-08-11 · 约 7 分钟阅读

PandasPandas 入门教程正则表达式StringDtype文本处理

本节目标:学会用正则表达式做文本匹配、提取与替换,并搞懂 pandas 3.0 的 StringDtype 新字符串类型与 object 的区别。

接 §48(文本数据 .str 访问器):上节讲了 .str 的日常方法,这节补两块硬核内容——正则表达式的三个主力方法,以及 pandas 3.0 全新默认的字符串类型 StringDtype。

正则三件套:contains、extract、replace

正则表达式(regex)是描述”字符串模式”的语言。比如 \d+ 表示”一个或多个数字”,[a-z]+ 表示”一串小写字母”。.str 访问器的方法大多支持正则,其中三个最常用。

contains:判断是否包含

str.contains 判断每个字符串是否匹配模式,返回布尔 Series。它和 Python 的 re.search 是同一套规则:字符串里任何位置匹配上,就算 True。

import pandas as pd

s = pd.Series(["订单A123", "订单B456", "无编号", None], dtype="str")
print(s.str.contains(r"\d"))
# 0     True
# 1     True
# 2    False
# 3      NaN
# dtype: boolean

两个参数要记住:

  • na=:缺失值怎么处理。默认返回 NaN,设成 na=False 就返回 False,方便直接当布尔索引用。
  • case=False:忽略大小写。想同时匹配 “abc” 和 “ABC” 时很省事。

contains 还有两个”亲戚”:str.match 要求从字符串开头匹配,str.fullmatch 要求整个字符串完全匹配。严格程度排序:contains(任意位置)< match(开头)< fullmatch(整体)。判断”整行是否都是手机号”这类需求,fullmatch 最合适。

extract:提取匹配内容

str.extract 把匹配到的内容抠出来,正则需要用括号圈出”捕获组”。一组对应一列,多组返回 DataFrame:

import pandas as pd

s = pd.Series(["2024-01-15", "2024-02-20", "日期丢失"], dtype="str")
df = s.str.extract(r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})")
print(df)
#    year month  day
# 0  2024    01   15
# 1  2024    02   20
# 2   NaN   NaN  NaN

(?P<year>...) 是命名捕获组,名字直接变成列名。没匹配上的行整行是 NaN——这正是清洗脏数据的好工具:一条字符串列,拆成干净的 year、month、day 三列。只有一个捕获组时,expand=False 返回 Series 而不是 DataFrame。

一个字符串里有多处匹配时,str.extract 只取第一处。想要全部,用 str.extractall,返回带 MultiIndex 的 DataFrame,最后一层叫 match 表示第几次匹配:

import pandas as pd

s = pd.Series(["a1a2", "b1", "c1"], dtype="str")
print(s.str.extractall(r"(?P<letter>[a-z])(?P<digit>\d)"))
#         letter digit
#   match
# 0 0          a     1
#   1          a     2
# 1 0          b     1
# 2 0          c     1

replace:按模式替换

str.replace 默认就开正则(regex=True)。想按字面替换,明确写 regex=False 更稳:

import pandas as pd

s = pd.Series(["价格:$100", "价格:$200", "价格:0"], dtype="str")
print(s.str.replace(r"\$\d+", "***", regex=True))
# 0    价格:***
# 1    价格:***
# 2     价格:0

# regex=False:点号按普通字符处理,不会匹配任意字符
t = pd.Series(["a.b", "b.c", "c"], dtype="str")
print(t.str.replace(".", "-", regex=False))
# 0    a-b
# 1    b-c
# 2      c

替换串里可以用 \1\2 引用捕获组;也可以传一个函数,收到匹配对象后返回替换文本,适合”把匹配到的内容加工后再替换”的场景。

Tip

正则写多了容易眼花。先在纯 Python 里用 re.findall 调试好模式,再搬进 .str 方法。. 匹配任意字符、$ 是行尾锚点,这些元字符要匹配字面含义必须转义——用 regex=False 能避开一大半转义问题。

常用正则小抄

不用背,用到查表:

模式含义例子
\d数字\d{4} 匹配 4 位数字
\w字母、数字、下划线\w+ 匹配一个”单词”
\s空白(空格、Tab、换行)\s+ 匹配连续空白
.任意字符(换行除外)a.c 能匹配 “abc”
^ / $开头 / 结尾^订单 匹配以”订单”开头
[ab]字符集合,a 或 b[0-9] 匹配一位数字
{n,m}重复 n 到 m 次\d{2,4} 匹配 2 到 4 位数字
`(xy)`分支:x 或 y

StringDtype:3.0 的新默认字符串类型

pandas 3.0 之前,字符串列默认是 NumPy 的 object 类型——它其实是个”杂物间”,什么 Python 对象都能往里塞。3.0 起字符串有了自己的类型:StringDtype,别名 "str"。你什么都不用做,只要创建字符串 Series,pandas 就自动推断成它:

import pandas as pd

s = pd.Series(["a", "b", None])
print(s.dtype)
# str

# 缺失值统一用 NaN,和 float 列一致
print(s)
# 0      a
# 1      b
# 2    NaN
# dtype: str

StringDtype 的几个关键特性:

  • 只存字符串:往里面塞数字直接报错,object 时代”一列混着字符串和数字”的乱象没有了。
  • 缺失值统一为 NaN:之前 object 列里 None、NaN 混着来,现在全部归一成 NaN。
  • 底层优先用 PyArrow:装了 pyarrow 包时,字符串由 Arrow 存储,内存占用和运算速度都优于 object;没装则退回 NumPy object 存储,行为不变。
  • astype("str") 保留缺失:旧版 astype(str) 会把 NaN 变成字符串 “nan”,3.0 起缺失值保持缺失。想”全部转成字符串”,用 s.map(str)

和 object 对比,一句话总结:

对比项object(旧)StringDtype(3.0 默认)
能存什么任意 Python 对象只能字符串 + 缺失值
缺失值None、NaN 混杂统一 NaN
内存效率高(PyArrow 存储时)
dtype 显示objectstr
Warning

两个容易踩的坑:一是用 s.dtype == "object" 判断字符串列会失效,改用 s.dtype == "str"pd.api.types.is_string_dtype(s.dtype);二是 3.0 里往字符串列赋值非字符串值会抛 TypeError,确实需要混存时先 s.astype("object")

还有两个细节。取底层数据时,s.values 不再返回 NumPy 数组而是 ExtensionArray,想要 NumPy 数组用 s.to_numpy();按类型筛列时,df.select_dtypes(include=["str"]) 能精准选出字符串列。还在用 pandas 2.x 的读者想提前体验新类型,可以设 pd.options.future.infer_string = True

还有个容易混的别名 "string":它同样代表 StringDtype,但缺失值用 pd.NA 而不是 NaN。日常用默认的 "str" 就够了。

小结

正则让 .str 从”会切菜”升级到”能按菜谱找食材”:contains 判断、extract 提取、replace 替换,三件套覆盖绝大多数文本清洗场景。StringDtype 则是 3.0 的地基变化——字符串不再是 object 的”租客”,有了自己的家,更省内存也更安全。下一节讲分类数据 Categorical,它和字符串类型配合,是处理”取值有限”的文本列的两大法宝。