14  Matching and Weighting - Part 2: weighting

Published

May 31, 2025

This is based on R’s MatchIt and WeightIt packages and their documentations. All credits go to Noah Greifer and coauthors. I write to summarize what I have learned.

14.1 Assumptions

Matching or weighting is usually used in observational studies. We have the following assumptions before using matching or weighting to make a causal claim:

  1. SUTVA
  2. ignorability (unconfoundedness, or no unobserved confounders)
  3. overlap (common support, or positivity)

With these assumptions, we can try to identify the treatment effect in an observational study.

14.2 Problem

The second assumption is saying controlling for \(X\), the treatment assignment \(D\) is independent of the potential outcomes \(Y(0)\) and \(Y(1)\), i.e., we have a pseudo-randomized experiment.

In reality, it’s rare we have a balanced distribution of \(X\) for treated and control. That’s when we need to use matching or weighting to balance the distribution of \(X\) between treatment and control groups.

14.3 weighting methods

The idea of weighting is to use weights to creat a pseudo population which has a balanced distribution of \(X\) for treated and control groups. The most popular weighting method is inverse probability of treatment weighting (IPTW). IPTW uses the propensity score to create weights.

There are a few other weighting methods that are gaining popularity.

14.3.1 Inverse probability of treatment weighting (IPTW)

IPTW is the most common weighting method. It uses the inverse of the propensity score as weights. The idea is to weight each treated unit by \(1/p\) and each control unit by \(1/(1-p)\), where \(p\) is the propensity score. This way, we can create a pseudo population where the distribution of \(X\) is balanced between treatment and control groups.

14.3.2 Covariate balancing propensity score (CBPS)

The same idea, but with a different way to estimate the propensity score. CBPS is a form of logistic regression where balance constraints are incorporated to a generalized method of moments estimation of the model coefficients.

14.3.3 Entropy balancing

Entropy balancing involves the specification of an optimization problem, the solution to which is then used to compute the weights. The constraints of the primal optimization problem correspond to covariate balance on the means (for binary and multi-category treatments) or treatment-covariate covariances (for continuous treatments), positivity of the weights, and that the weights sum to a certain value. Entropy balancing is doubly robust (for the ATT) in the sense that it is consistent either when the true propensity score model is a logistic regression of the treatment on the covariates or when the true outcome model for the control units is a linear regression of the outcome on the covariates, and it attains a semi-parametric efficiency bound when both are true. Entropy balancing will always yield exact mean balance on the included terms (not necessarily on the distribution of the covariates)

14.3.4 Energy balancing

Energy balancing is a method of estimating weights using optimization without a propensity score.

The primary benefit of energy balancing is that all features of the covariate distribution are balanced, not just means, as with other optimization-based methods like entropy balancing.

14.3.5 SuperLearner

SuperLearner works by fitting several machine learning models to the treatment and covariates and then taking a weighted combination of the generated predicted values to use as the propensity scores, which are then used to construct weights.

14.4 Example

Here is an example with Lalonde data, we are interested in the effect of treatment on “re78” (real earnings 1978).

14.4.1 IPW

library("WeightIt")
library(cobalt)
data(lalonde)
w.out1 <- weightit(treat ~ age + educ + race + married + nodegree + re74 + re75,
                  data = lalonde, estimand = "ATT", method = "glm")
w.out1 #print the output
A weightit object
 - method: "glm" (propensity score weighting with GLM)
 - number of obs.: 614
 - sampling weights: none
 - treatment: 2-category
 - estimand: ATT (focal: 1)
 - covariates: age, educ, race, married, nodegree, re74, re75
summary(w.out1)
                  Summary of weights

- Weight ranges:

          Min                                 Max
treated 1.            ||                    1.   
control 0.009 |---------------------------| 3.743

- Units with the 5 most extreme weights by group:
                                    
            5     4    3     2     1
 treated    1     1    1     1     1
          597   573  381   411   303
 control 3.03 3.059 3.24 3.523 3.743

- Weight statistics:

        Coef of Var   MAD Entropy # Zeros
treated       0.    0.      0.          0
control       1.818 1.289   1.098       0

- Effective Sample Sizes:

           Control Treated
Unweighted  429.       185
Weighted     99.82     185
library(cobalt)
bal.tab(w.out1, stats = c("m", "v"), thresholds = c(m = .05))
Balance Measures
                Type Diff.Adj         M.Threshold V.Ratio.Adj
prop.score  Distance  -0.0205     Balanced, <0.05      1.0324
age          Contin.   0.1188 Not Balanced, >0.05      0.4578
educ         Contin.  -0.0284     Balanced, <0.05      0.6636
race_black    Binary  -0.0022     Balanced, <0.05           .
race_hispan   Binary   0.0002     Balanced, <0.05           .
race_white    Binary   0.0021     Balanced, <0.05           .
married       Binary   0.0186     Balanced, <0.05           .
nodegree      Binary   0.0184     Balanced, <0.05           .
re74         Contin.  -0.0021     Balanced, <0.05      1.3206
re75         Contin.   0.0110     Balanced, <0.05      1.3938

Balance tally for mean differences
                    count
Balanced, <0.05         9
Not Balanced, >0.05     1

Variable with the greatest mean difference
 Variable Diff.Adj         M.Threshold
      age   0.1188 Not Balanced, >0.05

Effective sample sizes
           Control Treated
Unadjusted  429.       185
Adjusted     99.82     185

14.4.2 entropy balancing

w.out2 <- weightit(treat ~ age + educ + race + married + nodegree + re74 + re75,
                  data = lalonde, estimand = "ATT", method = "ebal")
summary(w.out2)
                  Summary of weights

- Weight ranges:

          Min                                 Max
treated 1.           ||                     1.   
control 0.008 |---------------------------| 4.062

- Units with the 5 most extreme weights by group:
                                     
             5     4    3     2     1
 treated     1     1    1     1     1
           608   381  597   303   411
 control 3.073 3.235 3.45 3.897 4.062

- Weight statistics:

        Coef of Var   MAD Entropy # Zeros
treated       0.    0.      0.          0
control       1.834 1.287   1.101       0

- Effective Sample Sizes:

           Control Treated
Unweighted  429.       185
Weighted     98.46     185
bal.tab(w.out2, stats = c("m", "v"), thresholds = c(m = .05))
Balance Measures
               Type Diff.Adj     M.Threshold V.Ratio.Adj
age         Contin.       -0 Balanced, <0.05      0.4096
educ        Contin.       -0 Balanced, <0.05      0.6635
race_black   Binary        0 Balanced, <0.05           .
race_hispan  Binary        0 Balanced, <0.05           .
race_white   Binary       -0 Balanced, <0.05           .
married      Binary       -0 Balanced, <0.05           .
nodegree     Binary        0 Balanced, <0.05           .
re74        Contin.       -0 Balanced, <0.05      1.3265
re75        Contin.       -0 Balanced, <0.05      1.3351

Balance tally for mean differences
                    count
Balanced, <0.05         9
Not Balanced, >0.05     0

Variable with the greatest mean difference
 Variable Diff.Adj     M.Threshold
     re74       -0 Balanced, <0.05

Effective sample sizes
           Control Treated
Unadjusted  429.       185
Adjusted     98.46     185

Suppose we are satisfied with the balance, we can use the weights to estimate the treatment effect. We can use the “marginaleffects” package to do that.

# Fit outcome model
fit <- lm_weightit(re78 ~ treat * (age + educ + race + married +
                                     nodegree + re74 + re75),
                   data = lalonde, weightit = w.out2)
# G-computation for the treatment effect
library("marginaleffects")
avg_comparisons(fit, variables = "treat",
                newdata = subset(lalonde, treat == 1))

 Estimate Std. Error    z Pr(>|z|)   S 2.5 % 97.5 %
     1273        770 1.65   0.0983 3.3  -236   2783

Term: treat
Type: probs
Comparison: 1 - 0

14.4.3 energy balancing

w.out3 <- tryCatch(
  weightit(treat ~ age + educ + race + married +
                  nodegree + re74 + re75, data = lalonde,
                method = "energy", estimand = "ATE"),
  error = function(e) { message("Energy balancing failed: ", e$message); NULL }
)
if (!is.null(w.out3)) summary(w.out3)
                  Summary of weights

- Weight ranges:

        Min                                  Max
treated   0 |---------------------------| 14.041
control   0 |---------------------|       11.065

- Units with the 5 most extreme weights by group:
                                        
            10   179   125    137    124
 treated 5.984  6.23 6.758 13.202 14.041
           537   553   573    420    608
 control 5.499 6.404 6.599  8.274 11.065

- Weight statistics:

        Coef of Var   MAD Entropy # Zeros
treated       1.821 1.054   0.92        0
control       1.264 0.866   0.579       0

- Effective Sample Sizes:

           Control Treated
Unweighted  429.    185.  
Weighted    165.42   43.04
if (!is.null(w.out3)) {
  fit <- lm_weightit(re78 ~ treat * (age + educ + race + married +
                                       nodegree + re74 + re75),
                     data = lalonde, weightit = w.out3)
  # G-computation for the treatment effect
  avg_comparisons(fit, variables = "treat")
}

 Estimate Std. Error       z Pr(>|z|)   S 2.5 % 97.5 %
      -73       1085 -0.0672    0.946 0.1 -2200   2054

Term: treat
Type: probs
Comparison: 1 - 0

14.5 comparing matching and weighting

Matching and weighting are popular in different fields. We used to say matching involves discarding data therefore changing the estimand, but now with full matching and other methods, matching can be done without losing data. We used to believe weighting can be unstable, as weights can be highly variable, but with CBPS, entropy balancing and energy balancing, weighting can perform much better.

So, in practice, we can try different methods, either matching or weighting, and see which one gives us better balance. Then use “marginaleffects” to get the treatment effect.


Systematic treatment: R · Julia.