
本文介绍如何使用 pandas 对 hr 系统中同一员工的多条任职记录进行去重聚合:以最高 fte 的记录为主干,同时将所有部门按 fte 降序拼接为描述字段,并支持带权重标注(如“school 2(0.5330 fte)”)。
本文介绍如何使用 pandas 对 hr 系统中同一员工的多条任职记录进行去重聚合:以最高 fte 的记录为主干,同时将所有部门按 fte 降序拼接为描述字段,并支持带权重标注(如“school 2(0.5330 fte)”)。
在人力资源数据分析中,员工常存在多角色、跨部门、多合同类型(如 CONP、TEPT)并发的复杂任职情况。原始数据库每日更新,导致同一 Employee# 对应多行记录——这些记录在关键业务字段(如 Department、FTE、Ending)上存在差异,无法简单用 drop_duplicates() 处理。目标是:对每位员工生成一条聚合记录,其中:
- 主部门(
Department)取其所有任职中FTE最高的那条; - 所有部门按
FTE从高到低排序,拼接为Description字段; - 支持扩展格式(如含 FTE 数值的可读描述);
- 其他字段(如
Title、Type、Starting、Ending)需合理选取代表值(通常取最高 FTE 行对应值,或按业务逻辑指定)。
✅ 核心实现步骤(Pandas)
首先确保数据已清洗完毕(如已剔除仅 EntryID 和 ChangeDate 不同的纯重复项),然后执行以下操作:
import pandas as pd
# 示例数据(注意:列名需与实际一致,此处使用问题中字段)
df = pd.DataFrame({
'EmployeeEntryID': [1420, 1420, 5540],
'ChangeDate': ['20241122', '20241122', '20241202'],
'Employee#': [1234, 1234, 1234],
'Firstname': ['Tom', 'Tom', 'Tom'],
'Lastname': ['Jones', 'Jones', 'Jones'],
'Type': ['CONP', 'TEPT', 'TEPT'],
'Title': ['TEACHER', 'TEACHER', 'TEACHER'],
'Department': ['School 2', 'School 3', 'School 1'],
'Starting': ['20240826', '20240826', '20240826'],
'Ending': ['99999999', '20250630', '20250630'],
'FTE': [0.5330, 0.1000, 0.1801]
})
# 步骤 1:按 Employee# 分组前,先按 FTE 降序排序(确保 highest FTE 排第一)
df_sorted = df.sort_values(['Employee#', 'FTE'], ascending=[True, False])
# 步骤 2:分组聚合 —— 关键在于区分「主字段」和「描述字段」
agg_dict = {
'Firstname': 'first',
'Lastname': 'first',
'Type': 'first', # 取最高 FTE 行的 Type(若需保留全部,见下方扩展)
'Title': 'first',
'Starting': 'first', # 注意:Starting 未必相同,此处按业务取首行;如需 earliest,用 'min'
'Ending': lambda x: str(x.max()), # 取最大 Ending(如 99999999 表示永久),或按需处理
'FTE': 'first', # 最高 FTE 值
}
# 步骤 3:构造 Description 字段(含 FTE 标注,推荐格式)
df_sorted['DeptWithFTE'] = df_sorted['Department'] + '(' + df_sorted['FTE'].round(4).astype(str) + ' FTE)'
description_series = df_sorted.groupby('Employee#')['DeptWithFTE'].apply(', '.join)
# 步骤 4:主聚合 + 注入 Description 和主 Department
result = (df_sorted
.groupby('Employee#')
.agg(agg_dict)
.assign(
Description=description_series,
Department=df_sorted.groupby('Employee#')['Department'].first()
)
.reset_index())
print(result[['Employee#', 'Firstname', 'Lastname', 'Type', 'Title',
'Department', 'Description', 'Starting', 'Ending', 'FTE']])输出结果: | Employee# | Firstname | Lastname | Type | Title | Department | Description | Starting | Ending | FTE | |-----------|-----------|----------|------|---------|------------|--------------------------------------------------|----------|----------|--------| | 1234 | Tom | Jones | CONP | TEACHER | School 2 | School 2(0.533 FTE), School 1(0.1801 FTE), School 3(0.1 FTE) | 20240826 | 99999999 | 0.5330 |
⚠️ 注意事项与最佳实践
-
字段一致性判断:
Type、Title等字段若在同员工多记录中存在实质差异(如一人同时是TEACHER和ADMIN),不应盲目取'first',而应改用lambda x: ', '.join(set(x))或交由业务方确认主职; -
日期字段处理:
Starting建议取min()(最早入职时间),Ending建议取max()(最晚终止时间),而非固定'first'; -
避免列名冲突:原答案中两次对
'Department'赋值会导致覆盖(Pandas 中字典 key 不可重复),务必拆分为独立赋值(如先聚合Description,再单独.assign(Department=...)); -
性能优化:大数据量时,先
sort_values(..., inplace=True)并设置ignore_index=True可提升groupby效率; -
空值安全:添加
.fillna('')到字符串拼接前,防止NaN导致整个Description为NaN。
通过该方法,你不仅能完成基础聚合,还可灵活扩展为带权重、多维度、业务语义清晰的员工任职快照,完美适配夜间 ETL 更新场景。

















