Коробчатая диаграмма по группам, а также определяемая пользователем диаграмма рассеяния (маркеры для подмножества значений)

Работая с лабораторными данными, я хочу наложить на коробчатую диаграмму подмножество точек данных, сгруппированных по лечению и упорядоченных по временным точкам. Объединить все элементы вместе в 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;

person Dante    schedule 21.12.2015    source источник
comment
Я сейчас смотрю на это. Я думаю, что основная проблема заключается в том, что в прямоугольной диаграмме используется дискретная ось X, а в диаграмме рассеяния - по линейной оси; это должно быть решено, не прибегая к использованию линейной оси, но мне нужно подумать о том, как это сделать. Можете ли вы добавить, какую версию SAS вы используете - очевидно, 9.4, поскольку некоторые из вышеперечисленных несовместимы с 9.3, но какой отладочный выпуск (TS1M3 или что-то более раннее?)   -  person Joe    schedule 21.12.2015
comment
И просто для пояснения - в идеале, все выбросы «нормального диапазона» должны быть выровнены по центру прямоугольной диаграммы (точно так же, как выбросы прямоугольной диаграммы)?   -  person Joe    schedule 21.12.2015
comment
Это сообщение в блоге, показывающее параметры / groupdisplay = cluster и clusterwidth, кажется потенциально актуальным. blogs.sas.com/content/graphicallyspeaking/2013 / 24/03 /   -  person Quentin    schedule 21.12.2015
comment
@Quentin К сожалению, это все еще не совсем подходит. На самом деле это немного лучше (когда сделано с дискретной осью), но не идеально (и тогда вам будет немного сложнее дискретно смещать его обратно). Интересно, есть ли способ заставить его работать, как-то добавляя отступы или что-то еще в диаграмму рассеяния (диаграмма, похоже, думает, что у нее немного меньше доступного места, чем у диаграммы рассеяния).   -  person Joe    schedule 21.12.2015
comment
Тем не менее, этот пост показывает хорошее альтернативное решение, которое может быть единственным действительно обобщенным решением - то есть, как использовать высокоуровневые графики для сворачивания ваших собственных коробчатых графиков.   -  person Joe    schedule 21.12.2015
comment
Спасибо за направление. Я использую SAS 9.4 TS уровня 1M2. @Quentin - Я сослался на несколько блогов с графическим языком. Полезно, но всегда не хватает одного элемента.   -  person Dante    schedule 21.12.2015
comment
Джо уже помогает больше, чем я мог бы. Но если вам не удастся с его помощью, Санджай Матанж часто откликается на community.sas.com.   -  person Quentin    schedule 21.12.2015
comment
Я также просто отмечу, что Boxplots труднее всего наложить - до недавнего времени они даже не допускали практически никаких наложений. Я не уверен, что им все еще следует ...   -  person Joe    schedule 21.12.2015


Ответы (2)


Итак - я думаю, что здесь есть решение, но я не уверен, насколько оно общее. Конечно, это работает только для двухэлементной коробчатой ​​диаграммы.

Проблема, с которой вы столкнулись прямо сейчас, заключается в том, что тип оси по умолчанию для диаграммы рассеяния является линейным, а не дискретным, в то время как коробчатая диаграмма по умолчанию является дискретной. Это всегда будет беспорядочно, если вы настроите это таким образом, хотя теоретически вы могли бы вычислить точную разницу и построить ее. Вы также можете использовать функцию аннотирования, хотя она будет иметь ту же проблему.

Однако, если вы настроили диаграмму рассеяния для использования дискретной оси, вы можете использовать опцию 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;
person Joe    schedule 21.12.2015
comment
Другие комментарии ... Я бы определенно разместил это на community.sas.com, если вы еще этого не сделали, у Санджая или Дэна Х может быть более простое решение. GTL может позволить вам сделать это лучше, хотя я также не знаю о какой-либо возможности получить точное положение коробчатых графиков на основе этого. - person Joe; 21.12.2015

Спасибо за понимание! Я застрял на одном и том же разъединении между дискретной осью прямоугольной диаграммы и реальной осью диаграммы рассеяния. Оказывается, с SAS 9.4 диаграммы разброса могут обрабатывать «категории», как vbox, но SAS называет это осью x, а не категорией. Этот пример SAS 9.4 a> также помог мне его взломать (естественно, сразу после того, как я сдался :).

Это довольно близко, и большая часть обработки предоставляется SAS (я всегда предпочитаю надежное решение):

введите описание изображения здесь

Обновленный код: «Категория» из VBOX - это «x» для 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;

Еще раз спасибо за то, что так быстро вернули меня в нужное русло!

person Dante    schedule 21.12.2015
comment
Хорошее решение. Похоже, что clusterwidth = 0,7 работает как для 2, так и для 3 случаев, так что это действительно может быть действительно общее решение. Должно быть, я что-то пропустил, когда тестировал это, так как не было похоже, что это работает, когда я попробовал. - person Joe; 21.12.2015
comment
Я знаю, что тестировал это раньше и не мог заставить его работать ... что на какое-то время привело меня к другим бесплодным путям. Это могла быть более старая версия SAS. Сейчас я использую SAS 9.4 TS Level 1M2. SGPLOT движется так быстро, что я должен помнить об этом и пробовать, пробовать еще раз. - person Dante; 21.12.2015
comment
Ага. Также - если вы еще этого не сделали, изучите GTL. GTL обычно немного опережает SGPLOT (который является всего лишь интерфейсом для GTL), поэтому некоторые функции будут доступны в младшей версии GTL или около того до того, как они появятся в SGPLOT. - person Joe; 21.12.2015
comment
Пробуем :) Мой палец ноги в воде. Надо пройти начальную кривую обучения GTL. - person Dante; 21.12.2015