Python处理同分隔符多义的字符串拆分及人员数据清洗问题
业务数据清洗与统计解决方案
原始业务数据
| 记录编号 | 一级人员 | 二级人员 | 日期 | 工作时长 |
|---|---|---|---|---|
| 1 | Tim David, Cameron Green - (Division 1) | 01/01/2023 | 5 | |
| 2 | Tim David - (Division 1) | Mitch, Eli Kin Marsh - (Division 2) | 02/02/2023 | 3 |
| 3 | David Warner - (Division 2), Travis Head - (Division 3) | 03/04/2023 | 1 | |
| 4 | Cameron Green - (Division 1) | Tim David - (Division 1) | 07/01/2023 | 2 |
预期统计结果
| 部门 | 人员 | 月份 | 工作时长 |
|---|---|---|---|
| Division 1 | Tim David | Jan-23 | 7 |
| Division 1 | Tim David | Feb-23 | 3 |
| Division 1 | Cameron Green | Jan-23 | 7 |
| Division 2 | Mitch Eli Kin Marsh | Feb-23 | 3 |
| Division 2 | David Warner | Apr-23 | 1 |
| Division 3 | Travis Head | Apr-23 | 1 |
现有问题与尝试
问题1:人员姓名拆分歧义
逗号既用作不同人员的分隔符(如记录1),也用作姓名内部的分隔符(如记录2),现有代码无法正确拆分出目标人员列表:
- 记录1期望输出:
['Tim David', 'Cameron Green'] - 记录2期望输出:
['Mitch Eli Kin Marsh']
尝试的Python代码
import re import pandas as pd def split_names(row): string = row['level 2 person'] pattern = '([\w\s,-]+)' names = re.split(pattern, string) name_list = list() for name in names: replacements = [('-', ''), ('(', ''), (')', '')] for char, replacement in replacements: if char in name: name= name.replace(char, replacement) name_list.append(name) while("" in name_list): # remove empty elements name_list.remove("") return name_list df['names'] = df.apply(split_names,axis=1)
问题2:部门自动分配
部分人员无单独部门标注(如记录1的两人共享Division 1),需要自动为这类人员匹配对应部门,生成与人员列表长度一致的部门列表。
完整解决方案
步骤1:解析二级人员的姓名与部门
利用字符串规律(部门固定格式为- (Division X)),先拆分人员块再提取对应部门,同时处理姓名内部的逗号:
import re import pandas as pd def parse_level2(row): text = row['二级人员'] result_names = [] result_divisions = [] # 先判断是否是多人员共享单个部门的情况 shared_div_match = re.search(r' - \(Division (\d+)\)$', text) if shared_div_match: division = f'Division {shared_div_match.group(1)}' name_text = re.sub(r' - \(Division \d+\)$', '', text) # 拆分不同人员,替换姓名内的逗号为空格 names = [n.strip().replace(',', ' ') for n in name_text.split(',')] result_names.extend(names) result_divisions.extend([division]*len(names)) else: # 单个人员带部门,或多人员各带部门的情况 person_blocks = re.split(r',\s*(?=[A-Z][a-z]+[^-,]+ - \(Division)', text) for block in person_blocks: if ' - (' in block: name_part, div_part = block.split(' - (', 1) division = div_part.rstrip(')') clean_name = name_part.replace(',', ' ').strip() result_names.append(clean_name) result_divisions.append(division) return pd.Series([result_names, result_divisions]) df[['二级人员列表', '二级部门列表']] = df.apply(parse_level2, axis=1)
步骤2:解析一级人员的姓名与部门
同样处理一级人员的姓名和部门提取:
def parse_level1(row): text = row['一级人员'] if pd.isna(text) or text.strip() == '': return pd.Series([[], []]) name_part, div_part = text.split(' - (', 1) division = div_part.rstrip(')') clean_name = name_part.replace(',', ' ').strip() return pd.Series([[clean_name], [division]]) df[['一级人员列表', '一级部门列表']] = df.apply(parse_level1, axis=1)
步骤3:合并并展开人员数据
将一级、二级人员数据合并,展开为单行单人员的记录格式:
# 合并人员与部门列表 df['人员列表'] = df['一级人员列表'] + df['二级人员列表'] df['部门列表'] = df['一级部门列表'] + df['二级部门列表'] # 展开列表,每行对应一个人员 exploded_df = df.explode(['人员列表', '部门列表']).drop( columns=['一级人员', '二级人员', '一级人员列表', '一级部门列表', '二级人员列表', '二级部门列表'] ) # 转换日期为目标月份格式 exploded_df['月份'] = pd.to_datetime(exploded_df['日期'], format='%d/%m/%Y').dt.strftime('%b-%y')
步骤4:统计月度总时长
按部门、人员、月份分组求和:
final_df = exploded_df.groupby(['部门列表', '人员列表', '月份'])['工作时长'].sum().reset_index() final_df.columns = ['部门', '人员', '月份', '工作时长']
执行上述代码后,final_df即可得到预期的统计结果。
内容的提问来源于stack exchange,提问作者sam_rox
相关产品推荐
相关产品推荐

