Read and transform data
data <- read_tsv("../ExternalData/Data_lymphnodes_141223.txt") %>%
mutate(Patient = factor(sub("Pt", "#", Patient), levels = paste0("#", 1:14)),
patient_node = factor(sub("Pt", "#", sub(" MR[0-9]", "", Unq_ID)),
levels = paste0(rep(paste0("#", 1:14), each = 8),"_", 1:8)),
`Pretreat/during` = factor(`Pretreat/during`, levels = c("Pretreat", "During")),
Rel_bladder = Rel_bladder / 100,
Baseline_bladder = Baseline_bladder / 100,
Shift_LR = Shift_X * 10,
Shift_AP = Shift_Y * 10,
Shift_CC = Shift_Z * 10,
Shift_LR_GTV = Shift_X_GTV * 10,
Shift_AP_GTV = Shift_Y_GTV * 10,
Shift_CC_GTV = Shift_Z_GTV * 10,
Location_plot = case_when(Location == "Pre" ~ "Presacral",
Location == "LN" ~ "Lateral nodes",
Location == "Meso" ~ "Mesorectum"))
Compare the shifs for bony and soft tissue match
bind_rows(data.frame(Location = data$Location_plot, Shift = data$Shift_LR, Shift_GTV = data$Shift_LR_GTV, Direction = "LR"),
data.frame(Location = data$Location_plot, Shift = data$Shift_AP, Shift_GTV = data$Shift_AP_GTV, Direction = "AP"),
data.frame(Location = data$Location_plot, Shift = data$Shift_CC, Shift_GTV = data$Shift_CC_GTV, Direction = "CC")) %>%
ggplot(aes(x = Shift, y = Shift_GTV)) +
geom_point() +
xlab("Bony match") +
ylab("Soft tissue match") +
facet_grid(cols = vars(Location), rows = vars(Direction))
First we try to plot the data for each patient and Shift direction
fig1 <- data %>%
dplyr::select(Patient, Location_plot, Shift_LR:Shift_CC) %>%
pivot_longer(cols = Shift_LR:Shift_CC) %>%
mutate(name = factor(sub("Shift_","", name), levels = c("LR", "AP", "CC"))) %>%
ggplot(aes(x = as.factor(Patient), y = value, fill = as.factor(Patient))) +
geom_boxplot() +
xlab("Patient") +
ylab("Shift (mm)") +
scale_fill_discrete(name = "Patient") +
#theme_bw() +
theme_classic() +
facet_grid(rows = vars(name), cols = vars(Location_plot), space = "free_x", scales = "free_x") +
theme(legend.position = "none", axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
strip.text.y.right = element_text(angle = 0)) +
scale_fill_manual(values = BloodCol)
fig1
ggsave(plot = fig1, filename = "../Output/fig1.pdf")
Then we plot the data for individual nodes
fig2 <- data %>% dplyr::select(Patient, patient_node, Location_plot, Shift_LR:Shift_CC) %>%
pivot_longer(cols = Shift_LR:Shift_CC) %>%
mutate(name = factor(sub("Shift_","", name), levels = c("LR", "AP", "CC"))) %>%
ggplot(aes(x = as.factor(patient_node), y = value, fill = as.factor(Patient))) +
geom_boxplot() +
xlab("Patient / Node") +
ylab("Shift (mm)") +
scale_fill_discrete(name = "Patient") +
theme_classic() +
#theme_bw() +
facet_grid(rows = vars(name), cols = vars(Location_plot), space = "free_x", scales = "free_x") +
theme(legend.position = "none", axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
strip.text.y.right = element_text(angle = 0)) +
scale_fill_manual(values = BloodCol)
fig2
ggsave(plot = fig2, filename = "../Output/fig2.pdf")
A lot of the variance seems to be between nodes within patients, i.e. modelling the variance with only an overall and patient offsets, might miss some systematic variance, that would be taken into account by including a node effect. We do this by utilizing a nested mixed model, with individual nodes nested within patients. This is done for all locations and directions in order to estimate the overall mean, systematic, and random variance. The systematic variance is calculated as the sum of variances for the patient and node effects.
sys_rand_results <- list()
for(location in c("Combined", "LN", "Meso", "Pre")){
for(direction in c("LR","AP","CC")){
if(location == "Combined"){
sys_rand_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ (1|Patient/patient_node)")), data)
} else{
sys_rand_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ (1|Patient/patient_node)")), data %>% filter(Location == location))
}
}
}
summary_results <- data.frame("location" = NULL, "direction" = NULL, "M"=NULL, Patient_Node_SD = NULL, Patient_SD = NULL, "Sys_SD"=NULL, "Rand_SD" = NULL)
for(name in names(sys_rand_results)){
location <- str_split(name, "_")[[1]][1]
direction <- str_split(name, "_")[[1]][2]
#cat(location, direction, "\n")
results <- broom.mixed::tidy(sys_rand_results[[name]])
M <- results$estimate[results$term == "(Intercept)"]
Patient_Node_SD <- results$estimate[results$group == "patient_node:Patient" & results$term == "sd__(Intercept)"]
Patient_SD <- results$estimate[results$group == "Patient" & results$term == "sd__(Intercept)"]
Sys_SD <- sqrt(sum(results$estimate[results$term == "sd__(Intercept)"])^2)
Rand_SD <- results$estimate[results$term == "sd__Observation"]
summary_results <- rbind(summary_results, data.frame(location, direction, M, Patient_Node_SD, Patient_SD, Sys_SD, Rand_SD))
}
summary_results %>%
pivot_longer(cols = M:Rand_SD) %>%
pivot_wider(names_from = direction) %>%
kable()
| location | name | LR | AP | CC |
|---|---|---|---|---|
| Combined | M | 0.2010718 | -0.6743243 | -0.3872820 |
| Combined | Patient_Node_SD | 1.1650586 | 1.4291795 | 1.0566977 |
| Combined | Patient_SD | 0.7981935 | 0.9786845 | 1.8069732 |
| Combined | Sys_SD | 1.9632521 | 2.4078641 | 2.8636709 |
| Combined | Rand_SD | 1.2373878 | 1.7499741 | 1.8351158 |
| LN | M | 0.4556550 | -0.8218578 | -0.0777707 |
| LN | Patient_Node_SD | 0.3765537 | 0.0000000 | 0.1482093 |
| LN | Patient_SD | 1.8799117 | 0.5911154 | 0.4147415 |
| LN | Sys_SD | 2.2564654 | 0.5911154 | 0.5629508 |
| LN | Rand_SD | 0.7944124 | 0.7028148 | 0.6418723 |
| Meso | M | 0.2675469 | -1.0468786 | -0.0084708 |
| Meso | Patient_Node_SD | 1.2938815 | 1.3142692 | 0.9713403 |
| Meso | Patient_SD | 0.8592124 | 1.4895386 | 1.2306529 |
| Meso | Sys_SD | 2.1530939 | 2.8038078 | 2.2019931 |
| Meso | Rand_SD | 1.3001927 | 1.8160538 | 2.0644052 |
| Pre | M | -0.1300072 | 0.1150739 | -1.5381713 |
| Pre | Patient_Node_SD | 0.4472965 | 0.0000000 | 0.0000000 |
| Pre | Patient_SD | 0.5663485 | 1.9273261 | 3.6762997 |
| Pre | Sys_SD | 1.0136450 | 1.9273261 | 3.6762997 |
| Pre | Rand_SD | 1.3660713 | 2.1588453 | 1.7674469 |
Model control
sys_rand_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
mutate(location = sapply(strsplit(name,"_"), function(x) x[[1]]),
direction = sapply(strsplit(name,"_"), function(x) x[[2]])) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_grid(cols = vars(direction), rows = vars(location))
We test if the location of the nodes have an effect on the magnitude of the shift in either direction
loc_results <- list()
for(direction in c("LR","AP","CC")){
loc_results[[direction]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ Location + (1|Patient/patient_node)")), data)
}
We extract the estimates and p.values of the location effects. The model design uses contrasts, i.e. “LN” is included in the intercept, and effects for “Meso” and “Pre” is given as the difference in mean compared to “LN”. Results indicate no significant difference in the average magnitude of the shift across the different locations for all directions.
lapply(loc_results, broom.mixed::tidy) %>%
bind_rows(.id = "Direction") %>%
filter(effect == "fixed") %>%
dplyr::select(Direction, estimate, term, p.value) %>%
pivot_wider(names_from = "Direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(term, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| term | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| (Intercept) | 0.2645534 | 0.5943285 | -0.1741210 | 0.7773523 | -0.8446492 | 0.2327226 |
| LocationMeso | -0.0085068 | 0.9864788 | -0.8031499 | 0.1914590 | 0.5896335 | 0.2789507 |
| LocationPre | -0.3256012 | 0.5974848 | 0.2386066 | 0.7498443 | 0.3815595 | 0.5661269 |
Model control
loc_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_wrap(~name, ncol = 3)
We test if the radiation time (Pretreat / During) of the nodes have an effect on the magnitude of the shift in either direction
predur_results <- list()
for(direction in c("LR","AP","CC")){
predur_results[[direction]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ `Pretreat/during` + (1|Patient/patient_node)")), data)
}
We extract the estimates and p.values of the effects. The model design uses contrasts, i.e. “Pretreat” is included in the intercept, and the effect of “During” is given as the difference in mean compared to “Pretreat”. Results indicate a significant difference in the magnitude of the shift in the AP direction.
lapply(predur_results, broom.mixed::tidy) %>%
bind_rows(.id = "Direction") %>%
filter(effect == "fixed") %>%
dplyr::select(Direction, estimate, term, p.value) %>%
pivot_wider(names_from = "Direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(term, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| term | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| (Intercept) | 0.1266453 | 0.6737760 | -0.1479231 | 0.6938437 | -0.4067930 | 0.4653648 |
Pretreat/duringDuring |
0.1240437 | 0.3921726 | -0.8773224 | 0.0000130 | 0.0325137 | 0.8798785 |
Model control
predur_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_wrap(~name, ncol = 3)
We test if the baseline bladder volume has an effect on the magnitude of the shift in either direction
baseline_bladder_results <- list()
for(direction in c("LR","AP","CC")){
baseline_bladder_results[[direction]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ Baseline_bladder + (1|Patient/patient_node)")), data)
}
We extract the estimates and p.values of the effects. We see no significant effect in either direction.
lapply(baseline_bladder_results, broom.mixed::tidy) %>%
bind_rows(.id = "Direction") %>%
filter(effect == "fixed") %>%
dplyr::select(Direction, estimate, term, p.value) %>%
pivot_wider(names_from = "Direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(term, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| term | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| (Intercept) | -0.3992842 | 0.5117812 | -1.3139761 | 0.1071471 | -0.3653089 | 0.7683312 |
| Baseline_bladder | 0.3496578 | 0.2697830 | 0.3712344 | 0.3525229 | -0.0142343 | 0.9817177 |
Model control
baseline_bladder_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_wrap(~name, ncol = 3)
Here we investigate the effect of the relative volume of the bladder on the magnitude of the shift in either direction and location. First we do a plot of Rel_bladder vs shift, and add simple regression lines for each patient/node combination.
data %>% dplyr::select(Patient, patient_node, Location, Rel_bladder, Shift_LR:Shift_CC) %>%
pivot_longer(cols = Shift_LR:Shift_CC) %>%
mutate(name = factor(sub("Shift_","", name), levels = c("LR", "AP", "CC"))) %>%
ggplot(aes(x = Rel_bladder, y = value, col = patient_node)) +
geom_point() +
stat_smooth(method = "lm",
formula = y ~ x,
geom = "smooth") +
facet_grid(rows = vars(Location), cols = vars(name)) +
theme(legend.position = "none")
We test the effect of the relative fill of the bladder combined and stratified by location and direction using the same nested mixed model as before, i.e. each patient/node gets an indvidual intercept.
#bladder_strat_results <- list()
#for(location in c("LN", "Meso", "Pre")){
# for(direction in c("LR","AP","CC")){
# bladder_strat_results[[paste0(location,"_",direction)]] <-
# lmer(as.formula(paste0("Shift_", direction, " ~ Rel_bladder + (1|Patient/patient_node)")), data %>% filter(Location == location))
# }
#}
bladder_strat_results <- list()
for(location in c("Combined", "LN", "Meso", "Pre")){
for(direction in c("LR","AP","CC")){
if(location == "Combined"){
bladder_strat_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ Rel_bladder + (1|Patient/patient_node)")), data)
} else{
bladder_strat_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ Rel_bladder + (1|Patient/patient_node)")), data %>% filter(Location == location))
}
}
}
Results show a tendency for the relative bladder volume to increase the magnitude of the shift in the LR direction for LN, and decrease the magnitude of the shift in the AP direction for Pre. For the combined model we see no significance. Effect sizes are given in mm/(100 ml).
lapply(bladder_strat_results, broom.mixed::tidy) %>%
bind_rows(.id = "test") %>%
filter(term == "Rel_bladder") %>%
mutate(location = sapply(strsplit(test,"_"), function(x) x[[1]]),
direction = sapply(strsplit(test,"_"), function(x) x[[2]])) %>%
dplyr::select(location, direction, estimate, p.value) %>%
pivot_wider(names_from = "direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(location, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| location | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| Combined | 0.1210409 | 0.4384574 | -0.2126829 | 0.3163010 | -0.0600586 | 0.8035887 |
| LN | 1.0306305 | 0.0000757 | -0.1629788 | 0.4385251 | -0.0433228 | 0.8072436 |
| Meso | -0.0145070 | 0.9386773 | -0.0864561 | 0.7448413 | -0.1855976 | 0.5018719 |
| Pre | 0.2362193 | 0.6491276 | -2.0525819 | 0.0160118 | 1.2142631 | 0.1067566 |
Model control
bladder_strat_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
mutate(location = sapply(strsplit(name,"_"), function(x) x[[1]]),
direction = sapply(strsplit(name,"_"), function(x) x[[2]])) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_grid(cols = vars(direction), rows = vars(location))
## model for bladder vs shift with interaction on location. Seems to disrupt the effect on shift_x in pre, seen in the stratified model.
bladder_comb_results <- list()
for(direction in c("LR","AP","CC")){
bladder_comb_results[[paste0(direction)]] <-
lmer(as.formula(paste0("Shift_", direction, " ~ Rel_bladder*Location + Baseline_bladder + `Pretreat/during` + (1|Patient/patient_node)")), data)
}
multires <- lapply(bladder_comb_results, broom.mixed::tidy)
kable(multires$LR, caption = "Results of multivariate model for LR")
| effect | group | term | estimate | std.error | statistic | df | p.value |
|---|---|---|---|---|---|---|---|
| fixed | NA | (Intercept) | -0.4483684 | 0.8267699 | -0.5423134 | 25.21826 | 0.5923632 |
| fixed | NA | Rel_bladder | 0.5403734 | 0.3721851 | 1.4518942 | 272.99282 | 0.1476793 |
| fixed | NA | LocationMeso | -0.2665242 | 0.6190836 | -0.4305142 | 80.93659 | 0.6679659 |
| fixed | NA | LocationPre | -0.4169470 | 0.7475431 | -0.5577565 | 86.44987 | 0.5784522 |
| fixed | NA | Baseline_bladder | 0.5902214 | 0.3386970 | 1.7426239 | 16.55700 | 0.0999399 |
| fixed | NA | Pretreat/duringDuring |
0.1602233 | 0.1465819 | 1.0930633 | 239.20257 | 0.2754656 |
| fixed | NA | Rel_bladder:LocationMeso | -0.3383002 | 0.3902254 | -0.8669354 | 234.24621 | 0.3868645 |
| fixed | NA | Rel_bladder:LocationPre | -0.0975009 | 0.6150846 | -0.1585162 | 296.27146 | 0.8741580 |
| ran_pars | patient_node:Patient | sd__(Intercept) | 1.2238461 | NA | NA | NA | NA |
| ran_pars | Patient | sd__(Intercept) | 0.7225389 | NA | NA | NA | NA |
| ran_pars | Residual | sd__Observation | 1.2332279 | NA | NA | NA | NA |
kable(multires$AP, caption = "Results of multivariate model for AP")
| effect | group | term | estimate | std.error | statistic | df | p.value |
|---|---|---|---|---|---|---|---|
| fixed | NA | (Intercept) | 0.3782706 | 1.0819588 | 0.3496165 | 26.50545 | 0.7293892 |
| fixed | NA | Rel_bladder | 0.2279167 | 0.4817259 | 0.4731253 | 248.20310 | 0.6365396 |
| fixed | NA | LocationMeso | -1.2175390 | 0.7473219 | -1.6292029 | 85.03680 | 0.1069692 |
| fixed | NA | LocationPre | -0.9596202 | 0.8937768 | -1.0736688 | 87.64266 | 0.2859184 |
| fixed | NA | Baseline_bladder | 0.1445068 | 0.4643286 | 0.3112166 | 18.21203 | 0.7591702 |
| fixed | NA | Pretreat/duringDuring |
-0.9193817 | 0.2001493 | -4.5934798 | 240.11055 | 0.0000070 |
| fixed | NA | Rel_bladder:LocationMeso | -0.4942605 | 0.4966695 | -0.9951497 | 191.45429 | 0.3209195 |
| fixed | NA | Rel_bladder:LocationPre | -1.9714140 | 0.8088849 | -2.4371998 | 287.48609 | 0.0154080 |
| ran_pars | patient_node:Patient | sd__(Intercept) | 1.3021503 | NA | NA | NA | NA |
| ran_pars | Patient | sd__(Intercept) | 1.1231176 | NA | NA | NA | NA |
| ran_pars | Residual | sd__Observation | 1.6839764 | NA | NA | NA | NA |
kable(multires$CC, caption = "Results of multivariate model for CC")
| effect | group | term | estimate | std.error | statistic | df | p.value |
|---|---|---|---|---|---|---|---|
| fixed | NA | (Intercept) | -0.3909911 | 1.3644571 | -0.2865544 | 17.12426 | 0.7778916 |
| fixed | NA | Rel_bladder | -0.0805933 | 0.4862616 | -0.1657406 | 205.42844 | 0.8685241 |
| fixed | NA | LocationMeso | 0.2747550 | 0.6956535 | 0.3949596 | 74.62727 | 0.6939993 |
| fixed | NA | LocationPre | 1.3354288 | 0.8212550 | 1.6260831 | 79.68728 | 0.1078805 |
| fixed | NA | Baseline_bladder | -0.2008768 | 0.6373896 | -0.3151554 | 13.54160 | 0.7574477 |
| fixed | NA | Pretreat/duringDuring |
-0.0020683 | 0.2192924 | -0.0094315 | 240.01235 | 0.9924827 |
| fixed | NA | Rel_bladder:LocationMeso | -0.2737144 | 0.4851239 | -0.5642154 | 128.79906 | 0.5735887 |
| fixed | NA | Rel_bladder:LocationPre | 1.9314657 | 0.8320990 | 2.3211970 | 248.98927 | 0.0210843 |
| ran_pars | patient_node:Patient | sd__(Intercept) | 0.9528923 | NA | NA | NA | NA |
| ran_pars | Patient | sd__(Intercept) | 1.8512367 | NA | NA | NA | NA |
| ran_pars | Residual | sd__Observation | 1.8441888 | NA | NA | NA | NA |
First we try to plot the data for each patient and Shift direction
data %>% dplyr::select(Patient, Location_plot, Shift_LR_GTV:Shift_CC_GTV) %>%
pivot_longer(cols = Shift_LR_GTV:Shift_CC_GTV) %>%
mutate(name = factor(sapply(str_split(name, "_"), function(x) x[2]), levels = c("LR", "AP", "CC"))) %>%
ggplot(aes(x = as.factor(Patient), y = value, fill = as.factor(Patient))) +
geom_boxplot() +
xlab("Patient") +
ylab("Shift (mm)") +
scale_fill_discrete(name = "Patient") +
#theme_bw() +
theme_classic() +
facet_grid(rows = vars(name), cols = vars(Location_plot), space = "free_x", scales = "free_x") +
theme(legend.position = "none", axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
strip.text.y.right = element_text(angle = 0)) +
scale_fill_manual(values = BloodCol)
Then we plot the data for individual nodes
data %>% dplyr::select(Patient, patient_node, Location_plot, Shift_LR_GTV:Shift_CC_GTV) %>%
pivot_longer(cols = Shift_LR_GTV:Shift_CC_GTV) %>%
mutate(name = factor(sapply(str_split(name, "_"), function(x) x[2]), levels = c("LR", "AP", "CC"))) %>%
ggplot(aes(x = as.factor(patient_node), y = value, fill = as.factor(Patient))) +
geom_boxplot() +
xlab("Patient / Node") +
ylab("Shift (mm)") +
scale_fill_discrete(name = "Patient") +
#theme_bw() +
theme_classic() +
facet_grid(rows = vars(name), cols = vars(Location_plot), space = "free_x", scales = "free_x") +
theme(legend.position = "none", axis.text.x = element_text(angle = 90, vjust = 0.5, hjust=1),
strip.text.y.right = element_text(angle = 0)) +
scale_fill_manual(values = BloodCol)
We use a similar model for estimating the systematic and random variance
sys_rand_results <- list()
for(location in c("Combined", "LN", "Meso", "Pre")){
for(direction in c("LR","AP","CC")){
if(location == "Combined"){
sys_rand_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ (1|Patient/patient_node)")), data)
} else{
sys_rand_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ (1|Patient/patient_node)")), data %>% filter(Location == location))
}
}
}
summary_results <- data.frame("location" = NULL, "direction" = NULL, "M"=NULL, Patient_Node_SD = NULL, Patient_SD = NULL, "Sys_SD"=NULL, "Rand_SD" = NULL)
for(name in names(sys_rand_results)){
location <- str_split(name, "_")[[1]][1]
direction <- str_split(name, "_")[[1]][2]
#cat(location, direction, "\n")
results <- broom.mixed::tidy(sys_rand_results[[name]])
M <- results$estimate[results$term == "(Intercept)"]
Patient_Node_SD <- results$estimate[results$group == "patient_node:Patient" & results$term == "sd__(Intercept)"]
Patient_SD <- results$estimate[results$group == "Patient" & results$term == "sd__(Intercept)"]
Sys_SD <- sqrt(sum(results$estimate[results$term == "sd__(Intercept)"])^2)
Rand_SD <- results$estimate[results$term == "sd__Observation"]
summary_results <- rbind(summary_results, data.frame(location, direction, M, Patient_Node_SD, Patient_SD, Sys_SD, Rand_SD))
}
summary_results %>%
pivot_longer(cols = M:Rand_SD) %>%
pivot_wider(names_from = direction) %>%
kable()
| location | name | LR | AP | CC |
|---|---|---|---|---|
| Combined | M | -0.2717650 | -0.1162171 | -0.0361357 |
| Combined | Patient_Node_SD | 1.0775641 | 1.3152756 | 0.8151997 |
| Combined | Patient_SD | 1.4395725 | 1.9131843 | 2.2620974 |
| Combined | Sys_SD | 2.5171366 | 3.2284599 | 3.0772971 |
| Combined | Rand_SD | 1.6623952 | 2.1772837 | 2.3860259 |
| LN | M | -0.4199141 | -0.0084884 | 0.6535926 |
| LN | Patient_Node_SD | 0.0000000 | 0.0000000 | 0.0000000 |
| LN | Patient_SD | 1.8853376 | 1.7554829 | 2.0785069 |
| LN | Sys_SD | 1.8853376 | 1.7554829 | 2.0785069 |
| LN | Rand_SD | 1.3275719 | 1.8435798 | 2.3356596 |
| Meso | M | -0.1853583 | -0.5154813 | 0.4862730 |
| Meso | Patient_Node_SD | 1.2058598 | 1.2535963 | 0.7943324 |
| Meso | Patient_SD | 1.5743252 | 2.2235336 | 1.7825555 |
| Meso | Sys_SD | 2.7801851 | 3.4771299 | 2.5768879 |
| Meso | Rand_SD | 1.8013233 | 2.0756091 | 2.4121591 |
| Pre | M | -0.4360391 | -0.3053377 | -1.1587052 |
| Pre | Patient_Node_SD | 0.2707124 | 0.0000000 | 0.0000000 |
| Pre | Patient_SD | 0.6023547 | 2.6858341 | 3.1918241 |
| Pre | Sys_SD | 0.8730670 | 2.6858341 | 3.1918241 |
| Pre | Rand_SD | 1.4189305 | 2.6386131 | 2.1843062 |
Model control
sys_rand_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
mutate(location = sapply(strsplit(name,"_"), function(x) x[[1]]),
direction = sapply(strsplit(name,"_"), function(x) x[[2]])) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_grid(cols = vars(direction), rows = vars(location))
We test if the location of the nodes have an effect on the magnitude of the shift in either direction
loc_results <- list()
for(direction in c("LR","AP","CC")){
loc_results[[direction]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ Location + (1|Patient/patient_node)")), data)
}
We extract the estimates and p.values of the location effects. The model design uses contrasts, i.e. “LN” is included in the intercept, and effects for “Meso” and “Pre” is given as the difference in mean compared to “LN”. Results indicate no significant difference in the magnitude of the shift across the different locations for all directions.
lapply(loc_results, broom.mixed::tidy) %>%
bind_rows(.id = "Direction") %>%
filter(effect == "fixed") %>%
dplyr::select(Direction, estimate, term, p.value) %>%
pivot_wider(names_from = "Direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(term, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| term | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| (Intercept) | -0.3422084 | 0.5804914 | 0.5798518 | 0.4613936 | -0.5184555 | 0.5160629 |
| LocationMeso | 0.1407248 | 0.7910635 | -1.0232053 | 0.1136012 | 0.6225594 | 0.2584632 |
| LocationPre | -0.1141043 | 0.8608102 | -0.0675561 | 0.9312341 | 0.3983822 | 0.5539322 |
Model control
loc_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_wrap(~name, ncol = 3)
We test if the radiation time (Pretreat / During) of the nodes have an effect on the magnitude of the shift in either direction
predur_results <- list()
for(direction in c("LR","AP","CC")){
predur_results[[direction]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ `Pretreat/during` + (1|Patient/patient_node)")), data)
}
We extract the estimates and p.values of the effects. The model design uses contrasts, i.e. “Pretreat” is included in the intercept, and the effect of “During” is given as the difference in mean compared to “Pretreat”. Results indicate a significant difference in the magnitude of the shift in the AP and CC directions.
lapply(predur_results, broom.mixed::tidy) %>%
bind_rows(.id = "Direction") %>%
filter(effect == "fixed") %>%
dplyr::select(Direction, estimate, term, p.value) %>%
pivot_wider(names_from = "Direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(term, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| term | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| (Intercept) | -0.2125844 | 0.6431913 | 0.5296932 | 0.3816160 | -0.5323658 | 0.4353069 |
Pretreat/duringDuring |
-0.0986339 | 0.6127151 | -1.0765027 | 0.0000173 | 0.8270492 | 0.0028483 |
Model control
predur_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_wrap(~name, ncol = 3)
We test if the baseline bladder volume has an effect on the magnitude of the shift in either direction
baseline_bladder_results <- list()
for(direction in c("LR","AP","CC")){
baseline_bladder_results[[direction]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ Baseline_bladder + (1|Patient/patient_node)")), data)
}
We extract the estimates and p.values of the effects. We see no significant effect in either direction.
lapply(baseline_bladder_results, broom.mixed::tidy) %>%
bind_rows(.id = "Direction") %>%
filter(effect == "fixed") %>%
dplyr::select(Direction, estimate, term, p.value) %>%
pivot_wider(names_from = "Direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(term, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| term | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| (Intercept) | 0.1177918 | 0.9065926 | -2.148502 | 0.0798561 | -0.4448488 | 0.7684851 |
| Baseline_bladder | -0.2225138 | 0.6611792 | 1.135978 | 0.0671120 | 0.2291203 | 0.7622322 |
Model control
baseline_bladder_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_wrap(~name, ncol = 3)
Here we investigate the effect of the relative volume of the bladder on the magnitude of the shift in either direction and location. First we do a plot of Rel_bladder vs shift, and add simple regression lines for each patient/node combination.
data %>% dplyr::select(Patient, patient_node, Location, Rel_bladder, Shift_LR_GTV:Shift_CC_GTV) %>%
pivot_longer(cols = Shift_LR_GTV:Shift_CC_GTV) %>%
mutate(name = factor(sapply(str_split(name, "_"), function(x) x[2]), levels = c("LR", "AP", "CC"))) %>%
ggplot(aes(x = Rel_bladder, y = value, col = patient_node)) +
geom_point() +
stat_smooth(method = "lm",
formula = y ~ x,
geom = "smooth") +
facet_grid(rows = vars(Location), cols = vars(name)) +
theme(legend.position = "none")
We test the effect of the relative fill of the bladder combined and stratified by location and direction using the same nested mixed model as before, i.e. each patient/node gets an indvidual intercept.
#bladder_strat_results <- list()
#for(location in c("LN", "Meso", "Pre")){
# for(direction in c("LR","AP","CC")){
# bladder_strat_results[[paste0(location,"_",direction)]] <-
# lmer(as.formula(paste0("Shift_", direction, "_GTV ~ Rel_bladder + (1|Patient/patient_node)")), data %>% filter(Location == location))
# }
#}
bladder_strat_results <- list()
for(location in c("Combined", "LN", "Meso", "Pre")){
for(direction in c("LR","AP","CC")){
if(location == "Combined"){
bladder_strat_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ Rel_bladder + (1|Patient/patient_node)")), data)
} else{
bladder_strat_results[[paste0(location,"_",direction)]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ Rel_bladder + (1|Patient/patient_node)")), data %>% filter(Location == location))
}
}
}
Results show a tendency for the relative bladder volume to increase the magnitude of the shift in the LR and AP directions for LN, while combining across locations only gives significance for AP. Effect sizes are given as mm/(100 ml)
lapply(bladder_strat_results, broom.mixed::tidy) %>%
bind_rows(.id = "test") %>%
filter(term == "Rel_bladder") %>%
mutate(location = sapply(strsplit(test,"_"), function(x) x[[1]]),
direction = sapply(strsplit(test,"_"), function(x) x[[2]])) %>%
dplyr::select(location, direction, estimate, p.value) %>%
pivot_wider(names_from = "direction", values_from = c("estimate", "p.value")) %>%
dplyr::select(location, estimate_LR, p.value_LR, estimate_AP, p.value_AP, estimate_CC, p.value_CC) %>%
kable()
| location | estimate_LR | p.value_LR | estimate_AP | p.value_AP | estimate_CC | p.value_CC |
|---|---|---|---|---|---|---|
| Combined | 0.3769833 | 0.0784180 | -0.6809947 | 0.0138516 | 0.2991526 | 0.3361438 |
| LN | 1.4067589 | 0.0012831 | -1.3309466 | 0.0138977 | 0.7930715 | 0.2468077 |
| Meso | 0.2005602 | 0.4480756 | -0.5287316 | 0.0908914 | -0.0232290 | 0.9448980 |
| Pre | 0.4055876 | 0.4439214 | -1.1283503 | 0.2961191 | 1.4343397 | 0.1161893 |
Model control
bladder_strat_results %>%
purrr::map(~ {.x %>% residuals}) %>%
bind_rows(.id = "id") %>%
column_to_rownames("id") %>%
t() %>%
data.frame() %>%
pivot_longer(everything()) %>%
mutate(location = sapply(strsplit(name,"_"), function(x) x[[1]]),
direction = sapply(strsplit(name,"_"), function(x) x[[2]])) %>%
ggplot(aes(sample = value)) +
stat_qq() +
stat_qq_line() +
facet_grid(cols = vars(direction), rows = vars(location))
## model for bladder vs shift with interaction on location. Seems to disrupt the effect on shift_x in pre, seen in the stratified model.
bladder_comb_results <- list()
for(direction in c("LR","AP","CC")){
bladder_comb_results[[paste0(direction)]] <-
lmer(as.formula(paste0("Shift_", direction, "_GTV ~ Rel_bladder*Location + Baseline_bladder + `Pretreat/during` + (1|Patient/patient_node)")), data)
}
multires <- lapply(bladder_comb_results, broom.mixed::tidy)
kable(multires$LR, caption = "Results of multivariate model for LR")
| effect | group | term | estimate | std.error | statistic | df | p.value |
|---|---|---|---|---|---|---|---|
| fixed | NA | (Intercept) | -0.0812829 | 1.2175389 | -0.0667600 | 18.82398 | 0.9474768 |
| fixed | NA | Rel_bladder | 0.5571822 | 0.4653913 | 1.1972339 | 230.69708 | 0.2324442 |
| fixed | NA | LocationMeso | -0.1028153 | 0.7092340 | -0.1449667 | 79.30152 | 0.8851050 |
| fixed | NA | LocationPre | -0.2108367 | 0.8372974 | -0.2518062 | 80.24619 | 0.8018346 |
| fixed | NA | Baseline_bladder | 0.1259385 | 0.5493045 | 0.2292691 | 13.55801 | 0.8220822 |
| fixed | NA | Pretreat/duringDuring |
-0.0445372 | 0.1972378 | -0.2258045 | 238.00351 | 0.8215473 |
| fixed | NA | Rel_bladder:LocationMeso | -0.2226693 | 0.4760226 | -0.4677703 | 165.27437 | 0.6405651 |
| fixed | NA | Rel_bladder:LocationPre | 0.0022740 | 0.7843740 | 0.0028991 | 274.76641 | 0.9976890 |
| ran_pars | patient_node:Patient | sd__(Intercept) | 1.1322225 | NA | NA | NA | NA |
| ran_pars | Patient | sd__(Intercept) | 1.5251324 | NA | NA | NA | NA |
| ran_pars | Residual | sd__Observation | 1.6587175 | NA | NA | NA | NA |
kable(multires$AP, caption = "Results of multivariate model for AP")
| effect | group | term | estimate | std.error | statistic | df | p.value |
|---|---|---|---|---|---|---|---|
| fixed | NA | (Intercept) | 0.2569344 | 1.4455307 | 0.1777440 | 22.02002 | 0.8605490 |
| fixed | NA | Rel_bladder | -0.4483474 | 0.5654427 | -0.7929139 | 215.87984 | 0.4286985 |
| fixed | NA | LocationMeso | -1.2396934 | 0.8306429 | -1.4924505 | 76.21745 | 0.1397090 |
| fixed | NA | LocationPre | -0.6231960 | 0.9828147 | -0.6340930 | 79.42560 | 0.5278422 |
| fixed | NA | Baseline_bladder | 0.4187503 | 0.6585909 | 0.6358276 | 16.47529 | 0.5336223 |
| fixed | NA | Pretreat/duringDuring |
-1.1921816 | 0.2479840 | -4.8074949 | 238.31113 | 0.0000027 |
| fixed | NA | Rel_bladder:LocationMeso | -0.3361974 | 0.5710881 | -0.5886962 | 143.58663 | 0.5569901 |
| fixed | NA | Rel_bladder:LocationPre | -0.9764154 | 0.9619141 | -1.0150754 | 263.27626 | 0.3110018 |
| ran_pars | patient_node:Patient | sd__(Intercept) | 1.2378443 | NA | NA | NA | NA |
| ran_pars | Patient | sd__(Intercept) | 1.8256947 | NA | NA | NA | NA |
| ran_pars | Residual | sd__Observation | 2.0857877 | NA | NA | NA | NA |
kable(multires$CC, caption = "Results of multivariate model for CC")
| effect | group | term | estimate | std.error | statistic | df | p.value |
|---|---|---|---|---|---|---|---|
| fixed | NA | (Intercept) | -1.3962200 | 1.6291271 | -0.8570357 | 15.38721 | 0.4045759 |
| fixed | NA | Rel_bladder | 0.8306066 | 0.5620517 | 1.4778119 | 182.60204 | 0.1411809 |
| fixed | NA | LocationMeso | 0.0035166 | 0.7368717 | 0.0047723 | 65.06397 | 0.9962069 |
| fixed | NA | LocationPre | 1.1271695 | 0.8792061 | 1.2820310 | 74.73726 | 0.2037944 |
| fixed | NA | Baseline_bladder | 0.6455385 | 0.7835178 | 0.8238977 | 13.32497 | 0.4244997 |
| fixed | NA | Pretreat/duringDuring |
0.8873404 | 0.2789002 | 3.1815699 | 240.20608 | 0.0016579 |
| fixed | NA | Rel_bladder:LocationMeso | -0.5764313 | 0.5348672 | -1.0777092 | 92.21149 | 0.2839748 |
| fixed | NA | Rel_bladder:LocationPre | 1.7084852 | 0.9771164 | 1.7484971 | 202.04164 | 0.0818968 |
| ran_pars | patient_node:Patient | sd__(Intercept) | 0.7089587 | NA | NA | NA | NA |
| ran_pars | Patient | sd__(Intercept) | 2.3093311 | NA | NA | NA | NA |
| ran_pars | Residual | sd__Observation | 2.3459189 | NA | NA | NA | NA |