params <- list(EVAL = TRUE) ## ----setup, message=FALSE, warning=FALSE-------------------------------------- library(knitr) library(dplyr) library(tidyr) library(ggplot2) library(purrr) library(broom) library(gganimate) library(multiverse) ## ----include=FALSE------------------------------------------------------------ M = multiverse() ## ----chunk-setup, include=FALSE----------------------------------------------- opts_chunk$set( echo = TRUE, eval = if (isTRUE(exists("params"))) params$EVAL else FALSE, fig.width = 6, fig.height = 4 ) ## ----data--------------------------------------------------------------------- data("vis_correlation") df <- vis_correlation |> group_by( vis, rbase, approach, sign ) |> select(-c(condition, visandsign, participant)) |> summarise( jnd = list(jnd) ) |> mutate( median = map_dbl(jnd, ~median(.x)), mads = map_dbl(jnd, ~mad(.x, constant = 1)), jnd_limit = 3 * mads ) |> ungroup() ## ----data-proc-errorbars------------------------------------------------------ df.error_bar <- df |> unnest( cols = c(jnd) ) |> filter( abs(jnd - median) <= jnd_limit ) |> group_by( vis, rbase, approach, sign ) |> summarise( jnd = list(jnd) ) |> mutate( sd = map_dbl(jnd, ~sd(.x)/sqrt(length(.x))), mean = map_dbl(jnd, ~mean(.x)) ) ## ----fig.height = 8, fig.width = 8-------------------------------------------- df.error_bar |> ggplot() + geom_errorbar(aes(x = rbase, ymin = (mean - sd), ymax = mean + sd, color = approach), width = 0.025) + geom_point( aes(x = rbase, y = mean, color = approach )) + geom_hline( yintercept = 0.45, linetype = "dashed", alpha = 0.25) + geom_abline( slope = -1, intercept = 1, linetype = "dashed", alpha = 0.25) + facet_wrap(vis ~ sign, ncol = 4) + theme_minimal() ## ----data-proc-regression-1--------------------------------------------------- filter_outliers <- function(x, median, limit) { return( x[abs(x - median) <= limit] ) } df.corr <- df |> # the following visualizations were excluded from the analysis filter( !((vis == "donut" | vis == "stackedarea" | vis == "stackedline" | vis == "stackedbar") & (sign == 1)) & !((vis == "radar" | vis == "line") & (sign == -1)) ) |> mutate( jnd = pmap(list(jnd, median, jnd_limit), filter_outliers) ) |> mutate( mean_jnd = map_dbl(jnd, mean), n = map_dbl(jnd, length) ) ## ----data-proc-regression-2--------------------------------------------------- rbase_adj_coef <- df.corr |> group_by( vis, sign, rbase ) |> summarise( jnd = list(jnd), mean_jnd = list(mean_jnd), n = list(n), approach = list(approach) ) |> mutate( rbase_adj_coef = map2_dbl(mean_jnd, n, ~ sum(.x * .y) / sum(.y)) ) |> mutate( rbase_adj_coef = map2_dbl(mean_jnd, n, ~ sum(.x * .y) / sum(.y)) ) |> unnest(cols = c(jnd, mean_jnd, approach) ) |> magrittr::extract2("rbase_adj_coef") df.corr_fitted <- df.corr |> tibble::add_column( rbase_adj_coef = rbase_adj_coef ) |> mutate( multiplier = ifelse(approach == "above", 1, -1), rbase = rbase + 0.5 * multiplier * rbase_adj_coef ) |> group_by( vis, sign ) |> summarise( mean_jnd = list(mean_jnd), rbase = list(rbase), .groups = "drop_last" ) |> mutate( fit = map2(rbase, mean_jnd, ~as.list(coef(lm(.y ~ .x)))), sign = factor(ifelse(sign == 1, "positive", "negative"), levels = c("positive", "negative")) ) |> unnest_wider( col = c(fit) ) |> rename( intercept = `(Intercept)`, coef = .x ) |> mutate( x = 0, xend = 1, y = intercept + coef * x, yend = intercept + coef * xend ) ## ----regression-vis, fig.width = 8, fig.height = 5---------------------------- df.corr_fitted |> ggplot() + geom_segment( aes( x=x, y=y, xend=xend, yend=yend, color = vis, linetype = sign) ) + coord_cartesian( xlim = c(0, 1), ylim = c(0, 0.6) ) + scale_y_continuous( breaks = seq(0, 1, by = 0.1)) + scale_x_continuous( breaks = seq(0, 1, by = 0.2)) + theme_minimal() ## ----------------------------------------------------------------------------- #data_original <- read.csv("master-alt.csv") n = 20 M = multiverse() inside(M, { set.seed(branch(seed, .options = 1:n)) data.i = vis_correlation |> group_by(rbase, sign, vis, approach) |> summarise( jnd = list(jnd), .groups = "drop_last" ) |> mutate( sample_size = map_dbl(jnd, length ), bootstrap_samples = map2(jnd, sample_size, ~ sample( .x, size = .y, replace = TRUE)) ) |> select(-jnd) |> unnest( cols = c(bootstrap_samples)) |> rename( jnd = bootstrap_samples ) }) ## ----------------------------------------------------------------------------- inside(M, { df.i <- data.i |> group_by( vis, rbase, approach, sign ) |> summarise( jnd = list(jnd), .groups = "drop_last" ) |> mutate( median = map_dbl(jnd, ~median(.x)), mads = map_dbl(jnd, ~mad(.x, constant = 1)), jnd_limit = 3 * mads ) |> ungroup() df.error_bar.i <- df.i |> unnest( cols = c(jnd) ) |> filter( abs(jnd - median) <= jnd_limit ) |> group_by( vis, rbase, approach, sign ) |> summarise( jnd = list(jnd), .groups = "drop_last" ) |> mutate( sd = map_dbl(jnd, ~sd(.x)/sqrt(length(.x))), mean = map_dbl(jnd, ~mean(.x)) ) df.corr.i <- df.i |> # the following visualizations were excluded from the analysis filter( !((vis == "donut" | vis == "stackedarea" | vis == "stackedline" | vis == "stackedbar") & (sign == 1)) & !((vis == "radar" | vis == "line") & (sign == -1)) ) |> mutate( jnd = pmap(list(jnd, median, jnd_limit), filter_outliers) ) |> mutate( mean_jnd = map_dbl(jnd, mean), n = map_dbl(jnd, length) ) rbase_adj_coef.i <- df.corr.i |> group_by( vis, sign, rbase ) |> summarise( jnd = list(jnd), mean_jnd = list(mean_jnd), n = list(n), approach = list(approach), .groups = "drop_last" ) |> mutate( rbase_adj_coef = map2_dbl(mean_jnd, n, ~ sum(.x * .y) / sum(.y)) ) |> mutate( rbase_adj_coef = map2_dbl(mean_jnd, n, ~ sum(.x * .y) / sum(.y)) ) |> unnest(cols = c(jnd, mean_jnd, approach) ) |> magrittr::extract2("rbase_adj_coef") df.corr_fitted.i <- df.corr.i |> tibble::add_column( rbase_adj_coef = rbase_adj_coef ) |> mutate( multiplier = ifelse(approach == "above", 1, -1), rbase = rbase + 0.5 * multiplier * rbase_adj_coef ) |> group_by( vis, sign ) |> summarise( mean_jnd = list(mean_jnd), rbase = list(rbase), .groups = "drop_last" ) |> mutate( fit = map2(rbase, mean_jnd, ~as.list(coef(lm(.y ~ .x)))), sign = factor(ifelse(sign == 1, "positive", "negative"), levels = c("positive", "negative")) ) |> unnest_wider( col = c(fit) ) |> rename( intercept = `(Intercept)`, coef = .x ) |> mutate( x = 0, xend = 1, y = intercept + coef * x, yend = intercept + coef * xend ) }) ## ----------------------------------------------------------------------------- execute_multiverse(M) ## ----vis-errorbars-above, fig.width = 8, fig.height = 5----------------------- p.above <- expand(M) |> mutate(data = map(.results, "df.error_bar.i")) |> unnest( cols = c(data) ) |> filter( sign == 1 ) |> ggplot() + geom_errorbar(aes(x = rbase, ymin = (mean - sd), ymax = mean + sd, color = approach), width = 0.025) + geom_point( aes(x = rbase, y = mean, color = approach )) + geom_hline( yintercept = 0.45, linetype = "dashed", alpha = 0.25) + geom_abline( slope = -1, intercept = 1, linetype = "dashed", alpha = 0.25) + facet_wrap( ~ vis, nrow = 2) + theme_minimal() + transition_manual(.universe) animate(p.above, nframes = n, fps = 3, width = 840, height = 320, units = "px") ## ----vis-errorbars-below------------------------------------------------------ p.below <- expand(M) |> mutate(data = map(.results, "df.error_bar.i")) |> unnest( cols = c(data) ) |> filter( sign == -1 ) |> ggplot() + geom_errorbar(aes(x = rbase, ymin = (mean - sd), ymax = mean + sd, color = approach), width = 0.025) + geom_point( aes(x = rbase, y = mean, color = approach )) + geom_hline( yintercept = 0.45, linetype = "dashed", alpha = 0.25) + geom_abline( slope = -1, intercept = 1, linetype = "dashed", alpha = 0.25) + facet_wrap( ~ vis, nrow = 2) + theme_minimal() + transition_manual(.universe) animate(p.below, nframes = n, fps = 3, width = 840, height = 320, units = "px") ## ----vis-regression-lines----------------------------------------------------- p.fitted_lines <- expand(M) |> mutate(data = map(.results, "df.corr_fitted.i")) |> unnest( cols = c(data) ) |> ggplot() + geom_segment( aes( x=x, y=y, xend=xend, yend=yend, color = vis, linetype = sign) ) + coord_cartesian( xlim = c(0, 1), ylim = c(0, 0.6) ) + scale_y_continuous( breaks = seq(0, 1, by = 0.1)) + scale_x_continuous( breaks = seq(0, 1, by = 0.2)) + theme_minimal() + transition_manual(.universe) animate(p.fitted_lines, nframes = n, fps = 3, width = 840, height = 480, units = "px")