7  ggplot2的其他元素-解释性图表

附录 C 有对主题元素的详细介绍。

7.1 Labels-标签

通过使用labs()函数,可以为图表添加标题、子标题、坐标轴标签和图例标题。

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class)) +
  labs(
    x = "Engine displacement (L)", # x轴标签
    y = "Highway fuel economy (mpg)", # y轴标签
    color = "Car type", # 图例标题
    title = "Fuel efficiency generally decreases with engine size", # 主标题
    subtitle = "Two seaters (sports cars) are an exception because of their light weight", # 子标题
    caption = "Data from fueleconomy.gov" # 图表说明
  )

除以上常规的标签外,labs()函数还支持以数学公式的形式代替普通文本标签,只需将引号替换为quote()即可,具体语法可参看 ?plotmath

df <- tibble(
  x = 1:10,
  y = cumsum(x^2)
)

ggplot(df, aes(x, y)) +
  geom_point() +
  labs(
    x = quote(x[i]),
    y = quote(sum(x[i]^2, i == 1, n))
  )

7.2 Annotations-注释

小节 7.1 中的标签外,对个别重点关注的观测值(组)也很有用。实现这个注释功能的基础函数时geom_text()

geom_text()geom_point()类似,但增加了一个美学属性label,用于在图形中条件文本标签。

注释有两种实现的方法:

7.2.1 第一种实现注释的方法-建立专门的数据框用作标注数据框

例如,我们提取每种驱动类型中发动机排量最大的车型信息:

# define annotate info dataframe
annotate_info <- mpg |>
  group_by(drv) |>
  arrange(desc(displ)) |>
  slice_head(n = 1) |>
  mutate(
    drive_type = case_when(
      drv == "f" ~ "front-wheel drive",
      drv == "r" ~ "rear-wheel drive",
      drv == "4" ~ "4-wheel dive"
    )
  ) |>
  select(displ, hwy, drive_type)
annotate_info
# A tibble: 3 × 4
# Groups:   drv [3]
  drv   displ   hwy drive_type       
  <chr> <dbl> <int> <chr>            
1 4       6.5    17 4-wheel dive     
2 f       5.3    25 front-wheel drive
3 r       7      24 rear-wheel drive 
# use annotate_info to annotate the data
ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point(alpha = 0.3) +
  geom_smooth(se = F) +
  geom_text(
    data = annotate_info,
    aes(x = displ, y = hwy, label = drive_type),
    fontface = "bold",
    size = 5,
    hjust = "right", # 水平位置调整
    vjust = "bottom" # 垂直位置调整
  ) +
  theme_sub_legend(position = "none")

上图中的注释和数据点存在重叠,影响了图形的阅读,此时可以使用ggrepel包的geom_label_repel()自动调整位置:

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point(alpha = 0.3) +
  geom_smooth(method = "loess", se = FALSE) +
  ggrepel::geom_label_repel(
    data = annotate_info,
    aes(x = displ, y = hwy, label = drive_type),
    fontface = "bold",
    size = 5,
    nudge_y = 2 # 垂直偏移
  ) +
  theme(legend.position = "none")

结合geom_text_repel()和特殊标记则可以突出异常点:

potential_outliers <- mpg |>
  filter(hwy > 40 | (hwy > 20 & displ > 5))

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point() +
  ggrepel::geom_text_repel(data = potential_outliers, aes(label = model)) +
  geom_point(
    data = potential_outliers,
    color = "red",
    size = 3,
    shape = "circle open" # 空心红圈标记
  )

7.2.2 第二种实现注释的方法-使用annotate()直接添加注释

annotate()函数允许直接在图形上添加注释,而无需创建单独的数据框。这种方法更适合添加少量、独立的标注元素,例如添加一段文本到图形的特定位置。

trend_text <- "Larger engine sizes tend to have lower fuel economy." |>
  str_wrap(width = 30) # 自动换行

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point() +
  annotate(
    geom = "label",
    x = 3.5,
    y = 38,
    label = trend_text,
    hjust = "left",
    color = "red"
  ) +
  annotate(
    geom = "segment",
    x = 3,
    y = 35,
    xend = 5,
    yend = 25,
    color = "red",
    arrow = arrow(type = "closed")
  )

其他标注特殊点的方法还包括:

  • 参考线:使用geom_hline()/geom_vline()。一般会将参考线的linetype设为虚线,并将color设为灰色或白色以减少视觉干扰,同时将其绘制在主数据层的下方。
  • 矩形标记:使用geom_rect()ggforce::geom_mark_hull()。矩形的边缘使用xmin, xmax, ymin, ymax参数定义。
  • 箭头指示:使用geom_segment(arrow = arrow())。使用xy参数定义箭头的起点,使用xendyend参数定义箭头的终点。

注释是传达可视化图表主要观点和有趣特征的强大工具,在调整注释的位置时要保持十足的耐心)!

7.3 标度

如果想要详细了解标度,还是需要阅读ggplot2官方文档。此外,ggplot2tor提供了详细的标度信息说明供查阅参考。

标度是什么?标度就是用来调整数据映射的图形属性,换句话说,标度控制了美学映射(aes())的视觉表现形式。

先来看一个例子:

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class))

上图已经很美观,但其实是因为ggplot2设置了一些默认的标度缺省条件,上图代码的完整版应该如下:

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class)) +
  # 以下为默认标度
  scale_x_continuous() +
  scale_y_continuous() +
  scale_color_discrete()

坐标轴和图例使用相同的图形属性

7.3.1 丰富的标度体系

标度函数表

标度函数均由_分割的三部分构成:scale_美学对象_标度名,每个标度函数内都有丰富的参数系统。

  • 美学对象名:例如,x, y, color, fill, shape, size, alpha, linetype等。
  • 标度名:例如,continuous(连续), discrete(离散), manual, gradient, gradient2, gradientn, datetime等。
scale_color_manual(
  palette = funcion(),
  limits = NULL,
  name = waiver(),
  labels = waiver(),
  breaks = waiver(),
  minor_breaks = waiver(),
  values = waiver(),
  ...
)
  • 参数name:设定坐标/图例的名字,不要名字可以name = NULL

  • 参数limits:设定坐标/图例的范围区间,

    • 连续型c(m, n)
    • 离散型c("a", "b", "b")
  • 参数labels: 控制显示在坐标/图例上的值(元素)

  • 参数breaks:设置坐标和图例的间隔标签:

    • 一般的,内置函数会自动完成这一操作
    • 也可人工制定一个字符型向量,与breaks提供的字符向量一一对应
    • 也可是函数,将breaks提供的字符型向量当作函数的输入
    • NULL,即去掉标签。
  • 参数values:设置颜色、形状等的视觉属性值

    • 要么与数值的顺序一致
    • 要么与breaks提供的字符型向量长度一致
    • 要么,用命名向量`c(“数据标签” = “视觉属性”)提供
  • 参数expand, 控制参数溢出量。

  • 参数range, 设置尺寸大小范围,比如针对点的相对大小。

7.3.2 坐标轴刻度和图例键

坐标轴和图例统称为引导元素。坐标轴用于 x 轴和 y 轴的美学映射;图例则用于其他所有情况。

影响坐标轴刻度和图例键外观的两个主要参数是: breakslabelsbreaks 控制刻度线的位置或与图例相关联的的数值,labels 则控制每个刻度线或图例对应的文本标签。

  • breaks 最常见的用途是覆盖默认。breaks 的另一个用途是在数据点相对较少时,用于精确标注观测值的位置。

  • labels 参数与 scales 包中的标签函数结合使用时,也可用于将数字格式化为货币、百分比等。

  • 利用 breakslabels 来控制图例的显示样式。对于分类变量的离散scalelabels 可以是一个命名列表,其中包含现有层级名称及其对应的期望标签。

我们具体看几个例子:

ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point() +
  scale_y_continuous(breaks = seq(15, 40, by = 5))

ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  scale_color_discrete(labels = c("4" = "4-wheel", "f" = "front", "r" = "rear"))

# 格式化坐标轴标签为货币形式
ggplot(diamonds, aes(x = price, y = cut)) +
  geom_boxplot() +
  scale_x_continuous(
    labels = scales::label_dollar(scale = 1 / 1000, suffix = "K")
  )

# 格式化图例标签为百分比形式
ggplot(diamonds, aes(x = cut, fill = clarity)) +
  geom_bar(position = "fill") +
  scale_y_continuous(
    labels = scales::label_percent(),
    name = "Percentage"
  )

presidential |>
  mutate(id = 33 + row_number()) |>
  ggplot(aes(x = start, y = id)) +
  geom_point() +
  geom_segment(aes(xend = end, yend = id)) +
  scale_x_date(name = NULL, breaks = presidential$start, date_labels = "'%y")

7.3.3 图例部局

布局建议:

  • 宽幅图形建议图例置于顶部或底部。
  • 窄幅图形建议图例置于左侧或右。
  • 使用legend.position = "none"可完全隐藏图例。

通过guide()函数配合guide_legend()guide_colorbar()可控制单个图例显示,有两个关键的参数:

  1. nrowncol:控制图例的行数和列数。
  2. override.aes:用于覆盖图例中显示的美学属性(例如增大图例点大小)。
# guide_legend() 控制离散图例布局
ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = class), alpha = 0.5) + # 半透明显示密集点
  geom_smooth(se = F) +
  theme(legend.position = "bottom") +
  guides(
    color = guide_legend(
      nrow = 2,
      override.aes = list(size = 5) # 图例点尺寸设为5倍
    )
  )

# guide_colorbar() 控制连续图例布局
ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = hwy), size = 3, alpha = 0.5) + # 半透明显示密集点
  geom_smooth(se = F) +
  theme(legend.position = "bottom") +
  guides(
    color = guide_colorbar(
      direction = "horizontal",
      barwidth = unit(5, "in"),
      barheight = unit(0.3, "in")
    )
  )

7.3.4 修改scale

除了修改个别细节,还可以直接把整个标度都替换掉。

最可能需要更换的scale主要有两种:连续位置scale和颜色scale。幸运的是,同样的原则适用于所有其他美学属性,因此一旦掌握了位置和颜色的调整,你就能迅速掌握其他scale的替换方法。

7.3.4.1 连续位置标度

默认情况下,ggplot2使用线性标度来映射连续变量的位置。但是,有时使用对数标度可以更好地展示数据的分布,尤其是当数据跨越多个数量级时。

ggplot(diamonds, aes(x = carat, y = price)) +
  geom_bin2d() +
  scale_x_log10() +
  scale_y_log10()

7.3.4.2 颜色标度

如果需要发散的(diverging)颜色标度,则应当使用 scale_color_gradient2()
# 离散颜色标度示例-scale_color_brewer()
ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = drv)) +
  scale_color_brewer(palette = "Set1")

# 离散颜色标度示例-scale_color_manual()
ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = drv)) +
  scale_color_manual(values = c("4" = "blue", "f" = "green", "r" = "red"))

# 连续颜色标度示例-scale_fill_gradient()
df <- tibble(
  x = rnorm(10000),
  y = rnorm(10000)
)
ggplot(df, aes(x = x, y = y)) +
  geom_hex() +
  coord_fixed() + # 保持x轴和y轴比例一致 +
  scale_fill_gradient(low = "blue", high = "red")

# 连续颜色标度示例-scale_fill_viridis_c()
ggplot(df, aes(x = x, y = y)) +
  geom_hex() +
  coord_fixed() + # 保持x轴和y轴比例一致 + 
  scale_fill_viridis_c()

# 分箱颜色标度示例-scale_fill_viridis_b()
ggplot(df, aes(x = x, y = y)) +
  geom_hex() +
  coord_fixed() + # 保持x轴和y轴比例一致 +
  scale_fill_viridis_b()

值得注意的是,所有颜色标度均提供两种变体: scale_color_*()scale_fill_*() 分别对应 color 和 fill 美学属性。

7.3.5 缩放

缩放是指调整图形的显示范围,以便更好地观察数据的某个子集。ggplot2提供了多种方法来实现缩放效果:

  • 调整绘图数据范围。
  • 通过scale_x_continuous函数设置标度范围限制,实际的效果是对数据集进行子集筛选。
  • coord_cartesian() 中设置 xlimylim 。实际的效果是对图表的特定地区进行放大。
# 通过调整数据范围实现缩放
mpg |>
  filter(displ >= 5 & displ <= 6 & hwy >= 10 & hwy <= 25) |>
  ggplot(aes(x = displ, y = hwy)) +
  geom_point(aes(color = drv)) +
  geom_smooth()

# 通过设置标度范围限制实现缩放
ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = drv)) +
  geom_smooth() +
  scale_x_continuous(limits = c(5, 6)) +
  scale_y_continuous(limits = c(10, 25))

# 通过 coord_cartesian() 实现缩放
ggplot(mpg, aes(x = displ, y = hwy)) +
  geom_point(aes(color = drv)) +
  geom_smooth() +
  coord_cartesian(xlim = c(5, 6), ylim = c(10, 25))

当我们需要在不同绘图中保证尺度一致,方便进行数据的对比时,针对单个scale分别设置limits参数是一个不错的选择。

比如当分别绘制SUV和小轿车的油耗数据时,两张图的坐标轴范围和图例显示不一致,SUV的x轴范围是4.0-6.5,轿车则是1.8-4.0;且图例也不同,SUV只有四驱和后驱,轿车只有前驱和四驱。两张图不能直接比较,需进行标度统一。

suv <- mpg |> filter(class == "suv")
compact <- mpg |> filter(class == "compact")
# 创建一致的标度范围
x_scale <- scale_x_continuous(limits = range(mpg$displ))
y_scale <- scale_y_continuous(limits = range(mpg$hwy))
color_scale <- scale_color_discrete(limits = unique(mpg$drv))

# 应用一致的标度范围
p1 <- ggplot(suv, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  x_scale +
  y_scale +
  color_scale

p2 <- ggplot(compact, aes(x = displ, y = hwy, color = drv)) +
  geom_point() +
  x_scale +
  y_scale +
  color_scale

p1 / p2

7.4 主题

主题设置可以参看这个网页官方文档

每一个元素函数都有一些列控制外观的参数。值得注意的是,元素函数使用下划线_连接,而其中的参数则使用.连接。具体的主题设置可参看 附录 C

设置主题的函数的基本形式如下:

theme(element_name = element_function()),其中,元素函数element_function()有以下四个:

  • element_text():文本,一般用于设定坐标轴、标签和标题的字体风格。
  • element_line():线条,一般用于控制线条或线段的颜色、类型、位置等。
  • element_rect():矩形区域,一般用于控背景矩形的颜色、大小、边界线条类型等。
  • element_blank():空白,不分配相应的绘图空间,即删除相应的绘图元素。

7.5 多图布局

在数据可视化过程中,常常需要将多个图表组合在一起以便进行比较和分析。patchwork包提供了一种简便的方法来实现多图布局。

  • patchwork的基础语法是使用加+\|将多个ggplot2图形对象连接起来。

  • 此外,使用patchwork::plot_layout()函数可以进一步控制布局的细节,例如合并图例、调整间距等。guide_area()函数用于创建一个专门的图例区域:

  • plot_layout()函数的heightswidth参数可以按找比例分配组合后的图标和图例的空间。

  • pathwork中运算符区别:

    • +添加图层或组合子图。
    • &批量修改主题(适用于 patchwork 全局)。
library(patchwork)
library(ggplot2)

# 创建3个图表对象
p1 <- ggplot(mpg, aes(x = displ, y = hwy, color = drv)) +
  geom_point(show.legend = F) +
  labs(title = "散点图:发动机排量 vs. 油耗")

p2 <- ggplot(mpg, aes(x = drv, y = hwy, fill = drv)) +
  geom_boxplot() +
  labs(title = "箱线图:驱动类型 vs. 油耗")

p3 <- ggplot(mpg, aes(x = cty, y = hwy, color = drv)) +
  geom_point(show.legend = F) +
  labs(title = "散点图:城市油耗 vs. 高速油耗")

# 使用 patchwork 组合图表并组合图例
(guide_area()) /
  (p1 + p2 + p3) +
  plot_layout(
    heights = c(1, 9), # 图例与图表高度比例
    guides = "collect" # 合并图例
  ) &
  theme(legend.position = "top") # 统一调整图例位置