Loop “for” apenas adiciona a camada final ggplot

Resumo: Quando eu uso um loop “for” para adicionar camadas a um gráfico de violino (em ggplot), a única camada adicionada é aquela criada pela iteração final do loop. Ainda no código explícito que imita o código que o loop produziria, todas as camadas são adicionadas.

Detalhes: Estou tentando criar charts de violino com camadas sobrepostas para mostrar a extensão em que as distribuições de estimativas se sobrepõem ou não a várias respostas da pergunta da pesquisa, estratificadas por local. Eu quero ser capaz de include qualquer número de lugares, então eu tenho uma coluna de dataframe para cada local e estou tentando usar um loop “for” para gerar uma camada de ggplot por local. Mas o loop apenas adiciona a camada da iteração final do loop.

Esse código ilustra o problema e algumas abordagens sugeridas falharam:

library(ggplot2) # Create a dataframe with 500 random normal values for responses to 3 survey questions from two cities topic <- c("Poverty %","Mean Age","% Smokers") place <- c("Chicago","Miami") n <- 500 mean <- c(35, 40,58, 50, 25,20) var <- c( 7, 1.5, 3, .25, .5, 1) df <- data.frame( topic=rep(topic,rep(n,length(topic))) ,c(rnorm(n,mean[1],var[1]),rnorm(n,mean[3],var[3]),rnorm(n,mean[5],var[5])) ,c(rnorm(n,mean[2],var[2]),rnorm(n,mean[4],var[4]),rnorm(n,mean[6],var[6])) ) names(df)[2:dim(df)[2]] <- place # Name those last two columns with the corresponding place name. head(df) # This "for" loop seems to only execute the final loop (ie, where p=3) g <- ggplot(df, aes(factor(topic), df[,2])) for (p in 2:dim(df)[2]) { g <- g + geom_violin(aes(y = df[,p], colour = place[p-1]), alpha = 0.3) } g # But mimicing what the for loop does in explicit code works fine, resulting in both "place"s being displayed in the graph. g <- ggplot(df, aes(factor(topic), df[,2])) g <- g + geom_violin(aes(y = df[,2], colour = place[2-1]), alpha = 0.3) g <- g + geom_violin(aes(y = df[,3], colour = place[3-1]), alpha = 0.3) g ## per http://stackoverflow.com/questions/18444620/set-layers-in-ggplot2-via-loop , I tried g <- ggplot(df, aes(factor(topic), df[,2])) for (p in 2:dim(df)[2]) { df1 <- df[,c(1,p)] g <- g + geom_violin(aes(y = df1[,2], colour = place[p-1]), alpha = 0.3) } g # but got the same undesired result # per http://stackoverflow.com/questions/15987367/how-to-add-layers-in-ggplot-using-a-for-loop , I tried g <- ggplot(df, aes(factor(topic), df[,2])) for (p in names(df)[-1]) { cat(p,"\n") g  0 # g <- g + geom_violin(aes_string(y = p ), alpha = 0.3) # produced this error: Error: stat_ydensity requires the following missing aesthetics: y } g # but that failed to produce any graphic, per the errors noted in the "for" loop above 

A razão pela qual isso está acontecendo é devido à “avaliação preguiçosa” de ggplot . Este é um problema comum quando o ggplot é usado dessa maneira (fazendo as camadas separadamente em um loop, ao invés de ter ggplot para ele, como na solução do @ hrbrmstr).

ggplot armazena os argumentos para aes(...) como expressões , e apenas os avalia quando o gráfico é renderizado. Então, nos seus loops, algo como

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

fica armazenado como está e é avaliado quando você renderiza o gráfico após a conclusão do loop. Neste ponto, p = 3, então todos os charts são renderizados com p = 3.

Então, a maneira “certa” de fazer isso é usar o melt(...) no pacote reshape2 , então converta seus dados do formato largo para o longo, e deixe o ggplot gerenciar as camadas para você. Eu coloquei “certo” entre aspas porque neste caso em particular há uma sutileza. Ao calcular as distribuições para os violinos usando o quadro de dados derretido, o ggplot usa o total geral (para Chicago e Miami) como a escala. Se você quiser violinos baseados na freqüência escalada individualmente, você precisa usar loops (infelizmente).

O caminho em torno do problema de avaliação lenta é colocar qualquer referência ao índice de loop na definição data=... Isso não é armazenado como uma expressão, os dados reais são armazenados na definição de plotagem. Então você poderia fazer isso:

 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 

que dá o mesmo resultado que o seu. Note que o índice p não aparece em aes(...) .


Atualização: uma nota sobre scale="width" (mencionada em um comentário). Isso faz com que todos os violinos tenham a mesma largura (veja abaixo), o que não é o mesmo escalonamento do código original do OP. IMO esta não é uma ótima maneira de visualizar os dados, pois sugere que há muito mais dados no grupo de Chicago.

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

Você pode fazer isso sem o loop:

 df.2 <- melt(df) gg <- ggplot(df.2, aes(x=topic, y=value)) gg <- gg + geom_violin(position="identity", aes(color=variable), alpha=0.3) gg 

insira a descrição da imagem aqui

Apenas evite usar o loop for então. Como sobre lapply vez disso:

 g <- g + lapply(2:ncol(df), function(p) { geom_violin(aes(y = df[,p], colour = place[p-1]), alpha = 0.3) }) 

EDIT: Isso realmente não funciona. Eu tinha p <- 2 no meu espaço de trabalho antes de executá-lo e, em seguida, produzia um gráfico apenas com os dados de Chicago. De qualquer forma, o princípio ainda deve funcionar (embora o melt seja provavelmente uma opção melhor):

 g <- ggplot(df, aes(x=factor(topic))) g + lapply(place, function(p) { geom_violin(aes_string(y = p), alpha = 0.3, color = which(p==place)) })