Графики рядом с ggplot2

207

Я хотел бы разместить два графика бок о бок, используя пакет ggplot2, т.е. сделать эквивалент par(mfrow=c(1,2)).

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

x <- rnorm(100)
eps <- rnorm(100,0,.2)
qplot(x,3*x+eps)
qplot(x,2*x+eps)

Нужно ли помещать их в один и тот же файл data.frame?

qplot(displ, hwy, data=mpg, facets = . ~ year) + geom_smooth()
  • 0
    Я думаю, что вы могли бы сделать это с решеткой. Является ли ggplot2 жестким требованием?
  • 7
    Нет. Но я уже потратил время на настройку qplots, так что это было так, как мне нравилось. :-) И я пытаюсь поиграться с ggplot.
Показать ещё 2 комментария
Теги:
ggplot2
visualization

10 ответов

347
Лучший ответ

Любые ggplots бок о бок (или n графиков на сетке)

Функция grid.arrange() в пакете gridExtra объединит несколько графиков; так вы ставите две бок о бок.

require(gridExtra)
plot1 <- qplot(1)
plot2 <- qplot(1)
grid.arrange(plot1, plot2, ncol=2)

Это полезно, когда два графика не основаны на одних и тех же данных, например, если вы хотите построить разные переменные без использования функции reshape().

Это будет отображать вывод как побочный эффект. Чтобы напечатать побочный эффект в файле, укажите драйвер устройства (например, pdf, png и т.д.), Например

pdf("foo.pdf")
grid.arrange(plot1, plot2)
dev.off()

или используйте arrangeGrob() в комбинации с ggsave(),

ggsave("foo.pdf", arrangeGrob(plot1, plot2))

Это эквивалент создания двух разных графиков с использованием par(mfrow = c(1,2)). Это не только экономит время на сбор данных, это необходимо, если вам нужны два разных графика.


Приложение: Использование граней

Границы полезны для создания похожих сюжетов для разных групп. Это указано ниже во многих ответах ниже, но я хочу подчеркнуть этот подход примерами, эквивалентными приведенным выше графикам.

mydata <- data.frame(myGroup = c('a', 'b'), myX = c(1,1))

qplot(data = mydata, 
    x = myX, 
    facets = ~myGroup)

ggplot(data = mydata) + 
    geom_bar(aes(myX)) + 
    facet_wrap(~myGroup)

Update

Функция plot_grid в cowplot стоит проверить как альтернативу grid.arrange. См. ответ по @claus-wilke ниже и эту виньетку для эквивалентного подхода; но функция позволяет более тонкие элементы управления по расположению и размеру на основе этой виньетки.

  • 2
    Когда я запускал ваш код с использованием объектов ggplot, sidebysideplot имеет значение null. Если вы хотите сохранить вывод в файл, используйте gridArrange. См. Stackoverflow.com/questions/17059099/…
  • 0
    @ Джим, спасибо, что указал на это. Я пересмотрел свой ответ. Дайте мне знать, если остались какие-либо вопросы.
Показать ещё 12 комментариев
60

Один недостаток решений на основе grid.arrange заключается в том, что они затрудняют маркировку графиков буквами (A, B и т.д.), как это требуется большинству журналов.

Я написал пакет cowplot, чтобы решить эту проблему (и несколько других), в частности функцию plot_grid():

library(cowplot)

iris1 <- ggplot(iris, aes(x = Species, y = Sepal.Length)) +
  geom_boxplot() + theme_bw()

iris2 <- ggplot(iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.7) + theme_bw() +
  theme(legend.position = c(0.8, 0.8))

plot_grid(iris1, iris2, labels = "AUTO")

Изображение 4323

Объект, возвращаемый plot_grid(), является другим объектом ggplot2, и вы можете сохранить его с помощью ggsave(), как обычно:

p <- plot_grid(iris1, iris2, labels = "AUTO")
ggsave("plot.pdf", p)

В качестве альтернативы вы можете использовать функцию cowplot save_plot(), которая представляет собой тонкую оболочку вокруг ggsave(), которая упрощает получение правильных размеров для комбинированных графиков, например:

p <- plot_grid(iris1, iris2, labels = "AUTO")
save_plot("plot.pdf", p, ncol = 2)

(Аргумент ncol = 2 сообщает save_plot(), что есть два графика бок о бок, а save_plot() делает сохраненное изображение вдвое большим.)

Более подробное описание того, как расположить графики в сетке, см. эту виньетку. Существует также виньетка, объясняющая, как сделать графики с общая легенда.

Одна из частых путаниц заключается в том, что пакет cowplot изменяет тему ggplot2 по умолчанию. Пакет ведет себя так, потому что он был первоначально написан для внутренних лабораторных целей, и мы никогда не используем тему по умолчанию. Если это вызывает проблемы, вы можете использовать один из следующих трех подходов для их работы:

1. Задайте тему вручную для каждого сюжета. Я считаю хорошей практикой всегда указывать конкретную тему для каждого сюжета, как и в + theme_bw() в приведенном выше примере. Если вы укажете конкретную тему, тема по умолчанию не имеет значения.

2. Верните тему по умолчанию обратно в ggplot2 по умолчанию. Вы можете сделать это с помощью одной строки кода:

theme_set(theme_gray())

3. Позвоните в функции cowplot без установки пакета. Вы также не можете вызывать library(cowplot) или require(cowplot) и вместо этого использовать функции cowplot, добавляя cowplot::. Например, приведенный выше пример с использованием темы ggplot2 по умолчанию станет следующим:

## Commented out, we don't call this
# library(cowplot)

iris1 <- ggplot(iris, aes(x = Species, y = Sepal.Length)) +
  geom_boxplot()

iris2 <- ggplot(iris, aes(x = Sepal.Length, fill = Species)) +
  geom_density(alpha = 0.7) +
  theme(legend.position = c(0.8, 0.8))

cowplot::plot_grid(iris1, iris2, labels = "AUTO")

Изображение 4324

  • 8
    Этот пакет cowplot действительно хорош! Я думаю, что это заслуживает большей популярности среди тех, кто любит использовать ggplot2. Спасибо! Только одно предложение, я думаю, было бы намного лучше, если бы была возможность добавить основной заголовок для комбинированных сюжетов.
  • 1
    Также я обнаружил одну маленькую проблему. Может быть, я не умею правильно пользоваться. Пожалуйста, скажите мне, если это так. Если я нанесу их на PDF-файл, он будет работать почти так же хорошо, за исключением того, что первая страница будет пустой страницей, а график появится на второй странице.
Показать ещё 8 комментариев
44

Вы можете использовать следующую функцию multiplot из кулинарная книга Winston Chang R

multiplot(plot1, plot2, cols=2)

multiplot <- function(..., plotlist=NULL, cols) {
    require(grid)

    # Make a list from the ... arguments and plotlist
    plots <- c(list(...), plotlist)

    numPlots = length(plots)

    # Make the panel
    plotCols = cols                          # Number of columns of plots
    plotRows = ceiling(numPlots/plotCols) # Number of rows needed, calculated from # of cols

    # Set up the page
    grid.newpage()
    pushViewport(viewport(layout = grid.layout(plotRows, plotCols)))
    vplayout <- function(x, y)
        viewport(layout.pos.row = x, layout.pos.col = y)

    # Make each plot, in the correct location
    for (i in 1:numPlots) {
        curRow = ceiling(i/plotCols)
        curCol = (i-1) %% plotCols + 1
        print(plots[[i]], vp = vplayout(curRow, curCol ))
    }

}
17

Да, важно, чтобы вы правильно упорядочили свои данные. Один из способов:

X <- data.frame(x=rep(x,2),
                y=c(3*x+eps, 2*x+eps),
                case=rep(c("first","second"), each=100))

qplot(x, y, data=X, facets = . ~ case) + geom_smooth()

Я уверен, что есть лучшие трюки в plyr или изменение формы - я все еще не совсем до скорости на всех этих мощных пакетах Хэдли.

15

Используя пакет reshape, вы можете сделать что-то вроде этого.

library(ggplot2)
wide <- data.frame(x = rnorm(100), eps = rnorm(100, 0, .2))
wide$first <- with(wide, 3 * x + eps)
wide$second <- with(wide, 2 * x + eps)
long <- melt(wide, id.vars = c("x", "eps"))
ggplot(long, aes(x = x, y = value)) + geom_smooth() + geom_point() + facet_grid(.~ variable)
9

Стивен Тернер разместил функцию arrange() в блоге Получение Генетики (см. сообщение для инструкций по применению); однако grid.arrange() рекомендуется, см. комментарий ниже)

vp.layout <- function(x, y) viewport(layout.pos.row=x, layout.pos.col=y)
arrange <- function(..., nrow=NULL, ncol=NULL, as.table=FALSE) {
 dots <- list(...)
 n <- length(dots)
 if(is.null(nrow) & is.null(ncol)) { nrow = floor(n/2) ; ncol = ceiling(n/nrow)}
 if(is.null(nrow)) { nrow = ceiling(n/ncol)}
 if(is.null(ncol)) { ncol = ceiling(n/nrow)}
        ## NOTE see n2mfrow in grDevices for possible alternative
grid.newpage()
pushViewport(viewport(layout=grid.layout(nrow,ncol) ) )
 ii.p <- 1
 for(ii.row in seq(1, nrow)){
 ii.table.row <- ii.row 
 if(as.table) {ii.table.row <- nrow - ii.table.row + 1}
  for(ii.col in seq(1, ncol)){
   ii.table <- ii.p
   if(ii.p > n) break
   print(dots[[ii.table]], vp=vp.layout(ii.table.row, ii.col))
   ii.p <- ii.p + 1
  }
 }
}
  • 8
    по сути, это очень устаревшая версия grid.arrange (жаль, что я не размещал ее в списках рассылки в то время, когда нет возможности обновить эти онлайн-ресурсы), лучше выбрать упакованную версию, если вы спросите меня
1

ggplot2 основан на сетке графики, которые предоставляют другую систему для размещения графиков на странице. Команда par(mfrow...) не имеет прямого эквивалента, поскольку объекты сетки (называемые grobs) не обязательно выводятся немедленно, но могут быть сохранены и обработаны как обычные объекты R перед преобразованием в графический вывод. Это обеспечивает большую гибкость, чем рисование этой модели базовой графики, но стратегия обязательно немного отличается.

Я написал grid.arrange(), чтобы обеспечить простой интерфейс как можно ближе к par(mfrow). В простейшей форме код будет выглядеть так:

library(ggplot2)
x <- rnorm(100)
eps <- rnorm(100,0,.2)
p1 <- qplot(x,3*x+eps)
p2 <- qplot(x,2*x+eps)

library(gridExtra)
grid.arrange(p1, p2, ncol = 2)

Изображение 4326

Дополнительные параметры подробно описаны в этой виньетки.

Одна общая жалоба заключается в том, что графики не обязательно выровнены, например. когда они имеют осевые метки разного размера, но это по дизайну: grid.arrange не делает попыток для особых объектов ggplot2, и относится к ним одинаково к другим гнетам (например, к сеткам решетки). Он просто помещает глыбы в прямоугольную компоновку.

Для специального случая объектов ggplot2 я написал еще одну функцию ggarrange с аналогичным интерфейсом, который пытается выровнять панели сюжетов (включая граненные участки) и пытается оценить пропорции, если они определены пользователем.

library(egg)
ggarrange(p1, p2, ncol = 2)

Обе функции совместимы с ggsave(). Для общего обзора различных вариантов и некоторого исторического контекста эта виньетка предлагает дополнительную информацию.

1

Пакет cowplot дает вам хороший способ сделать это в соответствии с публикацией.

x <- rnorm(100)
eps <- rnorm(100,0,.2)
A = qplot(x,3*x+eps, geom = c("point", "smooth"))+theme_gray()
B = qplot(x,2*x+eps, geom = c("point", "smooth"))+theme_gray()
cowplot::plot_grid(A, B, labels = c("A", "B"), align = "v")

Изображение 4325

  • 2
    См. Также более подробный ответ и обоснование авторов пакета выше stackoverflow.com/a/31223588/199217
1

Вышеупомянутые решения могут быть неэффективными, если вы хотите построить несколько графиков ggplot с помощью цикла (например, как здесь задано: Создание нескольких графиков в ggplot с разными значениями оси Y с использованием loop), который является желательным шагом при анализе неизвестных (или больших) наборов данных (например, когда вы хотите построить график всех переменных в наборе данных).

В приведенном ниже коде показано, как это сделать, используя вышеупомянутый "multipot()", источник которого находится здесь: http://www.cookbook-r.com/Graphs/Multiple_graphs_on_one_page_(ggplot2):

plotAllCounts <- function (dt){   
  plots <- list();
  for(i in 1:ncol(dt)) {
    strX = names(dt)[i]
    print(sprintf("%i: strX = %s", i, strX))
    plots[[i]] <- ggplot(dt) + xlab(strX) +
      geom_point(aes_string(strX),stat="count")
  }

  columnsToPlot <- floor(sqrt(ncol(dt)))
  multiplot(plotlist = plots, cols = columnsToPlot)
}

Теперь запустите функцию - чтобы получить счетчики для всех переменных, напечатанных с помощью ggplot на одной странице

dt = ggplot2::diamonds
plotAllCounts(dt)

Следует отметить, что:
 используя aes(get(strX)), который вы обычно используете в циклах при работе с ggplot, в приведенном выше коде вместо aes_string(strX) НЕ будет рисовать нужные графики. Вместо этого он будет строить последний график много раз. Я не понял, почему, возможно, придется делать aes и aes_string, вызываемые в ggplot.

В противном случае, надеюсь, вы найдете полезную функцию.

1

Используя tidyverse

x <- rnorm(100)
eps <- rnorm(100,0,.2)
df <- as.data.frame(cbind(x, eps)) %>% 
  mutate(p1 = 3*x+eps, p2 = 2*x+eps) %>% 
  tidyr::gather("plot", "value", 3:4) %>% 
  ggplot(aes(x = x , y = value))+ geom_point()+geom_smooth()+facet_wrap(~plot, ncol =2)

df
  • 1
    Вместо использования as.data.frame(cbind()) , просто используйте data.frame()
  • 1
    @ Axeman Спасибо. Я буду делать с этого момента

Ещё вопросы

Сообщество Overcoder
Наверх
Меню