How to Add Layers in Ggplot Using a For-Loop

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

adding layers in ggplot2 with for loops

ggplot does a bit of lazy evaluation, so in your for loop example i isn't evaluated immediately. If we look at the layers, we can see the i is still there as i, not as 3 and 4 in the respective iterations. When you print the plot, that is when i is evaluated--at whatever value it takes when you print the plot. When can even change i after the loop to cause problems:

aux <- 3:4
p <- ggplot(mtcars, aes(x = wt, y = mpg)) + geom_point()

for (i in 1:2) {
p <- p + geom_segment(aes(x = aux[i], y = 0, xend = aux[i], yend = 35), colour = "purple")
}

p$layers
# [[1]]
# geom_point: na.rm = FALSE
# stat_identity: na.rm = FALSE
# position_identity
#
# [[2]]
# mapping: x = ~aux[i], y = 0, xend = ~aux[i], yend = 35
# geom_segment: arrow = NULL, arrow.fill = NULL, lineend = butt, linejoin = round, na.rm = FALSE
# stat_identity: na.rm = FALSE
# position_identity
#
# [[3]]
# mapping: x = ~aux[i], y = 0, xend = ~aux[i], yend = 35
# geom_segment: arrow = NULL, arrow.fill = NULL, lineend = butt, linejoin = round, na.rm = FALSE
# stat_identity: na.rm = FALSE
# position_identity

## changing `i` later can still cause problems:
i = 5
print(p)
# Warning messages:
# 1: Removed 32 rows containing missing values (geom_segment).
# 2: Removed 32 rows containing missing values (geom_segment).

So, no, you can't really use for loops quite like that. There are probably workarounds, but this feels like an XY problem - this isn't how ggplot is intended to be used, so using it this way will be difficult.

It's hard to know what your real use case is, but in this case we could put aux data in a data frame and do it like this (ggplot is made to work with data frames):

ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point() +
geom_segment(
data = data.frame(aux),
aes(x = aux, xend = aux),
y = 0, yend = 35, colour = "purple"
)

Though, for the the special case of vertical lines, we can use geom_vline with the aux vector directly:

ggplot(mtcars, aes(x = wt, y = mpg)) +
geom_point() +
geom_vline(xintercept = aux, colour = "purple")

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

How to add 'stat_function()' layers in ggplot2 using a for loop?

A for loop doesn not create environments to capture i values for you. So you are creating the same function each time with a a free varaible i and that value is not "looked-up" until the plot is printed and after the for loop the value of i is just 10.

Instead, you should capture the value in the layer with the args= command. Those will be evaluated during the loop, not at draw time. Then just add the variable you captured as a parameter to the function.

for (i in 1:10) {
p <- p + stat_function(fun = function(x, i) (i + x), color = cols[i], args=list(i=i))
}
print(p)

Set layers in ggplot2 via loop

This problem is simple if you do the subsetting yourself:

library(ggplot2)
data <- data.frame(
col1 = c(1, 2, 2, 3, 3, 4),
col2 = c(1, 2, 2, 1, 2, 1),
z = rnorm(6))
ids <- unique(data$col2)

p1 <- ggplot(data, aes(col1, y = ..count..))

for(id in ids){
df <- data[data$col2 == id, ]
p1 <- p1 + geom_bar(data = df, fill = "blue", alpha = 0.2)
}
p1

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.

How can I speed up adding many ggplot layers/geoms in a for loop?

The loop slows the whole thing down, and it is not necessary, bad style, and should be avoided.
Rather, the outer loop (i) should be matched to a y-aesthetic, and the inner loop (j) should be matched to an x-aesthetic.
(Apart from that, one would usually use rather something like p <- p + geom_ribbon() instead of %<>%.)

Here is an approach using geom_tile(), but other geoms may be appropriate as well.
Note that I turn your list of two dataframes (lst_eg_data) into a single dataframe first.

df1 <- melt(lst_eg_data, id.vars = names(lst_eg_data[[1]]))

ggplot(df1, aes(x = LL, y = L1, fill = valColor, height = .5)) +
scale_fill_grey() +
geom_tile(show.legend = FALSE)

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)
}


Related Topics



Leave a reply



Submit