Saturday, November 10, 2012

Patient-focused Drug Development and Qualitative Methods in Clinical Trial

Almost 20 years ago, we introduced the qualitative methods in public health research in China. We organized the workshops and published the papers. We actually implemented the qualitative methods when we assessed the health care quality, KABP (Knowledge, Attitudes, Beliefs, and Practices) survey, and breast feeding research.


I have never thought that I would use any qualitative method since I am now a statistician and in the drug development field.  Last month, I attended a one-day interactive workshop “Clinical trial endpoints: methods and practice in developing measurements” with special emphasis on rare diseases. We spent majority of our time in discussing the qualitative methods (mainly the patient interview) to establish the content validity for patient oriented clinical trial endpoints (for example, the patient reported outcome (PRO)). For many orphan (rare) diseases, the characteristics of the disease may not be well defined and patient population in study may not be homogeneous. For the purpose of the clinical trials, it is challenging to identify a valid clinical endpoint to measure the efficacy. One approach to tackle this is to start with the patient. To interact with patients, the qualitative methods (for example the interview with patients) can be used to solicit the issues/items that are most concerned by the patients and hopefully we can based this information to derive a clinical endpoint for the clinical trials.

With patient-focused approach, the clinical endpoint should reflect the impact on:
  • Patient’s mortality
  • Patient’s function
  • Patient’s feeling
This will be much different from the biomarkers which may not have any direct impact on any of these (at least in the short term). It is probably true that the non-disease specific tool for health related quality of life measure may be less sensitive and less relevant comparing to any tool / instrument / scale derived with the inputs directly from patients.

One thing has become clear that the clinical trial endpoint derived with the patient-focused approach need qualification (not the formal ‘validation’). Qualification process is supposed to be not as time consuming as the formal validation.  In 2010, FDA issued its guidance on “qualification process for drug development tools”.

In the newly passed PDUFA V (prescription drug user fee act), FDA proposed patient-focused drug development and a systematic effort in PDUFA V:
  •  PDUFA V provides resources to support additional program staff to expand activities dedicated to providing review divisions with patient input
  • FDA will convene meetings with participation from review divisions, the relevant patient advocacy community, and other interested parties
  • FDA will hold four public workshops per year – a total of 20 meetings over 5 years. Each meeting will focus on a different disease area, reviewing the armamentarium for that indication, and identifying areas of unmet need.

FDA had already put this in action and organized its first meeting last Month to discuss the list of diseases to be included. The background and the plan about the patient-focused drug development program are described in the presentation by Dr Mullin.

A Statistician or Fortune Teller

The election is over. Other than President Obama, a statistician is shining. See the Yahoo article "He called it, and now Silver's a pop-culture star"  to see if Nate Silver is a statistician or fortune teller.

 

Would the simulation have any value in president election? Read the article "Flip this White House: Could random chance have made Al Gore president? What about Bob Dole?" to see what happens if the simulation is applied.

Thursday, October 18, 2012

Using SAS ODS Graphics with Example for Generating Kaplan-Meier Curves

Recently, I spent some time on exploring the feature of SAS ODS Graphics. ODS Graphics offers an easy way to generate high-quality statistical graphics without extensive programming using SAS/Graph.  ODS Graphics has been included in almost all of SAS/Stat Procedure.

In the discussions below, the example from SAS Proc Lifetest is used to illustrate the generation of Kaplan-Meier curve using ODS Graphics.

On the basis of SAS Example 49.2 Enhanced Survival Plot and Multiple-Comparison Adjustments, we can run the following program.

proc format;
      value risk 1='ALL' 2='AML-Low Risk' 3='AML-High Risk';
   
   data BMT;
      input Group T Status @@;
      format Group risk.;
      label T='Disease Free Time';
      datalines;
   1 2081 0 1 1602 0 1 1496 0 1 1462 0 1 1433 0
   1 1377 0 1 1330 0 1  996 0 1  226 0 1 1199 0
   1 1111 0 1  530 0 1 1182 0 1 1167 0 1  418 1
   1  383 1 1  276 1 1  104 1 1  609 1 1  172 1
   1  487 1 1  662 1 1  194 1 1  230 1 1  526 1
   1  122 1 1  129 1 1   74 1 1  122 1 1   86 1
   1  466 1 1  192 1 1  109 1 1   55 1 1    1 1
   1  107 1 1  110 1 1  332 1 2 2569 0 2 2506 0
   2 2409 0 2 2218 0 2 1857 0 2 1829 0 2 1562 0
   2 1470 0 2 1363 0 2 1030 0 2  860 0 2 1258 0
   2 2246 0 2 1870 0 2 1799 0 2 1709 0 2 1674 0
   2 1568 0 2 1527 0 2 1324 0 2  957 0 2  932 0
   2  847 0 2  848 0 2 1850 0 2 1843 0 2 1535 0
   2 1447 0 2 1384 0 2  414 1 2 2204 1 2 1063 1
   2  481 1 2  105 1 2  641 1 2  390 1 2  288 1
   2  421 1 2   79 1 2  748 1 2  486 1 2   48 1
   2  272 1 2 1074 1 2  381 1 2   10 1 2   53 1
   2   80 1 2   35 1 2  248 1 2  704 1 2  211 1
   2  219 1 2  606 1 3 2640 0 3 2430 0 3 2252 0
   3 2140 0 3 2133 0 3 1238 0 3 1631 0 3 2024 0
   3 1345 0 3 1136 0 3  845 0 3  422 1 3  162 1
   3   84 1 3  100 1 3    2 1 3   47 1 3  242 1
   3  456 1 3  268 1 3  318 1 3   32 1 3  467 1
   3   47 1 3  390 1 3  183 1 3  105 1 3  115 1
   3  164 1 3   93 1 3  120 1 3   80 1 3  677 1
   3   64 1 3  168 1 3   74 1 3   16 1 3  157 1
   3  625 1 3   48 1 3  273 1 3   63 1 3   76 1
   3  113 1 3  363 1
   ;

ods rtf file="c:\temp\test.doc" style=journal;
ods graphics on;
ods trace on;
proc lifetest data=BMT plots=survival(atrisk=0 to 2500 by 500);
      ods select SurvivalPlot;
      time T * Status(0);
      strata Group / test=logrank adjust=sidak;
      run;
ods trace off;
ods graphics off;
ods rtf close;

In the above statements,
ods rtf file="c:\temp\test.doc" specifies the ODS output as RTF file and the file location and file name. style=journal specifies the use of output in publication quality.
"ods graphics on" to invoke the ODS Graphics
"ods trace on" is not really necessary, but it is useful when we need to know the name/location of the SAS template. If we check the SAS Log Window, we will be able to see that the SAS template for the corresponding graph is Stat.Lifetest.Graphics.ProductLimitSurvival

Output Added:
-------------
Name:       SurvivalPlot
Label:      Survival Curves
Template:   Stat.Lifetest.Graphics.ProductLimitSurvival
Path:       Lifetest.SurvivalPlot
-------------

PROC LIFETEST is invoked to compute the product-limit estimate of the survivor function for each of three risk categories (1='ALL' 2='AML-Low Risk' 3='AML-High Risk'). Using ODS Graphics, you can display the number of subjects at risk in the survival plot. The PLOTS= option requests that the survival curves be plotted, and the ATRISK= suboption specifies the time points at which the at-risk numbers are displayed. In the STRATA statement, the ADJUST=SIDAK option requests the idák multiple-comparison adjustment, and by default, all paired comparisons are carried out.

After the run, we will need to use the following statements to close ODS.
ods trace off;
ods graphics off;
ods rtf close;

The above program will create a high quality figure like below:


Suppose we would like to change the title and labels of the figure above, we can use PROC Template. First thing we need to do is to identify which Template we need to modify. The SAS Log window from ods trace on statement indicates the following template.

Stat.Lifetest.Graphics.ProductLimitSurvival

To open this template, we would need to the pull down manu in SAS window, then choose View -> Results -> View -> Template
From Template window, we will locate the following folder:
SAShelp.tmplmst -> stat -> lifetest -> graphics

We will then see the template called ProductLimitSurvival. Double click this file, we will be able to see the contents in this template file.



The template file can be copied and pasted into the program window. We can then edit the template file to modify the title, labels, legends,…

Suppose we run the following program to generate the Kaplan-Meier curve (upward curve instead of the original downward curve). The upward curve is sometimes easier to be understood. For example, it is useful in the time to event variable where the event is a better outcome (for example, time to recovery, time to release from the hospital, time to immune tolerance (in Hemophilia). By changing the layout of the figure, we would also need to modify the label.


proc lifetest data=BMT plots=survival(f test);
      ods select failurePlot;
      time T * Status(0);
      strata Group / test=logrank adjust=sidak;
run;

plots=survival(f test) requests the failure Plot (upward) instead of the survival plot (downward). The ‘test’ indicates the display of the logrank test results.

From the LOG window trigged by ODS Trace On statement, we will be able to know that the template is Stat.Lifetest.Graphics.ProductLimitFailure which is located in the same folder as Stat.Lifetest.Graphics.ProductLimitSurvival discussed before.

Output Added:
-------------
Name:       FailurePlot
Label:      Failure Curves
Template:   Stat.Lifetest.Graphics.ProductLimitFailure
Path:       Lifetest.FailurePlot 



We can copy and paste the template statements and run as part of the program.

ods rtf file="c:\temp\test.doc" style=journal;
ods graphics on;
ods trace on;

proc template;                                                               
   define statgraph Stat.Lifetest.Graphics.ProductLimitFailure;              
      dynamic NStrata xName maxTime plotAtRisk plotCensored plotCL plotHW    
         plotEP labelCL labelHW labelEP yMin StratumID classAtRisk plotTest  
         GroupName Transparency SecondTitle TestName pValue;                 
      BeginGraph;                                                             
         if (NSTRATA=1)                                                      
            if (EXISTS(STRATUMID))                                           
            entrytitle "Kaplan-Meier Curve" " for " STRATUMID;       /*Revised the Figure title*/
         else                                                                
            entrytitle "Kaplan-Meier Curve";                        
         endif;                                                               
         if (PLOTATRISK)                                                     
            entrytitle "with Number of Subjects at Risk" / textattrs=        
            GRAPHVALUETEXT;                                                   
         endif;                                                              
         layout overlay / xaxisopts=(label="Time to Recovery" offsetmin=.05 linearopts /*xaxisopts=(label=" ") to change the x-axis label*/
            =(viewmax=MAXTIME)) yaxisopts=(label="Probability of Achieving Tolerance"       
            shortlabel="Failure" linearopts=(viewmin=0 viewmax=1 tickvaluelist
            =(0 .2 .4 .6 .8 1.0)));                                          
            if (PLOTHW=1 AND PLOTEP=0)                                       
               bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval (1-HW_UCL)
               x=TIME / modelname="Failure" fillattrs=GRAPHCONFIDENCE name=  
               "HW" legendlabel=LABELHW;                                      
            endif;                                                           
            if (PLOTHW=0 AND PLOTEP=1)                                       
               bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval (1-EP_UCL)
               x=TIME / modelname="Failure" fillattrs=GRAPHCONFIDENCE name=  
               "EP" legendlabel=LABELEP;                                     
            endif;                                                           
            if (PLOTHW=1 AND PLOTEP=1)                                       
               bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval (1-HW_UCL)
               x=TIME / modelname="Failure" fillattrs=GRAPHDATA1             
               datatransparency=.55 name="HW" legendlabel=LABELHW;           
            bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval (1-EP_UCL) x=
               TIME / modelname="Failure" fillattrs=GRAPHDATA2               
               datatransparency=.55 name="EP" legendlabel=LABELEP;           
            endif;                                                           
            if (PLOTCL=1)                                                    
               if (PLOTHW=1 OR PLOTEP=1)                                      
               bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval (1-SDF_UCL
               ) x=TIME / modelname="Failure" display=(outline) outlineattrs=
               GRAPHPREDICTIONLIMITS name="CL" legendlabel=LABELCL;           
            else                                                             
               bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval (1-SDF_UCL
               ) x=TIME / modelname="Failure" fillattrs=GRAPHCONFIDENCE name=
               "CL" legendlabel=LABELCL;                                     
            endif;                                                           
            endif;                                                           
            stepplot y=eval (1-SURVIVAL) x=TIME / name="Failure" rolename=(  
               _tip1=ATRISK _tip2=EVENT) tip=(y x Time _tip1 _tip2)          
               legendlabel="Failure";                                        
            if (PLOTCENSORED)                                                 
               scatterplot y=eval (1-CENSORED) x=TIME / markerattrs=(symbol= 
               plus) name="Censored" legendlabel="Censored";                 
            endif;                                                           
            if (PLOTCL=1 OR PLOTHW=1 OR PLOTEP=1)                            
               discretelegend "Censored" "CL" "HW" "EP" / location=outside   
               halign=center;                                                 
            else                                                             
               if (PLOTCENSORED=1)                                           
               discretelegend "Censored" / location=inside autoalign=(topleft
               bottomright);                                                 
            endif;                                                           
            endif;                                                           
            if (PLOTATRISK=1)                                                
               innermargin / align=bottom;                                   
               blockplot x=TATRISK block=ATRISK / repeatedvalues=true display=
                  (values) valuehalign=start valuefitpolicy=truncate         
                  labelposition=left labelattrs=GRAPHVALUETEXT valueattrs=   
                  GRAPHDATATEXT (size=7pt) includemissingclass=false;        
            endinnermargin;                                                  
            endif;                                                           
         endlayout;                                                           
         else                                                                
            entrytitle "Kaplan-Meier Curve";                       
         if (EXISTS(SECONDTITLE))                                            
            entrytitle SECONDTITLE / textattrs=GRAPHVALUETEXT;               
         endif;                                                              
         layout overlay / xaxisopts=(label="Time to Recovery" offsetmin=.05 linearopts
            =(viewmax=MAXTIME)) yaxisopts=(label="Probability of Recovery"     /*y-axis label*/  
            shortlabel="Failure" linearopts=(viewmin=0 viewmax=1 tickvaluelist
            =(0 .2 .4 .6 .8 1.0)));                                          
            if (PLOTHW=1)                                                    
               bandplot LimitUpper=eval (1-HW_LCL) LimitLower=eval (1-HW_UCL)
               x=TIME / group=STRATUM index=STRATUMNUM modelname="Failure"   
               datatransparency=Transparency;                                
            endif;                                                           
            if (PLOTEP=1)                                                    
               bandplot LimitUpper=eval (1-EP_LCL) LimitLower=eval (1-EP_UCL)
               x=TIME / group=STRATUM index=STRATUMNUM modelname="Failure"   
               datatransparency=Transparency;                                
            endif;                                                           
            if (PLOTCL=1)                                                    
               if (PLOTHW=1 OR PLOTEP=1)                                     
               bandplot LimitUpper=eval (1-SDF_LCL) LimitLower=eval (1-SDF_UCL
               ) x=TIME / group=STRATUM index=STRATUMNUM modelname="Failure" 
               display=(outline);                                            
            else                                                             
               bandplot LimitUpper=eval (1-SDF_UCL) LimitLower=eval (1-SDF_LCL
               ) x=TIME / group=STRATUM index=STRATUMNUM modelname="Failure" 
               datatransparency=Transparency;                                
            endif;                                                            
            endif;                                                           
            stepplot y=eval (1-SURVIVAL) x=TIME / group=STRATUM index=       
               STRATUMNUM name="Failure" rolename=(_tip1=ATRISK _tip2=EVENT) 
               tip=(y x Time _tip1 _tip2);                                   
            if (PLOTCENSORED)                                                
               scatterplot y=eval (1-CENSORED) x=TIME / group=STRATUM index= 
               STRATUMNUM markerattrs=(symbol=plus);                         
            endif;                                                           
            if (PLOTATRISK)                                                  
               innermargin / align=bottom;                                   
               blockplot x=TATRISK block=ATRISK / class=CLASSATRISK          
                  repeatedvalues=true display=(label values) valuehalign=start
                  valuefitpolicy=truncate labelposition=left labelattrs=     
                  GRAPHVALUETEXT valueattrs=GRAPHDATATEXT (size=7pt)         
                  includemissingclass=false;                                 
            endinnermargin;                                                   
            endif;                                                           
            DiscreteLegend "Failure" / title="Risk Group" location=inside autoalign=(bottomright); /*Revise the legend*/   
            if (PLOTCENSORED)                                                
               if (PLOTTEST)                                                 
               layout gridded / rows=2 autoalign=(TOPLEFT BOTTOMRIGHT BOTTOM 
               TOP) border=true BackgroundColor=GraphWalls:Color Opaque=true;
               entry "+ Censored";                                           
               if (PVALUE < .0001)                                           
                  entry TESTNAME " p " eval (PUT(PVALUE, PVALUE6.4));        
               else                                                          
                  entry TESTNAME " p=" eval (PUT(PVALUE, PVALUE6.4));        
               endif;                                                        
            endlayout;                                                       
            else                                                             
               layout gridded / rows=1 autoalign=(TOPLEFT BOTTOMRIGHT BOTTOM 
               TOP) border=true BackgroundColor=GraphWalls:Color Opaque=true;
               entry "+ Censored";                                           
            endlayout;                                                        
            endif;                                                           
            else                                                             
               if (PLOTTEST)                                                  
               layout gridded / rows=1 autoalign=(TOPLEFT BOTTOMRIGHT BOTTOM 
               TOP) border=true BackgroundColor=GraphWalls:Color Opaque=true;
               if (PVALUE < .0001)                                           
                  entry TESTNAME " p " eval (PUT(PVALUE, PVALUE6.4));        
               else                                                          
                  entry TESTNAME " p=" eval (PUT(PVALUE, PVALUE6.4));        
               endif;                                                        
            endlayout;                                                       
            endif;                                                           
            endif;                                                            
         endlayout;                                                          
         endif;                                                              
      EndGraph;                                                              
   end;                                                                      
run; 

proc lifetest data=BMT plots=survival(f test);
      ods select quartiles FailurePlot;
      time T * Status(0);
      strata Group / test=logrank adjust=sidak;
      run;
ods trace off;
ods graphics off;
ods rtf close;

At the end of the program, we use the following program to delete the user defined Template.
proc template;
   delete Stat.Lifetest.Graphics.ProductLimitFailure;
run;


Further reading: