Hi All
I'm reproducing a Kaplan-Meier stepplot in layout lattice.
Here is the result.
I'm wondering why in the right graph, the axis table with patients at risk is located a bit higher than the corresponding table on the left.
I attach the code I used, if of any help.
proc template;
define statgraph Fig_2_KM;
begingraph / collation=binary designheight=480 designwidth=1280;
layout lattice / rows=1 columns=2 rowgutter=10 columngutter=10 ;
/*Unmatched population*/
legenditem type=text name="log" / label="Logrank p=0.0007"
lineattrs=(color=black);
layout overlay / xaxisopts=(Label="Years of Follow-up"
LabelAttrs=(Weight=normal) type=linear) y2axisopts=(labelFitPolicy=Split)
yaxisopts=(Label=" " labelFitPolicy=Split
LabelAttrs=(Weight=normal) type=linear linearopts=(viewmin=0) )
y2axisopts=(labelFitPolicy=Split);
entry "in the Unmatched Population" / valign=top location=outside
textattrs=(weight=bold);
entry "Product-Limit Survival Estimates"/ valign=top location=outside
textattrs=(weight=bold size=12);
StepPlot X=Time Y=Survival / primary=true Group=Stratum Lineattrs=(Pattern=1
Thickness=1) LegendLabel="Survival Probability" NAME="STEP";
scatterplot x=time y=censored / markerattrs=(symbol=plus) name='c';
scatterplot x=time y=censored / markerattrs=(symbol=plus) GROUP=stratum;
discretelegend 'c' 'log' / location=inside halign=right valign=top
order=columnmajor valueattrs=(size=10) border=false;
drawtext textattrs=(size=10) 'Survival Probability' / x=-8 y=60 anchor=bottom
xspace=wallpercent yspace=wallpercent rotate=90 width=50;
/*--Subjects at risk-Unmatched--*/
InnerMargin / align=bottom opaque=true;
AxisTable Value=AtRisk X=tAtRisk / labelPosition=min Class=Stratum
LabelAttrs=(Weight=normal) ValueAttrs=(Size=10) colorGroup=Stratum
Display=(Label Values) title="Subjects at risk" titleattrs=(size=10);
EndInnerMargin;
DiscreteLegend "STEP";
endlayout;
/*Matched population*/
legenditem type=text name="log2" / label="Logrank p=0.9417"
lineattrs=(color=black);
layout overlay / xaxisopts=(Label="Years of Follow-up"
LabelAttrs=(Weight=normal) type=linear) y2axisopts=(labelFitPolicy=Split)
yaxisopts=(Label=" " labelFitPolicy=Split
LabelAttrs=(Weight=normal) type=linear linearopts=(viewmin=0) )
y2axisopts=(labelFitPolicy=Split);
entry "in the Matched Population" / valign=top location=outside
textattrs=(weight=bold);
entry "Product-Limit Survival Estimates"/ valign=top location=outside
textattrs=(weight=bold size=12);
StepPlot X=Time2 Y=Survival2 / primary=true group=Stratum2
Lineattrs=(Pattern=1 Thickness=1) LegendLabel="Survival Probability"
NAME="STEP2";
scatterplot x=time2 y=censored2 / markerattrs=(symbol=plus) name='c2'
legendlabel="Censored";
scatterplot x=time2 y=censored2 / markerattrs=(symbol=plus) GROUP=stratum2;
discretelegend 'c2' 'log2' / location=inside halign=right valign=top
order=columnmajor valueattrs=(size=10) border=false;
drawtext textattrs=(size=10) 'Survival Probability' / x=-8 y=60 anchor=bottom
xspace=wallpercent yspace=wallpercent rotate=90 width=50;
/*--Subjects at risk-Unmatched--*/
InnerMargin / align=bottom opaque=true;
AxisTable Value=AtRisk2 X=tAtRisk2 / labelPosition=min class=Stratum2
LabelAttrs=(Weight=normal) ValueAttrs=(Size=10) colorGroup=Stratum2
Display=(Label Values) title="Subjects at risk" titleattrs=(size=10);
EndInnerMargin;
DiscreteLegend "STEP";
endlayout;
endlayout;
endgraph;
end;
run;
Thanks in advance
Sincerely
A
Hello,
I was able to fix this by adding the INCLUDEMISSINGCLASS=FALSE option to the AXISTABLE statement.
Also putting out a plug for my macro NEWSURV that can make these kind of lattice of survival plots for you:
Can you provide the data?
Without the data, I can't test my idea, but it's possible that the data ranges are slightly different. Try adding
columndatarange=union rowdatarange=union
to the LAYOUT LATTICE options.
Without the data, this could take all day. Try this...try that.
The question in my mind is whether it is a data issue (the data are responsible for the difference) or whether it is a template issue (like a font difference that we aren't noticing). To determine whether the data are responsible, try this:
1. Temporarily modify the template so that the second plot uses Time, Survival, and Censored variables instead of Time2, Survival2, and Censored2. Any change?
2. If the problem still persists, put back Time2, Survival2, and Censored2, but now temporarily modify the template to use AtRisk, tAtRisk, and Stratum in the second AXISTABLE statement? Any change?
3. You can combine 1 and 2 so that you have identical plots on the left and the right. Hopefully, they will be identical in this case.
4. Notice that the first axistable has a three-digit number (147) whereas the second axistable does not. Play around with various alignment, justification, and formatting options to see whether that is the issue. (#2 should reveal whether it is an issue.) I'm not very familiar with AXISTABLE options, but I'd start with VALUEFORMAT=3. or try to right-align the numbers.
@Rick_SAS I've attached the data.
When I change Stratum2 with StratumNum2 --> I can see three different rows, namely
1
2
.
therefore I think that there is some missing value in Stratum2 and StratumNum2 that creates this problems.
Suggestion #1: nothing happens;
Suggestion #2: number of its at risk is correctly placed!
Suggestion #3: two identical plots.
Suggestion #4: I don't thin it is a problem of digits.
Because the problem manifests when I use the Stratum2 or StratumNum2 (which can be used alternatively, it's only a metter of how the two strata appear on the graph).
Tks!
I don't know why the axis tables are not aligned.
However, if you are interested in solving the problem another way, I suggest that you
1. Restructure the data from wide to long. That will get rid of the missing values in STRATUM2.
2. Use ODS Graphics Designer to mock up a template that creates the main features, then customize it by hand.
This will allow you to use the DATALATTICE layout, which is preferred if you are creating a panel like this with equated axes.
Here is a start, if you want to pursue it:
/* convert from wide to long */
data s1;
set lib.survival_unified;
keep Time Survival AtRisk Event Censored tAtRisk Stratum StratumNum;
run;
data s2;
set lib.survival_unified;
keep Time2 Survival2 AtRisk2 Event2 Censored2 tAtRisk2 Stratum2 StratumNum2;
rename Time2=Time
Survival2=Survival
AtRisk2=AtRisk
Event2=Event
Censored2=Censored
tAtRisk2=tAtRisk
Stratum2=Stratum
StratumNum2=StratumNum;
run;
data survival;
set s1(in=in1) s2;
if in1 then Group=1;
else Group=2;
run;
/* some sample code from ODS designer to get you started */
proc template;
define statgraph sgdesign;
dynamic _TIME _CENSORED _STRATUM _GROUP _TIME2 _SURVIVAL _STRATUM2 _TATRISK _ATRISK _STRATUM3 _GROUP2;
dynamic _panelnumber_;
begingraph / designwidth=1162 designheight=480;
entrytitle halign=center 'Type in your title...';
entryfootnote halign=left 'Type in your footnote...';
layout datalattice columnvar=_GROUP / cellwidthmin=1 cellheightmin=1 rowgutter=3 columngutter=3 rowdatarange=unionall row2datarange=unionall columndatarange=unionall column2datarange=unionall headerlabeldisplay=value rowaxisopts=( offsetmin=0.2 linearopts=( viewmin=0.0));
layout prototype / ;
scatterplot x=_TIME y=_CENSORED / group=_STRATUM name='scatter';
stepplot x=_TIME2 y=_SURVIVAL / group=_STRATUM2 name='step' connectorder=xaxis grouporder=data clusterwidth=0.85;
axistable x=_TATRISK value=_ATRISK / class=_STRATUM3 colorgroup=_GROUP2 stat=mean name='axistable_h2' labelposition=min;
endlayout;
endlayout;
endgraph;
end;
run;
proc sgrender data=WORK.SURVIVAL template=sgdesign;
dynamic _TIME="TIME" _CENSORED="CENSORED" _STRATUM="STRATUM" _GROUP="GROUP" _TIME2="TIME" _SURVIVAL="SURVIVAL" _STRATUM2="STRATUM" _TATRISK="TATRISK" _ATRISK="ATRISK" _STRATUM3="STRATUM" _GROUP2="GROUP";
run;
Hello,
I was able to fix this by adding the INCLUDEMISSINGCLASS=FALSE option to the AXISTABLE statement.
Also putting out a plug for my macro NEWSURV that can make these kind of lattice of survival plots for you:
Great job, Jeff.
I also wondered why the OP is constructing these plots by hand. There is a chapter in the SAS/STAT doc dedicated to creating these plots. If the issue is wanting to arrange them side-by-side, then look into the ODS LAYOUT statement, which enables you to arrange multiple graphs or tables into a grid.
Anyway, problems solved (I hope)
SAS Innovate 2025 is scheduled for May 6-9 in Orlando, FL. Sign up to be first to learn about the agenda and registration!
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.
Ready to level-up your skills? Choose your own adventure.