You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

355 line
10KB

  1. #' Gets the ... names
  2. #'
  3. #' Used to get the -year
  4. #'
  5. #' @param ... arguments
  6. #'
  7. #' @return a vector of the names of ...
  8. #'
  9. #' @examples
  10. #' x <- 1:10
  11. #' y <- 1
  12. #' get_quos_names(-x)
  13. #' get_quos_names(x:y)
  14. get_quos_names <- function(...) {
  15. q <- rlang::quos(...)
  16. purrr::map_chr(q, rlang::quo_name)
  17. }
  18. #' Parses a simple vector so that it looks like its input
  19. #'
  20. #' @param x a vector
  21. #'
  22. #' @return a string
  23. #'
  24. #' @examples
  25. #' dput_parser("x")
  26. #' dput_parser(c("x", "y"))
  27. dput_parser <- function(x) UseMethod("dput_parser")
  28. dput_parser.character <- function(x) {
  29. if (length(x) == 1) {
  30. sprintf('"%s"', x)
  31. } else {
  32. x <- capture.output(dput(x))
  33. paste(x, collapse = "")
  34. }
  35. }
  36. #' Adds color to processed tidy data
  37. #'
  38. #' @param x a processed data-frame as outputted by process_long or process_wide
  39. #' @param key_values the unique key-values
  40. #' @param color_fun the color function
  41. #' @param color_header the color for the header
  42. #' @param ... not used
  43. #'
  44. #' @return a data-frame with the colors
  45. #'
  46. #' @examples
  47. #' NULL
  48. add_color_tidyr <- function(x, key_values,
  49. color_fun = scales::brewer_pal(type = "qual", "Set1"),
  50. color_header = "#737373",
  51. color_id = "#d0d0d0") {
  52. color_dict <- color_fun(3)
  53. names(color_dict) <- c("id", "key", "value")
  54. x %>% mutate(.color = color_dict[.type])
  55. }
  56. #' Processes a wide dataframe and converts it into a dataset that can be plotted
  57. #'
  58. #' @param x a wide data frame
  59. #' @param ids a vector of id-variables that are already in the tidy-format
  60. #' @param key a vector of key-variables
  61. #' @param color_id the color for the id-body
  62. #' @param ...
  63. #'
  64. #' @return TODO
  65. #'
  66. #' @examples
  67. #' wide <- data_frame(
  68. #' year = 2010:2011,
  69. #' Alice = c(105, 110),
  70. #' Bob = c(100, 97),
  71. #' Charlie = c(90, 95)
  72. #' )
  73. #' process_wide(wide, ids = "year", key = "person")
  74. #' process_wide(wide, ids = "year", key = "person") %>% static_plot
  75. process_wide <- function(x, ids, key, color_id = "lightgray", ...) {
  76. if (!all(ids %in% names(x)))
  77. stop("all ids must be in x")
  78. nr <- nrow(x)
  79. nc <- ncol(x)
  80. key_values <- names(x)
  81. key_values <- key_values[!key_values %in% ids]
  82. id_values <- x %>% select(dplyr::one_of(ids))
  83. id_values <- id_values %>% tidyr::gather(key = ".key_map", value = ".id_map")
  84. x <- x %>% mutate(.r = row_number()) %>%
  85. tidyr::unite(dplyr::one_of(ids), col = ".id_map", remove = F)
  86. x <- x %>%
  87. gather(key = ".col", value = ".val", names(x)[grepl("^[^\\.]", names(x))]) %>%
  88. mutate(.key_map = .col,
  89. .type = ifelse(.col %in% ids, "id", "value"),
  90. .val = as.character(.val),
  91. .x = rep(1:nc, each = nr),
  92. .y = -rep(1:nr, nc),
  93. .header = F)
  94. # make sure that we have one id value per key
  95. tmp <- x %>% filter(.key_map %in% ids)
  96. x <- bind_rows(
  97. left_join(tmp %>% select(-.key_map),
  98. tmp %>% select(.id_map) %>% tidyr::crossing(.key_map = key_values),
  99. by = ".id_map"),
  100. x %>% filter(!.key_map %in% ids)
  101. )
  102. # add header:
  103. crosser <- tidyr::crossing(.id_map = as.character(id_values$.id_map),
  104. .key_map = key_values)
  105. key_header <- data_frame(
  106. .key_map = key_values,
  107. .r = 0,
  108. .col = key_values,
  109. .val = key_values,
  110. .type = "key",
  111. .x = length(ids) + 1:length(key_values),
  112. .y = 0,
  113. .header = TRUE) %>%
  114. left_join(crosser, by = ".key_map")
  115. id_header <- left_join(
  116. data_frame(.id_map = ids,
  117. .r = 0,
  118. .col = ids,
  119. .val = ids,
  120. .type = "id",
  121. .x = 1:length(ids),
  122. .y = 0,
  123. .header = TRUE),
  124. tidyr::crossing(.id_map = ids, .key_map = key_values),
  125. by = ".id_map"
  126. )
  127. x <- bind_rows(id_header, key_header, x)
  128. x <- x %>% tidyr::unite(.key_map, .id_map, .val, col = ".id", remove = F)
  129. x %>%
  130. add_color_tidyr(key_values = key_values) %>%
  131. mutate(.alpha = ifelse(.header == TRUE, 1, 0.6))
  132. }
  133. #' Processes a long dataframe and converts it into a dataset that can be plotted
  134. #'
  135. #' @param x a long data frame
  136. #' @param ids a vector of id-variables that are already in the tidy-format
  137. #' @param key a vector of key-variables
  138. #' @param ...
  139. #'
  140. #' @return TODO
  141. #'
  142. #' @examples
  143. #' long <- data_frame(
  144. #' year = c(2010L, 2011L, 2010L, 2011L, 2010L, 2011L),
  145. #' person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"),
  146. #' sales = c(105, 110, 100, 97, 90, 95)
  147. #' )
  148. #' process_long(long, ids = "year", key = "person", value = "sales")
  149. #' process_long(long, ids = "year", key = "person", value = "sales") %>% static_plot
  150. process_long <- function(x, ids, key, value, ...) {
  151. if (!all(c(ids, key, value) %in% names(x)))
  152. stop("all ids, key, and value must be names of x")
  153. nr <- nrow(x)
  154. nc <- ncol(x)
  155. xn <- names(x)
  156. x <- x %>% mutate(.r = row_number()) %>%
  157. tidyr::unite(ids, col = ".id_map", remove = F) %>%
  158. tidyr::unite(key, col = ".key_map", remove = F)
  159. key_values <- x %>% pull(key) %>% unique()
  160. type_dict <- c(rep("id", length(ids)), rep("key", length(key)), rep("value", length(value)))
  161. names(type_dict) <- c(ids, key, value)
  162. x_dict <- 1:nc
  163. names(x_dict) <- xn
  164. x <- x %>%
  165. tidyr::gather(key = ".col", value = ".val", names(x)[grepl("^[^\\.]", names(x))]) %>%
  166. mutate(
  167. .x = x_dict[.col],
  168. .y = -rep(1:nr, nc),
  169. .type = type_dict[.col],
  170. .val = as.character(.val),
  171. .header = FALSE
  172. )
  173. # add headers:
  174. id_headers <- tidyr::crossing(.id_map = ids, # x$.id_map %>% unique()
  175. .key_map = key_values,
  176. ) %>%
  177. mutate(
  178. .r = 0,
  179. .col = "id",
  180. .val = .id_map,
  181. .x = x_dict[.val],
  182. .y = 0,
  183. .type = "id",
  184. .header = TRUE
  185. )
  186. x <- x %>%
  187. dplyr::add_row(
  188. .before = T,
  189. .id_map = c(rep("key", length(key)), rep("value", length(value))),
  190. .key_map = c(rep("key", length(key)), rep("value", length(value))),
  191. .r = 0,
  192. .col = c(rep("key", length(key)), rep("value", length(value))),
  193. .val = c(key, value),
  194. .x = length(ids) + 1:length(c(key, value)),
  195. .y = 0,
  196. .type = c(rep("key", length(key)), rep("value", length(value))),
  197. .header = TRUE
  198. )
  199. x <- bind_rows(id_headers, x)
  200. x <- x %>%
  201. tidyr::unite(.key_map, .id_map, .val, col = ".id", remove = F)
  202. x %>% add_color_tidyr(key_values = key_values) %>%
  203. mutate(.alpha = ifelse(.header == TRUE, 1, 0.6))
  204. }
  205. #' Animates a gather or spread function
  206. #'
  207. #' internally used by animate_spread and animate_gather
  208. #'
  209. #' @param lhs the (processed) dataset on the left-side
  210. #' @param rhs the (processed) dataset on the right-side
  211. #' @param sequence a named vector of the sequence titles
  212. #' (current_state, final_state, operation, and reverse_operation)
  213. #' @param key_values the unique key-values
  214. #' @param export the export type, either gif, first or last. The latter two
  215. #' export ggplots of the first/last state of the join
  216. #' @param detailed boolean value if the animation should show one step for each
  217. #' key value
  218. #' @param ... further arguments passed to animate_plot
  219. #'
  220. #' @return the plot or the gif
  221. #'
  222. #' @examples
  223. #' NULL
  224. gather_spread <- function(lhs, rhs, sequence, key_values, export, detailed, ..., anim_opts = anim_options()) {
  225. # lhs is the one state of the df
  226. # rhs is the target state
  227. # animate the four steps: inital with sequence[["current_state]],
  228. # transformations by the unique key-values with sequence[["operation"]],
  229. # final with sequence[["final_state"]]
  230. # and back transformation with sequence[["reverse_operation]]
  231. # have lhs and rhs in the right format: preprocessed with ids, .x, .y etc.
  232. # have a color function that makes coloring easier
  233. # 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)
  234. # how much is the rhs to the left of lhs?
  235. if (!detailed) {
  236. anim_df <- bind_rows(
  237. lhs %>% mutate(.frame = 0),
  238. rhs %>% mutate(.frame = 1)
  239. )
  240. frame_labels <- c(sequence[["operation"]], sequence[["reverse_operation"]])
  241. title_string <- "{ifelse(transitioning, previous_state, ifelse(grepl('gather', next_state), 'Wide', 'Long'))}"
  242. tl <- 2
  243. sl <- 1
  244. } else {
  245. xshift <- 2
  246. rhs <- rhs %>% mutate(.x = .x + max(lhs$.x) + xshift)
  247. # the header rows
  248. header_start <- lhs %>% filter(.header == TRUE, !.key_map %in% key_values)
  249. header_end <- rhs %>% filter(.header == TRUE)
  250. state_start <- lhs %>% mutate(.frame = 0)
  251. state_end <- rhs %>% mutate(.frame = length(key_values) + 2)
  252. step_0 <- lhs %>% mutate(.frame = 1)
  253. # for each unique key-value move the respective entries
  254. keys_remaining <- lhs %>% filter(.key_map %in% key_values)
  255. keys_shifted <- lhs[0, ]
  256. key_steps <- lhs[0, ]
  257. f <- 1
  258. ids_remaining <- lhs %>% filter(.type == "id" & .header == FALSE)
  259. for (keyval in key_values) {
  260. f <- f + 1
  261. move_rhs <- rhs %>% filter(.key_map == keyval)
  262. keys_remaining <- keys_remaining %>% filter(.key_map != keyval)
  263. if (keyval == key_values[length(key_values)]) {
  264. header_start <- NULL
  265. }
  266. hd <- header_end %>% filter(.key_map == keyval |
  267. (.type %in% c("key", "value") &
  268. .col %in% c("key", "value")))
  269. keys_shifted <- bind_rows(keys_shifted, move_rhs)
  270. round_n <- bind_rows(header_start, hd,
  271. keys_remaining, keys_shifted) %>%
  272. mutate(.frame = f)
  273. key_steps <- bind_rows(key_steps, round_n)
  274. }
  275. anim_df <- bind_rows(state_start, step_0, key_steps, state_end)
  276. # form the .frame as proper factors
  277. frame_labels <- c(
  278. sequence[["current_state"]],
  279. paste(sequence[["operation"]], key_values),
  280. sequence[["final_state"]],
  281. sequence[["reverse_operation"]]
  282. )
  283. title_string <- "{gsub('\\\\) [a-zA-Z]+$', ')', previous_state)}"
  284. tl <- length(unique(anim_df$.frame)) * 2
  285. sl <- 1
  286. }
  287. frame_levels <- anim_df$.frame %>% unique()
  288. anim_df <- anim_df %>%
  289. mutate(.frame = factor(.frame,
  290. levels = frame_levels,
  291. labels = frame_labels))
  292. if (export == "gif") {
  293. animate_plot(anim_df, title = title_string, anim_opts = anim_opts)
  294. } else if (export == "first") {
  295. static_plot(state_start, anim_opts = anim_opts) #....
  296. } else if (export == "last") {
  297. static_plot(state_end, anim_opts = anim_opts) #....
  298. }
  299. # open issues: ... doesnt work properly.
  300. # especially if the id-arguments are passed in the gather-style, i.e., -year, or year:var
  301. }