I have data with observations of species at different locations across multiple years. Some years don't have any data. I wanted to create an animated map that included the years for which there's no data, and I succeeded at doing so, but it appears only temporarily.
Here's some reprex:
library(sf)
library(ggplot2)
library(gganimate)
library(transformr)
library(tidyr)
rb <- data.frame(YEAR = c(1995, 2000, 2000, 2001, 2002, 2002, 2002, 2004),
TOTAL = c(5, 10, 8, 14, 15, 12, 16, 15),
LONG = c(runif(8, -80, -78)),
LAT = c(runif(8, 38, 40)))
Filling in the missing years with a random set of coordinates within the range of my coordinates:
rb.zero.df <- data.frame(rb %>%
complete(YEAR = full_seq(1995:2004, 1),
fill = list(LONG = -78.3893,
LAT = 39.63938,
TOTAL = 0)))
rb.zero <- st_as_sf(rb.zero.df, coords = c("LONG", "LAT"), crs = 4326)
Then using ifelse
for alpha
in aes
and shadow_mark
to make sure the zero points don't show up:
rb.zero.map <- ggplot() +
geom_sf(data = rb.zero, aes(size = TOTAL, alpha = ifelse(TOTAL == 0, 0, 1))) +
scale_alpha(range = c(0,1)) +
guides(alpha = "none") +
transition_states(YEAR, transition_length = 0, state_length = 1) +
ggtitle("{closest_state}") +
shadow_mark(alpha = ifelse(rb.zero$TOTAL == 0, 0, 0.25))
animate(rb.zero.map)
This used to work, but now it produces this error:
Error in `[[<-.data.frame`(`*tmp*`, i, value = c(0.25, 0, 0, 0, 0, 0.25, :
replacement has 13 rows, data has 12
I don't know how to interpret this error in this context.
If I take the ifelse
out of shadow_mark
, the code works.
My session info:
R version 4.3.2 (2023-10-31)
Platform: aarch64-apple-darwin20 (64-bit)
Running under: macOS Ventura 13.3.1
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.11.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/New_York
tzcode source: internal
attached base packages:
[1] stats graphics grDevices utils datasets methods base
other attached packages:
[1] transformr_0.1.3 gifski_1.12.0-2 gganimate_1.0.7 tidyr_1.3.0
[5] viridis_0.6.4 viridisLite_0.4.2 spData_2.3.0 dplyr_1.1.4
[9] terra_1.7-65 sf_1.0-15 ggplot2_3.4.4 leaflet.extras2_1.2.2
[13] leaflet_2.2.1 tmap_3.99.9000
loaded via a namespace (and not attached):
[1] DBI_1.2.1 gridExtra_2.3 tmaptools_3.1-1 remotes_2.4.2.1
[5] rlang_1.1.3 magrittr_2.0.3 e1071_1.7-14 compiler_4.3.2
[9] mgcv_1.9-0 png_0.1-8 vctrs_0.6.5 stringr_1.5.1
[13] profvis_0.3.8 pkgconfig_2.0.3 crayon_1.5.2 fastmap_1.1.1
[17] ellipsis_0.3.2 labeling_0.4.3 lwgeom_0.2-13 leafem_0.2.3
[21] utf8_1.2.4 promises_1.2.1 sessioninfo_1.2.2 purrr_1.0.2
[25] cachem_1.0.8 progress_1.2.3 later_1.3.2 tweenr_2.0.2
[29] parallel_4.3.2 prettyunits_1.2.0 R6_2.5.1 stringi_1.8.3
[33] RColorBrewer_1.1-3 pkgload_1.3.4 stars_0.6-4 Rcpp_1.0.12
[37] usethis_2.2.2 base64enc_0.1-3 leaflet.providers_2.0.0 Matrix_1.6-1.1
[41] httpuv_1.6.13 splines_4.3.2 tidyselect_1.2.0 rstudioapi_0.15.0
[45] dichromat_2.0-0.1 abind_1.4-5 codetools_0.2-19 miniUI_0.1.1.1
[49] pkgbuild_1.4.3 lattice_0.21-9 tibble_3.2.1 leafsync_0.1.0
[53] plyr_1.8.9 shiny_1.8.0 withr_3.0.0 units_0.8-5
[57] proxy_0.4-27 urlchecker_1.0.1 lpSolve_5.6.20 pillar_1.9.0
[61] KernSmooth_2.23-22 generics_0.1.3 sp_2.1-2 hms_1.1.3
[65] munsell_0.5.0 scales_1.3.0 xtable_1.8-4 class_7.3-22
[69] glue_1.7.0 tools_4.3.2 leaflegend_1.2.0 data.table_1.14.10
[73] fs_1.6.3 XML_3.99-0.16.1 grid_4.3.2 crosstalk_1.2.1
[77] devtools_2.4.5 colorspace_2.1-0 nlme_3.1-163 cols4all_0.7
[81] raster_3.6-26 cli_3.6.2 fansi_1.0.6 gtable_0.3.4
[85] digest_0.6.34 widgetframe_0.3.1 classInt_0.4-10 htmlwidgets_1.6.4
[89] farver_2.1.1 memoise_2.0.1 htmltools_0.5.7 lifecycle_1.0.4
[93] mime_0.12
I have no clue why this code previously worked and now doesn't - the data and the code are the same. I'd appreciate thoughts on this and/or alternate solutions to the missing years problem. Thank you!
Use
shadow_mark(alpha = alpha / 4)
instead of
shadow_mark(alpha = ifelse(rb.zero$TOTAL == 0, 0, 0.25))
When I tried your example I got a different error:
Error: arguments have different crs
But this seems to be a bug in gganimate and the temporary fix was to remove the crs
altogether.
To your problem at hand, you are using shadow_mark
not in its intended way. If you debug the train
function of ShadowMark
(debug(get("train", ShadowMark))
) you will see that whatever you pass to shadow_mark
will be evaluated in the context of the data slice which is shown in the current animation step.
I am not deep into the internals of gganimate
but apparently they split the original data for each slice and do their magic.
The important bit is, that at a certain step the "current" data is just a subset of the whole dataset (enriched with aesthetics data), that is, it contains only a subset of the original rows.
In this context evaluating ifelse(rb.zero$TOTAL == 0, 0, 0.25))
does not make sense, because it evaluates always to a vector of length nrow(rb.zero.df)
(=13) which may result in the error you are seeing, especially when the current data slice is only 12 rows long (while ifelse(rb.zero$TOTAL == 0, 0, 0.25)
is always 13 elements long).
N.B. The mean thing is that for certain combinations you do not see the error immediately because of R's
recycling rules, only if the lengths are not compatible (typically not being a multiple of each other) the error is thrown. This, by the way, may explain why you did not see the error for certain combinations, because it happened to result always in compatible lengths.
Having said that, the way shadow_mark
is supposed to be used, is to supply the names of aesthetics
as parameters. This works, because shadow_mark
uses quosures, that is it evaluates its parameters later and in the right context. The context is the data slice mentioned before, where there is a alpha
slot in the aesthetics enriched data slice (as you defined this aesthatic).
Long story short you should create your plot like this:
## drop crs b/c of unfixed bug
rb.zero <- st_as_sf(rb.zero.df, coords = c("LONG", "LAT"))
rb.zero.map <- ggplot() +
geom_sf(data = rb.zero, aes(size = TOTAL, alpha = ifelse(TOTAL == 0, 0, 1))) +
scale_alpha(range = c(0,1)) +
guides(alpha = "none") +
transition_states(YEAR, transition_length = 0, state_length = 1) +
ggtitle("{closest_state}") +
### use alpha, gganimate knows how to evaluate it properly
shadow_mark(alpha = alpha / 4)
animate(rb.zero.map)