Code
print(inspect.signature(cm.TwoSLS))()
Closed-form linear IV / two-stage least squares
Group: Causal inference
TwoSLS estimates linear instrumental-variables models. With endogenous regressors X_e, exogenous controls X_c, instruments Z, and outcome y, it runs the projection of the second-stage design on the instrument span and then estimates
y = \alpha + X_e\beta_e + X_c\beta_c + u.
It supports multiple endogenous regressors and excluded instruments.
Let
\tilde X=[\mathbf 1,\ X_{\mathrm{endog}},\ X_{\mathrm{exog}}], \qquad \tilde Z=[\mathbf 1,\ X_{\mathrm{exog}},\ Z_{\mathrm{excluded}}],
with the intercept omitted from both matrices only when intercept fitting is disabled internally. The implementation first solves \tilde Z\Pi\approx\tilde X, forms \hat X=\tilde Z\hat\Pi=P_Z\tilde X, and then solves \hat X\theta\approx y. Thus the point estimate is
\hat\theta = (\tilde X'P_Z\tilde X)^{-1}\tilde X'P_Zy.
Weighted fitting applies \sqrt{w_i} to y_i, every regressor, and every instrument before constructing the projection. The sketched method applies one joint CountSketch to y, \tilde X, and \tilde Z and performs both stages in the sketched sample; it is an approximate point estimate.
Weights are analytic, not frequency weights. Zero-weight rows and zero-only clusters do not count toward summary, Wald, or Anderson-Rubin inference; HAC uses the active-row ordering. Prediction validates finite inputs and trained width, and failed refits clear old state. Sketched summaries record method, size, seed, original sample count, and inference_approximate=True; covariance does not account for sketch randomization.
The estimator implements 2SLS as two rectangular least-squares problems, avoiding an explicit n\times n projection matrix.
The explicit fitted-regressor route is easy to inspect and stable relative to normal equations, but it materializes another dense n\times p matrix. The implementation checks order conditions, not statistical rank or instrument strength; near-collinearity is left to the numerical solve and diagnostics.
Define g_i=z_i\hat u_i, G=-\tilde Z'\tilde X/n, W=(\tilde Z'\tilde Z/n)^{-1}, and A=G'WG. The homoskedastic covariance is
\widehat V_{\mathrm{vanilla}} = \frac{\hat\sigma^2}{n}A^{-1}, \qquad \hat\sigma^2=\frac{\hat u'\hat u}{n-p}.
The robust estimator replaces \hat\sigma^2W^{-1} with the empirical covariance \hat\Omega of g_i:
\widehat V = \frac1n A^{-1}G'W\hat\Omega WG A^{-1}.
The implemented \hat\Omega choices are HC1 iid scores, Bartlett Newey-West scores, and cluster sums with the same finite-sample corrections used by OLS. Wald tests use this covariance. The pairs bootstrap resamples all outcome, regressor, instrument, and weight rows together.
For one endogenous regressor, the Anderson-Rubin method tests H_0:\beta=\beta_0 by regressing y-\beta_0x_{\mathrm{endog}} on the included exogenous variables and excluded instruments and jointly testing the excluded-instrument coefficients. It is unavailable with multiple endogenous regressors.
The two least-squares stages scale with the instrument width as well as the regressor width, and inference forms dense instrument and parameter cross-products. Weak instruments do not necessarily cause a numerical error; they instead make \tilde X'P_Z\tilde X poorly conditioned and conventional Wald inference unreliable, which is why the scalar Anderson-Rubin test is exposed. CountSketch reduces the rows used in both point-estimation stages, but the fitted object retains the full sample and evaluates residuals and covariance there. Large instrument sets still incur quadratic storage and cubic dense-factorization costs in their width.
Constructor: cm.TwoSLS
Call fit(x_endog, x_exog, z, y). The predict(x) method expects a second-stage design matrix matching the fitted endogenous+exogenous columns. The robust linear covariance options match OLS: vanilla, hc1, newey_west, and cluster.
| Public method |
|---|
TwoSLS() |
anderson_rubin_test(self, /, beta=0.0, vcov='hc1', lags=None, clusters=None) |
bootstrap(self, /, n_bootstrap, seed=None) |
fit(self, /, x_endog, x_exog, z, y) |
fit_sketch(self, /, x_endog, x_exog, z, y, sketch_size, seed=None) |
fit_weighted(self, /, x_endog, x_exog, z, y, sample_weight) |
predict(self, /, x) |
summary(self, /, vcov='hc1', lags=None, clusters=None) |
wald_test(self, /, r, q=None, vcov=None, lags=None, clusters=None) |
rng = np.random.default_rng(9)
n = 400
z = rng.normal(size=(n, 2))
x_exog = rng.normal(size=(n, 1))
v = rng.normal(size=(n, 1))
u = 0.6 * v[:, 0] + rng.normal(scale=0.4, size=n)
x_endog = z @ np.array([[0.8], [-0.4]]) + 0.2 * x_exog + v
y = 0.5 + 1.2 * x_endog[:, 0] - 0.7 * x_exog[:, 0] + u
model = cm.TwoSLS()
model.fit(x_endog, x_exog, z, y)
print(model.summary()['coef'])
print(model.summary(vcov='newey_west', lags=3)['coef_se'])[ 1.22456127 -0.6589091 ]
[0.04054546 0.03621148]
summary() contractThe table below is generated by fitting the live class in this repository and then inspecting summary(). Shapes are shown because most values are plain NumPy arrays or scalars.
rng = np.random.default_rng(109)
n = 120
z = rng.normal(size=(n, 2))
x_exog = rng.normal(size=(n, 1))
v = rng.normal(size=(n, 1))
x_endog = z @ np.array([[0.8], [-0.4]]) + 0.2 * x_exog + v
y = 0.5 + 1.2 * x_endog[:, 0] - 0.7 * x_exog[:, 0] + 0.6 * v[:, 0] + rng.normal(size=n) * 0.4
model = cm.TwoSLS()
model.fit(x_endog, x_exog, z, y)
summary = model.summary()
display(HTML(html_table(["summary() key", "shape"], summary_shape_rows(summary))))| summary() key | shape |
|---|---|
intercept |
() |
coef |
(2,) |
intercept_se |
() |
coef_se |
(2,) |
vcov |
(3, 3) |
vcov_type |
() |
sketch_size |
() |
sketch_seed |
() |
inference_approximate |
() |
nobs |
() |
original_nobs |
() |
sketch_method |
() |