R назначение объектов ggplot для списка в цикле

Я использую for loop для назначения ggplots list, который затем передается plot_grid() (пакет cowplot). plot_grid размещает несколько ggplots рядом на одном рисунке. Это прекрасно работает вручную, но когда я использую for loop, последний сгенерированный график повторяется в каждом подкадре рисунка (показан ниже). Другими словами, все подкадры показывают один и тот же ggplot.

Вот пример игрушки:

require(cowplot)

dfrm <- data.frame(A=1:10, B=10:1)

v <- c("A","B")
dfmsize <- nrow(dfrm)
myplots <- vector("list",2)

count = 1
for(i in v){
    myplots[[count]] <- ggplot(dfrm, aes(x=1:dfmsize, y=dfrm[,i])) + geom_point() + labs(y=i)
    count = count +1
}
plot_grid(plotlist=myplots)

Ожидаемая цифра:

введите здесь описание изображения

Рисунок из for loop:

введите здесь описание изображения

Я попытался преобразовать элементы списка в grobs, как описано в этом вопрос, например:

mygrobs <- lapply(myplots, ggplotGrob)
plot_grid(plotlist=mygrobs)

Но я получил тот же результат.

Я думаю, что проблема заключается в назначении цикла, а не в plot_grid(), но я не вижу, что я делаю неправильно.


person someguyinafloppyhat    schedule 30.09.2016    source источник
comment
В этом ответе подробно рассматриваются некоторые мельчайшие детали ленивой оценки ggplot2.   -  person aosmith    schedule 01.10.2016


Ответы (4)


Ответы пока очень близки, но, на мой взгляд, неудовлетворительны. Проблема в следующем - после вашего цикла for:

myplots[[1]]$mapping
#* x -> 1:dfmsize
#* y -> dfrm[, i]
myplots[[1]]$plot_env
#<environment: R_GlobalEnv>

myplots[[2]]$mapping
#* x -> 1:dfmsize
#* y -> dfrm[, i]
myplots[[2]]$plot_env
#<environment: R_GlobalEnv>

i
#[1] "B"

Как упоминается в других ответах, ggplot фактически не оценивает эти выражения до построения графика, и, поскольку все они находятся в глобальной среде, а значение i равно "B", вы получаете нежелательные результаты.

Есть несколько способов избежать этой проблемы, самый простой из которых фактически упрощает ваши выражения:

myplots = lapply(v, function(col)
            ggplot(dfrm, aes(x=1:dfmsize, y=dfrm[,col])) + geom_point() + labs(y=col))

Причина, по которой это работает, заключается в том, что среда различна для каждого из значений в цикле lapply:

myplots[[1]]$mapping
#* x -> 1:dfmsize
#* y -> dfrm[, col]
myplots[[1]]$plot_env
#<environment: 0x000000000bc27b58>

myplots[[2]]$mapping
#* x -> 1:dfmsize
#* y -> dfrm[, col]
myplots[[2]]$plot_env
#<environment: 0x000000000af2ef40>

eval(quote(dfrm[, col]), env = myplots[[1]]$plot_env)
#[1]  1  2  3  4  5  6  7  8  9 10
eval(quote(dfrm[, col]), env = myplots[[2]]$plot_env)
#[1] 10  9  8  7  6  5  4  3  2  1

Таким образом, несмотря на то, что выражения одинаковы, результаты разные.

И если вам интересно, что именно хранится/копируется в среду lapply - неудивительно, что это просто имя столбца:

ls(myplots[[1]]$plot_env)
#[1] "col"
person eddi    schedule 30.09.2016
comment
Я отметил eddi как лучший ответ, потому что, отображая, а затем показывая plot_env каждую итерацию цикла, действительно ясно, что происходит. @jrandall: я не знал, что aes выполняет нестандартную оценку, поэтому, как вы упомянули, лучше использовать aes_q. Kudos to others for mentioning lapply` вместо зацикливания. - person someguyinafloppyhat; 01.10.2016
comment
@eddi Я пытаюсь убедить ggplot соблюдать значения переменных во время создания графика во время цикла for, когда я, наконец, просматриваю все графики, хранящиеся в списке графиков. Не могли бы вы посоветовать это? - person mavericks; 17.06.2020

Я считаю, что проблема здесь в том, что нестандартная оценка метода aes задерживает оценку i до тех пор, пока график не будет построен. К моменту построения графика i является последним значением (в игрушечном примере «B»), и, таким образом, эстетическое отображение y для всех графиков относится к этому последнему значению. Между тем, вызов labs использует стандартную оценку, поэтому метки правильно относятся к каждой итерации i в цикле.

Это можно исправить, просто используя стандартную ознакомительную версию функции сопоставления aes_q:

require(cowplot)

dfrm <- data.frame(A=1:10, B=10:1)

v <- c("A","B")
dfmsize <- nrow(dfrm)
myplots <- vector("list",2)

count = 1
for(i in v){
    myplots[[count]] <- ggplot(dfrm, aes_q(x=1:dfmsize, y=dfrm[,i])) + geom_point() + labs(y=i)
    count = count +1
}
plot_grid(plotlist=myplots)
person jrandall    schedule 30.09.2016
comment
Мне нравится, что вы явно упоминаете NSE. Это также можно проверить, фактически распечатав график внутри цикла перед его назначением в список, что на самом деле дает правильный вывод (в отличие от его печати после запуска цикла). - person jakub; 01.10.2016

Существует хорошее объяснение того, что происходит с ленивой оценкой ggplot2 и циклами for в [этот ответ](https://stackoverflow.com/a/26246791/2461552.

Я обычно переключаюсь на aes_string или aes_ для таких ситуаций, чтобы я мог использовать переменные как строки в ggplot2.

Я нахожу циклы lapply проще, чем цикл for в вашем случае, поскольку можно избежать инициализации списка и использования счетчика.

Сначала я добавляю переменную x в набор данных.

dfrm$index = 1:nrow(dfrm)

Теперь цикл lapply, проходящий по столбцам в v.

myplots = lapply(v, function(x) {
    ggplot(dfrm, aes_string(x = "index", y = x)) + 
        geom_point() +
        labs(y = x)
})

plot_grid(plotlist = myplots)
person aosmith    schedule 30.09.2016

Я думаю, что ggplot сбивается с толку, ища переменные x и y внутри dfrm, хотя на самом деле вы определяете их на лету. Если вы немного измените цикл for, чтобы построить новую подпрограмму data.frame в качестве первой строки, она будет работать нормально.

myplots <- list()
count = 1

for(i in v){
    df <- data.frame(x = 1:dfmsize, y = dfrm[,i])
    myplots[[count]] <- ggplot(df, aes(x=x, y=y)) + geom_point() + labs(y=i)
    count = count + 1
}
plot_grid(plotlist=myplots)

введите здесь описание изображения

person Nate    schedule 30.09.2016