SPB Git forge

spb/wp17_uqo

Public
3commits 1branches 1releases
29.6 MBsize
maindefault branch
47 min agolast push
TeX 69.3% Python 30.6%
7.8 KB · 122 lines python
Raw Blame History
1"""08_figures_ext.py -- additional figures (6-13) for the extended paper."""2import json3import numpy as np, pandas as pd4import matplotlib.pyplot as plt, matplotlib as mpl5from matplotlib.patches import Patch6mpl.rcParams.update({"font.size":11,"axes.spines.top":False,"axes.spines.right":False,7                     "figure.dpi":140,"savefig.dpi":200,"axes.grid":True,"grid.alpha":.25,8                     "font.family":"DejaVu Sans"})9D="data/processed"; F="figures"; T="results/tables"10df=pd.read_parquet(f"{D}/analysis_panel.parquet")11R2=json.load(open(f"{T}/results2.json"))12EV=json.load(open(f"{T}/results_event.json"))13NAVY="#1b3a6b"; RED="#b22222"; GREY="#9a9a9a"; GREEN="#2e7d32"; ORANGE="#e08214"1415# --- Fig 6: year-by-year Fama-MacBeth slope on AI composite ---16fm=pd.DataFrame(R2["fm_byyear"])17fig,ax=plt.subplots(figsize=(11,4.8))18cols=[RED if y>=2022 else NAVY for y in fm.year]19ax.bar(fm.year,fm.coef*100,yerr=fm.se*100*1.0,color=cols,edgecolor="k",lw=.5,capsize=3,error_kw={"alpha":.5})20ax.axhline(0,color="k",lw=.8); ax.set_xticks(fm.year); ax.tick_params(axis="x",rotation=45)21ax.set_title("Year-by-Year Fama–MacBeth Slope on the AI-Disclosure Composite\n(forward 12m return per cross-sectional SD, with size/momentum/volatility controls)",fontweight="bold")22ax.set_ylabel("Slope coefficient (pp per SD)"); ax.set_xlabel("Formation year (10-K fiscal year)")23ax.legend(handles=[Patch(color=NAVY,label="Pre-ChatGPT"),Patch(color=RED,label="Post-ChatGPT")],frameon=False)24fig.tight_layout(); fig.savefig(f"{F}/fig6_fm_byyear.png",bbox_inches="tight"); plt.close()2526# --- Fig 7: PLACEBO -- post-2022 Q5-Q1 spread for all 8 factors ---27pb=R2["placebo"]28names=list(pb.keys()); post=[pb[n]["post"][0]*100 for n in names]; isai=[pb[n]["is_ai"] for n in names]29order=np.argsort(post)[::-1]30names=[names[i] for i in order]; post=[post[i] for i in order]; isai=[isai[i] for i in order]31fig,ax=plt.subplots(figsize=(11,5))32cols=[RED if a else GREY for a in isai]33ax.barh(names,post,color=cols,edgecolor="k",lw=.5)34ax.axvline(0,color="k",lw=.8); ax.invert_yaxis()35for i,(n,v) in enumerate(zip(names,post)):36    t=pb[n]["post"][1]; ax.annotate(f"{v:+.1f} (t={t})",xy=(v,i),xytext=(4 if v>=0 else -4,0),37        textcoords="offset points",va="center",ha="left" if v>=0 else "right",fontsize=8.5)38ax.set_title("Placebo Test: Post-ChatGPT High-minus-Low Return Spread by Semantic Factor\n(AI factors in red; non-AI factors in grey)",fontweight="bold")39ax.set_xlabel("Q5 − Q1 forward 12m return, 2022–2024 (pp)")40ax.legend(handles=[Patch(color=RED,label="AI-related factor"),Patch(color=GREY,label="Non-AI factor")],frameon=False,loc="lower right")41fig.tight_layout(); fig.savefig(f"{F}/fig7_placebo.png",bbox_inches="tight"); plt.close()4243# --- Fig 8: Event-study CAR paths ---44paths=pd.read_parquet(f"{D}/event_car_paths.parquet")45fig,(ax1,ax2)=plt.subplots(1,2,figsize=(13,4.8),gridspec_kw={"width_ratios":[1.4,1]})46for c,col in [("High",RED),("Mid",ORANGE),("Low",NAVY)]:47    ax1.plot(paths.index,paths[c]*100,color=col,lw=1.8,label=f"{c} AI tercile")48ax1.axhline(0,color="k",lw=.7); ax1.axvline(pd.Timestamp("2022-11-30"),color="grey",ls="--",lw=1)49ax1.set_title("Cumulative Abnormal Return after ChatGPT,\nby pre-event AI disclosure (market-model)",fontweight="bold",fontsize=11)50ax1.set_ylabel("Mean CAR (%)"); ax1.legend(frameon=False)51ax1.tick_params(axis="x",rotation=30)52# H-L with horizon markers53ax2.plot(paths.index,paths["High_minus_Low"]*100,color="black",lw=2)54ax2.fill_between(paths.index,0,paths["High_minus_Low"]*100,color=RED,alpha=.15)55ax2.axhline(0,color="k",lw=.7); ax2.axvline(pd.Timestamp("2022-11-30"),color="grey",ls="--",lw=1)56ax2.set_title("High − Low AI CAR spread",fontweight="bold",fontsize=11)57ax2.set_ylabel("Spread (pp)"); ax2.tick_params(axis="x",rotation=30)58fig.tight_layout(); fig.savefig(f"{F}/fig8_event_car.png",bbox_inches="tight"); plt.close()5960# --- Fig 9: AI x Size double sort (pre vs post) ---61dpre=pd.read_csv(f"{T}/t6_double_sort_pre.csv",index_col=0)62dpost=pd.read_csv(f"{T}/t6_double_sort_post.csv",index_col=0)63fig,axes=plt.subplots(1,2,figsize=(12,4.6),sharey=True)64x=np.arange(3); w=.35; lab=["Lo","Mid","Hi"]65for ax,(tt,dd) in zip(axes,[("Pre-ChatGPT (2010–2021)",dpre),("Post-ChatGPT (2022–2024)",dpost)]):66    ax.bar(x-w/2,dd.loc["Small",lab],w,label="Small firms",color=NAVY,edgecolor="k",lw=.5)67    ax.bar(x+w/2,dd.loc["Large",lab],w,label="Large firms",color=ORANGE,edgecolor="k",lw=.5)68    ax.set_xticks(x); ax.set_xticklabels(["Low AI","Mid AI","High AI"]); ax.set_title(tt)69axes[0].set_ylabel("Mean forward 12m return (%)"); axes[0].legend(frameon=False)70fig.suptitle("AI Disclosure × Firm Size Double Sort",fontweight="bold",y=1.02)71fig.tight_layout(); fig.savefig(f"{F}/fig9_double_sort.png",bbox_inches="tight"); plt.close()7273# --- Fig 10: Mega-cap exclusion robustness ---74mg=R2["mag7"]75labs=["Post baseline","Post ex-Mag7","Post ex-top10","Pre ex-Mag7"]76keys=["baseline_post","ex_mag7_post","ex_top10_post","ex_mag7_pre"]77vals=[mg[k][0]*100 for k in keys]; ts=[mg[k][1] for k in keys]78fig,ax=plt.subplots(figsize=(9,4.4))79cols=[RED,RED,RED,NAVY]80b=ax.bar(labs,vals,color=cols,edgecolor="k",lw=.5)81for i,(v,t) in enumerate(zip(vals,ts)): ax.annotate(f"{v:+.1f}\n(t={t})",xy=(i,v),xytext=(0,4),textcoords="offset points",ha="center",fontsize=9)82ax.axhline(0,color="k",lw=.8); ax.set_ylabel("AI composite Q5−Q1 (pp)")83ax.set_title("Robustness: AI Return Spread Excluding Mega-Cap Winners",fontweight="bold")84fig.tight_layout(); fig.savefig(f"{F}/fig10_mag7.png",bbox_inches="tight"); plt.close()8586# --- Fig 11: event-time DiD coefficient profile ---87et=pd.DataFrame(R2["did_eventtime"])88fig,ax=plt.subplots(figsize=(10,4.6))89ax.errorbar(et.year,et.coef*100,yerr=et.se*100*1.96,fmt="o-",color=NAVY,capsize=3,lw=1.6)90ax.axhline(0,color="k",lw=.7); ax.axvline(2021.5,color=RED,ls="--",lw=1.2)91ax.annotate("ChatGPT",xy=(2021.55,ax.get_ylim()[1]*.8),color=RED,fontsize=9)92ax.set_title("Return Premium on Pre-Determined (2019–21) AI Exposure, by Year\n(event-study coefficients, 95% CI)",fontweight="bold")93ax.set_ylabel("Premium (pp per SD of fixed AI exposure)"); ax.set_xlabel("Formation year")94ax.set_xticks(et.year)95fig.tight_layout(); fig.savefig(f"{F}/fig11_eventtime.png",bbox_inches="tight"); plt.close()9697# --- Fig 12: AI score distribution by sector (boxplot) ---98order=df.groupby("sector")["ai_infrastructure_wmean"].median().sort_values().index.tolist()99data=[df[df.sector==s]["ai_adoption_wmean"].dropna().values for s in order]100fig,ax=plt.subplots(figsize=(11,5))101bp=ax.boxplot(data,vert=False,patch_artist=True,showfliers=False,widths=.6)102for patch in bp["boxes"]: patch.set_facecolor(NAVY); patch.set_alpha(.6)103for med in bp["medians"]: med.set_color(RED); med.set_linewidth(1.6)104ax.set_yticklabels(order); ax.set_xlabel("AI-adoption score (section-weighted cosine similarity)")105ax.set_title("AI-Adoption Disclosure Intensity by GICS Sector (2010–2026)",fontweight="bold")106fig.tight_layout(); fig.savefig(f"{F}/fig12_sector_box.png",bbox_inches="tight"); plt.close()107108# --- Fig 13: cross-sectional event CAR_1y vs pre-event AI exposure (binscatter) ---109car=pd.read_parquet(f"{D}/event_car.parquet").dropna(subset=["car_1y","ai_pre"])110car=car.copy(); car["bin"]=pd.qcut(car.ai_pre,12,labels=False,duplicates="drop")111b=car.groupby("bin").agg(x=("ai_pre","mean"),y=("car_1y","mean"))112fig,ax=plt.subplots(figsize=(8.5,5))113ax.scatter(b.x,b.y*100,color=NAVY,s=48,zorder=3)114z=np.polyfit(b.x,b.y*100,1); xs=np.linspace(b.x.min(),b.x.max(),50)115ax.plot(xs,np.polyval(z,xs),color=RED,lw=2)116ax.axhline(0,color="k",lw=.6)117ax.set_title(f"1-Year Abnormal Return after ChatGPT vs. Pre-Event AI Exposure\n(binned; slope ≈ {z[0]:.1f} pp per SD)",fontweight="bold")118ax.set_xlabel("Pre-event AI disclosure (FY2021, z-score)"); ax.set_ylabel("Market-model CAR, +1 year (%)")119fig.tight_layout(); fig.savefig(f"{F}/fig13_event_binscatter.png",bbox_inches="tight"); plt.close()120121print("figures 6-13 written")122