FairWage-ML

Benchmarking global salary inequality.

0.98
R² champion model
250k
Salary records
62.7%
Counterfactual gain
PythonXGBoostSHAPDiCE-MLMLflow

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:

SkGSB=y^kUS=f(xkcUS)S_k^{\text{GSB}} = \hat{y}_k^{\text{US}} = f(\mathbf{x}_k \oplus \mathbf{c}_{\text{US}})

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:

FWIkglobal=SkactualSkGSB\text{FWI}_k^{\text{global}} = \frac{S_k^{\text{actual}}}{S_k^{\text{GSB}}}

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 α\alpha (set to 0.5 — equal weighting between the two channels):

CDPBk=SkGSB(CoLRentkCoLRentUS)α(GDPpcPPPkGDPpcPPPUS)1α\text{CDPB}_k = S_k^{\text{GSB}} \left( \frac{\text{CoLRent}_k}{\text{CoLRent}_{\text{US}}} \right)^{\alpha} \left( \frac{\text{GDPpcPPP}_k}{\text{GDPpcPPP}_{\text{US}}} \right)^{1-\alpha} FWIkparity=SkactualCDPBk\text{FWI}_k^{\text{parity}} = \frac{S_k^{\text{actual}}}{\text{CDPB}_k}

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, α=0.5\alpha = 0.5 — 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 r=0.167|r| = 0.167, versus r=1.000|r| = 1.000 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

Choropleth of projected fair salary under the Cobb-Douglas parity benchmark across 121 countries, using the US counterfactual median as the skill-price anchor

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.