Boxplot by groups & blocks, with axis-aligned statistics

2.2k Views Asked by At

I've been building up a GTL template for a boxplot by group and timepoint blocks. I suspect that GTL in SAS 9.4 (TS Level 1M2) does not yet fully support such a multi-dimensional, axis-aligned BOXPLOT plus BLOCKPLOT combination.

  • If I'm wrong, I'd appreciate suggestions on the right approach.
  • If I'm right, I welcome suggestions for a more-or-less straightforward GTL work-around.

The code below produces the first plot, but I really want the 2nd plot. In other words, I want the BLOCKPLOT to support the same two x-axis levels as the BOXPLOT (timepoint and treatment group). However, the BLOCKPLOT will only stack CLASS (treatment group) within X-axis (timepoint). But the BOXPLOT will separate the GROUPs along the X-axis within the blocks.

I don't find any way to switch from BLOCKPLOT's "stack" approach to a "cluster" approach like BOXPLOT. (I've tried AXISTABLEs as well, but they behave like BLOCKPLOTs.)

Thanks for pointing out the right approach or suggesting a work-around.

Representative discussions I've already seen:

Plot from 9.4 (TS1M2) code, below: Plot from 9.4 (TS1M2) code, below

Obviously-manipulated plot that I would like to code: Obviously manipulated plot that I want from GTL

Code for first plot:

/* Set up test data structure, with few seed observations */
  proc sql;
    create table labstruct
      (  mygroup         char(3) label='Treatment Group'
       , myvisitnum      num     label='Visit number'
       , myvisitname     char(8) label='Visit name'
       , labtestname     char(8) label='Name of lab test'
       , labseed         num     label='Lab measurement seed'
       , lablow          num     label='Low end of normal range'
       , labhigh         num     label='High end of normal range'
      )
    ;
    insert into labstruct
      values('A', 1,  'Day 1',  'Test XYZ', 48, 40, 60)
      values('B', 1,  'Day 1',  'Test XYZ', 52, 40, 60)
      values('A', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
      values('B', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
      values('A', 10, 'Week 2', 'Test XYZ', 52, 40, 60)
      values('B', 10, 'Week 2', 'Test XYZ', 48, 40, 60)
    ;
  quit;

  /* Make more obs from the seeds above */
    data labdata;
      set labstruct;

      do repeat = 1 to 20;
        labvalue = labseed + 6*rannor(3297);
        if ranuni(59843)>0.1 then output;
      end;

      label labvalue = 'Lab measurement';
      drop repeat labseed;
    run;


/* Calculate summary stats, and merge onto lab data for use as "block" labels */
  proc summary data=labdata noprint;
    by myvisitnum mygroup;
    var labvalue;
    output out=labstats (drop=_type_) 
           n=n mean=mean std=std;
  run;

  /* Merge summary stats onto lab data, used in GTL template as "block" labels */
  data labdata;
    merge labdata (in=in_data)
          labstats (in=in_stats);
    by myvisitnum mygroup;
    label n      = 'n'
          mean   = 'Mean'
          std    = 'Std Dev';
  run;


proc template;
  define statgraph xalignedstats;
    dynamic _TRT _AVISIT _AVISITN _AVAL _STAT1 _STAT2 _STAT3;

    begingraph / border=false dataskin=none attrpriority=none;

      layout overlay / walldisplay=none ;

        innermargin / align=top separator=false pad=(top=0);
          blockplot x=_AVISITN block=_AVISIT /
                    display=(outline values) valuefitpolicy=split ;
        endinnermargin;

        boxplot x=_AVISITN y=_AVAL /
                group=_TRT groupdisplay=cluster clusterwidth=0.8 ;

        innermargin / align=bottom separator=false pad=(bottom=0);
          blockplot x=_AVISITN block=_TRT / class=_TRT label='Treatment' filltype=alternate;
          blockplot x=_AVISITN block=_STAT1 / class=_TRT label='n' filltype=alternate;
          blockplot x=_AVISITN block=_STAT2 / class=_TRT label='Mean' filltype=alternate;
          blockplot x=_AVISITN block=_STAT3 / class=_TRT label='Std Dev' filltype=alternate;
        endinnermargin;

      endlayout;

    endgraph;
  end;
run;

proc sgrender data=labdata template=xalignedstats;
  dynamic 
          _TRT     ='mygroup'
          _AVISITN ='myvisitnum' 
          _AVISIT  ='myvisitname' 
          _AVAL    ='labvalue'
          _STAT1   ='n'
          _STAT2   ='mean'
          _STAT3   ='std'
          ;
run;
1

There are 1 best solutions below

1
On BEST ANSWER

At least the core of this is answerable. If you use an axistable instead of a blockplot, you have the option of specifying classdisplay=cluster, which acts similarly to groupdisplay=cluster (why the names are different, no idea).

proc template;
  define statgraph xalignedstats;
    dynamic _TRT _AVISIT _AVISITN _AVAL _STAT1 _STAT2 _STAT3;

    begingraph / border=false dataskin=none attrpriority=none;

      layout overlay / walldisplay=none ;

        innermargin / align=top separator=false pad=(top=0);
          blockplot x=_AVISITN block=_AVISIT /
                    display=(outline values) valuefitpolicy=split ;
        endinnermargin;

        boxplot x=_AVISITN y=_AVAL /
                group=_TRT groupdisplay=cluster clusterwidth=0.8 ;

        innermargin / align=bottom separator=false pad=(bottom=0);
          axistable x=_AVISITN value=_TRT / class=_TRT label='Treatment' classdisplay=cluster colorgroup=_TRT;
          axistable x=_AVISITN value=_STAT1 / class=_TRT label='n'  classdisplay=cluster colorgroup=_TRT;
          axistable x=_AVISITN value=_STAT2 / class=_TRT label='Mean' classdisplay=cluster colorgroup=_TRT;
          axistable x=_AVISITN value=_STAT3 / class=_TRT label='Std Dev' classdisplay=cluster colorgroup=_TRT;
        endinnermargin;

      endlayout;

    endgraph;
  end;
run;

You may have a harder time (or impossible time) getting all of the formatting right, like the alternating colors, but this is at least a start. You could look into using colorgroup to make the colors match your line colors, for example, despite not having (much/any) control over background fill.