按组分组的Boxplot,以及用户定义的散点图(值子集的标记)

时间:2015-12-21 14:35:14

标签: sas scatter-plot boxplot outliers

使用实验室数据,我希望在按治疗分组的盒子图上覆盖数据点的子集,并按时间点排序。在SAS中将所有元素组合在一起并不简单,并且需要一种我无法设计或发现自己的聪明方法:)

理想情节的美妙之处在于它显示了两种不同类型的异常值:

  • 箱形图包括统计异常值 - 方形标记(1.5 IQR)
  • 然后覆盖#34;正常范围的标记"异常值 - 临床定义,特定于每个实验室测试。

当对数据进行分组(例如,通过处理)然后通过另一个变量(例如,时间点)进行阻塞或分类时,这是困难的。 SAS在内部确定箱线图的间距,因此这种间距很难模拟叠加的正常范围数据标记。在这个方向上的通用解决方案将是一个不可靠的kludge。

我已经在下面演示了这种方法,手动模仿叠加标记的组间隔 - 只是为了给出意图的概念。正如预期的那样,正常范围异常值不与boxplot组对齐。此外,满足两个异常值标准(统计和临床)的数据点显示为单独的点,而不是具有重叠标记的单点。我的绿色注释:

SGPLOT-overlay-fail

是否有一种简单,强大的方式来指示SAS在箱线图上叠加分组数据点,保持所有内容按预期方式对齐?

以下是重现该遗失的代码:

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('A', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
    values('A', 10, 'Week 2', 'Test XYZ', 52, 40, 60)
    values('B', 1,  'Day 1',  'Test XYZ', 52, 40, 60)
    values('B', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
    values('B', 10, 'Week 2', 'Test XYZ', 48, 40, 60)
  ;
quit;

data labdata;
  set labstruct;

  * Put normal range outliers on 2nd axis, manually separate groups on 2nd axis *;
  select (mygroup);
    when ('A') scatternum = myvisitnum - 1;
    when ('B') scatternum = myvisitnum + 1;
    otherwise;
  end;

  * Make more obs from the seeds above *;
  label labvalue = 'Lab measurement';
  do repeat = 1 to 20;
    labvalue = labseed + 6*rannor(3297);

    * Scatter plot ONLY normal range outliers *;
    if labvalue < lablow or labvalue > labhigh 
       then scattervalue = labvalue;
    else scattervalue = .;

    output;
  end;
  drop repeat labseed;
run;

proc sgplot data=labdata;
  block x=myvisitnum block=myvisitname / 
        nofill 
        lineattrs=(color=lightgray);
  vbox labvalue / 
       category=myvisitnum
       group=mygroup
       outlierattrs=(symbol=square);
  scatter x=scatternum y=scattervalue /
       group=mygroup
       x2axis
       jitter;
  x2axis display=none;
  keylegend / position=bottom type=marker;
run;

2 个答案:

答案 0 :(得分:3)

所以 - 我认为这里有一个解决方案,但我不确定它有多普遍。当然,它只适用于双元素箱图。

您现在遇到的问题是,散点图默认的轴类型是线性的,而不是离散的,而箱线图默认是离散的。如果你按照这种方式进行设置,这总是会很混乱,尽管理论上你可以找出确切的差异并绘制它。您也可以使用注释工具,但它会遇到同样的问题。

但是,如果将散点图设置为使用离散轴,则可以使用discreteoffset选项使事情正常排列 - 或多或少。不幸的是,没有办法在散点图上使用group来告诉SAS在适当的箱线图上放置适当的标记,所以默认情况下一切都在离散轴的中心;所以你需要在这里使用两个单独的图,一个用于a,一个用于b,一个带负偏移,一个带正值。

discreteoffset的优点是它应该是任何两组箱图的常数值,除非你对箱宽做了一些改动;无论实际绘图有多大,离散偏移量应该相同(因为它是为该值指定的块总宽度的百分比)。

这里要考虑的一些事项包括在你的箱图中有六个元素而不是三个(所以摆脱group并且只有六个不同的visnum值,a_1 b_1等等);这将保证每个箱图集中在离散轴的中心(然后您的散点图将具有0离散偏移)。您也可以考虑滚动自己的箱图;例如,计算你自己的IQR,然后使用高 - 低图绘制框并通过注释绘制胡须,然后散点图所有不同的异常值(不仅仅是你的'正常')。

这里的代码似乎适用于您的特定示例,并且希望适用于大多数情况类似(有两个条形图)。对于3个条形,它也可能很容易(1个条带有0偏移,其他两个可能大约+/- 0.25)。除此之外,你开始需要做更多的计算来确定盒子的位置,但整体SAS将非常擅长将它们分开,所以它通常相当简单。

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('A', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
    values('A', 10, 'Week 2', 'Test XYZ', 52, 40, 60)
    values('B', 1,  'Day 1',  'Test XYZ', 52, 40, 60)
    values('B', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
    values('B', 10, 'Week 2', 'Test XYZ', 48, 40, 60)
  ;
quit;

data labdata;
  set labstruct;

  * Put normal range outliers on 2nd axis, manually separate groups on 2nd axis *;
  select (mygroup);
    when ('A') a_scatternum = myvisitnum;  /* Note the separate names now, but no added +/- 1 */
    when ('B') b_scatternum = myvisitnum;
    otherwise;
  end;

  * Make more obs from the seeds above *;
  label labvalue = 'Lab measurement';
  do repeat = 1 to 20;
    labvalue = labseed + 6*rannor(3297);

    * Scatter plot ONLY normal range outliers *;
    if labvalue < lablow or labvalue > labhigh 
       then scattervalue = labvalue;
    else scattervalue = .;

    output;
  end;
  drop repeat labseed;
run;

proc sgplot data=labdata noautolegend;  /* suppress auto-legend */
  block x=myvisitnum block=myvisitname / 
        nofill 
        lineattrs=(color=lightgray);
  vbox labvalue / 
       category=myvisitnum
       group=mygroup
       outlierattrs=(symbol=square) name="boxplot"; /* Name for keylegend */
  scatter x=a_scatternum y=scattervalue /     /* Now you have two of these - and no need for an x2axis */
       group=mygroup discreteoffset=-0.175
        jitter
       ;  
  scatter x=b_scatternum y=scattervalue /
       group=mygroup discreteoffset=0.175
        jitter
       ;
  keylegend "boxplot" / position=bottom type=marker;  /* Needed to make a custom keylegend or else you have a mess with three plots in it */
run;

答案 1 :(得分:1)

感谢您的见解!我被困在boxplot离散轴和散点图实轴之间的相同断开。事实证明,使用SAS 9.4,散点图可以处理&#34;类别&#34;像vbox一样,但SAS将其称为x轴而不是类别。这个SAS 9.4 example也帮助我解决了这个问题(一旦我放弃了,自然会这样做)。

这非常接近,并且将大部分处理留给SAS(总是我更喜欢强大的解决方案):

enter image description here

更新的代码:&#34;类别&#34;来自VBOX的是&#34; x&#34;对于SCATTER。 注意,VBOX和SCATTER的默认群集宽度分别为0.7和0.85,因此我必须将它们显式设置为相同的值:

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('A', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
    values('A', 10, 'Week 2', 'Test XYZ', 52, 40, 60)
    values('B', 1,  'Day 1',  'Test XYZ', 52, 40, 60)
    values('B', 5,  'Week 1', 'Test XYZ', 50, 40, 60)
    values('B', 10, 'Week 2', 'Test XYZ', 48, 40, 60)
  ;
quit;

data labdata;
  set labstruct;

  * Make more obs from the seeds above *;
  label labvalue = 'Lab measurement';
  do repeat = 1 to 20;
    labvalue = labseed + 6*rannor(3297);

    * Scatter plot ONLY normal range outliers *;
    if labvalue < lablow or labvalue > labhigh 
       then scattervalue = labvalue;
    else scattervalue = .;

    output;
  end;
  drop repeat labseed;
run;

proc sgplot data=labdata;
  block x=myvisitnum block=myvisitname / 
        nofill 
        lineattrs=(color=lightgray);
  vbox labvalue / 
       category=myvisitnum
       group=mygroup
       groupdisplay=cluster
       clusterwidth=0.7
       outlierattrs=(symbol=square);
  scatter x=myvisitnum y=scattervalue /
       group=mygroup
       groupdisplay=cluster
       clusterwidth=0.7
       jitter;
  keylegend / 
       position=bottom type=marker;
run;

再次感谢您让我快速回到正轨!