DavZim před 7 roky
rodič
revize
ad19385c6e
Žádný účet není propojen s e-mailovou adresou tvůrce revize
100 změnil soubory, kde provedl 2057 přidání a 1280 odebrání
  1. +3
    -0
      .Rbuildignore
  2. +41
    -0
      DESCRIPTION
  3. +2
    -116
      LICENSE
  4. +21
    -0
      LICENSE.md
  5. +41
    -0
      NAMESPACE
  6. +0
    -37
      R/00_base_join.R
  7. +0
    -50
      R/00_base_set.R
  8. +0
    -28
      R/00_base_tidyr.R
  9. +0
    -7
      R/01_register-fonts.R
  10. +0
    -112
      R/02_functions.R
  11. +0
    -5
      R/03_check-folders.R
  12. +150
    -0
      R/animate_joins.R
  13. +244
    -0
      R/animate_options.R
  14. +133
    -0
      R/animate_sets.R
  15. +123
    -0
      R/animate_tidyr.R
  16. +0
    -48
      R/anti_join.R
  17. +0
    -29
      R/full_join.R
  18. +0
    -29
      R/inner_join.R
  19. +0
    -28
      R/intersect.R
  20. +0
    -27
      R/left_join.R
  21. +0
    -71
      R/left_join_extra.R
  22. +105
    -0
      R/move_together.R
  23. +76
    -0
      R/plot_helpers.R
  24. +155
    -0
      R/process_data_helpers.R
  25. +0
    -30
      R/right_join.R
  26. +0
    -30
      R/semi_join.R
  27. +0
    -102
      R/setdiff.R
  28. +355
    -0
      R/tidyr_helpers.R
  29. +0
    -97
      R/tidyr_spread_gather.R
  30. +0
    -42
      R/union.R
  31. +0
    -23
      R/union_all.R
  32. +11
    -0
      R/utils-pipe.R
  33. +26
    -0
      R/utils.R
  34. +27
    -0
      R/zzzz-package.R
  35. +142
    -162
      README.Rmd
  36. +242
    -163
      README.md
  37. binární
      images/anti-join.gif
  38. +160
    -0
      images/create_images.R
  39. binární
      images/full-join.gif
  40. binární
      images/gif/full-join.gif
  41. binární
      images/gif/gather.gif
  42. binární
      images/gif/inner-join.gif
  43. binární
      images/gif/intersect.gif
  44. binární
      images/gif/left-join.gif
  45. binární
      images/gif/right-join.gif
  46. binární
      images/gif/semi-join.gif
  47. binární
      images/gif/setdiff.gif
  48. binární
      images/gif/spread.gif
  49. binární
      images/gif/union-all.gif
  50. binární
      images/gif/union.gif
  51. binární
      images/inner-join.gif
  52. binární
      images/intersect.gif
  53. binární
      images/left-join-extra.gif
  54. binární
      images/left-join.gif
  55. binární
      images/right-join.gif
  56. binární
      images/semi-join.gif
  57. binární
      images/setdiff-rev.gif
  58. binární
      images/setdiff.gif
  59. binární
      images/static/png/anti-join.png
  60. binární
      images/static/png/full-join-first.png
  61. binární
      images/static/png/full-join-last.png
  62. binární
      images/static/png/full-join.png
  63. binární
      images/static/png/gather-first.png
  64. binární
      images/static/png/gather-last.png
  65. binární
      images/static/png/inner-join-first.png
  66. binární
      images/static/png/inner-join-last.png
  67. binární
      images/static/png/inner-join.png
  68. binární
      images/static/png/intersect-first.png
  69. binární
      images/static/png/intersect-last.png
  70. binární
      images/static/png/intersect.png
  71. binární
      images/static/png/left-join-extra-input.png
  72. binární
      images/static/png/left-join-extra.png
  73. binární
      images/static/png/left-join-first.png
  74. binární
      images/static/png/left-join-last.png
  75. binární
      images/static/png/left-join.png
  76. binární
      images/static/png/original-dfs-set-ops.png
  77. binární
      images/static/png/original-dfs-tidy.png
  78. binární
      images/static/png/original-dfs.png
  79. binární
      images/static/png/right-join-first.png
  80. binární
      images/static/png/right-join-last.png
  81. binární
      images/static/png/right-join.png
  82. binární
      images/static/png/semi-join-first.png
  83. binární
      images/static/png/semi-join-last.png
  84. binární
      images/static/png/semi-join.png
  85. binární
      images/static/png/setdiff-first.png
  86. binární
      images/static/png/setdiff-last.png
  87. binární
      images/static/png/setdiff-rev.png
  88. binární
      images/static/png/setdiff.png
  89. binární
      images/static/png/spread-first.png
  90. binární
      images/static/png/spread-last.png
  91. binární
      images/static/png/tidyr-gather.png
  92. binární
      images/static/png/tidyr-spread.png
  93. binární
      images/static/png/union-all-first.png
  94. binární
      images/static/png/union-all-last.png
  95. binární
      images/static/png/union-all.png
  96. binární
      images/static/png/union-first.png
  97. binární
      images/static/png/union-last.png
  98. binární
      images/static/png/union-rev.png
  99. binární
      images/static/png/union.png
  100. +0
    -44
      images/static/svg/anti-join.svg

+ 3
- 0
.Rbuildignore Zobrazit soubor

@@ -0,0 +1,3 @@
^LICENSE\.md$
^.*\.Rproj$
^\.Rproj\.user$

+ 41
- 0
DESCRIPTION Zobrazit soubor

@@ -0,0 +1,41 @@
Type: Package
Package: tidyexplain
Title: Animated Explanations of Tidyverse Verbs
Version: 0.0.1.9000
Date: 2018-08-27
Authors@R:
c(person(given = "Garrick",
family = "Aden-Buie",
role = c("aut", "cre"),
email = "g.adenbuie@gmail.com"),
person(given = "David",
family = "Zimmermann",
role = "aut",
email = "david_j_zimmermann@hotmail.com"),
person(given = "Tyler Grant",
family = "Smith",
role = "ctb"))
Description: Animated explanations of the verbs in the tidyverse
using gganimate and ggplot2.
License: MIT + file LICENSE
Depends:
gganimate (>= 0.9.9.9999),
ggplot2 (>= 3.0.0)
Imports:
dplyr,
magrittr,
purrr,
rlang (>= 0.1.2),
scales,
tidyr,
tidyselect
Suggests:
knitr,
roxygen2,
testthat,
viridis
VignetteBuilder:
knitr
Encoding: UTF-8
Roxygen: list(markdown = TRUE)
RoxygenNote: 6.1.1

+ 2
- 116
LICENSE Zobrazit soubor

@@ -1,116 +1,2 @@
CC0 1.0 Universal

Statement of Purpose

The laws of most jurisdictions throughout the world automatically confer
exclusive Copyright and Related Rights (defined below) upon the creator and
subsequent owner(s) (each and all, an "owner") of an original work of
authorship and/or a database (each, a "Work").

Certain owners wish to permanently relinquish those rights to a Work for the
purpose of contributing to a commons of creative, cultural and scientific
works ("Commons") that the public can reliably and without fear of later
claims of infringement build upon, modify, incorporate in other works, reuse
and redistribute as freely as possible in any form whatsoever and for any
purposes, including without limitation commercial purposes. These owners may
contribute to the Commons to promote the ideal of a free culture and the
further production of creative, cultural and scientific works, or to gain
reputation or greater distribution for their Work in part through the use and
efforts of others.

For these and/or other purposes and motivations, and without any expectation
of additional consideration or compensation, the person associating CC0 with a
Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
and publicly distribute the Work under its terms, with knowledge of his or her
Copyright and Related Rights in the Work and the meaning and intended legal
effect of CC0 on those rights.

1. Copyright and Related Rights. A Work made available under CC0 may be
protected by copyright and related or neighboring rights ("Copyright and
Related Rights"). Copyright and Related Rights include, but are not limited
to, the following:

i. the right to reproduce, adapt, distribute, perform, display, communicate,
and translate a Work;

ii. moral rights retained by the original author(s) and/or performer(s);

iii. publicity and privacy rights pertaining to a person's image or likeness
depicted in a Work;

iv. rights protecting against unfair competition in regards to a Work,
subject to the limitations in paragraph 4(a), below;

v. rights protecting the extraction, dissemination, use and reuse of data in
a Work;

vi. database rights (such as those arising under Directive 96/9/EC of the
European Parliament and of the Council of 11 March 1996 on the legal
protection of databases, and under any national implementation thereof,
including any amended or successor version of such directive); and

vii. other similar, equivalent or corresponding rights throughout the world
based on applicable law or treaty, and any national implementations thereof.

2. Waiver. To the greatest extent permitted by, but not in contravention of,
applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
and Related Rights and associated claims and causes of action, whether now
known or unknown (including existing as well as future claims and causes of
action), in the Work (i) in all territories worldwide, (ii) for the maximum
duration provided by applicable law or treaty (including future time
extensions), (iii) in any current or future medium and for any number of
copies, and (iv) for any purpose whatsoever, including without limitation
commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
the Waiver for the benefit of each member of the public at large and to the
detriment of Affirmer's heirs and successors, fully intending that such Waiver
shall not be subject to revocation, rescission, cancellation, termination, or
any other legal or equitable action to disrupt the quiet enjoyment of the Work
by the public as contemplated by Affirmer's express Statement of Purpose.

3. Public License Fallback. Should any part of the Waiver for any reason be
judged legally invalid or ineffective under applicable law, then the Waiver
shall be preserved to the maximum extent permitted taking into account
Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
is so judged Affirmer hereby grants to each affected person a royalty-free,
non transferable, non sublicensable, non exclusive, irrevocable and
unconditional license to exercise Affirmer's Copyright and Related Rights in
the Work (i) in all territories worldwide, (ii) for the maximum duration
provided by applicable law or treaty (including future time extensions), (iii)
in any current or future medium and for any number of copies, and (iv) for any
purpose whatsoever, including without limitation commercial, advertising or
promotional purposes (the "License"). The License shall be deemed effective as
of the date CC0 was applied by Affirmer to the Work. Should any part of the
License for any reason be judged legally invalid or ineffective under
applicable law, such partial invalidity or ineffectiveness shall not
invalidate the remainder of the License, and in such case Affirmer hereby
affirms that he or she will not (i) exercise any of his or her remaining
Copyright and Related Rights in the Work or (ii) assert any associated claims
and causes of action with respect to the Work, in either case contrary to
Affirmer's express Statement of Purpose.

4. Limitations and Disclaimers.

a. No trademark or patent rights held by Affirmer are waived, abandoned,
surrendered, licensed or otherwise affected by this document.

b. Affirmer offers the Work as-is and makes no representations or warranties
of any kind concerning the Work, express, implied, statutory or otherwise,
including without limitation warranties of title, merchantability, fitness
for a particular purpose, non infringement, or the absence of latent or
other defects, accuracy, or the present or absence of errors, whether or not
discoverable, all to the greatest extent permissible under applicable law.

c. Affirmer disclaims responsibility for clearing rights of other persons
that may apply to the Work or any use thereof, including without limitation
any person's Copyright and Related Rights in the Work. Further, Affirmer
disclaims responsibility for obtaining any necessary consents, permissions
or other rights required for any use of the Work.

d. Affirmer understands and acknowledges that Creative Commons is not a
party to this document and has no duty or obligation with respect to this
CC0 or use of the Work.

For more information, please see
<http://creativecommons.org/publicdomain/zero/1.0/>
YEAR: 2018
COPYRIGHT HOLDER: Garrick Aden-Buie

+ 21
- 0
LICENSE.md Zobrazit soubor

@@ -0,0 +1,21 @@
# MIT License

Copyright (c) 2018 Garrick Aden-Buie

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

+ 41
- 0
NAMESPACE Zobrazit soubor

@@ -0,0 +1,41 @@
# Generated by roxygen2: do not edit by hand

S3method(print,anim_opts)
export("%>%")
export(anim_options)
export(animate_anti_join)
export(animate_full_join)
export(animate_gather)
export(animate_inner_join)
export(animate_intersect)
export(animate_left_join)
export(animate_right_join)
export(animate_semi_join)
export(animate_setdiff)
export(animate_spread)
export(animate_union)
export(animate_union_all)
export(get_font_size)
export(is.anim_opts)
export(set_anim_options)
export(set_font_size)
importFrom(dplyr,anti_join)
importFrom(dplyr,arrange)
importFrom(dplyr,bind_cols)
importFrom(dplyr,bind_rows)
importFrom(dplyr,data_frame)
importFrom(dplyr,filter)
importFrom(dplyr,full_join)
importFrom(dplyr,group_by)
importFrom(dplyr,inner_join)
importFrom(dplyr,left_join)
importFrom(dplyr,mutate)
importFrom(dplyr,pull)
importFrom(dplyr,right_join)
importFrom(dplyr,row_number)
importFrom(dplyr,select)
importFrom(dplyr,semi_join)
importFrom(dplyr,slice)
importFrom(magrittr,"%>%")
importFrom(tidyr,gather)
importFrom(tidyr,spread)

+ 0
- 37
R/00_base_join.R Zobrazit soubor

@@ -1,37 +0,0 @@
# Animated dplyr joins with gganimate
# * Garrick Aden-Buie
# * garrickadenbuie.com
# * MIT License: https://opensource.org/licenses/MIT

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"))
}

source(here::here("R", "03_check-folders.R"))

plot_data_join <- function(x, title = "", xlims = xlim(0.5, 5.5), ylims = ylim(-3.5, -0.5)) {
plot_data(x, title) +
xlims + ylims
}

# 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_join_dfs <- proc_data(x, "x") %>%
bind_rows(mutate(proc_data(y, "y"), .x = .x + 3)) %>%
mutate(frame = 1)

+ 0
- 50
R/00_base_set.R Zobrazit soubor

@@ -1,50 +0,0 @@
# Animated dplyr set opertaions with gganimate
# * Contributed by Tyler Grant Smith <https://github.com/TylerGrantSmith>
# * and Garrick Aden-Buie <https://www.garrickadenbuie.com>
# * MIT License: https://opensource.org/licenses/MIT

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"))
}

source(here::here("R", "03_check-folders.R"))

# Initialize data processing function ----

proc_data_set <- function(x, .id = "x") {
proc_data(x, .id, colorize_row_id, "before")
}

plot_data_set <- function(x, title = "", xlims = xlim(1.5, 6.5), ylims = ylim(-3.5, -0.5)) {
filter(x, label != "id") %>%
plot_data(title) +
xlims + ylims
}

# Data ----

x <- tibble::tribble(
~id, ~x, ~y,
1, "1", "a",
2, "1", "b",
3, "2", "a"
)

y <- tibble::tribble(
~id, ~x, ~y,
1, "1", "a",
4, "2", "b"
)

initial_set_dfs <- bind_rows(
proc_data_set(x, "x"),
proc_data_set(y, "y") %>% mutate(.x = .x + 3)
) %>%
mutate(frame = 1)

+ 0
- 28
R/00_base_tidyr.R Zobrazit soubor

@@ -1,28 +0,0 @@
# Animated dplyr joins with gganimate
# * Garrick Aden-Buie
# * garrickadenbuie.com
# * MIT License: https://opensource.org/licenses/MIT

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"))
}

source(here::here("R", "03_check-folders.R"))

# Data ----
set.seed(42)
wide <- data_frame(
id = rep(1:2),
x = letters[1:2],
y = letters[3:4],
z = letters[5:6]
)

long <- tidyr::gather(wide, key, val, x:z)

+ 0
- 7
R/01_register-fonts.R Zobrazit soubor

@@ -1,7 +0,0 @@
# Note: I used Fira Sans and Mono (downloaded here from Google Fonts).
# Feel free to change font names below for desired fonts.

sysfonts::font_add_google("Fira Sans")
sysfonts::font_add_google("Fira Mono")
showtext::showtext_auto()
options(tidy_verb_anim.font_registered = TRUE)

+ 0
- 112
R/02_functions.R Zobrazit soubor

@@ -1,112 +0,0 @@
proc_data <- function(x, .id = "x", color_fun = colorize_keys, color_when = c("after", "before"), ...) {
color_when <- match.arg(color_when)
n_colors <- max(x$id)

if (color_when == "before") x <- color_fun(x, n_colors, ...)

x <- x %>%
mutate(.y = -row_number()) %>%
tidyr::gather("label", "value", setdiff(colnames(x), c(".y", "color"))) %>%
mutate(value = as.character(value)) %>%
group_by(.y) %>%
mutate(
.x = 1:n(),
.id = .id,
.width = 1
) %>%
ungroup(.y)

if (color_when == "after") x <- color_fun(x, n_colors, ...)
x
}

colorize_keys <- function(df, n_colors, key_col = "id", color_other = "#d0d0d0", color_missing = "#ffffff") {
# Assumes that key_col is integer
colors <- scales::brewer_pal(type = "qual", "Set1")(n_colors)
mutate(
df,
color = ifelse(label == key_col, value, n_colors + 1),
color = colors[as.integer(color)],
color = ifelse(is.na(color), "#d0d0d0", color),
color = ifelse(is.na(value), "#ffffff", color)
)
}

colorize_row_id <- function(df, n_colors, key_col = "id") {
# Assumes that key_col is integer
colors <- scales::brewer_pal(type = "qual", "Set1")(n_colors)
df$color <- colors[df[[key_col]]]
df
}

colorize_wide_tidyr <- function(df, n_colors, key_col = "id") {
n_colors <- n_colors + length(setdiff(unique(df$label), key_col))
colors <- scales::brewer_pal(type = "qual", "Set1")(n_colors)

df$value_int <- as.integer(gsub("[a-zA-Z]", "0", df$value))
max_id_color <- max(df$value_int)

df %>%
bind_rows(
filter(df, .y == "-1") %>% mutate(.y = 0)
) %>%
mutate(
idcp = max_id_color - 1L,
idc = case_when(
label == "id" ~ value_int,
TRUE ~ map_int(label, ~which(. == unique(label))) + idcp
)
) %>%
select(-idcp, -value_int) %>%
mutate(
idc = ifelse(.y == 0 & label == "id", 100, idc),
value = ifelse(.y == 0, label, value),
.id = ifelse(.y == 0, "n", .id),
color = colors[idc],
) %>%
filter(!is.na(color)) %>%
mutate(alpha = ifelse(label != "id" & .y < 0, 0.6, 1.0)) %>%
select(-idc)
}

plot_data <- function(x, title = "") {
if (!"alpha" %in% colnames(x)) x$alpha <- 1
if (!".text_color" %in% colnames(x)) x$`.text_color` <- "white"
if (!".text_size" %in% colnames(x)) x$`.text_size` <- 12
ggplot(x) +
aes(.x, .y, fill = color, label = value) +
geom_tile(aes(alpha = alpha), width = 0.9, height = 0.9) +
geom_text(aes(x = .x, color = .text_color, size = .text_size), hjust = 0.5, family = "Fira Sans") +
scale_fill_identity() +
scale_alpha_identity() +
scale_color_identity() +
scale_size_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, transition_length = 2, state_length = 1) {
x +
transition_states(frame, transition_length, state_length) +
enter_fade() +
exit_fade() +
ease_aes("sine-in-out")
}

save_static_plot <- function(g, filename, formats = c("png", "svg")) {
filenames <- formats %>%
purrr::set_names() %>%
purrr::map_chr(static_plot_filename, x = filename) %>%
purrr::iwalk(
~ ggsave(filename = .x, plot = g, dev = .y)
)
}

static_plot_filename <- function(x, ext) {
here::here("images", "static", ext, paste0(x, ".", ext))
}

options(tidy_verb_anim.functions_loaded = TRUE)

+ 0
- 5
R/03_check-folders.R Zobrazit soubor

@@ -1,5 +0,0 @@
if (!dir.exists(here::here("images"))) dir.create(here::here("images"))
png_path <- here::here("images", "static", "png")
svg_path <- here::here("images", "static", "svg")
if (!dir.exists(png_path)) dir.create(png_path, recursive = TRUE)
if (!dir.exists(svg_path)) dir.create(svg_path, recursive = TRUE)

+ 150
- 0
R/animate_joins.R Zobrazit soubor

@@ -0,0 +1,150 @@
#' Animates a join operation
#'
#' Functions to visualise the join operations either static as a ggplot, or
#' dynamic as a gif.
#'
#' @param x the x dataset
#' @param y the y dataset
#' @param by the by arguments for the join
#' @param export the export type, either gif, first or last. The latter two
#' export ggplots of the first/last state of the join
#' @param ... further arguments passed to anim_options()
#'
#' @return either a gif or a ggplot
#'
#' @seealso \code{\link[dplyr]{join}}
#'
#' @name animate_join
#' @examples
#' x <- data_frame(id = 1:3, x = paste0("x", 1:3))
#' y <- data_frame(id = (1:4)[-3], y = paste0("y", (1:4)[-3]))
#'
#' # Animate the first or last state of the join
#' animate_full_join(x, y, by = "id", export = "first")
#' animate_full_join(x, y, by = "id", export = "last")
#'
#' # animate the transition as a gif (default)
#' \donttest{
#' animate_full_join(x, y, by = "id", export = "gif")
#' }
#'
#' # different options include
#' \donttest{
#' animate_full_join(x, y, by = "id")
#' animate_inner_join(x, y, by = "id")
#' animate_left_join(x, y, by = "id")
#' animate_right_join(x, y, by = "id")
#' animate_semi_join(x, y, by = "id")
#' animate_anti_join(x, y, by = "id")
#'
#' # further arguments can be passed to all animate_* functions, see also ?anim_options
#' animate_full_join(
#' x, y, by = "id", export = "last",
#' text_size = 5, title_size = 25,
#' color_header = "black",
#' color_other = "lightblue",
#' color_fun = viridis::viridis
#' )
#' }
#'
#' # Save the results
#' \donttest{
#' # to save the ggplot, use
#' fj <- animate_full_join(x, y, by = "id", export = "last")
#' ggsave("full-join.pdf", fj)
#'
#' # to save the gif, use
#' fj <- animate_full_join(x, y, by = "id", export = "gif")
#' anim_save(fj, "full-join.gif")
#' }
animate_join <- function(
x,
y,
by,
type = c("full_join", "inner_join", "left_join", "right_join",
"semi_join", "anti_join"),
export = c("gif", "first", "last"),
...
) {
type <- match.arg(type)
export <- match.arg(export)
x_name <- get_input_text(x)
y_name <- get_input_text(y)
data <- make_named_data(x, y)

by_args <- if (length(by) == 1) sprintf("\"%s\"", by) else
sprintf("c(\"%s\")", paste(by, collapse = "\", \""))

title <- sprintf(paste0(type, "(%s, %s, by = %s)"), x_name, y_name, by_args)

if (type %in% c("semi_join", "anti_join")) {
# for semi and anti_joins, there is no adding of multiple rows
data$y <- dplyr::distinct(data$y)
}

ll <- process_join(data$x, data$y, by, ...)

step0 <- bind_rows(ll$x, ll$y) %>% mutate(.frame = 0, .alpha = 1)

step1 <- move_together(ll$x, ll$y, type) %>% mutate(.frame = 1)

all <- bind_rows(step0, step1)

if (export == "gif") {
animate_plot(all, title, ...)
} else if (export == "first") {
title <- ""
static_plot(step0, title, ...)
} else if (export == "last") {
static_plot(step1, title, ...)
}
}


#' @rdname animate_join
#' @export
animate_full_join <- function(x, y, by, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_join(x, y, by, type = "full_join", export = export, ...)
}

#' @rdname animate_join
#' @export
animate_inner_join <- function(x, y, by, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_join(x, y, by, type = "inner_join", export = export, ...)
}

#' @rdname animate_join
#' @export
animate_left_join <- function(x, y, by, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_join(x, y, by, type = "left_join", export = export, ...)
}

#' @rdname animate_join
#' @export
animate_right_join <- function(x, y, by, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_join(x, y, by, type = "right_join", export = export, ...)
}

#' @rdname animate_join
#' @export
animate_semi_join <- function(x, y, by, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_join(x, y, by, type = "semi_join", export = export, ...)
}

#' @rdname animate_join
#' @export
animate_anti_join <- function(x, y, by, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_join(x, y, by, type = "anti_join", export = export, ...)
}

+ 244
- 0
R/animate_options.R Zobrazit soubor

@@ -0,0 +1,244 @@
#' Animation Options
#'
#' Helper function to set animation and plotting options to be passed to
#' [animate_plot()] and [static_plot()].
#'
#' @param color_header Color of the header row.
#' @param color_other Color of the cells that are not highlighted otherwise.
#' @param color_missing Color of the missing cells.
#' @param color_fun A function that generates the colors for the highlighted
#' cells, default is [scales::brewer_pal()] Set1.
#' @param text_color Color of the text of the cells, default is a black or
#' white, based on the background color of the cell.
#' @param text_family Font family for the plot text, default is "Fira Mono". Use
#' [set_font_size()] to set global default font sizes.
#' @param title_family Font family for the plot title, default is "Fira Mono".
#' Use [set_font_size()] to set global default font sizes.
#' @param text_size Font size of the plot text, default is 5.
#' @param title_size Font size of the plot title, default is 17.
#' @param cell_width Width of a cell, default is 1.
#' @param cell_height Height of a cell, default is 1.
#' @param ease_default Default aes easing function. See [tweenr::display_ease()]
#' for more options. The tidyexplain default value is `sine-in-out`.
#' @param ease_other Additional aes easing options, specified as a named list.
#' List entries are named with the aesthetic to which the easeing should be
#' applied, consistent with [gganimate::ease_aes()]. E.g. `list(color =
#' "sine")`.
#' @param enter Enter fading function applied to objects in the animation. See
#' [gganimate::enter_exit] for a complete list of options. The tidyexplain
#' default is [gganimate::enter_fade()].
#' @param exit Exit fading function applied to objects in the animation. See
#' [gganimate::enter_exit] for a complete list of options. The tidyexplain
#' default is [gganimate::exit_fade()].
#' @inheritParams gganimate::transition_states
#' @export
anim_options <- function(
transition_length = NULL,
state_length = NULL,
ease_default = NULL,
ease_other = NULL,
enter = NULL,
exit = NULL,
text_family = NULL,
title_family = NULL,
text_size = NULL,
title_size = NULL,
color_header = NULL,
color_other = NULL,
color_missing = NULL,
color_fun = NULL,
text_color = NULL,
cell_width = NULL,
cell_height = NULL,
...
){
enter_name <- if (!missing(enter)) rlang::quo_name(rlang::enquo(enter))
exit_name <- if (!missing(exit)) rlang::quo_name(rlang::enquo(exit))
ao <- list(
transition_length = transition_length,
state_length = state_length,
ease_default = ease_default,
ease_other = ease_other,
enter = if (!is.null(enter)) setNames(list(enter), enter_name),
exit = if (!is.null(exit)) setNames(list(exit), exit_name),
text_family = text_family,
text_size = text_size,
title_family = title_family,
title_size = title_size,
color_header = color_header,
color_other = color_other,
color_missing = color_missing,
color_fun = color_fun,
text_color = text_color,
cell_width = cell_width,
cell_height = cell_height,
...
)
ao <- purrr::compact(ao)
structure(ao, class = "anim_opts")
}


# Global Animation Options Setters and Getters ----------------------------

#' @describeIn anim_options Set default animation options for the current session.
#' @param anim_opts An [anim_options()] options list.
#' @export
set_anim_options <- function(anim_opts = anim_options()) {
stopifnot(is.anim_opts(anim_opts))
ao_old <- plot_settings$anim_opts
plot_settings$anim_opts <- merge(anim_opts, plot_settings$anim_opts)
invisible(ao_old)
}

get_anim_opt <- function(anim_opt = NULL) {
if (is.null(anim_opt)) return(plot_settings$anim_opts)
if (anim_opt %in% c("text_size", "title_size")) rlang::abort(
"Use get_text_size() or get_title_size()"
)
plot_settings$anim_opts[[anim_opt]] %||% plot_settings$default[[anim_opt]]
}


# Animation Options Methods -----------------------------------------------

#' @export
print.anim_opts <- function(x) {
# Replace ggproto (enter/exit functions) with their names
if ("enter" %in% names(x)) x$enter <- paste("ggproto:", names(x$enter))
if ("exit" %in% names(x)) x$exit <- paste("ggproto:", names(x$exit))
anim_opts <- capture.output(str(x, no.list = TRUE))
cat(
paste0("<anim_options: ", length(x), " options>"),
anim_opts, sep = "\n"
)
}

#' @export
is.anim_opts <- function(ao) inherits(ao, "anim_opts")


# Fill, Validate, Merge Animation Options ---------------------------------

# Fills in default animation options
fill_anim_opts <- function(ao) {
ao$transition_length <- ao$transition_length %||% get_anim_opt("transition_length")
ao$state_length <- ao$state_length %||% get_anim_opt("state_length")
ao$ease_default <- ao$ease_default %||% get_anim_opt("ease_default")
ao$ease_other <- ao$ease_other %||% get_anim_opt("ease_other")
ao$enter <- ao$enter %||% get_anim_opt("enter")
ao$exit <- ao$exit %||% get_anim_opt("exit")
ao$text_family <- ao$text_family %||% get_anim_opt("text_family")
ao$title_family <- ao$title_family %||% get_anim_opt("title_family")
ao$color_header <- ao$color_header %||% get_anim_opt("color_header")
ao$color_other <- ao$color_other %||% get_anim_opt("color_other")
ao$color_missing <- ao$color_missing %||% get_anim_opt("color_missing")
ao$color_fun <- ao$color_fun %||% get_anim_opt("color_fun")
ao$text_color <- ao$text_color %||% get_anim_opt("text_color")
ao$cell_width <- ao$cell_width %||% get_anim_opt("cell_width")
ao$cell_height <- ao$cell_height %||% get_anim_opt("cell_height")

ao
}

validate_anim_opts <- function(ao, quiet = FALSE, strict = getOption("tidyexplain.strict_dots", FALSE)) {
if (!inherits(ao, "anim_opts")) {
rlang::warn("Use `anim_options()` to set `anim_opts`")
}
ao <- fill_anim_opts(ao)
stopifnot(is.ggproto(ao$enter[[1]]), is.ggproto(ao$exit[[1]]))
extra_names <- setdiff(names(ao), names(formals(anim_options)))
if (!quiet && length(extra_names)) {
extra_names <- paste0(sprintf("`%s`", extra_names), collapse = ", ")
msg <- paste("Unknown animation options will be ignored:", extra_names)
if (isTRUE(strict)) rlang::abort(msg) else rlang::warn(msg)
}
invisible(ao)
}

merge.anim_opts <- function(ao_new, ao_base = anim_options()) {
ao_new <- purrr::discard(ao_new, is.null)
ao_base <- purrr::discard(ao_base, is.null)
unique_base <- setdiff(names(ao_base), names(ao_new))
ao <- append(ao_new, ao_base[unique_base])
ao <- ao[names(formals(anim_options))]
ao <- purrr::discard(ao, is.null)
class(ao) <- "anim_opts"
ao
}


# Default Animation Options for Verb Families -----------------------------

default_anim_opts <- function(family, ao_custom = NULL) {
family_options <- c("join", "set", "gather", "spread")
family <- match.arg(family, family_options, several.ok = FALSE)
ao_default <- switch(
family,
"gather" = anim_options(enter = enter_fade(), exit = exit_fade(),
ease_default = "sine-in-out",
ease_other = list(y = "cubic-out", x = "cubic-in")),
"spread" = anim_options(enter = enter_fade(), exit = exit_fade(),
ease_default = "sine-in-out",
ease_other = list(y = "cubic-out", x = "cubic-in")),
anim_options()
)
if (is.null(ao_custom)) {
# User set globals override defaults
ao_custom <- get_anim_opt()
} else {
# Opts from function call override user-set globals
ao_custom <- merge(ao_custom, get_anim_opt())
}
# function > user-set global > default (> global default)
if (!is.null(ao_custom)) merge(ao_custom, ao_default) else ao_default
}

# Font Size Setters and Getters -------------------------------------------

#' Set Default Text Sizes for Animation Plots
#'
#' Sets the default text sizes for the animated and static plots produced by
#' this package during the current session.
#'
#' @param text_size Font size of value labels inside the data frame squares
#' @param title_size Font size of the function call or plot title
#' @export
set_font_size <- function(text_size = NULL, title_size = NULL) {
old <- list()
if (!is.null(text_size)) old$text_size <- set_text_size(text_size)
if (!is.null(title_size)) old$title_size <- set_title_size(title_size)
invisible(old)
}

#' @describeIn set_font_size Get current global font sizes
#' @export
get_font_size <- function() {
list("text_size" = get_text_size(), "title_size" = get_title_size())
}

set_text_size <- function(size) {
old <- plot_settings$text_size
set_anim_options(anim_options(text_size = size))
invisible(old)
}

set_title_size <- function(size) {
old <- plot_settings$title_size
set_anim_options(anim_options(title_size = size))
invisible(old)
}

get_text_size <- function(x = NULL) {
if (!is.null(x)) return(x)
plot_settings$anim_opts$text_size %||%
getFromNamespace("theme_void", "ggplot2")()$text$size %||%
plot_settings$default$text_size
}

get_title_size <- function(x = NULL) {
if (!is.null(x)) return(x)
plot_settings$anim_opts$title_size %||%
getFromNamespace("theme_void", "ggplot2")()$plot.title$size %||%
plot_settings$default$title_size
}

+ 133
- 0
R/animate_sets.R Zobrazit soubor

@@ -0,0 +1,133 @@
#' Animates a set operation
#'
#' Functions to visualise the set operations either static as a ggplot, or
#' dynamic as a gif.
#'
#' @param x the x dataset
#' @param y the y dataset
#' @param export the export type, either gif, first or last. The latter two
#' export ggplots of the first/last state of the join
#' @param type type of the set, i.e., intersect, setdiff, etc.
#' @param ... further argument passed to anim_options()
#'
#' @return either a gif or a ggplot
#'
#' @seealso \code{\link[dplyr]{setops}}
#'
#' @examples
#' x <- data_frame(x = c(1, 1, 2), y = c("a", "b", "a"))
#' y <- data_frame(x = c(1, 2), y = c("a", "b"))
#'
#' # Animate the first or last state of the set
#' animate_union(x, y, export = "first")
#' animate_union(x, y, export = "last")
#'
#' # animate the transition as a gif (default)
#' \donttest{
#' animate_union(x, y, export = "gif")
#' }
#'
#' # different options include
#' \donttest{
#' animate_union(x, y)
#' animate_union_all(x, y)
#' animate_intersect(x, y)
#' animate_setdiff(x, y)
#'
#' # further arguments can be passed to all animate_* functions
#' animate_union(
#' x, y,
#' text_size = 5, title_size = 25,
#' color_header = "black",
#' color_fun = viridis::viridis
#' )
#' }
#'
#' # Save the results
#' \dontrun{
#' # to save the ggplot, use
#' un <- animate_union(x, y, by = "id", export = "last")
#' ggsave("union.pdf", un)
#'
#' animate_union(x, y, by = "id", export = "gif")
#' # to save the gif, use
#' un <- animate_union(x, y, by = "id", export = "gif")
#' anim_save(un, "union.gif")
#' }
animate_set <- function(
x, y,
type = c("union", "union_all", "intersect", "setdiff"),
export = c("gif", "first", "last"),
...
) {
type <- match.arg(type)
export <- match.arg(export)
x_name <- get_input_text(x)
y_name <- get_input_text(y)
data <- make_named_data(x, y)

col_names <- purrr::map(data, names)

if (!all(names(data$x) %in% names(data$y)) && ncol(data$x) == ncol(data$y))
stop("x and y must have the same variables/column-names")

title <- sprintf(paste0(type, "(%s, %s)"), x_name, y_name)

if (type %in% c("union", "intersect", "setdiff")) {
data <- purrr::map(data, dplyr::distinct)
}

if (type == "union_all") {
ll <- process_join(data$x, data$y, by = names(data$x), fill = FALSE, ...)
ll <- purrr::map(ll, ~ mutate(., .id_long = paste(.id_long, .side, sep = "-")))
} else {
ll <- process_join(data$x, data$y, by = names(data$x), ...)
}

step0 <- bind_rows(ll$x, ll$y) %>% mutate(.frame = 0, .alpha = 1)

step1 <- move_together(ll$x, ll$y, type) %>% mutate(.frame = 1)

all <- bind_rows(step0, step1)

if (export == "gif") {
animate_plot(all, title, ...)
} else if (export == "first") {
title <- ""
static_plot(step0, title, ...)
} else if (export == "last") {
static_plot(step1, title, ...)
}
}

#' @rdname animate_set
#' @export
animate_union <- function(x, y, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_set(x, y, type = "union", export = export, ...)
}

#' @rdname animate_set
#' @export
animate_union_all <- function(x, y, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_set(x, y, type = "union_all", export = export, ...)
}

#' @rdname animate_set
#' @export
animate_intersect <- function(x, y, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_set(x, y, type = "intersect", export = export, ...)
}

#' @rdname animate_set
#' @export
animate_setdiff <- function(x, y, export = "gif", ...) {
x <- rlang::enquo(x)
y <- rlang::enquo(y)
animate_set(x, y, type = "setdiff", export = export, ...)
}

+ 123
- 0
R/animate_tidyr.R Zobrazit soubor

@@ -0,0 +1,123 @@
#' Animates the gather function
#'
#' @param w a data_frame in the wide format
#' @param key the key
#' @param value the value
#' @param ... further arguments passed to [tidyr::gather()], [process_wide()],
#' or [process_long()]
#' @param detailed boolean value if the animation should show one step for each
#' key value
#' @inheritParams animate_join
#' @inheritParams anim_options
#'
#' @return a gif or a ggplot
#' @export
#'
#' @examples
#' wide <- data_frame(
#' year = 2010:2011,
#' Alice = c(105, 110),
#' Bob = c(100, 97),
#' Charlie = c(90, 95)
#' )
#' animate_gather(wide, "person", "sales", -year, export = "first")
#' animate_gather(wide, "person", "sales", -year, export = "last")
#'
#' \donttest{
#' animate_gather(wide, "person", "sales", -year, export = "gif")
#' # if you want to have a less detailed animation, you can also use
#' animate_gather(wide, "person", "sales", -year, export = "gif", detailed = FALSE)
#' }
animate_gather <- function(w, key, value, ..., export = "gif", detailed = TRUE, anim_opts = anim_options()) {
anim_opts <- default_anim_opts("gather", anim_opts)
lhs <- w
rhs <- tidyr::gather(w, !!key, !!value, ...)

# construct the title sequence
wname <- deparse(substitute(w))
tidyr_selection <- get_quos_names(...)
ids <- setdiff(colnames(w), tidyselect::vars_select(colnames(w), ...))

id_string <- paste0(", ", paste(sprintf("%s", tidyr_selection), collapse = ", "))

sequence <- c(
current_state = "wide",
final_state = "long",
operation = sprintf("gather(%s, %s, %s%s)",
wname,
dput_parser(key),
dput_parser(value),
id_string),
reverse_operation = sprintf("spread(%s, %s, %s)",
"long",
dput_parser(key),
dput_parser(value))
)

key_values <- rhs %>% pull(key) %>% unique()
lhs_proc <- process_wide(lhs, ids, key, key_values, value, ...)
rhs_proc <- process_long(rhs, ids, key, value, ...)

gather_spread(lhs_proc, rhs_proc, sequence = sequence, key_values = key_values,
export = export, detailed = detailed, ..., anim_opts = anim_opts)
}


#' Animates the spread function
#'
#' @param l a data_frame in the long/tidy format
#' @param ... further arguments passed to [process_long] or [process_wide]
#' @inheritParams animate_gather
#' @inheritParams animate_join
#' @inheritParams anim_options
#'
#' @return a ggplot or a gif
#' @export
#'
#' @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)
#' )
#' animate_spread(long, key = "person", value = "sales", export = "first")
#' animate_spread(long, key = "person", value = "sales", export = "last")
#'
#' \donttest{
#' animate_spread(long, key = "person", value = "sales", export = "gif")
#' # if you want to have a less detailed animation, you can also use
#' animate_spread(long, key = "person", value = "sales", export = "gif", detailed = FALSE)
#' }
animate_spread <- function(l, key, value, export = "gif", detailed = TRUE, ..., anim_opts = anim_options()) {
anim_opts <- default_anim_opts("spread", anim_opts)

lhs <- l
rhs <- tidyr::spread(l, key = key, value = value)

# construct the title sequence
lname <- deparse(substitute(l))
ids <- names(lhs)
ids <- ids[!ids %in% c(key, value)]

id_string <- paste0(", ", paste(sprintf("-%s", ids), collapse = ", "))

sequence <- c(
current_state = "long",
final_state = "wide",
operation = sprintf("spread(%s, %s, %s)",
lname,
dput_parser(key),
dput_parser(value)),
reverse_operation = sprintf("gather(%s, %s, %s%s)",
"wide",
dput_parser(key),
dput_parser(value),
id_string)
)

lhs_proc <- process_long(lhs, ids, key, value, ...)
rhs_proc <- process_wide(rhs, ids, key, value, ...)

key_values <- lhs %>% pull(key) %>% unique()
gather_spread(lhs_proc, rhs_proc, sequence, key_values, export, detailed, ..., anim_opts = anim_opts)
}

+ 0
- 48
R/anti_join.R Zobrazit soubor

@@ -1,48 +0,0 @@
source(here::here("R/00_base_join.R"))

initial_join_dfs <- initial_join_dfs %>%
arrange(.x, .y) %>%
mutate(.obj = row_number(), .obj = .obj + 90 * as.integer(.id == "y"))

aj_step2 <- initial_join_dfs %>%
filter(.id == "x" | value %in% paste(1:2)) %>%
mutate(frame = 2,
.x = ifelse(.id == "y", 2.5, .x + 1.5),
alpha = case_when(
.x > 3 && .id == "x" ~ 0.5,
.y > -2.5 ~ 0.25,
TRUE ~ 1
))

aj_step3 <- aj_step2 %>%
filter(alpha == 1) %>%
mutate(frame = 3)

aj_step4 <- aj_step2 %>%
filter(alpha == 1) %>%
mutate(frame = 4, .y = -1)

aj <- bind_rows(
initial_join_dfs,
aj_step2,
aj_step3,
aj_step4
) %>%
mutate(
alpha = ifelse(is.na(alpha), 1, alpha),
.obj = ifelse(value == 4, 0, .obj)
) %>%
arrange(.obj, frame) %>%
plot_data("anti_join(x, y)") %>%
animate_plot(transition_length = c(2, 1, 2),
state_length = c(1, 0, 0, 1))

aj <- animate(aj)
anim_save(here::here("images", "anti-join.gif"), aj)

aj_g <- anti_join(x, y, by = "id") %>%
proc_data() %>%
mutate(.x = .x + 1.5) %>%
plot_data_join("anti_join(x, y)")

save_static_plot(aj_g, "anti-join")

+ 0
- 29
R/full_join.R Zobrazit soubor

@@ -1,29 +0,0 @@
source(here::here("R/00_base_join.R"))

fj_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)

fj_extra_blocks <- inner_join(x, y, "id") %>%
select(id) %>%
proc_data("y") %>%
mutate(frame = 2, .x = .x + 1)

fj <- initial_join_dfs %>%
bind_rows(fj_joined_df, fj_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)

fj_g <- full_join(x, y, "id") %>%
proc_data() %>%
mutate(.x = .x + 1) %>%
plot_data_join("full_join(x, y)", ylims = ylim(-4.5, -0.5))

save_static_plot(fj_g, "full-join")

+ 0
- 29
R/inner_join.R Zobrazit soubor

@@ -1,29 +0,0 @@
source(here::here("R/00_base_join.R"))

ij_joined_df <- inner_join(x, y, "id")
ij_joined_df <- bind_rows(
proc_data(ij_joined_df, "x"),
proc_data(ij_joined_df, "y")
) %>%
filter(!(label == "x" & .id == "y") & !(label == "y" & .id == "x")) %>%
mutate(frame = 2, .x = .x + 1)

ij <- bind_rows(
initial_join_dfs,
ij_joined_df
) %>%
mutate(removed = value %in% c("3", "4", "x3", "y4"),
removed = as.integer(removed)) %>%
arrange(desc(frame), removed, desc(.id)) %>%
plot_data("inner_join(x, y)") %>%
animate_plot()

ij <- animate(ij)
anim_save(here::here("images", "inner-join.gif"), ij)

ij_g <- inner_join(x, y, by = "id") %>%
proc_data() %>%
mutate(.x = .x + 1) %>%
plot_data_join("inner_join(x, y)")

save_static_plot(ij_g, "inner-join")

+ 0
- 28
R/intersect.R Zobrazit soubor

@@ -1,28 +0,0 @@
source(here::here("R/00_base_set.R"))

ins_df <- intersect(x,y)
ins_step2 <-
bind_rows(
proc_data_set(ins_df, "x"),
proc_data_set(ins_df, "y")
) %>%
filter(.y == -1) %>%
mutate(frame = 2, .x = .x + 1.5)

ins <-
initial_set_dfs %>%
bind_rows(ins_step2) %>%
arrange(desc(frame)) %>%
plot_data_set("intersect(x, y)") %>%
animate_plot()

ins <- animate(ins)

anim_save(here::here("images", "intersect.gif"), ins)

ins_g <- intersect(x, y) %>%
proc_data_set() %>%
mutate(.x = .x + 1.5) %>%
plot_data_set("intersect(x, y)")

save_static_plot(ins_g, "intersect")

+ 0
- 27
R/left_join.R Zobrazit soubor

@@ -1,27 +0,0 @@
source(here::here("R/00_base_join.R"))

lj_joined_dfs <- left_join(x, y, "id") %>%
proc_data("x") %>%
mutate(frame = 2, .x = .x + 1)

lj_extra_blocks <- inner_join(x, y, "id") %>%
select(id) %>%
proc_data("y") %>%
mutate(frame = 2, .x = .x + 1)

lj <- bind_rows(
initial_join_dfs,
lj_joined_dfs,
lj_extra_blocks
) %>%
mutate(color = ifelse(is.na(value), "#ffffff", color)) %>%
arrange(value) %>%
plot_data("left_join(x, y)") %>%
animate_plot()

lj <- animate(lj)
anim_save(here::here("images", "left-join.gif"), lj)

lj_g <- plot_data_join(lj_joined_dfs, "left_join(x, y)")

save_static_plot(lj_g, "left-join")

+ 0
- 71
R/left_join_extra.R Zobrazit soubor

@@ -1,71 +0,0 @@
source(here::here("R/00_base_join.R"))

y_extra <- bind_rows(y, data_frame(id = 2, y = "y5"))

# I manually linked objects together, it was late and this was easier...
anim_df <- tibble::tribble(
~.y, ~label, ~value, ~.x, ~.id, ~color, ~frame, ~obj,
-1L, "id", "1", 1, "x", "#E41A1C", 1, 1,
-2L, "id", "2", 1, "x", "#377EB8", 1, 2,
-2L, "id", "2", 1, "x", "#377EB8", 1, 3,
-3L, "id", "3", 1, "x", "#4DAF4A", 1, 4,
-1L, "x", "x1", 2, "x", "#d0d0d0", 1, 5,
-2L, "x", "x2", 2, "x", "#d0d0d0", 1, 6,
-3L, "x", "x3", 2, "x", "#d0d0d0", 1, 8,
-2L, "x", "x2", 2, "x", "#d0d0d0", 1, 7,
-1L, "id", "1", 4, "y", "#E41A1C", 1, 9,
-2L, "id", "2", 4, "y", "#377EB8", 1, 10,
-3L, "id", "4", 4, "y", "#984EA3", 1, 99,
-4L, "id", "2", 4, "y", "#377EB8", 1, 11,
-1L, "y", "y1", 5, "y", "#d0d0d0", 1, 12,
-2L, "y", "y2", 5, "y", "#d0d0d0", 1, 13,
-3L, "y", "y4", 5, "y", "#d0d0d0", 1, 98,
-4L, "y", "y5", 5, "y", "#d0d0d0", 1, 14,
-1L, "id", "1", 2, "x", "#E41A1C", 2, 1,
-2L, "id", "2", 2, "x", "#377EB8", 2, 2,
-3L, "id", "2", 2, "x", "#377EB8", 2, 3,
-4L, "id", "3", 2, "x", "#4DAF4A", 2, 4,
-1L, "x", "x1", 3, "x", "#d0d0d0", 2, 5,
-2L, "x", "x2", 3, "x", "#d0d0d0", 2, 6,
-3L, "x", "x2", 3, "x", "#d0d0d0", 2, 7,
-4L, "x", "x3", 3, "x", "#d0d0d0", 2, 8,
-1L, "y", "y1", 4, "x", "#d0d0d0", 2, 12,
-2L, "y", "y2", 4, "x", "#d0d0d0", 2, 13,
-3L, "y", "y5", 4, "x", "#d0d0d0", 2, 14,
-1L, "id", "1", 2, "y", "#E41A1C", 2, 9,
-2L, "id", "2", 2, "y", "#377EB8", 2, 10,
-3L, "id", "2", 2, "y", "#377EB8", 2, 11
)

lj_extra <- anim_df %>%
arrange(obj, frame) %>%
plot_data("left_join(x, y)") %>%
animate_plot()

lj_extra <- animate(lj_extra)
anim_save(here::here("images", "left-join-extra.gif"), lj_extra)

## Save static images
df_names <- data_frame(
.x = c(1.5, 4.5), .y = 0.25,
value = c("x", "y"),
size = 12,
color = "black"
)

g_input <- proc_data(y_extra) %>%
mutate(.x = .x + 3) %>%
bind_rows(proc_data(x)) %>%
plot_data() +
geom_text(data = df_names, family = "Fira Mono", size = 24) +
annotate("text", label = "↑ duplicate keys in y", x = 4.5, y = -4.75,
family = "Fira Sans", color = "grey45")

save_static_plot(g_input, "left-join-extra-input")

lj_g <- left_join(x, y_extra, by = "id") %>%
proc_data() %>%
mutate(.x = .x + 1) %>%
plot_data_join("left_join(x, y)", ylims = ylim(-4.5, -0.5))

save_static_plot(lj_g, "left-join-extra")

+ 105
- 0
R/move_together.R Zobrazit soubor

@@ -0,0 +1,105 @@

#' Combines two processed datasets and combines them for a given method
#'
#' @param lhs the left-hand side dataset
#' @param rhs the righ-hand side dataset
#' @param type a string of the desired combination method, allowed are all dplyr
#' joins or sets
#'
#' @return processed dataset of the combined values
#'
#' @examples
#' NULL
move_together <- function(lhs, rhs, type) {

all <- bind_rows(lhs, rhs)

# separate column and row-filter (ids)
x_cols <- dplyr::distinct(lhs, .col)
y_cols <- dplyr::distinct(rhs, .col)

# separate header columns from ids and treat them as columns
x_ids <- dplyr::distinct(lhs, .id, .id_long)
y_ids <- dplyr::distinct(rhs, .id, .id_long)

x_headers <- filter(x_ids, grepl("^\\.header", .id_long))
y_headers <- filter(y_ids, grepl("^\\.header", .id_long))

x_ids <- x_ids %>% filter(!grepl("^\\.header", .id_long))
y_ids <- y_ids %>% filter(!grepl("^\\.header", .id_long))

# assign two combiner functions depending on the type
# one for combining the columns (col_combiner)
# one for combining the rows (row_combiner)
if (type == "full_join") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::full_join
} else if (type == "inner_join") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::inner_join
} else if (type == "left_join") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::left_join
} else if (type == "right_join") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::right_join
} else if (type == "semi_join") {
col_combiner <- dplyr::left_join
row_combiner <- dplyr::semi_join
} else if (type == "anti_join") {
col_combiner <- dplyr::left_join
row_combiner <- dplyr::anti_join
} else if (type == "union") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::union
} else if (type == "union_all") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::union_all
} else if (type == "intersect") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::intersect
} else if (type == "setdiff") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::anti_join
} else if (type == "bind_rows") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::bind_rows
} else if (type == "bind_cols") {
col_combiner <- dplyr::full_join
row_combiner <- dplyr::left_join
} else {
stop("Unknown func")
}

take_cols <- col_combiner(x_cols, y_cols, by = ".col")
take_ids <- row_combiner(x_ids, y_ids, by = c(".id", ".id_long"))
take_headers <- col_combiner(x_headers, y_headers, by = c(".id", ".id_long"))

take_ids <- bind_rows(take_headers, take_ids)

take <- tidyr::crossing(take_ids, take_cols)

mid <- (2 + length(unique(lhs$.col)) + length(unique(rhs$.col))) / 2
xvals <- 1:nrow(take_cols)
xvals <- xvals - mean(xvals) + mid
names(xvals) <- pull(take_cols, .col)

yvals <- cumsum(ifelse(grepl("^\\.header", take_ids$.id_long), 0, -1))
names(yvals) <- pull(take_ids, .id_long)

take_vals <- semi_join(all, take %>% select(".id", ".col"),
by = c(".id", ".col")) %>%
mutate(.alpha = 1,
.x = xvals[.col],
.y = yvals[.id_long])

bind_rows(
# take,
take_vals,
# fade in place:
all %>% filter(!.id_long %in% take_ids$.id_long) %>% mutate(.alpha = 0),
# moving fade or fade in place as well:
all %>% filter(.id_long %in% take_ids$.id_long & !.col %in% take_cols$.col) %>%
mutate(.alpha = 0)
)
}

+ 76
- 0
R/plot_helpers.R Zobrazit soubor

@@ -0,0 +1,76 @@
#' Animate a Plot
#'
#' @param d a processed dataset
#' @param title the title of the plot
#' @param anim_opts Animation options generated with [anim_options()]. Overrides
#' any options set in `...`.
#' @return a `gganim` object
#' @examples
#' NULL
animate_plot <- function(
d,
title = "",
...,
anim_opts = anim_options(...)
) {
ao <- validate_anim_opts(anim_opts)
ease_opts <- if (!is.null(ao$ease_other)) {
ao$ease_other$default <- ao$ease_default
ao$ease_other
} else list(default = ao$ease_default)
ao_ease_aes <- do.call(ease_aes, ease_opts)

static_plot(d, title, anim_opts = ao) +
transition_states(.frame, ao$transition_length, ao$state_length) +
ao$enter[[1]] +
ao$exit[[1]] +
ao_ease_aes
}


#' Prints the tiles for a processed dataset statically
#'
#' @inheritParams animate_plot
#' @inheritDotParams anim_options
#'
#' @return a ggplot
#'
#' @examples
#' NULL
static_plot <- function(
d,
title = "",
...,
anim_opts = anim_options(...)
) {
ao <- validate_anim_opts(anim_opts)
text_size <- get_text_size(ao$text_size)
title_size <- get_title_size(ao$title_size)

if (!".alpha" %in% names(d)) d <- d %>% mutate(.alpha = 1)
if (!".textcolor" %in% names(d))
d <- d %>% mutate(.textcolor = choose_text_color(.color))

if (".id_long" %in% names(d)) {
d <- d %>% mutate(.item_id = paste(.id_long, .col, sep = "-"))
} else {
# tidyr
d <- d %>% mutate(.item_id = .id)
}

width <- ao$cell_width %||% 1
height <- ao$cell_height %||% 1

ggplot(d, aes(x = .x * width, y = .y * height, fill = .color, alpha = .alpha,
group = .item_id)) +
geom_tile(width = 0.9 * width, height = 0.9 * height) +
coord_equal() +
geom_text(data = d %>% filter(!is.na(.val)), aes(label = .val, color = .textcolor),
family = ao$text_family, size = text_size) +
scale_fill_identity() +
scale_color_identity() +
scale_alpha_identity() +
labs(title = title) +
theme_void() +
theme(plot.title = element_text(family = ao$title_family, hjust = 0.5, size = title_size))
}

+ 155
- 0
R/process_data_helpers.R Zobrazit soubor

@@ -0,0 +1,155 @@

#' Preprocess data
#'
#' @param x a left dataset
#' @param y a right dataset
#' @param by a by argument for joins / set operations
#' @param fill if missing ids should be filled
#' @param ... further arguments passed to add_color
#' @param ao anim_options()
#'
#' @return a preprocessed dataset
#'
#' @examples
#' NULL
process_join <- function(x, y, by, fill = TRUE, ...,
ao = anim_options(...)) {

#' test for
#' a <- c("unique", "mult", "mult", "also unique")
#' add_duplicate_number(a)
add_duplicate_number <- function(a) {
data_frame(v = a) %>%
group_by(v) %>%
mutate(id = paste(v, 1:n(), sep = "-")) %>%
pull(id)
}

x <- x %>%
tidyr::unite(dplyr::one_of(by), col = ".id", remove = FALSE) %>%
mutate(.id_long = add_duplicate_number(.id))

y <- y %>%
tidyr::unite(dplyr::one_of(by), col = ".id", remove = FALSE) %>%
mutate(.id_long = add_duplicate_number(.id))

ids <- dplyr::union(x %>% dplyr::select(.id, .id_long),
y %>% dplyr::select(.id, .id_long))

x_ <- process_data_join(x, ids, by, fill = fill, ao = ao)
y_ <- process_data_join(y, ids, by, fill = fill, ao = ao) %>%
mutate(.x = .x + ncol(x) - 1)

list(x = x_, y = y_)
}


#' Processes the data
#'
#' @param x a preprocessed dataset
#' @param ids a data_frame of ids (.id and .id_long)
#' @param by a vector of by-arguments
#' @param width the width of the tiles
#' @param side the side (x or y, lhs or rhs, etc)
#' @param fill if missing ids should be filled
#' @param ... further arguments passed to add_color
#' @param ao anim_options
#'
#' @return a data_frame including all necessary information
#'
#' @examples
#' NULL
process_data_join <- function(x, ids, by, width = 1, side = NA, fill = TRUE,
...,
ao = anim_options(...)) {
if (is.na(side)) side <- deparse(substitute(x))

x_names <- names(x)[grepl("^[^\\.]", names(x))]
x_keys <- 1:length(x_names)
names(x_keys) <- x_names

special_vars <- names(x)[grepl("^\\.", names(x))]

x <- x %>%
mutate(.r = row_number()) %>%
tidyr::gather_(key = ".col", value = ".val", names(x)[grepl("^[^.]", names(x))]) %>%
mutate(.x = x_keys[.col],
.y = -.r) %>%
bind_rows(data_frame(.id = ".header",
.id_long = paste(".header", x_names, sep = "_"),
.r = 0,
.col = x_names,
.val = x_names,
.x = x_keys, .y = 0), .) %>%
mutate(.width = width,
.side = side)

# if there are multiple values in the ids (-2, -3 etc) but they are not present
# in x, because it is in the second/other dataset, add these values here
id_long <- ids$.id_long
mis_ids <- id_long[!id_long %in% x$.id_long]
# if the missing value is a -1, that means the missing value comes not from
# missing dublicate ids
mis_ids <- mis_ids[grepl("[^-1]$", mis_ids)]
if (length(mis_ids) > 0 && fill) {
mis_ids_short <- gsub("-[0-9]+$", "", mis_ids)

# insert the missing ids at the right place
for (i in mis_ids_short) {
irow <- (1:nrow(x))[x$.id == i]
irow <- irow[1]
x <- bind_rows(
x %>% slice(1:irow),
x %>% filter(.id %in% mis_ids_short) %>% mutate(.id_long = mis_ids),
x %>% slice((irow + 1):nrow(x))
)
}
}

add_color_join(x, rev(ids$.id), by, ao)
}

#' Adds Color to a processed data_frame
#'
#' @param x a processed data_frame
#' @param ids a vector of ids for the color-matching
#' @param by a vector of column names that constitute the by-argument of joins/sets
#' @param color_header color for the header
#' @param color_other color for "inactive" values
#' @param color_missing color for missing values
#' @param color_fun the function to generate the colors
#' @param text_color the color for the text inside the tiles,
#' defaults to white/black depending on tile color
#' @param ...
#'
#' @return the processed data_frame with a new column .color
#'
#' @examples
#' NULL
add_color_join <- function(x, ids, by, ao, ...) {

color_header <- ao$color_header %||% get_anim_opt("color_header")
color_other <- ao$color_other %||% get_anim_opt("color_other")
color_missing <- ao$color_missing %||% get_anim_opt("color_missing")
color_fun <- ao$color_fun %||% get_anim_opt("color_fun")
text_color <- ao$text_color %||% get_anim_opt("text_color")

colors <- c(color_header, color_fun(length(ids)))
names(colors) <- c(".header", ids)

res <- x %>%
mutate(
.color = ifelse(is.na(.val),
color_missing,
ifelse(.col %in% by,
colors[.id],
color_other)),
.color = ifelse(.id == ".header", color_header, .color),
.textcolor = text_color)

if (is.na(text_color))
res <- res %>% mutate(.textcolor = choose_text_color(.color))

return(res)
}


+ 0
- 30
R/right_join.R Zobrazit soubor

@@ -1,30 +0,0 @@
source(here::here("R/00_base_join.R"))

rj_joined_dfs <- right_join(x, y, "id") %>%
proc_data("y") %>%
mutate(frame = 2, .x = .x + 1)

rj_extra_blocks <- inner_join(x, y, "id") %>%
select(id) %>%
proc_data("x") %>%
mutate(frame = 2, .x = .x + 1)

rj <- bind_rows(
initial_join_dfs,
rj_joined_dfs,
rj_extra_blocks
) %>%
filter(!is.na(value)) %>%
mutate(
.id = ifelse(label == "x", label, .id),
removed = as.integer(grepl("3", value))
) %>%
arrange(removed, value, .id, frame) %>%
plot_data("right_join(x, y)") %>%
animate_plot()

rj <- animate(rj)
anim_save(here::here("images", "right-join.gif"), rj)

rj_g <- plot_data(rj_joined_dfs, "right_join(x, y)")
save_static_plot(rj_g, "right-join")

+ 0
- 30
R/semi_join.R Zobrazit soubor

@@ -1,30 +0,0 @@
source(here::here("R/00_base_join.R"))

sj_joined_df <- semi_join(x, y, "id") %>%
proc_data("x") %>%
mutate(frame = 2, .x = .x + 1.5)

sj_extra_blocks <- inner_join(x, y, "id") %>%
select(id) %>%
proc_data("y") %>%
mutate(frame = 2, .x = .x + 1.5)

sj <- bind_rows(
initial_join_dfs,
sj_joined_df,
sj_extra_blocks
) %>%
arrange(value) %>%
plot_data("semi_join(x, y)") %>%
animate_plot()

sj <- animate(sj)
anim_save(here::here("images", "semi-join.gif"), sj)

# Static Images
sj_g <- semi_join(x, y, "id") %>%
proc_data() %>%
mutate(.x = .x + 1.5) %>%
plot_data_join("semi_join(x, y)")

save_static_plot(sj_g, "semi-join")

+ 0
- 102
R/setdiff.R Zobrazit soubor

@@ -1,102 +0,0 @@
source(here::here("R/00_base_set.R"))

# ---- setdiff(x, y) ----

# Dim elements unique to y
setd_step2 <- initial_set_dfs %>%
mutate(
frame = 2,
alpha = case_when(
.y == -1 ~ 0.55,
.id == "y" ~ 0.15,
TRUE ~ 1
)
)

# Merge, dim overlapping elements
setd_step3 <- initial_set_dfs %>%
filter(!(.id == "y" & .y == -2)) %>%
mutate(
frame = 3,
alpha = ifelse(.y == -1, 0.25, 1),
.x = ifelse(.id == "y", .x - 3, .x),
.x = .x + 1.5
)

# Result of setdiff
setd_step4 <- setdiff(x, y) %>%
proc_data_set("xy") %>%
mutate(frame = 4, .x = .x + 1.5)

setd <- bind_rows(
initial_set_dfs,
setd_step2,
setd_step3,
setd_step4
) %>%
mutate(alpha = ifelse(is.na(alpha), 1, alpha)) %>%
arrange(frame, desc(.y), desc(.id)) %>%
plot_data_set(., "setdiff(x, y)") %>%
animate_plot()

setd <- animate(setd)

anim_save(here::here("images", "setdiff.gif"), setd)

setd_g <- setdiff(x, y) %>%
proc_data_set() %>%
mutate(.x = .x + 1.5) %>%
plot_data_set("setdiff(x, y)")

save_static_plot(setd_g, "setdiff")


# ---- setdiff(y, x) ----

# Dim elements unique to x
setd2_step2 <- initial_set_dfs %>%
mutate(
frame = 2,
alpha = case_when(
.y == -1 ~ 0.55,
.id == "x" ~ 0.15,
TRUE ~ 1
)
)

# Merge, dim overlapping elements
setd2_step3 <- initial_set_dfs %>%
filter(!(.id == "x" & .y <= -2)) %>%
mutate(
frame = 3,
alpha = ifelse(.y == -1, 0.25, 1),
.x = ifelse(.id == "y", .x - 3, .x),
.x = .x + 1.5
)

# Result of setdiff
setd2_step4 <- setdiff(y, x) %>%
proc_data_set("xy") %>%
mutate(frame = 4, .x = .x + 1.5)

setd2 <- bind_rows(
initial_set_dfs,
setd2_step2,
setd2_step3,
setd2_step4
) %>%
mutate(alpha = ifelse(is.na(alpha), 1, alpha)) %>%
arrange(frame, desc(.y), .id) %>%
plot_data_set(., "setdiff(y, x)") %>%
animate_plot()

setd2 <- animate(setd2)

anim_save(here::here("images", "setdiff-rev.gif"), setd2)

setd2_g <- setdiff(x, y) %>%
proc_data_set() %>%
mutate(.x = .x + 1.5) %>%
plot_data_set("setdiff(y, x)")

save_static_plot(setd2_g, "setdiff-rev")

+ 355
- 0
R/tidyr_helpers.R Zobrazit soubor

@@ -0,0 +1,355 @@
#' 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 <- rlang::quos(...)
purrr::map_chr(q, rlang::quo_name)
}

#' 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) UseMethod("dput_parser")

dput_parser.character <- function(x) {
if (length(x) == 1) {
sprintf('"%s"', x)
} else {
x <- capture.output(dput(x))
paste(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 = 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 TODO
#'
#' @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(dplyr::one_of(ids))
id_values <- id_values %>% tidyr::gather(key = ".key_map", value = ".id_map")

x <- x %>% mutate(.r = row_number()) %>%
tidyr::unite(dplyr::one_of(ids), col = ".id_map", remove = F)

x <- x %>%
gather(key = ".col", value = ".val", names(x)[grepl("^[^\\.]", names(x))]) %>%
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),
.header = F)

# make sure that we have one id value per key
tmp <- x %>% filter(.key_map %in% ids)
x <- bind_rows(
left_join(tmp %>% select(-.key_map),
tmp %>% select(.id_map) %>% tidyr::crossing(.key_map = key_values),
by = ".id_map"),
x %>% filter(!.key_map %in% ids)
)

# add header:
crosser <- tidyr::crossing(.id_map = as.character(id_values$.id_map),
.key_map = key_values)
key_header <- data_frame(
.key_map = key_values,
.r = 0,
.col = key_values,
.val = key_values,
.type = "key",
.x = length(ids) + 1:length(key_values),
.y = 0,
.header = TRUE) %>%
left_join(crosser, by = ".key_map")

id_header <- left_join(
data_frame(.id_map = ids,
.r = 0,
.col = ids,
.val = ids,
.type = "id",
.x = 1:length(ids),
.y = 0,
.header = TRUE),
tidyr::crossing(.id_map = ids, .key_map = key_values),
by = ".id_map"
)

x <- bind_rows(id_header, key_header, x)

x <- x %>% tidyr::unite(.key_map, .id_map, .val, col = ".id", remove = F)

x %>%
add_color_tidyr(key_values = key_values) %>%
mutate(.alpha = ifelse(.header == TRUE, 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 TODO
#'
#' @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)
xn <- names(x)

x <- x %>% mutate(.r = row_number()) %>%
tidyr::unite(ids, col = ".id_map", remove = F) %>%
tidyr::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(ids, key, value)

x_dict <- 1:nc
names(x_dict) <- xn

x <- x %>%
tidyr::gather(key = ".col", value = ".val", names(x)[grepl("^[^\\.]", names(x))]) %>%
mutate(
.x = x_dict[.col],
.y = -rep(1:nr, nc),
.type = type_dict[.col],
.val = as.character(.val),
.header = FALSE
)

# add headers:

id_headers <- tidyr::crossing(.id_map = ids, # x$.id_map %>% unique()
.key_map = key_values,
) %>%
mutate(
.r = 0,
.col = "id",
.val = .id_map,
.x = x_dict[.val],
.y = 0,
.type = "id",
.header = TRUE
)

x <- x %>%
dplyr::add_row(
.before = T,
.id_map = c(rep("key", length(key)), rep("value", length(value))),
.key_map = c(rep("key", length(key)), rep("value", length(value))),
.r = 0,
.col = c(rep("key", length(key)), rep("value", length(value))),
.val = c(key, value),
.x = length(ids) + 1:length(c(key, value)),
.y = 0,
.type = c(rep("key", length(key)), rep("value", length(value))),
.header = TRUE
)

x <- bind_rows(id_headers, x)

x <- x %>%
tidyr::unite(.key_map, .id_map, .val, col = ".id", remove = F)

x %>% add_color_tidyr(key_values = key_values) %>%
mutate(.alpha = ifelse(.header == TRUE, 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, ...,
anim_opts = anim_options(...)) {
# 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?

if (!detailed) {
anim_df <- bind_rows(
lhs %>% mutate(.frame = 0),
rhs %>% mutate(.frame = 1)
)
frame_labels <- c(sequence[["operation"]], sequence[["reverse_operation"]])

title_string <- "{ifelse(transitioning, previous_state, ifelse(grepl('gather', next_state), 'Wide', 'Long'))}"

tl <- 2
sl <- 1

} else {
xshift <- 2

rhs <- rhs %>% mutate(.x = .x + max(lhs$.x) + xshift)
# the header rows
header_start <- lhs %>% filter(.header == TRUE, !.key_map %in% key_values)
header_end <- rhs %>% filter(.header == TRUE)

state_start <- lhs %>% mutate(.frame = 0)
state_end <- rhs %>% mutate(.frame = length(key_values) + 2)

step_0 <- lhs %>% mutate(.frame = 1)
# for each unique key-value move the respective entries
keys_remaining <- lhs %>% filter(.key_map %in% key_values)
keys_shifted <- lhs[0, ]
key_steps <- lhs[0, ]
f <- 1
ids_remaining <- lhs %>% filter(.type == "id" & .header == FALSE)

for (keyval in key_values) {
f <- f + 1
move_rhs <- rhs %>% filter(.key_map == keyval)

keys_remaining <- keys_remaining %>% filter(.key_map != keyval)

if (keyval == key_values[length(key_values)]) {
header_start <- NULL
}
hd <- header_end %>% filter(.key_map == keyval |
(.type %in% c("key", "value") &
.col %in% c("key", "value")))
keys_shifted <- bind_rows(keys_shifted, move_rhs)
round_n <- bind_rows(header_start, hd,
keys_remaining, keys_shifted) %>%
mutate(.frame = f)

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)}"

tl <- length(unique(anim_df$.frame)) * 2
sl <- 1
}

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, anim_opts = anim_opts)
} else if (export == "first") {
static_plot(state_start, anim_opts = anim_opts) #....
} else if (export == "last") {
static_plot(state_end, anim_opts = anim_opts) #....
}

# open issues: ... doesnt work properly.
# especially if the id-arguments are passed in the gather-style, i.e., -year, or year:var
}

+ 0
- 97
R/tidyr_spread_gather.R Zobrazit soubor

@@ -1,97 +0,0 @@
source(here::here("R", "00_base_tidyr.R"))

sg_wide <- wide %>%
proc_data("0-wide", colorize_wide_tidyr) %>%
mutate(frame = 1, .id = "0-wide")

sg_long <- wide %>%
tidyr::gather("key", "val", -id) %>%
proc_data("3-tall", color_fun = function(x, y) x) %>%
split(.$label)

sg_long$id <-
sg_wide %>%
filter(label == "id") %>%
select(value, color) %>%
left_join(sg_long$id, ., by = "value") %>%
mutate(alpha = 1)

sg_long$key <-
sg_wide %>%
filter(label != "id") %>%
select(label, color) %>%
left_join(sg_long$key, ., by = c("value" = "label")) %>%
distinct() %>%
mutate(alpha = 1)

sg_long$val <-
sg_wide %>%
filter(label != "id", .y < 0) %>%
select(value, color) %>%
left_join(sg_long$val, ., by = "value") %>%
mutate(alpha = 0.6)

sg_long <- bind_rows(sg_long) %>% mutate(frame = 2)

sg_long_labels <- data_frame(id = 1, a = "id", x = "key", y = "val") %>%
proc_data("4-label") %>%
filter(label != "id") %>%
mutate(color = "#FFFFFF", .y = 0, .x = .x -1, frame = 2, alpha = 1, label = recode(label, "a" = "id"))

sg_wide_labels <- data_frame(id = 1, a = "id") %>%
proc_data("2-label") %>%
filter(label != "id") %>%
mutate(color = "#FFFFFF", .y = 0, .x = .x -1, frame = 1, alpha = 1, label = recode(label, "a" = "id"))

sg_long_extra_keys <- map_dfr(
seq_len(nrow(wide) - 1),
~ filter(sg_wide, .y > -1) # Extra key blocks in long column
)

n_key_cols <- length(setdiff(colnames(wide), "id"))

sg_long_extra_id <- map_dfr(
seq_len(n_key_cols - 1),
~ filter(sg_wide, .x == 1) # Extra id column blocks for long column
)

sg_data <- bind_rows(
sg_wide,
sg_wide_labels,
sg_long,
sg_long_labels,
sg_long_extra_keys,
sg_long_extra_id
) %>%
mutate(
label = ifelse(value %in% setdiff(colnames(wide), "id"), "key", label),
label = ifelse(value %in% c("key", "val"), "zzz", label),
.text_color = ifelse(grepl("label", .id), "black", "white"),
.text_size = ifelse(grepl("label", .id), 8, 12)
) %>%
arrange(label, .id, value) %>%
mutate(frame = factor(frame, labels = c('spread(long, key, val)', 'gather(wide, key, val, x:z)'))) %>%
select(.x, .y, everything())

sg_static <-
sg_data %>%
split(.$frame) %>%
imap(~ plot_data(.x, .y) +
ylim(-6.5, 0.5) +
labs(subtitle = "returns") +
theme(plot.subtitle = element_text(family = "Fira Sans", size = 14, color = "grey50", hjust = 0.5, margin = margin(25)))
)

save_static_plot(sg_static[[1]], "tidyr-spread")
save_static_plot(sg_static[[2]], "tidyr-gather")

sg_anim <-
sg_data %>%
plot_data() %>%
animate_plot() +
view_follow() +
labs(title = "{ifelse(transitioning, next_state, ifelse(grepl('gather', next_state), 'long', 'wide'))}") +
ease_aes("sine-in-out", x = "exponential-out")

sg_anim <- animate(sg_anim)
anim_save(here::here("images", "tidyr-spread-gather.gif"), sg_anim)

+ 0
- 42
R/union.R Zobrazit soubor

@@ -1,42 +0,0 @@
source(here::here("R/00_base_set.R"))

# ---- union(x, y) ----
uxy <- bind_rows(
initial_set_dfs,
union(x, y) %>% proc_data_set("xy") %>% mutate(frame = 2, .x = .x + 1.5),
intersect(x, y) %>% proc_data_set("xy") %>% mutate(frame = 2, .y = -4, .x = .x + 1.5)
) %>%
plot_data_set("union(x, y)", ylims = ylim(-4.5, -0.5)) %>%
animate_plot()

uxy <- animate(uxy)

anim_save(here::here("images", "union.gif"), uxy)

uxy_g <- union(x, y) %>%
proc_data_set() %>%
mutate(.x = .x + 1.5) %>%
plot_data_set("union(x, y)", ylims = ylim(-0.5, -4.5))

save_static_plot(uxy_g, "union")


# ---- union(y, x) ----
uyx <- bind_rows(
initial_set_dfs,
union(y, x) %>% proc_data_set("xy") %>% mutate(frame = 2, .x = .x + 1.5),
intersect(y, x) %>% proc_data_set("xy") %>% mutate(frame = 2, .y = -4, .x = .x + 1.5)
) %>%
plot_data_set("union(y, x)", ylims = ylim(-4.5, -0.5)) %>%
animate_plot()

uyx <- animate(uyx)

anim_save(here::here("images", "union-rev.gif"), uyx)

uyx_g <- union(y, x) %>%
proc_data_set() %>%
mutate(.x = .x + 1.5) %>%
plot_data_set("union(y, x)", ylims = ylim(-4.5, -0.5))

save_static_plot(uyx_g, "union-rev")

+ 0
- 23
R/union_all.R Zobrazit soubor

@@ -1,23 +0,0 @@
source(here::here("R/00_base_set.R"))

ua <- bind_rows(
initial_set_dfs,
initial_set_dfs %>% mutate(frame = 2, .y = ifelse(.id == "y", .y - 3, .y)), # fly y down
proc_data_set(x, "ux") %>% mutate(frame = 3, .x = .x + 1.5), # merge
proc_data_set(y, "uy") %>% mutate(frame = 3, .x = .x + 1.5, .y = .y - 3), # un-merge
initial_set_dfs %>% mutate(frame = 4, .y = ifelse(.id == "y", .y - 3, .y)) # fly y up
) %>%
arrange(desc(frame)) %>%
plot_data_set("union_all(x, y)", ylims = ylim(-5.5, -0.5)) +
transition_states(frame, 1, c(1, 0, 1, 0))

ua <- animate(ua)

anim_save(here::here("images", "union-all.gif"), ua)

ua_g <- union_all(x, y) %>%
proc_data_set() %>%
mutate(.x = .x + 1.5) %>%
plot_data_set("union_all(x, y)", ylims = ylim(-5.5, -0.5))

save_static_plot(ua_g, "union-all")

+ 11
- 0
R/utils-pipe.R Zobrazit soubor

@@ -0,0 +1,11 @@
#' Pipe operator
#'
#' See \code{magrittr::\link[magrittr]{\%>\%}} for details.
#'
#' @name %>%
#' @rdname pipe
#' @keywords internal
#' @export
#' @importFrom magrittr %>%
#' @usage lhs \%>\% rhs
NULL

+ 26
- 0
R/utils.R Zobrazit soubor

@@ -0,0 +1,26 @@
`%||%` <- function(x, y) if (is.null(x)) y else x

choose_text_color <- function(x, black = "#000000", white = "#FFFFFF") {
# x = color_hex
color_rgb <- col2rgb(x)
# modified from https://stackoverflow.com/a/3943023/2022615
# following W3 guidelines: https://www.w3.org/TR/WCAG20/#relativeluminancedef
color_rgb <- color_rgb / 255
color_rgb[color_rgb <= 0.03928] <- color_rgb[color_rgb <= 0.03928]/12.92
color_rgb[color_rgb > 0.03928] <- ((color_rgb[color_rgb > 0.03928] + 0.055)/1.055)^2.4
lum <- t(color_rgb) %*% c(0.2126, 0.7152, 0.0722)
lum <- lum[,1]
# threshold is supposed to be 0.179 but 1/3 seems to work better for our plots
ifelse(lum > 1/3, black, white)
}

get_input_text <- function(x) {
if (!rlang::is_quosure(x)) x <- rlang::enquo(x)
rlang::quo_name(x)
}

make_named_data <- function(x, y, data_names = c("x", "y")) {
ll <- rlang::eval_tidy(rlang::quo(list(!!x, !!y)))
names(ll) <- data_names
ll
}

+ 27
- 0
R/zzzz-package.R Zobrazit soubor

@@ -0,0 +1,27 @@
#' @importFrom dplyr left_join right_join full_join inner_join semi_join anti_join
#' @importFrom dplyr mutate select filter arrange bind_rows bind_cols group_by pull slice data_frame row_number
#' @importFrom tidyr gather spread
#' @keywords internal
"_PACKAGE"

plot_settings <- new.env(parent = emptyenv())
plot_settings$default <- list(
transition_length = 2,
state_length = 1,
ease_default = "sine-in-out",
ease_other = NULL,
enter = setNames(list(enter_fade()), "enter_fade()"),
exit = setNames(list(exit_fade()), "exit_fade()"),
text_family = "Fira Mono",
title_family = "Fira Mono",
text_size = 5,
title_size = 17,
color_header = "#737373",
color_other = "#d0d0d0",
color_missing = "#ffffff",
color_fun = scales::brewer_pal(type = "qual", "Set1"),
text_color = NA,
cell_width = 1,
cell_height = 1
)


+ 142
- 162
README.Rmd Zobrazit soubor

@@ -1,5 +1,7 @@
---
output: github_document
editor_options:
chunk_output_type: console
---

<!-- README.md is generated from README.Rmd. Please edit that file -->
@@ -8,47 +10,45 @@ output: github_document
knitr::opts_chunk$set(
collapse = TRUE,
comment = "#>",
echo = FALSE,
echo = TRUE,
warning = FALSE,
message = FALSE,
fig.path = "man/figures/tidyexplain-",
cache = TRUE
)
library(tidyexplain)
set_font_size(11, 26)
```

[gganimate]: https://github.com/thomasp85/gganimate#README
[dplyr-two-table]: https://dplyr.tidyverse.org/articles/two-table.html
[r4ds]: http://r4ds.had.co.nz/
[r4ds-relational]: http://r4ds.had.co.nz/relational-data.html
[r4ds-set-ops]: http://r4ds.had.co.nz/relational-data.html#set-operations
[r4ds-tidy-data]: http://r4ds.had.co.nz/tidy-data.html#tidy-data-1
[tidyverse]: https://tidyverse.org
[tidyr]: https://tidyr.tidyverse.org
[r4ds-set-ops]: http://r4ds.had.co.nz/relation-data.html#set-operations

# Tidy Animated Verbs

Garrick Aden-Buie -- [&commat;grrrck](https://twitter.com/grrrck) -- [garrickadenbuie.com](https://www.garrickadenbuie.com). Set operations contributed by [Tyler Grant Smith](https://github.com/TylerGrantSmith).
Garrick Aden-Buie -- [&commat;grrrck](https://twitter.com/grrrck) -- [garrickadenbuie.com](https://www.garrickadenbuie.com).

David Zimmermann -- [&commat;dav_zim](https://twitter.com/dav_zim) -- [datashenanigan.wordpress.com](https://datashenanigan.wordpress.com/)

Set operations contributed by [Tyler Grant Smith](https://github.com/TylerGrantSmith).

[![Binder](http://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/gadenbuie/tidy-animated-verbs/master?urlpath=rstudio)
[![CC0](https://img.shields.io/badge/license_(images)_-CC0-green.svg)](https://creativecommons.org/publicdomain/zero/1.0/)
[![MIT](https://img.shields.io/badge/license_(code)_-MIT-green.svg)](https://opensource.org/licenses/MIT)

- [**Mutating Joins**](#mutating-joins) — [`inner_join()`](#inner-join), [`left_join()`](#left-join),
[`right_join()`](#right-join), [`full_join()`](#full-join)
- [**Filtering Joins**](#filtering-joins) — [`semi_join()`](#semi-join), [`anti_join()`](#anti-join)
- Mutating Joins: [`inner_join()`](#inner-join), [`left_join()`](#left-join),
[`right_join()`](#right-join), [`full_join()`](#full-join)
- Filtering Joins: [`semi_join()`](#semi-join), [`anti_join()`](#anti-join)

- [**Set Operations**](#set-operations) — [`union()`](#union), [`union_all()`](#union-all), [`intersect()`](#intersect), [`setdiff()`](#setdiff)
- Set Operations: [`union()`](#union), [`union_all()`](#union-all), [`intersect()`](#intersect), [`setdiff()`](#setdiff)

- [**Tidy Data**](#tidy-data) — [`spread()` and `gather()`](#spread-and-gather)
- Tidyr Operations: [`gather()`](#gather), [`spread()`](#spread)

- Learn more about
- [Using the animations and images](#usage)
- [Relational Data](#relational-data)
- [gganimate](#gganimate)
## Background

### Usage

Please feel free to use these images for teaching or learning about action verbs from the [tidyverse](https://tidyverse.org).
You can directly download the [original animations](images/) or static images in [svg](images/static/svg/) or [png](images/static/png/) formats, or you can use the [scripts](R/) to recreate the images locally.
@@ -56,46 +56,35 @@ You can directly download the [original animations](images/) or static images in
Currently, the animations cover the [dplyr two-table verbs][dplyr-two-table] and I'd like to expand the animations to include more verbs from the tidyverse.
[Suggestions are welcome!](https://github.com/gadenbuie/tidy-animated-verbs/issues)

### Relational Data

The [Relational Data][r4ds-relational] chapter of the
[R for Data Science][r4ds] book by Garrett Grolemund and Hadley Wickham
is an excellent resource for learning more about relational data.
## Installing

The [dplyr two-table verbs vignette][dplyr-two-table]
and Jenny Bryan's [Cheatsheet for dplyr join functions](http://stat545.com/bit001_dplyr-cheatsheet.html)
are also great resources.
The in-development version of `tidyexplain` can be installed with `devtools`:

### gganimate
```r
# install.package("devtools")
devtools::install_github("gadenbuie/tidy-animated-verbs")

The animations were made possible by the newly re-written [gganimate] package by
[Thomas Lin Pedersen](https://github.com/thomasp85)
(original by [Dave Robinson](https://github.com/dgrtwo)).
The [package readme][gganimate] provides an excellent (and quick) introduction to gganimte.
library(tidyexplain)
```

## Mutating Joins

> A mutating join allows you to combine variables from two tables. It first matches observations by their keys, then copies across variables from one table to the other.
> [R for Data Science: Mutating joins](http://r4ds.had.co.nz/relational-data.html#mutating-joins)

```{r intial-dfs}
source("R/00_base_join.R")
df_names <- data_frame(
.x = c(1.5, 4.5), .y = 0.25,
value = c("x", "y"),
size = 12,
color = "black"
x <- dplyr::data_frame(
id = 1:3,
x = paste0("x", 1:3)
)

g <- plot_data(initial_join_dfs) +
geom_text(data = df_names, family = "Fira Mono", size = 24)
y <- dplyr::data_frame(
id = (1:4)[-3],
y = paste0("y", (1:4)[-3])
)

save_static_plot(g, "original-dfs")
animate_full_join(x, y, by = c("id"), export = "first")
```

<img src="images/static/png/original-dfs.png" width="480px" />

```{r echo=TRUE}
```{r}
x
y
```
@@ -105,13 +94,12 @@ y
> All rows from `x` where there are matching values in `y`, and all columns from `x` and `y`.

```{r inner-join}
source("R/inner_join.R")
animate_inner_join(x, y, by = "id")
```

![](images/inner-join.gif)

```{r echo=TRUE}
inner_join(x, y, by = "id")
```{r}
dplyr::inner_join(x, y, by = "id")
```

### Left Join
@@ -119,13 +107,12 @@ inner_join(x, y, by = "id")
> All rows from `x`, and all columns from `x` and `y`. Rows in `x` with no match in `y` will have `NA` values in the new columns.

```{r left-join}
source("R/left_join.R")
animate_left_join(x, y, by = "id")
```

![](images/left-join.gif)

```{r echo=TRUE}
left_join(x, y, by = "id")
```{r}
dplyr::left_join(x, y, by = "id")
```

### Left Join (Extra Rows in y)
@@ -133,14 +120,14 @@ left_join(x, y, by = "id")
> ... If there are multiple matches between `x` and `y`, all combinations of the matches are returned.

```{r left-join-extra}
source("R/left_join_extra.R")
```
y_extra <- dplyr::bind_rows(y, dplyr::data_frame(id = 2, y = "y5"))
y_extra # has multiple rows with the key from `x`

![](images/left-join-extra.gif)
animate_left_join(x, y_extra, by = "id", title_size = 22)
```

```{r echo=TRUE}
y_extra # has multiple rows with the key from `x`
left_join(x, y_extra, by = "id")
```{r}
dplyr::left_join(x, y_extra, by = "id")
```

### Right Join
@@ -148,13 +135,12 @@ left_join(x, y_extra, by = "id")
> All rows from y, and all columns from `x` and `y`. Rows in `y` with no match in `x` will have `NA` values in the new columns.

```{r right-join}
source("R/right_join.R")
animate_right_join(x, y, by = "id")
```

![](images/right-join.gif)

```{r echo=TRUE}
right_join(x, y, by = "id")
```{r}
dplyr::right_join(x, y, by = "id")
```

### Full Join
@@ -162,34 +148,27 @@ right_join(x, y, by = "id")
> All rows and all columns from both `x` and `y`. Where there are not matching values, returns `NA` for the one missing.

```{r full-join}
source("R/full_join.R")
animate_full_join(x, y, by = "id")
```

![](images/full-join.gif)

```{r echo=TRUE}
full_join(x, y, by = "id")
```{r}
dplyr::full_join(x, y, by = "id")
```

## Filtering Joins

> Filtering joins match observations in the same way as mutating joins, but affect the observations, not the variables.
> ... Semi-joins are useful for matching filtered summary tables back to the original rows.
> ... Anti-joins are useful for diagnosing join mismatches.
> [R for Data Science: Filtering Joins](http://r4ds.had.co.nz/relational-data.html#filtering-joins)

### Semi Join

> All rows from `x` where there are matching values in `y`, keeping just columns from `x`.

```{r semi-join}
source("R/semi_join.R")
animate_semi_join(x, y, by = "id")
```

![](images/semi-join.gif)

```{r echo=TRUE}
semi_join(x, y, by = "id")
```{r}
dplyr::semi_join(x, y, by = "id")
```

### Anti Join
@@ -197,45 +176,31 @@ semi_join(x, y, by = "id")
> All rows from `x` where there are not matching values in `y`, keeping just columns from `x`.

```{r anti-join}
source("R/anti_join.R")
animate_anti_join(x, y, by = "id")
```

![](images/anti-join.gif)

```{r echo=TRUE}
anti_join(x, y, by = "id")
```{r}
dplyr::anti_join(x, y, by = "id")
```

## Set Operations

> Set operations are occasionally useful when you want to break a single complex filter into simpler pieces.
> All these operations work with a complete row, comparing the values of every variable.
> These expect the x and y inputs to have the same variables, and treat the observations like sets.
> [R for Data Science: Set operations](http://r4ds.had.co.nz/relational-data.html#set-operations)

```{r intial-dfs-so}
source("R/00_base_set.R")
df_names <- data_frame(
.x = c(2.5, 5.5), .y = 0.25,
value = c("x", "y"),
size = 12,
color = "black"
x <- dplyr::data_frame(
x = c(1, 1, 2),
y = c("a", "b", "a")
)
y <- dplyr::data_frame(
x = c(1, 2),
y = c("a", "b")
)

g <- plot_data_set(initial_set_dfs, "", NULL, NULL) +
geom_text(data = df_names, family = "Fira Mono", size = 24)

save_static_plot(g, "original-dfs-set-ops")
```

```{r remove-set-ops-ids}
x <- x %>% select(-id)
y <- y %>% select(-id)
animate_union(x, y, export = "first")
```

<img src="images/static/png/original-dfs-set-ops.png" width="480px" />

```{r echo=TRUE}
```{r}
x
y
```
@@ -245,20 +210,20 @@ y
> All unique rows from `x` and `y`.

```{r union}
source("R/union.R")
<<remove-set-ops-ids>>
animate_union(x, y)
```

![](images/union.gif)

```{r echo=TRUE}
union(x, y)
```{r}
dplyr::union(x, y)
```

![](images/union-rev.gif)

```{r echo=TRUE}
union(y, x)

```{r union-y-x}
animate_union(y, x)

dplyr::union(y, x)
```

### Union All
@@ -266,15 +231,13 @@ union(y, x)
> All rows from `x` and `y`, keeping duplicates.

```{r union-all}
source("R/union_all.R")
<<remove-set-ops-ids>>
animate_union_all(x, y)
```

![](images/union-all.gif)


```{r echo=TRUE}
union_all(x, y)
```{r}
dplyr::union_all(x, y)
```


@@ -283,14 +246,12 @@ union_all(x, y)
> Common rows in both `x` and `y`, keeping just unique rows.

```{r intersect}
source("R/intersect.R")
<<remove-set-ops-ids>>
animate_intersect(x, y)
```

![](images/intersect.gif)

```{r echo=TRUE}
intersect(x, y)
```{r}
dplyr::intersect(x, y)
```

### Set Difference
@@ -298,72 +259,91 @@ intersect(x, y)
> All rows from `x` which are not also rows in `y`, keeping just unique rows.

```{r setdiff}
source("R/setdiff.R")
<<remove-set-ops-ids>>
animate_setdiff(x, y)
```

![](images/setdiff.gif)

```{r echo=TRUE}
setdiff(x, y)
```{r}
dplyr::setdiff(x, y)
```

![](images/setdiff-rev.gif)

```{r echo=TRUE}
setdiff(y, x)
```{r setdiff-y-x}
animate_setdiff(y, x)

dplyr::setdiff(y, x)
```

## Tidy Data
## Tidy Data and `gather()`, `spread()` functionality

[Tidy data][r4ds-tidy-data] follows the following three rules:
[Tidy data](http://r4ds.had.co.nz/tidy-data.html#tidy-data-1) follows
the following three rules:

1. Each variable has its own column.
1. Each observation has its own row.
1. Each value has its own cell.
1. Each variable has its own column.
2. Each observation has its own row.
3. Each value has its own cell.

Many of the tools in the [tidyverse] expect data to be formatted as a tidy dataset and the [tidyr] package provides functions to help you organize your data into tidy data.
Many of the tools in the [tidyverse](https://tidyverse.org) expect data
to be formatted as a tidy dataset and the
[tidyr](https://tidyr.tidyverse.org) package provides functions to help
you organize your data into tidy data.

```{r tidyr-wide-long}
source("R/tidyr_spread_gather.R")
```{r}
long <- dplyr::data_frame(
year = c(2010, 2011, 2010, 2011, 2010, 2011),
person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"),
sales = c(105, 110, 100, 97, 90, 95)
)
wide <- dplyr::data_frame(
year = 2010:2011,
Alice = c(105, 110),
Bob = c(100, 97),
Charlie = c(90, 95)
)
```

tidy_plots <- list()
tidy_plots$wide <- bind_rows(sg_wide, sg_wide_labels)
tidy_plots$long <- bind_rows(sg_long, sg_long_labels)
### Gather

tidy_plots <- map(tidy_plots, ~ mutate(.,
.text_color = ifelse(grepl("id|key|val", value), "black", "white"),
.text_size = ifelse(grepl("id|key|val", value), 6, 10)
)) %>%
imap(~ plot_data(.x, .y))
> Gather takes multiple columns and collapses into key-value pairs, duplicating all other columns as needed. You use gather() when you notice that your column names are not names of variables, but values of a variable.

tidy_plots$wide <- tidy_plots$wide + ylim(-6.5, 0.5)
```{r gather}
set_font_size(4, 15)
set_anim_options(anim_options(cell_width = 2))
animate_gather(wide, key = "person", value = "sales", -year)
```

save_static_plot(cowplot::plot_grid(plotlist = tidy_plots, axis = "t"), "original-dfs-tidy")
```{r}
tidyr::gather(wide, key = "person", value = "sales", -year)
```

![](images/static/png/original-dfs-tidy.png)
### Spread

> Spread a key-value pair across multiple columns. Use it when an a column contains observations from multiple variables.

```{r spread}
animate_spread(long, key = "person", value = "sales")
```

```{r echo=TRUE}
wide
long
```{r}
tidyr::spread(long, key = "person", value = "sales")
```

### Spread and Gather

`spread(data, key, value)`
## Learn More

> Spread a key-value pair across multiple columns.
> Use it when an a column contains observations from multiple variables.
### Relational Data

`gather(data, key = "key", value = "value", ...)`
The [Relational Data](http://r4ds.had.co.nz/relation-data.html) chapter of the
[R for Data Science](http://r4ds.had.co.nz/) book by Garrett Grolemund and Hadley Wickham
is an excellent resource for learning more about relational data.

> Gather takes multiple columns and collapses into key-value pairs, duplicating all other columns as needed.
> You use `gather()` when you notice that your column names are not names of variables, but *values* of a variable.
The [dplyr two-table verbs vignette][dplyr-two-table]
and Jenny Bryan's [Cheatsheet for dplyr join functions](http://stat545.com/bit001_dplyr-cheatsheet.html)
are also great resources.

![](images/tidyr-spread-gather.gif)
### gganimate

```{r echo=TRUE}
gather(wide, key, val, x:z)
spread(long, key, val)
```
The animations were made possible by the newly re-written [gganimate] package by
[Thomas Lin Pedersen](https://github.com/thomasp85)
(original by [Dave Robinson](https://github.com/dgrtwo)).
The [package readme][gganimate] provides an excellent (and quick) introduction to gganimte.

+ 242
- 163
README.md Zobrazit soubor

@@ -4,38 +4,35 @@
# Tidy Animated Verbs

Garrick Aden-Buie – [@grrrck](https://twitter.com/grrrck) –
[garrickadenbuie.com](https://www.garrickadenbuie.com). Set operations
contributed by [Tyler Grant
[garrickadenbuie.com](https://www.garrickadenbuie.com).

David Zimmermann – [@dav\_zim](https://twitter.com/dav_zim) –
[datashenanigan.wordpress.com](https://datashenanigan.wordpress.com/)

Set operations contributed by [Tyler Grant
Smith](https://github.com/TylerGrantSmith).

[![Binder](http://mybinder.org/badge.svg)](https://mybinder.org/v2/gh/gadenbuie/tidy-animated-verbs/master?urlpath=rstudio)
[![CC0](https://img.shields.io/badge/license_\(images\)_-CC0-green.svg)](https://creativecommons.org/publicdomain/zero/1.0/)
[![MIT](https://img.shields.io/badge/license_\(code\)_-MIT-green.svg)](https://opensource.org/licenses/MIT)

- [**Mutating Joins**](#mutating-joins) —
[`inner_join()`](#inner-join), [`left_join()`](#left-join),
[`right_join()`](#right-join), [`full_join()`](#full-join)
- Mutating Joins: [`inner_join()`](#inner-join),
[`left_join()`](#left-join), [`right_join()`](#right-join),
[`full_join()`](#full-join)

- [**Filtering Joins**](#filtering-joins) —
[`semi_join()`](#semi-join), [`anti_join()`](#anti-join)
- Filtering Joins: [`semi_join()`](#semi-join),
[`anti_join()`](#anti-join)

- [**Set Operations**](#set-operations) — [`union()`](#union),
[`union_all()`](#union-all), [`intersect()`](#intersect),
[`setdiff()`](#setdiff)
- Set Operations: [`union()`](#union), [`union_all()`](#union-all),
[`intersect()`](#intersect), [`setdiff()`](#setdiff)

- [**Tidy Data**](#tidy-data) — [`spread()` and
`gather()`](#spread-and-gather)
- Tidyr Operations: [`gather()`](#gather), [`spread()`](#spread)

- Learn more about
- [Using the animations and images](#usage)
- [Relational Data](#relational-data)
- [gganimate](#gganimate)

## Background

### Usage

Please feel free to use these images for teaching or learning about
action verbs from the [tidyverse](https://tidyverse.org). You can
directly download the [original animations](images/) or static images in
@@ -48,37 +45,35 @@ to expand the animations to include more verbs from the tidyverse.
[Suggestions are
welcome\!](https://github.com/gadenbuie/tidy-animated-verbs/issues)

### Relational Data
## Installing

The [Relational Data](http://r4ds.had.co.nz/relational-data.html)
chapter of the [R for Data Science](http://r4ds.had.co.nz/) book by
Garrett Grolemund and Hadley Wickham is an excellent resource for
learning more about relational data.
The in-development version of `tidyexplain` can be installed with
`devtools`:

The [dplyr two-table verbs
vignette](https://dplyr.tidyverse.org/articles/two-table.html) and Jenny
Bryan’s [Cheatsheet for dplyr join
functions](http://stat545.com/bit001_dplyr-cheatsheet.html) are also
great resources.

### gganimate
``` r
# install.package("devtools")
devtools::install_github("gadenbuie/tidy-animated-verbs")

The animations were made possible by the newly re-written
[gganimate](https://github.com/thomasp85/gganimate#README) package by
[Thomas Lin Pedersen](https://github.com/thomasp85) (original by [Dave
Robinson](https://github.com/dgrtwo)). The [package
readme](https://github.com/thomasp85/gganimate#README) provides an
excellent (and quick) introduction to gganimte.
library(tidyexplain)
```

## Mutating Joins

> A mutating join allows you to combine variables from two tables. It
> first matches observations by their keys, then copies across variables
> from one table to the other.
> [R for Data Science: Mutating
> joins](http://r4ds.had.co.nz/relational-data.html#mutating-joins)
``` r
x <- dplyr::data_frame(
id = 1:3,
x = paste0("x", 1:3)
)

y <- dplyr::data_frame(
id = (1:4)[-3],
y = paste0("y", (1:4)[-3])
)

<img src="images/static/png/original-dfs.png" width="480px" />
animate_full_join(x, y, by = c("id"), export = "first")
```

![](man/figures/tidyexplain-intial-dfs-1.png)<!-- -->

``` r
x
@@ -102,10 +97,14 @@ y
> All rows from `x` where there are matching values in `y`, and all
> columns from `x` and `y`.

![](images/inner-join.gif)
``` r
animate_inner_join(x, y, by = "id")
```

![](man/figures/tidyexplain-inner-join-1.gif)<!-- -->

``` r
inner_join(x, y, by = "id")
dplyr::inner_join(x, y, by = "id")
#> # A tibble: 2 x 3
#> id x y
#> <int> <chr> <chr>
@@ -118,10 +117,14 @@ inner_join(x, y, by = "id")
> All rows from `x`, and all columns from `x` and `y`. Rows in `x` with
> no match in `y` will have `NA` values in the new columns.

![](images/left-join.gif)
``` r
animate_left_join(x, y, by = "id")
```

![](man/figures/tidyexplain-left-join-1.gif)<!-- -->

``` r
left_join(x, y, by = "id")
dplyr::left_join(x, y, by = "id")
#> # A tibble: 3 x 3
#> id x y
#> <int> <chr> <chr>
@@ -135,9 +138,8 @@ left_join(x, y, by = "id")
> … If there are multiple matches between `x` and `y`, all combinations
> of the matches are returned.

![](images/left-join-extra.gif)

``` r
y_extra <- dplyr::bind_rows(y, dplyr::data_frame(id = 2, y = "y5"))
y_extra # has multiple rows with the key from `x`
#> # A tibble: 4 x 2
#> id y
@@ -146,7 +148,14 @@ y_extra # has multiple rows with the key from `x`
#> 2 2 y2
#> 3 4 y4
#> 4 2 y5
left_join(x, y_extra, by = "id")

animate_left_join(x, y_extra, by = "id", title_size = 22)
```

![](man/figures/tidyexplain-left-join-extra-1.gif)<!-- -->

``` r
dplyr::left_join(x, y_extra, by = "id")
#> # A tibble: 4 x 3
#> id x y
#> <dbl> <chr> <chr>
@@ -161,10 +170,14 @@ left_join(x, y_extra, by = "id")
> All rows from y, and all columns from `x` and `y`. Rows in `y` with no
> match in `x` will have `NA` values in the new columns.

![](images/right-join.gif)
``` r
animate_right_join(x, y, by = "id")
```

![](man/figures/tidyexplain-right-join-1.gif)<!-- -->

``` r
right_join(x, y, by = "id")
dplyr::right_join(x, y, by = "id")
#> # A tibble: 3 x 3
#> id x y
#> <int> <chr> <chr>
@@ -178,10 +191,14 @@ right_join(x, y, by = "id")
> All rows and all columns from both `x` and `y`. Where there are not
> matching values, returns `NA` for the one missing.

![](images/full-join.gif)
``` r
animate_full_join(x, y, by = "id")
```

![](man/figures/tidyexplain-full-join-1.gif)<!-- -->

``` r
full_join(x, y, by = "id")
dplyr::full_join(x, y, by = "id")
#> # A tibble: 4 x 3
#> id x y
#> <int> <chr> <chr>
@@ -193,22 +210,19 @@ full_join(x, y, by = "id")

## Filtering Joins

> Filtering joins match observations in the same way as mutating joins,
> but affect the observations, not the variables. … Semi-joins are
> useful for matching filtered summary tables back to the original rows.
> … Anti-joins are useful for diagnosing join mismatches.
> [R for Data Science: Filtering
> Joins](http://r4ds.had.co.nz/relational-data.html#filtering-joins)

### Semi Join

> All rows from `x` where there are matching values in `y`, keeping just
> columns from `x`.

![](images/semi-join.gif)
``` r
animate_semi_join(x, y, by = "id")
```

![](man/figures/tidyexplain-semi-join-1.gif)<!-- -->

``` r
semi_join(x, y, by = "id")
dplyr::semi_join(x, y, by = "id")
#> # A tibble: 2 x 2
#> id x
#> <int> <chr>
@@ -221,10 +235,14 @@ semi_join(x, y, by = "id")
> All rows from `x` where there are not matching values in `y`, keeping
> just columns from `x`.

![](images/anti-join.gif)
``` r
animate_anti_join(x, y, by = "id")
```

![](man/figures/tidyexplain-anti-join-1.gif)<!-- -->

``` r
anti_join(x, y, by = "id")
dplyr::anti_join(x, y, by = "id")
#> # A tibble: 1 x 2
#> id x
#> <int> <chr>
@@ -233,92 +251,114 @@ anti_join(x, y, by = "id")

## Set Operations

> Set operations are occasionally useful when you want to break a single
> complex filter into simpler pieces. All these operations work with a
> complete row, comparing the values of every variable. These expect the
> x and y inputs to have the same variables, and treat the observations
> like sets.
> [R for Data Science: Set
> operations](http://r4ds.had.co.nz/relational-data.html#set-operations)
``` r
x <- dplyr::data_frame(
x = c(1, 1, 2),
y = c("a", "b", "a")
)
y <- dplyr::data_frame(
x = c(1, 2),
y = c("a", "b")
)

animate_union(x, y, export = "first")
```

<img src="images/static/png/original-dfs-set-ops.png" width="480px" />
![](man/figures/tidyexplain-intial-dfs-so-1.png)<!-- -->

``` r
x
#> # A tibble: 3 x 2
#> x y
#> <chr> <chr>
#> 1 1 a
#> 2 1 b
#> 3 2 a
#> x y
#> <dbl> <chr>
#> 1 1 a
#> 2 1 b
#> 3 2 a
y
#> # A tibble: 2 x 2
#> x y
#> <chr> <chr>
#> 1 1 a
#> 2 2 b
#> x y
#> <dbl> <chr>
#> 1 1 a
#> 2 2 b
```

### Union

> All unique rows from `x` and `y`.

![](images/union.gif)
``` r
animate_union(x, y)
```

![](man/figures/tidyexplain-union-1.gif)<!-- -->

``` r
union(x, y)
dplyr::union(x, y)
#> # A tibble: 4 x 2
#> x y
#> <chr> <chr>
#> 1 2 b
#> 2 2 a
#> 3 1 b
#> 4 1 a
#> x y
#> <dbl> <chr>
#> 1 2 b
#> 2 2 a
#> 3 1 b
#> 4 1 a
```

``` r
animate_union(y, x)
```

![](images/union-rev.gif)
![](man/figures/tidyexplain-union-y-x-1.gif)<!-- -->

``` r
union(y, x)

dplyr::union(y, x)
#> # A tibble: 4 x 2
#> x y
#> <chr> <chr>
#> 1 2 a
#> 2 1 b
#> 3 2 b
#> 4 1 a
#> x y
#> <dbl> <chr>
#> 1 2 a
#> 2 1 b
#> 3 2 b
#> 4 1 a
```

### Union All

> All rows from `x` and `y`, keeping duplicates.

![](images/union-all.gif)
``` r
animate_union_all(x, y)
```

![](man/figures/tidyexplain-union-all-1.gif)<!-- -->

``` r
union_all(x, y)
dplyr::union_all(x, y)
#> # A tibble: 5 x 2
#> x y
#> <chr> <chr>
#> 1 1 a
#> 2 1 b
#> 3 2 a
#> 4 1 a
#> 5 2 b
#> x y
#> <dbl> <chr>
#> 1 1 a
#> 2 1 b
#> 3 2 a
#> 4 1 a
#> 5 2 b
```

### Intersection

> Common rows in both `x` and `y`, keeping just unique rows.

![](images/intersect.gif)
``` r
animate_intersect(x, y)
```

![](man/figures/tidyexplain-intersect-1.gif)<!-- -->

``` r
intersect(x, y)
dplyr::intersect(x, y)
#> # A tibble: 1 x 2
#> x y
#> <chr> <chr>
#> 1 1 a
#> x y
#> <dbl> <chr>
#> 1 1 a
```

### Set Difference
@@ -326,28 +366,37 @@ intersect(x, y)
> All rows from `x` which are not also rows in `y`, keeping just unique
> rows.

![](images/setdiff.gif)
``` r
animate_setdiff(x, y)
```

![](man/figures/tidyexplain-setdiff-1.gif)<!-- -->

``` r
setdiff(x, y)
dplyr::setdiff(x, y)
#> # A tibble: 2 x 2
#> x y
#> <chr> <chr>
#> 1 1 b
#> 2 2 a
#> x y
#> <dbl> <chr>
#> 1 1 b
#> 2 2 a
```

![](images/setdiff-rev.gif)
``` r
animate_setdiff(y, x)
```

![](man/figures/tidyexplain-setdiff-y-x-1.gif)<!-- -->

``` r
setdiff(y, x)

dplyr::setdiff(y, x)
#> # A tibble: 1 x 2
#> x y
#> <chr> <chr>
#> 1 2 b
#> x y
#> <dbl> <chr>
#> 1 2 b
```

## Tidy Data
## Tidy Data and `gather()`, `spread()` functionality

[Tidy data](http://r4ds.had.co.nz/tidy-data.html#tidy-data-1) follows
the following three rules:
@@ -361,58 +410,88 @@ to be formatted as a tidy dataset and the
[tidyr](https://tidyr.tidyverse.org) package provides functions to help
you organize your data into tidy data.

![](images/static/png/original-dfs-tidy.png)
``` r
long <- dplyr::data_frame(
year = c(2010, 2011, 2010, 2011, 2010, 2011),
person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"),
sales = c(105, 110, 100, 97, 90, 95)
)
wide <- dplyr::data_frame(
year = 2010:2011,
Alice = c(105, 110),
Bob = c(100, 97),
Charlie = c(90, 95)
)
```

### Gather

> Gather takes multiple columns and collapses into key-value pairs,
> duplicating all other columns as needed. You use gather() when you
> notice that your column names are not names of variables, but values
> of a variable.

``` r
wide
#> # A tibble: 2 x 4
#> id x y z
#> <int> <chr> <chr> <chr>
#> 1 1 a c e
#> 2 2 b d f
long
#> # A tibble: 6 x 3
#> id key val
#> <int> <chr> <chr>
#> 1 1 x a
#> 2 2 x b
#> 3 1 y c
#> 4 2 y d
#> 5 1 z e
#> 6 2 z f
set_font_size(4, 15)
set_anim_options(anim_options(cell_width = 2))
animate_gather(wide, key = "person", value = "sales", -year)
```

### Spread and Gather
![](man/figures/tidyexplain-gather-1.gif)<!-- -->

`spread(data, key, value)`
``` r
tidyr::gather(wide, key = "person", value = "sales", -year)
#> # A tibble: 6 x 3
#> year person sales
#> <int> <chr> <dbl>
#> 1 2010 Alice 105
#> 2 2011 Alice 110
#> 3 2010 Bob 100
#> 4 2011 Bob 97
#> 5 2010 Charlie 90
#> 6 2011 Charlie 95
```

### Spread

> Spread a key-value pair across multiple columns. Use it when an a
> column contains observations from multiple variables.

`gather(data, key = "key", value = "value", ...)`

> Gather takes multiple columns and collapses into key-value pairs,
> duplicating all other columns as needed. You use `gather()` when you
> notice that your column names are not names of variables, but *values*
> of a variable.
``` r
animate_spread(long, key = "person", value = "sales")
```

![](images/tidyr-spread-gather.gif)
![](man/figures/tidyexplain-spread-1.gif)<!-- -->

``` r
gather(wide, key, val, x:z)
#> # A tibble: 6 x 3
#> id key val
#> <int> <chr> <chr>
#> 1 1 x a
#> 2 2 x b
#> 3 1 y c
#> 4 2 y d
#> 5 1 z e
#> 6 2 z f
spread(long, key, val)
tidyr::spread(long, key = "person", value = "sales")
#> # A tibble: 2 x 4
#> id x y z
#> <int> <chr> <chr> <chr>
#> 1 1 a c e
#> 2 2 b d f
#> year Alice Bob Charlie
#> <dbl> <dbl> <dbl> <dbl>
#> 1 2010 105 100 90
#> 2 2011 110 97 95
```

## Learn More

### Relational Data

The [Relational Data](http://r4ds.had.co.nz/relation-data.html) chapter
of the [R for Data Science](http://r4ds.had.co.nz/) book by Garrett
Grolemund and Hadley Wickham is an excellent resource for learning more
about relational data.

The [dplyr two-table verbs
vignette](https://dplyr.tidyverse.org/articles/two-table.html) and Jenny
Bryan’s [Cheatsheet for dplyr join
functions](http://stat545.com/bit001_dplyr-cheatsheet.html) are also
great resources.

### gganimate

The animations were made possible by the newly re-written
[gganimate](https://github.com/thomasp85/gganimate#README) package by
[Thomas Lin Pedersen](https://github.com/thomasp85) (original by [Dave
Robinson](https://github.com/dgrtwo)). The [package
readme](https://github.com/thomasp85/gganimate#README) provides an
excellent (and quick) introduction to gganimte.

binární
images/anti-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 473KB

+ 160
- 0
images/create_images.R Zobrazit soubor

@@ -0,0 +1,160 @@
library(tidyexplain)
library(here)
library(stringr)
set_font_size(title_size = 20)

check_and_create <- function(ff) {
if (!dir.exists(ff)) dir.create(ff, recursive = T)
}

check_and_create(here("images", "static", "png"))
check_and_create(here("images", "static", "svg"))
check_and_create(here("images", "gif"))

### Animate Joins

x <- dplyr::data_frame(
id = 1:3,
x = paste0("x", 1:3)
)

y <- dplyr::data_frame(
id = (1:4)[-3],
y = paste0("y", (1:4)[-3])
)

joins <- c(
full_join = animate_full_join,
inner_join = animate_inner_join,
left_join = animate_left_join,
right_join = animate_right_join,
semi_join = animate_semi_join
)

a <- sapply(1:length(joins), function(i) {
nam <- names(joins)[i]
nam <- str_replace(nam, "_", "-")
cat(nam, "\n")

width <- 7
height <- 7

gif_ <- joins[[i]](x, y, by = "id")
first_ <- joins[[i]](x, y, by = "id", export = "first")
last_ <- joins[[i]](x, y, by = "id", export = "last")

save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif")))
ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_,
height = height, width = width)
ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_,
height = height, width = width)
})

### Animate Sets

x <- tibble::tribble(
~x, ~y,
"1", "a",
"1", "b",
"2", "a"
)

y <- tibble::tribble(
~x, ~y,
"1", "a",
"2", "b"
)

sets <- c(
union = animate_union,
union_all = animate_union_all,
intersect = animate_intersect,
setdiff = animate_setdiff
)

a <- sapply(1:length(sets), function(i) {
nam <- names(sets)[i]
nam <- str_replace(nam, "_", "-")

cat(nam, "\n")

width <- 7
height <- 7

gif_ <- sets[[i]](x, y)
first_ <- sets[[i]](x, y, export = "first")
last_ <- sets[[i]](x, y, export = "last")

save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif")))
ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_,
height = height, width = width)
ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_,
height = height, width = width)
})


### Animate Gather Spread
set_font_size(text_size = 4)
set_anim_options(anim_options(cell_width = 2))

# Gather
wide <- dplyr::data_frame(
year = 2010:2011,
Alice = c(105, 110),
Bob = c(100, 97),
Charlie = c(90, 95)
)

nam <- "gather"
cat(nam, "\n")

width <- 7
height <- 7

gif_ <- animate_gather(wide, key = "person", value = "sales", -year, cell_width = 2)
first_ <- animate_gather(wide, key = "person", value = "sales", -year, export = "first")
last_ <- animate_gather(wide, key = "person", value = "sales", -year, export = "last")

save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif")))
ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_,
height = height, width = width)
ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_,
height = height, width = width)

# Spread
long <- dplyr::data_frame(
year = c(2010, 2011, 2010, 2011, 2010, 2011),
person = c("Alice", "Alice", "Bob", "Bob", "Charlie", "Charlie"),
sales = c(105, 110, 100, 97, 90, 95)
)
nam <- "spread"
cat(nam, "\n")

width <- 7
height <- 7

gif_ <- animate_spread(long, key = "person", value = "sales")
first_ <- animate_spread(long, key = "person", value = "sales", export = "first")
last_ <- animate_spread(long, key = "person", value = "sales", export = "last")

save_animation(animate(gif_), here("images", "gif", paste0(nam, ".gif")))
ggsave(here("images", "static", "png", paste0(nam, "-first.png")), first_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-first.svg")), first_,
height = height, width = width)
ggsave(here("images", "static", "png", paste0(nam, "-last.png")), last_,
height = height, width = width)
ggsave(here("images", "static", "svg", paste0(nam, "-last.svg")), last_,
height = height, width = width)

binární
images/full-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 620KB

binární
images/gif/full-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 745KB

binární
images/gif/gather.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 390KB

binární
images/gif/inner-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 735KB

binární
images/gif/intersect.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 539KB

binární
images/gif/left-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 744KB

binární
images/gif/right-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 747KB

binární
images/gif/semi-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 718KB

binární
images/gif/setdiff.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 592KB

binární
images/gif/spread.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 400KB

binární
images/gif/union-all.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 565KB

binární
images/gif/union.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 572KB

binární
images/inner-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 580KB

binární
images/intersect.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 447KB

binární
images/left-join-extra.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 742KB

binární
images/left-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 597KB

binární
images/right-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 610KB

binární
images/semi-join.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 562KB

binární
images/setdiff-rev.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 391KB

binární
images/setdiff.gif Zobrazit soubor

Před Za
Šířka: 480  |  Výška: 480  |  Velikost: 399KB

binární
images/static/png/anti-join.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 84KB

binární
images/static/png/full-join-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 54KB

binární
images/static/png/full-join-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 66KB

binární
images/static/png/full-join.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 108KB

binární
images/static/png/gather-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 38KB

binární
images/static/png/gather-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 63KB

binární
images/static/png/inner-join-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 54KB

binární
images/static/png/inner-join-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 53KB

binární
images/static/png/inner-join.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 94KB

binární
images/static/png/intersect-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 38KB

binární
images/static/png/intersect-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 36KB

binární
images/static/png/intersect.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 80KB

binární
images/static/png/left-join-extra-input.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 124KB

binární
images/static/png/left-join-extra.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 115KB

binární
images/static/png/left-join-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 54KB

binární
images/static/png/left-join-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 62KB

binární
images/static/png/left-join.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 103KB

binární
images/static/png/original-dfs-set-ops.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 95KB

binární
images/static/png/original-dfs-tidy.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 135KB

binární
images/static/png/original-dfs.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 109KB

binární
images/static/png/right-join-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 54KB

binární
images/static/png/right-join-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 59KB

binární
images/static/png/right-join.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 101KB

binární
images/static/png/semi-join-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 54KB

binární
images/static/png/semi-join-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 48KB

binární
images/static/png/semi-join.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 88KB

binární
images/static/png/setdiff-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 38KB

binární
images/static/png/setdiff-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 41KB

binární
images/static/png/setdiff-rev.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 84KB

binární
images/static/png/setdiff.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 84KB

binární
images/static/png/spread-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 63KB

binární
images/static/png/spread-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 38KB

binární
images/static/png/tidyr-gather.png Zobrazit soubor

Před Za
Šířka: 3429  |  Výška: 4658  |  Velikost: 373KB

binární
images/static/png/tidyr-spread.png Zobrazit soubor

Před Za
Šířka: 3429  |  Výška: 4658  |  Velikost: 347KB

binární
images/static/png/union-all-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 38KB

binární
images/static/png/union-all-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 50KB

binární
images/static/png/union-all.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 97KB

binární
images/static/png/union-first.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 38KB

binární
images/static/png/union-last.png Zobrazit soubor

Před Za
Šířka: 2100  |  Výška: 2100  |  Velikost: 46KB

binární
images/static/png/union-rev.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 91KB

binární
images/static/png/union.png Zobrazit soubor

Před Za
Šířka: 2099  |  Výška: 1499  |  Velikost: 91KB

+ 0
- 44
images/static/svg/anti-join.svg Zobrazit soubor

@@ -1,44 +0,0 @@
<?xml version='1.0' encoding='UTF-8' ?>
<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 504.00 360.00'>
<defs>
<style type='text/css'><![CDATA[
line, polyline, path, rect, circle {
fill: none;
stroke: #000000;
stroke-linecap: round;
stroke-linejoin: round;
stroke-miterlimit: 10.00;
}
]]></style>
</defs>
<rect width='100%' height='100%' style='stroke: none; fill: #FFFFFF;'/>
<defs>
<clipPath id='cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx'>
<rect x='0.00' y='41.25' width='504.00' height='302.40' />
</clipPath>
</defs>
<rect x='164.95' y='59.58' width='82.47' height='82.47' style='stroke-width: 0.21; stroke: none; stroke-linecap: butt; fill: #4DAF4A;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' />
<rect x='256.58' y='59.58' width='82.47' height='82.47' style='stroke-width: 0.21; stroke: none; stroke-linecap: butt; fill: #D0D0D0;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' />
<path d='M 205.35 89.39 L 205.35 89.39 L 205.76 89.40 L 206.17 89.42 L 206.56 89.46 L 206.94 89.52 L 207.31 89.59 L 207.66 89.67 L 208.01 89.78 L 208.34 89.89 L 208.66 90.03 L 208.96 90.18 L 208.96 90.18 L 209.26 90.34 L 209.55 90.51 L 209.82 90.70 L 210.07 90.89 L 210.31 91.10 L 210.54 91.32 L 210.75 91.54 L 210.94 91.78 L 211.12 92.03 L 211.29 92.29 L 211.29 92.29 L 211.44 92.56 L 211.58 92.84 L 211.70 93.11 L 211.81 93.40 L 211.90 93.69 L 211.97 93.98 L 212.03 94.28 L 212.07 94.58 L 212.10 94.88 L 212.11 95.20 L 212.11 95.20 L 212.09 95.61 L 212.06 96.00 L 212.00 96.38 L 211.91 96.75 L 211.80 97.10 L 211.66 97.44 L 211.50 97.76 L 211.32 98.07 L 211.11 98.36 L 210.88 98.64 L 210.88 98.64 L 210.63 98.90 L 210.37 99.14 L 210.09 99.37 L 209.79 99.57 L 209.48 99.76 L 209.15 99.94 L 208.80 100.09 L 208.44 100.23 L 208.06 100.35 L 207.67 100.45 L 207.67 100.45 L 208.12 100.51 L 208.55 100.59 L 208.96 100.69 L 209.36 100.81 L 209.74 100.97 L 210.10 101.14 L 210.45 101.34 L 210.78 101.57 L 211.09 101.82 L 211.39 102.09 L 211.39 102.09 L 211.66 102.39 L 211.90 102.71 L 212.12 103.05 L 212.31 103.40 L 212.46 103.78 L 212.59 104.18 L 212.69 104.60 L 212.77 105.04 L 212.81 105.50 L 212.82 105.99 L 212.82 105.99 L 212.81 106.38 L 212.78 106.76 L 212.74 107.14 L 212.67 107.51 L 212.58 107.87 L 212.48 108.23 L 212.35 108.57 L 212.21 108.91 L 212.05 109.25 L 211.87 109.57 L 211.87 109.57 L 211.67 109.88 L 211.45 110.19 L 211.22 110.47 L 210.97 110.75 L 210.71 111.00 L 210.42 111.25 L 210.13 111.48 L 209.81 111.70 L 209.48 111.91 L 209.14 112.10 L 209.14 112.10 L 208.77 112.27 L 208.40 112.42 L 208.02 112.55 L 207.63 112.67 L 207.22 112.76 L 206.81 112.84 L 206.38 112.90 L 205.94 112.95 L 205.50 112.98 L 205.04 112.98 L 205.04 112.98 L 204.62 112.98 L 204.22 112.95 L 203.82 112.92 L 203.43 112.86 L 203.04 112.80 L 202.66 112.71 L 202.29 112.62 L 201.92 112.50 L 201.57 112.38 L 201.21 112.23 L 201.21 112.23 L 200.87 112.07 L 200.54 111.89 L 200.21 111.69 L 199.89 111.49 L 199.58 111.26 L 199.28 111.02 L 198.99 110.77 L 198.71 110.50 L 198.44 110.21 L 198.18 109.91 L 200.05 108.17 L 200.05 108.17 L 200.28 108.40 L 200.51 108.61 L 200.73 108.81 L 200.96 109.00 L 201.19 109.18 L 201.42 109.34 L 201.65 109.49 L 201.88 109.63 L 202.11 109.76 L 202.34 109.88 L 202.34 109.88 L 202.58 109.98 L 202.83 110.07 L 203.08 110.16 L 203.33 110.23 L 203.59 110.29 L 203.85 110.34 L 204.11 110.37 L 204.38 110.40 L 204.66 110.42 L 204.94 110.42 L 204.94 110.42 L 205.36 110.41 L 205.76 110.38 L 206.14 110.32 L 206.51 110.23 L 206.86 110.13 L 207.18 109.99 L 207.50 109.84 L 207.79 109.66 L 208.06 109.46 L 208.32 109.23 L 208.32 109.23 L 208.55 108.98 L 208.76 108.70 L 208.94 108.42 L 209.10 108.11 L 209.24 107.80 L 209.35 107.46 L 209.43 107.11 L 209.50 106.74 L 209.53 106.35 L 209.54 105.95 L 209.54 105.95 L 209.53 105.51 L 209.50 105.10 L 209.44 104.72 L 209.36 104.36 L 209.25 104.03 L 209.13 103.73 L 208.98 103.45 L 208.80 103.20 L 208.60 102.97 L 208.38 102.78 L 208.38 102.78 L 208.14 102.60 L 207.88 102.44 L 207.59 102.31 L 207.29 102.19 L 206.96 102.08 L 206.61 102.00 L 206.24 101.94 L 205.85 101.89 L 205.44 101.86 L 205.00 101.85 L 203.30 101.85 L 203.67 99.43 L 204.83 99.43 L 204.83 99.43 L 205.18 99.42 L 205.52 99.39 L 205.84 99.33 L 206.16 99.26 L 206.45 99.17 L 206.74 99.05 L 207.02 98.91 L 207.28 98.75 L 207.53 98.57 L 207.77 98.37 L 207.77 98.37 L 208.00 98.15 L 208.20 97.91 L 208.38 97.66 L 208.53 97.39 L 208.67 97.10 L 208.77 96.79 L 208.86 96.47 L 208.92 96.13 L 208.95 95.77 L 208.96 95.40 L 208.96 95.40 L 208.95 95.09 L 208.92 94.79 L 208.87 94.50 L 208.80 94.23 L 208.71 93.97 L 208.60 93.72 L 208.46 93.49 L 208.31 93.27 L 208.13 93.07 L 207.94 92.87 L 207.94 92.87 L 207.73 92.69 L 207.50 92.53 L 207.26 92.39 L 207.01 92.26 L 206.74 92.16 L 206.45 92.07 L 206.15 92.00 L 205.84 91.96 L 205.52 91.93 L 205.17 91.92 L 205.17 91.92 L 204.91 91.92 L 204.65 91.94 L 204.39 91.96 L 204.14 91.99 L 203.89 92.04 L 203.65 92.09 L 203.41 92.15 L 203.18 92.22 L 202.94 92.31 L 202.72 92.40 L 202.72 92.40 L 202.50 92.50 L 202.28 92.61 L 202.05 92.74 L 201.83 92.88 L 201.61 93.03 L 201.38 93.19 L 201.15 93.37 L 200.92 93.55 L 200.69 93.75 L 200.46 93.97 L 198.82 92.09 L 198.82 92.09 L 199.41 91.58 L 200.01 91.12 L 200.63 90.71 L 201.26 90.36 L 201.91 90.07 L 202.56 89.82 L 203.24 89.63 L 203.93 89.50 L 204.63 89.42 L 205.35 89.39 Z' style='fill-rule: evenodd; fill: #FFFFFF; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' />
<path d='M 291.16 102.95 L 297.41 112.58 L 293.62 112.58 L 289.21 104.96 L 284.74 112.58 L 281.19 112.58 L 287.47 103.08 L 281.91 94.58 L 285.59 94.58 L 289.35 101.17 L 293.14 94.58 L 296.69 94.58 L 291.16 102.95 Z' style='fill-rule: evenodd; fill: #FFFFFF; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' />
<path d='M 305.26 89.39 L 305.26 89.39 L 305.68 89.40 L 306.08 89.42 L 306.48 89.46 L 306.86 89.52 L 307.22 89.59 L 307.58 89.67 L 307.92 89.78 L 308.26 89.89 L 308.57 90.03 L 308.88 90.18 L 308.88 90.18 L 309.18 90.34 L 309.47 90.51 L 309.73 90.70 L 309.99 90.89 L 310.23 91.10 L 310.45 91.32 L 310.66 91.54 L 310.86 91.78 L 311.04 92.03 L 311.20 92.29 L 311.20 92.29 L 311.36 92.56 L 311.50 92.84 L 311.62 93.11 L 311.73 93.40 L 311.82 93.69 L 311.89 93.98 L 311.95 94.28 L 311.99 94.58 L 312.01 94.88 L 312.02 95.20 L 312.02 95.20 L 312.01 95.61 L 311.97 96.00 L 311.91 96.38 L 311.83 96.75 L 311.71 97.10 L 311.58 97.44 L 311.42 97.76 L 311.24 98.07 L 311.03 98.36 L 310.79 98.64 L 310.79 98.64 L 310.55 98.90 L 310.28 99.14 L 310.00 99.37 L 309.71 99.57 L 309.39 99.76 L 309.06 99.94 L 308.72 100.09 L 308.36 100.23 L 307.98 100.35 L 307.58 100.45 L 307.58 100.45 L 308.03 100.51 L 308.46 100.59 L 308.88 100.69 L 309.28 100.81 L 309.66 100.97 L 310.02 101.14 L 310.37 101.34 L 310.70 101.57 L 311.01 101.82 L 311.30 102.09 L 311.30 102.09 L 311.58 102.39 L 311.82 102.71 L 312.04 103.05 L 312.22 103.40 L 312.38 103.78 L 312.51 104.18 L 312.61 104.60 L 312.68 105.04 L 312.72 105.50 L 312.74 105.99 L 312.74 105.99 L 312.73 106.38 L 312.70 106.76 L 312.65 107.14 L 312.59 107.51 L 312.50 107.87 L 312.39 108.23 L 312.27 108.57 L 312.13 108.91 L 311.96 109.25 L 311.78 109.57 L 311.78 109.57 L 311.58 109.88 L 311.37 110.19 L 311.14 110.47 L 310.89 110.75 L 310.62 111.00 L 310.34 111.25 L 310.04 111.48 L 309.73 111.70 L 309.40 111.91 L 309.05 112.10 L 309.05 112.10 L 308.69 112.27 L 308.32 112.42 L 307.94 112.55 L 307.54 112.67 L 307.14 112.76 L 306.72 112.84 L 306.30 112.90 L 305.86 112.95 L 305.41 112.98 L 304.95 112.98 L 304.95 112.98 L 304.54 112.98 L 304.13 112.95 L 303.74 112.92 L 303.34 112.86 L 302.96 112.80 L 302.58 112.71 L 302.21 112.62 L 301.84 112.50 L 301.48 112.38 L 301.13 112.23 L 301.13 112.23 L 300.79 112.07 L 300.45 111.89 L 300.13 111.69 L 299.81 111.49 L 299.50 111.26 L 299.20 111.02 L 298.91 110.77 L 298.63 110.50 L 298.36 110.21 L 298.09 109.91 L 299.97 108.17 L 299.97 108.17 L 300.19 108.40 L 300.42 108.61 L 300.65 108.81 L 300.88 109.00 L 301.10 109.18 L 301.33 109.34 L 301.56 109.49 L 301.79 109.63 L 302.02 109.76 L 302.26 109.88 L 302.26 109.88 L 302.50 109.98 L 302.74 110.07 L 302.99 110.16 L 303.25 110.23 L 303.50 110.29 L 303.76 110.34 L 304.03 110.37 L 304.30 110.40 L 304.57 110.42 L 304.85 110.42 L 304.85 110.42 L 305.27 110.41 L 305.68 110.38 L 306.06 110.32 L 306.43 110.23 L 306.77 110.13 L 307.10 109.99 L 307.41 109.84 L 307.70 109.66 L 307.98 109.46 L 308.23 109.23 L 308.23 109.23 L 308.47 108.98 L 308.67 108.70 L 308.86 108.42 L 309.02 108.11 L 309.15 107.80 L 309.26 107.46 L 309.35 107.11 L 309.41 106.74 L 309.45 106.35 L 309.46 105.95 L 309.46 105.95 L 309.45 105.51 L 309.41 105.10 L 309.36 104.72 L 309.28 104.36 L 309.17 104.03 L 309.04 103.73 L 308.89 103.45 L 308.72 103.20 L 308.52 102.97 L 308.30 102.78 L 308.30 102.78 L 308.06 102.60 L 307.79 102.44 L 307.51 102.31 L 307.20 102.19 L 306.87 102.08 L 306.53 102.00 L 306.16 101.94 L 305.77 101.89 L 305.35 101.86 L 304.92 101.85 L 303.21 101.85 L 303.59 99.43 L 304.75 99.43 L 304.75 99.43 L 305.10 99.42 L 305.43 99.39 L 305.76 99.33 L 306.07 99.26 L 306.37 99.17 L 306.66 99.05 L 306.93 98.91 L 307.20 98.75 L 307.45 98.57 L 307.69 98.37 L 307.69 98.37 L 307.91 98.15 L 308.12 97.91 L 308.30 97.66 L 308.45 97.39 L 308.58 97.10 L 308.69 96.79 L 308.77 96.47 L 308.83 96.13 L 308.87 95.77 L 308.88 95.40 L 308.88 95.40 L 308.87 95.09 L 308.84 94.79 L 308.79 94.50 L 308.72 94.23 L 308.62 93.97 L 308.51 93.72 L 308.38 93.49 L 308.23 93.27 L 308.05 93.07 L 307.86 92.87 L 307.86 92.87 L 307.64 92.69 L 307.42 92.53 L 307.18 92.39 L 306.92 92.26 L 306.65 92.16 L 306.37 92.07 L 306.07 92.00 L 305.76 91.96 L 305.43 91.93 L 305.09 91.92 L 305.09 91.92 L 304.83 91.92 L 304.57 91.94 L 304.31 91.96 L 304.06 91.99 L 303.81 92.04 L 303.57 92.09 L 303.33 92.15 L 303.09 92.22 L 302.86 92.31 L 302.63 92.40 L 302.63 92.40 L 302.41 92.50 L 302.19 92.61 L 301.97 92.74 L 301.75 92.88 L 301.52 93.03 L 301.30 93.19 L 301.07 93.37 L 300.84 93.55 L 300.61 93.75 L 300.38 93.97 L 298.74 92.09 L 298.74 92.09 L 299.33 91.58 L 299.93 91.12 L 300.55 90.71 L 301.18 90.36 L 301.82 90.07 L 302.48 89.82 L 303.15 89.63 L 303.84 89.50 L 304.54 89.42 L 305.26 89.39 Z' style='fill-rule: evenodd; fill: #FFFFFF; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzQzLjY0OXw0MS4yNDkx)' />
<defs>
<clipPath id='cpMHw1MDR8MzYwfDA='>
<rect x='0.00' y='0.00' width='504.00' height='360.00' />
</clipPath>
</defs>
<path d='M 155.47 35.61 L 155.47 35.61 L 155.47 35.75 L 155.48 35.89 L 155.49 36.02 L 155.51 36.14 L 155.53 36.25 L 155.56 36.36 L 155.59 36.46 L 155.63 36.55 L 155.67 36.63 L 155.71 36.71 L 155.71 36.71 L 155.76 36.78 L 155.82 36.84 L 155.88 36.90 L 155.95 36.95 L 156.03 37.00 L 156.11 37.05 L 156.20 37.10 L 156.30 37.14 L 156.40 37.18 L 156.50 37.21 L 156.00 38.65 L 156.00 38.65 L 155.64 38.59 L 155.32 38.51 L 155.02 38.39 L 154.74 38.25 L 154.50 38.08 L 154.28 37.89 L 154.09 37.67 L 153.93 37.43 L 153.80 37.15 L 153.70 36.85 L 153.70 36.85 L 153.56 37.02 L 153.41 37.18 L 153.26 37.34 L 153.10 37.48 L 152.93 37.62 L 152.76 37.74 L 152.58 37.86 L 152.39 37.98 L 152.19 38.08 L 151.99 38.17 L 151.99 38.17 L 151.79 38.26 L 151.58 38.34 L 151.37 38.41 L 151.15 38.47 L 150.92 38.52 L 150.70 38.56 L 150.46 38.59 L 150.23 38.61 L 149.98 38.63 L 149.74 38.63 L 149.74 38.63 L 149.36 38.62 L 149.01 38.59 L 148.67 38.54 L 148.35 38.46 L 148.04 38.37 L 147.75 38.25 L 147.47 38.11 L 147.21 37.95 L 146.96 37.77 L 146.74 37.57 L 146.74 37.57 L 146.53 37.36 L 146.35 37.12 L 146.19 36.88 L 146.04 36.61 L 145.93 36.34 L 145.83 36.05 L 145.75 35.74 L 145.70 35.42 L 145.67 35.09 L 145.66 34.74 L 145.66 34.74 L 145.67 34.36 L 145.72 34.00 L 145.79 33.66 L 145.90 33.33 L 146.03 33.02 L 146.20 32.73 L 146.40 32.46 L 146.62 32.20 L 146.88 31.96 L 147.17 31.74 L 147.17 31.74 L 147.49 31.54 L 147.84 31.36 L 148.21 31.20 L 148.61 31.07 L 149.03 30.95 L 149.49 30.86 L 149.97 30.78 L 150.47 30.73 L 151.00 30.70 L 151.56 30.69 L 153.43 30.69 L 153.43 29.65 L 153.43 29.65 L 153.42 29.39 L 153.40 29.15 L 153.36 28.92 L 153.31 28.70 L 153.24 28.50 L 153.16 28.32 L 153.06 28.15 L 152.94 28.00 L 152.81 27.86 L 152.66 27.73 L 152.66 27.73 L 152.50 27.62 L 152.33 27.52 L 152.14 27.43 L 151.94 27.35 L 151.72 27.28 L 151.49 27.23 L 151.25 27.19 L 150.99 27.16 L 150.72 27.14 L 150.43 27.13 L 150.43 27.13 L 150.14 27.14 L 149.84 27.16 L 149.53 27.19 L 149.21 27.24 L 148.89 27.30 L 148.56 27.37 L 148.22 27.45 L 147.87 27.55 L 147.51 27.66 L 147.14 27.78 L 146.59 26.25 L 146.59 26.25 L 147.04 26.10 L 147.47 25.96 L 147.90 25.84 L 148.33 25.74 L 148.75 25.65 L 149.16 25.58 L 149.56 25.53 L 149.96 25.49 L 150.36 25.46 L 150.74 25.45 L 150.74 25.45 L 151.19 25.47 L 151.62 25.50 L 152.03 25.55 L 152.41 25.63 L 152.77 25.72 L 153.11 25.84 L 153.43 25.98 L 153.72 26.15 L 154.00 26.33 L 154.25 26.53 L 154.25 26.53 L 154.48 26.76 L 154.69 27.00 L 154.87 27.26 L 155.03 27.53 L 155.17 27.82 L 155.28 28.13 L 155.36 28.46 L 155.42 28.80 L 155.46 29.16 L 155.47 29.53 L 155.47 35.61 ZM 150.24 37.12 L 150.24 37.12 L 150.43 37.11 L 150.61 37.10 L 150.79 37.08 L 150.97 37.04 L 151.15 37.00 L 151.33 36.95 L 151.50 36.88 L 151.68 36.81 L 151.85 36.73 L 152.02 36.64 L 152.02 36.64 L 152.19 36.54 L 152.35 36.43 L 152.51 36.32 L 152.66 36.20 L 152.80 36.07 L 152.94 35.93 L 153.07 35.79 L 153.20 35.64 L 153.32 35.48 L 153.43 35.32 L 153.43 32.08 L 151.61 32.08 L 151.61 32.08 L 151.22 32.09 L 150.85 32.11 L 150.50 32.14 L 150.18 32.19 L 149.87 32.25 L 149.59 32.32 L 149.34 32.41 L 149.10 32.51 L 148.89 32.62 L 148.70 32.75 L 148.70 32.75 L 148.54 32.89 L 148.38 33.04 L 148.25 33.21 L 148.14 33.39 L 148.04 33.58 L 147.96 33.78 L 147.90 34.00 L 147.85 34.22 L 147.82 34.47 L 147.82 34.72 L 147.82 34.72 L 147.84 35.17 L 147.91 35.58 L 148.03 35.94 L 148.20 36.25 L 148.42 36.52 L 148.69 36.73 L 149.00 36.90 L 149.37 37.02 L 149.78 37.09 L 150.24 37.12 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 160.80 25.72 L 162.53 25.72 L 162.70 27.49 L 162.70 27.49 L 162.85 27.31 L 163.01 27.13 L 163.18 26.96 L 163.35 26.80 L 163.54 26.64 L 163.73 26.50 L 163.93 26.36 L 164.13 26.23 L 164.35 26.12 L 164.57 26.01 L 164.57 26.01 L 164.80 25.90 L 165.03 25.81 L 165.26 25.72 L 165.49 25.65 L 165.72 25.59 L 165.95 25.54 L 166.18 25.50 L 166.41 25.48 L 166.64 25.46 L 166.87 25.45 L 166.87 25.45 L 167.55 25.49 L 168.15 25.60 L 168.68 25.79 L 169.15 26.05 L 169.54 26.39 L 169.86 26.80 L 170.10 27.29 L 170.28 27.85 L 170.39 28.49 L 170.42 29.20 L 170.42 38.37 L 168.41 38.37 L 168.41 30.69 L 168.41 30.69 L 168.41 30.41 L 168.40 30.15 L 168.39 29.90 L 168.38 29.66 L 168.37 29.44 L 168.35 29.23 L 168.33 29.03 L 168.30 28.85 L 168.27 28.68 L 168.24 28.53 L 168.24 28.53 L 168.21 28.38 L 168.17 28.24 L 168.13 28.11 L 168.07 27.99 L 168.02 27.88 L 167.95 27.77 L 167.89 27.67 L 167.81 27.58 L 167.73 27.50 L 167.64 27.42 L 167.64 27.42 L 167.54 27.35 L 167.44 27.29 L 167.32 27.24 L 167.20 27.19 L 167.06 27.15 L 166.92 27.12 L 166.77 27.09 L 166.60 27.08 L 166.43 27.07 L 166.25 27.06 L 166.25 27.06 L 166.04 27.07 L 165.84 27.09 L 165.64 27.12 L 165.44 27.16 L 165.25 27.22 L 165.05 27.29 L 164.86 27.37 L 164.67 27.46 L 164.49 27.57 L 164.30 27.69 L 164.30 27.69 L 164.12 27.81 L 163.95 27.95 L 163.79 28.08 L 163.63 28.22 L 163.48 28.37 L 163.33 28.52 L 163.19 28.68 L 163.06 28.84 L 162.93 29.00 L 162.82 29.17 L 162.82 38.37 L 160.80 38.37 L 160.80 25.72 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 185.26 37.72 L 185.26 37.72 L 185.12 37.80 L 184.99 37.88 L 184.84 37.96 L 184.69 38.03 L 184.54 38.10 L 184.37 38.17 L 184.21 38.23 L 184.03 38.29 L 183.86 38.34 L 183.67 38.39 L 183.67 38.39 L 183.49 38.44 L 183.31 38.48 L 183.12 38.51 L 182.94 38.54 L 182.76 38.57 L 182.58 38.59 L 182.40 38.61 L 182.21 38.62 L 182.03 38.63 L 181.85 38.63 L 181.85 38.63 L 181.46 38.62 L 181.09 38.59 L 180.73 38.54 L 180.39 38.47 L 180.07 38.37 L 179.77 38.26 L 179.48 38.12 L 179.21 37.97 L 178.96 37.79 L 178.73 37.60 L 178.73 37.60 L 178.51 37.38 L 178.32 37.15 L 178.15 36.91 L 178.01 36.65 L 177.88 36.38 L 177.78 36.10 L 177.70 35.80 L 177.65 35.49 L 177.61 35.17 L 177.60 34.84 L 177.60 27.30 L 174.70 27.30 L 174.70 25.72 L 177.60 25.72 L 177.60 22.86 L 179.62 22.62 L 179.62 25.72 L 184.01 25.72 L 183.74 27.30 L 179.62 27.30 L 179.62 34.81 L 179.62 34.81 L 179.62 35.02 L 179.64 35.22 L 179.67 35.41 L 179.71 35.59 L 179.76 35.76 L 179.82 35.91 L 179.90 36.06 L 179.98 36.19 L 180.08 36.31 L 180.19 36.42 L 180.19 36.42 L 180.31 36.52 L 180.45 36.61 L 180.60 36.69 L 180.76 36.76 L 180.94 36.82 L 181.13 36.87 L 181.34 36.90 L 181.56 36.93 L 181.79 36.95 L 182.04 36.95 L 182.04 36.95 L 182.30 36.94 L 182.55 36.93 L 182.80 36.90 L 183.05 36.85 L 183.29 36.80 L 183.53 36.73 L 183.77 36.66 L 184.01 36.57 L 184.24 36.46 L 184.46 36.35 L 185.26 37.72 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 194.40 19.81 L 194.40 19.81 L 194.53 19.82 L 194.66 19.83 L 194.78 19.85 L 194.90 19.88 L 195.01 19.92 L 195.11 19.96 L 195.21 20.01 L 195.31 20.08 L 195.40 20.14 L 195.48 20.22 L 195.48 20.22 L 195.56 20.30 L 195.63 20.39 L 195.69 20.47 L 195.74 20.57 L 195.79 20.67 L 195.82 20.77 L 195.85 20.88 L 195.87 20.99 L 195.88 21.11 L 195.89 21.23 L 195.89 21.23 L 195.88 21.35 L 195.87 21.47 L 195.85 21.59 L 195.82 21.69 L 195.79 21.80 L 195.74 21.90 L 195.69 22.00 L 195.63 22.09 L 195.56 22.18 L 195.48 22.26 L 195.48 22.26 L 195.40 22.34 L 195.31 22.40 L 195.21 22.46 L 195.11 22.51 L 195.01 22.55 L 194.90 22.58 L 194.78 22.61 L 194.66 22.63 L 194.53 22.64 L 194.40 22.65 L 194.40 22.65 L 194.27 22.64 L 194.15 22.63 L 194.03 22.61 L 193.92 22.58 L 193.81 22.55 L 193.71 22.51 L 193.61 22.46 L 193.52 22.40 L 193.43 22.34 L 193.34 22.26 L 193.34 22.26 L 193.27 22.18 L 193.21 22.09 L 193.15 22.00 L 193.10 21.90 L 193.06 21.80 L 193.02 21.69 L 192.99 21.59 L 192.98 21.47 L 192.96 21.35 L 192.96 21.23 L 192.96 21.23 L 192.96 21.11 L 192.98 20.99 L 192.99 20.88 L 193.02 20.77 L 193.06 20.67 L 193.10 20.57 L 193.15 20.47 L 193.21 20.39 L 193.27 20.30 L 193.34 20.22 L 193.34 20.22 L 193.43 20.14 L 193.52 20.08 L 193.61 20.01 L 193.71 19.96 L 193.81 19.92 L 193.92 19.88 L 194.03 19.85 L 194.15 19.83 L 194.27 19.82 L 194.40 19.81 ZM 196.01 36.76 L 199.56 36.76 L 199.56 38.37 L 190.10 38.37 L 190.10 36.76 L 193.99 36.76 L 193.99 27.33 L 190.22 27.33 L 190.22 25.72 L 196.01 25.72 L 196.01 36.76 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 202.80 40.96 L 214.80 40.96 L 214.80 42.73 L 202.80 42.73 L 202.80 40.96 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 224.81 19.81 L 224.81 19.81 L 224.94 19.82 L 225.06 19.83 L 225.18 19.85 L 225.29 19.88 L 225.40 19.92 L 225.50 19.96 L 225.60 20.01 L 225.69 20.08 L 225.78 20.14 L 225.86 20.22 L 225.86 20.22 L 225.94 20.30 L 226.01 20.39 L 226.07 20.47 L 226.13 20.57 L 226.17 20.67 L 226.21 20.77 L 226.24 20.88 L 226.26 20.99 L 226.27 21.11 L 226.27 21.23 L 226.27 21.23 L 226.27 21.35 L 226.26 21.47 L 226.24 21.59 L 226.21 21.69 L 226.17 21.80 L 226.13 21.90 L 226.07 22.00 L 226.01 22.09 L 225.94 22.18 L 225.86 22.26 L 225.86 22.26 L 225.78 22.34 L 225.69 22.40 L 225.60 22.46 L 225.50 22.51 L 225.40 22.55 L 225.29 22.58 L 225.18 22.61 L 225.06 22.63 L 224.94 22.64 L 224.81 22.65 L 224.81 22.65 L 224.68 22.64 L 224.56 22.63 L 224.44 22.61 L 224.33 22.58 L 224.22 22.55 L 224.12 22.51 L 224.02 22.46 L 223.92 22.40 L 223.84 22.34 L 223.75 22.26 L 223.75 22.26 L 223.67 22.18 L 223.61 22.09 L 223.54 22.00 L 223.49 21.90 L 223.45 21.80 L 223.41 21.69 L 223.38 21.59 L 223.36 21.47 L 223.35 21.35 L 223.34 21.23 L 223.34 21.23 L 223.35 21.11 L 223.36 20.99 L 223.38 20.88 L 223.41 20.77 L 223.45 20.67 L 223.49 20.57 L 223.54 20.47 L 223.61 20.39 L 223.67 20.30 L 223.75 20.22 L 223.75 20.22 L 223.84 20.14 L 223.92 20.08 L 224.02 20.01 L 224.12 19.96 L 224.22 19.92 L 224.33 19.88 L 224.44 19.85 L 224.56 19.83 L 224.68 19.82 L 224.81 19.81 ZM 226.68 36.35 L 226.68 36.35 L 226.66 36.95 L 226.60 37.51 L 226.50 38.06 L 226.35 38.57 L 226.17 39.06 L 225.95 39.52 L 225.68 39.95 L 225.37 40.35 L 225.03 40.73 L 224.64 41.08 L 224.64 41.08 L 224.22 41.41 L 223.75 41.72 L 223.25 42.01 L 222.71 42.28 L 222.13 42.52 L 221.51 42.75 L 220.85 42.96 L 220.15 43.14 L 219.42 43.31 L 218.64 43.45 L 218.30 41.89 L 218.30 41.89 L 218.90 41.79 L 219.47 41.66 L 220.01 41.52 L 220.52 41.37 L 221.00 41.20 L 221.46 41.01 L 221.89 40.81 L 222.29 40.60 L 222.66 40.37 L 223.01 40.12 L 223.01 40.12 L 223.32 39.86 L 223.60 39.57 L 223.85 39.27 L 224.07 38.94 L 224.25 38.58 L 224.40 38.21 L 224.51 37.81 L 224.60 37.38 L 224.65 36.94 L 224.66 36.47 L 224.66 27.33 L 219.41 27.33 L 219.41 25.72 L 226.68 25.72 L 226.68 36.35 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 237.62 25.45 L 237.62 25.45 L 238.13 25.47 L 238.62 25.53 L 239.08 25.61 L 239.52 25.74 L 239.93 25.90 L 240.32 26.09 L 240.69 26.32 L 241.03 26.59 L 241.34 26.89 L 241.63 27.23 L 241.63 27.23 L 241.90 27.59 L 242.14 27.98 L 242.35 28.40 L 242.54 28.84 L 242.69 29.31 L 242.82 29.80 L 242.92 30.32 L 242.99 30.86 L 243.03 31.43 L 243.05 32.03 L 243.05 32.03 L 243.03 32.61 L 242.99 33.17 L 242.92 33.71 L 242.82 34.23 L 242.69 34.72 L 242.53 35.19 L 242.34 35.63 L 242.13 36.05 L 241.88 36.45 L 241.61 36.83 L 241.61 36.83 L 241.31 37.17 L 240.99 37.48 L 240.65 37.75 L 240.29 37.98 L 239.90 38.18 L 239.49 38.34 L 239.05 38.47 L 238.59 38.56 L 238.11 38.61 L 237.60 38.63 L 237.60 38.63 L 237.09 38.61 L 236.60 38.56 L 236.14 38.47 L 235.70 38.35 L 235.28 38.19 L 234.89 38.00 L 234.53 37.77 L 234.18 37.51 L 233.86 37.21 L 233.57 36.88 L 233.57 36.88 L 233.30 36.51 L 233.06 36.12 L 232.85 35.70 L 232.66 35.25 L 232.51 34.78 L 232.38 34.29 L 232.28 33.77 L 232.21 33.22 L 232.17 32.65 L 232.15 32.05 L 232.15 32.05 L 232.17 31.47 L 232.21 30.90 L 232.28 30.36 L 232.38 29.85 L 232.51 29.35 L 232.66 28.89 L 232.85 28.44 L 233.06 28.02 L 233.30 27.63 L 233.57 27.25 L 233.57 27.25 L 233.87 26.91 L 234.19 26.61 L 234.54 26.34 L 234.91 26.10 L 235.30 25.90 L 235.72 25.74 L 236.16 25.62 L 236.62 25.53 L 237.11 25.47 L 237.62 25.45 ZM 237.62 27.11 L 237.62 27.11 L 237.30 27.12 L 237.00 27.16 L 236.71 27.22 L 236.43 27.31 L 236.18 27.42 L 235.94 27.55 L 235.72 27.71 L 235.51 27.89 L 235.32 28.10 L 235.15 28.33 L 235.15 28.33 L 235.00 28.59 L 234.86 28.87 L 234.74 29.18 L 234.63 29.52 L 234.54 29.88 L 234.47 30.26 L 234.41 30.67 L 234.37 31.11 L 234.34 31.57 L 234.34 32.05 L 234.34 32.05 L 234.34 32.54 L 234.37 33.00 L 234.41 33.44 L 234.46 33.85 L 234.53 34.23 L 234.62 34.59 L 234.72 34.93 L 234.84 35.23 L 234.98 35.52 L 235.13 35.77 L 235.13 35.77 L 235.30 36.00 L 235.49 36.21 L 235.69 36.39 L 235.92 36.54 L 236.15 36.67 L 236.41 36.78 L 236.68 36.87 L 236.97 36.93 L 237.28 36.96 L 237.60 36.97 L 237.60 36.97 L 237.92 36.96 L 238.23 36.93 L 238.52 36.87 L 238.79 36.78 L 239.04 36.67 L 239.28 36.54 L 239.50 36.39 L 239.70 36.21 L 239.88 36.00 L 240.05 35.77 L 240.05 35.77 L 240.20 35.52 L 240.34 35.23 L 240.46 34.92 L 240.57 34.59 L 240.66 34.23 L 240.73 33.84 L 240.79 33.43 L 240.83 32.99 L 240.86 32.52 L 240.86 32.03 L 240.86 32.03 L 240.86 31.54 L 240.83 31.08 L 240.79 30.65 L 240.73 30.24 L 240.66 29.86 L 240.57 29.50 L 240.46 29.17 L 240.34 28.87 L 240.20 28.59 L 240.05 28.33 L 240.05 28.33 L 239.88 28.10 L 239.70 27.89 L 239.50 27.71 L 239.28 27.55 L 239.05 27.42 L 238.80 27.31 L 238.53 27.22 L 238.24 27.16 L 237.94 27.12 L 237.62 27.11 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 252.00 19.81 L 252.00 19.81 L 252.13 19.82 L 252.26 19.83 L 252.38 19.85 L 252.50 19.88 L 252.61 19.92 L 252.71 19.96 L 252.81 20.01 L 252.91 20.08 L 253.00 20.14 L 253.08 20.22 L 253.08 20.22 L 253.16 20.30 L 253.23 20.39 L 253.29 20.47 L 253.34 20.57 L 253.39 20.67 L 253.42 20.77 L 253.45 20.88 L 253.47 20.99 L 253.48 21.11 L 253.49 21.23 L 253.49 21.23 L 253.48 21.35 L 253.47 21.47 L 253.45 21.59 L 253.42 21.69 L 253.39 21.80 L 253.34 21.90 L 253.29 22.00 L 253.23 22.09 L 253.16 22.18 L 253.08 22.26 L 253.08 22.26 L 253.00 22.34 L 252.91 22.40 L 252.81 22.46 L 252.71 22.51 L 252.61 22.55 L 252.50 22.58 L 252.38 22.61 L 252.26 22.63 L 252.13 22.64 L 252.00 22.65 L 252.00 22.65 L 251.87 22.64 L 251.75 22.63 L 251.63 22.61 L 251.52 22.58 L 251.41 22.55 L 251.31 22.51 L 251.21 22.46 L 251.12 22.40 L 251.03 22.34 L 250.94 22.26 L 250.94 22.26 L 250.87 22.18 L 250.81 22.09 L 250.75 22.00 L 250.70 21.90 L 250.66 21.80 L 250.62 21.69 L 250.59 21.59 L 250.58 21.47 L 250.56 21.35 L 250.56 21.23 L 250.56 21.23 L 250.56 21.11 L 250.58 20.99 L 250.59 20.88 L 250.62 20.77 L 250.66 20.67 L 250.70 20.57 L 250.75 20.47 L 250.81 20.39 L 250.87 20.30 L 250.94 20.22 L 250.94 20.22 L 251.03 20.14 L 251.12 20.08 L 251.21 20.01 L 251.31 19.96 L 251.41 19.92 L 251.52 19.88 L 251.63 19.85 L 251.75 19.83 L 251.87 19.82 L 252.00 19.81 ZM 253.61 36.76 L 257.16 36.76 L 257.16 38.37 L 247.70 38.37 L 247.70 36.76 L 251.59 36.76 L 251.59 27.33 L 247.82 27.33 L 247.82 25.72 L 253.61 25.72 L 253.61 36.76 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 261.60 25.72 L 263.33 25.72 L 263.50 27.49 L 263.50 27.49 L 263.65 27.31 L 263.81 27.13 L 263.98 26.96 L 264.15 26.80 L 264.34 26.64 L 264.53 26.50 L 264.73 26.36 L 264.93 26.23 L 265.15 26.12 L 265.37 26.01 L 265.37 26.01 L 265.60 25.90 L 265.83 25.81 L 266.06 25.72 L 266.29 25.65 L 266.52 25.59 L 266.75 25.54 L 266.98 25.50 L 267.21 25.48 L 267.44 25.46 L 267.67 25.45 L 267.67 25.45 L 268.35 25.49 L 268.95 25.60 L 269.48 25.79 L 269.95 26.05 L 270.34 26.39 L 270.66 26.80 L 270.90 27.29 L 271.08 27.85 L 271.19 28.49 L 271.22 29.20 L 271.22 38.37 L 269.21 38.37 L 269.21 30.69 L 269.21 30.69 L 269.21 30.41 L 269.20 30.15 L 269.19 29.90 L 269.18 29.66 L 269.17 29.44 L 269.15 29.23 L 269.13 29.03 L 269.10 28.85 L 269.07 28.68 L 269.04 28.53 L 269.04 28.53 L 269.01 28.38 L 268.97 28.24 L 268.93 28.11 L 268.87 27.99 L 268.82 27.88 L 268.75 27.77 L 268.69 27.67 L 268.61 27.58 L 268.53 27.50 L 268.44 27.42 L 268.44 27.42 L 268.34 27.35 L 268.24 27.29 L 268.12 27.24 L 268.00 27.19 L 267.86 27.15 L 267.72 27.12 L 267.57 27.09 L 267.40 27.08 L 267.23 27.07 L 267.05 27.06 L 267.05 27.06 L 266.84 27.07 L 266.64 27.09 L 266.44 27.12 L 266.24 27.16 L 266.05 27.22 L 265.85 27.29 L 265.66 27.37 L 265.47 27.46 L 265.29 27.57 L 265.10 27.69 L 265.10 27.69 L 264.92 27.81 L 264.75 27.95 L 264.59 28.08 L 264.43 28.22 L 264.28 28.37 L 264.13 28.52 L 263.99 28.68 L 263.86 28.84 L 263.73 29.00 L 263.62 29.17 L 263.62 38.37 L 261.60 38.37 L 261.60 25.72 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 278.71 29.97 L 278.71 29.97 L 278.72 30.45 L 278.73 30.92 L 278.76 31.38 L 278.80 31.82 L 278.85 32.26 L 278.91 32.68 L 278.98 33.09 L 279.07 33.48 L 279.16 33.87 L 279.26 34.24 L 279.26 34.24 L 279.38 34.60 L 279.51 34.95 L 279.64 35.29 L 279.79 35.63 L 279.95 35.96 L 280.12 36.29 L 280.30 36.61 L 280.49 36.92 L 280.69 37.23 L 280.90 37.53 L 280.90 37.53 L 281.12 37.82 L 281.37 38.12 L 281.62 38.42 L 281.89 38.72 L 282.18 39.03 L 282.48 39.34 L 282.79 39.66 L 283.12 39.98 L 283.47 40.30 L 283.82 40.62 L 282.79 41.73 L 282.79 41.73 L 282.38 41.39 L 281.98 41.05 L 281.59 40.71 L 281.23 40.37 L 280.87 40.03 L 280.53 39.70 L 280.21 39.36 L 279.90 39.03 L 279.61 38.70 L 279.34 38.37 L 279.34 38.37 L 279.08 38.03 L 278.83 37.69 L 278.60 37.34 L 278.38 36.98 L 278.17 36.61 L 277.97 36.24 L 277.78 35.86 L 277.61 35.47 L 277.45 35.07 L 277.30 34.67 L 277.30 34.67 L 277.16 34.26 L 277.05 33.83 L 276.94 33.39 L 276.85 32.94 L 276.77 32.47 L 276.71 32.00 L 276.66 31.51 L 276.63 31.01 L 276.61 30.49 L 276.60 29.97 L 276.60 29.97 L 276.61 29.44 L 276.63 28.93 L 276.66 28.42 L 276.71 27.93 L 276.77 27.46 L 276.85 26.99 L 276.94 26.54 L 277.05 26.10 L 277.16 25.68 L 277.30 25.26 L 277.30 25.26 L 277.45 24.86 L 277.61 24.46 L 277.78 24.07 L 277.97 23.69 L 278.17 23.32 L 278.38 22.95 L 278.60 22.59 L 278.83 22.24 L 279.08 21.90 L 279.34 21.57 L 279.34 21.57 L 279.61 21.23 L 279.90 20.90 L 280.21 20.57 L 280.53 20.23 L 280.87 19.90 L 281.23 19.56 L 281.59 19.22 L 281.98 18.89 L 282.38 18.55 L 282.79 18.21 L 283.82 19.31 L 283.82 19.31 L 283.46 19.64 L 283.11 19.96 L 282.78 20.27 L 282.46 20.59 L 282.16 20.90 L 281.87 21.21 L 281.60 21.51 L 281.34 21.81 L 281.10 22.11 L 280.87 22.41 L 280.87 22.41 L 280.66 22.70 L 280.46 23.01 L 280.27 23.32 L 280.09 23.64 L 279.92 23.96 L 279.77 24.29 L 279.62 24.63 L 279.48 24.98 L 279.36 25.33 L 279.24 25.69 L 279.24 25.69 L 279.14 26.06 L 279.05 26.44 L 278.97 26.83 L 278.90 27.24 L 278.84 27.66 L 278.80 28.10 L 278.76 28.54 L 278.73 29.00 L 278.72 29.48 L 278.71 29.97 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 289.34 38.37 L 294.00 31.69 L 289.90 25.72 L 292.30 25.72 L 295.22 30.42 L 298.18 25.72 L 300.50 25.72 L 296.40 31.60 L 301.06 38.37 L 298.61 38.37 L 295.13 32.94 L 291.65 38.37 L 289.34 38.37 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 309.60 34.41 L 309.60 34.41 L 309.76 34.41 L 309.92 34.43 L 310.07 34.46 L 310.21 34.50 L 310.35 34.55 L 310.48 34.61 L 310.61 34.69 L 310.74 34.77 L 310.85 34.87 L 310.97 34.98 L 310.97 34.98 L 311.08 35.10 L 311.18 35.21 L 311.26 35.34 L 311.34 35.46 L 311.40 35.59 L 311.45 35.73 L 311.49 35.87 L 311.52 36.02 L 311.54 36.17 L 311.54 36.33 L 311.54 36.33 L 311.54 36.51 L 311.53 36.69 L 311.50 36.87 L 311.47 37.06 L 311.43 37.24 L 311.38 37.44 L 311.32 37.63 L 311.25 37.82 L 311.17 38.02 L 311.09 38.22 L 309.14 42.66 L 307.32 42.66 L 308.45 37.93 L 308.45 37.93 L 308.38 37.88 L 308.32 37.82 L 308.26 37.76 L 308.20 37.70 L 308.14 37.63 L 308.09 37.56 L 308.04 37.48 L 307.99 37.40 L 307.94 37.32 L 307.90 37.24 L 307.90 37.24 L 307.85 37.15 L 307.82 37.06 L 307.79 36.98 L 307.76 36.89 L 307.73 36.80 L 307.71 36.71 L 307.70 36.62 L 307.69 36.53 L 307.68 36.44 L 307.68 36.35 L 307.68 36.35 L 307.69 36.19 L 307.70 36.03 L 307.73 35.88 L 307.77 35.74 L 307.82 35.60 L 307.88 35.47 L 307.95 35.34 L 308.03 35.21 L 308.13 35.10 L 308.23 34.98 L 308.23 34.98 L 308.35 34.87 L 308.46 34.77 L 308.59 34.69 L 308.72 34.61 L 308.85 34.55 L 308.99 34.50 L 309.13 34.46 L 309.28 34.43 L 309.44 34.41 L 309.60 34.41 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 339.70 38.44 L 339.70 38.44 L 339.54 38.87 L 339.38 39.28 L 339.20 39.67 L 339.01 40.04 L 338.81 40.40 L 338.60 40.74 L 338.38 41.06 L 338.15 41.36 L 337.91 41.65 L 337.66 41.92 L 337.66 41.92 L 337.39 42.17 L 337.10 42.39 L 336.79 42.60 L 336.45 42.79 L 336.10 42.95 L 335.73 43.09 L 335.34 43.22 L 334.92 43.32 L 334.49 43.40 L 334.03 43.45 L 333.74 41.85 L 333.74 41.85 L 333.99 41.80 L 334.23 41.75 L 334.46 41.70 L 334.68 41.64 L 334.88 41.57 L 335.08 41.50 L 335.27 41.43 L 335.44 41.35 L 335.61 41.26 L 335.76 41.17 L 335.76 41.17 L 335.91 41.09 L 336.05 40.99 L 336.19 40.89 L 336.32 40.79 L 336.44 40.68 L 336.56 40.57 L 336.67 40.45 L 336.77 40.33 L 336.87 40.20 L 336.96 40.07 L 336.96 40.07 L 337.05 39.94 L 337.13 39.79 L 337.21 39.64 L 337.29 39.49 L 337.37 39.32 L 337.45 39.15 L 337.53 38.96 L 337.61 38.77 L 337.68 38.57 L 337.75 38.37 L 337.08 38.37 L 332.69 25.72 L 334.82 25.72 L 338.45 36.83 L 342.02 25.72 L 344.11 25.72 L 339.70 38.44 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
<path d='M 354.89 29.97 L 354.89 29.97 L 354.88 29.48 L 354.87 29.00 L 354.84 28.54 L 354.80 28.10 L 354.75 27.66 L 354.69 27.24 L 354.62 26.83 L 354.53 26.44 L 354.44 26.06 L 354.34 25.69 L 354.34 25.69 L 354.22 25.33 L 354.10 24.98 L 353.97 24.63 L 353.82 24.29 L 353.66 23.96 L 353.50 23.64 L 353.31 23.32 L 353.12 23.01 L 352.92 22.70 L 352.70 22.41 L 352.70 22.41 L 352.48 22.11 L 352.24 21.81 L 351.99 21.51 L 351.72 21.21 L 351.43 20.90 L 351.13 20.59 L 350.82 20.27 L 350.48 19.96 L 350.14 19.64 L 349.78 19.31 L 350.81 18.21 L 350.81 18.21 L 351.22 18.55 L 351.62 18.89 L 352.00 19.22 L 352.37 19.56 L 352.72 19.90 L 353.06 20.23 L 353.38 20.57 L 353.68 20.90 L 353.97 21.23 L 354.24 21.57 L 354.24 21.57 L 354.50 21.90 L 354.75 22.24 L 354.99 22.59 L 355.21 22.95 L 355.42 23.32 L 355.62 23.69 L 355.80 24.07 L 355.98 24.46 L 356.13 24.86 L 356.28 25.26 L 356.28 25.26 L 356.42 25.68 L 356.54 26.10 L 356.65 26.54 L 356.74 26.99 L 356.82 27.46 L 356.88 27.93 L 356.94 28.42 L 356.97 28.93 L 356.99 29.44 L 357.00 29.97 L 357.00 29.97 L 356.99 30.49 L 356.97 31.01 L 356.94 31.51 L 356.88 32.00 L 356.82 32.47 L 356.74 32.94 L 356.65 33.39 L 356.54 33.83 L 356.42 34.26 L 356.28 34.67 L 356.28 34.67 L 356.13 35.07 L 355.98 35.47 L 355.80 35.86 L 355.62 36.24 L 355.42 36.61 L 355.21 36.98 L 354.99 37.34 L 354.75 37.69 L 354.50 38.03 L 354.24 38.37 L 354.24 38.37 L 353.97 38.70 L 353.68 39.03 L 353.38 39.36 L 353.06 39.70 L 352.72 40.03 L 352.37 40.37 L 352.00 40.71 L 351.62 41.05 L 351.22 41.39 L 350.81 41.73 L 349.78 40.62 L 349.78 40.62 L 350.13 40.30 L 350.48 39.98 L 350.80 39.66 L 351.12 39.34 L 351.41 39.03 L 351.70 38.72 L 351.97 38.42 L 352.22 38.12 L 352.46 37.82 L 352.68 37.53 L 352.68 37.53 L 352.90 37.23 L 353.10 36.92 L 353.29 36.61 L 353.47 36.29 L 353.65 35.96 L 353.81 35.63 L 353.96 35.29 L 354.09 34.95 L 354.22 34.60 L 354.34 34.24 L 354.34 34.24 L 354.44 33.87 L 354.53 33.48 L 354.62 33.09 L 354.69 32.68 L 354.75 32.26 L 354.80 31.82 L 354.84 31.38 L 354.87 30.92 L 354.88 30.45 L 354.89 29.97 Z' style='fill-rule: evenodd; fill: #000000; stroke-width: 0.75; stroke: none;' clip-path='url(#cpMHw1MDR8MzYwfDA=)' />
</svg>

Některé soubory nejsou zobrazny, neboť je v této revizi změněno mnoho souborů

Načítá se…
Zrušit
Uložit