Хранение объектов ggplot в списке из цикла в R

Моя проблема похожа на на эту; когда я генерирую сюжетные объекты (в данном случае гистограммы) в цикле, кажется, что все они переписываются последним сюжетом.

Чтобы отлаживать, в цикле я печатаю индекс и сгенерированный график, оба из которых отображаются правильно. Но когда я смотрю на графики, хранящиеся в списке, они все одинаковы кроме для ярлыка.

(Я использую multipot для создания составного изображения, но вы получаете тот же результат, если вы print (myplots[[1]]) через print(myplots[[4]]) по одному.)

Поскольку у меня уже есть прикрепленный файл данных (в отличие от плаката аналогичной проблемы), я не уверен, как решить проблему.

(btw, классы столбцов являются фактором в исходном наборе данных, который я приближаю здесь, но такая же проблема возникает, если они являются целыми)

Вот пример воспроизводимости:

library(ggplot2)
source("http://peterhaschke.com/Code/multiplot.R") #load multiplot function

#make sample data
col1 <- c(2, 4, 1, 2, 5, 1, 2, 0, 1, 4, 4, 3, 5, 2, 4, 3, 3, 6, 5, 3, 6, 4, 3, 4, 4, 3, 4, 
          2, 4, 3, 3, 5, 3, 5, 5, 0, 0, 3, 3, 6, 5, 4, 4, 1, 3, 3, 2, 0, 5, 3, 6, 6, 2, 3, 
          3, 1, 5, 3, 4, 6)
col2 <- c(2, 4, 4, 0, 4, 4, 4, 4, 1, 4, 4, 3, 5, 0, 4, 5, 3, 6, 5, 3, 6, 4, 4, 2, 4, 4, 4, 
          1, 1, 2, 2, 3, 3, 5, 0, 3, 4, 2, 4, 5, 5, 4, 4, 2, 3, 5, 2, 6, 5, 2, 4, 6, 3, 3, 
          3, 1, 4, 3, 5, 4)
col3 <- c(2, 5, 4, 1, 4, 2, 3, 0, 1, 3, 4, 2, 5, 1, 4, 3, 4, 6, 3, 4, 6, 4, 1, 3, 5, 4, 3, 
          2, 1, 3, 2, 2, 2, 4, 0, 1, 4, 4, 3, 5, 3, 2, 5, 2, 3, 3, 4, 2, 4, 2, 4, 5, 1, 3, 
          3, 3, 4, 3, 5, 4)
col4 <- c(2, 5, 2, 1, 4, 1, 3, 4, 1, 3, 5, 2, 4, 3, 5, 3, 4, 6, 3, 4, 6, 4, 3, 2, 5, 5, 4,
          2, 3, 2, 2, 3, 3, 4, 0, 1, 4, 3, 3, 5, 4, 4, 4, 3, 3, 5, 4, 3, 5, 3, 6, 6, 4, 2, 
          3, 3, 4, 4, 4, 6)
data2 <- data.frame(col1,col2,col3,col4)
data2[,1:4] <- lapply(data2[,1:4], as.factor)
colnames(data2)<- c("A","B","C", "D")

#generate plots
myplots <- list()  # new empty list
for (i in 1:4) {
  p1 <- ggplot(data=data.frame(data2),aes(x=data2[ ,i]))+ 
    geom_histogram(fill="lightgreen") +
    xlab(colnames(data2)[ i])
  print(i)
  print(p1)
  myplots[[i]] <- p1  # add each plot into plot list
}
multiplot(plotlist = myplots, cols = 4)

Когда я смотрю на сводку сюжетного объекта в списке сюжетов, это то, что я вижу

> summary(myplots[[1]])
data: A, B, C, D [60x4]
mapping:  x = data2[, i]
faceting: facet_null() 
-----------------------------------
geom_histogram: fill = lightgreen 
stat_bin:  
position_stack: (width = NULL, height = NULL)

Я думаю, что проблема mapping: x = data2[, i], но я в тупике! Я не могу отправлять изображения, поэтому вам нужно будет запустить мой пример и посмотреть на графики, если мое объяснение проблемы путается.

Спасибо!

Ответ 1

В дополнение к другому превосходному ответу, вот решение, которое использует "нормальную" оценку -looking, а не eval. Поскольку циклы for не имеют отдельной переменной области (т.е. они выполняются в текущей среде), нам нужно использовать local, чтобы обернуть блок for; кроме того, нам нужно сделать i локальной переменной - что мы можем сделать, переназначив ее собственное имя 1:

myplots <- vector('list', ncol(data2))

for (i in seq_along(data2)) {
    message(i)
    myplots[[i]] <- local({
        i <- i
        p1 <- ggplot(data2, aes(x = data2[[i]])) +
            geom_histogram(fill = "lightgreen") +
            xlab(colnames(data2)[i])
        print(p1)
    })
}

Тем не менее, более чистый способ - полностью отказаться от цикла for и использовать функции списка для получения результата. Это работает несколькими возможными способами. На мой взгляд, самое простое:

plot_data_column = function (data, column) {
    ggplot(data, aes_string(x = column)) +
        geom_histogram(fill = "lightgreen") +
        xlab(column)
}

myplots <- lapply(colnames(data2), plot_data_column, data = data2)

У этого есть несколько преимуществ: он проще и не загромождает среду (с помощью переменной цикла i).


1 Это может показаться странным: почему i <- i вообще имеет какой-либо эффект? - Потому что, выполняя присваивание, мы создаем новую локальную переменную с тем же именем, что и переменная во внешней области видимости. Мы также могли бы использовать другое имя, например, local_i <- i.

Ответ 2

Из-за всех цитирования выражений, которые передаются вокруг, i, который оценивается в конце цикла, это то, что i происходит в это время, что является его окончательным значением. Вы можете обойти это с помощью eval(substitute( ing в правильном значении во время каждой итерации.

myplots <- list()  # new empty list
for (i in 1:4) {
    p1 <- eval(substitute(
        ggplot(data=data.frame(data2),aes(x=data2[ ,i]))+ 
          geom_histogram(fill="lightgreen") +
          xlab(colnames(data2)[ i])
    ,list(i = i)))
    print(i)
    print(p1)
    myplots[[i]] <- p1  # add each plot into plot list
}
multiplot(plotlist = myplots, cols = 4)