Pretransplant natural antibody levels identify a subset of deceased donor kidney transplant recipients that benefit from infliximab induction

Statistical report


Authors and affiliations

Vojtech Petr1,2, Filip Tichanek1, Samuel L. Liu3, Felix Poppelaars2, Brandon Renner2, Jennifer Laskowski2, Shrey Purohit2, Ming Zhao3, Diana Jalal4,5, Peter S. Heeger6,#, Joshua M. Thurman2,#


1 Institute for Clinical and Experimental Medicine, Prague, Czech Republic
2 Department of Medicine, University of Colorado School of Medicine, Anschutz Medical Campus, Aurora, CO, USA
3 Feinberg Cardiovascular and Renal Research Institute, Northwestern University, Chicago, IL, USA
4 Department of Medicine, Carver College of Medicine, University of Iowa, IA, USA
5 Iowa City VA HCS, Iowa City, IA, USA
6 Departments of Medicine, Surgery and Biomedical Sciences, Cedars Sinai Medical Center, Los Angeles, CA, USA
# Co-senior authors, contributed equally


This is a statistical report of the study titled Pretransplant natural antibody levels identify a subset of deceased donor kidney transplant recipients that benefit from infliximab induction that has been published in the American Journal of Transplantation[1].

When using this code or data, cite the original publication:

V. Petr, F. Tichanek, S.L. Liu, F. Poppelaars, B. Renner, J. Laskowski, S. Purohit, M. Zhao, D. Jalal, P.S. Heeger, J.M. Thurman, Pretransplant natural antibody levels identify a subset of deceased donor kidney transplant recipients that benefit from infliximab induction, American Journal of Transplantation (2025). https://doi.org/10.1016/j.ajt.2025.06.003.

BibTeX entry is provided in a CITATION.bib file


Original GitHub repository: https://github.com/filip-tichanek/infliximab_nAb



1 Statistical modelling description

1.1 Full version

Analyses were performed in R (version 4.4.3) [2] and GraphPad Prism (version 10.2.3). Continuous variables are reported as medians with interquartile ranges and categorical variables as counts and percentages. Group comparisons were conducted using the Mann–Whitney test, and correlations were assessed via Spearman’s rank correlation.

Unadjusted effects of covariates (including nAb and their interaction with infliximab) were estimated using complete-case logistic regression for binary outcomes (DGF, infection, and BKV infection) and linear models for eGFR.

For outcome–nAb combinations where the 95% confidence interval of the interaction term did not include the null effect, we fit multivariable Bayesian models with a non-linear interaction between log\(_2\)-transformed nAb levels and infliximab using penalized B-splines with 5 knots (separately for each treatment group). Logistic Bayesian regression was used for binary outcomes, while Bayesian robust regression (Student’s t-distribution) was used for eGFR. Covariates were prespecified based on their well-documented association with kidney transplant outcomes and included donor and recipient sex, HLA mismatch count, KDPI, perfusion event, recipient age, and cold ischemia time.

Missing data (35 for HLA mismatch, 21 for perfusion event, 3 for cold ischemia time) were handled via multiple imputation (80 datasets) using the ‘mice’ package [3]. Binary variables were imputed via logistic regression, and numeric variables via predictive mean matching. Bayesian models were run using the ‘brms’ package [4] with Hamiltonian Monte Carlo sampling (4 chains, 2000 iterations including 1900 warm-ups per each of 80 imputed datasets, yielding 32,000 post-warmup samples). Posterior distributions were summarized by the median (point estimate) and the 2.5th and 97.5th percentiles (95% credible interval, 95% CrI).

Priors for fixed effects were Gaussian, centered at 0 (\(\mu = 0\)), with \(\sigma\) defined as follows:

In logistic model (binary outcomes):

\(\sigma = 4\) for binary predictors

\(\sigma = \frac{2}{\text{SD}(\text{predictor})}\) for numeric predictors

For robust regression (eGFR):

\(\sigma = 4 \times \text{SD}(\text{eGFR})\) for binary predictors

\(\sigma = \frac{2 \times \text{SD}(\text{eGFR})}{\text{SD}(\text{predictor})}\) for numeric predictors

Non-linear effects used Student’s t priors:

Baseline spline: \(df = 3, \mu = 0, \sigma = 0.6\)

Treatment-specific spline: \(df = 3, \mu = 0, \sigma = 0.3\)

Posterior estimates for infliximab effects were obtained at the 5th, 25th, 50th, 75th, and 95th percentiles of nAb levels. We quantified the modulation of infliximab treatment by comparing the posterior distributions of the treatment effect at the 5th and 95th percentiles of nAb levels. For each posterior sample, we computed the ratio of odds ratios (OR at 95th percentile / OR at 5th percentile) in logistic models and the difference in treatment effects (\(\beta_{\text{infliximab}}|\text{nAbs}=95^\text{th}\)\(\beta_{\text{infliximab}}|\text{nAbs}=5^\text{th}\)) in robust regression for eGFR.

Mediation analysis was performed to assess whether infliximab affected eGFR and whether this effect was mediated through DGF. Analyses were conducted separately for individuals with below- and above-median aCL IgG levels.

Because the highest aCL IgG levels were only observed in the control group, we performed a sensitivity analysis by restricting the dataset to patients with aCL IgG levels within the range observed in the infliximab group.

Another sensitivitu

1.2 Short version (manuscript)

All analyses were conducted in R (version 4.4.3). Continuous variables are reported as medians with interquartile ranges, and categorical variables as percentages. Spearman’s rank correlation assessed associations.

Unadjusted analyses used logistic regression for binary outcomes (DGF, infection, and BKV infection) and linear models for eGFR, both based on complete cases. When the 95% confidence interval of the interaction term between nAb and infliximab did not include the null effect, multivariable Bayesian models with non-linear interactions (using group-specific B splines) were fitted to using the ‘brms’ R package. These models employed logistic regression for binary outcomes and robust (Student’s t) regression for eGFR, with multiple imputation (80 datasets) for missing data.

Pre-specified covariates included donor and recipient sex, HLA mismatch count, KDPI, perfusion event, recipient age, and cold ischemia time. Posterior distributions were summarized by medians with 95% credible intervals (CrI). To evaluate infliximab treatment effects across different nAb levels, we obtained posterior distributions at the 5th, 25th, 50th, 75th, and 95th percentiles of these levels. To quantify how strongly nAb levels modulate the infliximab effect, we compared infliximab estimates at the 95th and 5th percentiles, reporting the ratio of odds ratios for binary outcomes and the difference in treatment effects for eGFR.

Bayesian mediation analysis was used to assess whether infliximab’s effect on eGFR is mediated through DGF, stratified by above- vs. below-median aCL IgG levels. A sensitivity analysis was performed by restricting the dataset to patients with aCL IgG levels within the range observed in the infliximab group.

A full description of the statistical modeling methods, including prior specifications and posterior sampling details, is provided in the Supplementary Methods and online with the code (https://filip-tichanek.github.io/infliximab_nAb).

2 Initiation

Packages

Open code
if (TRUE) {rm(list = ls() )}
if (TRUE) { 
  suppressWarnings(suppressMessages({
    library(tidyverse)
    library(stringr)
    library(stringi)
    library(ggpubr)
    library(emmeans)
    library(gtsummary)
    library(skimr)
    library(car)
    library(RJDBC)
    library(sjPlot)
    library(flextable)
    library(openxlsx)
    library(mgcv)
    library(pROC)
    library(cowplot)
    library(boot)
    library(glmnet)
    library(brms)
    library(projpred)
    library(janitor)
    library(arm)
    library(corrplot)
    library(lubridate)
    library(kableExtra)    
    library(ggdist)
    library(bayesplot)
    library(coxed)
    library(quantreg)
    library(ggbeeswarm)
    library(mgcv)
    library(mice)
    library(MASS)
    library(bayestestR)
     
    # Functions clashes
    select <- dplyr::select
    rename <- dplyr::rename
    mutate <- dplyr::mutate
    recode <- dplyr::recode
    summarize <- dplyr::summarize
    count <- dplyr::count
    
    # Simple math functions
    logit <-function(x){log(x/(1-x))}
    inv_logit <- function(x){exp(x)/(1+exp(x))}
  }))
}

Functions

Open code
run <- function(expr, path, reuse = TRUE) {
  fit <- NULL
  if (reuse) {
    path <- paste0(path, ".Rds")
    fit <- suppressWarnings(try(readRDS(path), silent = TRUE))
    if (inherits(fit, "try-error")) {
      fit <- NULL
    }
  }
  if (is.null(fit)) {
    fit <- eval(substitute(expr))
    if (reuse && !is.null(path) && nzchar(path)) {
      saveRDS(fit, file = path)
    }
  }
  return(fit)
}

Setting seeds and scipen

Open code
set.seed(2025)
options(scipen = 999)

3 Data

Upload data a create relevant variables. Alternative data are the same, but also contains newly-calculated eGFR (based not only on the central but also local measurements) used for some sensitivity analyses

Open code
data3 <- read.xlsx(
  "gitignore/data/dataset_updated_4_2024_ver2.xlsx")

data3 <- data3 %>%
  mutate(
    GPE_dep_log2 = log2(GPE_dep),
    MPE_dep_log2 = log2(MPE_dep),
    GPE_ind_log2 = log2(GPE_ind),
    MPE_ind_log2 = log2(MPE_ind),
    graft_failure = if_else(!is.na(later) & later == 'graft_loss', 1, 0)
  ) %>%
  mutate(
    GPL_BC_log2 = log2(GPL_BC),
    MPL_BC_log2 = log2(MPL_BC),
    GFR_MDRD_3 =if_else(graft_failure == 1, 10, GFR_MDRD_2)
  ) %>%
  mutate(CIT = CIT / 60) %>%
  mutate(treatment_group = if_else(
    infliximab == 1, "infiximab", "placebo"
  ))

There are two variables that are expected to affect the outcomes but include many missing values. Look which variables are correlated with these variables (perfusion_event and HLA_MM) so these may be helpful for the imputation.

Open code
safe_cor <- function(x, y) {
  if (sd(x, na.rm = TRUE) == 0 || sd(y, na.rm = TRUE) == 0) {
    NA_real_
  } else {
    cor(x, y, use = "pairwise.complete.obs", method = 'pearson')
  }
}

df <- data3 %>%
  select(where(is.numeric))

res <- tibble(
  variable       = names(df),
  cor_perfusion  = map_dbl(variable, ~ safe_cor(df[[.x]], df$perfusion_event)),
  cor_hla        = map_dbl(variable, ~ safe_cor(df[[.x]], df$HLA_MM)),
  n_na           = map_int(variable, ~ sum(is.na(df[[.x]])))
) %>%
  mutate(max_abs_cor = pmax(abs(cor_perfusion), abs(cor_hla), na.rm = TRUE)) %>%
  arrange(desc(max_abs_cor))
## Warning in cor(x, y, use = "pairwise.complete.obs", method = "pearson"): the
## standard deviation is zero
## Warning in cor(x, y, use = "pairwise.complete.obs", method = "pearson"): the
## standard deviation is zero
## Warning in cor(x, y, use = "pairwise.complete.obs", method = "pearson"): the
## standard deviation is zero

kableExtra::kable(res %>% filter(n_na<10, max_abs_cor>0.1))
variable cor_perfusion cor_hla n_na max_abs_cor
HLAMMGE3 0.1521796 0.7388502 0 0.7388502
BKV_viremia_time 0.2740103 -0.0162880 0 0.2740103
GFR_CKD -0.1605158 -0.2709311 2 0.2709311
race_2 0.2353864 -0.1944819 0 0.2353864
GFR_MDRD -0.1685550 -0.2334869 2 0.2334869
GPE_ind 0.2170082 0.0316690 1 0.2170082
race_donor_2 0.2131970 -0.0264465 0 0.2131970
GFR_MDRD_2 -0.1706397 -0.2099583 2 0.2099583
age_donor 0.1486748 0.1994518 1 0.1994518
GFR_MDRD_3 -0.1658982 -0.1955132 2 0.1955132
CIT 0.1903365 -0.1393050 3 0.1903365
CMV_IgG_rec -0.0818317 0.1860303 0 0.1860303
de_novo_DSA 0.0807407 0.1769103 2 0.1769103
KDPI 0.0744357 0.1750746 0 0.1750746
donor_type -0.1691540 0.0328450 0 0.1691540
GPE_ind_log2 0.1687633 0.0447728 1 0.1687633
ID_subject 0.1620089 -0.1226652 0 0.1620089
MPE_dep -0.1584339 0.0166590 1 0.1584339
MPL_BC -0.1555197 -0.0622218 0 0.1555197
MPL_BC_log2 -0.1518460 -0.0367600 0 0.1518460
GPL_BC_log2 -0.1480250 -0.1102327 0 0.1480250
RRT_preTx -0.0024901 -0.1451204 0 0.1451204
MPE_dep_log2 -0.1448089 0.0124194 1 0.1448089
GPL_BC -0.1132365 -0.1437448 0 0.1437448
CMV_IgG_donor 0.0710178 0.1437226 1 0.1437226
GPL -0.1130782 -0.1432358 0 0.1432358
male_sex 0.1431312 0.0824554 0 0.1431312
MPL -0.1421092 -0.0587360 0 0.1421092
WIT 0.1409576 -0.0638921 7 0.1409576
DGF -0.0424969 0.1340953 0 0.1340953
male_sex_donor -0.0485702 -0.1333746 0 0.1333746
dc_graftloss_event_24mo -0.1301364 -0.0487302 0 0.1301364
infliximab 0.0591993 0.1247757 0 0.1247757
MPE_ind -0.1246244 -0.0598818 1 0.1246244
GFR_days 0.1222818 0.0609560 2 0.1222818
rec_age -0.0243711 0.1218259 0 0.1218259
preformed_DSA -0.1142633 -0.0309393 2 0.1142633
MPE_ind_log2 -0.1014825 -0.0061797 1 0.1014825

3.1 Summary tables

Who will be deleted?

Open code
data3 %>% 
  filter(is.na(GFR_MDRD) |
         is.na(GPE_dep) |
         is.na(MPE_dep) 
         ) %>%  select(ID_subject, terminated_days, GFR_MDRD_2, GPE_ind)
##   ID_subject terminated_days GFR_MDRD_2 GPE_ind
## 1       3027               6         NA  0.0790
## 2      23007               0         NA  0.0975
## 3      57019              NA      54.28      NA

data3 <- data3 %>% 
  filter(!is.na(GFR_MDRD),
         !is.na(GPE_dep),
         !is.na(MPE_dep) 
         )

dim(data3)
## [1] 177 113

3.1.1 Demography

Open code
data3 %>% 
  filter(!is.na(GFR_MDRD_2),
         !is.na(GPE_dep),
         !is.na(MPE_dep)) %>% 
  select(male_sex, rec_age, race_2, race_1, ethnicity, treatment_group, CKD_cause_2, RRT_preTx, RRT_months, RRT_type, male_sex_donor, age_donor, race_donor_2, race_donor_1, donor_cause_narrow, donor_cause_anoxia, donor_cause_CVA, donor_type, ATG, standard_triple, HLA_MM, KDPI, WIT, CIT, perfusion_event, perfusion_time, preformed_DSA) %>% 
  tbl_summary(by = treatment_group, type = list(HLA_MM ~ "continuous")) %>% 
  add_p() %>% 
  add_overall()
## The following warnings were returned during `add_p()`:
## ! For variable `perfusion_time` (`treatment_group`) and "estimate",
##   "statistic", "p.value", "conf.low", and "conf.high" statistics: cannot
##   compute exact p-value with ties
## ! For variable `perfusion_time` (`treatment_group`) and "estimate",
##   "statistic", "p.value", "conf.low", and "conf.high" statistics: cannot
##   compute exact confidence intervals with ties
Table 1: Demography stratified by treatment

Characteristic

Overall
N = 177

1

infiximab
N = 87

1

placebo
N = 90

1

p-value

2
male_sex 109 (62%) 59 (68%) 50 (56%) 0.094
rec_age 54 (45, 61) 54 (46, 61) 55 (45, 62) >0.9
race_2 72 (41%) 35 (40%) 37 (41%) >0.9
race_1


0.9
    American Indian or Alaska Native 1 (0.6%) 1 (1.1%) 0 (0%)
    Asian 9 (5.1%) 5 (5.7%) 4 (4.4%)
    Black or African American 73 (41%) 34 (39%) 39 (43%)
    Native Hawaiian or Other Pacific Islander 3 (1.7%) 1 (1.1%) 2 (2.2%)
    Unknown or Not Reported 19 (11%) 11 (13%) 8 (8.9%)
    White 72 (41%) 35 (40%) 37 (41%)
ethnicity


0.2
    Hispanic or Latino 25 (14%) 15 (17%) 10 (11%)
    Not Hispanic or Latino 152 (86%) 72 (83%) 80 (89%)
CKD_cause_2


0.3
    1 44 (25%) 23 (26%) 21 (23%)
    2 42 (24%) 22 (25%) 20 (22%)
    3 58 (33%) 27 (31%) 31 (34%)
    4 18 (10%) 11 (13%) 7 (7.8%)
    5 15 (8.5%) 4 (4.6%) 11 (12%)
RRT_preTx 162 (92%) 80 (92%) 82 (91%) 0.8
RRT_months 62 (36, 95) 63 (37, 92) 62 (36, 96) >0.9
    Unknown 15 7 8
RRT_type 132 (81%) 64 (80%) 68 (83%) 0.6
    Unknown 15 7 8
male_sex_donor 98 (55%) 43 (49%) 55 (61%) 0.12
age_donor 43 (32, 50) 44 (32, 50) 42 (30, 50) 0.5
    Unknown 1 1 0
race_donor_2 109 (62%) 59 (68%) 50 (56%) 0.094
race_donor_1


0.033
    American Indian or Alaska Native 2 (1.1%) 0 (0%) 2 (2.2%)
    Asian 9 (5.1%) 2 (2.3%) 7 (7.8%)
    Black or African American 30 (17%) 10 (11%) 20 (22%)
    Native Hawaiian or Other Pacific Islander 1 (0.6%) 0 (0%) 1 (1.1%)
    Unknown or Not Reported 26 (15%) 16 (18%) 10 (11%)
    White 109 (62%) 59 (68%) 50 (56%)
donor_cause_narrow


>0.9
    Anoxia 82 (46%) 38 (44%) 44 (49%)
    Cardiovascular 5 (2.8%) 2 (2.3%) 3 (3.3%)
    Cerebrovascular Accident 42 (24%) 22 (25%) 20 (22%)
    CNS-Other 1 (0.6%) 0 (0%) 1 (1.1%)
    Drug Overdose 2 (1.1%) 1 (1.1%) 1 (1.1%)
    Head Trauma 38 (21%) 21 (24%) 17 (19%)
    Infection 2 (1.1%) 1 (1.1%) 1 (1.1%)
    Other 2 (1.1%) 1 (1.1%) 1 (1.1%)
    Respiratory Failure 3 (1.7%) 1 (1.1%) 2 (2.2%)
donor_cause_anoxia 82 (46%) 38 (44%) 44 (49%) 0.5
donor_cause_CVA 42 (24%) 22 (25%) 20 (22%) 0.6
donor_type 133 (75%) 64 (74%) 69 (77%) 0.6
ATG 176 (99%) 87 (100%) 89 (99%) >0.9
standard_triple 158 (89%) 76 (87%) 82 (91%) 0.4
HLA_MM 5.00 (4.00, 5.00) 5.00 (4.00, 6.00) 4.00 (3.50, 5.00) 0.080
    Unknown 35 17 18
KDPI 53 (37, 71) 53 (32, 71) 51 (39, 74) 0.4
WIT 33 (27, 42) 33 (27, 42) 33 (27, 40) 0.9
    Unknown 6 3 3
CIT 15 (10, 21) 16 (10, 21) 15 (10, 20) 0.8
    Unknown 3 3 0
perfusion_event 63 (40%) 34 (44%) 29 (37%) 0.4
    Unknown 21 9 12
perfusion_time 11.0 (8.0, 15.0) 12.0 (8.0, 15.0) 11.0 (6.0, 16.0) 0.5
    Unknown 144 68 76
preformed_DSA 5 (2.8%) 2 (2.3%) 3 (3.3%) >0.9
1

n (%); Median (Q1, Q3)

2

Pearson’s Chi-squared test; Wilcoxon rank sum test; Fisher’s exact test

3.1.2 Demography stratified treatment and aCL IgG groups

Open code
sum_data <- data3 %>%
  filter(!is.na(GFR_MDRD_2),
         !is.na(GPE_dep),
         !is.na(MPE_dep)) %>% 
  dplyr::select(
    DGF, infection_any, GFR_MDRD, GFR_MDRD_2, GFR_MDRD_3, BKV_event_2,
    
    GPL_BC,
    MPL_BC,
    GPE_dep,
    GPE_ind,
    MPE_dep,
    MPE_ind,
    
    male_sex,
    HLA_MM,
    male_sex_donor,
    KDPI,
    perfusion_event,
    CIT,
    infliximab,
    rec_age,
    race_2, race_1, ethnicity, CKD_cause_2, RRT_preTx, RRT_months, RRT_type, age_donor, 
    race_donor_2, race_donor_1, donor_cause_narrow, donor_cause_anoxia, donor_cause_CVA, 
    donor_type, ATG, standard_triple, WIT, perfusion_event, perfusion_time, preformed_DSA) %>% 
  
  mutate(aCL_IgG_category = if_else(GPL_BC < median(data3$GPL_BC, na.rm =TRUE),
                                    'low aCL IgG', 'high aCL'
                                    ),
         treatment = if_else(infliximab == 1, 'IFX', 'PCO')
         ) %>% 
  mutate(category = interaction(treatment, aCL_IgG_category)) 

sum_data %>% select(-c(infliximab, aCL_IgG_category, GPL_BC:MPE_ind)) %>% 
  gtsummary::tbl_summary(by = category,
                         type = list(HLA_MM ~ "continuous")) %>% 
  add_p() %>% 
  add_overall()
## The following errors were returned during `add_p()`:
## ✖ For variable `CKD_cause_2` (`category`) and "estimate", "p.value",
##   "conf.low", and "conf.high" statistics: FEXACT error 7(location). LDSTP=18630
##   is too small for this problem, (pastp=88.6719, ipn_0:=ipoin[itp=242]=2837,
##   stp[ipn_0]=84.7176). Increase workspace or consider using
##   'simulate.p.value=TRUE'
## ✖ For variable `donor_cause_narrow` (`category`) and "estimate", "p.value",
##   "conf.low", and "conf.high" statistics: FEXACT error 6.  LDKEY=621 is too
##   small for this problem, (ii := key2[itp=427] = 3378662, ldstp=18630) Try
##   increasing the size of the workspace and possibly 'mult'
## ✖ For variable `race_donor_1` (`category`) and "estimate", "p.value",
##   "conf.low", and "conf.high" statistics: FEXACT error 7(location). LDSTP=18630
##   is too small for this problem, (pastp=38.0864, ipn_0:=ipoin[itp=606]=5057,
##   stp[ipn_0]=37.1156). Increase workspace or consider using
##   'simulate.p.value=TRUE'
Table 2: Demography stratified by treatment and aCL IgG level (below and at/above median value

Characteristic

Overall
N = 177

1

IFX.high aCL
N = 41

1

PCO.high aCL
N = 48

1

IFX.low aCL IgG
N = 46

1

PCO.low aCL IgG
N = 42

1

p-value

2
DGF 60 (34%) 19 (46%) 14 (29%) 9 (20%) 18 (43%) 0.029
infection_any 75 (42%) 17 (41%) 17 (35%) 21 (46%) 20 (48%) 0.7
GFR_MDRD 50 (39, 65) 45 (35, 51) 58 (44, 69) 56 (40, 68) 47 (35, 58) 0.020
GFR_MDRD_2 50 (38, 65) 46 (38, 54) 56 (42, 67) 56 (39, 70) 44 (35, 62) 0.056
GFR_MDRD_3 50 (38, 65) 46 (38, 54) 56 (42, 67) 56 (39, 70) 44 (35, 62) 0.057
BKV_event_2 38 (21%) 12 (29%) 5 (10%) 13 (28%) 8 (19%) 0.10
male_sex 109 (62%) 26 (63%) 26 (54%) 33 (72%) 24 (57%) 0.3
HLA_MM 5.00 (4.00, 5.00) 5.00 (4.00, 5.00) 4.00 (4.00, 5.00) 5.00 (4.00, 6.00) 4.00 (3.00, 6.00) 0.14
    Unknown 35 8 11 9 7
male_sex_donor 98 (55%) 19 (46%) 30 (63%) 24 (52%) 25 (60%) 0.4
KDPI 53 (37, 71) 49 (29, 66) 51 (37, 74) 54 (35, 72) 51 (40, 74) 0.6
perfusion_event 63 (40%) 15 (41%) 14 (31%) 19 (46%) 15 (45%) 0.5
    Unknown 21 4 3 5 9
CIT 15 (10, 21) 17 (10, 21) 16 (10, 22) 14 (10, 20) 14 (10, 20) 0.7
    Unknown 3 1 0 2 0
rec_age 54 (45, 61) 53 (45, 60) 55 (45, 62) 56 (48, 63) 54 (45, 62) 0.8
race_2 72 (41%) 16 (39%) 18 (38%) 19 (41%) 19 (45%) 0.9
race_1




0.8
    American Indian or Alaska Native 1 (0.6%) 0 (0%) 0 (0%) 1 (2.2%) 0 (0%)
    Asian 9 (5.1%) 1 (2.4%) 3 (6.3%) 4 (8.7%) 1 (2.4%)
    Black or African American 73 (41%) 18 (44%) 22 (46%) 16 (35%) 17 (40%)
    Native Hawaiian or Other Pacific Islander 3 (1.7%) 1 (2.4%) 0 (0%) 0 (0%) 2 (4.8%)
    Unknown or Not Reported 19 (11%) 5 (12%) 5 (10%) 6 (13%) 3 (7.1%)
    White 72 (41%) 16 (39%) 18 (38%) 19 (41%) 19 (45%)
ethnicity




0.4
    Hispanic or Latino 25 (14%) 8 (20%) 7 (15%) 7 (15%) 3 (7.1%)
    Not Hispanic or Latino 152 (86%) 33 (80%) 41 (85%) 39 (85%) 39 (93%)
CKD_cause_2





    1 44 (25%) 10 (24%) 11 (23%) 13 (28%) 10 (24%)
    2 42 (24%) 9 (22%) 9 (19%) 13 (28%) 11 (26%)
    3 58 (33%) 13 (32%) 16 (33%) 14 (30%) 15 (36%)
    4 18 (10%) 5 (12%) 5 (10%) 6 (13%) 2 (4.8%)
    5 15 (8.5%) 4 (9.8%) 7 (15%) 0 (0%) 4 (9.5%)
RRT_preTx 162 (92%) 41 (100%) 40 (83%) 39 (85%) 42 (100%) <0.001
RRT_months 62 (36, 95) 73 (49, 105) 60 (39, 80) 51 (36, 81) 69 (34, 115) 0.3
    Unknown 15 0 8 7 0
RRT_type 132 (81%) 37 (90%) 34 (85%) 27 (69%) 34 (81%) 0.10
    Unknown 15 0 8 7 0
age_donor 43 (32, 50) 38 (32, 48) 42 (25, 50) 45 (34, 50) 42 (33, 52) 0.6
    Unknown 1 0 0 1 0
race_donor_2 109 (62%) 28 (68%) 27 (56%) 31 (67%) 23 (55%) 0.4
race_donor_1





    American Indian or Alaska Native 2 (1.1%) 0 (0%) 1 (2.1%) 0 (0%) 1 (2.4%)
    Asian 9 (5.1%) 1 (2.4%) 3 (6.3%) 1 (2.2%) 4 (9.5%)
    Black or African American 30 (17%) 3 (7.3%) 10 (21%) 7 (15%) 10 (24%)
    Native Hawaiian or Other Pacific Islander 1 (0.6%) 0 (0%) 0 (0%) 0 (0%) 1 (2.4%)
    Unknown or Not Reported 26 (15%) 9 (22%) 7 (15%) 7 (15%) 3 (7.1%)
    White 109 (62%) 28 (68%) 27 (56%) 31 (67%) 23 (55%)
donor_cause_narrow





    Anoxia 82 (46%) 18 (44%) 24 (50%) 20 (43%) 20 (48%)
    Cardiovascular 5 (2.8%) 1 (2.4%) 2 (4.2%) 1 (2.2%) 1 (2.4%)
    Cerebrovascular Accident 42 (24%) 7 (17%) 10 (21%) 15 (33%) 10 (24%)
    CNS-Other 1 (0.6%) 0 (0%) 0 (0%) 0 (0%) 1 (2.4%)
    Drug Overdose 2 (1.1%) 0 (0%) 0 (0%) 1 (2.2%) 1 (2.4%)
    Head Trauma 38 (21%) 14 (34%) 9 (19%) 7 (15%) 8 (19%)
    Infection 2 (1.1%) 1 (2.4%) 1 (2.1%) 0 (0%) 0 (0%)
    Other 2 (1.1%) 0 (0%) 1 (2.1%) 1 (2.2%) 0 (0%)
    Respiratory Failure 3 (1.7%) 0 (0%) 1 (2.1%) 1 (2.2%) 1 (2.4%)
donor_cause_anoxia 82 (46%) 18 (44%) 24 (50%) 20 (43%) 20 (48%) >0.9
donor_cause_CVA 42 (24%) 7 (17%) 10 (21%) 15 (33%) 10 (24%) 0.4
donor_type 133 (75%) 27 (66%) 36 (75%) 37 (80%) 33 (79%) 0.4
ATG 176 (99%) 41 (100%) 48 (100%) 46 (100%) 41 (98%) 0.5
standard_triple 158 (89%) 34 (83%) 44 (92%) 42 (91%) 38 (90%) 0.6
WIT 33 (27, 42) 32 (25, 42) 32 (26, 38) 35 (28, 43) 37 (29, 47) 0.2
    Unknown 6 1 1 2 2
perfusion_time 11.0 (8.0, 15.0) 14.0 (12.0, 16.0) 16.0 (11.0, 16.0) 11.0 (5.0, 14.0) 8.0 (5.0, 11.0) 0.030
    Unknown 144 32 41 36 35
preformed_DSA 5 (2.8%) 2 (4.9%) 2 (4.2%) 0 (0%) 1 (2.4%) 0.5
treatment




<0.001
    IFX 87 (49%) 41 (100%) 0 (0%) 46 (100%) 0 (0%)
    PCO 90 (51%) 0 (0%) 48 (100%) 0 (0%) 42 (100%)
1

n (%); Median (Q1, Q3)

2

Pearson’s Chi-squared test; Kruskal-Wallis rank sum test; Fisher’s exact test

3.1.3 Outcomes by treatment arm

Open code
sum_data %>% select(treatment, DGF:BKV_event_2) %>% 
  gtsummary::tbl_summary(by = treatment) %>% 
  add_p() %>% 
  add_overall()
Table 3: Post-transplant outcomes by treatment

Characteristic

Overall
N = 177

1

IFX
N = 87

1

PCO
N = 90

1

p-value

2
DGF 60 (34%) 28 (32%) 32 (36%) 0.6
infection_any 75 (42%) 38 (44%) 37 (41%) 0.7
GFR_MDRD 50 (39, 65) 50 (39, 62) 51 (39, 66) 0.3
GFR_MDRD_2 50 (38, 65) 50 (39, 65) 51 (38, 66) 0.5
GFR_MDRD_3 50 (38, 65) 50 (39, 65) 51 (38, 66) 0.6
BKV_event_2 38 (21%) 25 (29%) 13 (14%) 0.021
1

n (%); Median (Q1, Q3)

2

Pearson’s Chi-squared test; Wilcoxon rank sum test

3.1.4 Outcomes by treatment arm and DGF

Open code
sum_data %>% select(treatment, DGF:BKV_event_2) %>% 
  mutate(group = paste0('DGF', DGF, ', ', treatment)) %>% 
  gtsummary::tbl_summary(by = group) %>% 
  add_p() %>% 
  add_overall()
Table 4: Post-transplant outcomes by treatment and DGF

Characteristic

Overall
N = 177

1

DGF0, IFX
N = 59

1

DGF0, PCO
N = 58

1

DGF1, IFX
N = 28

1

DGF1, PCO
N = 32

1

p-value

2
treatment




<0.001
    IFX 87 (49%) 59 (100%) 0 (0%) 28 (100%) 0 (0%)
    PCO 90 (51%) 0 (0%) 58 (100%) 0 (0%) 32 (100%)
DGF 60 (34%) 0 (0%) 0 (0%) 28 (100%) 32 (100%) <0.001
infection_any 75 (42%) 26 (44%) 22 (38%) 12 (43%) 15 (47%) 0.8
GFR_MDRD 50 (39, 65) 56 (41, 67) 58 (43, 74) 40 (27, 48) 44 (30, 51) <0.001
GFR_MDRD_2 50 (38, 65) 55 (41, 67) 58 (42, 71) 40 (28, 50) 42 (30, 52) <0.001
GFR_MDRD_3 50 (38, 65) 55 (41, 67) 58 (42, 71) 40 (28, 50) 42 (30, 52) <0.001
BKV_event_2 38 (21%) 16 (27%) 7 (12%) 9 (32%) 6 (19%) 0.10
1

n (%); Median (Q1, Q3)

2

Pearson’s Chi-squared test; Kruskal-Wallis rank sum test

3.1.5 Antigens

3.1.5.1 In general

Open code
data3 %>%
  filter(
    !is.na(GFR_MDRD_2),
    !is.na(GPE_dep_log2),
    !is.na(MPE_dep_log2)
  ) %>%
  select(
    treatment_group,
    GPL_BC, MPL_BC, MPE_ind, MPE_dep, GPE_ind, GPE_dep
  ) %>%
  tbl_summary(by = treatment_group) %>%
  add_p() %>%
  add_overall()

Characteristic

Overall
N = 177

1

infiximab
N = 87

1

placebo
N = 90

1

p-value

2
GPL_BC 0.93 (0.60, 1.80) 0.87 (0.56, 1.55) 0.96 (0.65, 2.08) 0.3
MPL_BC 4 (2, 8) 3 (2, 6) 5 (2, 10) 0.005
MPE_ind 0.11 (0.09, 0.15) 0.11 (0.09, 0.15) 0.12 (0.09, 0.15) 0.6
MPE_dep 0.18 (0.13, 0.25) 0.18 (0.12, 0.25) 0.19 (0.14, 0.26) 0.4
GPE_ind 0.11 (0.09, 0.15) 0.12 (0.10, 0.15) 0.11 (0.09, 0.15) 0.7
GPE_dep 0.19 (0.16, 0.25) 0.19 (0.15, 0.26) 0.19 (0.16, 0.24) 0.3
1

Median (Q1, Q3)

2

Wilcoxon rank sum test

3.1.5.2 Only these with DGF

Open code
data3 %>% 
  filter(!is.na(GFR_MDRD_3),
         !is.na(GPE_dep_log2),
         !is.na(MPE_dep_log2)) %>% 
  filter(DGF == 1) %>% 
  select(treatment_group, 
         GPL_BC, MPL_BC, MPE_ind, MPE_dep, GPE_ind, GPE_dep) %>% 
  
  tbl_summary(by = treatment_group) %>% 
  add_p() %>% 
  add_overall()
## The following warnings were returned during `add_p()`:
## ! For variable `GPE_dep` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   p-value with ties
## ! For variable `GPE_dep` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   confidence intervals with ties
## ! For variable `GPE_ind` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   p-value with ties
## ! For variable `GPE_ind` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   confidence intervals with ties
## ! For variable `MPE_dep` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   p-value with ties
## ! For variable `MPE_dep` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   confidence intervals with ties
## ! For variable `MPE_ind` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   p-value with ties
## ! For variable `MPE_ind` (`treatment_group`) and "estimate", "statistic",
##   "p.value", "conf.low", and "conf.high" statistics: cannot compute exact
##   confidence intervals with ties

Characteristic

Overall
N = 60

1

infiximab
N = 28

1

placebo
N = 32

1

p-value

2
GPL_BC 0.96 (0.59, 2.55) 1.15 (0.75, 3.32) 0.80 (0.57, 1.87) 0.2
MPL_BC 6 (3, 9) 6 (3, 8) 6 (3, 12) 0.8
MPE_ind 0.11 (0.09, 0.15) 0.11 (0.09, 0.17) 0.11 (0.09, 0.14) >0.9
MPE_dep 0.17 (0.13, 0.25) 0.22 (0.14, 0.26) 0.16 (0.12, 0.25) 0.4
GPE_ind 0.12 (0.09, 0.15) 0.13 (0.10, 0.15) 0.11 (0.09, 0.15) 0.3
GPE_dep 0.20 (0.15, 0.28) 0.20 (0.16, 0.27) 0.18 (0.13, 0.30) 0.5
1

Median (Q1, Q3)

2

Wilcoxon rank sum exact test; Wilcoxon rank sum test

3.1.5.3 Only these without DGF

Open code
data3 %>% 
  filter(!is.na(GFR_MDRD_2),
         !is.na(GPE_dep_log2),
         !is.na(MPE_dep_log2)) %>% 
  filter(DGF == 0) %>% 
  select(treatment_group, 
         GPL_BC, MPL_BC, MPE_ind, MPE_dep, GPE_ind, GPE_dep) %>% 
  tbl_summary(by = treatment_group) %>% 
  add_p() %>% 
  add_overall()

Characteristic

Overall
N = 117

1

infiximab
N = 59

1

placebo
N = 58

1

p-value

2
GPL_BC 0.89 (0.61, 1.66) 0.79 (0.53, 1.28) 1.09 (0.70, 2.09) 0.018
MPL_BC 3.5 (2.0, 6.6) 2.6 (1.8, 4.7) 4.6 (2.5, 9.1) <0.001
MPE_ind 0.11 (0.09, 0.15) 0.11 (0.09, 0.15) 0.12 (0.09, 0.16) 0.5
MPE_dep 0.19 (0.13, 0.25) 0.17 (0.12, 0.23) 0.20 (0.15, 0.26) 0.072
GPE_ind 0.11 (0.09, 0.15) 0.11 (0.09, 0.15) 0.11 (0.10, 0.17) 0.8
GPE_dep 0.19 (0.16, 0.25) 0.19 (0.15, 0.26) 0.19 (0.16, 0.22) 0.6
1

Median (Q1, Q3)

2

Wilcoxon rank sum test

3.2 Antigens data distributions

Open code

data_hist <- data3 %>% 
  filter(!is.na(GFR_MDRD_2),
         !is.na(GPE_dep_log2),
         !is.na(MPE_dep_log2)) %>% 
  mutate(infliximab = if_else(infliximab == 1, 'IFX', 'PCO'))


p1 <- data_hist %>% 
  ggplot(aes(x = GPL_BC)) +
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p2 <- data_hist %>%
  ggplot(aes(x = GPL_BC_log2))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p3 <- data_hist %>%
  ggplot(aes(x = MPL_BC))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p4 <- data_hist %>%
  ggplot(aes(x = MPL_BC_log2))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p5 <- data_hist %>%
  ggplot(aes(x = MPE_ind))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p6 <- data_hist %>%
  ggplot(aes(x = MPE_ind_log2))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p7 <- data_hist %>%
  ggplot(aes(x = MPE_dep))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p8 <- data_hist %>%
  ggplot(aes(x = MPE_dep_log2))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p9 <- data_hist %>%
  ggplot(aes(x = GPE_ind))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p10 <- data_hist %>%
  ggplot(aes(x = GPE_ind_log2))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p11 <- data_hist %>%
  ggplot(aes(x = GPE_dep))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

p12 <- data_hist %>%
  ggplot(aes(x = GPE_dep_log2))+
  geom_histogram() +
  facet_wrap(~ infliximab, nrow = 2)

plot_grid(p1,p2,p3,p4, p5, p6, p7, p8, p9, p10, p11, p12, nrow = 3)
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.
## `stat_bin()` using `bins = 30`. Pick better value with `binwidth`.

4 Data - models-relevant

4.1 Define the model-relevant data

Note that eGFR based on the central measurement (GFR_MDRD) will be used for imputation, as it shows stronger correlations with variables containing missing values than either the combined local and central eGFR (GFR_MDRD_2) or the combined version with values set to 10 in cases of graft failure within the 2-year follow-up (GFR_MDRD_3)

Open code
data_model_priorImpute <- data3 %>%
  dplyr::select(
    
    # outcomes 
    DGF, infection_any, GFR_MDRD, GFR_MDRD_2, GFR_MDRD_3, BKV_event_2,
    
    # antigens
    GPL_BC_log2,
    MPL_BC_log2,
    GPE_dep_log2,
    GPE_ind_log2,
    MPE_dep_log2,
    MPE_ind_log2,
    
    # other covariates
    male_sex,
    HLA_MM,
    male_sex_donor,
    KDPI,
    perfusion_event,
    CIT,
    infliximab,
    rec_age,
    
    # variables for imputation only
    HLAMMGE3, 
    BKV_viremia_time,
    race_2,
    race_donor_2,
    age_donor,
    CMV_IgG_rec,
    de_novo_DSA,
    donor_type 
  ) %>% 
  filter(!is.na(GFR_MDRD_2),
         !is.na(GPE_dep_log2),
         !is.na(MPE_dep_log2))

data_model_priorImpute %>% summary()
##       DGF        infection_any       GFR_MDRD        GFR_MDRD_2    
##  Min.   :0.000   Min.   :0.0000   Min.   :  8.44   Min.   :  4.30  
##  1st Qu.:0.000   1st Qu.:0.0000   1st Qu.: 39.07   1st Qu.: 38.10  
##  Median :0.000   Median :0.0000   Median : 49.88   Median : 49.90  
##  Mean   :0.339   Mean   :0.4237   Mean   : 51.90   Mean   : 52.04  
##  3rd Qu.:1.000   3rd Qu.:1.0000   3rd Qu.: 64.98   3rd Qu.: 64.98  
##  Max.   :1.000   Max.   :1.0000   Max.   :123.48   Max.   :123.48  
##                                                                    
##    GFR_MDRD_3      BKV_event_2      GPL_BC_log2       MPL_BC_log2    
##  Min.   :  4.30   Min.   :0.0000   Min.   :-3.8616   Min.   :-5.533  
##  1st Qu.: 38.10   1st Qu.:0.0000   1st Qu.:-0.7449   1st Qu.: 1.093  
##  Median : 49.90   Median :0.0000   Median :-0.1046   Median : 2.043  
##  Mean   : 51.72   Mean   :0.2147   Mean   : 0.1147   Mean   : 1.996  
##  3rd Qu.: 64.98   3rd Qu.:0.0000   3rd Qu.: 0.8505   3rd Qu.: 2.942  
##  Max.   :123.48   Max.   :1.0000   Max.   : 4.9498   Max.   : 5.417  
##                                                                      
##   GPE_dep_log2      GPE_ind_log2       MPE_dep_log2       MPE_ind_log2    
##  Min.   :-3.6439   Min.   :-3.86775   Min.   :-3.60823   Min.   :-3.8997  
##  1st Qu.:-2.6666   1st Qu.:-3.41120   1st Qu.:-2.92686   1st Qu.:-3.4501  
##  Median :-2.3921   Median :-3.16488   Median :-2.45799   Median :-3.1520  
##  Mean   :-2.2821   Mean   :-2.92303   Mean   :-2.40099   Mean   :-3.0307  
##  3rd Qu.:-2.0058   3rd Qu.:-2.70840   3rd Qu.:-1.99712   3rd Qu.:-2.7037  
##  Max.   : 0.4163   Max.   :-0.03283   Max.   : 0.05866   Max.   :-0.5724  
##                                                                           
##     male_sex          HLA_MM      male_sex_donor        KDPI      
##  Min.   :0.0000   Min.   :0.000   Min.   :0.0000   Min.   :20.00  
##  1st Qu.:0.0000   1st Qu.:4.000   1st Qu.:0.0000   1st Qu.:37.00  
##  Median :1.0000   Median :5.000   Median :1.0000   Median :53.00  
##  Mean   :0.6158   Mean   :4.373   Mean   :0.5537   Mean   :53.77  
##  3rd Qu.:1.0000   3rd Qu.:5.000   3rd Qu.:1.0000   3rd Qu.:71.00  
##  Max.   :1.0000   Max.   :6.000   Max.   :1.0000   Max.   :93.00  
##                   NA's   :35                                      
##  perfusion_event       CIT           infliximab        rec_age     
##  Min.   :0.0000   Min.   : 3.267   Min.   :0.0000   Min.   :27.00  
##  1st Qu.:0.0000   1st Qu.:10.267   1st Qu.:0.0000   1st Qu.:45.00  
##  Median :0.0000   Median :15.325   Median :0.0000   Median :54.00  
##  Mean   :0.4038   Mean   :15.920   Mean   :0.4915   Mean   :53.15  
##  3rd Qu.:1.0000   3rd Qu.:20.508   3rd Qu.:1.0000   3rd Qu.:61.00  
##  Max.   :1.0000   Max.   :50.133   Max.   :1.0000   Max.   :73.00  
##  NA's   :21       NA's   :3                                        
##     HLAMMGE3      BKV_viremia_time     race_2        race_donor_2   
##  Min.   :0.0000   Min.   :  22.0   Min.   :0.0000   Min.   :0.0000  
##  1st Qu.:1.0000   1st Qu.: 196.0   1st Qu.:0.0000   1st Qu.:0.0000  
##  Median :1.0000   Median : 735.0   Median :0.0000   Median :1.0000  
##  Mean   :0.8475   Mean   : 693.9   Mean   :0.4068   Mean   :0.6158  
##  3rd Qu.:1.0000   3rd Qu.: 952.0   3rd Qu.:1.0000   3rd Qu.:1.0000  
##  Max.   :1.0000   Max.   :1854.0   Max.   :1.0000   Max.   :1.0000  
##                                                                     
##    age_donor      CMV_IgG_rec     de_novo_DSA        donor_type    
##  Min.   : 4.00   Min.   :0.000   Min.   :0.00000   Min.   :0.0000  
##  1st Qu.:31.75   1st Qu.:0.000   1st Qu.:0.00000   1st Qu.:1.0000  
##  Median :42.50   Median :1.000   Median :0.00000   Median :1.0000  
##  Mean   :40.33   Mean   :0.661   Mean   :0.06215   Mean   :0.7514  
##  3rd Qu.:50.00   3rd Qu.:1.000   3rd Qu.:0.00000   3rd Qu.:1.0000  
##  Max.   :69.00   Max.   :1.000   Max.   :1.00000   Max.   :1.0000  
##  NA's   :1

## Get mean values 
mmale_sex <- mean(data_model_priorImpute$male_sex, na.rm = TRUE)
mmale_sex_donor <- mean(data_model_priorImpute$male_sex_donor, na.rm = TRUE)
mperfusion_event <- mean(data_model_priorImpute$perfusion_event, na.rm = TRUE)
mrec_age <- mean(data_model_priorImpute$rec_age, na.rm = TRUE)
mKDPI <- mean(data_model_priorImpute$KDPI, na.rm = TRUE)
mCIT <- mean(data_model_priorImpute$CIT, na.rm = TRUE)
mHLA_MM <- mean(data_model_priorImpute$HLA_MM, na.rm = TRUE)
mDGF <- mean(data_model_priorImpute$DGF, na.rm = TRUE)

4.2 Explore data

Open code
skim(data_model_priorImpute)
Data summary
Name data_model_priorImpute
Number of rows 177
Number of columns 28
_______________________
Column type frequency:
numeric 28
________________________
Group variables None

Variable type: numeric

skim_variable n_missing complete_rate mean sd p0 p25 p50 p75 p100 hist
DGF 0 1.00 0.34 0.47 0.00 0.00 0.00 1.00 1.00 ▇▁▁▁▅
infection_any 0 1.00 0.42 0.50 0.00 0.00 0.00 1.00 1.00 ▇▁▁▁▆
GFR_MDRD 0 1.00 51.90 20.26 8.44 39.07 49.88 64.98 123.48 ▃▇▆▂▁
GFR_MDRD_2 0 1.00 52.04 20.66 4.30 38.10 49.90 64.98 123.48 ▂▇▇▂▁
GFR_MDRD_3 0 1.00 51.72 21.17 4.30 38.10 49.90 64.98 123.48 ▂▇▇▂▁
BKV_event_2 0 1.00 0.21 0.41 0.00 0.00 0.00 0.00 1.00 ▇▁▁▁▂
GPL_BC_log2 0 1.00 0.11 1.42 -3.86 -0.74 -0.10 0.85 4.95 ▁▇▇▃▁
MPL_BC_log2 0 1.00 2.00 1.56 -5.53 1.09 2.04 2.94 5.42 ▁▁▃▇▃
GPE_dep_log2 0 1.00 -2.28 0.65 -3.64 -2.67 -2.39 -2.01 0.42 ▂▇▂▁▁
GPE_ind_log2 0 1.00 -2.92 0.73 -3.87 -3.41 -3.16 -2.71 -0.03 ▇▅▁▁▁
MPE_dep_log2 0 1.00 -2.40 0.67 -3.61 -2.93 -2.46 -2.00 0.06 ▆▇▆▁▁
MPE_ind_log2 0 1.00 -3.03 0.57 -3.90 -3.45 -3.15 -2.70 -0.57 ▇▇▃▁▁
male_sex 0 1.00 0.62 0.49 0.00 0.00 1.00 1.00 1.00 ▅▁▁▁▇
HLA_MM 35 0.80 4.37 1.45 0.00 4.00 5.00 5.00 6.00 ▁▁▂▃▇
male_sex_donor 0 1.00 0.55 0.50 0.00 0.00 1.00 1.00 1.00 ▆▁▁▁▇
KDPI 0 1.00 53.77 20.60 20.00 37.00 53.00 71.00 93.00 ▇▇▆▆▅
perfusion_event 21 0.88 0.40 0.49 0.00 0.00 0.00 1.00 1.00 ▇▁▁▁▆
CIT 3 0.98 15.92 7.14 3.27 10.27 15.32 20.51 50.13 ▆▇▃▁▁
infliximab 0 1.00 0.49 0.50 0.00 0.00 0.00 1.00 1.00 ▇▁▁▁▇
rec_age 0 1.00 53.15 10.49 27.00 45.00 54.00 61.00 73.00 ▂▅▆▇▅
HLAMMGE3 0 1.00 0.85 0.36 0.00 1.00 1.00 1.00 1.00 ▂▁▁▁▇
BKV_viremia_time 0 1.00 693.92 471.35 22.00 196.00 735.00 952.00 1854.00 ▇▇▅▃▁
race_2 0 1.00 0.41 0.49 0.00 0.00 0.00 1.00 1.00 ▇▁▁▁▆
race_donor_2 0 1.00 0.62 0.49 0.00 0.00 1.00 1.00 1.00 ▅▁▁▁▇
age_donor 1 0.99 40.33 14.23 4.00 31.75 42.50 50.00 69.00 ▂▃▇▇▃
CMV_IgG_rec 0 1.00 0.66 0.47 0.00 0.00 1.00 1.00 1.00 ▅▁▁▁▇
de_novo_DSA 0 1.00 0.06 0.24 0.00 0.00 0.00 0.00 1.00 ▇▁▁▁▁
donor_type 0 1.00 0.75 0.43 0.00 1.00 1.00 1.00 1.00 ▂▁▁▁▇

4.3 Variables correlations

Is the missingness correlated with outcomes?

Open code
df2_corr <- data_model_priorImpute %>% mutate(
  NA_perfusion_event = if_else(is.na(perfusion_event), 1, 0),
  NA_HLA_MM = if_else(is.na(HLA_MM), 1, 0)
)

m1 <- glm(NA_perfusion_event ~ DGF, family = 'binomial', data = df2_corr)
summary(m1)
## 
## Call:
## glm(formula = NA_perfusion_event ~ DGF, family = "binomial", 
##     data = df2_corr)
## 
## Coefficients:
##             Estimate Std. Error z value         Pr(>|z|)    
## (Intercept)  -1.9169     0.2765  -6.932 0.00000000000415 ***
## DGF          -0.2803     0.5115  -0.548            0.584    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 128.93  on 176  degrees of freedom
## Residual deviance: 128.62  on 175  degrees of freedom
## AIC: 132.62
## 
## Number of Fisher Scoring iterations: 4

m2 <- glm(NA_perfusion_event ~ infection_any, family = 'binomial', data = df2_corr)
summary(m2)
## 
## Call:
## glm(formula = NA_perfusion_event ~ infection_any, family = "binomial", 
##     data = df2_corr)
## 
## Coefficients:
##               Estimate Std. Error z value        Pr(>|z|)    
## (Intercept)   -2.01490    0.30731  -6.556 0.0000000000551 ***
## infection_any  0.02247    0.46979   0.048           0.962    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 128.93  on 176  degrees of freedom
## Residual deviance: 128.93  on 175  degrees of freedom
## AIC: 132.93
## 
## Number of Fisher Scoring iterations: 4

m3 <- glm(NA_perfusion_event ~ BKV_event_2, family = 'binomial', data = df2_corr)
summary(m2)
## 
## Call:
## glm(formula = NA_perfusion_event ~ infection_any, family = "binomial", 
##     data = df2_corr)
## 
## Coefficients:
##               Estimate Std. Error z value        Pr(>|z|)    
## (Intercept)   -2.01490    0.30731  -6.556 0.0000000000551 ***
## infection_any  0.02247    0.46979   0.048           0.962    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 128.93  on 176  degrees of freedom
## Residual deviance: 128.93  on 175  degrees of freedom
## AIC: 132.93
## 
## Number of Fisher Scoring iterations: 4

m4 <- glm(NA_perfusion_event ~ GFR_MDRD_3, family = 'binomial', data = df2_corr)
summary(m4)
## 
## Call:
## glm(formula = NA_perfusion_event ~ GFR_MDRD_3, family = "binomial", 
##     data = df2_corr)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)   
## (Intercept) -1.770427   0.608599  -2.909  0.00363 **
## GFR_MDRD_3  -0.004611   0.011209  -0.411  0.68080   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 128.93  on 176  degrees of freedom
## Residual deviance: 128.76  on 175  degrees of freedom
## AIC: 132.76
## 
## Number of Fisher Scoring iterations: 4

m5 <- glm(NA_perfusion_event ~ GFR_MDRD_2, family = 'binomial', data = df2_corr)
summary(m5)
## 
## Call:
## glm(formula = NA_perfusion_event ~ GFR_MDRD_2, family = "binomial", 
##     data = df2_corr)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)   
## (Intercept) -1.890831   0.629648  -3.003  0.00267 **
## GFR_MDRD_2  -0.002215   0.011400  -0.194  0.84592   
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 128.93  on 176  degrees of freedom
## Residual deviance: 128.89  on 175  degrees of freedom
## AIC: 132.89
## 
## Number of Fisher Scoring iterations: 4

m6 <- glm(NA_perfusion_event ~ GFR_MDRD, family = 'binomial', data = df2_corr)
summary(m6)
## 
## Call:
## glm(formula = NA_perfusion_event ~ GFR_MDRD, family = "binomial", 
##     data = df2_corr)
## 
## Coefficients:
##              Estimate Std. Error z value Pr(>|z|)    
## (Intercept) -2.533940   0.655266  -3.867  0.00011 ***
## GFR_MDRD     0.009888   0.011130   0.888  0.37433    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## (Dispersion parameter for binomial family taken to be 1)
## 
##     Null deviance: 128.93  on 176  degrees of freedom
## Residual deviance: 128.16  on 175  degrees of freedom
## AIC: 132.16
## 
## Number of Fisher Scoring iterations: 4

How much are variables inter-correlated?

Open code
corrplot(cor(df2_corr, use = "pairwise.complete.obs", method = 'spearman'),
         method = 'square')
## Warning in cor(df2_corr, use = "pairwise.complete.obs", method = "spearman"):
## the standard deviation is zero
## Warning in cor(df2_corr, use = "pairwise.complete.obs", method = "spearman"):
## the standard deviation is zero

path <- "gitignore/figures/sup_figure_1"

if (!file.exists(path)) {
  pdf(path, width = 12, height = 12) 
  corrplot(cor(df2_corr, use = "pairwise.complete.obs", method = 'spearman'),
         method = 'square')
  dev.off() 
}
Figure 1: Spearman correlations between all variables, including transplant outcomes, natural antibody levels, and various clinical characteristics.

4.4 Multiple imputation

Open code
set.seed(2025)

data_model_priorImpute <- data_model_priorImpute %>% 
  select(
    -c(GFR_MDRD_2, GFR_MDRD_3)
  )

init <- mice(data_model_priorImpute, maxit = 0)
init$method["perfusion_event"] <- "logreg"

data_imputed <- run(
  expr = mice(
    data_model_priorImpute,
    method = init$method,
    m = 80
  ),
  path = "gitignore/run/data_imputed"
)

## connect data not used for imputation (imputed clinically)
imp_list <- complete(data_imputed, action = "all")
mean(data3$GFR_MDRD == imp_list[[1]]$GFR_MDRD)
## [1] 1

imp_long <- complete(data_imputed, action = "long", include = TRUE)

imp_list <- map(imp_list, ~ 
                  bind_cols(
                    .x, 
                    data3 %>% select(
                      terminated_days, graft_failure, death_event,
                      GFR_MDRD_2, GFR_MDRD_3, GFR_days_2
                      )
                    )
                )

imp_long <- imap_dfr(imp_list, ~ 
                       mutate(.x,
                              .imp = as.integer(.y),
                              .id  = row_number()
                              )
                     )

orig_stub <- data_model_priorImpute %>% mutate(.imp = 0, .id = row_number())
imp_long <- bind_rows(orig_stub, imp_long)

data_imputed <- as.mids(imp_long)

## Get the 1st imputed dataset
data_first <- complete(data_imputed, 'all')[1]$`1`

## update data prior imputation
data_model_priorImpute <- data_model_priorImpute %>% 
  mutate(
    GFR_MDRD_3 = data3$GFR_MDRD_3,
    GFR_MDRD_2 = data3$GFR_MDRD_2
    )

4.4.1 Do we treat graft failure well?

We need to explore whether these with the graft failure within the 2-years period have clearly smaller eGFR

Open code
par(mfrow = c(1, 3))

plot(data_first$GFR_MDRD ~ factor(data_first$graft_failure))
plot(data_first$GFR_MDRD_2 ~ factor(data_first$graft_failure))
plot(data_first$GFR_MDRD_3 ~ factor(data_first$graft_failure))

5 Data points of nAB levels

Open code
plotac <- "figure_1"
path <- "gitignore/figures"

 cole <- c(
   "#E89C20", "#803800",
   "#4C60FF", "#001070"
 )


 data_model_priorImpute <- data_model_priorImpute %>%
   mutate(
     group = factor(if_else(infliximab == 1, "infliximab", "control")),
     Group = case_when(
       infliximab == 1 & DGF == 0 ~ "IFX_DGF-",
       infliximab == 1 & DGF == 1 ~ "IFX_DGF+",
       infliximab == 0 & DGF == 0 ~ "ctrl_DGF-",
       infliximab == 0 & DGF == 1 ~ "ctrl_DGF+",
     )
   )

 p1 <- data_model_priorImpute %>%
   ggplot(aes(x = Group, y = GPL_BC_log2, color = Group)) +
   geom_boxplot(outlier.shape = NA, fill = NA, width = 0.8) +
   geom_beeswarm(alpha = 0.5, cex = 2.3, size = 1.4) +
   scale_color_manual(values = cole) +
   labs(
     y = expression(log[2] ~ "(aCL IgG [" * mu * "g/ml])"),
     x = NULL
   ) +
   theme(
     axis.text.x = element_text(angle = 45, hjust = 1),
     legend.position = "none"
   )

 p2 <- data_model_priorImpute %>%
   ggplot(aes(x = Group, y = MPL_BC_log2, color = Group)) +
   geom_boxplot(outlier.shape = NA, fill = NA, width = 0.8) +
   geom_beeswarm(alpha = 0.5, cex = 2.3, size = 1.4) +
   scale_color_manual(values = cole) +
   labs(
     y = expression(log[2] ~ "(aCL IgM [" * mu * "g/ml])"),
     x = NULL
   ) +
   theme(
     axis.text.x = element_text(angle = 45, hjust = 1),
     legend.position = "none"
   )


 data_model_priorImpute <- data_model_priorImpute %>%
   mutate(
     group = factor(if_else(infliximab == 1, "infliximab", "control")),
     Group = case_when(
       infliximab == 1 & infection_any == 0 ~ "IFX_non-infected",
       infliximab == 1 & infection_any == 1 ~ "IFX_infected",
       infliximab == 0 & infection_any == 0 ~ "ctrl_non-infected",
       infliximab == 0 & infection_any == 1 ~ "ctrl_infected",
     )
   )

 p3 <- data_model_priorImpute %>%
   ggplot(aes(x = Group, y = GPE_dep_log2, color = Group)) +
   geom_boxplot(outlier.shape = NA, fill = NA, width = 0.8) +
   geom_beeswarm(alpha = 0.5, cex = 2.3, size = 1.4) +
   scale_color_manual(values = cole) +
   # theme(axis.text.x = element_blank()) +
   labs(
     y = expression(log[2] ~ "(aPE IgG dep [OD])"),
     x = NULL
   ) +
   theme(
     axis.text.x = element_text(angle = 45, hjust = 1),
     legend.position = "none"
   )

 p4 <- data_model_priorImpute %>%
   ggplot(aes(x = Group, y = GPE_ind_log2, color = Group)) +
   geom_boxplot(outlier.shape = NA, fill = NA, width = 0.8) +
   geom_beeswarm(alpha = 0.5, cex = 2.3, size = 1.4) +
   scale_color_manual(values = cole) +
   # theme(axis.text.x = element_blank()) +
   labs(
     y = expression(log[2] ~ "(aPE IgG ind [OD])"),
     x = NULL
   ) +
   theme(
     axis.text.x = element_text(angle = 45, hjust = 1),
     legend.position = "none"
   )

 p5 <- data_model_priorImpute %>%
   ggplot(aes(x = Group, y = MPE_dep_log2, color = Group)) +
   geom_boxplot(outlier.shape = NA, fill = NA, width = 0.8) +
   geom_beeswarm(alpha = 0.5, cex = 2.3, size = 1.4) +
   scale_color_manual(values = cole) +
   # theme(axis.text.x = element_blank()) +
   labs(
     y = expression(log[2] ~ "(aPE IgM dep [OD])"),
     x = NULL
   ) +
   theme(
     axis.text.x = element_text(angle = 45, hjust = 1),
     legend.position = "none"
   )

 p6 <- data_model_priorImpute %>%
   ggplot(aes(x = Group, y = MPE_ind_log2, color = Group)) +
   geom_boxplot(outlier.shape = NA, fill = NA, width = 0.8) +
   geom_beeswarm(alpha = 0.5, cex = 2.3, size = 1.4) +
   scale_color_manual(values = cole) +
   # theme(axis.text.x = element_blank()) +
   labs(
     y = expression(log[2] ~ "(aPE IgM ind [OD])"),
     x = NULL
   ) +
   theme(
     axis.text.x = element_text(angle = 45, hjust = 1),
     legend.position = "none"
   )

   assign(
     plotac,
     cowplot::plot_grid(p1, p2, p3, p4, p5, p6,
       labels = c("A", "B", "C", "D", "E", "F"),
       ncol = 2, nrow = , rel_heights = c(1, 1.1, 1.1)
     )
   )

if (!file.exists(paste0(path, "/", plotac))) {
   ggsave(
     path = paste0(path),
     filename = plotac,
     device = "pdf",
     width = 6,
     height = 10
   )
 }

 get(plotac)
Figure 2: Distribution of natural antibody levels across groups defined by treatment (infliximab [IFX] vs. control [ctrl]) and key post-transplant outcomes: occurrence of peritransplant delayed graft function [DGF+ vs. DGF−; A to B] or post-transplant infection [“infected” vs. “non-infected”; C to F]. (A) Levels of anti-cardiolipin IgG. (B) Levels of anti-cardiolipin IgM. (C) Levels of co-factor-dependent anti-phosphatidylethanolamine IgG. (D) Levels of co-factor-independent anti-phosphatidylethanolamine IgG. (E) Levels of co-factor-dependent anti-phosphatidylethanolamine IgM. (F) Levels of co-factor-independent anti-phosphatidylethanolamine IgM.

6 Unadjsuted effects

Definition of function

Open code
univar_mod <- function(dat, 
                       outcome, 
                       predictors,
                       family = "logistic", 
                       level = 0.95,
                       rounding = 2
                       ) {
  
  N <- vector("double", length(predictors))
  OR <- vector("double", length(predictors))
  CI_L <- vector("double", length(predictors))
  CI_U <- vector("double", length(predictors))
  predictor <- vector("double", length(predictors))

  for (i in 1:length(predictors)) {
  
    data <- dat %>% dplyr::select(outcome, 
                                  all_of(unlist(strsplit(predictors[i], "\\*"))))
    data <- na.omit(data)

    N[i] <- nrow(data)

    formula <- paste0(outcome, " ~ ", predictors[i])

    if (family == "logistic") {
       suppressMessages(suppressWarnings({
      model <- glm(formula, data = data, family = "binomial"(link = "logit"))
      OR[i] <- exp(coef(model)[length(coef(model))])
      CI_L[i] <- exp(confint(model, level = level)[length(coef(model)), ])[1]
      CI_U[i] <- exp(confint(model, level = level)[length(coef(model)), ])[2]

      predictor[i] <- names(coef(model)[length(coef(model))])

      result <- data.frame(
        predictor, 
        OR = paste0(round(OR, rounding), ' [',round(CI_L, rounding), ', ',
                    round(CI_U, rounding),']'),
        N
      )
       }))
    } else if (family == "gaussian") {
       suppressMessages(suppressWarnings({
      model <- lm(formula, data = data)
      OR[i] <- coef(model)[length(coef(model))]
      CI_L[i] <- confint(model, level = level)[length(coef(model)), ][1]
      CI_U[i] <- confint(model, level = level)[length(coef(model)), ][2]

      predictor[i] <- names(coef(model)[length(coef(model))])

      result <- data.frame(
        predictor,
        effect = paste0(round(OR, rounding), ' [',round(CI_L, rounding), ', ',
                    round(CI_U, rounding),']'),
        N
      )
       }))
    } else {
      stop("incorrect family specification", call. = FALSE) 
    }
  }
  
  return(result)
}

preds <- c(
  "infliximab",
  "GPL_BC_log2", "MPL_BC_log2",
  "GPE_dep_log2", "GPE_ind_log2",
  "MPE_dep_log2", "MPE_ind_log2",
  "male_sex", "HLA_MM", "male_sex_donor", "KDPI",
  "perfusion_event", "CIT", "rec_age", "DGF",
  "infliximab*GPL_BC_log2", "infliximab*MPL_BC_log2",
  "infliximab*GPE_dep_log2", "infliximab*GPE_ind_log2",
  "infliximab*MPE_dep_log2", "infliximab*MPE_ind_log2"
)

preds_DGF <- c(
  "infliximab",
  "GPL_BC_log2", "MPL_BC_log2",
  "GPE_dep_log2", "GPE_ind_log2",
  "MPE_dep_log2", "MPE_ind_log2",
  "male_sex", "HLA_MM", "male_sex_donor", "KDPI",
  "perfusion_event", "CIT", "rec_age",
  "infliximab*GPL_BC_log2", "infliximab*MPL_BC_log2",
  "infliximab*GPE_dep_log2", "infliximab*GPE_ind_log2",
  "infliximab*MPE_dep_log2", "infliximab*MPE_ind_log2"
)

6.1 DGF

Open code
kableExtra::kable(
  univar_mod(dat = data_model_priorImpute,
           outcome = 'DGF',
           predictors = preds_DGF,
           family = 'logistic',
           rounding = 2
           ) %>% rename(
             `(R)OR` = `OR`
           )
  )
## Warning: Using an external vector in selections was deprecated in tidyselect 1.1.0.
## ℹ Please use `all_of()` or `any_of()` instead.
##   # Was:
##   data %>% select(outcome)
## 
##   # Now:
##   data %>% select(all_of(outcome))
## 
## See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
Table 5: Supplementary Table 5: Results from unadjusted logistic regression models assessing the effects of variables on delayed graft function (DGF) in patients with complete data for given predictor. The odds ratio (OR) represents the estimated change in DGF odds per one-unit increase in the predictor. For interaction terms, the ratio of odds ratios (ROR) quantifies the ratio of the infliximab effect (OR) on DGF at a given natural antibody (nAb) level to its effect when the nAb level is halved. A 95% confidence interval including 1 suggests no significant modification of the infliximab effect by nAb levels. (R)OR: odds ratio (for main effects) or ratio of odds ratios (for interactions) [95% confidence interval]. N: number of observations.
predictor (R)OR N
infliximab 0.86 [0.46, 1.6] 177
GPL_BC_log2 1.08 [0.86, 1.34] 177
MPL_BC_log2 1.2 [0.98, 1.5] 177
GPE_dep_log2 1 [0.61, 1.61] 177
GPE_ind_log2 1.02 [0.66, 1.55] 177
MPE_dep_log2 1.08 [0.67, 1.73] 177
MPE_ind_log2 0.96 [0.54, 1.64] 177
male_sex 2.2 [1.13, 4.43] 177
HLA_MM 1.25 [0.97, 1.65] 142
male_sex_donor 1.08 [0.58, 2.04] 177
KDPI 1.01 [0.99, 1.02] 177
perfusion_event 0.91 [0.46, 1.78] 156
CIT 1.07 [1.02, 1.12] 174
rec_age 0.99 [0.96, 1.02] 177
infliximab:GPL_BC_log2 1.84 [1.15, 3.02] 177
infliximab:MPL_BC_log2 1.52 [0.97, 2.43] 177
infliximab:GPE_dep_log2 1 [0.38, 2.62] 177
infliximab:GPE_ind_log2 1.04 [0.43, 2.45] 177
infliximab:MPE_dep_log2 2.32 [0.89, 6.21] 177
infliximab:MPE_ind_log2 1.59 [0.52, 5.04] 177

6.2 GFR

Open code
kableExtra::kable(
  univar_mod(dat = data_model_priorImpute,
           outcome = 'GFR_MDRD_3',
           predictors = preds,
           family = 'gaussian',
           rounding = 1
           ) 
  )
Table 6: Supplementary Table 6: Results from unadjusted linear models assessing the effects of variables on estimated glomerula filtration rate (eGFR) in patients with complete data for given predictor. Effect [95% confidennce interval] is shown and represents the estimated change in eGFR per one-unit increase in the predictor. For interaction terms, the effect quantifies the difference in the infliximab effect on eGFR at a given natural antibody (nAb) level vs its effect when the nAb level is halved. A 95% confidence interval including 0 suggests no significant modification of the infliximab effect by nAb levels. N: number of observations.
predictor effect N
infliximab -2.7 [-9, 3.6] 177
GPL_BC_log2 0.9 [-1.4, 3.1] 177
MPL_BC_log2 -0.3 [-2.4, 1.7] 177
GPE_dep_log2 1.2 [-3.6, 6] 177
GPE_ind_log2 -3.3 [-7.6, 0.9] 177
MPE_dep_log2 3.1 [-1.6, 7.8] 177
MPE_ind_log2 0.8 [-4.7, 6.3] 177
male_sex 0 [-6.5, 6.5] 177
HLA_MM -2.8 [-5.2, -0.5] 142
male_sex_donor 4.5 [-1.8, 10.8] 177
KDPI -0.3 [-0.4, -0.1] 177
perfusion_event -7 [-13.7, -0.4] 156
CIT 0.2 [-0.2, 0.7] 174
rec_age -0.1 [-0.4, 0.2] 177
DGF -14 [-20.3, -7.7] 177
infliximab:GPL_BC_log2 -5.4 [-9.9, -0.8] 177
infliximab:MPL_BC_log2 -3.7 [-7.8, 0.4] 177
infliximab:GPE_dep_log2 0.8 [-8.9, 10.5] 177
infliximab:GPE_ind_log2 3 [-5.7, 11.6] 177
infliximab:MPE_dep_log2 -4.3 [-13.9, 5.3] 177
infliximab:MPE_ind_log2 1.9 [-9.3, 13.1] 177

6.3 Infection

Open code
kableExtra::kable(
  univar_mod(dat = data_model_priorImpute,
           outcome = 'infection_any',
           predictors = preds,
           family = 'logistic'
           )%>% rename(
             `(R)OR` = `OR`
           )
)
Table 7: Supplementary Table 9: Results from unadjusted logistic regression models assessing the effects of variables on infection risk in patients with complete data for given predictor. The odds ratio (OR) represents the estimated change in odds of infection per one-unit increase in the predictor. For interaction terms, the ratio of odds ratios (ROR) quantifies the ratio of the infliximab effect (OR) on infection at a given natural antibody (nAb) level to its effect when the nAb level is halved. A 95% confidence interval including 1 suggests no significant modification of the infliximab effect by nAb levels. (R)OR: odds ratio (for main effects) or ratio of odds ratios (for interactions) [95% confidence interval]. N: number of observations.
predictor (R)OR N
infliximab 1.11 [0.61, 2.02] 177
GPL_BC_log2 0.91 [0.73, 1.12] 177
MPL_BC_log2 0.94 [0.77, 1.14] 177
GPE_dep_log2 0.68 [0.42, 1.09] 177
GPE_ind_log2 0.66 [0.41, 1.02] 177
MPE_dep_log2 0.84 [0.53, 1.32] 177
MPE_ind_log2 1.09 [0.64, 1.84] 177
male_sex 0.98 [0.53, 1.82] 177
HLA_MM 0.88 [0.69, 1.11] 142
male_sex_donor 0.87 [0.48, 1.58] 177
KDPI 1.02 [1, 1.04] 177
perfusion_event 0.75 [0.39, 1.43] 156
CIT 0.93 [0.89, 0.98] 174
rec_age 1 [0.97, 1.02] 177
DGF 1.18 [0.63, 2.2] 177
infliximab:GPL_BC_log2 1.23 [0.79, 1.92] 177
infliximab:MPL_BC_log2 1.22 [0.82, 1.83] 177
infliximab:GPE_dep_log2 0.35 [0.12, 0.94] 177
infliximab:GPE_ind_log2 0.31 [0.1, 0.83] 177
infliximab:MPE_dep_log2 0.49 [0.19, 1.23] 177
infliximab:MPE_ind_log2 1 [0.35, 2.9] 177

6.4 BKV

Open code
kableExtra::kable(
  univar_mod(dat = data_model_priorImpute,
           outcome = 'BKV_event_2',
           predictors = preds,
           family = 'logistic',
           rounding = 2
           ) %>% rename(
             `(R)OR` = `OR`)
)
Table 8: Supplementary Table 10: Results from unadjusted logistic regression models assessing the effects of variables on BKV infection risk in patients with complete data for given predictor. The odds ratio (OR) represents the estimated change in odds of BKV infection per one-unit increase in the predictor. For interaction terms, the ratio of odds ratios (ROR) quantifies the ratio of the infliximab effect (OR) on BKV infection at a given natural antibody (nAb) level to its effect when the nAb level is halved. A 95% confidence interval including 1 suggests no significant modification of the infliximab effect by nAb levels. (R)OR: odds ratio (for main effects) or ratio of odds ratios (for interactions) [95% confidence interval]. N: number of observations.
predictor (R)OR N
infliximab 2.39 [1.15, 5.18] 177
GPL_BC_log2 0.83 [0.63, 1.07] 177
MPL_BC_log2 1.12 [0.89, 1.44] 177
GPE_dep_log2 0.72 [0.39, 1.27] 177
GPE_ind_log2 1.01 [0.59, 1.6] 177
MPE_dep_log2 1.13 [0.66, 1.93] 177
MPE_ind_log2 1.22 [0.65, 2.2] 177
male_sex 1.71 [0.8, 3.84] 177
HLA_MM 1.12 [0.84, 1.55] 142
male_sex_donor 0.38 [0.18, 0.79] 177
KDPI 0.99 [0.98, 1.01] 177
perfusion_event 1.28 [0.57, 2.82] 156
CIT 0.95 [0.89, 1] 174
rec_age 1.03 [0.99, 1.06] 177
DGF 1.36 [0.64, 2.84] 177
infliximab:GPL_BC_log2 1.34 [0.77, 2.41] 177
infliximab:MPL_BC_log2 1.1 [0.65, 1.86] 177
infliximab:GPE_dep_log2 1.57 [0.44, 6.14] 177
infliximab:GPE_ind_log2 0.49 [0.17, 1.33] 177
infliximab:MPE_dep_log2 1 [0.31, 3.22] 177
infliximab:MPE_ind_log2 1.37 [0.4, 5.26] 177

7 DGF models

7.1 DGF by GPL_BC

7.1.1 Priors

Open code
# function to get prior
create_prior <- function(var, sigma, class = "b", coef = NULL) {
  set_prior(paste0("normal(0, ", sigma, ")"), class = class, coef = coef)
}

scaling_unit <- 1

7.1.1.1 Main effects model

Open code
priors_main <- c(
  create_prior("GPL_BC_log2",
    2 / sd(data_model_priorImpute$GPL_BC_log2, na.rm = TRUE),
    coef = "GPL_BC_log2"
  ),
   create_prior("IHLA_MMM5",
    2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$DGF)), ", 10)"),
    class = "Intercept"
  )
)

7.1.1.2 Non-linear penalized interaction model

Open code
priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
               coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
               coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
               coef = "IKDPIM50"),
  create_prior("ICITM15",
               2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
               coef = "ICITM15"),
  create_prior("male_sex",
               4,
               coef = "male_sex"),
  create_prior("perfusion_event",
               4,
               coef = "perfusion_event"),
  create_prior("male_sex_donor",
               4,
               coef = "male_sex_donor"),

  create_prior("infliximab",
               4,
               coef = "infliximab"),

 set_prior("student_t(3, 0, 0.6)", class = "sds", 
           coef = 's(GPL_BC_log2, bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 0.3)", class = "sds",  
           coef = 's(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$DGF)), ", 10)"),
            class = "Intercept")
)

7.1.2 Models

7.1.2.1 Main effects models

Open code
model_dgf_GPL_BC_main <- run(
  expr = brm_multiple(
    DGF ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      GPL_BC_log2,
    family = bernoulli(),
    data = data_imputed,
    prior = priors_main,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_dgf_GPL_BC_main",
  reuse = TRUE
)

summary(model_dgf_GPL_BC_main, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: DGF ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + GPL_BC_log2 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          -0.75      0.48    -1.70     0.18 1.01    18087    26121
## male_sex            0.81      0.38     0.07     1.58 1.01    35270    27169
## Irec_ageM50        -0.02      0.02    -0.05     0.01 1.01    35275    27008
## male_sex_donor      0.10      0.37    -0.62     0.83 1.01    35510    27598
## IKDPIM50            0.01      0.01    -0.01     0.02 1.01    36654    26896
## ICITM15             0.08      0.03     0.02     0.13 1.02    11475    27045
## IHLA_MMM5           0.26      0.13     0.01     0.53 1.06     3602    18106
## perfusion_event    -0.65      0.40    -1.45     0.13 1.06     3524    13012
## infliximab         -0.33      0.35    -1.04     0.37 1.01    37331    27918
## GPL_BC_log2         0.05      0.13    -0.20     0.29 1.01    37266    27873
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_dgf_GPL_BC_main)
##                           prior     class            coef group resp dpar nlpar
##                          (flat)         b                                      
##     normal(0, 1.41170525720746)         b     GPL_BC_log2                      
##    normal(0, 0.280307006032291)         b         ICITM15                      
##     normal(0, 1.38219051128263)         b       IHLA_MMM5                      
##   normal(0, 0.0970980394231448)         b        IKDPIM50                      
##                    normal(0, 4)         b      infliximab                      
##    normal(0, 0.190589762164436)         b     Irec_ageM50                      
##                    normal(0, 4)         b        male_sex                      
##                    normal(0, 4)         b  male_sex_donor                      
##                    normal(0, 4)         b perfusion_event                      
##  normal(-0.667829372575656, 10) Intercept                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user

tr <- round(exp(fixef(model_dgf_GPL_BC_main, robust = TRUE)[-1, c(1,3,4)]), 2)
colnames(tr)[1] <- 'OR'
kableExtra::kable(tr)
Table 9: Results from multivariable Bayesian logistic regression models examining the main effects of predictors, including infliximab treatment and log2-transformed anti-cardiolipin IgG (GPL_BC_log2), on delayed graft function (DGF), without interaction terms. The odds ratio (OR) represents the estimated change in the odds of DGF per one-unit increase in the predictor. OR: odds ratio; Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
OR Q2.5 Q97.5
male_sex 2.25 1.07 4.87
Irec_ageM50 0.98 0.95 1.01
male_sex_donor 1.11 0.54 2.28
IKDPIM50 1.01 0.99 1.02
ICITM15 1.08 1.02 1.14
IHLA_MMM5 1.30 1.01 1.70
perfusion_event 0.52 0.23 1.14
infliximab 0.72 0.35 1.45
GPL_BC_log2 1.05 0.81 1.34

7.1.2.2 Non-linear interaction model

Open code
model_dgf_GPL_BC_interaction_NL <- run(
  expr = brm_multiple(
    DGF ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(GPL_BC_log2, bs = 'ps', k = 5) +
    s(GPL_BC_log2, by = infliximab, bs = 'ps', k = 5),
    family = bernoulli(),
    data = data_imputed,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_dgf_GPL_BC_interaction_NL",
  reuse = TRUE
)

summary(model_dgf_GPL_BC_interaction_NL, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: DGF ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(GPL_BC_log2, bs = "ps", k = 5) + s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Smoothing Spline Hyperparameters:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sGPL_BC_log2_1)               0.83      0.61     0.05     2.95 1.02
## sds(sGPL_BC_log2infliximab_1)     0.23      0.21     0.01     1.16 1.01
##                               Bulk_ESS Tail_ESS
## sds(sGPL_BC_log2_1)              14045    12820
## sds(sGPL_BC_log2infliximab_1)    27968    14350
## 
## Regression Coefficients:
##                           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                    -0.88      0.49    -1.87     0.07 1.02    14612
## male_sex                      0.97      0.40     0.20     1.80 1.01    35062
## Irec_ageM50                  -0.02      0.02    -0.06     0.01 1.01    34006
## male_sex_donor                0.29      0.39    -0.47     1.06 1.01    34337
## IKDPIM50                      0.01      0.01    -0.01     0.03 1.01    34534
## ICITM15                       0.08      0.03     0.03     0.14 1.02     8750
## IHLA_MMM5                     0.31      0.14     0.05     0.60 1.06     3297
## perfusion_event              -0.75      0.43    -1.61     0.07 1.08     2768
## infliximab                    0.02      3.90    -7.66     7.65 1.02    12394
## sGPL_BC_log2_1               -1.27      1.52    -4.37     2.91 1.01    19570
## sGPL_BC_log2:infliximab_1     2.30      2.41    -2.36     7.02 1.02    13785
## sGPL_BC_log2:infliximab_2    -2.66      1.62    -5.80     0.53 1.01    14601
##                           Tail_ESS
## Intercept                    29212
## male_sex                     26720
## Irec_ageM50                  27092
## male_sex_donor               26886
## IKDPIM50                     27506
## ICITM15                      25453
## IHLA_MMM5                    11332
## perfusion_event               9621
## infliximab                   18177
## sGPL_BC_log2_1               15385
## sGPL_BC_log2:infliximab_1    20359
## sGPL_BC_log2:infliximab_2    22043
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_dgf_GPL_BC_interaction_NL)
##                           prior     class
##                          (flat)         b
##    normal(0, 0.280307006032291)         b
##     normal(0, 1.38219051128263)         b
##   normal(0, 0.0970980394231448)         b
##                    normal(0, 4)         b
##    normal(0, 0.190589762164436)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                          (flat)         b
##                          (flat)         b
##                          (flat)         b
##  normal(-0.667829372575656, 10) Intercept
##            student_t(3, 0, 2.5)       sds
##            student_t(3, 0, 0.6)       sds
##            student_t(3, 0, 0.3)       sds
##                                               coef group resp dpar nlpar lb ub
##                                                                               
##                                            ICITM15                            
##                                          IHLA_MMM5                            
##                                           IKDPIM50                            
##                                         infliximab                            
##                                        Irec_ageM50                            
##                                           male_sex                            
##                                     male_sex_donor                            
##                                    perfusion_event                            
##                                     sGPL_BC_log2_1                            
##                          sGPL_BC_log2:infliximab_1                            
##                          sGPL_BC_log2:infliximab_2                            
##                                                                               
##                                                                           0   
##                   s(GPL_BC_log2, bs = "ps", k = 5)                        0   
##  s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)                        0   
##        source
##       default
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##  (vectorized)
##  (vectorized)
##  (vectorized)
##          user
##       default
##          user
##          user

tr2 <- round(exp(fixef(model_dgf_GPL_BC_interaction_NL, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)]), 2)

antibody_seq <- quantile(data_model_priorImpute$GPL_BC_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
  `male_sex` = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_GPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get odds ratio for infliximab effect across antibody values
prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()
names(prediction) <- c('p05', 'p95')

infliximab_GPL_BC_log2_int <- quantile(
  exp((log(prediction$p95) - log(prediction$p05))/scaling_unit), 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:GPL_BC_log2'
colnames(tr2)[1] <- '(R)OR'
kableExtra::kable(tr2)
Table 10: Results from multivariable Bayesian logistic regression models incorporating a non-linear interaction between anti-cardiolipin IgG (GPL_BC_log2) and infliximab treatment on delayed graft function (DGF), while accounting for other covariates. Effects are presented as odds ratios (OR), with the last row reporting the ratio of odds ratios (ROR). The ROR represents the estimated ratio of infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgG, quantifying the interaction effect.Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
(R)OR Q2.5 Q97.5
male_sex 2.64 1.23 6.02
Irec_ageM50 0.98 0.94 1.01
male_sex_donor 1.34 0.62 2.88
IKDPIM50 1.01 0.99 1.03
ICITM15 1.09 1.03 1.15
IHLA_MMM5 1.36 1.05 1.83
perfusion_event 0.47 0.20 1.07
infliximab:GPL_BC_log2 46.56 4.36 610.02

7.1.3 Visualisation

Extract posterior draws

Open code
antibody_perc <- quantile(data_model_priorImpute$GPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_dgf_GPL_BC_interaction_NL,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')), 
  GPL_BC_log2 = data_prediction$GPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control"))) %>% 
  ggplot(aes(x = GPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 1),
                     breaks = c(seq(0, 1, by = 0.2))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  labs(x = expression(log[2]~"(aCL IgG ["*mu*"g/ml])"), y = "DGF risk") +
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text = element_text(size = 10),
        axis.title = element_text(size = 12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") +
  
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 0),
    aes(x = GPL_BC_log2),
    sides = "b",
    color = "black",
    linewidth = 0.12,
    inherit.aes = FALSE
  ) +
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 1),
    aes(x = GPL_BC_log2),
     sides = "t",
    color = "black",
    linewidth = 0.12,
    inherit.aes = FALSE
  ) + 
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", linewidth = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -1.801668  2.531821

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_GPL_BC_interaction_NL,
    newdata = data_prediction)

tr_ctrl <- (logit(tr[, c(ncol(tr)/2)]) - logit(tr[, c(1)])) 
tr_infliximab <- (logit(tr[,c(ncol(tr))]) - logit(tr[,c(ncol(tr)/2)+1]))

post_fix <- data.frame(
  b_GPL_BC_log2 = tr_ctrl,
  b_GPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = exp(b_GPL_BC_log2), 
         infliximab = exp(b_GPL_BC_log2_infliximab)) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##       control infliximab
## 2.5%      0.1        2.0
## 97.5%     1.1       91.2
## 50%       0.3       12.3
## 0.1%      0.0        0.7
## 99.9%     2.5      346.3

xpos <- 323

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect (OR) of aCL IgG increase (p05 to p95) on DGF', 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(1/600, 1800)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(1/512, 1/64, 1/8, 1, 8, 64, 512),
                     labels = c("1/512", "1/64", "1/8", "1", "8", "64", "512")) +
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 
## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 240
xseq <- c(1/64, 1/8, 1, 8, 64, 512)

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]
antibody_seq
##         5%        25%        50%        75%        95% 
## -1.8016683 -0.7449060 -0.1046076  0.8504991  2.5318211
2**antibody_seq
##        5%       25%       50%       75%       95% 
## 0.2868427 0.5967067 0.9300579 1.8031246 5.7830122

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_GPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(2)

CIS
##        p05  p25  p50  p75   p95
## 2.5%  0.03 0.13 0.27 0.60  1.38
## 97.5% 0.49 0.80 1.25 3.22 30.32
## 50%   0.13 0.34 0.59 1.37  6.24
## 0.1%  0.01 0.07 0.17 0.37  0.60
## 99.9% 1.02 1.28 1.93 5.41 82.13

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPL_BC_percentile') %>% 
  
  ggplot(aes(y = GPL_BC_percentile, x = value, fill = GPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on DGF risk (odds ratio)", 
         y = 'Percentile of aCL IgG value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(1/300, 1150)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(xseq),
                     labels = c("1/64", "1/8", "1", "8", "64", '512')) +
  
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Odds ratio: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Odds ratio: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Odds ratio: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

7.1.3.1 Figure merged

Open code
plotac <- 'figure_2'
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 3: Anti-cardiolipin IgG modulates infliximab effect on delayed graft function. (A) Predicted DGF risk by anti-cardiolipin IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of cardiolipin levels. (B) Posterior distribution of the estimated odds ratio comparing DGF odds at the 95th vs. 5th percentile of anti-cardiolipin IgG. (C) Posterior distribution of the infliximab effect (odds ratio) on DGF risk across anti-cardiolipin IgG percentiles. Results are from a multivariable Bayesian logistic model with B-splines.

7.2 DGF by MPL_BC

7.2.1 Priors

7.2.1.1 Main effects model

Open code
priors_main <- c(
  create_prior("IMPL_BC_log2M2",
    2 / sd(data_model_priorImpute$MPL_BC_log2, na.rm = TRUE),
    coef = "IMPL_BC_log2M2"
  ),
   create_prior("IHLA_MMM5",
    2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$DGF)), ", 10)"),
    class = "Intercept"
  )
)

7.2.1.2 Non-linear penalized interaction model

Open code
priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
               coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
               coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
               coef = "IKDPIM50"),
  create_prior("ICITM15",
               2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
               coef = "ICITM15"),
  create_prior("male_sex",
               4,
               coef = "male_sex"),
  create_prior("perfusion_event",
               4,
               coef = "perfusion_event"),
  create_prior("male_sex_donor",
               4,
               coef = "male_sex_donor"),

  create_prior("infliximab",
               4,
               coef = "infliximab"),
  
 set_prior("student_t(3, 0, 0.6)", class = "sds", 
           coef = 's(I(MPL_BC_log2 - 2), bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 0.3)", class = "sds",  
           coef = 's(I(MPL_BC_log2 - 2), by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$DGF)), ", 10)"),
            class = "Intercept")
)

7.2.2 Models

7.2.2.1 Main effects models

Open code
set.seed(2025)
model_dgf_MPL_BC_main <- run(
  expr = brm_multiple(
    DGF ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      I(MPL_BC_log2 - 2),
    family = bernoulli(),
    data = data_imputed,
    prior = priors_main,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_dgf_MPL_BC_main",
  reuse = TRUE
)

summary(model_dgf_MPL_BC_main, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: DGF ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + I(MPL_BC_log2 - 2) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          -0.88      0.48    -1.85     0.05 1.02    14414    25568
## male_sex            0.89      0.39     0.14     1.69 1.01    35387    28013
## Irec_ageM50        -0.02      0.02    -0.06     0.01 1.01    37218    27652
## male_sex_donor      0.14      0.37    -0.59     0.87 1.01    36459    27773
## IKDPIM50            0.01      0.01    -0.01     0.02 1.01    36554    27839
## ICITM15             0.07      0.03     0.02     0.13 1.02    11114    26679
## IHLA_MMM5           0.25      0.13    -0.00     0.52 1.06     3433    12047
## perfusion_event    -0.61      0.41    -1.45     0.19 1.07     3180    11578
## infliximab         -0.24      0.36    -0.97     0.48 1.01    35927    27005
## IMPL_BC_log2M2      0.20      0.12    -0.04     0.44 1.01    35829    26440
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_dgf_MPL_BC_main)
##                           prior     class            coef group resp dpar nlpar
##                          (flat)         b                                      
##    normal(0, 0.280307006032291)         b         ICITM15                      
##     normal(0, 1.38219051128263)         b       IHLA_MMM5                      
##   normal(0, 0.0970980394231448)         b        IKDPIM50                      
##     normal(0, 1.27934317633706)         b  IMPL_BC_log2M2                      
##                    normal(0, 4)         b      infliximab                      
##    normal(0, 0.190589762164436)         b     Irec_ageM50                      
##                    normal(0, 4)         b        male_sex                      
##                    normal(0, 4)         b  male_sex_donor                      
##                    normal(0, 4)         b perfusion_event                      
##  normal(-0.667829372575656, 10) Intercept                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user

tr <- round(exp(fixef(model_dgf_MPL_BC_main, robust = TRUE)[-1, c(1,3,4)]), 2)
colnames(tr)[1] <- 'OR'
kableExtra::kable(tr)
Table 11: Results from multivariable Bayesian logistic regression models examining the main effects of predictors, including infliximab treatment and log2-transformed anti-cardiolipin IgM (MPL_BC_log2), on delayed graft function (DGF), without interaction terms. The odds ratio (OR) represents the estimated change in the odds of DGF per one-unit increase in the predictor. OR: odds ratio; Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
OR Q2.5 Q97.5
male_sex 2.44 1.15 5.40
Irec_ageM50 0.98 0.95 1.01
male_sex_donor 1.15 0.56 2.40
IKDPIM50 1.01 0.99 1.02
ICITM15 1.08 1.02 1.14
IHLA_MMM5 1.28 1.00 1.69
perfusion_event 0.55 0.23 1.21
infliximab 0.79 0.38 1.61
IMPL_BC_log2M2 1.22 0.96 1.56

7.2.2.2 Non-linear interaction model

Open code
set.seed(2025)
model_dgf_MPL_BC_interaction_NL <- run(
  expr = brm_multiple(
    DGF ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +   
    s(I(MPL_BC_log2-2), bs = 'ps', k = 5) +
    s(I(MPL_BC_log2-2), by = infliximab, bs = 'ps', k = 5),
    family = bernoulli(),
    data = data_imputed,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_dgf_MPL_BC_interaction_NL",
  reuse = TRUE
)

summary(model_dgf_MPL_BC_interaction_NL, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: DGF ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(I(MPL_BC_log2 - 2), bs = "ps", k = 5) + s(I(MPL_BC_log2 - 2), by = infliximab, bs = "ps", k = 5) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Smoothing Spline Hyperparameters:
##                                  Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sIMPL_BC_log2M2_1)               0.42      0.38     0.02     1.87 1.01
## sds(sIMPL_BC_log2M2infliximab_1)     0.22      0.20     0.01     1.07 1.00
##                                  Bulk_ESS Tail_ESS
## sds(sIMPL_BC_log2M2_1)              22288    15370
## sds(sIMPL_BC_log2M2infliximab_1)    31518    16326
## 
## Regression Coefficients:
##                              Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                       -0.88      0.49    -1.86     0.05 1.02    18653
## male_sex                         0.90      0.39     0.14     1.70 1.01    36356
## Irec_ageM50                     -0.03      0.02    -0.06     0.01 1.01    34817
## male_sex_donor                   0.23      0.38    -0.51     1.00 1.01    36879
## IKDPIM50                         0.01      0.01    -0.01     0.02 1.01    35932
## ICITM15                          0.07      0.03     0.02     0.13 1.02    15639
## IHLA_MMM5                        0.25      0.13     0.00     0.53 1.06     3408
## perfusion_event                 -0.58      0.42    -1.42     0.23 1.08     2747
## infliximab                      -0.08      3.95    -7.77     7.71 1.02    12781
## sIMPL_BC_log2M2_1                0.36      1.27    -2.13     3.27 1.01    23892
## sIMPL_BC_log2M2:infliximab_1     1.13      2.95    -4.69     6.87 1.02    13362
## sIMPL_BC_log2M2:infliximab_2    -1.55      1.23    -4.00     0.82 1.01    16755
##                              Tail_ESS
## Intercept                       28581
## male_sex                        25744
## Irec_ageM50                     26590
## male_sex_donor                  27123
## IKDPIM50                        27389
## ICITM15                         24626
## IHLA_MMM5                       11220
## perfusion_event                  9277
## infliximab                      17925
## sIMPL_BC_log2M2_1               20627
## sIMPL_BC_log2M2:infliximab_1    18672
## sIMPL_BC_log2M2:infliximab_2    23546
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_dgf_MPL_BC_interaction_NL)
##                           prior     class
##                          (flat)         b
##    normal(0, 0.280307006032291)         b
##     normal(0, 1.38219051128263)         b
##   normal(0, 0.0970980394231448)         b
##                    normal(0, 4)         b
##    normal(0, 0.190589762164436)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                          (flat)         b
##                          (flat)         b
##                          (flat)         b
##  normal(-0.667829372575656, 10) Intercept
##            student_t(3, 0, 2.5)       sds
##            student_t(3, 0, 0.6)       sds
##            student_t(3, 0, 0.3)       sds
##                                                      coef group resp dpar nlpar
##                                                                                
##                                                   ICITM15                      
##                                                 IHLA_MMM5                      
##                                                  IKDPIM50                      
##                                                infliximab                      
##                                               Irec_ageM50                      
##                                                  male_sex                      
##                                            male_sex_donor                      
##                                           perfusion_event                      
##                                         sIMPL_BC_log2M2_1                      
##                              sIMPL_BC_log2M2:infliximab_1                      
##                              sIMPL_BC_log2M2:infliximab_2                      
##                                                                                
##                                                                                
##                   s(I(MPL_BC_log2 - 2), bs = "ps", k = 5)                      
##  s(I(MPL_BC_log2 - 2), by = infliximab, bs = "ps", k = 5)                      
##  lb ub       source
##             default
##                user
##                user
##                user
##                user
##                user
##                user
##                user
##                user
##        (vectorized)
##        (vectorized)
##        (vectorized)
##                user
##   0         default
##   0            user
##   0            user


tr2 <- round(exp(fixef(model_dgf_MPL_BC_interaction_NL, robust = TRUE)[-c(1,9:12), c(1,3,4)]), 2)

antibody_seq <- quantile(data_model_priorImpute$MPL_BC_log2, probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_MPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get odds ratio for infliximab effect across antibody values
prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()
names(prediction) <- c('p05', 'p95')

infliximab_MPL_BC_log2_int <- quantile(exp((log(prediction$p95) - log(prediction$p05))/scaling_unit), 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_MPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:MPL_BC_log2'
colnames(tr2)[1] <- '(R)OR'
kableExtra::kable(tr2)
Table 12: Results from multivariable Bayesian logistic regression models incorporating a non-linear interaction between anti-cardiolipin IgM (MPL_BC_log2) and infliximab treatment on delayed graft function (DGF), while accounting for other covariates. Effects are presented as odds ratios (OR), with the last row reporting the ratio of odds ratios (ROR). The ROR represents the estimated ratio of infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgG, quantifying the interaction effect.Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
(R)OR Q2.5 Q97.5
male_sex 2.47 1.14 5.49
Irec_ageM50 0.98 0.94 1.01
male_sex_donor 1.26 0.60 2.71
IKDPIM50 1.01 0.99 1.02
ICITM15 1.08 1.02 1.14
IHLA_MMM5 1.29 1.00 1.70
perfusion_event 0.56 0.24 1.26
infliximab:MPL_BC_log2 12.18 1.07 165.59

7.2.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_model_priorImpute$MPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_dgf_MPL_BC_interaction_NL,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')), 
  MPL_BC_log2 = data_prediction$MPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control"))) %>% 
  ggplot(aes(x = MPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 1),
                     breaks = c(seq(0, 1, by = 0.2))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  labs(x = expression(log[2]~"(aCL IgM ["*mu*"g/ml])"), y = "DGF risk") +
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size=10),
        axis.title=element_text(size=12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") +
  
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 0),
    aes(x = MPL_BC_log2),
    sides = "b",
    color = "black",
    size = 0.12,
    inherit.aes = FALSE
  ) +
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 1),
    aes(x = MPL_BC_log2),
     sides = "t",
    color = "black",
    size = 0.12,
    inherit.aes = FALSE
  )  + 
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##         5%        95% 
## -0.3963058  4.5810923

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_MPL_BC_interaction_NL,
    newdata = data_prediction)

tr_ctrl <- (logit(tr[,c(ncol(tr)/2)]) - logit(tr[,c(1)]))

tr_infliximab <- (logit(tr[,c(ncol(tr))]) - logit(tr[,c(ncol(tr)/2)+1]))

post_fix <- data.frame(
  b_MPL_BC_log2 = tr_ctrl,
  b_MPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = exp(b_MPL_BC_log2), 
         infliximab = exp(b_MPL_BC_log2_infliximab)) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##       control infliximab
## 2.5%      0.2        1.8
## 97.5%     4.8       82.4
## 50%       0.9       11.1
## 0.1%      0.1        0.7
## 99.9%    13.3      332.6

xpos <- 323

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = "Effect (OR) of aCL IgM (p05 to p95) on DGF", 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(1/600, 1800)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(1/512, 1/64, 1/8, 1, 8, 64, 512),
                     labels = c("1/512", "1/64", "1/8", "1", "8", "64", "512")) +
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 240
xseq <- c(1/64, 1/8, 1, 8, 64, 512)

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]
antibody_seq
##         5%        25%        50%        75%        95% 
## -0.3963058  1.0933124  2.0432905  2.9421729  4.5810923

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_MPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()

names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(2)

CIS
##        p05  p25  p50  p75   p95
## 2.5%  0.05 0.19 0.37 0.52  0.66
## 97.5% 0.94 1.15 1.61 2.85 12.59
## 50%   0.23 0.48 0.77 1.22  2.79
## 0.1%  0.02 0.11 0.24 0.32  0.28
## 99.9% 2.07 1.85 2.43 4.75 31.56

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'MPL_BC_percentile') %>% 
  
  ggplot(aes(y = MPL_BC_percentile, x = value, fill = MPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on DGF risk (odds ratio)", 
         y = 'Percentile of aCL IgM value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(1/300, 1000)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(xseq),
                     labels = c("1/64", "1/8", "1", "8", "64", '512')) +
  
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Odds ratio: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Odds ratio: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Odds ratio: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

7.2.3.1 Figure merged

Open code
plotac <- 'sup_figure_3'
path <- "gitignore/figures"

fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 4: Anti-cardiolipin IgM modulates infliximab effect on delayed graft function. (A) Predicted DGF risk by anti-cardiolipin IgM for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of cardiolipin levels. (B) Posterior distribution of the estimated odds ratio comparing DGF odds at the 95th vs. 5th percentile of anti-cardiolipin IgM. (C) Posterior distribution of the infliximab effect (odds ratio) on DGF risk across anti-cardiolipin IgM percentiles. Results are from a multivariable Bayesian logistic model with B-splines.

8 Infection models

8.1 Infection by GPE_dep

8.1.1 Priors

8.1.1.1 Main effects model

Open code
priors_main <- c(
  create_prior("IGPE_dep_log2P2",
    2 / sd(data_model_priorImpute$GPE_dep_log2, na.rm = TRUE),
    coef = "IGPE_dep_log2P2"
  ),
   create_prior("IHLA_MMM5",
    2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$infection_any)), ", 10)"),
    class = "Intercept"
  )
)

8.1.1.2 Non-linear penalized interaction model

Open code
priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
               coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
               coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
               coef = "IKDPIM50"),
  create_prior("ICITM15",
               2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
               coef = "ICITM15"),
  create_prior("male_sex",
               4,
               coef = "male_sex"),
  create_prior("perfusion_event",
               4,
               coef = "perfusion_event"),
  create_prior("male_sex_donor",
               4,
               coef = "male_sex_donor"),
  
  create_prior("infliximab",
               4,
               coef = "infliximab"),

 set_prior("student_t(3, 0, 0.6)", class = "sds", 
           coef = 's(I(GPE_dep_log2 + 2), bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 0.3)", class = "sds",  
           coef = 's(I(GPE_dep_log2 + 2), by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$infection_any)), ", 10)"),
            class = "Intercept")
)

8.1.2 Models

8.1.2.1 Main effects models

Open code
model_infection_any_GPE_dep_main <- run(
  expr = brm_multiple(
    infection_any ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      I(GPE_dep_log2 + 2),
    family = bernoulli(),
    data = data_imputed,
    prior = priors_main,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_infection_any_GPE_dep_main",
  reuse = TRUE
)

summary(model_infection_any_GPE_dep_main, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: infection_any ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + I(GPE_dep_log2 + 2) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          -1.20      0.47    -2.17    -0.30 1.03     8357    25111
## male_sex            0.28      0.36    -0.43     1.01 1.01    35581    26492
## Irec_ageM50        -0.03      0.02    -0.06     0.01 1.01    34812    27578
## male_sex_donor      0.22      0.36    -0.48     0.92 1.01    35747    27955
## IKDPIM50            0.03      0.01     0.02     0.05 1.00    31156    26870
## ICITM15            -0.10      0.03    -0.16    -0.04 1.01    21925    27001
## IHLA_MMM5          -0.27      0.13    -0.53    -0.04 1.05     3743    18243
## perfusion_event    -0.04      0.40    -0.83     0.74 1.07     2925    12861
## infliximab          0.40      0.35    -0.27     1.09 1.01    36151    28346
## IGPE_dep_log2P2    -0.55      0.28    -1.11    -0.02 1.01    35008    27707
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_infection_any_GPE_dep_main)
##                           prior     class            coef group resp dpar nlpar
##                          (flat)         b                                      
##    normal(0, 0.280307006032291)         b         ICITM15                      
##     normal(0, 3.05433848490195)         b IGPE_dep_log2P2                      
##     normal(0, 1.38219051128263)         b       IHLA_MMM5                      
##   normal(0, 0.0970980394231448)         b        IKDPIM50                      
##                    normal(0, 4)         b      infliximab                      
##    normal(0, 0.190589762164436)         b     Irec_ageM50                      
##                    normal(0, 4)         b        male_sex                      
##                    normal(0, 4)         b  male_sex_donor                      
##                    normal(0, 4)         b perfusion_event                      
##  normal(-0.307484699747961, 10) Intercept                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user

tr <- round(exp(fixef(model_infection_any_GPE_dep_main, robust = TRUE)[-1, c(1,3,4)]), 2)
colnames(tr)[1] <- 'OR'
kableExtra::kable(tr)
Table 13: Results from multivariable Bayesian logistic regression models examining the main effects of predictors, including infliximab treatment and log2-transformed co-factor-dependent phosphatidylethanolamine IgG (GPE_dep_log2), on infection risk, without interaction terms. The odds ratio (OR) represents the estimated change in the odds of infection per one-unit increase in the predictor. OR: odds ratio; Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
OR Q2.5 Q97.5
male_sex 1.32 0.65 2.76
Irec_ageM50 0.97 0.94 1.01
male_sex_donor 1.24 0.62 2.51
IKDPIM50 1.03 1.02 1.05
ICITM15 0.91 0.86 0.96
IHLA_MMM5 0.76 0.59 0.97
perfusion_event 0.96 0.44 2.09
infliximab 1.49 0.77 2.97
IGPE_dep_log2P2 0.58 0.33 0.98

8.1.2.2 Non-linear interaction model

Open code
model_infection_any_GPE_dep_interaction_NL <- run(
  expr = brm_multiple(
    infection_any ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(I(GPE_dep_log2+2), bs = 'ps', k = 5) +
    s(I(GPE_dep_log2+2), by = infliximab, bs = 'ps', k = 5),
    family = bernoulli(),
    data = data_imputed,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_infection_any_GPE_dep_interaction_NL",
  reuse = TRUE
)

summary(model_infection_any_GPE_dep_interaction_NL, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
## Warning: There were 1 divergent transitions after warmup. Increasing
## adapt_delta above 0.95 may help. See
## http://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: infection_any ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(I(GPE_dep_log2 + 2), bs = "ps", k = 5) + s(I(GPE_dep_log2 + 2), by = infliximab, bs = "ps", k = 5) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Smoothing Spline Hyperparameters:
##                                   Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sIGPE_dep_log2P2_1)               0.39      0.36     0.02     1.78 1.01
## sds(sIGPE_dep_log2P2infliximab_1)     0.22      0.21     0.01     1.20 1.00
##                                   Bulk_ESS Tail_ESS
## sds(sIGPE_dep_log2P2_1)              23011    16212
## sds(sIGPE_dep_log2P2infliximab_1)    29739    16611
## 
## Regression Coefficients:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## Intercept                        -0.96      0.47    -1.89    -0.05 1.02
## male_sex                          0.20      0.37    -0.53     0.93 1.01
## Irec_ageM50                      -0.03      0.02    -0.06     0.01 1.01
## male_sex_donor                    0.15      0.36    -0.57     0.87 1.01
## IKDPIM50                          0.03      0.01     0.01     0.05 1.01
## ICITM15                          -0.10      0.03    -0.16    -0.04 1.01
## IHLA_MMM5                        -0.27      0.12    -0.53    -0.03 1.05
## perfusion_event                   0.02      0.41    -0.77     0.83 1.07
## infliximab                        0.01      3.96    -7.77     7.88 1.02
## sIGPE_dep_log2P2_1               -0.01      1.21    -2.46     2.72 1.01
## sIGPE_dep_log2P2:infliximab_1    -1.54      2.40    -6.31     3.14 1.02
## sIGPE_dep_log2P2:infliximab_2     1.73      1.92    -2.01     5.47 1.01
##                               Bulk_ESS Tail_ESS
## Intercept                        10823    27696
## male_sex                         35504    26209
## Irec_ageM50                      34966    27068
## male_sex_donor                   36837    27583
## IKDPIM50                         33808    25857
## ICITM15                          17002    25866
## IHLA_MMM5                         3867    19114
## perfusion_event                   2911    11736
## infliximab                       12595    17936
## sIGPE_dep_log2P2_1               24856    21147
## sIGPE_dep_log2P2:infliximab_1    14000    20327
## sIGPE_dep_log2P2:infliximab_2    14554    20883
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_infection_any_GPE_dep_interaction_NL)
##                           prior     class
##                          (flat)         b
##    normal(0, 0.280307006032291)         b
##     normal(0, 1.38219051128263)         b
##   normal(0, 0.0970980394231448)         b
##                    normal(0, 4)         b
##    normal(0, 0.190589762164436)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                          (flat)         b
##                          (flat)         b
##                          (flat)         b
##  normal(-0.307484699747961, 10) Intercept
##            student_t(3, 0, 2.5)       sds
##            student_t(3, 0, 0.6)       sds
##            student_t(3, 0, 0.3)       sds
##                                                       coef group resp dpar
##                                                                           
##                                                    ICITM15                
##                                                  IHLA_MMM5                
##                                                   IKDPIM50                
##                                                 infliximab                
##                                                Irec_ageM50                
##                                                   male_sex                
##                                             male_sex_donor                
##                                            perfusion_event                
##                                         sIGPE_dep_log2P2_1                
##                              sIGPE_dep_log2P2:infliximab_1                
##                              sIGPE_dep_log2P2:infliximab_2                
##                                                                           
##                                                                           
##                   s(I(GPE_dep_log2 + 2), bs = "ps", k = 5)                
##  s(I(GPE_dep_log2 + 2), by = infliximab, bs = "ps", k = 5)                
##  nlpar lb ub       source
##                   default
##                      user
##                      user
##                      user
##                      user
##                      user
##                      user
##                      user
##                      user
##              (vectorized)
##              (vectorized)
##              (vectorized)
##                      user
##         0         default
##         0            user
##         0            user

tr2 <- round(exp(fixef(model_infection_any_GPE_dep_interaction_NL, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)]), 3)

antibody_seq <- quantile(data_model_priorImpute$GPE_dep_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_dep_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_infection_any_GPE_dep_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get odds ratio for infliximab effect across antibody values
prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()
names(prediction) <- c('p05', 'p95')

infliximab_GPE_dep_log2_int <- quantile(
  exp((log(prediction$p95) - log(prediction$p05))/scaling_unit), 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPE_dep_log2_int, 3))
row.names(tr2)[8] <- 'infliximab:GPE_dep_log2'
colnames(tr2)[1] <- '(R)OR'
kableExtra::kable(tr2)
Table 14: Results from multivariable Bayesian logistic regression models incorporating a non-linear interaction between co-factor-dependent phosphatidylethanolamine IgG (aPE-dep IgG, GPE_dep_log2) and infliximab treatment on infection risk, while accounting for other covariates. Effects are presented as odds ratios (OR), with the last row reporting the ratio of odds ratios (ROR). The ROR represents the estimated ratio of infliximab’s treatment effect at the 95th versus the 5th percentile of aPE-dep IgG, quantifying the interaction effect.Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
(R)OR Q2.5 Q97.5
male_sex 1.220 0.590 2.539
Irec_ageM50 0.972 0.938 1.006
male_sex_donor 1.166 0.568 2.392
IKDPIM50 1.034 1.015 1.054
ICITM15 0.905 0.851 0.957
IHLA_MMM5 0.763 0.589 0.969
perfusion_event 1.018 0.463 2.299
infliximab:GPE_dep_log2 0.097 0.008 0.995

8.1.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_model_priorImpute$GPE_dep_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_dep_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_infection_any_GPE_dep_interaction_NL,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')), 
  GPE_dep_log2 = data_prediction$GPE_dep_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control"))) %>% 
  ggplot(aes(x = GPE_dep_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 1),
                     breaks = c(seq(0, 1, by = 0.2))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  labs(x = expression(log[2]~"(aPE IgG dep [OD])"), y = "Infection risk") +
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size=10),
        axis.title=element_text(size=12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") +
  
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 0),
    aes(x = GPE_dep_log2),
    sides = "b",
    color = "black",
    size = 0.12,
    inherit.aes = FALSE
  ) +
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 1),
    aes(x = GPE_dep_log2),
     sides = "t",
    color = "black",
    size = 0.12,
    inherit.aes = FALSE
  )  + 
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -3.134163 -1.039488

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_dep_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_infection_any_GPE_dep_interaction_NL,
    newdata = data_prediction)

tr_ctrl <- (logit(tr[,c(ncol(tr)/2)]) - logit(tr[,c(1)])) 
tr_infliximab <- (logit(tr[,c(ncol(tr))]) - logit(tr[,c(ncol(tr)/2)+1]))

post_fix <- data.frame(
  b_GPE_dep_log2 = tr_ctrl,
  b_GPE_dep_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = exp(b_GPE_dep_log2), 
         infliximab = exp(b_GPE_dep_log2_infliximab)) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(2)

CIS
##       control infliximab
## 2.5%     0.18       0.01
## 97.5%    4.70       0.48
## 50%      0.91       0.09
## 0.1%     0.07       0.00
## 99.9%   12.90       1.23

xpos <- 323

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect (OR) of aPE IgG dep increase (p05 to p95) on infection', 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(1/600, 1800)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(1/512, 1/64, 1/8, 1, 8, 64, 512),
                     labels = c("1/512", "1/64", "1/8", "1", "8", "64", "512")) +
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 240
xseq <- c(1/64, 1/8, 1, 8, 64, 512)

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]
antibody_seq
##        5%       25%       50%       75%       95% 
## -3.134163 -2.666576 -2.392137 -2.005782 -1.039488

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_dep_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_infection_any_GPE_dep_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')


CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% round(3)

CIS
##          p05   p25   p50   p75   p95
## 2.5%   1.147 1.005 0.824 0.492 0.066
## 97.5% 12.692 5.193 3.466 2.425 1.754
## 50%    3.731 2.260 1.676 1.097 0.361
## 0.1%   0.588 0.657 0.561 0.310 0.023
## 99.9% 24.985 8.400 5.455 3.839 4.295

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPE_dep_percentile') %>% 
  
  ggplot(aes(y = GPE_dep_percentile, x = value, fill = GPE_dep_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on infection risk (odds ratio)", 
         y = 'Percentile of aPE IgG dep value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(1/300, 1000)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(xseq),
                     labels = c("1/64", "1/8", "1", "8", "64", '512')) +
  
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
    annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Odds ratio: ", round(CIS[3,5], 2)),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Odds ratio: ", round(CIS[3,4], 2)),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Odds ratio: ", round(CIS[3,3], 2)),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Odds ratio: ", round(CIS[3,2],2 )),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Odds ratio: ", round(CIS[3,1], 2)),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", round(CIS[1,5], 2), ", ", 
                          round(CIS[2,5], 2), "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", round(CIS[1,4], 2), ", ", 
                          round(CIS[2,4], 2), "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", round(CIS[1,3], 2), ", ", 
                          round(CIS[2,3], 2), "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", round(CIS[2,2], 2), "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", round(CIS[1,1], 2), ", ", 
                          round(CIS[2,1], 2), "]"),
           color = cole[1] ) 

8.1.3.1 Figure merged

Open code
plotac <- 'sup_figure_10'
path <- "gitignore/figures"

fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 5: Co-factor dependent anti-phosphatidylethanolamine IgG (aPE-dep IgG) modulates the effect of infliximab on infection risk (A) Predicted infection risk by aPE-dep IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of aPE-dep IgG levels. (B) Posterior distribution of the estimated odds ratio comparing infection odds at the 95th vs. 5th percentile of aPE-dep IgG. (C) Posterior distribution of the effect of infliximab (odds ratio) on infection risk across aPE-dep IgG percentiles. Results are from a multivariable Bayesian logistic model with B-splines.

8.2 Infection by GPE_ind

8.2.1 Priors

8.2.1.1 Main effects model

Open code
priors_main <- c(
  create_prior("IGPE_ind_log2P3",
    2 / sd(data_model_priorImpute$GPE_ind_log2, na.rm = TRUE),
    coef = "IGPE_ind_log2P3"
  ),
   create_prior("IHLA_MMM5",
    2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$infection_any)), ", 10)"),
    class = "Intercept"
  )
)

8.2.1.2 Non-linear penalized interaction model

Open code
priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
               coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
               coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
               coef = "IKDPIM50"),
  create_prior("ICITM15",
               2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
               coef = "ICITM15"),
  create_prior("male_sex",
               4,
               coef = "male_sex"),
  create_prior("perfusion_event",
               4,
               coef = "perfusion_event"),
  create_prior("male_sex_donor",
               4,
               coef = "male_sex_donor"),
  
  create_prior("infliximab",
               4,
               coef = "infliximab"),

 set_prior("student_t(3, 0, 0.6)", class = "sds", 
           coef = 's(I(GPE_ind_log2 + 3), bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 0.3)", class = "sds",  
           coef = 's(I(GPE_ind_log2 + 3), by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$infection_any)), ", 10)"),
            class = "Intercept")
)

8.2.2 Models

8.2.2.1 Main effects models

Open code
model_infection_any_GPE_ind_main <- run(
  expr = brm_multiple(
    infection_any ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      I(GPE_ind_log2 + 3),
    family = bernoulli(),
    data = data_imputed,
    prior = priors_main,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_infection_any_GPE_ind_main",
  reuse = TRUE
)

summary(model_infection_any_GPE_ind_main, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: infection_any ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + I(GPE_ind_log2 + 3) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          -0.98      0.45    -1.90    -0.12 1.02    11019    25593
## male_sex            0.27      0.36    -0.44     1.00 1.01    37004    28448
## Irec_ageM50        -0.02      0.02    -0.06     0.01 1.01    35806    28429
## male_sex_donor      0.21      0.36    -0.48     0.92 1.01    36893    27040
## IKDPIM50            0.03      0.01     0.01     0.05 1.01    33713    27071
## ICITM15            -0.09      0.03    -0.15    -0.04 1.01    20286    25862
## IHLA_MMM5          -0.27      0.12    -0.52    -0.03 1.05     4283    14455
## perfusion_event    -0.00      0.40    -0.79     0.80 1.07     2779    11061
## infliximab          0.33      0.35    -0.34     1.03 1.01    35569    27304
## IGPE_ind_log2P3    -0.42      0.25    -0.94     0.04 1.01    39171    26823
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_infection_any_GPE_ind_main)
##                           prior     class            coef group resp dpar nlpar
##                          (flat)         b                                      
##    normal(0, 0.280307006032291)         b         ICITM15                      
##     normal(0, 2.72256264350783)         b IGPE_ind_log2P3                      
##     normal(0, 1.38219051128263)         b       IHLA_MMM5                      
##   normal(0, 0.0970980394231448)         b        IKDPIM50                      
##                    normal(0, 4)         b      infliximab                      
##    normal(0, 0.190589762164436)         b     Irec_ageM50                      
##                    normal(0, 4)         b        male_sex                      
##                    normal(0, 4)         b  male_sex_donor                      
##                    normal(0, 4)         b perfusion_event                      
##  normal(-0.307484699747961, 10) Intercept                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user

tr <- round(exp(fixef(model_infection_any_GPE_ind_main, robust = TRUE)[-1, c(1,3,4)]), 2)
colnames(tr)[1] <- 'OR'
kableExtra::kable(tr)
Table 15: Results from multivariable Bayesian logistic regression models examining the main effects of predictors, including infliximab treatment and log2-transformed co-factor-independent phosphatidylethanolamine IgG (GPE_ind_log2), on infection risk, without interaction terms. The odds ratio (OR) represents the estimated change in the odds of infection per one-unit increase in the predictor. OR: odds ratio; Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
OR Q2.5 Q97.5
male_sex 1.32 0.64 2.71
Irec_ageM50 0.98 0.94 1.01
male_sex_donor 1.23 0.62 2.51
IKDPIM50 1.03 1.01 1.05
ICITM15 0.91 0.86 0.96
IHLA_MMM5 0.77 0.59 0.97
perfusion_event 1.00 0.45 2.23
infliximab 1.39 0.71 2.79
IGPE_ind_log2P3 0.66 0.39 1.05

8.2.2.2 Non-linear interaction model

Open code
model_infection_any_GPE_ind_interaction_NL <- run(
  expr = brm_multiple(
    infection_any ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(I(GPE_ind_log2+3), bs = 'ps', k = 5) +
    s(I(GPE_ind_log2+3), by = infliximab, bs = 'ps', k = 5),
    family = bernoulli(),
    data = data_imputed,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_infection_any_GPE_ind_interaction_NL",
  reuse = TRUE
)

summary(model_infection_any_GPE_ind_interaction_NL, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: infection_any ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(I(GPE_ind_log2 + 3), bs = "ps", k = 5) + s(I(GPE_ind_log2 + 3), by = infliximab, bs = "ps", k = 5) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Smoothing Spline Hyperparameters:
##                                   Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sIGPE_ind_log2P3_1)               0.36      0.33     0.02     1.69 1.01
## sds(sIGPE_ind_log2P3infliximab_1)     0.22      0.21     0.01     1.14 1.00
##                                   Bulk_ESS Tail_ESS
## sds(sIGPE_ind_log2P3_1)              20777    16750
## sds(sIGPE_ind_log2P3infliximab_1)    29345    15825
## 
## Regression Coefficients:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## Intercept                        -1.01      0.47    -1.96    -0.12 1.03
## male_sex                          0.23      0.37    -0.49     0.97 1.01
## Irec_ageM50                      -0.02      0.02    -0.06     0.01 1.01
## male_sex_donor                    0.20      0.36    -0.51     0.91 1.01
## IKDPIM50                          0.03      0.01     0.01     0.05 1.01
## ICITM15                          -0.10      0.03    -0.16    -0.04 1.01
## IHLA_MMM5                        -0.27      0.13    -0.54    -0.03 1.05
## perfusion_event                   0.06      0.42    -0.77     0.89 1.07
## infliximab                        0.02      3.94    -7.59     7.86 1.02
## sIGPE_ind_log2P3_1               -0.00      0.94    -2.19     1.95 1.01
## sIGPE_ind_log2P3:infliximab_1    -1.90      2.38    -6.69     2.67 1.02
## sIGPE_ind_log2P3:infliximab_2     2.02      2.37    -2.61     6.62 1.02
##                               Bulk_ESS Tail_ESS
## Intercept                         9152    25359
## male_sex                         35120    26888
## Irec_ageM50                      33206    26289
## male_sex_donor                   34551    26083
## IKDPIM50                         32183    26588
## ICITM15                          16827    25092
## IHLA_MMM5                         3866    13123
## perfusion_event                   2824    11080
## infliximab                       12055    17429
## sIGPE_ind_log2P3_1               25340    18221
## sIGPE_ind_log2P3:infliximab_1    13678    20478
## sIGPE_ind_log2P3:infliximab_2    13739    20092
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_infection_any_GPE_ind_interaction_NL)
##                           prior     class
##                          (flat)         b
##    normal(0, 0.280307006032291)         b
##     normal(0, 1.38219051128263)         b
##   normal(0, 0.0970980394231448)         b
##                    normal(0, 4)         b
##    normal(0, 0.190589762164436)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                          (flat)         b
##                          (flat)         b
##                          (flat)         b
##  normal(-0.307484699747961, 10) Intercept
##            student_t(3, 0, 2.5)       sds
##            student_t(3, 0, 0.6)       sds
##            student_t(3, 0, 0.3)       sds
##                                                       coef group resp dpar
##                                                                           
##                                                    ICITM15                
##                                                  IHLA_MMM5                
##                                                   IKDPIM50                
##                                                 infliximab                
##                                                Irec_ageM50                
##                                                   male_sex                
##                                             male_sex_donor                
##                                            perfusion_event                
##                                         sIGPE_ind_log2P3_1                
##                              sIGPE_ind_log2P3:infliximab_1                
##                              sIGPE_ind_log2P3:infliximab_2                
##                                                                           
##                                                                           
##                   s(I(GPE_ind_log2 + 3), bs = "ps", k = 5)                
##  s(I(GPE_ind_log2 + 3), by = infliximab, bs = "ps", k = 5)                
##  nlpar lb ub       source
##                   default
##                      user
##                      user
##                      user
##                      user
##                      user
##                      user
##                      user
##                      user
##              (vectorized)
##              (vectorized)
##              (vectorized)
##                      user
##         0         default
##         0            user
##         0            user

tr2 <- round(exp(fixef(model_infection_any_GPE_ind_interaction_NL, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)]), 2)

antibody_seq <- quantile(data_model_priorImpute$GPE_ind_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_ind_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_infection_any_GPE_ind_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get odds ratio for infliximab effect across antibody values
prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()
names(prediction) <- c('p05', 'p95')

infliximab_GPE_ind_log2_int <- quantile(
  exp((log(prediction$p95) - log(prediction$p05))/scaling_unit), 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPE_ind_log2_int, 4))
row.names(tr2)[8] <- 'infliximab:GPE_ind_log2'
colnames(tr2)[1] <- '(R)OR'
kableExtra::kable(tr2)
Table 16: Results from multivariable Bayesian logistic regression models incorporating a non-linear interaction between co-factor-independent phosphatidylethanolamine IgG (aPE-ind IgG, GPE_ind_log2) and infliximab treatment on infection risk, while accounting for other covariates. Effects are presented as odds ratios (OR), with the last row reporting the ratio of odds ratios (ROR). The ROR represents the estimated ratio of infliximab’s treatment effect at the 95th versus the 5th percentile of aPE-ind IgG, quantifying the interaction effect.Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
(R)OR Q2.5 Q97.5
male_sex 1.2600 0.610 2.6400
Irec_ageM50 0.9800 0.940 1.0100
male_sex_donor 1.2200 0.600 2.4900
IKDPIM50 1.0300 1.010 1.0500
ICITM15 0.9100 0.850 0.9600
IHLA_MMM5 0.7600 0.590 0.9700
perfusion_event 1.0600 0.460 2.4300
infliximab:GPE_ind_log2 0.0751 0.005 0.7543

8.2.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_model_priorImpute$GPE_ind_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_ind_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_infection_any_GPE_ind_interaction_NL,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')), 
  GPE_ind_log2 = data_prediction$GPE_ind_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control"))) %>% 
  ggplot(aes(x = GPE_ind_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 1),
                     breaks = c(seq(0, 1, by = 0.2))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  labs(x = expression(log[2]~"(aPE IgG ind [OD])"), y = "Infection risk") +
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size=10),
        axis.title=element_text(size=12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") +
  
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 0),
    aes(x = GPE_ind_log2),
    sides = "b",
    color = "black",
    size = 0.12,
    inherit.aes = FALSE
  ) +
  geom_rug(
    data = data_model_priorImpute %>% filter(DGF == 1),
    aes(x = GPE_ind_log2),
     sides = "t",
    color = "black",
    size = 0.12,
    inherit.aes = FALSE
  )  + 
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -3.654722 -1.490602

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_ind_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_infection_any_GPE_ind_interaction_NL,
    newdata = data_prediction)

tr_ctrl <- (logit(tr[,c(ncol(tr)/2)]) - logit(tr[,c(1)])) 
tr_infliximab <- (logit(tr[,c(ncol(tr))]) - logit(tr[,c(ncol(tr)/2)+1]))

post_fix <- data.frame(
  b_GPE_ind_log2 = tr_ctrl,
  b_GPE_ind_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = exp(b_GPE_ind_log2), 
         infliximab = exp(b_GPE_ind_log2_infliximab)) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(2)

CIS
##       control infliximab
## 2.5%     0.23       0.01
## 97.5%    3.76       0.47
## 50%      0.96       0.07
## 0.1%     0.10       0.00
## 99.9%    8.11       1.19

xpos <- 323

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect (OR) of aPE IgG ind increase (p05 to p95) on infection', 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(1/600, 1800)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(1/512, 1/64, 1/8, 1, 8, 64, 512),
                     labels = c("1/512", "1/64", "1/8", "1", "8", "64", "512")) +
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 240
xseq <- c(1/64, 1/8, 1, 8, 64, 512)

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]
antibody_seq
##        5%       25%       50%       75%       95% 
## -3.654722 -3.411195 -3.164884 -2.708396 -1.490602

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPE_ind_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_infection_any_GPE_ind_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(2)

CIS
##         p05  p25  p50  p75  p95
## 2.5%   1.11 1.01 0.85 0.45 0.03
## 97.5%  9.08 5.56 3.68 2.26 1.33
## 50%    3.12 2.34 1.75 1.02 0.23
## 0.1%   0.63 0.65 0.57 0.27 0.01
## 99.9% 16.77 9.12 5.86 3.60 3.26

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPE_ind_percentile') %>% 
  
  ggplot(aes(y = GPE_ind_percentile, x = value, fill = GPE_ind_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on infection risk (odds ratio)", 
         y = 'Percentile of aPE IgG ind value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(1/300, 1000)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(xseq),
                     labels = c("1/64", "1/8", "1", "8", "64", '512')) +
  
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Odds ratio: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Odds ratio: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Odds ratio: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

8.2.3.1 Figure merged

Open code
plotac <- 'sup_figure_9'
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 6: Co-factor independent anti-phosphatidylethanolamine IgG (aPE-ind IgG) modulates the effect of infliximab on infection risk (A) Predicted infection risk by aPE-dep IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of aPE-ind IgG levels. (B) Posterior distribution of the estimated odds ratio comparing infection odds at the 95th vs. 5th percentile of aPE-ind IgG. (C) Posterior distribution of the effect of infliximab (odds ratio) on infection risk across aPE-ind IgG percentiles. Results are from a multivariable Bayesian logistic model with B-splines.

9 GFR models

9.1 by GPL_BC

9.1.1 Priors

Open code

GFR_sd <- sd(data_model_priorImpute$GFR_MDRD_3)
GFR_sd
## [1] 21.17099

9.1.1.1 Main effects model

Open code
priors_main <- c(
  create_prior("GPL_BC_log2",
    GFR_sd*(2*GFR_sd) / sd(data_model_priorImpute$GPL_BC_log2, na.rm = TRUE),
    coef = "GPL_BC_log2"
  ),
   create_prior("IHLA_MMM5",
    (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", mean(data_model_priorImpute$GFR_MDRD_3), ", 200)"),
    class = "Intercept"
  )
)

9.1.1.2 Non-linear penalized interaction model

Open code
priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"),
  create_prior("ICITM15",
               (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"),
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"),
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"),
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"),
  
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"),

 set_prior("student_t(3, 0, 12)", class = "sds", 
           coef = 's(GPL_BC_log2, bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 6)", class = "sds",  
           coef = 's(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", mean(data_model_priorImpute$GFR_MDRD_3), ", 200)"),
            class = "Intercept")
)

9.1.2 Models

9.1.2.1 Main effects models

Open code
model_GFR_MDRD_3_GPL_BC_main <- run(
  expr = brm_multiple(
    GFR_MDRD_3 ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      GPL_BC_log2,
    family = student(),
    data = data_imputed,
    prior = priors_main,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_GPL_BC_main",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_GPL_BC_main, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + GPL_BC_log2 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          53.13      4.10    45.10    61.27 1.01    16406    26395
## male_sex            1.79      3.27    -4.58     8.15 1.01    38414    27869
## Irec_ageM50         0.02      0.15    -0.28     0.32 1.01    37809    27823
## male_sex_donor      0.90      3.20    -5.44     7.24 1.01    38901    27640
## IKDPIM50           -0.24      0.08    -0.40    -0.09 1.01    36390    28400
## ICITM15             0.28      0.23    -0.17     0.72 1.02     9808    27230
## IHLA_MMM5          -0.74      1.09    -2.93     1.42 1.06     3247    11176
## perfusion_event    -6.16      3.55   -13.10     0.87 1.06     3134    15250
## infliximab         -2.12      3.18    -8.29     4.13 1.01    38526    28245
## GPL_BC_log2         0.26      1.11    -1.96     2.46 1.01    39429    27240
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    18.99      1.38    16.21    21.76 1.01    28098    22430
## nu       15.72      9.37     5.22    50.79 1.00    28333    25429
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_3_GPL_BC_main)
##                          prior     class            coef group resp dpar nlpar
##                         (flat)         b                                      
##    normal(0, 632.741442420408)         b     GPL_BC_log2                      
##    normal(0, 5.93437623501858)         b         ICITM15                      
##    normal(0, 29.2623385998383)         b       IHLA_MMM5                      
##    normal(0, 2.05566141844214)         b        IKDPIM50                      
##    normal(0, 84.6839516288788)         b      infliximab                      
##    normal(0, 4.03497355002315)         b     Irec_ageM50                      
##    normal(0, 84.6839516288788)         b        male_sex                      
##    normal(0, 84.6839516288788)         b  male_sex_donor                      
##    normal(0, 84.6839516288788)         b perfusion_event                      
##  normal(51.7227118644068, 200) Intercept                                      
##                  gamma(2, 0.1)        nu                                      
##          student_t(3, 0, 19.7)     sigma                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##   1    default
##   0    default

tr <- round(fixef(model_GFR_MDRD_3_GPL_BC_main, robust = TRUE)[-1, c(1,3,4)], 2)
colnames(tr)[1] <- 'Estimate'
kableExtra::kable(tr)
Table 17: Results from multivariable Bayesian robust regression model examining the main effects of predictors, including infliximab treatment and log2-transformed anti-cardiolipin IgG (GPL_BC_log2), on estimated glomerular filtration rate (eGFR), without interaction terms. Estimate represents the estimated change in eGFR per one-unit increase in the predictor. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex 1.79 -4.58 8.15
Irec_ageM50 0.02 -0.28 0.32
male_sex_donor 0.90 -5.44 7.24
IKDPIM50 -0.24 -0.40 -0.09
ICITM15 0.28 -0.17 0.72
IHLA_MMM5 -0.74 -2.93 1.42
perfusion_event -6.16 -13.10 0.87
infliximab -2.12 -8.29 4.13
GPL_BC_log2 0.26 -1.96 2.46

9.1.2.2 Non-linear interaction model

Open code
model_GFR_MDRD_3_GPL_BC_interaction_NL <- run(
  expr = brm_multiple(
    GFR_MDRD_3 ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(GPL_BC_log2, bs = 'ps', k = 5) +
    s(GPL_BC_log2, by = infliximab, bs = 'ps', k = 5),
    family = student(),
    data = data_imputed,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_GPL_BC_interaction_NL",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_GPL_BC_interaction_NL, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
## Warning: There were 2 divergent transitions after warmup. Increasing
## adapt_delta above 0.95 may help. See
## http://mc-stan.org/misc/warnings.html#divergent-transitions-after-warmup
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(GPL_BC_log2, bs = "ps", k = 5) + s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Smoothing Spline Hyperparameters:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sGPL_BC_log2_1)              10.58      7.05     0.88    34.65 1.01
## sds(sGPL_BC_log2infliximab_1)     4.20      3.89     0.20    19.80 1.01
##                               Bulk_ESS Tail_ESS
## sds(sGPL_BC_log2_1)              16287    11879
## sds(sGPL_BC_log2infliximab_1)    33049    17349
## 
## Regression Coefficients:
##                           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                    54.18      4.05    46.38    62.11 1.01    18434
## male_sex                      1.08      3.19    -5.20     7.36 1.01    47958
## Irec_ageM50                   0.03      0.15    -0.27     0.32 1.01    45592
## male_sex_donor               -0.61      3.23    -6.93     5.76 1.01    46094
## IKDPIM50                     -0.25      0.08    -0.41    -0.09 1.01    43069
## ICITM15                       0.28      0.22    -0.16     0.72 1.02    13311
## IHLA_MMM5                    -0.91      1.08    -3.06     1.23 1.06     3272
## perfusion_event              -5.86      3.48   -12.66     0.90 1.05     3684
## infliximab                    0.18     81.74  -161.71   162.22 1.01    14656
## sGPL_BC_log2_1               15.85     14.73   -15.35    57.10 1.01    24674
## sGPL_BC_log2:infliximab_1   -16.62     46.80  -110.53    75.97 1.01    14735
## sGPL_BC_log2:infliximab_2    13.23     29.32   -45.55    71.28 1.01    14950
##                           Tail_ESS
## Intercept                    29314
## male_sex                     26709
## Irec_ageM50                  26752
## male_sex_donor               27159
## IKDPIM50                     27230
## ICITM15                      26250
## IHLA_MMM5                    13786
## perfusion_event              14214
## infliximab                   19615
## sGPL_BC_log2_1               19574
## sGPL_BC_log2:infliximab_1    19281
## sGPL_BC_log2:infliximab_2    19912
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    18.49      1.39    15.60    21.23 1.01    30097    21798
## nu       15.01      9.04     4.92    49.64 1.00    33096    25294
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_3_GPL_BC_interaction_NL)
##                          prior     class
##                         (flat)         b
##    normal(0, 5.93437623501858)         b
##    normal(0, 29.2623385998383)         b
##    normal(0, 2.05566141844214)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 4.03497355002315)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 84.6839516288788)         b
##                         (flat)         b
##                         (flat)         b
##                         (flat)         b
##  normal(51.7227118644068, 200) Intercept
##                  gamma(2, 0.1)        nu
##          student_t(3, 0, 19.7)       sds
##            student_t(3, 0, 12)       sds
##             student_t(3, 0, 6)       sds
##          student_t(3, 0, 19.7)     sigma
##                                               coef group resp dpar nlpar lb ub
##                                                                               
##                                            ICITM15                            
##                                          IHLA_MMM5                            
##                                           IKDPIM50                            
##                                         infliximab                            
##                                        Irec_ageM50                            
##                                           male_sex                            
##                                     male_sex_donor                            
##                                    perfusion_event                            
##                                     sGPL_BC_log2_1                            
##                          sGPL_BC_log2:infliximab_1                            
##                          sGPL_BC_log2:infliximab_2                            
##                                                                               
##                                                                           1   
##                                                                           0   
##                   s(GPL_BC_log2, bs = "ps", k = 5)                        0   
##  s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)                        0   
##                                                                           0   
##        source
##       default
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##  (vectorized)
##  (vectorized)
##  (vectorized)
##          user
##       default
##       default
##          user
##          user
##       default

tr2 <- round(fixef(model_GFR_MDRD_3_GPL_BC_interaction_NL, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)], 2)

antibody_seq <- quantile(data_model_priorImpute$GPL_BC_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get Estimate for infliximab effect across antibody values
prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()

names(prediction) <- c('p05', 'p95')

infliximab_GPL_BC_log2_int <- quantile(
  (prediction$p95 - prediction$p05)/scaling_unit, 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:GPL_BC_log2'
kableExtra::kable(tr2)
Table 18: Results from a multivariable Bayesian robust regression model incorporating a non-linear interaction between anti-cardiolipin IgG (GPL_BC_log2) and infliximab treatment on estimated glomerular filtration rate (eGFR), while accounting for other covariates. Estimate represents the estimated change in eGFR per one-unit increase in the predictor. The last row (infliximab:GPL_BC_log2) reports the difference in infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgG, quantifying the interaction effect. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex 1.08 -5.20 7.36
Irec_ageM50 0.03 -0.27 0.32
male_sex_donor -0.61 -6.93 5.76
IKDPIM50 -0.25 -0.41 -0.09
ICITM15 0.28 -0.16 0.72
IHLA_MMM5 -0.91 -3.06 1.23
perfusion_event -5.86 -12.66 0.90
infliximab:GPL_BC_log2 -23.24 -42.13 -4.18

9.1.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_model_priorImpute$GPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')),
  GPL_BC_log2 = data_prediction$GPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control")), 
         `Q2.5` = if_else(`Q2.5` < 0, 0, `Q2.5`)) %>% 
  ggplot(aes(x = GPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 125),
                      breaks = c(seq(0, 125, by = 25))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  geom_point(data = data_model_priorImpute, 
             aes(x = GPL_BC_log2, y = GFR_MDRD_3, col = group, fill = group)) +
  
  labs(x = expression(log[2]~"(aCL IgG ["*mu*"g/ml])"), 
       y = "GFR (mL/min/1.73 m²)") +
  
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size = 10),
        axis.title=element_text(size = 12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") + 
  
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -1.801668  2.531821

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL,
    newdata = data_prediction)

tr_ctrl <- (tr[,c(ncol(tr)/2)] - tr[,c(1)]) 
tr_infliximab <- (tr[,c(ncol(tr))] - tr[,c(ncol(tr)/2)+1])

post_fix <- data.frame(
  b_GPL_BC_log2 = tr_ctrl,
  b_GPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = b_GPL_BC_log2, 
         infliximab = b_GPL_BC_log2_infliximab) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##       control infliximab
## 2.5%     -1.9      -27.6
## 97.5%    22.6        1.7
## 50%      10.3      -12.9
## 0.1%     -9.3      -36.1
## 99.9%    29.8       10.1

xpos <- 39.2

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect of aCL IgG increase (p05 to p95) on eGFR',
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(-50, 52)) +
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 30

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##         p05  p25   p50   p75   p95
## 2.5%   -3.3 -5.1  -7.7 -13.6 -28.9
## 97.5%  18.0  9.2   5.0   0.6  -3.3
## 50%     7.3  2.0  -1.3  -6.5 -15.9
## 0.1%  -10.1 -9.6 -11.5 -17.3 -36.6
## 99.9%  24.0 13.2   8.6   4.3   3.4

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPL_BC_percentile') %>% 
  
  ggplot(aes(y = GPL_BC_percentile, x = value, fill = GPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on GFR (mL/min/1.73 m²)", 
         y = 'Percentile of aCL IgG value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(-40, 40)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Estimate: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Estimate: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Estimate: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

9.1.3.1 Figure merged

Open code
plotac <- 'sup_figure_4'
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 7: Supplementary Figure 4: Anti-cardiolipin IgG modulates the effect of infliximab on estimated glomerular filtration rate (eGFR). (A) Predicted eGFR by anti-cardiolipin IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of cardiolipin levels. (B) Posterior distribution of the estimated change in eGFR when anti-cardiolipin IgG increases from the 5th to the 95th sample percentile. (C) Posterior distribution of the effect of infliximab on eGFR across anti-cardiolipin IgG percentiles. Results are from a multivariable Bayesian robust regression with B-splines.

9.2 by GPL_BC - nonimputed failures

This sensitivity analysis aims to evaluate whether the results are consistent when the eGFR definition is modified — using the last available value before graft failure within two years of follow-up, instead of imputing a fixed value of 10 for all failures

9.2.1 Priors

Open code

GFR_sd <- sd(data_model_priorImpute$GFR_MDRD_2)
GFR_sd
## [1] 20.65503

9.2.1.1 Main effects model

Open code
priors_main <- c(
  create_prior("GPL_BC_log2",
    GFR_sd*(2*GFR_sd) / sd(data_model_priorImpute$GPL_BC_log2, na.rm = TRUE),
    coef = "GPL_BC_log2"
  ),
   create_prior("IHLA_MMM5",
    (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", mean(data_model_priorImpute$GFR_MDRD_2), ", 200)"),
    class = "Intercept"
  )
)

9.2.1.2 Non-linear penalized interaction model

Open code
priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"),
  create_prior("ICITM15",
               (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"),
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"),
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"),
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"),
  
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"),

 set_prior("student_t(3, 0, 12)", class = "sds", 
           coef = 's(GPL_BC_log2, bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 6)", class = "sds",  
           coef = 's(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", mean(data_model_priorImpute$GFR_MDRD_2), ", 200)"),
            class = "Intercept")
)

9.2.2 Models

9.2.2.1 Main effects models

Open code
model_GFR_MDRD_2_GPL_BC_main <- run(
  expr = brm_multiple(
    GFR_MDRD_2 ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      GPL_BC_log2,
    family = student(),
    data = data_imputed,
    prior = priors_main,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_2_GPL_BC_main",
  reuse = TRUE
)

summary(model_GFR_MDRD_2_GPL_BC_main, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_2 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + GPL_BC_log2 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          53.49      4.03    45.64    61.41 1.02    15993    26757
## male_sex            1.88      3.18    -4.38     8.08 1.01    38010    26941
## Irec_ageM50         0.02      0.15    -0.27     0.30 1.01    36815    27819
## male_sex_donor      1.01      3.17    -5.20     7.19 1.01    37320    27500
## IKDPIM50           -0.24      0.08    -0.39    -0.09 1.00    35494    27775
## ICITM15             0.28      0.22    -0.15     0.72 1.02    10627    25966
## IHLA_MMM5          -0.76      1.08    -2.88     1.33 1.06     3338    17289
## perfusion_event    -6.23      3.39   -12.97     0.51 1.06     3399    13342
## infliximab         -2.48      3.08    -8.56     3.59 1.01    35765    27828
## GPL_BC_log2         0.12      1.10    -2.03     2.26 1.01    38197    27820
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    18.60      1.30    16.00    21.20 1.00    27749    23012
## nu       17.32     10.08     5.74    52.43 1.00    29322    25293
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_2_GPL_BC_main)
##                          prior     class            coef group resp dpar nlpar
##                         (flat)         b                                      
##    normal(0, 602.276328897438)         b     GPL_BC_log2                      
##    normal(0, 5.78975030085778)         b         ICITM15                      
##    normal(0, 28.5491898394416)         b       IHLA_MMM5                      
##    normal(0, 2.00556315348783)         b        IKDPIM50                      
##    normal(0, 82.6201297329089)         b      infliximab                      
##    normal(0, 3.93663771894749)         b     Irec_ageM50                      
##    normal(0, 82.6201297329089)         b        male_sex                      
##    normal(0, 82.6201297329089)         b  male_sex_donor                      
##    normal(0, 82.6201297329089)         b perfusion_event                      
##  normal(52.0419774011299, 200) Intercept                                      
##                  gamma(2, 0.1)        nu                                      
##          student_t(3, 0, 19.7)     sigma                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##   1    default
##   0    default

tr <- round(fixef(model_GFR_MDRD_2_GPL_BC_main, robust = TRUE)[-1, c(1,3,4)], 2)
colnames(tr)[1] <- 'Estimate'
kableExtra::kable(tr)
Table 19: Results from multivariable Bayesian robust regression model examining the main effects of predictors, including infliximab treatment and log2-transformed anti-cardiolipin IgG (GPL_BC_log2), on estimated glomerular filtration rate (eGFR; GFR_MDRD_2,). No imputation was applied in cases of graft failure, and no interaction terms were included. Estimate represents the estimated change in eGFR per one-unit increase in the predictor. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex 1.88 -4.38 8.08
Irec_ageM50 0.02 -0.27 0.30
male_sex_donor 1.01 -5.20 7.19
IKDPIM50 -0.24 -0.39 -0.09
ICITM15 0.28 -0.15 0.72
IHLA_MMM5 -0.76 -2.88 1.33
perfusion_event -6.23 -12.97 0.51
infliximab -2.48 -8.56 3.59
GPL_BC_log2 0.12 -2.03 2.26

9.2.2.2 Non-linear interaction model

Open code
model_GFR_MDRD_2_GPL_BC_interaction_NL <- run(
  expr = brm_multiple(
    GFR_MDRD_2 ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(GPL_BC_log2, bs = 'ps', k = 5) +
    s(GPL_BC_log2, by = infliximab, bs = 'ps', k = 5),
    family = student(),
    data = data_imputed,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_2_GPL_BC_interaction_NL",
  reuse = TRUE
)

summary(model_GFR_MDRD_2_GPL_BC_interaction_NL, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_2 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(GPL_BC_log2, bs = "ps", k = 5) + s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Smoothing Spline Hyperparameters:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sGPL_BC_log2_1)              10.47      6.87     1.01    34.89 1.01
## sds(sGPL_BC_log2infliximab_1)     4.17      3.86     0.20    19.45 1.00
##                               Bulk_ESS Tail_ESS
## sds(sGPL_BC_log2_1)              16663    12838
## sds(sGPL_BC_log2infliximab_1)    34184    17036
## 
## Regression Coefficients:
##                           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                    54.53      3.90    46.78    62.26 1.01    18178
## male_sex                      1.18      3.07    -4.93     7.30 1.01    47019
## Irec_ageM50                   0.02      0.15    -0.26     0.31 1.01    45913
## male_sex_donor               -0.52      3.16    -6.67     5.73 1.01    47158
## IKDPIM50                     -0.25      0.08    -0.41    -0.10 1.01    42963
## ICITM15                       0.29      0.21    -0.14     0.71 1.02    11961
## IHLA_MMM5                    -0.92      1.06    -3.03     1.17 1.06     3210
## perfusion_event              -5.89      3.32   -12.43     0.68 1.05     4191
## infliximab                   -1.23     80.69  -159.19   157.37 1.01    14948
## sGPL_BC_log2_1               14.87     14.78   -15.57    55.53 1.01    24535
## sGPL_BC_log2:infliximab_1   -15.78     46.50  -106.59    74.82 1.01    15218
## sGPL_BC_log2:infliximab_2    12.64     28.58   -44.34    68.43 1.01    15240
##                           Tail_ESS
## Intercept                    27334
## male_sex                     26089
## Irec_ageM50                  27422
## male_sex_donor               27864
## IKDPIM50                     27758
## ICITM15                      26006
## IHLA_MMM5                    14291
## perfusion_event              21697
## infliximab                   20385
## sGPL_BC_log2_1               19710
## sGPL_BC_log2:infliximab_1    21475
## sGPL_BC_log2:infliximab_2    21127
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    18.07      1.32    15.41    20.67 1.01    32835    24576
## nu       15.88      9.41     5.26    50.45 1.00    35140    26447
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_2_GPL_BC_interaction_NL)
##                          prior     class
##                         (flat)         b
##    normal(0, 5.78975030085778)         b
##    normal(0, 28.5491898394416)         b
##    normal(0, 2.00556315348783)         b
##    normal(0, 82.6201297329089)         b
##    normal(0, 3.93663771894749)         b
##    normal(0, 82.6201297329089)         b
##    normal(0, 82.6201297329089)         b
##    normal(0, 82.6201297329089)         b
##                         (flat)         b
##                         (flat)         b
##                         (flat)         b
##  normal(52.0419774011299, 200) Intercept
##                  gamma(2, 0.1)        nu
##          student_t(3, 0, 19.7)       sds
##            student_t(3, 0, 12)       sds
##             student_t(3, 0, 6)       sds
##          student_t(3, 0, 19.7)     sigma
##                                               coef group resp dpar nlpar lb ub
##                                                                               
##                                            ICITM15                            
##                                          IHLA_MMM5                            
##                                           IKDPIM50                            
##                                         infliximab                            
##                                        Irec_ageM50                            
##                                           male_sex                            
##                                     male_sex_donor                            
##                                    perfusion_event                            
##                                     sGPL_BC_log2_1                            
##                          sGPL_BC_log2:infliximab_1                            
##                          sGPL_BC_log2:infliximab_2                            
##                                                                               
##                                                                           1   
##                                                                           0   
##                   s(GPL_BC_log2, bs = "ps", k = 5)                        0   
##  s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)                        0   
##                                                                           0   
##        source
##       default
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##  (vectorized)
##  (vectorized)
##  (vectorized)
##          user
##       default
##       default
##          user
##          user
##       default

tr2 <- round(fixef(model_GFR_MDRD_2_GPL_BC_interaction_NL, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)], 2)

antibody_seq <- quantile(data_model_priorImpute$GPL_BC_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_2_GPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get Estimate for infliximab effect across antibody values
prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()

names(prediction) <- c('p05', 'p95')

infliximab_GPL_BC_log2_int <- quantile(
  (prediction$p95 - prediction$p05)/scaling_unit, 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:GPL_BC_log2'
kableExtra::kable(tr2)
Table 20: Results from a multivariable Bayesian robust regression model incorporating a non-linear interaction between anti-cardiolipin IgG (GPL_BC_log2) and infliximab treatment on estimated glomerular filtration rate (GFR_MDRD_2), while accounting for other covariates. No imputation was applied in cases of graft failure (the last eGFR value prior graft failure or death was used). Estimate represents the estimated change in eGFR per one-unit increase in the predictor. The last row (infliximab:GPL_BC_log2) reports the difference in infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgG, quantifying the interaction effect. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex 1.18 -4.93 7.30
Irec_ageM50 0.02 -0.26 0.31
male_sex_donor -0.52 -6.67 5.73
IKDPIM50 -0.25 -0.41 -0.10
ICITM15 0.29 -0.14 0.71
IHLA_MMM5 -0.92 -3.03 1.17
perfusion_event -5.89 -12.43 0.68
infliximab:GPL_BC_log2 -22.04 -40.46 -3.95

9.2.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_model_priorImpute$GPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_GFR_MDRD_2_GPL_BC_interaction_NL,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')),
  GPL_BC_log2 = data_prediction$GPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control")), 
         `Q2.5` = if_else(`Q2.5` < 0, 0, `Q2.5`)) %>% 
  ggplot(aes(x = GPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 125),
                      breaks = c(seq(0, 125, by = 25))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  geom_point(data = data_model_priorImpute, 
             aes(x = GPL_BC_log2, y = GFR_MDRD_2, col = group, fill = group)) +
  
  labs(x = expression(log[2]~"(aCL IgG ["*mu*"g/ml])"), 
       y = "GFR (mL/min/1.73 m²)") +
  
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size = 10),
        axis.title=element_text(size = 12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") + 
  
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -1.801668  2.531821

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_2_GPL_BC_interaction_NL,
    newdata = data_prediction)

tr_ctrl <- (tr[,c(ncol(tr)/2)] - tr[,c(1)]) 
tr_infliximab <- (tr[,c(ncol(tr))] - tr[,c(ncol(tr)/2)+1])

post_fix <- data.frame(
  b_GPL_BC_log2 = tr_ctrl,
  b_GPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = b_GPL_BC_log2, 
         infliximab = b_GPL_BC_log2_infliximab) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##       control infliximab
## 2.5%     -2.7      -27.7
## 97.5%    20.8        1.3
## 50%       9.0      -13.1
## 0.1%     -9.1      -36.6
## 99.9%    27.7        9.7

xpos <- 39.2

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect of aCL IgG increase (p05 to p95) on eGFR',
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(-50, 52)) +
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 30

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_2_GPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##         p05  p25   p50   p75   p95
## 2.5%   -3.9 -5.5  -8.0 -13.5 -28.2
## 97.5%  16.7  8.3   4.4   0.2  -3.5
## 50%     6.4  1.4  -1.8  -6.6 -15.7
## 0.1%  -10.0 -9.6 -11.5 -17.7 -35.9
## 99.9%  22.8 12.4   8.1   4.3   3.5

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPL_BC_percentile') %>% 
  
  ggplot(aes(y = GPL_BC_percentile, x = value, fill = GPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on GFR (mL/min/1.73 m²)", 
         y = 'Percentile of aCL IgG value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(-40, 40)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Estimate: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Estimate: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Estimate: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

9.2.3.1 Figure merged

Open code
plotac <- 'sup_figure_7'
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 8: Supplemetary Figure 7: Anti-cardiolipin IgG modulates the effect of infliximab on estimated glomerular filtration rate (eGFR); unlike in Supplementary Figure 4, no imputation was applied in cases of graft failure. (A) Predicted eGFR by anti-cardiolipin IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of cardiolipin levels. (B) Posterior distribution of the estimated change in eGFR when anti-cardiolipin IgG increases from the 5th to the 95th sample percentile. (C) Posterior distribution of the effect of infliximab on eGFR across anti-cardiolipin IgG percentiles. Results are from a multivariable Bayesian robust regression with B-splines.

9.3 by MPL_BC

Open code

GFR_sd <- sd(data_model_priorImpute$GFR_MDRD_3)
GFR_sd
## [1] 21.17099

9.3.1 Priors

9.3.1.1 Main effects model

Open code
priors_main <- c(
  create_prior("IMPL_BC_log2M2",
    (2*GFR_sd) / sd(data_model_priorImpute$MPL_BC_log2, na.rm = TRUE),
    coef = "IMPL_BC_log2M2"
  ),
   create_prior("IHLA_MMM5",
    (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", mean(data_model_priorImpute$GFR_MDRD_3), ", 200)"),
    class = "Intercept"
  )
)

9.3.1.2 Non-linear penalized interaction model

Open code
priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"),
  create_prior("ICITM15",
               (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"),
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"),
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"),
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"),
  
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"),

 set_prior("student_t(3, 0, 12)", class = "sds", 
           coef = 's(I(MPL_BC_log2 - 2), bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 6)", class = "sds",  
           coef = 's(I(MPL_BC_log2 - 2), by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", mean(data_model_priorImpute$GFR_MDRD_3), ", 200)"),
            class = "Intercept")
)

9.3.2 Models

9.3.2.1 Main effects models

Open code
model_GFR_MDRD_3_MPL_BC_main <- run(
  expr = brm_multiple(
    GFR_MDRD_3 ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      I(MPL_BC_log2 - 2),
    family = student(),
    data = data_imputed,
    prior = priors_main,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_MPL_BC_main",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_MPL_BC_main, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + I(MPL_BC_log2 - 2) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          53.98      4.12    45.84    62.07 1.01    17881    26136
## male_sex            1.39      3.25    -5.09     7.91 1.01    38469    28129
## Irec_ageM50         0.02      0.15    -0.28     0.32 1.01    38402    27141
## male_sex_donor      0.66      3.21    -5.57     6.94 1.01    37506    27707
## IKDPIM50           -0.24      0.08    -0.39    -0.08 1.01    36437    27843
## ICITM15             0.31      0.22    -0.13     0.75 1.02    10533    25501
## IHLA_MMM5          -0.73      1.10    -2.89     1.43 1.06     3345    12707
## perfusion_event    -6.70      3.50   -13.61     0.23 1.06     3314    12070
## infliximab         -2.62      3.18    -8.94     3.67 1.01    37498    27533
## IMPL_BC_log2M2     -0.82      1.03    -2.82     1.23 1.01    36554    27952
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    18.91      1.39    16.08    21.65 1.01    27313    23629
## nu       15.11      9.07     5.10    49.59 1.00    29833    26721
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_3_MPL_BC_main)
##                          prior     class            coef group resp dpar nlpar
##                         (flat)         b                                      
##    normal(0, 5.93437623501858)         b         ICITM15                      
##    normal(0, 29.2623385998383)         b       IHLA_MMM5                      
##    normal(0, 2.05566141844214)         b        IKDPIM50                      
##    normal(0, 27.0849589154158)         b  IMPL_BC_log2M2                      
##    normal(0, 84.6839516288788)         b      infliximab                      
##    normal(0, 4.03497355002315)         b     Irec_ageM50                      
##    normal(0, 84.6839516288788)         b        male_sex                      
##    normal(0, 84.6839516288788)         b  male_sex_donor                      
##    normal(0, 84.6839516288788)         b perfusion_event                      
##  normal(51.7227118644068, 200) Intercept                                      
##                  gamma(2, 0.1)        nu                                      
##          student_t(3, 0, 19.7)     sigma                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##   1    default
##   0    default

tr <- round(fixef(model_GFR_MDRD_3_MPL_BC_main, robust = TRUE)[-1, c(1,3,4)], 2)
colnames(tr)[1] <- 'Estimate'
kableExtra::kable(tr)
Table 21: Results from multivariable Bayesian robust regression models examining the main effects of predictors, including infliximab treatment and log2-transformed anti-cardiolipin IgM (MPL_BC_log2), on estimated glomerular filtration rate (eGFR), without interaction terms. Estimate represents the estimated change in eGFR per one-unit increase in the predictor. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex 1.39 -5.09 7.91
Irec_ageM50 0.02 -0.28 0.32
male_sex_donor 0.66 -5.57 6.94
IKDPIM50 -0.24 -0.39 -0.08
ICITM15 0.31 -0.13 0.75
IHLA_MMM5 -0.73 -2.89 1.43
perfusion_event -6.70 -13.61 0.23
infliximab -2.62 -8.94 3.67
IMPL_BC_log2M2 -0.82 -2.82 1.23

9.3.2.2 Non-linear interaction model

Open code

model_GFR_MDRD_3_MPL_BC_interaction_NL <- run(
  expr = brm_multiple(
    GFR_MDRD_3 ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(I(MPL_BC_log2-2), bs = 'ps', k = 5) +
    s(I(MPL_BC_log2-2), by = infliximab, bs = 'ps', k = 5),
    family = student(),
    data = data_imputed,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 2000, warmup = 1900,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_MPL_BC_interaction_NL",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_MPL_BC_interaction_NL, robust = TRUE)
## Warning: Parts of the model have not converged (some Rhats are > 1.05). Be
## careful when analysing the results! We recommend running more iterations and/or
## setting stronger priors.
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(I(MPL_BC_log2 - 2), bs = "ps", k = 5) + s(I(MPL_BC_log2 - 2), by = infliximab, bs = "ps", k = 5) 
##    Data: data_imputed (Number of observations: 177) 
##   Draws: 320 chains, each with iter = 2000; warmup = 1900; thin = 1;
##          total post-warmup draws = 32000
## 
## Smoothing Spline Hyperparameters:
##                                  Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sIMPL_BC_log2M2_1)               5.47      5.12     0.27    26.04 1.01
## sds(sIMPL_BC_log2M2infliximab_1)     3.93      3.68     0.18    18.57 1.01
##                                  Bulk_ESS Tail_ESS
## sds(sIMPL_BC_log2M2_1)              23473    17212
## sds(sIMPL_BC_log2M2infliximab_1)    30914    16767
## 
## Regression Coefficients:
##                              Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                       53.59      4.13    45.45    61.86 1.01    18394
## male_sex                         1.61      3.25    -4.83     8.03 1.01    45915
## Irec_ageM50                      0.02      0.15    -0.27     0.32 1.01    45146
## male_sex_donor                   0.23      3.16    -6.09     6.54 1.01    47794
## IKDPIM50                        -0.24      0.08    -0.39    -0.08 1.01    44970
## ICITM15                          0.31      0.22    -0.13     0.75 1.02    11127
## IHLA_MMM5                       -0.75      1.10    -2.96     1.40 1.06     3332
## perfusion_event                 -7.06      3.54   -13.99    -0.11 1.06     3438
## infliximab                      -0.02     81.30  -163.96   161.14 1.01    14172
## sIMPL_BC_log2M2_1               -6.36     11.82   -33.38    20.35 1.01    28050
## sIMPL_BC_log2M2:infliximab_1   -12.17     59.34  -129.21   106.48 1.01    14377
## sIMPL_BC_log2M2:infliximab_2     8.82     20.16   -30.85    48.72 1.01    14618
##                              Tail_ESS
## Intercept                       28686
## male_sex                        26098
## Irec_ageM50                     26443
## male_sex_donor                  26205
## IKDPIM50                        28413
## ICITM15                         27006
## IHLA_MMM5                       17384
## perfusion_event                 14352
## infliximab                      18654
## sIMPL_BC_log2M2_1               21919
## sIMPL_BC_log2M2:infliximab_1    19452
## sIMPL_BC_log2M2:infliximab_2    20303
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    18.87      1.38    16.03    21.58 1.01    30872    23148
## nu       15.53      9.34     5.13    50.54 1.00    32611    27010
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_3_MPL_BC_interaction_NL)
##                          prior     class
##                         (flat)         b
##    normal(0, 5.93437623501858)         b
##    normal(0, 29.2623385998383)         b
##    normal(0, 2.05566141844214)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 4.03497355002315)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 84.6839516288788)         b
##                         (flat)         b
##                         (flat)         b
##                         (flat)         b
##  normal(51.7227118644068, 200) Intercept
##                  gamma(2, 0.1)        nu
##          student_t(3, 0, 19.7)       sds
##            student_t(3, 0, 12)       sds
##             student_t(3, 0, 6)       sds
##          student_t(3, 0, 19.7)     sigma
##                                                      coef group resp dpar nlpar
##                                                                                
##                                                   ICITM15                      
##                                                 IHLA_MMM5                      
##                                                  IKDPIM50                      
##                                                infliximab                      
##                                               Irec_ageM50                      
##                                                  male_sex                      
##                                            male_sex_donor                      
##                                           perfusion_event                      
##                                         sIMPL_BC_log2M2_1                      
##                              sIMPL_BC_log2M2:infliximab_1                      
##                              sIMPL_BC_log2M2:infliximab_2                      
##                                                                                
##                                                                                
##                                                                                
##                   s(I(MPL_BC_log2 - 2), bs = "ps", k = 5)                      
##  s(I(MPL_BC_log2 - 2), by = infliximab, bs = "ps", k = 5)                      
##                                                                                
##  lb ub       source
##             default
##                user
##                user
##                user
##                user
##                user
##                user
##                user
##                user
##        (vectorized)
##        (vectorized)
##        (vectorized)
##                user
##   1         default
##   0         default
##   0            user
##   0            user
##   0         default


tr2 <- round(fixef(model_GFR_MDRD_3_MPL_BC_interaction_NL, 
                   robust = TRUE)[-c(1,9:12), c(1,3,4)], 2)

antibody_seq <- quantile(data_model_priorImpute$MPL_BC_log2, probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_MPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get Estimate for infliximab effect across antibody values
prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()
names(prediction) <- c('p05', 'p95')

infliximab_MPL_BC_log2_int <- quantile((prediction$p95 - prediction$p05)/scaling_unit, 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_MPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:MPL_BC_log2'
kableExtra::kable(tr2)
Table 22: Results from a multivariable Bayesian robust regression model incorporating a non-linear interaction between anti-cardiolipin IgM (MPL_BC_log2) and infliximab treatment on estimated glomerular filtration rate (eGFR), while accounting for other covariates. Estimate represents the estimated change in eGFR per one-unit increase in the predictor. The last row (infliximab:MPL_BC_log2) reports the difference in infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgM, quantifying the interaction effect. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex 1.61 -4.83 8.03
Irec_ageM50 0.02 -0.27 0.32
male_sex_donor 0.23 -6.09 6.54
IKDPIM50 -0.24 -0.39 -0.08
ICITM15 0.31 -0.13 0.75
IHLA_MMM5 -0.75 -2.96 1.40
perfusion_event -7.06 -13.99 -0.11
infliximab:MPL_BC_log2 -17.91 -37.96 1.85

9.3.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_model_priorImpute$MPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
  male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_GFR_MDRD_3_MPL_BC_interaction_NL,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')), 
  MPL_BC_log2 = data_prediction$MPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control")), 
         `Q2.5` = if_else(`Q2.5` < 0, 0, `Q2.5`)) %>% 
  ggplot(aes(x = MPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 125),
                     breaks = c(seq(0, 125, by = 25))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  geom_point(data = data_model_priorImpute, 
             aes(x = MPL_BC_log2, y = GFR_MDRD_3, col = group, fill = group)) +
  
  labs(x = expression(log[2]~"(aCL IgM ["*mu*"g/ml])"), y = "GFR (mL/min/1.73 m²)") +
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size=10),
        axis.title=element_text(size=12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") +
  
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##         5%        95% 
## -0.3963058  4.5810923

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_MPL_BC_interaction_NL,
    newdata = data_prediction)

tr_ctrl <- (tr[,c(ncol(tr)/2)] - tr[,c(1)]) 

tr_infliximab <- (tr[,c(ncol(tr))] - tr[,c(ncol(tr)/2)+1])

post_fix <- data.frame(
  b_MPL_BC_log2 = tr_ctrl,
  b_MPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = (b_MPL_BC_log2), 
         infliximab = (b_MPL_BC_log2_infliximab)) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##       control infliximab
## 2.5%     -9.3      -28.1
## 97.5%    18.9        1.5
## 50%       4.7      -13.2
## 0.1%    -17.8      -37.7
## 99.9%    27.1        9.4

xpos <- 39.2

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect of aCL IgM increase (p05 to p95) on eGFR', 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(-50, 52)) +
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 30
xseq <- c(1/64, 1/8, 1, 8, 64, 512)

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]
antibody_seq
##         5%        25%        50%        75%        95% 
## -0.3963058  1.0933124  2.0432905  2.9421729  4.5810923

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `MPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_MPL_BC_interaction_NL,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()

names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##         p05   p25   p50   p75   p95
## 2.5%   -5.6  -6.5  -9.0 -13.4 -25.0
## 97.5%  17.1   8.0   3.7   1.4   0.2
## 50%     5.6   0.7  -2.7  -6.0 -12.3
## 0.1%  -11.9 -10.7 -12.5 -17.5 -33.0
## 99.9%  23.7  12.7   7.6   5.8   7.2

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'MPL_BC_percentile') %>% 
  
  ggplot(aes(y = MPL_BC_percentile, x = value, fill = MPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on GFR (mL/min/1.73 m²)", 
         y = 'Percentile of aCL IgM value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(-40, 40)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Estimate: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Estimate: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Estimate: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

9.3.3.1 Figure merged

Open code
plotac <- 'sup_figure_5'
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 9: Anti-cardiolipin IgM modulates the effect of infliximab on estimated glomerular filtration rate (eGFR). (A) Predicted eGFR by anti-cardiolipin IgM for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of cardiolipin levels. (B) Posterior distribution of the estimated change in eGFR when anti-cardiolipin IgM increases from the 5th to the 95th sample percentile. (C) Posterior distribution of the effect of infliximab on eGFR across anti-cardiolipin IgM percentiles. Results are from a multivariable Bayesian robust regression with B-splines.

10 Summarizing table

10.1 Logistic models

Open code

data_results_table_OR <- data.frame(
  
  outcome = c('DGF', 'DGF', 'infection', 'infection'),
  
  antibody = c('aCL IgG', 'aCL IgM', 'aPE-dep IgG', 'aPE-ind IgG'),
  
  `OR_p05` = c('0.13 [0.03, 0.49]',
               '0.23 [0.05, 0.94]',
               '3.73 [1.15, 12.69]',
               '3.12 [1.11, 9.08]'),
  
  OR_p25 = c('0.34 [0.13, 0.80]',
             '0.48 [0.19, 1.15]',
             '2.26 [1.005, 5.19]',
             '2.34 [1.01, 5.56]'),
  
  OR_p50 = c('0.59 [0.27, 1.25]',
             '0.77 [0.37, 1.61]',
             '1.68 [0.82, 3.47]',
             '1.75 [0.85, 3.68]'),
  
  OR_p75 = c('1.37 [0.60, 3.22]',
             '1.22 [0.52, 2.85]',
             '1.10 [0.49, 2.42]',
             '1.02 [0.45, 2.26]'),
  
  OR_p95 = c('6.24 [1.38, 30.32]',
             '2.79 [0.66, 12.59]',
             '0.36 [0.07, 1.75]',
             '0.23 [0.03, 1.33]'),
  
    p95_per_p05_ROR = c('46.6 [4.4, 610.0]',
                     '12.8 [1.1, 166.6]',
                     '0.097 [0.008, 0.995]',
                     '0.08 [0.005, 0.75]'
                     )
)



kableExtra::kable(data_results_table_OR)
Table 23: Table 1: Results from multivariable Bayesian logistic regression models with B-splines, estimating the non-linear interaction between natural antibody (antibody) levels and infliximab (IFX) treatment on the specified outcome. The antibody include anti-cardiolipin IgG or IgM (aCL IgG or IgM) and co-factor (ind)dependent anti-phosphatidylethanolamine (aPE-dep IgG and aPE-ind IgG respectively). IFX treatment effects (odds ratio [OR, infliximab vs. placebo]) are reported at the 5th, 25th, 50th, 75th, and 95th sample percentiles of nAb levels, each with [95% credible interval]. Interaction magnitudes are expressed as the ratio of odds ratios (ROR) for the IFX effect at the 95th vs. 5th percentile of the nAb level (p95_per_p05_ROR) with [95% credible intervals]
outcome antibody OR_p05 OR_p25 OR_p50 OR_p75 OR_p95 p95_per_p05_ROR
DGF aCL IgG 0.13 [0.03, 0.49] 0.34 [0.13, 0.80] 0.59 [0.27, 1.25] 1.37 [0.60, 3.22] 6.24 [1.38, 30.32] 46.6 [4.4, 610.0]
DGF aCL IgM 0.23 [0.05, 0.94] 0.48 [0.19, 1.15] 0.77 [0.37, 1.61] 1.22 [0.52, 2.85] 2.79 [0.66, 12.59] 12.8 [1.1, 166.6]
infection aPE-dep IgG 3.73 [1.15, 12.69] 2.26 [1.005, 5.19] 1.68 [0.82, 3.47] 1.10 [0.49, 2.42] 0.36 [0.07, 1.75] 0.097 [0.008, 0.995]
infection aPE-ind IgG 3.12 [1.11, 9.08] 2.34 [1.01, 5.56] 1.75 [0.85, 3.68] 1.02 [0.45, 2.26] 0.23 [0.03, 1.33] 0.08 [0.005, 0.75]

10.2 Linear models (eGFR)

Open code
data_results_table_cont <- data.frame(
  
  outcome = c('eGFR',  'eGFR', 'eGFR_last_prefail'),
  
  antibody = c('aCL IgG', 'aCL IgM', 'aCL IgG'),
  
  eff_p05 = c('7.3 [-3.3, 18.0]',
              '5.6 [-5.6, 17.1]',
              '6.4 [-3.9, 16.7]'),
  
  eff_p25 = c('2.0 [-5.1, 9.2]',
              '0.7 [-6.5, 8.0]',
              '1.4 [-5.5, 8.3]'),
  
  eff_p50 = c('-1.3 [-7.7, 5.0]',
              '-2.7 [-9.0, 3.7]',
              '-1.8 [-8.0, 4.4]'),
  
  eff_p75 = c('-6.5 [-13.6, 0.6]',
              '-6.0 [-13.4, 1.4]',
              '-6.6 [-13.5, 0.2]'),
  
  eff_p95 = c('-15.9 [-28.9, -3.3]',
              '-12.3 [-25.0, 0.2]',
              '-15.7 [-28.2, -3.5]'),
  
  p95_p05_DE = c('-23.2 [-42.1, -4.2]',
                 '-17.9 [-38.0, 1.9]',
                 '-22.0 [-40.5, 4.0]')
)

kableExtra::kable(data_results_table_cont)
Table 24: Table 2: Results from multivariable Bayesian robust regression models with B-splines, estimating the non-linear interaction between natural antibody (antibody) levels and infliximab (IFX) treatment on the specified outcome (either eGFR with imputed values in cases of graft failure within two years of follow-up [eGFR], or based on the last pre-failure eGFR measurement within two years [eGFR_last_prefail]). The antibody include anti-cardiolipin (aCL) IgG or IgM. IFX treatment absolute effect (eff, infliximab minus placebo) are reported at the 5th, 25th, 50th, 75th, and 95th sample percentiles of nAb levels, each with [95% credible interval]. Interaction magnitudes are expressed as the difference in treatment effects at the 95th minus 5th percentile (p95_p05_DE) with [95% credible intervals]
outcome antibody eff_p05 eff_p25 eff_p50 eff_p75 eff_p95 p95_p05_DE
eGFR aCL IgG 7.3 [-3.3, 18.0] 2.0 [-5.1, 9.2] -1.3 [-7.7, 5.0] -6.5 [-13.6, 0.6] -15.9 [-28.9, -3.3] -23.2 [-42.1, -4.2]
eGFR aCL IgM 5.6 [-5.6, 17.1] 0.7 [-6.5, 8.0] -2.7 [-9.0, 3.7] -6.0 [-13.4, 1.4] -12.3 [-25.0, 0.2] -17.9 [-38.0, 1.9]
eGFR_last_prefail aCL IgG 6.4 [-3.9, 16.7] 1.4 [-5.5, 8.3] -1.8 [-8.0, 4.4] -6.6 [-13.5, 0.2] -15.7 [-28.2, -3.5] -22.0 [-40.5, 4.0]

11 GFR: Mediation and AB-subgroup analysis

11.1 Define the subgroups

Open code

GPL_median <- median(data_model_priorImpute$GPL_BC_log2)
GPL_median
## [1] -0.1046076

data_imputed_highGPL <- data.frame(subset(
  data_first, 
  subset = data_first$GPL_BC_log2 >= GPL_median
))


data_imputed_lowGPL <- subset(
  data_first, 
  subset = data_first$GPL_BC_log2 < GPL_median
)

11.1.1 Priors

Open code
create_prior <- function(var, sigma, class = "b") {
  set_prior(paste0("normal(0, ", sigma, ")"), class = class, coef = var)
}

GFR_sd <- sd(data_model_priorImpute$GFR_MDRD_3)
GFR_sd
## [1] 21.17099

priors_subset <- c(
  
  create_prior("Irec_ageM50",
    (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE)
  ),
  create_prior("IHLA_MMM5",
    (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE)
  ),
  create_prior("IKDPIM50",
    (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE)
  ),
  create_prior("ICITM15",
    (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE)
  ),
  create_prior("male_sex",
    4*GFR_sd
  ),
  create_prior("perfusion_event",
    4*GFR_sd
  ),
  create_prior("male_sex_donor",
    4*GFR_sd
  ),
  create_prior("infliximab",
    4*GFR_sd
  ),

  set_prior(paste0("normal(", mean(data_model_priorImpute$GFR_MDRD_3), ", 200)"),
    class = "Intercept"
  )
)


create_prior <- function(var, sigma, class = "b",  resp = 'GFR_MDRD_3') {
  set_prior(paste0("normal(0, ", sigma, ")"), class = class, coef = var, resp = resp)
}

priors_mediation <- c(
  
  create_prior("Irec_ageM50",
    (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE)
  ),
  
  create_prior("Irec_ageM50",
    (2*GFR_sd) / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    resp = 'DGF'
  ),
  
  create_prior("IHLA_MMM5",
    (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE)
  ),
  create_prior("IHLA_MMM5",
    (2*GFR_sd) / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    resp = 'DGF'
  ),
  
  
  create_prior("IKDPIM50",
    (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE)
  ),
  create_prior("IKDPIM50",
    (2*GFR_sd) / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    resp = 'DGF'
  ),
  
  create_prior("ICITM15",
    (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE)
  ),
  create_prior("ICITM15",
    (2*GFR_sd) / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    resp = 'DGF'
  ),
  
  
  create_prior("male_sex",
    4*GFR_sd
  ),
  create_prior("male_sex",
    4*GFR_sd,
    resp = 'DGF'
  ),
  
  
  create_prior("perfusion_event",
    4*GFR_sd
  ),
  create_prior("perfusion_event",
    4*GFR_sd,
    resp = 'DGF'
  ),
  
  create_prior("male_sex_donor",
    4*GFR_sd
  ),
  create_prior("male_sex_donor",
    4*GFR_sd,
    resp = 'DGF'
  ),
  
  create_prior("infliximab",
    4*GFR_sd
  ),
  create_prior("infliximab",
    4*GFR_sd,
    resp = 'DGF'
  ),
  
  
  create_prior("DGF",
    4*GFR_sd
  )
)

11.1.2 GPL subgroup models

Open code
model_GFR_MDRD_3_GPLsubset_low <- run(
  expr = brm(
    GFR_MDRD_3 ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event + 
      infliximab,
    family = student(),
    data = data_imputed_lowGPL,
    prior = priors_subset,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 6000, warmup = 2000,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_GPLsubset_low",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_GPLsubset_low)
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab 
##    Data: data_imputed_lowGPL (Number of observations: 88) 
##   Draws: 4 chains, each with iter = 6000; warmup = 2000; thin = 1;
##          total post-warmup draws = 16000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          52.46      6.73    39.41    65.62 1.00    13509    12065
## male_sex           -0.09      5.11   -10.30     9.85 1.00    16331    11501
## Irec_ageM50         0.15      0.25    -0.33     0.64 1.00    14395    11553
## male_sex_donor     -1.99      4.89   -11.49     7.64 1.00    17367    12459
## IKDPIM50           -0.19      0.13    -0.45     0.07 1.00    13350    12286
## ICITM15             0.34      0.37    -0.39     1.07 1.00    15730    12347
## IHLA_MMM5          -2.18      1.74    -5.60     1.23 1.00    14721    12734
## perfusion_event    -9.86      5.01   -19.68    -0.08 1.00    16324    12384
## infliximab          6.69      4.93    -3.13    16.39 1.00    16223    11807
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    20.58      1.94    16.91    24.59 1.00    11642     9925
## nu       21.25     13.21     5.25    54.99 1.00    12313    10772
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).

model_GFR_MDRD_3_GPLsubset_high <- run(
  expr = brm(
    GFR_MDRD_3 ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      #GPL_BC_log2 +
      infliximab,
    family = student(),
    data = data_imputed_highGPL,
    prior = priors_subset,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 6000, warmup = 2000,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_GPLsubset_high",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_GPLsubset_high)
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab 
##    Data: data_imputed_highGPL (Number of observations: 89) 
##   Draws: 4 chains, each with iter = 6000; warmup = 2000; thin = 1;
##          total post-warmup draws = 16000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          55.29      4.70    46.08    64.60 1.00    15838    12336
## male_sex            2.39      4.19    -5.89    10.63 1.00    16915    12278
## Irec_ageM50        -0.12      0.19    -0.51     0.25 1.00    15773    11909
## male_sex_donor      2.09      4.33    -6.56    10.54 1.00    14250    11634
## IKDPIM50           -0.29      0.10    -0.49    -0.09 1.00    13747    12356
## ICITM15             0.22      0.27    -0.32     0.74 1.00    15390    12124
## IHLA_MMM5          -0.33      1.40    -3.09     2.39 1.00    16515    12259
## perfusion_event    -3.22      4.50   -11.96     5.71 1.00    14330    12062
## infliximab        -10.99      4.11   -19.06    -2.97 1.00    15817    11701
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    16.97      1.85    13.32    20.70 1.00     9173     7985
## nu       17.87     12.93     3.75    52.17 1.00    10702     9238
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).

11.1.3 Mediation

Prepare table

Open code
# Model for the mediator DGF
m1 <- bf(
  DGF ~ scale(male_sex) +
    scale(rec_age) +
    scale(male_sex_donor) +
    scale(KDPI) +
    scale(CIT) +
    scale(HLA_MM) +
    scale(perfusion_event) +
    infliximab,
  family = bernoulli(link = "logit")
)


# Model for the outcome GFR
m2 <- bf(
  GFR_MDRD_3 ~ scale(male_sex) +
    scale(rec_age) +
    scale(male_sex_donor) +
    scale(KDPI) +
    scale(CIT) +
    scale(HLA_MM) +
    scale(perfusion_event) +
    infliximab +
    DGF,
  family = student()
)

## Joint model - Low GPL_BC
model_GFR_GPL_mediate_low <- run(
  expr = brm(
  m1 + m2 + set_rescor(FALSE),
  data = data_imputed_lowGPL,
  priors_mediation,
  backend = "cmdstanr",
  seed = 2025,
  cores = 4, chains = 4,
  iter = 6000, warmup = 2000,
  control = list(adapt_delta = 0.95), refresh = 0),
  path = "gitignore/run/model_GFR_2_GPL_mediate_low",
  reuse = TRUE
)


## Joint model - high GPL_BC
model_GFR_GPL_mediate_high <- run(
  expr = brm(
  m1 + m2 + set_rescor(FALSE),
  data = data_imputed_highGPL,
  priors_mediation,
  backend = "cmdstanr",
  seed = 2025,
  cores = 4, chains = 4,
  iter = 6000, warmup = 2000,
  control = list(adapt_delta = 0.95), refresh = 0),
  path = "gitignore/run/model_GFR_2_GPL_mediate_high",
  reuse = TRUE
)
  


post_fix_low <- as.data.frame(model_GFR_GPL_mediate_low) %>% 
  
  select(b_GFRMDRD3_Intercept, b_GFRMDRD3_infliximab, b_GFRMDRD3_DGF,
         b_DGF_Intercept, b_DGF_infliximab) %>% 
  
  mutate(predicted_DGF_ctrl = inv_logit(b_DGF_Intercept),
         predicted_DGF_infx = inv_logit(b_DGF_Intercept + b_DGF_infliximab)) %>% 
  
  mutate(
    indirect_pred_ctrl = b_GFRMDRD3_Intercept + 
      (b_GFRMDRD3_infliximab * mean(data_imputed_lowGPL$infliximab) ) +
      (b_GFRMDRD3_DGF * predicted_DGF_ctrl),
    
    indirect_pred_infx = b_GFRMDRD3_Intercept + 
      (b_GFRMDRD3_infliximab * mean(data_imputed_lowGPL$infliximab) ) +
      (b_GFRMDRD3_DGF * predicted_DGF_infx),
      ) %>% 
  
  mutate(
    `Direct Effect (ADE)`  = b_GFRMDRD3_infliximab,
    `Indirect Effect (ACME)` = indirect_pred_infx - indirect_pred_ctrl,
    `Mediator Effect` = b_GFRMDRD3_DGF,
    `Total Effect` = (
           (indirect_pred_infx - indirect_pred_ctrl) + b_GFRMDRD3_infliximab
           )
         ) %>% 
  select(`Direct Effect (ADE)`:`Total Effect`)

med_eff_low <- sapply(
  post_fix_low,
  function(p) quantile(p, probs = c(0.5, 0.025, 0.975))
  ) %>% t() 


post_fix_high <- as.data.frame(model_GFR_GPL_mediate_high) %>% 
  
  select(b_GFRMDRD3_Intercept, b_GFRMDRD3_infliximab, b_GFRMDRD3_DGF,
         b_DGF_Intercept, b_DGF_infliximab) %>% 
  
  mutate(predicted_DGF_ctrl = inv_logit(b_DGF_Intercept),
         predicted_DGF_infx = inv_logit(b_DGF_Intercept + b_DGF_infliximab)) %>% 
  
  mutate(
    indirect_pred_ctrl = b_GFRMDRD3_Intercept + 
      (b_GFRMDRD3_infliximab * mean(data_imputed_highGPL$infliximab) ) +
      (b_GFRMDRD3_DGF * predicted_DGF_ctrl),
    
    indirect_pred_infx = b_GFRMDRD3_Intercept + 
      (b_GFRMDRD3_infliximab * mean(data_imputed_highGPL$infliximab) ) +
      (b_GFRMDRD3_DGF * predicted_DGF_infx),
      ) %>% 
  
  mutate(
    `Direct Effect (ADE)`  = b_GFRMDRD3_infliximab,
    `Indirect Effect (ACME)` = indirect_pred_infx - indirect_pred_ctrl,
    `Mediator Effect` = b_GFRMDRD3_DGF,
    `Total Effect` = (
           (indirect_pred_infx - indirect_pred_ctrl) + b_GFRMDRD3_infliximab
           )
         ) %>% 
  select(`Direct Effect (ADE)`:`Total Effect`)


med_eff_high <- sapply(
  post_fix_high,
  function(p) quantile(p, probs = c(0.5, 0.025, 0.975))
  ) %>% t()


mediation_effect <- data.frame(
  effect = rownames(med_eff_low),
  `Low_aCL` = paste0(
    round(c(med_eff_low[, 1]), 1), 
    ' [', 
    round(c(med_eff_low[, 2]), 1), 
    ', ', 
    round(c(med_eff_low[, 3]), 1),
    ']'
    ),
  `High_aCL` = paste0(
    round(c(med_eff_high[, 1]), 1), 
    ' [', 
    round(c(med_eff_high[, 2]), 1), 
    ', ', 
    round(c(med_eff_high[, 3]), 1),
    ']'
    )
  )

Print table

Open code
kableExtra::kable(mediation_effect)
Table 25: Supplementary Table 7: Results from Bayesian mediation analyses assessing the direct and indirect effects of infliximab on estimated glomerular filtration rate (eGFR). The indirect effect operates through the impact of infliximab on delayed graft function (DGF) risk. Separate joint models were used for patients with anti-cardiolipin IgG (aCL IgG) below (Low aCL) and at/above the median (High aCL). The Average Direct Effect (ADE [95% credible interval]) estimates the direct effect of infliximab on eGFR, controlling for the indirect pathway via DGF and other covariates. The Average Causal Mediation Effect (ACME [95% credible interval]) quantifies the indirect effect via DGF. The Mediator Effect represents the effect of DGF on eGFR, adjusting for infliximab and other covariates. The Total Effect captures the overall impact of infliximab on eGFR, combining both direct and indirect pathways.
effect Low_aCL High_aCL
Direct Effect (ADE) 1 [-8.4, 10.2] -8.9 [-16.8, -1.1]
Indirect Effect (ACME) 5.1 [1.3, 10.7] -2.1 [-6.2, 0.2]
Mediator Effect -17.8 [-27.6, -7.9] -11.4 [-19.9, -2.7]
Total Effect 6.4 [-3.3, 15.8] -11.2 [-19.5, -3.2]

12 Models without large GPL_BC

Open code

infl_max <- max(data_first[which(data_first$infliximab == 1), 'GPL_BC_log2']); infl_max
## [1] 2.740331
ctrl_max <- max(data_first[which(data_first$infliximab == 0), 'GPL_BC_log2']); ctrl_max
## [1] 4.949819

data_sens <- data_first %>% filter(GPL_BC_log2 <= infl_max) %>% 
  mutate(group = factor(if_else(infliximab == 1, 'infliximab', 'control')))

12.1 DGF by GPL_BC

12.1.1 Priors

Open code

# function to get prior
create_prior <- function(var, sigma, class = "b", coef = NULL) {
  set_prior(paste0("normal(0, ", sigma, ")"), class = class, coef = coef)
}


priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               2 / sd(data_sens$rec_age, na.rm = TRUE),
               coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               2 / sd(data_sens$HLA_MM, na.rm = TRUE),
               coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               2 / sd(data_sens$KDPI, na.rm = TRUE),
               coef = "IKDPIM50"),
  create_prior("ICITM15",
               2 / sd(data_sens$CIT, na.rm = TRUE),
               coef = "ICITM15"),
  create_prior("male_sex",
               4,
               coef = "male_sex"),
  create_prior("perfusion_event",
               4,
               coef = "perfusion_event"),
  create_prior("male_sex_donor",
               4,
               coef = "male_sex_donor"),

  create_prior("infliximab",
               4,
               coef = "infliximab"),

 set_prior("student_t(3, 0, 0.6)", class = "sds", 
           coef = 's(GPL_BC_log2, bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 0.3)", class = "sds",  
           coef = 's(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", logit(mean(data_sens$DGF)), ", 10)"),
            class = "Intercept")
)

12.1.2 Model

Open code
model_dgf_GPL_BC_interaction_NL_sens <- run(
  expr = brm(
    DGF ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(GPL_BC_log2, bs = 'ps', k = 5) +
    s(GPL_BC_log2, by = infliximab, bs = 'ps', k = 5),
    family = bernoulli(),
    data = data_sens,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 5000, warmup = 2000,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_dgf_GPL_BC_interaction_NL_sens",
  reuse = TRUE
)

summary(model_dgf_GPL_BC_interaction_NL_sens, robust = TRUE)
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: DGF ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(GPL_BC_log2, bs = "ps", k = 5) + s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5) 
##    Data: data_sens (Number of observations: 170) 
##   Draws: 4 chains, each with iter = 5000; warmup = 2000; thin = 1;
##          total post-warmup draws = 12000
## 
## Smoothing Spline Hyperparameters:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sGPL_BC_log2_1)               0.45      0.40     0.02     1.88 1.00
## sds(sGPL_BC_log2infliximab_1)     0.22      0.21     0.01     1.07 1.00
##                               Bulk_ESS Tail_ESS
## sds(sGPL_BC_log2_1)               7041     5216
## sds(sGPL_BC_log2infliximab_1)     8954     5147
## 
## Regression Coefficients:
##                           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                    -1.23      0.49    -2.25    -0.26 1.00    11736
## male_sex                      1.15      0.41     0.35     1.99 1.00    12193
## Irec_ageM50                  -0.01      0.02    -0.05     0.03 1.00    11702
## male_sex_donor                0.31      0.40    -0.45     1.11 1.00    10843
## IKDPIM50                      0.00      0.01    -0.02     0.02 1.00    10691
## ICITM15                       0.09      0.03     0.03     0.15 1.00    10923
## IHLA_MMM5                     0.25      0.14    -0.01     0.52 1.00    10410
## perfusion_event              -0.39      0.39    -1.17     0.39 1.00    11052
## infliximab                    0.06      3.89    -7.58     7.58 1.00     3997
## sGPL_BC_log2_1                2.05      1.19    -0.39     4.66 1.00     7724
## sGPL_BC_log2:infliximab_1     1.65      2.73    -3.57     6.90 1.00     4254
## sGPL_BC_log2:infliximab_2    -2.09      1.33    -4.69     0.48 1.00     4694
##                           Tail_ESS
## Intercept                     9635
## male_sex                      9580
## Irec_ageM50                   9522
## male_sex_donor                8929
## IKDPIM50                      8477
## ICITM15                       8949
## IHLA_MMM5                     8917
## perfusion_event               9013
## infliximab                    5974
## sGPL_BC_log2_1                6425
## sGPL_BC_log2:infliximab_1     6490
## sGPL_BC_log2:infliximab_2     6814
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_dgf_GPL_BC_interaction_NL_sens)
##                           prior     class
##                          (flat)         b
##    normal(0, 0.281193624307594)         b
##     normal(0, 1.32489040521642)         b
##   normal(0, 0.0974514024084987)         b
##                    normal(0, 4)         b
##    normal(0, 0.194443540354437)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                    normal(0, 4)         b
##                          (flat)         b
##                          (flat)         b
##                          (flat)         b
##  normal(-0.658055860748675, 10) Intercept
##            student_t(3, 0, 2.5)       sds
##            student_t(3, 0, 0.6)       sds
##            student_t(3, 0, 0.3)       sds
##                                               coef group resp dpar nlpar lb ub
##                                                                               
##                                            ICITM15                            
##                                          IHLA_MMM5                            
##                                           IKDPIM50                            
##                                         infliximab                            
##                                        Irec_ageM50                            
##                                           male_sex                            
##                                     male_sex_donor                            
##                                    perfusion_event                            
##                                     sGPL_BC_log2_1                            
##                          sGPL_BC_log2:infliximab_1                            
##                          sGPL_BC_log2:infliximab_2                            
##                                                                               
##                                                                           0   
##                   s(GPL_BC_log2, bs = "ps", k = 5)                        0   
##  s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)                        0   
##        source
##       default
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##  (vectorized)
##  (vectorized)
##  (vectorized)
##          user
##       default
##          user
##          user

tr2 <- round(exp(fixef(model_dgf_GPL_BC_interaction_NL_sens, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)]), 2)

antibody_seq <- quantile(data_sens$GPL_BC_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_GPL_BC_interaction_NL_sens,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get odds ratio for infliximab effect across antibody values
prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()
names(prediction) <- c('p05', 'p95')

infliximab_GPL_BC_log2_int <- quantile(
  exp((log(prediction$p95) - log(prediction$p05))/scaling_unit), 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:GPL_BC_log2'
colnames(tr2)[1] <- '(R)OR'
kableExtra::kable(tr2)
Table 26: Results from multivariable Bayesian logistic regression models incorporating a non-linear interaction between anti-cardiolipin IgG (GPL_BC_log2) and infliximab treatment on delayed graft function (DGF), while accounting for other covariates, restricting the dataset to patients with anti-cardiolipin IgG levels within the range observed in the infliximab group. Effects are presented as odds ratios (OR), with the last row reporting the ratio of odds ratios (ROR). The ROR represents the estimated ratio of infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgG, quantifying the interaction effect.Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
(R)OR Q2.5 Q97.5
male_sex 3.16 1.43 7.31
Irec_ageM50 0.99 0.95 1.03
male_sex_donor 1.36 0.64 3.04
IKDPIM50 1.00 0.98 1.02
ICITM15 1.09 1.03 1.16
IHLA_MMM5 1.28 0.99 1.68
perfusion_event 0.68 0.31 1.47
infliximab:GPL_BC_log2 49.76 4.11 715.86

12.1.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_sens$GPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_dgf_GPL_BC_interaction_NL_sens,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')), 
  GPL_BC_log2 = data_prediction$GPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control"))) %>% 
  ggplot(aes(x = GPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 1),
                     breaks = c(seq(0, 1, by = 0.2))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  labs(x = expression(log[2]~"(aCL IgG ["*mu*"g/ml])"), y = "DGF risk") +
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size=10),
        axis.title=element_text(size=12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") +
  
  geom_rug(
    data = data_sens %>% filter(DGF == 0),
    aes(x = GPL_BC_log2),
    sides = "b",
    color = "black",
    size = 0.12,
    inherit.aes = FALSE
  ) +
  geom_rug(
    data = data_sens %>% filter(DGF == 1),
    aes(x = GPL_BC_log2),
     sides = "t",
    color = "black",
    linewidth = 0.12,
    inherit.aes = FALSE
  ) + 
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -1.801712  2.071126

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_GPL_BC_interaction_NL_sens,
    newdata = data_prediction)

tr_ctrl <- (logit(tr[,c(ncol(tr)/2)]) - logit(tr[,c(1)]))
tr_infliximab <- (logit(tr[,c(ncol(tr))]) - logit(tr[,c(ncol(tr)/2)+1]))

post_fix <- data.frame(
  b_GPL_BC_log2 = tr_ctrl,
  b_GPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = exp(b_GPL_BC_log2), 
         infliximab = exp(b_GPL_BC_log2_infliximab)) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(2)

CIS
##       control infliximab
## 2.5%     0.03       1.76
## 97.5%    1.05      53.51
## 50%      0.18       9.02
## 0.1%     0.01       0.64
## 99.9%    3.12     171.65

xpos <- 323

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect (OR) of aCL IgG increase (p05 to p95) on DGF', 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(1/600, 1800)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(1/512, 1/64, 1/8, 1, 8, 64, 512),
                     labels = c("1/512", "1/64", "1/8", "1", "8", "64", "512")) +
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 240
xseq <- c(1/64, 1/8, 1, 8, 64, 512)

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]
antibody_seq
##         5%        25%        50%        75%        95% 
## -1.8017117 -0.7949708 -0.1340217  0.6308746  2.0711263

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_dgf_GPL_BC_interaction_NL_sens,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  (prediction_infliximab/(1-prediction_infliximab))/
     (prediction_ctrl/(1-prediction_ctrl))
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(2)

CIS
##        p05  p25  p50  p75   p95
## 2.5%  0.02 0.11 0.26 0.54  1.21
## 97.5% 0.46 0.74 1.23 2.93 27.34
## 50%   0.11 0.30 0.58 1.26  5.61
## 0.1%  0.01 0.06 0.16 0.33  0.50
## 99.9% 1.08 1.29 1.89 4.75 66.13

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPL_BC_percentile') %>% 
  
  ggplot(aes(y = GPL_BC_percentile, x = value, fill = GPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on DGF risk (odds ratio)", 
         y = 'Percentile of aCL IgG value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(1/300, 1150)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  scale_x_continuous(transform = 'log2',
                     breaks = c(xseq),
                     labels = c("1/64", "1/8", "1", "8", "64", '512')) +
  
  
  geom_vline(xintercept = 1, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Odds ratio: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Odds ratio: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Odds ratio: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Odds ratio: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Odds ratio: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

12.1.3.1 Figure merged

Open code
plotac <- "sup_figure_2"
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 10: Anti-cardiolipin IgG modulation of the effect of infliximab on delayed graft function (DGF) after excluding patients with anti-cardiolipin IgG levels above the maximum in the IFX group. (A) Predicted DGF risk by anti-cardiolipin IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles. (B) Posterior distribution of the estimated odds ratio comparing DGF odds at the 95th vs. 5th percentile of anti-cardiolipin IgG. (C) Posterior distribution of the effect of infliximab (odds ratio) on DGF across anti-cardiolipin IgG percentiles. Results are from a multivariable Bayesian logistic model with B-splines after data restriction.

12.2 GFR by GPL_BC

12.2.1 Prior

Open code
GFR_sd <- sd(data_model_priorImpute$GFR_MDRD_3)
GFR_sd
## [1] 21.17099

priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               (2*GFR_sd) / sd(data_sens$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"),  
  create_prior("IHLA_MMM5",
               (2*GFR_sd) / sd(data_sens$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"),
  create_prior("IKDPIM50",
               (2*GFR_sd) / sd(data_sens$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"),
  create_prior("ICITM15",
               (2*GFR_sd) / sd(data_sens$CIT, na.rm = TRUE),
    coef = "ICITM15"),
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"),
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"),
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"),
  
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"),

 set_prior("student_t(3, 0, 12)", class = "sds", 
           coef = 's(GPL_BC_log2, bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 6)", class = "sds",  
           coef = 's(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", mean(data_sens$GFR_MDRD_3), ", 200)"),
            class = "Intercept")
)

12.2.2 Model

Open code
model_GFR_MDRD_3_GPL_BC_interaction_NL_sens <- run(
  expr = brm(
    GFR_MDRD_3 ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(GPL_BC_log2, bs = 'ps', k = 5) +
    s(GPL_BC_log2, by = infliximab, bs = 'ps', k = 5),
    family = student(),
    data = data_sens,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 5000, warmup = 2000,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_GPL_BC_interaction_NL_sens",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_GPL_BC_interaction_NL_sens, robust = TRUE)
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(GPL_BC_log2, bs = "ps", k = 5) + s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5) 
##    Data: data_sens (Number of observations: 170) 
##   Draws: 4 chains, each with iter = 5000; warmup = 2000; thin = 1;
##          total post-warmup draws = 12000
## 
## Smoothing Spline Hyperparameters:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sGPL_BC_log2_1)              11.20      6.61     1.62    34.72 1.00
## sds(sGPL_BC_log2infliximab_1)     3.88      3.51     0.18    16.96 1.00
##                               Bulk_ESS Tail_ESS
## sds(sGPL_BC_log2_1)               6605     4667
## sds(sGPL_BC_log2infliximab_1)     9867     5840
## 
## Regression Coefficients:
##                           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                    54.48      3.96    46.64    62.36 1.00    12007
## male_sex                      0.41      3.26    -5.97     6.84 1.00    15848
## Irec_ageM50                   0.04      0.15    -0.26     0.34 1.00    13956
## male_sex_donor               -1.93      3.21    -8.21     4.26 1.00    12991
## IKDPIM50                     -0.24      0.08    -0.40    -0.08 1.00    12943
## ICITM15                       0.30      0.21    -0.12     0.73 1.00    13879
## IHLA_MMM5                    -1.31      1.09    -3.48     0.81 1.00    14212
## perfusion_event              -6.06      3.30   -12.45     0.55 1.00    14300
## infliximab                   -0.72     82.88  -163.16   159.97 1.00     5182
## sGPL_BC_log2_1              -15.58     14.08   -48.00    14.59 1.00     8623
## sGPL_BC_log2:infliximab_1    -7.88     56.44  -117.16   101.60 1.00     5192
## sGPL_BC_log2:infliximab_2     7.33     24.27   -39.34    54.80 1.00     5300
##                           Tail_ESS
## Intercept                     9598
## male_sex                      8660
## Irec_ageM50                   8904
## male_sex_donor                9353
## IKDPIM50                      8879
## ICITM15                       8775
## IHLA_MMM5                     8500
## perfusion_event               8473
## infliximab                    6764
## sGPL_BC_log2_1                7794
## sGPL_BC_log2:infliximab_1     6742
## sGPL_BC_log2:infliximab_2     6973
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    18.48      1.33    15.82    21.18 1.00     9271     8073
## nu       16.67      9.85     5.57    51.30 1.00    10706     8564
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_3_GPL_BC_interaction_NL_sens)
##                          prior     class
##                         (flat)         b
##    normal(0, 5.95314681980334)         b
##    normal(0, 28.0492387472281)         b
##    normal(0, 2.06314246193193)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 4.11656184148077)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 84.6839516288788)         b
##    normal(0, 84.6839516288788)         b
##                         (flat)         b
##                         (flat)         b
##                         (flat)         b
##  normal(51.1232941176471, 200) Intercept
##                  gamma(2, 0.1)        nu
##          student_t(3, 0, 19.5)       sds
##            student_t(3, 0, 12)       sds
##             student_t(3, 0, 6)       sds
##          student_t(3, 0, 19.5)     sigma
##                                               coef group resp dpar nlpar lb ub
##                                                                               
##                                            ICITM15                            
##                                          IHLA_MMM5                            
##                                           IKDPIM50                            
##                                         infliximab                            
##                                        Irec_ageM50                            
##                                           male_sex                            
##                                     male_sex_donor                            
##                                    perfusion_event                            
##                                     sGPL_BC_log2_1                            
##                          sGPL_BC_log2:infliximab_1                            
##                          sGPL_BC_log2:infliximab_2                            
##                                                                               
##                                                                           1   
##                                                                           0   
##                   s(GPL_BC_log2, bs = "ps", k = 5)                        0   
##  s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)                        0   
##                                                                           0   
##        source
##       default
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##  (vectorized)
##  (vectorized)
##  (vectorized)
##          user
##       default
##       default
##          user
##          user
##       default

tr2 <- round(fixef(model_GFR_MDRD_3_GPL_BC_interaction_NL_sens, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)], 2)

antibody_seq <- quantile(data_sens$GPL_BC_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get Estimate for infliximab effect across antibody values
prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()

names(prediction) <- c('p05', 'p95')

infliximab_GPL_BC_log2_int <- quantile(
  (prediction$p95 - prediction$p05)/scaling_unit, 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:GPL_BC_log2'
kableExtra::kable(tr2)
Table 27: Results from a multivariable Bayesian robust regression model incorporating a non-linear interaction between anti-cardiolipin IgG (GPL_BC_log2) and infliximab treatment on estimated glomerular filtration rate (eGFR), after excluding patients with anti-cardiolipin IgG levels above the maximum in the IFX group and while accounting for other covariates. Estimate represents the estimated change in eGFR per one-unit increase in the predictor. The last row (infliximab:GPL_BC_log2) reports the difference in infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgG, quantifying the interaction effect. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex 0.41 -5.97 6.84
Irec_ageM50 0.04 -0.26 0.34
male_sex_donor -1.93 -8.21 4.26
IKDPIM50 -0.24 -0.40 -0.08
ICITM15 0.30 -0.12 0.73
IHLA_MMM5 -1.31 -3.48 0.81
perfusion_event -6.06 -12.45 0.55
infliximab:GPL_BC_log2 -16.54 -36.53 3.22

12.2.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_sens$GPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')),
  GPL_BC_log2 = data_prediction$GPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control")), 
         `Q2.5` = if_else(`Q2.5` < 0, 0, `Q2.5`)) %>% 
  ggplot(aes(x = GPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 125),
                      breaks = c(seq(0, 125, by = 25))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  geom_point(data = data_sens, 
             aes(x = GPL_BC_log2, y = GFR_MDRD_3, col = group, fill = group)) +
  
  labs(x = expression(log[2]~"(aCL IgG ["*mu*"g/ml])"), 
       y = "GFR (mL/min/1.73 m²)") +
  
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size = 10),
        axis.title=element_text(size = 12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") + 
  
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -1.801712  2.071126

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens,
    newdata = data_prediction)

tr_ctrl <- (tr[,c(ncol(tr)/2)] - tr[,c(1)]) 
tr_infliximab <- (tr[,c(ncol(tr))] - tr[,c(ncol(tr)/2)+1])

post_fix <- data.frame(
  b_GPL_BC_log2 = tr_ctrl,
  b_GPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = b_GPL_BC_log2, 
         infliximab = b_GPL_BC_log2_infliximab) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##       control infliximab
## 2.5%     -9.0      -23.8
## 97.5%    20.5        2.3
## 50%       5.7      -10.7
## 0.1%    -18.8      -31.8
## 99.9%    30.6        9.0

xpos <- 39.2

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect of aCL IgG increase (p05 to p95) on eGFR', 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(-50, 52)) +
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 30

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##         p05   p25   p50   p75   p95
## 2.5%   -5.0  -5.6  -7.4 -11.4 -23.5
## 97.5%  17.4   9.4   5.5   2.7   2.4
## 50%     6.2   1.9  -0.9  -4.2 -10.3
## 0.1%  -11.3 -10.4 -11.8 -16.0 -30.6
## 99.9%  23.9  13.8   9.1   6.9  10.2

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPL_BC_percentile') %>% 
  
  ggplot(aes(y = GPL_BC_percentile, x = value, fill = GPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on GFR (mL/min/1.73 m²)", 
         y = 'Percentile of aCL IgG value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(-40, 40)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Estimate: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Estimate: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Estimate: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

12.2.3.1 Figure merged

Open code
plotac <- 'sup_figure_6'
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 11: Supplementary Figure 6: Anti-cardiolipin IgG modulation of the infliximab effect on estimated glomerula filtration rate (eGFR) after excluding patients with anti-cardiolipin IgG levels above the maximum in the IFX group. (A) Predicted eGFR by anti-cardiolipin IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of cardiolipin levels. (B) Posterior distribution of the estimated change in eGFR when anti-cardiolipin IgG increases from the 5th to the 95th sample percentile. (C) Posterior distribution of the effect of infliximab on eGFR across anti-cardiolipin IgG percentiles. Results are from a multivariable Bayesian robust regression with B-splines after data restriction.

13 DGF Subgroup analysis by GPL_BC

We will use data divided to patients with under- and above-median value of aCL IgG (GPL_BC)

13.1 Define priors

Open code
priors_subset <- c(
  
  create_prior("GPL_BC_log2",
    2 / sd(data_model_priorImpute$GPL_BC_log2, na.rm = TRUE),
    coef = "GPL_BC_log2"
  ),
   create_prior("IHLA_MMM5",
    2 / sd(data_model_priorImpute$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"
  ),
  create_prior("IKDPIM50",
    2 / sd(data_model_priorImpute$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"
  ),
  create_prior("Irec_ageM50",
    2 / sd(data_model_priorImpute$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"
),
  create_prior("ICITM15",
    2 / sd(data_model_priorImpute$CIT, na.rm = TRUE),
    coef = "ICITM15"
  ),
  create_prior("male_sex",
    4,
    coef = "male_sex"
  ),
  create_prior("perfusion_event",
    4,
    coef = "perfusion_event"
  ),
  create_prior("male_sex_donor",
    4,
    coef = "male_sex_donor"
  ),
  create_prior("infliximab",
    4,
    coef = "infliximab"
  ),
  set_prior(paste0("normal(", logit(mean(data_model_priorImpute$DGF)), ", 10)"),
    class = "Intercept"
  )
)

13.2 Fit model for \(<\) median aCL IgG level

Open code
model_dgf_GPL_BC_lowGPLBC <- run(
  expr = brm(
    DGF ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      GPL_BC_log2,
    family = bernoulli(),
    data = data_imputed_lowGPL,
    prior = priors_subset,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 4000, warmup = 2000,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_dgf_GPL_BC_lowGPLBC",
  reuse = TRUE
)

summary(model_dgf_GPL_BC_lowGPLBC, robust = TRUE)
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: DGF ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + GPL_BC_log2 
##    Data: data_imputed_lowGPL (Number of observations: 88) 
##   Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
##          total post-warmup draws = 8000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          -1.15      0.88    -2.92     0.57 1.00     6103     5642
## male_sex            0.66      0.61    -0.50     1.88 1.00     7547     5709
## Irec_ageM50        -0.00      0.03    -0.05     0.05 1.00     7614     6063
## male_sex_donor      0.63      0.56    -0.53     1.77 1.00     7908     5889
## IKDPIM50            0.00      0.01    -0.03     0.03 1.00     6795     5914
## ICITM15             0.08      0.04    -0.00     0.16 1.00     7322     6440
## IHLA_MMM5           0.26      0.20    -0.12     0.67 1.00     6960     6236
## perfusion_event    -0.43      0.58    -1.61     0.69 1.00     7869     6343
## infliximab         -1.55      0.57    -2.74    -0.45 1.00     6379     5667
## GPL_BC_log2        -0.49      0.35    -1.22     0.18 1.00     7280     6096
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_dgf_GPL_BC_lowGPLBC)
##                           prior     class            coef group resp dpar nlpar
##                          (flat)         b                                      
##     normal(0, 1.41170525720746)         b     GPL_BC_log2                      
##    normal(0, 0.280307006032291)         b         ICITM15                      
##     normal(0, 1.38219051128263)         b       IHLA_MMM5                      
##   normal(0, 0.0970980394231448)         b        IKDPIM50                      
##                    normal(0, 4)         b      infliximab                      
##    normal(0, 0.190589762164436)         b     Irec_ageM50                      
##                    normal(0, 4)         b        male_sex                      
##                    normal(0, 4)         b  male_sex_donor                      
##                    normal(0, 4)         b perfusion_event                      
##  normal(-0.667829372575656, 10) Intercept                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user

tr <- round(exp(fixef(model_dgf_GPL_BC_lowGPLBC, robust = TRUE)[-1, c(1,3,4)]), 2)
colnames(tr)[1] <- 'OR'
kableExtra::kable(tr)
Table 28: Results from multivariable Bayesian logistic regression models examining the main effects of predictors, including infliximab treatment and log2-transformed anti-cardiolipin IgG (GPL_BC_log2), on delayed graft function (DGF), without interaction terms and including only patients with anti-cardiolipin IgG level below median. The odds ratio (OR) represents the estimated change in the odds of DGF per one-unit increase in the predictor. OR: odds ratio; Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
OR Q2.5 Q97.5
male_sex 1.94 0.61 6.59
Irec_ageM50 1.00 0.95 1.05
male_sex_donor 1.87 0.59 5.88
IKDPIM50 1.00 0.97 1.03
ICITM15 1.08 1.00 1.18
IHLA_MMM5 1.30 0.89 1.96
perfusion_event 0.65 0.20 2.00
infliximab 0.21 0.06 0.64
GPL_BC_log2 0.61 0.29 1.20

13.3 Fit model for aCL \(\geq\) median

Open code
model_dgf_GPL_BC_highGPLBC <- run(
  expr = brm(
    DGF ~
      male_sex +
      I(rec_age - 50) +
      male_sex_donor +
      I(KDPI - 50) +
      I(CIT - 15) +
      I(HLA_MM - 5) +
      perfusion_event +
      infliximab +
      GPL_BC_log2,
    family = bernoulli(),
    data = data_imputed_highGPL,
    prior = priors_subset,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 4000, warmup = 2000,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_dgf_GPL_BC_highGPLBC",
  reuse = TRUE
)

summary(model_dgf_GPL_BC_highGPLBC, robust = TRUE)
##  Family: bernoulli 
##   Links: mu = logit 
## Formula: DGF ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + GPL_BC_log2 
##    Data: data_imputed_highGPL (Number of observations: 89) 
##   Draws: 4 chains, each with iter = 4000; warmup = 2000; thin = 1;
##          total post-warmup draws = 8000
## 
## Regression Coefficients:
##                 Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## Intercept          -2.17      0.82    -3.90    -0.66 1.00     5958     5518
## male_sex            1.56      0.59     0.44     2.78 1.00     6964     5785
## Irec_ageM50        -0.04      0.02    -0.09     0.01 1.00     7544     6006
## male_sex_donor      0.31      0.58    -0.82     1.46 1.00     7261     6549
## IKDPIM50            0.02      0.01    -0.00     0.05 1.00     6958     6417
## ICITM15             0.08      0.04     0.01     0.16 1.00     7101     5523
## IHLA_MMM5           0.33      0.20    -0.05     0.74 1.00     6725     5730
## perfusion_event    -0.96      0.61    -2.22     0.20 1.00     6888     5602
## infliximab          1.06      0.55     0.00     2.16 1.00     7743     6408
## GPL_BC_log2         0.33      0.25    -0.16     0.84 1.00     7068     6138
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_dgf_GPL_BC_highGPLBC)
##                           prior     class            coef group resp dpar nlpar
##                          (flat)         b                                      
##     normal(0, 1.41170525720746)         b     GPL_BC_log2                      
##    normal(0, 0.280307006032291)         b         ICITM15                      
##     normal(0, 1.38219051128263)         b       IHLA_MMM5                      
##   normal(0, 0.0970980394231448)         b        IKDPIM50                      
##                    normal(0, 4)         b      infliximab                      
##    normal(0, 0.190589762164436)         b     Irec_ageM50                      
##                    normal(0, 4)         b        male_sex                      
##                    normal(0, 4)         b  male_sex_donor                      
##                    normal(0, 4)         b perfusion_event                      
##  normal(-0.667829372575656, 10) Intercept                                      
##  lb ub  source
##        default
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user
##           user

tr <- round(exp(fixef(model_dgf_GPL_BC_highGPLBC, robust = TRUE)[-1, c(1,3,4)]), 3)
colnames(tr)[1] <- 'Odds ratio'
kableExtra::kable(tr)
Table 29: Results from multivariable Bayesian logistic regression models examining the main effects of predictors, including infliximab treatment and log2-transformed anti-cardiolipin IgG (GPL_BC_log2), on delayed graft function (DGF), without interaction terms and including only patients with anti-cardiolipin IgG level at/above median. The odds ratio (OR) represents the estimated change in the odds of DGF per one-unit increase in the predictor. OR: odds ratio; Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Odds ratio Q2.5 Q97.5
male_sex 4.754 1.548 16.143
Irec_ageM50 0.964 0.918 1.012
male_sex_donor 1.361 0.440 4.314
IKDPIM50 1.024 0.996 1.055
ICITM15 1.083 1.008 1.179
IHLA_MMM5 1.387 0.950 2.098
perfusion_event 0.382 0.108 1.218
infliximab 2.897 1.005 8.695
GPL_BC_log2 1.397 0.855 2.324

14 Models eGFR with at least 480 days of FU with graft functioning

Open code
data_sens2 <- data_first %>% 
  filter(
    GFR_days_2 >= 480 | death_event == 1 | graft_failure == 1
      ) %>% 
  mutate(group = factor(if_else(infliximab == 1, 'infliximab', 'control')))

dim(data_sens2)
## [1] 154  33

14.1 by GPL_BC

14.1.1 Prior

Open code

GFR_sd <- sd(data_sens2$GFR_MDRD_3)
GFR_sd
## [1] 19.93074

priors_interaction_pnl <- c(
  create_prior("Irec_ageM50",
               (2*GFR_sd) / sd(data_sens2$rec_age, na.rm = TRUE),
    coef = "Irec_ageM50"),  
  
  create_prior("IHLA_MMM5",
               (2*GFR_sd) / sd(data_sens2$HLA_MM, na.rm = TRUE),
    coef = "IHLA_MMM5"),
  
  create_prior("IKDPIM50",
               (2*GFR_sd) / sd(data_sens2$KDPI, na.rm = TRUE),
    coef = "IKDPIM50"),
  
  create_prior("ICITM15",
               (2*GFR_sd) / sd(data_sens2$CIT, na.rm = TRUE),
    coef = "ICITM15"),
  
  create_prior("male_sex",
    4*GFR_sd,
    coef = "male_sex"),
  
  create_prior("perfusion_event",
    4*GFR_sd,
    coef = "perfusion_event"),
  
  create_prior("male_sex_donor",
    4*GFR_sd,
    coef = "male_sex_donor"),
  
  create_prior("infliximab",
    4*GFR_sd,
    coef = "infliximab"),

 set_prior("student_t(3, 0, 12)", class = "sds", 
           coef = 's(GPL_BC_log2, bs = "ps", k = 5)'), 
 
 set_prior("student_t(3, 0, 6)", class = "sds",  
           coef = 's(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)'), 
 
  set_prior(paste0("normal(", mean(data_sens2$GFR_MDRD_3), ", 200)"),
            class = "Intercept")
)

14.1.2 Model

Open code
model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2 <- run(
  expr = brm(
    GFR_MDRD_3 ~
    male_sex +
    I(rec_age - 50) +
    male_sex_donor +
    I(KDPI - 50) +
    I(CIT - 15) +
    I(HLA_MM - 5) +
    perfusion_event +
    infliximab +
    s(GPL_BC_log2, bs = 'ps', k = 5) +
    s(GPL_BC_log2, by = infliximab, bs = 'ps', k = 5),
    family = student(),
    data = data_sens2,
    prior = priors_interaction_pnl,
    backend = "cmdstanr",
    seed = 2025,
    cores = 4, chains = 4,
    iter = 5000, warmup = 2000,
    control = list(adapt_delta = 0.95)
  ),
  path = "gitignore/run/model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2",
  reuse = TRUE
)

summary(model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2, robust = TRUE)
##  Family: student 
##   Links: mu = identity; sigma = identity; nu = identity 
## Formula: GFR_MDRD_3 ~ male_sex + I(rec_age - 50) + male_sex_donor + I(KDPI - 50) + I(CIT - 15) + I(HLA_MM - 5) + perfusion_event + infliximab + s(GPL_BC_log2, bs = "ps", k = 5) + s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5) 
##    Data: data_sens2 (Number of observations: 154) 
##   Draws: 4 chains, each with iter = 5000; warmup = 2000; thin = 1;
##          total post-warmup draws = 12000
## 
## Smoothing Spline Hyperparameters:
##                               Estimate Est.Error l-95% CI u-95% CI Rhat
## sds(sGPL_BC_log2_1)              12.04      7.29     1.37    36.66 1.00
## sds(sGPL_BC_log2infliximab_1)     4.12      3.89     0.17    19.70 1.00
##                               Bulk_ESS Tail_ESS
## sds(sGPL_BC_log2_1)               5502     3578
## sds(sGPL_BC_log2infliximab_1)    10206     5052
## 
## Regression Coefficients:
##                           Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS
## Intercept                    55.76      4.06    47.63    63.70 1.00    13224
## male_sex                     -1.06      3.33    -7.56     5.51 1.00    14555
## Irec_ageM50                   0.05      0.15    -0.25     0.35 1.00    14540
## male_sex_donor               -2.57      3.37    -9.26     4.05 1.00    13238
## IKDPIM50                     -0.29      0.08    -0.45    -0.12 1.00    13489
## ICITM15                       0.29      0.22    -0.15     0.71 1.00    14845
## IHLA_MMM5                    -1.23      1.11    -3.43     0.90 1.00    13879
## perfusion_event              -3.56      3.30   -10.19     2.95 1.00    14153
## infliximab                   -0.24     77.61  -152.88   148.64 1.00     5557
## sGPL_BC_log2_1                9.25     16.77   -26.48    50.93 1.00     9004
## sGPL_BC_log2:infliximab_1   -13.97     44.82  -100.82    73.77 1.00     5580
## sGPL_BC_log2:infliximab_2     9.71     28.48   -44.52    65.44 1.00     5760
##                           Tail_ESS
## Intercept                     9055
## male_sex                      7481
## Irec_ageM50                   9607
## male_sex_donor                8737
## IKDPIM50                      9901
## ICITM15                       9479
## IHLA_MMM5                     9397
## perfusion_event               9125
## infliximab                    6824
## sGPL_BC_log2_1                6562
## sGPL_BC_log2:infliximab_1     6926
## sGPL_BC_log2:infliximab_2     6841
## 
## Further Distributional Parameters:
##       Estimate Est.Error l-95% CI u-95% CI Rhat Bulk_ESS Tail_ESS
## sigma    17.96      1.25    15.50    20.51 1.00    11959     8561
## nu       21.30     12.37     6.57    61.21 1.00    12525     7831
## 
## Draws were sampled using sample(hmc). For each parameter, Bulk_ESS
## and Tail_ESS are effective sample size measures, and Rhat is the potential
## scale reduction factor on split chains (at convergence, Rhat = 1).
prior_summary(model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2)
##                          prior     class
##                         (flat)         b
##    normal(0, 5.48898844779997)         b
##    normal(0, 25.9053313823118)         b
##    normal(0, 1.95751635259431)         b
##    normal(0, 79.7229697409756)         b
##    normal(0, 3.82892016085826)         b
##    normal(0, 79.7229697409756)         b
##    normal(0, 79.7229697409756)         b
##    normal(0, 79.7229697409756)         b
##                         (flat)         b
##                         (flat)         b
##                         (flat)         b
##  normal(51.4243506493507, 200) Intercept
##                  gamma(2, 0.1)        nu
##          student_t(3, 0, 18.2)       sds
##            student_t(3, 0, 12)       sds
##             student_t(3, 0, 6)       sds
##          student_t(3, 0, 18.2)     sigma
##                                               coef group resp dpar nlpar lb ub
##                                                                               
##                                            ICITM15                            
##                                          IHLA_MMM5                            
##                                           IKDPIM50                            
##                                         infliximab                            
##                                        Irec_ageM50                            
##                                           male_sex                            
##                                     male_sex_donor                            
##                                    perfusion_event                            
##                                     sGPL_BC_log2_1                            
##                          sGPL_BC_log2:infliximab_1                            
##                          sGPL_BC_log2:infliximab_2                            
##                                                                               
##                                                                           1   
##                                                                           0   
##                   s(GPL_BC_log2, bs = "ps", k = 5)                        0   
##  s(GPL_BC_log2, by = infliximab, bs = "ps", k = 5)                        0   
##                                                                           0   
##        source
##       default
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##          user
##  (vectorized)
##  (vectorized)
##  (vectorized)
##          user
##       default
##       default
##          user
##          user
##       default

tr2 <- round(fixef(model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2, 
                       robust = TRUE)[-c(1,9:12), c(1,3,4)], 2)

antibody_seq <- quantile(data_sens2$GPL_BC_log2, 
                         probs = c(0.05, 0.95))

asl <- length(antibody_seq)
quant_dif <- antibody_seq[2] - antibody_seq[1]

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]
prediction_infliximab <- tr[, c((asl+1):(asl*2))]

## get Estimate for infliximab effect across antibody values
prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()

names(prediction) <- c('p05', 'p95')

infliximab_GPL_BC_log2_int <- quantile(
  (prediction$p95 - prediction$p05)/scaling_unit, 
         probs = c(0.5, 1/40, 39/40))

tr2 <- rbind(tr2, round(infliximab_GPL_BC_log2_int, 2))
row.names(tr2)[8] <- 'infliximab:GPL_BC_log2'
kableExtra::kable(tr2)
Table 30: Results from a multivariable Bayesian robust regression model incorporating a non-linear interaction between anti-cardiolipin IgG (GPL_BC_log2) and infliximab treatment on estimated glomerular filtration rate (eGFR), after excluding patients which were lost during follow-up before 480th day post TX and while accounting for other covariates. Estimate represents the estimated change in eGFR per one-unit increase in the predictor. The last row (infliximab:GPL_BC_log2) reports the difference in infliximab’s treatment effect at the 95th versus the 5th percentile of anti-cardiolipin IgG, quantifying the interaction effect. Q2.5 and Q97.5: lower and upper bounds of the 95% credible interval, respectively.
Estimate Q2.5 Q97.5
male_sex -1.06 -7.56 5.51
Irec_ageM50 0.05 -0.25 0.35
male_sex_donor -2.57 -9.26 4.05
IKDPIM50 -0.29 -0.45 -0.12
ICITM15 0.29 -0.15 0.71
IHLA_MMM5 -1.23 -3.43 0.90
perfusion_event -3.56 -10.19 2.95
infliximab:GPL_BC_log2 -17.76 -37.37 1.55

14.1.3 Visualisation - penalized non-linear effect

Extract posterior draws

Open code
antibody_perc <- quantile(data_sens2$GPL_BC_log2,
                           probs = c(0, 0.02, 0.05, 0.25, 0.5, 0.75, 0.95, 0.98, 1))

antibody_seq <- seq(antibody_perc[1], 
                      antibody_perc[length(antibody_perc)], 
                      length.out = 101)

## create prediction
data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

prediction <- data.frame(
  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2,
    newdata = data_prediction) %>% posterior_summary(
      robust = TRUE
      ) %>% data.frame() %>% select(-Est.Error),
  group = factor(if_else(data_prediction$infliximab == 1, 'infliximab', 'control')),
  GPL_BC_log2 = data_prediction$GPL_BC_log2)

Figure A

Open code
cole <- c('#CD7006', '#0028F0')

fig_a <- prediction %>% 
  mutate(group = factor(group, levels = c("infliximab", "control")), 
         `Q2.5` = if_else(`Q2.5` < 0, 0, `Q2.5`)) %>% 
  ggplot(aes(x = GPL_BC_log2, y = Estimate, col = group, fill = group)) + 
  geom_line(aes(y = Estimate), linewidth = 1) + 
  scale_y_continuous(limits = c(0, 125),
                      breaks = c(seq(0, 125, by = 25))) +
  
  geom_ribbon(aes(ymin = `Q2.5`, ymax = `Q97.5`),
               alpha = 0.4,  color = NA) +
  
  geom_point(data = data_sens2, 
             aes(x = GPL_BC_log2, y = GFR_MDRD_3, col = group, fill = group)) +
  
  labs(x = expression(log[2]~"(aCL IgG ["*mu*"g/ml])"), 
       y = "GFR (mL/min/1.73 m²)") +
  
  scale_color_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_fill_manual(values = cole, 
                       name = "group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  facet_grid(rows = vars(group)) +
  
  theme(axis.text=element_text(size = 10),
        axis.title=element_text(size = 12),
        strip.text.x = element_text(size = 12),
        legend.position = "none") + 
  
  geom_vline(xintercept = antibody_perc[3:7], linetype = 2, 
                            color = "grey50", size = 0.3)

Figure B

Open code

antibody_seq <- antibody_perc[c(3, 7)]
antibody_seq
##        5%       95% 
## -1.778673  2.435576

data_prediction <- data.frame(
   male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2,
    newdata = data_prediction)

tr_ctrl <- (tr[,c(ncol(tr)/2)] - tr[,c(1)]) 
tr_infliximab <- (tr[,c(ncol(tr))] - tr[,c(ncol(tr)/2)+1])

post_fix <- data.frame(
  b_GPL_BC_log2 = tr_ctrl,
  b_GPL_BC_log2_infliximab = tr_infliximab
)

tr <- post_fix %>% 
  mutate(control = b_GPL_BC_log2, 
         infliximab = b_GPL_BC_log2_infliximab) %>% 
  select(control, infliximab) %>% 
  data.frame()

CIS <- sapply(
  tr, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##       control infliximab
## 2.5%     -6.7      -25.6
## 97.5%    19.9        3.5
## 50%       6.7      -11.2
## 0.1%    -14.6      -33.4
## 99.9%    26.5       10.7

xpos <- 39.2

fig_b <- tr %>% 
  pivot_longer(values_to = 'value', 
               cols = c('control', 'infliximab'),
               names_to = 'group') %>% 
  ggplot(aes(x = value, y = group, fill = group)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha=0.5,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
    labs(x = 'Effect of aCL IgG increase (p05 to p95) on eGFR', 
         y = 'Treatment group') +
  
  scale_fill_manual(values = cole, 
                       name = "Treatment group",
                       breaks = c('control', 'infliximab'),
                       labels = c('control', 'infliximab')) +
  
  scale_y_discrete(expand = expansion(add = 0.1)) +
  coord_cartesian(xlim = c(-50, 52)) +
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
   
  annotate("text",  x = xpos, y = 2.85 , 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85 , 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +
  
  annotate("text",  x = xpos, y = 2.6 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.6 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

Figure C

Open code

cole <- c("#8B4789", "#8B5F77", "#8B7765", "#6F815C", "#548B54")
xpos <- 30

antibody_seq <- antibody_perc[c(3, 4, 5, 6, 7)]

asl <- length(antibody_seq)

data_prediction <- data.frame(
    male_sex = rep(mmale_sex, 2*length(antibody_seq)),
  `male_sex_donor` = rep(mmale_sex_donor, 2*length(antibody_seq)),
  `perfusion_event` = rep(mperfusion_event, 2*length(antibody_seq)),
  `infliximab` = c(rep(0, length(antibody_seq)), rep(1, length(antibody_seq))),
  `rec_age` = rep(mrec_age, 2*length(antibody_seq)),
  `KDPI` = rep(mKDPI, 2*length(antibody_seq)),
  `CIT` = rep(mCIT, 2*length(antibody_seq)),
  `HLA_MM` = rep(mHLA_MM, 2*length(antibody_seq)),
  `GPL_BC_log2` = c(antibody_seq, antibody_seq)
  )

tr <-  posterior_epred(
    model_GFR_MDRD_3_GPL_BC_interaction_NL_sens2,
    newdata = data_prediction)

prediction_ctrl <- tr[, c(1:asl)]

prediction_infliximab <- tr[, c((asl+1):(asl*2))]

prediction <- (
  prediction_infliximab-
     prediction_ctrl
  ) %>% data.frame()


names(prediction) <- c('p05', 'p25', 'p50', 'p75', 'p95')

CIS <- sapply(
  prediction, 
  function(p) quantile(p, probs = c(1/40, 39/40, 0.5, 1e-3, 1-1e-3))
  ) %>% 
  round(1)

CIS
##         p05   p25   p50   p75   p95
## 2.5%   -7.0  -7.3  -9.0 -13.2 -26.1
## 97.5%  15.6   8.4   4.2   1.2  -0.7
## 50%     4.3   0.5  -2.5  -6.1 -13.5
## 0.1%  -13.3 -11.4 -13.1 -17.9 -33.5
## 99.9%  22.7  12.6   8.0   5.4   7.4

fig_c <- prediction %>% 
  pivot_longer(values_to = 'value', 
               cols = c('p05', 'p25', 'p50', 'p75', 'p95'),
               names_to = 'GPL_BC_percentile') %>% 
  
  ggplot(aes(y = GPL_BC_percentile, x = value, fill = GPL_BC_percentile)) +
  
  stat_halfeye(.width = c(0.95), slab_alpha = 0.55,
               linewidth = 5,
               shape = 18,
               point_size = 5,
               normalize = "groups",
               p_limits = c(1e-3, 1-1e-3)) +
  
  labs(x = "Effect of infliximab on GFR (mL/min/1.73 m²)", 
         y = 'Percentile of aCL IgG value') +
  
  scale_fill_manual(values = cole, 
                       name = "Percentile of aCL IgG value",
                       breaks = c('p05', 'p25', 'p50', 'p75', 'p95'),
                       labels = c('p05', 'p25', 'p50', 'p75', 'p95')) +
  
  coord_cartesian(xlim = c(-40, 40)) +
  scale_y_discrete(expand = expansion(add = 0.1)) +
  
  
  geom_vline(xintercept = 0, linetype = 2, 
                color = "red", size = 0.6) +
  
  theme(axis.text = element_text(size = 12),
        axis.title = element_text(size = 12)) +
  
  theme(legend.position = "none") +
  
  
  annotate("text",  x = xpos, y = 5.85 , 
           label = paste0("Estimate: ", CIS[3,5]),
           color = cole[5] ) +
  
  annotate("text",  x = xpos, y = 4.85, 
           label = paste0("Estimate: ", CIS[3,4]),
           color = cole[4] ) +
   
  annotate("text",  x = xpos, y = 3.85, 
           label = paste0("Estimate: ", CIS[3,3]),
           color = cole[3] ) +
  
  annotate("text",  x = xpos, y = 2.85, 
           label = paste0("Estimate: ", CIS[3,2]),
           color = cole[2] ) +    
  
  annotate("text",  x = xpos, y = 1.85, 
           label = paste0("Estimate: ", CIS[3,1]),
           color = cole[1] ) +

  annotate("text",  x = xpos, y = 5.5 , 
           label = paste0("95% CI: [", CIS[1,5], ", ", CIS[2,5], "]"),
           color = cole[5] ) + 
  
  annotate("text",  x = xpos, y = 4.5 , 
           label = paste0("95% CI: [", CIS[1,4], ", ", CIS[2,4], "]"),
           color = cole[4] ) + 
  
  annotate("text",  x = xpos, y = 3.5 , 
           label = paste0("95% CI: [", CIS[1,3], ", ", CIS[2,3], "]"),
           color = cole[3] ) + 
  
  annotate("text",  x = xpos, y = 2.5 , 
           label = paste0("95% CI: [", CIS[1,2], ", ", CIS[2,2], "]"),
           color = cole[2] ) + 
  
  annotate("text",  x = xpos, y = 1.5 , 
           label = paste0("95% CI: [", CIS[1,1], ", ", CIS[2,1], "]"),
           color = cole[1] ) 

14.1.3.1 Figure merged

Open code
plotac <- 'sup_figure_8'
path <- "gitignore/figures"


fig <- cowplot::plot_grid(fig_b, fig_c,
  rel_heights = c(0.7, 1),
  labels = c("B", "C"),
  ncol = 1
)

assign(
  plotac,
  cowplot::plot_grid(
    fig_a, fig,
    rel_widths = c(0.6, 1),
    labels = c("A", "")
  )
)

get(plotac)

if (file.exists(paste0(path, "/", plotac)) == FALSE) {
  ggsave(
    path = paste0(path),
    filename = plotac,
    device = "pdf",
    width = 9,
    height = 6
  )
}
Figure 12: Anti-cardiolipin IgG modulation of the infliximab effect on estimated glomerula filtration rate (eGFR, both central and local) after excluding patients which were lost to follow-up before 480th day after TX. (A) Predicted eGFR by anti-cardiolipin IgG for infliximab (upper) and control (lower) groups, with covariates held at their sample means. Shaded regions represent 95% credible intervals; dashed lines indicate the 5th, 25th, 50th, 75th, and 95th percentiles of cardiolipin levels. (B) Posterior distribution of the estimated change in eGFR when anti-cardiolipin IgG increases from the 5th to the 95th sample percentile. (C) Posterior distribution of the effect of infliximab on eGFR across anti-cardiolipin IgG percentiles. Results are from a multivariable Bayesian robust regression with B-splines after data restriction.

16 Reproducibility

Open code
sessionInfo()
## R version 4.4.3 (2025-02-28)
## Platform: x86_64-pc-linux-gnu
## Running under: Ubuntu 22.04.5 LTS
## 
## Matrix products: default
## BLAS:   /usr/lib/x86_64-linux-gnu/blas/libblas.so.3.10.0 
## LAPACK: /usr/lib/x86_64-linux-gnu/lapack/liblapack.so.3.10.0
## 
## locale:
##  [1] LC_CTYPE=en_US.UTF-8       LC_NUMERIC=C              
##  [3] LC_TIME=cs_CZ.UTF-8        LC_COLLATE=en_US.UTF-8    
##  [5] LC_MONETARY=cs_CZ.UTF-8    LC_MESSAGES=en_US.UTF-8   
##  [7] LC_PAPER=cs_CZ.UTF-8       LC_NAME=C                 
##  [9] LC_ADDRESS=C               LC_TELEPHONE=C            
## [11] LC_MEASUREMENT=cs_CZ.UTF-8 LC_IDENTIFICATION=C       
## 
## time zone: Europe/Prague
## tzcode source: system (glibc)
## 
## attached base packages:
## [1] stats     graphics  grDevices utils     datasets  methods   base     
## 
## other attached packages:
##  [1] bayestestR_0.14.0 mice_3.17.0       ggbeeswarm_0.6.0  quantreg_5.98    
##  [5] SparseM_1.81      coxed_0.3.3       survival_3.7-0    rms_6.8-1        
##  [9] Hmisc_5.1-3       bayesplot_1.8.1   ggdist_3.3.2      kableExtra_1.4.0 
## [13] lubridate_1.9.4   corrplot_0.92     arm_1.12-2        lme4_1.1-35.5    
## [17] MASS_7.3-65       janitor_2.2.0     projpred_2.0.2    brms_2.21.0      
## [21] Rcpp_1.0.13       glmnet_4.1-8      Matrix_1.7-0      boot_1.3-31      
## [25] cowplot_1.1.1     pROC_1.18.0       mgcv_1.9-1        nlme_3.1-168     
## [29] openxlsx_4.2.8    flextable_0.9.6   sjPlot_2.8.16     RJDBC_0.2-10     
## [33] rJava_1.0-11      DBI_1.2.3         car_3.1-2         carData_3.0-5    
## [37] skimr_2.1.5       gtsummary_2.0.2   emmeans_1.10.4    ggpubr_0.4.0     
## [41] stringi_1.8.7     forcats_1.0.0     stringr_1.5.1     dplyr_1.1.4      
## [45] purrr_1.0.4       readr_2.1.5       tidyr_1.3.1       tibble_3.3.0     
## [49] ggplot2_3.5.1     tidyverse_1.3.1  
## 
## loaded via a namespace (and not attached):
##   [1] fs_1.6.6                matrixStats_1.3.0       httr_1.4.2             
##   [4] insight_0.20.2          repr_1.1.7              tools_4.4.3            
##   [7] backports_1.5.0         sjlabelled_1.2.0        R6_2.6.1               
##  [10] jomo_2.7-3              withr_3.0.2             Brobdingnag_1.2-7      
##  [13] prettyunits_1.2.0       gridExtra_2.3           cli_3.6.5              
##  [16] textshaping_0.3.6       performance_0.12.2      gt_0.11.0              
##  [19] officer_0.6.6           sandwich_3.0-1          labeling_0.4.2         
##  [22] sass_0.4.9              mvtnorm_1.1-3           polspline_1.1.25       
##  [25] ggridges_0.5.3          askpass_1.1             QuickJSR_1.3.1         
##  [28] systemfonts_1.0.4       commonmark_1.9.1        StanHeaders_2.32.10    
##  [31] foreign_0.8-90          gfonts_0.2.0            svglite_2.1.3          
##  [34] readxl_1.3.1            rstudioapi_0.16.0       httpcode_0.3.0         
##  [37] generics_0.1.4          shape_1.4.6             distributional_0.4.0   
##  [40] zip_2.2.0               inline_0.3.19           loo_2.4.1              
##  [43] abind_1.4-5             lifecycle_1.0.4         multcomp_1.4-18        
##  [46] yaml_2.3.10             snakecase_0.11.1        grid_4.4.3             
##  [49] promises_1.2.0.1        crayon_1.5.3            mitml_0.4-3            
##  [52] lattice_0.22-5          haven_2.4.3             pillar_1.11.0          
##  [55] knitr_1.50              estimability_1.5.1      codetools_0.2-19       
##  [58] pan_1.6                 glue_1.7.0              V8_4.4.2               
##  [61] fontLiberation_0.1.0    data.table_1.15.4       vctrs_0.6.5            
##  [64] cellranger_1.1.0        gtable_0.3.0            assertthat_0.2.1       
##  [67] datawizard_0.12.2       xfun_0.52               mime_0.12              
##  [70] coda_0.19-4             iterators_1.0.14        TH.data_1.1-0          
##  [73] fontquiver_0.2.1        rstan_2.32.6            tensorA_0.36.2.1       
##  [76] vipor_0.4.5             rpart_4.1.24            colorspace_2.0-2       
##  [79] nnet_7.3-20             tidyselect_1.2.1        processx_3.8.4         
##  [82] compiler_4.4.3          curl_6.4.0              rvest_1.0.2            
##  [85] htmlTable_2.4.0         xml2_1.3.3              fontBitstreamVera_0.1.1
##  [88] posterior_1.6.0         checkmate_2.3.2         scales_1.3.0           
##  [91] callr_3.7.6             digest_0.6.37           minqa_1.2.4            
##  [94] rmarkdown_2.27          htmltools_0.5.8.1       pkgconfig_2.0.3        
##  [97] base64enc_0.1-3         dbplyr_2.1.1            fastmap_1.2.0          
## [100] rlang_1.1.6             htmlwidgets_1.6.4       shiny_1.9.1            
## [103] farver_2.1.0            zoo_1.8-9               jsonlite_2.0.0         
## [106] magrittr_2.0.3          Formula_1.2-4           munsell_0.5.0          
## [109] gdtools_0.3.7           plyr_1.8.6              pkgbuild_1.3.1         
## [112] parallel_4.4.3          sjmisc_2.8.10           ggeffects_1.7.0        
## [115] splines_4.4.3           hms_1.1.3               sjstats_0.19.0         
## [118] ps_1.7.7                uuid_1.0-3              ggsignif_0.6.3         
## [121] markdown_1.13           reshape2_1.4.4          stats4_4.4.3           
## [124] rstantools_2.1.1        crul_1.5.0              reprex_2.0.1           
## [127] evaluate_1.0.4          RcppParallel_5.1.8      modelr_0.1.8           
## [130] nloptr_2.0.0            tzdb_0.5.0              foreach_1.5.2          
## [133] httpuv_1.6.5            MatrixModels_0.5-3      cards_0.2.2            
## [136] openssl_1.4.6           cardx_0.2.1             broom_1.0.6            
## [139] xtable_1.8-4            rstatix_0.7.0           later_1.3.0            
## [142] viridisLite_0.4.0       ragg_1.4.0              beeswarm_0.4.0         
## [145] cluster_2.1.8.1         gamm4_0.2-6             timechange_0.3.0       
## [148] cmdstanr_0.8.0.9000     bridgesampling_1.1-2

References

[1]
V. Petr, F. Tichanek, S.L. Liu, F. Poppelaars, B. Renner, J. Laskowski, S. Purohit, M. Zhao, D. Jalal, P.S. Heeger, J.M. Thurman, Pretransplant natural antibody levels identify a subset of deceased donor kidney transplant recipients that benefit from infliximab induction, American Journal of Transplantation (2025). https://doi.org/10.1016/j.ajt.2025.06.003.
[2]
R Core Team, R: A language and environment for statistical computing, R Foundation for Statistical Computing, Vienna, Austria, 2024. https://www.R-project.org/.
[3]
S. van Buuren, K. Groothuis-Oudshoorn, Mice: Multivariate imputation by chained equations in r, Journal of Statistical Software 45 (2011) 1–67. https://doi.org/10.18637/jss.v045.i03.
[4]
P.-C. Bürkner, Brms: An r package for bayesian multilevel models using stan, 80 (2017). https://doi.org/10.18637/jss.v080.i01.