SPB Git

Repository Public Research preview

Commit

FIRC0002-DATA-01 ECON-04 EDIT-02 ADVE-04 AI_C-02 LITE-04 THEO-01 (L1): Table 17 refit, explicit terms, rank/cond, firm/two-way/HC1 t, N, R2

Committed panel vintage 2026-10-28: rank 8/8, cond 2.82; SPID x post -1.57 pp (t -1.72 firm, -0.88 two-way);
composite +2.21 (6.34 / 2.40); any-AI x post +3.81 (0.62). tab_did_spid.tex regenerated (BEFORE = 3cf7560, all t = 0.00).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Simon-Pierre Boucher committed · Nov 5, 2026 parent 4ee370c
4 changed files +366 −12 Browse files
modified .gitignore +4 −0
@@ -49,3 +49,7 @@ data/processed_r3k/*
49 49
50 50 # Stage D review venv (pinned, see reproducibility/FIRC-0002/environment)
51 51 .venv-review/
52 +
53 +# Stage D review: regenerated derived data (never on top of the staged author inputs)
54 +data/processed_r3k_v2/*
55 +!data/processed_r3k_v2/README.md
modified paper/tables/tab_did_spid.tex +18 −12
@@ -1,19 +1,25 @@
1 −\begin{tabular}{lrr}
1 +\begin{tabular}{lrrrr}
2 2 \toprule
3 −Specification & Coefficient (pp) & $t$ \\
3 +Specification & Coefficient (pp) & $t$ (firm cl.) & $t$ (firm $\times$ year cl.) & $t$ (HC1) \\
4 4 \midrule
5 −DiD: treated (above-median 2019--21 exposure) $\times$ post & +2.54 & 0.98 \\
6 −DiD: continuous exposure $\times$ post & +1.11 & 0.69 \\
5 +DiD: treated (above-median 2019--21 exposure) $\times$ post (firm + year FE) & +2.54 & & 0.98 & \\
6 +DiD: continuous exposure $\times$ post (firm + year FE) & +1.11 & & 0.69 & \\
7 7 \midrule
8 −SPID technology projects ($z$) & +0.11 & 0.00 \\
9 −SPID technology projects ($z$) $\times$ post & -1.66 & -0.00 \\
10 −AI composite ($z$) & +2.23 & 0.00 \\
11 −AI composite $\times$ post & +0.59 & 0.00 \\
8 +SPID technology projects ($z$) & +0.08 & 0.24 & 0.13 & 0.21 \\
9 +SPID technology projects ($z$) $\times$ post & -1.57 & -1.72 & -0.88 & -1.79 \\
10 +AI composite ($z$) & +2.21 & 6.34 & 2.40 & 5.99 \\
11 +AI composite $\times$ post & +0.59 & 0.66 & 0.36 & 0.63 \\
12 +Post-launch cohort (2022--24) & +7.13 & 8.67 & 0.72 & 8.53 \\
13 +\quad SPID technology projects $\times$ post, cohort FE instead of post dummy & -1.66 & -1.84 & -0.91 & -1.94 \\
14 +\quad $N$ = 25{,}135 firm-years (2{,}125 firms, 15 cohorts); $R^2$ = 0.023; design rank 8/8, condition number 2.8 \\
12 15 \addlinespace
13 −Any AI-initiative project (0/1) & -1.86 & -0.00 \\
14 −Any AI-initiative project (0/1) $\times$ post & +5.08 & 0.00 \\
15 −AI composite ($z$) & +2.26 & 0.00 \\
16 −AI composite $\times$ post & +0.33 & 0.00 \\
16 +Any AI-initiative project (0/1) & +0.17 & 0.04 & 0.02 & 0.04 \\
17 +Any AI-initiative project (0/1) $\times$ post & +3.81 & 0.62 & 0.31 & 0.61 \\
18 +AI composite ($z$) & +2.22 & 6.41 & 2.43 & 6.02 \\
19 +AI composite $\times$ post & +0.34 & 0.38 & 0.21 & 0.36 \\
20 +Post-launch cohort (2022--24) & +6.98 & 8.38 & 0.70 & 8.24 \\
21 +\quad Any AI-initiative project $\times$ post, cohort FE instead of post dummy & +5.09 & 0.85 & 0.61 & 0.86 \\
22 +\quad $N$ = 25{,}135 firm-years (2{,}125 firms, 15 cohorts); $R^2$ = 0.023; design rank 8/8, condition number 18.3 \\
17 23 \addlinespace
18 24 \bottomrule
19 25 \end{tabular}
\ No newline at end of file
added results/r3k_v2/t17_refit.json +276 −0
@@ -0,0 +1,276 @@
1 +{
2 + "panel_vintage": "data/processed_r3k/analysis_panel.parquet (author, 2026-10-28, post-r3k_04 overwrite)",
3 + "n_panel_rows": 27468,
4 + "spid_tech": {
5 + "base": {
6 + "formula": "fwd_w ~ z_SPID_TECH + post + z_SPID_TECH:post + z_AI_COMPOS + z_AI_COMPOS:post + z_size + z_mom",
7 + "n": 25135,
8 + "r2": 0.023,
9 + "rank": 8,
10 + "k": 8,
11 + "cond": 2.82,
12 + "n_firms": 2125,
13 + "n_years": 15,
14 + "coef": {
15 + "z_SPID_TECH": {
16 + "coef_pct": 0.08,
17 + "t_firm": 0.24,
18 + "t_year": 0.13,
19 + "t_twoway": 0.13,
20 + "t_hc1": 0.21,
21 + "se_firm_pct": 0.32
22 + },
23 + "post": {
24 + "coef_pct": 7.13,
25 + "t_firm": 8.67,
26 + "t_year": 0.72,
27 + "t_twoway": 0.72,
28 + "t_hc1": 8.53,
29 + "se_firm_pct": 0.82
30 + },
31 + "z_SPID_TECH:post": {
32 + "coef_pct": -1.57,
33 + "t_firm": -1.72,
34 + "t_year": -0.88,
35 + "t_twoway": -0.88,
36 + "t_hc1": -1.79,
37 + "se_firm_pct": 0.91
38 + },
39 + "z_AI_COMPOS": {
40 + "coef_pct": 2.21,
41 + "t_firm": 6.34,
42 + "t_year": 2.38,
43 + "t_twoway": 2.4,
44 + "t_hc1": 5.99,
45 + "se_firm_pct": 0.35
46 + },
47 + "z_AI_COMPOS:post": {
48 + "coef_pct": 0.59,
49 + "t_firm": 0.66,
50 + "t_year": 0.35,
51 + "t_twoway": 0.36,
52 + "t_hc1": 0.63,
53 + "se_firm_pct": 0.89
54 + },
55 + "z_size": {
56 + "coef_pct": -6.98,
57 + "t_firm": -15.52,
58 + "t_year": -4.29,
59 + "t_twoway": -4.25,
60 + "t_hc1": -17.87,
61 + "se_firm_pct": 0.45
62 + },
63 + "z_mom": {
64 + "coef_pct": -0.96,
65 + "t_firm": -2.21,
66 + "t_year": -0.84,
67 + "t_twoway": -0.85,
68 + "t_hc1": -2.08,
69 + "se_firm_pct": 0.43
70 + }
71 + }
72 + },
73 + "year_fe": {
74 + "formula": "fwd_w ~ z_SPID_TECH + z_SPID_TECH:post + z_AI_COMPOS + z_AI_COMPOS:post + z_size + z_mom + C(fiscal_year)",
75 + "n": 25135,
76 + "r2": 0.172,
77 + "rank": 21,
78 + "k": 21,
79 + "cond": 19.25,
80 + "n_firms": 2125,
81 + "n_years": 15,
82 + "coef": {
83 + "z_SPID_TECH": {
84 + "coef_pct": 0.11,
85 + "t_firm": 0.35,
86 + "t_year": 0.19,
87 + "t_twoway": 0.19,
88 + "t_hc1": 0.33,
89 + "se_firm_pct": 0.3
90 + },
91 + "z_SPID_TECH:post": {
92 + "coef_pct": -1.66,
93 + "t_firm": -1.84,
94 + "t_year": -0.92,
95 + "t_twoway": -0.91,
96 + "t_hc1": -1.94,
97 + "se_firm_pct": 0.9
98 + },
99 + "z_AI_COMPOS": {
100 + "coef_pct": 2.23,
101 + "t_firm": 6.56,
102 + "t_year": 2.4,
103 + "t_twoway": 2.39,
104 + "t_hc1": 6.77,
105 + "se_firm_pct": 0.34
106 + },
107 + "z_AI_COMPOS:post": {
108 + "coef_pct": 0.59,
109 + "t_firm": 0.66,
110 + "t_year": 0.35,
111 + "t_twoway": 0.35,
112 + "t_hc1": 0.65,
113 + "se_firm_pct": 0.89
114 + },
115 + "z_size": {
116 + "coef_pct": -6.99,
117 + "t_firm": -15.59,
118 + "t_year": -4.29,
119 + "t_twoway": -4.24,
120 + "t_hc1": -18.94,
121 + "se_firm_pct": 0.45
122 + },
123 + "z_mom": {
124 + "coef_pct": -0.94,
125 + "t_firm": -2.28,
126 + "t_year": -0.85,
127 + "t_twoway": -0.86,
128 + "t_hc1": -2.16,
129 + "se_firm_pct": 0.41
130 + }
131 + }
132 + }
133 + },
134 + "spid_ai_any": {
135 + "base": {
136 + "formula": "fwd_w ~ spid_ai_any + post + spid_ai_any:post + z_AI_COMPOS + z_AI_COMPOS:post + z_size + z_mom",
137 + "n": 25135,
138 + "r2": 0.0228,
139 + "rank": 8,
140 + "k": 8,
141 + "cond": 18.34,
142 + "n_firms": 2125,
143 + "n_years": 15,
144 + "coef": {
145 + "spid_ai_any": {
146 + "coef_pct": 0.17,
147 + "t_firm": 0.04,
148 + "t_year": 0.02,
149 + "t_twoway": 0.02,
150 + "t_hc1": 0.04,
151 + "se_firm_pct": 4.0
152 + },
153 + "post": {
154 + "coef_pct": 6.98,
155 + "t_firm": 8.38,
156 + "t_year": 0.7,
157 + "t_twoway": 0.7,
158 + "t_hc1": 8.24,
159 + "se_firm_pct": 0.83
160 + },
161 + "spid_ai_any:post": {
162 + "coef_pct": 3.81,
163 + "t_firm": 0.62,
164 + "t_year": 0.31,
165 + "t_twoway": 0.31,
166 + "t_hc1": 0.61,
167 + "se_firm_pct": 6.12
168 + },
169 + "z_AI_COMPOS": {
170 + "coef_pct": 2.22,
171 + "t_firm": 6.41,
172 + "t_year": 2.4,
173 + "t_twoway": 2.43,
174 + "t_hc1": 6.02,
175 + "se_firm_pct": 0.35
176 + },
177 + "z_AI_COMPOS:post": {
178 + "coef_pct": 0.34,
179 + "t_firm": 0.38,
180 + "t_year": 0.2,
181 + "t_twoway": 0.21,
182 + "t_hc1": 0.36,
183 + "se_firm_pct": 0.9
184 + },
185 + "z_size": {
186 + "coef_pct": -6.99,
187 + "t_firm": -15.55,
188 + "t_year": -4.28,
189 + "t_twoway": -4.24,
190 + "t_hc1": -17.92,
191 + "se_firm_pct": 0.45
192 + },
193 + "z_mom": {
194 + "coef_pct": -0.95,
195 + "t_firm": -2.19,
196 + "t_year": -0.83,
197 + "t_twoway": -0.84,
198 + "t_hc1": -2.06,
199 + "se_firm_pct": 0.43
200 + }
201 + }
202 + },
203 + "year_fe": {
204 + "formula": "fwd_w ~ spid_ai_any + spid_ai_any:post + z_AI_COMPOS + z_AI_COMPOS:post + z_size + z_mom + C(fiscal_year)",
205 + "n": 25135,
206 + "r2": 0.1718,
207 + "rank": 21,
208 + "k": 21,
209 + "cond": 18.95,
210 + "n_firms": 2125,
211 + "n_years": 15,
212 + "coef": {
213 + "spid_ai_any": {
214 + "coef_pct": -1.86,
215 + "t_firm": -0.49,
216 + "t_year": -0.6,
217 + "t_twoway": -0.52,
218 + "t_hc1": -0.56,
219 + "se_firm_pct": 3.82
220 + },
221 + "spid_ai_any:post": {
222 + "coef_pct": 5.09,
223 + "t_firm": 0.85,
224 + "t_year": 0.61,
225 + "t_twoway": 0.61,
226 + "t_hc1": 0.86,
227 + "se_firm_pct": 6.01
228 + },
229 + "z_AI_COMPOS": {
230 + "coef_pct": 2.26,
231 + "t_firm": 6.72,
232 + "t_year": 2.47,
233 + "t_twoway": 2.47,
234 + "t_hc1": 6.86,
235 + "se_firm_pct": 0.34
236 + },
237 + "z_AI_COMPOS:post": {
238 + "coef_pct": 0.33,
239 + "t_firm": 0.36,
240 + "t_year": 0.2,
241 + "t_twoway": 0.2,
242 + "t_hc1": 0.36,
243 + "se_firm_pct": 0.9
244 + },
245 + "z_size": {
246 + "coef_pct": -7.0,
247 + "t_firm": -15.62,
248 + "t_year": -4.3,
249 + "t_twoway": -4.25,
250 + "t_hc1": -18.99,
251 + "se_firm_pct": 0.45
252 + },
253 + "z_mom": {
254 + "coef_pct": -0.93,
255 + "t_firm": -2.26,
256 + "t_year": -0.84,
257 + "t_twoway": -0.85,
258 + "t_hc1": -2.14,
259 + "se_firm_pct": 0.41
260 + }
261 + }
262 + }
263 + },
264 + "did": {
265 + "binary": {
266 + "coef_pct": 2.54,
267 + "t_twoway": 0.98,
268 + "n": 24909
269 + },
270 + "continuous": {
271 + "coef_pct": 1.11,
272 + "t_twoway": 0.69,
273 + "n": 24909
274 + }
275 + }
276 +}
\ No newline at end of file
added scripts/r3k_11_t17_refit.py +68 −0
@@ -0,0 +1,68 @@
1 +"""r3k_11_t17_refit.py — Stage D (FIRC-0002, package L1, cluster C: DATA-01, ECON-04, EDIT-02, ADVE-04, AI_C-02, LITE-04, THEO-01).
2 +Table 17 (tab_did_spid) refit on the COMMITTED analysis panel (staged read-only copy, vintage 2026-10-28, the
3 +post-r3k_04 overwrite) with an explicit-terms formula, design-matrix rank / condition number, firm-clustered,
4 +two-way (firm x year, Cameron-Gelbach-Miller) and HC1 standard errors, N and R2, plus a cohort(year)-FE variant.
5 +Also re-estimates the two DiD rows of the same table (unchanged specification) so the table has one producing script.
6 +Outputs: results/r3k_v2/t17_refit.json, paper/tables/tab_did_spid.tex (regenerated; BEFORE = git HEAD 3cf7560).
7 +Run: OMP_NUM_THREADS=4 nice -n 10 .venv-review/bin/python -I scripts/r3k_11_t17_refit.py
8 +"""
9 +import json, warnings, numpy as np, pandas as pd, statsmodels.api as sm, statsmodels.formula.api as smf
10 +from linearmodels.panel import PanelOLS
11 +warnings.filterwarnings("ignore")
12 +D = "data/processed_r3k"; T = "results/r3k_v2"; P = "paper/tables"
13 +df = pd.read_parquet(f"{D}/analysis_panel.parquet")
14 +assert "post" in df.columns and df.columns.tolist().count("post") == 1, "duplicated post column"
15 +OUT = {"panel_vintage": "data/processed_r3k/analysis_panel.parquet (author, 2026-10-28, post-r3k_04 overwrite)", "n_panel_rows": int(len(df))}
16 +
17 +def cgm_cov(res, g1, g2):
18 + """Two-way clustered covariance (Cameron, Gelbach & Miller 2011): V_g1 + V_g2 - V_(g1 x g2), from the fitted OLS."""
19 + v1 = res.get_robustcov_results(cov_type="cluster", groups=g1).cov_params()
20 + v2 = res.get_robustcov_results(cov_type="cluster", groups=g2).cov_params()
21 + gg = pd.factorize(pd.Series(list(zip(g1, g2))))[0]
22 + v12 = res.get_robustcov_results(cov_type="cluster", groups=gg).cov_params()
23 + return v1 + v2 - v12
24 +
25 +def fit(formula, d, label):
26 + d = d.copy(); y, X = pd.DataFrame(), None
27 + r = smf.ols(formula, data=d).fit()
28 + X = r.model.exog; rank = int(np.linalg.matrix_rank(X)); cond = float(np.linalg.cond(X))
29 + g_firm = pd.factorize(d["ticker"])[0]; g_year = pd.factorize(d["fiscal_year"])[0]
30 + se_firm = np.sqrt(np.diag(r.get_robustcov_results(cov_type="cluster", groups=g_firm).cov_params()))
31 + se_year = np.sqrt(np.diag(r.get_robustcov_results(cov_type="cluster", groups=g_year).cov_params()))
32 + se_tw = np.sqrt(np.diag(cgm_cov(r, g_firm, g_year))); se_hc1 = np.sqrt(np.diag(r.get_robustcov_results(cov_type="HC1").cov_params()))
33 + names = r.model.exog_names; out = {"formula": formula, "n": int(r.nobs), "r2": round(float(r.rsquared), 4), "rank": rank, "k": int(X.shape[1]), "cond": round(cond, 2), "n_firms": int(d.ticker.nunique()), "n_years": int(d.fiscal_year.nunique()), "coef": {}}
34 + for i, v in enumerate(names):
35 + if v.startswith("C(fiscal_year)") or v == "Intercept": continue
36 + b = float(r.params.iloc[i]); out["coef"][v] = {"coef_pct": round(b * 100, 2), "t_firm": round(b / se_firm[i], 2), "t_year": round(b / se_year[i], 2), "t_twoway": round(b / se_tw[i], 2), "t_hc1": round(b / se_hc1[i], 2), "se_firm_pct": round(se_firm[i] * 100, 2)}
37 + print(label, "N", out["n"], "R2", out["r2"], "rank", rank, "/", out["k"], "cond", round(cond, 2)); return out
38 +
39 +for k, lab in [("z_SPID_TECH", "spid_tech"), ("spid_ai_any", "spid_ai_any")]:
40 + d2 = df.dropna(subset=["fwd_w", k, "z_AI_COMPOS", "z_size", "z_mom", "post"]).copy()
41 + f_base = f"fwd_w ~ {k} + post + {k}:post + z_AI_COMPOS + z_AI_COMPOS:post + z_size + z_mom"
42 + f_yfe = f"fwd_w ~ {k} + {k}:post + z_AI_COMPOS + z_AI_COMPOS:post + z_size + z_mom + C(fiscal_year)"
43 + OUT[lab] = {"base": fit(f_base, d2, lab + " base"), "year_fe": fit(f_yfe, d2, lab + " yearFE")}
44 + for v, c in OUT[lab]["base"]["coef"].items(): print(" ", v, c)
45 +
46 +# DiD rows (same specification as r3k_04 block G, re-estimated here so the table has one producing script)
47 +exp_c = df[df.fiscal_year.isin([2019, 2020, 2021])].groupby("ticker")[["ai_adoption_wmean", "ai_infrastructure_wmean"]].mean().mean(axis=1)
48 +dd = df.copy(); dd["pre_AI_cont"] = dd.ticker.map((exp_c - exp_c.mean()) / exp_c.std()); dd["treated"] = dd.ticker.map((exp_c > exp_c.median()).astype(float))
49 +did = dd.dropna(subset=["fwd_w", "pre_AI_cont", "z_size", "z_mom"]).copy(); did["tp"] = did.treated * did.post; did["cp"] = did.pre_AI_cont * did.post; did = did.set_index(["ticker", "fiscal_year"])
50 +mb = PanelOLS.from_formula("fwd_w ~ tp + z_size + z_mom + EntityEffects + TimeEffects", data=did, drop_absorbed=True).fit(cov_type="clustered", cluster_entity=True, cluster_time=True)
51 +mc = PanelOLS.from_formula("fwd_w ~ cp + z_size + z_mom + EntityEffects + TimeEffects", data=did, drop_absorbed=True).fit(cov_type="clustered", cluster_entity=True, cluster_time=True)
52 +OUT["did"] = {"binary": {"coef_pct": round(float(mb.params["tp"]) * 100, 2), "t_twoway": round(float(mb.tstats["tp"]), 2), "n": int(mb.nobs)}, "continuous": {"coef_pct": round(float(mc.params["cp"]) * 100, 2), "t_twoway": round(float(mc.tstats["cp"]), 2), "n": int(mc.nobs)}}
53 +print("DiD", OUT["did"])
54 +json.dump(OUT, open(f"{T}/t17_refit.json", "w"), indent=2)
55 +
56 +# ---- Table 17 (tab_did_spid.tex), regenerated with the three inference columns, N, R2, rank/cond in the notes ----
57 +def n(x): return f"{int(x):,}".replace(",", "{,}")
58 +body = "\\begin{tabular}{lrrrr}\n\\toprule\nSpecification & Coefficient (pp) & $t$ (firm cl.) & $t$ (firm $\\times$ year cl.) & $t$ (HC1) \\\\\n\\midrule\n"
59 +Dd = OUT["did"]
60 +body += f"DiD: treated (above-median 2019--21 exposure) $\\times$ post (firm + year FE) & {Dd['binary']['coef_pct']:+.2f} & & {Dd['binary']['t_twoway']:.2f} & \\\\\nDiD: continuous exposure $\\times$ post (firm + year FE) & {Dd['continuous']['coef_pct']:+.2f} & & {Dd['continuous']['t_twoway']:.2f} & \\\\\n\\midrule\n"
61 +for lab, l in [("spid_tech", "SPID technology projects ($z$)"), ("spid_ai_any", "Any AI-initiative project (0/1)")]:
62 + b = OUT[lab]["base"]; k = "z_SPID_TECH" if lab == "spid_tech" else "spid_ai_any"
63 + for v, vl in [(k, l), (f"{k}:post", l + " $\\times$ post"), ("z_AI_COMPOS", "AI composite ($z$)"), ("z_AI_COMPOS:post", "AI composite $\\times$ post"), ("post", "Post-launch cohort (2022--24)")]:
64 + c = b["coef"][v]; body += f"{vl} & {c['coef_pct']:+.2f} & {c['t_firm']:.2f} & {c['t_twoway']:.2f} & {c['t_hc1']:.2f} \\\\\n"
65 + y = OUT[lab]["year_fe"]["coef"][f"{k}:post"]; body += f"\\quad {l.split(' (')[0]} $\\times$ post, cohort FE instead of post dummy & {y['coef_pct']:+.2f} & {y['t_firm']:.2f} & {y['t_twoway']:.2f} & {y['t_hc1']:.2f} \\\\\n"
66 + body += f"\\quad $N$ = {n(b['n'])} firm-years ({n(b['n_firms'])} firms, {b['n_years']} cohorts); $R^2$ = {b['r2']:.3f}; design rank {b['rank']}/{b['k']}, condition number {b['cond']:.1f} \\\\\n\\addlinespace\n"
67 +body += "\\bottomrule\n\\end{tabular}"
68 +open(f"{P}/tab_did_spid.tex", "w").write(body); print("table tab_did_spid.tex")
69
Menu

Theme