This vignette accompanies the Results section of the RobinCar Family paper.

Case Study 1: ACTG175 Dataset

Data Manipulation

We use data from the Juraska et al. (2022) R package, which comes from a study of nucleoside treatment regimens for individuals with HIV-1 (Hammer et al. 1996) called the AIDS Clinical Trials Group Study 175. This trial had stratified permuted block randomization.

# Data are from the speff2trial package
data <- tibble(speff2trial::ACTG175)

data <- data %>% mutate(
  # Create continuous and binary outcomes
  y_cont = cd420 - cd40,
  y_bin = as.numeric((y_cont / cd40) > 0.5)
) %>% filter(
  # Focus on two treatment arms
  arms %in% c("0", "1")
) %>% mutate(
  # Factor variables for treatment
  # and stratification variable
  arms = factor(arms),
  strat = factor(strat)
)

Linear Adjustment

The following shows a linear model with treatment-by-covariate interactions, including strata as covariates, and using the four covariates of weight, hemophilia status, and prior use of non-zidovudine antiretroviral therapy. These specifications ensure that the covariate adjustment has guaranteed efficiency gain (asymptotically).

robin_lm(
  y_cont ~ arms * (strat + wtkg + hemo + oprior), 
  treatment = arms ~ pb(strat), 
  data=data)
## Model        :  y_cont ~ arms * (strat + wtkg + hemo + oprior) 
## Randomization:  arms ~ pb(strat)  ( Permuted-Block )
## Variance Type:  vcovG 
## Marginal Mean: 
##   Estimate  Std.Err    2.5 %  97.5 %
## 0 -16.9304   4.4915 -25.7336 -8.1273
## 1  54.0531   6.2848  41.7352 66.3710
## 
## Contrast     :  h_diff
##          Estimate Std.Err Z Value  Pr(>|z|)    
## 1 v.s. 0   70.984   7.673  9.2511 < 2.2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Generalized Linear Models

For the binary outcome, we can use a logistic regression model instead. Rather than a linear contrast, we could instead estimate a log risk ratio. This does not change the marginal mean estimates, but it changes the contrast estimates (and p-values).

robin_glm(
  y_bin ~ arms * (strat + wtkg + hemo + oprior),
  treatment = arms ~ pb(strat),
  family = binomial(link = "logit"),
  contrast = "log_risk_ratio",
  data=data
)
## Model        :  y_bin ~ arms * (strat + wtkg + hemo + oprior) 
## Randomization:  arms ~ pb(strat)  ( Permuted-Block )
## Variance Type:  vcovG 
## Marginal Mean: 
##    Estimate   Std.Err     2.5 % 97.5 %
## 0 0.0493622 0.0093041 0.0311264 0.0676
## 1 0.1835664 0.0168944 0.1504539 0.2167
## 
## Contrast     :  log_risk_ratio
##          Estimate Std.Err Z Value  Pr(>|z|)    
## 1 v.s. 0  1.31339 0.20904  6.2831 3.318e-10 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Mantel-Haenszel

RobinCar includes functions for robust Mantel-Haenszel estimation. Here we create a new variable that is the interaction of prior use of non-zidovudine antiretroviral therapy and hemophilia status, and use that as the stratification variable.

By default, the function uses the average treatment effect as the estimand of interest. Specifying the estimand argument as “MH” does not change the estimate, but it does change the standard error calculation.

The MH function in RobinCar is only valid for simple randomization.

data <- data %>% mutate(
  X=interaction(oprior, hemo)
)

robincar_mh(
  data, "arms", "y_bin",
  strata_cols=c("oprior", "hemo")
)
## Treatment group contrasts based on ATE
## Estimand: ATE
## Stratified by oprior, hemo
## SE calculated via modified Greenland's estimator
## 
## Contrasts:
## # A tibble: 1 × 4
##   contrast    estimate     se `pval (2-sided)`
##   <chr[1d]>      <dbl>  <dbl>            <dbl>
## 1 treat 1 - 0    0.134 0.0193         3.83e-12

Survival Analysis

For a survival outcome with right-censoring, we can use the RobinCar2 function robin_surv to do a covariate-adjusted, stratified log-rank test.

surv <- robin_surv(
  Surv(days, cens) ~ wtkg + oprior + hemo + strat,
  treatment = arms ~ pb(strat),
  data = data
)
surv
## Model        : Surv(days, cens) ~ wtkg + oprior + hemo + strat
## Randomization: arms ~ pb(strat) (Permuted-Block)
## Covariates adjusted for: wtkg, oprior, hemo, strat (including interactions with arms)
## 
## Contrast     : Covariate-adjusted Log Hazard Ratio
## 
##          Estimate  Std.Err Z Value Pr(>|z|)    
## 1 v.s. 0 -0.68785  0.12237 -5.6209  1.9e-08 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Test         : Covariate-adjusted Log-Rank
## 
##          Test Stat.  Pr(>|z|)    
## 1 v.s. 0    -5.7444 9.223e-09 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

You can use the table function to see the number of events and number at-risk

table(surv)
## Number of patients and events per treatment arm:
##   arms Patients Events
## 1    0      532    181
## 2    1      522    103

Case Study 2: RCT of Indomethacin for Post-ERCP Pancreatitis

Data Manipulation

This case study uses a different dataset, medicaldata::indo_rct, which was a randomized trial of rectal indomethacin to prevent post-ERCP pancreatitis (Elmunzer, Higgins, et al. 2012). This study used simple randomization stratified by study site (this is not the same as stratified permuted block randomization in the first example).

## Loading required package: medicaldata
data2 <- medicaldata::indo_rct %>%
  mutate(
    outcome2=as.numeric(outcome)-1
  ) %>%
  select(
    -c(outcome, status, type, bleed)
  )

First fit a really simple logistic regression working model only adjusting for site, and a risk difference as our contrast of interest.

robin_glm(
  outcome2 ~ rx * site, 
  treatment = rx ~ sr(1), 
  data=data2,
  family=binomial(link="logit"),
  contrast = "difference"
)
## Model        :  outcome2 ~ rx * site 
## Randomization:  rx ~ sr(1)  ( Simple )
## Variance Type:  vcovG 
## Marginal Mean: 
##                Estimate  Std.Err    2.5 % 97.5 %
## 0_placebo      0.167499 0.021227 0.125894 0.2091
## 1_indomethacin 0.092527 0.016766 0.059666 0.1254
## 
## Contrast     :  difference
##                                Estimate   Std.Err Z Value Pr(>|z|)   
## 1_indomethacin v.s. 0_placebo -0.074971  0.026910  -2.786 0.005337 **
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

There are 27 baseline covariates included in this dataset and 602 patients. If we include all of the covariates in a logistic regression working model, the model fails to converge and returns NA.

# Create formula with all baseline covariates included in dataset
covariates <- setdiff(colnames(data2), c("id", "outcome2", "rx"))
formula <- as.formula(
  sprintf("outcome2 ~ rx * (%s)", paste0(covariates, collapse=" + "))
)
robin_glm(
  as.formula(formula), 
  treatment = rx ~ sr(1), 
  data=data2,
  family=binomial(link="logit"),
  contrast = "difference"
)
## Model        :  outcome2 ~ rx * (site + age + risk + gender + sod + pep + recpanc +      psphinc + precut + difcan + pneudil + amp + paninj + acinar +      brush + asa81 + asa325 + asa + prophystent + therastent +      pdstent + sodsom + bsphinc + bstent + chole + pbmal + train) 
## Randomization:  rx ~ sr(1)  ( Simple )
## Variance Type:  vcovG 
## Marginal Mean: 
##                Estimate Std.Err 2.5 % 97.5 %
## 0_placebo            NA      NA    NA     NA
## 1_indomethacin       NA      NA    NA     NA
## 
## Contrast     :  difference
##                               Estimate Std.Err Z Value Pr(>|z|)
## 1_indomethacin v.s. 0_placebo       NA      NA      NA       NA

If you do want to include all of the baseline covariates because you aren’t sure which ones are the most prognostic, this is a use-case for the SuperLearner approach. We’ll test an ensemble of random forest (SL.ranger), elastic net regression (Sl.glmnet), and XGboost (SL.xgboost). SuperLearner will figure out the optimal combination of these approaches. See this link for documentation on the SuperLearner.

RobinCar includes SuperLearner working models with cross-fitting to ensure we aren’t over-fitting to the data. We will use 5 folds for cross-fitting. Since cross-fitting is a random process, we’ll also set a seed for reproducibility.

set.seed(1989)

sl <- robincar_SL(
  df=data2,
  response_col="outcome2",
  treat_col="rx",
  covariate_cols=covariates,
  SL_libraries=c("SL.xgboost", "SL.glmnet", "SL.ranger"),
  car_scheme="simple",
  k_split=5
)
## Done!
## Warning in func(): Prediction unbiasedness does not hold.
# Make a linear contrast
contrast <- robincar_contrast(sl, contrast_h="diff")
contrast
## Treatment group contrasts using linear contrast
## 
## Contrasts:
## # A tibble: 1 × 4
##   contrast                         estimate     se `pval (2-sided)`
##   <chr>                               <dbl>  <dbl>            <dbl>
## 1 treat 1_indomethacin - 0_placebo  -0.0812 0.0269          0.00256
## 
## Variance-Covariance Matrix for Contrasts:
##              [,1]
## [1,] 0.0007245833