Error while plotting with ggsave and other save functions
Asked Answered
W

2

7

I have a problem concerning the function ggsave() and I would be really grateful for any help and or suggestions/solutions. I am creating four plots and put them all in one big plot, and since I want to loop the whole function using all columns in my dataframe I want to save the created plots in a specified folder (preferably with an identifying name).

    plotting_fun3 <- function(Q){
  
  plot1 <- plot_likert(
    t(Q),
    title = "Total population",
    legend.labels = c("strongly disagree","disagree", "neither nor", "agree", "strongly agree"),
    grid.range = c(1.6, 1.1),
    expand.grid = FALSE,
    axis.labels = c(" "),
    values = "sum.outside",
    show.prc.sign = TRUE,
    catcount = 4,
    cat.neutral = 3,
    
  )
  
  plot2 <- plot_likert(
    t(Q[survey$animal=="Dogs"]),
    title = "Female",
    legend.labels = c("strongly disagree","disagree", "neither nor", "agree", "strongly agree"),
    grid.range = c(1.6, 1.1),
    expand.grid = FALSE,
    axis.labels = c(" "),
    values = "sum.outside",
    show.prc.sign = TRUE,
    catcount = 4,
    cat.neutral = 3,
    
  )
  
  plot3 <- plot_likert(
    t(Q[survey$animal=="Cats"]),
    title = "Male",
    legend.labels = c("strongly disagree","disagree", "neither nor", "agree", "strongly agree"),
    grid.range = c(1.6, 1.1),
    expand.grid = FALSE,
    axis.labels = c(" "),
    values = "sum.outside",
    show.prc.sign = TRUE,
    catcount = 4,
    cat.neutral = 3,
    
  )
  
  plot4 <- plot_likert(
    t(Q[survey$animal=="Turtle"]),
    title = "Others",
    legend.labels = c("strongly disagree","disagree", "neither nor", "agree", "strongly agree"),
    grid.range = c(1.6, 1.1),
    expand.grid = FALSE,
    axis.labels = c(" "),
    values = "sum.outside",
    show.prc.sign = TRUE,
    catcount = 4,
    cat.neutral = 3,
    
  )
  
  theplot <- ggarrange(plot1, plot2, plot3, plot4, 
                       labels = NULL,
                       common.legend = TRUE,
                       legend = "bottom",
                       ncol = 1, nrow = 4)
  
  #ggsave(filename=paste(Q,".png",sep=""), plot=theplot, device = "png")
  
  #ggsave(filename=paste("animal_plot", ID, ".jpeg"), plot=plots[[x]])
  
  #ggsave(path = "/myDirectory",
  #       device = "png", filename = "animal_plot", plot = theplot)
  
  #save_plot(filename = "hello", plot = theplot, 
  #          "/myDirectory",
  #          device = "png")

  #ggsave(sprintf("%s.pdf", Q), device = "pdf")
  
  return(theplot)
}

The commented lines show all kinds of ways I have tried to save the plot in my directory. I encounter 2 different problems:

Either: Most of the ggsave suggestions I found on stack overflow. Several of them did not include the line device = "png". If I leave out this line of code I always get something like this:

 Fehler: `device` must be NULL, a string or a function.
Run `rlang::last_error()` to see where the error occurred. 

If I follow that command I get:

<error/rlang_error>
`device` must be NULL, a string or a function.
Backtrace:
 1. global::plotting_fun3(survey[, 9])
 2. ggplot2::ggsave(sprintf("%s.pdf", Q))
 3. ggplot2:::plot_dev(device, filename, dpi = dpi)
Run `rlang::last_trace()` to see the full context.
> rlang::last_trace()
<error/rlang_error>
`device` must be NULL, a string or a function.
Backtrace:
    █
 1. └─global::plotting_fun3(survey[, 9])
 2.   └─ggplot2::ggsave(sprintf("%s.pdf", Q))
 3.     └─ggplot2:::plot_dev(device, filename, dpi = dpi)

So online I found people with the same or similar problem and the suggestion has always been to use device = "png" or similar.

Now if I do this I encounter a different problem: The plots are saved in the right directory but the name is wrong. Usually the name is "3.png" or "3.pdf" or depending on what I create. If "3.png" already exists it gives the file another number.

I had this problem in an older project three months ago and couldn't solve it and now I have it again.

For what it's worth, I use macOS Mojave 10.14.6, my R version is Version 1.3.1093

Thank you in advance for any thoughts, suggestions or other comments.

[EDIT]

Here is some sample data:

 > str(myDF[,c(2,9:10)])
 data.frame':   123 obs. of  3 variables:
 $ animal: chr  "Cats" "Cats" "Turtles" "Cats" ...
 $ q8    : int  3 5 5 3 4 4 2 5 3 5 ...
 $ q9.1  : int  4 5 5 4 3 4 2 4 2 4 ...

The values stay between 1 and 5 for all observations. They actually represent answers such as "strongly agree", "agree", "neither agree nor disagree"...etc.

Alternatively, if you prefer this to the other one:

> myDF[,c(2,9:10)]
     animal q8 q9.1
1      Cats  3    4
2      Cats  5    5
3   Turtles  5    5
4      Cats  3    4
5   Turtles  4    3
6   Turtles  4    4
7   Turtles  2    2
8      Cats  5    4
9      Cats  3    2
10  Turtles  5    4
11  Turtles  4    3
12  Turtles  3    3
13     Dogs  3    3
14     Cats  3    3
15     Dogs  1    1
16     Dogs  1    3
Wax answered 3/1, 2021 at 19:50 Comment(4)
It's unclear to me exactly what's happening. Please make your code reproducible by adding some sample data so we can actually run and test the code. Also it seems like you could easily simply the problem by just trying to draw one plot at first. Is this specific to plot_likert or ggarrange? Does each of your attempts give the exact same error message?Disjunctive
Thanks for the quick answer. So first: Making just one plot doesn't help, and I experience the same problem if I make a plot using ggplot(). variable <- rnorm(50, 5, 1) y <- rnorm(50, 2.5, 0.25) df <- as.data.frame(cbind(variable,y)) myplot <- ggplot(df,mapping = aes(variable,y))+ geom_point() ggsave(filename=paste0(variable,".png",sep=""), path = "/myDirectory", plot=myplot). I get the same response. "Device must be NULL, a string or a function." If the filename is a simple string it works but I will make at least 150 plots so that is not an option.Wax
I added some data to the original post ;)Wax
Mainly I just want to find a way to automatically save the plots by their column name so that I can differentiate each survey question. I also thought about writing their column name within the plots but 1. it didn't manage to do that, 2. I would later have to erase it manually anyway.Wax
W
5

The issue with file name is due to you use Q which is a dataframe in the filename defintion so it will result in some very messy way depend on how your system handling filename.

# This command result in a few long character depend on number of columns in Q.
# 4 columns w+ill result 4 long character and ggsvave will return the error
# Error: `device` must be NULL, a string or a function.
ggsave(filename=paste(Q,".png",sep=""), plot=theplot, device = "png")
  
# Again not sure what ID is here but if it was a dataframe you got
# same error with previous one.
ggsave(filename=paste("animal_plot", ID, ".jpeg"), plot=plots[[x]])

# This one it doesn't specific a file name but a directory
# ggsave will return an error:
# Error: Unknown graphics device ''
# If you specify device = "png" - error will be:
# Error in grid.newpage() : could not open file '/home/sinh' 
ggsave(path = "/myDirectory",
       device = "png", filename = "animal_plot", plot = theplot)

# Why there is a param "/myDirectory" here? and you should specify the extention
# in the file name. So the correct param is:
# filename = "/myDirectory/hello.png"
save_plot(filename = "hello", plot = theplot, 
          "/myDirectory",
          device = "png")

Here is one that should work properly but you need to input file name manually:

character_variable <- "my_character_variable_here_"
index_number <- 20
# If you specify sep = "" then just need to use paste0
file_name <- paste0(character_variable, index_number)
ggsave(filename=paste(file_name, ".jpeg"), plot=plots[[x]], device = "png")

And here is my rewrite function based on your function. You may try it out and tweak it a bit

# df is your survey data.frame
# q_column_name is the name of questionare column that you want to graph.
# the final output file will use q_column_name as file name.
plotting_fun3 <- function(df, q_column_name){
  
  require(foreach)
  require(dplyr)
  require(tidyr)
  
  graph_data <- df %>% select(one_of("animal", q_column_name))
  
  plot1 <- plot_likert(
    t(graph_data),
    title = "Total population",
    legend.labels = c("strongly disagree","disagree", "neither nor", "agree", "strongly agree"),
    grid.range = c(1.6, 1.1),
    expand.grid = FALSE,
    axis.labels = c(" "),
    values = "sum.outside",
    show.prc.sign = TRUE,
    catcount = 4,
    cat.neutral = 3,
  )
  
  animal_plots <- foreach(current_animal = c("Dog", "Cats", "Turtle")) %do% {
    plot_likert(
      t(graph_data %>% filter(animal == current_animal)),
      title = "Female",
      legend.labels = c("strongly disagree","disagree", "neither nor", "agree", "strongly agree"),
      grid.range = c(1.6, 1.1),
      expand.grid = FALSE,
      axis.labels = c(" "),
      values = "sum.outside",
      show.prc.sign = TRUE,
      catcount = 4,
      cat.neutral = 3
    )
  }
  
  theplot <- ggarrange(plot1, animal_plots[[1]],
                       animal_plots[[2]], animal_plots[[3]], 
                       labels = NULL,
                       common.legend = TRUE,
                       legend = "bottom",
                       ncol = 1, nrow = 4)
  
  ggsave(filename=paste(q_column_name, ".png",sep=""), plot=theplot, device = "png")
  
  return(theplot)
}

Here is how to use the function

# Assume that your survey dataframe variable is myDF
my_new_plot <- plotting_fun3(df = myDF, q_column_name = "q8")

[Updated] - Added the function to solve the graph issue.

Westhead answered 4/1, 2021 at 13:28 Comment(2)
Hi! Thank you for your effort!. Since I don't know some of the functions you used very well, how am I supposed to enter the function inputs? I tried plotting_fun3(df[,9], q_column_name = "q8") but it did not work, what do I do wrong? And how would you suggest do I use it in lapply? Should I make a column name vector in order for lapply to use all the right names? Let's say I want plots for df[,9:11] and col_names are c("q8","q9.1","q9.2") do you think your function can work like this? I never used lapply before when I have two inputs that need to be loopedWax
Thanks @SinhNguyen! I now used your idea of creating a function of two unknowns (with the filename as its own variable) but used my old function for it. It does work for singular plots but I could not get it to work with lapply() using two inputs. However I found a different solution. I take my old function with lapply and afterwwards I access the plots from temporary files, order them and name them according to a vector of column names like this: plot_names <- colnames(myDF[,9:56].Wax
S
1

For anyone encountering a similar problem in a less complicated setting: double check if you are passing a valid path/filename string to ggsave()!

In my case, I made a mistake with string processing using str_split() and path concatenation. Therefore, ggsave() was not given a valid single-string path, promting the Error: 'device' must be NULL, a string or a function. The error had nothing to do with my 'device' but I was simply not passing a proper string. Once I fixed the path, the issue was solved.

Selfexistent answered 7/3, 2022 at 14:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.