thomasp85 / gganimate

A Grammar of Animated Graphics
https://gganimate.com
Other
1.95k stars 308 forks source link

Animation fails when transition_states variable is a factor, but not if it is a character? #434

Closed saeeshm closed 3 years ago

saeeshm commented 3 years ago

Hello,

I have another issue working with the same dataset as #429. As I mentioned, after applying the workaround for "filled" data that I described there, my animation rendered. However, I needed to construct more descriptive labels for each state, so I created a new variable that was a label based on the values of the SES-category variable (included in reprex below).

When the class of this variable is character, the animation works fine. But the order of states is incorrect - it goes from percentile 1 to 5 to percentile 11 to 15, rather than to 6-10 as it should. Arranging the table before creating the animation also has no impact on the order of states in the animation:

popAnim-GAMA

(this is a testing example so it moves quite fast because of having very few frames. The full gif is too big to upload here).

To fix this, I tried turning it into an ordered factor, but when I pass the variable as a factor to SES states the animation fails, with the same error I was receiving in issue #429.

Error in tween_state(as.data.frame(full_set$from), as.data.frame(full_set$to),  : 
  identical(classes, col_classes(to)) is not TRUE

So overall, I can't order the states when states is a character variable, and it fails to animate when it is a factor?

Here is the reprex:

# !usr/bin/env Rscript

# Author: Saeesh Mangwani
# Date: 2021-03-19

# Description: Animation issues for reprex

# ==== Loading libraries ====
library(tidyverse)
library(sf)
#> Linking to GEOS 3.8.0, GDAL 3.0.4, PROJ 6.3.1
library(ragg)
library(reprex)
library(gganimate)
# Script where I define some helper functions for working with my data, including
# the custom theme function I use later (theme_accra)
source("C:/Users/saees/codeProjects/RProjects/accra/accra_viz.R")
#> 
#> Attaching package: 'rlang'
#> The following objects are masked from 'package:purrr':
#> 
#>     %@%, as_function, flatten, flatten_chr, flatten_dbl, flatten_int,
#>     flatten_lgl, flatten_raw, invoke, list_along, modify, prepend,
#>     splice
# Adding a showtext font 'lmroman
library(showtext)
#> Loading required package: sysfonts
#> Loading required package: showtextdb
font_paths(new = "C:/Users/saees/Appdata/Local/Microsoft/Windows/Fonts")
#> [1] "C:\\Users\\saees\\AppData\\Local\\Microsoft\\Windows\\Fonts"
#> [2] "C:\\Windows\\Fonts"
font_add(family = "lmroman", regular = "lmroman10-regular.otf")
showtext_auto()
# ==== Reading data ====

# Reading the census and the shapefile, ensuring only relevant variables are selected
census <- census <- read_csv("C:/Users/saees/codeProjects/RProjects/accra/data/gama/gama20210216_filled.csv")
#> 
#> -- Column specification --------------------------------------------------------
#> cols(
#>   .default = col_double()
#> )
#> i Use `spec()` for the full column specifications.
accra <- read_sf("C:/Users/saees/codeProjects/RProjects/accra/data/shp/GAMA_20200420.shp", query = "SELECT ea_code9ch FROM GAMA_20200420")

# Turning the eacode variable in the census dataset to a string to make joining with the shapefile easier
census <- census %>% 
  mutate(ea_code9ch = str_pad(ea_code9ch, width = 9, side = 'left', pad = '0'))

# Joining data to shapefile
gama <- census %>% 
  inner_join(accra, by = c("ea_code9ch" = "ea_code9ch")) %>% 
  st_as_sf()

# Removing the extra dataset to only keep gama
rm(list = c('census', 'accra'))

# ==== Adding more descriptive labels to the SES cat variable ====

# THe number of percentile groups in the dataset
n_percentiles <- 20
# First splitting a vector of 100 into the number of groups defined above to mimic
# the percentile distribution of SES_cat
percentiles <- split(1:100, sort(rep_len(1:n_percentiles, 100)))
# Turning each list into only a min and max, as that is all we need to construct a
# range label
percentiles <- map(percentiles, ~{ c(min(.x), max(.x))})

# Getting the SES-categories as a vector, and creating a parallel vector of
# labelled names based on it
percentile_labels <- map_chr(as.character(gama$SES_cat_g3), ~ {
  case_when(
    # If the category is not 30 or 40, constructing the label from the right
    # percentile bin
    !(.x %in% c(30, 40)) ~ paste0(percentiles[[.x]][1], "-", percentiles[[.x]][2], "th Percentile"),
    # Otherwise using default labels
    .x == 30 ~ "Students & university residents",
    .x == 40 ~ "Homeless and outdoor sleeper residents"
  )
})

# Getting a correctly ordered factor for the cat labels, based on the order of the
# categorical variable and passing this as the new label variable
gama$SES_cat_labels <- fct_reorder(percentile_labels, as.numeric(gama$SES_cat_g3))

# Illustrating the outcome of this
gama %>% 
  arrange(ea_code9ch, as.numeric(SES_cat_g3)) %>% 
  select(SES_cat_g3, SES_cat_labels) %>% 
  head(n = 10)
#> Simple feature collection with 10 features and 2 fields
#> geometry type:  MULTIPOLYGON
#> dimension:      XY
#> bbox:           xmin: -0.3847607 ymin: 5.484172 xmax: -0.3786746 ymax: 5.488131
#> geographic CRS: WGS 84
#> # A tibble: 10 x 3
#>    SES_cat_g3 SES_cat_labels                                            geometry
#>         <dbl> <fct>                                           <MULTIPOLYGON [°]>
#>  1          1 1-5th Percentile (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  2          2 6-10th Percenti~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  3          3 11-15th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  4          4 16-20th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  5          5 21-25th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  6          6 26-30th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  7          7 31-35th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  8          8 36-40th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#>  9          9 41-45th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~
#> 10         10 46-50th Percent~ (((-0.3786746 5.486327, -0.3788675 5.486252, -0.~

# ==== Issue with not rendering when states variable is a factor ====

p <- gama %>% 
  mutate(group = 1L) %>%
  # Removing some EAs and district to only map the relevant area
  filter(district_code %in% c(10304)) %>%
  filter(!(SES_cat_g3 %in% c(30, 40))) %>% 
  # Arranging the data seems to have no impact?
  arrange(ea_code9ch, as.numeric(SES_cat_g3)) %>% 
  # If I turn the variable back to character, the animation renders. Otherwise, it fails:
  # mutate(SES_cat_labels = as.character(SES_cat_labels)) %>% 
  # Enumeration area code, group, median logged income, socio-economic category
  dplyr::select(ea_code9ch, group, ni_num, SES_cat_labels) %>% 
  # Turning population data to a factor to plot it using quintile breaks
  mutate(ni_num = cut(ni_num, breaks = quantile(gama$medlec, probs = seq(0,1,0.2), na.rm = T), ordered_result = T)) %>% 
  # Ensuring it is a factor
  mutate(ni_num = as_factor(ni_num)) %>% 
  # Plotting
  ggplot() +
  geom_sf(aes(fill = ni_num, group = group), colour = "white", size = 0.2) +
  scale_fill_brewer(palette = "OrRd") +
  labs(title = "Median Income: SES Category {closest_frame}",
       subtitle = "Greater Accra Metropolitan Area",
       fill = "Median Income\n(Quantiles)",
       x = NULL,
       y = NULL) +
  # Custom theme that I've made, only edits layout variables like font panel grid, legend, etc.
  theme_accra(theme_font = "lmroman", tscale = 7)

# Transition states (the SES cat variable is grouping factor that defines different socio-economic categories).
anim <- p + transition_states(SES_cat_labels, transition_length = 2, state_length = 2, wrap = T) + exit_fade()

# Using a small number of frames to test whether it's working
anim_save("popAnim-GAMA.gif", 
          animation = anim, 
          device = "ragg_png", width = 6.5, height = 5.5,
          scaling = 0.45, units = "in", res = 300,
          nframes = 20, fps = 10)
#> Error in tween_state(as.data.frame(full_set$from), as.data.frame(full_set$to), : identical(classes, col_classes(to)) is not TRUE

And my session information:

R version 4.0.4 (2021-02-15)
Platform: x86_64-w64-mingw32/x64 (64-bit)
Running under: Windows 10 x64 (build 19042)

Matrix products: default

locale:
[1] LC_COLLATE=English_Canada.1252  LC_CTYPE=English_Canada.1252    LC_MONETARY=English_Canada.1252 LC_NUMERIC=C                    LC_TIME=English_Canada.1252    

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
 [1] reprex_1.0.0    showtext_0.9-2  showtextdb_3.0  sysfonts_0.8.3  knitr_1.31      ragg_1.1.1      gifski_0.8.6    gganimate_1.0.7 ggspatial_1.1.5 tmap_3.3       
[11] sf_0.9-7        forcats_0.5.1   stringr_1.4.0   dplyr_1.0.4     purrr_0.3.4     readr_1.4.0     tidyr_1.1.2     tibble_3.1.0    ggplot2_3.3.3   tidyverse_1.3.0
[21] rlang_0.4.10   

loaded via a namespace (and not attached):
 [1] leafem_0.1.3           colorspace_2.0-0       ellipsis_0.3.1         class_7.3-18           leaflet_2.0.4.1        base64enc_0.1-3        fs_1.5.0              
 [8] dichromat_2.0-0        rstudioapi_0.13        farver_2.1.0           fansi_0.4.2            lubridate_1.7.9.2      xml2_1.3.2             codetools_0.2-18      
[15] jsonlite_1.7.2         tmaptools_3.1-1        broom_0.7.5            dbplyr_2.1.0           png_0.1-7              clipr_0.7.1            compiler_4.0.4        
[22] httr_1.4.2             backports_1.2.1        assertthat_0.2.1       cli_2.3.1              tweenr_1.0.1           htmltools_0.5.1.1      prettyunits_1.1.1     
[29] tools_4.0.4            gtable_0.3.0           glue_1.4.2             Rcpp_1.0.6             jquerylib_0.1.3        cellranger_1.1.0       styler_1.3.2          
[36] raster_3.4-5           vctrs_0.3.6            transformr_0.1.3       leafsync_0.1.0         crosstalk_1.1.1        lwgeom_0.2-5           xfun_0.21             
[43] ps_1.6.0               rvest_0.3.6            lpSolve_5.6.15         lifecycle_1.0.0        pacman_0.5.1           XML_3.99-0.5           scales_1.1.1          
[50] hms_1.0.0              parallel_4.0.4         RColorBrewer_1.1-2     yaml_2.2.1             sass_0.3.1             stringi_1.5.3          highr_0.8             
[57] e1071_1.7-4            pkgconfig_2.0.3        systemfonts_1.0.1.9000 evaluate_0.14          lattice_0.20-41        htmlwidgets_1.5.3      tidyselect_1.1.0      
[64] processx_3.4.5         plyr_1.8.6             magrittr_2.0.1         R6_2.5.0               generics_0.1.0         DBI_1.1.1              pillar_1.5.0          
[71] haven_2.3.1            withr_2.4.1            units_0.7-0            stars_0.5-1            abind_1.4-5            sp_1.4-5               modelr_0.1.8          
[78] crayon_1.4.1           KernSmooth_2.23-18     utf8_1.1.4             rmarkdown_2.7          progress_1.2.2         grid_4.0.4             readxl_1.3.1          
[85] callr_3.5.1            digest_0.6.27          classInt_0.4-3         textshaping_0.3.1      munsell_0.5.0          viridisLite_0.3.0      bslib_0.2.4   
thomasp85 commented 3 years ago

I think this may have been fixed with the latest tweenr release - can I get you to confirm?

saeeshm commented 3 years ago

Confirmed! Thank you so much :)