Estimate a shift-share IV regression with several confidence intervals
Source:R/estimate.R
ssb_estimate.RdComputes the shift-share 2SLS point estimate of `x` on `y` (instrumented by the constructed Bartik instrument, controls partialled out via FWL) and reports a panel of intervals side by side so the practical importance of the inference method is visible:
`iid` — classical (homoskedastic) IV,
`ehw` — Eicker-Huber-White (heteroskedasticity-robust),
`akm`, `akm0` — Adao-Kolesar-Morales exposure-robust inference, via ShiftShareSE when installed,
`cluster` — naive cluster-robust (needs `cluster` in the design),
`twoway` — two-way cluster-robust (needs `cluster` in the design and `cluster2` here).
The point estimate is identical across rows; only the standard errors and intervals differ, which is exactly what makes the comparison instructive.
Arguments
- design
An [ssb_design()] object.
- methods
Which methods to report. `NULL` (default) resolves by the design's route: `c("ehw", "cluster")` on the share route (dropping `cluster` if no cluster variable is set), `c("akm", "akm0")` on the shift route. Supply a character vector of `"iid"`, `"ehw"`, `"cluster"`, `"twoway"`, `"akm"`, `"akm0"` to override.
- level
Confidence level for the reported intervals.
- cluster2
Optional second clustering column in `data` for the `"twoway"` method (paired with the design's `cluster`).
- shock_cluster
Optional grouping of the shocks for the AKM / AKM0 variance: a column name in the shocks table, or a vector of length equal to the number of shock-cells. Use it when shocks are mutually correlated within groups — e.g. sub-industries within broader industries, or sector cells of the same sector across periods — so the exposure-robust variance is clustered at the group level (Adao, Kolesar & Morales 2019). It is passed to ShiftShareSE as `sector_cvar`, which sums the shock-level scores within groups before squaring; the package's test suite verifies this against a hand-computed cluster-robust shock-level regression, so the grouping is genuinely used rather than shocks being treated as independent.
**Exposure-robust engine.** `akm` / `akm0` are computed by ShiftShareSE when it is installed. When it is not, they are computed natively from the Borusyak-Hull-Jaravel shock-level scores (the shocks residualised on the shock-level controls, see [ssb_shock_iv()]): `akm` is the HC0 SE of the shock-level IV and `akm0` inverts the shock-level Anderson-Rubin test, following the same conventions as ShiftShareSE (no small-sample factor; a disjoint or unbounded confidence set under a weak instrument). The two engines coincide exactly when the location controls lie in the span of the shares (an intercept with complete shares, period fixed effects) and differ only by finite-sample terms in how the shocks are residualised otherwise; the `note` column records which engine produced the row.
Value
A `data.frame` of class `ssb_estimate` with one row per method (`estimate`, `std.error`, `conf.low`, `conf.high`), carrying the route-appropriate first-stage F (`fstat`: conventional HC1 on the share route, exposure-robust shock-level F on the shift route — see [ssb_first_stage()] — with its `fstat_label`) and the identification route as attributes. Plot with [ssb_plot_ci()].
Details
The primary object of the comparison is the **confidence interval**, not the standard error: AKM0 in particular is defined directly as a (possibly asymmetric, possibly unbounded) interval, and the `std.error` reported for it is a symmetric pseudo-SE implied by that interval rather than a conventional standard error. Read the table and [ssb_plot_ci()] figure as a comparison of intervals. The AKM0 confidence *set* need not be an interval at all: it can be the whole real line or the complement of an interval (a union of two rays). This happens when the Anderson-Rubin statistic cannot reject arbitrarily large \(|\beta_0|\), which — because that statistic is bounded by the number of (clusters of) shocks — is common with few effective shocks even when the Wald first-stage F is large. ShiftShareSE encodes the latter as `conf.low > conf.high`; `ssb_estimate()` flags both cases in the `note` column and the table/plot methods render them accordingly.
**The default methods follow the identification route of the design** (Borusyak-Hull-Jaravel JEP practical guide):
`exogenous = "share"` (Goldsmith-Pinkham-Sorkin-Swift): identification comes from the shares, and conventional inference is appropriate — the default is `ehw` plus `cluster` when the design has a `cluster` variable.
`exogenous = "shift"` (Borusyak-Hull-Jaravel; Adao-Kolesar-Morales): with as-good-as-random shocks, conventional EHW / cluster standard errors over-reject because residuals are correlated across units with similar exposure — the default is the exposure-robust panel `akm` / `akm0`.
Supplying `methods` explicitly overrides the default, e.g. to place the conventional and exposure-robust intervals side by side for a methodological comparison; the printed table always states the route so the headline inference is unambiguous. `iid` (homoskedastic) and `twoway` are only reported on request (add `cluster2` for two-way clustering). The `cluster` row needs a `cluster` column in the design (set via [ssb_design()]); without one it is reported as `NA` with a note rather than an error.
Examples
sim <- ssb_simulate(n_loc = 80, n_sec = 10, seed = 1)
d <- ssb_design(sim$data, sim$shares, sim$shocks, exogenous = "share")
ssb_estimate(d) # share route: conventional (EHW) inference
#> <ssBartik estimate>
#> route : exogenous SHARE -> conventional (EHW / cluster) inference
#> first-stage F : 19.8 (robust)
#> method estimate std.error conf.low conf.high note
#> EHW 1.45 0.199 1.06 1.84