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;
3 REPLIES 3
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

asb_ro
Calcite | Level 5

I had a late-70s era All Star Baseball game that my brother and I played countless times growing up in the 80's.

 

I rediscovered the game at my parents' house last week.  That led me to search for a template for creating new cards for my 9 year old who loves baseball.  So excited to find this! My son and I worked together this afternoon to create 20 new cards for his favorite players, and now I can introduce him to one of my favorite games from when I was his age.

 

Thank you for your efforts, 7 years ago, and for sharing them with the community!

hackathon24-white-horiz.png

The 2025 SAS Hackathon Kicks Off on June 11!

Watch the live Hackathon Kickoff to get all the essential information about the SAS Hackathon—including how to join, how to participate, and expert tips for success.

YouTube LinkedIn

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
  • 3 replies
  • 3055 views
  • 6 likes
  • 3 in conversation