一、实际应用场景描述
在中型及以上企业的人力资源管理中,经常出现:
- 企业需制定或调整岗位薪资标准(Salary Band)
- 市场上同岗位薪资随城市、行业、经验年限波动明显
- 企业内部薪资数据分散在 HR 系统 / Excel 中,缺乏统一口径
- 管理层关心:“我们给得够不够?哪些岗位容易因薪资偏低导致离职?”
典型使用方:
- HR 薪酬组(薪酬对标、调薪预算)
- 业务负责人(招聘定薪、保留核心员工)
- BI / 数据分析岗(人力效能分析)
该场景属于 BI 中 人力资源分析(People Analytics)+ 描述性与诊断性分析 的范畴。
二、引入痛点(Business Pain Points)
从管理与数据角度,可抽象为:
1. 外部市场数据缺失或滞后
- 依赖年度薪酬报告,频率低、颗粒度粗
- 难以及时感知招聘平台上的薪资变化
2. 内部薪资结构不透明
- 同岗不同薪、薪资倒挂
- 缺乏“市场分位值(P25/P50/P75)”对标
3. 决策依据不足
- 调薪往往“拍脑袋”或只凭个别 offer
- 难以量化:低于市场多少会带来流失风险
4. 无法闭环验证
- 薪资调整后,难以后续跟踪离职率、招聘接受率等效果
三、核心逻辑讲解(BI + 数据分析视角)
1. 问题拆解
整体流程为:
市场薪资数据 ──► 清洗 / 标准化 ──► 按岗位+城市+经验分组统计
│
▼
企业内部员工薪资 ──► 同口径聚合 ──► 与市场分位值比对
│
▼
输出:竞争力缺口、异常岗位、调薪建议
2. 关键统计口径(BI 指标设计)
- 月薪估算:将“10-15k×13薪”等转为月度均值
- 分组维度:岗位、城市、工作年限区间
- 市场指标:
- P25 / P50(中位数)/ P75
- 对比指标:
- 企业 P50 与市场 P50 差值
- 低于市场 P25 的人数占比(风险人群)
3. 优化目标(中性描述)
- 识别明显低于市场合理区间的岗位/员工
- 为薪酬宽带(Salary Band)校准提供数据依据
- 降低因“薪资明显偏低”带来的可预防性流失风险
四、Python 程序模块化设计
项目结构
salary_benchmark/
│
├── data_loader.py # 数据读取与基础清洗
├── market_stats.py # 市场薪资统计(分位值)
├── benchmark.py # 企业薪资对标
├── reporter.py # 结果输出与摘要
├── main.py # 入口程序
└── README.md
五、代码模块化示例(注释清晰)
1. data_loader.py
import pandas as pd
def load_market_data(path: str) -> pd.DataFrame:
"""
读取市场招聘薪资数据(例如从公开数据集或合规来源)
必要字段:
- job_title
- city
- experience_year_min
- experience_year_max
- salary_min
- salary_max
- salary_months(年薪月数,通常12)
"""
df = pd.read_csv(path)
return df
def load_internal_salary(path: str) -> pd.DataFrame:
"""
读取企业内部薪资数据
必要字段:
- emp_id
- job_title
- city
- experience_years
- monthly_salary
"""
df = pd.read_csv(path)
return df
2. market_stats.py
import numpy as np
def calc_monthly_salary(df: pd.DataFrame) -> pd.DataFrame:
"""
计算市场岗位估算月薪
"""
df = df.copy()
df["monthly_salary"] = (
(df["salary_min"] + df["salary_max"]) / 2 / df["salary_months"]
)
return df
def group_market_stats(df: pd.DataFrame) -> pd.DataFrame:
"""
按岗位+城市+经验区间分组,输出分位值
"""
df["exp_band"] = pd.cut(
df["experience_year_min"],
bins=[0, 1, 3, 5, 10, 100],
labels=["0-1", "1-3", "3-5", "5-10", "10+"]
)
stats = (
df.groupby(["job_title", "city", "exp_band"])["monthly_salary"]
.agg(
market_p25="quantile",
market_p50="median",
market_p75="quantile",
count="count"
)
.reset_index()
)
stats["market_p25"] = stats["market_p25"].apply(lambda x: round(x, 0))
stats["market_p50"] = stats["market_p50"].apply(lambda x: round(x, 0))
stats["market_p75"] = stats["market_p75"].apply(lambda x: round(x, 0))
return stats
3. benchmark.py
def assign_exp_band(years):
"""
将员工工作年限映射到同一分组
"""
if years < 1:
return "0-1"
elif years < 3:
return "1-3"
elif years < 5:
return "3-5"
elif years < 10:
return "5-10"
else:
return "10+"
def benchmark_internal(internal_df: pd.DataFrame,
market_stats_df: pd.DataFrame) -> pd.DataFrame:
"""
将内部薪资与市场规模薪资对标
"""
df = internal_df.copy()
df["exp_band"] = df["experience_years"].apply(assign_exp_band)
df = df.merge(
market_stats_df,
on=["job_title", "city", "exp_band"],
how="left"
)
df["gap_vs_market_p50"] = df["monthly_salary"] - df["market_p50"]
df["below_market_p25"] = df["monthly_salary"] < df["market_p25"]
return df
4. reporter.py
def summary_report(bench_df: pd.DataFrame) -> None:
"""
输出诊断摘要(控制台示例)
"""
total = len(bench_df)
risk = bench_df["below_market_p25"].sum()
print("===== 薪资对标摘要 =====")
print(f"样本人数: {total}")
print(f"低于市场P25人数: {risk}")
print(f"占比: {risk / total:.1%}")
risk_jobs = (
bench_df[bench_df["below_market_p25"]]
.groupby("job_title")["emp_id"]
.count()
.sort_values(ascending=False)
)
print("\n风险岗位(低于P25人数):")
print(risk_jobs.head(10))
5. main.py
from data_loader import load_market_data, load_internal_salary
from market_stats import calc_monthly_salary, group_market_stats
from benchmark import benchmark_internal
from reporter import summary_report
def main():
market_raw = load_market_data("market_jobs.csv")
internal = load_internal_salary("internal_salary.csv")
market = calc_monthly_salary(market_raw)
market_stats = group_market_stats(market)
result = benchmark_internal(internal, market_stats)
summary_report(result)
result.to_csv("salary_benchmark_result.csv", index=False)
if __name__ == "__main__":
main()
六、README 文件(示例)
# Salary Benchmark Tool
## 简介
基于 Python 的薪资行情统计与企业内部薪资对标示例程序,
用于支持薪酬分析、岗位薪资校准与保留风险识别。
## 数据要求
- market_jobs.csv:岗位、城市、经验、薪资区间
- internal_salary.csv:员工、岗位、城市、经验、月薪
## 运行方式
bash
pip install pandas numpy
python main.py
## 输出
- 控制台摘要报告
- salary_benchmark_result.csv(含市场分位值与差距)
七、核心知识点卡片(Course Concepts)
类别 内容
数据层 数据清洗、月薪标准化、分组聚合
BI 指标 P25/P50/P75、薪资缺口、风险人群占比
分析类型 描述性分析 + 诊断性分析
人力域 薪酬宽带、外部公平性、保留风险
技术栈 Pandas、NumPy、分组统计
管理视角 调薪依据、岗位竞争力、数据驱动 HR
八、去营销化说明
- 不依赖任何商业 HR 软件或薪酬数据库
- 可使用公开数据集、脱敏样本数据或教学数据
- 逻辑以可解释统计为主,避免黑盒模型导向
- 目标为“分析支持”,不等同于完整薪酬制度设计
九、总结
该示例说明:
- 薪资体系优化可以先从“市场对标 + 内部诊断”做起
- Python 很适合做这类 分组统计 + 比对分析 的 BI 原型
- 输出结果应服务于:识别问题 → 限定范围 → 支持协商与调整
- 在企业中,这类分析通常进一步扩展为:薪酬仪表盘(Dashboard)、季度对标、流失关联分析等
利用AI解决实际问题如果你觉得这个工具好用,欢迎关注长安牧笛!