Pandas的Series和DataFrame列上有一个.str访问器,它把Python标准库re的大部分能力向量化封装。处理字符串列时,先把它转成str Series,再用正则方法,避免Python循环。这在实际清洗中非常有用。

一、正则分割:用str.split把混杂字段拆成结构化列
普通split只能按固定分隔符切割,遇到多个空格、符号变化就难处理。str.split支持正则,设置expand=True可以返回DataFrame。比如日志行"2024-05-01 10:23:45 ERROR payment-service 503",里面同时包含空格和短横线,如果只用空格拆,服务和状态码会黏在一起。用正则字符类可以一次拆开。
import pandas as pd
s = pd.Series([
'2024-05-01 10:23:45 ERROR payment-service 503',
'2024/05/02|09:15:00 WARN user-service 200',
'2024-05-03 11:00:00 INFO order-service 200',
])
parts = s.str.split(r'[\s|]+', expand=True)
parts.columns = ['date', 'time', 'level', 'service', 'code']
print(parts)
这里的正则[\s|]+表示一到多个空白字符或竖线,注意竖线放在字符类中就是普通字符,不表示或运算。expand=True很关键,如果不写,返回的是Series,其中每个元素为列表,后续处理还得用apply,性能会退化。还可以用n参数限制分割次数,例如str.split(r'\s+', n=2, expand=True)只切前两段,剩余内容留在最后一列。这适合只拆出前几个字段、保留尾部自由文本的场景。另一个细节是:如果某行没有匹配分隔符,expand模式下会填充None,可以通过fillna统一处理。
实际业务里经常遇到分隔符不统一的数据,比如有的是空格,有的是制表符,有的是竖线。直接用正则字符类把可能的符号都集中起来,比反复调用split更省事。切分完成后最好及时重命名列,后续筛选和计算可读性更高。
二、正则修改与提取:str.replace和str.extract的实战用法
修改字符串时,str.replace默认支持正则,但Pandas 2.x里最好显式写regex=True,避免部分版本把模式当字面量处理。例如把订单号中间四位数字打码:INV-20240501-7788变成INV-2024****-7788。使用捕获组匹配前面的年份和后面的尾号,替换中间四位。
import pandas as pd
order = pd.Series([
'INV-20240501-7788',
'INV-20231212-1024',
'INV-20220930-0917',
])
masked = order.str.replace(
r'(INV-\d{4})\d{4}(-\d{4})',
r'\1****\2',
regex=True,
)
print(masked)
替换参数里的\1和\2是正则反向引用,分别对应第一组和第二组捕获内容。需要注意,替换字符串中的反斜杠必须保留,否则会变成普通字符。提取用str.extract配合命名捕获组,例如从URL中拆协议、域名、路径。命名组会让结果列名更清晰,不用再用数字索引猜测含义。
import pandas as pd
urls = pd.Series([
'https://api.ipipp.com/v1/orders?id=123',
'http://admin.ipipp.com/login',
'ftp://files.ipipp.com/data.csv',
])
pattern = r'(?P<scheme>\w+)://(?P<host>[\w.-]+)(?P<path>/.*)?'
extracted = urls.str.extract(pattern, expand=True)
print(extracted)
str.extract会把命名组转成列名,如果模式中有未命名捕获组,列名默认是0、1。提取后可以用astype转换类型。另一个常用功能是str.contains配合正则做布尔过滤,例如筛选出状态码为5xx的日志,可以写成logs[logs.str.contains(r' (5\d{2})$', regex=True)]。这个正则匹配行尾的500到599状态码,避免匹配到其他位置出现的数字。
import pandas as pd
logs = pd.Series([
'GET /api 200',
'POST /checkout 503',
'GET /health 200',
'PUT /user 404',
])
server_errors = logs[logs.str.contains(r' (5\d{2})$', regex=True)]
print(server_errors)
contains返回布尔Series,直接当作过滤条件非常方便。如果还想提取具体状态码,可以配合str.extract,把正则中的捕获组单独拿出来。这样过滤和提取一步完成,比先contains再split更简洁。
三、字符串连接:str.cat与分组拼接
多列文本合并,str.cat是向量化首选,比Python的+更适合带分隔符和处理缺失值。str.cat可以接受另一个Series或DataFrame列,还可以指定sep、na_rep。例子:把省份、城市、街道拼接为完整地址,跳过缺失项。
import pandas as pd
df = pd.DataFrame({
'province': ['广东省', '浙江省', None, '江苏省'],
'city': ['深圳市', '杭州市', '南京市', '苏州市'],
'street': ['南山区科技园', '西湖区文三路', '玄武区', None],
})
df['full_address'] = df['province'].str.cat(
[df['city'], df['street']],
sep='-',
na_rep='未知',
)
print(df[['province', 'city', 'street', 'full_address']])
上面代码把None替换成“未知”再拼接,如果希望跳过缺失项而不是占位,可以先用dropna()或条件赋值处理。另一个容易忽略的点:str.cat默认要求Series索引对齐,如果传入的列表是DataFrame的列,要对齐索引;若索引不一致,结果会出错。建议先reset_index(drop=True)或确保来自同一个DataFrame。
分组拼接是str.cat的进阶用法。比如把同一个用户的多条行为记录合并成一条,可以用groupby加agg。这样每个分组内的Series调用str.cat,把事件用逗号连起来。
import pandas as pd
actions = pd.DataFrame({
'user_id': [1, 1, 2, 2, 2],
'event': ['login', 'view_item', 'login', 'add_cart', 'pay'],
})
grouped = actions.groupby('user_id')['event'].agg(
lambda x: x.str.cat(sep=','),
)
print(grouped)
分组操作时要注意,agg里的lambda接收的是Series,所以可以直接用.str.cat。如果分组内元素很多,拼接后的字符串可能很长,这时可以考虑限制最近N条或做聚合统计。分组拼接在用户行为分析、审批记录追踪等场景非常实用,比在应用层循环拼接效率高得多。
四、综合实战:清洗混合订单日志
结合前面所有操作,完成一个小场景:原始订单日志包含时间、订单号、金额和状态,字段分隔符不统一。我们希望拆列、打码订单号中的日期部分、提取状态并过滤失败订单。
import pandas as pd
raw = pd.Series([
'2024-05-01|ORD-20240501-7788|299.00|SUCCESS',
'2024-05-02 ORD-20231212-1024 89.50 FAILED',
'2024-05-03 ORD-20220930-0917 430.00 SUCCESS',
'2024-05-04|ORD-20240115-0033|120.00|PENDING',
])
# 先用正则统一分割
df = raw.str.split(r'[\s|]+', expand=True)
df.columns = ['date', 'order_no', 'amount', 'status']
# 打码订单号中间八位日期
df['order_no_masked'] = df['order_no'].str.replace(
r'(ORD-\d{4})\d{4}(-\d{4})',
r'\1****\2',
regex=True,
)
# 金额转数值
df['amount'] = df['amount'].astype(float)
# 提取失败订单
failed = df[df['status'].str.contains('FAILED', regex=True)]
print(df)
print(failed)
这段代码展示了一条完整链路:正则分割替代多次split、捕获组替换做脱敏、contains过滤状态。如果要提取状态码或更细的字段,可以在split之后使用str.extract继续处理。注意正则分隔符[\s|]+对“2024-05-02 ORD...”这类空格分隔同样有效。若原始数据中出现多个连续空格,加号可以处理,不会产生空列。
实际生产环境里,建议在清洗前先检查Series的dtype,如果是object可能混入非字符串值,使用.str访问器前需要用astype(str)转换,或者用pd.to_numeric处理数值列。另外,正则操作是向量化的,但复杂模式仍然有开销,百万行数据可以先在样本上验证匹配结果,再全量执行。对于需要反复使用的正则,可以先用re.compile编译,传给str.replace时也能提升一点效率。
Pandas的Series.str方法族把正则和向量化结合,适合完成字符串分割、修改、提取和连接。掌握expand、捕获组、命名组、na_rep这些参数,可以少写很多循环。实际使用时注意显式指定regex参数,避免版本差异带来的歧义。处理完字符串后,及时转换数据类型,让后续数值计算或过滤更可靠。

