I am trying to write a plotting function where you can pass bare column names to select which columns are plotted. I would like also to be able to specify a string as the color.
I have found that I need to use shQuote if I want to pass a string to aes_string. Now my problem is to figure out if a bare name or a string was passed. How would I do this?
dat <- data.frame(
time = factor(c("Lunch","Dinner"), levels=c("Lunch","Dinner")),
total_bill = c(14.89, 17.23)
)
plot_it <- function(dat, x,y, fill){
require(rlang)
require(ggplot2)
x <- enquo(x)
y <- enquo(y)
fill <- enquo(fill)
xN <- quo_name(x)
yN <- quo_name(y)
fillN <- quo_name(fill)
ggplot(data=dat, aes_string(x=xN, y=yN, fill=fillN)) +
geom_bar(stat="identity")
}
This works:
plot_it(dat, time, total_bill, time)
This does not:
plot_it(dat, time, total_bill, "grey")
Note that this requires the newest versions of rlang and ggplot2.
Based on @akrun's suggestion of how to detect which case we had (was removed) I found something that does what I asked for:
plot_it <- function(dat, x, y, fill) {
lst <- as.list(match.call())
if(is.character(lst$fill)){
fillN <- shQuote(fill)
} else{
fillN <- quo_name(enquo(fill))
}
x <- enquo(x)
y <- enquo(y)
xN <- quo_name(x)
yN <- quo_name(y)
p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill=fillN)) +
geom_bar(stat="identity")
return(p)
}
Turns out this doesn't actually do what I had in mind since it assigns the quoted value as a factor to assign colors by. Not the actual color.
I came up with this that seems to work but is not really elegant:
plot_it <- function(dat, x, y, fill) {
lst <- as.list(match.call())
if(!(type_of(lst$fill)=="symbol" | (type_of(lst$fill)=="string" & length(lst$fill)==1))) stop("Fill must either be a bare name or a vector of length 1.")
x <- enquo(x)
y <- enquo(y)
xN <- quo_name(x)
yN <- quo_name(y)
if(is.character(lst$fill)){
dat[,"fillN"] <- fill
fillN <- fill
p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill = shQuote(fillN))) +
scale_fill_manual(name="fill", values=setNames(fillN,fillN))
} else{
fillN <- quo_name(enquo(fill))
p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill = fillN))
}
p <- p + geom_bar(stat="identity")
return(p)
}
Any idea to make this a bit more elegant?
Working from your answer and Eumenedies' answer/test cases, here is a version that:
geom_col
, per Eumenedies' suggestionThe main trick is that, if fill
isn't a factor in the plot, you want it outside of the aes
/ aes_string
block.
plot_it <- function(dat, x, y, fill) {
lst <- as.list(match.call())
xN <- quo_name(enquo(x))
yN <- quo_name(enquo(y))
if(is.character(lst$fill)) {
p <- ggplot(data=dat, aes_string(x=xN, y=yN)) +
geom_col(fill = fill)
} else {
p <- ggplot(data=dat, aes_string(x=xN, y=yN, fill = quo_name(enquo(fill)))) +
geom_col()
}
return(p)
}
plot_it(dat, time, total_bill, time)
plot_it(dat, time, total_bill, "blue")
plot_it(dat, time, total_bill, "blue") + geom_point()
You could make the if
block shorter by moving the fill
aesthetic in the second case to the geom_col
call, but that will extend in a different way if you add more geoms.
Also, once ggplot
is updated to support rlang
, it would be cleaner to avoid aes_string
and quo_name
combination and just use !!fill
.
Note that, assuming that the fill
factor exists, if it's always going to be the same as the x
factor, it would probably make more sense to have a version where fill
is an optional argument. You would only overwrite the default per-factor color if the argument is included.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With