1

I would like to create a set of custom functions in ggplot using multiple variables in a dataframe to calculate the function output. Here's a toy example with slopes and y-intersects stored as 'a' and 'b' in two columns of a data frame. One simple solution I was able to figure out is to manually cycle through the values. output1

library(ggplot2)
# define slope (a) and y intersect (b) of lines
df <- data.frame(a=c(2, 1), b=c(0, -5))
# plot lines with slopes (a) and y intersect (b)
plot <- ggplot() + xlim(-50, 50) +
     geom_function(fun = function(x) df$a[1]*x+df$b[1]) +
     geom_function(fun = function(x) df$a[2]*x+df$b[2])
print(plot)

If I use a for loop ggplot seems to replace the first function with the second function and I'm not really sure why? output2

library(ggplot2)
# define slope (a) and y intersect (b) of lines
df <- data.frame(a=c(2, 1), b=c(0, -5))
# plot lines with slopes (a) and y intersect (b)
plot <- ggplot() + xlim(-50, 50)
for (i in 1:nrow(df)) {plot <- plot + geom_function(fun = function(x) df$a[i]*x+df$b[i])}
print(plot)

However, neither option is ideal in my eyes, but my intuitive solution clearly gives incorrect results. output3

library(ggplot2)
# define slope (a) and y intersect (b) of lines
df <- data.frame(a=c(2, 1), b=c(0, -5))
# plot lines with slopes (a) and y intersect (b)
plot <- ggplot() + xlim(-50, 50) +
     geom_function(fun = function(x) df$a*x+df$b)
print(plot)

Is there no easy way to solve this within ggplot? I don't really want to create a new data frame for discrete x values to retain the option to plot functions instead of points connected by a line.


Clarification

@Allan and @Magnus, I only chose the linear plots as the simplest example, but I would like to plot a custom function f(x) <- s/sqrt(x)/(2*c) for many s and c values. My current work-around has drawbacks (need to enumerate every pair of factors; only plots line connecting discrete data points not smooth function; ...). output4

library(ggplot2)
# define s and c 
df <- data.frame(s=c(0.2, 0.5, 0.5), c=c(3, 5, 7))
# plot lines with function f(x) <- s/sqrt(x)/(2*c)
plot <- ggplot() + xlim(0, 50) +
     geom_line(data=data.frame(x=1:50, y=df$s[1]/sqrt(1:50/(2*df$c[1]))), aes(x=x, y=y)) +
     geom_line(data=data.frame(x=1:50, y=df$s[2]/sqrt(1:50/(2*df$c[2]))), aes(x=x, y=y)) +
     geom_line(data=data.frame(x=1:50, y=df$s[3]/sqrt(1:50/(2*df$c[3]))), aes(x=x, y=y))
print(plot)

My 'intuitive' solution of calling the plot without specifying the row item geom_line(data=data.frame(x=1:50, y=df$s/sqrt(1:50/(2*df$c))), aes(x=x, y=y) doesn't work and would only address part of the problem anyway.

Mario Niepel
  • 1,095
  • 4
  • 19

3 Answers3

2

After a couple of weeks with R the answer to my previous question is kinda obvious. I was simply not creating the layers the right way and the for-loop is inefficient at doing so anyway. Without going into the details of the data, I was performing a 'power analysis of some of our studies. enter image description here

I was able to show the average behavior across all studies, but I wanted to overlay a graph of the power of the study against sample number based on the variance of all individual studies as well.

I simply added a function called by mapply to just add a bunch of additional layers that I can then append to the basic plot. `my_funct describes the relationship between n, cv, and power of the study.

#// function to plot layers of dashed line for each study
geom_layer <- function (study, cv) { geom_function(aes(color=study), fun = my_funct, args = (cv), alpha=.7, linetype="dashed", size = 1) }

#// set up geom_lines for all other studies and store in layers
layers <- mapply(geom_layer, data_stats$study, data_stats$cv, SIMPLIFY = F )

#// combine plot and layers
print(plot + layers)

enter image description here

Mario Niepel
  • 1,095
  • 4
  • 19
0

I'm not sure what you are looking for. If you want to loop through a dataframe which consist of slopes and intercept values, then here is a way. The thing to keep in mind here is evaluation of expressions. See more info in this post. If this is not what you are looking for, please provide an example of the preferred output.

library(ggplot2)

# Either
df <- data.frame(a=c(10, 5), b=c(50, -5))
plot <- ggplot()
for (i in 1:nrow(df)) {print(plot + geom_function(fun = function(x) df$a[i]*x+df$b[i]))}


# Or the list way to save each plot
df <- data.frame(a=c(10, 5), b=c(50, -5))
plot_list <- vector("list", nrow(df))
for (i in 1:nrow(df)) {
  plot_list[[i]] <- local({
    i <- i
    p <- ggplot() + geom_function(fun = function(x) df$a[i]*x+df$b[i])
    print(p)
  })
}

Created on 2020-12-01 by the reprex package (v0.3.0)

Magnus Nordmo
  • 923
  • 7
  • 10
0

If you want all your lines on a single plot and you are planning to use simple straight lines with a slope and intercept, you could use geom_abline. To give a slightly expanded example:

library(ggplot2)

# define slope (a) and y intersect (b) of lines
df <- data.frame(a = c(2, 1, -0.5, 1.5), b = c(0, -5, 5, 3))

# plot lines with slopes (a) and y intersect (b)
ggplot(df) + 
  xlim(-50, 50) +
  ylim(-50, 50) +
  geom_abline(aes(slope = a, intercept = b, color = factor(seq(nrow(df))))) +
  labs(color = "equation")

enter image description here

Allan Cameron
  • 147,086
  • 7
  • 49
  • 87
  • Thank you @Allan. I think I should have been more clear that the linear plots are simply an example. I would like to do this for a custom non-linear function `f(x) <- s/sqrt(x)/(2*c)`. I will amend the post to make this more clear. – Mario Niepel Dec 01 '20 at 13:27