BookmarkSubscribeRSS Feed
🔒 This topic is solved and locked. Need further help from the community? Please sign in and ask a new question.
bstarr
Quartz | Level 8

Hello SAS experts,

 

I am trying to create some plots with annotations using PROC TEMPLATE. Currently, I have the annotations set up using an annotation dataset (rather than incorporating the annotations into the PROC TEMPLATE procedure itself using statements such as drawtext). I am getting it close to working correctly, but the annotations are appearing for all BY groups in each plot. That is, BY group=1 shows annotations for all BY groups, even though I have the BY group variable in the annotations dataset.

 

Is there a way I can have the annotations show only for each corresponding BY group? Sample code to reproduce my plot is below:

 

data graphdata;
input rpt_strat $ est delta level CI;
format Est CI percentn11.2;

datalines;
20-to-44 -.0383 -0.002374357 0.05 -.0572
20-to-44 . -0.002374357 0.05 -.0195
45-to-64 -.0317 -0.004262066 0.05 -.0525
45-to-64 . -0.004262066 0.05 -.0109
;
run;
ods path(prepend) Work.Templat(update);
%SGANNO;

data anno;
set graphdata;
    if Est ~= .;
    %SGTEXT(label="Estimate", x1=Est, y1=75, x1space='datavalue', y1space='graphpercent', anchor='center',width=200, widthunit='pixel');
    keep rpt_strat;
run;

proc template;
    define statgraph CIPlot;
        begingraph / designwidth=800px designheight=200px;
            layout overlay / yaxisOpts=(display=none)
                             xAxisOpts=(label="Rate");
                annotate;
                scatterplot x=Est y=level / markerattrs=(symbol=circlefilled size=10) datalabel=Est datalabelposition=top;
                seriesplot  x=CI y =level / display=(markers) markerattrs=(symbol=ibeam size=10) datalabel=CI datalabelposition=top;
                referenceline x=delta   / lineattrs=(pattern=2);
                referenceline x=0       / lineattrs=(pattern=1);
            endlayout;
        endgraph;
    end;
run;

proc sgrender data=graphdata template=CIPlot sganno=anno;
by rpt_strat;
title1 "Non-inferiority Confidence Interval for Measure";
title2 "Ages #byval1";
run;

Note that "Estimate" is being displayed twice on each graph: it should only be displayed once per graph, above the big blue dot. Is there a way to fix this?

 

Also, if anyone knows how to suppress the automatic BY group heading "rpt_strat=20-to-44" and "rpt_strat=45-to-64" I'd appreciate that too!

 

I'm using SAS 9.4 TS1M5

 

Thank you,

Brian

 
1 ACCEPTED SOLUTION

Accepted Solutions
ballardw
Super User

@bstarr wrote:

Thank you, I was afraid of that. I will populate the annotations within PROC TEMPLATE itself. I get that's how it works, but it seems like it should work in a way that the annotation is done separately for each BY group. Just one of those things, I suppose! 🙂


Something else: a better label variable, use the splitchar options.

data graphdata;
infile datalines missover;
input rpt_strat $ est delta level CI ;
format Est CI percentn11.2;
if est ~= . then do;
   Text=catx('*',"Estimate",put(est,percentn11.2));
end;


datalines;
20-to-44 -.0383 -0.002374357 0.05 -.0572
20-to-44 . -0.002374357 0.05 -.0195
45-to-64 -.0317 -0.004262066 0.05 -.0525
45-to-64 . -0.004262066 0.05 -.0109
;
run;

proc template;
    define statgraph CIPlot;
        begingraph / designwidth=800px designheight=200px;
            layout overlay / yaxisOpts=(display=none)
                             xAxisOpts=(label="Rate");
                annotate;
                scatterplot x=Est y=level / markerattrs=(symbol=circlefilled size=10) 
                                           datalabel=text 
                                           DATALABELSPLIT=TRUE 
                                           datalabelsplitchar='*' 
                                           datalabelposition=top;
                seriesplot  x=CI y =level / display=(markers) markerattrs=(symbol=ibeam size=10) datalabel=CI datalabelposition=top;
                referenceline x=delta   / lineattrs=(pattern=2);
                referenceline x=0       / lineattrs=(pattern=1);
            endlayout;
        endgraph;
    end;
run;

proc sgrender data=graphdata template=CIPlot ;
by rpt_strat;
title1 "Non-inferiority Confidence Interval for Measure";
title2 "Ages #byval1";
run;title;

You can set some datalabelattrs to change text to some extent.

 

View solution in original post

8 REPLIES 8
ballardw
Super User

The behavior is as expected: EACH plot will plot the entire Annotate set, or at least the bits that would fit within the plot area.

 

I suggest adding another variable and using a TEXTPLOT statement with the text and desired positions to match the by variables.

 

I'm not aware of another way to get annotate sets to match on by variables only.

bstarr
Quartz | Level 8

Thank you, I was afraid of that. I will populate the annotations within PROC TEMPLATE itself. I get that's how it works, but it seems like it should work in a way that the annotation is done separately for each BY group. Just one of those things, I suppose! 🙂

ballardw
Super User

@bstarr wrote:

Thank you, I was afraid of that. I will populate the annotations within PROC TEMPLATE itself. I get that's how it works, but it seems like it should work in a way that the annotation is done separately for each BY group. Just one of those things, I suppose! 🙂


Something else: a better label variable, use the splitchar options.

data graphdata;
infile datalines missover;
input rpt_strat $ est delta level CI ;
format Est CI percentn11.2;
if est ~= . then do;
   Text=catx('*',"Estimate",put(est,percentn11.2));
end;


datalines;
20-to-44 -.0383 -0.002374357 0.05 -.0572
20-to-44 . -0.002374357 0.05 -.0195
45-to-64 -.0317 -0.004262066 0.05 -.0525
45-to-64 . -0.004262066 0.05 -.0109
;
run;

proc template;
    define statgraph CIPlot;
        begingraph / designwidth=800px designheight=200px;
            layout overlay / yaxisOpts=(display=none)
                             xAxisOpts=(label="Rate");
                annotate;
                scatterplot x=Est y=level / markerattrs=(symbol=circlefilled size=10) 
                                           datalabel=text 
                                           DATALABELSPLIT=TRUE 
                                           datalabelsplitchar='*' 
                                           datalabelposition=top;
                seriesplot  x=CI y =level / display=(markers) markerattrs=(symbol=ibeam size=10) datalabel=CI datalabelposition=top;
                referenceline x=delta   / lineattrs=(pattern=2);
                referenceline x=0       / lineattrs=(pattern=1);
            endlayout;
        endgraph;
    end;
run;

proc sgrender data=graphdata template=CIPlot ;
by rpt_strat;
title1 "Non-inferiority Confidence Interval for Measure";
title2 "Ages #byval1";
run;title;

You can set some datalabelattrs to change text to some extent.

 

bstarr
Quartz | Level 8

Nifty, thank you! I will work with this.

sdengland
SAS Employee

Another way is to use the ID column in your Anno data set to subset your annotate observations. In this case, the ID column value would be the same as the rpt_strat column. To create the ID column, add parameter id=rpt_strat to the %SGTEXT macro call in your Anno DATA step:

data anno;
set graphdata;
    if Est ~= .;
    %SGTEXT(id=rpt_strat, label="Estimate", x1=Est, y1=75, x1space='datavalue', y1space='graphpercent', anchor='center',width=200, widthunit='pixel');
    keep rpt_strat;
run;

 

In your CIPlot template code, to get the BY-Group variable value into your template, create dynamic variable _BYVAL_, and then in your ANNOTATE statement, add option ID = _BYVAL_:

proc template;
    define statgraph CIPlot;
        dynamic _BYVAL_;
        begingraph / designwidth=800px designheight=200px;
            layout overlay / yaxisOpts=(display=none)
                             xAxisOpts=(label="Rate");
                annotate / id=_BYVAL_;
                scatterplot x=Est y=level / markerattrs=(symbol=circlefilled size=10) datalabel=Est datalabelposition=top;
                seriesplot  x=CI y =level / display=(markers) markerattrs=(symbol=ibeam size=10) datalabel=CI datalabelposition=top;
                referenceline x=delta   / lineattrs=(pattern=2);
                referenceline x=0       / lineattrs=(pattern=1);
            endlayout;
        endgraph;
    end;
run;

Finally, to suppress the automatic BY-line, specify system option NOBYLINE:

 

options nobyline;
proc sgrender data=graphdata template=CIPlot sganno=anno;
by rpt_strat;
title1 "Non-inferiority Confidence Interval for Measure";
title2 "Ages #byval1";
run;
options byline; /* Restore BY-line */

 

 

You can find information about subsetting annotate observations in Subsetting Annotations in SAS Graph Template Language: User’s Guide. Hope this helps.

 

Steve

bstarr
Quartz | Level 8

Thank you, Steve. This is excellent! Does just what I need and still uses the annotation dataset.

WarrenKuhfeld
Rhodochrosite | Level 12

I had never seen or thought of Steve's trick.  Very nice!  Inspired by it, I will write a blog for graphically speaking (https://blogs.sas.com/content/graphicallyspeaking/) in the next few days that shows how to specify the graph syntax in PROC SGPLOT with a BY statement, slightly post-process the template, then create the graphs with custom SG annotations for each BY group.  The latter part is based on my multiple blogs and papers on highly customized graphs.  Here is a preview of the code (again, giving credit to Steve for the idea of adding an ID variable that matches the BY variable).  I'll explore other ways too.

 

proc sort data=sashelp.class out=c;
   by sex;
run;

data anno;
   x1 = 20; y1 = 85; function = 'Text'; dataspace = 'GraphPercent'; width = 100;
   label = 'Female Students'; id = 'F'; output;
   label = 'Male Students';   id = 'M'; output;
run;

proc sgplot data=c tmplout='tmp.tmp';
   ods exclude sgplot;
   scatter y=weight x=height;
   by sex;
run;

data _null_;
   infile 'tmp.tmp';
   input;
   _infile_ = tranwrd(_infile_, 'sgplot;', 'by;');
   if _n_ eq 1 then call execute('proc template;');
   call execute(_infile_);
   if find(_infile_, 'layout overlay') then 
      call execute('dynamic _byval_; annotate / id=_byval_;');
run;      

ods html body='b.html';
title;
options nobyline;
proc sgrender data=c template=by sganno=anno; by sex; run;
options byline;
ods html close;
bstarr
Quartz | Level 8

Excellent, I'm glad this sparked some interest! Thank you for sharing the code sample and I look forward to reading the blog post!

SAS INNOVATE 2024

Innovate_SAS_Blue.png

Registration is open! SAS is returning to Vegas for an AI and analytics experience like no other! Whether you're an executive, manager, end user or SAS partner, SAS Innovate is designed for everyone on your team. Register for just $495 by 12/31/2023.

If you are interested in speaking, there is still time to submit a session idea. More details are posted on the website. 

Register now!

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.

Get the $99 certification deal.jpg

 

 

Back in the Classroom!

Select SAS Training centers are offering in-person courses. View upcoming courses for:

View all other training opportunities.

Discussion stats
  • 8 replies
  • 2677 views
  • 3 likes
  • 4 in conversation