作为一名数据科学家,您经常会遇到杂乱无章的非结构化文本数据。在分析这些数据之前,您需要对其进行清理、提取相关信息并将其转换为结构化格式。这正是正则表达式的用武之地。
将正则表达式(regex)视为一种专门用于描述文本模式的微型语言。一旦掌握了核心概念,您就能用短短几行代码执行复杂的文本操作,而使用标准字符串方法则需要数十行代码。
🔗 GitHub上的代码链接。您还可以查看这个快速参考的正则表达式表格。
正则表达式思维模式
掌握正则表达式的关键在于建立正确的思维模式。从本质上讲,正则表达式只是一个从左到右遍历文本、试图寻找匹配项的模式。
想象您正在书中寻找特定模式。您逐页扫描,寻找该模式。这基本上就是正则表达式的工作原理——它逐个字符地扫描文本,检查当前位置是否匹配您的模式。
让我们从导入Python内置的re模块开始:
import re
1. 字面字符:构建您的第一个正则表达式模式
最简单的正则表达式模式匹配精确的文本。如果您想在文本中查找单词"data",可以使用:
text = "Data science is cool as you get to work with real-world data"
matches = re.findall(r"data", text)
print(matches)
注意,这只找到了小写的"data",而忽略了开头大写的"Data"。
输出 >>> ['data']
默认情况下,正则表达式区分大小写。这给我们带来了第一课:要明确指定您想匹配的内容。
matches = re.findall(r"data", text, re.IGNORECASE)
print(matches)
输出 >>> ['Data', 'data']
字符串前面的r表示"原始字符串"。这在正则表达式中很重要,因为反斜杠用于特殊序列,而原始字符串可以防止Python解释这些反斜杠。
2. 元字符:超越字面匹配
使正则表达式有用的是它使用元字符定义模式的能力。这些特殊字符的意义超出了它们的字面表示。
通配符:点号(.)
点号匹配除换行符外的任何字符。当您知道模式的一部分但不确定其他部分时,这特别有用:
text = "The cat sat on the mat. The bat flew over the rat."
pattern = r"The ... "
matches = re.findall(pattern, text)
print(matches)
这里,我们查找"The"后跟任意三个字符和一个空格。
输出 >>> ['The cat ', 'The bat ']
点号非常强大,但有时过于强大——它能匹配任何内容!这就是字符类发挥作用的地方。
字符类:使用[]进行特定匹配
字符类允许您定义要匹配的一组字符:
text = "The cat sat on the mat. The bat flew over the rat."
pattern = r"[cb]at"
matches = re.findall(pattern, text)
print(matches)
此模式查找"cat"或"bat"——集合[cb]中的任何字符后跟"at"。
输出 >>> ['cat', 'bat']
当某个位置可能出现有限的一组字符时,字符类是完美的选择。
您还可以在字符类中使用范围:
# 查找所有以a-d开头的小写单词
pattern = r"\b[a-d][a-z]*\b"
text = "apple banana cherry date elephant fig grape kiwi lemon mango orange"
matches = re.findall(pattern, text)
print(matches)
这里,\b表示单词边界(稍后会详细介绍),[a-d]匹配从a到d的任何小写字母,[a-z]*匹配零个或多个小写字母。
输出 >>> ['apple', 'banana', 'cherry', 'date']
量词:指定重复
通常,您会想匹配重复出现的模式。量词让您可以指定字符或组应该出现的次数。让我们查找所有电话号码,无论它们是否使用连字符:
text = "Phone numbers: 555-1234, 555-5678, 5551234"
pattern = r"\b\d{3}-?\d{4}\b"
matches = re.findall(pattern, text)
print(matches)
这会得到以下结果:
输出 >>> ['555-1234', '555-5678', '5551234']
分解这个模式:
-
\b确保我们在单词边界处
-
\d{3}精确匹配3位数字
-
-?匹配零个或一个连字符(?使连字符可选)
-
\d{4}精确匹配4位数字
-
\b确保我们在另一个单词边界处
这比编写多个模式或复杂的字符串操作来处理不同格式要优雅得多。
3. 锚点:在特定位置查找模式
有时您只想在文本中的特定位置查找模式。锚点可以帮助实现这一点:
text = "Python is popular in data science."
# ^锚定到字符串开头
start_matches = re.findall(r"^Python", text)
print(start_matches)
# $锚定到字符串结尾
end_matches = re.findall(r"science\.$", text)
print(end_matches)
这输出:
['Python'] ['science.']
锚点不匹配字符;它们匹配位置。这对于验证像电子邮件地址这样的格式非常有用,其中特定元素必须出现在开头或结尾。
4. 捕获组:提取特定部分
在数据科学中,您通常不仅想找到模式——还想提取这些模式的特定部分。用圆括号创建的捕获组让您可以做到这一点:
text = "Dates: 2023-10-15, 2022-05-22"
pattern = r"(\d{4})-(\d{2})-(\d{2})"
# findall返回捕获组的元组
matches = re.findall(pattern, text)
print(matches)
# 您可以使用这些创建结构化数据
for year, month, day in matches:
print(f"Year: {year}, Month: {month}, Day: {day}")
这是输出:
[('2023', '10', '15'), ('2022', '05', '22')] Year: 2023, Month: 10, Day: 15 Year: 2022, Month: 05, Day: 22
这对于从非结构化文本中提取结构化信息特别有帮助,这是数据科学中的常见任务。
5. 命名组:使正则表达式更易读
对于复杂模式,记住每个组捕获的内容可能具有挑战性。命名组解决了这个问题:
text = "Contact: john.doe@example.com"
pattern = r"(?P<username>[\w.]+)@(?P<domain>[\w.]+)"
match_ = re.search(pattern, text)
if match_:
print(f"Username: {match.group('username')}")
print(f"Domain: {match.group('domain')}")
这会给出:
Username: john.doe Domain: example.com
命名组使您的正则表达式更自文档化且更易于维护。
处理真实数据:实际示例
让我们看看正则表达式如何应用于常见的数据科学任务。
示例1:清理杂乱数据
假设您有一个产品代码不一致的数据集:
product_codes = [
"PROD-123",
"Product 456",
"prod_789",
"PR-101",
"p-202"
]
您想标准化这些代码以仅提取数字部分:
cleaned_codes = []
for code in product_codes:
# 仅提取数字部分
match = re.search(r"\d+", code)
if match:
cleaned_codes.append(match.group())
print(cleaned_codes)
输出:
['123', '456', '789', '101', '202']
这比编写多个字符串操作来处理不同格式要干净得多。
示例2:从文本中提取信息
假设您有客户服务日志并需要提取信息:
log = "ISSUE #1234 [2023-10-15] Customer reported app crash on iPhone 12, iOS 15.2"
您可以使用正则表达式提取结构化数据:
# 提取问题编号、日期、设备和操作系统版本
pattern = r"ISSUE #(\d+) \[(\d{4}-\d{2}-\d{2})\].*?(iPhone \d+).*?(iOS \d+\.\d+)"
match = re.search(pattern, log)
if match:
issue_num, date, device, ios_version = match.groups()
print(f"Issue: {issue_num}")
print(f"Date: {date}")
print(f"Device: {device}")
print(f"iOS Version: {ios_version}")
输出:
Issue: 1234 Date: 2023-10-15 Device: iPhone 12 iOS Version: iOS 15.2
示例3:数据验证
正则表达式对于验证数据格式很有用:
def validate_email(email):
"""验证电子邮件格式,并解释其有效或无效的原因"""
pattern = r"^[\w.%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$"
if not re.match(pattern, email):
# 检查具体问题
if '@' not in email:
return False, "缺少@符号"
username, domain = email.split('@', 1)
if not username:
return False, "用户名为空"
if '.' not in domain:
return False, "域名无效(缺少顶级域名)"
return False, "电子邮件格式无效"
return True, "有效的电子邮件"
现在用不同的电子邮件测试:
# 用不同的电子邮件测试
emails = ["user@example.com", "invalid@.com", "no_at_sign.com", "user@example.co.uk"]
for email in emails:
valid, reason = validate_email(email)
print(f"{email}: {reason}")
输出:
user@example.com: 有效的电子邮件 invalid@.com: 电子邮件格式无效 no_at_sign.com: 缺少@符号user@example.co.uk: 有效的电子邮件
此函数不仅验证电子邮件,还解释它们有效或无效的原因,这比简单的真/假结果更有用。
高级技术:超越基础正则表达式
随着您对正则表达式越来越熟悉,您会遇到基本模式不够用的情况。以下是一些高级技术:
前瞻和后顾
这些是"零宽度断言",检查模式是否存在而不将其包含在匹配中:
# 密码验证
password = "Password123"
has_uppercase = bool(re.search(r"(?=.*[A-Z])", password))
has_lowercase = bool(re.search(r"(?=.*[a-z])", password))
has_digit = bool(re.search(r"(?=.*\d)", password))
is_long_enough = len(password) >= 8
if all([has_uppercase, has_lowercase, has_digit, is_long_enough]):
print("密码符合要求")
else:
print("密码不符合所有要求")
输出:
密码符合要求
前瞻(?=.*[A-Z])检查字符串中是否有大写字母,而不实际捕获它。
非贪婪匹配
量词默认是"贪婪的",意味着它们尽可能多地匹配。在量词后添加?使其变为"非贪婪":
text = "<div>First content</div><div>Second content</div>"
# 贪婪匹配(默认)
greedy = re.findall(r"<div>(.*)</div>", text)
print(f"贪婪: {greedy}")
# 非贪婪匹配
non_greedy = re.findall(r"<div>(.*?)</div>", text)
print(f"非贪婪: {non_greedy}")
输出:
贪婪: ['First content
Second content'] 非贪婪: ['First content', 'Second content']
理解贪婪和非贪婪匹配之间的区别对于解析嵌套结构(如HTML或JSON)是必要的。
学习和调试正则表达式
在学习正则表达式时:
-
从字面匹配开始:在添加复杂性之前匹配精确字符串
-
添加字符类:学习匹配字符类别
-
掌握量词:理解重复模式
-
使用捕获组:提取结构化数据
-
学习锚点和边界:控制模式的匹配位置
-
探索高级技术:前瞻、非贪婪匹配等
关键是持续学习——从简单的开始,然后根据需要逐渐学习其他内容。
当您的正则表达式不按预期工作时:
-
分解它:测试更简单的模式版本以隔离问题
-
可视化:使用regex101.com等工具逐步查看模式匹配方式
-
使用样本数据测试:创建涵盖不同场景的小测试用例
例如,如果您尝试匹配电话号码但模式不起作用,请先尝试仅匹配区号,然后逐步添加更多组件。
总结
正则表达式是数据科学中文本处理的强大工具。它们允许您:
-
从非结构化文本中提取结构化信息
-
清理和标准化不一致的数据格式
-
根据特定模式验证数据
-
通过复杂的搜索和替换操作转换文本
请记住,正则表达式是一项随着时间发展的技能。不要试图记住每个元字符和技术——相反,专注于理解基本原则,并定期使用真实世界的数据问题进行练习。