/* Additional File 1 for 2018 Stevens et al. manuscript
   "Power from pairs: assessing the statistical value of 
    paired samples in tests for differential expression"

   SAS code to implement the probability distribution method
   for calculating approximate power with count expression
   (RNA-Seq) data
        
  */
  



/* Define macro to increment a global macro variable by one unit */  
%macro incrementvar(vartoincrement);
  %let &vartoincrement = %eval(&&&vartoincrement. + 1);
%mend;



/*******************************************************************

   Define main macro powercheck, with two arguments: 

      NumTotSamples = total number of samples
      FC = fold change to detect

   This macro uses two data sets defined before the macro is called:

      SubjVarRange is a data set giving a range of subject variance
      component values to use in power approximation, and has
      three variables (columns) and a user-selected number of rows:
        SubjVar = value of subject variance component to be used in
                  negative binomial generalized linear mixed model
        SubjVar10thRoot = tenth root of SubjVar
        Scale = value of scale parameter to be used in 
                negative binomial generalized linear mixed model

      pctPairedRange is a data set giving a range of percentages
      (proportions, actually) of subjects to consider as having
      paired samples in the hypothetical study design, and has
      one variable (column) and a user-selected number of rows:
        pctPaired = proportion of subjects to consider as having
                    paired samples (value can range 0 to 1)

   The macro creates a contour plot of the approximated power to
   detect the given fold change FC when NumTotSamples samples
   can be afforded, at each combination of SubjVar10thRoot
   (essentially the strength of possible subject-level confounding
   factors) and pctPaired.

   The macro also creates a data set called PowerFrame, with the
   necessary elements to recreate the contour plot in another software
   package.

    
*******************************************************************/

%macro powercheck(NumTotSamples, FC);

 data PowerFrame; SubjVar=.; 
 data keepNobs; NobsUsed=.; run;

 %global ii;
 %global jj;
 %local runnum;
 %let runnum=0;

 /* Temporarily suspend output */
 ods exclude all;
 
 
 /* Determine range of i (SubjVar levels) and j (pctPaired levels) */
 proc means data=SubjVarRange n;  var scale;  output out=out1 n=n; run;
 data out1; set out1;
   CALL SYMPUT('useNumSubjVar',n);
 run;
 proc means data=pctPairedRange n;  var pctPaired;  output out=out2 n=n; run;
 data out2; set out2;
   CALL SYMPUT('useNumPctPaired',n);
 run;

 %do i = 1 %to &useNumSubjVar;

   %let ii = &i;
   
   /* Define &useSubjVar  */
   data tempSubjVar; set SubjVarRange; if _n_ = &ii; keep SubjVar SubjVar10thRoot scale; run;
   data tempSubjVar; set tempSubjVar; 
       CALL SYMPUT('useScale',scale);
       CALL SYMPUT('useSubjVar',SubjVar);
       CALL SYMPUT('useSubjVar10thRoot',subjVar10thRoot);
   run;


  %do j = 1 %to &useNumPctPaired;
  
      %incrementvar(runnum);

      %local zeroP; 
      %local zeroU;
      
      data tempEX; run;
  
      %let jj = &j;

      /* Define &usePctPaired */
      data temppctPairedRange; set pctPairedRange; if _n_ = &jj;
        Npaired = ceil(&NumTotSamples*pctPaired/2);
        Nunpair = floor(&NumTotSamples*(1-pctPaired));
      data temppctPairedRange; set temppctPairedRange;
        zeroPaired   = (Npaired=0);
        zeroUnpaired = (Nunpair=0);
        keep pctPaired Npaired Nunpair zeroPaired zeroUnpair; run;
      data temppctPairedRange; set temppctPairedRange; 
           CALL SYMPUT('usePctPaired',pctPaired); 
           CALL SYMPUT('useNpaired',Npaired);
           CALL SYMPUT('useNunpair',Nunpair);
           CALL SYMPUT('zeroP',zeroPaired);
           CALL SYMPUT('zeroU',zeroUnpair);
      run;

      /* Define initial 'exemplary' data set  */
     
      %IF &zeroP ne 1 %THEN %DO;
          %IF &zeroU ne 1 %THEN
        %DO;
           /* Paired subjects: tumor samples */
           data exPairT; 
               do Subj=1 to &useNpaired by 1; 
                  Trt='T';
              output; end;
           run;
           /* Paired subjects: normal samples */
           data exPairN; 
               do Subj=1 to &useNpaired by 1; 
                  Trt='N';
              output; end;
           run;
           /* Unpaired subjects: tumor samples */
           data exUnpairT; 
              do Subj=(&useNpaired+1) to (&useNpaired+&useNunpair/2) by 1; 
                 Trt='T';
              output; end;
           run;
           /* Unpaired subjects: normal samples */
           data exUnpairN; 
               do Subj=(&useNpaired+&useNunpair/2 + 1) to (&useNpaired+&useNunpair) by 1; 
                 Trt='N';
               output; end;
           run;
           /* Put it all together */
           data tempEX; set exPairT exPairN exUnpairT exUnpairN;
             if Trt='N' then estY=400;
             if Trt='T' then estY=400*&FC;
             logN = 15;
           run;
           
           /* GLIMMIX */
           data power; run;
           proc glimmix data=tempEX noprofile method=quad;
               class Trt Subj;
               model estY = Trt / dist=negbinomial link=log offset=logN;
               random Intercept / Subject=Subj;
               parms (&useSubjVar)(&useScale) / hold=1,2;
               ods output tests3=power;
               ods output Nobs=NumObs;
           run;

        %END; %END;

     /* For zero paired subjects */   
     %IF &zeroP = 1 %THEN
       %DO;
           /* Unpaired subjects: tumor samples */
           data exUnpairT; 
              do Subj=1 to (&useNunpair/2) by 1; 
                 Trt='T';
              output; end;
           run;
           /* Unpaired subjects: normal samples */
           data exUnpairN; 
               do Subj=(&useNunpair/2 + 1) to (&useNunpair) by 1; 
                 Trt='N';
               output; end;
           run;
           /* Put it all together */
           data tempEX; set exUnpairT exUnpairN;
             if Trt='N' then estY=400;
             if Trt='T' then estY=400*&FC;
             logN = 15;
           run;
           
           /* GLIMMIX: NOTE that Subject is nested here since no subjects have any paired samples */
           data power; run;
           proc glimmix data=tempEX noprofile method=quad;
               class Trt Subj;
               model estY = Trt / dist=negbinomial link=log offset=logN;
               random Intercept / Subject=Subj(Trt);
               parms (&useSubjVar)(&useScale) / hold=1,2;
               ods output tests3=power;
               ods output Nobs=NumObs;
           run;
           
      %END;
      
      /* For zero unpaired subjects */
      %IF &zeroU = 1 %THEN
        %DO;
           /* Paired subjects: tumor samples */
           data exPairT; 
               do Subj=1 to &useNpaired by 1; 
                  Trt='T';
              output; end;
           run;
           /* Paired subjects: normal samples */
           data exPairN; 
               do Subj=1 to &useNpaired by 1; 
                  Trt='N';
              output; end;
           run;
           /* Put it all together */
           data tempEX; set exPairT exPairN;
             if Trt='N' then estY=400;
             if Trt='T' then estY=400*&FC;
             logN = 15;
           run;
           /* GLIMMIX */
           data power; run;
           proc glimmix data=tempEX noprofile method=quad;
               class Trt Subj;
               model estY = Trt / dist=negbinomial link=log offset=logN;
               random Intercept / Subject=Subj;
               parms (&useSubjVar)(&useScale) / hold=1,2;
               ods output tests3=power;
               ods output Nobs=NumObs;
           run;
        %END;
   
     /* Check sample sizes */
     data NumObs; set NumObs;
       if Label = 'Number of Observations Used';
       pctPaired=&usepctPaired;
       SubjVar10thRoot = &useSubjVar10thRoot;
       runnum = &runnum;
       keep pctPaired SubjVar10thRoot NobsUsed runnum;
     data keepNobs; set keepNobs NumObs;
     run;
   
      /* Power */
      data power; set power;  where Effect = 'Trt';  alpha = 0.05; nonCent_param = NumDF*Fvalue; FCrit = finv(1-alpha,NumDF,DenDF,0);
         Power = 1 - probf(Fcrit,NumDF,DenDF,nonCent_param);
         pctPaired = &usepctPaired;
         SubjVar = &useSubjVar;
         SubjVar10thRoot = &useSubjVar10thRoot;
         runnum = &runnum;
         keep Power pctPaired SubjVar SubjVar10thRoot runnum;
      run;
         
      /* Save result */
     data PowerFrame; set PowerFrame power;
     run;
     
     /* End j and i loops, respectively */
   %end;
 %end;

 data PowerFrame; set PowerFrame; if _n_ ne 1;
 run;
 data keepNobs; set keepNobs; if _n_ ne 1; keep NobsUsed runnum;
 run;
 data tempOut; merge PowerFrame keepNobs; by runnum; run; 

 /* Un-suspend output, and end macro */
 ods exclude none;

 
 axis1 label=("proportion subjects with paired samples");
 axis2 label=(angle=90 "strength of confounding factor (10th root of subj.var)");
 proc gcontour data=PowerFrame;
    plot subjVar10thRoot*pctPaired=power / join pattern haxis=axis1 vaxis=axis2;
    title1 "Power contours for N=&NumTotSamples total samples and FC=&FC";
 run;
 quit;



%mend;



/* Define ranges of contour plot axes to consider */
data pctPairedRange; do i = 0 to 30;
  pctPaired = i/30;  output;  end;  drop i;
data subjVarRange; do i = 0 to 30;
  subjVar10thRoot = 0.5 + i/30*2.0; SubjVar = subjVar10thRoot**10;  Scale=1.15;  output;  end;  drop i;
run;


/* Macro calls --(these six runs took a total of 30 minutes run-time) */

%powercheck(20,1.25);
data pf20; set PowerFrame; run;

%powercheck(50,1.25);
data pf50; set PowerFrame; run;

%powercheck(100,1.25);
data pf100; set PowerFrame; run;

%powercheck(200,1.25);
data pf200; set PowerFrame; run;

%powercheck(500,1.25);
data pf500; set PowerFrame; run;

%powercheck(1000,1.25);
data pf1000; set PowerFrame; run;

