基于正则的Python CASE语句实现:DataFrame列生成问题求助
解决Pandas中根据FileName生成RdwyId列的问题
问题背景
先明确下你的需求:你有这样一个DataFrame:
| FileName |
|---|
| 01011RT0TU7 |
| 11041NT4TU8 |
| 51391RST0U2 |
| 01011645RT0TU9 |
| 11311455TX0TU8 |
| 51041545ST3TU9 |
想要新增一列RdwyId,规则很清晰:
- 如果
FileName前5位全是数字,就拿这5位拼接"000"当RdwyId - 如果前8位全是数字,直接取这8位当
RdwyId
你尝试了三种方案都没成功,咱们先拆解下每个方案的问题,再给你靠谱的解决办法。
你的三个方案为什么失败?
方案1的问题
rdwyre1=re.compile(r'\d\d\d\d\d') rdwyre2=re.compile(r'\d\d\d\d\d\d\d\d') rdwy1=rdwyre1.findall(str(thous["FileName"])) rdwy2=rdwyre2.findall(str(thous["FileName"])) thous["RdwyId"]=re.sub(r'\d\d\d\d\d', str(thous["FileName"].loc[:4])+"000",thous["FileName"])
- 你把整个
FileName列转成字符串str(thous["FileName"]),这会把所有单元格值拼成一个大字符串,正则匹配的是整个列的文本,不是单个单元格 re.sub只能处理单个字符串,直接传Pandas Series会报错,而且你的替换逻辑也不对——你不是要修改原文件名,而是要生成新的RdwyId值
方案2的问题
thous["RdwyId"]=np.select( [ re.search(r'\d\d\d\d\d',thous["FileName"])!="None", rdwyre2.findall(str(thous["FileName"]))!="None" ], [ rdwyre1.findall(str(thous["FileName"]))+"000", rdwyre2.findall(str(thous["FileName"])), ], default="Unknown" )
re.search和findall都只能处理单个字符串,你传整个Series进去根本没法正确匹配findall返回的是列表,你拿它和字符串"None"比较永远是False,条件判断完全失效- 就算条件对了,列表和"000"拼接会得到
['01011']000这种奇怪的结果,不是你要的字符串
方案3的问题
thous=thous.assign(RdwyID=lambda x: str(rdwyre1.search(x).group())+"000" if bool(rdwyre1.search(x))==True else str(rdwyre2.search(x).group()))
- 这里的
lambda x里的x是整个DataFrame,不是单个FileName单元格!你相当于在搜索整个DataFrame的文本,完全不是你要的操作 - 就算你改成针对
FileName列,当某个值不满足前5位数字时,直接调用rdwyre2.search(x).group()会报错——如果前8位也不是数字,search返回None,调用group()会直接抛出AttributeError
正确的解决方法
方法1:用Pandas字符串方法(直观易懂,适合新手)
咱们直接针对每个单元格处理,用字符串切片和判断就搞定:
import pandas as pd # 先构造你的DataFrame data = { "FileName": [ "01011RT0TU7", "11041NT4TU8", "51391RST0U2", "01011645RT0TU9", "11311455TX0TU8", "51041545ST3TU9" ] } thous = pd.DataFrame(data) # 定义生成RdwyId的函数 def get_rdwy_id(file_name): # 先判断前5位是不是全数字,且第6位不是数字(避免和前8位数字的情况冲突) if file_name[:5].isdigit() and not file_name[5].isdigit(): return file_name[:5] + "000" # 再判断前8位是不是全数字 elif file_name[:8].isdigit(): return file_name[:8] # 可选:处理不符合两种规则的情况,返回默认值 else: return "Unknown" # 对每个FileName单元格应用这个函数 thous["RdwyId"] = thous["FileName"].apply(get_rdwy_id)
运行后就能得到你想要的结果:
FileName RdwyId 0 01011RT0TU7 01011000 1 11041NT4TU8 11041000 2 51391RST0U2 51391000 3 01011645RT0TU9 01011645 4 11311455TX0TU8 11311455 5 51041545ST3TU9 51041545
方法2:用正则表达式提取(更灵活,适合规则变化的场景)
如果以后规则有调整,用正则会更方便。咱们用str.extract匹配两种模式:
# 匹配两种模式:开头5位数字+非数字,或者开头8位数字 extracted = thous["FileName"].str.extract(r'^(\d{5})(?=\D)|^(\d{8})', expand=False) # 取每行第一个非空的匹配结果 thous["RdwyId"] = extracted.bfill(axis=1).iloc[:, 0] # 对5位的结果拼接000 thous["RdwyId"] = thous["RdwyId"].apply(lambda x: x + "000" if len(x) == 5 else x)
解释下正则:
^(\d{5})(?=\D):匹配开头5位数字,且后面跟着非数字(确保不是前8位数字的情况),提取这5位^(\d{8}):匹配开头8位数字,提取这8位bfill(axis=1):对每一行,从左到右取第一个非空的匹配值
核心要点总结
你之前踩的坑本质是没有针对单个单元格处理,而是把整个Series当成单个字符串操作,混淆了Pandas Series和单个Python字符串的处理逻辑。用apply方法或者Pandas的str系列方法,都是逐个处理单元格的正确姿势,这是处理这类问题的关键。
内容的提问来源于stack exchange,提问作者SQLserving
相关产品推荐
相关产品推荐

