I tried to write a function which would run other functions "safely". By safely here I just mean I don't want scripts to fall over if some plot functions fail. Simple example:
library(ggplot2)
library(purrr)
## function I thought I could use to run other functions safely
safe_plot <- function(.FUN, ...) {
safe_fun <- purrr::possibly(.f = .FUN, otherwise = NULL)
safe_fun(...)
}
## Simple plot function
plot_fun <- function(df) {
df %>%
ggplot(aes(xvar, yvar)) +
geom_point()
}
## Works for good data
some_data <- data.frame(xvar = 1:10, yvar = 1:10)
safe_plot(.FUN = plot_fun, df = some_data)
## Works here for data that is not defined:
> safe_plot(.FUN = plot_fun, df = not_defined)
NULL
## Falls over for an empty data frame
empty_data <- data.frame()
> safe_plot(.FUN = plot_fun, df = empty_data)
Error in FUN(X[[i]], ...) : object 'xvar' not found
What I would like is a generic function that I can pass plot functions to which - if an error occurs - won't stop a script. I know I could do the following for example:
## Simple plot function with check
plot_fun <- function(df) {
if (nrow(df) > 0) {
df %>%
ggplot(aes(xvar, yvar)) +
geom_point()
}
}
But here I'm more interested in learning about why purrr::possibly did not catch the error and a perhaps a better way to go about this problem...assuming I don't care what may have caused an error.
You could directly use tryCatch.
In either cases, the reason for the error not being catched by possibly and eventually tryCatch is due to the lazy evaluation of print.ggplot which occurs outside tryCatch.
Solution is to force print() into tryCatch():
library(ggplot2)
safe_plot <- function(.FUN, ...) {
tryCatch(print(.FUN(...)),error = function(e) NULL)
}
## Simple plot function
plot_fun <- function(df) {
df %>%
ggplot(aes(xvar, yvar)) +
geom_point()
}
## Works for good data
some_data <- data.frame(xvar = 1:10, yvar = 1:10)
safe_plot(.FUN = plot_fun, df = some_data)
#> NULL
empty_data <- data.frame()
safe_plot(.FUN = plot_fun, df = empty_data)
#> NULL
Related
How can you create a function that calls some predefined functions simultaneously?
E.g. I have 3 different functions like
myplot(data)
fmodel(data)
mymodel <- fmodel(data)
myconclusion(model = mymodel)
Now I want to create a new function that calls those predefined functions (from 1 to 3). What should I do?
I tried to do something like the below and receive the following error message, but I don't what was wrong.
P/s: my model involves linear regression and I've already put in the 'data' arguments.
myplot(mydata)
fmodel(mydata)
myconclusion(mymodel)
funlist <- list(
F1 = myplot
F2 = fmodel
mymodel <- fmodel
F3 = myconclusion
)
callfun <- function(funrange, data, ...){
for(i in funrange){
funlist[[i]](...)
}
}
callfun(1:3, data = mydata)
#Error in model.frame.default(formula = Y ~ X, data = mydata, drop.unused.levels = TRUE) :
#argument "data" is missing, with no default
Running the 3 functions inside another function should execute them, However, depending on what the functions actually do, there may not be any visible output.
f1 <- function(mydata, mymodel){
myplot(mydata)
fmodel(mydata)
myconclusion(mymodel)
}
f1(mydata, mymodel)
Again, depending on what these functions actually do will dictate the output.
EDIT
Here is an example for you
my_plot <- function(my_data){
my_data %>%
ggplot(aes(mpg, hp))+
geom_point()
}
my_model <- function(my_data){
my_data %>%
lm(mpg ~ hp, data = .) %>%
summary
}
my_model_2 <- function(my_data){
my_data %>%
lm(mpg ~ disp, data = .) %>%
summary
}
f1 <- function(my_data){
my_plot(my_data)
my_model(my_data)
my_model_2(my_data)
}
If you call f1(mtcars), all you will see is the output from my_model_2(), because that was the last function to be executed. my_plot() and my_model() were still executed, but you just couldn't see the results because all it does is preview a plot in the viewer, or print the model summary to the console.
One way to 'see' the plot produced by my_plot() is to change what it does, from previewing a plot in the viewer, to saving a copy of the plot. This may be done like this:
my_plot <- function(my_data){
my_data %>%
ggplot(aes(mpg, hp))+
geom_point()
ggsave('my_saved_plot.png')
}
Or, wrapping each function inside print will print the model summaries to the console, and show the plot in the viewer
f1 <- function(my_data){
print(my_plot(my_data))
print(my_model(my_data))
print(my_model_2(my_data))
}
This is a follow up question to this question which didn't get any traction. I realise now that this has nothing to do with purrr::possibly specifically as tryCatch also doesn't work like I thought.
I'm trying to write a function which will run any other arbitrary function without throwing an error. This might not be good practice but I want to understand why this does not work:
library(ggplot2)
## function I thought I could use to run other functions safely
safe_plot <- function(.FUN, ...) {
tryCatch({
.FUN(...)
},
error = function(e) {
message(e)
print("I have got passed the error")
})
}
## Simple plot function
plot_fun <- function(df) {
ggplot(df, aes(xvar, yvar)) +
geom_point()
}
## Works for good data
some_data <- data.frame(xvar = 1:10, yvar = 1:10)
safe_plot(.FUN = plot_fun, df = some_data)
## Works here for data that is not defined:
> safe_plot(.FUN = plot_fun, df = not_defined)
object 'not_defined' not found[1] " I have got passed the error"
## Falls over for an empty data frame
empty_data <- data.frame()
safe_plot(.FUN = plot_fun, df = empty_data)
## Why does't this get past this error and print the message?
> safe_plot(.FUN = plot_fun, df = empty_data)
Error in FUN(X[[i]], ...) : object 'xvar' not found
Why doesn't the last call get to the print statement? I suspect I am abusing tryCatch but I don't know why. Reading some other questions (1, 2) I checked for the class of what tryCatch returns:
> ## What is returned
> empty_data <- data.frame()
> what_is_this <- safe_plot(.FUN = plot_fun, df = empty_data)
> what_is_this
Error in FUN(X[[i]], ...) : object 'xvar' not found
> class(what_is_this)
[1] "gg" "ggplot"
So in the plot function above ggplot(df, does not fail because there is a df of empty_data. The error must occur in aes(xar, . But why does this not return an error class and instead returns a ggplot?
I am creating an R package. I have a function that returns a list of plots. The plots are constructed with a complex layout using functions including gridExtra::gtable_cbind. This returns a list of gtable objects. I would like the user to be able to render one of the plots by simply calling output[[1]] or similar (i.e. I don't want them to have to load any other packages to render the plot in the plotting pane). However the gtable object requires a call to grid::grid.draw(output[[1]]) to draw.
I have tried solutions like ggpubr::as_ggplot() but this is cumbersome and causes a Warning message: In diff.default(xscale) : reached elapsed time limit for more complex layouts.
Is there a simple solution for this problem?
reproducible example
This example is simpler so does not tend to return the time limit warning.
returning list of gtables
my_fn1 <- function() {
iris_list <- split(iris, iris$Species)
plots_laidout <- list()
for (yvar in c('Sepal.Length', 'Petal.Length', 'Petal.Width')) {
plot_list <- lapply(iris_list, function(dat) ggplot2::ggplotGrob(ggplot2::ggplot(dat, ggplot2::aes_string(x = 'Sepal.Length', y = yvar)) + ggplot2::geom_point()))
plots_laidout[[length(plots_laidout) + 1]] <- do.call(gridExtra::gtable_cbind, plot_list)
}
return(plots_laidout)
}
output1 <- my_fn1()
grid::grid.draw(output1[[1]])
returning list of coerced ggplots
my_fn2 <- function() {
iris_list <- split(iris, iris$Species)
plots_laidout <- list()
for (yvar in c('Sepal.Length', 'Petal.Length', 'Petal.Width')) {
plot_list <- lapply(iris_list, function(dat) ggplot2::ggplotGrob(ggplot2::ggplot(dat, ggplot2::aes_string(x = 'Sepal.Length', y = yvar)) + ggplot2::geom_point()))
plots_laidout[[length(plots_laidout) + 1]] <- ggpubr::as_ggplot(do.call(gridExtra::gtable_cbind, plot_list))
}
return(plots_laidout)
}
output2 <- my_fn2()
output2[[1]]
I you want special behaviour of printing the object your function returns, you can wrap it in an S3 class and write a custom print method for it. In this case, the print method would then plot the outcome.
library(ggplot2)
my_fn <- function() {
iris_list <- split(iris, iris$Species)
plots_laidout <- list()
for (yvar in c('Sepal.Length', 'Petal.Length', 'Petal.Width')) {
plot_list <- lapply(iris_list, function(dat) ggplot2::ggplotGrob(
ggplot2::ggplot(dat, ggplot2::aes_string(x = 'Sepal.Length', y = yvar)) +
ggplot2::geom_point()
))
plot_list <- do.call(gridExtra::gtable_cbind, plot_list)
# Set new class
class(plot_list) <- c("my_class", class(plot_list))
plots_laidout[[length(plots_laidout) + 1]] <- plot_list
}
return(plots_laidout)
}
# Define an S3 print method
print.my_class <- function(x, newpage = TRUE) {
if (newpage) {
grid::grid.newpage()
}
grid::grid.draw(x)
invisible(x)
}
output <- my_fn()
# Outputs plot when printing elements of output
output[[1]]
Created on 2021-04-05 by the reprex package (v1.0.0)
I'm trying to pass arguments as a list to a method. I'm creating methods of stuff to pass to a data.frame. Example:
dfApply <- function(df, ...) {
UseMethod("dfApply", df)
}
dfApply.sample <- function(df, size, ...) {
# Stuff
df <- sample_frac(df, size = size)
return(df)
}
Now, if I call the function:
args <- list(size = 0.5)
class(df) <- c("sample", class(df))
df <- dfApply(df, args)
The method still receives it as a list().
Is there a way to pass arguments like this?
EDIT:
As mentioned in the comments, do.call() solves the problem (for now), but I have to define every argument in args:
args <- list(df = df, size = 0.5)
class(df) <- c("sample", class(df))
df <- do.call(dfApply, args)
Is this a wise way to implement methods? Doesn't seem right.
I am creating some functions for myself and I don't know how to proceed in order to use an object (e.g. a value) returned from one function to another one, while the console is still running. As an example:
first <- function(x){
return(x)
}
second <- function(y){
z <- x + y
return(z)
}
So if you call these functions with a '+'...
first(x = 5) +
second(y = 5)
I would expect a value of 10. In this particular case, obviously the function second() can't find the object x, because the latter one was assigned in the first() environment.
This style of programming is similar to ggplot(), for example:
ggplot(aes(x = x, y = y), data = data) +
geom_point()
I know this type of programming implies the use of environments, but I can't get it work. Any suggestions?
Thanks!
EDIT
Looking to ggplot package in github I figured it out, I think:
hh_first <- function(data) {
h <- structure(list(data = data), class = c("hh"))
h
}
"+.hh" <- function(e1, e2) {
add_hh(e1, e2)
}
add_hh <- function(h, object) {
h$data <- paste(h$data, object, sep = "")
h$data
}
hh_second <- function(data) {
data
}
For example...
hh_first('Hi') +
hh_second(', how are you?')
Returns a string 'Hi, how are you?'. The plus operator in this case works with objects of class 'hh'.
Any suggestions regarding the code or perhaps possible errors that this kind of coding may produce are welcome.
Try:
first <- function(x){
return(x)
}
second <- function(x ,y){
z <- x + y
return(z)
}
second(first(5), 5)
OR
myX <- first(5)
second(myX, 5)
OR
library(magrittr) # Which uses pipes, %>%, to pass the results of a function to the first variable of the second function
first(5) %>% second(5)