Tuplet Processing

Updated: 03 April 2011

Introduction

"Tuplet" processing is a name I have given to the method I use to insert values from SAS procedure ODS tables into the output report at specified positions. The word "tuplet" comes from the end of the words "sextuplet", "septuplet" etc. which refers to the number of segments. It is a very powerful way of extending a clinical safety reporting system so that it can do efficacy processing as well. This technique can be applied to any existing safety reporting system to give it efficacy reporting capabilities and thereby create a tool that will speed up the production of efficacy reports whilst at the same time maintaining the consistent output style of the current safety reporting system.

You may want to extend your own reporting system in this way due to the reporting advantages it can offer. Some organisations have made a considerable investment in their own reporting systems and it is not practical to replace them. Using this method you can add efficacy reporting capabilities into the existing system and thereby extend the power and longevity of the system and protect the investment made.

I believe this is the first time this technique has been applied to an existing safety reporting system and as such you should consider that it might therefore be the intellectual property of the author.

How it works

The idea behind "tuplet" processing is to pass the same dataset you are calculating description statistics for (or category counts and percentages) to a routine that will invoke the appropriate statistical procedure with the desired option to create an output ODS table and, knowing what variable names are present in the ODS table (either the raw table or a transposed version), to place the value (or two values if it is a range) in the output report in the desired statistics column at the desired statistics order position in the output ("statistics order position" means that if you were showing N, Min and Max on separate lines then the statistics order positions would be "1" for "N", "2" for "Min" and "3" for "Max" etc.).

The name of the statistics procedure required is implicit from the prefix of the "tuplet" name (e.g. freqsept= parameters call "proc freq", glmquad= parameters call "proc glm") and the number of segments is obvious from what follows the prefix (e.g. "quad" implies 4 segments, "sept" implies 7 segments), the required option to generate the desired ODS table is provided as one of the segments, the ODS table name itself is supplied as a segment (prefixed by "Tr" if a transposed version is required) and may have a "where clause" attached as part of the name. Finally a "put statement" is required that formats the values(s) you require (or it might be plain text in some cases). A more detailed example of the coding will be given later.

The redesign

The Spectre clinical reporting macros had to be redesigned before the "tuplet" method could be implemented so that the columns in which the statistical values were displayed were numbered variables (STAT0, STAT1, STAT2 etc.) and the fields were all character. In this way the column name supplied to the "tuplet" call generates the appropriate stat column name (e.g. "3" will generate the column "STAT3"). If you intend to implement this method then you might have to redesign your reporting system along these lines, such that your stats columns are numbered in this way and to ensure they are character fields. Only then will your reporting code be in a position where you can add "tuplet" processing.

Detailed code

Detailed code for the "freqsept=" parameters will be described here. This is a relatively new and stable version of using this method and is the most easily described.

Firstly, this is a description of the freqsept= parameters and their segments as shown in the header of the main reporting macro %unistats :
 
/                   ------------------------------------------------------------ 
/ freqsept1-20      Proc freq septuplet statements for the placement of values 
/                   from ods tables in the output report/dataset in the form: 
/                      varname(s)#keyword#missing#dset#column#statord#code 
/                   where "varname(s)" is the variable name or list of variable 
/                   names separated by spaces, "keyword" is the proc freq option 
/                   name, "missing" is "Y" or "N" for whether to include missing 
/                   values in the calculation, "dset" is the ods table name with 
/                   the attached where clause (if you prefix this table name 
/                   with "Tr" it will assume you want the table transposed, 
/                   "column"=2-9 for the STAT column number, "statord" is the 
/                   order number in the list of descriptive statistics and 
/                   "code" is the code to format the value(s) for variables in 
/                   that dataset. 
/                   ------------------------------------------------------------ 

The seven segments of the freqsept parameters are as follows:

1) The variable name (a list of variables separated by spaces is allowed)
2) The keyword is the "proc freq" tables statement option
3) Missing is a "Y" or "N" to say whether missing values are to be included in the calculation
4) Dset is the ods table name produced as a result of using the keyword you specified. If you place "Tr" at the front of the name then it will make available a transposed version available.
5) Column is the stats column in which to place the output for the variable you specify. You can use columns 2 - 9 for this.
6) Statord in the order number of the statistic which will commonly be "1" (or "2" if you want to place another value underneath the first one)
7) Code is your put statement to format the results.

A check is made on the variable name(s) in the first segment and if the current variable matches that name then the routine is invoked.
 

    /*--------------------------------*
         Call the UNISEPT macro
     *--------------------------------*/

  %do i=1 %to 20;

    %if %length(&&freqsept&i) %then %do;
      %if %sysfunc(indexw(%upcase(%scan(&&freqsept&i,1,#)),&varname))
        %then %unisept(&&freqsept&i);
    %end;

  %end;
 

The %unisept macro called is not a standalone macro and is instead internally defined within the %unipvals macro as follows:
 

    /*--------------------------------*
          Define UNISEPT macro
     *--------------------------------*/ 

%macro unisept(sept);

  %local statno odstab trodstab oneway usemiss keyword keyword2 bytreat;
  %let statno=%scan(&sept,5,#);
  %let usemiss=%upcase(%substr(%sysfunc(dequote(%scan(&sept,3,#))),1,1));
  %let odstab=%scan(%scan(&sept,4,#),1,%str( =%());
  %let trodstab=;
  %if "%upcase(%substr(&odstab,1,2))" = "TR" 
   and "%upcase(%sysfunc(subpad(&odstab,1,9)))" NE "TRENDTEST" %then %do;
    %let trodstab=&odstab;
    %let odstab=%substr(&odstab,3);
  %end;
  %let oneway=;
  %let bytreat=;
  %let keyword=%scan(&sept,2,#);
  %let keyword2=%upcase(%scan(&keyword,1,%str( =%()));
  %if %sysfunc(indexw(BINOMIAL TESTF TESTP,&keyword2))
    %then %let oneway=Y;
  %if &oneway = Y %then %let bytreat=&trtvar;

  %if %length(&bytreat) %then %do;
    proc sort data=_pvaldsin;
      by &bytreat &byvars &byvars2;
    run;
  %end;

  ods output &odstab=&odstab;

  proc freq data=_pvaldsin;
    by &bytreat &byvars &byvars2;
    where
    %if &usemiss NE Y %then %do;
      not missing(&respvar)
    %end;
    ;
    %if &oneway = Y %then %do;
      tables _statname / &keyword;
    %end;
    %else %do;
      tables &trtvar*&respvar / &keyword;
    %end;
    format &trtvar;
  run;
 

  %if %length(&trodstab) %then %do;
    proc sort data=&odstab out=&trodstab;
      by &bytreat &byvars &byvars2;
    run;

    proc transpose data=&trodstab(where=(Name1 NE " ")) out=&trodstab;
      by &bytreat &byvars &byvars2;
      var nvalue1;
      id Name1;
    run;
  %end;

  data _uniquadupd;
    length STAT&statno $ 30 _statord 8 _test $ 8;
    set %scan(&sept,4,#);
    by &bytreat &byvars &byvars2;
    if not last.%scan(&bytreat &byvars &byvars2,-1,%str( )) then do;
      put "ERROR: (unipvals) The unisept submacro has detected that you are trying";
      put "  to add more than one observation in each by group to the output for the";
      put "  by group: &bytreat &byvars &byvars2.";
      put "  You need to select only one observation for this by use of a where clause";
      put "  in your 4th sept parameter dataset segment: %scan(&sept,4,#)";
      put (_all_) (=);
      stop;
    end;
    if _test=" " then _test=" ";
    _statord=%scan(&sept,6,#);
    STAT&statno=%scan(&sept,7,#);
    KEEP &byvars &byvars2 _statord _test STAT&statno;
  run;

  %if not %sysfunc(exist(&dsout)) %then %do;
    data &dsout;
      set _uniquadupd;
    run;
  %end;
  %else %do;

    data &dsout;
      update &dsout _uniquadupd;
      by &byvars &byvars2 _statord;
    run;
  %end;

  proc datasets nolist memtype=data;
    delete _uniquadupd &odstab &trodstab;
  run;
  quit;

%mend unisept;
 

The above macro "updates" the &dsout dataset which gets passed back to the %unistats macro where it gets concatenated with the collected dataset containing statistical values. It has to be concatenated using a "set" statement since the STAT variable created is dynamic and new occurences of this will be lost if "proc append" is used and it is not already present on the base dataset.

The macro %unicatrep that gets called to display the values in the output report has to detect which of the STAT variables are present in the dataset supplied to it and to display them accordingly. The processing for this is shown below where it is mostly calculating the report width.
 

%do i=0 %to 9;
  %if %varnum(&dsin,STAT&i) %then %do;
    %if &i EQ 0 %then %do;
      %if &showstat0 EQ Y %then %do;
        %let trtwidth=%eval(&trtwidth+&trtspace+&&stat&i.w);
        %let statvars=&statvars STAT&i;
      %end;
    %end;
    %else %if &i EQ 1 %then %do;
      %if &showstat1 EQ Y %then %do;
        %let trtwidth=%eval(&trtwidth+&trtspace+&&stat&i.w);
        %let statvars=&statvars STAT&i;
      %end;
    %end;
    %else %do;
      %let trtwidth=%eval(&trtwidth+&trtspace+&&stat&i.w);
      %let statvars=&statvars STAT&i;
    %end;
  %end;
%end;
 

Later in the code, within the "proc report" step, the presence of these variables is again detected. Whether STAT0 (reserved for China's version of the FDA) or STAT1 are shown are controlled by special parameters "showstat0" and "showstat1" but for all other STAT variables detected, they will be displayed.
 

    %do i=0 %to 9;
      %if %varnum(&dsin,STAT&i) %then %do;
        %if &i EQ 0 %then %do;
          %if &showstat0 EQ Y %then %do;
            define STAT&i / width=&&stat&i.w &&STAT&i.align &&STAT&i.LBL display spacing=&trtspace
            %if %length(&outputwidthpct) %then %do;
              %if &PDF NE Y %then %do;
                style(COLUMN)=[cellwidth=%eval(45*(&&stat&i.w+&trtspace)/&repwidth)% ]
              %end;
            %end;
          %end;
        %end;
        %else %if &i EQ 1 %then %do;
          %if &showstat1 EQ Y %then %do;
            define STAT&i / width=&&stat&i.w &&STAT&i.align &&STAT&i.LBL display spacing=&trtspace
            %if %length(&outputwidthpct) %then %do;
              %if &PDF NE Y %then %do;
                style(COLUMN)=[cellwidth=%eval(45*(&&stat&i.w+&trtspace)/&repwidth)% ]
              %end;
            %end;
          %end;
        %end;
        %else %do;
          define STAT&i / width=&&stat&i.w &&STAT&i.align &&STAT&i.LBL display spacing=&trtspace
          %if %length(&outputwidthpct) %then %do;
            %if &PDF NE Y %then %do;
              style(COLUMN)=[cellwidth=%eval(45*(&&stat&i.w+&trtspace)/&repwidth)% ]
            %end;
          %end;
        %end;
        ;
      %end;
    %end;
 

For full details of the processing you should refer to the macros directly and search on "STAT" as a lot of minor details have been omitted so as not to obscure an understanding of the main processing behind the "tuplet" calls. For example, parameters exist for STAT column labels, widths and alignments as you would expect.

Conclusion

The mode of working of the "tuplet" processing has been described here as well as detailed coding for the freqsept= parameter processing explained. Please note that this method is quite likely the intellectual property of the author. You should not implement this method in your own existing reporting system without clarifying this issue.

 
 

Use the "Back" button of your browser to return to the previous page.

contact the author