| @@ -0,0 +1,3 @@ | |||
| .Rproj.user | |||
| README_cache | |||
| .DS_Store | |||
| @@ -0,0 +1,35 @@ | |||
| # Animated dplyr joins with gganimate | |||
| # * Garrick Aden-Buie | |||
| # * garrickadenbuie.com | |||
| # * MIT License: https://opensource.org/licenses/MIT | |||
| # Note: I used Fira Sans and Fira Mono fonts. | |||
| # Use search and replace to use a different font if Fira is not available. | |||
| library(tidyverse) | |||
| library(gganimate) | |||
| if (!getOption("tidy_verb_anim.font_registered", FALSE)) { | |||
| source(here::here("R", "01_register-fonts.R")) | |||
| } | |||
| if (!getOption("tidy_verb_anim.functions_loaded", FALSE)) { | |||
| source(here::here("R", "02_functions.R")) | |||
| } | |||
| if (!dir.exists(here::here("images"))) dir.create(here::here("images")) | |||
| # Data ---- | |||
| x <- data_frame( | |||
| id = 1:3, | |||
| x = paste0("x", 1:3) | |||
| ) | |||
| y <- data_frame( | |||
| id = (1:4)[-3], | |||
| y = paste0("y", (1:4)[-3]) | |||
| ) | |||
| initial_dfs <- proc_data(x, "x") %>% | |||
| bind_rows(mutate(proc_data(y, "y"), .x = .x + 3)) %>% | |||
| mutate(frame = 1) | |||
| @@ -0,0 +1,4 @@ | |||
| sysfonts::font_add_google("Fira Sans") | |||
| sysfonts::font_add_google("Fira Mono") | |||
| showtext::showtext_auto() | |||
| options(tidy_verb_anim.font_registered = TRUE) | |||
| @@ -0,0 +1,38 @@ | |||
| proc_data <- function(x, .id = "x") { | |||
| colors <- scales::brewer_pal(type = "qual", "Set1")(max(x$id)) | |||
| x %>% | |||
| mutate(.y = -row_number()) %>% | |||
| tidyr::gather("label", "value", -.y) %>% | |||
| mutate(value = as.character(value)) %>% | |||
| group_by(.y) %>% | |||
| mutate( | |||
| .x = 1:n(), | |||
| .id = .id, | |||
| color = ifelse(label == "id", value, max(x$id) + 1), | |||
| color = colors[as.integer(color)], | |||
| color = ifelse(is.na(color), "#d0d0d0", color), | |||
| color = ifelse(is.na(value), "#ffffff", color) | |||
| ) | |||
| } | |||
| plot_data <- function(x, title = "") { | |||
| ggplot(x) + | |||
| aes(.x, .y, fill = color, label = value) + | |||
| geom_tile(color = "white", size = 3) + | |||
| geom_text(aes(x = .x), hjust = 0.5, size = 12, family = "Fira Sans", color = "white") + | |||
| scale_fill_identity() + | |||
| coord_equal() + | |||
| ggtitle(title) + | |||
| theme_void() + | |||
| theme(plot.title = element_text(family = "Fira Mono", hjust = 0.5, size = 24)) + | |||
| guides(fill = FALSE) | |||
| } | |||
| animate_plot <- function(x) { | |||
| x + | |||
| transition_states(frame, transition_length = 2, state_length = 1) + | |||
| enter_fade() + | |||
| exit_fade() + | |||
| ease_aes("sine-in-out") | |||
| } | |||
| options(tidy_verb_anim.functions_loaded = TRUE) | |||
| @@ -0,0 +1,35 @@ | |||
| source(here::here("R/00_base.R")) | |||
| step2 <- initial_dfs %>% | |||
| filter(.id == "x" | value %in% paste(1:2)) %>% | |||
| mutate( | |||
| frame = 2, | |||
| .x = ifelse(.id == "y", 1, .x), | |||
| .x = .x + 1 | |||
| ) | |||
| step3 <- step2 %>% | |||
| filter(grepl("3", value)) %>% | |||
| ungroup() %>% | |||
| mutate(frame = 3, .y = -1) | |||
| aj <- initial_dfs %>% | |||
| mutate(removed = .id == "y", removed = as.integer(removed)) %>% | |||
| bind_rows(step2, step3) %>% | |||
| mutate(removed = ifelse(is.na(removed), 0, removed)) %>% | |||
| arrange(removed, .y, .x, desc(frame)) %>% #View() | |||
| mutate(alpha = case_when( | |||
| grepl("3", value) ~ 1, | |||
| frame == 2 & label == "id" ~ 0.25, | |||
| frame == 2 ~ 0.65, | |||
| TRUE ~ 1 | |||
| )) %>% | |||
| { | |||
| plot_data(., "anti_join(x, y)") + | |||
| aes(alpha = alpha) + | |||
| scale_alpha_identity() | |||
| } %>% | |||
| animate_plot() | |||
| aj <- animate(aj) | |||
| anim_save(here::here("images", "anti-join.gif"), aj) | |||
| @@ -0,0 +1,22 @@ | |||
| source(here::here("R/00_base.R")) | |||
| joined_df <- full_join(x, y, "id") %>% | |||
| proc_data("x") %>% | |||
| mutate(.id = ifelse(value %in% c("4", "y4"), "y", .id)) %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| fj <- initial_dfs %>% | |||
| bind_rows(joined_df, extra_blocks) %>% | |||
| plot_data("full_join(x, y)") + | |||
| transition_states(frame, transition_length = 2, state_length = 1) + | |||
| enter_appear() + | |||
| exit_disappear(early = TRUE) + | |||
| ease_aes("sine-in-out") | |||
| fj <- animate(fj) | |||
| anim_save(here::here("images", "full-join.gif"), fj) | |||
| @@ -0,0 +1,20 @@ | |||
| source(here::here("R/00_base.R")) | |||
| joined_df <- inner_join(x, y, "id") | |||
| joined_df <- bind_rows( | |||
| proc_data(joined_df, "x"), | |||
| proc_data(joined_df, "y") | |||
| ) %>% | |||
| filter(!(label == "x" & .id == "y") & !(label == "y" & .id == "x")) %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| ij <- initial_dfs %>% | |||
| bind_rows(joined_df) %>% | |||
| mutate(removed = value %in% c("3", "4", "x3", "y4"), | |||
| removed = as.integer(removed)) %>% | |||
| arrange(desc(frame), removed) %>% | |||
| plot_data("inner_join(x, y)") %>% | |||
| animate_plot() | |||
| ij <- animate(ij) | |||
| anim_save(here::here("images", "inner-join.gif"), ij) | |||
| @@ -0,0 +1,20 @@ | |||
| source(here::here("R/00_base.R")) | |||
| joined_dfs <- left_join(x, y, "id") %>% | |||
| proc_data("x") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| lj <- initial_dfs %>% | |||
| bind_rows(joined_dfs) %>% | |||
| bind_rows(extra_blocks) %>% | |||
| mutate(color = ifelse(is.na(value), "#ffffff", color)) %>% | |||
| plot_data("left_join(x, y)") %>% | |||
| animate_plot() | |||
| lj <- animate(lj) | |||
| anim_save(here::here("images", "left-join.gif"), lj) | |||
| @@ -0,0 +1,20 @@ | |||
| source(here::here("R/00_base.R")) | |||
| joined_dfs <- right_join(x, y, "id") %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("x") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| rj <- initial_dfs %>% | |||
| bind_rows(joined_dfs, extra_blocks) %>% | |||
| arrange(desc(.id), frame, desc(label), value) %>% | |||
| plot_data("right_join(x, y)") %>% | |||
| animate_plot() | |||
| rj <- animate(rj) | |||
| anim_save(here::here("images", "right-join.gif"), rj) | |||
| @@ -0,0 +1,18 @@ | |||
| source(here::here("R/00_base.R")) | |||
| joined_df <- semi_join(x, y, "id") %>% | |||
| proc_data("x") %>% | |||
| mutate(frame = 2, .x = .x + 1) | |||
| extra_blocks <- inner_join(x, y, "id") %>% | |||
| select(id) %>% | |||
| proc_data("y") %>% | |||
| mutate(frame = 2) | |||
| sj <- initial_dfs %>% | |||
| bind_rows(joined_df, extra_blocks) %>% | |||
| plot_data("semi_join(x, y)") %>% | |||
| animate_plot() | |||
| sj <- animate(sj) | |||
| anim_save(here::here("images", "semi-join.gif"), sj) | |||
| @@ -0,0 +1,119 @@ | |||
| --- | |||
| output: github_document | |||
| --- | |||
| <!-- README.md is generated from README.Rmd. Please edit that file --> | |||
| ```{r setup, include = FALSE} | |||
| knitr::opts_chunk$set( | |||
| collapse = TRUE, | |||
| comment = "#>", | |||
| echo = FALSE, | |||
| warning = FALSE, | |||
| message = FALSE, | |||
| cache = TRUE | |||
| ) | |||
| ``` | |||
| # Tidy Animated Verbs | |||
| Garrick Aden-Buie -- [@grrrck](https://twitter.com/grrrck) -- [garrickadenbuie.com](https://www.garrickadenbuie.com) | |||
| ## Mutate Joins | |||
| ```{r intial-dfs} | |||
| source("R/00_base.R") | |||
| df_names <- data_frame( | |||
| .x = c(1.5, 4.5), .y = 0.25, | |||
| value = c("x", "y"), | |||
| size = 12, | |||
| color = "black" | |||
| ) | |||
| g <- plot_data(initial_dfs) + | |||
| geom_text(data = df_names, family = "Fira Mono", size = 24) | |||
| ggsave(g, file = here::here("images/original-dfs.png")) | |||
| ``` | |||
| <img src="images/original-dfs.png" style="max-width: 480px" /> | |||
| ```{r echo=TRUE} | |||
| x | |||
| y | |||
| ``` | |||
| ### Inner Join | |||
| ```{r inner-join} | |||
| source("R/inner_join.R") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| inner_join(x, y, by = "id") | |||
| ``` | |||
| ### Left Join | |||
| ```{r left-join} | |||
| source("R/left_join.R") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| left_join(x, y, by = "id") | |||
| ``` | |||
| ### Right Join | |||
| ```{r right-join} | |||
| source("R/right_join.R") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| right_join(x, y, by = "id") | |||
| ``` | |||
| ### Full Join | |||
| ```{r full-join} | |||
| source("R/full_join.R") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| full_join(x, y, by = "id") | |||
| ``` | |||
| ## Filtering Joins | |||
| ### Semi Join | |||
| ```{r semi-join} | |||
| source("R/semi_join.R") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| semi_join(x, y, by = "id") | |||
| ``` | |||
| ### Anti Join | |||
| ```{r anti-join} | |||
| source("R/anti_join.R") | |||
| ``` | |||
|  | |||
| ```{r echo=TRUE} | |||
| anti_join(x, y, by = "id") | |||
| ``` | |||
| @@ -0,0 +1,111 @@ | |||
| <!-- README.md is generated from README.Rmd. Please edit that file --> | |||
| # Tidy Animated Verbs | |||
| Garrick Aden-Buie – [@grrrck](https://twitter.com/grrrck) – | |||
| [garrickadenbuie.com](https://www.garrickadenbuie.com) | |||
| ## Mutate Joins | |||
| <img src="images/original-dfs.png" style="max-width: 480px" /> | |||
| ``` r | |||
| x | |||
| #> # A tibble: 3 x 2 | |||
| #> id x | |||
| #> <int> <chr> | |||
| #> 1 1 x1 | |||
| #> 2 2 x2 | |||
| #> 3 3 x3 | |||
| y | |||
| #> # A tibble: 3 x 2 | |||
| #> id y | |||
| #> <int> <chr> | |||
| #> 1 1 y1 | |||
| #> 2 2 y2 | |||
| #> 3 4 y4 | |||
| ``` | |||
| ### Inner Join | |||
|  | |||
| ``` r | |||
| inner_join(x, y, by = "id") | |||
| #> # A tibble: 2 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| #> 1 1 x1 y1 | |||
| #> 2 2 x2 y2 | |||
| ``` | |||
| ### Left Join | |||
|  | |||
| ``` r | |||
| left_join(x, y, by = "id") | |||
| #> # A tibble: 3 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| #> 1 1 x1 y1 | |||
| #> 2 2 x2 y2 | |||
| #> 3 3 x3 <NA> | |||
| ``` | |||
| ### Right Join | |||
|  | |||
| ``` r | |||
| right_join(x, y, by = "id") | |||
| #> # A tibble: 3 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| #> 1 1 x1 y1 | |||
| #> 2 2 x2 y2 | |||
| #> 3 4 <NA> y4 | |||
| ``` | |||
| ### Full Join | |||
|  | |||
| ``` r | |||
| full_join(x, y, by = "id") | |||
| #> # A tibble: 4 x 3 | |||
| #> id x y | |||
| #> <int> <chr> <chr> | |||
| #> 1 1 x1 y1 | |||
| #> 2 2 x2 y2 | |||
| #> 3 3 x3 <NA> | |||
| #> 4 4 <NA> y4 | |||
| ``` | |||
| ## Filtering Joins | |||
| ### Semi Join | |||
|  | |||
| ``` r | |||
| semi_join(x, y, by = "id") | |||
| #> # A tibble: 2 x 2 | |||
| #> id x | |||
| #> <int> <chr> | |||
| #> 1 1 x1 | |||
| #> 2 2 x2 | |||
| ``` | |||
| ### Anti Join | |||
|  | |||
| ``` r | |||
| anti_join(x, y, by = "id") | |||
| #> # A tibble: 1 x 2 | |||
| #> id x | |||
| #> <int> <chr> | |||
| #> 1 3 x3 | |||
| ``` | |||
| @@ -0,0 +1,6 @@ | |||
| install.packages("tidyverse") | |||
| install.packages("rmarkdown") | |||
| install.packages("here") | |||
| install.packages(c("sysfonts", "jsonlite", "curl", "showtext")) | |||
| install.packages("devtools") | |||
| devtools::install_github("thomasp85/gganimate") | |||
| @@ -0,0 +1 @@ | |||
| r-2018-0815 | |||
| @@ -0,0 +1,21 @@ | |||
| Version: 1.0 | |||
| RestoreWorkspace: No | |||
| SaveWorkspace: No | |||
| AlwaysSaveHistory: Default | |||
| EnableCodeIndexing: Yes | |||
| UseSpacesForTab: Yes | |||
| NumSpacesForTab: 2 | |||
| Encoding: UTF-8 | |||
| RnwWeave: Sweave | |||
| LaTeX: pdfLaTeX | |||
| AutoAppendNewline: Yes | |||
| StripTrailingWhitespace: Yes | |||
| BuildType: Package | |||
| PackageUseDevtools: Yes | |||
| PackageInstallArgs: --no-multiarch --with-keep.source | |||
| PackageRoxygenize: rd,collate,namespace | |||