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
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")
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()
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)
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)
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))
:
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 facet
ing method.
For example I'd keep the same color for the same Sex
and different point shapes for different ID
:
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
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)
# discrete scale
gg + aes(colour = factor(exp))
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)
Related Topics
Does Ifelse Really Calculate Both of Its Vectors Every Time? Is It Slow
How to Remove All Duplicates So That None Are Left in a Data Frame
How to Read Multiple .Txt Files into R
Plot Multiple Columns on the Same Graph in R
Is There an R Function For Finding the Index of an Element in a Vector
Plot a Legend Outside of the Plotting Area in Base Graphics
Cannot Install R-Forge Package Using Install.Packages
Why Are My Dplyr Group_By & Summarize Not Working Properly? (Name-Collision With Plyr)
How to Set Limits For Axes in Ggplot2 R Plots
How to Find Common Elements from Multiple Vectors
Annotating Text on Individual Facet in Ggplot2
Interpreting "Condition Has Length ≫ 1" Warning from 'If' Function
Why Can't R'S Ifelse Statements Return Vectors
Why Does Data.Table Update Names(Dt) by Reference, Even If I Assign to Another Variable
Using Ggplot2, How to Insert a Break in the Axis
How Can Two Strings Be Concatenated