Reputation: 33
I am trying to use geom_rect
in a for
loop, but it does not respect my limits. It does if I call it outside of the context of a for
loop. Is this a bug? Or is there something I don't understand about geom_rect
? outPlot_free
and outPlot1
should be identical (since .2 = .2/1), but the rectangles in outPlot1
are truncated, and interestingly they are identical to outPlot2
, outPlot3
and outPlot4
.
library('ggplot2')
library('ggrepel')
sum_df <- data.frame(matrix(NA, nrow=10, ncol=3))
colnames(sum_df) <- c("Variable", "Male", "Female")
sum_df$Variable <- c("a", "b", "c", "d", "e", "f", "g", "h", "i", "j")
covar = .7*.1*.1
Sigma = matrix(ncol=2,nrow=2,c(.2^2,covar,covar,.2^2))
temp = eigen(Sigma)
SqrtSigma = temp$vectors%*%diag(sqrt(temp$values))%*%t(temp$vectors)
XYvec = c(0,0) + SqrtSigma%*%rnorm(2)
for(i in 1:10){
XYvec = c(0,0) + SqrtSigma%*%rnorm(2)
sum_df$Female[i] = XYvec[1]
sum_df$Male[i] = XYvec[2]
}
outPlot_free <- ggplot(sum_df, aes(x=Male, y=Female)) + theme_minimal() +
geom_rect(aes(xmin=-.2, xmax=.2, ymin=-Inf, ymax=Inf), fill="grey97", color=NA, alpha=.5, size=0) +
geom_rect(aes(ymin=-.2, ymax=.2, xmin=-Inf, xmax=Inf), fill="grey97", color=NA, alpha=.5, size=0) +
geom_point() + geom_text_repel(aes(label=Variable)) +
scale_x_continuous(limits=c(-1, 1), breaks=round(seq(-1, 1, .1), digits=2)) +
scale_y_continuous(limits=c(-1, 1), breaks=round(seq(-1, 1, .1), digits=2)) +
geom_abline(intercept=0, slope=1, linetype="dotdash", alpha=.5) +
scale_color_manual(values=c("grey60", "black")) + xlab("Female") + ylab("Male") +
geom_hline(yintercept=.2, linetype="dashed", color="slateblue") + geom_vline(xintercept=.2, linetype="dashed", color="slateblue") +
geom_hline(yintercept=-.2, linetype="dashed", color="slateblue") + geom_vline(xintercept=-.2, linetype="dashed", color="slateblue")
for (q in 1:4) {
covar = .7*.1*.1
Sigma = matrix(ncol=2,nrow=2,c(.2^2,covar,covar,.2^2))
temp = eigen(Sigma)
SqrtSigma = temp$vectors%*%diag(sqrt(temp$values))%*%t(temp$vectors)
XYvec = c(0,0) + SqrtSigma%*%rnorm(2)
for(i in 1:10){
XYvec = c(0,0) + SqrtSigma%*%rnorm(2)
sum_df$Female[i] = XYvec[1]
sum_df$Male[i] = XYvec[2]
}
outPlot <- ggplot(sum_df, aes(x=Male, y=Female)) + theme_minimal() +
geom_rect(aes(xmin=-.2/q, xmax=.2/q, ymin=-Inf, ymax=Inf), fill="grey97", color=NA, alpha=.5, size=0) +
geom_rect(aes(ymin=-.2/q, ymax=.2/q, xmin=-Inf, xmax=Inf), fill="grey97", color=NA, alpha=.5, size=0) +
geom_point() + geom_text_repel(aes(label=Variable)) +
scale_x_continuous(limits=c(-1, 1), breaks=round(seq(-1, 1, .1), digits=2)) +
scale_y_continuous(limits=c(-1, 1), breaks=round(seq(-1, 1, .1), digits=2)) +
geom_abline(intercept=0, slope=1, linetype="dotdash", alpha=.5) +
scale_color_manual(values=c("grey60", "black")) + xlab("Female") + ylab("Male") +
geom_hline(yintercept=.2, linetype="dashed", color="slateblue") + geom_vline(xintercept=.2, linetype="dashed", color="slateblue") +
geom_hline(yintercept=-.2, linetype="dashed", color="slateblue") + geom_vline(xintercept=-.2, linetype="dashed", color="slateblue")
assign(paste0("outPlot", q), outPlot)
}
outPlot_free
outPlot1
outPlot2
outPlot3
outPlot4
Created on 2019-11-09 by the reprex package (v0.3.0)
outPlot_free
and outPlot1
should be identical except for the plotted points, since they were independently simulated.
Upvotes: 2
Views: 583
Reputation: 1
Wilke's answer is excellent, but it can be improved a little:
# this replaces the for loop
plots <- lapply(1:2, plot_fun)
plot_grid(plotlist = plots, labels = c(1:2))
the above code make use of force()
implicitly, which can be modified as below without force()
at all and show the lapply()
's power.
data <- data.frame(x = c(0, 1))
plots <- lapply(1:2, function(i){ggplot(data, aes(x, y = i)) + geom_line() + ylim(0, 3)})
plot_grid(plotlist = plots, labels = c(1:2))
Upvotes: 0
Reputation: 17790
The problem you're running into is lazy evaluation in R. It's a common problem when writing code containing loops, in particular if you're approaching the language from a procedural mindset. For more details, see e.g. here: http://adv-r.had.co.nz/Functions.html
In the following example, the first is what you're doing (in effect), and the second is what you should be doing.
# doesn't work as expected, as the variable i in the function call
# is evaluated only after the loop is run
x <- list()
for (i in 1:3) {
x[[i]] <- function() {i}
}
x[[1]]()
#> [1] 3
x[[2]]()
#> [1] 3
x[[3]]()
#> [1] 3
# by writing a function generator, we can bind the variable i
# to the specific function we're generating in each iteration
# of the loop
x <- list()
f_generator <- function(i) {
force(i)
function() {i}
}
for (i in 1:3) {
x[[i]] <- f_generator(i)
}
x[[1]]()
#> [1] 1
x[[2]]()
#> [1] 2
x[[3]]()
#> [1] 3
Created on 2019-11-09 by the reprex package (v0.3.0)
In the context of your code, write a function that generates the plot, call force()
on all the arguments to that function, and then inside the for()
loop call that function to create the specific plot objects you need. See the following example.
library(ggplot2)
library(cowplot)
# this doesn't work, the line in the first plot should be placed
# at y = 1 but is placed at y = 2
plots <- list()
for (i in 1:2) {
data <- data.frame(x = c(0, 1))
plots[[i]] <- ggplot(data, aes(x, y = i)) + geom_line() + ylim(0, 3)
}
plot_grid(plotlist = plots, labels = c(1:2))
# this does work
plots <- list()
plot_fun <- function(i) {
force(i)
data <- data.frame(x = c(0, 1))
ggplot(data, aes(x, y = i)) + geom_line() + ylim(0, 3)
}
for (i in 1:2) {
plots[[i]] <- plot_fun(i)
}
plot_grid(plotlist = plots, labels = c(1:2))
And finally, once you have written a function that generates your plots, the idiomatic approach in R would be to not write a for
loop but instead use lapply()
or map()
. It turns out that if you get used to using these functions instead of for
loops you're much less likely to run into the problem you're experiencing, because R is not a procedural language.
# this replaces the for loop
plots <- lapply(1:2, plot_fun)
plot_grid(plotlist = plots, labels = c(1:2))
Created on 2019-11-09 by the reprex package (v0.3.0)
Upvotes: 1