【问题标题】:Boxplot by groups, plus a user-defined scatter plot (markers for a subset of values)分组箱线图,加上用户定义的散点图(值子集的标记)
【发布时间】:2016-03-27 16:06:50
【问题描述】:

在处理实验室数据时,我想在按治疗分组并按时间点排序的箱线图上叠加数据点的子集。在 SAS 中将所有元素组合在一起并不简单,需要一种我自己无法设计或发现的巧妙方法:)

所需图的美妙之处在于它显示了 2 种不同类型的异常值:

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

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

我在下面演示了这种手动模拟重叠标记的组分离的方法——只是为了给出一个意图的概念。正如预期的那样,正常范围异常值与箱线图组不对齐。此外,同时满足异常值标准(统计和临床)的数据点显示为单独的点,而不是带有重叠标记的单个点。我的绿色注释:

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;

【问题讨论】:

  • 我现在正在看这个。我认为核心问题是箱线图使用离散X轴,而散点图使用线性轴;这应该可以在不使用线性轴的情况下解决,但我需要考虑如何解决。您能否添加您正在使用的 SAS 版本 - 显然是 9.4,因为上述某些版本与 9.3 不兼容,但是什么维护版本(TS1M3 或更早版本?)
  • 澄清一下 - 理想情况下,所有的“正常范围”异常值将沿着箱线图的中心对齐(就像箱线图异常值一样)?
  • 这篇显示 /groupdisplay=cluster 和 clusterwidth 选项的博文似乎可能相关。 blogs.sas.com/content/graphicallyspeaking/2013/03/24/…
  • @Quentin 不幸的是,这还不完全一致。它实际上要好一些(当使用离散轴完成时),但它并不理想(然后你有一个更难的时间离散抵消它)。我想知道是否有办法让它工作,尽管以某种方式向散点图添加填充或其他东西(箱线图似乎认为它的可用空间比散点图少一些)。
  • Joe 提供的帮助已经超出了我的能力范围。但是,如果您在他的帮助下没有成功,Sanjay Matange 会经常在 community.sas.com 上回复。

标签: sas scatter-plot boxplot outliers


【解决方案1】:

感谢您的见解!我被困在箱线图离散轴和散点图实轴之间的相同断开上。事实证明,在 SAS 9.4 中,散点图可以像 vbox 一样处理“类别”,但 SAS 将其称为 x 轴而不是类别。这个SAS 9.4 example 也帮助我破解了它(一旦我放弃了,自然:)。

这非常接近,并将大部分处理留给 SAS(我一直更喜欢稳健的解决方案):

更新后的代码:VBOX 中的“类别”是 SCATTER 的“x”。 注意 VBOX 和 SCATTER 的默认 cluster-width 是不同的,分别为 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;

再次感谢您让我如此迅速地回到正轨!

【讨论】:

  • 很好的解决方案。看起来 clusterwidth=0.7 确实适用于 2 和 3 种情况 - 所以这可能确实是一个真正通用的解决方案。我在测试它时一定错过了一些东西,因为当我尝试它时它看起来并不工作。
  • 我知道我之前已经测试过这个,但无法让它工作......这让我在其他没有结果的道路上走了一段时间。可能是较旧的 SAS 版本。我现在使用的是 SAS 9.4 TS Level 1M2。 SGPLOT 进展如此之快 - 我必须牢记这一点,然后再试一次。
  • 是的。另外 - 如果您还没有,请学习 GTL。 GTL 通常比 SGPLOT 领先一点(它只是 GTL 的一个前端),所以 GTL 中的一些功能将在一个次要版本中可用,或者在它们出现在 SGPLOT 之前。
  • 尝试 :) 我的脚趾在水里。必须克服最初的 GTL 学习曲线。
【解决方案2】:

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

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

但是,如果您将散点图设置为使用离散轴,则可以使用 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;

【讨论】:

  • 其他 cmets... 如果您还没有,我肯定会在 community.sas.com 上发布此内容,Sanjay 或 Dan H 可能有更简单的解决方案。 GTL 可能会让你做得更好,尽管我不知道有任何能力从中获得箱线图的确切位置。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-31
  • 2020-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-29
  • 2017-11-16
相关资源
最近更新 更多