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.png

Join us for SAS Innovate April 16-19 at the Aria in Las Vegas. Bring the team and save big with our group pricing for a limited time only.

Pre-conference courses and tutorials are filling up fast and are always a sellout. Register today to reserve your seat.

 

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.

Click image to register for webinarClick image to register for webinar

Classroom Training Available!

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

View all other training opportunities.

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