Sunday, April 7, 2024

Simpson's Paradox - in integrated analyses or in sub-group analyses

Simpson's paradox is a statistical paradox wherein the successes of groups seem reversed when the groups are combined. Simpson's paradox occurs when we combine data or when we perform the sub-group analyses.

According to Wikipedia, the Simpson's paradox is defined as the following:
Simpson's paradox is a phenomenon in probability and statistics in which a trend appears in several groups of data but disappears or reverses when the groups are combined. This result is often encountered in social-science and medical-science statistics, and is particularly problematic when frequency data are unduly given causal interpretations. The paradox can be resolved when confounding variables and causal relations are appropriately addressed in the statistical modeling. Simpson's paradox has been used to illustrate the kind of misleading results that the misuse of statistics can generate

It gave several examples of the Simpson's paradox including the kidney stone treatment example:

Kidney stone treatment

Another example comes from a real-life medical study comparing the success rates of two treatments for kidney stones. The table below shows the success rates (the term success rate here actually means the success proportion) and numbers of treatments for treatments involving both small and large kidney stones, where Treatment A includes open surgical procedures and Treatment B includes closed surgical procedures. The numbers in parentheses indicate the number of success cases over the total size of the group.


The paradoxical conclusion is that treatment A is more effective when used on small stones, and also when used on large stones, yet treatment B appears to be more effective when considering both sizes at the same time. In this example, the "lurking" variable (or confounding variable) causing the paradox is the size of the stones, which was not previously known to researchers to be important until its effects were included.

Here are some additional links to explain the Simpson's paradox:

In clinical trials, Simpson's paradox can occur when the overall treatment effect observed in the entire study population is different from the treatment effect observed within subgroups of the population. This phenomenon can lead to misleading conclusions about the effectiveness of a treatment if not properly addressed.

For example, consider a clinical trial evaluating the effectiveness of two treatments (Drug A and Drug B) for a certain medical condition. The overall analysis of the trial data might suggest that Drug A is more effective than Drug B in improving patient outcomes. However, when the data is stratified by important demographic or clinical variables, such as age, gender, or disease severity, a different picture may emerge.

Let's say that within each age group, Drug B appears to be more effective than Drug A. However, the older age group constitutes a larger proportion of the study population, and within this group, patients tend to have poorer outcomes regardless of the treatment they receive. As a result, the overall analysis might erroneously indicate that Drug A is more effective, when in fact Drug B is more effective within each age group.

In this scenario, Simpson's paradox occurs because the distribution of confounding variables (in this case, age) differs between the treatment groups, leading to a reversal of the observed treatment effects when the data is aggregated. To avoid drawing misleading conclusions from clinical trial data affected by Simpson's paradox, it's crucial to conduct subgroup analyses and consider potential confounding variables that may influence treatment outcomes. Additionally, techniques such as propensity score matching or regression adjustment can help mitigate the impact of confounding variables and provide more accurate estimates of treatment effects.

Simpson's paradox can occur in pooling the data from multiple clinical trials, in meta analysis, in integrated summary of effectiveness (ISE), and in integrated summary of safety (ISS). 

FDA guidance for industry "Integrated Summary of Effectiveness" discussed 'pooled analyses of data from more than one study' and specifically mentioned  



Simpson's paradox can be a subject of the regulatory review comments when pooling the data or combining the data from multiple studies. 

Statistical review on BI's NDA of Empagliflozin for the treatment of type 2 diabetes:


FDA Briefing Document Cardiovascular and Renal Drugs Advisory Committee Meeting July 15, 2021 Roxadustat for the treatment of anemia due to chronic kidney disease (CKD)

... crude pooling of trials with varying allocation ratios can also lead to confounding by trial (i.e., Simpson’s paradox). 
In a DIA webinar presentation (2024) Integrated Safety Analyses in Drug Marketing Applications: Avoiding Common Mistakes, Mary Nilsson provided an example of Simpson's paradox when pooling the safety data from three randomized controlled clinical trials - Simpson's paradox can obscure the safety signals in the pooled analyses. She suggested that the stratification as an approach to address the Simpson's paradox issue. For the integrated safety analyses, the analytical methods should always be stratified by study - take differences within studies first, then takes the average of those differences.


This same issue was mentioned in a paper by SUH (2009) The use of atypical antipsychotics in dementia: rethinking Simpson’s paradox"
for safety analysis, pooling the data from multiple studies may obscure the finding of the safety signals. 
Science.org had an article in 2018 FDA's revolving door: Companies often hire agency staffers who managed their successful drug reviews. It described a case of pooled data analyses obscuring the signal of sudden cardiac death due to Simpson's paradox.  raise conflict of interest questions
In 2009, for example, an FDA panel weighed whether the agency should approve AstraZeneca's widely prescribed antipsychotic drug quetiapine (Seroquel) for a wider range of conditions. The panel heard from health policy expert Wayne Ray of Vanderbilt University in Nashville, who described his research linking the drug to sudden cardiac death when used with certain other medications. Ray recalls "an FDA staff member who gave a very negative presentation on our paper." And according to the meeting transcript, the agency's then-Director of Psychiatric Products Thomas Laughren, who was instrumental in shepherding Seroquel and similar drugs through the review process and personally signed their FDA approvals, also challenged Ray's results and defended AstraZeneca's clinical trial findings in the discussion that followed. The company's "analysis should have been able to pick up a difference in sudden cardiac death, and they didn't find any difference between drug and placebo," he said.

Ray told Laughren and the panel that AstraZeneca had pooled data from all its trials as though the data were one data set, causing a well-known statistical error called Simpson's paradox. To take the company's conclusion "as definitive" would be "very dangerous," Ray said, according to the transcript. Laughren responded by calling sudden death "a pretty definitive event."

Ultimately, the committee voted overwhelmingly to advise approval of the drug for new indications and made no recommendation on labeling it to warn about sudden cardiac death. Later evidence showed that the cardiac problems Ray described are real, and in 2011, FDA required adding a warning on Seroquel's label.
I consulted ChatGPT about strategies for addressing potential Simpson's paradox, and I wholeheartedly endorse the following responses.
To minimize the potential biases caused by Simpson's paradox, you can employ several strategies during data analysis:

Awareness: Be aware of the possibility of Simpson's paradox and actively look for it, especially when dealing with aggregated data or subgroup analyses.

Visualization: Visualize the data in various ways to explore relationships between variables. Use scatter plots, histograms, box plots, and other visualization techniques to identify patterns and potential confounding variables.

Stratification: Stratify the data by relevant variables to examine relationships within homogeneous subgroups. This allows you to identify any instances where the direction of association changes across subgroups.

Control Variables: Include potential confounding variables as control variables in your analysis. By controlling for these variables, you can assess the relationship between the variables of interest while holding other factors constant.

Causal Inference Techniques: Use causal inference techniques, such as causal mediation analysis or propensity score matching, to account for potential confounding variables and identify true causal relationships.

Sensitivity Analysis: Conduct sensitivity analyses to assess the robustness of your findings to different assumptions and model specifications. This helps you understand the potential impact of confounding variables on your results.

Expert Consultation: Consult with domain experts to ensure that you're considering all relevant variables and potential sources of bias in your analysis.

By incorporating these strategies into your data analysis, you can minimize the potential biases caused by Simpson's paradox and obtain more accurate and reliable results.


Sunday, February 28, 2016

Display all categories on CRF even there is no count

Very often in a statistical summary table for a variable (such as race, age group), we will need to display all categories listed on the case report form even through there may be no subject falling into one of the categories.

See paper by Phillips and Klein "Oh No, a Zero Row: 5 Ways to Summarize Absolutely Nothing" for discussions of various ways in handling this.

SPARSE option in PROC FREQ for filling the data set

The SPARSE Option The SPARSE option in PROC FREQ is not properly named. It is neither meager nor thin in its ability. It is a very powerful option in the table statement. Simply stated, the SPARSE option provides “all possible combinations of levels of the variables in the table, even when some combination levels do not occur in the data.”1 This is a huge help when trying to zero-fill a data set. Although current versions of SAS® now contain the new PRELOADFMT option for TABULATE and REPORT, for some types of reports, this new option will not work.

See the article by Chris Moriak

Monday, February 1, 2016

How to find a character or word from a free text variable?

It is often difficult to summarize the information from a free text variable. If we do, it may require to identify / find the character or word using a SAS function.

SAS function Index() can be used for this purpose. The following statement will identify any records with the word 'PNEUMONIA' contained in the VARNAME variable.

       where index(varname, 'PNEUMONIA')>=1;

if the word we need to find has mixed lower and upper cases, we can use the upcase() function.

       where index(upcase(varname), 'PNEUMONIA')>=1;

Reference:
How can I find things in a character variable in SAS?

Friday, January 15, 2016

Calculating the difference between the last value and the first value using RETAIN statement

The program below demonstrates the use of retain statement to calculate the difference between the last value and the first value for each id. 

data x1;
       input id days score;
 datalines;
1 1 23
1 2 34
2 1 45
2 2 46
3 1 35
3 2 40
;

proc sort; 
    by id;
run;

data new;
     set x1;
     by id;   
      if first.id then do;
           score1=score;
           retain score1;
        end;
   diff=score-score1;
   if last.id then output;
run;

proc print;
run;



Monday, January 4, 2016

Forest Plot of Hazard Ratios by Patient Subgroups using SAS






















The original paper can be found here: 
Base SAS: GTL Template Language
SAS 9.4 GTL feature: Value statement's TextAttrs option

%let graphs='.';

%let dpi=100;
%let w=8in;
%let h=4.5in;

/*--Leading blanks in the subgroup variable must be non--blank spaces  --*/
/*--Use character value 'A0', or copy from Windows System Character Map--*/
/*--Regular leading blanks will be stripped, losing the indentation    --*/
data forest;
  input Indent Subgroup $3-27 Count Percent Mean  Low  High  PCIGroup Group PValue;
  format PCIGroup Group PValue 7.2;
  zero=0; 
  if count ne . then CountPct=put(count, 4.0) || "(" || put(percent, 3.0) || ")";
  datalines;
0 Overall..................2166  100  1.3   0.9   1.5  17.2  15.6  .
0 Age.......................     .    .     .     .    .     .     0.05
2 <= 65 Yr.................1534   71  1.5   1.05  1.9  17.0  13.2   .
2 > 65 Yr.................. 632   29  0.8   0.6   1.25 17.8  21.3   .
0 Sex.......................     .    .     .     .    .     .     0.13
2 Male.....................1690   78  1.5   1.05  1.9  16.8  13.5   .
2 Female................... 476   22  0.8   0.6   1.3  18.3  22.9   . 
0 Race or ethnic group......     .    .     .     .    .     .     0.52
2 Nonwhite................. 428   20  1.05  0.6   1.8  18.8  17.8   .
2 White....................1738   80  1.2   0.6   1.6  16.7  15.0   . 
0 From MI to Randomization..     .    .     .     .    .     .     0.81
2 <= 7 days................ 963   44  1.2   0.8   1.5  18.9  18.6   .
2 > 7 days.................1203   56  1.15  0.75  1.5  15.9  12.9   .
0 Infract-related artery....     .    .     .     .    .     .     0.38
2 LAD...................... 781   36  1.4   0.9   1.9  20.1  16.2   .
2 Other....................1385   64  1.1   0.8   1.4  15.6  15.3   . 
0 Ejection Fraction.........     .    .     .     .    .     .     0.48
2 < 50%....................1151   54  1.2   0.8   1.5  22.6  20.4   .
2 >= 50%................... 999   46  0.9   0.6   1.4  10.7  11.1   . 
0 Diabetes..................     .    .     .     .    .     .     0.41
2 Yes...................... 446   21  1.4   0.9   2.0  29.3  23.3   .
2 No.......................1720   79  1.1   0.8   1.5  14.4  13.5   . 
0 Killip class..............     .    .     .     .    .     .     0.39
2 I........................1740   81  1.2   0.8   1.6  15.2  13.1   .
2 II-IV.................... 413   19  0.95  0.6   1.5  25.3  26.9   . 
;
run;

/*--Replace '.' in subgroup with blank--*/
data forest2;
  set forest;
  subgroup=translate(subgroup, ' ', '.');
  val=mod(_N_-1, 6);
  indent=ifn(indent eq 2, 1, 0);
  if val eq 1 or val eq 2 or val eq 3 then ref=subgroup;
  run;

/*--Create font with smaller fonts for axis label, value and data--*/
proc template;
  define style listingSF; 
    parent = Styles.Listing; 
    style GraphFonts from GraphFonts                                                      
      "Fonts used in graph styles" /                                       
      'GraphDataFont' = (", ",7pt)                                
      'GraphValueFont' = (", ",6pt)
      'GraphLabelFont' = (", ",6pt, bold); 
  end;
run;

/*--Define templage for Forest Plot--*/
/*--Template uses a Layout Lattice of 6 columns--*/
proc template;
  define statgraph Forest;
  dynamic _show_bands _color _thk;
    begingraph;
      entrytitle 'Forest Plot of Hazard Ratios by Patient Subgroups ';
      discreteattrmap name='text';
        value '0' / textattrs=(weight=bold);
        value other;
      enddiscreteattrmap;
      discreteattrvar attrvar=type var=indent attrmap='text';

      layout lattice / columns=6 columnweights=(0.25 0.1 0.4 0.08 0.08 0.09);

      /*--Column headers--*/
      sidebar / align=top;
        layout lattice / rows=2 columns=4 columnweights=(0.2 0.25 0.25 0.3);
          entry textattrs=(size=8) halign=left "Subgroup";
          entry textattrs=(size=8) halign=left " No.of Patients (%)";
          entry textattrs=(size=8) halign=left "Hazard Ratio";
          entry halign=center textattrs=(size=8) "4-Yr Cumulative Event Rate" ;
          entry " "; 
          entry " "; 
          entry " "; 
          entry halign=center textattrs=(size=8) "Medical Therapy";
        endlayout;
      endsidebar;

      /*--First Subgroup column, shows only the Y2 axis--*/
      layout overlay / walldisplay=none xaxisopts=(display=none) 
          yaxisopts=(reverse=true display=none 
                     tickvalueattrs=(weight=bold));
        referenceline y=ref / lineattrs=(thickness=_thk color=_color);
        axistable y=subgroup value=subgroup / indentweight=indent textgroup=type;
       endlayout;

       /*--Second column showing Count and percent--*/
       layout overlay / xaxisopts=(display=none) 
            yaxisopts=(reverse=true display=none) walldisplay=none;
         referenceline y=ref / lineattrs=(thickness=_thk color=_color);
         axistable y=subgroup value=countpct;
       endlayout;

       /*--Third column showing odds ratio graph--*/
       layout overlay / xaxisopts=(label='   <---PCI Better----  ----Medical Therapy Better--->'  
           linearopts=(tickvaluepriority=true 
                       tickvaluelist=(0.0 0.5 1.0 1.5 2.0 2.5)))
           yaxisopts=(reverse=true display=none) walldisplay=none;
         referenceline y=ref / lineattrs=(thickness=_thk color=_color);
         scatterplot y=subgroup x=mean / xerrorlower=low xerrorupper=high 
           markerattrs=(symbol=squarefilled);
         referenceline x=1;
       endlayout;

       /*--Fourth column showing PCIGroup--*/
       layout overlay / x2axisopts=(display=(tickvalues) offsetmin=0.25 offsetmax=0.25) 
            yaxisopts=(reverse=true display=none) walldisplay=none;
         referenceline y=ref / lineattrs=(thickness=_thk color=_color);
         axistable y=subgroup value=PCIGroup / display=(label) labelposition=max;
       endlayout;

       /*--Fifth column showing Group--*/
       layout overlay / x2axisopts=(display=(tickvalues) offsetmin=0.25 offsetmax=0.25) 
            yaxisopts=(reverse=true display=none) walldisplay=none;
         referenceline y=ref / lineattrs=(thickness=_thk color=_color);
         axistable y=subgroup value=group / display=(label) labelposition=max;
       endlayout;

       /*--Sixth column showing P-Values--*/
       layout overlay / x2axisopts=(display=(tickvalues) offsetmin=0.25 offsetmax=0.25) 
           yaxisopts=(reverse=true display=none) walldisplay=none;
         referenceline y=ref / lineattrs=(thickness=_thk color=_color);
         axistable y=subgroup value=pvalue / display=(label) labelposition=max;
       endlayout;

     endlayout;
     entryfootnote halign=left textattrs=(size=7) 
       'The p-value is from the test statistic for testing the interaction between the '
       'treatment and any subgroup variable';
     entryfootnote halign=left 'This graph uses the new AXISTABLE plot to display the textual columns';
   endgraph;
  end;
run;

/*--Need format to show missing as blank--*/
proc format;
  value misblank
    . = ' ';
run;

/*----Create Graph-----*/
ods listing style=htmlblue gpath=&graphs image_dpi=&dpi;
ods graphics / reset noscale width=&w height=&h imagename='GTL_ForestPlot';
proc sgrender data=Forest2 template=Forest;
format pvalue group pcigroup misblank7.2;
dynamic _color='cxf0f0f0' _thk=12;
run;