I have defined a function that takes a data.frame and returns a plot, which I later on pass to plotly. I need this function to be flexible and it's going to be called a number of times (that's why I wrote a function). A simple reproducible example:
a <- data.frame(x = 1:3, y = c(2, 6, 3))
library(ggplot2)
library(plotly)
plotTrend <- function(x){
var1 <- names(x)[1]
var2 <- names(x)[2]
p <- ggplot(a, aes(x = get(var1), y = get(var2)))+
geom_point()+
geom_smooth(method = "lm")
return(p)
}
Of course I can call plotTrend on a and I'll get the plot I'm expecting.
But when I call ggplotly on it, the tooltip reads an ugly get(var1) instead of the name of the column ("x" in this example).
plotTrend(a)
ggplotly()
I'm aware I could create a text column for the data.frame inside the function, and call ggplotly(tooltip = "text") (I read plenty of questions in SO about that), but I wondered if there's another way to get the right names in the tooltips, either by modifying the function or by using some special argument in ggplotly.
My expected output is:
A plotly plot with
Tooltips that accurately read the values and whose names are "x" and "y"
We can use aes_string to display the evaluated column names in the ggplotly tooltips:
library(ggplot2)
library(plotly)
a <- data.frame(x = 1:3, y = c(2, 6, 3))
var1 <- names(a)[1]
var2 <- names(a)[2]
p <- ggplot(a, aes_string(x = var1, y = var2)) +
geom_point()+
geom_smooth(method = "lm")
ggplotly(p)
NB: this works the same inside the plotTrend function call.
Alternatively, use tidy evaluation to pass the column names as function arguments in the plotTrend function:
plotTrend <- function(data, x, y) {
x_var <- enquo(x)
y_var <- enquo(y)
p <- ggplot(data, aes(x = !!x_var, y = !!y_var)) +
geom_point()+
geom_smooth(method = "lm")
return(p)
}
plotTrend(a, x = x, y = y) %>%
ggplotly()
Related
I have a data frame with x,y coordinates and dozens of columns I want to colour the points by, using scale_colour_steps. I wanted to create multiple plots in a loop using a variable to be passed to aes.
Here is a simple example that shows the error I get:
df <- data.frame(
x = runif(100),
y = runif(100),
z1 = rnorm(100)
)
cn <- colnames(df)[3]
gg <- list()
for(i in 1:length(cn)) {
gg[[i]] <- ggplot(df, aes(x, y, colour = !!cn[i])) +
geom_point() +
scale_colour_steps()
}
gg
However all I get for this is
[[1]]
Error: Binned scales only support continuous data
I have tried converting the variable to numeric using as.numeric(as.character(.)) but that hasn't worked for me.
How can I resolve this error?
Try :
library(ggplot2)
df <- data.frame(
x = runif(100),
y = runif(100),
z1 = rnorm(100)
)
cn <- colnames(df)[3]
gg <- vector('list', length(cn))
for(i in 1:length(cn)) {
gg[[i]] <- ggplot(df, aes(x, y, colour = .data[[cn[i]]])) +
geom_point() +
scale_colour_steps()
}
gg
Your attempt should work if you change the colour part as colour = !!sym(cn[i])) in your code. However, it is now preferred to use .data pronoun instead of !!sym().
I'm making a series of plots programmatically, and I want to pass the name of the tibble (or dataframe) into the title of my ggplot2 plot, so I know which is which.
deparse(substitute(x)) works for making a single plot from a tibble, but outputs "." when called via purrr::map() when making plots from a list of tibbles.
#initialize data frame
myDf <- tibble(x = LETTERS[1:5], y = sample(1:10, 5))
#initialize function
myPlot <- function(df) {
title = deparse(substitute(df))
ggplot(df, aes(x, y)) +
geom_col() +
ggtitle(title)
}
#call function
myPlot(myDf)
This gives me a plot with the title myDF.
Now I want to do the same thing with a list of plots:
#initialize list of data frames
myDFs <- vector("list", 0)
myDFs$first <- tibble(x = LETTERS[1:5], y = sample(1:10, 5))
myDFs$second <- tibble(x = LETTERS[1:5], y = sample(1:10, 5))
myDFs$third <- tibble(x = LETTERS[1:5], y = sample(1:10, 5))
#initialize same function
myPlot <- function(df) {
title = deparse(substitute(df))
ggplot(df, aes(x, y)) +
geom_col() +
ggtitle(title)
}
#call function with purrr::map
map(myDFs, myPlot)
Now each is titled with the same title: .x[[i]]
I'd love to know how to pass a more informative title through map. It doesn't have to be pretty, but it does have to be unique. Thank you in advance!
We could use imap which is made for such operations
myPlot <- function(df, names) {
ggplot(df, aes(x, y)) +
geom_col() +
ggtitle(names)
}
purrr::imap(myDFs, myPlot)
We could use Map from base R
Map(myPlot, myDFs, names(myDFs))
Or using iwalk
purrr::iwalk(myDFs, ~ myPlot(.x, .y))
where
myPlot <- function(data, nameVec){
ggplot(data, aes(x, y)) +
geom_col() +
ggtitle(nameVec)
}
I am trying to figure out how to create multiple ggplots with one function, to avoid tedious copying and pasting. I do not want to have to change argument names in the function when I want to use different columns in the same data.frame. There may be completely different approach to this problem, but I am including two attempts that almost worked but still fall short of what I want.
Thanks!
Edit
I would also like the function to add a facet depending on on argument, such as groupBy="brand", for example. I think aes_string along side of https://stat.ethz.ch/pipermail/r-help/2009-October/213946.html may get me there. I included my facet request as part of my question, because aes_string alone falls short of my goal of being able to facet as part of the plot function. I added brand to the dataset, just to share what I could not find by searching online today.
library(ggplot2)
### sample data ###
n=25
dataTest = data.frame(
xVar=sample(1:3, n, replace=TRUE),
yVar = rnorm(n, 5, 2),
zVar=rnorm(n, 5, .5),
brand=letters[1:5])
### a first attempt ###
### works, but forces me to create a new function whenever column names need to change
my_plot =function(data) {ggplot(data=data, aes(x=xVar, y=yVar))+geom_bar(stat="identity")}
do.call("my_plot", list(data=dataTest))
### wish for something like this... but this does not work
my_plot = function(data) {ggplot(data=data, aes(x=x, y=y))+geom_bar(stat="identity")}
do.call("my_plot", list(data=dataTest, x=xVar, y=yVar))
do.call("my_plot", list(data=dataTest, x=xVar, y=zVar))
### a second attempt, does not work ###
my.plot = function(x, y, data)
{
arguments <- as.list(match.call())
data = eval(arguments$data, envir=data)
x = eval(arguments$x, envir=data)
y = eval(arguments$y, envir=data)
p=ggplot(data=data, aes(x, y))+geom_bar(stat="identity")
return(p)
}
my.plot(x=xVar, y=yVar, data=dataTest)
Using aes_string will allow you to pass character strings into your ggplot2 function, allowing you to programmatically change it more easily:
my.plot = function(x, y, data)
{
p=ggplot(data, aes_string(x=x, y=y))+geom_bar(stat="identity")
print(p)
}
my.plot(x="xVar", y="yVar", data=dataTest)
my.plot(x="xVar", y="zVar", data=dataTest)
What about using %+% to update the plots instead?
Example:
library(ggplot2)
### sample data ###
n=25
dataTest = data.frame(
xVar=sample(1:3, n, replace=TRUE),
yVar = rnorm(n, 5, 2),
zVar=rnorm(n, 5, .5) )
p1 <- ggplot(data = dataTest, aes(x = xVar, y = yVar)) +
geom_bar(stat = "identity")
aes2 <- aes(x = xVar, y = zVar)
p2 <- p1 %+% aes2
p1:
p2:
EDIT
or as #sebastian-c mentioned, aes_string
plots <- function(x, y, data = dataTest) {
p1 <- ggplot(data = data, aes_string(x = x, y = y)) +
geom_bar(stat = "identity")
p1
}
plots('xVar','yVar')
plots('xVar','zVar')
EDIT 2: beat me to the punch :o
Using #sebastian-c's answer and other sources, I have a function that I think will work and I wanted to share it. I think I see #Henrik's solution, but it seems like more typing, as I have 4 groups, 4 'x' categories, and a third category related to time (year, quarters, months).
library(ggplot2)
### sample data ###
n=25
dataTest = data.frame(
xVar=sample(1:3, n, replace=TRUE),
yVar = rnorm(n, 5, 2),
zVar=rnorm(n, 5, .5),
brand=letters[1:5])
### function
my.plot = function(x, y, data, group=NULL)
{
p=ggplot(data, aes_string(x=x, y=y, fill=group))+
geom_bar(stat="identity")
# make a facet if group is not null
if(length(group)>0) {
facets = facet_wrap(formula(paste("~", group)))
p = p + facets
}
return(p)
}
I would like to use a variable of the dataframe passed to the data parameter of function the ggplot in another ggplot2 function in the same call.
For instance, in the following example I want to refer to the variable x in the dataframe passed to the data parameter in ggplot in another function scale_x_continuous such as in:
library(ggplot2)
set.seed(2017)
samp <- sample(x = 20, size= 1000, replace = T)
ggplot(data = data.frame(x = samp), mapping = aes(x = x)) + geom_bar() +
scale_x_continuous(breaks = seq(min(x), max(x)))
And I get the error :
Error in seq(min(x)) : object 'x' not found
which I understand. Of course I can avoid the problem by doing :
df <- data.frame(x = samp)
ggplot(data = df, mapping = aes(x = x)) + geom_bar() +
scale_x_continuous(breaks = seq(min(df$x), max(df$x)))
but I don't want to be forced to define the object df outside the call to ggplot. I want to be able to directly refer to the variables in the dataframe I passed in data.
Thanks a lot
The scale_x_continuous function does not evaluate it's parameters in the data environment. One reason for this is that each layer can have it's own data source so by the time you got to the scales it wouldn't be clear which data environment is the "correct" one any more.
You could write a helper function to initialize the plot with your default. For example
helper <- function(df, col) {
ggplot(data = df, mapping = aes_string(x = col)) +
scale_x_continuous(breaks = seq(min(df[[col]]), max(df[[col]])))
}
and then call
helper(data.frame(x = samp), "x") + geom_bar()
Or you could write a wrapper around just the scale part. For example
scale_x_custom <- function(x) {
scale_x_continuous(breaks = seq(min(x) , max(x)))
}
and then you can add your custom scale to your plot
ggplot(data = df, mapping = aes(x = x)) +
geom_bar() +
scale_x_custom(df$x)
Or since you just want breaks at integer values, you can calculate the breaks from the default limits without needed to actually specify the data. For example
scale_x_custom <- function() {
scale_x_continuous(expand=expansion(0, .3),
breaks = function(x) {
seq(ceiling(min(x)), floor(max(x)))
})
}
ggplot(data = df, mapping = aes(x = x)) +
geom_bar() +
scale_x_custom()
Another less than ideal alternative would be to utilize the . special symbol in combination with {} which is imported from magrittr.
Enclosing the ggplot call in curly brackets allows one to reference . multiple times.
data.frame(x = samp) %>%
{ggplot(data = ., mapping = aes(x = x)) + geom_bar() +
scale_x_continuous(breaks = seq(min(.$x), max(.$x)))}
I am trying to figure out how to create multiple ggplots with one function, to avoid tedious copying and pasting. I do not want to have to change argument names in the function when I want to use different columns in the same data.frame. There may be completely different approach to this problem, but I am including two attempts that almost worked but still fall short of what I want.
Thanks!
Edit
I would also like the function to add a facet depending on on argument, such as groupBy="brand", for example. I think aes_string along side of https://stat.ethz.ch/pipermail/r-help/2009-October/213946.html may get me there. I included my facet request as part of my question, because aes_string alone falls short of my goal of being able to facet as part of the plot function. I added brand to the dataset, just to share what I could not find by searching online today.
library(ggplot2)
### sample data ###
n=25
dataTest = data.frame(
xVar=sample(1:3, n, replace=TRUE),
yVar = rnorm(n, 5, 2),
zVar=rnorm(n, 5, .5),
brand=letters[1:5])
### a first attempt ###
### works, but forces me to create a new function whenever column names need to change
my_plot =function(data) {ggplot(data=data, aes(x=xVar, y=yVar))+geom_bar(stat="identity")}
do.call("my_plot", list(data=dataTest))
### wish for something like this... but this does not work
my_plot = function(data) {ggplot(data=data, aes(x=x, y=y))+geom_bar(stat="identity")}
do.call("my_plot", list(data=dataTest, x=xVar, y=yVar))
do.call("my_plot", list(data=dataTest, x=xVar, y=zVar))
### a second attempt, does not work ###
my.plot = function(x, y, data)
{
arguments <- as.list(match.call())
data = eval(arguments$data, envir=data)
x = eval(arguments$x, envir=data)
y = eval(arguments$y, envir=data)
p=ggplot(data=data, aes(x, y))+geom_bar(stat="identity")
return(p)
}
my.plot(x=xVar, y=yVar, data=dataTest)
Using aes_string will allow you to pass character strings into your ggplot2 function, allowing you to programmatically change it more easily:
my.plot = function(x, y, data)
{
p=ggplot(data, aes_string(x=x, y=y))+geom_bar(stat="identity")
print(p)
}
my.plot(x="xVar", y="yVar", data=dataTest)
my.plot(x="xVar", y="zVar", data=dataTest)
What about using %+% to update the plots instead?
Example:
library(ggplot2)
### sample data ###
n=25
dataTest = data.frame(
xVar=sample(1:3, n, replace=TRUE),
yVar = rnorm(n, 5, 2),
zVar=rnorm(n, 5, .5) )
p1 <- ggplot(data = dataTest, aes(x = xVar, y = yVar)) +
geom_bar(stat = "identity")
aes2 <- aes(x = xVar, y = zVar)
p2 <- p1 %+% aes2
p1:
p2:
EDIT
or as #sebastian-c mentioned, aes_string
plots <- function(x, y, data = dataTest) {
p1 <- ggplot(data = data, aes_string(x = x, y = y)) +
geom_bar(stat = "identity")
p1
}
plots('xVar','yVar')
plots('xVar','zVar')
EDIT 2: beat me to the punch :o
Using #sebastian-c's answer and other sources, I have a function that I think will work and I wanted to share it. I think I see #Henrik's solution, but it seems like more typing, as I have 4 groups, 4 'x' categories, and a third category related to time (year, quarters, months).
library(ggplot2)
### sample data ###
n=25
dataTest = data.frame(
xVar=sample(1:3, n, replace=TRUE),
yVar = rnorm(n, 5, 2),
zVar=rnorm(n, 5, .5),
brand=letters[1:5])
### function
my.plot = function(x, y, data, group=NULL)
{
p=ggplot(data, aes_string(x=x, y=y, fill=group))+
geom_bar(stat="identity")
# make a facet if group is not null
if(length(group)>0) {
facets = facet_wrap(formula(paste("~", group)))
p = p + facets
}
return(p)
}