将以上文本处理方法组合成完整的数据处理流程,并掌握向量化、性能优化与最佳实践。
1. 综合实践案例
案例 1:用户信息清洗
import pandas as pd
import re
raw = pd.DataFrame({
'原始信息': [
'张三 | 138-0000-0000 | zhangsan@test.com | 北京',
'李四 | 13911112222 | li@test.org | 上海',
'王五 | 010-12345678 | wang@demo.net | 广州',
None
]
})
# 1. 去除空行
df = raw.dropna().copy()
# 2. 拆分字段
split = df['原始信息'].str.split('|', expand=True)
df[['姓名', '电话', '邮箱', '城市']] = split
# 3. 清理各字段
df['姓名'] = df['姓名'].str.strip()
df['电话'] = df['电话'].str.strip().str.replace(r'[^0-9]', '', regex=True)
df['邮箱'] = df['邮箱'].str.strip().str.lower()
df['城市'] = df['城市'].str.strip()
# 4. 增加派生列
df['手机号'] = df['电话'].where(df['电话'].str.match(r'^1\d{10}$'))
df['邮箱有效'] = df['邮箱'].str.contains(r'@', regex=True, na=False)
# 5. 输出结果
print(df)案例 2:日志解析
logs = pd.Series([
'2024-01-01 10:00:00 INFO: 用户登录 user=1001',
'2024-01-02 11:30:00 ERROR: 数据库连接失败 user=1002',
'2024-01-03 09:15:00 INFO: 用户退出 user=1001'
])
# 提取时间、级别、消息、用户ID
parsed = logs.str.extract(
r'(?P<时间>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+'
r'(?P<级别>\w+):\s+'
r'(?P<消息>.*?)(?:\s+user=)?(?P<用户>\d+)?'
)
parsed['时间'] = pd.to_datetime(parsed['时间'])
print(parsed)案例 3:文本分类特征
comments = pd.DataFrame({'评论': [
'这个产品很好用', '服务太差了', '快递很慢',
'质量不错,推荐', '再也不买了'
]})
# 情感词匹配
positive_words = ['好', '推荐', '不错']
negative_words = ['差', '慢', '再也不']
comments['积极'] = comments['评论'].apply(
lambda x: any(w in str(x) for w in positive_words)
)
comments['消极'] = comments['评论'].apply(
lambda x: any(w in str(x) for w in negative_words)
)
# 简单情感判断
def sentiment(text):
if any(w in text for w in positive_words):
return '正面'
if any(w in text for w in negative_words):
return '负面'
return '中性'
comments['情感'] = comments['评论'].apply(sentiment)
print(comments)2. 性能优化
2.1 优先使用向量化 .str 方法
| 低效方式 | 高效方式 |
|---|---|
df['col'].apply(lambda x: x.lower()) | df['col'].str.lower() |
df['col'].apply(lambda x: 'a' in x) | df['col'].str.contains('a') |
循环 + re.search | df['col'].str.extract(r'...') |
# 不推荐
df['小写'] = df['文本'].apply(lambda x: x.lower())
# 推荐
df['小写'] = df['文本'].str.lower()原因
.str方法在底层按列一次性执行,避免 Python 级循环与函数调用开销,性能可提升数倍到数十倍。
2.2 指定 regex 参数
# 不推荐:自动推断
df['文本'].str.replace('old', 'new')
# 推荐:显式指明
df['文本'].str.replace('old', 'new', regex=False)显式指定
regex=False可避免不必要的正则编译。
2.3 使用 string dtype
# 推荐:string 扩展类型拥有更一致的 API 行为
df['文本'] = df['文本'].astype('string')
# 加快大规模转换
df = df.convert_dtypes()2.4 分段处理超大文本列
# 对超大列分块处理,避免内存峰值
result = pd.Series(index=df.index, dtype='string')
for start in range(0, len(df), 10000):
chunk = df['文本'].iloc[start:start+10000]
result.iloc[start:start+10000] = chunk.str.lower()2.5 避免反复 apply
# 多次 apply 合并为一次
# 不推荐
df['a'] = df['文本'].apply(lambda x: x.strip())
df['b'] = df['文本'].apply(lambda x: x.upper())
# 推荐:一次处理
df[['a', 'b']] = df['文本'].apply(
lambda x: pd.Series([x.strip(), x.upper()])
)注意
若难以向量化,且文本长度较短、数据量不大,
apply也可接受。优先保证代码可读性。
3. 内存优化
3.1 转为 category
当字符串列取值有限且重复率高时,转为 category 可大幅节省内存:
df['城市'] = df['城市'].astype('category')
df['城市'].cat.categories3.2 使用 string dtype
df['描述'] = df['描述'].astype('string')3.3 检查内存占用
df['文本'].memory_usage(deep=True)
df.info(memory_usage='deep')4. 最佳实践
| 实践 | 说明 |
|---|---|
| 统一类型 | 文本列统一为 string dtype |
| 统一大小写 | 尽早执行 str.lower() / str.strip() |
| 缺失值策略 | 明确 na=True/False/NaN 行为 |
| 使用命名正则组 | 让提取结果列名清晰 |
| 分步验证 | 每步 head() / sample() 检查 |
| 保留原始列 | 新增清洗列而非覆盖原列,便于追溯 |
| 链式操作管道 | 用 .pipe() 串联清洗步骤 |
链式管道示例
def clean_text(df):
return (df
.assign(
姓名=df['姓名'].str.strip().str.title(),
邮箱=df['邮箱'].str.strip().str.lower(),
电话=df['电话'].str.replace(r'[^0-9]', '', regex=True)
)
.dropna(subset=['姓名', '邮箱'])
.drop_duplicates(subset=['邮箱'])
)
df_clean = df.pipe(clean_text)5. 常见问题与排查
| 问题 | 原因 | 解决 |
|---|---|---|
AttributeError: Can only use .str accessor with string values | 列不是字符串 | astype('string') |
| 匹配结果全为 False | 大小写或正则问题 | case=False 或检查正则 |
| 提取大量 NaN | 正则未匹配 | 使用 extractall 或调整模式 |
| 替换未生效 | regex 未显式 | regex=True / regex=False |
| 性能极慢 | 大量 apply | 改用 .str 向量化 |
小结
- 向量化优先:
.str替代apply和循环- 显式参数:
regex=指定正则行为- 类型优化:
stringdtype、category降低内存- 流程规范:分步验证、保留原始列、管道化处理