/**************************************************************************** %gsurvplot( indsn = fin, colvar = trt01pn trtgrp, timevar = aval, times = &timelist, censvar = CNSR(1), dispcens = YES, xaxislabel = %nrstr("Months"), yaxislabel = %nrstr("Percent of subjects without event"), yaxisopts = linearopts=(viewmin=0 viewmax=100 tickvaluelist=(0 20 40 60 80 100)), hsize = 22 cm, vsize = 12 cm, markersize = 5, plotname = gefos01, style = STATISTICAL, legendpos = OUTSIDE); Remarks : The macro will input a dataset of time-to-event data, perform the Kaplan-Meier analysis to get survival estimates, then output as a PNG the survival curve Macro Parameters : indsn (R): Input dataset containing at least the comparison variable (colvar), timevar, and censvar. colvar (R): The subject grouping/comparison variable (e.g. treatment group). If there is a numeric colvar and a character decode variable, colvar should be specified as colvar = trtpn trtp. timevar (R): Time-to-event variable. times (R): List of timepoints to be plotted along the x-axis of the figure, separated by a space. For instance, if the timevar is in months and the maximum timevar value is 26, you might want to have times = 0 6 12 18 24 30. censvar (R): Censoring flag variable, with values indicating censoring in parentheses. For example, if the censvar variable is coded 0/1 where 0 is event and 1 is censored, specify as censvar=(1). cuminc: If YES, the survival curves will be plotted as originating at 0% and ascending towards 100%. Otherwise, the survival curves will originate at 100% and descend towards 0%. (default=) dispcens: If YES, censored obs will be plotted on the survival curves. Otherwise, censored obs will not be plotted on the survival curves. (default=YES) dispmedian: If YES, a dotted horizontal reference line will extend from 50% on the y-axis, a dotted vertical reference line will extend from each curve at the median survival time to the x-axis, and the value of each median will be annotated on the figure. (default=) xaxisopts: Additional SAS code to be included in the XAXISOPTS statement yaxisopts: Additional SAS code to be included in the YAXISOPTS statement xaxislabel: x-axis label Value should be enclosed in quotes (i.e. xasislabel = "Label") Default: xaxislabel= " " yaxislabel: y-axis label Value should be enclosed in quotes (i.e. yasislabel = "Label") Default: yaxislabel= "% Subjects" NOTE THE FOLLOWING FOR XAXISLABEL AND YAXISLABEL Both of these parameters are used as the values for LABEL = options for an axis. SAS documentation for such is reprinted below: LABEL="string" | ( "string" �"string" ) specifies the axis label. The string can be either a string literal or a dynamic. The list form implies that all included string literals or dynamics will be concatenated. subtitle: figure subtitle, if desired NOTE THE FOLLOWING FOR SUBTITLE: When specifying, if the value is only a text string, you must enclose the value in qoutes such as subtitle="Albumin (g/dL)". If the value includes a text command, the text command must be enclosed in braces, such as subtitle={unicode beta}{sub 2} microglobulin markersize: size of symbols in figure. Default: markersize=7px. NOTE if markersize is set to 0 symbols will be suppressed from the figure annotate: The annotate paramaeter is a placeholder within the graphics template which should allow rudimentary annotation to be added to a figure using ENTRY statements. For example, annotate = %str(entry halign=left "textstring" / valign=center) hsize (R): Horizontal size of the figure, including units (IN, INCH or CM)(default=15 cm) vsize (R): Vertical size of the figure, including units (IN, INCH or CM)(default=9 cm) plotname (R): Name of output PNG file to be created by the macro legendpos: Position of the legend INSIDE or OUTSIDE (default=OUTSIDE) style (R): Parent style in PROC TEMPLATE (default=JOURNAL) Macro Sample Call : %rmgsurvplot(indsn = timedat, colvar = trtgpn trtgp, timevar = time, times = 0 12 24 36 48, censvar = flg(1), dispcens = YES, xaxislabel = "Months", plotname = figure1); *****************************************************************************/ %macro gsurvplot(indsn = , colvar = , timevar = , times = %str(0 12 24 36 48), censvar = , xaxislabel = " ", xaxisopts = , yaxislabel = "% Subjects", yaxisopts = , cuminc = , dispcens = YES, dispmedian = , hsize = 15 cm, vsize = 9 cm, plotname = , subtitle = , annotate = , markersize = 7px, style = JOURNAL, legendpos = OUTSIDE); %if %superq(indsn) eq or %superq(colvar) eq or %superq(timevar) eq or %superq(times) eq or %superq(censvar) eq or %superq(plotname) eq %then %do; %put ' ATTENTION: EACH OF INDSN, COLVAR, TIMEVAR, TIMES, CENSVAR, AND PLOTNAME MUST BE SPECIFIED. MACRO WILL TERMINATE.'; %goto eom; %end; /************************************************************************* * Check that &indsn exists and not empty *************************************************************************/; %local ds dsid close rmgmvnd; %let ds = &indsn; %let dsid = %sysfunc(open(&ds,i)); %let rmgmvnd = no; %if &dsid = 0 %then %do; %put; %put %str(USER WARNING: &sysmacroname Macro Aborted. The data set (&indsn) does not exist.); %put; %let dsid = %sysfunc(close(&dsid)); %goto eom; %end; %if %sysfunc(attrn(&dsid,ANY)) = 0 %then %do; %let rmgmvnd = yes; %put; %put %str(USER WARNING: &sysmacroname Macro: The data set (&indsn) is empty.); %put; %let dsid = %sysfunc(close(&dsid)); %end; %if &rmgmvnd = no %then %do; %if &dsid > 0 %then %let dsid = %sysfunc(close(&dsid)); /* close dsid new remediation */ /************************************************************************* * get the product limit estimates from proc lifetest *************************************************************************/; proc sort data=&indsn out=_rm010; by &colvar &timevar; run; ods listing close; ods output productlimitestimates=_rm030 Quartiles=_rm025; proc lifetest data=_rm010(keep=&colvar &timevar %scan(&censvar,1,'(')); strata &colvar; time &timevar*&censvar; run; ods output close; ods listing; /************************************************************************* * process the product limit estimates for plotting *************************************************************************/; data _rm040; set _rm030; by &colvar &timevar; retain _lastvalue; drop _lastvalue; rename censor=_censor_; label _censored='Censored observation'; %if %upcase(&cuminc)=YES %then %do; if first.%scan(&colvar,-1) then _lastvalue=failure; _yvalue=coalesce(failure,_lastvalue)*100; _stat=left; if censor then _censored=_yvalue; output; _lastvalue=coalesce(failure,_lastvalue); %end; %else %do; if first.%scan(&colvar,-1) then _lastvalue=survival; _yvalue=coalesce(survival,_lastvalue)*100; _stat=left; if censor then _censored=_yvalue; output; _lastvalue=coalesce(survival,_lastvalue); %end; run; /************************************************************************* * process the estimates of the median survival time *************************************************************************/; data _rm050; set _rm040; by &colvar; retain _medianset; if first.%scan(&colvar,-1) then _medianset=0; if _yvalue %if %upcase(&cuminc)=YES %then ge;%else le; 50 and _medianset=0 then do; _median=strip(put(&timevar,8.1)); _medianset=1; end; drop _medianset; run; proc sort nodupkey data=_rm050(keep=&colvar) out=_rm100; by &colvar; run; /************************************************************************* * derive subjects at risk *************************************************************************/; data _null_; set _rm100; %let k=1; %do %until(%scan(×,&k,' ') EQ ); %GLOBAL _times&k; _timesk=scan("×",&k,' '); call symput("_times&k",trim(left(_timesk))); %let k=%eval(&k+1); %end; call symput("_n_times",%eval(&k-1)); run; %do k=1 %to &_n_times; data _atr&k; set _rm050(%if &&_times&k eq 0 %then %do; where=(&timevar=0) %end; %else %do; where=(&timevar lt &&_times&k) %end;); by &colvar; if last.%scan(&colvar,-1); &timevar=&&_times&k; _freq_=strip(put(_stat,best.)); keep &colvar &timevar _freq_; run; %end; data _rm120; set %do l=1 %to &_n_times; _atr&l %end;; by &colvar &timevar; run; data _rm130; set _rm120 _rm050 _rm025(where=(percent=50)); run; data _rm200; set _rm130; run; /************************************************************************* * handle graph size -> transform to cm when inches are specified *************************************************************************/; %let vunit=%upcase(%scan(&vsize,-1,' 0123456789.')); %let hunit=%upcase(%scan(&hsize,-1,' 0123456789.')); %if &vunit=IN or &vunit=INCH %then %let vsize=%sysevalf(%scan(&vsize,1,%str( icIC))*2.54); %if &hunit=IN or &hunit=INCH %then %let hsize=%sysevalf(%scan(&hsize,1,%str( icIC))*2.54); %if &vunit=CM %then %let vsize=%sysevalf(%scan(&vsize,1,%str( icIC))*1); %if &hunit=CM %then %let hsize=%sysevalf(%scan(&hsize,1,%str( icIC))*1); /************************************************************************* * determine number of groups of subjects being plotted *************************************************************************/; %let groupp=%scan(&colvar,-1); %let groupo=%scan(&colvar,1); %local ngroups; proc sql noprint; %if %superq(groupp) ne %then %do; select count(distinct &groupp) into: ngroups from _rm010; %end; %else %let ngroups=1; quit; /************************************************************************* * establish lines and symbols for the plot *************************************************************************/; ods path reset; ods path (prepend)work.templat(UPDATE); ods path show; proc template; define style GDOlifeplotBW; parent=Styles.&style; /* Change Axis Color to Color of Text */ class GraphColors / "gaxis" = GraphColors('gtext'); %do i=1 %to &ngroups; class GraphData&i / markersymbol = "&&rmmarkersymbol_survplot&i" linestyle = &&rmlinestyle_survplot&i; %end; end; run; /************************************************************************* * allocate space to accommodate the frequency table below the graph *************************************************************************/; %local availableheight ngroups freqtablespace graphspace; %let availableHeight = &vsize; %if %superq(subtitle) ne %then %let availableHeight=%sysevalf(&availableHeight-0.5); %if %superq(xaxislabel) ne %then %let availableHeight=%sysevalf(&availableHeight-0.5); %if %superq(colvar) ne %then %do; %if %superq(legendpos) eq OUTSIDE %then %let availableHeight=%sysevalf(&availableHeight-0.5); %end; %let freqtablespace = %sysevalf(((1+&ngroups)*0.48+0.1)/(&availableHeight)); %let graphspace = %sysevalf(1-((1+&ngroups)*0.48+0.1)/(&availableHeight)); /************************************************************************* * create layout for the figure *************************************************************************/; proc template; define statgraph work._lifeplot/STORE = work.templat; BEGINGRAPH / pad=(top=1pt bottom=1pt); %if %superq(subtitle) ne %then ENTRYTITLE &subtitle / TEXTATTRS=(FAMILY="&RMFNT_GRAPH" SIZE=&rmfntsize_graph_header);; LAYOUT LATTICE / COLUMNS = 1 ROWS = 2 ROWWEIGHTS = (&graphspace &freqtablespace) COLUMNDATARANGE = UNIONALL; /************************** THE FIRST CELL HOLDS THE SURVIVAL PLOT ******************************/ CELL; LAYOUT OVERLAY / BORDER = FALSE XAXISOPTS = ( DISPLAY = (LABEL LINE TICKS TICKVALUES) LINEAROPTS = (TICKVALUELIST = (×) VIEWMIN = %scan(×,1) VIEWMAX = %scan(×,-1) &xaxisopts ) OFFSETMIN = 0.05 OFFSETMAX = 0.05 %if %superq(xaxislabel) ne %then LABEL = &xaxislabel LABELATTRS = GraphValueText(FAMILY="&RMFNT_GRAPH" SIZE=&RMFNTSIZE_GRAPH_AXIS); ) YAXISOPTS = ( %if %superq(yaxislabel) ne %then LABEL = &yaxislabel LABELATTRS = GraphValueText(FAMILY="&RMFNT_GRAPH" SIZE=&RMFNTSIZE_GRAPH_AXIS); &yaxisopts ) OPAQUE = FALSE; /* Plot Censored observations */ %if %upcase(&dispcens)=YES %then SCATTERPLOT X = &timevar Y = _censored / GROUP = &groupp NAME='Censored' MARKERATTRS = (SIZE = &markersize);; /* Plot KM Curves */ STEPPLOT X = &timevar Y = _yvalue / NAME = 'KMCurves' GROUP = &groupp %if %upcase(&dispmedian)=YES %then DATALABEL=_median;; /* add median line */ %if %upcase(&dispmedian)=YES %then %do; LINEPARM X = 0 Y = 50 SLOPE = 0 / LINEATTRS = (PATTERN = DOT) CURVELABEL = 'Median' CURVELABELATTRS = (COLOR=BLACK); DROPLINE X = estimate Y = 50 / DROPTO = X LINEATTRS = (PATTERN=DOT COLOR=BLACK); %end; /* add inside legend */ %if %upcase(&legendpos)=INSIDE %then %do; DISCRETELEGEND 'KMCurves' %if %upcase(&dispcens)=YES %then 'Censored'; / BORDER = TRUE AUTOALIGN = (BOTTOMRIGHT TOPRIGHT TOPLEFT BOTTOMLEFT) LOCATION = INSIDE ACROSS = 1 %if %upcase(&dispcens)=YES %then MERGE = TRUE;; %end; &annotate; ENDLAYOUT; ENDCELL; /************************************************************************************************/ /************************** THE SECOND CELL HOLDS THE SUBJECTS AT RISK PLOT ******************************/ CELL; /* Define the bottom area and fill with a scatter plot of the counts as character values:*/ LAYOUT OVERLAY / BORDER = FALSE WALLDISPLAY = NONE XAXISOPTS = ( DISPLAY = NONE LINEAROPTS=(TICKVALUELIST=(×)) OFFSETMIN = 0.02 OFFSETMAX = 0.02) X2AXISOPTS = (DISPLAY = NONE) Y2AXISOPTS = (DISPLAY = NONE) YAXISOPTS = (TYPE=DISCRETE DISPLAY = (TICKVALUES) TICKVALUEATTRS=(SIZE=&rmfntsize_graph_data) REVERSE=true); %if NOT(&ngroups=1) %then %do; SCATTERPLOT X = &timevar Y = _sampleSizeLabel / MARKERCHARACTERATTRS = (COLOR = GraphValueText:Color FAMILY ="&rmfnt_graph" SIZE=&rmfntsize_graph_data) MARKERCHARACTER = _sampleSizeLabelDummy; SCATTERPLOT X = &timevar Y = &groupp / MARKERCHARACTERATTRS = (COLOR = GraphValueText:contrastcolor FAMILY ="&rmfnt_graph" SIZE=&rmfntsize_graph_data) MARKERCHARACTER = _freq_; %end; %else %do; SCATTERPLOT X = &timevar Y = grouppp / MARKERCHARACTERATTRS = (COLOR = GraphValueText:contrastcolor FAMILY ="&rmfnt_graph" SIZE=&rmfntsize_graph_data) MARKERCHARACTER = _freq_; %end; ENDLAYOUT; ENDCELL; /*********************************************************************************************************/ %if %upcase(&legendpos)=OUTSIDE %then %do; SIDEBAR; DISCRETELEGEND 'KMCurves' %if %upcase(&dispcens)=YES %then 'Censored'; / BORDER = FALSE VALIGN = BOTTOM HALIGN = RIGHT LOCATION = OUTSIDE %if %upcase(&dispcens)=YES %then MERGE = TRUE;; ENDSIDEBAR; %end; ENDLAYOUT; ENDGRAPH; END; RUN; /************************************************************************* * create figure *************************************************************************/; options nodate nonumber nobyline; ods results off; %let startobs = 1; %let eof = 0; %let imageCnt = 1; %do %until(&eof); data _rm300; set _rm200(firstobs=&startobs) end=eof; retain _observation &startobs; %if not(&ngroups=1) %then %do; _sampleSizeLabel=" Subjects at risk"; _sampleSizeLabelDummy = ' '; output; %end; %else %do; grouppp=" Subjects at risk"; %end; _observation+1; if eof then do; call symput('eof',eof); call symput('startobs',_observation); stop; end; run; ods listing image_dpi=300 gpath="&opath" style=GDOLifePlotBW; ods graphics on / imageName = "&plotname" imagefmt = png border = off scale = no reset = index width = &hsize cm height = &vsize cm; ods escapechar="~"; proc sgrender data=_rm300 template=work._lifeplot; run; ods listing close; ods graphics off; %end; ods listing; proc datasets nolist; delete _rm010 _rm025 _rm030 _rm040 _rm050 _rm100 _rm110 _rm120 _rm130 _rm200 _rm300 %do l=1 %to &_n_times; _atr&l %end;; quit; %end; %else %if &rmgmvnd = yes %then %do; %rmgnodata; %end; %eom: %mend gsurvplot;