Kernel and ridge matching¶
Each treated unit is compared with a kernel-weighted average of the
controls of its stratum within one bandwidth of its propensity score. Ridge matching
replaces the average with Frölich’s (2004) ridge-regularized local-linear
fit. The default bandwidth ("pair_distance") is set from the 1:1
nearest-neighbor distances; CrossValidation(outcome=) chooses it by
cross-validating an outcome. Standard errors are the influence
function’s, with the weights held fixed.
import polars as pl
from match_stream import CrossValidation, match_kernel, simulate_matching
pl.Config.set_tbl_hide_dataframe_shape(True)
df = simulate_matching(100_000)
kw = dict(exact=["r", "i"], verbose=False)
m = match_kernel("d ~ x1 + x2 + x3", df, **kw)
m.summary()
m.effect("y").summary()
r = match_kernel("d ~ x1 + x2 + x3", df, kernel="epan", ridge=True, **kw)
print(f"\nridge, bandwidth {r.bandwidth:.5f}:")
r.effect("y").summary()
grid = [0.002, 0.005, 0.01, 0.02, 0.05, 0.1]
cv = match_kernel("d ~ x1 + x2 + x3", df, ridge=True,
bandwidth=CrossValidation(outcome="y", grid=grid), **kw)
print("\ncross-validation:")
print(cv.bandwidth_info.cv)
print(f"chosen: {cv.bandwidth}")
cv.effect("y").summary()
Output:
match-stream: kernel matching, epan kernel, estimand ATT
distance: ps bandwidth: 0.000417348 (pair_distance)
exact: r, i (20 strata)
available matched unmatched
treated 25,497 23,716 1,781
control 74,503 63,809 10,694
### y: kernel matching on ps
Estimand: ATT Matched focal units: 23,716
Standard errors: influence function, weights fixed, clustered by unit (87,525)
┌─────────────┬──────────┬────────────┬───────────┬──────────┐
│ Coefficient ┆ Estimate ┆ Std. Error ┆ t value ┆ Pr(>|t|) │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞═════════════╪══════════╪════════════╪═══════════╪══════════╡
│ ATT:y ┆ 0.990778 ┆ 0.012987 ┆ 76.290996 ┆ 0.0 │
└─────────────┴──────────┴────────────┴───────────┴──────────┘
ridge, bandwidth 0.00042:
### y: ridge matching on ps
Estimand: ATT Matched focal units: 23,716
Standard errors: influence function, weights fixed, clustered by unit (87,525)
┌─────────────┬──────────┬────────────┬───────────┬──────────┐
│ Coefficient ┆ Estimate ┆ Std. Error ┆ t value ┆ Pr(>|t|) │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞═════════════╪══════════╪════════════╪═══════════╪══════════╡
│ ATT:y ┆ 0.99224 ┆ 0.013223 ┆ 75.040366 ┆ 0.0 │
└─────────────┴──────────┴────────────┴───────────┴──────────┘
cross-validation:
┌───────────┬───────────┬─────────┐
│ bandwidth ┆ criterion ┆ weight │
│ --- ┆ --- ┆ --- │
│ f64 ┆ f64 ┆ f64 │
╞═══════════╪═══════════╪═════════╡
│ 0.002 ┆ 2.010846 ┆ 74203.0 │
│ 0.005 ┆ 1.950153 ┆ 74203.0 │
│ 0.01 ┆ 1.929642 ┆ 74203.0 │
│ 0.02 ┆ 1.919829 ┆ 74203.0 │
│ 0.05 ┆ 1.914433 ┆ 74203.0 │
│ 0.1 ┆ 1.912883 ┆ 74203.0 │
└───────────┴───────────┴─────────┘
chosen: 0.1
### y: ridge matching on ps
Estimand: ATT Matched focal units: 25,494
Standard errors: influence function, weights fixed, clustered by unit (99,997)
┌─────────────┬──────────┬────────────┬───────────┬──────────┐
│ Coefficient ┆ Estimate ┆ Std. Error ┆ t value ┆ Pr(>|t|) │
│ --- ┆ --- ┆ --- ┆ --- ┆ --- │
│ str ┆ f64 ┆ f64 ┆ f64 ┆ f64 │
╞═════════════╪══════════╪════════════╪═══════════╪══════════╡
│ ATT:y ┆ 1.011057 ┆ 0.012162 ┆ 83.131974 ┆ 0.0 │
└─────────────┴──────────┴────────────┴───────────┴──────────┘
Notes.
The default bandwidth (
"pair_distance") is 1.5 times the 90th percentile of the nonzero 1:1 nearest-neighbor distances. It is narrow: here 1,781 treated units have no control within it and are unmatched.Cross-validation predicts each control’s outcome from the other controls (leave one out), for each bandwidth on the grid. It picks the bandwidth with the smallest mean squared error over a fixed set of controls.
Here the outcome is smooth in the score, so the widest bandwidth wins and nearly every treated unit is matched (25,494 of 25,497).
match-stream warns when the minimum is at the edge of the grid: widen
CrossValidation(grid=)then.
Computation. Kernel and ridge weights come from window sums, so no pairs are formed (see How it works).