ggplot2 - Error bars using a custom function - r

I wish to generate error bar for each data point in ggplot2 using a generic function that extracts column names for the same using the names function. Following is a demo code:
plotfn <- function(data, xind, yind, yerr) {
yerrbar <- aes_string(ymin=names(data)[yind]-names(data)[yerr], ymin=names(data) [yind]+names(data)[yerr])
p <- ggplot(data, aes_string(x=names(data)[xind], y=names(data)[yind]) + geom_point() + geom_errorbar(yerrbar)
p
}
errdf <- data.frame('X'=rnorm(100, 2, 3), 'Y'=rnorm(100, 5, 6), 'eY'=rnorm(100))
plotfn(errdf, 1, 2, 3)
Running this gives the following error:
Error in names(data)[yind] - names(data)[yerr] :
non-numeric argument to binary operator
Any suggestions? Thanks.

You will need to pass a character string containing the - ('a-b' not 'a'-'b')
eg,
ggplot(mtcars,aes_string(y = 'mpg-disp',x = 'am')) + geom_point()
In your example
plotfn <- function(data, xind, yind, yerr) {
# subset the names now so it is slightly less typing later
yerr_names <- names(data)[c(yind,yerr)]
yerrbar <- aes_string(ymin = paste(yerr_names, collapse = '-'),
ymax = paste(yerr_names,collapse='+'))
p <- ggplot(data, aes_string(x=names(data)[xind], y=names(data)[yind])) +
geom_point() +
geom_errorbar(mapping = yerrbar)
p
}
# a slightly smaller, reproducible example
set.seed(1)
errdf <- data.frame('X'=rnorm(10, 2, 3), 'Y'=rnorm(10, 5, 6), 'eY'=rnorm(10))
plotfn(errdf, 1, 2, 3)

Related

Use a gradient fill under a facet wrap of density curves in ggplot in R?

Similar questions have been asked before in other forms. Some can be found here and here. However, I cant seem to adapt them when using a facet wrap displaying multiple density plots.
I tried adapting the other examples, but failed... I also tried using the ggpattern package, but when there is a large amount of data, it takes several minutes on my machine to create a plot.
I am trying to create a gradient under the density curve... but with the gradient pointing down. Something like in the example image below:
Some example data to work with:
library(ggplot2)
set.seed(321)
# create data
varNames <- c("x1", "x2", "x3")
df <- data.frame(
var = sample(varNames, 100, replace = T),
val = runif(100)
)
# create plot
ggplot(df, aes(x = val)) +
geom_density(aes(colour = var, fill = var)) +
facet_wrap(~var) +
theme_bw() +
theme(legend.position = "none")
You can use teunbrand's function, but you will need to apply it to each facet. Here simply looping over it with lapply
library(tidyverse)
library(polyclip)
#> polyclip 1.10-0 built from Clipper C++ version 6.4.0
## This is teunbrands function copied without any change!!
## from https://stackoverflow.com/a/64695516/7941188
fade_polygon <- function(x, y, n = 100) {
poly <- data.frame(x = x, y = y)
# Create bounding-box edges
yseq <- seq(min(poly$y), max(poly$y), length.out = n)
xlim <- range(poly$x) + c(-1, 1)
# Pair y-edges
grad <- cbind(head(yseq, -1), tail(yseq, -1))
# Add vertical ID
grad <- cbind(grad, seq_len(nrow(grad)))
# Slice up the polygon
grad <- apply(grad, 1, function(range) {
# Create bounding box
bbox <- data.frame(x = c(xlim, rev(xlim)),
y = c(range[1], range[1:2], range[2]))
# Do actual slicing
slice <- polyclip::polyclip(poly, bbox)
# Format as data.frame
for (i in seq_along(slice)) {
slice[[i]] <- data.frame(
x = slice[[i]]$x,
y = slice[[i]]$y,
value = range[3],
id = c(1, rep(0, length(slice[[i]]$x) - 1))
)
}
slice <- do.call(rbind, slice)
})
# Combine slices
grad <- do.call(rbind, grad)
# Create IDs
grad$id <- cumsum(grad$id)
return(grad)
}
## now here starts the change, loop over your variables. I'm creating the data frame directly instead of keeping the density object
dens <- lapply(split(df, df$var), function(x) {
dens <- density(x$val)
data.frame(x = dens$x, y = dens$y)
}
)
## we need this one for the plot, but still need the list
dens_df <- bind_rows(dens, .id = "var")
grad <- bind_rows(lapply(dens, function(x) fade_polygon(x$x, x$y)), .id = "var")
ggplot(grad, aes(x, y)) +
geom_line(data = dens_df) +
geom_polygon(aes(alpha = value, group = id),
fill = "blue") +
facet_wrap(~var) +
scale_alpha_continuous(range = c(0, 1))
Created on 2021-12-05 by the reprex package (v2.0.1)

How to wrap ggplot2 as a function in R

I want to wrap the following codes as a function
ez <- function(x,a) {
z<-x^3+1
return(z)
}
Q1 <- c(1,2,3,4,5)
ggplot(tibble(x = c(-10, 10)), aes(x)) +
map(1:length(Q1),
~stat_function(fun = ez, aes(color = paste0("sand ", .)), args=list(a = Q1[.])))
These codes develop multiple curves, but they are OVERLAP and it does not matter.
I want to generate a function like this
plot <- function(a) {
Q1 <- c(1,2,3,4,5)
ggplot(tibble(x = c(-6, 6)), aes(x)) +
map(1:length(Q1),
~stat_function(fun = ez, aes(color = paste0("sand ", .)), args=list(a = Q1[.])))
}
plot(2, 4, 3,6)
Maybe this is what your are looking for. As far as I get it you want to make a function, which you can pass a vector of parameters and which returns a plot of curves for the chosen parameters. To this end:
Pass the parameter values as a vector, i.e. put it in c(...)
In your plot function simply loop over a
Note: I adjusted the function ez to give different values (and non-overlapping curves) depending on a
ez <- function(x,a) {
z<-x^3+a^3
return(z)
}
library(ggplot2)
library(tibble)
library(purrr)
plot <- function(a) {
ggplot(tibble(x = c(-6, 6)), aes(x)) +
map(a,
~stat_function(fun = ez, aes(color = paste0("sand ", .)), args=list(a = .)))
}
plot(c(2, 4, 3, 6))

ggforce geom_mark_ellipse spanning categorial / factor values

I'm trying to use geom_mark_ellipse from ggforce package for circling a specific subset of my data. While one dimension of my data is numeric, the other is categorial and when trying to add a single ellipse geom_mark_ellipse draws two ellipses for each applicable value of the categorial dimension. This probably sounds more complicated than is, so here's a simple example:
library(tidyverse)
library(ggforce)
set.seed(20)
my_data <- tibble(x = rnorm(20, 7, 5),
y = factor(rep(c("a", "b", "c", "d"), 5), ordered = TRUE),
z = rnorm(20, 10, 2))
ggplot(my_data, aes(x, y, size = z)) +
geom_point() +
geom_mark_ellipse(size = 1, aes(filter = ((y %in% c("c", "d")) & (x > 6) ))) +
ggtitle("geom_ellipse with factor on y-axis")
This yields following chart:
While I'd like to get a chart like this:
set.seed(20)
my_data2 <- tibble(x = rnorm(20, 7, 5),
y = rep(c(1,2,3,4), 5),
z = rnorm(20, 10, 2))
ggplot(my_data2, aes(x, y, size = z)) +
geom_point() +
geom_mark_ellipse(size = 1, aes(filter = ((y %in% c(3, 4)) & (x > 6) ))) +
ggtitle("geom_ellipse with numerical on y-axis")
Certainly, a workaround is to coerce the factor into numeric - still: is there a way to "tell" geom_mark_ellipse to span factor levels (or other type of categorial variable values) or is this behaviour an intended feature?

Using ... function argument as input to another function

I would like to use ... to pass arguments into ggplot in a different function. For example:
dat <- data.frame(x = c(1, 2, 3), y = c(1, 2, 3))
f <- function(dat) {
ylimits = c(min(dat$x, dat$y), max(dat$x, dat$y))
g(dat, ylim = ylimits)
}
g <- function(dat, ...) {
args <- eval(substitute(alist(...)))
ggplot(dat, aes(x = x, y = y)) + geom_point() + coord_cartesian(ylim = args[['ylim']])
}
f(dat)
I tried using eval(args[['ylim']]), various combinations of quote/deparse/substitute but I haven't been able to get it to evaluate properly.
The environment of the previous function isn't passed along with the object, so if you save the call and then try to evaluate it the expression in g it won't be able to find ylimits, which only exists in f's environment.
One option is to use the lazyeval package, but it is currently being deprecated in favor of rlang, whose dots_list will do the trick nicely for you:
library(ggplot2)
dat <- data.frame(x = c(1, 2, 3), y = c(1, 2, 3))
f <- function(dat) {
ylimits = c(min(dat$x, dat$y), max(dat$x, dat$y))
g(dat, ylim = ylimits)
}
g <- function(dat, ...) {
args <- rlang::dots_list(...)
ggplot(dat, aes(x = x, y = y)) + geom_point() + coord_cartesian(ylim = eval(args[['ylim']]))
}
f(dat)

Using geom_text in a for loop with ggplot2

I want to display a list of text labels on a ggplot graph with the geom_text() function.
The positions of those labels are stored in a list.
When using the code below, only the second label appears.
x <- seq(0, 10, by = 0.1)
y <- sin(x)
df <- data.frame(x, y)
g <- ggplot(data = df, aes(x, y)) + geom_line()
pos.x <- list(5, 6)
pos.y <- list(0, 0.5)
for (i in 1:2) {
g <- g + geom_text(aes(x = pos.x[[i]], y = pos.y[[i]], label = paste("Test", i)))
}
print(g)
Any idea what is wrong with this code?
I agree with #user2728808 answer as a good solution, but here is what was wrong with your code.
Removing the aes from your geom_text will solve the problem. aes should be used for mapping variables from the data argument to aesthetics. Using it any differently, either by using $ or supplying single values can give unexpected results.
Code
for (i in 1:2) {
g <- g + geom_text(x = pos.x[[i]], y = pos.y[[i]], label = paste("Test", i))
}
I'm not exactly sure how geom_text can be used within a for-loop, but you can achieve the desired result by defining the text labels in advance and using annotate instead. See the code below.
library(ggplot2)
x <- seq(0, 10, by = 0.1)
y <- sin(x)
df <- data.frame(x, y)
pos.x <- c(5, 6)
pos.y <- c(0, 0.5)
titles <- paste("Test",1:2)
ggplot(data = df, aes(x, y)) + geom_line() +
annotate("text", x = pos.x, y = pos.y, label = titles)

Resources