Pandas使用教程 - 正则表达式在 Pandas 中的应用
目录
- 基础篇15:正则表达式在 Pandas 中的应用
- 1. 正则表达式简介
- 1.1 什么是正则表达式?
- 1.2 常见正则表达式语法
- 2. Pandas 中的字符串方法与正则表达式
- 2.1 str.contains()
- 2.2 str.extract()
- 2.3 str.findall()
- 2.4 str.replace()
- 2.5 str.match()
- 3. 正则表达式在数据清洗中的常见应用
- 3.1 清洗脏数据
- 3.2 提取特定模式
- 3.3 替换不符合规范的字符
- 3.4 数据拆分
- 4. 结合 Pandas 应用正则表达式的实战技巧
- 4.1 向量化操作
- 4.2 使用捕获组提取数据
- 4.3 替换时使用反向引用
- 4.4 配合 apply() 进行自定义处理
- 5. 实战案例:利用正则表达式进行数据清洗与特征提取
- 5.1 案例背景
- 5.2 数据读取与预览
- 5.3 提取电子邮件地址
- 5.4 替换电话号码为统一格式
- 5.5 综合清洗操作
- 6. 进阶技巧与注意事项
- 6.1 注意正则表达式的性能
- 6.2 处理缺失值(NaN)
- 6.3 使用 raw 字符串
- 6.4 调试正则表达式
- 6.5 多步骤处理
- 7. 综合案例:从用户评论中提取关键信息
- 7.1 数据准备
- 7.2 提取电子邮件地址
- 7.3 统一电话号码格式
- 7.4 提取 URL 信息
- 7.5 统计域名频次
- 7.6 整合输出
- 8. 总结
基础篇15:正则表达式在 Pandas 中的应用
在数据预处理中,经常会遇到文本数据中的各种模式匹配、提取和替换问题。正则表达式(Regular Expression)作为一种强大的文本匹配工具,能够描述字符串匹配规则,在数据清洗、格式转换、特征提取以及文本挖掘等方面发挥重要作用。Pandas 通过 Series.str 模块将正则表达式功能与向量化字符串操作结合,使得批量处理文本数据变得既高效又简洁。
本文将详细介绍正则表达式的基本概念、常见语法规则以及如何在 Pandas 中应用正则表达式完成各种操作,并结合多个实例展示其实际应用场景。
1. 正则表达式简介
1.1 什么是正则表达式?
正则表达式(Regular Expression,简称 regex)是一种用于描述和匹配字符串模式的工具。它允许你定义一个模式,然后用这个模式去搜索、替换或拆分字符串。数学上,可以将正则表达式看作一种描述字符串集合
L
L
L 的方法,例如:
L
=
{
w
∣
w
满足正则表达式
R
}
L = \{ w \mid w \text{ 满足正则表达式 } R \}
L={w∣w 满足正则表达式 R}
其中,
R
R
R 表示一个正则表达式,
w
w
w 为满足该模式的字符串。
正则表达式在文本数据处理中的常见应用包括:
- 匹配:判断字符串是否符合某个模式。
- 提取:从字符串中抽取符合模式的子串。
- 替换:将符合模式的部分替换为新的字符串。
- 拆分:根据匹配模式对字符串进行分割。
1.2 常见正则表达式语法
以下是一些常见的正则表达式语法规则及示例:
.
:匹配除换行符外的任意字符。例如:a.c
可匹配 “abc”、“aXc”等。*
:匹配前一个字符出现 0 次或多次,例如ab*c
可匹配 “ac”、“abc”、“abbc” 等。+
:匹配前一个字符出现 1 次或多次,例如ab+c
必须至少出现一次 b。?
:匹配前一个字符出现 0 次或 1 次。[]
:匹配括号内的任一字符,例如[abc]
匹配 “a”、“b” 或 “c”;[0-9]
匹配任一数字。^
与$
:分别匹配字符串的开头和结尾,例如^Hello
匹配以 “Hello” 开头的字符串;world!$
匹配以 “world!” 结尾的字符串。\d
、\w
、\s
:分别匹配数字、字母数字下划线以及空白字符;其对应的大写形式\D
、\W
、\S
匹配相反的情况。{m,n}
:匹配前一个字符至少出现 m 次,至多 n 次,例如a{2,4}
匹配 “aa”、“aaa”、“aaaa”。|
:表示逻辑“或”,例如cat|dog
匹配 “cat” 或 “dog”。- 分组:使用小括号
()
对表达式进行分组,便于提取和引用。例如:(ab)+
匹配 “ab”、“abab” 等。
这些语法构成了正则表达式的基础,通过组合这些规则可以构造出非常灵活的匹配模式。
2. Pandas 中的字符串方法与正则表达式
Pandas 为 Series 和 DataFrame 中的字符串操作提供了一整套向量化的方法,所有这些方法都位于 .str
访问器下。常见的方法包括:
- str.contains():判断每个字符串是否包含某个正则表达式模式,返回布尔 Series。
- str.match():判断字符串是否在起始位置匹配给定正则表达式,返回布尔 Series。
- str.extract():根据正则表达式提取捕获组,返回一个 DataFrame。
- str.findall():返回所有匹配正则表达式的子串,结果为列表形式。
- str.replace():使用正则表达式替换字符串中匹配部分。
- str.split():按照正则表达式拆分字符串,返回列表或 Series。
这些方法均支持传入参数 regex=True
(默认启用正则表达式)以及各种标志参数(如 flags=re.IGNORECASE
等),从而实现灵活的文本匹配和转换。
2.1 str.contains()
str.contains()
方法用于检测字符串中是否存在匹配模式。示例:
import pandas as pd
s = pd.Series(["apple", "banana", "cherry", "date"])
# 检查是否包含字母 a(忽略大小写)
result = s.str.contains("a", case=False)
print(result)
输出:
0 True
1 True
2 False
3 False
dtype: bool
该方法常用于数据过滤,例如筛选出含有特定关键词的行。[citeturn0search0]
2.2 str.extract()
str.extract()
用于从字符串中提取符合捕获组的部分。示例:
s = pd.Series(["Order 1234", "Invoice 5678", "Receipt 91011"])
# 提取数字部分
extracted = s.str.extract("(\d+)")
print(extracted)
输出:
0
0 1234
1 5678
2 91011
可以看出,正则表达式 (\d+)
捕获了连续数字序列,返回结果为 DataFrame。
数学上,若字符串
s
s
s 中存在数字序列,则提取结果为:
KaTeX parse error: Undefined control sequence: \d at position 26: …{extract}(s, "(\̲d̲+)")
2.3 str.findall()
str.findall()
返回字符串中所有匹配正则表达式的子串,结果为列表形式:
s = pd.Series(["abc123def", "456ghi789", "no digits here"])
matches = s.str.findall("\d+")
print(matches)
输出:
0 [123]
1 [456, 789]
2 []
dtype: object
这种方法适用于需要获取字符串中所有符合条件的信息时使用。
2.4 str.replace()
str.replace()
方法支持正则表达式替换操作,可以将匹配到的子串替换成指定字符串:
s = pd.Series(["hello 123 world", "test 456 case"])
# 将所有数字替换为 #
replaced = s.str.replace("\d+", "#", regex=True)
print(replaced)
输出:
0 hello # world
1 test # case
dtype: object
这里,正则表达式 \d+
匹配连续数字序列,替换为 “#”。
2.5 str.match()
str.match()
判断字符串是否从开头匹配给定模式,与 contains 不同,它只检查起始部分:
s = pd.Series(["abc123", "123abc", "abc456"])
# 判断是否以 abc 开头
match_result = s.str.match("abc")
print(match_result)
输出:
0 True
1 False
2 True
dtype: bool
3. 正则表达式在数据清洗中的常见应用
在实际数据预处理中,我们经常需要利用正则表达式解决以下问题:
3.1 清洗脏数据
有时,数据中可能包含不必要的字符或格式错误的数据。正则表达式可以帮助去除多余的空格、特殊符号,或者替换错误格式的数据。例如,清洗电话号码中的空格和横线:
phone_series = pd.Series(["(123) 456-7890", "123-456 7890", "1234567890"])
# 移除括号、空格和横线
clean_phone = phone_series.str.replace("[\(\)\-\s]", "", regex=True)
print(clean_phone)
输出:
0 1234567890
1 1234567890
2 1234567890
dtype: object
3.2 提取特定模式
例如,从一段文本中提取出电子邮件地址。假设数据集中包含用户评论,评论中嵌入了邮箱地址,我们可以利用正则表达式提取:
comments = pd.Series([
"请联系我,邮箱:user@example.com,谢谢!",
"没有邮箱信息",
"另一个邮箱:test.user@domain.org,可用于反馈。"
])
emails = comments.str.extract("([\w\.-]+@[\w\.-]+\.\w+)")
print(emails)
输出可能为:
0
0 user@example.com
1 NaN
2 test.user@domain.org
正则表达式 ([\w\.-]+@[\w\.-]+\.\w+)
用于匹配电子邮件地址格式。
3.3 替换不符合规范的字符
在文本数据中,有时需要将非 ASCII 字符、特殊符号等替换掉,或者统一格式。例如,将中文全角符号替换为半角:
text_series = pd.Series(["ABC,123!", "测试,全角标点。"])
# 使用正则表达式替换全角逗号和句号为半角
converted = text_series.str.replace(",", ",", regex=True).str.replace("。", ".", regex=True)
print(converted)
输出:
0 ABC,123!
1 测试,半角标点.
dtype: object
(注:实际应用中可能需要更复杂的映射,此处仅为示例。)
3.4 数据拆分
对于一些合并在一起的数据,如地址信息、姓名等,也可以利用正则表达式拆分出有用的信息。例如,将带有分隔符的字符串拆分为多个部分:
address_series = pd.Series(["北京市-海淀区-中关村", "上海市-浦东新区-陆家嘴"])
split_addresses = address_series.str.split("-", expand=True)
print(split_addresses)
输出:
0 1 2
0 北京市 海淀区 中关村
1 上海市 浦东新区 陆家嘴
4. 结合 Pandas 应用正则表达式的实战技巧
在 Pandas 中使用正则表达式主要依赖于向量化的字符串方法,这不仅大大提高了处理速度,还使得代码更加简洁。下面介绍几个常见的技巧和注意事项:
4.1 向量化操作
Pandas 的字符串方法都是向量化的,这意味着你可以一次性对整个 Series 应用正则表达式,而无需遍历每个元素。例如:
df["clean_text"] = df["raw_text"].str.replace(r"\s+", " ", regex=True)
此代码将 raw_text
列中所有连续空白字符替换为一个空格,既简洁又高效。
4.2 使用捕获组提取数据
利用 str.extract()
方法时,正则表达式中的括号用于捕获需要提取的部分。捕获组可以为数据提取提供命名:
# 命名捕获组提取日期:格式为 YYYY-MM-DD
df["date_extracted"] = df["text"].str.extract(r"(?P<date>\d{4}-\d{2}-\d{2})")
这样提取出的列名为 “date_extracted”,便于后续使用。
4.3 替换时使用反向引用
在使用 str.replace()
进行替换时,可以利用反向引用 $1、$2 等来引用捕获组。例如,假设需要将 “abc123” 形式的字符串转换为 “123abc”:
s = pd.Series(["abc123", "def456"])
# 将前三个字母和后面的数字交换位置
rearranged = s.str.replace(r"([a-zA-Z]{3})(\d+)", r"\2\1", regex=True)
print(rearranged)
输出:
0 123abc
1 456def
dtype: object
这里,\1
和 \2
分别表示第一个和第二个捕获组的内容。
4.4 配合 apply() 进行自定义处理
虽然 Pandas 的内置字符串方法已经非常强大,但有时我们需要进行更为复杂的正则处理,这时可以结合 apply() 和 lambda 表达式来实现。例如,对某一列进行多步正则匹配和替换:
def custom_clean(text):
# 首先移除所有 HTML 标签
text = pd.Series(text).str.replace(r"<.*?>", "", regex=True).iloc[0]
# 然后替换连续的空格为单个空格
text = pd.Series(text).str.replace(r"\s+", " ", regex=True).iloc[0]
return text
df["clean_text"] = df["raw_text"].apply(custom_clean)
这种方式灵活度较高,但在数据量较大时可能会略微降低性能。
5. 实战案例:利用正则表达式进行数据清洗与特征提取
下面通过一个实际案例,展示如何利用 Pandas 中的正则表达式方法对文本数据进行清洗和特征提取。
5.1 案例背景
假设我们有一份包含用户反馈信息的 CSV 文件,其中有一列 “feedback” 存储了用户的评论内容。评论中可能包含电子邮件地址、电话号码以及其他噪音信息。我们需要对这列数据进行清洗,提取出电子邮件地址,并将评论中的电话号码替换为统一格式。
5.2 数据读取与预览
首先读取数据:
import pandas as pd
# 模拟用户反馈数据
data_feedback = {
"id": [1, 2, 3, 4],
"feedback": [
"请联系我,邮箱 user@example.com,电话:138-1234-5678。",
"反馈不错,但我的号码是(010) 88886666,请回复。",
"无有效联系方式。",
"邮件:test.user@domain.org; 电话:+86 139 8765 4321。"
]
}
df_feedback = pd.DataFrame(data_feedback)
print("原始反馈数据:")
print(df_feedback)
输出结果:
id feedback
0 1 请联系我,邮箱 user@example.com,电话:138-1234-5678。
1 2 反馈不错,但我的号码是(010) 88886666,请回复。
2 3 无有效联系方式。
3 4 邮件:test.user@domain.org; 电话:+86 139 8765 4321。
5.3 提取电子邮件地址
利用 str.extract()
提取反馈中的电子邮件地址:
# 提取电子邮件地址的正则表达式:匹配字母、数字、点、连字符组合的邮箱格式
df_feedback["email"] = df_feedback["feedback"].str.extract(r"([\w\.-]+@[\w\.-]+\.\w+)")
print("提取后的电子邮件地址:")
print(df_feedback[["id", "email"]])
输出结果:
id email
0 1 user@example.com
1 2 NaN
2 3 NaN
3 4 test.user@domain.org
可以看到,第 0 行和第 4 行成功提取了电子邮件地址。
5.4 替换电话号码为统一格式
针对电话号码的格式多样性,我们可以先用正则表达式匹配常见格式,然后将其替换为统一的格式(例如,所有数字连续形式)。示例:
import re
# 定义一个函数,将匹配到的电话号码中的非数字字符去除
def normalize_phone(text):
# 使用正则表达式匹配电话号码(简单示例:匹配包含数字、空格、括号、加号、横线的组合)
pattern = r"([\+\d\(\)\-\s]{7,})"
# 替换操作:将匹配到的部分进行处理
return re.sub(pattern, lambda m: re.sub(r"\D", "", m.group(0)), text)
# 对反馈文本中的电话号码进行替换
df_feedback["feedback_clean"] = df_feedback["feedback"].str.replace(r"([\+\d\(\)\-\s]{7,})", lambda m: re.sub(r"\D", "", m.group(0)), regex=True)
print("清洗后的反馈文本:")
print(df_feedback[["id", "feedback_clean"]])
运行后,反馈文本中的电话号码将被替换为仅包含数字的格式。例如,“138-1234-5678” 会变成 “13812345678”,“(010) 88886666” 变成 “01088886666”。这种统一格式便于后续数据存储或进一步分析。
5.5 综合清洗操作
我们还可以将多步清洗操作组合在一起,利用链式调用和 pipe() 方法提高代码可读性。例如,对反馈文本同时提取电子邮件和规范电话号码:
def clean_feedback(df):
# 提取电子邮件地址
df["email"] = df["feedback"].str.extract(r"([\w\.-]+@[\w\.-]+\.\w+)")
# 替换电话号码中的非数字字符
df["feedback_clean"] = df["feedback"].str.replace(r"([\+\d\(\)\-\s]{7,})", lambda m: re.sub(r"\D", "", m.group(0)), regex=True)
return df
df_feedback_clean = df_feedback.pipe(clean_feedback)
print("综合清洗后的反馈数据:")
print(df_feedback_clean)
通过 pipe() 链式调用,可以使代码更具模块化和可维护性。
6. 进阶技巧与注意事项
在 Pandas 中使用正则表达式进行数据处理时,还有一些进阶技巧和常见注意事项需要掌握:
6.1 注意正则表达式的性能
正则表达式虽然功能强大,但在大数据量下计算开销较大。建议:
- 尽量使用向量化的字符串方法,而不是对每个元素使用 Python 循环;
- 对于复杂正则表达式,考虑提前编译正则表达式(使用 re.compile()),以提高重复匹配的效率;
- 如果只需要简单匹配,尽量使用简单模式,避免过多使用贪婪匹配等可能导致性能下降的模式。
6.2 处理缺失值(NaN)
在使用 Pandas 字符串方法时,如果 Series 中存在 NaN 值,这些方法通常会返回 NaN。可以先使用 fillna() 方法填充缺失值:
df["feedback"] = df["feedback"].fillna("")
这样可以避免在正则操作中产生错误或不期望的结果。
6.3 使用 raw 字符串
在编写正则表达式时,建议使用 Python 的原始字符串(以 r 开头的字符串),这样可以避免转义字符问题。例如:
pattern = r"\d+"
而非 "\\d+"
。
6.4 调试正则表达式
对于复杂的正则表达式,可以先在单个字符串上测试,再将其应用于整个 Series。借助在线正则表达式工具(如 regex101.com)也能快速调试和验证正则表达式的正确性。
6.5 多步骤处理
对于复杂文本清洗任务,建议将正则处理拆分为多个步骤,每步单独调用 Pandas 的字符串方法或自定义函数,这样更易于调试和维护。利用 pipe() 或 apply() 可以帮助实现这种模块化设计。
7. 综合案例:从用户评论中提取关键信息
为加深理解,下面给出一个综合案例,假设我们有一份用户评论数据,评论中可能包含电子邮件、电话号码以及 URL。我们的目标是:
- 提取评论中的电子邮件地址。
- 将评论中的电话号码统一为纯数字格式。
- 提取 URL,并统计各个域名的出现频次。
7.1 数据准备
import pandas as pd
import re
data_comments = {
"评论": [
"联系我:user1@example.com,电话:138-1234-5678,访问 https://www.example.com 获取更多信息。",
"请发送邮件至 contact@domain.org 或拨打(010) 88886666。",
"无联系方式,请访问 http://testsite.net。",
"用户反馈: 邮件 test.user@service.cn; 电话:+86 139 8765 4321; 更多请看 https://service.cn/about。"
]
}
df_comments = pd.DataFrame(data_comments)
print("原始评论数据:")
print(df_comments)
7.2 提取电子邮件地址
df_comments["email"] = df_comments["评论"].str.extract(r"([\w\.-]+@[\w\.-]+\.\w+)")
7.3 统一电话号码格式
电话号码可能有多种格式,统一方式为提取所有数字:
df_comments["phone"] = df_comments["评论"].str.extract(r"([\+\d\(\)\-\s]{7,})")
# 对提取到的电话号码去除所有非数字字符
df_comments["phone"] = df_comments["phone"].apply(lambda x: re.sub(r"\D", "", x) if pd.notna(x) else x)
7.4 提取 URL 信息
利用正则表达式提取 URL,并进一步解析域名:
# 提取 URL,简单模式示例:匹配 http 或 https 开头的 URL
df_comments["url"] = df_comments["评论"].str.extract(r"(https?://[\w\.-/]+)")
# 提取域名部分
df_comments["domain"] = df_comments["url"].str.extract(r"https?://([\w\.-]+)")
7.5 统计域名频次
利用 Pandas 的 value_counts 方法统计域名出现次数:
domain_counts = df_comments["domain"].value_counts()
print("域名出现频次:")
print(domain_counts)
7.6 整合输出
将所有提取结果整合输出:
print("综合清洗后的评论数据:")
print(df_comments[["email", "phone", "url", "domain"]])
通过这一系列操作,我们能够从用户评论中自动提取出关键信息,为后续的数据分析和建模提供有效特征。
8. 总结
本文详细讲解了正则表达式在 Pandas 中的应用,主要内容包括:
- 正则表达式基础:介绍了正则表达式的基本概念和常用语法,如字符匹配、数量词、捕获组、反向引用等。
- Pandas 字符串方法:重点说明了 Pandas 中的 str.contains、str.extract、str.findall、str.replace 和 str.match 等方法,并展示了如何利用这些方法实现批量文本匹配、提取和替换。
- 应用场景:通过清洗电话号码、提取电子邮件地址、拆分地址信息等案例,展示了正则表达式在数据清洗、格式统一和特征提取中的实际应用。
- 进阶技巧:讨论了使用向量化操作、捕获组、反向引用以及结合 apply() 实现自定义正则处理的高级方法,同时强调了性能优化、缺失值处理和调试技巧。
- 综合实战案例:以用户评论数据为例,展示了如何一步步利用正则表达式提取电子邮件、规范电话号码、提取 URL 并统计域名频次,最终得到清洗后的数据,为后续分析打下基础。
- 流程图示意:利用 Mermaid 语法绘制了数据清洗的基本流程图,帮助读者直观理解各个步骤之间的关系。
正则表达式作为一种灵活且高效的文本处理工具,与 Pandas 的向量化字符串方法相结合,为大规模文本数据清洗和特征提取提供了极大的便利。无论是数据科学家、数据分析师还是机器学习工程师,掌握正则表达式的应用都是提升数据处理效率和质量的重要技能。
在实际项目中,建议:
- 充分利用 Pandas 的内置字符串方法来实现向量化正则处理;
- 在遇到复杂文本处理任务时,合理拆分步骤并结合自定义函数进行逐步调试;
- 注意正则表达式的性能问题,对于大规模数据,建议先使用 re.compile() 预编译正则表达式;
- 使用在线工具(如 regex101.com)测试和验证正则表达式,以确保其正确性;
- 结合数据可视化工具,直观展示正则处理前后的数据对比,从而验证数据清洗效果。
总之,正则表达式在 Pandas 中的应用为数据清洗和特征提取提供了强大的支持。希望本文能够帮助你全面理解并灵活运用正则表达式技术,在实际数据处理中高效提取和清洗文本数据,从而为后续分析和建模打下坚实基础。