From c316478ddd21dbae3e299ed5c20849d9a8b4a177 Mon Sep 17 00:00:00 2001 From: HayaTaga Date: Wed, 14 Oct 2026 16:04:37 +0900 Subject: [PATCH 1/4] Bundle the Cao-Dowd Proposition 99 panel load_california_cd(51) and load_california_cd(41) load the authors' 50-state-plus-DC cigarette-sales panel (1970-2000, treated from 1989), copied from jcao0/synthetic-control-spillover at commit 86004020, with the paper's exposure sets. .gitattributes keeps the authors' state list byte-identical (BOM and CRLF). Co-Authored-By: Claude Opus 5.5 --- .gitattributes | 2 + pyproject.toml | 2 +- scspill/data/__init__.py | 60 +- .../data/files/cao_dowd/cigs_consumption.csv | 1582 +++++++++++++++++ scspill/data/files/cao_dowd/state_list.csv | 52 + 5 files changed, 1691 insertions(+), 7 deletions(-) create mode 100644 .gitattributes create mode 100644 scspill/data/files/cao_dowd/cigs_consumption.csv create mode 100644 scspill/data/files/cao_dowd/state_list.csv diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..82a1564 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# Preserve the authors' BOM and CRLF in the frozen state-list input. +scspill/data/files/cao_dowd/state_list.csv whitespace=cr-at-eol diff --git a/pyproject.toml b/pyproject.toml index c64ee26..ebc0720 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,7 +87,7 @@ exclude = ["scspill.tests*"] [tool.setuptools.package-data] scspill = ["py.typed"] -"scspill.data" = ["files/*.csv"] +"scspill.data" = ["files/*.csv", "files/cao_dowd/*.csv"] # --------------------------------------------------------------------------- # Tooling diff --git a/scspill/data/__init__.py b/scspill/data/__init__.py index f72ef9a..003ab13 100644 --- a/scspill/data/__init__.py +++ b/scspill/data/__init__.py @@ -1,18 +1,20 @@ """Bundled case-study spillover panels for scspill. Ships the two empirical applications of the ``sar`` model's source article -(Sakaguchi & Tagawa 2026) inside the wheel, each as a long panel plus its -spatial-weight specification. Nothing about them is model-specific: they are -ordinary spillover panels and will serve any model added later. +(Sakaguchi & Tagawa 2026), plus Cao and Dowd's 51/41-unit California panel. +The latter has a different sample and intervention date from the SAR +California example. * :func:`load_california` -- the California Proposition 99 tobacco panel (Abadie, Diamond & Hainmueller 2010): 39 states, 1970-2000, treatment in 1988; rook-contiguity spatial weights. * :func:`load_sudan` -- the 2011 Sudan secession panel: 34 African countries, 2000-2015, treatment in 2011; average bilateral-trade spatial weights. +* :func:`load_california_cd` -- the Cao--Dowd Proposition 99 panel: 51 or + 41 regions, 1970-2000, treatment from 1989; labeled affected-unit set. -The CSV files are verbatim copies of the replication package's nonproprietary -data exports. Both weight objects are stored raw (unnormalized) -- +The SAR CSV files are verbatim copies of that replication package's +nonproprietary data exports. Both SAR weight objects are stored raw -- row-normalization of ``W`` and sum-normalization of ``w`` happen inside the estimator, exactly as in the R package. """ @@ -27,7 +29,53 @@ from ..exceptions import ScspillDataError -__all__ = ["SpillPanel", "load_california", "load_sudan"] +__all__ = ["CDPanel", "SpillPanel", "load_california", "load_california_cd", "load_sudan"] + + +@dataclass(frozen=True) +class CDPanel: + """Cao--Dowd Proposition 99 panel and published exposure structure.""" + + df: pd.DataFrame + affected_units: tuple[str, ...] + specification: int + + def config_kwargs(self) -> dict[str, Any]: + """Return inputs for ``SCSPILL({"method": "cd", ...})``.""" + return { + "method": "cd", + "df": self.df, + "outcome": "cigs", + "treat": "treated", + "unitid": "state", + "time": "year", + "affected_units": list(self.affected_units), + } + + +def load_california_cd(specification: int = 51) -> CDPanel: + """Load the authors' 1970--2000 Proposition 99 panel, treated from 1989. + + ``51`` uses every state and DC with the 12 policy-contaminated states and + three neighbors as exposed units. ``41`` removes ten non-neighbor policy + states, leaving Arizona, Nevada and Oregon as exposed controls. + """ + if specification not in (41, 51): + raise ScspillDataError("Cao--Dowd California specification must be 41 or 51.") + root = files("scspill.data").joinpath("files", "cao_dowd") + with root.joinpath("cigs_consumption.csv").open("r", encoding="utf-8") as fh: + df = pd.read_csv(fh) + with root.joinpath("state_list.csv").open("r", encoding="utf-8-sig") as fh: + flags = pd.read_csv(fh) + policy = set(flags.loc[flags["missing12"] == 1, "state"]) + neighbors = set(flags.loc[flags["neighbor"] == 1, "state"]) + if specification == 41: + df = df.loc[~df["state"].isin(policy - neighbors)].copy() + affected = tuple(sorted(neighbors)) + else: + affected = tuple(sorted(policy | neighbors)) + df["treated"] = ((df["state"] == "CA") & (df["year"] >= 1989)).astype(int) + return CDPanel(df=df, affected_units=affected, specification=specification) #: Mapping from the R-mangled WDI column headers in ``sudan_panel.csv`` to the diff --git a/scspill/data/files/cao_dowd/cigs_consumption.csv b/scspill/data/files/cao_dowd/cigs_consumption.csv new file mode 100644 index 0000000..cce1e59 --- /dev/null +++ b/scspill/data/files/cao_dowd/cigs_consumption.csv @@ -0,0 +1,1582 @@ +state,year,cigs +AK,1970,121.3 +AK,1971,123 +AK,1972,130 +AK,1973,125.8 +AK,1974,130.4 +AK,1975,150.4 +AK,1976,164.8 +AK,1977,159.3 +AK,1978,143.6 +AK,1979,138.1 +AK,1980,134 +AK,1981,144 +AK,1982,148.3 +AK,1983,146.9 +AK,1984,133.6 +AK,1985,128.9 +AK,1986,126.3 +AK,1987,110.4 +AK,1988,80 +AK,1989,94.4 +AK,1990,100.2 +AK,1991,101.8 +AK,1992,98.5 +AK,1993,95.2 +AK,1994,94.1 +AK,1995,89.5 +AK,1996,89.9 +AK,1997,81.7 +AK,1998,78.6 +AK,1999,70.5 +AK,2000,66 +AL,1970,89.8 +AL,1971,95.4 +AL,1972,101.1 +AL,1973,102.9 +AL,1974,108.2 +AL,1975,111.7 +AL,1976,116.2 +AL,1977,117.1 +AL,1978,123 +AL,1979,121.4 +AL,1980,123.2 +AL,1981,119.6 +AL,1982,119.1 +AL,1983,116.3 +AL,1984,113 +AL,1985,114.5 +AL,1986,116.3 +AL,1987,114 +AL,1988,112.1 +AL,1989,105.6 +AL,1990,108.6 +AL,1991,107.9 +AL,1992,109.1 +AL,1993,108.5 +AL,1994,107.1 +AL,1995,102.6 +AL,1996,101.4 +AL,1997,104.9 +AL,1998,106.2 +AL,1999,100.7 +AL,2000,96.2 +AR,1970,100.3 +AR,1971,104.1 +AR,1972,103.9 +AR,1973,108 +AR,1974,109.7 +AR,1975,114.8 +AR,1976,119.1 +AR,1977,122.6 +AR,1978,127.3 +AR,1979,126.5 +AR,1980,131.8 +AR,1981,128.7 +AR,1982,127.4 +AR,1983,128 +AR,1984,123.1 +AR,1985,125.8 +AR,1986,126 +AR,1987,122.3 +AR,1988,121.5 +AR,1989,118.3 +AR,1990,113.1 +AR,1991,116.8 +AR,1992,126 +AR,1993,113.8 +AR,1994,108.8 +AR,1995,113 +AR,1996,110.7 +AR,1997,108.7 +AR,1998,109.5 +AR,1999,104.8 +AR,2000,99.4 +AZ,1970,115.2 +AZ,1971,109.6 +AZ,1972,125 +AZ,1973,128.3 +AZ,1974,133.1 +AZ,1975,121.8 +AZ,1976,122.3 +AZ,1977,121.7 +AZ,1978,124.7 +AZ,1979,124.6 +AZ,1980,126.8 +AZ,1981,113.8 +AZ,1982,113.5 +AZ,1983,111.1 +AZ,1984,107.1 +AZ,1985,107.1 +AZ,1986,107.9 +AZ,1987,106.1 +AZ,1988,102.2 +AZ,1989,96.8 +AZ,1990,88.9 +AZ,1991,81.2 +AZ,1992,79 +AZ,1993,80.3 +AZ,1994,79 +AZ,1995,77.3 +AZ,1996,71.4 +AZ,1997,64.6 +AZ,1998,66 +AZ,1999,61.7 +AZ,2000,58.5 +CA,1970,123 +CA,1971,121 +CA,1972,123.5 +CA,1973,124.4 +CA,1974,126.7 +CA,1975,127.1 +CA,1976,128 +CA,1977,126.4 +CA,1978,126.1 +CA,1979,121.9 +CA,1980,120.2 +CA,1981,118.6 +CA,1982,115.4 +CA,1983,110.8 +CA,1984,104.8 +CA,1985,102.8 +CA,1986,99.7 +CA,1987,97.5 +CA,1988,90.1 +CA,1989,82.4 +CA,1990,77.8 +CA,1991,68.7 +CA,1992,67.5 +CA,1993,63.4 +CA,1994,58.6 +CA,1995,56.4 +CA,1996,54.5 +CA,1997,53.8 +CA,1998,52.3 +CA,1999,47.2 +CA,2000,41.6 +CO,1970,124.8 +CO,1971,125.5 +CO,1972,134.3 +CO,1973,137.9 +CO,1974,132.8 +CO,1975,131 +CO,1976,134.2 +CO,1977,132 +CO,1978,129.2 +CO,1979,131.5 +CO,1980,131 +CO,1981,133.8 +CO,1982,130.5 +CO,1983,125.3 +CO,1984,119.7 +CO,1985,112.4 +CO,1986,109.9 +CO,1987,102.4 +CO,1988,94.6 +CO,1989,88.8 +CO,1990,87.4 +CO,1991,90.2 +CO,1992,88.3 +CO,1993,88.6 +CO,1994,89.1 +CO,1995,85.4 +CO,1996,83.1 +CO,1997,81.3 +CO,1998,81.2 +CO,1999,79.6 +CO,2000,73 +CT,1970,120 +CT,1971,117.6 +CT,1972,110.8 +CT,1973,109.3 +CT,1974,112.4 +CT,1975,110.2 +CT,1976,113.4 +CT,1977,117.3 +CT,1978,117.5 +CT,1979,117.4 +CT,1980,118 +CT,1981,116.4 +CT,1982,114.7 +CT,1983,114.1 +CT,1984,112.5 +CT,1985,111 +CT,1986,108.5 +CT,1987,109 +CT,1988,104.8 +CT,1989,100.6 +CT,1990,91.5 +CT,1991,86.7 +CT,1992,83.6 +CT,1993,79.1 +CT,1994,76.6 +CT,1995,79.3 +CT,1996,76 +CT,1997,75.9 +CT,1998,75.5 +CT,1999,73.4 +CT,2000,71.4 +DC,1970,200.4 +DC,1971,213 +DC,1972,220.6 +DC,1973,209.4 +DC,1974,182.7 +DC,1975,176.5 +DC,1976,167.7 +DC,1977,136.7 +DC,1978,130.3 +DC,1979,128.5 +DC,1980,129.7 +DC,1981,131.9 +DC,1982,132 +DC,1983,124.5 +DC,1984,124.6 +DC,1985,122.1 +DC,1986,117.2 +DC,1987,113 +DC,1988,105.4 +DC,1989,101.1 +DC,1990,84.1 +DC,1991,82.2 +DC,1992,82.6 +DC,1993,68 +DC,1994,54.1 +DC,1995,57.9 +DC,1996,56 +DC,1997,54.3 +DC,1998,50.9 +DC,1999,50.8 +DC,2000,50.2 +DE,1970,155 +DE,1971,161.1 +DE,1972,156.3 +DE,1973,154.7 +DE,1974,151.3 +DE,1975,147.6 +DE,1976,153 +DE,1977,153.3 +DE,1978,155.5 +DE,1979,150.2 +DE,1980,150.5 +DE,1981,152.6 +DE,1982,154.1 +DE,1983,149.6 +DE,1984,144 +DE,1985,144.5 +DE,1986,142.4 +DE,1987,141 +DE,1988,137.1 +DE,1989,131.7 +DE,1990,127.2 +DE,1991,118.8 +DE,1992,120 +DE,1993,123.8 +DE,1994,126.1 +DE,1995,127.2 +DE,1996,128.3 +DE,1997,124.1 +DE,1998,132.8 +DE,1999,139.5 +DE,2000,140.7 +FL,1970,123.6 +FL,1971,123 +FL,1972,126 +FL,1973,132.6 +FL,1974,136 +FL,1975,131.9 +FL,1976,130.3 +FL,1977,133.1 +FL,1978,133.4 +FL,1979,135.4 +FL,1980,139 +FL,1981,132.8 +FL,1982,131.8 +FL,1983,127.6 +FL,1984,124.7 +FL,1985,124.2 +FL,1986,124.7 +FL,1987,125.1 +FL,1988,119.5 +FL,1989,113.8 +FL,1990,110.1 +FL,1991,98.2 +FL,1992,95.9 +FL,1993,96.8 +FL,1994,93.6 +FL,1995,95.4 +FL,1996,91.8 +FL,1997,93 +FL,1998,91.7 +FL,1999,88.2 +FL,2000,82.5 +GA,1970,109.9 +GA,1971,115.7 +GA,1972,117 +GA,1973,119.8 +GA,1974,123.7 +GA,1975,122.9 +GA,1976,125.9 +GA,1977,127.9 +GA,1978,130.6 +GA,1979,131 +GA,1980,134 +GA,1981,131.7 +GA,1982,131.2 +GA,1983,128.6 +GA,1984,126.3 +GA,1985,128.8 +GA,1986,129 +GA,1987,129.3 +GA,1988,124.1 +GA,1989,117.1 +GA,1990,113.8 +GA,1991,109.6 +GA,1992,109.2 +GA,1993,109.2 +GA,1994,107.8 +GA,1995,100.3 +GA,1996,102.7 +GA,1997,100.6 +GA,1998,100.5 +GA,1999,97.1 +GA,2000,88.1 +HI,1970,82.1 +HI,1971,85 +HI,1972,81.6 +HI,1973,79.2 +HI,1974,89.3 +HI,1975,92.4 +HI,1976,99.4 +HI,1977,95.1 +HI,1978,93.2 +HI,1979,88.8 +HI,1980,89.4 +HI,1981,85.6 +HI,1982,77.7 +HI,1983,80 +HI,1984,75.7 +HI,1985,69.8 +HI,1986,61.2 +HI,1987,54.7 +HI,1988,55.6 +HI,1989,57.4 +HI,1990,48.9 +HI,1991,54.7 +HI,1992,51.3 +HI,1993,52.4 +HI,1994,44.2 +HI,1995,45.7 +HI,1996,51.6 +HI,1997,49.1 +HI,1998,35.5 +HI,1999,32.5 +HI,2000,31.9 +IA,1970,108.5 +IA,1971,108.4 +IA,1972,109.4 +IA,1973,110.6 +IA,1974,116.1 +IA,1975,120.5 +IA,1976,124.4 +IA,1977,125.5 +IA,1978,127.1 +IA,1979,124.2 +IA,1980,124.6 +IA,1981,132.9 +IA,1982,116.2 +IA,1983,115.6 +IA,1984,111.2 +IA,1985,109.4 +IA,1986,104.1 +IA,1987,101.1 +IA,1988,100.2 +IA,1989,94.4 +IA,1990,95.4 +IA,1991,97.1 +IA,1992,95.2 +IA,1993,92.5 +IA,1994,93.4 +IA,1995,93 +IA,1996,94 +IA,1997,93.9 +IA,1998,94 +IA,1999,91.7 +IA,2000,88.9 +ID,1970,102.4 +ID,1971,108.5 +ID,1972,126.1 +ID,1973,121.8 +ID,1974,125.6 +ID,1975,123.3 +ID,1976,125.1 +ID,1977,125 +ID,1978,122.8 +ID,1979,117.5 +ID,1980,115.2 +ID,1981,114.1 +ID,1982,111.5 +ID,1983,111.3 +ID,1984,103.6 +ID,1985,100.7 +ID,1986,96.7 +ID,1987,95 +ID,1988,84.5 +ID,1989,78.4 +ID,1990,90.1 +ID,1991,85.4 +ID,1992,85.1 +ID,1993,86.7 +ID,1994,93 +ID,1995,78.2 +ID,1996,73.6 +ID,1997,75 +ID,1998,78.9 +ID,1999,75.1 +ID,2000,66.9 +IL,1970,124.8 +IL,1971,125.6 +IL,1972,126.6 +IL,1973,124.4 +IL,1974,131.9 +IL,1975,131.8 +IL,1976,134.4 +IL,1977,134 +IL,1978,136.7 +IL,1979,135.3 +IL,1980,135.2 +IL,1981,133 +IL,1982,130.7 +IL,1983,127.9 +IL,1984,124 +IL,1985,121.6 +IL,1986,118.2 +IL,1987,109.5 +IL,1988,107.6 +IL,1989,104.6 +IL,1990,94.1 +IL,1991,96.1 +IL,1992,94.8 +IL,1993,94.6 +IL,1994,85.7 +IL,1995,84.3 +IL,1996,81.8 +IL,1997,79.6 +IL,1998,80.3 +IL,1999,72.2 +IL,2000,70 +IN,1970,134.6 +IN,1971,139.3 +IN,1972,149.2 +IN,1973,156 +IN,1974,159.6 +IN,1975,162.4 +IN,1976,166.6 +IN,1977,173 +IN,1978,150.9 +IN,1979,148.9 +IN,1980,146.9 +IN,1981,148.5 +IN,1982,147.7 +IN,1983,143 +IN,1984,137.8 +IN,1985,135.3 +IN,1986,137.6 +IN,1987,134 +IN,1988,134 +IN,1989,132.5 +IN,1990,128.3 +IN,1991,127.2 +IN,1992,128.2 +IN,1993,126.8 +IN,1994,128.2 +IN,1995,135.4 +IN,1996,135.1 +IN,1997,135.3 +IN,1998,135.9 +IN,1999,133.3 +IN,2000,125.5 +KS,1970,114 +KS,1971,102.8 +KS,1972,111 +KS,1973,115.2 +KS,1974,118.6 +KS,1975,123.4 +KS,1976,127.7 +KS,1977,127.9 +KS,1978,127.1 +KS,1979,126.4 +KS,1980,127.1 +KS,1981,132 +KS,1982,130.9 +KS,1983,127.6 +KS,1984,121.7 +KS,1985,115.7 +KS,1986,109.4 +KS,1987,105.2 +KS,1988,103.2 +KS,1989,96.5 +KS,1990,94.3 +KS,1991,91.8 +KS,1992,90 +KS,1993,89.9 +KS,1994,89.1 +KS,1995,90.1 +KS,1996,88.7 +KS,1997,89.2 +KS,1998,87.6 +KS,1999,83.3 +KS,2000,79.8 +KY,1970,155.8 +KY,1971,163.5 +KY,1972,179.4 +KY,1973,201.9 +KY,1974,212.4 +KY,1975,223 +KY,1976,230.9 +KY,1977,229.4 +KY,1978,224.7 +KY,1979,214.9 +KY,1980,215.3 +KY,1981,209.7 +KY,1982,210.6 +KY,1983,201.1 +KY,1984,183.2 +KY,1985,182.4 +KY,1986,179.8 +KY,1987,171.2 +KY,1988,173.2 +KY,1989,171.6 +KY,1990,182.5 +KY,1991,170.8 +KY,1992,167.6 +KY,1993,167.6 +KY,1994,170.2 +KY,1995,175.3 +KY,1996,179 +KY,1997,186.8 +KY,1998,171.3 +KY,1999,165.3 +KY,2000,156.2 +LA,1970,115.9 +LA,1971,119.8 +LA,1972,125.3 +LA,1973,126.7 +LA,1974,129.9 +LA,1975,133.6 +LA,1976,139.6 +LA,1977,140 +LA,1978,142.7 +LA,1979,140.1 +LA,1980,143.8 +LA,1981,144 +LA,1982,143.9 +LA,1983,133.7 +LA,1984,128.9 +LA,1985,125 +LA,1986,121.2 +LA,1987,116.5 +LA,1988,110.9 +LA,1989,103.6 +LA,1990,101.5 +LA,1991,107.2 +LA,1992,108.5 +LA,1993,106.2 +LA,1994,105.3 +LA,1995,105.7 +LA,1996,106.8 +LA,1997,105.3 +LA,1998,103.2 +LA,1999,101 +LA,2000,104.3 +MA,1970,124.3 +MA,1971,121.4 +MA,1972,117.9 +MA,1973,121.2 +MA,1974,124.3 +MA,1975,126.1 +MA,1976,116.9 +MA,1977,118.9 +MA,1978,120.5 +MA,1979,118.2 +MA,1980,120.5 +MA,1981,123.2 +MA,1982,122.3 +MA,1983,119.8 +MA,1984,116 +MA,1985,117.2 +MA,1986,115.8 +MA,1987,113.8 +MA,1988,111.7 +MA,1989,104.4 +MA,1990,99.4 +MA,1991,93.5 +MA,1992,90.5 +MA,1993,84.1 +MA,1994,77 +MA,1995,76.9 +MA,1996,74.6 +MA,1997,66.7 +MA,1998,63.6 +MA,1999,60.4 +MA,2000,58.3 +MD,1970,123.5 +MD,1971,126.7 +MD,1972,133.2 +MD,1973,137.3 +MD,1974,144.8 +MD,1975,146.1 +MD,1976,137.1 +MD,1977,135.7 +MD,1978,134.1 +MD,1979,131.9 +MD,1980,131.5 +MD,1981,137.1 +MD,1982,131.3 +MD,1983,128.2 +MD,1984,122.5 +MD,1985,121.9 +MD,1986,121.8 +MD,1987,119.3 +MD,1988,115 +MD,1989,109 +MD,1990,102.8 +MD,1991,97.6 +MD,1992,91.7 +MD,1993,82.6 +MD,1994,80.8 +MD,1995,77.1 +MD,1996,75 +MD,1997,72.7 +MD,1998,72 +MD,1999,71.4 +MD,2000,57.7 +ME,1970,128.5 +ME,1971,133.2 +ME,1972,136.5 +ME,1973,138 +ME,1974,142.1 +ME,1975,140.7 +ME,1976,144.9 +ME,1977,145.6 +ME,1978,143.9 +ME,1979,138.5 +ME,1980,141.2 +ME,1981,138.9 +ME,1982,139.5 +ME,1983,135.4 +ME,1984,135.5 +ME,1985,127.9 +ME,1986,119 +ME,1987,125 +ME,1988,125 +ME,1989,122.4 +ME,1990,117.5 +ME,1991,116.1 +ME,1992,114.7 +ME,1993,108.5 +ME,1994,101.6 +ME,1995,102.3 +ME,1996,100 +ME,1997,101.1 +ME,1998,94.5 +ME,1999,85.5 +ME,2000,82.9 +MI,1970,128.6 +MI,1971,126.1 +MI,1972,131.4 +MI,1973,133.7 +MI,1974,138.8 +MI,1975,136.8 +MI,1976,138 +MI,1977,140.3 +MI,1978,141.8 +MI,1979,140.9 +MI,1980,140.7 +MI,1981,139.5 +MI,1982,137.3 +MI,1983,128.3 +MI,1984,128.2 +MI,1985,126.6 +MI,1986,126.5 +MI,1987,122.1 +MI,1988,121.1 +MI,1989,116.6 +MI,1990,114.4 +MI,1991,110.3 +MI,1992,107.6 +MI,1993,106.5 +MI,1994,112.9 +MI,1995,82.8 +MI,1996,82.9 +MI,1997,75.6 +MI,1998,73.7 +MI,1999,81.7 +MI,2000,83.7 +MN,1970,104.3 +MN,1971,116.4 +MN,1972,96.8 +MN,1973,106.8 +MN,1974,110.6 +MN,1975,111.5 +MN,1976,116.7 +MN,1977,117.2 +MN,1978,118.9 +MN,1979,118.3 +MN,1980,117.7 +MN,1981,120.8 +MN,1982,119.4 +MN,1983,113.2 +MN,1984,110.8 +MN,1985,113 +MN,1986,104.8 +MN,1987,108.8 +MN,1988,94.1 +MN,1989,92.3 +MN,1990,90.7 +MN,1991,86.2 +MN,1992,83.8 +MN,1993,81.6 +MN,1994,83.4 +MN,1995,84.1 +MN,1996,81.7 +MN,1997,84.1 +MN,1998,83.2 +MN,1999,80.7 +MN,2000,76 +MO,1970,121.3 +MO,1971,127.6 +MO,1972,130 +MO,1973,132.1 +MO,1974,135.4 +MO,1975,135.6 +MO,1976,139.5 +MO,1977,140.8 +MO,1978,141.8 +MO,1979,140.2 +MO,1980,142.1 +MO,1981,140.5 +MO,1982,139.7 +MO,1983,134.1 +MO,1984,130 +MO,1985,129.2 +MO,1986,128.8 +MO,1987,128.7 +MO,1988,127.4 +MO,1989,122.8 +MO,1990,119.1 +MO,1991,119.9 +MO,1992,122.3 +MO,1993,121.6 +MO,1994,119.4 +MO,1995,124 +MO,1996,124.1 +MO,1997,120.6 +MO,1998,120.1 +MO,1999,118 +MO,2000,113.8 +MS,1970,93.4 +MS,1971,105.4 +MS,1972,112.1 +MS,1973,115 +MS,1974,117.1 +MS,1975,116.8 +MS,1976,120.9 +MS,1977,122.1 +MS,1978,124.9 +MS,1979,123.9 +MS,1980,127 +MS,1981,125.3 +MS,1982,125.8 +MS,1983,122.3 +MS,1984,116.4 +MS,1985,115.3 +MS,1986,113.2 +MS,1987,110 +MS,1988,109 +MS,1989,108.3 +MS,1990,101.8 +MS,1991,105.6 +MS,1992,103.9 +MS,1993,105.4 +MS,1994,106 +MS,1995,107.5 +MS,1996,106.9 +MS,1997,106.3 +MS,1998,107 +MS,1999,103.9 +MS,2000,97.2 +MT,1970,111.2 +MT,1971,115.6 +MT,1972,122.2 +MT,1973,119.9 +MT,1974,121.9 +MT,1975,123.7 +MT,1976,124.9 +MT,1977,127 +MT,1978,127.2 +MT,1979,120.3 +MT,1980,122 +MT,1981,121.1 +MT,1982,122.4 +MT,1983,113.7 +MT,1984,110.1 +MT,1985,103.6 +MT,1986,97.8 +MT,1987,91.7 +MT,1988,87.1 +MT,1989,86.2 +MT,1990,84.7 +MT,1991,82.9 +MT,1992,86.6 +MT,1993,86 +MT,1994,88.2 +MT,1995,90.5 +MT,1996,87.3 +MT,1997,88.9 +MT,1998,89.1 +MT,1999,82.6 +MT,2000,75.5 +NC,1970,172.4 +NC,1971,187.6 +NC,1972,214.1 +NC,1973,226.5 +NC,1974,227.3 +NC,1975,226 +NC,1976,230.2 +NC,1977,217 +NC,1978,205.5 +NC,1979,197.3 +NC,1980,187.8 +NC,1981,179.3 +NC,1982,179 +NC,1983,169.7 +NC,1984,160.6 +NC,1985,156.3 +NC,1986,154.4 +NC,1987,150.5 +NC,1988,146 +NC,1989,139.3 +NC,1990,133.7 +NC,1991,132.7 +NC,1992,128.8 +NC,1993,129.7 +NC,1994,112.7 +NC,1995,124.9 +NC,1996,129.7 +NC,1997,125.6 +NC,1998,126 +NC,1999,113.1 +NC,2000,109 +ND,1970,93.8 +ND,1971,98.5 +ND,1972,103.8 +ND,1973,108.7 +ND,1974,110.5 +ND,1975,117.9 +ND,1976,125.4 +ND,1977,122.2 +ND,1978,121.9 +ND,1979,121.3 +ND,1980,123.7 +ND,1981,125.7 +ND,1982,126.8 +ND,1983,119.6 +ND,1984,109.4 +ND,1985,103.2 +ND,1986,99.8 +ND,1987,92.3 +ND,1988,87.1 +ND,1989,84.1 +ND,1990,77.1 +ND,1991,85.2 +ND,1992,74.4 +ND,1993,83 +ND,1994,81 +ND,1995,80.6 +ND,1996,80.8 +ND,1997,77.5 +ND,1998,79.1 +ND,1999,74.7 +ND,2000,72.5 +NE,1970,108.1 +NE,1971,108.6 +NE,1972,104.9 +NE,1973,106.6 +NE,1974,110.5 +NE,1975,114.1 +NE,1976,118.1 +NE,1977,117.7 +NE,1978,117.4 +NE,1979,116.1 +NE,1980,116.3 +NE,1981,117 +NE,1982,117.1 +NE,1983,110.8 +NE,1984,107.7 +NE,1985,105.1 +NE,1986,103.1 +NE,1987,101.3 +NE,1988,92.9 +NE,1989,93.8 +NE,1990,89.9 +NE,1991,92.4 +NE,1992,90.6 +NE,1993,91.1 +NE,1994,85.9 +NE,1995,88.5 +NE,1996,86.2 +NE,1997,85.5 +NE,1998,79.7 +NE,1999,86.8 +NE,2000,77.6 +NH,1970,265.7 +NH,1971,278 +NH,1972,296.2 +NH,1973,279 +NH,1974,269.8 +NH,1975,269.1 +NH,1976,290.5 +NH,1977,278.8 +NH,1978,269.6 +NH,1979,254.6 +NH,1980,247.8 +NH,1981,245.4 +NH,1982,239.8 +NH,1983,232.9 +NH,1984,215.1 +NH,1985,201.1 +NH,1986,195.9 +NH,1987,195.1 +NH,1988,180.4 +NH,1989,172.9 +NH,1990,152.4 +NH,1991,144.8 +NH,1992,143.7 +NH,1993,148.9 +NH,1994,153.8 +NH,1995,158.5 +NH,1996,158 +NH,1997,174.4 +NH,1998,173.8 +NH,1999,171.7 +NH,2000,147.3 +NJ,1970,120.7 +NJ,1971,125.1 +NJ,1972,128.4 +NJ,1973,120.2 +NJ,1974,121.4 +NJ,1975,122.3 +NJ,1976,122.4 +NJ,1977,122.8 +NJ,1978,123.9 +NJ,1979,123.8 +NJ,1980,124.3 +NJ,1981,127.9 +NJ,1982,126.3 +NJ,1983,121.3 +NJ,1984,117.8 +NJ,1985,116.9 +NJ,1986,115.5 +NJ,1987,111.6 +NJ,1988,107.8 +NJ,1989,103 +NJ,1990,98.6 +NJ,1991,86.7 +NJ,1992,86.6 +NJ,1993,82.9 +NJ,1994,81.6 +NJ,1995,81 +NJ,1996,79.1 +NJ,1997,77 +NJ,1998,72.6 +NJ,1999,63.6 +NJ,2000,60.1 +NM,1970,90 +NM,1971,92.6 +NM,1972,99.3 +NM,1973,98.9 +NM,1974,100.3 +NM,1975,103.1 +NM,1976,102.4 +NM,1977,102.4 +NM,1978,103.1 +NM,1979,101 +NM,1980,102.7 +NM,1981,103 +NM,1982,97.5 +NM,1983,96.3 +NM,1984,88.9 +NM,1985,88 +NM,1986,88.2 +NM,1987,82.3 +NM,1988,77.7 +NM,1989,74.4 +NM,1990,70.8 +NM,1991,69.9 +NM,1992,71.4 +NM,1993,69 +NM,1994,68.2 +NM,1995,67 +NM,1996,65.7 +NM,1997,61.8 +NM,1998,62.6 +NM,1999,59.7 +NM,2000,53.8 +NV,1970,189.5 +NV,1971,190.5 +NV,1972,198.6 +NV,1973,201.5 +NV,1974,204.7 +NV,1975,205.2 +NV,1976,201.4 +NV,1977,190.8 +NV,1978,187 +NV,1979,183.3 +NV,1980,177.7 +NV,1981,171.9 +NV,1982,165.1 +NV,1983,159.2 +NV,1984,136.6 +NV,1985,146.7 +NV,1986,142.6 +NV,1987,147.7 +NV,1988,141.9 +NV,1989,137.9 +NV,1990,137.3 +NV,1991,115.5 +NV,1992,110 +NV,1993,108.1 +NV,1994,105.2 +NV,1995,100.9 +NV,1996,99 +NV,1997,95.6 +NV,1998,102.4 +NV,1999,103.9 +NV,2000,93.2 +NY,1970,119 +NY,1971,123.3 +NY,1972,119.9 +NY,1973,118.7 +NY,1974,121.6 +NY,1975,123.9 +NY,1976,124.6 +NY,1977,125.5 +NY,1978,126.4 +NY,1979,124.4 +NY,1980,127.6 +NY,1981,130.1 +NY,1982,128.5 +NY,1983,124.6 +NY,1984,119.3 +NY,1985,115.9 +NY,1986,114 +NY,1987,108.9 +NY,1988,106.2 +NY,1989,100.1 +NY,1990,93.7 +NY,1991,86.4 +NY,1992,82.4 +NY,1993,78.2 +NY,1994,71.6 +NY,1995,70.5 +NY,1996,65.9 +NY,1997,64.5 +NY,1998,64.9 +NY,1999,62.9 +NY,2000,57.8 +OH,1970,121.6 +OH,1971,124.6 +OH,1972,124.4 +OH,1973,120.5 +OH,1974,122.1 +OH,1975,122.5 +OH,1976,124.6 +OH,1977,127.3 +OH,1978,131.3 +OH,1979,130.9 +OH,1980,133.5 +OH,1981,132.8 +OH,1982,134 +OH,1983,130 +OH,1984,127.1 +OH,1985,126.7 +OH,1986,126.3 +OH,1987,124.6 +OH,1988,122.4 +OH,1989,118.6 +OH,1990,115.5 +OH,1991,113.2 +OH,1992,112.3 +OH,1993,108.9 +OH,1994,108.6 +OH,1995,111.7 +OH,1996,107.6 +OH,1997,108.6 +OH,1998,106.4 +OH,1999,104 +OH,2000,99.9 +OK,1970,108.4 +OK,1971,115.4 +OK,1972,121.7 +OK,1973,124.1 +OK,1974,130.5 +OK,1975,132.9 +OK,1976,138.6 +OK,1977,140.4 +OK,1978,143.6 +OK,1979,141.6 +OK,1980,141.6 +OK,1981,143.7 +OK,1982,147 +OK,1983,140 +OK,1984,128.1 +OK,1985,124.2 +OK,1986,119.9 +OK,1987,113.1 +OK,1988,103.6 +OK,1989,97.5 +OK,1990,88.4 +OK,1991,87.8 +OK,1992,86.3 +OK,1993,86.2 +OK,1994,104.8 +OK,1995,109.5 +OK,1996,110.8 +OK,1997,111.8 +OK,1998,112.2 +OK,1999,111.4 +OK,2000,108.9 +OR,1970,157 +OR,1971,157.9 +OR,1972,160.4 +OR,1973,155.3 +OR,1974,155.8 +OR,1975,154.4 +OR,1976,156.5 +OR,1977,155.6 +OR,1978,156.3 +OR,1979,150.7 +OR,1980,146.5 +OR,1981,143.1 +OR,1982,135.3 +OR,1983,124.6 +OR,1984,118 +OR,1985,118.6 +OR,1986,112 +OR,1987,108.9 +OR,1988,108.2 +OR,1989,106.6 +OR,1990,102.5 +OR,1991,100.1 +OR,1992,101.6 +OR,1993,98.7 +OR,1994,96.6 +OR,1995,94.6 +OR,1996,94.3 +OR,1997,89.5 +OR,1998,84.2 +OR,1999,79.9 +OR,2000,72 +PA,1970,107.3 +PA,1971,106.3 +PA,1972,109 +PA,1973,110.7 +PA,1974,114.2 +PA,1975,114.6 +PA,1976,118.8 +PA,1977,120.1 +PA,1978,122.3 +PA,1979,122.6 +PA,1980,124 +PA,1981,125.2 +PA,1982,123.3 +PA,1983,125.3 +PA,1984,115.3 +PA,1985,115.8 +PA,1986,113.9 +PA,1987,110.6 +PA,1988,107.6 +PA,1989,107.1 +PA,1990,101.3 +PA,1991,102.5 +PA,1992,96.2 +PA,1993,94.7 +PA,1994,95.4 +PA,1995,95.4 +PA,1996,93.3 +PA,1997,92.9 +PA,1998,92.1 +PA,1999,91.1 +PA,2000,87.9 +RI,1970,123.9 +RI,1971,123.2 +RI,1972,134.4 +RI,1973,142 +RI,1974,146.1 +RI,1975,154.7 +RI,1976,150.2 +RI,1977,148.8 +RI,1978,146.8 +RI,1979,145.8 +RI,1980,149.3 +RI,1981,151.2 +RI,1982,146.3 +RI,1983,135.8 +RI,1984,136.9 +RI,1985,133.4 +RI,1986,136.3 +RI,1987,124.4 +RI,1988,138 +RI,1989,120.8 +RI,1990,101.4 +RI,1991,103.6 +RI,1992,100.1 +RI,1993,94.1 +RI,1994,91.9 +RI,1995,90.8 +RI,1996,87.5 +RI,1997,90 +RI,1998,88.7 +RI,1999,86.9 +RI,2000,83.1 +SC,1970,103.6 +SC,1971,115 +SC,1972,118.7 +SC,1973,125.5 +SC,1974,129.7 +SC,1975,130.5 +SC,1976,136.8 +SC,1977,137.2 +SC,1978,140.4 +SC,1979,135.7 +SC,1980,138.3 +SC,1981,136.1 +SC,1982,136 +SC,1983,131.1 +SC,1984,127 +SC,1985,125.4 +SC,1986,126.6 +SC,1987,126.6 +SC,1988,124.4 +SC,1989,122.4 +SC,1990,118.6 +SC,1991,121.5 +SC,1992,112.9 +SC,1993,115.2 +SC,1994,112.2 +SC,1995,109.2 +SC,1996,102.9 +SC,1997,124.5 +SC,1998,126.9 +SC,1999,109.4 +SC,2000,103.9 +SD,1970,92.7 +SD,1971,96.7 +SD,1972,103 +SD,1973,103.5 +SD,1974,108.4 +SD,1975,113.5 +SD,1976,116.7 +SD,1977,115.6 +SD,1978,116.9 +SD,1979,117.4 +SD,1980,114.7 +SD,1981,115.7 +SD,1982,113 +SD,1983,109.8 +SD,1984,105.7 +SD,1985,104.4 +SD,1986,97 +SD,1987,95.8 +SD,1988,91.9 +SD,1989,87.4 +SD,1990,88.3 +SD,1991,91.8 +SD,1992,93 +SD,1993,91.6 +SD,1994,94.8 +SD,1995,98.6 +SD,1996,92.3 +SD,1997,88.8 +SD,1998,88.3 +SD,1999,83.5 +SD,2000,75.1 +TN,1970,99.8 +TN,1971,106.3 +TN,1972,111.5 +TN,1973,109.7 +TN,1974,114.8 +TN,1975,117.4 +TN,1976,121.7 +TN,1977,124.6 +TN,1978,127.3 +TN,1979,127.2 +TN,1980,130.4 +TN,1981,129.1 +TN,1982,131.4 +TN,1983,129 +TN,1984,125.1 +TN,1985,128.7 +TN,1986,107.9 +TN,1987,130.6 +TN,1988,125.3 +TN,1989,124.7 +TN,1990,121.8 +TN,1991,120.6 +TN,1992,121 +TN,1993,120.8 +TN,1994,118.8 +TN,1995,125.4 +TN,1996,119.2 +TN,1997,118.9 +TN,1998,119.7 +TN,1999,115.8 +TN,2000,108.7 +TX,1970,106.4 +TX,1971,108.9 +TX,1972,108.6 +TX,1973,110.4 +TX,1974,114.7 +TX,1975,116 +TX,1976,121.4 +TX,1977,124.2 +TX,1978,126.6 +TX,1979,126.4 +TX,1980,129.7 +TX,1981,129 +TX,1982,131.2 +TX,1983,126.4 +TX,1984,117.2 +TX,1985,115.9 +TX,1986,113.7 +TX,1987,105.8 +TX,1988,96.5 +TX,1989,94.5 +TX,1990,85.6 +TX,1991,79.6 +TX,1992,77.2 +TX,1993,81.3 +TX,1994,78.8 +TX,1995,75.2 +TX,1996,74.6 +TX,1997,72.6 +TX,1998,73.2 +TX,1999,67.8 +TX,2000,69.3 +UT,1970,65.5 +UT,1971,67.7 +UT,1972,71.3 +UT,1973,72.7 +UT,1974,75.6 +UT,1975,75.8 +UT,1976,77.9 +UT,1977,78.9 +UT,1978,79.6 +UT,1979,79.1 +UT,1980,74.8 +UT,1981,77.6 +UT,1982,73.6 +UT,1983,69 +UT,1984,66.3 +UT,1985,66.5 +UT,1986,64.4 +UT,1987,67.7 +UT,1988,55 +UT,1989,57 +UT,1990,53.4 +UT,1991,53.5 +UT,1992,55 +UT,1993,56.2 +UT,1994,55.8 +UT,1995,52 +UT,1996,54 +UT,1997,57 +UT,1998,42.3 +UT,1999,43.9 +UT,2000,40.7 +VA,1970,124.3 +VA,1971,128.4 +VA,1972,137 +VA,1973,143.1 +VA,1974,149.6 +VA,1975,152.7 +VA,1976,158.1 +VA,1977,157.7 +VA,1978,155.9 +VA,1979,151.8 +VA,1980,148.9 +VA,1981,149.9 +VA,1982,147.4 +VA,1983,144.7 +VA,1984,136.8 +VA,1985,134.6 +VA,1986,135.8 +VA,1987,133 +VA,1988,129.5 +VA,1989,122.5 +VA,1990,118.9 +VA,1991,109.1 +VA,1992,108.2 +VA,1993,105.4 +VA,1994,106.2 +VA,1995,106.7 +VA,1996,104.6 +VA,1997,108 +VA,1998,105.6 +VA,1999,102.1 +VA,2000,96.7 +VT,1970,122.6 +VT,1971,124.4 +VT,1972,138 +VT,1973,146.8 +VT,1974,151.8 +VT,1975,155.5 +VT,1976,171.1 +VT,1977,169.4 +VT,1978,162.4 +VT,1979,160.9 +VT,1980,161.6 +VT,1981,163.8 +VT,1982,162.3 +VT,1983,153.8 +VT,1984,144.3 +VT,1985,144.5 +VT,1986,131.2 +VT,1987,128.3 +VT,1988,128.7 +VT,1989,120.9 +VT,1990,124.3 +VT,1991,120.9 +VT,1992,126.5 +VT,1993,117.2 +VT,1994,120.3 +VT,1995,123.2 +VT,1996,102.5 +VT,1997,97.7 +VT,1998,97 +VT,1999,94.1 +VT,2000,88.9 +WA,1970,96.7 +WA,1971,97 +WA,1972,88.5 +WA,1973,91 +WA,1974,98.6 +WA,1975,99.5 +WA,1976,100.3 +WA,1977,99.3 +WA,1978,101.3 +WA,1979,101.4 +WA,1980,101.4 +WA,1981,112.7 +WA,1982,106.6 +WA,1983,102.4 +WA,1984,96.2 +WA,1985,96.5 +WA,1986,94.2 +WA,1987,91.3 +WA,1988,88.2 +WA,1989,86.1 +WA,1990,83.4 +WA,1991,78.7 +WA,1992,81.1 +WA,1993,79.4 +WA,1994,72.6 +WA,1995,67.4 +WA,1996,58.1 +WA,1997,55.6 +WA,1998,56.4 +WA,1999,55.1 +WA,2000,52.8 +WI,1970,106.4 +WI,1971,105.4 +WI,1972,108.8 +WI,1973,109.5 +WI,1974,111.8 +WI,1975,113.5 +WI,1976,115.4 +WI,1977,117.2 +WI,1978,116.7 +WI,1979,117.1 +WI,1980,117.6 +WI,1981,119.9 +WI,1982,115.6 +WI,1983,106.3 +WI,1984,105.6 +WI,1985,107 +WI,1986,105.4 +WI,1987,106 +WI,1988,102.6 +WI,1989,100.3 +WI,1990,94.1 +WI,1991,95.5 +WI,1992,96.2 +WI,1993,91.2 +WI,1994,91.8 +WI,1995,93.5 +WI,1996,92.1 +WI,1997,91.9 +WI,1998,88.7 +WI,1999,84.4 +WI,2000,80.1 +WV,1970,114.5 +WV,1971,111.5 +WV,1972,117.5 +WV,1973,116.6 +WV,1974,119.9 +WV,1975,123.2 +WV,1976,129.7 +WV,1977,133.9 +WV,1978,131.6 +WV,1979,122.1 +WV,1980,122.3 +WV,1981,120.5 +WV,1982,119.8 +WV,1983,115.7 +WV,1984,111.9 +WV,1985,109.1 +WV,1986,112.1 +WV,1987,107.5 +WV,1988,109.1 +WV,1989,104 +WV,1990,104.1 +WV,1991,100.1 +WV,1992,98 +WV,1993,111 +WV,1994,104.2 +WV,1995,115.8 +WV,1996,112.7 +WV,1997,114.5 +WV,1998,114.6 +WV,1999,112.4 +WV,2000,107.9 +WY,1970,132.2 +WY,1971,131.7 +WY,1972,140 +WY,1973,141.2 +WY,1974,145.8 +WY,1975,160.7 +WY,1976,161.5 +WY,1977,160.4 +WY,1978,160.3 +WY,1979,168.6 +WY,1980,158.1 +WY,1981,163.1 +WY,1982,157.7 +WY,1983,141.2 +WY,1984,128.9 +WY,1985,125.7 +WY,1986,124.8 +WY,1987,110.4 +WY,1988,114.3 +WY,1989,111.4 +WY,1990,96.9 +WY,1991,109.1 +WY,1992,110.8 +WY,1993,108.4 +WY,1994,111.2 +WY,1995,115 +WY,1996,110.3 +WY,1997,108.8 +WY,1998,102.9 +WY,1999,104.8 +WY,2000,90.5 diff --git a/scspill/data/files/cao_dowd/state_list.csv b/scspill/data/files/cao_dowd/state_list.csv new file mode 100644 index 0000000..f1c84be --- /dev/null +++ b/scspill/data/files/cao_dowd/state_list.csv @@ -0,0 +1,52 @@ +state,treated,missing12,neighbor +CA,1,0,0 +AK,0,1,0 +AL,0,0,0 +AR,0,0,0 +AZ,0,1,1 +CO,0,0,0 +CT,0,0,0 +DC,0,1,0 +DE,0,0,0 +FL,0,1,0 +GA,0,0,0 +HI,0,1,0 +IA,0,0,0 +ID,0,0,0 +IL,0,0,0 +IN,0,0,0 +KS,0,0,0 +KY,0,0,0 +LA,0,0,0 +MA,0,1,0 +MD,0,1,0 +ME,0,0,0 +MI,0,1,0 +MN,0,0,0 +MO,0,0,0 +MS,0,0,0 +MT,0,0,0 +NC,0,0,0 +ND,0,0,0 +NE,0,0,0 +NH,0,0,0 +NJ,0,1,0 +NM,0,0,0 +NV,0,0,1 +NY,0,1,0 +OH,0,0,0 +OK,0,0,0 +OR,0,1,1 +PA,0,0,0 +RI,0,0,0 +SC,0,0,0 +SD,0,0,0 +TN,0,0,0 +TX,0,0,0 +UT,0,0,0 +VA,0,0,0 +VT,0,0,0 +WA,0,1,0 +WI,0,0,0 +WV,0,0,0 +WY,0,0,0 \ No newline at end of file From 1ca0280a4d3dfbb98e349310386b5838fba3f1d3 Mon Sep 17 00:00:00 2001 From: HayaTaga Date: Wed, 14 Oct 2026 16:05:03 +0900 Subject: [PATCH 2/4] Add the cd model: Cao & Dowd SCM with spillovers method="cd" fits an intercept-adjusted simplex SCM for every unit, a fixed exposure structure (affected_units or a labeled effect_matrix), period-specific direct and spillover effects, Andrews-type P tests for C alpha_t = d, and the kappa_A specification test (arXiv:1902.07343v4). The defaults follow the authors' Proposition 99 replication code, so a default fit reproduces the paper's Section 6 tables: interpolated critical values (quantile_method="linear") and intervals that add the signed pre-treatment error quantiles (ci_method="published", returned by effect_ci). The paper's Section 4 definitions are available as quantile_method="empirical", ci_method="inversion" and reference_mode="leave_one_period_out". test_cd.py checks the 51- and 41-unit fits against the authors' frozen output, and Monte Carlo, leave-one-out and kappa_A statistics against their simulation code; the fixtures are in benchmarks/reference/cao_dowd. Co-Authored-By: Claude Opus 5.5 --- benchmarks/reference/cao_dowd/README.md | 41 ++ .../cao_dowd/inference_replication_1.npz | Bin 0 -> 99736 bytes benchmarks/reference/cao_dowd/kappa_rep1.json | 112 ++++ .../loo_reference_stationary_r05_rep1.npy | Bin 0 -> 528 bytes benchmarks/reference/cao_dowd/results_41.json | 363 ++++++++++++ benchmarks/reference/cao_dowd/results_51.json | 374 +++++++++++++ scspill/__init__.py | 15 +- scspill/config_models.py | 1 + scspill/estimators/scspill.py | 40 +- scspill/tests/test_cd.py | 263 +++++++++ scspill/tests/test_config.py | 10 +- scspill/utils/scspill_helpers/cd.py | 520 ++++++++++++++++++ scspill/utils/scspill_helpers/config.py | 7 +- scspill/utils/scspill_helpers/structures.py | 2 +- 14 files changed, 1722 insertions(+), 26 deletions(-) create mode 100644 benchmarks/reference/cao_dowd/README.md create mode 100644 benchmarks/reference/cao_dowd/inference_replication_1.npz create mode 100644 benchmarks/reference/cao_dowd/kappa_rep1.json create mode 100644 benchmarks/reference/cao_dowd/loo_reference_stationary_r05_rep1.npy create mode 100644 benchmarks/reference/cao_dowd/results_41.json create mode 100644 benchmarks/reference/cao_dowd/results_51.json create mode 100644 scspill/tests/test_cd.py create mode 100644 scspill/utils/scspill_helpers/cd.py diff --git a/benchmarks/reference/cao_dowd/README.md b/benchmarks/reference/cao_dowd/README.md new file mode 100644 index 0000000..4b4e231 --- /dev/null +++ b/benchmarks/reference/cao_dowd/README.md @@ -0,0 +1,41 @@ +# Cao–Dowd reference inputs + +Source: [jcao0/synthetic-control-spillover](https://github.com/jcao0/synthetic-control-spillover), +commit `86004020a248c7ee18f3c8a68b1e41db6abc0dda`, for +[Cao and Dowd, arXiv:1902.07343v4](https://arxiv.org/html/1902.07343v4). + +- `scspill/data/files/cao_dowd/cigs_consumption.csv` and `state_list.csv` are + copied from `replication files/application/prop99 spillover 51 units/data/`. + These are the CDC-based public-domain input data: 50 states plus DC, + 1970–2000, with treatment beginning in 1989. They are distinct from the + package's older 39-state, 1988-onward California panel. +- `results_51.json` and `results_41.json` are the authors' frozen application + outputs in the respective `prop99 spillover * units/output/` directories. +- `inference_replication_1.npz` contains the six first-replication outcome + panels generated by the authors' inference DGP and the corresponding + SP-A direct-effect estimates at effect 5 from + `inference/provenance/published_estimates.npz`. The settings are stationary + and integrated factors, each at pure-donor distance $r=0,0.5,1$. +- `loo_reference_stationary_r05_rep1.npy` holds all 50 squared SP-A + leave-one-pre-period-out reference statistics for the stationary, $r=0.5$ + first inference replication. They were independently generated by the + authors' `inference/src/procedures.py::andrews_loo_references` at the same + pinned commit. The slow estimator test compares all 50 statistics. +- `kappa_rep1.json` records the author's range and homogeneous-structure + $\kappa_A$ statistics and all 50 full-sample reference values on the + stationary, $r=0.5$ first inference panel. The statistic was generated with + `kappa_a/src/structures.py` and the fitted SCM equations with + `inference/src/scm.py` at the pinned commit. + +The bundled application CSV SHA-256 checksums are +`2b96994df9f28f7a28948fe765a20efa65270cb36662f44422899bbf98119ddc` +and `8e35c51b30e233bb8059be282d22b7f72637dc2950d7275158f1be26bd4bece8`, +respectively. The frozen output checksums are +`06357ece7e87a6b981e23bbe6338219849259b09303f917544d0f718a5eb55aa` +for 51 units, `0cf41c805905c6c989a66262a721800d674632ec958acc7f835be76f78aaa1f3` +for 41 units. The derived simulation fixture has SHA-256 +`7634351bfb1ea95242b74a89dfe8d10d865f48f72572ddbde50a4944cdec88de`. +The leave-one-out reference vector has SHA-256 +`b07e8cf0ed30fbdbc8d654ac8e2e11819239547cb8ff2c664f7ec4345a1d7e1a`. +The $\kappa_A$ reference JSON has SHA-256 +`267c7b9fd9e09e0c458ffcad78044468e99c36d5c8bec86d21af358ae15f9a45`. diff --git a/benchmarks/reference/cao_dowd/inference_replication_1.npz b/benchmarks/reference/cao_dowd/inference_replication_1.npz new file mode 100644 index 0000000000000000000000000000000000000000..dbd1e5bad092e43b48037e8870b963ee569e63ce GIT binary patch literal 99736 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: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 YB=yR^?6SbtA4=!#oikfg7{d7-0G2M%bN~PV literal 0 HcmV?d00001 diff --git a/benchmarks/reference/cao_dowd/results_41.json b/benchmarks/reference/cao_dowd/results_41.json new file mode 100644 index 0000000..3fc89db --- /dev/null +++ b/benchmarks/reference/cao_dowd/results_41.json @@ -0,0 +1,363 @@ +{ + "spec": 41, + "units": 41, + "k": 4, + "exposed": [ + "CA", + "AZ", + "NV", + "OR" + ], + "scm_pool_units": 39, + "years": [ + 1970, + 1971, + 1972, + 1973, + 1974, + 1975, + 1976, + 1977, + 1978, + 1979, + 1980, + 1981, + 1982, + 1983, + 1984, + 1985, + 1986, + 1987, + 1988, + 1989, + 1990, + 1991, + 1992, + 1993, + 1994, + 1995, + 1996, + 1997, + 1998, + 1999, + 2000 + ], + "T_pre": 19, + "obs": [ + 123.0, + 121.0, + 123.5, + 124.4, + 126.7, + 127.1, + 128.0, + 126.4, + 126.1, + 121.9, + 120.2, + 118.6, + 115.4, + 110.8, + 104.8, + 102.8, + 99.7, + 97.5, + 90.1, + 82.4, + 77.8, + 68.7, + 67.5, + 63.4, + 58.6, + 56.4, + 54.5, + 53.8, + 52.3, + 47.2, + 41.6 + ], + "scm": [ + 121.03559675240484, + 121.9435247894134, + 124.62564982136527, + 124.04070839569889, + 126.29778109217665, + 126.12802575303073, + 128.65051415323472, + 126.31397426102063, + 124.96673051012118, + 122.44839588146868, + 120.9371650160719, + 118.95092013779544, + 116.04728550944611, + 111.7811444065151, + 103.27498774397753, + 102.38763020072791, + 98.95958806608812, + 97.3878117968743, + 91.82256571256887, + 88.18328500618372, + 82.09672831252293, + 76.0301778241935, + 73.57338269601927, + 72.25277628640136, + 69.39971913133152, + 69.4133396221185, + 67.02714436932618, + 66.70761472316771, + 67.67314150905867, + 65.8674057593783, + 58.97908560234633 + ], + "sp": [ + 121.24025201985182, + 121.85164771954157, + 124.34375048250212, + 124.32559952884318, + 126.1149625330545, + 125.9488663972927, + 128.5750054120392, + 126.50253096052398, + 125.10683533268713, + 122.81241832324596, + 120.98739586607343, + 119.26192101749842, + 116.03209277628494, + 110.96481527315402, + 103.45003108534273, + 102.41104629479163, + 98.90710067337824, + 97.2006109945064, + 91.96311730938797, + 83.36690460908169, + 73.61966627261683, + 73.529456566541, + 72.01898304693351, + 71.95298465583045, + 67.53608153102081, + 70.36233068413517, + 68.66252141140399, + 69.73643399235016, + 68.27189945432146, + 67.443906563139, + 58.42401727770659 + ], + "te": [ + -0.9669046090816749, + 4.180333727383178, + -4.829456566541002, + -4.5189830469335135, + -8.552984655830452, + -8.936081531020804, + -13.96233068413518, + -14.162521411403993, + -15.936433992350157, + -15.971899454321463, + -20.243906563138985, + -16.824017277706588 + ], + "te_ci": [ + -4.301451484935192, + 3.4234562426899378 + ], + "te_pvalue": [ + 0.5789473684210527, + 0.05263157894736842, + 0.0, + 0.05263157894736842, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "spillover_test": [ + 1, + 1, + 1, + 1, + 1, + 1, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "lambda_min": 0.5432517687088025, + "lambda_max": 2.3584104656262226, + "cond": 4.341284467847529, + "support_median": 6.0, + "support_max": 11, + "uAZ": [ + 3.3103681336869215, + -6.45325824639265, + 3.3754690968767562, + 4.752151936238064, + 6.201123949806444, + -4.196255414695088, + -5.020251445678099, + -3.659270385555118, + 0.026866500090392265, + 1.7948122473492987, + 4.8009740048636615, + -4.998047750576575, + -2.859002330333297, + -2.0229462425067055, + 2.150631271380945, + -0.45283409812044795, + 2.2082359378272542, + -0.4757340405273567, + 1.5169668762656556, + 8.265425316203078, + 4.78129556781991, + 0.13074069608386005, + -0.05658923251886558, + 0.347340626512099, + 0.938785768581426, + 4.628281856655049, + -1.6890816683903536, + -8.052261396753291, + -7.3654406794747445, + -8.022062225080722, + -2.0070304615289194 + ], + "ciAZ": [ + -7.096274347804453, + 6.19328592671738 + ], + "pAZ": [ + 0.0, + 0.3684210526315789, + 1.0, + 1.0, + 1.0, + 0.8421052631578947, + 0.3684210526315789, + 0.7894736842105263, + 0.0, + 0.0, + 0.0, + 0.7368421052631579 + ], + "uNV": [ + 8.346087256579665, + 2.938534673413301, + -0.5050726679093316, + 2.2714838398662494, + 5.434554301550122, + 7.259931115009579, + -0.47498006819421335, + -5.379828508176388, + -5.1709323951714055, + -1.8430041294749913, + -1.2963666921102117, + -2.027046043799544, + -3.6956423564202225, + 0.40888817048113424, + -14.092264759048703, + -2.0825094973670844, + -1.2560763615432506, + 7.081009407992468, + 4.083234714322913, + 22.4600579714719, + 38.20393610063767, + 17.88399353402303, + 13.21104535400626, + 8.40732800647911, + 10.593387490447382, + -1.2109781484234468, + -3.974768699056019, + -11.628995745580315, + -5.817554476847964, + 0.013252027763283457, + 4.319996095319082 + ], + "ciNV": [ + -13.865471892448491, + 18.001750858109517 + ], + "pNV": [ + 0.0, + 0.0, + 0.05263157894736842, + 0.15789473684210525, + 0.3684210526315789, + 0.2631578947368421, + 0.7894736842105263, + 0.631578947368421, + 0.15789473684210525, + 0.47368421052631576, + 1.0, + 0.5789473684210527 + ], + "uOR": [ + 4.812230565217504, + 2.6924433329947988, + -0.5550691833147141, + -1.0659292174167092, + -0.1762138560278288, + -1.3545985116879535, + -5.472820102695124, + -1.0006508474309328, + 3.4973808213142092, + 3.116760578636274, + 2.8602830301705637, + 2.3339963324607993, + -0.887560780063211, + -6.849005564256743, + -0.1408795123076345, + 2.0495466199495525, + -0.8302669611155977, + -4.877888997242877, + 1.8482422528155524, + 18.362811645086147, + 32.06086366680145, + 31.2613822548075, + 32.379474175997075, + 27.77217698600626, + 24.661771379314796, + 17.57889797879717, + 19.298490892215817, + 7.672237178342751, + 2.4385243959894547, + -1.5638299984691824, + 7.368234118625371 + ], + "ciOR": [ + -11.378110249050598, + 12.068001901431465 + ], + "pOR": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.21052631578947367, + 0.5789473684210527, + 0.5789473684210527, + 0.21052631578947367 + ], + "ca_weights": { + "CT": 0.24098156758036102, + "NV": 0.16377009026101794, + "NE": 0.15511874473566314, + "OR": 0.12074738829099117, + "CO": 0.11994544277848386, + "IL": 0.07894471165396805, + "NC": 0.035914677722353586, + "NH": 0.03293865571177756, + "KS": 0.031129946785812313, + "MT": 0.02050877421432828 + }, + "ca_intercept": -20.032016652525527, + "ca_rmspe_pre": 0.9180912378829023 +} \ No newline at end of file diff --git a/benchmarks/reference/cao_dowd/results_51.json b/benchmarks/reference/cao_dowd/results_51.json new file mode 100644 index 0000000..9a0c298 --- /dev/null +++ b/benchmarks/reference/cao_dowd/results_51.json @@ -0,0 +1,374 @@ +{ + "spec": 51, + "units": 51, + "k": 14, + "exposed": [ + "CA", + "AK", + "AZ", + "DC", + "FL", + "HI", + "MA", + "MD", + "MI", + "NJ", + "NV", + "NY", + "OR", + "WA" + ], + "scm_pool_units": 39, + "years": [ + 1970, + 1971, + 1972, + 1973, + 1974, + 1975, + 1976, + 1977, + 1978, + 1979, + 1980, + 1981, + 1982, + 1983, + 1984, + 1985, + 1986, + 1987, + 1988, + 1989, + 1990, + 1991, + 1992, + 1993, + 1994, + 1995, + 1996, + 1997, + 1998, + 1999, + 2000 + ], + "T_pre": 19, + "obs": [ + 123.0, + 121.0, + 123.5, + 124.4, + 126.7, + 127.1, + 128.0, + 126.4, + 126.1, + 121.9, + 120.2, + 118.6, + 115.4, + 110.8, + 104.8, + 102.8, + 99.7, + 97.5, + 90.1, + 82.4, + 77.8, + 68.7, + 67.5, + 63.4, + 58.6, + 56.4, + 54.5, + 53.8, + 52.3, + 47.2, + 41.6 + ], + "scm": [ + 121.03559675240484, + 121.9435247894134, + 124.62564982136527, + 124.04070839569889, + 126.29778109217665, + 126.12802575303073, + 128.65051415323472, + 126.31397426102063, + 124.96673051012118, + 122.44839588146868, + 120.9371650160719, + 118.95092013779544, + 116.04728550944611, + 111.7811444065151, + 103.27498774397753, + 102.38763020072791, + 98.95958806608812, + 97.3878117968743, + 91.82256571256887, + 88.18328500618372, + 82.09672831252293, + 76.0301778241935, + 73.57338269601927, + 72.25277628640136, + 69.39971913133152, + 69.4133396221185, + 67.02714436932618, + 66.70761472316771, + 67.67314150905867, + 65.8674057593783, + 58.97908560234633 + ], + "sp": [ + 121.92477664581043, + 121.43813025084717, + 124.58294958790344, + 123.86259716809457, + 126.40515656113212, + 126.87895357348475, + 128.1980541000673, + 126.58778195224929, + 125.37729644994093, + 121.865364636675, + 120.56657160036374, + 118.8518894769351, + 115.80284471431872, + 110.89873511012036, + 103.73309752689457, + 103.51188423529727, + 99.84228459368529, + 96.82189439469875, + 90.84973742148075, + 82.31539627769534, + 74.08353058672053, + 72.45593330017549, + 70.9246531593901, + 71.01264847055259, + 69.51252951174925, + 69.23257831473674, + 67.58224452878808, + 68.71214038203074, + 68.37973974258463, + 66.15802036663672, + 57.08840913445824 + ], + "te": [ + 0.08460372230465855, + 3.716469413279463, + -3.755933300175487, + -3.4246531593901097, + -7.61264847055259, + -10.912529511749254, + -12.832578314736738, + -13.082244528788076, + -14.912140382030742, + -16.07973974258463, + -18.95802036663671, + -15.488409134458237 + ], + "te_ci": [ + -3.9593189900494346, + 3.1873882165643317 + ], + "te_pvalue": [ + 1.0, + 0.05263157894736842, + 0.05263157894736842, + 0.05263157894736842, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "spillover_test": [ + 0, + 1, + 1, + 1, + 0, + 1, + 1, + 1, + 1, + 1, + 1, + 1 + ], + "lambda_min": 0.221540129924041, + "lambda_max": 2.7658216904010406, + "cond": 12.484517777205204, + "support_median": 7.0, + "support_max": 14, + "uAZ": [ + 2.292997202226587, + -5.198484845445833, + 3.9976029636314223, + 0.8341107181678211, + 3.7519889306348375, + -3.8650601614093887, + -2.102713206885653, + -1.7527470889570793, + 2.7847254162654167, + 2.269796253899287, + 2.8731008704518217, + -3.9967741350591552, + -3.220927624281856, + -0.6132337181997656, + -0.389195846825217, + 0.2765672056979298, + 1.2433705851905046, + -0.4193289878144384, + 1.2342054687125454, + 4.990980276234831, + -11.241447345143545, + -15.365465721044684, + -16.54884994458616, + -15.136040055574123, + -16.051291848670065, + -2.958225438679426, + -7.195445817822032, + -10.47803389531696, + -9.691153778055874, + -10.433079588322817, + -7.474339703561894 + ], + "ciAZ": [ + -4.986875678947999, + 6.720650965301914 + ], + "pAZ": [ + 0.15789473684210525, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.42105263157894735, + 0.05263157894736842, + 0.0, + 0.0, + 0.0, + 0.05263157894736842 + ], + "uNV": [ + 5.632028894647021, + -0.6453364058157831, + -4.217962477787239, + -1.8724447311115568, + 3.5837051645347913, + 5.953151968555034, + 0.44528016036582585, + -1.6595125472648533, + -1.0221319703341578, + 1.4132613373523952, + 1.3516843609417322, + 0.1970844535527192, + -2.3333942815142734, + 1.6259094940288321, + -14.180041948890334, + -2.5905159633315122, + -1.9606618744867887, + 6.643019081300224, + 3.636877285257583, + 14.967365232650836, + 26.867691299812964, + 3.831292134152011, + -1.6090392380305385, + -5.11965642898089, + 2.670576730965645, + -9.684297383683875, + -12.39608608581913, + -13.869418503679515, + -8.657621299393275, + -1.4649268144588146, + -1.8933719030512286 + ], + "ciNV": [ + -12.442938604918666, + 12.685665747299975 + ], + "pNV": [ + 0.0, + 0.0, + 0.5263157894736842, + 0.7894736842105263, + 0.47368421052631576, + 0.5789473684210527, + 0.21052631578947367, + 0.15789473684210525, + 0.0, + 0.21052631578947367, + 0.8421052631578947, + 0.7368421052631579 + ], + "uOR": [ + 4.7468854507849585, + 2.4784980163187242, + -0.9839245054496928, + -1.5741195984281546, + -0.016740424443554502, + -1.8488602257302773, + -5.136193219851776, + 0.006410872304485338, + 4.387384210938649, + 3.112182121412502, + 3.0466562347176875, + 1.8804887465886395, + -1.4648783668055785, + -6.651272353711562, + -0.4951175607804714, + 1.8427428596517927, + -1.1568684088146028, + -4.129676005556291, + 1.9564021568544234, + 13.90300976863315, + 26.222668606786367, + 23.45568383956229, + 23.332388795383388, + 19.7609324074306, + 19.42945173942901, + 11.959813554933358, + 14.469738836173747, + 6.0048931174590425, + 0.9922225086838468, + -2.5216571468559836, + 4.710534443942803 + ], + "ciOR": [ + -8.565954982928405, + 8.960787151832687 + ], + "pOR": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.21052631578947367, + 0.7894736842105263, + 0.5263157894736842, + 0.2631578947368421 + ], + "ca_weights": { + "OR": 0.27550305237933004, + "MA": 0.20628963656673088, + "AZ": 0.14803408405414048, + "AK": 0.10078165512882611, + "NV": 0.06899440483918341, + "CT": 0.06131172516757177, + "MN": 0.03565451939125902, + "HI": 0.0345533459713232, + "KS": 0.03322940251049153, + "NH": 0.0305515791416624, + "DC": 0.005096594615076314 + }, + "ca_intercept": -16.163852521865493, + "ca_rmspe_pre": 0.589402935565328 +} \ No newline at end of file diff --git a/scspill/__init__.py b/scspill/__init__.py index 01a93e1..46df9c5 100644 --- a/scspill/__init__.py +++ b/scspill/__init__.py @@ -6,7 +6,7 @@ by every control unit. The package is organized around a model layer, selected by ``method``. -Exactly one model ships today: +Two models are implemented: ``"sar"`` The Bayesian spatial-autoregressive model of Sakaguchi & Tagawa (2026), @@ -19,15 +19,20 @@ block (latent AR(1) factors, covariates, and a random-walk Metropolis step for ``rho``) conditional on the posterior mean of ``alpha``. +``"cd"`` + Cao and Dowd's intercept-adjusted, all-unit SCM with a researcher-supplied + spillover structure. It reports period-specific direct and spillover + effects, Andrews P tests, confidence intervals, and specification tests. + Further spillover-aware models are planned behind the same interface; the catalogue at https://quarcs-lab.github.io/scspill/models/ tracks them and states plainly which are implemented. Public API:: - from scspill import SCSPILL, SCSPILLConfig + from scspill import SCSPILL, SCSPILLConfig, CDConfig - result = SCSPILL(config).fit() # -> SCSPILLResults (method="sar") + result = SCSPILL(config).fit() # -> SCSPILLResults or CDResults plus the companion subpackages :mod:`scspill.validation` (the ``sar`` model's Geweke joint distribution test, prior sensitivity, and prior predictive @@ -53,6 +58,8 @@ __all__ = [ "SCSPILL", + "CDConfig", + "CDResults", "SCSPILLConfig", "SCSPILLResults", "ScspillConfigError", @@ -69,6 +76,8 @@ "SCSPILL": ("scspill.estimators.scspill", "SCSPILL"), "SCSPILLConfig": ("scspill.utils.scspill_helpers.config", "SCSPILLConfig"), "SCSPILLResults": ("scspill.utils.scspill_helpers.structures", "SCSPILLResults"), + "CDConfig": ("scspill.utils.scspill_helpers.cd", "CDConfig"), + "CDResults": ("scspill.utils.scspill_helpers.cd", "CDResults"), } diff --git a/scspill/config_models.py b/scspill/config_models.py index e24084f..9e9c42f 100644 --- a/scspill/config_models.py +++ b/scspill/config_models.py @@ -582,6 +582,7 @@ def plot(self, kind: str = "auto", *, ax: Any = None, **overrides: Any) -> Any: # --------------------------------------------------------------------------- _RELOCATED_CONFIGS = { "SCSPILLConfig": "scspill.utils.scspill_helpers.config", + "CDConfig": "scspill.utils.scspill_helpers.cd", } diff --git a/scspill/estimators/scspill.py b/scspill/estimators/scspill.py index 6d88a1a..af04c13 100644 --- a/scspill/estimators/scspill.py +++ b/scspill/estimators/scspill.py @@ -1,6 +1,6 @@ -"""The ``sar`` model: Bayesian spatial-autoregressive spillover SCM. +"""Dispatch spillover SCM models selected by ``method``. -scspill's first model -- today its only one -- implementing: +The default ``sar`` model implements: Sakaguchi, S., & Tagawa, H. (2026). "Identification and Bayesian Inference for Synthetic Control Methods with Spillover Effects." The @@ -34,6 +34,7 @@ ) from ..utils.datautils import balance from ..utils.results_helpers import build_effect_submodels, make_weights_results +from ..utils.scspill_helpers.cd import CDConfig, CDResults, fit_cd from ..utils.scspill_helpers.config import SCSPILLConfig from ..utils.scspill_helpers.sar.inference import build_inference, prediction_interval_spec from ..utils.scspill_helpers.sar.pipeline import run_scspill @@ -42,17 +43,18 @@ class SCSPILL: - """Spillover-aware synthetic control estimator (model ``sar``). + """Spillover-aware synthetic control estimator (models ``sar`` and ``cd``). - Fits the Bayesian spatial-autoregressive spillover model of Sakaguchi & - Tagawa (2026) -- scspill's first and, today, only model. Select it with - ``method="sar"``; that is the default, so existing code needs no change. + The default ``sar`` fit is the Bayesian spatial-autoregressive model of + Sakaguchi and Tagawa (2026). ``method="cd"`` fits the Cao--Dowd (2019, + v4) all-unit SCM and end-of-sample inference. Parameters ---------- - config : SCSPILLConfig or dict + config : SCSPILLConfig, CDConfig, or dict The estimation configuration; a dict is coerced into - :class:`scspill.config_models.SCSPILLConfig`. + :class:`scspill.config_models.SCSPILLConfig` or ``CDConfig`` according + to ``method``. Examples -------- @@ -70,14 +72,20 @@ class SCSPILL: ``` """ - def __init__(self, config: SCSPILLConfig | dict) -> None: + def __init__(self, config: SCSPILLConfig | CDConfig | dict) -> None: if isinstance(config, dict): try: - config = SCSPILLConfig(**config) + config = ( + CDConfig(**config) + if config.get("method", "sar") == "cd" + else SCSPILLConfig(**config) + ) except ValidationError as exc: raise ScspillConfigError(f"Invalid SCSPILL configuration: {exc}") from exc - if not isinstance(config, SCSPILLConfig): - raise ScspillConfigError("config must be a SCSPILLConfig or a dict of its fields.") + if not isinstance(config, (SCSPILLConfig, CDConfig)): + raise ScspillConfigError( + "config must be a SCSPILLConfig, CDConfig, or a dict of fields." + ) self.config = config # Convenience attribute unpacking (mlsynth convention). self.df = config.df @@ -88,7 +96,7 @@ def __init__(self, config: SCSPILLConfig | dict) -> None: self.method = config.method self.display_graphs = config.display_graphs - def fit(self) -> SCSPILLResults: + def fit(self) -> SCSPILLResults | CDResults: """Estimate the model and return standardized results. Runs the balance check, panel preparation, the two-step sampler, the @@ -109,6 +117,12 @@ def fit(self) -> SCSPILLResults: """ cfg = self.config + if isinstance(cfg, CDConfig): + cd_result = fit_cd(cfg) + if cfg.display_graphs: + cd_result.plot(kind="auto") + return cd_result + try: balance(cfg.df, cfg.unitid, cfg.time) inputs = prepare_scspill_inputs( diff --git a/scspill/tests/test_cd.py b/scspill/tests/test_cd.py new file mode 100644 index 0000000..4c64c82 --- /dev/null +++ b/scspill/tests/test_cd.py @@ -0,0 +1,263 @@ +"""Cao--Dowd estimator, inference, and Proposition 99 replication checks.""" + +import json +from pathlib import Path + +import numpy as np +import pandas as pd +import pytest + +from scspill import SCSPILL, CDConfig, CDResults, ScspillConfigError, ScspillEstimationError +from scspill.data import load_california_cd +from scspill.utils.scspill_helpers.cd import _scm_all + +REFERENCE = Path(__file__).resolve().parents[2] / "benchmarks/reference/cao_dowd" + + +def test_scm_is_optimal_with_tiny_maxiter_and_falls_back_to_fista(monkeypatch): + rng = np.random.default_rng(17) + y = rng.normal(size=(4, 12)) + y[0] = 0.85 * y[1] + 0.15 * y[2] + 0.02 * rng.normal(size=12) + short_intercepts, short_weights = _scm_all(y, maxiter=1, ftol=1e-12) + regular_intercepts, regular_weights = _scm_all(y, maxiter=900, ftol=1e-12) + short_loss = np.sum((y[0] - short_intercepts[0] - short_weights[0] @ y) ** 2) + regular_loss = np.sum((y[0] - regular_intercepts[0] - regular_weights[0] @ y) ** 2) + assert short_loss <= regular_loss + 1e-5 + + # A failed SLSQP falls back to projected gradient with the same optimum. + from scipy.optimize import OptimizeResult + + def failing(fun, x0, **kwargs): + return OptimizeResult(x=np.full_like(x0, np.nan), success=False) + + monkeypatch.setattr("scspill.utils.scspill_helpers.cd.minimize", failing) + fb_intercepts, fb_weights = _scm_all(y, maxiter=900, ftol=1e-12) + fb_loss = np.sum((y[0] - fb_intercepts[0] - fb_weights[0] @ y) ** 2) + assert fb_loss <= regular_loss + 1e-4 + assert fb_weights[0].sum() == pytest.approx(1) + + +@pytest.mark.parametrize("spec", [51, 41]) +def test_application_against_authors_frozen_output(spec): + panel = load_california_cd(spec) + expected = json.loads((REFERENCE / f"results_{spec}.json").read_text()) + result = SCSPILL({**panel.config_kwargs(), "display_graphs": False}).fit() + assert isinstance(result, CDResults) + assert len(result.unit_labels) == spec + assert result.t_pre == 19 + assert result.rank == expected["k"] + assert result.condition_number == pytest.approx(expected["cond"], abs=0.03) + np.testing.assert_allclose(result.effects_panel["CA"], expected["te"], atol=0.03) + np.testing.assert_allclose(result.counterfactual, expected["sp"], atol=0.03) + np.testing.assert_allclose( + result.published_ci["CA"].iloc[0].values - result.effects_panel["CA"].iloc[0], + expected["te_ci"], + atol=0.02, + ) + np.testing.assert_allclose(result.pointwise_p_values["CA"], expected["te_pvalue"]) + for state in ("AZ", "NV", "OR"): + np.testing.assert_allclose( + result.effects_panel[state], expected["u" + state][19:], atol=0.03 + ) + np.testing.assert_allclose( + result.pre_residuals[state], expected["u" + state][:19], atol=0.03 + ) + np.testing.assert_allclose(result.pointwise_p_values[state], expected["p" + state]) + + +def test_defaults_follow_the_authors_application_code(): + """Default critical values and intervals reproduce Figure 4 and Table S.6.""" + panel = load_california_cd(51) + result = SCSPILL({**panel.config_kwargs(), "display_graphs": False}).fit() + assert (result.quantile_method, result.ci_method) == ("linear", "published") + assert result.effect_ci is result.published_ci + spill = np.eye(len(result.unit_labels))[1:] + years = result.effects_panel.index + linear = [t for t in years if result.test_hypothesis(spill, period=t).reject] + paper = [ + t + for t in years + if result.test_hypothesis(spill, period=t, quantile_method="empirical").reject + ] + assert linear == [1990, 1991, 1992, *range(1994, 2001)] + assert 1994 not in paper and 1995 not in paper + # Prediction band is the published CI mapped to the counterfactual scale. + band = result.time_series.counterfactual_lower + observed = result.time_series.observed_outcome + ci = result.published_ci["CA"] + np.testing.assert_allclose(band[19:], observed[19:] - ci["upper"].to_numpy()) + + inverted = SCSPILL( + {**panel.config_kwargs(), "ci_method": "inversion", "display_graphs": False} + ).fit() + assert inverted.effect_ci is inverted.pointwise_ci + width = inverted.pointwise_ci["CA"].eval("upper - lower") + assert np.allclose(width, width.iloc[0]) # symmetric, constant half-width + + +def test_general_hypothesis_and_distinct_intervals(): + panel = load_california_cd(41) + result = SCSPILL({**panel.config_kwargs(), "display_graphs": False}).fit() + c = pd.DataFrame(0.0, index=["CA+NV"], columns=result.unit_labels) + c.loc["CA+NV", ["CA", "NV"]] = 1 + test = result.test_hypothesis(c, period=1990, d=0) + assert test.reference_statistics.shape == (19,) + assert 0 <= test.p_value <= 1 + assert test.statistic >= 0 + assert result.pointwise_ci["CA"].loc[1990, "lower"] != pytest.approx( + result.published_ci["CA"].loc[1990, "lower"] + ) + assert result.att_ci is None # a pointwise interval is not an ATT interval + assert result.time_series.has_prediction_interval + assert len(result.kappa_test) == 12 + + +def test_labeled_effect_matrix_and_rank_failure(): + panel = load_california_cd(41) + labels = list(pd.unique(panel.df["state"])) + a = pd.DataFrame(0.0, index=labels[::-1], columns=["direct", "shared"]) + a.loc["CA", "direct"] = 1 + a.loc[["AZ", "NV", "OR"], "shared"] = 1 + config = CDConfig( + **{ + **panel.config_kwargs(), + "affected_units": None, + "effect_matrix": a, + "display_graphs": False, + } + ) + result = SCSPILL(config).fit() + assert result.effect_matrix.loc["CA", "direct"] == 1 + assert result.effects_panel.loc[1990, "AZ"] == pytest.approx( + result.effects_panel.loc[1990, "NV"] + ) + bad = pd.DataFrame(np.eye(len(labels)), index=labels, columns=labels) + with pytest.raises(ScspillEstimationError, match="unidentified"): + SCSPILL( + { + **panel.config_kwargs(), + "affected_units": None, + "effect_matrix": bad, + "display_graphs": False, + } + ).fit() + + +def test_leave_one_period_out_and_input_validation(): + panel = load_california_cd(41) + small = panel.df.loc[ + panel.df["state"].isin(["CA", "AZ", "NV", "OR", "CO"]) + & panel.df["year"].between(1970, 1977) + ].copy() + small.loc[small["state"] == "CA", "treated"] = ( + small.loc[small["state"] == "CA", "year"] >= 1976 + ).astype(int) + cfg = { + **panel.config_kwargs(), + "df": small, + "affected_units": ["AZ", "NV"], + "display_graphs": False, + } + full = SCSPILL(cfg).fit() + loo = SCSPILL({**cfg, "reference_mode": "leave_one_period_out"}).fit() + assert not np.allclose(full.placebo_effects, loo.placebo_effects) + assert loo.kappa_test[1976].reference_statistics.size == 6 + with pytest.raises(ScspillConfigError): + SCSPILL({**cfg, "affected_units": ["unknown"]}).fit() + with pytest.raises(ScspillConfigError): + loo.test_hypothesis(np.ones((1, 2))) + with pytest.raises(ScspillConfigError): + loo.test_hypothesis(np.empty((0, len(loo.unit_labels)))) + with pytest.raises(ScspillConfigError): + loo.test_hypothesis(np.eye(len(loo.unit_labels))[0], d=[0, 1]) + + +def test_monte_carlo_first_replication_against_published_estimates(): + """Check all six Section S.3.2 settings against author DGP draws and estimates.""" + with np.load(REFERENCE / "inference_replication_1.npz") as fixture: + panels = fixture["panels"] + expected = fixture["sp_estimates"] + labels = [f"U{i:02d}" for i in range(51)] + for panel, expected_estimate in zip(panels, expected, strict=True): + df = pd.DataFrame(panel, index=labels, columns=range(51)).stack().rename("y").reset_index() + df.columns = ["unit", "time", "y"] + df["treated"] = ((df["unit"] == "U00") & (df["time"] == 50)).astype(int) + result = SCSPILL( + { + "method": "cd", + "df": df, + "outcome": "y", + "treat": "treated", + "unitid": "unit", + "time": "time", + "affected_units": labels[1:14], + "display_graphs": False, + } + ).fit() + assert result.effects_panel.iloc[0, 0] == pytest.approx(expected_estimate, abs=0.001) + + +def test_kappa_against_author_reference(): + """Compare range and homogeneous structure statistics with author code.""" + with np.load(REFERENCE / "inference_replication_1.npz") as fixture: + panel = fixture["panels"][1] + expected = json.loads((REFERENCE / "kappa_rep1.json").read_text()) + labels = [f"U{i:02d}" for i in range(51)] + df = pd.DataFrame(panel, index=labels, columns=range(51)).stack().rename("y").reset_index() + df.columns = ["unit", "time", "y"] + df["treated"] = ((df["unit"] == "U00") & (df["time"] == 50)).astype(int) + base = { + "method": "cd", + "df": df, + "outcome": "y", + "treat": "treated", + "unitid": "unit", + "time": "time", + "display_graphs": False, + } + range_fit = SCSPILL({**base, "affected_units": labels[1:14]}).fit() + homogeneous = pd.DataFrame(0.0, index=labels, columns=["direct", "shared"]) + homogeneous.loc["U00", "direct"] = 1 + homogeneous.loc[labels[1:14], "shared"] = 1 + homogeneous_fit = SCSPILL({**base, "effect_matrix": homogeneous}).fit() + for key, result in (("range", range_fit), ("homogeneous", homogeneous_fit)): + assert result.kappa_test[50].statistic == pytest.approx( + expected[key]["statistic"], abs=0.001 + ) + np.testing.assert_allclose( + result.kappa_test[50].reference_statistics, + expected[key]["reference"], + atol=0.001, + rtol=0, + ) + + +@pytest.mark.slow +def test_inference_leave_one_out_against_author_reference(): + """Check all 50 squared SP-A placebo statistics, not just the effect.""" + with np.load(REFERENCE / "inference_replication_1.npz") as fixture: + panel = fixture["panels"][1] # stationary factors, r=0.5, replication 1 + author = np.load(REFERENCE / "loo_reference_stationary_r05_rep1.npy") + labels = [f"U{i:02d}" for i in range(51)] + df = pd.DataFrame(panel, index=labels, columns=range(51)).stack().rename("y").reset_index() + df.columns = ["unit", "time", "y"] + df["treated"] = ((df["unit"] == "U00") & (df["time"] == 50)).astype(int) + result = SCSPILL( + { + "method": "cd", + "df": df, + "outcome": "y", + "treat": "treated", + "unitid": "unit", + "time": "time", + "affected_units": labels[1:14], + "reference_mode": "leave_one_period_out", + "display_graphs": False, + } + ).fit() + observed = result.placebo_effects["U00"].to_numpy() ** 2 + np.testing.assert_allclose(observed, author, atol=0.001, rtol=0) + # The authors' Monte Carlo code uses the paper's left-inverse critical value. + test = result.test_hypothesis(np.eye(51)[0], period=50, quantile_method="empirical") + assert test.critical_value == pytest.approx(np.sort(author)[47], abs=0.001) + assert test.p_value == float(np.mean(author >= test.statistic)) diff --git a/scspill/tests/test_config.py b/scspill/tests/test_config.py index ead48fe..e7dc93f 100644 --- a/scspill/tests/test_config.py +++ b/scspill/tests/test_config.py @@ -123,7 +123,7 @@ def test_r_argument_mapping_documented(): def test_method_defaults_to_sar(panel): - """Omitting `method` must keep selecting the only implemented model.""" + """Omitting `method` must keep selecting the SAR model.""" assert _cfg(panel).method == "sar" @@ -131,8 +131,8 @@ def test_method_accepts_sar_explicitly(panel): assert _cfg(panel, method="sar").method == "sar" -def test_planned_methods_are_rejected_not_silently_accepted(panel): - """The roadmap names must not validate: they are not implemented.""" - for planned in ("cd", "iscm", "grossi"): +def test_non_sar_methods_are_rejected_by_sar_config(panel): + """Model-specific fields must not validate against the SAR config.""" + for method in ("cd", "iscm", "grossi"): with pytest.raises(ValidationError): - _cfg(panel, method=planned) + _cfg(panel, method=method) diff --git a/scspill/utils/scspill_helpers/cd.py b/scspill/utils/scspill_helpers/cd.py new file mode 100644 index 0000000..dc319a6 --- /dev/null +++ b/scspill/utils/scspill_helpers/cd.py @@ -0,0 +1,520 @@ +"""Cao--Dowd synthetic control with spillovers and end-of-sample inference. + +The defaults follow the authors' Proposition 99 replication code at commit +86004020 (Cao and Dowd, arXiv:1902.07343v4, Section 6), which is what the +paper's empirical tables report: + +* critical values use the linearly interpolated sample quantile + (``quantile_method="linear"``), where the paper's Section 4 defines the + left inverse of the empirical CDF (``"empirical"``); +* pointwise intervals add the signed pre-treatment projected-error quantiles + to the estimate (``ci_method="published"``), where inverting the squared + P test gives a symmetric interval (``"inversion"``). + +Both intervals are always stored; ``effect_ci`` is the one selected. +""" + +from __future__ import annotations + +from typing import Any, Literal + +import numpy as np +import pandas as pd +from pydantic import BaseModel, ConfigDict, Field +from scipy.optimize import minimize + +from ...config_models import ( + BaseEstimatorConfig, + BaseEstimatorResults, + InferenceResults, + MethodDetailsResults, +) +from ...exceptions import ScspillConfigError, ScspillDataError, ScspillEstimationError +from ..results_helpers import build_effect_submodels, make_weights_results + + +class CDConfig(BaseEstimatorConfig): + """Cao--Dowd configuration for one treated unit and a fixed spillover map. + + ``affected_units`` creates one effect parameter per named unit, including + the treated unit automatically. Alternatively, ``effect_matrix`` is a + DataFrame with one row per unit and one column per effect parameter; it + must contain a direct-effect column supported only on the treated unit. + The latter permits homogeneous or otherwise structured spillovers. + """ + + method: Literal["cd"] = "cd" + affected_units: list[Any] | None = None + effect_matrix: pd.DataFrame | None = None + reference_mode: Literal["full", "leave_one_period_out"] = "full" + quantile_method: Literal["linear", "empirical"] = Field( + default="linear", + description=( + "Critical-value rule for every P and kappa test. 'linear' is the " + "interpolated sample quantile of the authors' application code; " + "'empirical' is the paper's left inverse of the empirical CDF, also " + "used in the authors' Monte Carlo studies." + ), + ) + ci_method: Literal["published", "inversion"] = Field( + default="published", + description=( + "Interval reported as effect_ci. 'published' adds signed placebo " + "quantiles to the estimate (the paper's tables); 'inversion' inverts " + "the squared P test and is symmetric." + ), + ) + ci: float = Field(default=0.95, gt=0, lt=1) + scm_maxiter: int = Field(default=900, ge=1) + scm_ftol: float = Field(default=1e-12, gt=0) + + +def _empirical_quantile(values: np.ndarray, probability: float) -> float: + """Left inverse of the empirical CDF, as defined in v4 Section 4.""" + x = np.sort(np.asarray(values, dtype=float).reshape(-1)) + return float(x[max(0, int(np.ceil(probability * len(x))) - 1)]) + + +def _critical_value(values: np.ndarray, probability: float, method: str) -> float: + """Critical value under ``quantile_method`` (see :class:`CDConfig`).""" + if method == "empirical": + return _empirical_quantile(values, probability) + if method == "linear": + return float(np.quantile(values, probability)) + raise ScspillConfigError(f"Unknown quantile_method {method!r}.") + + +class CDTestResult(BaseModel): + """A P test with its pre-intervention reference distribution.""" + + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") + statistic: float + critical_value: float + p_value: float + reject: bool + reference_statistics: np.ndarray + + +class CDResults(BaseEstimatorResults): + """Cao--Dowd fit, including effects, SCM equations, and P-test results.""" + + model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") + method: Literal["cd"] = "cd" + treated_label: Any + unit_labels: tuple[Any, ...] + time_labels: np.ndarray + t_pre: int + intercepts: pd.Series + scm_matrix: pd.DataFrame + effect_matrix: pd.DataFrame + effects_panel: pd.DataFrame + spillover_panel: pd.DataFrame + pre_residuals: pd.DataFrame + placebo_effects: pd.DataFrame + pointwise_p_values: pd.DataFrame + pointwise_ci: dict[Any, pd.DataFrame] + published_ci: dict[Any, pd.DataFrame] + kappa_test: dict[Any, CDTestResult] + rank: int + condition_number: float + reference_mode: str + quantile_method: Literal["linear", "empirical"] + ci_method: Literal["published", "inversion"] + ci_level: float + + @property + def effect_ci(self) -> dict[Any, pd.DataFrame]: + """Per-unit pointwise intervals selected by ``ci_method``.""" + return self.published_ci if self.ci_method == "published" else self.pointwise_ci + + def _critical(self, refs: np.ndarray, quantile_method: str | None) -> float: + return _critical_value(refs, self.ci_level, quantile_method or self.quantile_method) + + def _parse_c_d( + self, C: np.ndarray | pd.DataFrame, d: np.ndarray | float | None + ) -> tuple[np.ndarray, np.ndarray]: + """Validate the contrast matrix ``C`` and null vector ``d``.""" + if isinstance(C, pd.DataFrame): + if set(C.columns) != set(self.unit_labels) or C.columns.has_duplicates: + raise ScspillConfigError("C columns must match the unit labels exactly.") + c = C.loc[:, list(self.unit_labels)].to_numpy(dtype=float) + else: + c = np.atleast_2d(np.asarray(C, dtype=float)) + if ( + c.ndim != 2 + or c.shape[0] == 0 + or c.shape[1] != len(self.unit_labels) + or not np.isfinite(c).all() + ): + raise ScspillConfigError("C must be a finite matrix with one column per unit.") + q = c.shape[0] + try: + null = np.zeros(q) if d is None else np.broadcast_to(np.asarray(d, dtype=float), (q,)) + except ValueError as exc: + raise ScspillConfigError("d must have one entry per row of C.") from exc + if not np.isfinite(null).all(): + raise ScspillConfigError("d must be finite.") + return c, null + + def test_hypothesis( + self, + C: np.ndarray | pd.DataFrame, + *, + d: np.ndarray | float | None = None, + period: Any | None = None, + weight: np.ndarray | Literal["inverse_covariance"] | None = None, + quantile_method: Literal["empirical", "linear"] | None = None, + ) -> CDTestResult: + """Test ``C @ effect_t = d`` by the pre-intervention P distribution. + + ``C`` has one column per unit in ``unit_labels`` order, or labeled + columns when passed as a DataFrame. ``period`` is a post-period label. + The default statistic is the squared Euclidean norm; an explicit + positive-definite weight or the inverse placebo second moment is allowed. + ``quantile_method`` overrides the fit's critical-value rule. + """ + if period is None: + period = self.effects_panel.index[0] + if period not in self.effects_panel.index: + raise ScspillConfigError(f"Unknown post-treatment period {period!r}.") + c, null = self._parse_c_d(C, d) + q = c.shape[0] + pre = c @ self.placebo_effects.to_numpy(dtype=float).T + if weight is None: + w = np.eye(q) + elif isinstance(weight, str): + if weight != "inverse_covariance" or pre.shape[1] <= q: + raise ScspillConfigError( + "Inverse covariance requires more reference periods than rows of C." + ) + second_moment = pre @ pre.T / pre.shape[1] + try: + w = np.linalg.inv(np.atleast_2d(second_moment)) + except np.linalg.LinAlgError as exc: + raise ScspillConfigError("Projected placebo second moment is singular.") from exc + else: + w = np.asarray(weight, dtype=float) + if w.shape != (q, q) or not np.isfinite(w).all() or not np.allclose(w, w.T): + raise ScspillConfigError("weight must be a finite symmetric square matrix.") + if np.linalg.eigvalsh(w).min() <= 0: + raise ScspillConfigError("weight must be positive definite.") + observed = c @ self.effects_panel.loc[period].to_numpy(dtype=float) - null + statistic = float(observed @ w @ observed) + refs = np.einsum("it,ij,jt->t", pre, w, pre) + critical = self._critical(refs, quantile_method) + return CDTestResult( + statistic=statistic, + critical_value=critical, + p_value=float(np.mean(refs >= statistic)), + reject=bool(statistic > critical), + reference_statistics=refs, + ) + + +def _prepare( + config: CDConfig, +) -> tuple[np.ndarray, tuple[Any, ...], np.ndarray, int, Any, pd.DataFrame]: + """Validate and arrange the balanced panel as units by periods.""" + df = config.df + if df[[config.unitid, config.time]].isna().any().any(): + raise ScspillDataError("Unit and time labels cannot be missing.") + if df.duplicated([config.unitid, config.time]).any(): + raise ScspillDataError("Panel has duplicate unit-time observations.") + if df[[config.outcome, config.treat]].isna().any().any(): + raise ScspillDataError("Outcomes and treatment flags must be complete.") + units = list(pd.unique(df[config.unitid])) + times = np.asarray(sorted(pd.unique(df[config.time]))) + y = df.pivot(index=config.unitid, columns=config.time, values=config.outcome).reindex( + index=units, columns=times + ) + treat = df.pivot(index=config.unitid, columns=config.time, values=config.treat).reindex( + index=units, columns=times + ) + if y.isna().any().any() or treat.isna().any().any(): + raise ScspillDataError("Cao--Dowd requires a balanced panel.") + if not np.isfinite(y.to_numpy(dtype=float)).all(): + raise ScspillDataError("Outcomes must be finite.") + flag = treat.to_numpy(dtype=float) + if not np.isin(flag, [0, 1]).all(): + raise ScspillDataError("Treatment flags must be binary.") + treated = np.flatnonzero(flag.sum(axis=1)) + if len(treated) != 1: + raise ScspillDataError("Exactly one treated unit is required.") + idx = int(treated[0]) + t0 = int(np.argmax(flag[idx] == 1)) + if t0 < 3 or t0 >= len(times) or not np.all(flag[idx, t0:] == 1): + raise ScspillDataError("Treatment must begin after at least three pre periods and persist.") + label = units.pop(idx) + units.insert(0, label) + yy = y.loc[units].to_numpy(dtype=float) + if len(units) < 3: + raise ScspillDataError("At least three units are required for all-unit SCM.") + if config.effect_matrix is not None and config.affected_units is not None: + raise ScspillConfigError("Choose effect_matrix or affected_units, not both.") + if config.effect_matrix is not None: + a = config.effect_matrix + if a.index.has_duplicates or a.columns.has_duplicates or set(a.index) != set(units): + raise ScspillConfigError("effect_matrix needs unique rows matching every unit label.") + a = a.loc[units].astype(float) + if not np.isfinite(a.to_numpy()).all() or a.shape[1] == 0: + raise ScspillConfigError("effect_matrix must be finite and nonempty.") + direct = np.zeros(len(units)) + direct[0] = 1 + if not np.any(np.all(np.isclose(a.to_numpy().T, direct), axis=1)): + raise ScspillConfigError( + "effect_matrix must contain a direct-effect column for the treated unit." + ) + else: + affected = [] if config.affected_units is None else list(config.affected_units) + if len(set(affected)) != len(affected) or any(x not in units for x in affected): + raise ScspillConfigError("affected_units must be distinct known unit labels.") + selected = [label] + [x for x in affected if x != label] + a = pd.DataFrame(0.0, index=units, columns=selected) + for x in selected: + a.loc[x, x] = 1.0 + return yy, tuple(units), times, t0, label, a + + +def _scm_all(y_pre: np.ndarray, *, maxiter: int, ftol: float) -> tuple[np.ndarray, np.ndarray]: + """Fit an intercept and simplex donor weights for every unit.""" + n = y_pre.shape[0] + intercepts = np.zeros(n) + b_matrix = np.zeros((n, n)) + for i in range(n): + target = y_pre[i] + donors = np.delete(y_pre, i, axis=0).T + centered_y = target - target.mean() + centered_x = donors - donors.mean(axis=0) + + # Scale-free objective with analytic derivatives. SLSQP is the primary + # solve; projected gradient is the fallback if it fails or is infeasible. + scale = max(float(np.std(centered_y)), 1.0) + scaled_y = centered_y / scale + scaled_x = centered_x / scale + + def objective( + candidate: np.ndarray, + target: np.ndarray = scaled_y, + donor_matrix: np.ndarray = scaled_x, + ) -> float: + return float(np.sum((target - donor_matrix @ candidate) ** 2)) + + def gradient( + candidate: np.ndarray, + target: np.ndarray = scaled_y, + donor_matrix: np.ndarray = scaled_x, + ) -> np.ndarray: + return 2 * donor_matrix.T @ (donor_matrix @ candidate - target) + + fit = minimize( + objective, + np.full(n - 1, 1 / (n - 1)), + jac=gradient, + method="SLSQP", + bounds=[(0, None)] * (n - 1), + constraints=[ + {"type": "eq", "fun": lambda b: b.sum() - 1, "jac": lambda b: np.ones(n - 1)} + ], + options={"maxiter": max(3000, maxiter), "ftol": ftol}, + ) + weights = np.clip(fit.x, 0, None) + if ( + not fit.success + or not np.isfinite(weights).all() + or weights.sum() <= 0 + or abs(weights.sum() - 1) > 1e-4 + ): + weights = _simplex_fista(centered_y, centered_x) + weights /= weights.sum() + intercepts[i] = target.mean() - donors.mean(axis=0) @ weights + b_matrix[i] = np.insert(weights, i, 0.0) + return intercepts, b_matrix + + +def _simplex_project(values: np.ndarray) -> np.ndarray: + """Project one vector onto the nonnegative unit simplex.""" + ordered = np.sort(values)[::-1] + thresholds = (np.cumsum(ordered) - 1) / np.arange(1, len(values) + 1) + active = np.flatnonzero(ordered > thresholds) + if active.size == 0: + raise ScspillEstimationError("Simplex projection has no active coordinate.") + projected = np.maximum(values - thresholds[active[-1]], 0) + return projected / projected.sum() + + +def _simplex_fista(target: np.ndarray, donors: np.ndarray) -> np.ndarray: + """Solve the centered SCM problem by restarted projected gradient.""" + n = donors.shape[1] + current = np.full(n, 1 / n) + extrapolated = current.copy() + momentum = 1.0 + lipschitz = max(2 * float(np.linalg.norm(donors, ord=2)) ** 2, np.finfo(float).eps) + previous_loss = float("inf") + for _ in range(50_000): + gradient = 2 * donors.T @ (donors @ extrapolated - target) + candidate = _simplex_project(extrapolated - gradient / lipschitz) + loss = float(np.sum((target - donors @ candidate) ** 2)) + if loss > previous_loss + 1e-13: + extrapolated = current.copy() + momentum = 1.0 + gradient = 2 * donors.T @ (donors @ extrapolated - target) + candidate = _simplex_project(extrapolated - gradient / lipschitz) + loss = float(np.sum((target - donors @ candidate) ** 2)) + candidate_gradient = 2 * donors.T @ (donors @ candidate - target) + fixed_point = _simplex_project(candidate - candidate_gradient / lipschitz) + if np.max(np.abs(candidate - fixed_point)) <= 1e-9: + return candidate + next_momentum = (1 + np.sqrt(1 + 4 * momentum**2)) / 2 + extrapolated = candidate + (momentum - 1) / next_momentum * (candidate - current) + current = candidate + momentum = next_momentum + previous_loss = loss + raise ScspillEstimationError("Simplex SCM did not converge after 50,000 fallback iterations.") + + +def _projection(b: np.ndarray, a: np.ndarray) -> tuple[np.ndarray, np.ndarray, int, float]: + """Build the effect map G and residual-space projector after rank checks.""" + r = np.eye(len(b)) - b + ra = r @ a + singular = np.linalg.svd(ra, compute_uv=False) + tol = max(ra.shape) * np.finfo(float).eps * singular[0] + rank = int(np.sum(singular > tol)) + if rank != a.shape[1]: + raise ScspillEstimationError( + f"Cao--Dowd effects unidentified: rank((I-B)A)={rank} < {a.shape[1]}." + ) + pinv = np.linalg.pinv(ra) + g = a @ pinv + projector = ra @ pinv + # The paper reports the condition number of A'(I-B)'(I-B)A, whose + # eigenvalue ratio is the square of the singular-value ratio of (I-B)A. + return g, projector, rank, float((singular[0] / singular[-1]) ** 2) + + +def fit_cd(config: CDConfig) -> CDResults: + """Fit Cao--Dowd and compute pointwise and specification-test references.""" + y, labels, times, t0, treated, a_df = _prepare(config) + a_matrix = a_df.to_numpy(dtype=float) + intercepts, b = _scm_all(y[:, :t0], maxiter=config.scm_maxiter, ftol=config.scm_ftol) + r = np.eye(len(labels)) - b + g, projector, rank, condition = _projection(b, a_matrix) + residuals = r @ y[:, :t0] - intercepts[:, None] + post_target = r @ y[:, t0:] - intercepts[:, None] + alpha = g @ post_target + if config.reference_mode == "full": + placebo = g @ residuals + kappa_pre_vectors = (np.eye(len(labels)) - projector) @ residuals + else: + placebo = np.empty((len(labels), t0)) + kappa_pre_vectors = np.empty((len(labels), t0)) + for held in range(t0): + kept = np.arange(t0) != held + fold_a, fold_b = _scm_all( + y[:, :t0][:, kept], maxiter=config.scm_maxiter, ftol=config.scm_ftol + ) + fold_g, fold_projector, _, _ = _projection(fold_b, a_matrix) + fold_resid = (np.eye(len(labels)) - fold_b) @ y[:, held] - fold_a + placebo[:, held] = fold_g @ fold_resid + kappa_pre_vectors[:, held] = (np.eye(len(labels)) - fold_projector) @ fold_resid + kappa_post_vectors = (np.eye(len(labels)) - projector) @ post_target + kappa_ref = np.linalg.norm(kappa_pre_vectors, axis=0) + kappa_post = np.linalg.norm(kappa_post_vectors, axis=0) + post_times = times[t0:] + effects = pd.DataFrame(alpha.T, index=post_times, columns=labels) + placebo_df = pd.DataFrame(placebo.T, index=times[:t0], columns=labels) + residual_df = pd.DataFrame(residuals.T, index=times[:t0], columns=labels) + pointwise = {} + published = {} + pvalues = pd.DataFrame(index=post_times, columns=labels, dtype=float) + for j, label in enumerate(labels): + pre = placebo[j] + threshold = np.sqrt(_critical_value(pre**2, config.ci, config.quantile_method)) + pointwise[label] = pd.DataFrame( + {"lower": alpha[j] - threshold, "upper": alpha[j] + threshold}, index=post_times + ) + qlo, qhi = np.quantile(pre, [(1 - config.ci) / 2, (1 + config.ci) / 2]) + published[label] = pd.DataFrame( + {"lower": alpha[j] + qlo, "upper": alpha[j] + qhi}, index=post_times + ) + pvalues[label] = np.mean(pre[:, None] ** 2 >= alpha[j][None, :] ** 2, axis=0) + kappa = {} + critical_kappa = _critical_value(kappa_ref, config.ci, config.quantile_method) + for period, statistic in zip(post_times, kappa_post, strict=True): + kappa[period] = CDTestResult( + statistic=float(statistic), + critical_value=critical_kappa, + p_value=float(np.mean(kappa_ref >= statistic)), + reject=bool(statistic > critical_kappa), + reference_statistics=kappa_ref.copy(), + ) + selected_ci = published if config.ci_method == "published" else pointwise + cf = np.r_[intercepts[0] + b[0] @ y[:, :t0], y[0, t0:] - alpha[0]] + pre_spill = np.zeros((t0, len(labels) - 1)) + spill = pd.DataFrame(np.vstack([pre_spill, alpha[1:].T]), index=times, columns=list(labels[1:])) + weights = make_weights_results( + {labels[j]: b[0, j] for j in range(1, len(labels))}, + "intercept-adjusted simplex SCM", + ) + submodels = build_effect_submodels( + observed_outcome=y[0], + counterfactual_outcome=cf, + n_pre_periods=t0, + n_post_periods=len(times) - t0, + time_periods=times, + intervention_time=times[t0], + weights=weights, + inference=InferenceResults( + method=f"cao_dowd_P_{config.reference_mode}", + confidence_level=config.ci, + details="Pointwise and joint inference is in CDResults; no ATT interval is implied.", + ), + prediction_interval={ + "lower": y[0, t0:] - selected_ci[treated]["upper"].to_numpy(), + "upper": y[0, t0:] - selected_ci[treated]["lower"].to_numpy(), + "periods": post_times, + "level": config.ci, + "kind": ( + "published_signed_quantile" + if config.ci_method == "published" + else "pointwise_P_inversion" + ), + }, + ) + return CDResults( + **submodels, + method_details=MethodDetailsResults( + method_name="SCSPILL/cd", + is_recommended=True, + parameters_used={ + "method": "cd", + "reference_mode": config.reference_mode, + "quantile_method": config.quantile_method, + "ci_method": config.ci_method, + "ci": config.ci, + "scm_maxiter": config.scm_maxiter, + "scm_ftol": config.scm_ftol, + }, + ), + treated_label=treated, + unit_labels=labels, + time_labels=times, + t_pre=t0, + intercepts=pd.Series(intercepts, index=labels), + scm_matrix=pd.DataFrame(b, index=labels, columns=labels), + effect_matrix=a_df, + effects_panel=effects, + spillover_panel=spill, + pre_residuals=residual_df, + placebo_effects=placebo_df, + pointwise_p_values=pvalues, + pointwise_ci=pointwise, + published_ci=published, + kappa_test=kappa, + rank=rank, + condition_number=condition, + reference_mode=config.reference_mode, + quantile_method=config.quantile_method, + ci_method=config.ci_method, + ci_level=config.ci, + plot_config=config.resolved_plot(), + ) diff --git a/scspill/utils/scspill_helpers/config.py b/scspill/utils/scspill_helpers/config.py index 372c371..a407184 100644 --- a/scspill/utils/scspill_helpers/config.py +++ b/scspill/utils/scspill_helpers/config.py @@ -11,7 +11,7 @@ class SCSPILLConfig(BaseEstimatorConfig): - """Configuration for a spillover synthetic-control fit (today, model ``sar``). + """Configuration for the Bayesian ``sar`` spillover synthetic-control fit. Implements: @@ -72,9 +72,8 @@ class SCSPILLConfig(BaseEstimatorConfig): "spatial-autoregressive Bayesian SCM: donor outcomes follow a SAR " "process under the supplied weights (spatial_w, spatial_W), and a " "two-step sampler estimates the synthetic weights alpha and the " - "spillover intensity rho. It is the only implemented model, and the " - "default, so omitting this field is correct. Models added later " - "select here rather than through a new class; see " + "spillover intensity rho. It remains the default; Cao--Dowd uses " + "a separate CDConfig selected by method='cd'. See " "https://quarcs-lab.github.io/scspill/models/ for the catalogue." ), ) diff --git a/scspill/utils/scspill_helpers/structures.py b/scspill/utils/scspill_helpers/structures.py index 55d459c..874459b 100644 --- a/scspill/utils/scspill_helpers/structures.py +++ b/scspill/utils/scspill_helpers/structures.py @@ -289,7 +289,7 @@ class SCSPILLResults(BaseEstimatorResults): :attr:`method` records which spillover model produced the fit. It mirrors ``SCSPILLConfig.method`` and the suffix of ``method_details.method_name`` (``"SCSPILL/"``). Only ``"sar"`` - exists today; a model added later declares its own posterior blocks + uses this result; another model declares its own estimation blocks alongside :attr:`alpha_posterior` / :attr:`sar_posterior`, which are SAR-specific, while :attr:`inputs`, :attr:`effects_detail` and the flat effect surface are the cross-model contract. From 2bea1d74f55838737c6c1d7ac0f09f33704284ae Mon Sep 17 00:00:00 2001 From: HayaTaga Date: Wed, 14 Oct 2026 16:05:03 +0900 Subject: [PATCH 3/4] Document the cd model and its caveats Adds docs/models/cd.qmd: usage, inference options, how to reproduce Tables S.6-S.8 and Figure 4, and the three places where the authors' application code departs from the paper's Section 4 definitions. The model catalogue, README, datasets page, API reference, llms.txt and CHANGELOG now list cd as implemented. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 24 +++++- README.md | 17 +++-- docs/_quarto.yml | 32 +++++--- docs/articles/datasets.qmd | 12 ++- docs/index.qmd | 5 +- docs/llms.txt | 23 ++++-- docs/models/cd.qmd | 152 +++++++++++++++++++++++++++++++++++++ docs/models/index.qmd | 23 +++--- docs/models/sar.qmd | 4 +- docs/use-with-llms.qmd | 7 +- tools/build_llms_txt.py | 25 ++++-- 11 files changed, 273 insertions(+), 51 deletions(-) create mode 100644 docs/models/cd.qmd diff --git a/CHANGELOG.md b/CHANGELOG.md index 9393c03..300942a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,29 @@ ## Unreleased -Authorship and citation metadata only; no functional change. +### New model: `cd` (Cao & Dowd) + +- **`method="cd"` implements Cao and Dowd (arXiv:1902.07343v4)**: an + intercept-adjusted simplex SCM for every unit, a fixed exposure structure + given by `affected_units` or a labeled `effect_matrix`, period-specific + direct and spillover effects, Andrews-type P tests for `C α_t = d`, and the + `κ_A` specification test. `load_california_cd(51)` and + `load_california_cd(41)` bundle the authors' 50-state-plus-DC panel. +- **The defaults follow the authors' Proposition 99 replication code**, so a + default fit reproduces the paper's Section 6 tables and Figure 4: + interpolated critical values (`quantile_method="linear"`) and intervals + that add the signed pre-treatment error quantiles to the estimate + (`ci_method="published"`, returned by `effect_ci`). The paper's Section 4 + definitions are available as `quantile_method="empirical"`, + `ci_method="inversion"` and `reference_mode="leave_one_period_out"`; the + differences are listed under Caveats in `docs/models/cd.qmd`. +- `scspill/tests/test_cd.py` checks the 51- and 41-unit fits against the + authors' frozen output (`jcao0/synthetic-control-spillover`, commit + `86004020`), stored in `benchmarks/reference/cao_dowd/`. + +### Authorship and citation + +No functional change. - **The software has three authors and one citation.** Shosei Sakaguchi and Hayato Tagawa have joined the project as co-authors, so `CITATION.cff`, the diff --git a/README.md b/README.md index 637c367..f5a98da 100644 --- a/README.md +++ b/README.md @@ -24,18 +24,19 @@ express at all. | Model | `method` | Reference | Class | Docs | |---|---|---|---|---| | Bayesian spatial-autoregressive spillover SCM | `"sar"` | [Sakaguchi & Tagawa (2026)](https://doi.org/10.1093/ectj/utag006), *The Econometrics Journal* | `SCSPILL` | [`sar`](https://quarcs-lab.github.io/scspill/models/sar.html) | +| SCM with spillover effects | `"cd"` | [Cao & Dowd, v4](https://arxiv.org/html/1902.07343v4) | `SCSPILL` | [`cd`](https://quarcs-lab.github.io/scspill/models/cd.html) | -One model ships today. `sar` routes spillovers through spatial weights you +`sar` routes spillovers through spatial weights you supply, scaled by a single intensity ρ that is estimated rather than assumed, fits unconstrained horseshoe-shrunk synthetic weights, and returns full Bayesian uncertainty for both estimands. At ρ = 0 it collapses exactly to the Bayesian horseshoe synthetic control. -Three further spillover-aware models — Cao & Dowd, the inclusive SCM of -Di Stefano & Mellace, and the partial-interference SCG of Grossi et al. — are -on the [roadmap](https://quarcs-lab.github.io/scspill/models/#planned). They -are **not implemented**, and `SCSPILLConfig` rejects their names rather than -falling back silently. +`cd` uses intercept-adjusted SCM equations for every unit and a specified +exposure structure to recover direct and spillover effects. It includes +Andrews P tests and a structure test. The inclusive SCM of Di Stefano & +Mellace and partial-interference SCG of Grossi et al. remain on the +[roadmap](https://quarcs-lab.github.io/scspill/models/#planned). The estimator architecture follows [mlsynth](https://github.com/jgreathouse9/mlsynth) (a pydantic config in, a @@ -76,10 +77,10 @@ result.plot(kind="panel") # counterfactual | effect | top spillovers | Subpackage | What it does | Docs | |---|---|---| -| `scspill` | The model layer. `SCSPILL(config).fit()` — today the `sar` model's two-step Bayesian sampler (horseshoe synthetic weights, SAR spillover block, adaptive Metropolis for the spillover intensity) and its identification formulas | [Get started](https://quarcs-lab.github.io/scspill/get-started.html) | +| `scspill` | The model layer. `SCSPILL(config).fit()` dispatches the Bayesian `sar` and Cao–Dowd `cd` estimators | [Models](https://quarcs-lab.github.io/scspill/models/) | | `scspill.validation` | `sar`'s sampler validation: the Geweke (2004) joint distribution test, prior-sensitivity grids, prior predictive checks | [Validation](https://quarcs-lab.github.io/scspill/articles/validation.html) | | `scspill.simulate` | `sar`'s Monte Carlo engine: rook-lattice SAR DGP, SCM/BSCM/SCSPILL comparison, the Tables 1–2 grid | [Simulation study](https://quarcs-lab.github.io/scspill/articles/simulation-study.html) | -| `scspill.data` | The bundled California Prop 99 and Sudan secession spillover panels — model-agnostic, usable by any model added later | [Datasets](https://quarcs-lab.github.io/scspill/articles/datasets.html) | +| `scspill.data` | California and Sudan panels, plus the authors' 51/41-unit Cao–Dowd Proposition 99 data | [Datasets](https://quarcs-lab.github.io/scspill/articles/datasets.html) | ## `sar` is validated, not just implemented diff --git a/docs/_quarto.yml b/docs/_quarto.yml index af86949..fe48b18 100644 --- a/docs/_quarto.yml +++ b/docs/_quarto.yml @@ -25,6 +25,8 @@ website: - text: "---" - text: "sar — Bayesian spatial-autoregressive spillover SCM" file: models/sar.qmd + - text: "cd — Cao–Dowd SCM with spillovers" + file: models/cd.qmd - text: "Sudan case study" file: sudan.qmd - text: "For AI / LLMs" @@ -83,18 +85,16 @@ quartodoc: - title: Estimator desc: >- Configure once, fit once. `SCSPILL(config).fit()` runs the spillover - model named by `method` — today `"sar"`, whose two-step sampler fits - the horseshoe synthetic weights and then the SAR spillover block — and - returns a `SCSPILLResults` with the treatment effect, its credible - interval, and the spillover received by every donor. The + model named by `method`: `"sar"` runs the Bayesian spatial sampler; + `"cd"` runs Cao–Dowd all-unit SCM and P-test inference. It returns + the model-specific result and a shared effect surface. The [model catalogue](../models/index.qmd) says which models exist. package: scspill.estimators.scspill contents: - SCSPILL - title: Configuration desc: >- - The pydantic input contract — `method` (the spillover model; `"sar"` - is the only accepted value today), the long panel and its column + The pydantic input contract for `"sar"` — the long panel and its column names, the spatial weights `spatial_w` / `spatial_W` (label-aligned), covariates, the MCMC budget, priors, and the paper-correct switches (`beta_prior`, `propagate_alpha`, `adapt_rho`) with their R-compatible @@ -102,6 +102,14 @@ quartodoc: package: scspill.utils.scspill_helpers.config contents: - SCSPILLConfig + - title: Cao–Dowd configuration and results + desc: >- + Fixed exposure sets or labeled A matrices, all-unit SCM equations, + pointwise P tests, and kappa_A structure tests. + package: scspill.utils.scspill_helpers.cd + contents: + - CDConfig + - CDResults - title: Results desc: >- The standardized posterior report — flat accessors (`att`, `att_ci`, @@ -164,14 +172,14 @@ quartodoc: - mcmc_summary - title: Datasets desc: >- - Two bundled spillover case studies, shipped inside the wheel — the - California Proposition 99 tobacco panel (39 states, 1970–2000, rook - contiguity weights) and the 2011 Sudan secession panel (34 African - countries, 2000–2015, bilateral-trade weights). Each loader returns a - `SpillPanel` whose `config_kwargs()` feeds any spillover model's - configuration directly. + Three bundled panels: the 39-state California and Sudan SAR case + studies, plus the 51/41-unit California data used by Cao–Dowd with + treatment beginning in 1989. Each loader supplies model-specific + configuration fields. package: scspill.data contents: - load_california - load_sudan - SpillPanel + - load_california_cd + - CDPanel diff --git a/docs/articles/datasets.qmd b/docs/articles/datasets.qmd index 4f59882..b452e08 100644 --- a/docs/articles/datasets.qmd +++ b/docs/articles/datasets.qmd @@ -1,9 +1,9 @@ --- title: "The bundled datasets" -subtitle: "Provenance and dictionaries for the two case studies" +subtitle: "Provenance and dictionaries for the SAR and Cao–Dowd case studies" --- -Both of the paper's empirical applications ship inside the wheel as plain +Both of the SAR paper's empirical applications ship inside the wheel as plain CSVs (about 150 kB total), copied verbatim from the replication package's nonproprietary export. Each loader returns a `SpillPanel` — the long panel with a 0/1 `treated` column, the raw spatial weights aligned by donor label, @@ -11,6 +11,14 @@ and a `config_kwargs()` helper that feeds `SCSPILLConfig` directly. The weights are stored *unnormalized*; the estimator row-normalizes `spatial_W` and scales `spatial_w` to sum to one, exactly as the R package does. +## Cao–Dowd Proposition 99 + +`load_california_cd(51)` and `load_california_cd(41)` load a separate +51-region panel (50 states and DC, 1970–2000, treatment from 1989) taken +from the authors' [replication repository](https://github.com/jcao0/synthetic-control-spillover). +It is not interchangeable with `load_california()`; see the +[`cd` model page](../models/cd.qmd). + ## California Proposition 99 ```{python} diff --git a/docs/index.qmd b/docs/index.qmd index 22571ad..a88c9ac 100644 --- a/docs/index.qmd +++ b/docs/index.qmd @@ -67,12 +67,11 @@ RMSE, and coverage across estimators. | Model | `method` | Reference | Status | |---|---|---|---| | [Bayesian spatial-autoregressive spillover SCM](models/sar.qmd) | `"sar"` | Sakaguchi & Tagawa (2026), *Econometrics Journal* | **Available** | -| SCM with spillover effects | `"cd"` | Cao & Dowd (2019, rev. 2026) | Planned | +| [SCM with spillover effects](models/cd.qmd) | `"cd"` | Cao & Dowd (2019, rev. 2026) | **Available** | | Inclusive synthetic control | `"iscm"` | Di Stefano & Mellace (2024) | Planned | | Partial-interference SCG | `"grossi"` | Grossi et al. (2025), *JRSS-A* | Planned | -The planned rows are a roadmap, not a feature list: they are not implemented -and their `method` names are rejected rather than silently substituted. The +The remaining planned rows are a roadmap. Their `method` names are rejected. The [model catalogue](models/index.qmd) explains what each would assume, and why having several matters — the assumptions are not nested, so two models bracket the answer. diff --git a/docs/llms.txt b/docs/llms.txt index 443d1fa..c73ac43 100644 --- a/docs/llms.txt +++ b/docs/llms.txt @@ -8,13 +8,14 @@ Install: `pip install scspill` (or `pip install "scspill[numba]"` for JIT-compiled samplers). -Models: exactly ONE is implemented -- `method="sar"` (Sakaguchi & Tagawa, -The Econometrics Journal 2026), class SCSPILL, and the default. It routes +Models: TWO are implemented -- `method="sar"` (Sakaguchi & Tagawa, +The Econometrics Journal 2026), the default, and `method="cd"` (Cao & Dowd, +arXiv:1902.07343v4). `sar` routes spillovers through user-supplied spatial weights scaled by one estimated intensity rho, via two-step MCMC (horseshoe synthetic weights, then a SAR -spillover block). The catalogue also lists `cd` (Cao & Dowd), `iscm` (Di -Stefano & Mellace) and `grossi` (Grossi et al.) as PLANNED: they are NOT -implemented and SCSPILLConfig rejects them. Do not emit code using them. +spillover block). `cd` fits intercept-adjusted all-unit SCM with a fixed +exposure matrix and P-test inference. `iscm` (Di Stefano & Mellace) and +`grossi` (Grossi et al.) remain planned and are not implemented. Catalogue: https://quarcs-lab.github.io/scspill/models/index.html Minimal contract (model `sar`): SCSPILL({df, outcome, treat, unitid, time, @@ -24,11 +25,19 @@ result with .att, .att_ci, .rho_hat, .rho_ci, .counterfactual, treated unit and date are inferred from the 0/1 `treat` column; spatial weights align by donor label. +Minimal contract (model `cd`): SCSPILL({method: "cd", df, outcome, treat, +unitid, time, affected_units? or effect_matrix?, reference_mode?, +quantile_method?, ci_method?}).fit() -> CDResults with .effects_panel, +.effect_ci (= .published_ci by default, or .pointwise_ci), +.pointwise_p_values, .kappa_test, and .test_hypothesis(C). The 51-unit +1989-onward replication panel is load_california_cd(51). + ## Docs - [Get started (California Prop 99)](https://quarcs-lab.github.io/scspill/get-started.html) - [Models: catalogue and roadmap](https://quarcs-lab.github.io/scspill/models/index.html) - [The `sar` model (Sakaguchi & Tagawa)](https://quarcs-lab.github.io/scspill/models/sar.html) +- [The `cd` model (Cao & Dowd)](https://quarcs-lab.github.io/scspill/models/cd.html) - [Sudan case study](https://quarcs-lab.github.io/scspill/sudan.html) - [Validating the sampler](https://quarcs-lab.github.io/scspill/articles/validation.html) - [The Monte Carlo simulation study](https://quarcs-lab.github.io/scspill/articles/simulation-study.html) @@ -42,7 +51,7 @@ weights align by donor label. ### Top level -`SCSPILL`, `SCSPILLConfig`, `SCSPILLResults` +`SCSPILL`, `SCSPILLConfig`, `SCSPILLResults`, `CDConfig`, `CDResults` ### scspill.validation @@ -54,7 +63,7 @@ weights align by donor label. ### scspill.data -`SpillPanel`, `load_california`, `load_sudan` +`CDPanel`, `SpillPanel`, `load_california`, `load_california_cd`, `load_sudan` ## Full corpus diff --git a/docs/models/cd.qmd b/docs/models/cd.qmd new file mode 100644 index 0000000..3bd6d76 --- /dev/null +++ b/docs/models/cd.qmd @@ -0,0 +1,152 @@ +--- +title: "`cd` — Cao–Dowd synthetic control with spillovers" +subtitle: "Single treated unit, fixed exposure structure, and end-of-sample inference" +--- + +`method="cd"` implements [Cao and Dowd, v4](https://arxiv.org/html/1902.07343v4). +It fits an intercept and simplex synthetic-control weights for **every** unit +using pre-treatment outcomes. The researcher supplies a set of potentially +exposed units or a labeled, fixed structure matrix $A$. The model permits a +different direct and spillover effect in each post-treatment period. + +## Fit + +```python +from scspill import SCSPILL +from scspill.data import load_california_cd + +panel = load_california_cd(51) # 50 states + DC; 1970–2000; post starts 1989 +result = SCSPILL({**panel.config_kwargs(), "display_graphs": False}).fit() +result.effects_panel.loc[1990, ["CA", "NV"]] +result.effect_ci["CA"].loc[1990] +``` + +The existing `load_california()` is a different 39-state, 1988-onward case +study and does not reproduce this paper. `load_california_cd(41)` supplies +the paper's reduced specification. + +For a custom panel, pass `df`, `outcome`, `treat`, `unitid`, `time`, and either +`affected_units` or a pandas `effect_matrix`. The treatment flag is 1 only +for the single treated unit from the first post period onward. The +`effect_matrix` rows must cover every unit by label and include one column +equal to the treated-unit indicator. Other columns may encode shared or +heterogeneous spillovers. All columns of $(I-\widehat B)A$ must be linearly +independent; the fit raises an identification error otherwise. + +Writing $R=I-\widehat B$, the period-specific effect is + +$$ +\widehat\alpha_t=A[(RA)'(RA)]^{-1}(RA)'(RY_t-\widehat a). +$$ + +## Inference + +The defaults follow the authors' Proposition 99 replication code, so a +default fit reproduces the paper's Section 6 tables and Figure 4. The +departures that convention makes from the paper's own Section 4 definitions +are listed under [Caveats](#caveats). + +`result.test_hypothesis(C, d=0, period=1990)` tests a general linear +restriction $C\alpha_t=d$ by the squared P statistic. `C` can be a labeled +DataFrame or a matrix in `result.unit_labels` order. Identity and inverse +projected second-moment weights are available. Set +`reference_mode="leave_one_period_out"` to refit all SCM equations for each +held-out pre-treatment period; `"full"` matches the application code and is +the default. + +`result.effect_ci[unit]` is the pointwise interval selected by `ci_method`. +Both versions are always stored: `published_ci` (the default, as in the +paper's tables) and `pointwise_ci` (the inverse of the squared P test). +`pointwise_p_values` holds the period-wise P values, and +`kappa_test[period]` checks the assumed structure $A$ period by period +(Section 5.1.2). `att` is the arithmetic mean of the post-period direct +estimates; no ATT confidence interval is implied by the pointwise intervals. + +## Caveats {#caveats} + +The paper's empirical results and its stated procedure differ in three +places. `cd` follows the empirical results by default; each difference has a +switch. + +| # | Paper, Section 4 | Authors' application code (default) | Switch | +|---|---|---|---| +| 1 | Critical value $\inf\{x:\widehat F(x)\ge 1-\tau\}$, the left inverse of the empirical CDF | NumPy's linearly interpolated quantile | `quantile_method="empirical"` | +| 2 | Intervals by inverting the squared P test, which makes them symmetric about $\widehat\alpha$ | $\widehat\alpha + [q_{\tau/2},\, q_{1-\tau/2}]$ of the signed pre-treatment errors $e_i'\widehat G\widehat u_t$ | `ci_method="inversion"` | +| 3 | Leave-one-period-out references recommended for finite samples | Full-sample references | `reference_mode="leave_one_period_out"` | + +What each one changes in practice: + +1. With only 19 pre-treatment periods the two rules pick different order + statistics. The paper's 95% critical value is + $\lceil 0.95 \times 19 \rceil = 19$th, the largest reference statistic, + so it rejects only when the post-treatment statistic exceeds all 19. The + interpolated quantile sits at position 18.1, just above the second + largest. In the 51-unit application the joint test for any spillover + rejects in 1990–1992 and 1994–2000 under the default, as in Figure 4. In + 1994 and 1995 the statistic (1745.8 and 1725.3) lies between the + interpolated cut-off (1482.8) and the largest reference value (1828.9), + so the paper's definition does not reject, and the paper's own P value, + $\#\{\widehat P_t \ge P\}/19 = 0.053$, is above 0.05. +2. Since $\widehat\alpha-\alpha\approx Gu_{T+1}$, a percentile interval for + $\alpha$ subtracts the quantiles, $[\widehat\alpha-q_{1-\tau/2},\, + \widehat\alpha-q_{\tau/2}]$. The published interval adds them, which + agrees with that only when the error distribution is symmetric; for + California in 1989 it gives $[-3.87, 3.27]$ where the subtracting form + gives $[-3.11, 4.04]$. In the authors' repository the Proposition 99 + code (`run_spec.py` computes the quantiles, `make_figures.py` plots + `te + lo` to `te + hi`) adds, while their continuous-treatment + application (`continuous_treatment.m`) subtracts. +3. Leave-one-out references widen the intervals substantially in the + California application; for California itself the range of the + reference errors more than doubles. + +The authors' own studies are not uniform either: their Monte Carlo size and +power studies and the $\kappa_A$ study use the left-inverse critical value +(`quantile_method="empirical"`), and the repository's README notes that its +confidence intervals were checked against the original MATLAB output only +for the point estimates. + +## Reproducing the paper + +The bundled panel is the input of the [authors' replication +package](https://github.com/jcao0/synthetic-control-spillover) at commit +`86004020`. A default fit reproduces Section 6: + +```python +import numpy as np +import pandas as pd +from scspill import SCSPILL +from scspill.data import load_california_cd + +fit = SCSPILL({**load_california_cd(51).config_kwargs(), "display_graphs": False}).fit() + +# Table S.6: estimate, 95% interval and P value for CA (direct) and NV, AZ, OR +table_s6 = pd.concat( + { + unit: fit.effect_ci[unit].assign( + estimate=fit.effects_panel[unit], p_value=fit.pointwise_p_values[unit] + ) + for unit in ["CA", "NV", "AZ", "OR"] + }, + axis=1, +).round(2) + +# Figure 4b shading: years in which "no spillover anywhere" is rejected +no_spill = np.eye(len(fit.unit_labels))[1:] +[t for t in fit.effects_panel.index if fit.test_hypothesis(no_spill, period=t).reject] + +# Table S.8: California's synthetic-control weights and intercept +fit.scm_matrix.loc["CA"].nlargest(10), fit.intercepts["CA"] +``` + +`load_california_cd(41)` gives the robustness specification of Table S.7. +`scspill/tests/test_cd.py` checks both fits against the authors' saved +output: P values match exactly, and effects agree to within 0.01 packs. +The remaining gap is the simplex solver. With the authors' settings, SciPy's +SLSQP stops short of the optimum for every unit of the 51-unit panel; `cd` +uses scaled objectives with analytic gradients and reaches an objective at +or below theirs for every unit, so a few published two-decimal values +differ in the last digit. + +Multiple treated units, covariates, time-varying $A$, and alternative +estimators are outside this implementation. diff --git a/docs/models/index.qmd b/docs/models/index.qmd index ddf5d1b..a30202a 100644 --- a/docs/models/index.qmd +++ b/docs/models/index.qmd @@ -27,17 +27,13 @@ worth having more than one — run two and they bracket the answer. ## Available now -::: {.callout-important} -## One model ships today -scspill implements **one** model. Everything under -[Planned](#planned) below is a roadmap: none of it is implemented, and none of -those `method` names is accepted. Passing one raises a validation error rather -than silently falling back. -::: +scspill implements two models. `sar` remains the default, and `cd` uses a +specified spillover structure and pre-treatment P-test reference distribution. | Model | `method` | Reference | Class | Page | |---|---|---|---|---| | Bayesian spatial-autoregressive spillover SCM | `"sar"` | Sakaguchi & Tagawa (2026), *The Econometrics Journal*, [doi:10.1093/ectj/utag006](https://doi.org/10.1093/ectj/utag006) | `SCSPILL` | [`sar` →](sar.qmd) | +| SCM with spillover effects | `"cd"` | Cao & Dowd, [arXiv:1902.07343v4](https://arxiv.org/html/1902.07343v4) | `SCSPILL` | [`cd` →](cd.qmd) | ### `sar` — spillovers as a spatial process @@ -68,18 +64,27 @@ Full statement of the model, the identification theorem, the samplers, and the six documented departures from the authors' R replication package are on [the `sar` page](sar.qmd). +### `cd` — spillovers under a specified structure + +`cd` estimates an intercept and simplex synthetic-control weights for each +unit, then uses a fixed exposure matrix $A$ to separate direct and spillover +effects by post-treatment period. Andrews P tests use pre-treatment variation; +the $\kappa_A$ test checks whether the specified structure fits. The +[model page](cd.qmd) documents the 51-state Proposition 99 replication, +inference options, and the distinction between the paper's P-test interval +and the published application's interval calculation. + ## Planned {#planned} ::: {.callout-warning} ## Not implemented -The three models below are candidates, not features. They are listed so the +The two models below are candidates, not features. They are listed so the package's intended scope is legible and so the `method` names stay coherent — **none of them exists in scspill today**, and there is no release date. ::: | Model | Reserved `method` | Reference | How it *would* identify the spillover | |---|---|---|---| -| SCM with spillover effects | `"cd"` | Cao & Dowd, [arXiv:1902.07343](https://arxiv.org/abs/1902.07343) (2019, rev. 2026) | *Keep* the exposed donors and impose a researcher-specified spillover-structure matrix, linear in unknown parameters, recovering direct and per-unit spillover effects jointly. Comes with a misspecification test for the structure you assumed. | | Inclusive synthetic control | `"iscm"` | Di Stefano & Mellace, [arXiv:2403.17624](https://arxiv.org/abs/2403.17624) (2024) | *Keep* the exposed donors with no parametric spillover structure at all: build a synthetic control for every affected unit, then invert the system of their own cross-weights to de-contaminate the gaps. | | Partial-interference synthetic control group | `"grossi"` | Grossi, Mariani, Mattei, Lattarulo & Öner (2025), *JRSS-A* 188(1):223–240, [arXiv:2004.05027](https://arxiv.org/abs/2004.05027) | *Drop* the treated unit's whole cluster from the pool and rebuild from the far, clean clusters only. Cleanest identification when partial interference is defensible, at the cost of pre-treatment fit. | diff --git a/docs/models/sar.qmd b/docs/models/sar.qmd index 5c82bd3..8a095b1 100644 --- a/docs/models/sar.qmd +++ b/docs/models/sar.qmd @@ -5,8 +5,8 @@ aliases: - /articles/method.html --- -`sar` is scspill's first model, and today its only one — the -[model catalogue](index.qmd) lists what else is planned. It implements +`sar` is scspill's default model. The [model catalogue](index.qmd) also lists +the implemented [`cd` model](cd.qmd) and future work. `sar` implements Sakaguchi & Tagawa's *Identification and Bayesian Inference for Synthetic Control Methods with Spillover Effects*. This page states the model, the identification result, and the samplers, and documents precisely where this diff --git a/docs/use-with-llms.qmd b/docs/use-with-llms.qmd index 9dd7364..055dd81 100644 --- a/docs/use-with-llms.qmd +++ b/docs/use-with-llms.qmd @@ -51,6 +51,11 @@ result.diagnostics() # DataFrame: mean/sd/quantiles/ESS/R-hat per chain result.plot(kind="panel") # counterfactual | effect | top spillovers ``` +For Cao–Dowd, pass `load_california_cd(51).config_kwargs()` to `SCSPILL`; +the `CDResults` it returns has `effects_panel`, `effect_ci`, +`pointwise_p_values`, `test_hypothesis` and `kappa_test`. See the +[`cd` model page](models/cd.qmd). + ## Rules of thumb for agents - The treated unit and the treatment date are inferred from the `treat` @@ -65,7 +70,7 @@ result.plot(kind="panel") # counterfactual | effect | top spillovers `ScspillPlottingError` — all subclass `ScspillError`. - Sampler validation lives in `scspill.validation` (`geweke_test`, `prior_sensitivity`, `prior_predictive`); Monte Carlo tooling in - `scspill.simulate`; the two case studies in `scspill.data`. + `scspill.simulate`; bundled panels in `scspill.data`. - The estimator's architecture mirrors [mlsynth](https://github.com/jgreathouse9/mlsynth) (pydantic config in, standardized results object out), so mlsynth patterns transfer. diff --git a/tools/build_llms_txt.py b/tools/build_llms_txt.py index 6f8a260..cd89fe9 100644 --- a/tools/build_llms_txt.py +++ b/tools/build_llms_txt.py @@ -27,6 +27,7 @@ ("Get started (California Prop 99)", "get-started.qmd", "get-started.html"), ("Models: catalogue and roadmap", "models/index.qmd", "models/index.html"), ("The `sar` model (Sakaguchi & Tagawa)", "models/sar.qmd", "models/sar.html"), + ("The `cd` model (Cao & Dowd)", "models/cd.qmd", "models/cd.html"), ("Sudan case study", "sudan.qmd", "sudan.html"), ("Validating the sampler", "articles/validation.qmd", "articles/validation.html"), ( @@ -40,7 +41,11 @@ ] API_GROUPS = [ - ("Top level", "scspill", ["SCSPILL", "SCSPILLConfig", "SCSPILLResults"]), + ( + "Top level", + "scspill", + ["SCSPILL", "SCSPILLConfig", "SCSPILLResults", "CDConfig", "CDResults"], + ), ("scspill.validation", "scspill.validation", None), ("scspill.simulate", "scspill.simulate", None), ("scspill.data", "scspill.data", None), @@ -60,13 +65,14 @@ def canonical() -> str: 'Install: `pip install scspill` (or `pip install "scspill[numba]"` for', "JIT-compiled samplers).", "", - 'Models: exactly ONE is implemented -- `method="sar"` (Sakaguchi & Tagawa,', - "The Econometrics Journal 2026), class SCSPILL, and the default. It routes", + 'Models: TWO are implemented -- `method="sar"` (Sakaguchi & Tagawa,', + 'The Econometrics Journal 2026), the default, and `method="cd"` (Cao & Dowd,', + "arXiv:1902.07343v4). `sar` routes", "spillovers through user-supplied spatial weights scaled by one estimated", "intensity rho, via two-step MCMC (horseshoe synthetic weights, then a SAR", - "spillover block). The catalogue also lists `cd` (Cao & Dowd), `iscm` (Di", - "Stefano & Mellace) and `grossi` (Grossi et al.) as PLANNED: they are NOT", - "implemented and SCSPILLConfig rejects them. Do not emit code using them.", + "spillover block). `cd` fits intercept-adjusted all-unit SCM with a fixed", + "exposure matrix and P-test inference. `iscm` (Di Stefano & Mellace) and", + "`grossi` (Grossi et al.) remain planned and are not implemented.", f"Catalogue: {BASE}/models/index.html", "", "Minimal contract (model `sar`): SCSPILL({df, outcome, treat, unitid, time,", @@ -76,6 +82,13 @@ def canonical() -> str: "treated unit and date are inferred from the 0/1 `treat` column; spatial", "weights align by donor label.", "", + 'Minimal contract (model `cd`): SCSPILL({method: "cd", df, outcome, treat,', + "unitid, time, affected_units? or effect_matrix?, reference_mode?,", + "quantile_method?, ci_method?}).fit() -> CDResults with .effects_panel,", + ".effect_ci (= .published_ci by default, or .pointwise_ci),", + ".pointwise_p_values, .kappa_test, and .test_hypothesis(C). The 51-unit", + "1989-onward replication panel is load_california_cd(51).", + "", "## Docs", "", ] From 13e314e79b349bfa8ac7f2d38e16ac67eb38884e Mon Sep 17 00:00:00 2001 From: HayaTaga Date: Wed, 14 Oct 2026 16:09:33 +0900 Subject: [PATCH 4/4] Make scm_maxiter control the SLSQP iteration limit The solver applied max(3000, scm_maxiter), so the field (default 900) had no effect. Default it to 3000 and pass it through unchanged. Co-Authored-By: Claude Opus 5.5 --- scspill/tests/test_cd.py | 4 ++-- scspill/utils/scspill_helpers/cd.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scspill/tests/test_cd.py b/scspill/tests/test_cd.py index 4c64c82..a308b1a 100644 --- a/scspill/tests/test_cd.py +++ b/scspill/tests/test_cd.py @@ -19,7 +19,7 @@ def test_scm_is_optimal_with_tiny_maxiter_and_falls_back_to_fista(monkeypatch): y = rng.normal(size=(4, 12)) y[0] = 0.85 * y[1] + 0.15 * y[2] + 0.02 * rng.normal(size=12) short_intercepts, short_weights = _scm_all(y, maxiter=1, ftol=1e-12) - regular_intercepts, regular_weights = _scm_all(y, maxiter=900, ftol=1e-12) + regular_intercepts, regular_weights = _scm_all(y, maxiter=3000, ftol=1e-12) short_loss = np.sum((y[0] - short_intercepts[0] - short_weights[0] @ y) ** 2) regular_loss = np.sum((y[0] - regular_intercepts[0] - regular_weights[0] @ y) ** 2) assert short_loss <= regular_loss + 1e-5 @@ -31,7 +31,7 @@ def failing(fun, x0, **kwargs): return OptimizeResult(x=np.full_like(x0, np.nan), success=False) monkeypatch.setattr("scspill.utils.scspill_helpers.cd.minimize", failing) - fb_intercepts, fb_weights = _scm_all(y, maxiter=900, ftol=1e-12) + fb_intercepts, fb_weights = _scm_all(y, maxiter=3000, ftol=1e-12) fb_loss = np.sum((y[0] - fb_intercepts[0] - fb_weights[0] @ y) ** 2) assert fb_loss <= regular_loss + 1e-4 assert fb_weights[0].sum() == pytest.approx(1) diff --git a/scspill/utils/scspill_helpers/cd.py b/scspill/utils/scspill_helpers/cd.py index dc319a6..18ba669 100644 --- a/scspill/utils/scspill_helpers/cd.py +++ b/scspill/utils/scspill_helpers/cd.py @@ -65,7 +65,7 @@ class CDConfig(BaseEstimatorConfig): ), ) ci: float = Field(default=0.95, gt=0, lt=1) - scm_maxiter: int = Field(default=900, ge=1) + scm_maxiter: int = Field(default=3000, ge=1) scm_ftol: float = Field(default=1e-12, gt=0) @@ -315,7 +315,7 @@ def gradient( constraints=[ {"type": "eq", "fun": lambda b: b.sum() - 1, "jac": lambda b: np.ones(n - 1)} ], - options={"maxiter": max(3000, maxiter), "ftol": ftol}, + options={"maxiter": maxiter, "ftol": ftol}, ) weights = np.clip(fit.x, 0, None) if (