FairWage-ML
Benchmarking global salary inequality.
- 0.98
- R² champion model
- 250k
- Salary records
- 62.7%
- Counterfactual gain
The problem
A salary number means nothing without context. $60k in San Francisco and $60k in Port of Spain are different lives — yet most salary benchmarks ignore cost-of-living, purchasing-power parity, and rent burden entirely. FairWage-ML asks a sharper question: who is actually underpaid, once the economics of where they live are priced in?
The pipeline
An end-to-end ML pipeline over 250,000 salary records, integrating macroeconomic indicators — cost-of-living indices, PPP, rent burden — into the feature space alongside role, experience, and geography.
- Champion model — XGBoost reached R² = 0.98 with an RMSE of $5,213, outperforming MLP and linear baselines.
- Explainability — SHAP beeswarm and waterfall plots identified job title, country, and experience as the primary salary drivers.
- Experiment tracking via MLflow across model iterations.
The 250,000 records didn't arrive as one clean table — they're a merge of two datasets: a synthetic job-salary set (job title, experience, education, industry, company size, remote status, salary) and a country-level cost-of-living dataset (Numbeo's cost-of-living, rent, groceries, and purchasing-power indices). We left-joined on country to keep every salary record intact, which meant accepting that ~25,000 rows (10%) would have no macroeconomic match — those countries simply weren't in the Numbeo set. Rather than drop them and quietly bias the sample toward better-covered countries, we kept the gaps and flagged them, same as we did for IQR-detected outliers: an outlier_flag column instead of deletion. Losing 10% of records to get a "complete" table would have been the wrong trade — the missingness itself was informative about which countries get measured at all.
The fairness framework
Prediction alone doesn't answer the fairness question, so the project builds a Fair Wage Index on top of the champion model: a counterfactual repricing benchmark, adjusted for local macroeconomic conditions using a Cobb-Douglas parity structure.
First, the Global Skill Benchmark answers "what would this worker earn if their exact skill profile were priced in the US labor market?" — computed by swapping in US country-level features and re-running the trained regressor:
Dividing actual salary by that counterfactual gives the global Fair Wage Index — a worker's pay as a fraction of their US-priced skill benchmark:
A strict US comparison ignores that living costs genuinely differ across countries, so the Cobb-Douglas Parity Benchmark discounts the GSB by local cost-of-living-plus-rent and GDP-per-capita-at-PPP, each raised to a weight (set to 0.5 — equal weighting between the two channels):
Using DiCE-ML, the pipeline also generates counterfactual career pathways: minimal, actionable changes to a worker's profile and their predicted effect. The headline finding — senior role transitions yield up to 62.7% salary gains for developing-economy workers.
The honest caveats matter here. The salary data is synthetic, so the wage gaps and anomaly rates describe how the framework behaves on its data-generating process, not a verified real-world labor market — a distinction I'd want any reader to hold onto before citing the 46.2% figure as a real finding. The developing-economy side of the analysis is also a single case study (India, since 8 of the dataset's 9 countries sit above Numbeo's purchasing-power baseline), which limits how far the cross-country claim generalizes. And both fairness indices bake in normative choices — the US as skill-price anchor, — that are transparent but not neutral; a different anchor or weighting would shift the numbers. What gives me more confidence in the mechanism itself: a placebo test that reruns the same pipeline treating Germany as the "anomalous" country against the other developed economies returns a rank-biserial effect of only , versus for the real India signal — the framework isn't just finding noise, it's discriminating a genuine structural gap from ordinary between-country variation.
Results
- R² = 0.98, RMSE $5,213 on the champion XGBoost model (vs. R² = 0.955 linear baseline)
- 250,000 records with macroeconomic feature integration, 224,935 after excluding rows with no cost-of-living match
- FWI_global = 0.538 for India — 46.2% below the US skill-price benchmark, confirmed by a Mann-Whitney U test (p < 0.001, rank-biserial r = 1.000 — complete stochastic separation from every developed-country worker in the set)
- 2.5% of workers (845) flagged as consensus underpayment anomalies by a three-method detector (z-score, IQR, Isolation Forest), consistent across all nine countries (2.3%–2.8%)
- 62.7% counterfactual salary gains identified for developing-economy workers transitioning to a senior role

One nuance worth flagging on the parity side: India's FWI_parity comes out to 3.088 — above parity, the opposite direction from FWI_global's 0.538. That's not a contradiction so much as an artifact of the benchmark: India's very low cost-of-living and GDP-per-capita produce such a small Cobb-Douglas denominator ($31k) that the actual median salary ($96k) reads as "overpaid" relative to it. It's a good reminder that a fairness index is only as trustworthy as the benchmark underneath it, and worth stress-testing before treating either number as ground truth.
