Counterfactual Synthetic Control with Instrumented PCA (CSC-IPCA)#
When to Use This Method#
You have a single treated unit and a pool of untreated controls observed over many periods, an intervention switches on at a known date, and — this is the distinguishing feature — you also observe a rich set of time-varying covariates for every unit. The units move together through shared, unobserved forces but not in parallel, and the treated unit may sit outside the range spanned by the controls, so a convex donor-weight fit would have to extrapolate. This is the regime where difference-in-differences (which asserts parallel trends) and plain synthetic control (which pins the counterfactual to a convex combination of controls, and so refuses to extrapolate) are hard to justify, and where you would like the many covariates to do real work rather than being collapsed to a single pre-period average.
The CSC-IPCA estimator of Wang ([Wang2024]) targets that regime by writing each unit’s untreated outcome as a small number of latent common factors \(\mathbf{f}_t\) weighted by factor loadings — but instead of free unit-specific loadings \(\boldsymbol{\lambda}_i\), it makes the loadings a linear projection of the observed covariates,
where \(\mathbf{x}_{it}\) is the unit’s covariate vector at time \(t\) and \(\boldsymbol{\Gamma}\) is an \(L \times K\) mapping matrix shared across units. This is instrumented principal component analysis (Kelly, Pruitt and Su [KPS2019]), imported from asset pricing. The loadings inherit the time variation of the covariates, and the covariates — not a convex hull condition — carry the information that pins the counterfactual.
Why instrument the loadings#
A plain interactive-fixed-effects estimator (Causal Factor Model (CFM), Factor Model Approach (FMA)) learns a free loading for each unit from its outcome history alone. CSC-IPCA instead projects the loadings onto the covariates, which buys three things the paper emphasises. It needs no convex-hull or common-support condition, so the treated unit can lie outside the donor range. It extracts signal from many covariates at once through the dimension-reducing map \(\boldsymbol{\Gamma}\), rather than from the outcome path alone. And it does not require the interactive-fixed -effects model to be correctly specified in the covariates, because the loadings absorb their predictive content. The cost is that the covariates must genuinely carry the loading information: the method’s edge over a plain factor model appears precisely when the covariates are informative and only partially observed, and shrinks to nothing when every relevant covariate is already in hand (then a correctly specified factor model does as well).
Reach for CSC-IPCA when#
you observe many time-varying covariates that plausibly drive the outcome, and want them to identify the counterfactual;
the treated unit sits outside the donor convex hull, so a simplex synthetic control must extrapolate;
you distrust a hand-specified interactive-fixed-effects model and would rather let covariates instrument the loadings;
the pre-period is long relative to the number of covariates times factors.
Do not use CSC-IPCA when#
you have no covariates, or only a handful of weakly predictive ones — the loadings are then unidentified or uninformative, and an outcome-only factor estimator (Causal Factor Model (CFM), Factor Model Approach (FMA)) is the right tool;
the pre-period is shorter than \(L \times K\), so the treated mapping is not identified (see Assumption 4);
you have fewer covariates than factors, \(L < K\), so the projected loadings cannot span the factor space.
Notation#
Let \(i = 1\) denote the treated unit and \(i = 2, \dots, n\) the controls, observed for \(t = 1, \dots, T\). The intervention occurs at \(T_0 + 1\), so periods \(t \le T_0\) are pre-treatment and \(t > T_0\) post-treatment. Each unit carries an \(L\)-vector of observed covariates \(\mathbf{x}_{it}\). The untreated potential outcome follows the instrumented factor model
with \(\mathbf{f}_t\) a \(K\)-vector of latent common factors, \(\boldsymbol{\Gamma}\) the \(L \times K\) mapping matrix, and \(\epsilon_{it}\) idiosyncratic noise. The treated unit’s observed outcome is \(Y_{it} = Y_{it}(0) + \delta_{it}\,D_{it}\), and the estimand is the per-period effect \(\delta_{1t}\) for \(t > T_0\) together with its post-period average, the ATT \(\overline{\delta} = (T - T_0)^{-1} \sum_{t > T_0} \delta_{1t}\). The imputed counterfactual is \(\widehat Y_{1t}(0) = (\mathbf{x}_{1t}^\top \widehat{\boldsymbol{\Gamma}}) \widehat{\mathbf{f}}_t\) and the reported effect is \(Y_{1t} - \widehat Y_{1t}(0)\).
Assumptions#
Instrumented factor structure. The untreated outcome admits the factor model above with loadings that are a linear projection of the covariates, \(\boldsymbol{\lambda}_{it} = \mathbf{x}_{it}^\top \boldsymbol{\Gamma}\), for a fixed number of factors \(K\) common across treated and controls.
Remark. The controls identify \(\boldsymbol{\Gamma}\) and \(\mathbf{f}_t\); the treated unit’s covariates then generate its loadings without ever fitting a free treated loading. This is what lets the treated unit lie outside the donor hull — its counterfactual is built from its own covariates through the shared map, not from a donor average.
Covariate orthogonality (exclusion). The covariates are orthogonal to the idiosyncratic errors, \(\mathbb{E}[\mathbf{x}_{it}\,\epsilon_{it}] = 0\).
Remark. This is the exclusion restriction of the instrumenting step, exactly as in two-stage regression: the covariates may drive the loadings but must not correlate with the leftover noise, or the mapping is contaminated.
Enough covariates. There are at least as many covariates as factors, \(L \ge K\).
Remark. The projected loadings live in an \(L\)-dimensional space mapped to \(K\) factors; if \(L < K\) the map cannot span the factor space and the factor solve is singular. The config rejects \(L < K\) early.
Long enough pre-period. The treated mapping is solved from the treated unit’s \(T_0\) pre-treatment periods, which identify the \(LK\) entries of \(\boldsymbol{\Gamma}\) only if \(T_0 \ge L K\).
Remark. With a single treated unit each pre-period contributes one observation to the \(LK\) normal equations, so a short pre-period or too many covariate-factor products leaves the mapping unidentified. The setup raises a clear error when \(T_0 < LK\).
Weak dependence and stationarity. The covariates and errors are weakly dependent with finite second moments, and the mapping lives in a compact parameter space.
Remark. These are the regularity conditions of Wang (2024, Assumption 2) and Kelly, Pruitt and Su (2019): they deliver consistency of the alternating-least-squares estimates and the central limit theorem behind the conformal calibration.
Estimation#
The mapping and factors are estimated by alternating least squares, because the loadings are \(\mathbf{x}_{it}^\top \boldsymbol{\Gamma}\) rather than a free \(\boldsymbol{\lambda}_i\), so eigendecomposition does not apply. With the factors held fixed the \(\boldsymbol{\Gamma}\) step is a single linear solve of the \(LK\) normal equations; with \(\boldsymbol{\Gamma}\) fixed each \(\mathbf{f}_t\) is a \(K\)-vector least-squares solve. The four steps are: estimate \((\widehat{\boldsymbol{\Gamma}}_{\text{ctrl}}, \widehat{\mathbf{f}}_t)\) on the control panel over the whole period; re-estimate the treated mapping \(\widehat{\boldsymbol{\Gamma}}_{\text{tr}}\) on the treated pre-period holding \(\widehat{\mathbf{f}}_t\) fixed; normalise \((\widehat{\boldsymbol{\Gamma}}_{\text{tr}}, \widehat{\mathbf{f}})\) to the identifiable rotation (\(\boldsymbol{\Gamma}^\top \boldsymbol{\Gamma} = \mathbf{I}_K\), \(\mathbf{f}\mathbf{f}^\top / T\) diagonal); and impute \(\widehat Y_{1t}(0)\) over every period. The normalisation fixes the rotational indeterminacy of any factor model and leaves the counterfactual unchanged.
Inference and diagnostics#
Inference is the moving-block conformal procedure of Chernozhukov, Wuthrich and Zhu ([CWZ2021]), which needs no asymptotic variance formula and is robust to misspecification. For a candidate effect \(\theta\) the treated series is adjusted under the sharp null \(Y_{1t} - \theta\), the model is re-estimated, and the post-treatment residual is compared against the pre-treatment residuals by permutation; the candidate is kept in the \((1 - \alpha)\) band when its block-permutation p-value is at least \(\alpha\). Because the factors are learned from the controls only, they do not depend on \(\theta\) and are reused across the null grid. The estimator returns a per-period band — the primary effect-over-time output — from a block of length one, and an ATT band from a moving block of length \(T - T_0\) under a constant-effect null.
The number of factors \(K\) is set directly by n_factors (default 2,
the paper’s choice for its empirical study); there is no data-driven selection,
so a one-versus-two-factor robustness pass is advisable.
Example#
import pandas as pd
from mlsynth import CSCIPCA
# Wang (2024): Brexit -> UK foreign direct investment. The UK is treated
# from 2017; nine macro covariates instrument the loadings.
df = pd.read_csv("basedata/fdi_oecd_brexit.csv")
covs = ["log_gdp", "log_gdp_percap", "import_to_gdp", "export_to_gdp",
"inflation_gdp_deflator", "gross_capital_forma_gdp", "unemployment",
"employment_15", "log_population"]
res = CSCIPCA({
"df": df, "outcome": "fdi", "treat": "treated",
"unitid": "country", "time": "year",
"covariates": covs,
"n_factors": 2,
"alpha": 0.10, # 90% conformal band
"display_graphs": False,
}).fit()
res.att # mean post-2017 FDI effect
res.design.tau # per-year effect path (2017: ~-7.8, ...)
res.inference_detail.ci_lower_t # per-year conformal band
res.inference_detail.att_p_value # block-permutation p-value of H0: ATT = 0
The reported gap is \(Y_{1t} - \widehat Y_{1t}(0)\); counterfactual
is the imputed untreated path
\((\mathbf{x}_{1t}^\top \widehat{\boldsymbol{\Gamma}})
\widehat{\mathbf{f}}_t\). Because CSC-IPCA is a factor-model counterfactual, it
carries no donor weights.
Verification#
CSC-IPCA reproduces Wang’s Brexit / UK foreign-direct-investment application on
the author’s data (basedata/fdi_oecd_brexit.csv): the reported per-year ATT
path — \(-7.8\) (2017), \(-12.9\) (2018), \(-18.3\) (2019) — is
matched to the second decimal (durable case
benchmarks/cases/cscipca_brexit.py). It is further validated by a
cross-validation against the authors’ reference implementation (counterfactual
and ATT matched to machine precision through dataprep) and by the paper’s
linear factor Monte Carlo, where the bias shrinks as the share of observed
covariates rises and the estimator beats an extrapolating simplex synthetic
control (benchmarks/cases/cscipca_mc.py). See CSC-IPCA — Counterfactual and Synthetic Control with Instrumented PCA (Wang 2024)
for the full comparison.
Core API#
- class mlsynth.CSCIPCA(config: CSCIPCAConfig | dict)#
Counterfactual and Synthetic Control with Instrumented PCA (Wang 2024).
- Parameters:
config (CSCIPCAConfig or dict) – Configuration object. See
mlsynth.config_models.CSCIPCAConfig.- Returns:
CSCIPCAResults – Frozen container with the estimated design (mapping
Gamma, factorsF, counterfactual, effect path, ATT), the moving-block conformal inference, and the standardized result sub-models.
- fit() CSCIPCAResults#
Run the CSC-IPCA pipeline end to end.
- class mlsynth.config_models.CSCIPCAConfig(*, df: ~pandas.DataFrame, outcome: str, treat: str, unitid: str, time: str, display_graphs: bool = True, save: bool | str = False, counterfactual_color: ~typing.List[str] = <factory>, treated_color: str = 'black', plot: ~mlsynth.config_models.PlotConfig = <factory>, covariates: ~typing.Annotated[~typing.List[str], ~annotated_types.MinLen(min_length=1)], n_factors: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)] = 2, max_iter: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)] = 100, tol: ~typing.Annotated[float, ~annotated_types.Gt(gt=0)] = 1e-06, alpha: ~typing.Annotated[float, ~annotated_types.Gt(gt=0.0), ~annotated_types.Lt(lt=1.0)] = 0.05, inference: bool = True, n_nulls: ~typing.Annotated[int, ~annotated_types.Ge(ge=3)] = 201, null_grid_scale: ~typing.Annotated[float, ~annotated_types.Gt(gt=0)] = 6.0)#
Configuration for the CSC-IPCA estimator.
Implements Wang (2024), “Counterfactual and Synthetic Control Method: Causal Inference with Instrumented Principal Component Analysis”. CSC-IPCA is a factor-model counterfactual imputer whose factor loadings are a linear projection of observed covariates,
Lambda_it = X_it Gamma(instrumented principal component analysis, Kelly-Pruitt-Su 2019/2020). The mapping matrixGammaand the latent factorsF_tare estimated by alternating least squares on the control units; the treated unit’s mapping is re-estimated on its pre-treatment periods, and the counterfactual ishat Y_it(0) = X_it hat Gamma hat F_t.Beyond the common fields (
df,outcome,treat,unitid,time,display_graphs,save, colours), CSC-IPCA reads:- Parameters:
covariates (list of str) – Column names of the time-varying covariates that instrument the factor loadings. At least one is required – this is the
X_itcube the method is built on; with no covariates the model degenerates to plain PCA/IFE and one of the outcome-only factor estimators (CFM,FMA) is the right tool instead.n_factors (int) – Number of latent common factors
K. Defaults to 2 (the paper’s empirical choice for the Brexit application).max_iter (int) – Maximum alternating-least-squares iterations.
tol (float) – Convergence tolerance on the max absolute change in
GammaandFbetween ALS iterations.alpha (float) – Two-sided significance level for the conformal confidence band (
0.05-> 95% band). The paper uses0.10for the Brexit study.inference (bool) – Whether to run the moving-block conformal inference (Chernozhukov et al. 2021) for a per-period confidence band. When
Falseonly the point counterfactual + ATT are returned (much faster).n_nulls (int) – Number of candidate null values per post-period on the conformal grid.
null_grid_scale (float) – Half-width of the per-period conformal grid, in multiples of the pre-treatment fit RMSE around the point effect.
- model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True, 'extra': 'forbid'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].
References#
Wang, C. (2024). “Counterfactual and Synthetic Control Method: Causal Inference with Instrumented Principal Component Analysis.” Job Market Paper.
Kelly, B. T., Pruitt, S., & Su, Y. (2019). “Characteristics Are Covariances: A Unified Model of Risk and Return.” Journal of Financial Economics 134(3):501-524.
Chernozhukov, V., Wuthrich, K., & Zhu, Y. (2021). “An Exact and Robust Conformal Inference Method for Counterfactual and Synthetic Control.” Journal of the American Statistical Association 116(536):1849-1864.