different titles for each element after using facet_wrap() - r

After setting ncol = 1 in the facet_wrap() function, I'm trying to use ggtitle() function inside the facet_wrap() function to set a different title for each graph created (there are only two of them).
ggplot(df, aes(x, y)) +
geom_point() +
facet_wrap(~ var, ncol = 1) +
ggtitle(function(x) paste("Title for", df$title[df$var == x]))
I'm trying to use the value of the "title" column of the dataframe, where the value of the "var" column matches the value of the current plot's var.
But I get this error:
Error in as.character(x$label) :
cannot coerce type 'closure' to vector of type 'character'
How can I set different titles for each graph in ggplot2 using the facet_wrap() function with ncol=1?
Thanks, Ido

Here is something that might give you what you want. My comment above still stands, but this may be more what you are looking for.
library(ggplot2)
library(dplyr)
df <- mtcars %>%
mutate(strip_title = paste(cyl, "Cylinders"))
ggplot(df, aes(x = mpg, y = wt)) +
geom_point() +
facet_wrap(~strip_title, ncol = 1)

Related

R: Programmatically changing ggplot scale labels to Greek letters with expressions

I am trying to change the labels in a ggplot object to Greek symbols for an arbitrary number of labels. Thanks to this post, I can do this manually when I know the number of labels in advance and the number is not too large:
# Simulate data
df <- data.frame(name = rep(c("alpha1","alpha2"), 50),
value = rnorm(100))
# Create a plot with greek letters for labels
ggplot(df, aes(x = value, y = name)) + geom_density() +
scale_y_discrete(labels = c("alpha1" = expression(alpha[1]),
"alpha2" = expression(alpha[2])))
For our purposes, assume I need to change k default labels, where each of the k labels is the pre-fix "alpha" followed by a number 1:k. Their corresponding updated labels would substitute the greek letter for "alpha" and use a subscript. An example of this is below:
# default labels
paste0("alpha", 1:k)
# desired labels
for (i in 1:k) { expression(alpha[i]) }
I was able to hack together the below programmatic solution that appears to produce the desired result thanks to this post:
ggplot(df, aes(x = value, y = name)) + geom_density() +
scale_y_discrete(labels = parse(text = paste("alpha[", 1:length(unique(df)), "]")))
However, I do not understand this code and am seeking clarification about:
What is parse() doing here that expression() otherwise would do?
While I understand everything to the right-hand side of =, what is text doing on the left-hand side of the =?
Another option to achieve your desired result would be to add a new column to your data which contains the ?plotmath expression as a string and map this new column on y. Afterwards you could use scales::label_parse() to parse the expressions:
set.seed(123)
df <- data.frame(name = rep(c("alpha1","alpha2"), 50),
value = rnorm(100))
df$label <- gsub("^(.*?)(\\d+)$", "\\1[\\2]", df$name)
library(ggplot2)
library(scales)
ggplot(df, aes(x = value, y = label)) + geom_density() +
scale_y_discrete(labels = scales::label_parse())

How to modify axis labels within ggplot labs()

Say I want to modify a ggplot axis label with the str_to_title() function.
library(tidyverse)
mtcars %>%
ggplot(aes(x = wt, y = mpg)) +
geom_point() +
labs(x = ~str_to_title(.x))
Rather than my x-axis being labeled 'Wt' it will be labeled 'str_to_title(.x)'. Is there a way to apply functions within the labs() function?
labs doesn't do programmatic NSE like many other components of ggplot2. One option is to define the columns programmatically, use aes_ and as.name (or other ways too) and it'll work.
library(ggplot2)
library(stringr) # str_to_title
xx <- "wt"; yy <- "mpg"
ggplot(mtcars, aes_(x = as.name(xx), y = as.name(yy))) +
geom_point() +
labs(x = str_to_title(xx))

How do I pass multiple arguments through an R function to ggplot, both aesthetics and scale format?

How do I pass multiple arguments through to my ggplot function?
Here is an example of the plot I want to automate.
library(ggplot2)
library(scales)
p <- ggplot(diamonds, aes(x=cut, y=price) ) +
geom_boxplot() +
scale_y_continuous(labels = dollar)
p
But I want to graph multiple different variables and use the appropriate scale e.g. price, depth etc, some are in dollars.
So I made a function
myfunction <- function(var1,var2){
p <- ggplot(diamonds, aes(x=cut, y= var1) ) +
geom_boxplot() +
scale_y_continuous(labels = var2)
p
return(p)
}
When I test the function, it doesn't work. Both arguments cause different errors on their own.
myfunction("price","dollar")
For var1 I get:
Error: Discrete value supplied to continuous scale
and var2:
Error in f(..., self = self) : Breaks and labels are different lengths
Question 1. Why doesn't that work? This is the most important question for me.
I then wish to make multiple graphs, which I can do with a for loop, but I keep hearing I should do it with apply. Here's what I tried.
Question 2. How would you make the multiple graphs work with apply?
FirstPlotData <- c("price","dollar")
SecondPlotData <- c("depth", "comma")
plotMetaData <- data.frame(FirstPlotData,SecondPlotData)
lapply doesn't work for me with multiple arguments. Can it pass multiple arguments?
lapply(plotMetaData, function(avar,bvar)myfunction(avar, bvar))
Would mapply work? How?
mapply(mytestfunction,plotMetaData[1,],plotMetaDataList[2,])
Thanks in advance. I note that I could do the multiple graphs with facet, but for my more complex example, with hiding outliers, scaling, and also doing stats, then doing the multiple plots and putting in a {cowplot} grid seems easier.
Try this
library(ggplot2)
library(scales)
library(rlang) # for sym
myfunction <- function(var1,var2){
p <- ggplot(diamonds, aes(x=cut, y= !! sym(var1)) ) +
geom_boxplot() +
scale_y_continuous(labels = get(var2))
p
return(p)
}
myfunction('price','dollar')
You probably want aes_string. This function has been designed to make programming with ggplot easier (similar ideas have also been applied to dplyr commands). The following works:
library(tidyverse)
data(diamonds)
myfunction <- function(var1){
p <- ggplot(diamonds, aes_string(x="cut", y= var1) ) +
geom_boxplot()
p
return(p)
}
myfunction("price")
Why?
contrast the following:
# works
ggplot(diamonds, aes(x=cut, y= price) ) + geom_boxplot()
# these 2 are equivalent, but do not work
ggplot(diamonds, aes(x=cut, y= "price") ) + geom_boxplot()
var1 = "price"
ggplot(diamonds, aes(x=cut, y= var1) ) + geom_boxplot()
# these 2 are equivalent, both works but inputs are strings
ggplot(diamonds, aes_string(x="cut", y= "price") ) + geom_boxplot()
var1 = "price"
ggplot(diamonds, aes_string(x="cut", y= var1) ) + geom_boxplot()
Using apply?
For this purpose I would be inclined to use loops (others are welcome to disagree). If you are set on using an apply approach then you probably want apply as lapply, mapply, vapply and sapply are list-, multivariate-, vector- and simple-apply respectively.
A more ggplot way of doing this now, is using .data pronoun.
library(ggplot2)
myfunction <- function(var1, var2) {
p <- ggplot(diamonds, aes(x = cut, y = .data[[var1]])) +
geom_boxplot() +
scale_y_continuous(
labels = getFromNamespace(x = var2, ns = "scales")
)
p
return(p)
}
myfunction("price", "dollar")
myfunction("price", "comma")
Then to create multiple plots with these function by passing multiple arguments, a better and tidier approach is using map functions from the {purrr}
plots <- purrr::map2(
.x = c("price", "price"),
.y = c("dollar", "comma"),
.f = myfunction
)
So, plots[[1]] contains the 1st plot with var1 = "price" and var2 = "dollar" and plots[[2]] contains the 2nd plot with var1 = "price" and var2 = "comma".

R: Unexplainable behavior of ggplot inside a function

I have composed a function that develops histograms using ggplot2 on the numerical columns of a dataframe that will be passed to it. The function stores these plots into a list and then returns the list.
However when I run the function I get the same plot again and again.
My code is the following and I provide also a reproducible example.
hist_of_columns = function(data, class, variables_to_exclude = c()){
library(ggplot2)
library(ggthemes)
data = as.data.frame(data)
variables_numeric = names(data)[unlist(lapply(data, function(x){is.numeric(x) | is.integer(x)}))]
variables_not_to_plot = c(class, variables_to_exclude)
variables_to_plot = setdiff(variables_numeric, variables_not_to_plot)
indices = match(variables_to_plot, names(data))
index_of_class = match(class, names(data))
plots = list()
for (i in (1 : length(variables_to_plot))){
p = ggplot(data, aes(x= data[, indices[i]], color= data[, index_of_class], fill=data[, index_of_class])) +
geom_histogram(aes(y=..density..), alpha=0.3,
position="identity", bins = 100)+ theme_economist() +
geom_density(alpha=.2) + xlab(names(data)[indices[i]]) + labs(fill = class) + guides(color = FALSE)
name = names(data)[indices[i]]
plots[[name]] = p
}
plots
}
data(mtcars)
mtcars$am = factor(mtcars$am)
data = mtcars
variables_to_exclude = 'mpg'
class = 'am'
plots = hist_of_columns(data, class, variables_to_exclude)
If you check the list plots you will discover that it contains the same plot repeated.
Simply use aes_string to pass string variables into the ggplot() call. Right now, your plot uses different data sources, not aligned with ggplot's data argument. Below x, color, and fill are separate, unrelated vectors though they derive from same source but ggplot does not know that:
ggplot(data, aes(x= data[, indices[i]], color= data[, index_of_class], fill=data[, index_of_class]))
However, with aes_string, passing string names to x, color, and fill will point to data:
ggplot(data, aes_string(x= names(data)[indices[i]], color= class, fill= class))
Here is strategy using tidyeval that does what you are after:
library(rlang)
library(tidyverse)
hist_of_cols <- function(data, class, drop_vars) {
# tidyeval overhead
class_enq <- enquo(class)
drop_enqs <- enquo(drop_vars)
data %>%
group_by(!!class_enq) %>% # keep the 'class' column always
select(-!!drop_enqs) %>% # drop any 'drop_vars'
select_if(is.numeric) %>% # keep only numeric columns
gather("key", "value", -!!class_enq) %>% # go to long form
split(.$key) %>% # make a list of data frames
map(~ ggplot(., aes(value, fill = !!class_enq)) + # plot as usual
geom_histogram() +
geom_density(alpha = .5) +
labs(x = unique(.$key)))
}
hist_of_cols(mtcars, am, mpg)
hist_of_cols(mtcars, am, c(mpg, wt))

Data driven plot names in data.table

This is a personal project to learn the syntax of the data.table package. I am trying to use the data values to create multiple graphs and label each based on the by group value. For example, given the following data:
# Generate dummy data
require(data.table)
set.seed(222)
DT = data.table(grp=rep(c("a","b","c"),each=10),
x = rnorm(30, mean=5, sd=1),
y = rnorm(30, mean=8, sd=1))
setkey(DT, grp)
The data consists of random x and y values for 3 groups (a, b, and c). I can create a formatted plot of all values with the following code:
# Example of plotting all groups in one plot
require(ggplot2)
p <- ggplot(data=DT, aes(x = x, y = y)) +
aes(shape = factor(grp))+
geom_point(aes(colour = factor(grp), shape = factor(grp)), size = 3) +
labs(title = "Group: ALL")
p
This creates the following plot:
Instead I would like to create a separate plot for each by group, and change the plot title from “Group: ALL” to “Group: a”, “Group: b”, “Group: c”, etc. The documentation for data.table says:
.BY is a list containing a length 1 vector for each item in by. This can be useful when by is not known in advance. The by variables are also available to j directly by name; useful for example for titles of graphs if j is a plot command, or to branch with if()
That being said, I do not understand how to use .BY or .SD to create separate plots for each group. Your help is appreciated.
Here is the data.table solution, though again, not what I would recommend:
make_plot <- function(dat, grp.name) {
print(
ggplot(dat, aes(x=x, y=y)) +
geom_point() + labs(title=paste0("Group: ", grp.name$grp))
)
NULL
}
DT[, make_plot(.SD, .BY), by=grp]
What you really should do for this particular application is what #dmartin recommends. At least, that's what I would do.
Instead of using data.table, you could use facet_grid in ggplot with the labeller argument:
p <- ggplot(data=DT, aes(x = x, y = y)) + aes(shape = factor(grp)) +
geom_point(aes(colour = factor(grp), shape = factor(grp)), size = 3) +
facet_grid(. ~ grp, labeller = label_both)
See the ggplot documentation for more information.
I see you already have a "facetting" option. I had done this
p+facet_wrap('grp')
But this gives the same result:
p+facet_wrap(~grp)

Resources