From dca229be117b26c85076a56eb2143e32f841b9f0 Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Fri, 28 May 2021 10:31:02 -0700 Subject: [PATCH 01/20] Add LOO variation plot --- R/loo_variation_plot.R | 151 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 R/loo_variation_plot.R diff --git a/R/loo_variation_plot.R b/R/loo_variation_plot.R new file mode 100644 index 00000000..6ae56916 --- /dev/null +++ b/R/loo_variation_plot.R @@ -0,0 +1,151 @@ +#' Compare models across domains +#' +#' The LOO variation plot shows how the predictive accuracy of two different +#' models changes as the predictor is varied. This can is useful for identifying +#' opportunities for model stacking or expansion. +#' +#' @param y A vector of observations. See Details. +#' @param psis_object_1,psis_object_2 If using loo version 2.0.0 or greater, +#' an object returned by the `[loo::psis()]` function (or by the +#' `[loo::loo()]` function with argument `save_psis` set to `TRUE`). +#' @param ... Currently unused. +#' @param group A grouping variable (a vector or factor) the same length +#' as `y`. Each value in group is interpreted as the group level pertaining +#' to the corresponding value of `y`. If `FALSE`, ignored. +#' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control +#' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` +#' arguments of `[ggplot2::geom_jitter()]` to control the appearance of +#' points. `jitter` can be either a number or a vector of numbers. +#' Passing a single number will jitter variables along the x axis only, while +#' passing a vector will jitter along both axes. +#' @param quantiles Boolean that determines whether to plot the quantiles of +#' `y` rather than `y` itself. Useful when `y` has a very irregular +#' distribution. +#' @param sortByGroup Sort observations by `group`, then plot against an +#' arbitrary index. Plotting by index can be useful when categories have +#' very different sample sizes. +#' +#' +#' @template return-ggplot +#' +#' @template reference-vis-paper +#' +#' @examples +#' +#' library(loo) +#' +#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", +#' "#F0E442", "#0072B2","#CC79A7") +#' +#' # Plot using groups from WHO +#' +#' plot_loo_dif(factor(GM@data$super_region_name), loo3, loo2, +#' group = GM@data$super_region_name, alpha = .5, +#' jitter = c(.45, .2) +#' ) + +#' xlab("Region") + scale_colour_manual(values=cbPalette) +#' +#' # Plot using groups identified with clustering +#' +#' plot_loo_dif(factor(GM@data$cluster_region), loo3, loo2, +#' group = GM@data$super_region_name, alpha = .5, +#' jitter = c(.45, .2) +#' ) + +#' xlab("Cluster Group") + scale_colour_manual(values=cbPalette) +#' +#' # Plot using an index variable to reduce crowding +#' +#' plot_loo_dif(1:2980, loo3, loo2, group = GM@data$super_region_name, +#' alpha = .5, sortByGroup = TRUE, +#' ) + +#' xlab("Index") + scale_colour_manual(values=cbPalette) +#' +#' +plot_loo_dif <- + function(y, + psis_object_1, + psis_object_2, + ..., + group = FALSE, + outlier_thresh = FALSE, + size = 1, + alpha = 1, + jitter = 0, + quantiles = FALSE, + sortByGroup = FALSE + ){ + + # Adding a 0 at the end lets users provide a single number as input. + # In this case, only horizontal jitter is applied. + jitter <- c(jitter, 0) + + elpdDif <- psis_object_1$pointwise[, "elpd_loo"] - + psis_object_2$pointwise[, "elpd_loo"] + + + if (quantiles){ + # If quantiles is set to true, replace all y values with their quantile + y <- ecdf(y)(y) + } + + + if (sortByGroup){ + if (identical(group, FALSE) || !identical(y, 1:length(y))){ + stop("ERROR: sortByGroup should only be used for grouping categorical + variables, then plotting them with an arbitrary index. You can + create such an index using `1:length(data)`. + ") + } + + values <- group_by(tibble(group, elpdDif), factor(group)) %>% + arrange(.by_group = TRUE) + + elpdDif <- pull(values, elpdDif) + group <- pull(values, group) + + } + + + plot <- ggplot(mapping=aes(y, elpdDif)) + + geom_hline(yintercept=0) + + xlab(ifelse(sortByGroup, "y", "Index")) + + ylab(expression(ELPD[i][1] - ELPD[i][2])) + + labs(color = "Groups") + + + + if (identical(group, FALSE)){ + # Don't color by group if no groups are passed + plot <- plot + + geom_jitter(width = jitter[1], height = jitter[2], + alpha = alpha, size = size + ) + } + else{ + # If group is passed, use color + plot <- plot + + geom_jitter(aes(color = factor(group)), + width = jitter[1], height = jitter[2], + alpha = alpha, size = size + ) + } + + if (!identical(outlier_thresh, FALSE)){ + # Flag outliers + is_outlier <- elpdDif > outlier_thresh + index <- 1:length(y) + outlier_labs <- index[is_outlier] + + plot <- plot + annotate("text", + x = y[is_outlier], + y = elpdDif[outlier_labs], + label = outlier_labs, + size = 4 + ) + + + } + + return(plot) + } + From 8786be39692e796c34d48d1a987ecbff753bea6a Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Fri, 28 May 2021 10:32:06 -0700 Subject: [PATCH 02/20] typo --- R/loo_variation_plot.R | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/R/loo_variation_plot.R b/R/loo_variation_plot.R index 6ae56916..98d9fd5f 100644 --- a/R/loo_variation_plot.R +++ b/R/loo_variation_plot.R @@ -1,7 +1,7 @@ #' Compare models across domains #' -#' The LOO variation plot shows how the predictive accuracy of two different -#' models changes as the predictor is varied. This can is useful for identifying +#' The LOO difference plot shows how the difference in the ELPD of two different +#' models changes when a predictor is varied. This can is useful for identifying #' opportunities for model stacking or expansion. #' #' @param y A vector of observations. See Details. @@ -61,7 +61,7 @@ #' xlab("Index") + scale_colour_manual(values=cbPalette) #' #' -plot_loo_dif <- +plot_loo_variation <- function(y, psis_object_1, psis_object_2, From 8e329d06efb76c53f977a55afa56c1c62d75d037 Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Fri, 28 May 2021 12:34:33 -0700 Subject: [PATCH 03/20] Typo --- R/loo_difference_plot.R | 151 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 R/loo_difference_plot.R diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R new file mode 100644 index 00000000..8f2ba6f5 --- /dev/null +++ b/R/loo_difference_plot.R @@ -0,0 +1,151 @@ +#' Compare models across domains +#' +#' The LOO difference plot shows how the ELPD of two different models +#' changes when a predictor is varied. This can is useful for identifying +#' opportunities for model stacking or expansion. +#' +#' @param y A vector of observations. See Details. +#' @param psis_object_1,psis_object_2 If using loo version 2.0.0 or greater, +#' an object returned by the `[loo::psis()]` function (or by the +#' `[loo::loo()]` function with argument `save_psis` set to `TRUE`). +#' @param ... Currently unused. +#' @param group A grouping variable (a vector or factor) the same length +#' as `y`. Each value in group is interpreted as the group level pertaining +#' to the corresponding value of `y`. If `FALSE`, ignored. +#' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control +#' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` +#' arguments of `[ggplot2::geom_jitter()]` to control the appearance of +#' points. `jitter` can be either a number or a vector of numbers. +#' Passing a single number will jitter variables along the x axis only, while +#' passing a vector will jitter along both axes. +#' @param quantiles Boolean that determines whether to plot the quantiles of +#' `y` rather than `y` itself. Useful when `y` has a very irregular +#' distribution. +#' @param sortByGroup Sort observations by `group`, then plot against an +#' arbitrary index. Plotting by index can be useful when categories have +#' very different sample sizes. +#' +#' +#' @template return-ggplot +#' +#' @template reference-vis-paper +#' +#' @examples +#' +#' library(loo) +#' +#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", +#' "#F0E442", "#0072B2","#CC79A7") +#' +#' # Plot using groups from WHO +#' +#' plot_loo_dif(factor(GM@data$super_region_name), loo3, loo2, +#' group = GM@data$super_region_name, alpha = .5, +#' jitter = c(.45, .2) +#' ) + +#' xlab("Region") + scale_colour_manual(values=cbPalette) +#' +#' # Plot using groups identified with clustering +#' +#' plot_loo_dif(factor(GM@data$cluster_region), loo3, loo2, +#' group = GM@data$super_region_name, alpha = .5, +#' jitter = c(.45, .2) +#' ) + +#' xlab("Cluster Group") + scale_colour_manual(values=cbPalette) +#' +#' # Plot using an index variable to reduce crowding +#' +#' plot_loo_dif(1:2980, loo3, loo2, group = GM@data$super_region_name, +#' alpha = .5, sortByGroup = TRUE, +#' ) + +#' xlab("Index") + scale_colour_manual(values=cbPalette) +#' +#' +plot_loo_variation <- + function(y, + psis_object_1, + psis_object_2, + ..., + group = FALSE, + outlier_thresh = FALSE, + size = 1, + alpha = 1, + jitter = 0, + quantiles = FALSE, + sortByGroup = FALSE + ){ + + # Adding a 0 at the end lets users provide a single number as input. + # In this case, only horizontal jitter is applied. + jitter <- c(jitter, 0) + + elpdDif <- psis_object_1$pointwise[, "elpd_loo"] - + psis_object_2$pointwise[, "elpd_loo"] + + + if (quantiles){ + # If quantiles is set to true, replace all y values with their quantile + y <- ecdf(y)(y) + } + + + if (sortByGroup){ + if (identical(group, FALSE) || !identical(y, 1:length(y))){ + stop("ERROR: sortByGroup should only be used for grouping categorical + variables, then plotting them with an arbitrary index. You can + create such an index using `1:length(data)`. + ") + } + + values <- group_by(tibble(group, elpdDif), factor(group)) %>% + arrange(.by_group = TRUE) + + elpdDif <- pull(values, elpdDif) + group <- pull(values, group) + + } + + + plot <- ggplot(mapping=aes(y, elpdDif)) + + geom_hline(yintercept=0) + + xlab(ifelse(sortByGroup, "y", "Index")) + + ylab(expression(ELPD[i][1] - ELPD[i][2])) + + labs(color = "Groups") + + + + if (identical(group, FALSE)){ + # Don't color by group if no groups are passed + plot <- plot + + geom_jitter(width = jitter[1], height = jitter[2], + alpha = alpha, size = size + ) + } + else{ + # If group is passed, use color + plot <- plot + + geom_jitter(aes(color = factor(group)), + width = jitter[1], height = jitter[2], + alpha = alpha, size = size + ) + } + + if (!identical(outlier_thresh, FALSE)){ + # Flag outliers + is_outlier <- elpdDif > outlier_thresh + index <- 1:length(y) + outlier_labs <- index[is_outlier] + + plot <- plot + annotate("text", + x = y[is_outlier], + y = elpdDif[outlier_labs], + label = outlier_labs, + size = 4 + ) + + + } + + return(plot) + } + From 90ecb40c25919c69a9693780fbcce433df1b2113 Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Fri, 28 May 2021 12:35:16 -0700 Subject: [PATCH 04/20] Rename file --- R/loo_variation_plot.R | 151 ----------------------------------------- 1 file changed, 151 deletions(-) delete mode 100644 R/loo_variation_plot.R diff --git a/R/loo_variation_plot.R b/R/loo_variation_plot.R deleted file mode 100644 index 98d9fd5f..00000000 --- a/R/loo_variation_plot.R +++ /dev/null @@ -1,151 +0,0 @@ -#' Compare models across domains -#' -#' The LOO difference plot shows how the difference in the ELPD of two different -#' models changes when a predictor is varied. This can is useful for identifying -#' opportunities for model stacking or expansion. -#' -#' @param y A vector of observations. See Details. -#' @param psis_object_1,psis_object_2 If using loo version 2.0.0 or greater, -#' an object returned by the `[loo::psis()]` function (or by the -#' `[loo::loo()]` function with argument `save_psis` set to `TRUE`). -#' @param ... Currently unused. -#' @param group A grouping variable (a vector or factor) the same length -#' as `y`. Each value in group is interpreted as the group level pertaining -#' to the corresponding value of `y`. If `FALSE`, ignored. -#' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control -#' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` -#' arguments of `[ggplot2::geom_jitter()]` to control the appearance of -#' points. `jitter` can be either a number or a vector of numbers. -#' Passing a single number will jitter variables along the x axis only, while -#' passing a vector will jitter along both axes. -#' @param quantiles Boolean that determines whether to plot the quantiles of -#' `y` rather than `y` itself. Useful when `y` has a very irregular -#' distribution. -#' @param sortByGroup Sort observations by `group`, then plot against an -#' arbitrary index. Plotting by index can be useful when categories have -#' very different sample sizes. -#' -#' -#' @template return-ggplot -#' -#' @template reference-vis-paper -#' -#' @examples -#' -#' library(loo) -#' -#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", -#' "#F0E442", "#0072B2","#CC79A7") -#' -#' # Plot using groups from WHO -#' -#' plot_loo_dif(factor(GM@data$super_region_name), loo3, loo2, -#' group = GM@data$super_region_name, alpha = .5, -#' jitter = c(.45, .2) -#' ) + -#' xlab("Region") + scale_colour_manual(values=cbPalette) -#' -#' # Plot using groups identified with clustering -#' -#' plot_loo_dif(factor(GM@data$cluster_region), loo3, loo2, -#' group = GM@data$super_region_name, alpha = .5, -#' jitter = c(.45, .2) -#' ) + -#' xlab("Cluster Group") + scale_colour_manual(values=cbPalette) -#' -#' # Plot using an index variable to reduce crowding -#' -#' plot_loo_dif(1:2980, loo3, loo2, group = GM@data$super_region_name, -#' alpha = .5, sortByGroup = TRUE, -#' ) + -#' xlab("Index") + scale_colour_manual(values=cbPalette) -#' -#' -plot_loo_variation <- - function(y, - psis_object_1, - psis_object_2, - ..., - group = FALSE, - outlier_thresh = FALSE, - size = 1, - alpha = 1, - jitter = 0, - quantiles = FALSE, - sortByGroup = FALSE - ){ - - # Adding a 0 at the end lets users provide a single number as input. - # In this case, only horizontal jitter is applied. - jitter <- c(jitter, 0) - - elpdDif <- psis_object_1$pointwise[, "elpd_loo"] - - psis_object_2$pointwise[, "elpd_loo"] - - - if (quantiles){ - # If quantiles is set to true, replace all y values with their quantile - y <- ecdf(y)(y) - } - - - if (sortByGroup){ - if (identical(group, FALSE) || !identical(y, 1:length(y))){ - stop("ERROR: sortByGroup should only be used for grouping categorical - variables, then plotting them with an arbitrary index. You can - create such an index using `1:length(data)`. - ") - } - - values <- group_by(tibble(group, elpdDif), factor(group)) %>% - arrange(.by_group = TRUE) - - elpdDif <- pull(values, elpdDif) - group <- pull(values, group) - - } - - - plot <- ggplot(mapping=aes(y, elpdDif)) + - geom_hline(yintercept=0) + - xlab(ifelse(sortByGroup, "y", "Index")) + - ylab(expression(ELPD[i][1] - ELPD[i][2])) + - labs(color = "Groups") - - - - if (identical(group, FALSE)){ - # Don't color by group if no groups are passed - plot <- plot + - geom_jitter(width = jitter[1], height = jitter[2], - alpha = alpha, size = size - ) - } - else{ - # If group is passed, use color - plot <- plot + - geom_jitter(aes(color = factor(group)), - width = jitter[1], height = jitter[2], - alpha = alpha, size = size - ) - } - - if (!identical(outlier_thresh, FALSE)){ - # Flag outliers - is_outlier <- elpdDif > outlier_thresh - index <- 1:length(y) - outlier_labs <- index[is_outlier] - - plot <- plot + annotate("text", - x = y[is_outlier], - y = elpdDif[outlier_labs], - label = outlier_labs, - size = 4 - ) - - - } - - return(plot) - } - From 48b97b61dd5fa47ccf72dbac71595afbd8ecc6e3 Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Fri, 28 May 2021 15:00:03 -0700 Subject: [PATCH 05/20] Made recommended changes --- R/loo_difference_plot.R | 66 ++++++++++++++++++++--------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 8f2ba6f5..8d3bc6cb 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -12,6 +12,8 @@ #' @param group A grouping variable (a vector or factor) the same length #' as `y`. Each value in group is interpreted as the group level pertaining #' to the corresponding value of `y`. If `FALSE`, ignored. +#' @param outlier_thresh Flag values when the difference in the ELPD exceeds +#' this threshold. Defaults to `NULL`, in which case no values are flagged. #' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control #' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` #' arguments of `[ggplot2::geom_jitter()]` to control the appearance of @@ -21,7 +23,7 @@ #' @param quantiles Boolean that determines whether to plot the quantiles of #' `y` rather than `y` itself. Useful when `y` has a very irregular #' distribution. -#' @param sortByGroup Sort observations by `group`, then plot against an +#' @param sort_by_group Sort observations by `group`, then plot against an #' arbitrary index. Plotting by index can be useful when categories have #' very different sample sizes. #' @@ -56,23 +58,23 @@ #' # Plot using an index variable to reduce crowding #' #' plot_loo_dif(1:2980, loo3, loo2, group = GM@data$super_region_name, -#' alpha = .5, sortByGroup = TRUE, +#' alpha = .5, sort_by_group = TRUE, #' ) + #' xlab("Index") + scale_colour_manual(values=cbPalette) #' #' -plot_loo_variation <- +plot_loo_dif <- function(y, psis_object_1, psis_object_2, ..., - group = FALSE, - outlier_thresh = FALSE, + group = NULL, + outlier_thresh = NULL, size = 1, alpha = 1, jitter = 0, quantiles = FALSE, - sortByGroup = FALSE + sort_by_group = FALSE ){ # Adding a 0 at the end lets users provide a single number as input. @@ -89,59 +91,57 @@ plot_loo_variation <- } - if (sortByGroup){ - if (identical(group, FALSE) || !identical(y, 1:length(y))){ - stop("ERROR: sortByGroup should only be used for grouping categorical + if (sort_by_group){ + if (identical(group, NULL) || !identical(y, 1:length(y))){ + stop("ERROR: sort_by_group should only be used for grouping categorical variables, then plotting them with an arbitrary index. You can create such an index using `1:length(data)`. ") } - values <- group_by(tibble(group, elpdDif), factor(group)) %>% - arrange(.by_group = TRUE) - - elpdDif <- pull(values, elpdDif) - group <- pull(values, group) + ordering <- order(group) + elpdDif <- elpdDif[ordering] + group <- group[ordering] } - plot <- ggplot(mapping=aes(y, elpdDif)) + - geom_hline(yintercept=0) + - xlab(ifelse(sortByGroup, "y", "Index")) + - ylab(expression(ELPD[i][1] - ELPD[i][2])) + - labs(color = "Groups") + plot <- ggplot2::ggplot(mapping=aes(y, elpdDif)) + + ggplot2::geom_hline(yintercept=0) + + ggplot2::xlab(ifelse(sort_by_group, "y", "Index")) + + ggplot2::ylab(expression(ELPD[i][1] - ELPD[i][2])) + + ggplot2::labs(color = "Groups") if (identical(group, FALSE)){ # Don't color by group if no groups are passed plot <- plot + - geom_jitter(width = jitter[1], height = jitter[2], - alpha = alpha, size = size - ) + ggplot2::geom_jitter(width = jitter[1], height = jitter[2], + alpha = alpha, size = size + ) } else{ # If group is passed, use color plot <- plot + - geom_jitter(aes(color = factor(group)), - width = jitter[1], height = jitter[2], - alpha = alpha, size = size - ) + ggplot2::geom_jitter(aes(color = factor(group)), + width = jitter[1], height = jitter[2], + alpha = alpha, size = size + ) } - if (!identical(outlier_thresh, FALSE)){ + if (!identical(outlier_thresh, NULL)){ # Flag outliers is_outlier <- elpdDif > outlier_thresh index <- 1:length(y) outlier_labs <- index[is_outlier] - plot <- plot + annotate("text", - x = y[is_outlier], - y = elpdDif[outlier_labs], - label = outlier_labs, - size = 4 - ) + plot <- plot + ggplot2::annotate("text", + x = y[is_outlier], + y = elpdDif[outlier_labs], + label = outlier_labs, + size = 4 + ) } From 5399cb8ccd92e5ddb8e76c3e2aeed6c6c2d93354 Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Sun, 30 May 2021 21:28:19 -0700 Subject: [PATCH 06/20] Added continuous example, removed quantile option --- R/loo_difference_plot.R | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 8d3bc6cb..119a8be1 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -20,9 +20,6 @@ #' points. `jitter` can be either a number or a vector of numbers. #' Passing a single number will jitter variables along the x axis only, while #' passing a vector will jitter along both axes. -#' @param quantiles Boolean that determines whether to plot the quantiles of -#' `y` rather than `y` itself. Useful when `y` has a very irregular -#' distribution. #' @param sort_by_group Sort observations by `group`, then plot against an #' arbitrary index. Plotting by index can be useful when categories have #' very different sample sizes. @@ -73,7 +70,6 @@ plot_loo_dif <- size = 1, alpha = 1, jitter = 0, - quantiles = FALSE, sort_by_group = FALSE ){ @@ -83,12 +79,6 @@ plot_loo_dif <- elpdDif <- psis_object_1$pointwise[, "elpd_loo"] - psis_object_2$pointwise[, "elpd_loo"] - - - if (quantiles){ - # If quantiles is set to true, replace all y values with their quantile - y <- ecdf(y)(y) - } if (sort_by_group){ From 4d085d61e698d10adefe348537a8eae3f56fe54a Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Mon, 31 May 2021 09:54:27 -0700 Subject: [PATCH 07/20] Fixed error in example --- R/loo_difference_plot.R | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 119a8be1..9cdf49a0 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -58,8 +58,45 @@ #' alpha = .5, sort_by_group = TRUE, #' ) + #' xlab("Index") + scale_colour_manual(values=cbPalette) +#' +#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", +#' "#F0E442", "#0072B2","#CC79A7") +#' +#' options(mc.cores = parallel::detectCores()) +#' options(loo.cores = parallel::detectCores()) +#' +#' data(kidiq) +#' +#' t_prior <- student_t(df = 10, location = 0, scale = .5) +#' coef_prior <- student_t(df = 10, location = .5, scale = .25) +#' kidiq$kid_std <- (kidiq$kid_score - 100) / 15 +#' kidiq$mom_std <- (kidiq$mom_iq - 100) / 15 +#' kidiq$age_std <- (kidiq$mom_age - mean(kidiq$mom_age)) / sd(kidiq$mom_age) +#' kidiq$hs_cent <- kidiq$mom_hs - mean(kidiq$mom_hs) +#' +#' coFit <- stan_glm(kid_std ~ hs_cent, data = kidiq, +#' family = gaussian(), prior = coef_prior, +#' prior_intercept = t_prior, +#' seed = 1776, chains = 2 +#' ) +#' iqFit <- stan_glm(kid_std ~ mom_std + hs_cent, data = kidiq, +#' family = gaussian(), +#' prior = coef_prior, prior_intercept = t_prior, +#' seed = 1776, chains = 2 +#' ) +#' +#' +#' coLoo <- loo(iqFit, save_psis = TRUE) +#' iqLoo <- loo(coFit, save_psis = TRUE) +#' +#' +#' plot_loo_dif(kidiq$mom_iq, coLoo, iqLoo, group = kidiq$mom_hs, +#' alpha = .5, jitter = c(.1, .1) +#' ) + ggplot2::geom_smooth() + +#' ggplot2::xlab("IQ of Mother") + +#' ggplot2::scale_colour_manual(values=cbPalette) #' -#' + plot_loo_dif <- function(y, psis_object_1, From d4b86415dbb5279f020850b2de4cef32502cb4b2 Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Mon, 31 May 2021 09:55:22 -0700 Subject: [PATCH 08/20] Typo --- R/loo_difference_plot.R | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 9cdf49a0..557e3d4a 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -59,11 +59,8 @@ #' ) + #' xlab("Index") + scale_colour_manual(values=cbPalette) #' -#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", -#' "#F0E442", "#0072B2","#CC79A7") -#' -#' options(mc.cores = parallel::detectCores()) -#' options(loo.cores = parallel::detectCores()) +#' +#' # Example using kid IQ Dataset with a continuous predictor #' #' data(kidiq) #' From a0417dda24128c6a36cf87c1e50d0a97befaedf4 Mon Sep 17 00:00:00 2001 From: Closed-Limelike-Curves Date: Thu, 3 Jun 2021 10:07:56 -0700 Subject: [PATCH 09/20] Typo/formatting --- R/loo_difference_plot.R | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 557e3d4a..78cba763 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -89,7 +89,8 @@ #' #' plot_loo_dif(kidiq$mom_iq, coLoo, iqLoo, group = kidiq$mom_hs, #' alpha = .5, jitter = c(.1, .1) -#' ) + ggplot2::geom_smooth() + +#' ) + +#' ggplot2::geom_smooth() + #' ggplot2::xlab("IQ of Mother") + #' ggplot2::scale_colour_manual(values=cbPalette) #' From 33094d7a2cc5f00e63c9ed4f173323a909b87e03 Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 12:53:02 -0700 Subject: [PATCH 10/20] Formatting/style changes --- R/loo_difference_plot.R | 211 ++++++++++++++++++++-------------------- 1 file changed, 104 insertions(+), 107 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 78cba763..13af841d 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -1,71 +1,63 @@ #' Compare models across domains -#' +#' #' The LOO difference plot shows how the ELPD of two different models #' changes when a predictor is varied. This can is useful for identifying #' opportunities for model stacking or expansion. -#' +#' #' @param y A vector of observations. See Details. -#' @param psis_object_1,psis_object_2 If using loo version 2.0.0 or greater, -#' an object returned by the `[loo::psis()]` function (or by the +#' @param psis_object_1,psis_object_2 If using loo version 2.0.0 or greater, +#' an object returned by the `[loo::psis()]` function (or by the #' `[loo::loo()]` function with argument `save_psis` set to `TRUE`). #' @param ... Currently unused. -#' @param group A grouping variable (a vector or factor) the same length -#' as `y`. Each value in group is interpreted as the group level pertaining +#' @param group A grouping variable (a vector or factor) the same length +#' as `y`. Each value in group is interpreted as the group level pertaining #' to the corresponding value of `y`. If `FALSE`, ignored. #' @param outlier_thresh Flag values when the difference in the ELPD exceeds #' this threshold. Defaults to `NULL`, in which case no values are flagged. -#' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control -#' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` -#' arguments of `[ggplot2::geom_jitter()]` to control the appearance of +#' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control +#' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` +#' arguments of `[ggplot2::geom_jitter()]` to control the appearance of #' points. `jitter` can be either a number or a vector of numbers. -#' Passing a single number will jitter variables along the x axis only, while +#' Passing a single number will jitter variables along the x axis only, while #' passing a vector will jitter along both axes. -#' @param sort_by_group Sort observations by `group`, then plot against an -#' arbitrary index. Plotting by index can be useful when categories have -#' very different sample sizes. -#' -#' +#' @param sort_by_group Sort observations by `group`, then plot against an +#' arbitrary index. Plotting by index can be useful when categories have +#' very different sample sizes. +#' +#' #' @template return-ggplot -#' #' @template reference-vis-paper -#' -#' @examples -#' -#' library(loo) -#' -#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", +#' +#' @examples +#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", #' "#F0E442", "#0072B2","#CC79A7") -#' +#' #' # Plot using groups from WHO -#' -#' plot_loo_dif(factor(GM@data$super_region_name), loo3, loo2, -#' group = GM@data$super_region_name, alpha = .5, -#' jitter = c(.45, .2) -#' ) + +#' plot_loo_dif(factor(GM@data$super_region_name), loo3, loo2, +#' group = GM@data$super_region_name, alpha = 0.5, +#' jitter = c(0.45, 0.2) +#' ) + #' xlab("Region") + scale_colour_manual(values=cbPalette) -#' +#' #' # Plot using groups identified with clustering -#' -#' plot_loo_dif(factor(GM@data$cluster_region), loo3, loo2, -#' group = GM@data$super_region_name, alpha = .5, -#' jitter = c(.45, .2) -#' ) + +#' plot_loo_dif(factor(GM@data$cluster_region), loo3, loo2, +#' group = GM@data$super_region_name, alpha = 0.5, +#' jitter = c(0.45, 0.2) +#' ) + #' xlab("Cluster Group") + scale_colour_manual(values=cbPalette) -#' +#' #' # Plot using an index variable to reduce crowding -#' -#' plot_loo_dif(1:2980, loo3, loo2, group = GM@data$super_region_name, -#' alpha = .5, sort_by_group = TRUE, -#' ) + +#' plot_loo_dif(1:2980, loo3, loo2, group = GM@data$super_region_name, +#' alpha = 0.5, sort_by_group = TRUE +#' ) + #' xlab("Index") + scale_colour_manual(values=cbPalette) #' -#' -#' # Example using kid IQ Dataset with a continuous predictor #' +#' # Example using kid IQ Dataset with a continuous predictor #' data(kidiq) #' -#' t_prior <- student_t(df = 10, location = 0, scale = .5) -#' coef_prior <- student_t(df = 10, location = .5, scale = .25) +#' t_prior <- student_t(df = 10, location = 0, scale = 0.5) +#' coef_prior <- student_t(df = 10, location = 0.5, scale = 0.25) #' kidiq$kid_std <- (kidiq$kid_score - 100) / 15 #' kidiq$mom_std <- (kidiq$mom_iq - 100) / 15 #' kidiq$age_std <- (kidiq$mom_age - mean(kidiq$mom_age)) / sd(kidiq$mom_age) @@ -82,95 +74,100 @@ #' seed = 1776, chains = 2 #' ) #' -#' #' coLoo <- loo(iqFit, save_psis = TRUE) #' iqLoo <- loo(coFit, save_psis = TRUE) #' -#' #' plot_loo_dif(kidiq$mom_iq, coLoo, iqLoo, group = kidiq$mom_hs, -#' alpha = .5, jitter = c(.1, .1) +#' alpha = 0.5, jitter = c(0.1, 0.1) #' ) + #' ggplot2::geom_smooth() + #' ggplot2::xlab("IQ of Mother") + #' ggplot2::scale_colour_manual(values=cbPalette) -#' +plot_loo_dif <- + function( + y, + psis_object_1, + psis_object_2, + ..., + group = NULL, + outlier_thresh = NULL, + size = 1, + alpha = 1, + jitter = 0, + sort_by_group = FALSE + ) { + if (!requireNamespace("ggplot2", quietly = TRUE)) { + stop( + "Please install 'ggplot2' to use `plot_loo_dif()`.", + call. = FALSE + ) + } + {} -plot_loo_dif <- - function(y, - psis_object_1, - psis_object_2, - ..., - group = NULL, - outlier_thresh = NULL, - size = 1, - alpha = 1, - jitter = 0, - sort_by_group = FALSE - ){ - # Adding a 0 at the end lets users provide a single number as input. # In this case, only horizontal jitter is applied. - jitter <- c(jitter, 0) - - elpdDif <- psis_object_1$pointwise[, "elpd_loo"] - - psis_object_2$pointwise[, "elpd_loo"] + jitter <- c(jitter, 0) + + elpdDif <- psis_object_1$pointwise[, "elpd_loo"] - + psis_object_2$pointwise[, "elpd_loo"] - - if (sort_by_group){ - if (identical(group, NULL) || !identical(y, 1:length(y))){ - stop("ERROR: sort_by_group should only be used for grouping categorical + if (sort_by_group) { + if (identical(group, NULL) || !identical(y, seq_along(y))) { + stop( + "ERROR: sort_by_group should only be used for grouping categorical variables, then plotting them with an arbitrary index. You can create such an index using `1:length(data)`. - ") + " + ) } - + ordering <- order(group) elpdDif <- elpdDif[ordering] group <- group[ordering] - } - - - plot <- ggplot2::ggplot(mapping=aes(y, elpdDif)) + - ggplot2::geom_hline(yintercept=0) + - ggplot2::xlab(ifelse(sort_by_group, "y", "Index")) + - ggplot2::ylab(expression(ELPD[i][1] - ELPD[i][2])) + - ggplot2::labs(color = "Groups") - - - if (identical(group, FALSE)){ + plot <- ggplot2::ggplot(mapping = aes(y, elpdDif)) + + ggplot2::geom_hline(yintercept = 0) + + ggplot2::xlab(ifelse(sort_by_group, "y", "Index")) + + ggplot2::ylab(expression(ELPD[i][1] - ELPD[i][2])) + + ggplot2::labs(color = "Groups") + + if (identical(group, FALSE)) { # Don't color by group if no groups are passed - plot <- plot + - ggplot2::geom_jitter(width = jitter[1], height = jitter[2], - alpha = alpha, size = size - ) - } - else{ + plot <- plot + + ggplot2::geom_jitter( + width = jitter[1], + height = jitter[2], + alpha = alpha, + size = size + ) + } else { # If group is passed, use color - plot <- plot + - ggplot2::geom_jitter(aes(color = factor(group)), - width = jitter[1], height = jitter[2], - alpha = alpha, size = size - ) + plot <- plot + + ggplot2::geom_jitter( + aes(color = factor(group)), + width = jitter[1], + height = jitter[2], + alpha = alpha, + size = size + ) } - - if (!identical(outlier_thresh, NULL)){ + + if (!identical(outlier_thresh, NULL)) { # Flag outliers is_outlier <- elpdDif > outlier_thresh - index <- 1:length(y) + index <- seq_along(y) outlier_labs <- index[is_outlier] - - plot <- plot + ggplot2::annotate("text", - x = y[is_outlier], - y = elpdDif[outlier_labs], - label = outlier_labs, - size = 4 - ) - - + + plot <- plot + + ggplot2::annotate( + "text", + x = y[is_outlier], + y = elpdDif[outlier_labs], + label = outlier_labs, + size = 4 + ) } - - return(plot) - } + plot + } From e12b4562f6ba9763b2cb352d2d566e9717efe293 Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 13:06:01 -0700 Subject: [PATCH 11/20] Few renames and some small R things --- R/loo_difference_plot.R | 47 ++++++++++++++++++++++------------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 13af841d..c807a02e 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -8,7 +8,6 @@ #' @param psis_object_1,psis_object_2 If using loo version 2.0.0 or greater, #' an object returned by the `[loo::psis()]` function (or by the #' `[loo::loo()]` function with argument `save_psis` set to `TRUE`). -#' @param ... Currently unused. #' @param group A grouping variable (a vector or factor) the same length #' as `y`. Each value in group is interpreted as the group level pertaining #' to the corresponding value of `y`. If `FALSE`, ignored. @@ -24,30 +23,30 @@ #' arbitrary index. Plotting by index can be useful when categories have #' very different sample sizes. #' -#' #' @template return-ggplot #' @template reference-vis-paper #' +#' #' @examples #' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", #' "#F0E442", "#0072B2","#CC79A7") #' #' # Plot using groups from WHO -#' plot_loo_dif(factor(GM@data$super_region_name), loo3, loo2, +#' plot_loo_difference(factor(GM@data$super_region_name), loo3, loo2, #' group = GM@data$super_region_name, alpha = 0.5, #' jitter = c(0.45, 0.2) #' ) + #' xlab("Region") + scale_colour_manual(values=cbPalette) #' #' # Plot using groups identified with clustering -#' plot_loo_dif(factor(GM@data$cluster_region), loo3, loo2, +#' plot_loo_difference(factor(GM@data$cluster_region), loo3, loo2, #' group = GM@data$super_region_name, alpha = 0.5, #' jitter = c(0.45, 0.2) #' ) + #' xlab("Cluster Group") + scale_colour_manual(values=cbPalette) #' #' # Plot using an index variable to reduce crowding -#' plot_loo_dif(1:2980, loo3, loo2, group = GM@data$super_region_name, +#' plot_loo_difference(1:2980, loo3, loo2, group = GM@data$super_region_name, #' alpha = 0.5, sort_by_group = TRUE #' ) + #' xlab("Index") + scale_colour_manual(values=cbPalette) @@ -77,18 +76,19 @@ #' coLoo <- loo(iqFit, save_psis = TRUE) #' iqLoo <- loo(coFit, save_psis = TRUE) #' -#' plot_loo_dif(kidiq$mom_iq, coLoo, iqLoo, group = kidiq$mom_hs, +#' plot_loo_difference(kidiq$mom_iq, coLoo, iqLoo, group = kidiq$mom_hs, #' alpha = 0.5, jitter = c(0.1, 0.1) #' ) + #' ggplot2::geom_smooth() + #' ggplot2::xlab("IQ of Mother") + #' ggplot2::scale_colour_manual(values=cbPalette) -plot_loo_dif <- +#' +#' @export +plot_loo_difference <- function( y, psis_object_1, psis_object_2, - ..., group = NULL, outlier_thresh = NULL, size = 1, @@ -98,23 +98,24 @@ plot_loo_dif <- ) { if (!requireNamespace("ggplot2", quietly = TRUE)) { stop( - "Please install 'ggplot2' to use `plot_loo_dif()`.", + "Please install 'ggplot2' to use `plot_loo_difference()`.", call. = FALSE ) } - {} # Adding a 0 at the end lets users provide a single number as input. # In this case, only horizontal jitter is applied. - jitter <- c(jitter, 0) + if (length(jitter) == 1L) { + jitter <- c(jitter, 0) + } - elpdDif <- psis_object_1$pointwise[, "elpd_loo"] - + elpd_diff <- psis_object_1$pointwise[, "elpd_loo"] - psis_object_2$pointwise[, "elpd_loo"] if (sort_by_group) { if (identical(group, NULL) || !identical(y, seq_along(y))) { stop( - "ERROR: sort_by_group should only be used for grouping categorical + "sort_by_group should only be used for grouping categorical variables, then plotting them with an arbitrary index. You can create such an index using `1:length(data)`. " @@ -122,17 +123,19 @@ plot_loo_dif <- } ordering <- order(group) - elpdDif <- elpdDif[ordering] + elpd_diff <- elpd_diff[ordering] group <- group[ordering] } - plot <- ggplot2::ggplot(mapping = aes(y, elpdDif)) + + plot <- ggplot2::ggplot(mapping = ggplot2::aes(y, elpd_diff)) + ggplot2::geom_hline(yintercept = 0) + - ggplot2::xlab(ifelse(sort_by_group, "y", "Index")) + - ggplot2::ylab(expression(ELPD[i][1] - ELPD[i][2])) + - ggplot2::labs(color = "Groups") + ggplot2::labs( + x = if (sort_by_group) "y" else "Index", + y = expression(ELPD[i][1] - ELPD[i][2]), + color = "Groups" + ) - if (identical(group, FALSE)) { + if (is.null(group)) { # Don't color by group if no groups are passed plot <- plot + ggplot2::geom_jitter( @@ -145,7 +148,7 @@ plot_loo_dif <- # If group is passed, use color plot <- plot + ggplot2::geom_jitter( - aes(color = factor(group)), + ggplot2::aes(color = factor(group)), width = jitter[1], height = jitter[2], alpha = alpha, @@ -155,7 +158,7 @@ plot_loo_dif <- if (!identical(outlier_thresh, NULL)) { # Flag outliers - is_outlier <- elpdDif > outlier_thresh + is_outlier <- elpd_diff > outlier_thresh index <- seq_along(y) outlier_labs <- index[is_outlier] @@ -163,7 +166,7 @@ plot_loo_dif <- ggplot2::annotate( "text", x = y[is_outlier], - y = elpdDif[outlier_labs], + y = elpd_diff[outlier_labs], label = outlier_labs, size = 4 ) From 5c23d92e2d6bed475da901fb16c8b6a49e3fba13 Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 13:09:58 -0700 Subject: [PATCH 12/20] No vertical jitter, removed bayesplot roxygen template refs --- R/loo_difference_plot.R | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index c807a02e..52fda692 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -16,16 +16,14 @@ #' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control #' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` #' arguments of `[ggplot2::geom_jitter()]` to control the appearance of -#' points. `jitter` can be either a number or a vector of numbers. -#' Passing a single number will jitter variables along the x axis only, while -#' passing a vector will jitter along both axes. +#' points. `jitter` will jitter variables along the x axis only. #' @param sort_by_group Sort observations by `group`, then plot against an #' arbitrary index. Plotting by index can be useful when categories have #' very different sample sizes. #' -#' @template return-ggplot -#' @template reference-vis-paper +#' @template bayesvis-reference #' +#' @return A [ggplot2::ggplot()] object. #' #' @examples #' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", @@ -103,12 +101,6 @@ plot_loo_difference <- ) } - # Adding a 0 at the end lets users provide a single number as input. - # In this case, only horizontal jitter is applied. - if (length(jitter) == 1L) { - jitter <- c(jitter, 0) - } - elpd_diff <- psis_object_1$pointwise[, "elpd_loo"] - psis_object_2$pointwise[, "elpd_loo"] From 7932852e4389f5c9322385fe134e197b5552c4fa Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 13:29:04 -0700 Subject: [PATCH 13/20] Simpler example; removed outlier threshold; use checkmate --- R/loo_difference_plot.R | 152 +++++++++++++++------------------------- 1 file changed, 55 insertions(+), 97 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 52fda692..0a99183f 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -5,90 +5,42 @@ #' opportunities for model stacking or expansion. #' #' @param y A vector of observations. See Details. -#' @param psis_object_1,psis_object_2 If using loo version 2.0.0 or greater, -#' an object returned by the `[loo::psis()]` function (or by the -#' `[loo::loo()]` function with argument `save_psis` set to `TRUE`). +#' @param loo_1,loo_2 Objects returned by [loo()]. #' @param group A grouping variable (a vector or factor) the same length -#' as `y`. Each value in group is interpreted as the group level pertaining -#' to the corresponding value of `y`. If `FALSE`, ignored. -#' @param outlier_thresh Flag values when the difference in the ELPD exceeds -#' this threshold. Defaults to `NULL`, in which case no values are flagged. -#' @param size,alpha,jitter Passed to `[ggplot2::geom_point()]` to control -#' aesthetics. `size` and `alpha` are passed to to the `size` and `alpha` -#' arguments of `[ggplot2::geom_jitter()]` to control the appearance of -#' points. `jitter` will jitter variables along the x axis only. +#' as `y`. Each value in group is interpreted as the group level pertaining +#' to the corresponding value of `y`. +#' @param size,alpha Point size and opacity passed to [ggplot2::geom_jitter()]. +#' @param jitter Amount of horizontal jitter passed as the `width` argument +#' to [ggplot2::geom_jitter()]. #' @param sort_by_group Sort observations by `group`, then plot against an -#' arbitrary index. Plotting by index can be useful when categories have -#' very different sample sizes. +#' arbitrary index. Plotting by index can be useful when categories have +#' very different sample sizes. #' #' @template bayesvis-reference #' #' @return A [ggplot2::ggplot()] object. #' #' @examples -#' cbPalette <- c("#636363", "#E69F00", "#56B4E9", "#009E73", -#' "#F0E442", "#0072B2","#CC79A7") +#' log_lik <- example_loglik_matrix() #' -#' # Plot using groups from WHO -#' plot_loo_difference(factor(GM@data$super_region_name), loo3, loo2, -#' group = GM@data$super_region_name, alpha = 0.5, -#' jitter = c(0.45, 0.2) -#' ) + -#' xlab("Region") + scale_colour_manual(values=cbPalette) +#' shift <- seq(-0.5, 0.5, length.out = ncol(log_lik)) +#' log_lik_2 <- sweep(log_lik, 2, shift, FUN = "+") #' -#' # Plot using groups identified with clustering -#' plot_loo_difference(factor(GM@data$cluster_region), loo3, loo2, -#' group = GM@data$super_region_name, alpha = 0.5, -#' jitter = c(0.45, 0.2) -#' ) + -#' xlab("Cluster Group") + scale_colour_manual(values=cbPalette) +#' loo_1 <- loo(log_lik) +#' loo_2 <- loo(log_lik_2) #' -#' # Plot using an index variable to reduce crowding -#' plot_loo_difference(1:2980, loo3, loo2, group = GM@data$super_region_name, -#' alpha = 0.5, sort_by_group = TRUE -#' ) + -#' xlab("Index") + scale_colour_manual(values=cbPalette) -#' -#' -#' # Example using kid IQ Dataset with a continuous predictor -#' data(kidiq) -#' -#' t_prior <- student_t(df = 10, location = 0, scale = 0.5) -#' coef_prior <- student_t(df = 10, location = 0.5, scale = 0.25) -#' kidiq$kid_std <- (kidiq$kid_score - 100) / 15 -#' kidiq$mom_std <- (kidiq$mom_iq - 100) / 15 -#' kidiq$age_std <- (kidiq$mom_age - mean(kidiq$mom_age)) / sd(kidiq$mom_age) -#' kidiq$hs_cent <- kidiq$mom_hs - mean(kidiq$mom_hs) -#' -#' coFit <- stan_glm(kid_std ~ hs_cent, data = kidiq, -#' family = gaussian(), prior = coef_prior, -#' prior_intercept = t_prior, -#' seed = 1776, chains = 2 -#' ) -#' iqFit <- stan_glm(kid_std ~ mom_std + hs_cent, data = kidiq, -#' family = gaussian(), -#' prior = coef_prior, prior_intercept = t_prior, -#' seed = 1776, chains = 2 +#' plot_loo_difference( +#' seq_len(ncol(log_lik)), +#' loo_1, +#' loo_2 #' ) -#' -#' coLoo <- loo(iqFit, save_psis = TRUE) -#' iqLoo <- loo(coFit, save_psis = TRUE) -#' -#' plot_loo_difference(kidiq$mom_iq, coLoo, iqLoo, group = kidiq$mom_hs, -#' alpha = 0.5, jitter = c(0.1, 0.1) -#' ) + -#' ggplot2::geom_smooth() + -#' ggplot2::xlab("IQ of Mother") + -#' ggplot2::scale_colour_manual(values=cbPalette) -#' #' @export plot_loo_difference <- function( y, - psis_object_1, - psis_object_2, + loo_1, + loo_2, group = NULL, - outlier_thresh = NULL, size = 1, alpha = 1, jitter = 0, @@ -101,13 +53,37 @@ plot_loo_difference <- ) } - elpd_diff <- psis_object_1$pointwise[, "elpd_loo"] - - psis_object_2$pointwise[, "elpd_loo"] + checkmate::assert_class(loo_1, "loo") + checkmate::assert_class(loo_2, "loo") + + elpd_1 <- pointwise(loo_1, "elpd_loo") + elpd_2 <- pointwise(loo_2, "elpd_loo") + + checkmate::assert_true(length(elpd_1) == length(elpd_2)) + + checkmate::assert_vector( + y, + len = length(elpd_1) + ) + + if (!is.null(group)) { + checkmate::assert_vector( + group, + len = length(y) + ) + } + + checkmate::assert_number( + jitter, + lower = 0 + ) + + elpd_diff <- elpd_1 - elpd_2 if (sort_by_group) { - if (identical(group, NULL) || !identical(y, seq_along(y))) { + if (is.null(group) || !identical(y, seq_along(y))) { stop( - "sort_by_group should only be used for grouping categorical + "`sort_by_group` should only be used for grouping categorical variables, then plotting them with an arbitrary index. You can create such an index using `1:length(data)`. " @@ -122,46 +98,28 @@ plot_loo_difference <- plot <- ggplot2::ggplot(mapping = ggplot2::aes(y, elpd_diff)) + ggplot2::geom_hline(yintercept = 0) + ggplot2::labs( - x = if (sort_by_group) "y" else "Index", - y = expression(ELPD[i][1] - ELPD[i][2]), - color = "Groups" + x = if (sort_by_group) "Index" else "y", + y = expression(ELPD[i][1] - ELPD[i][2]) ) if (is.null(group)) { - # Don't color by group if no groups are passed plot <- plot + ggplot2::geom_jitter( - width = jitter[1], - height = jitter[2], + width = jitter, + height = 0, alpha = alpha, size = size ) } else { - # If group is passed, use color plot <- plot + ggplot2::geom_jitter( ggplot2::aes(color = factor(group)), - width = jitter[1], - height = jitter[2], + width = jitter, + height = 0, alpha = alpha, size = size - ) - } - - if (!identical(outlier_thresh, NULL)) { - # Flag outliers - is_outlier <- elpd_diff > outlier_thresh - index <- seq_along(y) - outlier_labs <- index[is_outlier] - - plot <- plot + - ggplot2::annotate( - "text", - x = y[is_outlier], - y = elpd_diff[outlier_labs], - label = outlier_labs, - size = 4 - ) + ) + + ggplot2::labs(color = "Groups") } plot From a22b8704d9cba1b35504aa0931cfb23380527053 Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 13:30:56 -0700 Subject: [PATCH 14/20] Small language changes to sort_by_group --- R/loo_difference_plot.R | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 0a99183f..71a44192 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -1,7 +1,7 @@ #' Compare models across domains #' #' The LOO difference plot shows how the ELPD of two different models -#' changes when a predictor is varied. This can is useful for identifying +#' changes when a predictor is varied. This can be useful for identifying #' opportunities for model stacking or expansion. #' #' @param y A vector of observations. See Details. @@ -12,9 +12,10 @@ #' @param size,alpha Point size and opacity passed to [ggplot2::geom_jitter()]. #' @param jitter Amount of horizontal jitter passed as the `width` argument #' to [ggplot2::geom_jitter()]. -#' @param sort_by_group Sort observations by `group`, then plot against an -#' arbitrary index. Plotting by index can be useful when categories have -#' very different sample sizes. +#' @param sort_by_group If `TRUE`, observations are ordered by `group` +#' and the x-axis is replaced by a sequential index. The supplied `y` values +#' are therefore not used as x coordinates. Plotting by index can be useful +#' when categories have very different sample sizes. #' #' @template bayesvis-reference #' @@ -81,18 +82,17 @@ plot_loo_difference <- elpd_diff <- elpd_1 - elpd_2 if (sort_by_group) { - if (is.null(group) || !identical(y, seq_along(y))) { + if (is.null(group)) { stop( - "`sort_by_group` should only be used for grouping categorical - variables, then plotting them with an arbitrary index. You can - create such an index using `1:length(data)`. - " + "`group` must be supplied when `sort_by_group = TRUE`.", + call. = FALSE ) } ordering <- order(group) elpd_diff <- elpd_diff[ordering] group <- group[ordering] + y <- seq_along(elpd_diff) } plot <- ggplot2::ggplot(mapping = ggplot2::aes(y, elpd_diff)) + From 0d5456d1e4179ef637c7c8b94f26c7e8effd658e Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 13:36:08 -0700 Subject: [PATCH 15/20] Pass data to ggplot instead of capturing --- R/loo_difference_plot.R | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 71a44192..4dcf4d1b 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -54,13 +54,19 @@ plot_loo_difference <- ) } + checkmate::assert_flag(sort_by_group) checkmate::assert_class(loo_1, "loo") checkmate::assert_class(loo_2, "loo") elpd_1 <- pointwise(loo_1, "elpd_loo") elpd_2 <- pointwise(loo_2, "elpd_loo") - checkmate::assert_true(length(elpd_1) == length(elpd_2)) + if (length(elpd_1) != length(elpd_2)) { + stop( + "`loo_1` and `loo_2` must contain the same number of observations.", + call. = FALSE + ) + } checkmate::assert_vector( y, @@ -74,11 +80,6 @@ plot_loo_difference <- ) } - checkmate::assert_number( - jitter, - lower = 0 - ) - elpd_diff <- elpd_1 - elpd_2 if (sort_by_group) { @@ -95,7 +96,18 @@ plot_loo_difference <- y <- seq_along(elpd_diff) } - plot <- ggplot2::ggplot(mapping = ggplot2::aes(y, elpd_diff)) + + plot_data <- data.frame( + y = y, + elpd_diff = elpd_diff + ) + if (!is.null(group)) { + plot_data$group <- factor(group) + } + + plot <- ggplot2::ggplot( + data = plot_data, + mapping = ggplot2::aes(x = y, y = elpd_diff) + ) + ggplot2::geom_hline(yintercept = 0) + ggplot2::labs( x = if (sort_by_group) "Index" else "y", @@ -113,7 +125,7 @@ plot_loo_difference <- } else { plot <- plot + ggplot2::geom_jitter( - ggplot2::aes(color = factor(group)), + ggplot2::aes(color = group), width = jitter, height = 0, alpha = alpha, From ec754aca333c0dfff5027e178f34757a342ea412 Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 14:36:04 -0700 Subject: [PATCH 16/20] Added back thresholded labelling --- R/loo_difference_plot.R | 109 ++++++++++++++++++++++++++++++++-------- 1 file changed, 88 insertions(+), 21 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index 4dcf4d1b..b8f6fbee 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -2,13 +2,14 @@ #' #' The LOO difference plot shows how the ELPD of two different models #' changes when a predictor is varied. This can be useful for identifying -#' opportunities for model stacking or expansion. +#' opportunities for model stacking or expansion. Pointwise differences +#' are computed as `loo_1 - loo_2`, so positive values indicate better +#' predictive performance for `loo_1`. #' -#' @param y A vector of observations. See Details. +#' @param y A vector of observations. #' @param loo_1,loo_2 Objects returned by [loo()]. -#' @param group A grouping variable (a vector or factor) the same length -#' as `y`. Each value in group is interpreted as the group level pertaining -#' to the corresponding value of `y`. +#' @param group An optional grouping variable with the same length as `y`. +#' Points are colored according to group membership. #' @param size,alpha Point size and opacity passed to [ggplot2::geom_jitter()]. #' @param jitter Amount of horizontal jitter passed as the `width` argument #' to [ggplot2::geom_jitter()]. @@ -16,14 +17,21 @@ #' and the x-axis is replaced by a sequential index. The supplied `y` values #' are therefore not used as x coordinates. Plotting by index can be useful #' when categories have very different sample sizes. +#' @param label_threshold Optional nonnegative threshold for labeling +#' observations. Observations for which the absolute pointwise ELPD +#' difference exceeds this value are labeled. If `NULL`, no observations +#' are labeled. +#' @param labels Optional vector of labels with the same length as `y`, used +#' for observations selected by `label_threshold`. If `NULL`, observation +#' indices are used. #' #' @template bayesvis-reference #' #' @return A [ggplot2::ggplot()] object. #' #' @examples +#' # Artificial example #' log_lik <- example_loglik_matrix() -#' #' shift <- seq(-0.5, 0.5, length.out = ncol(log_lik)) #' log_lik_2 <- sweep(log_lik, 2, shift, FUN = "+") #' @@ -35,6 +43,26 @@ #' loo_1, #' loo_2 #' ) +#' +#' # Label observations with large pointwise ELPD differences +#' plot_loo_difference( +#' seq_len(ncol(log_lik)), +#' loo_1, +#' loo_2, +#' label_threshold = 0.3 +#' ) +#' +#' # Create interspersed groups, then sort them in the plot +#' group <- rep(c("A", "A", "A", "B"), length.out = ncol(log_lik)) +#' +#' plot_loo_difference( +#' seq_len(ncol(log_lik)), +#' loo_1, +#' loo_2, +#' group = group, +#' sort_by_group = TRUE +#' ) +#' #' @export plot_loo_difference <- function( @@ -45,7 +73,9 @@ plot_loo_difference <- size = 1, alpha = 1, jitter = 0, - sort_by_group = FALSE + sort_by_group = FALSE, + label_threshold = NULL, + labels = NULL ) { if (!requireNamespace("ggplot2", quietly = TRUE)) { stop( @@ -55,31 +85,43 @@ plot_loo_difference <- } checkmate::assert_flag(sort_by_group) - checkmate::assert_class(loo_1, "loo") - checkmate::assert_class(loo_2, "loo") + loo_compare_checks(nlist(loo_1, loo_2)) elpd_1 <- pointwise(loo_1, "elpd_loo") elpd_2 <- pointwise(loo_2, "elpd_loo") - if (length(elpd_1) != length(elpd_2)) { - stop( - "`loo_1` and `loo_2` must contain the same number of observations.", - call. = FALSE - ) - } - - checkmate::assert_vector( + checkmate::assert_atomic_vector( y, len = length(elpd_1) ) if (!is.null(group)) { - checkmate::assert_vector( + checkmate::assert_atomic_vector( group, + len = length(y), + any.missing = FALSE + ) + } + + if (!is.null(label_threshold)) { + checkmate::assert_number( + label_threshold, + lower = 0, + finite = TRUE + ) + } + + if (!is.null(labels)) { + checkmate::assert_atomic_vector( + labels, len = length(y) ) } + if (!is.null(label_threshold) && is.null(labels)) { + labels <- seq_along(y) + } + elpd_diff <- elpd_1 - elpd_2 if (sort_by_group) { @@ -93,6 +135,11 @@ plot_loo_difference <- ordering <- order(group) elpd_diff <- elpd_diff[ordering] group <- group[ordering] + + if (!is.null(labels)) { + labels <- labels[ordering] + } + y <- seq_along(elpd_diff) } @@ -100,18 +147,23 @@ plot_loo_difference <- y = y, elpd_diff = elpd_diff ) + if (!is.null(group)) { plot_data$group <- factor(group) } + if (!is.null(labels)) { + plot_data$labels <- labels + } + plot <- ggplot2::ggplot( data = plot_data, mapping = ggplot2::aes(x = y, y = elpd_diff) ) + ggplot2::geom_hline(yintercept = 0) + ggplot2::labs( - x = if (sort_by_group) "Index" else "y", - y = expression(ELPD[i][1] - ELPD[i][2]) + x = if (sort_by_group) "Index" else NULL, + y = "Pointwise ELPD Difference (loo_1 - loo_2)" ) if (is.null(group)) { @@ -131,7 +183,22 @@ plot_loo_difference <- alpha = alpha, size = size ) + - ggplot2::labs(color = "Groups") + ggplot2::labs(color = "Group") + } + + if (!is.null(label_threshold)) { + label_data <- plot_data[ + abs(plot_data$elpd_diff) > label_threshold, + , + drop = FALSE + ] + + plot <- plot + + ggplot2::geom_text( + data = label_data, + ggplot2::aes(label = labels), + vjust = -0.5 + ) } plot From a49df8a00a1b38207b76973a19b73784576412a7 Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sat, 5 Sep 2026 14:53:17 -0700 Subject: [PATCH 17/20] Small changes --- R/loo_difference_plot.R | 49 +++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index b8f6fbee..be192a3c 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -10,13 +10,14 @@ #' @param loo_1,loo_2 Objects returned by [loo()]. #' @param group An optional grouping variable with the same length as `y`. #' Points are colored according to group membership. -#' @param size,alpha Point size and opacity passed to [ggplot2::geom_jitter()]. -#' @param jitter Amount of horizontal jitter passed as the `width` argument -#' to [ggplot2::geom_jitter()]. +#' @param size,alpha Point size and opacity passed to [ggplot2::geom_point()]. +#' @param jitter Amount of horizontal jitter passed to +#' [ggplot2::position_jitter()]. #' @param sort_by_group If `TRUE`, observations are ordered by `group` #' and the x-axis is replaced by a sequential index. The supplied `y` values #' are therefore not used as x coordinates. Plotting by index can be useful -#' when categories have very different sample sizes. +#' when categories have very different sample sizes. To control the group +#' order, supply `group` as a factor with levels in the desired order. #' @param label_threshold Optional nonnegative threshold for labeling #' observations. Observations for which the absolute pointwise ELPD #' difference exceeds this value are labeled. If `NULL`, no observations @@ -87,12 +88,12 @@ plot_loo_difference <- checkmate::assert_flag(sort_by_group) loo_compare_checks(nlist(loo_1, loo_2)) - elpd_1 <- pointwise(loo_1, "elpd_loo") - elpd_2 <- pointwise(loo_2, "elpd_loo") + # elpd_diffs(a, b) computes b - a + elpd_diff <- elpd_diffs(loo_2, loo_1) checkmate::assert_atomic_vector( y, - len = length(elpd_1) + len = length(elpd_diff) ) if (!is.null(group)) { @@ -122,8 +123,6 @@ plot_loo_difference <- labels <- seq_along(y) } - elpd_diff <- elpd_1 - elpd_2 - if (sort_by_group) { if (is.null(group)) { stop( @@ -152,10 +151,20 @@ plot_loo_difference <- plot_data$group <- factor(group) } - if (!is.null(labels)) { - plot_data$labels <- labels + if (!is.null(label_threshold)) { + plot_data$labels <- ifelse( + abs(plot_data$elpd_diff) > label_threshold, + as.character(labels), + "" + ) } + jitter_position <- ggplot2::position_jitter( + width = jitter, + height = 0, + seed = 1 + ) + plot <- ggplot2::ggplot( data = plot_data, mapping = ggplot2::aes(x = y, y = elpd_diff) @@ -168,18 +177,16 @@ plot_loo_difference <- if (is.null(group)) { plot <- plot + - ggplot2::geom_jitter( - width = jitter, - height = 0, + ggplot2::geom_point( + position = jitter_position, alpha = alpha, size = size ) } else { plot <- plot + - ggplot2::geom_jitter( + ggplot2::geom_point( ggplot2::aes(color = group), - width = jitter, - height = 0, + position = jitter_position, alpha = alpha, size = size ) + @@ -187,16 +194,10 @@ plot_loo_difference <- } if (!is.null(label_threshold)) { - label_data <- plot_data[ - abs(plot_data$elpd_diff) > label_threshold, - , - drop = FALSE - ] - plot <- plot + ggplot2::geom_text( - data = label_data, ggplot2::aes(label = labels), + position = jitter_position, vjust = -0.5 ) } From 74f4aa6fe9947060b27e23d1e336891193be2a0f Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sun, 6 Sep 2026 13:03:29 -0700 Subject: [PATCH 18/20] Added non-snapshot tests --- NAMESPACE | 1 + man/plot_loo_difference.Rd | 100 +++++++++++++++++++ tests/testthat/test-loo_difference_plot.R | 114 ++++++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 man/plot_loo_difference.Rd create mode 100644 tests/testthat/test-loo_difference_plot.R diff --git a/NAMESPACE b/NAMESPACE index 3405d737..0429f1b5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -145,6 +145,7 @@ export(pareto_k_ids) export(pareto_k_influence_values) export(pareto_k_table) export(pareto_k_values) +export(plot_loo_difference) export(pointwise) export(print_dims) export(pseudobma_weights) diff --git a/man/plot_loo_difference.Rd b/man/plot_loo_difference.Rd new file mode 100644 index 00000000..e628913c --- /dev/null +++ b/man/plot_loo_difference.Rd @@ -0,0 +1,100 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/loo_difference_plot.R +\name{plot_loo_difference} +\alias{plot_loo_difference} +\title{Compare models across domains} +\usage{ +plot_loo_difference( + y, + loo_1, + loo_2, + group = NULL, + size = 1, + alpha = 1, + jitter = 0, + sort_by_group = FALSE, + label_threshold = NULL, + labels = NULL +) +} +\arguments{ +\item{y}{A vector of observations.} + +\item{loo_1, loo_2}{Objects returned by \code{\link[=loo]{loo()}}.} + +\item{group}{An optional grouping variable with the same length as \code{y}. +Points are colored according to group membership.} + +\item{size, alpha}{Point size and opacity passed to \code{\link[ggplot2:geom_point]{ggplot2::geom_point()}}.} + +\item{jitter}{Amount of horizontal jitter passed to +\code{\link[ggplot2:position_jitter]{ggplot2::position_jitter()}}.} + +\item{sort_by_group}{If \code{TRUE}, observations are ordered by \code{group} +and the x-axis is replaced by a sequential index. The supplied \code{y} values +are therefore not used as x coordinates. Plotting by index can be useful +when categories have very different sample sizes. To control the group +order, supply \code{group} as a factor with levels in the desired order.} + +\item{label_threshold}{Optional nonnegative threshold for labeling +observations. Observations for which the absolute pointwise ELPD +difference exceeds this value are labeled. If \code{NULL}, no observations +are labeled.} + +\item{labels}{Optional vector of labels with the same length as \code{y}, used +for observations selected by \code{label_threshold}. If \code{NULL}, observation +indices are used.} +} +\value{ +A \code{\link[ggplot2:ggplot]{ggplot2::ggplot()}} object. +} +\description{ +The LOO difference plot shows how the ELPD of two different models +changes when a predictor is varied. This can be useful for identifying +opportunities for model stacking or expansion. Pointwise differences +are computed as \code{loo_1 - loo_2}, so positive values indicate better +predictive performance for \code{loo_1}. +} +\examples{ +# Artificial example +log_lik <- example_loglik_matrix() +shift <- seq(-0.5, 0.5, length.out = ncol(log_lik)) +log_lik_2 <- sweep(log_lik, 2, shift, FUN = "+") + +loo_1 <- loo(log_lik) +loo_2 <- loo(log_lik_2) + +plot_loo_difference( + seq_len(ncol(log_lik)), + loo_1, + loo_2 +) + +# Label observations with large pointwise ELPD differences +plot_loo_difference( + seq_len(ncol(log_lik)), + loo_1, + loo_2, + label_threshold = 0.3 +) + +# Create interspersed groups, then sort them in the plot +group <- rep(c("A", "A", "A", "B"), length.out = ncol(log_lik)) + +plot_loo_difference( + seq_len(ncol(log_lik)), + loo_1, + loo_2, + group = group, + sort_by_group = TRUE +) + +} +\references{ +Gabry, J. , Simpson, D. , Vehtari, A. , Betancourt, M. and +Gelman, A. (2019), Visualization in Bayesian workflow. +\emph{J. R. Stat. Soc. A}, 182: 389-402. doi:10.1111/rssa.12378 +(\href{https://rss.onlinelibrary.wiley.com/doi/full/10.1111/rssa.12378}{journal version}, +\href{https://arxiv.org/abs/1709.01449}{preprint arXiv:1709.01449}, +\href{https://github.com/jgabry/bayes-vis-paper}{code on GitHub}) +} diff --git a/tests/testthat/test-loo_difference_plot.R b/tests/testthat/test-loo_difference_plot.R new file mode 100644 index 00000000..59e8d1ac --- /dev/null +++ b/tests/testthat/test-loo_difference_plot.R @@ -0,0 +1,114 @@ +skip_if_not_installed("ggplot2") + +log_lik <- example_loglik_matrix() +shift <- seq(-0.5, 0.5, length.out = ncol(log_lik)) +log_lik_2 <- sweep(log_lik, 2, shift, FUN = "+") + +loo_1 <- loo(log_lik) +loo_2 <- loo(log_lik_2) +y <- seq_len(ncol(log_lik)) + +test_that("plot_loo_difference returns pointwise ELPD differences", { + p <- plot_loo_difference(y, loo_1, loo_2) + + expect_s3_class(p, "ggplot") + expect_equal(p$data$y, y) + expect_equal(p$data$elpd_diff, -shift) +}) + +test_that("plot_loo_difference sorts observations and labels by group", { + group <- factor( + rep(c("A", "B"), length.out = length(y)), + levels = c("B", "A") + ) + labels <- paste0("obs", y) + ordering <- order(group) + + p <- plot_loo_difference( + y, + loo_1, + loo_2, + group = group, + sort_by_group = TRUE, + label_threshold = 0.3, + labels = labels + ) + + expect_equal(p$data$y, seq_along(y)) + expect_equal(p$data$elpd_diff, -shift[ordering]) + expect_equal( + as.character(p$data$group), + as.character(group[ordering]) + ) + expect_equal( + p$data$labels, + ifelse( + abs(shift[ordering]) > 0.3, + labels[ordering], + "" + ) + ) +}) + +test_that("plot_loo_difference uses observation indices as default labels", { + p <- plot_loo_difference( + y, + loo_1, + loo_2, + label_threshold = 0.3 + ) + + expect_equal( + p$data$labels, + ifelse( + abs(shift) > 0.3, + as.character(seq_along(y)), + "" + ) + ) +}) + +test_that("plot_loo_difference checks observation-level arguments", { + expect_error( + plot_loo_difference(y[-1], loo_1, loo_2) + ) + + expect_error( + plot_loo_difference( + y, + loo_1, + loo_2, + group = rep("A", length(y) - 1) + ) + ) + + expect_error( + plot_loo_difference( + y, + loo_1, + loo_2, + label_threshold = -1 + ) + ) + + expect_error( + plot_loo_difference( + y, + loo_1, + loo_2, + label_threshold = 0.3, + labels = y[-1] + ) + ) + + expect_error( + plot_loo_difference( + y, + loo_1, + loo_2, + sort_by_group = TRUE + ), + "`group` must be supplied", + fixed = TRUE + ) +}) From 2fb210d8a014a7394f58c1937a0b88c48bb0d73c Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Sun, 6 Sep 2026 13:12:23 -0700 Subject: [PATCH 19/20] Added func to model comparision section --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index 0a216a02..9acc5235 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -95,6 +95,7 @@ reference: - loo_model_weights - stacking_weights - pseudobma_weights + - plot_loo_difference - title: Helper functions for K-fold CV contents: - kfold_split_random From 01370605e37fd289162ac8b5540a6bdc5c2af955 Mon Sep 17 00:00:00 2001 From: VisruthSK Date: Tue, 8 Sep 2026 16:40:05 -0700 Subject: [PATCH 20/20] labels require label_threshold --- R/loo_difference_plot.R | 21 ++++++++++++++------- tests/testthat/test-loo_difference_plot.R | 11 +++++++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/R/loo_difference_plot.R b/R/loo_difference_plot.R index be192a3c..dc60f3f5 100644 --- a/R/loo_difference_plot.R +++ b/R/loo_difference_plot.R @@ -104,6 +104,20 @@ plot_loo_difference <- ) } + if (!is.null(labels)) { + checkmate::assert_atomic_vector( + labels, + len = length(y) + ) + + if (is.null(label_threshold)) { + stop( + "`label_threshold` must be supplied when `labels` is supplied.", + call. = FALSE + ) + } + } + if (!is.null(label_threshold)) { checkmate::assert_number( label_threshold, @@ -112,13 +126,6 @@ plot_loo_difference <- ) } - if (!is.null(labels)) { - checkmate::assert_atomic_vector( - labels, - len = length(y) - ) - } - if (!is.null(label_threshold) && is.null(labels)) { labels <- seq_along(y) } diff --git a/tests/testthat/test-loo_difference_plot.R b/tests/testthat/test-loo_difference_plot.R index 59e8d1ac..7e294f55 100644 --- a/tests/testthat/test-loo_difference_plot.R +++ b/tests/testthat/test-loo_difference_plot.R @@ -101,6 +101,17 @@ test_that("plot_loo_difference checks observation-level arguments", { ) ) + expect_error( + plot_loo_difference( + y, + loo_1, + loo_2, + labels = y + ), + "`label_threshold` must be supplied when `labels` is supplied.", + fixed = TRUE + ) + expect_error( plot_loo_difference( y,