Differentially Private Synthetic Control (DPSC)#
Overview#
DPSC is a synthetic control estimator that releases its counterfactual under
a formal privacy guarantee (Rho, Cummings & Misra 2023). It answers a question
none of the other estimators in mlsynth address: when the donor pool is
sensitive – patient records in a clinical external-control arm, proprietary
firm-level series in a data cooperative – how do you publish the synthetic
control, and the effect it implies, without leaking any single donor’s data?
The estimator is a ridge synthetic control fitted with differentially private empirical risk minimisation. The pre-period regression coefficients are learned privately, the post-intervention donor matrix is privatised, and the released counterfactual is formed from the two. Differential privacy then bounds, in a mathematically precise sense, how much an adversary who sees the released output can learn about any one donor.
Privacy is not free. The released estimate is a single draw of a randomised
mechanism, and the noise trades off against accuracy at a rate controlled by
the privacy budget. DPSC reports that noise honestly as the dominant source
of uncertainty.
When to use this estimator#
The donor pool is sensitive or proprietary – the donors are people or entities whose individual series must be protected – and the counterfactual or the effect will be released externally (to a regulator, a publication, a counterparty).
You need a provable, quantifiable privacy guarantee (an \(\varepsilon\)), not ad hoc de-identification.
The donor pool is reasonably large and the pre-period long: the privacy noise averages down with panel size, so this is where differential privacy is affordable.
Do not reach for it for ordinary policy evaluation on public aggregates (Proposition 99 states, country GDP, your own first-party stores). There is no individual to protect, and the accuracy cost is then pure loss – use Vanilla Synthetic Control (VanillaSC) instead. On a small donor pool the privacy noise is large; see the honest caveat below.
Notation#
Let \(j = 1\) denote the treated unit and \(\mathcal{N}_0 \coloneqq \{2, \dots, N\}\) the donor pool of cardinality \(N_0\). Time runs over \(t \in \{1, \dots, T\}\), 1-indexed; the intervention takes effect after period \(T_0\), splitting time into the pre-period \(\mathcal{T}_1 \coloneqq \{t : t \le T_0\}\) of length \(T_0\) and the post-period \(\mathcal{T}_2 \coloneqq \{t : t > T_0\}\).
The treated series is \(\mathbf{y}_1 = (y_{11}, \dots, y_{1T})^\top\), and the donor outcomes stack into \(\mathbf{Y}_0 \coloneqq [\mathbf{y}_j]_{j \in \mathcal{N}_0} \in \mathbb{R}^{T \times N_0}\) (one column per donor); \(\mathbf{Y}_{0,\mathcal{T}_1} \in \mathbb{R}^{T_0 \times N_0}\) is its pre-period block. Regression weights are \(\mathbf{f} \in \mathbb{R}^{N_0}\) – unlike the canonical simplex weights \(\mathbf{w}\) of Vanilla Synthetic Control (VanillaSC), these are unconstrained ridge coefficients. The synthetic counterfactual is \(\widehat{\mathbf{y}}_1 \coloneqq \mathbf{Y}_0\,\widehat{\mathbf{f}}\) with entries \(\widehat{y}_{1t}\), the per-period effect is \(\tau_t \coloneqq y_{1t} - \widehat{y}_{1t}\), and the ATT is \(\widehat{\tau} \coloneqq |\mathcal{T}_2|^{-1} \sum_{t \in \mathcal{T}_2} \tau_t\).
The privacy parameters are the budgets \(\varepsilon_1\) (Stage 1, coefficients) and \(\varepsilon_2\) (Stage 2, released donors), with total budget \(\varepsilon = \varepsilon_1 + \varepsilon_2\); the approximate-DP slack is \(\delta\); the ridge penalty is \(\lambda\); and \(\Delta\) denotes an \(\ell_2\) sensitivity. The significance level is \(\alpha\).
Differential privacy#
A randomised mechanism \(\mathcal{M}\) is \((\varepsilon, \delta)\)-differentially private (Dwork et al. 2006) if for every pair of neighbouring datasets \(\mathcal{D}, \mathcal{D}'\) and every measurable set \(S\) of outputs,
Smaller \(\varepsilon\) is stronger privacy; \(\delta = 0\) is pure \(\varepsilon\)-DP. The crux for synthetic control is what “neighbouring” means. Synthetic control regresses vertically – each time point is a sample, each donor a feature – so changing one donor changes an entire column of the design. A neighbouring dataset therefore replaces one donor’s whole series \(\mathbf{y}_j\), and the privacy guarantee protects donors, not the treated unit (Rho et al. 2023, Sec 3).
Privacy is calibrated to sensitivity, the largest change in a statistic that swapping one donor can cause. For a mechanism releasing a vector \(g\), the Laplace mechanism adds noise scaled to its \(\ell_2\) sensitivity \(\Delta_g \coloneqq \max_{\mathcal{D} \sim \mathcal{D}'} \lVert g(\mathcal{D}) - g(\mathcal{D}') \rVert_2\); here noise is drawn from the high-dimensional Laplace distribution (a magnitude \(\sim \mathrm{Lap}(\cdot)\) times a uniform direction; Chaudhuri, Monteleoni & Sarwate 2011).
Identifying assumptions#
DPSC inherits the synthetic-control identifying assumptions and adds the conditions differential privacy needs.
Pre-treatment fit. There exist coefficients \(\mathbf{f}\) under which the donors reproduce the treated pre-period path, \(y_{1t} \approx (\mathbf{Y}_0 \mathbf{f})_t\) for \(t \in \mathcal{T}_1\).
Remark. As in Vanilla Synthetic Control (VanillaSC), a good pre-fit is the empirical certificate of a credible counterfactual. The ridge penalty \(\lambda\) trades a little pre-fit for a lower sensitivity (see below), so under privacy the fit is deliberately shrunk.
No anticipation. Treatment has no effect before \(T_0\), so the pre-period outcomes reflect the no-intervention path.
Remark. Date \(T_0\) at the first plausible response, not the nominal policy date; a contaminated pre-period biases the extrapolated counterfactual.
No interference and untreated donors (SUTVA). The treatment of unit \(1\) does not change any donor’s outcome, and every donor is untreated, so \(\mathbf{Y}_0\) carries only no-intervention outcomes.
Bounded donor data. The donor observations lie in a bounded range, so the sensitivity \(\Delta\) is finite and the noise calibration is valid.
Remark. This is the additional condition privacy demands. It is what makes the influence of any single donor’s series bounded, hence privatisable; the sensitivity bound below assumes it. Unbounded or heavy-tailed donors break the guarantee and must be clipped before fitting.
Note that differential privacy is a property of the release mechanism, not of the data-generating process, so DPSC needs no homoskedasticity or exchangeability assumption for its privacy guarantee – those enter only when one interprets the ATT causally, exactly as in the non-private synthetic control.
Mathematical formulation#
The base estimator is a ridge synthetic control: on the pre-period,
and the counterfactual extrapolates it, \(\widehat{\mathbf{y}}_1 = \mathbf{Y}_0 \widehat{\mathbf{f}}\). Privacy is injected in two independent stages.
Stage 1 – private coefficients. Two mechanisms are offered.
Output perturbation (Algorithm 2) fits \(\widehat{\mathbf{f}}\) and adds high-dimensional Laplace noise calibrated to the coefficient sensitivity
The sensitivity grows with the pre-period length \(T_0\) and the donor count \(N_0\) (a longer or wider design is more exposed to one donor’s change) and shrinks with \(\lambda\) (regularisation damps any donor’s influence). Larger \(\lambda\) therefore buys less noise at the cost of a more biased, shrunken fit.
Objective perturbation (Algorithm 3) instead perturbs the objective with a random linear term \(\mathbf{b}\) and solves exactly,
with \(\mathbf{b}\) Laplace (pure \(\varepsilon\)-DP, \(\delta = 0\)) or Gaussian (\((\varepsilon, \delta)\)-DP), and a curvature slack \(\Delta_c \ge 0\) added when the base regularisation is too weak for the requested budget (Kifer, Smith & Thakurta 2012). Because the noise enters the objective rather than the solution, the optimiser keeps \(\widetilde{\mathbf{f}}\) bounded, which is why objective perturbation is far more stable than output perturbation on typical panels.
Stage 2 – private release. The released donor matrix is privatised, \(\widetilde{\mathbf{Y}}_0 = \mathbf{Y}_0 + \mathrm{Lap}(2\sqrt{R}/\varepsilon_2)\) over the \(R\) released rows, and the counterfactual is \(\widehat{\mathbf{y}}_1 = \widetilde{\mathbf{Y}}_0\, \widetilde{\mathbf{f}}\).
Privacy and accuracy. By the composition theorem the release is \(\varepsilon\)-DP with \(\varepsilon = \varepsilon_1 + \varepsilon_2\). Relative to the non-private estimator, the root-mean-square error inflates by a factor \(O(1/\varepsilon)\) – the unavoidable price of privacy (Rho et al. 2023, Sec C).
Inference#
The privatised release is one draw of a randomised mechanism, so its dominant
uncertainty is the privacy noise itself. DPSC reports the released
counterfactual and ATT from a single seeded draw, and quantifies the noise by
the Monte Carlo standard deviation of the ATT over n_draws independent
privatised draws, giving \(\widehat{\tau} \pm z_{1 - \alpha/2}\,
\widehat{\mathrm{se}}\). The non-private ridge ATT (the
\(\varepsilon \to \infty\) target) is recorded alongside, so the privacy
cost is visible directly.
A caveat, stated plainly. Output perturbation is unusable on a small donor pool: the coefficient noise inflates \(\lVert \widetilde{\mathbf{f}} \rVert\) and the Stage-2 term amplifies it, so a single release can be off by many multiples of the effect even at a nominal budget. Objective perturbation (the default) is the viable mechanism, but even it roughly doubles the effective uncertainty at weak privacy, and meaningful privacy (\(\varepsilon \approx 1\)) on a few-dozen-donor panel costs a biased estimate with a wide band. The favourable regime is a large donor pool and a long pre-period, where the noise averages down.
Scaling with the donor pool. The two mechanisms respond oppositely to more donors, and the reason is instructive. The coefficient sensitivity \(\Delta = 4 T_0 \sqrt{8 + N_0} / \lambda\) grows as \(\sqrt{N_0}\), so a wider donor pool raises the raw noise budget. Output perturbation injects that noise straight into the coefficients and the counterfactual \(\mathbf{Y}_0 \widetilde{\mathbf{f}}\) amplifies it, so its error does not fall – and can worsen – as \(N_0\) grows. Objective perturbation instead places the noise inside the regularised program, where the redundancy of a low-rank donor pool lets the solver average it out; the averaging beats the \(\sqrt{N_0}\) sensitivity growth, so the privacy noise on the ATT falls with \(N_0\). In a controlled factor-model sweep (fixed \(T_0\), \(\varepsilon_1 = \varepsilon_2 = 1\)) the objective mechanism’s ATT standard deviation dropped roughly threefold as the pool grew from 5 to 20 donors and continued down thereafter, while the output mechanism stayed one to two orders of magnitude larger. This is the concrete sense in which DPSC is a large-donor-pool method: prefer the objective mechanism, and add donors when you can. Note the pre-period length enters \(\Delta\) linearly, so lengthening \(T_0\) raises the noise budget faster than widening the pool – the “more data helps” intuition applies cleanly to donors under the objective mechanism, not to the pre-period.
Example#
import pandas as pd
from mlsynth import DPSC
df = pd.read_csv(
"https://raw.githubusercontent.com/jgreathouse9/mlsynth/"
"refs/heads/main/basedata/smoking_data.csv"
)
df["treat"] = (df["Proposition 99"]).astype(int)
res = DPSC({
"df": df,
"outcome": "cigsale",
"treat": "treat",
"unitid": "state",
"time": "year",
"mechanism": "objective", # or "output"
"epsilon1": 1.0, # Stage 1 budget (coefficients)
"epsilon2": 1.0, # Stage 2 budget (released donors)
"ridge_lambda": 10.0,
"n_draws": 500, # privacy-noise Monte Carlo
"seed": 0,
"display_graphs": False,
}).fit()
print(res.effects.att) # the private ATT (one release)
print(res.inference.standard_error) # the privacy noise
print(res.inference.details["att_non_private"]) # the epsilon -> infinity target
Verification#
DPSC is cross-validated against the authors’ own code (srho1/dpsc) on the
Proposition 99 panel. Under a shared Mersenne-Twister seed, mlsynth reproduces
the authors’ private coefficients and private counterfactual value-for-value for
both mechanisms (the objective program solved in closed form against their
cvxpy), to solver tolerance. See the durable benchmark case
benchmarks/cases/dpsc_prop99.py
and the DPSC – Differentially Private Synthetic Control (Proposition 99) page.
Core API#
DPSC: differentially private synthetic control (Rho, Cummings & Misra 2023).
A ridge synthetic control whose released counterfactual satisfies
epsilon-differential privacy over the donor pool, via differentially private
empirical risk minimization. The regression coefficients are learned privately
(output perturbation, Algorithm 2, or objective perturbation, Algorithm 3), and
the post-intervention donor matrix is privatized before the counterfactual is
formed – so publishing the synthetic control leaks a provably bounded amount
about any single donor.
Use it when the donor pool is sensitive (patient records, proprietary firm-level series) and the counterfactual must be released externally. On small donor pools the privacy noise is large; the method is favorable when the donor pool is big and the pre-period long.
Reference: Rho, Cummings & Misra (2023), “Differentially Private Synthetic
Control”, AISTATS. Authors’ code: srho1/dpsc.
- class mlsynth.estimators.dpsc.DPSC(config: DPSCConfig | dict)#
Bases:
objectDifferentially private synthetic control estimator.
- Parameters:
config (DPSCConfig or dict) – Configuration. See
mlsynth.utils.dpsc_helpers.config.DPSCConfig.- Returns:
BaseEstimatorResults – The released private counterfactual and ATT, with the privacy noise quantified as the standard error / interval, and the non-private ridge ATT recorded for reference in
method_details.
Examples
>>> from mlsynth import DPSC >>> res = DPSC({"df": panel, "outcome": "cigsale", "treat": "treat", ... "unitid": "state", "time": "year", ... "mechanism": "objective", "epsilon1": 1.0, "epsilon2": 1.0, ... "display_graphs": False}).fit() >>> res.effects.att
- fit() BaseEstimatorResults#
Fit DPSC and return the standardized results container.
Configuration#
- class mlsynth.utils.dpsc_helpers.config.DPSCConfig(*, 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>, mechanism: ~typing.Literal['objective', 'output'] = 'objective', epsilon1: ~typing.Annotated[float, ~annotated_types.Gt(gt=0)] = 1.0, epsilon2: ~typing.Annotated[float, ~annotated_types.Gt(gt=0)] = 1.0, ridge_lambda: ~typing.Annotated[float, ~annotated_types.Gt(gt=0)] = 10.0, delta: ~typing.Annotated[float, ~annotated_types.Ge(ge=0.0), ~annotated_types.Lt(lt=1.0)] = 0.0, n_draws: ~typing.Annotated[int, ~annotated_types.Ge(ge=1)] = 500, seed: int = 0, alpha: ~typing.Annotated[float, ~annotated_types.Gt(gt=0.0), ~annotated_types.Lt(lt=1.0)] = 0.05)#
Bases:
BaseEstimatorConfigConfiguration for
mlsynth.DPSC.Differentially private synthetic control (Rho, Cummings & Misra 2023): a ridge synthetic control whose released counterfactual satisfies
epsilon-differential privacy over the donor pool. Privacy is spent in two stages – learning the regression coefficients (epsilon1) and releasing the post-intervention prediction (epsilon2) – so the total budget isepsilon1 + epsilon2.The default
mechanism="objective"is the objective-perturbation estimator (Algorithm 3), which perturbs the ridge objective; it is far more stable than output perturbation on typical panels."output"(Algorithm 2) perturbs the fitted coefficients directly and is provided for completeness – its variance is large unless the donor pool is big.- mechanism: Literal['objective', 'output']#
- model_config: ClassVar[ConfigDict] = {'arbitrary_types_allowed': True, 'extra': 'forbid'}#
Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].