if (!require(pacman)) install.packages("pacman")
::p_load(rio, here, magrittr, janitor, tidyverse, skimr, phdcocktail, gtsummary, survival,
pacman
huxtable, openxlsx, ggpubr, powerjoin, rms, dcurves, epiR, pROC, glue, predRupdate)
source("helpers.R")
Calibration, Clinical Utility and Specificity of Clinical Decision Support Tools in Inflammatory Bowel Disease
The aim of this analysis notebook is to reproduce the reported analysis from the paper Calibration, Clinical Utility and Specificity of Clinical Decision Support Tools in Inflammatory Bowel Disease
.
Requirements:
To run the analysis, you will need:
- Raw data files: These should be placed inside the
input
folder within the home directory of your project. The required files are:- Pheno_CD_VDZ.xlsx: Excel sheet containing metadata on CD patients starting VDZ.
- Pheno_UC_VDZ.xlsx: Excel sheet containing metadata on UC patients starting VDZ.
- Pheno_CD_UST.xlsx: Excel sheet containing metadata on CD patients starting UST.
- VDZ_UST_troughs.RData: R workspace containing two dataframes with trough levels of VDZ and UST.
These data files can be obtained upon reasonable request to the corresponding authors, Séverine Vermeire, via severine.vermeire@uzleuven.be and Bram Verstockt, via bram.verstockt@uzleuven.be.
- Complementary files deposited in the GitHub repository for this analysis:
- renv.lock, .Rprofile, renv/settings.json and renv/activate.R: These files are necessary to reproduce the same versions of the packages used in the original analysis.
- helpers.R: This script should be sourced at the beginning of the analysis to produce some customized functions.
- variable-selection-instability.R: This script reproduces the bootstrap analysis exploring the stability of variable selection in clinical prediction models.
1. Install and load needed packages/functions
2. Import phenodata and pre-process input
<- import(here("input", list.files("input", pattern = "uc_vdz", ignore.case = TRUE)))
vdz_uc_starters <- import(here("input", list.files("input", pattern = "cd_vdz", ignore.case = TRUE)))
vdz_cd_starters <- import(here("input", list.files("input", pattern = "cd_ust", ignore.case = TRUE))) ust_cd_starters
Fix excel dates
<- c("start_date", "visit_date", "diagnosis_date", "birth_date", "last_followup_date", "stop_date",
important_date_vrs "stop_date_itt", "dose_optimisation_date", "surgery_after_date", "date_lab", "endoscopy_date")
<- convert_date_columns(vdz_uc_starters, important_date_vrs)
vdz_uc_starters <- convert_date_columns(vdz_cd_starters, important_date_vrs)
vdz_cd_starters %<>% mutate(across(where(is.POSIXct), ymd)) ust_cd_starters
Add dates of outcomes assessment columns
<- add_assessment_dates(vdz_uc_starters)
vdz_uc_starters <- add_assessment_dates(vdz_cd_starters)
vdz_cd_starters <- add_assessment_dates(ust_cd_starters) ust_cd_starters
Copy outcome data to the baseline row
%<>%
vdz_uc_starters group_by(patient_id) %>%
fill(c(clinical_response_pmayo:cs_dependent_outcome, clinical_assessment_date, endoscopic_assessment_date), .direction = "up") %>%
ungroup()
%<>%
vdz_cd_starters group_by(patient_id) %>%
fill(c(clinical_response:cs_dependent_outcome, clinical_assessment_date, endoscopic_assessment_date), .direction = "up") %>%
ungroup()
%<>%
ust_cd_starters group_by(patient_id) %>%
fill(c(clinical_response:cs_dependent_outcome, clinical_assessment_date, endoscopic_assessment_date), .direction = "up") %>%
ungroup()
Filter eligible patients
<- vdz_uc_starters %>% filter(excluded == 0)
vdz_uc_eligible <- vdz_cd_starters %>% filter(excluded == 0)
vdz_cd_eligible <- ust_cd_starters %>% filter(excluded == 0) ust_cd_eligible
Filter included patients
<- vdz_uc_eligible %>% filter(baseline_disease_activity_type != "clinical")
vdz_uc_included %<>% filter(pga > 1)
vdz_uc_included %<>% filter(clinical_assessment == 1)
vdz_uc_included %<>% filter(mes > 1, !is.na(disease_duration_yrs), !is.na(previous_antitnf), !is.na(albumin_g_L))
vdz_uc_included
<- vdz_cd_eligible %>% filter(baseline_disease_activity_type != "clinical")
vdz_cd_included %<>% filter(pga > 1)
vdz_cd_included %<>% filter(clinical_assessment == 1)
vdz_cd_included %<>% filter(!is.na(intestinal_resections_before), !is.na(fistulizing_disease_prior),
vdz_cd_included !is.na(previous_antitnf), !is.na(albumin_g_L), !is.na(crp_mg_L))
<- ust_cd_eligible %>% filter(baseline_disease_activity_type != "clinical")
ust_cd_included %<>% filter(pga > 1)
ust_cd_included %<>% filter(clinical_assessment == 1)
ust_cd_included %<>% filter(!is.na(cdst_category)) ust_cd_included
3. Get baseline characteristics (Table 1)
Identify needed columns and define new data frames
<- c("gender", "age_yrs", "disease_duration_yrs", "smoking", "bmi_kg_m2", "albumin_g_L","crp_mg_L", "calprotectin_ug_g",
tblone_vrs "concomitant_cs", "concomitant_imm", "concomitant_5asa", "previous_advancedrx_amount", "previous_antitnf",
"intestinal_resections_before", "age_at_diagnosis_yrs",
"disease_location", "upper_gi_disease", "disease_behaviour","peri_anal_disease", "ses_cd",
"disease_extent", "mes",
"fistulizing_disease_prior", "fistulizing_disease_current")
<- vdz_cd_included %>% select(any_of(tblone_vrs))
vdz_cd_tblone_df <- vdz_uc_included %>% select(any_of(tblone_vrs))
vdz_uc_tblone_df <- ust_cd_included %>% select(any_of(tblone_vrs)) ust_cd_tblone_df
Manipulate columns
<- c("age_yrs", "disease_duration_yrs", "bmi_kg_m2", "albumin_g_L","crp_mg_L", "calprotectin_ug_g", "ses_cd")
continuous_vrs
%<>% mutate(across(all_of(continuous_vrs), ~ round(as.numeric(.), 1)))
vdz_cd_tblone_df %<>% mutate(across(all_of(continuous_vrs), ~ round(as.numeric(.), 1)))
ust_cd_tblone_df %<>% mutate(across(any_of(continuous_vrs), ~ round(as.numeric(.), 1)))
vdz_uc_tblone_df
%<>% mutate(disease_behaviour = if_else(str_detect(disease_behaviour, "B3"), "B3", disease_behaviour))
vdz_cd_tblone_df %<>% mutate(disease_behaviour = if_else(str_detect(disease_behaviour, "B3"), "B3", disease_behaviour))
ust_cd_tblone_df
%<>% mutate(female_gender = if_else(gender == "F", 1, 0), .after = gender) %>% select(-gender)
ust_cd_tblone_df %<>% mutate(female_gender = if_else(gender == "F", 1, 0), .after = gender) %>% select(-gender)
vdz_cd_tblone_df %<>% mutate(female_gender = if_else(gender == "F", 1, 0), .after = gender) %>% select(-gender)
vdz_uc_tblone_df
%<>% mutate(prior_smoker = if_else(smoking == 0, 0, 1), .after = smoking) %>% select(-smoking)
ust_cd_tblone_df %<>% mutate(prior_smoker = if_else(smoking == 0, 0, 1), .after = smoking) %>% select(-smoking)
vdz_cd_tblone_df %<>% mutate(prior_smoker = if_else(smoking == 0, 0, 1), .after = smoking) %>% select(-smoking)
vdz_uc_tblone_df
%<>% get_age_montreal() %>% select(-age_at_diagnosis_yrs)
ust_cd_tblone_df %<>% get_age_montreal() %>% select(-age_at_diagnosis_yrs)
vdz_cd_tblone_df %<>% get_age_montreal() %>% select(-age_at_diagnosis_yrs)
vdz_uc_tblone_df
%<>% recode_steroids()
ust_cd_tblone_df %<>% recode_steroids()
vdz_cd_tblone_df %<>% recode_steroids()
vdz_uc_tblone_df
%<>% mutate(concomitant_imm = if_else(concomitant_imm == "0", 0, 1),
ust_cd_tblone_df concomitant_5asa = if_else(concomitant_5asa == "0", 0, 1))
%<>% mutate(concomitant_imm = if_else(concomitant_imm == "0", 0, 1),
vdz_cd_tblone_df concomitant_5asa = if_else(concomitant_5asa == "0", 0, 1))
%<>% mutate(concomitant_imm = if_else(concomitant_imm == "0", 0, 1),
vdz_uc_tblone_df concomitant_5asa = if_else(concomitant_5asa == "0", 0, 1))
%<>% recode_previous_advancedrx()
ust_cd_tblone_df %<>% recode_previous_advancedrx()
vdz_cd_tblone_df %<>% recode_previous_advancedrx()
vdz_uc_tblone_df
<- c("concomitant_cs", "previous_advancedrx_amount", "disease_location", "disease_behaviour", "disease_extent", "mes", "age_montreal")
categorical_vrs
<- c("female_gender", "prior_smoker", "concomitant_imm", "concomitant_5asa", "previous_antitnf", "upper_gi_disease", "peri_anal_disease",
dichotomous_vrs "intestinal_resections_before", "fistulizing_disease_prior", "fistulizing_disease_current")
Recode variables to publication shape
$variable_label[ibd_data_dict$variable == "ses_cd"] <- "Simple endoscopic score for CD"
ibd_data_dict
%<>%
ibd_data_dict add_row(variable = "prior_smoker", variable_label = "Prior smoking history", value = NA, value_label = NA) %>%
add_row(variable = "intestinal_resections_before", variable_label = "Prior bowel surgery", value = NA, value_label = NA) %>%
add_row(variable = "previous_advancedrx_amount", variable_label = "Number of previous advanced therapies", value = NA, value_label = NA) %>%
add_row(variable = "albumin_g_L", variable_label = "Serum albumin (g/L)", value = NA, value_label = NA) %>%
add_row(variable = "crp_mg_L", variable_label = "C-reactive protein (mg/L)", value = NA, value_label = NA) %>%
add_row(variable = "previous_antitnf", variable_label = "Prior anti-TNF exposure", value = NA, value_label = NA) %>%
add_row(variable = "fistulizing_disease_prior", variable_label = "Prior fistulizing disease", value = NA, value_label = NA) %>%
add_row(variable = "fistulizing_disease_current", variable_label = "Baseline actively draining fistula", value = NA, value_label = NA)
<- c("concomitant_cs", "disease_location", "disease_behaviour", "disease_extent", "age_montreal")
categoricals_to_recode
$previous_advancedrx_amount <- factor(ust_cd_tblone_df$previous_advancedrx_amount, levels = c("0", "1", "2", "≥ 3"), ordered = TRUE)
ust_cd_tblone_df$previous_advancedrx_amount <- factor(vdz_cd_tblone_df$previous_advancedrx_amount, levels = c("0", "1", "2", "≥ 3"), ordered = TRUE)
vdz_cd_tblone_df$previous_advancedrx_amount <- factor(vdz_uc_tblone_df$previous_advancedrx_amount, levels = c("0", "1", "2", "≥ 3"), ordered = TRUE)
vdz_uc_tblone_df$mes <- factor(vdz_uc_tblone_df$mes, levels = c("2", "3"), ordered = TRUE)
vdz_uc_tblone_df
<- recode_vrs(ust_cd_tblone_df, data_dictionary = ibd_data_dict, vrs = categoricals_to_recode, factor = TRUE)
ust_cd_tblone_df <- recode_vrs(vdz_cd_tblone_df, data_dictionary = ibd_data_dict, vrs = categoricals_to_recode, factor = TRUE)
vdz_cd_tblone_df <- recode_vrs(vdz_uc_tblone_df, data_dictionary = ibd_data_dict, vrs = categoricals_to_recode, factor = TRUE) vdz_uc_tblone_df
Make the tables
theme_gtsummary_journal(journal = "jama")
theme_gtsummary_compact()
VDZ-CD
<- vdz_cd_tblone_df %>%
vdz_cd_tblone tbl_summary(type = list(any_of(continuous_vrs) ~ "continuous",
any_of(categorical_vrs) ~ "categorical",
any_of(dichotomous_vrs) ~ "dichotomous"),
missing_text = "missing, n",
digits = list(all_categorical() ~ c(0, 1))) %>%
bold_labels() %>%
italicize_levels()
<- c("upper_gi_disease", "peri_anal_disease")
labels_to_levels
<- vdz_cd_tblone[["table_body"]]
tableone_body
$row_type[tableone_body$variable %in% labels_to_levels] <- "level"
tableone_body
$stat_label[tableone_body$variable %in% labels_to_levels] <- NA
tableone_body
<- tableone_body[tableone_body$label != "None",]
tableone_body
"table_body"]] <- tableone_body vdz_cd_tblone[[
VDZ-UC
<- vdz_uc_tblone_df %>%
vdz_uc_tblone tbl_summary(type = list(any_of(continuous_vrs) ~ "continuous",
any_of(categorical_vrs) ~ "categorical",
any_of(dichotomous_vrs) ~ "dichotomous"),
missing_text = "missing, n",
digits = list(all_categorical() ~ c(0, 1))) %>%
bold_labels() %>%
italicize_levels()
<- vdz_uc_tblone[["table_body"]]
tableone_body
<- tableone_body[tableone_body$label != "None",]
tableone_body
"table_body"]] <- tableone_body vdz_uc_tblone[[
UST-CD
<- ust_cd_tblone_df %>%
ust_cd_tblone tbl_summary(type = list(any_of(continuous_vrs) ~ "continuous",
any_of(categorical_vrs) ~ "categorical",
any_of(dichotomous_vrs) ~ "dichotomous"),
missing_text = "missing, n",
digits = list(all_categorical() ~ c(0, 1))) %>%
bold_labels() %>%
italicize_levels()
<- ust_cd_tblone[["table_body"]]
tableone_body
$row_type[tableone_body$variable %in% labels_to_levels] <- "level"
tableone_body
$stat_label[tableone_body$variable %in% labels_to_levels] <- NA
tableone_body
<- tableone_body[tableone_body$label != "None",]
tableone_body
"table_body"]] <- tableone_body ust_cd_tblone[[
Merge the tables
tbl_merge(
tbls = list(vdz_cd_tblone, vdz_uc_tblone, ust_cd_tblone),
tab_spanner = c("**VDZ-CD**", "**VDZ-UC**", "**UST-CD**")
)
Characteristic | VDZ-CD | VDZ-UC | UST-CD |
---|---|---|---|
N = 280 | N = 218 | N = 194 | |
Female, n (%) | 170 (60.7) | 118 (54.1) | 121 (62.4) |
Age (year), Median (IQR) | 41 (29 – 55) | 41 (29 – 53) | 41 (30 – 56) |
Disease duration (year), Median (IQR) | 11 (4 – 22) | 8 (2 – 14) | 13 (6 – 24) |
Prior smoking history, n (%) | 134 (47.9) | 66 (30.3) | 91 (46.9) |
Body mass index (kg/m²), Median (IQR) | 23.8 (21.3 – 27.1) | 23.8 (20.7 – 27.3) | 23.4 (20.6 – 26.9) |
Serum albumin (g/L), Median (IQR) | 41.9 (39.2 – 44.1) | 42.0 (39.4 – 44.7) | 41.2 (38.9 – 43.3) |
C-reactive protein (mg/L), Median (IQR) | 5 (2 – 14) | 3 (1 – 8) | 8 (3 – 19) |
Faecal calprotectin (ug/g), Median (IQR) | 649 (271 – 1,800) | 1,673 (665 – 1,800) | 985 (405 – 1,800) |
missing, n | 159 | 125 | 50 |
Concomitant corticosteroids, n (%) | |||
Topical | 37 (13.2) | 77 (35.3) | 12 (6.2) |
Systemic | 34 (12.1) | 35 (16.1) | 13 (6.7) |
Concomitant immunomodulators, n (%) | 14 (5.0) | 7 (3.2) | 0 (0.0) |
Concomitant 5-aminosalicylates, n (%) | 4 (1.4) | 112 (51.4) | 0 (0.0) |
Number of previous advanced therapies, n (%) | |||
0 | 72 (25.7) | 82 (37.6) | 39 (20.1) |
1 | 81 (28.9) | 78 (35.8) | 38 (19.6) |
2 | 104 (37.1) | 50 (22.9) | 54 (27.8) |
≥ 3 | 23 (8.2) | 8 (3.7) | 63 (32.5) |
Prior anti-TNF exposure, n (%) | 204 (72.9) | 136 (62.4) | 151 (77.8) |
Prior bowel surgery, n (%) | 131 (46.8) | 0 (0.0) | 96 (49.5) |
Age at diagnosis (year), n (%) | |||
< 17 years | 42 (15.0) | 21 (9.6) | 27 (13.9) |
17-40 years | 190 (67.9) | 132 (60.6) | 139 (71.6) |
> 40 years | 48 (17.1) | 65 (29.8) | 28 (14.4) |
Disease location, n (%) | |||
Ileal | 81 (28.9) | 60 (30.9) | |
Colonic | 41 (14.6) | 22 (11.3) | |
Ileocolonic | 158 (56.4) | 112 (57.7) | |
Upper GI modifier | 19 (6.8) | 18 (9.3) | |
Disease behaviour, n (%) | |||
Inflammatory | 109 (38.9) | 78 (40.2) | |
Fibrostenotic | 110 (39.3) | 73 (37.6) | |
Penetrating | 61 (21.8) | 43 (22.2) | |
Perianal modifier | 86 (30.7) | 69 (35.6) | |
Simple endoscopic score for CD, Median (IQR) | 7.5 (5.0 – 15.0) | 10 (6 – 16) | |
missing, n | 184 | 40 | |
Prior fistulizing disease, n (%) | 95 (33.9) | 70 (36.1) | |
Baseline actively draining fistula, n (%) | 14 (5.0) | 9 (4.6) | |
Disease extent | |||
Proctitis | 18 (8.3) | ||
Left-sided colitis | 115 (52.8) | ||
Extensive colitis | 85 (39.0) | ||
Mayo endoscopic subscore | |||
2 | 98 (45.0) | ||
3 | 120 (55.0) |
4. Calculate CDST points and assign response categories
VDZ-UC
Ensure that we have all CDST variables in numeric shape ready for calculations
%<>% mutate(no_previous_antitnf = if_else(previous_antitnf == 1, 0, 1), .after = previous_antitnf)
vdz_uc_included
is.numeric(vdz_uc_included$no_previous_antitnf)
[1] TRUE
is.numeric(vdz_uc_included$albumin_g_L)
[1] TRUE
%<>% mutate(moderate_endo_activity = if_else(mes == 2, 1, 0), .after = mes)
vdz_uc_included
is.numeric(vdz_uc_included$moderate_endo_activity)
[1] TRUE
%<>% mutate(dxduration_above_two = if_else(disease_duration_yrs >= 2, 1, 0), .after = disease_duration_yrs)
vdz_uc_included
is.numeric(vdz_uc_included$dxduration_above_two)
[1] TRUE
Calculate CDST points
%<>% mutate(cdst_points = 3*dxduration_above_two + 3*no_previous_antitnf + 2*moderate_endo_activity + 0.65*albumin_g_L) vdz_uc_included
Assign CDST categories
%<>% mutate(cdst_category = case_when(cdst_points <= 26 ~ "Low",
vdz_uc_included > 26 & cdst_points <= 32 ~ "Intermediate",
cdst_points > 32 ~ "High")
cdst_points
)
%>% count(cdst_category) vdz_uc_included
cdst_category | n |
---|---|
High | 95 |
Intermediate | 105 |
Low | 18 |
$cdst_category <- factor(vdz_uc_included$cdst_category, levels = c("Low", "Intermediate", "High"), ordered = TRUE) vdz_uc_included
VDZ-CD
Ensure that we have all CDST variables in numeric shape ready for calculations
%<>% mutate(no_previous_antitnf = if_else(previous_antitnf == 1, 0, 1), .after = previous_antitnf)
vdz_cd_included
is.numeric(vdz_cd_included$no_previous_antitnf)
[1] TRUE
%<>% mutate(no_surgery_before = if_else(intestinal_resections_before == 1, 0, 1), .after = intestinal_resections_before)
vdz_cd_included
is.numeric(vdz_cd_included$no_surgery_before)
[1] TRUE
%<>% mutate(no_fistulizing_before = if_else(fistulizing_disease_prior == 1, 0, 1), .after = fistulizing_disease_prior)
vdz_cd_included
is.numeric(vdz_cd_included$no_fistulizing_before)
[1] TRUE
is.numeric(vdz_cd_included$albumin_g_L)
[1] TRUE
is.numeric(vdz_cd_included$crp_mg_L)
[1] TRUE
Calculate CDST points
%<>% mutate(cdst_points = 2*no_surgery_before + 3*no_previous_antitnf + 2*no_fistulizing_before + 0.4*albumin_g_L - case_when(crp_mg_L >= 3 & crp_mg_L <= 10 ~ 0.5,
vdz_cd_included > 10 ~ 3,
crp_mg_L .default = 0))
Assign CDST categories
%<>% mutate(cdst_category = case_when(cdst_points <= 13 ~ "Low",
vdz_cd_included > 13 & cdst_points <= 19 ~ "Intermediate",
cdst_points > 19 ~ "High")
cdst_points
)
%>% count(cdst_category) vdz_cd_included
cdst_category | n |
---|---|
High | 121 |
Intermediate | 146 |
Low | 13 |
$cdst_category <- factor(vdz_cd_included$cdst_category, levels = c("Low", "Intermediate", "High"), ordered = TRUE) vdz_cd_included
UST-CD
%>% count(cdst_category) ust_cd_included
cdst_category | n |
---|---|
High | 80 |
Intermediate | 95 |
Low | 19 |
$cdst_category <- factor(ust_cd_included$cdst_category, levels = c("Low", "Intermediate", "High"), ordered = TRUE) ust_cd_included
5. Calculate discrimination for effectiveness outcomes
VDZ-CD
Clinical response
%<>% mutate(clinical_assessment_before_optimisation = clinical_assessment_date <= dose_optimisation_date) %>%
vdz_cd_included replace_na(list(clinical_assessment_before_optimisation = TRUE))
%<>% mutate(clinical_response_cdst = clinical_response == 1 & clinical_assessment_before_optimisation)
vdz_cd_included
%>% count(cdst_category, clinical_response_cdst) vdz_cd_included
cdst_category | clinical_response_cdst | n |
---|---|---|
Low | FALSE | 7 |
Low | TRUE | 6 |
Intermediate | FALSE | 64 |
Intermediate | TRUE | 82 |
High | FALSE | 31 |
High | TRUE | 90 |
options(scipen = 999)
<- chisq.test(vdz_cd_included$cdst_category, vdz_cd_included$clinical_response_cdst)
chisq_vdz_cd_clinical_response
<- vdz_cd_included %>%
vdz_cd_cdst_tabulation_clinical_response group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(clinical_response_cdst),
percentage = scales::percent(achieved/total))
Clinical remission
%<>% mutate(clinical_remission_cdst = clinical_remission == 1 & clinical_assessment_before_optimisation)
vdz_cd_included
%>% count(cdst_category, clinical_remission_cdst) vdz_cd_included
cdst_category | clinical_remission_cdst | n |
---|---|---|
Low | FALSE | 13 |
Intermediate | FALSE | 126 |
Intermediate | TRUE | 20 |
High | FALSE | 96 |
High | TRUE | 25 |
<- chisq.test(vdz_cd_included$cdst_category, vdz_cd_included$clinical_remission_cdst)
chisq_vdz_cd_clinical_remission
<- vdz_cd_included %>%
vdz_cd_cdst_tabulation_clinical_remission group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(clinical_remission_cdst),
percentage = scales::percent(achieved/total))
Endoscopic response
<- vdz_cd_included %>%
vdz_cd_included_endoscopic filter(endoscopic_assessment == 1)
%<>% mutate(endoscopic_assessment_before_optimisation = endoscopic_assessment_date <= dose_optimisation_date) %>%
vdz_cd_included_endoscopic replace_na(list(endoscopic_assessment_before_optimisation = TRUE))
%<>% mutate(endoscopic_response_cdst = endoscopic_response == 1 & endoscopic_assessment_before_optimisation)
vdz_cd_included_endoscopic
%>% count(cdst_category, endoscopic_response_cdst) vdz_cd_included_endoscopic
cdst_category | endoscopic_response_cdst | n |
---|---|---|
Low | FALSE | 8 |
Low | TRUE | 2 |
Intermediate | FALSE | 51 |
Intermediate | TRUE | 53 |
High | FALSE | 33 |
High | TRUE | 66 |
<- chisq.test(vdz_cd_included_endoscopic$cdst_category, vdz_cd_included_endoscopic$endoscopic_response_cdst)
chisq_vdz_cd_endoscopic_response
<- vdz_cd_included_endoscopic %>%
vdz_cd_cdst_tabulation_endoscopic_response group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(endoscopic_response_cdst),
percentage = scales::percent(achieved/total))
Endoscopic remission
%<>% mutate(endoscopic_remission_cdst = endoscopic_remission == 1 & endoscopic_assessment_before_optimisation)
vdz_cd_included_endoscopic
%>% count(cdst_category, endoscopic_remission_cdst) vdz_cd_included_endoscopic
cdst_category | endoscopic_remission_cdst | n |
---|---|---|
Low | FALSE | 9 |
Low | TRUE | 1 |
Intermediate | FALSE | 84 |
Intermediate | TRUE | 20 |
High | FALSE | 57 |
High | TRUE | 42 |
<- chisq.test(vdz_cd_included_endoscopic$cdst_category, vdz_cd_included_endoscopic$endoscopic_remission_cdst)
chisq_vdz_cd_endoscopic_remission
<- vdz_cd_included_endoscopic %>%
vdz_cd_cdst_tabulation_endoscopic_remission group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(endoscopic_remission_cdst),
percentage = scales::percent(achieved/total))
VDZ-UC
Clinical response
%<>% mutate(clinical_assessment_before_optimisation = clinical_assessment_date <= dose_optimisation_date) %>%
vdz_uc_included replace_na(list(clinical_assessment_before_optimisation = TRUE))
%<>% mutate(clinical_response_cdst = clinical_response_pga == 1 & clinical_assessment_before_optimisation)
vdz_uc_included
%>% count(cdst_category, clinical_response_cdst) vdz_uc_included
cdst_category | clinical_response_cdst | n |
---|---|---|
Low | FALSE | 9 |
Low | TRUE | 9 |
Intermediate | FALSE | 47 |
Intermediate | TRUE | 58 |
High | FALSE | 20 |
High | TRUE | 75 |
<- chisq.test(vdz_uc_included$cdst_category, vdz_uc_included$clinical_response_cdst)
chisq_vdz_uc_clinical_response
<- vdz_uc_included %>%
vdz_uc_cdst_tabulation_clinical_response group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(clinical_response_cdst),
percentage = scales::percent(achieved/total))
Clinical remission
%<>% mutate(clinical_remission_cdst = clinical_remission_pga == 1 & clinical_assessment_before_optimisation)
vdz_uc_included
%>% count(cdst_category, clinical_remission_cdst) vdz_uc_included
cdst_category | clinical_remission_cdst | n |
---|---|---|
Low | FALSE | 15 |
Low | TRUE | 3 |
Intermediate | FALSE | 90 |
Intermediate | TRUE | 15 |
High | FALSE | 62 |
High | TRUE | 33 |
<- chisq.test(vdz_uc_included$cdst_category, vdz_uc_included$clinical_remission_cdst)
chisq_vdz_uc_clinical_remission
<- vdz_uc_included %>%
vdz_uc_cdst_tabulation_clinical_remission group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(clinical_remission_cdst),
percentage = scales::percent(achieved/total))
Endoscopic response
<- vdz_uc_included %>%
vdz_uc_included_endoscopic filter(endoscopic_assessment == 1)
%<>% mutate(endoscopic_assessment_before_optimisation = endoscopic_assessment_date <= dose_optimisation_date) %>%
vdz_uc_included_endoscopic replace_na(list(endoscopic_assessment_before_optimisation = TRUE))
%<>% mutate(endoscopic_response_cdst = endoscopic_response == 1 & endoscopic_assessment_before_optimisation)
vdz_uc_included_endoscopic
%>% count(cdst_category, endoscopic_response_cdst) vdz_uc_included_endoscopic
cdst_category | endoscopic_response_cdst | n |
---|---|---|
Low | FALSE | 10 |
Low | TRUE | 8 |
Intermediate | FALSE | 63 |
Intermediate | TRUE | 42 |
High | FALSE | 26 |
High | TRUE | 68 |
<- chisq.test(vdz_uc_included_endoscopic$cdst_category, vdz_uc_included_endoscopic$endoscopic_response_cdst)
chisq_vdz_uc_endoscopic_response
<- vdz_uc_included_endoscopic %>%
vdz_uc_cdst_tabulation_endoscopic_response group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(endoscopic_response_cdst),
percentage = scales::percent(achieved/total))
Endoscopic remission
%<>% mutate(endoscopic_remission_cdst = endoscopic_remission == 1 & endoscopic_assessment_before_optimisation)
vdz_uc_included_endoscopic
%>% count(cdst_category, endoscopic_remission_cdst) vdz_uc_included_endoscopic
cdst_category | endoscopic_remission_cdst | n |
---|---|---|
Low | FALSE | 11 |
Low | TRUE | 7 |
Intermediate | FALSE | 79 |
Intermediate | TRUE | 26 |
High | FALSE | 53 |
High | TRUE | 41 |
<- chisq.test(vdz_uc_included_endoscopic$cdst_category, vdz_uc_included_endoscopic$endoscopic_remission_cdst)
chisq_vdz_uc_endoscopic_remission
<- vdz_uc_included_endoscopic %>%
vdz_uc_cdst_tabulation_endoscopic_remission group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(endoscopic_remission_cdst),
percentage = scales::percent(achieved/total))
UST-CD
Clinical response
%<>% mutate(clinical_assessment_before_optimisation = clinical_assessment_date <= first_dose_optimisation_date) %>%
ust_cd_included replace_na(list(clinical_assessment_before_optimisation = TRUE))
%<>% mutate(clinical_response_cdst = clinical_response == 1 & clinical_assessment_before_optimisation)
ust_cd_included
%>% count(cdst_category, clinical_response_cdst) ust_cd_included
cdst_category | clinical_response_cdst | n |
---|---|---|
Low | FALSE | 4 |
Low | TRUE | 15 |
Intermediate | FALSE | 45 |
Intermediate | TRUE | 50 |
High | FALSE | 31 |
High | TRUE | 49 |
<- chisq.test(ust_cd_included$cdst_category, ust_cd_included$clinical_response_cdst)
chisq_ust_cd_clinical_response
<- ust_cd_included %>%
ust_cd_cdst_tabulation_clinical_response group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(clinical_response_cdst),
percentage = scales::percent(achieved/total))
Clinical remission
%<>% mutate(clinical_remission_cdst = clinical_remission == 1 & clinical_assessment_before_optimisation)
ust_cd_included
%>% count(cdst_category, clinical_remission_cdst) ust_cd_included
cdst_category | clinical_remission_cdst | n |
---|---|---|
Low | FALSE | 16 |
Low | TRUE | 3 |
Intermediate | FALSE | 86 |
Intermediate | TRUE | 9 |
High | FALSE | 61 |
High | TRUE | 19 |
<- chisq.test(ust_cd_included$cdst_category, ust_cd_included$clinical_remission_cdst)
chisq_ust_cd_clinical_remission
<- ust_cd_included %>%
ust_cd_cdst_tabulation_clinical_remission group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(clinical_remission_cdst),
percentage = scales::percent(achieved/total))
Endoscopic response
<- ust_cd_included %>%
ust_cd_included_endoscopic filter(endoscopic_assessment == 1)
%<>% mutate(endoscopic_assessment_before_optimisation = endoscopic_assessment_date <= first_dose_optimisation_date) %>%
ust_cd_included_endoscopic replace_na(list(endoscopic_assessment_before_optimisation = TRUE))
%<>% mutate(endoscopic_response_cdst = endoscopic_response == 1 & endoscopic_assessment_before_optimisation)
ust_cd_included_endoscopic
%>% count(cdst_category, endoscopic_response_cdst) ust_cd_included_endoscopic
cdst_category | endoscopic_response_cdst | n |
---|---|---|
Low | FALSE | 6 |
Low | TRUE | 11 |
Intermediate | FALSE | 39 |
Intermediate | TRUE | 42 |
High | FALSE | 33 |
High | TRUE | 35 |
<- chisq.test(ust_cd_included_endoscopic$cdst_category, ust_cd_included_endoscopic$endoscopic_response_cdst)
chisq_ust_cd_endoscopic_response
<- ust_cd_included_endoscopic %>%
ust_cd_cdst_tabulation_endoscopic_response group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(endoscopic_response_cdst),
percentage = scales::percent(achieved/total))
Endoscopic remission
%<>% mutate(endoscopic_remission_cdst = endoscopic_remission == 1 & endoscopic_assessment_before_optimisation)
ust_cd_included_endoscopic
%>% count(cdst_category, endoscopic_remission_cdst) ust_cd_included_endoscopic
cdst_category | endoscopic_remission_cdst | n |
---|---|---|
Low | FALSE | 15 |
Low | TRUE | 2 |
Intermediate | FALSE | 74 |
Intermediate | TRUE | 7 |
High | FALSE | 54 |
High | TRUE | 14 |
<- chisq.test(ust_cd_included_endoscopic$cdst_category, ust_cd_included_endoscopic$endoscopic_remission_cdst)
chisq_ust_cd_endoscopic_remission
<- ust_cd_included_endoscopic %>%
ust_cd_cdst_tabulation_endoscopic_remission group_by(cdst_category) %>%
summarise(total = n(),
achieved = sum(endoscopic_remission_cdst),
percentage = scales::percent(achieved/total))
6. Visualize discrimination for effectiveness outcomes
VDZ-CD
<- ggplot(data = vdz_cd_included, aes(x = cdst_category, fill = clinical_response_cdst)) +
vdz_cd_clinical_response_gg geom_bar() +
scale_y_continuous(limits = c(0, 160), breaks = seq(0, 160, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Clinical response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
axis.title.x = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_cd_cdst_tabulation_clinical_response$percentage, "\n", "(", vdz_cd_cdst_tabulation_clinical_response$achieved, "/", vdz_cd_cdst_tabulation_clinical_response$total, ")"),
x = 1:3, y = vdz_cd_cdst_tabulation_clinical_response$achieved + 7,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_cd_clinical_response$p.value), x = 2, y = 158.5, size = 4, fontface = "bold")
<- ggplot(data = vdz_cd_included, aes(x = cdst_category, fill = clinical_remission_cdst)) +
vdz_cd_clinical_remission_gg geom_bar() +
scale_y_continuous(limits = c(0, 160), breaks = seq(0, 160, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Clinical remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_cd_cdst_tabulation_clinical_remission$percentage, "\n", "(", vdz_cd_cdst_tabulation_clinical_remission$achieved, "/", vdz_cd_cdst_tabulation_clinical_remission$total, ")"),
x = 1:3, y = vdz_cd_cdst_tabulation_clinical_remission$achieved + 7,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_cd_clinical_remission$p.value), x = 2, y = 158.5, size = 4, fontface = "bold")
<- ggplot(data = vdz_cd_included_endoscopic, aes(x = cdst_category, fill = endoscopic_response_cdst)) +
vdz_cd_endoscopic_response_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Endoscopic response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.text = element_text(face = "bold"),
axis.title.x = element_blank(),
legend.position = "none") +
annotate(geom = "text", label = paste0(vdz_cd_cdst_tabulation_endoscopic_response$percentage, "\n", "(", vdz_cd_cdst_tabulation_endoscopic_response$achieved, "/", vdz_cd_cdst_tabulation_endoscopic_response$total, ")"),
x = 1:3, y = vdz_cd_cdst_tabulation_endoscopic_response$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_cd_endoscopic_response$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
<- ggplot(data = vdz_cd_included_endoscopic, aes(x = cdst_category, fill = endoscopic_remission_cdst)) +
vdz_cd_endoscopic_remission_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(title = "Endoscopic remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.text = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_cd_cdst_tabulation_endoscopic_remission$percentage, "\n", "(", vdz_cd_cdst_tabulation_endoscopic_remission$achieved, "/", vdz_cd_cdst_tabulation_endoscopic_remission$total, ")"),
x = 1:3, y = vdz_cd_cdst_tabulation_endoscopic_remission$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_cd_endoscopic_remission$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
Combine figures in 4 panels
<- ggarrange(vdz_cd_clinical_response_gg, vdz_cd_clinical_remission_gg, vdz_cd_endoscopic_response_gg, vdz_cd_endoscopic_remission_gg, nrow = 2, ncol = 2)
vdz_cd_bars_combined
annotate_figure(vdz_cd_bars_combined, bottom = text_grob("Clinical Decision Support Tool's response probability groups", face = "bold", vjust = 0.1))
VDZ-UC
<- ggplot(data = vdz_uc_included, aes(x = cdst_category, fill = clinical_response_cdst)) +
vdz_uc_clinical_response_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Clinical response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
axis.title.x = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_uc_cdst_tabulation_clinical_response$percentage, "\n", "(", vdz_uc_cdst_tabulation_clinical_response$achieved, "/", vdz_uc_cdst_tabulation_clinical_response$total, ")"),
x = 1:3, y = vdz_uc_cdst_tabulation_clinical_response$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_uc_clinical_response$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
<- ggplot(data = vdz_uc_included, aes(x = cdst_category, fill = clinical_remission_cdst)) +
vdz_uc_clinical_remission_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Clinical remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_uc_cdst_tabulation_clinical_remission$percentage, "\n", "(", vdz_uc_cdst_tabulation_clinical_remission$achieved, "/", vdz_uc_cdst_tabulation_clinical_remission$total, ")"),
x = 1:3, y = vdz_uc_cdst_tabulation_clinical_remission$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_uc_clinical_remission$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
<- ggplot(data = vdz_uc_included_endoscopic, aes(x = cdst_category, fill = endoscopic_response_cdst)) +
vdz_uc_endoscopic_response_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Endoscopic response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.text = element_text(face = "bold"),
axis.title.x = element_blank(),
legend.position = "none") +
annotate(geom = "text", label = paste0(vdz_uc_cdst_tabulation_endoscopic_response$percentage, "\n", "(", vdz_uc_cdst_tabulation_endoscopic_response$achieved, "/", vdz_uc_cdst_tabulation_endoscopic_response$total, ")"),
x = 1:3, y = vdz_uc_cdst_tabulation_endoscopic_response$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_uc_endoscopic_response$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
<- ggplot(data = vdz_uc_included_endoscopic, aes(x = cdst_category, fill = endoscopic_remission_cdst)) +
vdz_uc_endoscopic_remission_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(title = "Endoscopic remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.text = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_uc_cdst_tabulation_endoscopic_remission$percentage, "\n", "(", vdz_uc_cdst_tabulation_endoscopic_remission$achieved, "/", vdz_uc_cdst_tabulation_endoscopic_remission$total, ")"),
x = 1:3, y = vdz_uc_cdst_tabulation_endoscopic_remission$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_uc_endoscopic_remission$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
Combine figures in 4 panels
<- ggarrange(vdz_uc_clinical_response_gg, vdz_uc_clinical_remission_gg, vdz_uc_endoscopic_response_gg, vdz_uc_endoscopic_remission_gg, nrow = 2, ncol = 2)
vdz_uc_bars_combined
annotate_figure(vdz_uc_bars_combined, bottom = text_grob("Clinical Decision Support Tool's response probability groups", face = "bold", vjust = 0.1))
UST-CD
<- ggplot(data = ust_cd_included, aes(x = cdst_category, fill = clinical_response_cdst)) +
ust_cd_clinical_response_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Clinical response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
axis.title.x = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_clinical_response$percentage, "\n", "(", ust_cd_cdst_tabulation_clinical_response$achieved, "/", ust_cd_cdst_tabulation_clinical_response$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_clinical_response$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_clinical_response$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
<- ggplot(data = ust_cd_included, aes(x = cdst_category, fill = clinical_remission_cdst)) +
ust_cd_clinical_remission_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Clinical remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_clinical_remission$percentage, "\n", "(", ust_cd_cdst_tabulation_clinical_remission$achieved, "/", ust_cd_cdst_tabulation_clinical_remission$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_clinical_remission$achieved + 5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_clinical_remission$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
<- ggplot(data = ust_cd_included_endoscopic, aes(x = cdst_category, fill = endoscopic_response_cdst)) +
ust_cd_endoscopic_response_gg geom_bar() +
scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Endoscopic response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.text = element_text(face = "bold"),
axis.title.x = element_blank(),
legend.position = "none") +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_endoscopic_response$percentage, "\n", "(", ust_cd_cdst_tabulation_endoscopic_response$achieved, "/", ust_cd_cdst_tabulation_endoscopic_response$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_endoscopic_response$achieved + 4,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_endoscopic_response$p.value), x = 2, y = 98.5, size = 4, fontface = "bold")
<- ggplot(data = ust_cd_included_endoscopic, aes(x = cdst_category, fill = endoscopic_remission_cdst)) +
ust_cd_endoscopic_remission_gg geom_bar() +
scale_y_continuous(limits = c(0, 100), breaks = seq(0, 100, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(title = "Endoscopic remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.text = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_endoscopic_remission$percentage, "\n", "(", ust_cd_cdst_tabulation_endoscopic_remission$achieved, "/", ust_cd_cdst_tabulation_endoscopic_remission$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_endoscopic_remission$achieved + 4,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_endoscopic_remission$p.value), x = 2, y = 98.5, size = 4, fontface = "bold")
Combine them in 4 panels
<- ggarrange(ust_cd_clinical_response_gg, ust_cd_clinical_remission_gg, ust_cd_endoscopic_response_gg, ust_cd_endoscopic_remission_gg, nrow = 2, ncol = 2)
ust_cd_bars_combined
annotate_figure(ust_cd_bars_combined, bottom = text_grob("Clinical Decision Support Tool's response probability groups", face = "bold", vjust = 0.1))
7. Calculate other discrimination metrics for effectiveness outcomes
VDZ-CD
%<>% mutate(cdst_category_high = cdst_category == "High")
vdz_cd_included
<- vdz_cd_included %>%
vector_vdz_cd_discrimination count(cdst_category_high, clinical_remission_cdst) %>%
arrange(cdst_category_high, clinical_remission_cdst)
<- rev(vector_vdz_cd_discrimination$n)
vector_vdz_cd_discrimination
<- epi.tests(vector_vdz_cd_discrimination, method = "exact", digits = 2,
epi_test_vdz_cd conf.level = 0.95)
<- roc(vdz_cd_included$clinical_remission_cdst, vdz_cd_included$cdst_points) roc_vdz_cd
VDZ-UC
%<>% mutate(cdst_category_high = cdst_category == "High")
vdz_uc_included
<- vdz_uc_included %>%
vector_vdz_uc_discrimination count(cdst_category_high, clinical_remission_cdst) %>%
arrange(cdst_category_high, clinical_remission_cdst)
<- rev(vector_vdz_uc_discrimination$n)
vector_vdz_uc_discrimination
<- epi.tests(vector_vdz_uc_discrimination, method = "exact", digits = 2,
epi_test_vdz_uc conf.level = 0.95)
<- roc(vdz_uc_included$clinical_remission_cdst, vdz_uc_included$cdst_points) roc_vdz_uc
UST-CD
%<>% mutate(cdst_category_high = cdst_category == "High")
ust_cd_included
<- ust_cd_included %>%
vector_ust_cd_discrimination count(cdst_category_high, clinical_remission_cdst) %>%
arrange(cdst_category_high, clinical_remission_cdst)
<- rev(vector_ust_cd_discrimination$n)
vector_ust_cd_discrimination
<- epi.tests(vector_ust_cd_discrimination, method = "exact", digits = 2,
epi_test_ust_cd conf.level = 0.95)
Combine metrics
<- bind_rows("vdz-cd" = epi_test_vdz_cd$detail[epi_test_vdz_cd$detail[["statistic"]] %in% c("se", "sp", "lr.pos", "lr.neg"),],
metrics_combined "vdz-uc" = epi_test_vdz_uc$detail[epi_test_vdz_uc$detail[["statistic"]] %in% c("se", "sp", "lr.pos", "lr.neg"),],
"ust-cd" = epi_test_vdz_cd$detail[epi_test_vdz_cd$detail[["statistic"]] %in% c("se", "sp", "lr.pos", "lr.neg"),],
.id = "tool")
%<>%
metrics_combined mutate(across(c(est, lower, upper), ~ if_else(statistic %in% c("se", "sp"), . * 100, .))) %>%
mutate(across(c(est, lower, upper),
~ if_else(statistic %in% c("se", "sp"), round(., 1), round(., 2))))
%<>%
metrics_combined mutate(across(where(is.numeric), ~ round(., 2))) %>%
mutate(value = str_glue("{est} ({lower}-{upper})")) %>%
select(-c(est, lower, upper))
%<>%
metrics_combined pivot_wider(names_from = statistic, values_from = value)
<- glue("{round(ci(roc_vdz_cd)[2] * 100, 1)} ({round(ci(roc_vdz_cd)[1] * 100, 1)}-{round(ci(roc_vdz_cd)[3] * 100, 1)})")
roc_vdz_cd_ci <- glue("{round(ci(roc_vdz_uc)[2] * 100, 1)} ({round(ci(roc_vdz_uc)[1] * 100, 1)}-{round(ci(roc_vdz_uc)[3] * 100, 1)})")
roc_vdz_uc_ci
<- cbind(metrics_combined, auc = c(roc_vdz_cd_ci, roc_vdz_uc_ci, "*"))
metrics_combined
$tool <- c("VDZ in CD patients", "VDZ in UC patients", "UST in CD patients")
metrics_combined
colnames(metrics_combined) <- c(
"CDST",
"Sensitivity (%) (95% CI)",
"Specificity (%) (95% CI)",
"PLR (95% CI)",
"NLR (95% CI)",
"AUC (%) (95% CI)"
)
%>%
metrics_combined ::gt() %>%
gt::tab_style(
gtstyle = gt::cell_text(align = "left"),
locations = gt::cells_body(columns = c(CDST))
%>%
) ::tab_style(
gtstyle = gt::cell_text(align = "center"),
locations = gt::cells_body(columns = c(`Sensitivity (%) (95% CI)`, `Specificity (%) (95% CI)`, `PLR (95% CI)`, `NLR (95% CI)`, `AUC (%) (95% CI)`))
%>%
) ::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_labels(everything())
)
CDST | Sensitivity (%) (95% CI) | Specificity (%) (95% CI) | PLR (95% CI) | NLR (95% CI) | AUC (%) (95% CI) |
---|---|---|---|---|---|
VDZ in CD patients | 55.6 (40-70.4) | 59.1 (52.6-65.5) | 1.36 (1-1.84) | 0.75 (0.53-1.06) | 61.3 (53.2-69.4) |
VDZ in UC patients | 64.7 (50.1-77.6) | 62.9 (55.1-70.2) | 1.74 (1.31-2.31) | 0.56 (0.38-0.83) | 65 (56.3-73.7) |
UST in CD patients | 55.6 (40-70.4) | 59.1 (52.6-65.5) | 1.36 (1-1.84) | 0.75 (0.53-1.06) | * |
8. Get rates of therapy outcomes
<- c("clinical_response", "clinical_response_pga", "clinical_response_cdst", "clinical_remission",
outcomes_not_endoscopic "clinical_remission_pga", "clinical_remission_cdst", "persistence", "persistence_without_escalation")
<- c("endoscopic_response", "endoscopic_response_cdst", "endoscopic_remission", "endoscopic_remission_cdst")
outcomes_endoscopic
<- outcome_pct(vdz_cd_included, outcomes_not_endoscopic)
vdz_cd_outcomes_not_endoscopic
<- outcome_pct(vdz_cd_included_endoscopic, outcomes_endoscopic)
vdz_cd_outcomes_endoscopic
<- bind_rows(vdz_cd_outcomes_endoscopic, vdz_cd_outcomes_not_endoscopic)
vdz_cd_outcomes
<- outcome_pct(vdz_uc_included, outcomes_not_endoscopic)
vdz_uc_outcomes_not_endoscopic
<- outcome_pct(vdz_uc_included_endoscopic, outcomes_endoscopic)
vdz_uc_outcomes_endoscopic
<- bind_rows(vdz_uc_outcomes_endoscopic, vdz_uc_outcomes_not_endoscopic)
vdz_uc_outcomes
<- outcome_pct(ust_cd_included, outcomes_not_endoscopic)
ust_cd_outcomes_not_endoscopic
<- outcome_pct(ust_cd_included_endoscopic, outcomes_endoscopic)
ust_cd_outcomes_endoscopic
<- bind_rows(ust_cd_outcomes_endoscopic, ust_cd_outcomes_not_endoscopic)
ust_cd_outcomes
<- vdz_cd_outcomes %>% select(outcome, vdz_cd = report) %>%
combined_outcomes bind_cols(vdz_uc_outcomes %>% select(vdz_uc = report),
%>% select(ust_cd = report)) %>%
ust_cd_outcomes mutate(outcome = str_replace_all(outcome, "_", " "),
outcome = str_to_sentence(outcome),
outcome = str_replace(outcome, "cdst", "without escalation"))
%>%
combined_outcomes ::gt() %>%
gt::cols_label(
gtoutcome = "Outcome",
vdz_cd = "VDZ-CD",
vdz_uc = "VDZ-UC",
ust_cd = "UST-CD"
%>%
) ::tab_style(
gtstyle = gt::cell_text(align = "left"),
locations = gt::cells_body(columns = outcome)
%>%
) ::tab_style(
gtstyle = gt::cell_text(align = "center"),
locations = gt::cells_body(columns = c(vdz_cd, vdz_uc, ust_cd))
%>%
) ::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_labels(everything())
)
Outcome | VDZ-CD | VDZ-UC | UST-CD |
---|---|---|---|
Endoscopic response | 123/213 (57.7%) | 118/217 (54.4%) | 89/166 (53.6%) |
Endoscopic response without escalation | 121/213 (56.8%) | 118/217 (54.4%) | 88/166 (53%) |
Endoscopic remission | 64/213 (30%) | 74/217 (34.1%) | 24/166 (14.5%) |
Endoscopic remission without escalation | 63/213 (29.6%) | 74/217 (34.1%) | 23/166 (13.9%) |
Clinical response | 185/280 (66.1%) | 142/218 (65.1%) | 116/194 (59.8%) |
Clinical response without escalation | 178/280 (63.6%) | 142/218 (65.1%) | 114/194 (58.8%) |
Clinical remission | 45/280 (16.1%) | 51/218 (23.4%) | 31/194 (16%) |
Clinical remission without escalation | 45/280 (16.1%) | 51/218 (23.4%) | 31/194 (16%) |
9. Calculate & visualize discrimination for trough levels
Load trough data and get it in shape for analysis
load("input/VDZ_UST_troughs.RData")
<- UST_troughs %>% filter(interval == 16)
UST_troughs_induction <- VDZ_troughs %>% filter(between(interval, 13, 15))
VDZ_troughs_induction
<- power_inner_join(vdz_uc_included %>% select_keys_and(cdst_category),
cdst_vdz_uc_troughs %>% select_keys_and(trough),
VDZ_troughs_induction by = "patient_id")
%>% count(cdst_category) cdst_vdz_uc_troughs
cdst_category | n |
---|---|
Low | 6 |
Intermediate | 54 |
High | 45 |
%<>% group_by(cdst_category) %>% mutate(median = round(median(trough),1), total = n()) %>% ungroup()
cdst_vdz_uc_troughs
<- power_inner_join(vdz_cd_included %>% select_keys_and(cdst_category),
cdst_vdz_cd_troughs %>% select_keys_and(trough),
VDZ_troughs_induction by = "patient_id")
%>% count(cdst_category) cdst_vdz_cd_troughs
cdst_category | n |
---|---|
Low | 11 |
Intermediate | 80 |
High | 55 |
%<>% group_by(cdst_category) %>% mutate(median = round(median(trough),1), total = n()) %>% ungroup()
cdst_vdz_cd_troughs
<- power_inner_join(ust_cd_included %>% select_keys_and(cdst_category),
cdst_ust_cd_troughs %>% select_keys_and(trough),
VDZ_troughs_induction by = "patient_id")
%>% count(cdst_category) cdst_ust_cd_troughs
cdst_category | n |
---|---|
Low | 5 |
Intermediate | 24 |
High | 18 |
%<>% group_by(cdst_category) %>% mutate(median = round(median(trough),1), total = n()) %>% ungroup()
cdst_ust_cd_troughs
<- cdst_vdz_cd_troughs %>%
vdz_cd_troughs_medians distinct(cdst_category, median, total)
<- cdst_vdz_uc_troughs %>%
vdz_uc_troughs_medians distinct(cdst_category, median, total)
<- cdst_ust_cd_troughs %>%
ust_cd_troughs_medians distinct(cdst_category, median, total)
Test for significance
<- kruskal.test(trough ~ cdst_category, data = cdst_vdz_cd_troughs)
kruskal_vdz_cd_troughs <- kruskal.test(trough ~ cdst_category, data = cdst_vdz_uc_troughs)
kruskal_vdz_uc_troughs <- kruskal.test(trough ~ cdst_category, data = cdst_ust_cd_troughs) kruskal_ust_cd_troughs
Make box plots per cdst category
<- ggplot(data = cdst_vdz_cd_troughs, aes(x = cdst_category, y = trough)) +
vdz_cd_troughs_gg geom_boxplot(width = 0.35, outlier.shape = NA, coef = FALSE)+
geom_jitter(size = 0.4) +
geom_text(data = vdz_cd_troughs_medians, aes(label = str_c("median = ", median, " \n(n", "=", total, ")"), y = -0.5), size = 3, fontface = "bold") +
coord_cartesian(expand = 1.5, ylim = c(0, 70)) +
scale_y_continuous(breaks = seq(0, 70, 10)) +
theme(axis.ticks.x = element_blank(),
panel.background = element_blank(),
axis.line.y = element_line(),
axis.title = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "None") +
annotate(geom = "text", label = format_p_value(kruskal_vdz_cd_troughs$p.value), x = 2, y = 68, size = 4, fontface = "bold")
<- ggplot(data = cdst_vdz_uc_troughs, aes(x = cdst_category, y = trough)) +
vdz_uc_troughs_gg geom_boxplot(width = 0.35, outlier.shape = NA, coef = FALSE)+
geom_jitter(size = 0.4) +
geom_text(data = vdz_uc_troughs_medians, aes(label = str_c("median = ", median, " \n(n", "=", total, ")"), y = -0.5), size = 3, fontface = "bold") +
coord_cartesian(expand = 1.5, ylim = c(0, 41)) +
scale_y_continuous(breaks = seq(0, 41, 10)) +
theme(axis.ticks.x = element_blank(),
panel.background = element_blank(),
axis.line.y = element_line(),
axis.title = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "None") +
annotate(geom = "text", label = format_p_value(kruskal_vdz_uc_troughs$p.value), x = 2, y = 40, size = 4, fontface = "bold")
<- ggplot(data = cdst_ust_cd_troughs, aes(x = cdst_category, y = trough)) +
ust_cd_troughs_gg geom_boxplot(width = 0.35, outlier.shape = NA, coef = FALSE)+
geom_jitter(size = 0.4) +
geom_text(data = ust_cd_troughs_medians, aes(label = str_c("median = ", median, " \n(n", "=", total, ")"), y = -0.5), size = 3, fontface = "bold") +
coord_cartesian(expand = 1.5, ylim = c(0, 50)) +
scale_y_continuous(breaks = seq(0, 50, 10)) +
theme(axis.ticks.x = element_blank(),
panel.background = element_blank(),
axis.line.y = element_line(),
axis.title = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "None") +
annotate(geom = "text", label = format_p_value(kruskal_ust_cd_troughs$p.value), x = 2, y = 48.8, size = 4, fontface = "bold")
Combine the plots
<- ggarrange(vdz_cd_troughs_gg, vdz_uc_troughs_gg, ust_cd_troughs_gg, labels = c("A", "B", "C"), nrow = 1, ncol = 3,
troughs_combined label.x = c(-0.05, -0.05, -0.05))
annotate_figure(troughs_combined, bottom = text_grob("Clinical Decision Support Tool's response probability groups", face = "bold", vjust = 0.1),
left = text_grob("Post-induction trough levels (ug/mL)", face = "bold", vjust = 0.4, rot = 90))
10. Calculate & visualize discrimination for dose optimization
VDZ-CD
%<>% mutate(dose_optimisation = recode(dose_optimisation,
vdz_cd_included "0" = FALSE,
"1" = TRUE))
%>% count(cdst_category, dose_optimisation) vdz_cd_included
cdst_category | dose_optimisation | n |
---|---|---|
Low | FALSE | 7 |
Low | TRUE | 6 |
Intermediate | FALSE | 88 |
Intermediate | TRUE | 58 |
High | FALSE | 82 |
High | TRUE | 39 |
<- chisq.test(vdz_cd_included$cdst_category, vdz_cd_included$dose_optimisation)
chisq_vdz_cd_optimisation
<- vdz_cd_included %>%
vdz_cd_cdst_tabulation_optimisation group_by(cdst_category) %>%
summarise(total = n(),
optimized = sum(dose_optimisation),
percentage =scales::percent(optimized/total))
<- ggplot(data = vdz_cd_included, aes(x = cdst_category, fill = dose_optimisation)) +
vdz_cd_optimisation_gg geom_bar() +
scale_y_continuous(limits = c(0, 160), breaks = seq(0, 160, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Crohn’s disease") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_cd_cdst_tabulation_optimisation$percentage, "\n", "(", vdz_cd_cdst_tabulation_optimisation$optimized, "/", vdz_cd_cdst_tabulation_optimisation$total, ")"),
x = 1:3, y = vdz_cd_cdst_tabulation_optimisation$optimized + 4.5,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_cd_optimisation$p.value), x = 2, y = 158, size = 4, fontface = "bold")
VDZ-UC
%<>% mutate(dose_optimisation = recode(dose_optimisation,
vdz_uc_included "0" = FALSE,
"1" = TRUE))
%>% count(cdst_category, dose_optimisation) vdz_uc_included
cdst_category | dose_optimisation | n |
---|---|---|
Low | FALSE | 9 |
Low | TRUE | 9 |
Intermediate | FALSE | 62 |
Intermediate | TRUE | 43 |
High | FALSE | 66 |
High | TRUE | 29 |
<- chisq.test(vdz_uc_included$cdst_category, vdz_uc_included$dose_optimisation)
chisq_vdz_uc_optimisation
<- vdz_uc_included %>%
vdz_uc_cdst_tabulation_optimisation group_by(cdst_category) %>%
summarise(total = n(),
optimized = sum(dose_optimisation),
percentage =scales::percent(optimized/total))
<- ggplot(data = vdz_uc_included, aes(x = cdst_category, fill = dose_optimisation)) +
vdz_uc_optimisation_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(title = "Ulcerative Colitis") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold"),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none",
plot.title = element_text(hjust = 0.5, face = "bold", size = 13)) +
annotate(geom = "text", label = paste0(vdz_uc_cdst_tabulation_optimisation$percentage, "\n", "(", vdz_uc_cdst_tabulation_optimisation$optimized, "/", vdz_uc_cdst_tabulation_optimisation$total, ")"),
x = 1:3, y = vdz_uc_cdst_tabulation_optimisation$optimized + 3.3,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_vdz_uc_optimisation$p.value), x = 2, y = 118.4, size = 4, fontface = "bold")
Combine plots in 2 panels
<- ggarrange(vdz_cd_optimisation_gg, vdz_uc_optimisation_gg, nrow = 1, ncol = 2)
vdz_uc_optimisation_combined
annotate_figure(vdz_uc_optimisation_combined, bottom = text_grob("Clinical Decision Support Tool's response probability groups", face = "bold", vjust = 0.1))
11. Assess specificity of the VDZ-CD tool in UST-CD cohort
Prepare VDZ-CD tool variables
%<>% mutate(no_previous_antitnf = if_else(previous_antitnf == 1, 0, 1), .after = previous_antitnf)
ust_cd_included
is.numeric(ust_cd_included$no_previous_antitnf)
[1] TRUE
%<>% mutate(no_surgery_before = if_else(intestinal_resections_before == 1, 0, 1), .after = intestinal_resections_before)
ust_cd_included
is.numeric(ust_cd_included$no_surgery_before)
[1] TRUE
%<>% mutate(no_fistulizing_before = if_else(fistulizing_disease_prior == 1, 0, 1), .after = fistulizing_disease_prior)
ust_cd_included
is.numeric(ust_cd_included$no_fistulizing_before)
[1] TRUE
is.numeric(ust_cd_included$albumin_g_L)
[1] TRUE
is.numeric(ust_cd_included$crp_mg_L)
[1] TRUE
Calculate CDST points for the vdz tool
%<>% mutate(cdst_points_vdz = 2*no_surgery_before + 3*no_previous_antitnf + 2*no_fistulizing_before + 0.4*albumin_g_L - case_when(crp_mg_L >= 3 & crp_mg_L <= 10 ~ 0.5,
ust_cd_included > 10 ~ 3,
crp_mg_L .default = 0))
Assign CDST categories for the vdz tool
%<>% mutate(cdst_category_vdz = case_when(cdst_points_vdz <= 13 ~ "Low",
ust_cd_included > 13 & cdst_points_vdz <= 19 ~ "Intermediate",
cdst_points_vdz > 19 ~ "High")
cdst_points_vdz
)
%>% count(cdst_category_vdz) ust_cd_included
cdst_category_vdz | n |
---|---|
High | 67 |
Intermediate | 114 |
Low | 13 |
$cdst_category_vdz <- factor(ust_cd_included$cdst_category_vdz, levels = c("Low", "Intermediate", "High"), ordered = TRUE) ust_cd_included
Get agreement percentage between vdz and ust tools
%>%
ust_cd_included summarise(agreement = round(sum(cdst_category == cdst_category_vdz)/n()*100, 1))
agreement |
---|
73.7 |
%>%
ust_cd_included filter(clinical_remission_cdst == 1) %>%
summarise(agreement = round(sum(cdst_category == cdst_category_vdz)/n()*100, 1))
agreement |
---|
77.4 |
%>%
ust_cd_included filter(clinical_response_cdst == 0) %>%
summarise(agreement = round(sum(cdst_category == cdst_category_vdz)/n()*100, 1))
agreement |
---|
73.8 |
Calculate discrimination percentages for: Clinica response
<- chisq.test(ust_cd_included$cdst_category_vdz, ust_cd_included$clinical_response_cdst)
chisq_ust_cd_clinical_response_vdz
<- ust_cd_included %>%
ust_cd_cdst_tabulation_clinical_response_vdz group_by(cdst_category_vdz) %>%
summarise(total = n(),
achieved = sum(clinical_response_cdst),
percentage =scales::percent(achieved/total))
Clinica remission
<- chisq.test(ust_cd_included$cdst_category_vdz, ust_cd_included$clinical_remission_cdst)
chisq_ust_cd_clinical_remission_vdz
<- ust_cd_included %>%
ust_cd_cdst_tabulation_clinical_remission_vdz group_by(cdst_category_vdz) %>%
summarise(total = n(),
achieved = sum(clinical_remission_cdst),
percentage =scales::percent(achieved/total))
Endoscopic response
<- power_left_join(ust_cd_included_endoscopic,
ust_cd_included_endoscopic %>% select_keys_and(cdst_category_vdz),
ust_cd_included by = "patient_id")
<- chisq.test(ust_cd_included_endoscopic$cdst_category_vdz, ust_cd_included_endoscopic$endoscopic_response_cdst)
chisq_ust_cd_endoscopic_response_vdz
<- ust_cd_included_endoscopic %>%
ust_cd_cdst_tabulation_endoscopic_response_vdz group_by(cdst_category_vdz) %>%
summarise(total = n(),
achieved = sum(endoscopic_response_cdst),
percentage =scales::percent(achieved/total))
Endoscopic remission
<- chisq.test(ust_cd_included_endoscopic$cdst_category_vdz, ust_cd_included_endoscopic$endoscopic_remission_cdst)
chisq_ust_cd_endoscopic_remission_vdz
<- ust_cd_included_endoscopic %>%
ust_cd_cdst_tabulation_endoscopic_remission_vdz group_by(cdst_category_vdz) %>%
summarise(total = n(),
achieved = sum(endoscopic_remission_cdst),
percentage =scales::percent(achieved/total))
Visualize discrimination bars
<- ggplot(data = ust_cd_included, aes(x = cdst_category_vdz, fill = clinical_response_cdst)) +
ust_cd_clinical_response_vdz_gg geom_bar() +
scale_y_continuous(limits = c(0, 140), breaks = seq(0, 140, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Clinical response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.title.x = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none") +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_clinical_response_vdz$percentage, "\n", "(", ust_cd_cdst_tabulation_clinical_response_vdz$achieved, "/", ust_cd_cdst_tabulation_clinical_response_vdz$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_clinical_response_vdz$achieved + 6,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_clinical_response_vdz$p.value), x = 2, y = 138.5, size = 4, fontface = "bold")
<- ggplot(data = ust_cd_included, aes(x = cdst_category_vdz, fill = clinical_remission_cdst)) +
ust_cd_clinical_remission_vdz_gg geom_bar() +
scale_y_continuous(limits = c(0, 140), breaks = seq(0, 140, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(title = "Clinical remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none") +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_clinical_remission_vdz$percentage, "\n", "(", ust_cd_cdst_tabulation_clinical_remission_vdz$achieved, "/", ust_cd_cdst_tabulation_clinical_remission_vdz$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_clinical_remission_vdz$achieved + 6,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_clinical_remission_vdz$p.value), x = 2, y = 138.5, size = 4, fontface = "bold")
<- ggplot(data = ust_cd_included_endoscopic, aes(x = cdst_category_vdz, fill = endoscopic_response_cdst)) +
ust_cd_endoscopic_response_vdz_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(y = "Number of patients", title = "Endoscopic response") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.title.x = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none") +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_endoscopic_response_vdz$percentage, "\n", "(", ust_cd_cdst_tabulation_endoscopic_response_vdz$achieved, "/", ust_cd_cdst_tabulation_endoscopic_response_vdz$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_endoscopic_response_vdz$achieved + 5.3,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_endoscopic_response_vdz$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
<- ggplot(data = ust_cd_included_endoscopic, aes(x = cdst_category_vdz, fill = endoscopic_remission_cdst)) +
ust_cd_endoscopic_remission_vdz_gg geom_bar() +
scale_y_continuous(limits = c(0, 120), breaks = seq(0, 120, 20)) +
scale_fill_manual(values = c("#D55E00", "#999999"), labels = c("yes", "no"), breaks = c(TRUE, FALSE)) +
coord_cartesian(expand = 0) +
labs(title = "Endoscopic remission") +
theme(axis.ticks = element_blank(),
panel.background = element_blank(),
axis.title = element_text(face = "bold", size = 13),
plot.title = element_text(hjust = 0.5, face = "bold", size = 13),
axis.title.x = element_blank(),
axis.title.y = element_blank(),
axis.text = element_text(face = "bold"),
legend.position = "none") +
annotate(geom = "text", label = paste0(ust_cd_cdst_tabulation_endoscopic_remission_vdz$percentage, "\n", "(", ust_cd_cdst_tabulation_endoscopic_remission_vdz$achieved, "/", ust_cd_cdst_tabulation_endoscopic_remission_vdz$total, ")"),
x = 1:3, y = ust_cd_cdst_tabulation_endoscopic_remission_vdz$achieved + 5.3,
size = 3, fontface = "bold") +
annotate(geom = "text", label = format_p_value(chisq_ust_cd_endoscopic_remission_vdz$p.value), x = 2, y = 118.5, size = 4, fontface = "bold")
Combine plots in 4 panels
<- ggarrange(ust_cd_clinical_response_vdz_gg, ust_cd_clinical_remission_vdz_gg, ust_cd_endoscopic_response_vdz_gg, ust_cd_endoscopic_remission_vdz_gg, nrow = 2, ncol = 2)
ust_cd_vdz_bars_combined
annotate_figure(ust_cd_vdz_bars_combined, bottom = text_grob("Clinical Decision Support Tool's response probability groups", face = "bold", vjust = 0.1))
12. Evaluate CDST calibration
Calculate logits and convert them to probabilities
%<>%
vdz_cd_included mutate(logit = -3.0722 + 0.2305*no_surgery_before + 0.3483*no_previous_antitnf + 0.1979*no_fistulizing_before + 0.0436*albumin_g_L - 0.0098*crp_mg_L,
prob = logit2prob(logit))
%<>%
vdz_uc_included mutate(logit = -3.7038 + 0.2622*dxduration_above_two + 0.2820*no_previous_antitnf + 0.1847*moderate_endo_activity + 0.0647*albumin_g_L,
prob = logit2prob(logit))
Define a clinically-relevant outcome: persistence & persistence without escalation
%<>% mutate(persistence_without_escalation = stop_current_drug_itt == 0 & dose_optimisation == 0)
vdz_cd_included %<>% mutate(persistence_without_escalation = stop_current_drug_itt == 0 & dose_optimisation == 0)
vdz_uc_included %<>% mutate(persistence_without_escalation = stop_current_drug_itt == 0 & dose_optimisation == 0)
ust_cd_included
%<>% mutate(persistence = stop_current_drug_itt == 0)
vdz_cd_included %<>% mutate(persistence = stop_current_drug_itt == 0)
vdz_uc_included %<>% mutate(persistence = stop_current_drug_itt == 0) ust_cd_included
Visualize calibration plots
par(mfrow=c(3,2))
::val.prob(vdz_cd_included$prob, vdz_cd_included$clinical_remission_cdst, statloc = F, logistic.cal = F, g = 10) rms
Dxy C (ROC) R2 D D:Chi-sq
0.17862884161 0.58931442080 -0.08314444580 -0.05113826943 -13.31871543951
D:p U U:Chi-sq U:p Q
NA 0.05932023319 18.60966529419 0.00009098348 -0.11045850262
Brier Intercept Slope Emax E90
0.14476423642 -0.65070440020 1.01127074257 0.16792792579 0.16166755327
Eavg S:z S:p
0.10881941218 -3.82536680788 0.00013057752
mtext('A', adj = 0, line = 1, font = 2)
::val.prob(vdz_uc_included$prob, vdz_uc_included$clinical_remission_cdst, statloc = F, logistic.cal = F, g = 10) rms
Dxy C (ROC) R2 D D:Chi-sq
0.3035106258 0.6517553129 -0.0616547022 -0.0446573598 -8.7353044358
D:p U U:Chi-sq U:p Q
NA 0.0567842995 14.3789772935 0.0007544748 -0.1014416593
Brier Intercept Slope Emax E90
0.1867562420 -0.5028711530 1.1430083801 0.4119663381 0.1703225522
Eavg S:z S:p
0.1209726878 -3.4821176169 0.0004974651
mtext('B', adj = 0, line = 1, font = 2)
::val.prob(vdz_cd_included$prob, vdz_cd_included$persistence_without_escalation, statloc = F, logistic.cal = F, g = 10) rms
Dxy C (ROC) R2 D D:Chi-sq D:p
0.357506793 0.678753397 0.068796680 0.047490031 14.297208726 NA
U U:Chi-sq U:p Q Brier Intercept
0.042172491 13.808297370 0.001003613 0.005317541 0.215184305 1.270721589
Slope Emax E90 Eavg S:z S:p
1.928436064 0.319500179 0.189713594 0.090107399 2.020589697 0.043322256
mtext('C', adj = 0, line = 1, font = 2)
::val.prob(vdz_uc_included$prob, vdz_uc_included$persistence_without_escalation, statloc = F, logistic.cal = F, g = 10) rms
Dxy C (ROC) R2 D D:Chi-sq D:p
0.31491549 0.65745774 0.07109483 0.04712249 11.27270173 NA
U U:Chi-sq U:p Q Brier Intercept
0.01502521 5.27549670 0.07152213 0.03209727 0.20177544 0.30622051
Slope Emax E90 Eavg S:z S:p
1.89496508 0.18284498 0.10107597 0.06405022 -1.99595232 0.04593911
mtext('D', adj = 0, line = 1, font = 2)
::val.prob(vdz_cd_included$prob, vdz_cd_included$persistence, statloc = F, logistic.cal = F, g = 10) rms
Dxy C (ROC)
0.3067973055725658482196 0.6533986527862829518654
R2 D
-0.2791177371817529229148 -0.1936330041949115565725
D:Chi-sq D:p
-53.2172411745752356182493 NA
U U:Chi-sq
0.2710559208947198595041 77.8956578505215588847932
U:p Q
0.0000000000000000000000 -0.4646889250896314438322
Brier Intercept
0.2920824410165137607898 1.7383988222184387772984
Slope Emax
1.6477130652951839095977 0.3993827373260716573355
E90 Eavg
0.3285369249726436402526 0.2405269877686071477996
S:z S:p
8.4043262035508128349193 0.0000000000000000430325
mtext('E', adj = 0, line = 1, font = 2)
::val.prob(vdz_uc_included$prob, vdz_uc_included$persistence, statloc = F, logistic.cal = F, g = 10) rms
Dxy C (ROC) R2 D D:Chi-sq D:p
0.26418485 0.63209243 0.02392832 0.01342047 3.92566262 NA
U U:Chi-sq U:p Q Brier Intercept
0.02648111 7.77288151 0.02051825 -0.01306064 0.24170389 0.61371546
Slope Emax E90 Eavg S:z S:p
1.40477097 0.16086479 0.14688310 0.08653667 1.95501037 0.05058185
mtext('F', adj = 0, line = 1, font = 2)
13. Decision curve analysis
Plot the curves
For clinical remission
<- dca(clinical_remission_cdst ~ prob,
vdz_cd_dca_plot_remission data = vdz_cd_included,
thresholds = seq(0, 0.5, 0.01),
label = list(prob = "Clinical Decision Support Tool")) %>%
plot(smooth = FALSE, style = "bw") + theme(panel.grid = element_blank()) +
scale_x_continuous(breaks = seq(0, 0.5, 0.05), labels = scales::percent_format(accuracy = 1)) +
scale_y_continuous(breaks = seq(0, 0.7, 0.1))
<- dca(clinical_remission_cdst ~ prob,
vdz_uc_dca_plot_remission data = vdz_uc_included,
thresholds = seq(0, 0.55, 0.01),
label = list(prob = "Clinical Decision Support Tool")) %>%
plot(smooth = FALSE, style = "bw") + theme(panel.grid = element_blank()) +
scale_x_continuous(breaks = seq(0, 0.55, 0.05), labels = scales::percent_format(accuracy = 1)) +
scale_y_continuous(breaks = seq(0, 0.6, 0.1))
For persistence without escalation
<- dca(persistence_without_escalation ~ prob,
vdz_cd_dca_plot_persistence_without_escalation data = vdz_cd_included,
thresholds = seq(0, 0.5, 0.01),
label = list(prob = "Clinical Decision Support Tool")) %>%
plot(smooth = FALSE, style = "bw") + theme(panel.grid = element_blank()) +
scale_x_continuous(breaks = seq(0, 0.5, 0.05), labels = scales::percent_format(accuracy = 1)) +
scale_y_continuous(breaks = seq(0, 0.7, 0.1))
<- dca(persistence_without_escalation ~ prob,
vdz_uc_dca_plot_persistence_without_escalation data = vdz_uc_included,
thresholds = seq(0, 0.55, 0.01),
label = list(prob = "Clinical Decision Support Tool")) %>%
plot(smooth = FALSE, style = "bw") + theme(panel.grid = element_blank()) +
scale_x_continuous(breaks = seq(0, 0.55, 0.05), labels = scales::percent_format(accuracy = 1)) +
scale_y_continuous(breaks = seq(0, 0.6, 0.1))
For persistence
<- dca(persistence ~ prob,
vdz_cd_dca_plot_persistence data = vdz_cd_included,
thresholds = seq(0, 0.5, 0.01),
label = list(prob = "Clinical Decision Support Tool")) %>%
plot(smooth = FALSE, style = "bw") + theme(panel.grid = element_blank()) +
scale_x_continuous(breaks = seq(0, 0.5, 0.05), labels = scales::percent_format(accuracy = 1)) +
scale_y_continuous(breaks = seq(0, 0.7, 0.1))
<- dca(persistence ~ prob,
vdz_uc_dca_plot_persistence data = vdz_uc_included,
thresholds = seq(0, 0.55, 0.01),
label = list(prob = "Clinical Decision Support Tool")) %>%
plot(smooth = FALSE, style = "bw") + theme(panel.grid = element_blank()) +
scale_x_continuous(breaks = seq(0, 0.55, 0.05), labels = scales::percent_format(accuracy = 1)) +
scale_y_continuous(breaks = seq(0, 0.6, 0.1))
Combine the plots
ggarrange(vdz_cd_dca_plot_remission, vdz_uc_dca_plot_remission,
vdz_cd_dca_plot_persistence_without_escalation, vdz_uc_dca_plot_persistence_without_escalation,
vdz_cd_dca_plot_persistence, vdz_uc_dca_plot_persistence,labels = c("A", "B", "C", "D", "E", "F"), label.y = c(1.012, 1.012, 1.012, 1.012, 1.012, 1.012), nrow = 3, ncol = 2,
common.legend = TRUE, legend="bottom")
Make net benefit tables
For clinical remission
<- dca(clinical_remission_cdst ~ prob,
vdz_cd_dca_tbl_remission data = vdz_cd_included,
thresholds = seq(0.1, 0.5, 0.1),
label = list(prob = "Clinical Decision Support Tool")) %>%
as_tibble() %>%
select(label, threshold, net_benefit)
<- dca(clinical_remission_cdst ~ prob,
vdz_uc_dca_tbl_remission data = vdz_uc_included,
thresholds = seq(0.1, 0.5, 0.1),
label = list(prob = "Clinical Decision Support Tool")) %>%
as_tibble() %>%
select(label, threshold, net_benefit)
<- bind_cols(
combined_dca_tbl_remission %>%
vdz_cd_dca_tbl_remission rename_with(~ paste0("cd_", .)),
%>%
vdz_uc_dca_tbl_remission rename_with(~ paste0("uc_", .))
)
%>%
combined_dca_tbl_remission ::gt() %>%
gt::cols_label(
gtcd_label = "Strategy",
cd_threshold = "Decision Threshold",
cd_net_benefit = "Net Benefit",
uc_label = "Strategy",
uc_threshold = "Decision Threshold",
uc_net_benefit = "Net Benefit"
%>%
) ::fmt_percent(
gtcolumns = c(cd_threshold, uc_threshold),
decimals = 0
%>%
) ::fmt(
gtcolumns = c(cd_net_benefit, uc_net_benefit),
fns = function(x) ifelse(x == 0, "0", formatC(x, format = "f", digits = 3))
%>%
) ::tab_spanner(
gtlabel = "Crohn's Disease",
columns = c(cd_label, cd_threshold, cd_net_benefit)
%>%
) ::tab_spanner(
gtlabel = "Ulcerative Colitis",
columns = c(uc_label, uc_threshold, uc_net_benefit)
%>%
) ::cols_align("left", columns = everything()) %>%
gt::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_spanners(spanners = c("Crohn's Disease", "Ulcerative Colitis"))
%>%
) ::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_labels(columns = everything())
)
Crohn's Disease | Ulcerative Colitis | ||||
---|---|---|---|---|---|
Strategy | Decision Threshold | Net Benefit | Strategy | Decision Threshold | Net Benefit |
Treat All | 10% | 0.067 | Treat All | 10% | 0.149 |
Treat All | 20% | -0.049 | Treat All | 20% | 0.042 |
Treat All | 30% | -0.199 | Treat All | 30% | -0.094 |
Treat All | 40% | -0.399 | Treat All | 40% | -0.277 |
Treat All | 50% | -0.679 | Treat All | 50% | -0.532 |
Treat None | 10% | 0 | Treat None | 10% | 0 |
Treat None | 20% | 0 | Treat None | 20% | 0 |
Treat None | 30% | 0 | Treat None | 30% | 0 |
Treat None | 40% | 0 | Treat None | 40% | 0 |
Treat None | 50% | 0 | Treat None | 50% | 0 |
Clinical Decision Support Tool | 10% | 0.069 | Clinical Decision Support Tool | 10% | 0.144 |
Clinical Decision Support Tool | 20% | -0.019 | Clinical Decision Support Tool | 20% | 0.039 |
Clinical Decision Support Tool | 30% | -0.034 | Clinical Decision Support Tool | 30% | -0.042 |
Clinical Decision Support Tool | 40% | -0.005 | Clinical Decision Support Tool | 40% | -0.028 |
Clinical Decision Support Tool | 50% | 0 | Clinical Decision Support Tool | 50% | -0.009 |
For persistence without escalation
<- dca(persistence_without_escalation ~ prob,
vdz_cd_dca_tbl_persistence_without_escalation data = vdz_cd_included,
thresholds = seq(0.1, 0.5, 0.1),
label = list(prob = "Clinical Decision Support Tool")) %>%
as_tibble() %>%
select(label, threshold, net_benefit)
<- dca(persistence_without_escalation ~ prob,
vdz_uc_dca_tbl_persistence_without_escalation data = vdz_uc_included,
thresholds = seq(0.1, 0.5, 0.1),
label = list(prob = "Clinical Decision Support Tool")) %>%
as_tibble() %>%
select(label, threshold, net_benefit)
<- bind_cols(
combined_dca_tbl_persistence_without_escalation %>%
vdz_cd_dca_tbl_persistence_without_escalation rename_with(~ paste0("cd_", .)),
%>%
vdz_uc_dca_tbl_persistence_without_escalation rename_with(~ paste0("uc_", .))
)
%>%
combined_dca_tbl_persistence_without_escalation ::gt() %>%
gt::cols_label(
gtcd_label = "Strategy",
cd_threshold = "Decision Threshold",
cd_net_benefit = "Net Benefit",
uc_label = "Strategy",
uc_threshold = "Decision Threshold",
uc_net_benefit = "Net Benefit"
%>%
) ::fmt_percent(
gtcolumns = c(cd_threshold, uc_threshold),
decimals = 0
%>%
) ::fmt(
gtcolumns = c(cd_net_benefit, uc_net_benefit),
fns = function(x) ifelse(x == 0, "0", formatC(x, format = "f", digits = 3))
%>%
) ::tab_spanner(
gtlabel = "Crohn's Disease",
columns = c(cd_label, cd_threshold, cd_net_benefit)
%>%
) ::tab_spanner(
gtlabel = "Ulcerative Colitis",
columns = c(uc_label, uc_threshold, uc_net_benefit)
%>%
) ::cols_align("left", columns = everything()) %>%
gt::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_spanners(spanners = c("Crohn's Disease", "Ulcerative Colitis"))
%>%
) ::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_labels(columns = everything())
)
Crohn's Disease | Ulcerative Colitis | ||||
---|---|---|---|---|---|
Strategy | Decision Threshold | Net Benefit | Strategy | Decision Threshold | Net Benefit |
Treat All | 10% | 0.270 | Treat All | 10% | 0.230 |
Treat All | 20% | 0.179 | Treat All | 20% | 0.134 |
Treat All | 30% | 0.061 | Treat All | 30% | 0.010 |
Treat All | 40% | -0.095 | Treat All | 40% | -0.154 |
Treat All | 50% | -0.314 | Treat All | 50% | -0.385 |
Treat None | 10% | 0 | Treat None | 10% | 0 |
Treat None | 20% | 0 | Treat None | 20% | 0 |
Treat None | 30% | 0 | Treat None | 30% | 0 |
Treat None | 40% | 0 | Treat None | 40% | 0 |
Treat None | 50% | 0 | Treat None | 50% | 0 |
Clinical Decision Support Tool | 10% | 0.272 | Clinical Decision Support Tool | 10% | 0.231 |
Clinical Decision Support Tool | 20% | 0.204 | Clinical Decision Support Tool | 20% | 0.142 |
Clinical Decision Support Tool | 30% | 0.088 | Clinical Decision Support Tool | 30% | 0.050 |
Clinical Decision Support Tool | 40% | 0.019 | Clinical Decision Support Tool | 40% | 0.018 |
Clinical Decision Support Tool | 50% | 0 | Clinical Decision Support Tool | 50% | 0.009 |
For persistence
<- dca(persistence ~ prob,
vdz_cd_dca_tbl_persistence data = vdz_cd_included,
thresholds = seq(0.1, 0.5, 0.1),
label = list(prob = "Clinical Decision Support Tool")) %>%
as_tibble() %>%
select(label, threshold, net_benefit)
<- dca(persistence ~ prob,
vdz_uc_dca_tbl_persistence data = vdz_uc_included,
thresholds = seq(0.1, 0.5, 0.1),
label = list(prob = "Clinical Decision Support Tool")) %>%
as_tibble() %>%
select(label, threshold, net_benefit)
<- bind_cols(
combined_dca_tbl_persistence %>%
vdz_cd_dca_tbl_persistence rename_with(~ paste0("cd_", .)),
%>%
vdz_uc_dca_tbl_persistence rename_with(~ paste0("uc_", .))
)
%>%
combined_dca_tbl_persistence ::gt() %>%
gt::cols_label(
gtcd_label = "Strategy",
cd_threshold = "Decision Threshold",
cd_net_benefit = "Net Benefit",
uc_label = "Strategy",
uc_threshold = "Decision Threshold",
uc_net_benefit = "Net Benefit"
%>%
) ::fmt_percent(
gtcolumns = c(cd_threshold, uc_threshold),
decimals = 0
%>%
) ::fmt(
gtcolumns = c(cd_net_benefit, uc_net_benefit),
fns = function(x) ifelse(x == 0, "0", formatC(x, format = "f", digits = 3))
%>%
) ::tab_spanner(
gtlabel = "Crohn's Disease",
columns = c(cd_label, cd_threshold, cd_net_benefit)
%>%
) ::tab_spanner(
gtlabel = "Ulcerative Colitis",
columns = c(uc_label, uc_threshold, uc_net_benefit)
%>%
) ::cols_align("left", columns = everything()) %>%
gt::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_spanners(spanners = c("Crohn's Disease", "Ulcerative Colitis"))
%>%
) ::tab_style(
gtstyle = gt::cell_text(weight = "bold"),
locations = gt::cells_column_labels(columns = everything())
)
Crohn's Disease | Ulcerative Colitis | ||||
---|---|---|---|---|---|
Strategy | Decision Threshold | Net Benefit | Strategy | Decision Threshold | Net Benefit |
Treat All | 10% | 0.452 | Treat All | 10% | 0.373 |
Treat All | 20% | 0.384 | Treat All | 20% | 0.295 |
Treat All | 30% | 0.296 | Treat All | 30% | 0.194 |
Treat All | 40% | 0.179 | Treat All | 40% | 0.060 |
Treat All | 50% | 0.014 | Treat All | 50% | -0.128 |
Treat None | 10% | 0 | Treat None | 10% | 0 |
Treat None | 20% | 0 | Treat None | 20% | 0 |
Treat None | 30% | 0 | Treat None | 30% | 0 |
Treat None | 40% | 0 | Treat None | 40% | 0 |
Treat None | 50% | 0 | Treat None | 50% | 0 |
Clinical Decision Support Tool | 10% | 0.454 | Clinical Decision Support Tool | 10% | 0.374 |
Clinical Decision Support Tool | 20% | 0.374 | Clinical Decision Support Tool | 20% | 0.303 |
Clinical Decision Support Tool | 30% | 0.144 | Clinical Decision Support Tool | 30% | 0.194 |
Clinical Decision Support Tool | 40% | 0.031 | Clinical Decision Support Tool | 40% | 0.057 |
Clinical Decision Support Tool | 50% | 0 | Clinical Decision Support Tool | 50% | 0.009 |
14. Assess the relationship between CDST variables and outcome
Calculate probabilities
VDZ-CD
<- c("no_previous_antitnf", "no_surgery_before", "no_fistulizing_before", "albumin_g_L", "crp_mg_L")
vdz_cd_modelvars
<- vdz_cd_included %>%
vdz_cd_coefs_df select(c(all_of(vdz_cd_modelvars), "clinical_remission_cdst"))
<- glm(formula = paste("clinical_remission_cdst ~", paste(vdz_cd_modelvars, collapse = "+")), data = vdz_cd_coefs_df, family = "binomial")
vdz_cd_mvassociations_mdl
<- vdz_cd_coefs_df %>%
vdz_cd_mvassociations_df mutate(probs = predict(vdz_cd_mvassociations_mdl, vdz_cd_coefs_df, type = "response"))
%<>%
vdz_cd_mvassociations_df mutate(logit = predict(vdz_cd_mvassociations_mdl, vdz_cd_coefs_df))
VDZ-UC
<- c("no_previous_antitnf", "dxduration_above_two", "moderate_endo_activity", "albumin_g_L")
vdz_uc_modelvars
<- vdz_uc_included %>%
vdz_uc_coefs_df select(c(all_of(vdz_uc_modelvars), "clinical_remission_cdst"))
<- glm(formula = paste("clinical_remission_cdst ~", paste(vdz_uc_modelvars, collapse = "+")), data = vdz_uc_coefs_df, family = "binomial")
vdz_uc_mvassociations_mdl
<- vdz_uc_coefs_df %>%
vdz_uc_mvassociations_df mutate(probs = predict(vdz_uc_mvassociations_mdl, vdz_uc_coefs_df, type = "response"))
%<>%
vdz_uc_mvassociations_df mutate(logit = predict(vdz_uc_mvassociations_mdl, vdz_uc_coefs_df))
UST-CD
%<>% mutate(no_previous_antitnf = if_else(previous_antitnf == 1, 0, 1), .after = previous_antitnf)
ust_cd_included
is.numeric(ust_cd_included$no_previous_antitnf)
[1] TRUE
%<>% mutate(no_surgery_before = if_else(intestinal_resections_before == 1, 0, 1), .after = intestinal_resections_before)
ust_cd_included
is.numeric(ust_cd_included$no_surgery_before)
[1] TRUE
%<>% mutate(no_prior_smoking = if_else(smoking %in% c(1,2), 0, 1), .after = smoking)
ust_cd_included
is.numeric(ust_cd_included$no_prior_smoking)
[1] TRUE
%<>% mutate(no_fistulizing_current = if_else(fistulizing_disease_current == 1, 0, 1), .after = fistulizing_disease_current)
ust_cd_included
is.numeric(ust_cd_included$no_fistulizing_current)
[1] TRUE
is.numeric(ust_cd_included$albumin_g_L)
[1] TRUE
<- c("no_previous_antitnf", "no_surgery_before", "no_prior_smoking", "no_fistulizing_current", "albumin_g_L")
ust_cd_modelvars <- ust_cd_included %>%
ust_cd_coefs_df select(c(all_of(ust_cd_modelvars), "clinical_remission_cdst"))
<- glm(formula = paste("clinical_remission_cdst ~", paste(ust_cd_modelvars, collapse = "+")), data = ust_cd_coefs_df, family = "binomial")
ust_cd_mvassociations_mdl
<- ust_cd_coefs_df %>%
ust_cd_mvassociations_df mutate(probs = predict(ust_cd_mvassociations_mdl, ust_cd_coefs_df, type = "response"))
Visualize associations
VDZ-CD
$no_previous_antitnf <- fct_recode(as.character(vdz_cd_mvassociations_df$no_previous_antitnf), "Yes" = "0", "No" = "1")
vdz_cd_mvassociations_df$no_surgery_before <- fct_recode(as.character(vdz_cd_mvassociations_df$no_surgery_before), "Yes" = "0", "No" = "1")
vdz_cd_mvassociations_df$no_fistulizing_before <- fct_recode(as.character(vdz_cd_mvassociations_df$no_fistulizing_before), "Yes" = "0", "No" = "1")
vdz_cd_mvassociations_df
<- ggplot2::ggplot(data = vdz_cd_mvassociations_df, aes(x = no_previous_antitnf, y = probs)) +
vdz_cd_mv_antitnf geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Prior anti-TNF exposure") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = vdz_cd_mvassociations_df, aes(x = no_surgery_before, y = probs)) +
vdz_cd_mv_surgery geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Prior bowel surgery") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = vdz_cd_mvassociations_df, aes(x = no_fistulizing_before, y = probs)) +
vdz_cd_mv_fistulizing geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Prior fistulizing disease") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = vdz_cd_mvassociations_df, aes(x = albumin_g_L, y = probs)) +
vdz_cd_mv_albumin geom_point() +
ylab("Probability of clinical remission") +
xlab("Baseline albumin (g/L)") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = vdz_cd_mvassociations_df, aes(x = crp_mg_L, y = probs)) +
vdz_cd_mv_crp geom_point() +
ylab("Probability of clinical remission") +
xlab("Baseline C-reactive protein (mg/L)") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank()) +
coord_cartesian(xlim = c(0, 150))
<- ggarrange(vdz_cd_mv_antitnf, vdz_cd_mv_surgery, vdz_cd_mv_fistulizing, vdz_cd_mv_albumin, vdz_cd_mv_crp)
vdz_cd_mv_combined
annotate_figure(vdz_cd_mv_combined, left = text_grob("Probability of clinical remission", face = "bold", rot = 90))
VDZ-UC
$no_previous_antitnf <- fct_recode(as.character(vdz_uc_mvassociations_df$no_previous_antitnf), "Yes" = "0", "No" = "1")
vdz_uc_mvassociations_df$dxduration_above_two <- fct_recode(as.character(vdz_uc_mvassociations_df$dxduration_above_two), "< 2" = "0", "≥ 2" = "1")
vdz_uc_mvassociations_df$moderate_endo_activity <- fct_recode(as.character(vdz_uc_mvassociations_df$moderate_endo_activity), "3" = "0", "2" = "1")
vdz_uc_mvassociations_df
<- ggplot2::ggplot(data = vdz_uc_mvassociations_df, aes(x = no_previous_antitnf, y = probs)) +
vdz_uc_mv_antitnf geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Prior anti-TNF exposure") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = vdz_uc_mvassociations_df, aes(x = dxduration_above_two, y = probs)) +
vdz_uc_mv_duration geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Disease duration (year)") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = vdz_uc_mvassociations_df, aes(x = moderate_endo_activity, y = probs)) +
vdz_uc_mv_endo geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Mayo endoscopic subscore") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = vdz_uc_mvassociations_df, aes(x = albumin_g_L, y = probs)) +
vdz_uc_mv_albumin geom_point() +
ylab("Probability of clinical remission") +
xlab("Baseline albumin (g/L)") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggarrange(vdz_uc_mv_antitnf, vdz_uc_mv_duration, vdz_uc_mv_endo, vdz_uc_mv_albumin)
vdz_uc_mv_combined
annotate_figure(vdz_uc_mv_combined, left = text_grob("Probability of clinical remission", face = "bold", rot = 90))
UST-CD
$no_previous_antitnf <- fct_recode(as.character(ust_cd_mvassociations_df$no_previous_antitnf), "Yes" = "0", "No" = "1")
ust_cd_mvassociations_df$no_surgery_before <- fct_recode(as.character(ust_cd_mvassociations_df$no_surgery_before), "Yes" = "0", "No" = "1")
ust_cd_mvassociations_df$no_prior_smoking <- fct_recode(as.character(ust_cd_mvassociations_df$no_prior_smoking), "Yes" = "0", "No" = "1")
ust_cd_mvassociations_df$no_fistulizing_current <- fct_recode(as.character(ust_cd_mvassociations_df$no_fistulizing_current), "Yes" = "0", "No" = "1")
ust_cd_mvassociations_df
<- ggplot2::ggplot(data = ust_cd_mvassociations_df, aes(x = no_previous_antitnf, y = probs)) +
ust_cd_mv_antitnf geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Prior anti-TNF exposure") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = ust_cd_mvassociations_df, aes(x = no_surgery_before, y = probs)) +
ust_cd_mv_surgery geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Prior bowel surgery") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = ust_cd_mvassociations_df, aes(x = no_prior_smoking, y = probs)) +
ust_cd_mv_smoking geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Prior smoking history") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = ust_cd_mvassociations_df, aes(x = no_fistulizing_current, y = probs)) +
ust_cd_mv_fistulizing geom_boxplot(outlier.shape = NA) +
ylab("Probability of clinical remission") +
xlab("Baseline actively draining fistula") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggplot2::ggplot(data = ust_cd_mvassociations_df, aes(x = albumin_g_L, y = probs)) +
ust_cd_mv_albumin geom_point() +
ylab("Probability of clinical remission") +
xlab("Baseline albumin (g/L)") +
scale_y_continuous(breaks = seq(0, 1, 0.1), labels = scales::percent_format(accuracy = 1)) +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.y = element_blank())
<- ggarrange(ust_cd_mv_antitnf, ust_cd_mv_surgery, ust_cd_mv_smoking, ust_cd_mv_fistulizing, ust_cd_mv_albumin)
ust_cd_mv_combined
annotate_figure(ust_cd_mv_combined, left = text_grob("Probability of clinical remission", face = "bold", rot = 90))
15. Check linearity assumption for numeric variables
<- ggplot(vdz_cd_mvassociations_df, aes(logit, albumin_g_L))+
linearity_albumin geom_point(size = 0.5, alpha = 0.5) +
geom_smooth(method = "loess", col = "black") +
ylab("Baseline albumin (g/L)") +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_text(face = "bold")) +
coord_cartesian(expand = 0)
<- ggplot(vdz_cd_mvassociations_df, aes(logit, crp_mg_L))+
linearity_crp geom_point(size = 0.5, alpha = 0.5) +
geom_smooth(method = "loess", col = "black") +
ylab("Baseline C-reactive protein (mg/L)") +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_text(face = "bold")) +
coord_cartesian(expand = 0)
<- ggplot(vdz_uc_mvassociations_df, aes(logit, albumin_g_L))+
linearity_albumin_uc geom_point(size = 0.5, alpha = 0.5) +
geom_smooth(method = "loess", col = "black") +
ylab("Baseline albumin (g/L)") +
theme_bw() +
theme(panel.grid = element_blank(),
axis.ticks = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_text(face = "bold")) +
coord_cartesian(expand = 0)
<- ggarrange(linearity_albumin, linearity_crp, linearity_albumin_uc, labels = c("A", "B", "C"), nrow = 1)
linearity_combined
annotate_figure(linearity_combined, bottom = text_grob("Log odds of clinical remission", face = "bold"))