Цикл "для" добавляет только последний слой ggplot

Резюме. Когда я использую цикл "для" для добавления слоев в график скрипки (в ggplot), единственным добавленным слоем является тот, который создается последней итерацией цикла. Тем не менее, в явном коде, который имитирует код, создаваемый циклом, все слои добавляются.

Подробности: Я пытаюсь создать скриптовые графики с перекрывающимися слоями, чтобы показать, насколько распределенные оценки выполняют или не перекрываются для нескольких ответов на вопрос обследований, стратифицированных по месту. Я хочу, чтобы иметь возможность включать любое количество мест, поэтому у меня есть один столбец с помощью dataframe для каждого места, и я пытаюсь использовать цикл "для" для создания одного слоя ggplot для каждого места. Но цикл только добавляет слой из конечной итерации цикла.

Этот код иллюстрирует проблему и некоторые предлагаемые подходы, которые не выполнялись:

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 (i.e., 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/info/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/info/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 <- g + geom_violin(aes_string(y = p, colour = p), alpha = 0.3)  # produced this error: Error in unit(tic_pos.c, "mm") : 'x' and 'units' must have length > 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

Ответ 1

Причина, по которой это происходит, объясняется ggplot "ленивой оценкой". Это обычная проблема, когда ggplot используется таким образом (делая слои отдельно в цикле, вместо того чтобы иметь ggplot для вас, как в решении @hrbrmstr).

ggplot хранит аргументы aes(...) в качестве выражений и оценивает их только при визуализации графика. Итак, в ваших циклах, что-то вроде

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

хранится как есть и оценивается при рендеринге графика после завершения цикла. В этот момент p = 3, поэтому все графики отображаются с p = 3.

Таким образом, "правильный" способ сделать это - использовать melt(...) в пакете reshape2, чтобы преобразовать ваши данные из широкоформатного формата и позволить ggplot управлять слоями для вас. Я ставлю "правильно" в кавычки, потому что в этом конкретном случае есть тонкость. При расчете распределений для скрипки с использованием кадра расплавленных данных ggplot использует общую сумму (как для Чикаго, так и для Майами) в качестве шкалы. Если вы хотите, чтобы скрипки по частоте масштабировались индивидуально, вам нужно использовать петли (к сожалению).

Путь вокруг ленивой проблемы оценки заключается в том, чтобы ссылаться на индекс цикла в определении data=.... Это не сохраняется как выражение, фактические данные хранятся в определении графика. Поэтому вы можете сделать это:

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

который дает тот же результат, что и ваш. Обратите внимание, что индекс p не отображается в aes(...).


Обновление: заметка о scale="width" (упоминается в комментарии). Это приводит к тому, что все скрипки имеют одинаковую ширину (см. Ниже), что не такое же масштабирование, как в исходном коде OP. ИМО это не отличный способ визуализации данных, так как предполагает, что в чикагской группе гораздо больше данных.

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

Ответ 2

Просто избегайте использования цикла for. Как насчет lapply вместо:

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

EDIT: Это действительно не работает. У меня было p <- 2 в моей рабочей области, прежде чем запускать его, а затем он создал график только с данными Чикаго. Во всяком случае, принцип должен по-прежнему работать (хотя melt, вероятно, лучший вариант):

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

Ответ 3

Вы можете сделать это без цикла:

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

enter image description here