[Stata] Decomposing Group Gaps: oaxaca, fairlie, mvdcmp, and nopo

Decomposition methods split a difference in an outcome between two groups into two parts: the part that comes from the groups having different characteristics (the “explained” or “endowments” part), and the part that remains when the groups are compared at the same characteristics (the “unexplained” or “coefficients” part). The method began with Blinder (1973) and Oaxaca (1973) for wage gaps by gender and race, and it is now common in health disparities research.

This post runs four Stata commands on the same comparison, adults with limited English proficiency and other adults, because each answers a slightly different version of the question:

  • oaxaca by Ben Jann, the standard Blinder-Oaxaca decomposition for a continuous outcome, with detailed contributions by variable.
  • fairlie by Ben Jann, Fairlie’s nonlinear decomposition for a binary outcome.
  • mvdcmp by Daniel Powers, Hirotoshi Yoshioka, and Myeong-Su Yun, a decomposition for logit, probit, Poisson, and other nonlinear models that splits both parts by variable.
  • nopo by Maximilian Sprengholz and Maik Hamjediers, Ñopo’s matching-based decomposition, which also shows how much of a gap comes from people with no comparable counterpart in the other group.
Diagram of the decomposition: limited English proficiency relates to the outcomes through differences in age, gender, insurance, education, and income (explained part) and directly (unexplained part)
Variables in the decomposition examples.

Installation

Stata
ssc install oaxaca
ssc install fairlie
ssc install mvdcmp
ssc install devcon
ssc install nopo
ssc install kmatch

oaxaca (4.1.1, April 2023) and fairlie run on Stata 9.2 or newer, mvdcmp (3.0, July 2023) needs Stata 16, and nopo (1.0.0, February 2024) needs Stata 15. mvdcmp calls devcon and nopo calls kmatch, so both have to be installed as well.

Data

The examples use the 2024 California Health Interview Survey (CHIS) adult public use file, which has 24,810 adult respondents. The CHIS public use files are free and available through a brief application on the UCLA Center for Health Policy Research site. Every post in this series builds its variables with the same short do-file, 00_chis_prep.do:

Stata
use "chis_adult_2024.dta", clear

* outcomes
gen k6      = distress if distress >= 0          // K6 score, past month (0-24)
gen nousual = usual == 2                         // no usual source of care
gen delay   = ah22 == 1                          // delayed or did not get medical care, past 12 months
gen erv     = er == 1                            // any ER visit, past 12 months
gen uninsured = ins == 2
gen needhelp  = af81 == 1 if af81 > 0            // needed help for emotional/mental or substance problem
gen fairpoor  = ab1 >= 4                         // fair or poor self-rated health
gen spd       = dstrs12 == 1                     // likely serious psychological distress, past year

* groups
gen lep     = spk_eng == 3                       // speaks English not well or not at all
gen foreign = ah33new == 2
gen female  = srsex == 2

* covariates
gen age = srage_p1
recode ahedc_p1 (1 2 = 1 "Less than high school") (3 = 2 "High school") ///
    (4 5 6 = 3 "Some college") (7 8 9 = 4 "Bachelor's or more"), gen(educ)
gen pov = povll
label define povllf 4 "300%+ FPL", modify
label values pov povllf
recode ombsrr_p1 (1 = 1 "Latino") (2 = 2 "White") (3 = 3 "Black") (5 = 4 "Asian") ///
    (4 6 = 5 "Other or multiracial"), gen(race)

* CHIS design: final weight plus 80 jackknife replicate weights
svyset [pw=rakedw0], jkrweight(rakedw1-rakedw80, multiplier(1)) vce(jackknife) mse

Limited English proficiency (lep) means the respondent reported speaking English not well or not at all; 1,520 respondents (6.1 percent) meet that definition. Commands that accept the svy prefix use the jackknife replicate weights; commands that do not accept it use the final weight rakedw0 as a probability weight, which gives the right point estimates and robust standard errors that ignore the replicate design.

For these commands, the education and income categories enter as indicator variables, and age is in decades so its coefficients are easier to read:

Stata
tab educ, gen(ed)
tab pov, gen(pv)
gen age10 = age/10

The gaps

Stata
svy: mean nousual k6, over(lep)
Output
---------------------------------------------------------------
              |              Jknife *
              |       Mean   std. err.     [95% conf. interval]
--------------+------------------------------------------------
c.nousual@lep |
          No  |   .1443258   .0031556      .1380447    .1506068
         Yes  |    .229081     .01448      .2002594    .2579027
              |
     c.k6@lep |
          No  |   4.520119   .0491033      4.422382    4.617857
         Yes  |   4.030831   .1388643      3.754429    4.307234
---------------------------------------------------------------

Adults with limited English proficiency are more likely to have no usual source of care (22.9 percent compared with 14.4 percent) and report slightly lower K6 distress scores (4.03 compared with 4.52).

oaxaca for a continuous outcome

Stata
oaxaca k6 age10 female uninsured normalize(ed1-ed4) normalize(pv1-pv4) ///
    [pw=rakedw0], by(lep) pooled
Output
Blinder-Oaxaca decomposition                            Number of obs = 24,794
Group 1: lep = 0                                N of obs 1        =     23,275
Group 2: lep = 1                                N of obs 2        =      1,519

    explained: (X1 - X2) * b
  unexplained: X1 * (b1 - b) + X2 * (b - b2)
               with b from pooled model (including group dummy)

------------------------------------------------------------------------------
          k6 | Coefficient  Std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
overall      |
     group_1 |   4.520119   .0568359    79.53   0.000     4.408723    4.631516
     group_2 |   4.030831   .1733719    23.25   0.000     3.691029    4.370634
  difference |   .4892879   .1824504     2.68   0.007     .1316917    .8468842
   explained |    .000868   .1573911     0.01   0.996    -.3076129     .309349
 unexplained |   .4884199   .2357918     2.07   0.038     .0262764    .9505634
-------------+----------------------------------------------------------------
explained    |
       age10 |   .4858763   .0573815     8.47   0.000     .3734106    .5983421
      female |  -.0290274   .0110474    -2.63   0.009    -.0506799    -.007375
   uninsured |   .0345112    .034016     1.01   0.310    -.0321591    .1011814
         ed1 |  -.0918062    .109787    -0.84   0.403    -.3069846    .1233723
         ...
         pv4 |  -.2474612   .0383409    -6.45   0.000     -.322608   -.1723145
------------------------------------------------------------------------------

pooled uses the coefficients from a model fit to both groups (with a group indicator) as the reference, which is a common choice when neither group is a natural standard. normalize() matters for categorical predictors: without it, the detailed unexplained contributions of a set of indicators change with the choice of reference category, and normalizing reports them as deviations from the overall mean so they do not depend on that choice.

The K6 gap is 0.49 points. The explained part is essentially zero (0.001), but not because characteristics do not matter: the contributions offset each other. Adults with limited English proficiency are older on average, and older adults report less distress, so age alone accounts for +0.49 points. Their lower incomes push the other way, and the top income category alone accounts for −0.25 points. Looking only at the total explained part would hide both.

fairlie for a binary outcome

The Blinder-Oaxaca decomposition is linear. For a binary outcome, Fairlie’s method (Fairlie, 2005) uses a logit or probit model and computes each variable’s contribution by replacing its distribution in one group with that of the other, one variable at a time. Because the order of replacement can affect the results, ro randomizes the order in each replication, and reps() sets the number of replications.

Stata
set seed 20260915
fairlie nousual age10 female uninsured ed2 ed3 ed4 pv2 pv3 pv4 [pw=rakedw0], ///
    by(lep) pooled(lep) reps(100) ro
Output
Non-linear decomposition by lep (G)                     Number of obs = 24,810
                                                  N of obs G=0    =      23290
                                                  N of obs G=1    =       1520
                                                  Pr(Y!=0|G=0)    =  .14410169
                                                  Pr(Y!=0|G=1)    =  .23155036
                                                  Difference      = -.08744867
                                                  Total explained = -.03883133
------------------------------------------------------------------------------
     nousual | Coefficient  Std. err.      z    P>|z|     [95% conf. interval]
-------------+----------------------------------------------------------------
       age10 |   .0311272   .0024141    12.89   0.000     .0263956    .0358588
      female |   .0035586    .000837     4.25   0.000     .0019182    .0051991
   uninsured |  -.0337109   .0030137   -11.19   0.000    -.0396175   -.0278042
         ed2 |  -.0012268   .0013439    -0.91   0.361    -.0038608    .0014072
         ed3 |  -.0015906   .0029146    -0.55   0.585    -.0073032    .0041219
         ed4 |  -.0122313   .0058304    -2.10   0.036    -.0236586    -.000804
         pv2 |   .0032396   .0013855     2.34   0.019      .000524    .0059551
         pv3 |   .0002019   .0004828     0.42   0.676    -.0007443    .0011482
         pv4 |  -.0282868   .0049914    -5.67   0.000    -.0380697   -.0185039
------------------------------------------------------------------------------

fairlie reports the difference as group 0 minus group 1, so the gap is −0.087 and contributions with the same sign as the gap help account for it. The characteristics in the model account for 0.039 of the 0.087 gap, or 44 percent. Being uninsured (−0.034) and the top income category (−0.028) account for most of that, while age works against it (+0.031), because adults with limited English proficiency are older and older adults are more likely to have a usual source of care. pooled(lep) uses a pooled model that includes the group indicator, as the help file recommends. Note that fairlie does not take a seed() option; set the seed before the command.

mvdcmp

mvdcmp puts the estimation command after a colon, and splits both the characteristics part (E) and the coefficients part (C) by variable. The group named first is the reference group A.

Stata
mvdcmp lep: logit nousual age10 female uninsured ed2 ed3 ed4 pv2 pv3 pv4 [pw=rakedw0]
Output
         Reference group  (A):lep==1             Mean = 0.2316
         Comparison group (B):lep==0             Mean = 0.1441
-----------------------------------------------------------------------------
                       nousual |      Coef.  Std. Err.       z    P>|z|    Pct.
-------------------------------+---------------------------------------------
                             E |    0.02601    0.02053    1.27    0.205   29.74
                             C |    0.06144    0.02187    2.81    0.005   70.26
-------------------------------+---------------------------------------------
                             R |    0.08745    0.01717    5.09    0.000

                      Due to Difference in Characteristics (E)
                         age10 |   -0.02005    0.00817   -2.45    0.014  -22.93
                        female |   -0.00338    0.00276   -1.22    0.221   -3.87
                     uninsured |    0.02346    0.00776    3.02    0.002   26.82
                           ...
                           pv4 |    0.01773    0.02191    0.81    0.418   20.27

(The output is shortened here.) The raw gap R is 0.087, the same as in fairlie. Here 30 percent is attributed to characteristics, compared with 44 percent in fairlie, because mvdcmp evaluates the characteristics part with the coefficients of group A, adults with limited English proficiency, while the fairlie call above used pooled coefficients. The choice of reference coefficients is part of the method, and a paper should report which one it used. The pattern by variable is the same in both: insurance and income account for part of the gap and age offsets it.

mvdcmp has a norm() option for sets of indicators. When I gave it the full sets (ed1-ed4, pv1-pv4), the logit model dropped one indicator in each set and the total gap R no longer matched the observed difference, so the example uses ordinary indicators with an omitted reference category.

nopo

The regression-based methods compare the groups as if every combination of characteristics occurred in both. Ñopo’s decomposition (Ñopo, 2008) matches people exactly on their characteristics and separates the part of the gap that comes from people who have no match in the other group. Hamjediers and Sprengholz (2023) argue that this lack of common support is often ignored in regression decompositions. Exact matching needs categorical variables, so age enters in four groups.

Stata
gen agegrp = irecode(age, 29, 44, 64)
nopo decomp k6 agegrp female uninsured educ pov [pw=rakedw0], by(lep)
Output
Nopo decomposition                       N                         =     24794
Exact matching:                          N strata                  =       242
                                         N matched strata          =       179
------------------------------------------------------------------------------
                             |              N / %                     Mean
                             | ---------------------------------  ------------
Group                        |  Matched    Unmatched       Total            k6
-----------------------------+------------------------------------------------
A: lep == 0 (bref)           |    22409          866       23275          4.52
   No                        |     96.3          3.7
B: lep == 1 (xref)           |     1500           19        1519          4.03
   Yes                       |     98.7          1.3
------------------------------------------------------------------------------
          k6 | Coefficient
-------------+----------------------------------------------------------------
           D |  -.4892879
          D0 |  -.4030192
          DX |   .0785433
          DA |  -.1550645
          DB |  -.0097475
------------------------------------------------------------------------------

D is the total gap (B minus A, −0.49). D0 is the part that remains among matched people with the same characteristics (−0.40), DX is the part due to different distributions of characteristics among matched people (+0.08), and DA and DB are the parts due to unmatched people in each group. Here 866 adults without limited English proficiency, 3.7 percent of that group, have combinations of characteristics that no adult with limited English proficiency in the sample shares, and they account for −0.16 points of the gap. The four parts sum to the total.

nopo gapoverdist plots each component across the distribution of the outcome:

Stata
nopo gapoverdist, twopts(scale(1.4))
Line plot of the Nopo decomposition components across 100 quantiles of K6, with the total gap near zero in the lower half and negative in the upper quantiles
Ñopo decomposition components across the K6 distribution.

The gap is close to zero at most quantiles and largest in the top fifth of the distribution, where adults with limited English proficiency report less severe distress. The lines are jagged because K6 takes only whole-number values, so many quantiles share the same score.

Notes

Decomposition is descriptive. The explained part says how much of a gap goes with differences in measured characteristics under the chosen reference coefficients; it does not say that changing those characteristics would close the gap. These commands take probability weights, and oaxaca and mvdcmp also accept the svy prefix, but the examples above use [pw=rakedw0] with robust standard errors rather than the CHIS replicate weights.

References

Blinder, A. S. (1973). Wage discrimination: Reduced form and structural estimates. The Journal of Human Resources, 8(4), 436–455. https://doi.org/10.2307/144855

Fairlie, R. W. (2005). An extension of the Blinder-Oaxaca decomposition technique to logit and probit models. Journal of Economic and Social Measurement, 30(4), 305–316. https://doi.org/10.3233/JEM-2005-0259

Hamjediers, M., & Sprengholz, M. (2023). Comparing the incomparable? Issues of lacking common support, functional-form misspecification, and insufficient sample size in decompositions. Sociological Methodology, 53(2), 344–365. https://doi.org/10.1177/00811750231169729

Jann, B. (2008). The Blinder–Oaxaca decomposition for linear regression models. The Stata Journal, 8(4), 453–479. https://doi.org/10.1177/1536867X0800800401

Ñopo, H. (2008). Matching as a tool to decompose wage gaps. Review of Economics and Statistics, 90(2), 290–299. https://doi.org/10.1162/rest.90.2.290

Oaxaca, R. (1973). Male-female wage differentials in urban labor markets. International Economic Review, 14(3), 693–709. https://doi.org/10.2307/2525981

Powers, D. A., Yoshioka, H., & Yun, M.-S. (2011). mvdcmp: Multivariate decomposition for nonlinear response models. The Stata Journal, 11(4), 556–576. https://doi.org/10.1177/1536867X1101100404

  • September 2, 2026