Pandas 文本数据处理

在实际数据中,文本数据(如姓名、地址、评论等)非常普遍。Pandas 通过 .str 访问器提供了一套强大的向量化字符串操作,让你能够像处理 Python 字符串一样处理整个 Series,并支持正则表达式、提取、替换等高级功能。

📌 准备示例数据

import pandas as pd
import numpy as np

df = pd.DataFrame({
    '姓名': [' 张三 ', '李四', '王五', '赵六', '陈七'],
    '邮箱': ['zhangsan@example.com', 'lisi@work.com', 'wangwu@test.org', 'zhaoliu@company.cn', np.nan],
    '电话': ['138-1234-5678', '139-8765-4321', '158-1111-2222', '177-3333-4444', '186-5555-6666'],
    '备注': ['VIP', '普通', 'VIP,老客户', '新用户', '普通']
})
print(df)

🔧 .str 访问器

要使用 Pandas 的字符串方法,需要通过 .str 访问器,它会将 Series 的每个元素视为字符串,并应用相应的方法。

# 去除姓名两端的空格
df['姓名'] = df['姓名'].str.strip()
print(df['姓名'])

# 将邮箱转为小写(若存在)
df['邮箱小写'] = df['邮箱'].str.lower()
print(df[['邮箱', '邮箱小写']])

📋 常用字符串方法

下表列出了一些常用的 .str 方法,用法与 Python 内置字符串方法类似。

.str.lower()
.str.upper()
.str.len()
.str.strip()
.str.split()
.str.cat()
.str.contains()
.str.startswith()
.str.endswith()
.str.count()
.str.replace()
.str.repeat()
# 计算邮箱长度
df['邮箱长度'] = df['邮箱'].str.len()
print(df[['邮箱', '邮箱长度']])

# 判断邮箱是否以 'com' 结尾
df['是否com邮箱'] = df['邮箱'].str.endswith('.com')
print(df[['邮箱', '是否com邮箱']])

# 统计电话中 '-' 出现的次数
df['连字符数'] = df['电话'].str.count('-')
print(df[['电话', '连字符数']])

# 将电话中的 '-' 替换为空格
df['电话_clean'] = df['电话'].str.replace('-', ' ')
print(df[['电话', '电话_clean']])

🔍 字符串包含与匹配

.str.contains() 可以检查每个字符串是否包含某个模式(支持正则)。

# 查找邮箱中包含 'example' 的行
mask = df['邮箱'].str.contains('example', na=False)  # na=False 将缺失值视为 False
print(df[mask])

# 备注中是否包含 'VIP'(注意正则元字符)
df['是否VIP'] = df['备注'].str.contains(r'VIP', regex=True, na=False)
print(df[['备注', '是否VIP']])

📎 分割与展开

.str.split() 配合 expand=True 可以将分割后的结果展开为多列。

# 将电话按 '-' 分割成三列
phone_split = df['电话'].str.split('-', expand=True)
phone_split.columns = ['区号', '中间', '后四位']
df = pd.concat([df, phone_split], axis=1)
print(df[['电话', '区号', '中间', '后四位']].head())

# 将备注按 ',' 分割(可能有多项)
note_split = df['备注'].str.split(',', expand=True)
print(note_split)

🧪 正则表达式提取:.str.extract()

.str.extract() 用于从字符串中提取匹配正则表达式分组的内容,返回 DataFrame。

# 从邮箱中提取用户名和域名
pattern = r'(?P<用户名>.*)@(?P<域名>.*)'
extracted = df['邮箱'].str.extract(pattern)
print(extracted)

# 提取电话中的前三位(区号)
df['电话前缀'] = df['电话'].str.extract(r'^(\d{3})')
print(df[['电话', '电话前缀']])

📌 正则表达式提取所有匹配:.str.extractall()

当每个字符串可能包含多个匹配时,使用 extractall()

# 模拟数据:多个标签
df_tags = pd.DataFrame({'tags': ['apple,banana', 'cat,dog,fish', 'red,blue,green']})
# 提取所有单词(\w+)
result = df_tags['tags'].str.extractall(r'(\w+)')
print(result)

🔄 替换与正则替换

.str.replace() 支持正则表达式,可以完成复杂的替换任务。

# 将电话中的所有数字替换为 '#'
df['电话隐藏'] = df['电话'].str.replace(r'\d', '#', regex=True)
print(df[['电话', '电话隐藏']])

# 去除多余的空格(多个空格替换为一个)
df['备注_clean'] = df['备注'].str.replace(r'\s+', ' ', regex=True)
print(df[['备注', '备注_clean']])

🏷️ 虚拟变量:.str.get_dummies()

对于用分隔符分隔的类别列,.str.get_dummies() 可以快速生成虚拟变量(one-hot encoding)。

# 从备注列生成虚拟变量(按 ',' 分割)
dummies = df['备注'].str.get_dummies(sep=',')
print(dummies)

# 合并到原 DataFrame
df = pd.concat([df, dummies], axis=1)
print(df.head())

🧩 处理缺失值

字符串方法遇到缺失值默认返回 NaN,可通过参数控制。

# 邮箱列有 NaN,直接调用 .str.lower() 会保留 NaN
print(df['邮箱'].str.lower())

# 使用 fillna 先填充
df['邮箱_filled'] = df['邮箱'].fillna('').str.lower()
print(df['邮箱_filled'])

🧪 综合示例:清洗用户信息

import pandas as pd

# 原始脏数据
raw = pd.DataFrame({
    'name': ['  Alice  ', 'Bob', 'Charlie ', '  David', 'Eve'],
    'email': ['alice@example.com', 'bob@work.com', 'charlie@test', 'david@company', np.nan],
    'phone': ['123-456-7890', '234-567-8901', '345-6789', '456-789-0123', '567-890-1234'],
    'tags': ['new,premium', 'regular', 'premium,vip', 'new', 'regular,new']
})

print("原始数据:")
print(raw)

# 1. 清洗姓名:去除空格,首字母大写
raw['name'] = raw['name'].str.strip().str.title()

# 2. 验证邮箱格式(简单检查是否包含 @ 和 .)
raw['email_valid'] = raw['email'].str.contains(r'.+@.+\..+', regex=True, na=False)

# 3. 提取电话区号(前三位)并标准化电话格式
raw['area_code'] = raw['phone'].str.extract(r'^(\d{3})')
raw['phone_std'] = raw['phone'].str.replace(r'(\d{3})-(\d{3})-(\d{4})', r'\1-\2-\3', regex=True)

# 4. 从 tags 生成虚拟变量
tags_dummies = raw['tags'].str.get_dummies(sep=',')
raw = pd.concat([raw, tags_dummies], axis=1)

print("\n清洗后数据:")
print(raw)

⚠️ 注意事项

缺失值处理:字符串方法遇到 NaN 默认返回 NaN,建议用 na=Falsefillna() 控制。
性能考虑:.str 方法是对 Python 字符串的循环封装,对于超大数据集可能较慢。考虑使用正则表达式或向量化操作。
正则表达式转义:特殊字符需要转义,建议使用原始字符串(r'...')避免歧义。
链式操作:多个 .str 方法可以链式调用,但要注意中间结果可能是 NaN。
最佳实践:
  • 先用 fillna() 处理缺失值,或使用参数 na=False/na='' 等控制行为。
  • 对于复杂模式匹配,优先使用正则表达式方法(extract, contains 等)。
  • 需要生成虚拟变量时,.str.get_dummies() 比循环 + pd.get_dummies() 更高效。
  • 处理超大文本数据时,考虑使用 apply + Python 函数可能会比 .str 更快(需测试)。