MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python正则表达式在数据清洗中的应用

2023-06-156.1k 阅读

Python正则表达式基础

正则表达式简介

正则表达式是一种用于描述、匹配和操作字符串模式的强大工具。在Python中,通过re模块来支持正则表达式操作。正则表达式使用特殊的字符和语法来定义字符串模式,它能够高效地从文本中提取特定信息、验证数据格式以及进行文本替换等操作。

例如,常见的正则表达式模式\d表示匹配任意一个数字字符,[a-zA-Z]表示匹配任意一个英文字母字符。这些基本的模式元素可以组合起来,形成复杂的匹配规则。

Python的re模块

Python的re模块提供了一系列函数来处理正则表达式。常用的函数包括:

  • re.search(pattern, string):在字符串中搜索匹配正则表达式模式的第一个位置,返回一个匹配对象,如果没有匹配则返回None
import re
match = re.search(r'\d+', 'abc123def')
if match:
    print(match.group())

上述代码中,r'\d+'表示匹配一个或多个数字,re.search在字符串'abc123def'中搜索到了123,并通过match.group()获取到匹配的内容。

  • re.match(pattern, string):从字符串的开头开始匹配正则表达式模式,返回一个匹配对象,如果开头不匹配则返回None
import re
match = re.match(r'abc', 'abc123')
if match:
    print(match.group())

这里re.match从字符串'abc123'开头匹配'abc',匹配成功并输出abc。如果字符串是'123abc',则re.match会返回None

  • re.findall(pattern, string):在字符串中查找所有匹配正则表达式模式的子串,并以列表形式返回。
import re
matches = re.findall(r'\d+', 'abc123def456')
print(matches)

该代码会在字符串'abc123def456'中找到所有数字子串['123', '456']并输出。

  • re.sub(pattern, repl, string):将字符串中所有匹配正则表达式模式的子串替换为指定的字符串repl
import re
new_string = re.sub(r'\d+', 'X', 'abc123def456')
print(new_string)

此代码将字符串中的数字子串替换为'X',输出abcXdefX

数据清洗中的常见问题与正则表达式应用场景

数据格式标准化

在实际的数据收集过程中,数据格式往往不统一。例如日期格式,可能会出现2023-01-0101/01/20232023年1月1日等多种形式。通过正则表达式可以将这些不同格式的日期统一转化为一种标准格式。

假设我们要将mm/dd/yyyy格式的日期转化为yyyy - mm - dd格式。

import re


def convert_date_format(date_str):
    match = re.match(r'(\d{2})/(\d{2})/(\d{4})', date_str)
    if match:
        month, day, year = match.groups()
        return f'{year}-{month}-{day}'
    return date_str


date1 = '01/15/2023'
print(convert_date_format(date1))

上述代码中,re.match使用正则表达式(\d{2})/(\d{2})/(\d{4})匹配mm/dd/yyyy格式的日期,通过match.groups()获取到月、日、年的部分,然后重新组合成标准格式。

去除无效字符

数据中常常包含一些无效字符,比如HTML标签、特殊符号等。以去除HTML标签为例,HTML标签通常以<开始,以>结束。

import re


def remove_html_tags(html_str):
    return re.sub(r'<.*?>', '', html_str)


html_content = '<p>这是一段包含HTML标签的文本</p>'
print(remove_html_tags(html_content))

这里re.sub使用正则表达式<.*?>匹配HTML标签,将其替换为空字符串,从而去除了HTML标签,输出这是一段包含HTML标签的文本

数据提取与筛选

从非结构化文本中提取特定信息也是数据清洗的重要环节。例如,从一篇新闻文章中提取所有的邮箱地址。邮箱地址的一般格式为用户名@域名,可以用正则表达式来匹配。

import re


def extract_emails(text):
    return re.findall(r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+', text)


article = '联系我们请发邮件至test@example.com或者info@domain.net'
print(extract_emails(article))

re.findall使用上述正则表达式在文章中搜索邮箱地址,输出['test@example.com', 'info@domain.net']

复杂数据清洗场景下的正则表达式应用

嵌套结构数据处理

在处理一些包含嵌套结构的数据时,正则表达式也能发挥作用。比如处理嵌套的括号表达式,假设我们有一个字符串(a+(b*c)),要提取最内层括号内的表达式。

import re


def extract_inner_brackets(expr):
    pattern = r'\(([^()]*)\)'
    while re.search(pattern, expr):
        match = re.search(pattern, expr)
        inner_expr = match.group(1)
        expr = expr.replace(match.group(0), inner_expr)
    return expr


expr1 = '(a+(b*c))'
print(extract_inner_brackets(expr1))

上述代码中,通过不断匹配最内层括号\(([^()]*)\),并将其替换为括号内的内容,最终得到去除外层括号后的表达式a+(b*c)

多条件组合匹配

有时需要根据多个条件来匹配和清洗数据。例如,我们要从一段文本中提取所有符合特定格式的身份证号码,身份证号码一般为18位数字,最后一位可能是数字或X。同时,要排除一些明显错误的号码,比如全部为0的号码。

import re


def validate_and_extract_idnums(text):
    pattern = r'(?!0{18})(\d{17}[\dXx])'
    return re.findall(pattern, text)


text_with_idnums = '这里有身份证号11010519491231002X和000000000000000000'
print(validate_and_extract_idnums(text_with_idnums))

这里正则表达式(?!0{18})(\d{17}[\dXx])首先使用(?!0{18})来排除全部为0的情况,然后匹配17位数字加最后一位数字或X的身份证号码格式,最终输出['11010519491231002X']

处理不规范数据

实际数据中常常存在不规范的情况,比如电话号码可能包含空格、短横线等分隔符,且位数可能有变化。假设我们要处理国内手机号码,一般为11位数字,但可能存在如138 - 1234 - 5678这样的格式。

import re


def clean_phone_number(phone_str):
    phone_str = re.sub(r'[ -]', '', phone_str)
    if re.fullmatch(r'1[3-9]\d{9}', phone_str):
        return phone_str
    return None


phone1 = '138 - 1234 - 5678'
phone2 = '12345678901'
print(clean_phone_number(phone1))
print(clean_phone_number(phone2))

首先re.sub去除字符串中的空格和短横线,然后使用re.fullmatch来匹配标准的11位手机号码格式。对于phone1,经过处理后符合格式输出13812345678,而phone2由于不符合格式输出None

正则表达式优化与性能提升

避免贪婪匹配

在使用正则表达式时,贪婪匹配可能会导致性能问题和匹配结果不准确。例如,.*是贪婪匹配模式,它会尽可能多地匹配字符。假设我们要匹配HTML标签内的文本,使用<.*>可能会匹配到过多内容。

import re


html_text = '<p>段落1</p><p>段落2</p>'
# 贪婪匹配,会匹配到整个文本
match1 = re.search(r'<.*>', html_text)
if match1:
    print(match1.group())
# 非贪婪匹配,只匹配到第一个<p>标签内容
match2 = re.search(r'<.*?>', html_text)
if match2:
    print(match2.group())

上述代码中,r'<.*>'会匹配到<p>段落1</p><p>段落2</p>,而r'<.*?>'使用非贪婪匹配模式,只匹配到<p>段落1</p>。在数据清洗中,正确使用贪婪与非贪婪模式可以提高匹配的准确性和效率。

预编译正则表达式

对于需要多次使用的正则表达式,预编译可以提高性能。re.compile函数可以将正则表达式编译成一个对象,后续使用该对象进行匹配操作会更快。

import re


# 预编译正则表达式
email_pattern = re.compile(r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+')
text_list = ['test@example.com', 'info@domain.net', 'invalid_email']
for text in text_list:
    match = email_pattern.search(text)
    if match:
        print(match.group())

这里通过re.compile预编译了邮箱地址的正则表达式,在遍历列表进行匹配时,比每次都使用re.search直接匹配效率更高,尤其是在处理大量数据时,性能提升更为明显。

合理使用字符类与量词

在构建正则表达式时,合理使用字符类和量词可以简化表达式并提高匹配效率。例如,\d表示任意数字字符,比[0123456789]更简洁。同时,准确使用量词*(零次或多次)、+(一次或多次)、?(零次或一次)等也很重要。如果我们要匹配一个可能包含空格的数字字符串,可以使用\s*\d+\s*,这里\s*表示零个或多个空白字符,\d+表示一个或多个数字字符。

import re


texts = ['  123  ', '456', 'abc']
for text in texts:
    match = re.search(r'\s*\d+\s*', text)
    if match:
        print(match.group())

上述代码能够准确匹配包含空格的数字字符串,并且由于使用了简洁合理的字符类和量词,在匹配效率上也有一定保障。

结合其他Python库进行数据清洗

pandas库结合

pandas是Python中常用的数据处理库,它与正则表达式结合可以高效处理表格数据。例如,我们有一个包含邮箱地址的DataFrame,要验证并提取有效的邮箱地址。

import pandas as pd
import re


data = {
    'email': ['test@example.com', 'invalid_email', 'info@domain.net']
}
df = pd.DataFrame(data)


def validate_email(email):
    pattern = r'[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+'
    if re.fullmatch(pattern, email):
        return email
    return None


df['valid_email'] = df['email'].apply(validate_email)
print(df)

上述代码使用pandasapply方法对DataFrame中的每一个邮箱地址应用正则表达式验证函数,将有效的邮箱地址提取到新的列valid_email中。

BeautifulSoup库结合处理HTML数据

在处理HTML数据时,BeautifulSoup库用于解析HTML结构,而正则表达式可以进一步在解析后的文本中进行数据清洗和提取。比如,我们要从一个HTML页面中提取所有链接的文本内容,并且确保链接文本不包含特定的关键词。

from bs4 import BeautifulSoup
import re


html = """
<html>
<body>
<a href="#">首页</a>
<a href="#">关于我们</a>
<a href="#">联系我们(请勿点击)</a>
</body>
</html>
"""
soup = BeautifulSoup(html, 'html.parser')
links = soup.find_all('a')
pattern = re.compile(r'^(?!.*请勿点击).*$')
for link in links:
    link_text = link.get_text()
    if pattern.match(link_text):
        print(link_text)

这里BeautifulSoup解析HTML获取所有的<a>标签,然后使用正则表达式^(?!.*请勿点击).*$来筛选掉包含请勿点击关键词的链接文本,输出符合条件的链接文本首页关于我们

numpy库结合处理数值型数据中的字符串杂质

numpy是Python中用于数值计算的库。在处理数值型数据时,有时数据中可能包含一些字符串杂质,比如单位等。我们可以结合正则表达式和numpy来清理这些杂质并进行数值计算。

import numpy as np
import re


data = np.array(['123kg', '456g', '789'])
def clean_number(num_str):
    match = re.search(r'\d+', num_str)
    if match:
        return int(match.group())
    return None


cleaned_data = np.array([clean_number(num) for num in data if clean_number(num) is not None])
print(np.sum(cleaned_data))

上述代码使用正则表达式从包含单位的字符串中提取数字,然后使用numpy对提取后的数字进行求和计算,输出1368

通过以上对Python正则表达式在数据清洗中的应用介绍,我们可以看到正则表达式在数据清洗过程中是一个非常强大且灵活的工具,结合其他Python库能够更高效地处理各种复杂的数据清洗任务。在实际应用中,需要根据具体的数据特点和需求,合理构建和使用正则表达式,以达到最佳的数据清洗效果。