BookmarkSubscribeRSS Feed
tc
Lapis Lazuli | Level 10 tc
Lapis Lazuli | Level 10

SAS OUTPUT

KrisBryant.png

Seeing Chevell Parker pull-donut-charts-out-of-a-SAS-Excel-hat at SASGF 2017 made me try to recall, "When did I see a donut chart for the first time?" Well, SAS/GRAPH users have had donut charts in their bag-of-dataviz-tricks for about a quarter of a century. But that wasn't it. Even earlier users of donut charts, including me, were kids who grew up playing the sadly-discontinued Cadaco All-Star Baseball board game, where pie-charts-with-a-hole-in-the-middle were used to present MLB batting stats on game discs since 1941. So, after reading Prashant Hebbar and Sanjay Matange's SASGF paper on Easy Polar Graphs with SG Procedures, I thought to myself, "Could I use SAS ODS Graphics to create All-Star Baseball disc knock-offs?"

 

SAS CODE

* Fun with SAS ODS Graphics: Knock-off of All-Star Baseball "donut chart" discs;

data mlbstats;                                             * Create disc categories from MLB stats;
infile cards dlm='|' dsd firstobs=2;                       * Read MLB stats;
input Player : $30. Pos : $20. Year Team : $3. LG : $2. G AB R H TB _2B _3B HR RBI BB IBB SO SB CS AVG OBP SLG OPS GO_AO;
PlayerNum+1;                                               * Assign player sequence #;
player=upcase(player);                                     * Player name;
pos=upcase(pos);                                           * Player position;
wedgeID=01; stat=HR; output;                               * Home run;
wedgeID=02; stat=(AB-H-SO)*GO_AO/(GO_AO+1)/3; output;      * Ground out I;
wedgeID=03; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out I;
wedgeID=04; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out II;
wedgeID=05; stat=_3B; output;                              * Triple;
wedgeID=06; stat=(AB-H-SO)*GO_AO/(GO_AO+1)/3; output;      * Ground out II;
wedgeID=07; stat=(H-HR-_3B-_2B)/2; output;                 * Single I;
wedgeID=08; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out III;
wedgeID=09; stat=BB/2; output;                             * Walk I;
wedgeID=09; stat=BB/2; output;                             * Walk II;
wedgeID=10; stat=SO/2; output;                             * Strikeout I;
wedgeID=10; stat=SO/2; output;                             * Strikeout II;
wedgeID=11; stat=_2B; output;                              * Double;
wedgeID=12; stat=(AB-H-SO)*GO_AO/(GO_AO+1)/3; output;      * Ground out III;
wedgeID=13; stat=(H-HR-_3B-_2B)/2; output;                 * Single II;
wedgeID=14; stat=(AB-H-SO)*(1-GO_AO/(GO_AO+1))/4; output;  * Fly out IV;
cards;                                                     * 2016-17 MLB Cubs batting stats (source: mlb.com);
Player|Pos|Year|Team|LG|G|AB|R|H|TB|2B|3B|HR|RBI|BB|IBB|SO|SB|CS|AVG|OBP|SLG|OPS|GO_AO|
Kris*Bryant|Third Base|2016|CHC|NL|155|603|121|176|334|35|3|39|102|75|5|154|8|5|0.292|0.385|0.554|0.939|0.59|
Anthony*Rizzo|First Base|2016|CHC|NL|155|583|94|170|317|43|4|32|109|74|8|108|3|5|0.292|0.385|0.544|0.928|0.91|
Dexter*Fowler|Center Field|2016|CHC|NL|125|456|84|126|204|25|7|13|48|79|0|124|13|4|.276|.393|.447|.840|0.91
;
proc sql;                                                  * Assign % of total stats to each wedge;
create table mlbstatsrnd as 
select m.*, m.stat/s.sumstat as pctsumstat,           
      case when wedgeID^=1 then ranuni(100) end as rndseq  /* Keep HR wedge at top of disc, others random order */
from mlbstats m, (select PlayerNum, sum(stat) as sumstat from mlbstats group by 1) s 
where m.PlayerNum=s.PlayerNum order by PlayerNum, rndseq;

data mlbstatschart;                                        * Prep data for charting;
retain startradians;                                       * Wedge start (radians);
pi=constant('pi');
set mlbstatsrnd;
by PlayerNum;                                              * Center HR wedge at top;
if first.playernum then StartRadians=90/360*2*pi-.5*pctsumstat*2*pi;
xWedge=cos(StartRadians); yWedge=sin(startradians);        * Wedge start x/y coordinates;
xTxt=.84*cos(StartRadians+.5*pctsumstat*2*pi);             * Wedge category # x/y coordinates;
yTxt=.84*sin(startradians+.5*pctsumstat*2*pi);
startradians=startradians+pctsumstat*2*pi;                 * Update wedge start (radians);
if first.playernum then do;                            
  xPlayer=0; yPlayer=.55;                                  * Player name x/y coordinates;
  xPos=0; yPos=-.45;                                       * Player position x/y coordinates;
  end;

options nobyline;                                          * Time to make a disc with GTL!;
ods graphics on / reset antialias width=5in height=5in noborder;
proc template;
 define statgraph AllStarBaseballDisc;
 begingraph; 
   layout overlayequated / equatetype=square walldisplay=none
     xaxisopts=(display=none offsetmin=0.001 offsetmax=0.001)
     yaxisopts=(display=none offsetmin=0.001 offsetmax=0.001);
     ellipseparm semimajor=1 semiminor=1 slope=0           /* Grey outer circle */
       xorigin=0 yorigin=0 / display=(fill) fillattrs=(color=lightgrey);
     vectorplot x=xWedge y=yWedge xorigin=0 yorigin=0 /    /* Stat wedge dividers */
       arrowheads=false lineattrs=(color=black thickness=2pt);
     ellipseparm semimajor=.68 semiminor=.68 slope=0      /* Red inner circle */
       xorigin=0 yorigin=0 / display=all fillattrs=(color=red) outlineattrs=(color=black thickness=5pt);
     textplot x=xTxt y=yTxt text=wedgeID /                /* Stat # */
       position=center textattrs=(color=black size=20pt weight=bold) strip=true;  
     textplot x=xPlayer y=yPlayer text=Player /           /* Player name */  
       position=bottom textattrs=(color=white size=16pt weight=bold) strip=true splitchar='*' splitpolicy=splitalways;
     textplot x=xPos y=yPos text=Pos /                    /* Player position */ 
       position=top textattrs=(color=white size=16pt weight=bold) strip=true;
    endlayout;
  endGraph;
 end; 

proc sgrender data=mlbstatschart template=AllStarBaseballDisc;
by playernum;                                            * One disc per player;
2 REPLIES 2
thomp7050
Pyrite | Level 9

Nice!  You could also make it interactive with d3.js (one of my favorite js libraries):

 

https://bl.ocks.org/mbostock/5944371

 

tc
Lapis Lazuli | Level 10 tc
Lapis Lazuli | Level 10

A spinner would be fun! Smiley Happy

SAS Innovate 2025: Save the Date

 SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!

Save the date!

How to Concatenate Values

Learn how use the CAT functions in SAS to join values from multiple variables into a single value.

Find more tutorials on the SAS Users YouTube channel.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 2 replies
  • 2070 views
  • 3 likes
  • 2 in conversation