BookmarkSubscribeRSS Feed
☑ This topic is solved. Need further help from the community? Please sign in and ask a new question.
SASuserlot
Barite | Level 11

Hi All. 

I am trying to create a forest plot. However, I am encountering some issues.

1. one of the texts is long, so I am trying to wrap it so that the actual ratio bars have enough space. I tried with ODSESCAPECHAR, (*ESC*){unicode '000a'x}. But not able to achieve it. Circled Red in Screen shot,

2. How I can control the width of Axis tables? I tried Column weights options but no luck. * Blue lines in Screen shot

SASuserlot_0-1754241853843.png

 


proc format;
  value subgrp
    0   = 'Study Level'
	1   = "Psychotic events continuiously Increasing the type.(*ESC*){unicode '000a'x} This text is big it have to wrap so that bars have enough space"
    1.1 = '= 10'
	1.2 = '>10'

	;
run;
         

data forest;
  input Indent Subgroup  lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper;
  datalines;

0 0 2.06 29 1.03 32 1.02 0.72 1.33
0 1 . . . . . . . 
2 1.1 2.1 19 1.17 20 0.93 0.52 1.33
2 1.2 1.93 10 0.83 12 1.09 0.52 1.55

;

run;

data forest2;
  length subgroup_ $200 col3-col5 $20;
  set forest;
  subgroup_=strip(put(subgroup, subgrp.));
  indent=ifn(indent eq 2, 1, 0);
  obsID=_n_;
  if mod(_N_-1, 6) in (1, 2, 3) then ref=obsID;
  if indent>0 or subgroup=0 then do;
	  col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')';
	  col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')';
	  col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')';
	  mean=lsdiff;
	  low=cilower;
	  high=ciupper;

  end;
run;

ods path(prepend) work.templat(update);
options orientation=landscape;
ods graphics on / imagefmt=png attrpriority=none height=5.1 in width=9 in 
                  border=off ;

proc template;
  define statgraph Forest;
  dynamic _show_bands _color _thk;
    begingraph;
      discreteattrmap name='text';
        value '0' / textattrs=(weight=bold);
        value other;
      enddiscreteattrmap;
      discreteattrvar attrvar=type var=indent attrmap='text';

      layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/;


      *** left-side table ***;
      layout overlay / walldisplay=none xaxisopts=(display=(line tick tickvalues label) label='<------Favors Placebo         Favors Treatment----->' 
                            labelAttrs=(size=7 weight=bold )
                            linearOpts=(tickValuePriority=true 
                                        tickValueList=( 0.0 0.5 1.0 1.5 2.0 ))
                            lineExtent=data)
          yaxisopts=(reverse=true display=none 
                     tickvalueattrs=(weight=bold));

        *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
		innermargin/align=left ;
           axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type; 
		endinnermargin;

       *** graph ***;
         referenceline y=ref/ lineattrs=(thickness=_thk color=_color);
         scatterplot y=obsID x=mean / xerrorlower=low xerrorupper=high 
           markerattrs=(symbol=squarefilled);
         referenceline x=1;

      *** right side table ***;
		 innermargin/align=right;
            *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
            axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=7pt)  ;
            axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=7pt) ;
            axistable y=obsID value=col5 /label='LS Mean Diff       (95% CI)' labelattrs=(size=7pt);
         endinnermargin;

	endlayout;
    endlayout; *** end lattice ***;

   endgraph;
  end;
run;


*** create the graph ***;


ods rtf file = "C:\Users\test.RTF";

proc sgrender data=forest2 template=Forest;
dynamic _color='cxff9797'  _thk=15;
run;

ods rtf close;

Thank you

 

1 ACCEPTED SOLUTION

Accepted Solutions
Ksharp
Super User
proc format;
  value subgrp
    0   = 'Study Level'
	1.1   = "Psychotic events continuiously "
	1.2  = "Increasing the type."
	1.3 =  "This text is big it have to wrap "
	1.4=   "so that bars have enough space."
    2.1 = '= 10'
	2.2 = '>10'

	;
run;
         

data forest;
  input Indent Subgroup  lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper;
  datalines;

0 0 2.06 29 1.03 32 1.02 0.72 1.33
0 1.1 . . . . . . . 
0 1.2 . . . . . . . 
0 1.3 . . . . . . . 
0 1.4 . . . . . . . 
2 2.1 2.1 19 1.17 20 0.93 0.52 1.33
2 2.2 1.93 10 0.83 12 1.09 0.52 1.55

;

run;

data forest2;
  length subgroup_ $200 col3-col5 $20;
  set forest;
  subgroup_=strip(put(subgroup, subgrp.));
  indent=ifn(indent eq 2, 1, 0);
  obsID=_n_;

  if _n_>1 then ref=obsID;

  if indent>0 or subgroup=0 then do;
	  col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')';
	  col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')';
	  col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')';
	  mean=lsdiff;
	  low=cilower;
	  high=ciupper;

  end;
run;

ods path(prepend) work.templat(update);
options orientation=landscape;
ods graphics on / imagefmt=png attrpriority=none height=3.8 in width=9 in
                  border=off ;

proc template;
  define statgraph Forest;
  dynamic _show_bands _color _thk;
    begingraph;
      discreteattrmap name='text';
        value '0' / textattrs=(weight=bold);
        value other;
      enddiscreteattrmap;
      discreteattrvar attrvar=type var=indent attrmap='text';

      layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/;


      *** left-side table ***;
      layout overlay / walldisplay=none xaxisopts=(display=(line ticks tickvalues label) label='<------Favors Placebo   Favors Treatment----->' 
                            labelAttrs=(size=12  weight=bold ) tickvalueattrs=(size=12)
                            linearOpts=(tickValuePriority=true 
                                        tickValueList=( 0.0 0.5 1.0 1.5 2.0 ))
                            lineExtent=data)
          yaxisopts=(reverse=true display=none
                     tickvalueattrs=(weight=bold));

        *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
		innermargin/align=left ;
           axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type  valueattrs=(size=12); 
		endinnermargin;

       *** graph ***;
         referenceline y=ref/ lineattrs=(thickness=_thk color=_color);
         scatterplot y=obsID x=mean / xerrorlower=low xerrorupper=high 
           markerattrs=(symbol=squarefilled);
         referenceline x=1;

      *** right side table ***;
		 innermargin/align=right;
            *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
            axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12) ;
            axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12);
            axistable y=obsID value=col5 /label='LS Mean Diff       (95% CI)' labelattrs=(size=12) valueattrs=(size=12);
         endinnermargin;

	endlayout;
    endlayout; *** end lattice ***;

   endgraph;
  end;
run;


*** create the graph ***;



proc sgrender data=forest2 template=Forest;
dynamic _color='cxff9797'  _thk=45;
run;

Ksharp_0-1754360186164.png

 

View solution in original post

5 REPLIES 5
Ksharp
Super User

You need to use TWO rows or more to split this long text .

Here is an example(proc sgplot)   I used before to split a long text into THREE rows.

 

data forest_subgroup;
infile cards truncover expandtabs;
  label Subgroup='子组' 
  Count='人数' Percent='Mean(SD)' 
  Count_test='人数' Percent_test='Mean(SD)'
  ci='组间差(95%CI)'
  pvalue='P值';
  input Id Subgroup : $80. Count Percent : $40.  Count_test Percent_test  : $40.   Mean  Low  High   ci :$40. pvalue $;
  indentWt=1;
  ObsId=_n_; 
  datalines;
1 总体                        332  6.35(3.45)	111	 3.73(3.98) 	2.62   1.85  3.39  2.62(1.85,3.39)	<0.001
1 年龄                   .     .  .     .  .       .       .      .
2 1~3岁                  	101	5.80(3.43)	36	3.75(4.38)	2.05 0.63 3.47   2.05(0.63,3.47)	0.005
2 4~7岁	                    120	6.30(3.69)	37	2.73(3.65)	3.58 2.21 4.95   3.58(2.21,4.95)	<0.001
2 8~14岁                 	111	6.91(3.12)	38	4.69(3.75)	2.22 0.99 3.44   2.22(0.99,3.44)	<0.001
1 基线VAS评分             .     .  .     .  .       .       .      .
2 ≤6分	                    78	3.44(2.73)	37	1.31(2.79)	2.13 1.04 3.21   2.13(1.04,3.21)	<0.001
2 >6分                     	254	7.25(3.14)	74	4.94(3.95)	2.31 1.32 3.30   2.31(1.32,3.30)	<0.001
1 中位病程     .     .  .     .  .       .       .      .
2 中位病程以下	            162	6.32(3.79)	63	3.65(4.43)	2.67 1.50 3.83   2.67(1.50,3.83)	<0.001
2 中位病程以上	            170	6.38(3.09)	48	3.83(3.35)	2.55 1.54 3.56   2.55(1.54,3.56)	<0.001
1 剔除使用             .     .  .     .  .       .       .      .
1 “未指定的草药和传统药物” 	298	6.41(3.22)	107	3.78(4.04)	2.64 1.78 3.49   2.64(1.78,3.49)	<0.001
1 的受试者             .     .  .     .  .       .       .      .
1 增加01中心		            364	6.23(3.36)	120	3.67(3.91)	2.56 1.78 3.35   2.56(1.78,3.35)	<0.001
1 年龄(增加01中心)     .     .  .     .  .       .       .      .
2 1~3岁                    	104	5.77(3.39)	37	3.65(4.37)	 2.12 0.53  3.71  2.12(0.53,3.71)	0.010
2 4~7岁                 	135	6.23(3.56)	41	2.91(3.52)   3.32  2.07  4.57	3.32(2.07,4.57)	<0.001
2 8~14岁                  	125	6.62(3.08)	42	4.44(3.79)	2.19  1.03  3.34   2.19(1.03,3.34)	<0.001
;
run;



data forest_subgroup;
 set forest_subgroup;
  indentWt=1;
  ObsId=_n_; 
run;
data forest_subgroup_2;
  set forest_subgroup nobs=n end=last;
  length text $20;
  val=mod(_N_-1, 6);
/*  if val eq 1 or val eq 2 or val eq 3 then ref=obsid;*/
 if obsid in (2:5 9:11 15 ) then ref=obsid;
  /*--Separate Subgroup headers and obs into separate columns--*/
  indentwt=2;
  if id=1 then indentWt=0;

  output;
  if last then do;
    call missing (subgroup, count, percent, Count_test ,Percent_test,   mean, low, high,  ci,
                   indentwt, val, ref ,pvalue);
        obsid=n+1; 
    xl=1; yl=n+1; text='P'; output;;
        xl=4; yl=n+1; text='T'; output;
  end;
  run;

/*--Define Format with Unicode for the left and right arrows--*/
proc format;;
  value $txt
  "T" = "试验组更好 (*ESC*){Unicode '2192'x}"
  "P" = "(*ESC*){Unicode '2190'x} 对照组更好";
run;

/*--Attribute maps for Subgroup Test attributes--*/
data attrmap;
  length textweight $10;
  id='text'; value='1'; textcolor='Black'; textsize=15; textweight='bold'; output;
  id='text'; value='2'; textcolor='Black'; textsize=10; textweight='normal'; output;
run;
/*--Forest Plot--*/
options missing=' ' orientation=landscape ;
/*ods listing style=htmlblue;*/
title j=l h=1.2  "                                                          试验组                      对照组";
/*ods listing gpath='c:\temp\' image_dpi=400;*/
/*ods graphics / reset width=5in height=3in imagename='Subgroup_Forest_SG_94';*/
ods rtf file='c:\temp\temp.rtf' style=htmlblue;
ods graphics / reset width=9.6in height=4.5in   noborder noscale outputfmt=png;
proc sgplot data=forest_subgroup_2 nowall noborder nocycleattrs dattrmap=attrmap noautolegend;
  format text $txt.;
  styleattrs axisextent=data;
  refline ref / lineattrs=(thickness=20 color=grayee);  /*<-- discrete thickness*/
  highlow y=obsid low=low high=high /lowcap=serif highcap=serif lineattrs=(color=black);   /*clipcap CLIPCAPSHAPE=CLOSEDARROW */
  scatter y=obsid x=mean / markerattrs=(symbol=squarefilled size=8 color=black );
  scatter y=obsid x=mean / markerattrs=(size=0) x2axis;
  refline 0 / axis=x;

  text x=xl y=obsid text=text / position=bottom contributeoffsets=none strip  textattrs=(size=10);
  yaxistable subgroup  / location=inside position=left textgroup=id labelattrs=(size=10)  
            LABELJUSTIFY=left  valueattrs=(size=10 ) 
             textgroupid=text indentweight=indentWt;
  yaxistable  Count Percent  Count_test Percent_test ci pvalue/ VALUEJUSTIFY=right
  location=inside position=left labelattrs=(size=10) valueattrs=(size=10) pad=(right=15px) ;
  yaxis reverse display=none colorbands=odd colorbandsattrs=(transparency=1)  ;
  xaxis display=(nolabel) values=(0 to 5 by 1);
  x2axis label='组间差' display=(noline noticks novalues) labelattrs=(size=10)  ;
run;
ods rtf close;

Ksharp_0-1754269642134.png

 

Ksharp
Super User
proc format;
  value subgrp
    0   = 'Study Level'
	1.1   = "Psychotic events continuiously "
	1.2  = "Increasing the type."
	1.3 =  "This text is big it have to wrap "
	1.4=   "so that bars have enough space."
    2.1 = '= 10'
	2.2 = '>10'

	;
run;
         

data forest;
  input Indent Subgroup  lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper;
  datalines;

0 0 2.06 29 1.03 32 1.02 0.72 1.33
0 1.1 . . . . . . . 
0 1.2 . . . . . . . 
0 1.3 . . . . . . . 
0 1.4 . . . . . . . 
2 2.1 2.1 19 1.17 20 0.93 0.52 1.33
2 2.2 1.93 10 0.83 12 1.09 0.52 1.55

;

run;

data forest2;
  length subgroup_ $200 col3-col5 $20;
  set forest;
  subgroup_=strip(put(subgroup, subgrp.));
  indent=ifn(indent eq 2, 1, 0);
  obsID=_n_;

  if _n_>1 then ref=obsID;

  if indent>0 or subgroup=0 then do;
	  col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')';
	  col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')';
	  col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')';
	  mean=lsdiff;
	  low=cilower;
	  high=ciupper;

  end;
run;

ods path(prepend) work.templat(update);
options orientation=landscape;
ods graphics on / imagefmt=png attrpriority=none height=3.8 in width=9 in
                  border=off ;

proc template;
  define statgraph Forest;
  dynamic _show_bands _color _thk;
    begingraph;
      discreteattrmap name='text';
        value '0' / textattrs=(weight=bold);
        value other;
      enddiscreteattrmap;
      discreteattrvar attrvar=type var=indent attrmap='text';

      layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/;


      *** left-side table ***;
      layout overlay / walldisplay=none xaxisopts=(display=(line ticks tickvalues label) label='<------Favors Placebo   Favors Treatment----->' 
                            labelAttrs=(size=12  weight=bold ) tickvalueattrs=(size=12)
                            linearOpts=(tickValuePriority=true 
                                        tickValueList=( 0.0 0.5 1.0 1.5 2.0 ))
                            lineExtent=data)
          yaxisopts=(reverse=true display=none
                     tickvalueattrs=(weight=bold));

        *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
		innermargin/align=left ;
           axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type  valueattrs=(size=12); 
		endinnermargin;

       *** graph ***;
         referenceline y=ref/ lineattrs=(thickness=_thk color=_color);
         scatterplot y=obsID x=mean / xerrorlower=low xerrorupper=high 
           markerattrs=(symbol=squarefilled);
         referenceline x=1;

      *** right side table ***;
		 innermargin/align=right;
            *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
            axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12) ;
            axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12);
            axistable y=obsID value=col5 /label='LS Mean Diff       (95% CI)' labelattrs=(size=12) valueattrs=(size=12);
         endinnermargin;

	endlayout;
    endlayout; *** end lattice ***;

   endgraph;
  end;
run;


*** create the graph ***;



proc sgrender data=forest2 template=Forest;
dynamic _color='cxff9797'  _thk=45;
run;

Ksharp_0-1754360186164.png

 

SASuserlot
Barite | Level 11

Thank you very much @Ksharp . you are Awesome. I have a question: what is the usual way we display if the upper limit of CI is beyond the Scale we display in the figure? For example, my upper limit CI is  25 for " >10 "row, which will be difficult to display due to space. Can we add, like, an arrow instead of Caps or something to the upper limit?

Ksharp
Super User

You need the help from option "clipcap=true CLIPCAPSHAPE=CLOSEDARROW" of highlow statement.

 
proc format;
  value subgrp
    0   = 'Study Level'
	1.1   = "Psychotic events continuiously "
	1.2  = "Increasing the type."
	1.3 =  "This text is big it have to wrap "
	1.4=   "so that bars have enough space."
    2.1 = '= 10'
	2.2 = '>10'

	;
run;

data forest;
  input Indent Subgroup  lsmean1 count1 lsmean2 count2 lsdiff cilower ciupper;
  datalines;

0 0 2.06 29 1.03 32 1.02 0.72 1.33
0 1.1 . . . . . . . 
0 1.2 . . . . . . . 
0 1.3 . . . . . . . 
0 1.4 . . . . . . . 
2 2.1 2.1 19 1.17 20 0.93 0.52 1.33
2 2.2 1.93 10 0.83 12 1.09 0.52 12.55

;

run;

data forest2;
  length subgroup_ $200 col3-col5 $20;
  set forest;
  subgroup_=strip(put(subgroup, subgrp.));
  indent=ifn(indent eq 2, 1, 0);
  obsID=_n_;

  if _n_>1 then ref=obsID;

  if indent>0 or subgroup=0 then do;
	  col3 = strip(put(lsmean1, best.)) || ' ('||strip(put(count1, best.)) ||')';
	  col4 = strip(put(lsmean2, best.))|| ' ('||strip(put(count2, best.)) ||')';
	  col5 = strip(put(lsdiff, best.)) || ' ('||strip(put(cilower, best.)) ||', '||strip(put(ciupper, best.))||')';
	  mean=lsdiff;
	  low=cilower;
	  high=ciupper;

  end;
run;

ods path(prepend) work.templat(update);
options orientation=landscape;
ods graphics on / imagefmt=png attrpriority=none height=3.8 in width=9 in
                  border=off ;

proc template;
  define statgraph Forest;
  dynamic _show_bands _color _thk;
    begingraph;
      discreteattrmap name='text';
        value '0' / textattrs=(weight=bold);
        value other;
      enddiscreteattrmap;
      discreteattrvar attrvar=type var=indent attrmap='text';

      layout lattice / columns=1/* columnweights=(0.2 0.4 0.1 0.1 0.1 0.1)*/;


      *** left-side table ***;
      layout overlay / walldisplay=none xaxisopts=(display=(line ticks tickvalues label) label='<------Favors Placebo   Favors Treatment----->' 
                            labelAttrs=(size=12  weight=bold ) tickvalueattrs=(size=12)
                            linearOpts=(tickValuePriority=true viewmax=2 viewmin=0  TICKVALUEPRIORITY=false
                                        tickValueList=( 0.0 0.5 1.0 1.5 2.0 ))
                            lineExtent=data )
          yaxisopts=(reverse=true display=none
                     tickvalueattrs=(weight=bold));

        *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
		innermargin/align=left ;
           axistable y=obsID value=subgroup_ / label='Study/Subgroup' indentweight=indent display=(values) textgroup=type  valueattrs=(size=12); 
		endinnermargin;

       *** graph ***;
         referenceline y=ref/ lineattrs=(thickness=_thk color=_color);
        scatterplot y=obsID x=mean /markerattrs=(symbol=squarefilled);
		highlowplot y=obsID low=low high=high /lowcap=serif highcap=serif lineattrs=(color=black)   clipcap=true CLIPCAPSHAPE=CLOSEDARROW ;
         referenceline x=1;

      *** right side table ***;
		 innermargin/align=right;
            *referenceline y=ref / lineattrs=(thickness=_thk color=_color);
            axistable y=obsID value=col3 /label='Treatment LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12) ;
            axistable y=obsID value=col4 /label='Placebo LS Mean (n)' labelattrs=(size=12) valueattrs=(size=12);
            axistable y=obsID value=col5 /label='LS Mean Diff       (95% CI)' labelattrs=(size=12) valueattrs=(size=12);
         endinnermargin;

	endlayout;
    endlayout; *** end lattice ***;

   endgraph;
  end;
run;


*** create the graph ***;



proc sgrender data=forest2 template=Forest;
dynamic _color='cxff9797'  _thk=45;
run;
Ksharp_0-1754443276439.png

 

SASuserlot
Barite | Level 11

Thank you very much.

hackathon24-white-horiz.png

2025 SAS Hackathon: There is still time!

Good news: We've extended SAS Hackathon registration until Sept. 12, so you still have time to be part of our biggest event yet – our five-year anniversary!

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.

SAS Training: Just a Click Away

 Ready to level-up your skills? Choose your own adventure.

Browse our catalog!

Discussion stats
  • 5 replies
  • 714 views
  • 2 likes
  • 2 in conversation