Search code examples
rgifpurrr

Problem with order of frames when creating a GIF in R (Libraries: "magick" and "purrr")


I'm creating a GIF image in R. First, I use a loop to create, and save, all plots in my working directory. Next, using the libraries magick and purrr I call those plots and join them to create a GIF. My problem is the following:

When the GIF is created, frames are not in the correct order. Each plot has the number case in its title, so it can be seen that they are not in order. Here my code:

# my df (result) is all possible combinations of cumulative frequency for # a 5 likert scale where each row sum is 1 along all its levels.
setwd(\myworkingdirectory...)
library(dplyr)
library(magick)
library(purrr)

values <- seq(0, 1, by=0.1)
values
df <- expand.grid(A1 = values, A2 = values, A3 = values, A4 = values, A5 = values)
df$TestSum <- rowSums(df)
result <- df[df$TestSum == 1, 1:5]
colnames(result) <- c("F1","F2","F3","F4","F5")
rownames(result) <- c(1:nrow(result))

colnames(result) <- c("F1","F2","F3","F4","F5")
> head(result)
   F1  F2 F3 F4 F5
1 1.0 0.0  0  0  0
2 0.9 0.1  0  0  0
3 0.8 0.2  0  0  0
4 0.7 0.3  0  0  0
5 0.6 0.4  0  0  0
6 0.5 0.5  0  0  0

#to simplify for the example...
result <- result[seq(1,nrow(result),9),]

Likert_plots <- function(Yr){
  png(filename = sprintf("%i%i.png",00,Yr))
  barplot(as.matrix(result[Yr,]), ylim = c(0,1), main = paste0("Case ", Yr), ylab = "Cumulative Freq.", xlab = "Levels")
  print(paste0("saving plot ", Yr))
  dev.off()
}

for (i in 1:nrow(result)) {
  Likert_plots(i)
}

list.files(pattern = "*.png", full.names = T) %>% 
  map(image_read) %>% 
  image_join() %>% 
  image_animate(fps=2) %>% 
  image_write("name.gif")

The name of those plots are correct and I think they are not the problem (e.g.: "01.png", "02.png" ... "0103.png") I think the problem may be in the function which call them, e.g.: list.files(), map() or image_join(). Probably, this possible "guilty" function not respect the ordering numbers but another order. It that case: Can I change it?

I appreciate any help on this. Here the GIF to see the problem. Note that the order follows some logical order: [1, 10, 100, 101, 102, 103, 2 , 11, 12, 13, 14, 15, 16, 17, 18, 19, 2, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 3, 31, 32, 33, 34, 35, 36, 37, 38, 39, 4, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 5, 50, 51 ..., 6, 60, 61 ... 7, 70, 71 ... 8, 80, 81 ... and so son]

enter image description here


Solution

  • The issue is with your file names. The "some logical order" you saw is from how list.files sorts filenames. Since they're strings and not numbers, they get sorted based on the first character, then the second, etc. In your code you have png(filename = sprintf("%i%i.png",00,Yr)) which I'll simplify to purrr::map_chr(1:105, ~ sprintf("%i%i.png",00,.)). If you sort the output (which remember is a string, not an integer) you get

    purrr::map_chr(1:105, ~ sprintf("%i%i.png",00,.)) %>% sort %>% head
    #> [1] "01.png"   "010.png"  "0100.png" "0101.png" "0102.png" "0103.png"
    

    Which is the order your gif is assembled in. The problem is that a single leading zero is prepended to all of your numbers. To get correct ordering use str_pad from stringr to add a variable number of leading zeros to result in 4 digit numbers. (see here for other options)

    purrr::map_chr(1:105, ~ stringr::str_pad(., 4, pad = "0")) %>% paste(".png", sep="") %>% sort %>% head
    #> [1] "0001.png" "0002.png" "0003.png" "0004.png" "0005.png" "0006.png"
    

    So change your png(filename = sprintf("%i%i.png",00,Yr)) to png(filename = paste(stringr::str_pad(Yr, 4, pad = "0"), ".png", sep=""))

    There is a way to still use sprintf to get 4 digit numbers using png(filename = sprintf("%04d.png",Yr)), but I think it's much less readable than using stringr.