"For" Loop Only Adds the Final Ggplot Layer

for loop only adds the final ggplot layer

The reason this is happening is due to ggplot's "lazy evaluation". This is a common problem when ggplot is used this way (making the layers separately in a loop, rather than having ggplot to it for you, as in @hrbrmstr's solution).

ggplot stores the arguments to aes(...) as expressions, and only evaluates them when the plot is rendered. So, in your loops, something like

aes(y = df[,p], colour = place[p-1])

gets stored as is, and evaluated when you render the plot, after the loop completes. At this point, p=3 so all the plots are rendered with p=3.

So the "right" way to do this is to use melt(...) in the reshape2 package so convert your data from wide to long format, and let ggplot manage the layers for you. I put "right" in quotes because in this particular case there is a subtlety. When calculating the distributions for the violins using the melted data frame, ggplot uses the grand total (for both Chicago and Miami) as the scale. If you want violins based on frequency scaled individually, you need to use loops (sadly).

The way around the lazy evaluation problem is to put any reference to the loop index in the data=... definition. This is not stored as an expression, the actual data is stored in the plot definition. So you could do this:

g <- ggplot(df,aes(x=topic))
for (p in 2:length(df)) {
gg.data <- data.frame(topic=df$topic,value=df[,p],city=names(df)[p])
g <- g + geom_violin(data=gg.data,aes(y=value, color=city))
}
g

Sample Image

which gives the same result as yours. Note that the index p does not show up in aes(...).


Update: A note about scale="width" (mentioned in a comment). This causes all the violins to have the same width (see below), which is not the same scaling as in OP's original code. IMO this is not a great way to visualize the data, as it suggests there is much more data in the Chicago group.

ggplot(gg) +geom_violin(aes(x=topic,y=value,color=variable),
alpha=0.3,position="identity",scale="width")

Sample Image

ggplot map: for loop only adds final map layer

You are currently initiating your base map at the beginning in each iteration of the loop, which replaces plot that you made in the previous iteration. Initiate the base map before starting the loop to add all layers to the same map.

addLayer <- function(){
glob <- globalenv()
customFiles <- data.frame(ls(pattern = "^(?i)new", envir = glob))
colnames(customFiles) <- "X"
customFiles$X <- as.character(customFiles$X)
plot <- myMap
for(i in 1:length(customFiles$X)){
gg.data <- get(paste(customFiles$X[i]))
if(grepl("^SpatialPolygons", class(get(paste(customFiles$X[i])))) == TRUE){
plot <- plot + geom_polygon(data = gg.data, aes(long, lat, group= group))
}
if(grepl("^SpatialLines", class(get(paste(customFiles$X[i])))) == TRUE){
plot <- plot + geom_path(data = gg.data, aes(long, lat, group= group))
}
if(grepl("^SpatialPoints", class(get(paste(customFiles$X[i])))) == TRUE){
plot <- plot + geom_point(data = data.frame(coordinates(gg.data)), aes(long, lat))
}
}
print(plot)
}

how to add layers in ggplot using a for-loop

One approach would be to reshape your data frame from wide format to long format using function melt() from library reshape2. In new data frame you will have x1 values, variable that determine from which column data came, and value that contains all original y values.

Now you can plot all data with one ggplot() and geom_line() call and use variable to have for example separate color for each line.

 library(reshape2)
df.long<-melt(df,id.vars="x1")
head(df.long)
x1 variable value
1 1 y1 2.0
2 2 y1 5.4
3 3 y1 7.1
4 4 y1 4.6
5 5 y1 5.0
6 1 y2 0.4
ggplot(df.long,aes(x1,value,color=variable))+geom_line()

Sample Image

If you really want to use for() loop (not the best way) then you should use names(df)[-1] instead of seq(). This will make vector of column names (except first column). Then inside geom_line() use aes_string(y=i) to select column by their name.

plotAllLayers<-function(df){
p<-ggplot(data=df,aes(df[,1]))
for(i in names(df)[-1]){
p<-p+geom_line(aes_string(y=i))
}
return(p)
}

plotAllLayers(df)

Sample Image

Strange behavior with ggplots geom_point in for loop

Interesting finding. This is because you're using a for loop and they have also to me often enough difficult to understand behaviour regarding object creation and evaluation. In your case, ggplot doesn't draw the plots until the last end, and then the last vector 'y' is used for the plot. I find the easiest way to avoid this problem is using another way to loop instead. I prefer the apply family.

That said - my advice is to avoid using vectors in aes() - this only causes headaches.

I just found this thread which explains the problem much better. Suggest closing this question as a duplicate. "for" loop only adds the final ggplot layer

library(ggplot2)
library(dplyr)

df <- data.frame( case=1:2, y1=c(1, 2), y2=c(2, 4), y3=c(3, 8), y4=c(4, 16), y5=c(5, 32))

x <- 1:5

plot_list <- lapply(1:2, function(i){
data <- df %>% dplyr::filter(case == i)
y <- data %>% dplyr::select(starts_with('y')) %>% unlist(use.name=FALSE)
graph <- ggplot() +
geom_point(aes(x=x, y=y))
graph
})

gridExtra::grid.arrange(grobs=plot_list, ncol=2)

Sample Image

Created on 2022-02-08 by the reprex package (v2.0.1)

for loop with ggplot only uses last value, even with vector defined outside the loop

The immediate problem is resolvable transforming the ggplot object to a Grob and use it in grid.arrange. The root problem is probably caused by lazy evaluation (thanks @baptiste(? - comment removed)).

Just change huddlist[[i]] <- huddling to huddlist[[i]] <- ggplot_gtable(ggplot_build(huddling)):

Sample Image

However what you are trying to do is mapping two different dimensions (Sex and ID) to the same aesthetic.
What I would do is to separate those dimensions, adding an aes and using the standard faceting method.

For example I'd keep the same color for the same Sex and different point shapes for different ID:
Sample Image

While not entirely different I think this is better, for example in the first plot I assume that the same color applies to the same individual, when that's not the case.

data

set.seed(4887)
Strain <- rep(c(rep("A", times = 2), rep("B", times = 4)), times = 2)
Sex_ID <- rep(c("M_1", "F_2", "M_3", "F_4", "M_5", "F_6"), times = 2)
State <- rep(c("virgin", "mated", "expecting", "parent"), each = 6)
Huddling <- runif(8, 1.5, 3.8)

d <- data.frame(Strain, Sex_ID, State, Huddling)

code for the first plot

level<-levels(d$Strain)
huddlist<-list()

# How many colours do we need? Different reds for each female, blues for males
len <- c(length(d$Sex_ID[d$Strain=="A"])/8,length(d$Sex_ID[d$Strain=="B"])/8)

for(i in 1:length(level)){
ss<- subset(d, Strain==level[i]) # subset only for one species at a time
m <- scales::seq_gradient_pal("cyan2", "midnightblue", "Lab")(seq(0,1,length.out = len[i]))
f<-scales::seq_gradient_pal("tomato", "red4", "Lab")(seq(0,1,length.out = len[i]))
fm<-c(f,m)
ymax <- max(ss$Huddling); ymin <- min(ss$Huddling)

# The plot
huddling<-ggplot(ss, aes(x=factor(State), y=Huddling, color=factor(Sex_ID), group=factor(Sex_ID)))+
geom_point(shape=21, size=3, position=position_dodge(width=0.3))+
geom_line(size=0.7, position=position_dodge(width=0.3)) +
scale_color_manual(values=fm)+
scale_fill_manual(values="white")+
ylim(ymin,ymax)+
labs(y="Time huddling (s)", x="Reproductive stage")+
theme_classic()+
theme(axis.line.x = element_line(color="black", size = 1),
axis.line.y = element_line(color="black", size = 1))+
theme(axis.text=element_text(size=17),axis.title=element_text(size=19,face="bold"))+
theme(legend.title=element_text(size=17))+
theme(legend.text=element_text(size=15))+
theme(legend.position="none")+ # if legend should be removed
theme(plot.title = element_text(lineheight=.8, face="bold",size=22))+
scale_x_discrete(limits=c("virgin", "mated", "expecting", "parent"), labels=c("Virgin", "Mated", "Expecting", "Parent"))

huddlist[[i]] <- ggplot_gtable(ggplot_build(huddling))
}

library(gridExtra)
do.call("grid.arrange", c(huddlist))

code for the second plot

library(tidyr)
d <- d %>%
separate(Sex_ID, c('Sex', 'ID'), sep = '_')

ggplot(d, aes(x = factor(State), y = Huddling, color = Sex, group = ID, shape = ID))+
facet_grid(Strain ~ ., scales = 'free_y') +
geom_point(size = 3, position = position_dodge(width=0.3), show.legend = F) +
geom_line(size = 0.7, position = position_dodge(width=0.3)) +
scale_color_manual(values = c('red4', 'midnightblue')) +
scale_fill_manual(values = "white") +
scale_x_discrete(limits = c("virgin", "mated", "expecting", "parent"),
labels = c("Virgin", "Mated", "Expecting", "Parent")) +
labs(y = "Time huddling (s)", x = "Reproductive stage") +
theme_classic() +
theme(axis.line.x = element_line(color = "black", size = 1),
axis.line.y = element_line(color = "black", size = 1),
axis.text = element_text(size = 17),
axis.title = element_text(size = 19,face = "bold"),
legend.title = element_text(size = 17),
legend.text = element_text(size = 15),
plot.title = element_text(lineheight = .8, face = "bold",size = 22))

Adding layers to ggplots works but adding in a loop does not

You're a victim of lazy evaluation. [See, for example, here.] A for loop uses lazy evaluation. Fortunately, lapply does not. So,

p <- ggplot() + coord_fixed(xlim = q, ylim = q)
lapply(
1:2,
function(i) p <<- p + geom_point(aes(x=x0[i], y=y0[i]))
)

gives you what you want.

Note the use of <<- as a quick and dirty fix.

ggplot loop adding curves fails, but works one at a time

Baptiste suggested to create the entire data.frame with all variables first, and then plot it (preferably in long format). The answer provided by Gene creates the data in wide format requiring to loop over the columns.

The code below creates the data in long format and plots all curves in one call:

# create data in long format
df <- expand.grid(x = 0:10/10, exp = 1:4/4)
df$y <- df$x^df$exp

# plot
library(ggplot2)
gg <- ggplot(df, aes(x, y, group = exp)) + geom_line()
gg

Sample Image

Note that geom_line() is used here because it connects the observations in order of the variable on the x axis. geom_path() connects the observations in the order in which they appear in the data.

The different curves can be colour-coded as well:

# continous scale
gg + aes(colour = exp)

Sample Image

# discrete scale
gg + aes(colour = factor(exp))

Sample Image

Note that by including the colour aesthetic in the call to aes() an appropriate legend is created by default.

ggplot only shows one entry when run in a for loop

You need to tell ggplot to evaluate the expressions in aes() as external variables.

g <- ggplot(arcs)

for(yc in 1:3) {
y0 <- yc * 10
for(xc in 1:3) {
x0 <- xc * 10
g <- g + ggforce::geom_arc_bar(aes(x0 = {{x0}}, y0 = {{y0}},
r0 = radius - 2, r = radius, start = 0, end = 2 * pi),
fill = 'blue', alpha = 0.1, colour = NA)
}
}
g <- g +
coord_equal()
print(g)

Sample Image



Related Topics



Leave a reply



Submit