Pass name of list iteration during map to ggplot - r

df1 <- mtcars
df2 <- mtcars
combined_mtcars <- list(first_df = df1, second_df = df2)
# make the plots
map(.x = combined_mtcars, .f = function(i) {
ggplot(i, aes(x = hp, y = mpg, group = cyl)) +
geom_line()
})
This generates two charts, one for each df in combined_mtcars.
I would like to add a title to each chart where the title is the name of the iteration, for "first_df" for the first plot and "second_df" for the second one.
Tried:
imap(.x = combined_mtcars, .f = function(i) {
ggplot(i, aes(x = hp, y = mpg, group = cyl)) +
geom_line() +
ggtitle(.y)
})
Which gave an error "Error in .f(.x[[i]], .y[[i]], ...) : unused argument (.y[[i]])
"
How can I pass the iteration name to ggplot?

Related

Add additional labels from a DataFrame to a facet_grid with existing label

I have a set data that I need to add to levels of labels. One on a single chart within the facet grid, and one from a small dataframe with entries for for each chart.
In the example below you'll see that I can add to a single chart no problem but when I try to add from the df I get the error -
Error in FUN(X[[i]], ...) : object 'wt' not found
Preparation:
library(ggplot2)
p <- ggplot(mtcars, aes(mpg, wt)) + geom_line()
p <- p + facet_grid(. ~ cyl)
ann_text <- data.frame(mpg = 30,wt = 5,lab = "Text",
cyl = factor(8,levels = c("4","6","8")))
dfl <- data.frame(name = c('Jim',"Bob", "Sue"), r = c(-0.2, 0.5, -0.4))
Single Label:
p + geom_text(data = ann_text,label = "Text")
Multiple Labels:
p + geom_text(data = ann_text,label = "Text") +
geom_text(data = dfl, mpg = 30,wt = 5, aes(label = r))
The method I'm using is trying to recreate other examples I've found here on SO and elsewhere but I seem to be missing something.
It's not working in your second code because in the second geom_text, your mpg and wt in not in aes(). Also, these two variables are absent in your dfl.
If you wish to have better control of the labelling of your r variable, you can create extra columns in dfl specifying the x and y location of the label, and use these variables in geom_text(aes()).
Note that I have modified the y position in the second geom_text to avoid overlapping "0,2" with "Text".
library(ggplot2)
p <- ggplot(mtcars, aes(mpg, wt)) + geom_line()
p <- p + facet_grid(. ~ cyl)
ann_text <- data.frame(mpg = 30,wt = 5,lab = "Text",
cyl = factor(8,levels = c("4","6","8")))
dfl <- data.frame(name = c('Jim',"Bob", "Sue"), r = c(-0.2, 0.5, -0.4))
p + geom_text(data = ann_text,label = "Text") +
geom_text(data = dfl, aes(30, 4, label = r), check_overlap = T)
Created on 2022-05-06 by the reprex package (v2.0.1)

Aesthetics and bang-bang in ggplot2

I am trying to write a function that creates a barplot but I have trouble getting the fill aesthetic right.
If I use fill = !!x leads to Quosures can only be unquoted within a quasiquotation context.
and fill = x leads to Aesthetics must be either length 1 or the same as the data (4): fill
My Code:
genBar <- function(data, x, y) {
x <- enquo(x)
y <- enquo(y)
plot <- ggplot(data) +
geom_bar(aes(!!x, !!y),
stat = 'identity',
fill = <help>)
return(plot)
}
fill should be inside aes. Try :
library(ggplot2)
genBar <- function(data, x, y) {
plot <- ggplot(data) +
geom_bar(aes({{x}}, {{y}}, fill = {{x}}),
stat = 'identity')
return(plot)
}
genBar(mtcars, cyl, mpg)
If you want to pass column names as string use .data pronoun.
genBar <- function(data, x, y) {
plot <- ggplot(data) +
geom_bar(aes(.data[[x]], .data[[y]], fill = .data[[x]]),
stat = 'identity')
return(plot)
}
genBar(mtcars, "cyl", "mpg")
Are you looking for something like this?
library(dplyr)
library(ggplot2)
genBar <- function(data, x, y) {
x <- enquo(x)
y <- enquo(y)
plot <- ggplot(data) +
geom_bar(aes(!!x, !!y, fill = !!x),
stat = 'identity')
return(plot)
}
iris %>%
group_by(Species) %>%
summarize(Size = mean(Petal.Length)) %>%
genBar(Species, Size)
Created on 2020-12-04 by the reprex package (v0.3.0)

Changing legend order in ggplot within a function

I want to plot a data frame within a function. The legend should be ordered in a specific way. To keep things simple in my example I just reverse the order. I actually want to select a specific row and push it to the last position of the legend.
By the way I am creating a new R package, if this is in any way relevant.
Plotting outside of a function
attach(iris)
library(ggplot2)
# This is a normal plot
p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = Species) ) +
geom_bar( stat = "identity")
p
# This is a plot with reversed legend
iris$Species <- factor(iris$Species, levels = rev(levels(iris$Species)))
p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = Species) ) +
geom_bar( stat = "identity")
p
Plotting inside a function
The most simple approach was just to use variables, which obviously doesn't work
f1 <- function(myvariable) {
iris$myvariable <- factor(iris$myvariable, levels = rev(levels(iris$myvariable)))
p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = Species) ) +
geom_bar( stat = "identity")
p
}
f1("Species")
#> Error in `$<-.data.frame`(`*tmp*`, myvariable, value = integer(0)) :
replacement has 0 rows, data has 150
I tried to use quasiquotation, but this approach only let me plot the data frame. I cannot reverse the order yet.
library(rlang)
# Only plotting works
f2 <- function(myvariable) {
v1 <- ensym(myvariable)
p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = eval(expr(`$`(iris, !!v1))))) +
geom_bar( stat = "identity")
p
}
f2("Species")
# This crashes
f3 <- function(myvariable) {
v1 <- ensym(myvariable)
expr(`$`(iris, !!v1)) <- factor(expr(`$`(iris, !!v1)), levels = rev(levels(expr(`$`(iris, !!v1)))))
p <- ggplot(iris, aes(x = Sepal.Width, y = Sepal.Length, fill = eval(expr(`$`(iris, !!v1))))) +
geom_bar( stat = "identity")
p
}
f3("Species")
#> Error in `*tmp*`$!!v1 : invalid subscript type 'language'
So the main problem is, that I cannot assign something using quasiquotation.
A couple of things:
You can use [[ instead of $ to access data frame columns programmatically.
You can use ggplot's aes_string to minimize notes during R CMD check (since you mentioned you're doing a package).
You can "send" a factor level to the end with fct_relevel from package forcats.
Which translates to:
f <- function(df, var) {
lev <- levels(df[[var]])
df[[var]] <- forcats::fct_relevel(df[[var]], lev[1L], after = length(lev) - 1L)
ggplot(df, aes_string(x = "Sepal.Width", y = "Sepal.Length", fill = var)) +
geom_bar(stat = "identity")
}
f(iris, "Species")

Save a list of plots dynamically

I am able to generate some plots based on a list of data frames:
df1 <- mtcars
df2 <- mtcars
combined_mtcars <- list(first_df = df1, second_df = df2)
# make the plots
imap(.x = combined_mtcars, ~ggplot(.x, aes(x = hp, y = mpg, group = cyl)) +
geom_line() +
ggtitle(.y))
I wanted to then save each plot to a directory called /plots. So I tried adding ggsave like so:
imap(.x = combined_mtcars, ~ggplot(.x, aes(x = hp, y = mpg, group = cyl)) +
geom_line() +
ggtitle(.y)) %>%
imap(~ggsave(plot = .y, file = paste0("/plots/", .y, ".png")))
This resulted in error "Saving 6.62 x 5.57 in image
Error in UseMethod("grid.draw") :
no applicable method for 'grid.draw' applied to an object of class "character"".
How can I save each iteration where the filename is the same as the title .y?
We need to make sure the ggplot object is being passed as the first argument, using the tag argument in the labs() function allows us to assign the plot to a "variable".
imap(.x = combined_mtcars, ~ggplot(.x, aes(x = hp, y = mpg, group = cyl)) +
geom_line() +
labs(title = .y, tag="Plot")%>%
imap(~ggsave(plot = Plot, file = paste0("/plots/", .y, ".png")))
If that does not work, try this since ggsave may default to the correct plot.
imap(.x = combined_mtcars, ~ggplot(.x, aes(x = hp, y = mpg, group = cyl)) +
geom_line() +
ggtitle(.y)) %>%
imap(~ggsave(file = paste0("/plots/", .y, ".png")))

How to set y axis limits when using map2(~ggplot...?

How can I set different y axis limits in each plot when using purrr::map2?
I would like to set the y-axis lower limit to half the maximum y-axis value, something like: max(y-axis value/2).
data(mtcars)
library(tidyverse)
mtcars_split <-
mtcars %>%
split(mtcars$cyl)
plots <- map2(
mtcars_split,
names(mtcars_split),
~ggplot(data = .x, mapping = aes(y = mpg, x = wt)) +
geom_jitter() +
ggtitle(.y)+
scale_y_continuous(limits=c(max(.y)/2,NA))
)
plots
Error in max(.y)/2 : non-numeric argument to binary operator
.y is the name of the dataframe, which is why max(.y)/2 is giving you that error. This should give you what you want:
plots <- imap(
mtcars_split,
~ggplot(data = .x, mapping = aes(y = mpg, x = wt)) +
geom_jitter() +
ggtitle(.y) +
scale_y_continuous(limits=c(max(.x$mpg)/2,NA))
)
Note that imap(x, ...) is just shorthand for map2(x, names(x), ...).
This doesn't work based on the y-axis value, but it gets the job done if you don't mind specifying your y-column twice:
plots <- map2(
mtcars_split,
names(mtcars_split),
~ggplot(data = .x, mapping = aes(y = mpg, x = wt)) +
geom_jitter() +
ggtitle(.y)+
scale_y_continuous(limits=c(max(.x$mpg)/2,NA))
)
Or maybe a safer option:
plots <- map2(
mtcars_split,
names(mtcars_split),
~{
ploty <- 'mpg'
plotx <- 'wt'
ggplot(data = .x, mapping = aes_string(y = ploty, x = plotx)) +
geom_jitter() +
ggtitle(.y)+
scale_y_continuous(limits=c(max(.x[[ploty]])/2,NA))
}
)

Resources