Good Evening Fellow SAS Boffins,
I am in the process of replacing a number of manually updated graphs in a Word document with SAS (9.4m4) alternatives. I have hit a dead end when it comes to replicating a Pie of a Pie Chart similar to the image below.
I am not married to the concept of using SAS/GRAPH and ODS if there is an AMO or VA alternative I would be keen to explore the idea.
Any help would be greatly appreciated.
Hello, I found this topic while I was hanging out at PharmaSUG and thought it looked like a fun challenge. I made an example of doing this with the CARS dataset within the SASHELP library.
The program gets the frequencies (I get rid of hybrid due to it's very small size in the example). I make the pie slices with the Polygon plot, the outlines with series plot, and the percentages with TEXT plot. Note I have to do a great bit of trigonometry to make this work where I assume the left circle has a radius of 1. If you need to change the size of this circle then some changes to code would need to happen. I force any percentages smaller than 8% into a separate group, and then make the second pie chart based on them. I have a macro variable in a %let to determine the distance between the circles. It should autosize the second circle based on this macro variable. There are many tweaks I could do to make it look better, but I think it's a fun start.
proc freq data=sashelp.cars noprint;
table type / out=frq;
where type ^='Hybrid';
run;
proc sql;
create table plota as
select type,sum(percent) as percent from
(select ifc(percent lt 8,' ',type) as type,(percent lt 8) as other,percent from frq)
group by type;
create table plotb as
select type,percent,percent/sum(percent) as ppt,sum(percent) as othpct from frq where percent lt 8;
quit;
%let dist=2;
data plota2 (drop=text xtext ytext) texta (keep=text xtext ytext);
set plota end=last;
retain endpoint id;
percent=percent/100;
if type=' ' then do;
id=1;
text=strip(put(percent*100,12.0))||'%';
xtext=0.5*cos(0);ytext=0.5*sin(0);output texta;
series=1;x=0;y=0;output plota2;
series=0;
do i = (2*constant('pi'))*(-percent/2) to (2*constant('pi'))*(percent/2) by (2*constant('pi'))*(percent/50);
x=1*cos(i);
y=1*sin(i);
output plota2;
end;
series=1;x=x*(&dist/cos(percent*constant('pi')));y=y*(&dist/cos(percent*constant('pi')));output plota2;
endpoint=percent/2;
end;
else do;
id=id+1;
text=strip(put(percent*100,12.0))||'%';
xtext=0.6*cos(2*constant('pi')*(percent/2+endpoint));ytext=0.6*sin(2*constant('pi')*(percent/2+endpoint));output texta;
series=1;x=0;y=0;output plota2;
series=0;
do i = (2*constant('pi'))*(endpoint) to (2*constant('pi'))*(percent+endpoint) by (2*constant('pi'))*(percent/50);
x=1*cos(i);
y=1*sin(i);
output plota2;
end;
if last then do;
series=1;x=x*(&dist/cos(percent*constant('pi')));y=y*(&dist/cos(percent*constant('pi')));output plota2;
end;
else do;
series=1;output plota2;
end;
endpoint=endpoint+percent;
end;
run;
data plotb2(drop=text xtext ytext) textb (keep=text xtext ytext);
set plotb;
retain endpoint id;
if _n_=1 then do;
endpoint=0;
call symput('radius',strip(put(2*&dist*tan(othpct/100*constant('pi')),best12.)));
end;
id+1;
text=strip(put(percent,12.0))||'%';
xtext=&dist+0.5*(&dist*tan(othpct/100*constant('pi')))*cos(2*constant('pi')*(ppt/2+endpoint));
ytext=0.5*(&dist*tan(othpct/100*constant('pi')))*sin(2*constant('pi')*(ppt/2+endpoint));output textb;
series=1;x=&dist;y=0;output plotb2;
series=0;
do i = (2*constant('pi'))*(endpoint) to (2*constant('pi'))*(ppt+endpoint) by (2*constant('pi'))*(ppt/50);
x=&dist+(&dist*tan(othpct/100*constant('pi')))*cos(i);
y=(&dist*tan(othpct/100*constant('pi')))*sin(i);
output plotb2;
end;
series=1;output plotb2;
endpoint=endpoint+ppt;
run;
data text;
set texta textb;
run;
data plot;
set plota2 (in=a) plotb2 (in=b);
retain lastid;
if a then lastid=id;
else if b then do;
circle=2;
id=id+lastid;
end;
run;
data plot;
merge plot text;
run;
ods path WORK.TEMPLAT(UPDATE) SASHELP.TMPLMST (READ);
proc template;
define statgraph pieofpie;
begingraph / designheight=5in designwidth=10in;
layout overlay / xaxisopts=(display=none linearopts=(viewmin=-1.1 viewmax=4)) yaxisopts=(display=none linearopts=(viewmin=-1.1 viewmax=1.1));
/*Left Pie Chart*/
polygonplot x=eval(ifn(circle^=2,x,.)) y=y id=id / outlineattrs=(color=black pattern=1) display=(fill) group=type name='p1';
seriesplot x=eval(ifn(series=1 and circle^=2,x,.)) y=y / lineattrs=(color=black pattern=1) group=id;
polygonplot x=eval(ifn(circle=2,x,.)) y=y id=id / outlineattrs=(color=black pattern=1) display=(fill) group=type name='p2';
seriesplot x=eval(ifn(series=1 and circle=2,x,.)) y=y / lineattrs=(color=black pattern=1) group=id;
DRAWOVAL X=0 Y=0 WIDTH=2 HEIGHT=2 / display=(outline) outlineattrs=(color=black) heightunit=data
drawspace=datavalue widthunit=data;
DRAWOVAL X=&dist Y=0 WIDTH=%sysevalf(&radius) HEIGHT=%sysevalf(&radius) / display=(outline) outlineattrs=(color=black) heightunit=data
drawspace=datavalue widthunit=data;
textplot x=xtext y=ytext text=text / textattrs=(size=16pt) position=center;
discretelegend 'p1' / exclude=(' ') location=inside halign=right valign=center border=false across=1 valueattrs=(size=18pt)
displayclipped=true;
endlayout;
endgraph;
end;
run;
ods graphics / reset scale=off imagename='pieofpie';
proc sgrender data=plot template=pieofpie;
run;
Well, I don't know myself, have a look through this blog, its my goto to find anything graph orientated, and has examples:
http://blogs.sas.com/content/graphicallyspeaking/
It may be that you need to create two graphs side by side, only the right one with a legend:
https://blogs.sas.com/content/graphicallyspeaking/2012/03/26/let-them-eat-pie/
Something like that with two pie charts.
Thanks @RW9. I have spent a fair chunk of the day going through that blog (and others). It is a great resource.
I was also considering the concept of having 2 pie graphs side by side, but I doubt the stakeholders will buy into it. They are pretty committed to the existing look and feel, but may have to make some concessions.
Thank you again.
@ Robert Allison might make it happen via SAS/GRAPH .
UPDATED:
I know it is too old.
I just want to post my code (PROC SGPLOT) for somebody who need it.
proc format;
value $fmt(default=80)
'Very Heavy (> 25)','Moderate (6-15)','Light (1-5)'=' other'; /*add a white blank before 'other' is to make 'other' appeared at first place in PERCENT_MAIN dataset*/
run;
data have;
set sashelp.heart(keep=Smoking_Status rename=(Smoking_Status=type) where=(type is not missing));
category=put(type,$fmt.);
run;
proc freq data=have noprint order=internal;
table category/out=percent_main; /*get main pie percent*/
table category*Type/out=percent_all; /*get sub pie percent*/
run;
/*process main pie*/
data main_temp;
set percent_main;
PERCENT=0.01*PERCENT;
if _n_=1 then percent_cum=PERCENT/2; /*when category='other'*/
else percent_cum+PERCENT;
keep category percent_cum PERCENT;
format PERCENT percent8.2;
run;
data main_pie;
set main_temp;
pi=constant('pi');
lag_percent_cum=lag(percent_cum);
id=_n_;x=0;y=0;output;
if _n_=1 then do; /*when category='other'*/
do theta=-2*pi*percent_cum to 2*pi*percent_cum by 0.001;
x=cos(theta);y=sin(theta); output;
end;
end;
else do;
do theta=2*pi*lag_percent_cum to 2*pi*percent_cum by 0.001;
x=cos(theta);y=sin(theta); output;
end;
end;
keep category id x y PERCENT;
run;
/*Keep line position*/
data line_pos(rename=(id=line_id x=line_x y=line_y));
set main_pie(where=(id=1 and x ne 0)) end=last;
if _n_=1 then do;output;x=2;y=-0.5;output;end;
if last then do;id=id+1;output;x=2;y=0.5;output;end;
keep id x y;
run;
/*process sub pie*/
proc sql;
create table sub_temp as
select type,0.01*PERCENT as PERCENT format=percent8.2,PERCENT/sum(PERCENT) as new_percent
from percent_all
where strip(category)='other';
quit;
data sub_temp;
set sub_temp(rename=(type=category));
percent_cum+new_percent;
keep category percent_cum PERCENT;
run;
data sub_pie;
set sub_temp;
pi=constant('pi');
lag_percent_cum=coalesce(lag(percent_cum),0);
id=10000+_n_;x=2;y=0;output;
do theta=2*pi*lag_percent_cum to 2*pi*percent_cum by 0.001;
x=2+0.5*cos(theta);y=0.5*sin(theta); output;
end;
keep category id x y PERCENT;
run;
/*Plot pie of pie chart*/
data pie;
length category $ 200;
set main_pie sub_pie line_pos;
run;
ods graphics/noborder ANTIALIAS ANTIALIASMAX=10000000 width=800px height=600px;
proc sgplot data=pie aspect=0.6;
series x=line_x y=line_y/group=line_id lineattrs=(color=black);
polygon id=id x=x y=y/group=category label=PERCENT labelattrs=(color=black size=12) fill nooutline dataskin=sheen
LABELLOC=INSIDEBBOX LABELPOS=CENTER name='x';
xaxis display=none;
yaxis display=none;
keylegend 'x'/location=inside position=se across=1 exclude=('other' ' ') autoitemsize VALUEATTRS=(size=10);
run;
Hello, I found this topic while I was hanging out at PharmaSUG and thought it looked like a fun challenge. I made an example of doing this with the CARS dataset within the SASHELP library.
The program gets the frequencies (I get rid of hybrid due to it's very small size in the example). I make the pie slices with the Polygon plot, the outlines with series plot, and the percentages with TEXT plot. Note I have to do a great bit of trigonometry to make this work where I assume the left circle has a radius of 1. If you need to change the size of this circle then some changes to code would need to happen. I force any percentages smaller than 8% into a separate group, and then make the second pie chart based on them. I have a macro variable in a %let to determine the distance between the circles. It should autosize the second circle based on this macro variable. There are many tweaks I could do to make it look better, but I think it's a fun start.
proc freq data=sashelp.cars noprint;
table type / out=frq;
where type ^='Hybrid';
run;
proc sql;
create table plota as
select type,sum(percent) as percent from
(select ifc(percent lt 8,' ',type) as type,(percent lt 8) as other,percent from frq)
group by type;
create table plotb as
select type,percent,percent/sum(percent) as ppt,sum(percent) as othpct from frq where percent lt 8;
quit;
%let dist=2;
data plota2 (drop=text xtext ytext) texta (keep=text xtext ytext);
set plota end=last;
retain endpoint id;
percent=percent/100;
if type=' ' then do;
id=1;
text=strip(put(percent*100,12.0))||'%';
xtext=0.5*cos(0);ytext=0.5*sin(0);output texta;
series=1;x=0;y=0;output plota2;
series=0;
do i = (2*constant('pi'))*(-percent/2) to (2*constant('pi'))*(percent/2) by (2*constant('pi'))*(percent/50);
x=1*cos(i);
y=1*sin(i);
output plota2;
end;
series=1;x=x*(&dist/cos(percent*constant('pi')));y=y*(&dist/cos(percent*constant('pi')));output plota2;
endpoint=percent/2;
end;
else do;
id=id+1;
text=strip(put(percent*100,12.0))||'%';
xtext=0.6*cos(2*constant('pi')*(percent/2+endpoint));ytext=0.6*sin(2*constant('pi')*(percent/2+endpoint));output texta;
series=1;x=0;y=0;output plota2;
series=0;
do i = (2*constant('pi'))*(endpoint) to (2*constant('pi'))*(percent+endpoint) by (2*constant('pi'))*(percent/50);
x=1*cos(i);
y=1*sin(i);
output plota2;
end;
if last then do;
series=1;x=x*(&dist/cos(percent*constant('pi')));y=y*(&dist/cos(percent*constant('pi')));output plota2;
end;
else do;
series=1;output plota2;
end;
endpoint=endpoint+percent;
end;
run;
data plotb2(drop=text xtext ytext) textb (keep=text xtext ytext);
set plotb;
retain endpoint id;
if _n_=1 then do;
endpoint=0;
call symput('radius',strip(put(2*&dist*tan(othpct/100*constant('pi')),best12.)));
end;
id+1;
text=strip(put(percent,12.0))||'%';
xtext=&dist+0.5*(&dist*tan(othpct/100*constant('pi')))*cos(2*constant('pi')*(ppt/2+endpoint));
ytext=0.5*(&dist*tan(othpct/100*constant('pi')))*sin(2*constant('pi')*(ppt/2+endpoint));output textb;
series=1;x=&dist;y=0;output plotb2;
series=0;
do i = (2*constant('pi'))*(endpoint) to (2*constant('pi'))*(ppt+endpoint) by (2*constant('pi'))*(ppt/50);
x=&dist+(&dist*tan(othpct/100*constant('pi')))*cos(i);
y=(&dist*tan(othpct/100*constant('pi')))*sin(i);
output plotb2;
end;
series=1;output plotb2;
endpoint=endpoint+ppt;
run;
data text;
set texta textb;
run;
data plot;
set plota2 (in=a) plotb2 (in=b);
retain lastid;
if a then lastid=id;
else if b then do;
circle=2;
id=id+lastid;
end;
run;
data plot;
merge plot text;
run;
ods path WORK.TEMPLAT(UPDATE) SASHELP.TMPLMST (READ);
proc template;
define statgraph pieofpie;
begingraph / designheight=5in designwidth=10in;
layout overlay / xaxisopts=(display=none linearopts=(viewmin=-1.1 viewmax=4)) yaxisopts=(display=none linearopts=(viewmin=-1.1 viewmax=1.1));
/*Left Pie Chart*/
polygonplot x=eval(ifn(circle^=2,x,.)) y=y id=id / outlineattrs=(color=black pattern=1) display=(fill) group=type name='p1';
seriesplot x=eval(ifn(series=1 and circle^=2,x,.)) y=y / lineattrs=(color=black pattern=1) group=id;
polygonplot x=eval(ifn(circle=2,x,.)) y=y id=id / outlineattrs=(color=black pattern=1) display=(fill) group=type name='p2';
seriesplot x=eval(ifn(series=1 and circle=2,x,.)) y=y / lineattrs=(color=black pattern=1) group=id;
DRAWOVAL X=0 Y=0 WIDTH=2 HEIGHT=2 / display=(outline) outlineattrs=(color=black) heightunit=data
drawspace=datavalue widthunit=data;
DRAWOVAL X=&dist Y=0 WIDTH=%sysevalf(&radius) HEIGHT=%sysevalf(&radius) / display=(outline) outlineattrs=(color=black) heightunit=data
drawspace=datavalue widthunit=data;
textplot x=xtext y=ytext text=text / textattrs=(size=16pt) position=center;
discretelegend 'p1' / exclude=(' ') location=inside halign=right valign=center border=false across=1 valueattrs=(size=18pt)
displayclipped=true;
endlayout;
endgraph;
end;
run;
ods graphics / reset scale=off imagename='pieofpie';
proc sgrender data=plot template=pieofpie;
run;
@JeffMeyers that is a truly outstanding solution. I can't wait to try it.
Are you ready for the spotlight? We're accepting content ideas for SAS Innovate 2025 to be held May 6-9 in Orlando, FL. The call is open until September 16. Read more here about why you should contribute and what is in it for you!
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.
Select SAS Training centers are offering in-person courses. View upcoming courses for: