#' Gets the ... names #' #' Used to get the -year #' #' @param ... arguments #' #' @return a vector of the names of ... #' #' @examples #' x <- 1:10 #' y <- 1 #' get_quos_names(-x) #' get_quos_names(x:y) get_quos_names <- function(...) { q <- quos(...) sapply(q, function(i) as.character(i[[2]])) } #' Parses a simple vector so that it looks like its input #' #' @param x a vector #' #' @return a string #' #' @examples #' dput_parser("x") #' dput_parser(c("x", "y")) dput_parser <- function(x) { ifelse(length(x) == 1, sprintf("'%s'", x), paste0("c(", paste(sprintf("'%s'", x), collapse = ", "), ")")) } #' Adds color to processed tidy data #' #' @param x a processed data-frame as outputted by process_long or process_wide #' @param key_values the unique key-values #' @param color_fun the color function #' @param color_header the color for the header #' @param ... not used #' #' @return a data-frame with the colors #' #' @examples #' NULL add_color_tidyr <- function(x, key_values, color_fun = scales::brewer_pal(type = "qual", "Set1"), color_header = "#737373", color_id = "#d0d0d0") { color_dict <- color_fun(3) names(color_dict) <- c("id", "key", "value") x %>% mutate(.color = ifelse(.id_map == ".header" & !.val %in% key_values, color_header, color_dict[.type])) } #' Processes a wide dataframe and converts it into a dataset that can be plotted #' #' @param x a wide data frame #' @param ids a vector of id-variables that are already in the tidy-format #' @param key a vector of key-variables #' @param color_id the color for the id-body #' @param ... #' #' @return #' #' @examples #' wide <- data_frame( #' year = 2010:2011, #' Alice = c(105, 110), #' Bob = c(100, 97), #' Charlie = c(90, 95) #' ) #' process_wide(wide, ids = "year", key = "person") #' process_wide(wide, ids = "year", key = "person") %>% static_plot process_wide <- function(x, ids, key, color_id = "lightgray", ...) { if (!all(ids %in% names(x))) stop("all ids must be in x") nr <- nrow(x) nc <- ncol(x) key_values <- names(x) key_values <- key_values[!key_values %in% ids] id_values <- x %>% select(one_of(ids)) id_values <- id_values %>% gather(key = ".key_map", value = ".id_map") x <- x %>% mutate(.r = row_number()) %>% unite(one_of(ids), col = ".id_map", remove = F) x <- x %>% gather(key = ".col", value = ".val", names(x) %>% str_subset("^[^\\.]")) %>% mutate(.key_map = .col, .type = ifelse(.col %in% ids, "id", "value"), .val = as.character(.val), .x = rep(1:nc, each = nr), .y = -rep(1:nr, nc)) # the .key_map == ids need to be redirected to the key-values and multiplied ids_key_map <- tidyr::crossing(.key_map = key_values, .col = ids) x <- bind_rows( x %>% filter(!.key_map %in% ids), x %>% filter(.key_map %in% ids) %>% select(-.key_map) %>% left_join(ids_key_map, by = ".col") ) # due to the untidiness of the wide-data, we need to treat the keys in th header # specially key_mapper <- tidyr::crossing(id_values %>% select(.id_map), .key_map = key_values) %>% mutate(.id_map = as.character(.id_map)) key_frame <- data_frame(.r = 0, .col = key_values, .val = key_values, .x = 1:length(key_values) + length(ids), .y = 0, .type = "key", .key_map = key_values) %>% left_join(key_mapper, by = ".key_map") # add headers x <- x %>% bind_rows( data_frame(.id_map = ".header", .r = 0, .col = ids, .val = ids, .x = 1:length(ids), .y = 0, .type = "id", .key_map = key_values), key_frame, . ) %>% unite(.id_map, .key_map, .val, col = ".id", remove = F) x %>% add_color_tidyr(key_values = key_values) %>% mutate(.alpha = ifelse(.id_map == ".header", 1, 0.6)) } #' Processes a long dataframe and converts it into a dataset that can be plotted #' #' @param x a long data frame #' @param ids a vector of id-variables that are already in the tidy-format #' @param key a vector of key-variables #' @param ... #' #' @return #' #' @examples #' long <- data_frame( #' year = c(2010L, 2011L, 2010L, 2011L, 2010L, 2011L), #' person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"), #' sales = c(105, 110, 100, 97, 90, 95) #' ) #' process_long(long, ids = "year", key = "person", value = "sales") #' process_long(long, ids = "year", key = "person", value = "sales") %>% static_plot process_long <- function(x, ids, key, value, ...) { if (!all(c(ids, key, value) %in% names(x))) stop("all ids, key, and value must be names of x") nr <- nrow(x) nc <- ncol(x) x <- x %>% mutate(.r = row_number()) %>% unite(ids, col = ".id_map", remove = F) %>% unite(key, col = ".key_map", remove = F) key_values <- x %>% pull(key) %>% unique() type_dict <- c(rep("id", length(ids)), rep("key", length(key)), rep("value", length(value))) names(type_dict) <- c(rep(ids, length(ids)), rep(key, length(key)), rep(value, length(value))) x <- x %>% gather(key = ".col", value = ".val", names(x) %>% str_subset("^[^\\.]")) %>% mutate( .x = rep(1:nc, each = nr), .y = -rep(1:nr, nc), .type = type_dict[.col], .val = as.character(.val) ) %>% bind_rows( tidyr::crossing(.id_map = ".header", .r = 0, .col = ids, .val = ids, .x = 1:length(ids), .y = 0, .type = "id", .key_map = key_values), data_frame(.id_map = ".header", .r = 0, .col = key, .val = key, .x = 1 + 1:length(key), .y = 0, .type = "key", .key_map = key_values), data_frame(.id_map = ".header", .r = 0, .col = value, .val = value, .x = 1 + length(key) + 1:length(value), .y = 0, .type = "value", .key_map = "value"), . ) %>% unite(.id_map, .key_map, .val, col = ".id", remove = F) x %>% add_color_tidyr(key_values = key_values) %>% mutate(.alpha = ifelse(.id_map == ".header", 1, 0.6)) } #' Animates a gather or spread function #' #' internally used by animate_spread and animate_gather #' #' @param lhs the (processed) dataset on the left-side #' @param rhs the (processed) dataset on the right-side #' @param sequence a named vector of the sequence titles #' (current_state, final_state, operation, and reverse_operation) #' @param key_values the unique key-values #' @param export the export type, either gif, first or last. The latter two #' export ggplots of the first/last state of the join #' @param detailed boolean value if the animation should show one step for each #' key value #' @param ... further arguments passed to animate_plot #' #' @return the plot or the gif #' #' @examples #' NULL gather_spread <- function(lhs, rhs, sequence, key_values, export, detailed, ...) { # lhs is the one state of the df # rhs is the target state # animate the four steps: inital with sequence[["current_state]], # transformations by the unique key-values with sequence[["operation"]], # final with sequence[["final_state"]] # and back transformation with sequence[["reverse_operation]] # have lhs and rhs in the right format: preprocessed with ids, .x, .y etc. # have a color function that makes coloring easier # transformations: for each key-variable: respective ids "fly in", keys fly in and ids fly in (all in one step for one key. i.e., Alice) # how much is the rhs to the left of lhs? xshift <- 2 state_start <- lhs %>% mutate(.frame = 0) step_0 <- lhs %>% mutate(.frame = 1) state_end <- rhs %>% mutate(.frame = length(key_values) + 2, .x = .x + max(lhs$.x) + xshift) if (detailed) { # take one instance of the first headers start_headers <- lhs %>% filter(.id_map == ".header" & !.val %in% key_values) %>% group_by(.col, .val) %>% slice(1) %>% ungroup() end_headers <- state_end %>% filter(.id_map == ".header") # for each unique key-value move the respective entries keys_to_shift <- lhs %>% filter(.key_map %in% key_values) keys_shifted <- lhs[0, ] key_steps <- lhs[0, ] i <- 1 for (keyval in key_values) { i <- i + 1 keys_shifted <- bind_rows(keys_shifted, filter(state_end, .key_map == keyval)) keys_to_shift <- keys_to_shift %>% filter(.key_map != keyval) if (keyval == key_values[length(key_values)]) { # in the last round, we dont want to save the start headers start_headers <- NULL } round_n <- bind_rows(end_headers, start_headers, keys_shifted, keys_to_shift) %>% mutate(.frame = i) key_steps <- bind_rows(key_steps, round_n) } anim_df <- bind_rows(state_start, step_0, key_steps, state_end) # form the .frame as proper factors frame_labels <- c( sequence[["current_state"]], paste(sequence[["operation"]], key_values), sequence[["final_state"]], sequence[["reverse_operation"]] ) title_string <- "{gsub('\\\\) [a-zA-Z]+$', ')', previous_state)}" } else { anim_df <- bind_rows(state_start, state_end) frame_labels <- c( sequence[["operation"]], sequence[["reverse_operation"]] ) title_string <- "{ifelse(transitioning, previous_state, ifelse(grepl('gather', next_state), 'Wide', 'Long'))}" } frame_levels <- anim_df$.frame %>% unique() anim_df <- anim_df %>% mutate(.frame = factor(.frame, levels = frame_levels, labels = frame_labels)) if (export == "gif") { animate_plot(anim_df, title = title_string) #... } else if (export == "first") { static_plot(state_start) #.... } else if (export == "last") { static_plot(state_end) #.... } # open issues: ... doesnt work properly. # especially if the id-arguments are passed in the gather-style, i.e., -year, or year:var }