spb/uqo-research-db
Public
1"""Determinants of announced project capital.23DV: log(total_amount_usd) for the 9,321 projects with a stated dollar figure.4Hedonic-style regressions with project, status, sector, year and firm fixed5effects; cluster-robust SE by firm.6"""7from __future__ import annotations89import warnings10import numpy as np11import pandas as pd12import statsmodels.formula.api as smf1314from common import DATA, set_style, savefig, write_table, save_results15import matplotlib.pyplot as plt1617warnings.filterwarnings("ignore")18set_style()1920p = pd.read_parquet(DATA / "projects.parquet")21d = p[p["has_amount"]].copy()22d["ann_year"] = d["ann_year"].astype(int)23d["sector"] = d["sector"].fillna("Unknown")24d["log_nfil"] = np.log(d["n_filings"].clip(lower=1))25d["is_ai_i"] = d["is_ai"].astype(int)26# baseline type = the modal type so coefficients read as deviations27base_type = d["project_type"].mode().iloc[0]28d["project_type"] = pd.Categorical(d["project_type"],29 categories=[base_type] + [t for t in d["project_type"].unique() if t != base_type])30groups = d["cik"]3132# Model 1: sector + year FE33m1 = smf.ols("log_amount ~ is_ai_i + n_tech + log_nfil + C(status) + C(project_type) + C(sector) + C(ann_year)",34 data=d).fit(cov_type="cluster", cov_kwds={"groups": groups})35# Model 2: firm + year FE (absorb 500 firm dummies)36m2 = smf.ols("log_amount ~ is_ai_i + n_tech + log_nfil + C(status) + C(project_type) + C(cik) + C(ann_year)",37 data=d).fit(cov_type="cluster", cov_kwds={"groups": groups})3839KEEP = ["is_ai_i", "n_tech", "log_nfil",40 "C(status)[T.in_progress]", "C(status)[T.completed]", "C(status)[T.planned]"]41LBL = {"is_ai_i": "AI project", "n_tech": "\\# technologies", "log_nfil": "log(\\# filings)",42 "C(status)[T.in_progress]": "Status: in progress", "C(status)[T.completed]": "Status: completed",43 "C(status)[T.planned]": "Status: planned"}444546def fmt(m, k):47 if k not in m.params.index:48 return None49 b, se, pv = m.params[k], m.bse[k], m.pvalues[k]50 star = "***" if pv < .01 else "**" if pv < .05 else "*" if pv < .1 else ""51 return f"{b:.3f}{star}", f"({se:.3f})"525354lt = [r"\begin{tabular}{lcc}", r"\toprule",55 r" & (1) Sector+Year FE & (2) Firm+Year FE \\", r"\midrule"]56for k in KEEP:57 a, b = fmt(m1, k), fmt(m2, k)58 lt.append(f"{LBL[k]} & {a[0] if a else ''} & {b[0] if b else ''} \\\\")59 lt.append(f" & {a[1] if a else ''} & {b[1] if b else ''} \\\\")60lt += [r"\midrule",61 r"Project-type FE & Yes & Yes \\",62 r"Sector FE & Yes & -- \\",63 r"Firm FE & -- & Yes \\",64 r"Year FE & Yes & Yes \\",65 f"Observations & {int(m1.nobs):,} & {int(m2.nobs):,} \\\\",66 f"$R^2$ & {m1.rsquared:.3f} & {m2.rsquared:.3f} \\\\",67 r"\bottomrule", r"\end{tabular}"]68write_table("tab_amount_models", "\n".join(lt))6970# ---- coefficient plot: project-type premia (model 2) ----------------------71TYPE_LABEL = {72 "ma_integration": "M&A integration", "capex_program": "Capex program",73 "plant_construction": "Plant construction", "infrastructure": "Infrastructure",74 "data_center": "Data center", "energy_transition": "Energy transition",75 "product_launch": "Product launch", "rd_program": "R&D program",76 "ai_initiative": "AI initiative", "sustainability": "Sustainability",77 "cost_reduction": "Cost reduction", "geographic_expansion": "Geo. expansion",78 "manufacturing_expansion": "Mfg. expansion", "partnership": "Partnership",79 "supply_chain": "Supply chain", "automation": "Automation",80 "cloud_migration": "Cloud migration", "digital_transformation": "Digital transf.",81 "erp_implementation": "ERP", "technology_deployment": "Tech. deployment",82 "drug_pipeline": "Drug pipeline",83}84rows = []85for k in m2.params.index:86 if k.startswith("C(project_type)[T."):87 t = k.split("[T.")[1].rstrip("]")88 rows.append((TYPE_LABEL.get(t, t), m2.params[k], m2.bse[k]))89rows.sort(key=lambda r: r[1])90fig, ax = plt.subplots(figsize=(6.8, 6.2))91ys = range(len(rows))92ax.errorbar([r[1] for r in rows], list(ys),93 xerr=[1.96 * r[2] for r in rows], fmt="o", color="#1b3a5b", capsize=3, ms=4)94ax.axvline(0, color="grey", ls="--", lw=0.8)95ax.set_yticks(list(ys)); ax.set_yticklabels([r[0] for r in rows], fontsize=8)96ax.set_xlabel(f"log-capital premium vs. {TYPE_LABEL.get(base_type, base_type)} (firm+year FE)")97ax.set_title("Announced-capital premia by project type")98savefig(fig, "fig_amount_coef.pdf")99100save_results("amount_models", {101 "n": int(m1.nobs), "baseline_type": base_type,102 "r2_sector": float(m1.rsquared), "r2_firm": float(m2.rsquared),103 "ai_coef": [float(m2.params["is_ai_i"]), float(m2.bse["is_ai_i"]), float(m2.pvalues["is_ai_i"])],104 "ntech_coef": [float(m2.params["n_tech"]), float(m2.pvalues["n_tech"])],105 "ai_pct_effect": float(np.exp(m2.params["is_ai_i"]) - 1),106})107print("amount models done | R2(firm FE) =", round(m2.rsquared, 3))108