python

关注公众号 jb51net

关闭
首页 > 脚本专栏 > python > Pandas字符串处理

Pandas中字符串处理的核心方法与正则匹配实战教学

作者:小庄-Python办公

本文介绍了Pandas中字符串处理的核心方法,重点讲解了str访问器的常用操作技巧,包括字符串分割、拼接、替换与提取等,同时深入讲解了正则表达式的基础语法及其在Pandas中的应用,希望帮助大家掌握高效处理文本数据的技能

本节学习目标

为什么学这个?

在真实数据中,文本数据无处不在

这些文本数据往往格式不统一、包含多余字符或需要从中提取关键信息。比如:

如果你只会用基础的字符串方法(如 Python 的 .split().replace()),处理 Pandas 数据会非常低效。Pandas 的 str 访问器专为批量文本处理设计,配合正则表达式,可以完成极其复杂的文本操作。

打个比方:如果说数据筛选是手术刀,那字符串处理就是"显微镜"——它让你看清文字中最微小的细节,并从中提取出有价值的信息。

核心知识点讲解

str 访问器入门

Pandas 的 Series 对象有一个 .str 属性,它提供了数十种字符串操作方法。这些方法会自动应用到 Series 的每一个元素上(向量化操作)。

import pandas as pd
import numpy as np

# 创建示例数据
df = pd.DataFrame({
    '姓名': ['张三', '李四', '王五', '赵六', '孙七'],
    '邮箱': ['zhang@email.com', 'li@work.cn', 'wang@school.edu', 'zhao@gmail.com', 'sun@company.org'],
    '电话': ['138-0000-1234', '139 0000 5678', '13600009012', '186-0000-3456', '185 0000 7890'],
    '地址': ['北京市朝阳区', '上海市浦东新区', '广州市天河区', '深圳市南山区', '杭州市西湖区'],
    '备注': ['VIP客户, 高价值', '新客户', '老客户, 多次购买', None, 'VIP客户']
})

# ===== 使用 .str 访问字符串方法 =====

# 转大写/小写
print(df['邮箱'].str.upper())
print(df['邮箱'].str.lower())

# 获取长度
print(df['地址'].str.len())

# 判断是否包含某字符串
print(df['地址'].str.contains('区'))  # 都包含"区"

# 判断开头和结尾
print(df['地址'].str.startswith('北京'))
print(df['地址'].str.endswith('区'))

# 替换
print(df['电话'].str.replace('-', ''))
print(df['电话'].str.replace('-', '').str.replace(' ', ''))

# 去除空白字符
print(df['电话'].str.strip())  # 去除首尾空白

字符串常用方法详解

1. 分割字符串(split)

# ===== 分割字符串 =====
df = pd.DataFrame({
    '全名': ['张 三', '李 四', '王 五'],
    '标签': '电子产品,服装,食品',  # 这个会被广播到所有行
    '地址': ['北京市-朝阳区-望京', '上海市-浦东区-陆家嘴', '广州市-天河区-体育西']
})

# 按空格分割姓名
name_split = df['全名'].str.split(' ')
print(name_split)
# 输出:
# 0    [张, 三]
# 1    [李, 四]
# 2    [王, 五]

# 分割后展开为多列
name_df = df['全名'].str.split(' ', expand=True)
name_df.columns = ['姓', '名']
print(name_df)

# 分割地址
addr_df = df['地址'].str.split('-', expand=True)
addr_df.columns = ['省/市', '区', '街道']
print(addr_df)

# 只取分割后的第一部分
print(df['地址'].str.split('-').str[0])  # ['北京市', '上海市', '广州市']

2. 拼接字符串(cat)

# ===== 拼接字符串 =====

# Series 之间拼接
s1 = pd.Series(['北京', '上海', '广州'])
s2 = pd.Series(['朝阳', '浦东', '天河'])
print(s1.str.cat(s2, sep='-'))  # 北京-朝阳, 上海-浦东, 广州-天河

# 拼接常量
print(s1.str.cat('-中国'))  # 北京-中国, 上海-中国, 广州-中国

# 在 DataFrame 中拼接多列
df = pd.DataFrame({
    '姓': ['张', '李', '王'],
    '名': ['三', '四', '五']
})
df['全名'] = df['姓'].str.cat(df['名'])
print(df)
# 也可以直接:
df['全名'] = df['姓'] + df['名']  # 更简洁

3. 子串提取(slice)

# ===== 提取子串 =====
df = pd.DataFrame({
    '身份证号': ['110101199001011234', '310101199202025678', '440101199503039012']
})

# 提取出生年份(第7-10位)
df['出生年份'] = df['身份证号'].str[6:10]

# 提取地区码(前6位)
df['地区码'] = df['身份证号'].str[:6]

print(df)

4. 大小写与空白处理

# ===== 大小写转换 =====
df = pd.DataFrame({
    'text': ['Hello World', 'PYTHON', 'machine learning', 'Data Science']
})

print(df['text'].str.lower())        # 全部小写
print(df['text'].str.upper())        # 全部大写
print(df['text'].str.title())        # 首字母大写
print(df['text'].str.capitalize())   # 句子首字母大写
print(df['text'].str.swapcase())     # 大小写互换

# ===== 空白处理 =====
df2 = pd.DataFrame({
    'text': ['  hello  ', 'world\n', '\tpython\t', '  data  science  ']
})

print(df2['text'].str.strip())       # 去除首尾空白
print(df2['text'].str.lstrip())      # 去除左侧空白
print(df2['text'].str.rstrip())      # 去除右侧空白

5. 对齐与填充

# ===== 字符串对齐 =====
df = pd.DataFrame({
    '编号': ['1', '23', '456', '7890']
})

# 右对齐,左边用 0 填充到 5 位
print(df['编号'].str.pad(5, side='left', fillchar='0'))
# 输出:00001, 00023, 00456, 07890

# 等价方法:zfill
print(df['编号'].str.zfill(5))  # 更常用!

正则表达式(Regular Expression)基础

1. 什么是正则表达式?

正则表达式是一种用模式来匹配字符串的语言。它用简短的符号组合描述字符串的格式规则。

比喻:如果你要找"所有以 138 开头的11位手机号",在正则中就是 138\d{8}

2. 常用正则元字符

符号含义示例匹配
.任意字符(除换行)a.cabc, a_c, a c
\d数字(0-9)\d\d12, 99
\w字母、数字、下划线a\wbaxb, a1b, a_b
\s空白字符a\sba b
^字符串开头^abcabc…
$字符串结尾xyz$…xyz
*0 次或多次ab*cac, abc, abbc
+1 次或多次ab+cabc, abbc(不匹配 ac)
?0 次或 1 次ab?cac, abc
{n}恰好 n 次\d{4}1234
{n,m}n 到 m 次\d{2,4}12, 123, 1234
[...]字符集合[abc]a 或 b 或 c
(?:)非捕获分组--
```cat

3. 在 Pandas 中使用正则

# ===== str.contains() —— 正则匹配 =====
df = pd.DataFrame({
    '邮箱': ['abc@email.com', 'xyz@work.cn', 'test123@school.edu', 'admin@company.org', 'user']
})

# 匹配邮箱格式
mask = df['邮箱'].str.contains(r'^[\w.]+@[\w.]+\.\w+$', regex=True)
print(df[mask])

# ===== str.match() —— 从头匹配 =====
# match 从字符串开头开始匹配
df2 = pd.DataFrame({
    '文本': ['123-456-7890', 'ABC-DEF', '42-99', 'xyz', '100-200-300']
})

# 匹配"数字-数字"格式(从开头)
print(df2['文本'].str.match(r'^\d+-\d+'))

# ===== str.extract() —— 提取匹配内容 =====
df3 = pd.DataFrame({
    '日志': ['2024-01-15 INFO: User login', '2024-02-20 ERROR: Timeout',
             '2024-03-10 WARN: Disk full', '2024-04-05 DEBUG: Test']
})

# 提取日期和日志级别
extracted = df3['日志'].str.extract(r'(\d{4}-\d{2}-\d{2})\s+(\w+):')
extracted.columns = ['日期', '级别']
print(extracted)

# ===== str.extractall() —— 提取所有匹配 =====
df4 = pd.DataFrame({
    '文本': ['价格是100元,折扣价80元', '售价200元,运费15元', '免费']
})

# 提取所有数字
matches = df4['文本'].str.extractall(r'(\d+)元')
print(matches)

实战场景:常见文本处理任务

场景 1:邮箱提取

# 从文本中提取邮箱地址
df = pd.DataFrame({
    '描述': [
        '联系人:张三,邮箱 zhang@company.com,电话 138xxxx',
        '邮箱:li.work@gmail.com,请发邮件联系',
        '无联系方式',
        'admin@school.edu.cn 是官方邮箱',
        '请发邮件到 test123@work.org 或 backup@test.com'
    ]
})

# 提取第一个邮箱地址
df['邮箱'] = df['描述'].str.extract(r'([\w.+-]+@[\w.-]+\.\w+)')
print(df)

场景 2:地址解析

# 将地址拆分为省、市、区
df = pd.DataFrame({
    '完整地址': [
        '广东省广州市天河区天河路1号',
        '北京市朝阳区建国路88号',
        '浙江省杭州市西湖区文三路100号',
        '上海市浦东新区世纪大道1号'
    ]
})

# 用正则提取(简化版,实际地址格式更复杂)
addr = df['完整地址'].str.extract(
    r'(?P<省>[^省]+省|(?P<直辖市>[北上广][^\s]+?市))(?P<市>[^市]+市)(?P<区>[^区]+区)'
)
print(addr)

# 更简单的方式:固定模式拆分
# 假设格式都是"省/市+市名+区名+详细地址"
df['省份'] = df['完整地址'].str.extract(r'^([^省市]+[省]?)(?:省)?(.+?)市')

场景 3:数据清洗与标准化

# 清理不规范的电话号码
df = pd.DataFrame({
    '电话': [
        '138-0000-1234',
        '139 0000 5678',
        '13600009012',
        '186(0000)3456',
        '185-0000-7890',
        '+86 138 0000 1234'
    ]
})

# 去除所有非数字字符
df['电话_清理'] = df['电话'].str.replace(r'\D', '', regex=True)

# 去掉国家代码
df['电话_清理'] = df['电话_清理'].str.replace(r'^86', '', regex=True)

# 标准化格式
df['电话_标准'] = df['电话_清理'].str.replace(
    r'(\d{3})(\d{4})(\d{4})', r'\1-\2-\3', regex=True
)

print(df)

场景 4:关键词提取与分类

# 根据评论内容自动分类
df = pd.DataFrame({
    '评论': [
        '这个手机屏幕很大,拍照效果很好',
        '电池太不耐用了,半天就没电',
        '价格太贵了,性价比不高',
        '物流很快,包装也很好',
        '音质不错,但是低音效果一般',
        '运行速度很快,打游戏很流畅'
    ]
})

# 定义关键词
keywords = {
    '屏幕': '显示',
    '拍照': '拍照',
    '电池': '续航',
    '没电': '续航',
    '价格': '价格',
    '贵': '价格',
    '性价比': '价格',
    '物流': '物流',
    '包装': '物流',
    '音质': '音质',
    '低音': '音质',
    '速度': '性能',
    '流畅': '性能'
}

# 创建一个匹配函数
def classify_review(text):
    categories = []
    for keyword, category in keywords.items():
        if keyword in str(text):
            categories.append(category)
    return list(set(categories))  # 去重

df['分类'] = df['评论'].apply(classify_review)
print(df)

高级正则技巧

1. 命名分组

# 使用命名分组提取信息
df = pd.DataFrame({
    '日期文本': ['生于1990年05月20日', '入职于2020年01月15日', '更新于2024年03月10日']
})

result = df['日期文本'].str.extract(
    r'生于|入职于|更新于(?P<年>\d{4})年(?P<月>\d{2})月(?P<日>\d{2})日'
)
# 更准确的写法:
result = df['日期文本'].str.extract(
    r'(?P<年>\d{4})年(?P<月>\d{2})月(?P<日>\d{2})日'
)
print(result)

2. 零宽断言(Lookaround)

# 正向先行断言:匹配后面跟着特定内容的字符串
df = pd.DataFrame({
    '文本': ['价格是100元', '重量是200克', '时间是30分钟', '长度是50厘米']
})

# 提取"是"和"克"之间的数字
df['数字'] = df['文本'].str.extract(r'(?<=是)\d+(?=克)')
print(df)
# 只有"重量是200克"能匹配到 200

3. 多模式匹配

# 同时用多个正则进行替换
df = pd.DataFrame({
    '文本': ['[重要] 会议通知 (2024)', '{内部} 财务报表 【加密】']
})

# 去除所有括号及其内容
df['清理后'] = df['文本'].str.replace(r'[\[\]\(\)\{\}【】].*?[\]\)\}】]', '', regex=True)
df['清理后'] = df['清理后'].str.strip()
print(df)

实战练习

练习 1:学生信息清洗

题目:以下学生信息数据格式混乱,请清洗。

# 参考答案
import pandas as pd
import numpy as np

df = pd.DataFrame({
    '姓名': [' 张三 ', '李四', ' 王五 ', '赵六  ', '孙七'],
    '邮箱': ['ZHANG@EMAIL.COM', 'li@work.CN', 'wang@SCHOOL.edu', 'zHAO@gmail.COM', 'sun@company.ORG'],
    '成绩': ['95分', '87分', '92分', '78分', '88分']
})

# 1. 去除姓名前后空白
df['姓名'] = df['姓名'].str.strip()

# 2. 邮箱标准化为首字母大写
df['邮箱'] = df['邮箱'].str.lower()

# 3. 提取成绩数字并转为整数
df['成绩'] = df['成绩'].str.replace('分', '').astype(int)

print("清洗后:")
print(df)
print(f"\n数据类型:\n{df.dtypes}")

练习 2:日志文件解析

题目:解析以下服务器日志数据。

# 参考答案
import pandas as pd

df = pd.DataFrame({
    '日志': [
        '[2024-01-15 10:30:22] ERROR 192.168.1.100 Connection timeout',
        '[2024-01-15 10:31:05] INFO 192.168.1.101 Request processed',
        '[2024-01-15 10:32:18] WARNING 192.168.1.102 Disk usage high',
        '[2024-01-15 10:33:45] ERROR 192.168.1.100 Connection timeout',
        '[2024-01-15 10:35:00] INFO 192.168.1.103 User login'
    ]
})

# 1. 提取时间、级别、IP、消息
parsed = df['日志'].str.extract(
    r'\[(?P<时间>[\d-]+ [\d:]+)\]\s+(?P<级别>\w+)\s+(?P<IP>[\d.]+)\s+(?P<消息>.+)'
)

print("解析结果:")
print(parsed)

# 2. 统计各错误级别数量
print("\n错误级别统计:")
print(parsed['级别'].value_counts())

# 3. 统计每个 IP 的错误次数
error_df = parsed[parsed['级别'] == 'ERROR']
print("\n各IP错误次数:")
print(error_df['IP'].value_counts())

练习 3:商品信息提取

题目:从商品描述中提取关键信息。

# 参考答案
import pandas as pd

df = pd.DataFrame({
    '商品描述': [
        'Apple iPhone 15 Pro 256GB 深空黑色 ¥8999',
        '华为 Mate 60 Pro 512GB 雅丹黑 ¥6999',
        '小米14 12GB+256GB 黑色 ¥3999',
        'Samsung Galaxy S24 128GB ¥5499',
        'OPPO Find X7 Ultra 16GB+512GB ¥5999'
    ]
})

# 提取品牌(第一个英文单词或中文字)
df['品牌'] = df['商品描述'].str.extract(r'^([A-Za-z]+|[\u4e00-\u9fa5]{1,4})')

# 提取价格
df['价格'] = df['商品描述'].str.extract(r'¥(\d+)').astype(int)

# 提取存储容量
df['存储'] = df['商品描述'].str.extract(r'(\d+GB)')

# 提取型号关键词
df['型号'] = df['商品描述'].str.extract(r'(?:iPhone|Mate|Galaxy|Find|小米|华为)\s*(\w+)')

print(df[['品牌', '型号', '存储', '价格']])

总结

本节我们系统学习了 Pandas 中的字符串处理与正则匹配:

str 访问器常用方法

正则表达式核心:

到此这篇关于Pandas中字符串处理的核心方法与正则匹配实战教学的文章就介绍到这了,更多相关Pandas字符串处理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文