美文网首页R ggplotR语言ggplot
R 数据可视化 —— 图形排列之 cowplot

R 数据可视化 —— 图形排列之 cowplot

作者: 名本无名 | 来源:发表于2021-06-26 13:24 被阅读0次

    前言

    cowplot 包是 ggplot 的一个简单插件,可以对多个图形进行排列和对齐,来生成复杂的出版级别的图片,还提供了一些主题和帮助函数。

    安装

    install.packages("cowplot")
    # 安装最新的开发版本
    remotes::install_github("wilkelab/cowplot")
    

    导入相关包

    library(tidyverse)
    library(cowplot)
    

    示例

    1. 主题

    ggplot2 的默认主题是这样的

    ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species)) + 
      geom_point()
    

    我们更换上不同的 cowplot 主题,来看看效果。例如,经典的 cowplot 主题

    ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species)) + 
      geom_point() +
      theme_cowplot(font_size = 12)
    

    网格最小化主题

    ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species)) + 
      geom_point() +
      theme_minimal_grid(font_size = 12)
    

    水平网格线最小化主题

    ggplot(iris, aes(Sepal.Length, fill = Species)) + 
      geom_density(alpha = 0.5) +
      scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
      theme_minimal_hgrid(font_size = 12)
    

    2. 图形对齐

    cowplot 的图形对齐与图形排列是相互独立的,例如,对于如下两个图形

    p1 <- ggplot(mtcars, aes(disp, mpg)) + 
      geom_point(colour = 'red')
    p2 <- ggplot(mtcars, aes(disp, hp)) + 
      geom_point(colour = 'blue')
    

    虽然它们的横坐标是一样的,但是两个图形的宽度还是有细微的差别,第一幅图更宽。这是由于第二幅图的 y 轴标签更大,占用了更宽的空间,我们可以使用 align_plots 来对齐两幅图

    aligned <- align_plots(p1, p2, align = "v")
    ggdraw(aligned[[1]])
    ggdraw(aligned[[2]])
    

    align = "v" 表示竖直方向对齐,align = "h" 表示水平方向对齐

    我们可以使用 plot_grid() 函数来同时完成对齐和重排两个步骤,上面的代码可以写成

    plot_grid(p1, p2, ncol = 1, align = "v")
    

    ncol 参数用于指定列数

    这是在我们对齐的轴完全一样的情况下,如果我们图形的轴不一样,会怎么样呢

    p1 <- ggplot(mtcars, aes(disp, mpg)) + 
      geom_point() +
      theme_minimal_grid(14) + 
      panel_border(color = "black")
    
    p2 <- ggplot(mtcars, aes(factor(vs), colour = factor(vs))) + 
      geom_bar() + 
      facet_wrap(~am) +
      scale_y_continuous(expand = expansion(mult = c(0, 0.1))) +
      theme_minimal_hgrid(12) +
      panel_border(color = "black") +
      theme(strip.background = element_rect(fill = "gray80"))
    
    plot_grid(p1, p2, align = "h", rel_widths = c(1, 1.3))
    

    同时抛出了警告信息

    Warning message:
    Graphs cannot be horizontally aligned unless the axis parameter is set. Placing graphs unaligned.
    

    告诉我们,图形并没有对齐,需要设置 axis 参数才能生效。

    rel_widths 参数用于设置两张图片的宽度比例

    例如,我们可以让其按照底部的轴线对齐

    plot_grid(p1, p2, align = "h", axis = "b", rel_widths = c(1, 1.3))
    

    或者按照底部和顶部轴线对齐

    plot_grid(p1, p2, align = "h", axis = "bt", rel_widths = c(1, 1.3))
    

    axis 参数可以是 t(top)r(right)b(bottom)l(left) 的任意组合

    另一个例子,在用于相同数量的元素的图中,我们也可以设置按 axis 对齐

    city_mpg <- mpg %>%
      mutate(class = fct_lump(class, 4, other_level = "other")) %>%
      group_by(class) %>%
      summarize(
        mean_mpg = mean(cty),
        count = n()
      ) %>% mutate(
        class = fct_reorder(class, count)
      )
    
    
    p1 <- ggplot(city_mpg, aes(class, count, fill = class)) + 
      geom_col(show.legend = FALSE) + 
      ylim(0, 65) + 
      coord_flip()
    
    p2 <- ggplot(city_mpg, aes(mean_mpg, count)) + 
      geom_point()
    
    plot_grid(p1, p2, ncol = 1, align = 'v')
    

    由于两幅图的 y 轴标签大小不一致,导致第二幅图的 y 轴标题与标签之间间距较大,我们可以设置 axis = "l" 按左侧轴对齐

    plot_grid(p1, p2, ncol = 1, align = 'v', axis = 'l')
    

    最后,我们来介绍何如将两幅图重叠。我们先绘制一张条形图,用于展示每种类型汽车的数量

    city_mpg <- city_mpg %>%
      mutate(class = fct_reorder(class, -mean_mpg))
    
    p1 <- ggplot(city_mpg, aes(class, count)) +
      geom_col(fill = "#6297E770") + 
      scale_y_continuous(
        expand = expansion(mult = c(0, 0.05)),
        position = "right"
      ) +
      theme_minimal_hgrid(11, rel_small = 1) +
      theme(
        panel.grid.major = element_line(color = "#6297E770"),
        axis.line.x = element_blank(),
        axis.text.x = element_blank(),
        axis.title.x = element_blank(),
        axis.ticks = element_blank(),
        axis.ticks.length = grid::unit(0, "pt"),
        axis.text.y = element_text(color = "#6297E7"),
        axis.title.y = element_text(color = "#6297E7")
      )
    p1
    

    再绘制每种类型的汽车均值

    p2 <- ggplot(city_mpg, aes(class, mean_mpg)) + 
      geom_point(size = 3, color = "#D5442D") + 
      scale_y_continuous(limits = c(10, 21)) +
      theme_half_open(11, rel_small = 1) +
      theme(
        axis.ticks.y = element_line(color = "#BB2D05"),
        axis.text.y = element_text(color = "#BB2D05"),
        axis.title.y = element_text(color = "#BB2D05"),
        axis.line.y = element_line(color = "#BB2D05")
      )
    p2
    
    aligned_plots <- align_plots(p1, p2, align="hv", axis="tblr")
    ggdraw(aligned_plots[[1]]) + draw_plot(aligned_plots[[2]])
    

    3. 图上绘制图形

    cowplot 包提供了用于在图上绘制的函数,这些函数可以添加任意的图形、注释或背景,可以在其他图上放置图形,或者组合不同的图形系统,如 ggplot2gridlatticebase,并返回一个 ggplot2 对象。

    3.1 添加注释

    例如,对于下面这张图

    p <- ggplot(mpg, aes(displ, cty)) +
      geom_point() +
      theme_minimal_grid(12)
    p
    

    我们使用 ggdraw() 将图封装成一个绘图环境,然后使用 draw*() 函数添加注释,例如,添加一个水印

    ggdraw(p) + 
      draw_label("Watermark", color = "#C0A0A0", size = 50, angle = 45)
    

    ggdraw(p) 用于捕获绘图快照,并将该绘图转换为图片,然后将该图片绘制在一个新的 ggplot2 画布中。

    draw_* 函数是常用的几何对象的封装,上面的代码也可以用 geom_text() 来替换 draw_label()

    ggdraw(p) + 
      geom_text(
        data = data.frame(x = 0.5, y = 0.5, label = "Watermark"),
        aes(x, y, label = label),
        hjust = 0.5, vjust = 0.5, angle = 45, size = 50/.pt,
        color = "#C0A0A0",
        inherit.aes = FALSE
      )
    

    由于 ggplot2 的字体大小是 mm,需要除以 .pt 进行转换,而 draw_label 会在内部进行转换。

    我们可以将 ggdraw() 当做 ggplot2 一个绘图对象,可以在其后修改主题

    ggdraw(p) + 
      draw_label("Watermark", color = "#C0A0A0", size = 50, angle = 45) +
      theme(
        plot.background = element_rect(fill = "cornsilk", color = NA)
      )
    

    如果要保存图片,可以使用 ggsave() 函数,也可以使用 cowplot 提供的 save_plot() 函数,可以自动调整图形为合适的大小

    如果要让水印在画布的底层,只需先使用 ggdraw 绘制一个空图层,然后调整绘制的顺序即可

    ggdraw() + 
      draw_label("Watermark", color = "#C0A0A0", size = 50, angle = 45) +
      draw_plot(p)
    

    上面的例子需要保证图片的背景是完全透明的,例如,我们将散点图的背景设置为 theme_classic() 水印将会被遮盖

    ggdraw() + 
      draw_label("Watermark", color = "#C0A0A0", size = 50, angle = 45) +
      draw_plot(
        p + theme_classic()
      )
    

    可以将主题设置为 theme_half_open()

    ggdraw() + 
      draw_label("Watermark", color = "#C0A0A0", size = 50, angle = 45) +
      draw_plot(
        p + theme_half_open()
      )
    

    3.2 插入图形

    draw_plot() 函数允许我们将任意大小的图形放置在画布的任意位置,可以很容易的将一个图形嵌入到另一个图形中,构建组合图形

    例如,在一个大图中,添加一个小图

    inset <- ggplot(mpg, aes(drv)) + 
      geom_bar(fill = "#fb8072", alpha = 0.7) + 
      scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
      theme_minimal_hgrid(11)
    
    ggdraw(p + theme_half_open(12)) +
      draw_plot(inset, .45, .45, .5, .5) +
      draw_plot_label(
        c("A", "B"),
        c(0, 0.45),
        c(1, 0.95),
        size = 12
      )
    

    组合 ggplot2base 图形

    # 需要 gridGraphics 包
    # install.packages("gridGraphics")
    # 设置为表达式的形式
    inset <- ~{
      counts <- table(mpg$drv)
      par(
        cex = 0.8,
        mar = c(3, 3, 1, 1),
        mgp = c(2, 1, 0)
      )
      barplot(counts, xlab = "drv", ylab = "count", col = "#80b1d3")
    }
    
    ggdraw(p + theme_half_open(12)) +
      draw_plot(inset, .45, .45, .5, .5) +
      draw_plot_label(
        c("A", "B"),
        c(0, 0.45),
        c(1, 0.95),
        size = 12
      )
    

    3.3 绝对位置

    默认情况下,ggdraw() 函数使用的是相对位置,xy 轴都是在 0-1 之间。

    如果要通过绝对位置进行定位,可以使用 grid 图形系统,并搭配 draw_grob() 绘制函数

    例如,我们在左上角 1 英寸处添加一个 1 英寸的正方形

    library(grid)
    
    rect <- rectGrob(
      x = unit(1, "in"),
      y = unit(1, "npc") - unit(1, "in"),
      width = unit(1, "in"),
      height = unit(1, "in"),
      hjust = 0, vjust = 1,
      gp = gpar(fill = "skyblue2", alpha = 0.5)
    )
    
    ggdraw(p) +
      draw_grob(rect)
    

    我们改变图形的大小,但是正方形的大小和位置不会变


    3.4 组合静态图片

    draw_image() 可以在绘图中添加静态图片,需要先安装 magick 包。

    例如,添加背景图片

    library(magick)
    
    library(magick)
    
    img <- system.file("extdata", "cow.jpg", package = "cowplot") %>%
      image_read() %>%
      image_resize("570x380") %>%
      image_colorize(35, "white")
    
    p <- ggplot(mpg, aes(displ, fill = class)) +
      geom_density(alpha = 0.7) +
      scale_fill_grey() +
      coord_cartesian(expand = FALSE) +
      theme_minimal_hgrid(11, color = "grey30")
    
    ggdraw() + 
      draw_image(img) + 
      draw_plot(p)
    

    或者,添加 logo

    logo_file <- system.file("extdata", "logo.png", package = "cowplot")
    
    ggdraw() + 
      draw_plot(p) +
      draw_image(
        logo_file, x = 1, y = 1, hjust = 1, vjust = 1, halign = 1, valign = 1,
        width = 0.15
      )
    

    4. 混合不同绘图框架

    cowplot 绘图函数(ggdraw()draw_plot()plot_grid())不仅支持 ggplot2 绘图系统,还支持 base 绘图系统,但是要先安装 gridGraphics

    例如,我们用 ggdraw() 绘制基础图形,然后使用 ggplot2 主题

    p1 <- function() {
      par(
        mar = c(3, 3, 1, 1),
        mgp = c(2, 1, 0)
      )
      boxplot(mpg ~ cyl, xlab = "cyl", 
        ylab = "mpg", data = mtcars, 
        col = c("red", "blue", "green")
      )
    }
    
    ggdraw(p1) +
      theme(plot.background = element_rect(fill = "cornsilk"))
    

    添加 logo

    logo_file <- system.file("extdata", "logo.png", package = "cowplot")
    
    ggdraw() + 
      draw_image(
        logo_file,
        x = 1, width = 0.1,
        hjust = 1, halign = 1, valign = 0
      ) +
      draw_plot(p1)
    

    使用 plot_grid() 函数可以将 baseggplot2 图形以网格的布局绘制在一起

    p2 <- ggplot(data = mtcars, aes(factor(cyl), mpg)) + 
      geom_boxplot()
    plot_grid(p1, p2)
    

    base 绘图可以封装为函数的形式,并将相应的图形返回,作为一种被记录的绘图。或者封装为表达式的形式,如 3.2 例子。

    例如,我们绘制一个基础图形

    par(mar = c(3, 3, 1, 1), mgp = c(2, 1, 0))
    boxplot(mpg ~ cyl, xlab = "cyl", ylab = "mpg", data = mtcars)
    

    这个图形会被 recordPlot() 函数记录,然后,可以使用 ggdraw() 函数对被记录的图形进行绘制

    p1_recorded <- recordPlot()
    ggdraw(p1_recorded)
    

    我们也可以将绘图代码放在大括号中,并包裹成表达式

    p1_formula <- ~{
      par(
        mar = c(3, 3, 1, 1), 
        mgp = c(2, 1, 0)
      )
      boxplot(mpg ~ cyl, xlab = "cyl", ylab = "mpg", data = mtcars)
    }
    ggdraw(p1_formula)
    

    还支持 lattice 图形和 grid 图形对象

    # base R
    p1 <- ~{
      par(
        mar = c(3, 3, 1, 1), 
        mgp = c(2, 1, 0)
      )
      boxplot(mpg ~ cyl, xlab = "cyl", ylab = "mpg", data = mtcars)
    }
    
    # ggplot2
    p2 <- ggplot(data = mtcars, aes(factor(cyl), mpg)) + geom_boxplot()
    
    # lattice
    library(lattice)
    p3 <- bwplot(~mpg | cyl, data = mtcars)
    
    # 圆形对象
    library(grid)
    p4 <- circleGrob(r = 0.3, gp = gpar(fill = "skyblue"))
    
    # 组合
    plot_grid(p1, p2, p3, p4, rel_heights = c(.6, 1), labels = "auto")
    

    只要能返回 grid 对象的其他绘图包,都可以

    library(VennDiagram)
    
    p_venn <- draw.pairwise.venn(
      100, 70, 30,
      c("First", "Second"),
      fill = c("light blue", "pink"),
      alpha = c(0.7, 0.7),
      ind = FALSE
    )
    
    # 绘图韦恩图并添加矩形框和边距
    ggdraw(p_venn) +
      theme(
        plot.background = element_rect(fill = NA),
        plot.margin = margin(12, 12, 12, 12)
      )
    

    5. 网格布局

    5.1 基本使用

    plot_grid 用于将图形按照网格布局进行排列,它是基于 ggdraw()draw_*() 函数来绘制图形,图形对齐是通过 align_plots() 函数。

    plot_grid 可以很容易地对多个图形就行排列并添加标签

    p1 <- ggplot(mtcars, aes(disp, mpg)) + 
      geom_point(colour = "red")
    p2 <- ggplot(mtcars, aes(qsec, mpg)) +
      geom_point(colour = "blue")
    
    plot_grid(p1, p2, labels = c('A', 'B'))
    

    如果将 labels 参数设置为 AUTO 或者 auto,会自动添加大写或小写的标签

    plot_grid(p1, p2, labels = "AUTO")
    
    plot_grid(p1, p2, labels = "auto")
    

    plot_grid 默认不会将图形对齐

    p3 <- p1 + 
      # 使用更大刻度标签
      theme(axis.text.x = element_text(size = 14, angle = 90, vjust = 0.5))
    
    # 没有对齐
    plot_grid(p3, p2, labels = "AUTO")
    

    水平对齐

    plot_grid(p3, p2, labels = "AUTO", align = "h")
    

    5.2 图形微调

    label_size 用于控制标签的大小,默认为 14

    plot_grid(p1, p2, labels = "AUTO", label_size = 12)
    

    对字体进行调整

    plot_grid(
      p1, p2,
      labels = "AUTO", 
      label_fontfamily = "serif",
      label_fontface = "plain",
      label_colour = "blue"
    )
    

    可以使用 label_xlabel_y 参数控制标签的位置,hjustvjust 参数用于控制对齐方式

    plot_grid(
      p1, p2,
      labels = "AUTO",
      label_size = 12,
      label_x = 0, label_y = 0,
      hjust = -0.5, vjust = -0.5
    )
    

    当然,对于多个图形,可以传递向量值的方式来分别控制每个图形

    行数和列数可以使用 nrowncol 参数来控制

    plot_grid(
      p1, p2,
      labels = "AUTO", ncol = 1
    )
    

    可以使用 NULL 来表示网格中该位置为空白,如果设置了自动设置标签,也会为空白图添加上标签

    plot_grid(
      p1, NULL, NULL, p2,
      labels = "AUTO", ncol = 2
    )
    

    rel_widthsrel_heights 参数用于设置图形的相对宽度和高度

    plot_grid(p1, p2, labels = "AUTO", rel_widths = c(1, 2))
    

    5.3 嵌套绘图

    plot_grid() 可以嵌套使用,生成更加复杂的图形布局,例如

    bottom_row <- plot_grid(p1, p2, labels = c('B', 'C'), label_size = 12)
    p3 <- ggplot(mtcars, aes(x = qsec, y = disp, colour = factor(gear))) + 
      geom_point(show.legend = FALSE) + facet_wrap(~gear)
    
    plot_grid(p3, bottom_row, labels = c('A', ''), label_size = 12, ncol = 1)
    

    这种情况下,图形的对齐将会变得比较困难,我们可以使用 align_plots() 函数来显式的对齐图形

    # 首先,将顶行(p3)与底行第一个图形(p1)按左对齐
    plots <- align_plots(p3, p1, align = 'v', axis = 'l')
    # 底行
    bottom_row <- plot_grid(plots[[2]], p2, labels = c('B', 'C'), label_size = 12)
    
    # 将对齐后的 p3 与底行进行组合
    plot_grid(plots[[1]], bottom_row, labels = c('A', ''), label_size = 12, ncol = 1)
    

    5.4 组合标题

    如果我们要为组合图形添加一个跨越整个图形的标题,需要将标题也作为一个图形对象,并使用 plot_grid 来组合标题图形和绘图区域图形

    p1 <- ggplot(mtcars, aes(x = disp, y = mpg)) + 
      geom_point(colour = "blue") + 
      theme_half_open(12) + 
      background_grid(minor = 'none')
    
    p2 <- ggplot(mtcars, aes(x = hp, y = mpg)) + 
      geom_point(colour = "green") + 
      theme_half_open(12) + 
      background_grid(minor = 'none')
    
    plot_row <- plot_grid(p1, p2)
    
    # 添加标题
    title <- ggdraw() + 
      draw_label(
        "Miles per gallon decline with displacement and horsepower",
        fontface = 'bold',
        x = 0,
        hjust = 0
      ) +
      theme(
        # 添加边距,使标题左对齐边缘
        plot.margin = margin(0, 0, 0, 7)
      )
    plot_grid(
      title, plot_row,
      ncol = 1,
      # 控制标题与图形的高度比例
      rel_heights = c(0.1, 1)
    )
    

    6. 共享图例

    下面,我们介绍如何为组合图形设置图例,假设我们有如下三个图形

    dsamp <- diamonds[sample(nrow(diamonds), 1000), ]
    
    # 绘图函数
    plot_diamonds <- function(xaes) {
      xaes <- enquo(xaes)
      ggplot(dsamp, aes(!!xaes, price, color = clarity)) +
        geom_point() +
        theme_half_open(12) +
        # 设置左右边距为 0
        theme(plot.margin = margin(6, 0, 6, 0))
    }
    
    # 添加图形
    p1 <- plot_diamonds(carat)
    p2 <- plot_diamonds(depth) + ylab(NULL)
    p3 <- plot_diamonds(color) + ylab(NULL)
    
    # 将图形排列成一行
    prow <- plot_grid(
      p1 + theme(legend.position="none"),
      p2 + theme(legend.position="none"),
      p3 + theme(legend.position="none"),
      align = 'vh',
      labels = c("A", "B", "C"),
      hjust = -1,
      nrow = 1
    )
    prow
    

    手动添加图例

    legend <- get_legend(
      # 为图例留出足够空间
      p1 + theme(legend.box.margin = margin(0, 0, 0, 12))
    )
    
    # 将图例和之前的图形进行组合,并设置宽度比例
    plot_grid(prow, legend, rel_widths = c(3, .4))
    

    添加水平图例

    # 获取水平布局的图例
    legend_b <- get_legend(
      p1 + 
        guides(color = guide_legend(nrow = 1)) +
        theme(legend.position = "bottom")
    )
    
    # 组合图例之前的图形,并设置高度比例
    plot_grid(prow, legend_b, ncol = 1, rel_heights = c(1, .1))
    

    在图形中间添加图例

    # 将所有图形放置在同一行,并在 B 和 C 之间留出空间
    prow <- plot_grid(
      p1 + theme(legend.position="none"),
      p2 + theme(legend.position="none"),
      NULL,
      p3 + theme(legend.position="none"),
      align = 'vh',
      labels = c("A", "B", "", "C"),
      hjust = -1,
      nrow = 1,
      rel_widths = c(1, 1, .3, 1)
    )
    
    # 添加图例
    prow + draw_grob(legend, 2/3.3, 0, .3/3.3, 1)
    

    下面再来一个更复杂的例子

    # plot 1
    p1 <- ggplot(iris, aes(Sepal.Length, Sepal.Width, color = Species)) + 
      geom_point() + 
      stat_smooth(method = "lm") +
      facet_grid(. ~ Species) +
      theme_half_open(12) +
      background_grid(major = 'y', minor = "none") + 
      panel_border() + 
      theme(legend.position = "none")
    
    # plot 2
    p2 <- ggplot(iris, aes(Sepal.Length, fill = Species)) +
      geom_density(alpha = .7) + 
      scale_y_continuous(expand = expansion(mult = c(0, 0.05))) +
      theme_half_open(12) +
      theme(legend.justification = "top")
    p2a <- p2 + theme(legend.position = "none")
    
    # plot 3
    p3 <- ggplot(iris, aes(Sepal.Width, fill = Species)) +
      geom_density(alpha = .7) + 
      scale_y_continuous(expand = c(0, 0)) +
      theme_half_open(12) +
      theme(legend.position = "none")
    
    # legend
    legend <- get_legend(p2)
    
    # 所有图形竖直对齐
    plots <- align_plots(p1, p2a, p3, align = 'v', axis = 'l')
    
    # 将后面两张图以及图例组合在一起,并放置在第二行
    bottom_row <- plot_grid(
      plots[[2]], plots[[3]], legend,
      labels = c("B", "C"),
      rel_widths = c(1, 1, .3),
      nrow = 1
    )
    # 组合所有图形
    plot_grid(plots[[1]], bottom_row, labels = c("A"), ncol = 1)
    

    相关文章

      网友评论

        本文标题:R 数据可视化 —— 图形排列之 cowplot

        本文链接:https://www.haomeiwen.com/subject/rdroyltx.html