Animate a map based on year values

Hi there! I have an Argentina map with each state, that i got from GADM and converted it to sf. So i have the list of states "NAME_1" with its shape in "geometry".

library(tidyverse)
library(raster)
library(sf)
arg <- getData("GADM",country="ARG",level=1)
arg_sf<-st_as_sf(arg) %>% select(c(NAME_1,geometry))

In the other hand I have some values in a data frame that shows how fair income is distributed in each state (there are 24 states) over 12 years.

head(gini)
       NAME_1       year    gini
1	Buenos Aires	2003	0,481
2	Buenos Aires	2004	0,460
3	Buenos Aires	2005	0,441
4	Buenos Aires	2006	0,457
5	Buenos Aires	2007	0,442
6	Buenos Aires	2008	0,425

...
...
...

I would like to animate the map, so each state changes its colour everytime the year changes.
So I merge the values with the geometry of the state :

total <- as.tibble(merge(gini,arg_sf, by="NAME_1"))

argentina<- ggplot(total) +
  geom_sf(col = NA, aes(fill=(gini),geometry=geometry)) +
  scale_fill_discrete() + 
  coord_sf(datum = NA) +
  theme_minimal() +
argentina

So then i can animate it with gganimate

anim<-argentina + transition_states(year,
                                 transition_length = 2,
                                 state_length = 1)

But I am pretty sure there is something wrong! It is taking it too long to animate it and the file goes so heavy that I have an error as a result that says I am out of memory.
I think that it is related to the fact that when I merge, I am copying the "geometry" of each state once for every observation, and that might make it quite heavy.

Is this the correct way to plot or animate a map?

Thank you!

I think the most important thing for speed is to reduce the detail of the geographical polygons.
In this example I use rmapshaper::ms_simplify, I belive the default is to keep 5% of the vertices


library(raster)
library(sf)
library(rgeos)

library(tidyverse)
library(gganimate)
library(transformr)
library(gifski)
library(rmapshaper)

arg <- getData("GADM",country="ARG",level=1)
arg_sf<-st_as_sf(arg) %>% select(c(NAME_1,geometry))


#make up 10year data

nms<-unique(arg_sf$NAME_1)
set.seed(42)
(dataparms <- tibble(NAME_1=nms) %>% rowwise() %>% mutate(
                    mean_val = runif(1,100,1000),
                    stddv_val = runif(1,100,1000)) %>% ungroup)
(fakedata <- expand_grid(dataparms,tibble(year=factor(2010:2020))) %>%
    rowwise %>% mutate(gini=rnorm(1,mean_val,stddv_val)) %>% ungroup %>% select(NAME_1,year,gini))

simp_arg_sf <- rmapshaper::ms_simplify(arg_sf)
arg_sf_tibb <- as_tibble(simp_arg_sf)
total <- as.tibble(merge(fakedata,arg_sf_tibb, by="NAME_1"))

#

argentina<- ggplot(total) +
  geom_sf(col = NA, aes(fill=(gini),geometry=geometry,group=NAME_1)) +
  scale_fill_continuous() + 
  coord_sf(datum = NA) +
  theme_minimal()  + 
  geom_text(mapping=aes(x=-65,y=-20,label=year))

anim<-argentina + transition_states(year,
                                    transition_length = .5,
                                    state_length = .5)

gganimate::animate(anim, renderer=gifski_renderer(),width = 800, height = 600,
                   nframes=50,fps=10)
2 Likes

A couple things seem unusual in your ggplot call:

argentina<- ggplot(total) +
  geom_sf(col = NA, aes(fill=(gini),geometry=geometry)) +
  scale_fill_discrete() + 
  coord_sf(datum = NA) +
  theme_minimal() +
argentina
  • the specification of geometry = geometry in aes is superfluous; you can rely on defaults unless you are dealing with an object with more geometry columns (rather unusual, and not the case of GADM data)
  • the gini coefficient from your data frame does not look discrete (so scale_fill_continuous seems a better idea)
  • the non specified datum in coord_sf call seems strange; GADM is in WGS84, so I suggest coord_sf(st_crs(4326))
  • I don't quite understand your adding of object argentina on the last line of ggplot (after theme_minimal)

The full size GADM file is about 5 MB, which does not seem like much, but animating it via {gganimate} took a while on my mid range laptop, and used up about 8 gigs of RAM. The simplification mentioned by @nirgrahamuk is definitely a good idea.

I suggest the following approach, loosely based on your gini data frame - as it has only Buenos Aires data, and as {dplyr} does not support cross join it is somewhat convoluted at the beginning, but the plotting and animation parts are hopefully straightforward.

library(sf)
library(dplyr)
library(ggplot2)
library(gganimate)

# gadm file
borders <- readRDS(url("https://biogeo.ucdavis.edu/data/gadm3.6/Rsf/gadm36_ARG_1_sf.rds"))

# inner join to fake data - because dplyr can't cross join,
# and gganimate needs the rows with NAs
fake_data <- data.frame(NAME_0 = "Argentina",
                        year = 2003:2008)

borders <- borders %>% 
  inner_join(fake_data, by = "NAME_0") %>% 
  select(NAME_1, year)

# real data - a sample...
data <- tibble::tribble(~NAME_1,~year, ~gini,
                "Buenos Aires", 2003,	0.481,
                "Buenos Aires", 2004,	0.460,
                "Buenos Aires", 2005,	0.441,
                "Buenos Aires", 2006,	0.457,
                "Buenos Aires", 2007,	0.442,
                "Buenos Aires", 2008,	0.425) 

chrt_src <- borders %>% 
  left_join(data, by = c("NAME_1", "year"))

# plot the result
plot <- ggplot(data = chrt_src, aes(fill = gini)) +
  scale_fill_continuous() +
  geom_sf() + 
  coord_sf(crs = st_crs(4326)) +
  theme_void() +
  transition_time(year)

# animate the plot as a gif
animate(plot = plot,
        fps = 15,
        renderer = gifski_renderer(loop = T))

anim_save('argentina.gif')

argentina

2 Likes

Thank you! You helped me in a lot of ways to improve both my map and my speed.

You are amazing!

Thank you for your notes! Yes, there are a lot of irregularities in my maps, that is why I am sharing here.
Your points were really usefull in order to improve my code and how can i share it with others.

Thank you so much for your time!

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.