SAS編程-宏:簡單描述性統(tǒng)計量的輸出

在臨床試驗TFL編程中,簡單的描述性統(tǒng)計量與頻數匯總表格的數量占表格總量的絕對大頭。從提高編程效率的角度看,為這兩類表格建立穩(wěn)定的宏程序輸出是一件非常高效率的事情。

這篇文章介紹,數值變量的簡單描述性統(tǒng)計量輸出的一些考慮,主要有4方面的內容:

  1. 試驗匯總組的處理
  2. 橫向數據轉換為縱向數據
  3. 文字說明行的處理
  4. 小數位數的處理

完整宏程序代碼在文章的第5節(jié),如果讀者想要引用宏程序的話,可能需要根據自己項目要求更新統(tǒng)計量的布局和小數位保留的設置。

簡單描述性統(tǒng)計量輸出的樣式,各家公司大同小異,這篇文章采用以下樣式:

Layout

計算統(tǒng)計量的過程步選擇Proc Means, 使用其他過程步也可以。

分析數據來源于SASHELP.CLASS數據集,簡單處理下,新建一個分組變量:

data class0;
  set sashelp.class;
  if _n_ < 10 then trt01pn = 1;
  else trt01pn = 2;
run;

1. 試驗匯總組的處理

TFLShell中會明說明輸出Table是否有匯總組。最常見的處理匯總組的方式是,在原始數據集利用Data步的Output語句,新建匯總組;我推薦大家嘗試使用Format過程步中的Multilabel選項進行構建匯總組,這個方法不需要在原始數據集中進行新建分組處理,一定程度減少了分析數據集的觀測數。

具體介紹文章參考:SAS編程:生成Table時,匯總組(Total)組如何處理?。

示例代碼如下:

proc format;
  value trt01pn (notsorted multilabel)
    1 = 1      
    2 = 2  
    1,2 = 99
  ;
run;

proc means data = class0 nway completetypes;
  format trt01pn trt01pn.;
  class trt01pn / preloadfmt mlf order = data;
  var height;
  output n=n_ mean=mean_ std=sd_  median=median_ q1=q1_ q3=q3_ min=min_ max=max_ out=stat1;
run;

程序運行結果如下:

Stat 1

結果中匯總組(trt01pn = 99)的相關統(tǒng)計量也被輸出來。這里Means過程步中使用的選項nway、preloadfmt、mlf都不能被省略,關于各個選項作用,讀者可以參考SAS官方文檔,這里就不再贅述。

2. 橫向數據轉換為縱向數據

輸出統(tǒng)計量數值之后,需要再根據Shell中具體的Layout進行處理。對于試驗分組受試者頻數為0的情形,shell中可能有特別的顯示,代碼示例如下:

data stat2;
  set stat1;
  length n mean sd median q1q3 minmax $200;

  if n_ ne 0 then do;
     n = strip(put(n_, 8.0));
     mean = strip(put(mean_, 8.1));
     sd = strip(put(sd_, 8.1));
     median = strip(put(median_, 8.1));
     q1q3 = strip(put(q1_, 8.1))||", "||strip(put(q3_, 8.1));
     minmax = strip(put(min_, 8.1))||", "||strip(put(max_, 8.1));    
  end;
  else do;
     n = strip(put(n_, 8.0));
     mean = "-";
     sd = "-";
     median = "-";
     q1q3 = "-, -";
     minmax = "-, -";  
  end;
run;

輸出結果如下:

Stat 2

數據整理完畢后,需要將橫向數據轉換為縱向數據,常用的方法有2種:

  1. Data步中Output語句;
  2. Transpose過程步

從代碼和輸出數據集的簡潔角度考慮,使用Transpose過程步要好一些:

proc transpose data = stat2 out = stat3 prefix = trt_;
  var n mean sd median q1q3 minmax;
  id trt01pn;
run;

結果如下:

Stat 3

Shell中的內容已大體具備,首列的內容通常以_NAME_取值的Format進行展示。同時,為了后續(xù)排序方便,也會新建排序變量??紤]到,宏程序批量處理內容比較多,新建Section變量,方便標識輸出結果的不同部分。統(tǒng)計量的排序變量,以_NAME_取值的Informat進行展示。

proc format;
  value $stat
    "n" = "n"
    "mean" = "Mean"
    "sd" = "SD"
    "median" = "Median"
    "q1q3" = "Q1, Q3"
    "minmax" = "Min, Max"
  ;

  invalue statn
    "n" = 1
    "mean" = 2
    "sd" = 3
    "median" = 4
    "q1q3" = 5
    "minmax" = 6
  ;
run;

data stat4;
  retain section cat1n col1 trt_:;
  set stat3;

  section = 1;
  cat1n = input(_name_, statn.);

  length col1 $200;
  col1 = put(_name_, stat.);

  keep section cat1n col1 trt_:;
run;

輸出結果如下,主體顯示內容已經完成:

Stat 4

3. 文字說明行的處理

統(tǒng)計量首行展示的文字說明行,通常有2種做法。

第1種,新建一個包含文字說明信息的數據集與主體數據集進行縱向拼接;第2種,在主體數據集中使用Output語句,多生成一行記錄用于放置文字說明信息。

這里提供第3種方法,在橫向數據轉換成縱向數據之前,數據集保留一個空變量。同時,也對這個空變量進行轉置,這樣輸出數據集就會有多出一行。這一行就用于放置文字說明信息,informat中的排序變量值也需要進行相應的設置,其對應的說明信息直接在Data步中進行賦值,程序如下:(Stat2數據集中多了textline變量)

data stat2;
  set stat1;
  length textline n mean sd median q1q3 minmax $200;

  if n_ ne 0 then do;
    textline = "";
     n = strip(put(n_, 8.0));
     mean = strip(put(mean_, 8.1));
     sd = strip(put(sd_, 8.1));
     median = strip(put(median_, 8.1));
     q1q3 = strip(put(q1_, 8.1))||", "||strip(put(q3_, 8.1));
     minmax = strip(put(min_, 8.1))||", "||strip(put(max_, 8.1));    
  end;
  else do;
    textline = "";
     n = strip(put(n_, 8.0));
     mean = "-";
     sd = "-";
     median = "-";
     q1q3 = "-, -";
     minmax = "-, -";  
  end;
run;


proc transpose data = stat2 out = stat3 prefix = trt_;
  var textline n mean sd median q1q3 minmax;
  id trt01pn;
run;

proc format;
  value $stat
    "n" = "n"
    "mean" = "Mean"
    "sd" = "SD"
    "median" = "Median"
    "q1q3" = "Q1, Q3"
    "minmax" = "Min, Max"
  ;

  invalue statn
    "textline" = 0
    "n" = 1
    "mean" = 2
    "sd" = 3
    "median" = 4
    "q1q3" = 5
    "minmax" = 6
  ;
run;

data stat4;
  retain section cat1n col1 trt_:;
  set stat3;

  section = 1;
  cat1n = input(_name_, statn.);

  length col1 $200;
  if _name_ ne "textline" then col1 = put(_name_, stat.);
  else col1 = "Height (cm)";

  keep section cat1n col1 trt_:;
run;

這樣,簡單描述性統(tǒng)計量就輸出完畢:

Stat 4

4. 小數位數的處理

4.1 統(tǒng)計量小數位數宏變量的生成

關于各統(tǒng)計量的小數位數,不同的公司、不同的統(tǒng)計師可能有不同的要求。在確定小數位數要求后,常規(guī)做法,是將原始小數位數作為宏變量,代入Data步中的Format。

不過,上述操作的代碼會顯得雜亂。我們可以在Data步之外提前設置好各統(tǒng)計量Format的宏變量,后續(xù)直接引用宏變量。

%macro deci(dp=);
  %let d_n    = 8.0; %put d_n = &d_n.;
  %let d_mean = 8.%eval(&dp.+1); %put d_mean = &d_mean.;
  %let d_sd   = 8.%eval(&dp.+1); %put d_sd = &d_sd.;
  %let d_medi = 8.%eval(&dp.+1); %put d_median  = &d_medi.;
  %let d_qq   = 8.%eval(&dp.+1); %put d_qq = &d_qq.;
  %let d_mm   = 8.%eval(&dp.+0); %put d_mm = &d_mm.;

%mend;

%deci(dp = 0);

運行輸出后,日志顯示如下:

Log 1

通常小數位數的展示不超過4位,輸出時需要對小數位數進行限制,當小數位數超過4時,小數位數取4

%macro deci(dp=);
  %global d_n    d_mean d_sd   d_medi d_qq   d_mm  ;
  %let d_n    = 8.0; %put d_n = &d_n.;
  %let d_mean = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_mean = &d_mean.;
  %let d_sd   = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_sd = &d_sd.;
  %let d_medi = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_medi  = &d_medi.;
  %let d_qq   = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_qq = &d_qq.;
  %let d_mm   = 8.%sysfunc(min( %eval(&dp.+0), 4 )); %put d_mm = &d_mm.;

%mend;

%deci(dp = 5);

以上程序運行輸出如下:

Log 2
4.2 “-0”問題的處理

在之前的文章中介紹過,保留小數位數,常用的方法是直接put具體的數字格式。不過,這種處理方式在負數十分位向0進位時,可能會產生一些偏誤。

我用代碼進行實例,對數字0.4, 0.5, -0.4, -0.49 -0.5四舍五入保留整數。使用兩種方法,一種是直接put數值格式8.0;一種是使用函數Round處理之后,再put數值格式8.0。

data tmp;
    input a @@;

    bput = strip(put(a, 8.0));
    bround = strip(put(round(a, 1), 8.0));
    
    datalines;
        0.4
        0.5
        -0.4
        -0.49
        -0.5
    ;
run;
Result 1

從結果中可以看到,對于-0.4-0.49這2個數值,四舍五入應該進位成0,但是直接Put時,結果是-0,與想要的結果不同。更一般的,對于(-0.5, 0)這個范圍的數值,put函數保留整數時,結果為-0

我沒細究這個現(xiàn)象的原因,建議讀者在保留小數位數時,先使用Round函數,再put相應的格式。t同時,兩個函數保留的小數位數要相同,否則會產生二次誤差。

結合上面的描述,還需設置round函數精度,方便調用,代碼如下:

%macro deci(dp=);

  %global d_n d_mean d_sd d_medi d_qq d_mm r_n r_mean r_sd r_medi r_qq r_mm;

  %let d_n    = 8.0; %put d_n = &d_n.;
  %let d_mean = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_mean = &d_mean.;
  %let d_sd   = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_sd = &d_sd.;
  %let d_medi = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_medi  = &d_medi.;
  %let d_qq   = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_qq = &d_qq.;
  %let d_mm   = 8.%sysfunc(min( %eval(&dp.+0), 4 )); %put d_mm = &d_mm.;

  %let r_n = 1; %put r_n = &r_n.;
  %let r_mean = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_mean = &r_mean.;
  %let r_sd   = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_sd = &r_sd.;
  %let r_medi = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_medi = &r_medi.;
  %let r_qq   = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_qq = &r_qq.;
  %let r_mm   = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+0), 4 )) )); %put r_mm  = &r_mm.;

%mend;

%deci(dp = 5);

日志輸出結果如下:

Log 3

Data步中,對應的更新如下:

%deci(dp =1);

data stat2;
  set stat1;
  length textline n mean sd median q1q3 minmax $200;

  if n_ ne 0 then do;
    textline = "";
     n = strip(put(round(n_, &r_n.), &d_n.));
     mean = strip(put(round(mean_, &r_mean.), &d_mean.));
     sd = strip(put(round(sd_, &r_sd.), &d_sd.));
     median = strip(put(round(median_, &r_medi.), &d_medi.));
     q1q3 = strip(put(round(q1_, &r_qq.), &d_qq.))||", "||strip(put(round(q3_, &r_qq.), &d_qq.));
     minmax = strip(put(round(min_, &r_mm.), &d_mm.))||", "||strip(put(round(max_, &r_mm.), &d_mm.));    
  end;
  else do;
    textlin = "";
     n = strip(put(n_, 8.0));
     mean = "-";
     sd = "-";
     median = "-";
     q1q3 = "-, -";
     minmax = "-, -";  
  end;
run;

輸出結果如下:

Stat2

輸出結果已經按照設置的小數位數展示,這樣每次只需要更新宏參數dp就可以更新所有統(tǒng)計量的小數位。

至于小數位數的確認,取決于統(tǒng)計師或公司的要求。如果是從實際數據中獲取,還需要額外的獲取處理,這里就不進一步展開。

5. 宏程序匯總

宏程序需要使用統(tǒng)計量的Format($stat.)進行第一列展示,也需要Informat(statn.)生成排序變量??紤]到,如果這些格式生成放在宏程序中,每次調用宏都會新生成一次。所以,我將這些程序放到宏程序之外。

***1. Macro for decimal places;
%macro deci(dp=);

  %global d_n d_mean d_sd d_medi d_qq d_mm r_n r_mean r_sd r_medi r_qq r_mm;

  %let d_n    = 8.0; %put d_n = &d_n.;
  %let d_mean = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_mean = &d_mean.;
  %let d_sd   = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_sd = &d_sd.;
  %let d_medi = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_medi  = &d_medi.;
  %let d_qq   = 8.%sysfunc(min( %eval(&dp.+1), 4 )); %put d_qq = &d_qq.;
  %let d_mm   = 8.%sysfunc(min( %eval(&dp.+0), 4 )); %put d_mm = &d_mm.;

  %let r_n = 1; %put r_n = &r_n.;
  %let r_mean = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_mean = &r_mean.;
  %let r_sd   = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_sd = &r_sd.;
  %let r_medi = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_medi = &r_medi.;
  %let r_qq   = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+1), 4 )) )); %put r_qq = &r_qq.;
  %let r_mm   = %sysevalf(0.1**(%sysfunc(min( %eval(&dp.+0), 4 )) )); %put r_mm  = &r_mm.;

%mend;


***2. Macro for Statistics;
%macro stat(indt=, trtvar=, trtfmt=, anlvar=, dp=, textline=, section=, outdt=);

**2.1 Get statistics;
proc means data = &indt. nway completetypes;
  format &trtvar. &trtfmt..;
  class trt01pn / preloadfmt mlf order = data;
  var &anlvar.;
  output n=n_ mean=mean_ std=sd_  median=median_ q1=q1_ q3=q3_ min=min_ max=max_ out=stat1;
run;


**2.2 Get decimal places for statistic;
%deci(dp = &dp.);


**2.3 Table display;
data stat2;
  set stat1;
  length textline n mean sd median q1q3 minmax $200;

  if n_ ne 0 then do;
    textline = "";
     n = strip(put(round(n_, &r_n.), &d_n.));
     mean = strip(put(round(mean_, &r_mean.), &d_mean.));
     sd = strip(put(round(sd_, &r_sd.), &d_sd.));
     median = strip(put(round(median_, &r_medi.), &d_medi.));
     q1q3 = strip(put(round(q1_, &r_qq.), &d_qq.))||", "||strip(put(round(q3_, &r_qq.), &d_qq.));
     minmax = strip(put(round(min_, &r_mm.), &d_mm.))||", "||strip(put(round(max_, &r_mm.), &d_mm.));    
  end;
  else do;
    textlin = "";
     n = strip(put(n_, 8.0));
     mean = "-";
     sd = "-";
     median = "-";
     q1q3 = "-, -";
     minmax = "-, -";  
  end;
run;


**2.4 Transpose results;
proc transpose data = stat2 out = stat3 prefix = trt_;
  var textline n mean sd median q1q3 minmax;
  id &trtvar.;
run;



**2.5 Create output dataset;
data &outdt.;
  retain section cat1n col1 trt_:;
  set stat3;

  section = &section.;
  cat1n = input(_name_, statn.);

  length col1 $200;
  if _name_ ne "textline" then col1 = put(_name_, stat.);
  else col1 = "&textline.";

  keep section cat1n col1 trt_:;
run;

%mend stat;


***3. Invoke the macro;
proc format;
  value trt01pn (notsorted multilabel)
    1 = 1      
    2 = 2  
    1,2 = 99
  ;

  value $stat
    "n" = "n"
    "mean" = "Mean"
    "sd" = "SD"
    "median" = "Median"
    "q1q3" = "Q1, Q3"
    "minmax" = "Min, Max"
  ;

  invalue statn
    "textline" = 0
    "n" = 1
    "mean" = 2
    "sd" = 3
    "median" = 4
    "q1q3" = 5
    "minmax" = 6
  ;
run;

%stat(
  indt = class0
  ,trtvar = trt01pn
  ,trtfmt = trt01pn
  ,anlvar = height
  ,dp = 1
  ,textline = Heigth (cm)
  ,section = 1
  ,outdt = sec_1
);

以上程序,運行結果如下:

Sec_1

6. 擴展與延伸

從宏的程序看,只設置了一個試驗分組變量。這里可以有讀者要問,如果輸出表格需要多個試驗分組變量,如何處理呢?

我建議,這種情況不使用宏程序,程序不復雜直接手動編寫,簡潔方便。

宏程序一般處理重復的編程內容,例如,Baseline Demogrphic這類匯總表,有多個不同的分析變量,內容高度重復化,程序中宏程序會調用很多次。但對于多個分組變量的情形,有這樣的宏,程序中也只會調用一次。在我看來,這是沒有必要的。

當然,對于多個分組變量的亞組分析,每個亞組也可以看成是重復單一的內容。但是,這樣簡單機械的處理抹去了,各亞組之間的聯(lián)系。這就像數學的幾何題一樣,我們可以通過復雜推理得到正確結果,不過,有時候一條輔助線可以讓整個解題過程簡潔的多得多。

這里舉個LB簡單描述統(tǒng)計量輸出的例子,LB中可能有二三十個Parameter,如果單獨依次出每個Paramter對應的內容,那過程就太過繁瑣;如果在宏程序中添加多個分組變量,一次調用就能解決,那跟手動寫出完整輸出過程又沒什么區(qū)別。

proc means data = adlb noprint nway completetypes;
  by trt01an parcat1n paramn paramcd avisitn;
  format anrindn anrindn.;
  class anrindn / preloadfmt order = data;

  var aval ;

  output n = n_ mean=mean_ median=median_ q1=q1_ q3=q3_ min=min_ max=max_ out = stat1;
run;

當然,這種情況下,如果不了解頻數表的輸出的邏輯與過程,確實是直接調用宏程序,來得更簡單高效一點。

總結

這篇文章介紹了,簡單描述性統(tǒng)計量的宏程序輸出,計算統(tǒng)計量的匯總組時采用Format過程步中的multilabel選項,使用轉置的方式進行統(tǒng)計量Layout的處理,小數位數的保留使用roundput函數一起進行處理。

希望對讀者日常編程工作,有所幫助。

感謝閱讀, 歡迎關注:SAS茶談!
若有疑問,歡迎評論交流!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容