TeX 74.5%
Python 25.4%
1"""04_figures.py -- publication figures for the AI-disclosure / returns study."""2import json3import numpy as np4import pandas as pd5import matplotlib.pyplot as plt6import matplotlib as mpl78mpl.rcParams.update({"font.size":11,"axes.spines.top":False,"axes.spines.right":False,9 "figure.dpi":140,"savefig.dpi":200,"axes.grid":True,"grid.alpha":.25,10 "font.family":"DejaVu Sans"})11D="data/processed"; F="figures"12df=pd.read_parquet(f"{D}/analysis_panel.parquet")13RES=json.load(open("results/tables/results.json"))14NAVY="#1b3a6b"; RED="#b22222"; GREY="#8a8a8a"; GREEN="#2e7d32"1516# ---- Fig 1: forward return by AI quintile, full / pre / post ----17def qmeans(d, sig):18 out={}19 for q in range(1,6):20 out[q]=[]21 for yr,g in d.groupby("fiscal_year"):22 g=g.dropna(subset=[sig,"fwd_ret"])23 if len(g)<25: continue24 g=g.copy(); g["q"]=pd.qcut(g[sig],5,labels=False,duplicates="drop")+125 for q in range(1,6): out[q].append(g[g.q==q].fwd_ret.mean())26 return [np.mean(out[q]) for q in range(1,6)]2728fig,axes=plt.subplots(1,3,figsize=(13,4.2),sharey=True)29panels=[("Full sample (2010-2024)",df),("Pre-ChatGPT (2010-2021)",df[df.fiscal_year<2022]),30 ("Post-ChatGPT (2022-2024)",df[df.fiscal_year>=2022])]31for ax,(title,d) in zip(axes,panels):32 v=qmeans(d,"z_AI_COMPOS")33 bars=ax.bar(range(1,6),[x*100 for x in v],color=[GREY,GREY,GREY,GREY,NAVY],edgecolor="k",lw=.5)34 ax.set_title(title,fontsize=11); ax.set_xlabel("AI-disclosure quintile (1=low, 5=high)")35 ax.set_xticks(range(1,6))36 sp=(v[4]-v[0])*10037 ax.annotate(f"Q5−Q1 = {sp:+.1f} pp",xy=(.5,.93),xycoords="axes fraction",ha="center",38 fontsize=10,fontweight="bold",color=RED if sp>3 else "k")39axes[0].set_ylabel("Mean forward 12-month return (%)")40fig.suptitle("AI-Disclosure Intensity and Subsequent S&P 500 Returns",fontsize=13,fontweight="bold",y=1.02)41fig.tight_layout(); fig.savefig(f"{F}/fig1_quintile_returns.png",bbox_inches="tight"); plt.close()4243# ---- Fig 2: cumulative long-short (AI composite) ----44fig,ax=plt.subplots(figsize=(11,5))45for k,col,lab in [("AI_COMPOS",NAVY,"AI opportunity (adoption+infra)"),("AI_RISK",GREEN,"AI disruption-risk salience")]:46 ts=pd.read_parquet(f"{D}/ls_ts_{k}.parquet").sort_values("date")47 ts["cum"]=(1+ts["ls"]).cumprod()48 ax.plot(ts["date"],ts["cum"],color=col,lw=1.8,label=lab)49ax.axhline(1,color="k",lw=.7)50ax.axvline(pd.Timestamp("2022-11-30"),color=RED,ls="--",lw=1.3)51ax.annotate("ChatGPT\n(Nov 2022)",xy=(pd.Timestamp("2022-11-30"),ax.get_ylim()[1]*.6),52 color=RED,fontsize=9,ha="right")53ax.set_title("Cumulative Value of $1 in the High-minus-Low AI Disclosure Portfolio\n(equal-weight, annually rebalanced, self-financing)",fontweight="bold")54ax.set_ylabel("Growth of $1 (long-short)"); ax.legend(frameon=False,loc="upper left")55fig.tight_layout(); fig.savefig(f"{F}/fig2_cumulative_ls.png",bbox_inches="tight"); plt.close()5657# ---- Fig 3: annual Q5-Q1 spread bars ----58def annual_spread(sig):59 rec=[]60 for yr,g in df.groupby("fiscal_year"):61 g=g.dropna(subset=[sig,"fwd_ret"])62 if len(g)<25: continue63 g=g.copy(); g["q"]=pd.qcut(g[sig],5,labels=False,duplicates="drop")+164 rec.append((yr,(g[g.q==5].fwd_ret.mean()-g[g.q==1].fwd_ret.mean())*100))65 return pd.DataFrame(rec,columns=["year","sp"])66sp=annual_spread("z_AI_COMPOS")67fig,ax=plt.subplots(figsize=(11,4.6))68cols=[RED if y>=2022 else NAVY for y in sp.year]69ax.bar(sp.year,sp.sp,color=cols,edgecolor="k",lw=.5)70ax.axhline(0,color="k",lw=.8)71ax.set_title("Annual High-minus-Low AI-Disclosure Return Spread by Formation Year",fontweight="bold")72ax.set_ylabel("Q5 − Q1 forward 12m return (pp)"); ax.set_xlabel("Fiscal year of 10-K (signal)")73ax.set_xticks(sp.year); ax.tick_params(axis="x",rotation=45)74from matplotlib.patches import Patch75ax.legend(handles=[Patch(color=NAVY,label="Pre-ChatGPT (2010-2021)"),Patch(color=RED,label="Post-ChatGPT (2022-2024)")],frameon=False)76fig.tight_layout(); fig.savefig(f"{F}/fig3_annual_spread.png",bbox_inches="tight"); plt.close()7778# ---- Fig 4: secular rise of AI language (context) ----79fig,ax=plt.subplots(figsize=(11,4.6))80for col,c,lab in [("ai_adoption_wmean",NAVY,"AI adoption"),("ai_infrastructure_wmean",RED,"AI infrastructure"),81 ("ai_disruption_risk_wmean",GREEN,"AI disruption risk")]:82 g=df.groupby("fiscal_year")[col].mean()83 ax.plot(g.index,g.values,marker="o",color=c,label=lab,lw=1.8)84ax.axvline(2022,color="grey",ls="--",lw=1)85ax.set_title("Rising AI Language Across S&P 500 10-K Filings (cross-sectional mean similarity)",fontweight="bold")86ax.set_ylabel("Section-weighted cosine similarity to AI anchor"); ax.set_xlabel("Fiscal year")87ax.legend(frameon=False)88fig.tight_layout(); fig.savefig(f"{F}/fig4_ai_language_trend.png",bbox_inches="tight"); plt.close()8990# ---- Fig 5: binscatter AI score vs forward return, pre vs post ----91fig,axes=plt.subplots(1,2,figsize=(12,4.6),sharey=True)92for ax,(title,d) in zip(axes,[("Pre-ChatGPT (2010-2021)",df[df.fiscal_year<2022]),93 ("Post-ChatGPT (2022-2024)",df[df.fiscal_year>=2022])]):94 d=d.dropna(subset=["z_AI_COMPOS","fwd_ret"])95 d=d.copy(); d["bin"]=pd.qcut(d.z_AI_COMPOS,12,labels=False,duplicates="drop")96 b=d.groupby("bin").agg(x=("z_AI_COMPOS","mean"),y=("fwd_ret","mean"))97 ax.scatter(b.x,b.y*100,color=NAVY,s=45,zorder=3)98 z=np.polyfit(b.x,b.y*100,1); xs=np.linspace(b.x.min(),b.x.max(),50)99 ax.plot(xs,np.polyval(z,xs),color=RED,lw=2)100 ax.set_title(f"{title}\nslope={z[0]:.2f} pp / SD",fontsize=10)101 ax.set_xlabel("AI-disclosure composite (z-score)")102axes[0].set_ylabel("Mean forward 12m return (%)")103fig.suptitle("AI Disclosure vs. Subsequent Return: Binned Scatter",fontweight="bold",y=1.02)104fig.tight_layout(); fig.savefig(f"{F}/fig5_binscatter.png",bbox_inches="tight"); plt.close()105106print("figures written:", [f"fig{i}" for i in range(1,6)])107