本文介绍一种高效、可复用的 Pandas 数据筛选方法:按 manager_movement 类型分别处理——剔除 transfer_out,保留全部 transfer_in,并对 no_change 按 year 和 email_address 去重(保留最后出现的记录),最终合并结果。
本文介绍一种高效、可复用的 pandas 数据筛选方法:按 `manager_movement` 类型分别处理——剔除 `transfer_out`,保留全部 `transfer_in`,并对 `no_change` 按 `year` 和 `email_address` 去重(保留最后出现的记录),最终合并结果。
在实际数据分析中,常需对同一实体(如员工邮箱)在不同时间点的多条记录实施差异化保留策略。本例中的业务逻辑明确分为三类:
- ✅ transfer_in:全部保留(代表新任管理者入职,具有最高优先级);
- ❌ transfer_out:全部剔除(代表离任,无需后续跟踪);
- ? no_change:需去重,但不是简单全局去重,而是以 (year, email_address) 为组合键,保留每个年份-邮箱组合下最后出现的一行(体现最新状态)。
直接使用 apply + 行级条件判断(如原尝试中的 df.apply(get_required_rows, axis=1))不仅语义不清、性能低下,且易因索引错位导致逻辑错误。更优解是分而治之 + 合并归一:先按条件切片,再对各子集独立应用变换,最后纵向拼接。
以下是推荐实现方式(封装为可复用函数):
import pandas as pd
def filter_manager_records(df: pd.DataFrame) -> pd.DataFrame:
"""
根据 manager_movement 状态执行差异化过滤:
- 排除所有 'transfer_out' 记录;
- 保留所有 'transfer_in' 记录;
- 对 'no_change' 记录按 (year, email_address) 去重,keep='last'。
Parameters:
-----------
df : pd.DataFrame
输入 DataFrame,必须包含列:'year', 'year_month', 'manager_movement', 'email_address'
Returns:
--------
pd.DataFrame
过滤后的新 DataFrame,行序为 transfer_in 在前、no_change 在后。
"""
# 步骤1:剔除 transfer_out
df_clean = df[df["manager_movement"] != "transfer_out"].copy()
# 步骤2:分离 transfer_in(全部保留)
df_in = df_clean[df_clean["manager_movement"] == "transfer_in"]
# 步骤3:处理 no_change(按 year + email_address 去重,保留最后)
df_nochange = df_clean[df_clean["manager_movement"] == "no_change"]
df_nochange_dedup = df_nochange.drop_duplicates(
subset=["year", "email_address"],
keep="last"
)
# 步骤4:合并结果(注意:concat 默认保持各部分原始顺序)
result = pd.concat([df_in, df_nochange_dedup], ignore_index=True)
return result
# 使用示例
# filtered_df = filter_manager_records(original_df)
✅ 关键优势说明:
- 逻辑清晰:每类状态独立处理,避免嵌套条件与状态耦合;
- 性能优异:利用布尔索引和向量化 drop_duplicates,远快于 apply 行循环;
- 可维护性强:函数签名明确、文档完整,便于单元测试与后续扩展(例如增加 promotion 类型支持);
- 健壮性高:.copy() 避免 SettingWithCopyWarning;ignore_index=True 重置索引防止重复。
⚠️ 注意事项:
- 确保输入 DataFrame 的 manager_movement 列值严格为 'transfer_in'/'transfer_out'/'no_change'(注意大小写与空格,原问题中示例代码误写为 'No Change',需统一);
- drop_duplicates(..., keep='last') 依赖原始行序——因此务必保证数据已按时间逻辑(如 year_month 升序)预排序,否则“最后”可能不符合业务意义;若未排序,建议先执行:
df = df.sort_values(['year', 'year_month', 'email_address']).reset_index(drop=True)
该方案简洁、高效、符合 Pandas 最佳实践,适用于 HR 系统、组织架构变更分析等典型场景。










