spb/uqo-research-db
Public
1"""Technology-adoption diffusion: AI initiatives.23(a) Firm-year logit for the probability of announcing >=1 AI project.4(b) Logistic (Bass-type) diffusion curve fit to the cumulative share of firms5 that have ever announced an AI project.6"""7from __future__ import annotations89import warnings10import numpy as np11import pandas as pd12import statsmodels.formula.api as smf13from scipy.optimize import curve_fit1415from common import DATA, set_style, savefig, write_table, save_results16import matplotlib.pyplot as plt1718warnings.filterwarnings("ignore")19set_style()2021panel = pd.read_parquet(DATA / "panel.parquet").copy()22panel["sector"] = panel["sector"].fillna("Unknown")23panel["year_c"] = panel["year"] - panel["year"].min()24panel = panel.sort_values(["cik", "year"])25panel["projects_lag"] = panel.groupby("cik")["n_projects"].shift(1).fillna(0)2627# ---- (a) logit ------------------------------------------------------------28logit = smf.logit("any_ai ~ year_c + post_covid + projects_lag + C(sector)",29 data=panel).fit(disp=0, maxiter=200)30mfx = logit.get_margeff()31me = pd.Series(mfx.margeff, index=logit.params.index[1:]) # margeff excludes const32me_se = pd.Series(mfx.margeff_se, index=logit.params.index[1:])3334KEEP = ["year_c", "post_covid", "projects_lag"]35LBL = {"year_c": "Year trend", "post_covid": "Post-COVID (2020+)", "projects_lag": "Projects$_{t-1}$"}36lt = [r"\begin{tabular}{lcc}", r"\toprule",37 r" & Coefficient & Avg. marginal effect \\", r"\midrule"]38for k in KEEP:39 b, se, pv = logit.params[k], logit.bse[k], logit.pvalues[k]40 star = "***" if pv < .01 else "**" if pv < .05 else "*" if pv < .1 else ""41 m_ = me[k]; m_se = me_se[k]42 lt.append(f"{LBL[k]} & {b:.3f}{star} & {m_:.4f} \\\\")43 lt.append(f" & ({se:.3f}) & ({m_se:.4f}) \\\\")44lt += [r"\midrule", r"Sector FE & Yes & \\",45 f"Observations & {int(logit.nobs):,} & \\\\",46 f"Pseudo $R^2$ & {logit.prsquared:.3f} & \\\\",47 r"\bottomrule", r"\end{tabular}"]48write_table("tab_ai_logit", "\n".join(lt))4950# ---- (b) diffusion S-curve ------------------------------------------------51p = pd.read_parquet(DATA / "projects.parquet")52ai = p[p["is_ai"] & (p["ann_year"].between(2010, 2025))].copy()53ai["ann_year"] = ai["ann_year"].astype(int)54first_ai = ai.groupby("cik")["ann_year"].min()55years = np.arange(2010, 2026)56n_firms = p["cik"].nunique()57cum_adopt = np.array([(first_ai <= y).sum() for y in years]) / n_firms5859def logistic(t, K, r, t0):60 return K / (1 + np.exp(-r * (t - t0)))6162t = years - 201063try:64 popt, _ = curve_fit(logistic, t, cum_adopt, p0=[0.8, 0.4, 8], maxfev=10000)65except Exception:66 popt = [cum_adopt.max(), 0.3, 8]67K, r, t0 = popt68fit = logistic(t, *popt)6970# AI share of all projects per year71share = (p[p.ann_year.between(2010, 2025)].assign(y=lambda x: x.ann_year.astype(int))72 .groupby("y")["is_ai"].mean())7374fig, axes = plt.subplots(1, 2, figsize=(10.6, 4.0))75ax = axes[0]76ax.plot(years, cum_adopt, "o", color="#c44e52", label="Observed", ms=5)77ax.plot(years, fit, "-", color="#1b3a5b", lw=2, label="Logistic fit")78ax.axvline(2010 + t0, color="grey", ls=":", lw=1)79ax.set_title("Cumulative AI-project adoption across firms")80ax.set_xlabel("Year"); ax.set_ylabel("Share of firms ever announcing AI")81ax.legend()82ax.text(0.04, 0.92, f"$K$={K:.2f}, $r$={r:.2f}, $t_0$={2010+t0:.1f}",83 transform=ax.transAxes, fontsize=9, va="top")84ax2 = axes[1]85ax2.plot(share.index, share.values, "o-", color="#2e8b57", lw=1.8)86ax2.set_title("AI share of all new projects per year")87ax2.set_xlabel("Year"); ax2.set_ylabel("AI share of announcements")88savefig(fig, "fig_ai_diffusion.pdf")8990save_results("diffusion", {91 "logit_post_covid": [float(logit.params["post_covid"]), float(logit.pvalues["post_covid"])],92 "mfx_year_trend": float(me["year_c"]), "mfx_post_covid": float(me["post_covid"]),93 "logistic_K": float(K), "logistic_r": float(r), "inflection_year": float(2010 + t0),94 "ai_share_2025": float(share.get(2025, np.nan)), "ai_share_2015": float(share.get(2015, np.nan)),95 "pseudo_r2": float(logit.prsquared),96})97print(f"diffusion done | inflection={2010+t0:.1f}, K={K:.2f}, r={r:.2f}")98