You are on page 1of 6

A SAS MACRO FOR PRODUCING CLINICAL LABORATORY SHIFT TABLE

Shi-Tao Yeh, GlaxoSmithKline, King of Prussia, PA

Figure 1 Sample Output from lbsf1.sas Program


ABSTRACT baseline and then at selected time points or time
intervals. In other words, it displays Lab value transitions
The results of all safety-related laboratory tests are with respect to normal range and clinical concern level
collected in clinical trial studies. A shift table is a table categories from a baseline period to a specific on-therapy
that displays the number of subjects who are low, visit period.
normal, or high at baseline and the shift or transition at
selected time points or time intervals.
The vendor who performs the laboratory test usually
provides normal range for numeric Lab parameters. The
This table provides key information about the health and
clinician may specify clinical concern level for the study.
welfare of the subjects during the clinical study.
The study team uses a 'Lab Flagging' program to flag the
Lab records to see whether it is above or below the
This paper provides a comprehensive, flexible, and
concern level.
modular SAS reporting approach for producing a
laboratory shift table.
There are several types of LST presentation formats and
The SAS product used in this paper is SAS BASE on layout. Figure 1 shows the LST layout that is adopted by
UNIX platform. GlaxoSmithKline as standard LST presentation.

LAB FLAGGING
INTRODUCTION
Prior to summarization, the raw data from a clinical
A clinical trial study collects all safety-related clinical laboratory require Lab Flagging. Lab flagging is a
laboratory (Lab) tests for safety analysis. SAS process that marks each laboratory record in the
programmers in the clinical study team support the database using parameter specific clinical criteria. The
clinical data analysis and reporting process. criteria often include: 1) values are outside of the
supplied normal range, 2) values are high or low enough
One of the more frequently requested reports is the Lab to cause clinical concern, 3) values have increased or
Shift Table (LST). It is a clinical report that summarizes decreased by a specified amount to cause clinical
the number of subjects who are low, normal, or high concern. There are three flags from this flagging process:
at 1) normal range flag, 2) shift from baseline flag, and 3)

clinical concern flag. Each flag may contain the symbols:


H for above the range, L for below the range, and I for baseflag default=R, flag for baseline records
within the range. cellindx default=labshift, cell index dataset
name
For the illustration purposes, the Lab flagging criteria and
flags are converted to the following symbols and labels
on the report: STEP 2: SET UP REPORTING MAIN PROGRAM

Symbol Label Description The main reporting program completes a number of


relatively generic steps in the process.
+ RR High above high value of
clinical concern 1) assign the number of lab parameters and by group
H NR High above high value of normal variables to loop index variables
range
I W/in NR within normal range 2) subset valid lab flag values from the data
L NR Low below low value of normal
3) separate baseline and other visits
range
- RR Low below low value of clinical 4) convert flagging values to the formatted symbols to
concern be displayed on the matrix
5) merge the baseline records into the other visit
records
LST is presented in a matrix format, shown in Figure 1.
The first column is the visit or time interval label. The 6) count the number of subjects in each matrix cell and
second column is n which is the total subjects for each the total denominators
visit or time interval. A subject must have both baseline
and target visit/interval test values to be counted in the 7) compute the cell percentages
LST. 8) determine which parameters do not have clinical
concern flags and blank out the clinical concern rows
The baseline flagged values are shown in rows of the for these pages
matrix and target visit or interval flagged values are
shown in columns. Each cell represents a shift from a STEP 3: CREATE A REPORTING MODULE
specific baseline flagged category to a specific target
visit flagged category. The LST also provides the The reporting module produces a matrix page of output
percentage for each cell. The percentage of the value in for each laboratory parameter and by group requested.
a single cell is the count of the cell divided by n.

When a user selects time interval with multiple visits, STEP 4: CREATE A CELL INDEX MODULE
the worst case test value or the last test value carried
forward can be selected for each subject. The final step produces an optional cell index to
provide the detailed laboratory data that comprise
This paper presents a novel and comprehensive each cell.
approach that is simple, efficient, and produces a readily
interpretable transition presentation in the LST. The
approach consists of several SAS macros, and it is STEP 5: CREATE SASMAN PROGRAM
discussed in the following sections. The sample SAS
codes are shown in Appendix I. The SASMAN program is a protocol-specific module that
allows the other modules of the LST program to remain
relatively generic. It is also the data preparation step
STEP 1: SET INITIATION AND ENVIRONMENT
before invoking the LST macro. The SASMAN module
VARIABLES increases the flexibility of the LST software by performing
the following tasks:
The first step sets up macro variables, computing
environment variables, and formats used for reporting the 1) read in the flagged Lab dataset,
LST. The input parameters for this macro are as follows:
2) make any required data subset selection,
PARAMETER DESCRIPTION
dataset the input dataset name 3) select the target Lab variable list,
noprintv no print variable which controls the
order of display 4) define the column variables and baseline variables
byvars a categorical variable list for page by in the LST and feed-in dataset,
variables that appeared in the display
5) select the last value or worst case based on
byfmts format name for byvars
protocol-specific requirements,
pidvars default=usubjid, subject variable name
colvar a variable name for the first column in 6) use data steps to prepare dataset to be feed in to
the display main program.
colfmt format name for colvar
INVOCATION SAMPLE CODE run;

The following sample code is an example of SASMAN


program before calling the LST macro. SAMPLE OUTPUTS

proc format; Two sample pages of output are presented. The


value labses first page, shown in Figure 1, represents a
2 = 'BASELINE' laboratory parameter for which normal range and
8 = 'WEEK 8' clinical concern criteria were specified. The second
9 = 'FOLLOW-UP' sample page in Figure 2, represents a laboratory
; parameter for which only clinical concern criteria
were specified.
data lab;
set _ardata.lab;
lbstxrhi = lborxrhi;
lbstxrlo = lborxrlo;

lab_parm = trim(left(lbtest)) || '(' ||


trim(left(lbstunit)) || ')';
if visitnum = 1 and lbflag='B' then
visitnum=2;
if visitnum=15 then visitnum=9;
if visitnum=1 or (visitnum = 2 and lbflag
= 'U' ) or trtcd=1 then delete;
if visitnum in (2,8,9);
keep lbnrcd lbxrcd lborres lbstresn
lbflag usubjid subjid lbtest lbtestcd
lbstunit lbstxrhi lbstxrlo visit visitnum Figure 2. Sample Output
trtcd trtgrp lab_parm;

data dem; CONCLUSIONS


set _ardata.demo;
if trtcd = 1 then delete;
keep usubjid; This LST approach provides a framework that is simple,
proc sort;by usubjid; comprehensive, efficient, and produces a readily
interpretable transition presentation in the LST. This
data final; modular design makes the best use of the SAS system
set lab; capability of providing robust generic code, and
vorder = visitnum; customizable data processing components.
label lab_parm = 'Test'
trtgrp = 'Treatment'; In summary, this approach

proc sort;by usubjid; * Provides a framework for the rapid


development and modification of a LST
data final; program.
merge dem(in=a) final; * Provides a compact and easy-to-read
by usubjid; matrix format that allows one page per
if a; laboratory parameter.
%rstart(driver=titles.lis, * Allows lab parameters with and without
rptdir=$_list, suffix=a, outfile=lb_4); missing values and clinical concern levels
options ls=90 ps=31 missing=' ' nobyline; to be presented in the same set of pages.
%inc 'lbsf1.sas';

%lbsf1(dataset=final, APPENDIX I: MACRO CODE


noprintv=vorder,
byvars = lab_parm trtgrp, %macro lbsf1 (
colvar = visitnum, dataset=,
baseflag = B, noprintv=,
cellindx = labshift, byvars=,
colfmt = labses byfmts=,
); pidvars=usubjid,
colvar=,
%rstop; colfmt=,
run; baseflag=R,
cellindx=labshift
proc datasets; ) ;
delete final lab; proc format ;
else if bf1 = 'H' then _row = 2 ;
value labrow 1 = 'RR High' else if bf1 = 'L' then _row = 4 ;
2 = 'NR High' else _row = 3 ;
3 = 'W/in NR'
4 = 'NR Low ' run ;
5 = 'RR Low ' %* Only take the latest information within
6 = 'Total '; the column variable ;
picture pp (round) data other ;
0 = ' ' (noedit) set other ;
other = '0009%)' (prefix='('); by &pidvars &byvars &colvar ;
if last.&colvar ;
%* Separate all by variable information ;
%let numbys=1; if lbxrcd = 'H' then _col = 1 ;
%do %while (%scan(&byvars, &numbys) ne ) else if lbxrcd = 'L' then _col = 5 ;
; else if lbnrcd = 'H' then _col = 2 ;
%let byvar&numbys = %scan(&byvars, else if lbnrcd = 'L' then _col = 4 ;
&numbys) ; else _col = 3 ;
%let byfmt&numbys = %scan(&byfmts, run ;
&numbys,#) ;
%if &&byfmt&numbys = ! %then %let %* Merge the baseine into the other visit
byfmt&numbys =; ;
%let numbys = %eval(&numbys + 1) ; data tablek ;
%end ; merge other(in=a) baseline(in=b) ;
%let numbys = %eval(&numbys - 1) ; by &pidvars &byvars ;
if a and b ;
%* Sort the input dataset ; run ;
%* Extract only flag values that have *proc print data=tablek;run;
values we can plot in the ; data tablek;
%* shift table ; set tablek;
proc sort data=&dataset output;
out=table ; _row = 6;
by &pidvars &byvars &colvar ; output;
where proc sort data=tablek;by &byvars &colvar
lbnrcd in ('H', 'L', 'I'); _row _col;
run ; proc datasets nolist nofs ;
*proc print data=table;run; delete other baseline ;
%* Separate baseline and other visits ; quit ;
data baseline (keep=&pidvars &byvars
lbnrcd lbxrcd lborres lbstresn lbflag %* Count the number of patients ;
rename=(lbnrcd= bf1 proc summary data=tablek nway missing ;
lbxrcd = bf3 lbstresn=base_val)) class &byvars &colvar _row _col ;
other (keep=&byvars &pidvars
&colvar lbnrcd lbxrcd lbstresn lborres ) ; output out=table(rename=_freq_=num) ;
set table ; run ;
if lbflag = "&baseflag"
then output baseline ; %* Count again for totals ;
else if lbflag ^= "&baseflag" then
output other ; proc summary data=table nway missing ;
run ; class &byvars &colvar _row ;
var num ;
proc datasets nolist nofs ; output out=tablet sum= ;
delete table ; run ;
run ; proc summary data=table nway missing ;
class &byvars &colvar;
%* Get latest assessment with flags within var num ;
each visit for each pid ; output out=allpid sum=allpid ;
%* Set the row and column co-ordinate ; run ;
data baseline ; *proc print data=allpid;run;
set baseline ; data allpid;
by &pidvars &byvars ; set allpid;
%do i=1 %to 6;
%if %length(&byvars) gt 0 %then %do ; _row = &i;
if last.&&byvar&numbys ; output;
%end ; %end;
*proc print ;run;
if bf3 = 'H' then _row = 1 ; proc sort;
else if bf3 = 'L' then _row = 5 ; by &byvars &colvar _row ;
%if %length(&&byfmt&i) gt 0 %then
data table ; %do ;
set table(in=a) tablet(in=b) ; format &&byvar&i &&byfmt&i... ;
by &byvars &colvar _row ; %end ;
if b then _col = 6 ; %end ;
run ; %if %length(&colfmt) gt 0 %then %do ;
*proc print data=table;run; format &colvar &colfmt.. ;
%* Calculate the percentages now ; %end ;
data table ; run ;
merge table(in=a) tablet(in=b
rename=(num=tnum)) allpid; %let byvars=&byvars ;
by &byvars &colvar _row ;
output ; proc datasets nolist nofs ;
_row = _row + 6 ; delete template ;
if tnum ne . then num = 200 * num / quit ;
allpid;
output ; %* Row format for table ;
run ; run ;

proc datasets nolist nofs ; %* Transpose data to get one line per row
delete tablet ; shift ;
run ; proc sort data=table ;
by &byvars _row &colvar _col ;
%* Generate a template ; run ;
proc sort data=table (keep=&byvars *proc print data=table;run;
&colvar) out=tmpl1 nodupkey ; proc transpose data=table out=table
by &byvars &colvar ; prefix=col let ;
run ; by &byvars _row &colvar ;
var num ;
data template ; id _col ;
set tmpl1 ; run ;
by &byvars &colvar ;
do _row = 1 to 12 ; data f3;
do _col = 1 to 6 ; set &dataset;
output ; if lbxrcd = 'I';
end ; keep %scan(&byvars , 1);
end ; proc sort data=f3 nodupkey;by %scan(
run ; &byvars ,1);
*proc print data=f3;run;
proc datasets nolist nofs ; *proc print data=table;run;
delete tmpl1 ; proc sort data=table; by %scan( &byvars ,
quit ; 1);

%* Merge template into counts ; data tn tp;;


proc sort data=table ; merge table(in=a) f3(in=b);
by &byvars &colvar _row _col ; by %scan( &byvars , 1) ;
run ; if a;
if not b and _row in (1,5,7,11) then do;
data table ; %do i=1 %to 6;
merge table template ; col&i=.;
by &byvars &colvar _row _col ; %end;
if num = . then num = 0 ; end;
* if _row = 7 or _col = 8 then num = . if not b and _row in (2,3,4,6,8,9,10,12)
; then do;
retain pg cnt ; col1=.;
if _n_ = 1 then cnt = 0 ; col5=.;
%if %length(&byvars) gt 0 %then %do ; end;
if first.&&byvar&numbys then cnt = 0 if _row in (1,2,3,4,5,6) then output
; tn;
%end ; else if _row in (7,8,9,10,11,12) then
if first.&colvar then cnt + 1 ; output tp;
if cnt = 4 then do ;
pg + 1 ; data tall;
cnt = 1 ; set tn;
end ; tall = col6;
%do i = 1 %to &numbys ; if _row=6 ;
keep &byvars &colvar tall;
*proc print data=tn;run; (' Total
' (col6 col6p)))
proc sort data=tall;by &byvars &colvar; );
%if %length(&noprintv) gt 0 %then %do;
data tp; define &noprintv / order noprint;
set tp; %end;
_row = _row - 6; define time1 /'Planned^Relative^Time
%do i=1 %to 6; ' width=10 order flow ;
col&i.p=col&i; define tall /'n' width=4 order
%end; spacing=1 ;
drop col1 col2 col3 col4 col5 col6; define _row / group
'Baseline^Value[1]' order=data width=8
proc sort data=tn;by &byvars _row left format=labrow. ;
&colvar; define acvar / across order=data
proc sort data=tp;by &byvars _row 'Time Period Value';
&colvar; %do i=1 %to 6;
define col&i / sum ' ' f=3. width=3 ;
*proc print data=tn;run; define col&i.p / sum '' f=pp.
*proc print data=tp;run; width=6;
data table; %end;
merge tn tp; break after tall/skip;
by &byvars _row &colvar; run ;
acvar=' ';
%if %length(&colfmt) eq 0 %then %do; proc datasets nolist nofs ;
time1=&colvar; delete table tp tn f3 &dataset;
%end; quit ;
%else %do;
time1=put(&colvar ,$&colfmt..); %* Create dataset for the cellindex ;
%end; %* NOTE: The cell index is not generated
proc sort data=table;by &byvars &colvar; here. ;
*proc print data=table;run; data &cellindx ;
*proc print data=tall;run; set tablek ;
baseline =
data table; substr(put(_row,labrow.),1,1) ;
merge table tall; interval =
by &byvars &colvar; substr(put(_col,labrow.),1,1) ;
run; keep &byvars &pidvars &colvar baseline
%if %length(&noprintv) gt 0 %then %do; interval ;
data noprint; run ;
set &dataset; proc datasets nolist nofs ;
keep &byvars &colvar &noprintv; delete tablek ;
proc sort data=noprint nodupkey;by quit ;
&byvars &colvar &noprintv; %mend lbsf1;
data table;
merge table(in=a) noprint;
by &byvars &colvar; ACKNOWLEDGMENTS
if a;
run; The author would like to thank Jitendra Shah for his
%end; earlier work on this macro and Randall Austin from GSK
for his suggestion of this reporting format.
%* Create the table ;
proc report data=table nowd headline TRADEMARKS
missing split='^' spacing=1;
%if %length(&byvars) gt 0 %then %do ;
by &byvars ; SAS is a registered trademark of SAS Institute Inc., in
%end ; the USA and other countries.
columns ( &noprintv time1 tall _row
acvar, ( '--' ('Reference^Range High' indicates USA registration.
(col1 col1p)) AUTHOR CONTACT INFORMATION
('Normal^Range High' (col2 col2p))
Shi-Tao Yeh, Ph. D.
('Within^Norm Range' (col3 col3p)) (610)787-3856(W)
E-mail: shi-tao_yeh-1@gsk.com
('Normal^Range Low' (col4 col4p))

('Reference^Range Low' (col5 col5p))

You might also like