Related
I have a function draw_plot() that takes in a data frame and other parameters(through the ellipsis) and simply returns a line plot.
draw_plot <- function(data, ...){
plot <- ggplot(data = data, mapping = aes(x = x, y = y))
plot <- plot + do.call("geom_line", c(list(...)))
return(plot)}
data <- tibble::tibble(x = c(1,2,3,4,5,6), y = c(5,6,7,8,9,10))
draw_plot(data, test = 2, wrong = "")
If I call the function as above, with parameters with key values which the geom_line() function doesn't recognize, it returns a warning saying 'Ignoring unknown parameters: test, wrong'.
I am hoping to improve the function to facilitate more parameters, and other ggplot geoms. (ex: draw_plot(data, stat, ...)) The stat parameter will not be valid for geom_line(). Therefore I want to capture the original warning message and append 'stat' to it. ex: 'Ignoring unknown parameters: test, wrong, stat'
To do this, is there any way that I can capture this particular warning message into a variable? p.s. I am trying to stick to base R to do this.
Here is how you can capture the warning message -
draw_plot <- function(data, ...){
plot <- ggplot(data = data, mapping = aes(x = x, y = y))
plot <- tryCatch(plot + do.call("geom_line", c(list(...))),
warning = function(w) {
if(grepl('Ignoring unknown parameters', w$message))
wmsg <<- as.character(w$message)
return(NULL)
})
if(exists('wmsg')) print(wmsg)
return(plot)
}
draw_plot(data, test = 2, wrong = "")
draw_plot(data)
Context
Reading the vignette Programming with dplyr I tried to use the ... and !!! operators to implement a function that would wrap around ggplot functions and would accept an arbitrary number of arguments that would define which variables in a dataframe were to be mapped to each aesthetic.
My goal
I wanted to define a function plot_points2() such that
plot_points2(df, x = x, y = y, color = z) would be equivalent to df %>% ggplot( mapping = aes(x = x, y = y, color = z) ) + geom_point(alpha = 0.1)
plot_points2(df, x = x, y = z, color = y) would be equivalent to df %>% ggplot( mapping = aes(x = x, y = z, color = y) ) + geom_point(alpha = 0.1)
plot_points2(df, x = x, y = z) would be equivalent to df %>% ggplot( mapping = aes(x = x, y = z) ) + geom_point(alpha = 0.1)
What failed
packages
require(tidyverse)
require(rlang)
reduced example dataset
df <- tibble(g1= sample(x = c(1,2,3), replace = T, size = 10000),
g2= sample(x = c("a","b","c"), replace = T, size = 10000),
x = rnorm(10000, 50, 10),
y = rnorm(10000, 0, 20) + x*2,
z = rnorm(10000, 10, 5))
df
my attempt
plot_points2 <- function(d, ...){
args <- quos(...)
print(args)
ggplot(data = d, mapping = aes(!!!args)) + geom_point(alpha = 0.1)
}
plot_points2(df, x = x, y = y, color = z)
the error
Error: Can't use `!!!` at top level
Call `rlang::last_error()` to see a backtrace
Why I think it should work
I figure what I wanted to acomplish isn't much different from an example in the vignette that uses these operators to make a function that wraps around mutate(), and passes multiple arguments that defined the grouping variables (in deed I was able to implement a function that does that to the example dataset above I'm posting as an example), but somehow the latter works and the former doesn't:
this works
add_dif_to_group_mean <- function(df, ...) {
groups <- quos(...)
df %>% group_by(!!!groups) %>% mutate(x_dif = x-mean(x),
y_dif = y-mean(y),
z_dif = z-mean(z))
}
df %>% add_dif_to_group_mean(g1)
df %>% add_dif_to_group_mean(g1, g2)
this doesn't
plot_points2 <- function(d, ...){
args <- quos(...)
print(args)
ggplot(data = d, mapping = aes(!!!args)) + geom_point(alpha = 0.1)
}
plot_points2(df, x = x, y = y, color = z)
I also read that the problem could be related with aes() being evaluated only when the plot is printed, but in that case I think using !! and unpacking manually should raise the same error but it doesn't:
plot_points2b <- function(d, ...){
args <- quos(...)
print(args)
ggplot(data = d, mapping = aes(x = !!args[[1]],
y = !!args[[2]],
color = !!args[[3]])) +
geom_point(alpha = 0.1)
}
plot_points2b(df, x = x, y = y, color = z)
In deed this last example works fine if you plot 3 variables, but it doesn't allow you to plot a number of variables different from 3
eg: plot_points2b(df, x = x, y = z) is not equivalent to
df %>% ggplot( mapping = aes(x = x, y = z) ) + geom_point(alpha = 0.1)
In stead it raises the error:
Error in args[[3]] : subscript out of bounds
Anyone knows what concept am I missing here? Thank you in advance!
Your specific use case is an example in ?aes. aes automatically quotes its arguments. One can simply directly pass the dots. Try:
plot_points3 <- function(d, ...){
print(aes(...))
ggplot(d, aes(...)) + geom_point(alpha = 0.1)
}
plot_points3(df, x = x, y = y, color = z)
This nicely prints:
Aesthetic mapping:
* `x` -> `x`
* `y` -> `y`
* `colour` -> `z`
And yields the required plot.
As mentioned in my comment, I think you may already have x and y in your environment and that is why some of your code is working. I'm not totally sure what you are trying to achieve but I think you are doing too much rlang for getting your code to run without error.
For example:
plot_points <- function(d, ...){
ggplot(data = d, mapping = aes(x = x, y = y)) +
geom_point(alpha = 0.1)
}
plot_points (df, x, y)
will make your plot without any reason to add the overhead and complexity of !!! or enquo().
You were on this path here too, where this much simpler code works fine:
add_dif_to_group_mean <- function(., ...) {
df %>% group_by(g1) %>% mutate(x_dif = x-mean(x),
y_dif = y-mean(y),
z_dif = z-mean(z))
}
df %>% add_dif_to_group_mean(g1)
Likewise:
plot_points2 <- function(d, ...){
ggplot(data = d, mapping = aes(x=x, y=y, color=z)) +
geom_point(alpha = 0.1)
}
plot_points2(df, x = x, y = y, color = z)
works fine from what I can tell.
So I understand that maybe you are working through the examples in the book, which is great. But I think there is a missing issue somewhere that would make it so you have to do all the extra stuff in a real world function. For example, maybe you want to pass in strings like "x" and "y" instead of x and y?
I'm trying to write a function that can be called using the '+'-based ggplot2 syntax.
myplot + myfunction
Specifically, the function I'm writing symmetrizes the y-axis about zero, so it needs to determine the y-axis range for the input plot.
So let,
ylim_sym <- function(p){
get_y_range <- function(p){
ggplot2::ggplot_build(p)$layout$panel_ranges[[1]]$y.range
}
max_offset <- max(abs(get_y_range(p)))
p + ylim(- max_offset, max_offset)
}
With this function, the following works:
qplot(x = 1:10, y = exp(rnorm(10))) %>% ylim_sym()
But this doesn't work because of some precedence issue between +.gg and %>%:
qplot(x = 1:10, y = exp(rnorm(10))) +
geom_abline(slope = 0) %>%
ylim_sym()
(I could write the latter (all_my_ggplot_pipeline) %>% ylim_sym() but it's pretty ugly syntax).
Ideally, I'd like to be able to write ylim_sym such that it can be piped like so,
qplot(x = 1:10, y = exp(rnorm(10))) + ylim_sym()
but I can't work out how to access the plot on the LHS of + within ylim_sym
Any ideas?
I was able to solve it by doing the following.
StatSymYLim <- ggproto(
"StatSymYLim", Stat,
compute_group = function(data, scales) {
out <- data.frame(
x = median(data$x),
y = c(-1, 1) * max(abs(data$y))
)
out
},
required_aes = c("x", "y")
)
ylim_sym <- function(...){
geom_blank(..., stat = StatSymYLim)
}
Then the following works as required:
qplot(x = 1:10, y = exp(rnorm(10))) +
geom_abline(slope = 0) +
ylim_sym()
My understanding of ggplot2 internals is pretty shaky to be fair, so this might be a naive solution.
Note: your function needs an update as the structure of the object has slightly changed
Using package ggfun this would work:
# devtools::install_github("moodymudskipper/ggfun")
library(ggfun)
ylim_sym <- function(p){
get_y_range <- function(p){
ggplot2::ggplot_build(p)$layout$panel_params[[1]]$y.range
}
max_offset <- max(abs(get_y_range(p)))
p + ylim(- max_offset, max_offset)
}
qplot(x = 1:10, y = exp(rnorm(10))) +
geom_abline(slope = 0) +
ylim_sym
I know that the title of question sounds silly but I want to create a function test_f which can use several parameters of functions used within test_f ( I used latest version of ggplot2 with new function aes_ ).
E.g.
devtools::install_github('hadley/scales')
devtools::install_github('hadley/ggplot2')
test <- function(data,x,y,...){
ggplot(data, aes_(substitute(x), substitute(y)))+
geom_point(...)+
scale_y_continuous(...)
}
When
test(mtcars, qsec, mpg,limit = c(1,100))
everything works, but
test(mtcars, qsec, mpg,size = 5)
it shows an error: Error in scale_y_continuous: Unused parameter (size = 5).
I know why it happened but I wonder whether it is any possiblity to use ... for more than one internal function instead of putting all parameters into test_f like below?
test <- function(data,x,y,..., size = 5, limit = c(1,100){
...
...
}
You can, but it just depends on how the receiving functions handle things:
f2 <- function(three, ...) {
g <- as.list(match.call())
print(sprintf("three (from named args) = %d", three))
if ("five" %in% names(g)) print(sprintf("five (from ...) = %d", g$five))
}
f1 <- function(x, y, ...) {
if (missing(x)) stop("x is missing", call.=FALSE)
if (missing(y)) stop("y is missing", call.=FALSE)
g <- as.list(match.call())
print(sprintf("x = %d", x))
print(sprintf("y = %d", y))
f2(...)
}
f1(1, 2, three=4, five=6)
## [1] "x = 1"
## [1] "y = 2"
## [1] "three (from named args) = 4"
## [1] "five (from ...) = 6"
Since what you're getting stuck on is scale_y_continuous (and, hence, continuous_scale) complaining about the unused parameter, you can pass in only what it will accept from the ... list. It means some internal legwork for your function, but it's definitely doable:
mygg <- function(data, x, y, ...) {
gg <- ggplot(data=data, aes_(substitute(x), substitute(y)))
# get what geom_point accepts
geom_point_aes <- c("x", "y", "alpha", "colour", "color", "fill", "shape", "size", "stroke")
point_params <- unique(c(geom_point_aes,
names(formals(geom_point)),
names(formals(layer))))
# get what scale_y_continuous accepts
scale_y_params <- unique(c(names(formals(scale_y_continuous)),
names(formals(continuous_scale))))
# get all ... params passed in (if any)
args <- list(...)
if (length(args) > 0) {
# get all the arg names
arg_names <- names(args)
# which ones are left for point
gg <- gg + do.call(geom_point,
sapply(intersect(arg_names, point_params),
function(x) { list(args[[x]]) }))
# which ones are left for scale_y
gg <- gg + do.call(scale_y_continuous,
sapply(intersect(arg_names, scale_y_params),
function(x) { list(args[[x]]) }))
} else {
gg <- gg + geom_point() + scale_y_continuous()
}
return(gg)
}
I won't clutter up the answer with pngs but if you run the following you should see what the modified function does.
mygg(mtcars, mpg, wt)
mygg(mtcars, mpg, wt, color="blue")
mygg(mtcars, mpg, wt, limits=c(3,4))
mygg(mtcars, mpg, wt, fill="green", color="blue", shape=21, limits=c(3,4), left="over")
it's probably worth mentioning the alternative strategy of using one or two lists to wrap the optional arguments. Borrowing from the other answer,
mygg <- function(data, x, y,
geom_pars = list(),
scale_pars = list()) {
p <- ggplot(data=data, aes_(substitute(x), substitute(y)))
g <- do.call(geom_point, geom_pars)
s <- do.call(scale_y_continuous, scale_pars)
p + list(g, s)
}
Calling the function is a bit more verbose, but often less confusing because we're explicit about where the arguments should go.
mygg(mtcars, mpg, wt)
mygg(mtcars, mpg, wt, geom_pars=list(color="blue"))
mygg(mtcars, mpg, wt, scale_pars=list(limits=c(3,4)))
mygg(mtcars, mpg, wt,
geom_pars=list(fill="green", color="blue", shape=21),
scale_pars=list(limits=c(3,4)))
I have tried to fix this problem a number of ways, but I am new to R, so I don't know the tips and tricks. I am trying to graph a polynomial function using code for a quadratic function, provided by my teacher, however, I keep running into the "unused arguments error". the issue is that I have given three arguments, which is what the function expects. this is the code I entered:
> quadratic <- function(x, u.values){
+ X <- cbind(1, x, x^2)
+ return(X %*% u.values)
+ }
> dev.new()
> ggplot() +
+ geom_point(aes(x = t,y = y),data= GData)+
+ stat_function(fun=quadratic(args=c(1.9604816, -0.1201548, -4.9768049)))
Error in quadratic(args = c(1.9604816, -0.1201548, -4.9768049)) :
unused argument(s) (args = c(1.9604816, -0.1201548, -4.9768049))
As you've defined it, quadratic doesn't have an args parameter. That's what's causing your error.
I'm guessing -- but I can't test, since your example isn't reproducible -- that you should change args in your stat_function call to something like args = list(u.values = c(1.9604816, -0.1201548, -4.9768049)).
Edit:
To clarify, args parameter of stat_function takes additional arguments to the function you specify (quadratic in this case) as a list. What you're doing is passing a named argument args to quadratic, when you want to be passing a named argument args to stat_function.
Here is a reproducible example that works:
g <- data.frame(t = seq(0.1, 1, by = 0.1), y = seq(2.1, 3, by = 0.1))
quadratic <- function(x, u.values){
X <- cbind(1, x, x^2)
return(X %*% u.values)
}
ggplot(data = g, aes(x = t, y = y)) +
geom_point() +
stat_function(fun = quadratic,
args = list(u.values = c(1.9604816, -0.1201548, -4.9768049)))
Obviously this data has nothing to do with yours, but the quadratic function is applied correctly to the data and the results are plotted.