/* Program to accompany the article "A statistical analysis of Stephen Curry's shooting" published 21Mar2016, The DO Loop, http://blogs.sas.com/content/iml/2016/03/21/statistical-analysis-stephen-curry-shooting.html The data are described and obtained from Robert Allison's article "How to graph NBA data with SAS" published 14MAR2016, SAS Learning Post, http://blogs.sas.com/content/sastraining/2016/03/14/how-to-graph-nba-data-with-sas/ */ libname nba "C:\Users\frwick\Documents\My SAS Files\Blog"; proc format; value ShotFmt 1='Made' 0='Missed'; run; data Curry; set nba.Nba_Stephen_Curry(rename=(Shot_Made_Flag = Shot_Made)); keep Shot_Made X Y Shot_Distance Angle Loc_X Loc_Y Seconds_Remaining; X = Loc_x / 10; Y = Loc_Y / 10; /* (LOC_X, LOC_Y) in 1/10 feet */ Angle = atan2(Y, X) * 180 / constant("pi"); if y<0 && x<0 then Angle=Angle+360; label Shot_Made = "Shot Made" Angle = "Angle (degrees)" Shot_Distance = "Distance (feet)"; format Shot_Made ShotFmt.; run; proc means data=Curry; var Angle X Y Shot_Made Shot_Distance; run; /* examine shots greater than 30 feet. Discover that theese are "buzzer beaters" */ proc means data=Curry; where Shot_Distance <= 30; var Angle X Y Shot_Made Shot_Distance; run; proc sgplot data=Curry; scatter x=Seconds_Remaining y=Shot_Distance / group=Shot_Made; run; data test; set Curry; in30 = (Shot_Distance<=30); run; proc freq data=test; tables in30; run; /************************/ /* Show graph of shot locations */ %macro RGB2CX(r,g,b); CX%sysfunc(putn(&r.,hex2.))%sysfunc( putn(&g.,hex2.))%sysfunc(putn(&b.,hex2.)) %mend; ods graphics / width=450px height=500px; title "Stephen Curry Shots: 2015-2016 Season"; title2 "Through March 14, 2016"; /* half-court line is 47 feet; 3pt is 23.9 or 22 feet */ ods graphics / attrpriority=none imagemap=on tipmax=3000; proc sgplot data=Curry aspect=1; *styleattrs datacontrastcolors = (GraphData2:ContrastColor GraphData1:ContrastColor); styleattrs datacontrastcolors = (%RGB2CX(162,58,46) %RGB2CX(68,86,148)) datasymbols=(Circle X); scatter x = x y=y / group=Shot_Made tip=(Angle Shot_Distance); refline 42.25 / axis=y; /* 47 - 4.75 b/c center of basket is 4.75 ft from baseline */ xaxis min=-25 max=25; run; ods graphics / imagemap=off; /************************/ /* show graph of angles */ data angles; do Angle = 0 to 180 by 30; x=0; y=0; output; theta = Angle * constant("pi") / 180; x = cos(theta); y = sin(theta); output; end; run; ods graphics / width=400px height=230px; title "Shot Angles"; proc sgplot data=angles aspect=0.5 nocycleattrs; series x=x y=y / group=Angle curvelabel curvelabelattrs=(size=16) lineattrs=GraphData1; xaxis display=none; yaxis display=none; run; /************************/ ods graphics / reset; /* logistic model of distance */ ods html sge=on; ods select EffectPlot; ods output EffectPlot=Probs; proc logistic data=Curry plots(only)=(fitplot(extend=0)); model Shot_Made(event='Made') = Shot_Distance; run; ods html sge=off; /***************************************************/ /* from here, restrict to Shot_Distance <= 30 feet */ /***************************************************/ /* logistic model of angle and distance */ ods html sge=on; ods select fitpanel; proc logistic data=Curry; where Shot_Distance <= 30; model Shot_Made(event='Made') = Angle | Shot_Distance; effectplot fit(x=Shot_Distance) / noobs nrows=3 ncols=2 at(Angle=(120 60 150 30 180 0)); run; /* logistic model of X and Y using splines */ *ods select contourfitplot; proc logistic data=curry; where Shot_Distance <= 30; effect spl = spline(X Y / degree=2); model Shot_Made(event='Made') = spl / SCALE=NONE AGGREGATE; effectplot contour / showCLegend; run; ods html sge=off; /**********************************************************/