Создание таблиц

Кроме графиков еще одним важнейшим методом представления данных как для публикаций, так и для презентаций являются таблицы. В SAS таблицы могут создавать различные процедуры, начиная с MEANS и кончая FREQ. Однако многие специалисты рекомендуют для использования специальную “табличную” процедуру, которая так и называется TABULATE. Мощь этой процедуры заключается в том, что она позволяет создавать многомерные таблицы как количественных, так и качественных данных, а с появлением в 8 версии SAS ODS (output delivery system) стало возможным создавать эти таблицы сразу же в формате текстового процессора (rtf) или электронных печатных документов (pdf). Эти возможности затмевают некоторую сложность процедуры TABULATE, особенно ее необычный для SAS синтаксис.

Дело в том, что процедура TABULATE была разработана программистами Министерства Труда США и затем была внесена в набор базовых процедур SAS. Для того, чтобы освоить эту процедуру следует начать с простейшего примера ее использования.

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

Далее надо указать процедуре, какая переменная в данной таблице (или переменные) является качественной (команда VAR). После этого можно вызывать команду построения таблицы TABLE. В этой команде, как минимум, должна быть указана одна из описанных выше как количественная или качественная (см. ниже) переменная. Если в команде TABLE будет указана переменная, не описывавшаяся раньше, процедура закончит свою работу выдав сообщение об ошибке.

Если мы вызовем процедуру с указанием одной количественной переменной, например, так:

PROC TABULATE DATA=mydat.nm99;
VAR sbp;
TABLE sbp;
RUN;

то процедура создаст таблицу из одной ячейки, где будет указана сумма всех значений данной переменной:

--------------
| SBP        |
|------------|
| SUM        |
|------------|
| 15139.00   |
--------------

Однако нас интересует не сумма значений переменной sbp (систолическое артериальное давление), а среднее значение. Для того, чтобы указать, какой именно статистический показатель мы хотим внести нам надо будет указать его наименование через звездочку связав его с именем переменной (sbp*MEAN). Результат будет почти аналогичен, только вместо суммы в таблице будет стоять среднее значение.

 --------------
 |    SBP     |
 |------------|
 |    MEAN    |
 |------------|
 |      151.39|
 --------------

Теперь попробуем добавить в таблицу дополнительные столбцы. Делается это просто при помощи описания соответствующих переменных как количественные или качественные, а затем внесение в команду TABLE. Для каждой переменной можно описать свою статистику (через звездочку), а можно использовать одну статистику для всех переменных. Тогда надо просто заключить переменные в круглые скобки, а статистику связать звездочкой со скобками:

PROC TABULATE DATA=mydat.nm99;
VAR sbp dbp;
TABLE (sbp dbp)*MEAN;
RUN;

Результат показан ниже:

---------------------------
|    SBP     |    DBP     |
|------------+------------|
|    MEAN    |    MEAN    |
|------------+------------|
|      151.39|       90.78|
---------------------------

Надо заметить, что аналогично тому, как мы перечисляем несколько переменных для того, чтобы для всех них заказать одну статистику, также мы можем перечислить несколько статистик (в скобках) и связать их с одной переменной, например, если нам нужно количество наблюдений и среднее для систолического АД, мы можем написать:

TABLE sbp*(N MEAN);

Кроме того, не обязательно заказывать одинаковые статистические показатели для всех переменных, которые мы вносим в таблицу. Например, если количество лиц у которых мы измерили САД и ДАД одинаковое, нам вряд ли надо два раза указывать их количество. Мы можем воспользоваться следующей записью:

PROC TABULATE DATA=mydat.nm99;
VAR sbp dbp;
TABLE sbp*N (sbp dbp)*MEAN;
RUN;

Результатом будет следующая таблица:

 ---------------------------------------- 
 |    SBP     |    SBP     |    DBP     | 
 |------------+------------+------------| 
 |     N      |    MEAN    |    MEAN    | 
 |------------+------------+------------| 
 |      100.00|      151.39|       90.78| 
 ---------------------------------------- 

Уже неплохо, но пока еще далеко от совершенства. Во-первых, нам явно не нужна подпись sbp в столбце с количеством наблюдений. Во-вторых, количество наблюдений не может быть дробным и, соответственно, два десятичных знака после запятой нам абсолютно не нужны. Для решения этих проблем нам придется поменять подписи в столбцах и изменить формат наблюдений.

Изменять подписи в столбцах достаточно просто. Надо выбрать, подпись к какому показателю мы хотим поменять, затем через знак равенства написать новую подпись и все. В данном примере мы хотим, чтобы вместо первой подписи sbp шло пустое место. Напишем соответствующую программу:

PROC TABULATE DATA=mydat.nmu99;
VAR sbp dbp;                   
TABLE sbp=' '*N (sbp dbp)*MEAN;
RUN; 

Обратите внимание на то, что мы приравняли первое появление sbp знаку пробела и в таблице эта подпись исчезла:

 ----------------------------------------
 |            |    SBP     |    DBP     |
 |            |------------+------------|
 |     N      |    MEAN    |    MEAN    |
 |------------+------------+------------|
 |      100.00|      151.39|       90.78|
 ----------------------------------------

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

Ранее мы уже встречались с процедурой создания формата FORMAT. Эта процедура позволяет заменить при распечатки качественные наблюдения закодированные числами метками, содержащими буквенно-цифровые обозначения. Для создания описания соответствия цифр меткам используется команда VALUE. Однако нам сейчас нужно другое - заменить числовые значения в формате с десятичной точкой на значения в целочисленном формате. Для этого используется команда PICTURE. Команда PICTURE содержит описание формата, диапазон приемлемых значений и собственно формат. Например, для нашего случая мы можем написать:

PICTURE num LOW-HIGH=’000’;

Здесь мы указываем, что имя данного формата num, что диапазон значений не ограничен (простирается от самого низкого значения в файле данных - LOW, до самого высокого значения - HIGH). Сами же данные должны быть не более трех знаков в длину, без десятичной точки. Мы можем описать одной командой PICTURE несколько форматов, а в одной процедуре FORMAT может содержаться несколько форматных команд.

Теперь немного усложним задачу, нам надо иметь в таблице не только средние значения, но и ошибки средних. Мы их хотим пометить М и m, а для ошибки средней использовать префикс “±”. При этом в ошибке средней должно быть два знака после десятичной запятой.

Для выполнения поставленной задачи мы определили новый формат - se, который должен включать четыре позиции с двумя знаками после десятичной запятой. Обратите внимание, что мы обозначили места, где должны быть подставлены значения нулями и девятками. Дело в том, что если использовать только нули (знак означает, что цифра может быть и не напечатана) иногда исчезает все значение. Кроме того, в данном формате значения имеют префикс в виде символа “плюс-минус”. Теперь остается только связать определение формата с теми значениями, для которых он должен использоваться. В процедуре TABULATE это делается с помощью опции f= (как читатель догадывается от английского format - формат). Опция f “привязывается” к нужному нам значению также при помощи знака “звездочки”. Вот как выглядит созданная нами программа:

PROC FORMAT;                                        
PICTURE num  LOW-HIGH='000';                        
PICTURE se LOW-HIGH='09.99' (PREFIX='±');           
RUN;                                                
PROC TABULATE DATA=MYDAT.NMU99;                     
VAR sbp dbp;                                        
TABLE sbp=' '*N*F=num. 
 sbp*(MEAN='M' STDERR='±m'*F=se.)
 dbp*(MEAN='M' STDERR='±m'*F=se.);                   
RUN;   

А вот созданная с ее помощью табличка:

  ------------------------------------------- 
  |   |       SBP        |       DBP        | 
  |   |------------------+------------------| 
  | N |     M      | ±m  |     M      | ±m  | 
  |---+------------+-----+------------+-----| 
  |100|      151.39|±2.63|       90.78|±1.55| 
  ------------------------------------------- 

Однако редко нам нужна бывает таблица, состоящая только из одной строки. Чаще всего необходимо бывает разбить суммарные данные по еще какой-то классифицирующей переменной и сделать несколько строк в таблице, каждая из которых будет отражать отдельный класс данной переменной. В нашем случае это может быть, например, переменная содержащая информацию о том, курит ли данный пациент (smpr). Поскольку эта переменная классифицирующая, процедуре TABULATE надо сообщить об этом при помощи команды CLASS (понятно, сокращение от CLASSIFICATION - классифицирующая).

Далее возможны два варианта - мы хотим, чтобы классифицирующая переменная располагалась в колонках (т.е. каждый столбец данных был поделен на несколько частей, соответственно уровням этой классифицирующей переменной) или, как мы хотели ранее классифицирующая переменная будет расположена в строках. Для первого варианта надо просто добавить имя переменной первой в списке через знак “звездочки”. Если мы так сделаем для нашего примера (smpr*sbp*(mean stderr)), у нас будут созданы несколько новых столбцов, каждый из которых будет нести информацию о среднем и ошибке среднего.

   ------------------------------------- 
  |        0         |        1         | 
   ------------------------------------- 
  |       SBP        |       SBP        | 
  |------------------+------------------| 
  |     M      | ±m  |     M      | ±m  | 

Если мы хотим, чтобы были поделены на отдельные колонки

   -------------------------------------  
  |                 SBP                 | 
   ------------------------------------- 
  |        M         |       ±m         | 
  |------------------+------------------| 
  |    0    | 1      |     0    | 1     |   

нам следует добавить классифицирующую переменную через “звездочку” в конце (например, (sbp*mean sbp*stderr)*smpr, после каждого показателя, который следует делить. Надо заметить, что использование круглых скобок сокращает объем работы, зато их раскрытие позволяет лучше контролировать внешний вид таблицы.

Однако вернемся к нашей основной задаче - мы хотим поставить классифицирующую переменную в строках. Добавление строк в процедуре TABULATE проводится путем ввода указания на классифицирующую переменную перед основным описанием таблицы (перед описанием столбцов) через запятую, например,

smpr, sbp*(mean stderr)

Иными словами, создание таблицы в процедуре TABULATE очень логично - если смотреть слева-направо, как мы читаем таблицу, то первой идет классифицирующая переменная (в строках), затем запятая и после этого первые столбцы, вторые столбцы и т.д.

Целиком программа может выглядеть так:

PROC FORMAT;                                     
PICTURE num  LOW-HIGH='000';                     
PICTURE se LOW-HIGH='09.99' (PREFIX='±');        
VALUE smk 0='YES' 1='NO';                        
RUN;                                             
PROC TABULATE DATA=mydat.nmu99;                  
LABEL smpr= 'SMOKE?';                            
FORMAT smpr smk.;                                
VAR sbp dbp;  
CLASS smpr;                        
TABLE smpr,                                      
 sbp=' '*N*F=num. 
 sbp*(MEAN='M' STDERR='±m'*F=se.)
 dbp*(MEAN='M' STDERR='±m'*F=se.);                
RUN;

В данной программе появилось несколько компонент, знакомых читателю по другим разделам книги, но впервые использующихся в данном разделе. Во-первых в процедуре FORMAT мы создали форматное определение (команда VALUE), которое заменяет числовые значения, используемые в переменной smpr (курение), на более понятные - “да” (yes) или “нет” (no). Затем, уже в процедуре TABULATE, мы связали это форматное определение с переменной smpr (команда FORMAT), и определили метку для переменной smpr (команда LABEL). Мы могли установить метку и в команде TABULATE, но решили продемонстрировать иное решение проблемы. Описание переменных включало две команды - для количественных переменных VAR и для качественных - CLASS. Затем мы описали переменную в строках таблицы (указав переменную smpr), поставили запятую и детально описали переменные столбцов включая формат в ячейках.

Результат должен выглядеть примерно так:

        -------------------------------------------------------------
        |                 |   |       SBP        |       DBP        |
        |                 |   |------------------+------------------|
        |                 | N |     M      | ±m  |     M      | ±m  |
        |-----------------+---+------------+-----+------------+-----|
        |smoke?           |   |            |     |            |     |
        |-----------------|   |            |     |            |     |
        |yes              | 32|      150.62|±4.83|       89.69|±3.20|
        |-----------------+---+------------+-----+------------+-----|
        |no               | 54|      151.98|±3.80|       90.91|±2.07|
        -------------------------------------------------------------

Переменных в строках также может быть больше одной, они могут формировать подгруппы, например, если мы бы захотели посмотреть уровни давления в зависимости от уровня образования и отношения к курению. Тогда (если игнорировать форматирование), все, что нам надо было бы написать сводилось бы к:

TABLE educ*smpr, (sbp dbp)*mean;

Надо только не забыть описать переменную educ как классифицирующую. В принципе, в столбцах могут стоять и количественные переменные, единственное правило, в таблице не должно быть ячеек образованных двумя количественными переменными (почему, наверное, очевидно). Однако ничто не мешает нам превратить количественную переменную в качественную. Например, мы хотим проиллюстрировать, как уровень артериального давления меняется при изменении возраста. Тогда единственно, что нам потребуется, это ввести дополнительный шаг DATA, который создаст новую переменную, например ageg (группы по возрасту), которая будет принимать значения 1 если обследованный моложе 50 лет и 2 если старше. Введем также дополнительный формат, который будет заменять эти значения на метки, объясняющие читателю, из кого составлена данная группа:

PROC FORMAT;                                     
PICTURE num  LOW-HIGH='000';                     
PICTURE se LOW-HIGH='09.99' (PREFIX='±');        
VALUE smk 0='YES' 1='NO';                        
VALUE  ageg 1='<50 Y' 2='50+ Y';                 
RUN;                                             
DATA new; 
SET mydat.nmu99;                       
IF age< 50 THEN ageg=1; ELSE ageg=2;             
KEEP smpr ageg sbp dbp; RUN;                     
PROC TABULATE DATA=new;                          
LABEL smpr= 'SMOKE?' ageg='AGE';                 
FORMAT smpr smk.  ageg  ageg.;                   
VAR sbp dbp;  
CLASS smpr ageg;                   
TABLE ageg*smpr,                                 
 sbp=' '*N*F=num. 
 sbp*(MEAN='M' STDERR='±m'*F=se.)
 dbp*(MEAN='M' STDERR='±m'*F=se.);                
RUN;  

Обратите внимание на то, что мы использовали команду KEEP для того, чтобы оставить в результирующем файле только четыре переменных. Это позволяет не переписывать в файл 108 других переменных, что должно ускорить обработку запроса. Пользователи мощных машин, возможно, и не заметят разницы, однако, например, iPAQ 3870 “задумался” при отсутствии данной команды минуты на две, прежде чем полностью скопировал 100 наблюдений в новый файл, тогда как при использовании команды KEEP он выполнил все процедуру за несколько секунд.

Затем мы запустили процедуру TABULATE, связали форматы с соответствующими переменными, описали переменный как качественные или количественные и вызвали основную команду - TABLE с двумя перменными строк (ageg и smpr), связанными “звездочкой”, и двумя переменными столбцов (sbp и dbp) к каждой из которых “привязано” по два статистических показателя и еще один показатель связан только с одной переменной - sbp. В результате получается вот такая таблица:

        -------------------------------------------------------------
        |                 |   |       SBP        |       DBP        |
        |                 |   |------------------+------------------|
        |                 | N |     M      | ±m  |     M      | ±m  |
        |-----------------+---+------------+-----+------------+-----|
        |age     |smoke?  |   |            |     |            |     |
        |--------+--------|   |            |     |            |     |
        |<50 y   |yes     |  7|      142.43|±7.31|       86.43|±5.35|
        |        |--------+---+------------+-----+------------+-----|
        |        |no      | 27|      147.26|±5.72|       90.85|±3.30|
        |--------+--------+---+------------+-----+------------+-----|
        |50+ y   |yes     | 25|      152.92|±5.82|       90.60|±3.84|
        |        |--------+---+------------+-----+------------+-----|
        |        |no      | 27|      156.70|±4.94|       90.96|±2.58|
        -------------------------------------------------------------

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

Второй проблемой мы займемся чуть позже, а пока попробуем сотворить табличку с русскими буквами. Тут нам потребуется уже версия SAS для Windows. К сожалению, процедура TABULATE относится к модулю SAS/BASE и, соответственно, использование графического шрифта CYRILLIC для нее заказано.

Ранее мы уже разбирали, как в SAS 6.12 запустить использование кириллических шрифтов в редакторе программ (что автоматически позволит использовать их и в окне результатов). Таким образом если система уже настроена на работу с кириллическими шрифтами, то единственно, что потребуется сделать, это заменить в процедуре FORMAT форматные определения на определения, содержащие русские обозначения, перевести на русский метки переменных и метки в команде TABLE процедуры TABULATE (если это надо, например менять в вышеописанном примере M и m на что-нибудь другое не стоит).

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

-----------------------------------------------------------------
|                     |   |       САД        |       ДАД        |
|                     |   |------------------+------------------|
|                     | N |     M      | ±m  |     M      | ±m  |
|---------------------+---+------------+-----+------------+-----|
|Возраст   |Курит?    |   |            |     |            |     |
|----------+----------|   |            |     |            |     |
|моложе 50 |Да        |796|      133.55|±0.70|       89.21|±0.40|
|лет       |----------+---+------------+-----+------------+-----|
|          |Нет       |161|      135.08|±0.58|       88.08|±0.34|
|----------+----------+---+------------+-----+------------+-----|
|старше 50 |Да        |608|      145.79|±0.99|       91.19|±0.51|
|лет       |----------+---+------------+-----+------------+-----|
|          |Нет       |677|      145.99|±0.90|       89.36|±0.46|
----------------------------------------------------------------

Если исследователю требуется создать табличку на русском языке в системе 6.04 или в его операционной системе кириллические шрифты отсутствуют, то можно воспользоваться обходным маневром, задействовав-таки SAS/GRAPH. Для этого надо будет вначале сохранить создаваемую процедурой TABULATE табличку во внешнем файле. Подписи надо создавать так, как это делалось при использовании шрифта CYRILLIC (транслитерацией). Затем этот файл надо будет открыть и напечатать при помощи процедуры GPRINT, которая создает графическую копию таблицы:

GOPTIONS RESET=ALL;
FILENAME LISFILE "C:\TEMP\TABOUT.LIS";
PROC PRINTTO PRINT=LISFILE NEW ;
RUN;
PROC FORMAT;
 PICTURE num  LOW-HIGH='000';
 PICTURE se LOW-HIGH='09.99' ;
 VALUE smk 0='Da ' 1='Net';
 VALUE  ageg 1='molohe 50 let' 2='starqe 50 let';
RUN;
DATA new;
 SET iem.mu99;
 IF age<50 THEN ageg=1; ELSE ageg=2;
 KEEP smpr ageg sbp dbp;
RUN;
PROC TABULATE DATA=new FORMCHAR='+----+++---';
 LABEL smpr= 'Kurit?' ageg='Vozrast';
 LABEL sbp='SAD' dbp='DAD';
 FORMAT smpr smk.  ageg  ageg.;
 VAR sbp dbp;
 CLASS smpr ageg;
 TABLE ageg*smpr,
  sbp=' '*N*F=num.
  sbp*(MEAN='M' STDERR='m'*F=se.)
  dbp*(MEAN='M' STDERR='m'*F=se.)/ROW=FLOAT;
RUN;
PROC PRINTTO;
RUN;
GOPTIONS HTEXT=0.6 DEVICE=WIN FTEXT=CYRILLIU;
PROC GPRINT FILEREF=LISFILE;
RUN;

Надо только обратить внимание на то, чтобы для печати использовался не шрифт CYRILLIC (это пропорциональный шрифт - ширина букв различна), а шрифт CYRILLU (моноширинный шрифт, с одинаковой шириной букв, аналог Courier). Табличка, которая получится не так красива, как изготавливаемые при помощи процедур экспорта в HTML, однако для экстренных случаев вполне сойдет.

Теперь можно заняться другой насущной проблемой - сохранением таблицы в “табличном” формате, а не в виде последовательности символов, разделенный псевдосимволами. Для этого нужен формат, который поддерживает таблицы. Для версии 6.12 SAS таким форматом будет HTML, для версии 8 - как HTML, так и RTF.

Для сохранения результатов процедуры TABULATE в формате таблицы HTML существует специальная макропрограмма (TAB2HTM), имеющаяся в стандартной поставке системы. Для того, чтобы эта программа записала результаты в HTML файл, ее надо вызвать два раза - первый раз отметив место, с которого следует начать запись, а второй раз там, где запись должна быть закончена. На практике это означает вызвать ее перед процедурой TABULATE и сразу же после нее. SAS Institute предупреждает, что использование макропрограммы для записи результатов работы других процедур может привести к непредсказуемым последствиям

Первый вызов TAB2HTM осуществляется командой

%TAB2HTM (CAPTURE=ON, RUNMODE=B);
OPTIONS FORMCHAR=’82838485868788898A8B8C’X;

После процедуры TABULATE мы пишем:

%TAB2HTM (CAPTURE=OFF, RUNMODE=B, OPENMODE=REPLACE, HTMLFILE=’d:\test.htm’);

Строго говоря, первый вызов просто сообщает, что системе надо начинать создание HTML-файла. Используется две опции - CAPTURE=ON (“захват”=вкл.), иными словами собственно команда старта захвата данных для трансформации в HTML и RUNMODE=B, которая устанавливает пакетный (batch), т.е. автоматический, режим работы макропрограммы. Понятно, что если есть автоматический, то должен быть и ручной (интерактивный, по терминологии SAS) режим захвата данных. Он действительно существует, запускается при помощи команды %TAB2HTM(RUNMODE=I), но мы его разбирать не будем, остановимся только на автоматическом режиме.

Собственно создание файла осуществляется при повторном вызове программы TAB2HTM с опцией CAPTURE=OFF. Тогда же мы должны указать имя файла, куда будет записываться результат (опция HTMLFILE). Для того, чтобы система не “ругалась” если указанный файл существует, мы должны указать опцию OPENMODE с ключевым словом REPLACE. Тогда новый файл будет записан поверх старого. Если мы хотим добавить таблицу в конец существующего файла, нам следует воспользоваться ключевым словом APPEND в той же опции.

После выполнения всех этих команд, будет создан HTML файл, содержащий таблицу с необходимыми нам данными, причем русские буквы в ней будут русскими буквами. Теперь эту таблицу можно скопировать в буфер обмена и вставить в документ MS Word или же открыть сам HTML файл в MS Word и, отредактировав его там, сохранить в формате документа Word, а не HTML.

Полностью программа создания таблицы и результат приведены ниже:

PROC FORMAT;
PICTURE num  LOW-HIGH='000';
PICTURE se LOW-HIGH='09.99' (PREFIX='±');
VALUE smk 0='Да ' 1='Нет';
VALUE  ageg 1='моложе 50 лет' 2='старше 50 лет';
RUN;
DATA new;
SET iem.mu99;
IF age<50 THEN ageg=1; ELSE ageg=2;
KEEP smpr ageg sbp dbp;
RUN;

%TAB2HTM (CAPTURE=ON, RUNMODE=B);
OPTIONS FORMCHAR='82838485868788898A8B8C'X;
PROC TABULATE DATA=new;
LABEL smpr= 'Курит?' ageg='Возраст';
LABEL sbp='САД' dbp='ДАД';
FORMAT smpr smk.  ageg  ageg.;
VAR sbp dbp;
CLASS smpr ageg;
TABLE ageg*smpr,
 sbp=' '*N*F=num.
 sbp*(MEAN='M' STDERR='±m'*F=se.)
 dbp*(MEAN='M' STDERR='±m'*F=se.);
RUN;
%TAB2HTM(CAPTURE=OFF, RUNMODE=B,
OPENMODE=REPLACE, HTMLFILE='D:\SAMPLE.HTM');

 

N

САД

ДАД

 

M±m

M±m

Возраст

Курит?

796

133.55±0.70

89.21±0.40

моложе 50 лет

Да

Нет

161

135.08±0.58

88.08±0.34

старше 50 лет

Да

608

145.79±0.99

91.19±0.51

Нет

677

145.99±0.90

89.36±0.46

Мы немного улучшили таблицу в MS WORD использовав команду слияния ячеек таблицы и убрали промежутки между колонками, описывающими средние значения и ошибки средних.

Для экономии времени и отказа от необходимости помнить все команды, исследователь может создать макропрограмму, которая будет создавать таблицу и сохранять ее в HTML файле (tab.htm) в корневом каталоге SAS. Выглядеть эта программа должна примерно так:

%MACRO TABIT(stroka, stolbec, peremen, formatt, stat);
%TAB2HTM (CAPTURE=ON, RUNMODE=B);
OPTIONS FORMCHAR='82838485868788898A8B8C'X;
PROC TABULATE DATA=temp;
CLASS &stroka &stolbec;
VAR &peremen;
TABLE &stroka, &stolbec*&peremen*(&stat)*&formatt/ ROW=FLOAT;
RUN;
%TAB2HTM (CAPTURE=OFF, RUNMODE=B, OPENMODE=REPLACE, HTMLFILE=tab.htm);
%MEND TABIT;

Данная программа предполагает, что пользователь записал данные в файл temp и создал все необходимы форматные определения, а также присвоил их качественным переменным одновременно с метками самих переменных (это можно сделать в шаге DATA, когда исходные данные будут копироваться в файл temp). Далее он вызывает программу TABIT, указав переменные в строках, столбцах и переменную, для которой мы хотели бы рассчитать статистические показатели. Например, если у нас есть адекватно оформленный файл (со всеми форматами уже присвоенными) под названием fmu99, и мы хотим построить таблицу уровней САД в зависимости от курения и образования, мы можем воспользоваться следующими командами:

DATA temp; SET fmu99;
RUN;
%TABIT(educ, smpr, sbp, 6.2, N MEAN);

Конечно, пользоваться такой макропрограммой проще, но за счет этого исследователь теряет гибкость, присущую процедуре TABULATE, поэтому мы рекомендовали бы составить описание таблицы средствами процедуры TABULATE и пользоваться макропрограммой только если надо быстро составить простую табличку.

Следует еще отметить, что процедура TABULATE может использоваться для построения таблиц по данным, сгенерированным другими процедурами. Например, нам необходимо ввести в таблицу медиану, а процедура TABULATE не рассчитывает этот статистический показатель. Тогда мы можем использовать следующий трюк - рассчитать медиану в процедуре UNIVARIATE, сохранить результаты в файле данных, затем совместить эти данные с исходным файлом и использовать результат для создания таблицы процедурой TABULATE.

PROC SORT DATA=iem.mu99; 
 BY educ smpr; 
RUN; 
PROC UNIVARIATE DATA=iem.mu99 NOPRINT; 
 BY educ smpr; 
 VAR sbp; 
 OUTPUT OUT=tempmed MEDIAN=medsbp; 
RUN; 
DATA temp; 
 MERGE iem.mu99 tempmed; 
BY educ smpr; 
RUN; 
PROC TABULATE DATA=temp; 
 CLASS educ smpr; 
 VAR sbp medsbp; 
 TABLE educ*smpr=' ', 
  sbp=' '*(N MEAN STD) medsbp=' '*MEAN='MEDIAN'; 
RUN;

В данной программе мы вначале создали отсортированный по качественным переменным educ и smpr (которые потом будут определять строки нашей таблицы) файл, рассчитали медианные значения и сохранили их в файле. Затем мы внесли эти данные в новый файл, составленный из исходного файла и файла, созданного процедурой UNIVAIATE. При вносе медианы в исходный файл одно значение повторялось для данной комбинации значений educ и smpr столько раз, сколько соответствующих наблюдений было в файле. Затем уже этот файл использовался для построения таблицы.

Мы можем использовать в качестве входного файла процедуры TABULATE данные, генерируемые процедурой FREQ, а также результаты ряда других процедур. К сожалению, не все статистические процедуры позволяют сохранять результаты в файле данных, поэтому использовать их напрямую в процедуре TABULATE невозможно.

В 8 версии SAS появились дополнительные инструменты экспорта результатов работы в различные другие форматы. Эта подсистема получила название ODS (output delivery system - система направления результатов).

Появление ODS символизировало достаточно серьезное изменение идеологии представления выходных данных в SAS. До появления этой системы у пользователя было два пути сохранения результата - листинг (текстовые таблицы, содержащие описание результатов анализа) или специальные OUTPUT - файлы, которые создавали ряд процедур. Листинг обычно содержал всю необходимую информацию, но дополнительная обработка результатов, переданных туда (например, убрать или добавить столбцы в таблице, поменять подписи) была сложной и обычно выполнялась после копирования результатов в текстовом процессоре. С другой стороны, OUTPUT-файлы позволяли легкую обработку и модификацию данных, однако возможность их создания существовала отнюдь не во всех процедурах и часто они не включали все нужные исследователю параметры.

С появлением ODS ситуация резко изменилась. Все результаты стали рассматриваться как набор объектов, которые можно направить туда, куда хочет исследователь. По умолчанию, все объекты направляются в окно результатов, т.е. создают знакомый по всем предыдущим версиям листинг. Однако ничто не мешает пользователю найти интересующую его таблицу и указать, что она должна быть направлена в файл данных или только она должна быть направлена в файл результатов (или листинг). Причем обычно, результат работы процедуры представлен не одним, а несколькими объектами, что повышает контроль пользователя за тем, какие данные он сохраняет или, наоборот, хочет изъять.

В 8 версии SAS было создано несколько направлений (“контейнеров”) для ODS - листинг (list), принтер (printer), файл данных (OUTPUT-файл, output), гипертекстовой файл (HTML) и RTF-файл (RTF). Надо заметить, что официально до производственной стадии RTF в 8 версии не дошел и стал полностью поддерживаться SAS Institute только в 9 версии (здесь стоит заметить, что сама ODS присутствовала уже в версии 6.12, но только в экспериментальной версии и с поддержкой очень ограниченного числа результирующих форматов).

Перенаправление результата работы процедуры в тот или иной “контейнер” ODS мероприятие достаточно простое. Надо только указать, что ODS включена и куда надо направлять результат. Например, чтобы результат пошел в HTML-файл следует написать ODS HTML BODY=имя_файла; завершение процесса захвата и перенаправления объектов осуществляется командой ODS HTML CLOSE. Иными словами мы указываем точку начала захвата и имя файла, а затем точку окончания захвата.

К сожалению, эта простая в теории система разбивается о проблемы с кириллическими шрифтами. Попытка создать HTML или RTF файлы с русскими буквами приводит к появлению "крякозябр", которые никаким образом не не хотят превращаться в русские буквы (если речь идет об импортированном в MS Word RTF-файле). Причиной тому является автоматическое указание языка (кодовой страницы) в заголовках HTML и RTF-файлов. Стандартная кодовая таблица для файлов на английском языке Windows-1252 (она также включает специальные символы европейских языков), тогда как кириллическая таблица обозначается Windows-1251. Для RTF-файла относительно простым, хотя и грубым решением является редактирование заголовка файла в простом текстовом редакторе, не понимающем RTF (для того, чтобы он не распознал, что это служебный заголовок). Таким редактором является, например, "Блокнот" Windows. После загрузки файла в редактор надо найти строчку, содержащую слово ansicpg1252 и исправить ее на ansicpg1251. Затем следует изменить строку fcharset0 на fcharset204 (русские буквы, наиболее важная часть операции), сохранить файл и загрузить его в Word. Хотя работы и немного, но выполнять ее каждый раз вряд ли интересно, кроме того, чем больше файл RTF тем ниже вероятность, что он поместится в "Блокнот" (правда, файл должен быть действительно большим для того, чтобы это произошло…)

Для обхода проблем надо воспользоваться экспортом в HTML. С ходу на экране будут все те же "крякозябры", поскольку SAS автоматически вставит строчку "charset=windows-1252" в заголовок файла, тогда как там должно быть "charset=windows-1251". К счастью, мы можем попросить SAS создать заголовок заранее и лишь присоединить к нему текст таблицы, использовав следующий код:

FILENAME example 'd:\new3.htm' MOD;
DATA _NULL_;
 FILE example;
 PUT '<HTML>';
 PUT '<HEAD>';
 PUT '<TITLE>SAS OUTPUT&l;/TITLE>';
 PUT '&l;META HTTP-EQUIV="CONTENT-TYPE" CONTENT="TEXT/HTML;
       CHARSET=WINDOWS-1251">';
 PUT '</HEAD>';
 PUT '<BODY >';
RUN;
ODS HTML BODY=example(NOTOP);

Мы вначале указываем, что будем работать с файлом new3.htm, который в дальнейшем будем называть example. Затем нам надо создать собственно заголовок файла в шаге DATA. Для того, чтобы не "замусоривать" временную директорию SAS файлами, в шаге DATA мы используем специальный тип файла _NULL_, который позволяет выполнять нужные нам действия, но самого файла SAS во временной директории не создают. В этом шаге мы вносим в файл example все необходимые строки заголовка и закрываем его (основным является, конечно, строка с указанием CHARSET. Теперь можно запустить систему ODS, указав, что результаты надо записывать в файл example, но создавать к этому файлу заголовок не надо (опция NOTOP).

Если теперь запустить процедуру TABULATE, то ее результаты в виде таблицы будут записаны в файл new3.htm (который известен под "псевдонимом" example) и оттуда ее легко будет импортировать в MS Word или другой текстовый редактор.

Теперь можно вернуться к нашей проблеме - как взять материалы из распечатки некоей процедуры (например, процедуры логистической регрессии - LOGISTIC) и создать из нее красивую табличку с подписями по-русски. Для этого надо вначале выбрать в распечатке ту таблицу (тот блок данных), который мы будем использовать для создания таблицы. Для этого надо воспользоваться командой

ODS TRACE OUTPUT;

Затем надо запустить на выполнение интересующую нас процедуру. В окне сообщений системы (LOG) появится информация о том, какие блоки формируются системой ODS, например:

-------------
Name:       GlobalTests
Label:      Global Tests
Template:   Stat.Logistic.GlobalTests
Path:       Logistic.GlobalTests
-------------

Output Added:
-------------
Name:       ParameterEstimates
Label:      Parameter Estimates
Template:   Stat.Logistic.ParameterEstimates
Path:       Logistic.ParameterEstimates
-------------

Здесь приведены описания двух блоков данных, формируемых ODS при запуске процедуры LOGISTIC. Нас интересует в этой распечатке только имя этих блоков. Мы находим тот, который содержит интересующие нас данные и начинаем работать с ним. Поскольку мы хотим сделать таблицу результатов логистической регрессии, нас будет интересовать блок под названием ParameterEstimates. Записать его в файл данных можно при помощи команды

ODS OUTPUT ParameterEstimates=имя_файла;

Выполнение этой команды создаст файл SAS с заданным нами именем, который будет создержать интересующую нас табличку результатов логистической регрессии. Теперь остается только поменять подписи и форматирование и можно использовать процедуру TABULATE для создания таблички на русском языке:

ODS OUTPUT ParameterEstimates=mum;
PROC LOGISTIC DATA=iem.morthor DESCENDING;
MODEL mort99=age sbp dbp educ;
RUN;
DATA mum; SET mum;
SELECT (Variable);
WHEN ('AGE')       varrus='Возраст    ';
WHEN ('Intercept') varrus='В          ';
WHEN ('SBP')       varrus='САД        ';
WHEN ('DBP')       varrus='ДАД        ';
WHEN ('EDUC')      varrus='Образование';
END;
RUN;
FILENAME example 'd:\logtab.htm';
DATA _NULL_;
 FILE example;
 PUT '<HTML>';
 PUT '<HEAD>';
 PUT '<TITLE>SAS OUTPUT&l;/TITLE>';
 PUT '&l;META HTTP-EQUIV="CONTENT-TYPE" CONTENT="TEXT/HTML;
       CHARSET=WINDOWS-1251">';
 PUT '</HEAD>';
 PUT '<BODY >';
RUN;
ODS HTML BODY=example(NOTOP);
PROC TABULATE DATA=mum ORDER=DATA FORMCHAR='|---|+|---|';
CLASS varrus;
VAR Estimate StdErr ProbChiSq;
TABLE Varrus='Переменная', 
 (Estimate='Коэффициент регрессии'  StdErr='Ошибка коэффициента' ProbChiSq='p')*SUM=' '*f=5.3;
RUN;
ODS HTML CLOSE;

Вначале мы сохраняем соответствующую таблицу в файле mum, затем создаем переменную, содержащую русские наименования переменных (varrus). Далее создаем заголовок HTML-файла и вызываем ODS для создания тела файла. Само тело файла создается процедурой TABULATE, в которой мы меняем подписи строк и столбцов на русские. Поскольку в таблице каждая строка уникальна, то статистика SUM, используемая в TABULATE просто воспроизводит данные соответствующей строки. Таким образом мы получаем таблицу на русском языке с той информацией, которая нас интересовала и в том формате, который нас интересует:

 

Коэффициент регрессии

Ошибка коэффициента

p

Переменная

-8.87

0.920

0.000

В

возраст

0.094

0.014

0.000

САД

0.026

0.008

0.001

ДАД

-.016

0.014

0.251

Образование

0.298

0.090

0.001

 

Можно, конечно, было бы просто перенаправить результат работы процедуры LOGISTIC в файл HTML формата, затем скопировать нужную нам таблицу с обозначениями латиницей, вставить ее в Word и там осуществить редактирование с переводом, однако если таблица достаточно большая, а нам кроме подписей надо менять формат значений (например, уменьшить количество знаков после запятой или иным другим образом модифицировать таблицу), это будет сделать проще с помощью программных кодов. Кроме того, однажды отработав код создания таблицы, его можно будет запускать и с измененными данными и легко вносить изменения если вдруг понадобится немного поменять подписи или столбцы. Иными словами, если исследователь предполагает, что таблицу он будет модифицировать (разумное предположение при начале работы), то лучше создать программу с описанием таблицы, сохранить ее и затем использовать по мере надобности.

Кроме простого экспорта данных ODS позволяет пользоваться различными дополнительными опциями, например стилями. Стили поддерживаются, в первую очередь, для HTML экспорта. Идея их аналогична идее стилей форматирования в текстовом процессоре. Поскольку данные хранятся в контейнере отдельно, мы можем описать правила их представления - шрифт, цвета, особенности заголовком и подписей, а затем наложить эти на данные из контейнера. Стили подключаются при помощи опии STYLE=имя_стиля в команде ODS.

Например, мы можем написать

ODS HTML BODY=example STYLE=BarrettsBlue;

Система SAS создаст таблицу с голубой закраской заголовков строк и столбцов и серым телом таблицы. Можно использовать и другие стили. Стили можно модифицировать при помощи процедуры TEMPLATE.

PROC TEMPLATE;
DEFINE STYLE mystyle;
PARENT=STYLES.BarrettsBlue;
REPLACE FONTS/
 'TitleFont' = ("Times New Roman",14pt,Bold )
 'TitleFont2' = ("Times New Roman",14pt,Bold )
 'docFont' = ("Times New Roman",8pt)
 'StrongFont' = ("Times New Roman",10pt,Bold Italic)
 'FixedStrongFont' = ("Courier",8pt,Bold)
 'EmphasisFont' = ("Times New Roman",8pt,Italic)
 'FixedEmphasisFont' = ("Courier",8pt,Italic)
 'headingFont' = ("Times New Roman",12pt,Bold)
 'headingEmphasisFont' = ("Times New Roman",12pt,Bold Italic)
 'FixedHeadingFont' = ("Courier",12pt,Bold)
 'BatchFixedFont' = ("Courier",8pt)
 'FixedFont' = ("Courier",8pt);
END;
RUN;

В данном примере мы решили изменить используемые по умолчанию шрифта с Arial на Times New Roman (больше, чем Arial подходящие для публикации). В качестве исходного стиля использован BarrettsBlue, в котором и произведена замена шрифтов при помощи команды REPLACE FONTS. Однако модифицировать имеющиеся в поставке SAS стили не следует, поэтому мы создали новый стиль, назвав его mystyle. Теперь остается только указать SAS, что надо использовать этот стиль при создании таблицы:

ODS HTML BODY=exampl(NOTOP) STYLE=mystyle;

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

Таким образом система SAS обладает развитыми возможностями по созданию таблиц как для публикационных, так и презентационных целей. Большая часть таблиц может быть составлена в рамках процедуры TABULATE предоставляющей тонкий и полный контроль за внешним видом, форматом данных и подписями в таблицах. Для улучшения внешнего вида таблиц и облегчения их экспорта для использования в других офисных программах необходимо прибегать либо к программе перекодирования в HTML формат TAB2HTM (для версии 6.12), либо использовать систему ODS. Последняя позволяет захватывать информацию из различных процедур SAS и перенаправлять ее для дальнейшего использования в файлы (откуда ее можно брать для создания таблиц процедурой TABULATE), либо сохранять в различных форматах для использования в других программах (HTML, RTF, PDF). Кроме того, ODS позволяет пользоваться поставляемыми SAS и модифицируемыми пользователем стилями оформления данных, которые позволят легко и быстро менять внешний вид таблиц SAS.