Using geom_text in a for loop with ggplot2 - r

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)

Related

Setting color levels in contourplots in ggplot R

I am plotting contour plots using ggplot in loop. I have few concerns -
the color levels are different in all iterations, how do it keep it steady iterations?
the number and range of levels are also changing with iteration, how to keep it constant across iterations ?
the length occupied by color scale is much longer than actual figure. How do I adjust that ?
How do I manually set the levels of colors in contours?
I have attached a sample below. Can someone please edit in the same code with comments
library(tidyverse)
library(gridExtra)
library(grid)
# data generation
x <- seq(-10, 10, 0.2)
y <- seq(-10, 10, 0.2)
tbl <- crossing(x, y)
for (i in seq(1, 2)) # to create two sample plots
{
# initialize list to store subplots
p <- list()
for (j in seq(1, 3)) # to create 3 subplots
{
# for randomness
a <- runif(1)
b <- runif(1)
# add z
tbl <- tbl %>%
mutate(z = a*(x - a)^2 + b*(y - b)^2)
# plot contours
p[[j]] <- ggplot(data = tbl,
aes(x = x,
y = y,
z = z)) +
geom_contour_filled(alpha = 0.8) +
theme_bw() +
theme(legend.position = "right") +
theme(aspect.ratio = 1) +
ggtitle("Sample")
}
p <- grid.arrange(p[[1]], p[[2]], p[[3]],
ncol = 3)
ggsave(paste0("iteration - ", i, ".png"),
p,
width = 8,
height = 3)
}
The actual plots are subplot for another plot, so I can increase its size. Therefore, width and height cannot be increased in ggsave.
Thanks
You can set breaks in geom_contour_filled. You can change your pngs by doubling their size but halfing their resolution. They will remain the same in terms of pixel dimensions.
for (i in seq(1, 2)) # to create two sample plots
{
p <- list()
for (j in seq(1, 3)) # to create 3 subplots
{
# for randomness
a <- runif(1)
b <- runif(1)
tbl <- tbl %>%
mutate(z = a*(x - a)^2 + b*(y - b)^2)
p[[j]] <- ggplot(data = tbl,
aes(x = x,
y = y,
z = z)) +
geom_contour_filled(alpha = 0.8, breaks = 0:9 * 20) +
scale_fill_viridis_d(drop = FALSE) +
theme_bw() +
theme(legend.position = "right") +
theme(aspect.ratio = 1) +
ggtitle("Sample")
}
p <- grid.arrange(p[[1]], p[[2]], p[[3]],
ncol = 3)
ggsave(paste0("iteration - ", i, ".png"),
p,
width = 16,
height = 6,
dpi = 150)
}
iteration-1.png
iteration-2.png

Manually Set Scale of contour plot using geom_contour_filled

I would like manually adjust the scales of two contour plots such that each have the same scale even though they contain different ranges of values in the z-direction.
For instance, lets say that I want to make contour plots of z1 and z2:
x = 1:15
y = 1:15
z1 = x %*% t(y)
z2 = 50+1.5*(x %*% t(y))
data <- data.frame(
x = as.vector(col(z1)),
y = as.vector(row(z1)),
z1 = as.vector(z1),
z2 = as.vector(z2)
)
ggplot(data, aes(x, y, z = z1)) +
geom_contour_filled(bins = 8)
ggplot(data, aes(x, y, z = z2)) +
geom_contour_filled(bins = 8)
Is there a way I can manually adjust the scale of each plot such that each contain the same number of levels (in this case bins = 8), the minimum is the same for both (in this case min(z1)), and the max is the same for both (max(z2))?
One can manually define a vector of desired breaks points and then pass the vector to the "breaks" option in the geom_contour_filled() function.
In the below script, finds 8 break intervals between the grand minimum and the grand maximum of the dataset.
Also there are 2 functions defined to create the palette and label names for the legend.
#establish the min and max of scale
grandmin <- min(z1, z2)-1
grandmax <- max(z2, z2)
#define the number of breaks. In this case 8 +1
mybreaks <- seq(grandmin, ceiling(round(grandmax, 0)), length.out = 9)
#Function to return the dersired number of colors
mycolors<- function(x) {
colors<-colorRampPalette(c("darkblue", "yellow"))( 8 )
colors[1:x]
}
#Function to create labels for legend
breaklabel <- function(x){
labels<- paste0(mybreaks[1:8], "-", mybreaks[2:9])
labels[1:x]
}
ggplot(data, aes(x, y, z = z1)) +
geom_contour_filled(breaks= mybreaks, show.legend = TRUE) +
scale_fill_manual(palette=mycolors, values=breaklabel(8), name="Value", drop=FALSE) +
theme(legend.position = "right")
ggplot(data, aes(x, y, z = z2)) +
geom_contour_filled(breaks= mybreaks, show.legend = TRUE) +
scale_fill_manual(palette=mycolors, values=breaklabel(8), name="Value", drop=FALSE)

List of plots generated in ggplot2 using scale_color_gradientn have wrong coloring

I'm attempting to use library(scales) and scale_color_gradientn() to create a custom mapping of colors to a continuous variable, in an attempt to limit the effect of outliers on the coloring of my plot. This works for a single plot, but does not work when I use a loop to generate several plots and store them in a list.
Here is a minimal working example:
library(ggplot2)
library(scales)
data1 <- as.data.frame(cbind(x = rnorm(100),
y = rnorm(100),
v1 = rnorm(100, mean = 2, sd = 1),
v2 = rnorm(100, mean = -2, sd = 1)))
#add outliers
data1[1,"v1"] <- 200
data1[2,"v1"] <- -200
data1[1,"v2"] <- 50
data1[2,"v2"] <- -50
#define color palette
cols <- colorRampPalette(c("#3540FF","black","#FF3535"))(n = 100)
#simple color scale
col2 <- scale_color_gradient2(low = "#3540FF",
mid = "black",
high = "#FF3535"
)
#outlier-adjusted color scale
{
aa <- min(data1$v1)
bb <- quantile(data1$v1, 0.05)
cc <- quantile(data1$v1, 0.95)
dd <- max(data1$v1)
coln <- scale_color_gradientn(colors = cols[c(1,5,95,100)],
values = rescale(c(aa,bb,cc,dd),
limits = c(aa,dd))
)
}
Plots:
1. Plot with simple scales - outliers cause scales to stretch out.
ggplot(data1, aes(x = x, y = y, color = v1))+
geom_point()+
col2
2. Plot with outlier-adjusted scales - correct color scaling.
ggplot(data1, aes(x = x, y = y, color = v1))+
geom_point()+
coln
3. The scales for v1 do not work for v2 as the data is different.
ggplot(data1, aes(x = x, y = y, color = v2))+
geom_point()+
coln
#loop to produce list of plots each with own scale
{
plots <- list()
k <- 1
for (i in c("v1","v2")){
aa <- min(data1[,i])
bb <- quantile(data1[,i],0.05)
cc <- quantile(data1[,i], 0.95)
dd <- max(data1[,i])
colm <- scale_color_gradientn(colors = cols[c(1,5,95,100)],
values = rescale(c(aa,bb,cc,dd),
limits = c(aa,dd)))
plots[[k]] <- ggplot(data1, aes_string(x = "x",
y = "y",
color = i
))+
geom_point()+
colm
k <- k + 1
}
}
4. First plot has the wrong scales.
plots[[1]]
5. Second plot has the correct scales.
plots[[2]]
So I'm guessing this has something to do with the scale_color_gradientn() function being called when the plotting takes place, rather than within the loop.
If anyone can help with this, it'd be much appreciated. In base R I would bin the continuous data and assigning hex colors into a vector used for fill color, but I'm unsure how I can apply this within ggplot.
You need to use a closure (function with associated environment):
{
plots <- list()
k <- 1
for (i in c("v1", "v2")){
colm <- function() {
aa <- min(data1[, i])
bb <- quantile(data1[, i], 0.05)
cc <- quantile(data1[, i], 0.95)
dd <- max(data1[, i])
scale_color_gradientn(colors = cols[c(1, 5, 95, 100)],
values = rescale(c(aa, bb, cc, dd),
limits = c(aa, dd)))
}
plots[[k]] <- ggplot(data1, aes_string(x = "x",
y = "y",
color = i)) +
geom_point() +
colm()
k <- k + 1
}
}
plots[[1]]
plots[[2]]

Transform color scale to probability-transformed color distribution with scale_fill_gradientn()

I am trying to visualize heavily tailed raster data, and I would like a non-linear mapping of colors to the range of the values. There are a couple of similar questions, but they don't really solve my specific problem (see links below).
library(ggplot2)
library(scales)
set.seed(42)
dat <- data.frame(
x = floor(runif(10000, min=1, max=100)),
y = floor(runif(10000, min=2, max=1000)),
z = rlnorm(10000, 1, 1) )
# colors for the colour scale:
col.pal <- colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan", "#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))
fill.colors <- col.pal(64)
This is how the data look like if not transformed in some way:
ggplot(dat, aes(x = x, y = y, fill = z)) +
geom_tile(width=2, height=30) +
scale_fill_gradientn(colours=fill.colors)
My question is sort of a follow-up question related to
this one or this one , and the solution given here actually yields exactly the plot I want, except for the legend:
qn <- rescale(quantile(dat$z, probs=seq(0, 1, length.out=length(fill.colors))))
ggplot(dat, aes(x = x, y = y, fill = z)) +
geom_tile(width=2, height=30) +
scale_fill_gradientn(colours=fill.colors, values = qn)
Now I want the colour scale in the legend to represent the non-linear distribution of the values (now only the red part of the scale is visible), i.e. the legend should as well be based on quantiles. Is there a way to accomplish this?
I thought the trans argument within the colour scale might do the trick, as suggested here , but that throws an error, I think because qnorm(pnorm(dat$z)) results in some infinite values (I don't completely understand the function though..).
norm_trans <- function(){
trans_new('norm', function(x) pnorm(x), function(x) qnorm(x))
}
ggplot(dat, aes(x = x, y = y, fill = z)) +
geom_tile(width=2, height=30) +
scale_fill_gradientn(colours=fill.colors, trans = 'norm')
> Error in seq.default(from = best$lmin, to = best$lmax, by = best$lstep) : 'from' must be of length 1
So, does anybody know how to have a quantile-based colour distribution in the plot and in the legend?
This code will make manual breaks with a pnorm transformation. Is this what you are after?
ggplot(dat, aes(x = x, y = y, fill = z)) +
geom_tile(width=2, height=30) +
scale_fill_gradientn(colours=fill.colors,
trans = 'norm',
breaks = quantile(dat$z, probs = c(0, 0.25, 1))
)
I believe you have been looking for a quantile transform. Unfortunately there is none in scales, but it is not to hard to build one yourself (on the fly):
make_quantile_trans <- function(x, format = scales::label_number()) {
name <- paste0("quantiles_of_", deparse1(substitute(x)))
xs <- sort(x)
N <- length(xs)
transform <- function(x) findInterval(x, xs)/N # find the last element that is smaller
inverse <- function(q) xs[1+floor(q*(N-1))]
scales::trans_new(
name = name,
transform = transform,
inverse = inverse,
breaks = function(x, n = 5) inverse(scales::extended_breaks()(transform(x), n)),
minor_breaks = function(x, n = 5) inverse(scales::regular_minor_breaks()(transform(x), n)),
format = format,
domain = xs[c(1, N)]
)
}
ggplot(dat, aes(x = x, y = y, fill = z)) +
geom_tile(width=2, height=30) +
scale_fill_gradientn(colours=fill.colors, trans = make_quantile_trans(dat$z))
Created on 2021-11-12 by the reprex package (v2.0.1)

How can I format axis labels with exponents with ggplot2 and scales?

With the new version ggplot2 and scales, I can't figure out how to get axis label in scientific notation. For example:
x <- 1:4
y <- c(0, 0.0001, 0.0002, 0.0003)
dd <- data.frame(x, y)
ggplot(dd, aes(x, y)) + geom_point()
gives me
I'd like the axis labels to be 0, 5 x 10^-5, 1 x 10^-4, 1.5 x 10^-4, etc. I can't figure out the correct combination of scale_y_continuous() and math_format() (at least I think those are what I need).
scale_y_log10() log transforms the axis, which I don't want. scale_y_continuous(label = math_format()) just gives me 10^0, 10^5e-5, etc. I see why the latter gives that result, but it's not what I'm looking for.
I am using ggplot2_0.9.1 and scales_0.2.1
I adapted Brian's answer and I think I got what you're after.
Simply by adding a parse() to the scientific_10() function (and changing 'x' to the correct 'times' symbol), you end up with this:
x <- 1:4
y <- c(0, 0.0001, 0.0002, 0.0003)
dd <- data.frame(x, y)
scientific_10 <- function(x) {
parse(text=gsub("e", " %*% 10^", scales::scientific_format()(x)))
}
ggplot(dd, aes(x, y)) + geom_point()+scale_y_continuous(label=scientific_10)
You might still want to smarten up the function so it deals with 0 a little more elegantly, but I think that's it!
As per the comments on the accepted solution, OP is looking to format exponents as exponents. This can be done with the trans_format and trans_breaks functions in the scales package:
library(ggplot2)
library(scales)
x <- 1:4
y <- c(0, 0.0001, 0.0002, 0.0003)
dd <- data.frame(x, y)
ggplot(dd, aes(x, y)) + geom_point() +
scale_y_log10("y",
breaks = trans_breaks("log10", function(x) 10^x),
labels = trans_format("log10", math_format(10^.x)))
scale_y_continuous(label=scientific_format())
gives labels with e instead of 10:
I suppose if you really want 10's in there, you could then wrap that in another function.
scientific_10 <- function(x) {
gsub("e", " x 10^", scientific_format()(x))
}
ggplot(dd, aes(x, y)) + geom_point() +
scale_y_continuous(label=scientific_10)
Riffing off of Tom's answer above, the following removes + signs, and handles 0 better (the function is anonymously inlined as well):
scale_y_continuous(label= function(x) {ifelse(x==0, "0", parse(text=gsub("[+]", "", gsub("e", " %*% 10^", scientific_format()(x)))))} ) +
I wrote a version of scientific_10 that avoids the scales package; it also removes leading zeroes in exponents (10^04 to 10^4, etc.). This was adapted from the helpful answers given above.
I've also included wrapper scale functions below.
scientific_10 <- function(x) {
xout <- gsub("1e", "10^{", format(x),fixed=TRUE)
xout <- gsub("{-0", "{-", xout,fixed=TRUE)
xout <- gsub("{+", "{", xout,fixed=TRUE)
xout <- gsub("{0", "{", xout,fixed=TRUE)
xout <- paste(xout,"}",sep="")
return(parse(text=xout))
}
scale_x_log10nice <- function(name=NULL,omag=seq(-10,20),...) {
breaks10 <- 10^omag
scale_x_log10(name,breaks=breaks10,labels=scientific_10(breaks10),...)
}
scale_y_log10nice <- function(name=NULL,omag=seq(-10,20),...) {
breaks10 <- 10^omag
scale_y_log10(name,breaks=breaks10,labels=scientific_10(breaks10),...)
}
scale_loglog <- function(...) {
list(scale_x_log10nice(...),scale_y_log10nice(...))
}
qplot(x=exp(5*rnorm(100)),geom="density",kernel="rectangular") +
scale_x_log10nice()
I think this became really easy using the great ggtext-package.
What I did was:
library(ggplot)
library(ggtext)
ggplot(mtcars, aes(x = log10(mpg), y = wt)) +
geom_point() +
scale_x_continuous(labels = function(x){return(paste0("10^", x))}) +
theme(
axis.text.x = element_markdown()
)
Merging the previous answer I've created a function that can get arbitrary power ot tens for x and y as a multiply factor and then create the plot adding the factor near the axes
library(ggplot2)
x <- seq(1,25)
y <- rnorm(25)/10000
dd <- data.frame(x, y)
trim10 <- function(x) {
parse(text=gsub(".*e", "x10^", scales::scientific_format()(x)))
}
ggplot_trim10 <- function(x,y,xfac,yfac,...){
xnew <- x*xfac
ynew <- y*yfac
dd <- data.frame(xnew,ynew)
xunit <- abs(max(xnew) - min(xnew))
yunit <- abs(max(ynew) - min(ynew))
x_min_label <- min(xnew) - xunit*0.1
x_max_label <- max(xnew) - xunit*0.1
y_min_label <- min(ynew) - yunit*0.1
y_max_label <- max(ynew) - yunit*0.1
ggplot(data=dd, aes(x=xnew, y=ynew),...) +
geom_line() +
annotate("text", x = x_max_label, y = y_min_label, label = trim10(xfac)) +
annotate("text", x = x_min_label, y = y_max_label, label = trim10(yfac)) +
coord_cartesian(xlim = c(min(xnew), max(xnew)),ylim = c(min(ynew),max(ynew)), clip = "off")
}
ggplot_trim10(x,y,10,10)
as a note I know that "x" is not the correct symbol but I was getting a bit crazy mixing expression, paste etc. if anyone will fix it it would be great

Resources