Reputation: 31
I am trying to create some functions based on ggplot
to create custom plots. The creation of functions is very easy and straight forward, but I cannot figure out the way to share information between the functions to help in the plot creation.
Here is a simplified example
library(ggplot2)
my_ggplot <- function(dat, x, y, cols) {
ggplot(dat, aes(!!sym(x), !!sym(y))) +
geom_point(color = cols$dots)
}
my_geom <- function(dat, x, cols) {
xmean <- mean(dat[[x]], na.rm = T)
exit <- list(
geom_smooth(aes(color = cols$line), method = "loess", se = FALSE),
geom_vline(xintercept = xmean, color = cols$line)
)
}
mycolors <- list(dots = "blue", line = "red")
Here, my_plot
creates the base plot and, if I want to, I can add couple of lines to it using my_geom
. I need a way to control the colors so, I have defined an S3 class object, which in this example is simply the list mycolors
.
So, when passing all the parameters to each function, the result is perfectly fine:
my_ggplot(mpg, 'displ', 'hwy', mycolors) +
my_geom(mpg, "displ", mycolors)
But I want to be able to "inherit" values from my_ggplot
to my_geom
so that the following code could work:
my_ggplot(mpg, 'displ', 'hwy', mycolors) +
my_geom()
But still, my_geom
keeps certain level of independence in case I want to use it with different ggplot()
functions. Especially important for me is to be able to pass the dataset between functions, in the example I calculate the mean and use it later in geom_vline
to keep it simple, but in practice I need to do some data wrangling and calculations before I can pass the values to the geom.
Upvotes: 1
Views: 329
Reputation: 23807
Another option. This might work by defining your data and color arguments as NULL, and with a simple if/else statement to create a list based on presence of provided data, respectively. It really depends on the use case. In my example, there are two if else statements. One for the data, the other for the color (in case the data was not passed to the second function).
It might be best to create your own stat, it really depends on what type of data transformation and geometry you have in mind. geom_vline is a bit of a special situation and might not be the best chosen example.
The advantage of this little bit of extra effort is that it doesn’t need a hard coded y aesthetic for your line.
I think Stefan's approach with the color is excellent - I've used this here too.
library(ggplot2)
my_ggplot <- function(dat, x, y, cols) {
ggplot(dat, aes(x = !!sym(x), y = !!sym(y))) +
geom_point(aes(color = "dots"), show.legend = F) +
scale_color_manual(values = cols)
}
StatMyline <- ggproto("StatMyline", Stat,
compute_group = function(data, scales) {
data.frame(
x = mean(data$x),
xend = mean(data$x),
y = -Inf, yend = Inf
)
},
required_aes = c("x", "y")
)
stat_myline <- function(mapping = NULL, data = NULL, geom = "segment",
position = "identity", na.rm = FALSE, show.legend = NA,
inherit.aes = TRUE, ...) {
layer(
stat = StatMyline, data = data, mapping = mapping, geom = geom,
position = position, show.legend = show.legend, inherit.aes = inherit.aes,
params = list(na.rm = na.rm, ...)
)
}
mycolors <- list(dots = "blue", line = "red")
my_geom <- function(dat = NULL, x, cols = NULL) {
## if dat is provided, compute using your provided data and the provided color
if (!is.null(dat)) {
xmean <- mean(dat[[x]], na.rm = T)
list(
geom_smooth(aes(color = "line"), method = "loess", se = FALSE, show.legend = F),
geom_vline(aes(color = "line", xintercept = xmean), show.legend = F)
)
} else {
list(
geom_smooth(method = "loess", se = FALSE, aes(color = "line"), show.legend = F),
stat_myline(aes(color = "line"), show.legend = F),
if(!is.null(cols)) scale_color_manual(values = cols) else NULL
)
}
}
p1 <- my_ggplot(mpg, "displ", "hwy", mycolors) +
my_geom(mpg, "displ", mycolors) +
ggtitle("With data + color ")
p2 <- my_ggplot(mpg, "displ", "hwy", mycolors) +
my_geom() +
ggtitle("Inheriting data + color")
p3 <- ggplot(mtcars, aes(hp, mpg)) +
geom_point() +
my_geom(cols = mycolors) +
ggtitle("without my_ggplot")
library(patchwork)
p1 + p2 + p3
#> `geom_smooth()` using formula = 'y ~ x'
#> `geom_smooth()` using formula = 'y ~ x'
#> `geom_smooth()` using formula = 'y ~ x'
Created on 2023-04-13 with reprex v2.0.2
Upvotes: 2
Reputation: 125797
One possible approach to remove the dependency on the dat
and the x
argument would be to use stat_summary
to compute the mean of the variable mapped on the x
aes and to add the vline
similar to my answer on this post. Second, for the colors one option would be to map on the color
aes and to set the color palette via scale_color_manual
. This way the colors would be available in my_geom
too. Of course does this only work when you create your plot via my_ggplot
. Not perfect.
library(ggplot2)
my_ggplot <- function(dat, x, y, cols) {
ggplot(dat, aes(!!sym(x), !!sym(y))) +
geom_point(aes(color = "dots"), show.legend = FALSE) +
scale_color_manual(values = cols)
}
my_geom <- function() {
list(
geom_smooth(aes(color = "line"), method = "loess", se = FALSE, show.legend = FALSE),
stat_summary(aes(xintercept = after_stat(x), y = 0, color = "line"),
fun = mean, geom = "vline", orientation = "y", show.legend = FALSE
)
)
}
mycolors <- list(dots = "blue", line = "red")
my_ggplot(mpg, "displ", "hwy", mycolors) +
my_geom()
#> `geom_smooth()` using formula = 'y ~ x'
Finally here is an example of applying my_geom
to a ggplot
created from scratch:
ggplot(mtcars, aes(hp, mpg)) +
geom_point() +
my_geom()
#> `geom_smooth()` using formula = 'y ~ x'
Upvotes: 2