github star gitee star atomgit star PyPI Downloads AI 编程 AI 交流群

大家好,我是正在实战各种AI项目的程序员晚枫。

今天学习Pandas中的字符串处理,这是清洗文本数据的必备技能。

无论是处理用户输入、解析日志文件,还是提取关键信息,掌握这些技巧都能让你事半功倍。


.str访问器

Pandas为Series提供了.str访问器,可以像操作Python字符串一样处理整列数据。

1
2
3
4
5
6
7
8
import pandas as pd

df = pd.DataFrame({
'姓名': [' 张三 ', '李四', '王五'],
'邮箱': ['zhangsan@example.com', 'LiSi@test.COM', 'wangwu@company.cn'],
'电话': ['138-0013-8000', '13900138001', '137 0013 7000'],
'地址': ['北京市朝阳区', '上海市浦东新区', '广州市天河区']
})

1. 大小写转换

1
2
3
4
5
6
7
8
# 转小写
df['邮箱_小写'] = df['邮箱'].str.lower()

# 转大写
df['邮箱_大写'] = df['邮箱'].str.upper()

# 首字母大写
df['姓名_规范'] = df['姓名'].str.title()

2. 去除空白字符

1
2
3
4
5
6
7
8
9
10
11
# 去除首尾空格
df['姓名_去空格'] = df['姓名'].str.strip()

# 去除左侧空格
df['姓名'] = df['姓名'].str.lstrip()

# 去除右侧空格
df['姓名'] = df['姓名'].str.rstrip()

# 去除所有空格(包括中间)
df['电话_纯数字'] = df['电话'].str.replace(' ', '').str.replace('-', '')

3. 字符串替换

1
2
3
4
5
6
7
8
# 简单替换
df['电话_标准'] = df['电话'].str.replace('-', ' ')

# 正则表达式替换(删除所有非数字字符)
df['电话_数字'] = df['电话'].str.replace(r'\D', '', regex=True)

# 多字符替换
df['地址_简写'] = df['地址'].str.replace('市', '').str.replace('区', '')

4. 分割与提取

1
2
3
4
5
6
7
8
9
10
11
# 按字符分割
df[['城市', '区县']] = df['地址'].str.split('市', expand=True)

# 限制分割次数
df['邮箱域名'] = df['邮箱'].str.split('@').str[1]

# 提取子串(位置)
df['区号'] = df['电话_数字'].str[:3] # 前3位

# 提取子串(正则)
df['运营商'] = df['电话_数字'].str.extract(r'(1[38]\d)')

5. 包含与匹配

1
2
3
4
5
6
7
8
9
10
11
# 是否包含子串
df['是移动'] = df['电话'].str.contains('138|139')

# 以某字符串开头
df['是北京'] = df['地址'].str.startswith('北京')

# 以某字符串结尾
df['是com邮箱'] = df['邮箱'].str.endswith('.com')

# 正则匹配
df['有效邮箱'] = df['邮箱'].str.match(r'^[\w\.-]+@[\w\.-]+\.\w+$')

6. 查找与计数

1
2
3
4
5
6
7
8
# 查找位置
df['@位置'] = df['邮箱'].str.find('@')

# 统计出现次数
df['e的数量'] = df['邮箱'].str.count('e')

# 获取长度
df['邮箱长度'] = df['邮箱'].str.len()

7. 填充对齐

1
2
3
4
5
6
7
8
9
# 左填充(补齐位数)
df['订单号'] = df['订单号'].str.zfill(8) # 00001234

# 居中
df['姓名_居中'] = df['姓名'].str.center(10, '*')

# 左右填充
df['姓名_左对齐'] = df['姓名'].str.ljust(10)
df['姓名_右对齐'] = df['姓名'].str.rjust(10)

8. 条件筛选

1
2
3
4
5
6
7
8
# 筛选包含特定字符串的行
gmail_users = df[df['邮箱'].str.contains('gmail', na=False)]

# 多条件筛选
beijing_mobile = df[
(df['地址'].str.contains('北京')) &
(df['电话'].str.contains('138'))
]

9. 高级提取

1
2
3
4
5
6
# 提取所有匹配项
texts = pd.Series(['订单A123金额¥500', '订单B456金额¥800'])
orders = texts.str.extractall(r'订单(\w+)金额¥(\d+)')

# 命名捕获组
df[['用户名', '域名']] = df['邮箱'].str.extract(r'(?P<user>[\w\.]+)@(?P<domain>[\w\.]+)')

10. 实战: messy 数据清洗

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import pandas as pd
import re

# 原始 messy 数据
messy_data = pd.DataFrame({
'客户信息': [
' 张三 | zhangsan@EXAMPLE.com | 138-0013-8000 | 北京 朝阳 ',
'李四,lisi@test.COM,13900138001,上海浦东',
'王五 | wangwu@Company.cn | 137 0013 7000 | 广州-天河'
]
})

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

def clean_customer_info(info):
"""清洗客户信息"""
# 统一分隔符为空格
info = re.sub(r'[,|]', ' ', info)

# 分割成列表
parts = info.split()

# 清理每个部分
cleaned = []
for part in parts:
part = part.strip()
if '@' in part: # 邮箱
part = part.lower()
elif part.replace('-', '').replace(' ', '').isdigit(): # 电话
part = re.sub(r'\D', '', part)
cleaned.append(part)

return cleaned

# 应用清洗
expanded = messy_data['客户信息'].apply(clean_customer_info)
messy_data[['姓名', '邮箱', '电话', '地址']] = pd.DataFrame(expanded.tolist())

print("\n清洗后:")
print(messy_data[['姓名', '邮箱', '电话', '地址']])

性能对比:向量化的str方法 vs apply

1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd
import numpy as np

s = pd.Series(['Hello World'] * 100000)

# str向量方法
%timeit s.str.lower() # 约10ms

# apply + lambda
%timeit s.apply(lambda x: x.lower()) # 约100ms,慢10倍

# 列表推导式
%timeit pd.Series([x.lower() for x in s]) # 约50ms

进阶用法

正则提取与替换

1
2
3
4
5
6
7
8
9
10
11
12
13
# 从文本中提取信息
df = pd.DataFrame({
'text': ['订单#12345金额¥500', '订单#67890金额¥1200', '订单#11111金额¥300']
})

# 提取订单号
df['order_id'] = df['text'].str.extract(r'#(\d+)')

# 提取金额
df['amount'] = df['text'].str.extract(r'¥(\d+)').astype(float)

# 提取多个组
df[['prefix', 'number']] = df['text'].str.extract(r'(订单)#(\d+)')

文本清洗流水线

1
2
3
4
5
6
7
8
# 清洗用户输入的地址数据
df['address_clean'] = (df['address']
.str.strip() # 去首尾空格
.str.replace(r'\s+', ' ', regex=True) # 多空格变单空格
.str.replace('省', '', regex=False) # 去掉"省"
.str.replace('市', '', regex=False) # 去掉"市"
.str.lower() # 转小写
)

避坑指南

❌ 坑1:NaN和str方法

1
2
3
4
5
6
s = pd.Series(['hello', np.nan, 'WORLD'])
s.str.lower() # NaN不会被影响,保持NaN

# 但contains需要注意
s.str.contains('hello') # NaN位置返回NaN
s.str.contains('hello', na=False) # NaN位置返回False(更安全)

❌ 坑2:正则特殊字符

1
2
3
4
5
# 搜索包含特殊字符的文本
text = 'price: $100.00'
text.str.contains('$100') # 错误!$是正则特殊字符
text.str.contains('$100', regex=False) # 关闭正则
text.str.contains(r'\$100') # 转义

实战案例:清洗用户评论数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import pandas as pd
import numpy as np

np.random.seed(42)
n = 5000
comments = pd.DataFrame({
'user': [f'@用户{i:04d}' for i in range(n)],
'comment': [
f'这个产品{np.random.choice(["真的很好用", "还不错", "一般般", "太差了"])}!'
f'评分:{np.random.randint(1, 6)}星 '
f'#{np.random.choice(["推荐", "不推荐", "回购", "踩雷"])}'
for i in range(n)
],
'date': pd.date_range('2025-01-01', periods=n, freq='30min')
})

# 1. 提取评分
comments['rating'] = comments['comment'].str.extract(r'评分:(\d)').astype(int)

# 2. 提取标签
comments['tag'] = comments['comment'].str.extract(r'#(\w+)')

# 3. 情感分类
def classify_sentiment(text):
if any(w in text for w in ['很好', '不错', '推荐', '回购']):
return '正面'
elif any(w in text for w in ['差', '踩雷', '不推荐']):
return '负面'
return '中性'

comments['sentiment'] = comments['comment'].apply(classify_sentiment)

# 4. 统计
print("=== 评论分析 ===")
print(f"评分分布:\n{comments['rating'].value_counts().sort_index()}")
print(f"\n情感分布:\n{comments['sentiment'].value_counts()}")
print(f"\n标签分布:\n{comments['tag'].value_counts()}")

文本分析实战技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 1. 关键词提取
df['has_discount'] = df['comment'].str.contains('打折|优惠|折扣|促销', regex=True)
df['has_complaint'] = df['comment'].str.contains('差|烂|退款|投诉', regex=True)

# 2. 文本长度统计
df['comment_length'] = df['comment'].str.len()
df['word_count'] = df['comment'].str.split().str.len()

# 3. 手机号脱敏
df['phone_masked'] = df['phone'].str.replace(r'(\d{3})\d{4}(\d{4})', r'****', regex=True)

# 4. 地址标准化
df['city'] = df['address'].str.extract(r'(北京|上海|广州|深圳|成都|重庆|杭州|武汉)')

# 5. 邮箱域名提取
df['email_domain'] = df['email'].str.split('@').str[1]

# 6. 拼音转汉字(需要pypinyin库)
from pypinyin import pin, lazy_pinyin
df['pinyin'] = df['name'].apply(lambda x: ''.join(lazy_pinyin(x)))

文本清洗checklist

在处理实际文本数据时,按这个checklist逐一检查:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def clean_text_column(s):
'''文本列清洗标准流程'''
# 1. 去首尾空格
s = s.str.strip()

# 2. 统一大小写(按需)
s = s.str.lower() # 或 .upper()

# 3. 去多余空格
s = s.str.replace(r'\s+', ' ', regex=True)

# 4. 去特殊字符
s = s.str.replace(r'[^\w\s]', '', regex=True)

# 5. 统一标点
s = s.str.replace(',', ',').str.replace('。', '.')

# 6. 处理缺失值
s = s.fillna('')

return s

# 使用
df['clean_name'] = clean_text_column(df['name'])

常见文本处理场景

1
2
3
4
5
6
7
8
9
10
11
12
# 提取手机号
df['phone'] = df['text'].str.extract(r'(1[3-9]\d{9})')

# 提取邮箱
df['email'] = df['text'].str.extract(r'([\w.]+@[\w.]+)')

# 提取金额
df['amount'] = df['text'].str.extract(r'(\d+\.?\d*)[元万]').astype(float)

# 计算文本相似度
from difflib import SequenceMatcher
df['similarity'] = df.apply(lambda r: SequenceMatcher(None, r['text_a'], r['text_b']).ratio(), axis=1)

下节预告

下一课我们将学习高效数据处理技巧,掌握优化Pandas性能的方法。

👉 继续阅读:Pandas高效数据处理技巧


💬 加入学习交流群

扫码加入Python学习交流群,和数千名同学一起进步:

👉 点击加入交流群

群里不定期分享:

  • 数据分析实战案例
  • Python学习资料
  • 求职面试经验
  • 行业最新动态

推荐:AI Python数据分析实战营

🎁 限时福利:送《利用Python进行数据分析》实体书

👉 点击了解详情


课程导航

上一篇: Pandas数据变换-时间序列处理

下一篇: Pandas高效数据处理技巧


PS:文本数据清洗占数据分析工作的很大一部分。熟练掌握.str访问器,效率提升10倍。



📚 推荐教材

主教材《Excel+Python 飞速搞定数据分析与处理(图灵出品)》

💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!