Я борюсь с созданием нескольких ggplots с помощью цикла. Я использую данные в следующем формате:
a <- c(1,2,3,4)
b <- c(5,6,7,8)
c <- c(9,10,11,12)
d <- c(13,14,15,16)
time <- c(1,2,3,4)
data <- cbind(a,b,c,d,time)
То, что я хочу создать, — это список графиков, отображающих одну из букв в зависимости от переменного времени. Что я пробовал следующим образом:
library(ggplot2)
library(gridExtra)
plots <- list()
for (i in 1:4){
plots[[i]] <- ggplot() + geom_line(data = data, aes(x = time, y = data[,i]))
}
grid.arrange(plots[[1]], plots[[2]], plots[[3]], plots[[4]])
В результате получается в четыре раза больше четвертого сюжета. Как правильно проиндексировать это, чтобы создать четыре предполагаемых графика?
(Впереди: причина того, что все ваши графики идентичны, связана с «ленивой» оценкой кода ggplot. См. Мой № 2 ниже, где я указываю, что data[,i]
оценивается, когда вы пытаетесь построить данные, и в этот момент i
4, последний проход в петле for
.)
Обычно предпочтительнее/рекомендуется использовать data.frame
s вместо матриц или векторов (как вы делаете здесь). Это дает немного больше силы и контроля.
data <- data.frame(a,b,c,d,time)
Кроме того, я предпочитаю lapply
for
-петлям и list
-ам по разным (некоторые субъективным) причинам. В конечном счете, проблема, с которой вы столкнулись, заключается в том, что ggplot2
лениво оценивает данные, поэтому plots
представляет собой список с четырьмя графиками, которые ссылаются на i
... и это реализуется, когда вы пытаетесь построить их все, и в этот момент i
4 (из последнего прохода через петлю). Одним из преимуществ использования lapply
является то, что ссылка на i
является только локальной (внутри anon-func) версией i
, которая сохраняется, как и следовало ожидать.
plots <- lapply(names(data)[1:4],
function(nm) ggplot(data, aes(x = time, y = .data[[nm]])) + geom_line())
gridExtra::grid.arrange(plots[[1]], plots[[2]])
Я также предпочитаю patchwork
gridExtra
, в основном потому, что он делает более настраиваемые макеты немного более интуитивно понятными, а также добавляет такие функции, как выравнивание по осям, общие легенды, общие заголовки и т. д. (ни одна из этих других функций здесь не демонстрируется).
library(patchwork)
plots[[1]] / plots[[2]] # same plot
plots[[1]] + plots[[2]] # side-by-side instead of top/bottom
(plots[[1]] + plots[[2]]) / (plots[[3]] + plots[[4]]) # grid
В конечном счете, однако, я предполагаю, что аспекты могут быть полезными и очень мощными. Для этого нам нужно преобразовать данные в «длинный формат», чтобы имена столбцов a
-b
фактически находились в одном столбце.
reshape2::melt(data, id.vars = "time") |>
ggplot(aes(time, value)) +
geom_line() +
facet_grid(variable ~ ., scales = "free_y")
Я предпочел независимые (бесплатные) y-шкалы, следовательно, scales = "free_y"
. Попробуйте без него, если хотите увидеть варианты. (Есть также scales = "free_x"
и scales = "free"
(оба).)
Чтобы увидеть, что я имею в виду под «длинным» форматом:
reshape2::melt(data, id.vars = "time")
# time variable value
# 1 1 a 1
# 2 2 a 2
# 3 3 a 3
# 4 4 a 4
# 5 1 b 5
# 6 2 b 6
# 7 3 b 7
# 8 4 b 8
# 9 1 c 9
# 10 2 c 10
# 11 3 c 11
# 12 4 c 12
# 13 1 d 13
# 14 2 d 14
# 15 3 d 15
# 16 4 d 16
Это также можно сделать с помощью tidyr::pivot_longer(data, -time)
, хотя имя variable
теперь name
. Для этого использования нет преимущества для reshape2::melt
или tidyr::pivot_longer
; в последнем есть возможности для значительно более сложного поворота, не относящегося к этим данным.
Данные
data <- structure(list(a = c(1, 2, 3, 4), b = c(5, 6, 7, 8), c = c(9, 10, 11, 12), d = c(13, 14, 15, 16), time = c(1, 2, 3, 4)), class = "data.frame", row.names = c(NA, -4L))