---
title: "Matching and Weighting - Part 2: weighting"
date: "2025-05-31"
---
This chapter follows R's `WeightIt` package (Greifer et al.). The assumptions — SUTVA, ignorability, and overlap — are the same as in the [matching chapter](matching-part1.qmd).
## Weighting methods
Weighting creates a pseudo-population in which $X$ is balanced across treatment and control. The most common approach is inverse probability of treatment weighting (IPTW), which uses the propensity score. Several alternatives are gaining traction.
### Inverse probability of treatment weighting (IPTW)
IPTW is the most common weighting method. It uses the inverse of the propensity score as weights. For the **ATE**, weight each treated unit by $1/p$ and each control unit by $1/(1-p)$, where $p$ is the propensity score. For the **ATT** --- which is what the examples below request via `estimand = "ATT"` --- the treated units all get weight $1$ and the controls get $p/(1-p)$; you can see this in the weight summaries, where the treated range is exactly $1$ to $1$. Either way the point is the same: create a pseudo population in which the distribution of $X$ is balanced between the groups.
### 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.
### 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).
### 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.
### 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.
## Example
Here is an example with Lalonde data, we are interested in the effect of treatment on "re78" (real earnings 1978).
### IPW
```{r}
#| include: true
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
summary(w.out1)
```
The treated weights are all exactly 1, as the ATT requires, and the control
weights run from 0.009 to 3.743. The cost is in the effective sample size: 429
controls carry the information of 99.8 equally weighted ones, a loss of more
than three quarters.
```{r}
#| include: true
library(cobalt)
bal.tab(w.out1, stats = c("m", "v"), thresholds = c(m = .05))
```
Nine of ten contrasts now pass the strict 0.05 threshold, against none before
weighting. `age` is the exception at 0.1188. Note also the variance ratios,
which IPTW does not target: `age` sits at 0.458 and `re74` at 1.321, so the
*means* are balanced while the *spreads* are not.
### entropy balancing
```{r}
#| include: true
w.out2 <- weightit(treat ~ age + educ + race + married + nodegree + re74 + re75,
data = lalonde, estimand = "ATT", method = "ebal")
summary(w.out2)
bal.tab(w.out2, stats = c("m", "v"), thresholds = c(m = .05))
```
Every mean difference is now zero to three decimals — that is the constraint
entropy balancing imposes directly, rather than hoping a propensity model
delivers it — and all nine pass. The effective sample size is 98.46, essentially
the same as IPTW's 99.82, so the exact balance came at no extra cost in
precision here. The variance ratios are unchanged in character (`age` 0.410,
`re74` 1.327): entropy balancing constrains means, not distributions.
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.
```{r}
# 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))
```
The entropy-balanced ATT is \$1,273 with a standard error of \$770,
$p = 0.098$, and a 95% interval of $[-236, 2783]$ that includes zero.
### energy balancing
```{r}
#| include: true
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)
```
```{r}
#| include: true
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")
}
```
Two things are worth noticing in that output rather than passing over it. First,
the estimand changed: `w.out3` requests the **ATE**, while the entropy-balancing
fit above targeted the **ATT**, so the two numbers ($-73$ against $1273$) are not
competing estimates of the same quantity. Second, look at the weight summary.
Entropy balancing gives every treated unit a weight of exactly $1$, as the ATT
requires. Energy balancing spreads the treated weights from $0$ to $14.0$ --- one
treated unit is effectively discarded and another counts as fourteen --- and the
resulting standard error ($1085$) is much larger than entropy balancing's ($770$).
Balancing the whole covariate *distribution* rather than just its means is a
genuine advantage, but it is bought with weight variability, and that shows up
directly in the precision of the estimate.
## 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.
It is worth putting numbers on that, because on this data the choice changes the
conclusion. Full matching in the [previous chapter](matching-part1.qmd) gave an
ATT of \$1,977 with a standard error of \$704 and $p = 0.005$. Entropy balancing
above gives \$1,273 with \$770 and $p = 0.098$. The two point estimates are
about one standard error apart, and one interval excludes zero while the other
does not.
Note which way that cuts. Entropy balancing achieves *exact* mean balance, while
full matching leaves `educ` at $-0.096$; the better-balanced method gives the
smaller and less significant estimate. Better balance on means is not the same
thing as a more credible answer, and reporting whichever method happened to
cross the significance line would be the wrong response to this. The honest
summary reports both and says the data do not settle it.
---
<!-- see-also-footer -->
*Systematic treatment: [R](https://xiangao.github.io/causal_econometrics_guide/matching.html) · [Julia](https://xiangao.github.io/causal_econometrics_julia/matching.html).*