Logo
Section 1 / 5

ggplot2 中的分面(Faceting)

学习切面(faceting)的基础知识以及如何在 ggplot2 中使用切面。

导入

ggplot2 中的分面(Faceting)

学习切面(faceting)的基础知识以及如何在 ggplot2 中使用切面。

初识分面(Faceting)

ggplot2 包提供了多种强大的数据可视化与分析功能,如几何对象(geoms)、图层叠加、美学映射、主题设置以及统计变换等,使用户能够灵活定制和探索数据。

接下来我们来学习 ggplot2 的另一个强大功能——分面(Faceting)。

分面 允许我们将多个图形排列在同一页面上。当处理大型数据集时,这一功能非常有用,因为它能让我们聚焦于数据的特定子集,而不会被整体数据的复杂性所淹没。此外,将数据划分为更小的组别,有助于更容易地发现数据中的模式和趋势。

ggplot2 中的分面类型

ggplot2 提供了三种分面方式,用于在单页上排列多个图形:

  • facet_null():生成单一图表,这是默认设置。
  • facet_wrap():将一维的面板带(ribbon)组织为二维布局,但仅针对实际存在的变量组合。
  • facet_grid():基于指定的行变量和列变量创建二维网格面板,包含所有可能的变量组合,即使某些组合没有数据也会保留对应位置。

facet_grid() 和 facet_wrap() 的主要区别在于:facet_grid() 会生成所有可能的变量组合对应的子图,即使部分子图为空;而 facet_wrap() 仅根据实际存在的数据生成子图。

为了理解分面的应用,我们首先创建一个冰淇淋销售数据集,其中包含以下信息:

  • 不同口味的冰淇淋。
  • 每勺的价格。
  • 客户购买时的室外气温。
  • 销售冰淇淋的不同店铺。
  • 各店铺的总销售额等。
1set.seed(123) 2df <- tibble( 3 flavor = sample(c("Vanilla", "Chocolate", "Strawberry", "Cookies and cream"), 50, replace = TRUE), 4 scoops_sold = sample(1:6, 50, replace = TRUE), 5 price_per_scoop = round(runif(50, 1, 5), 2), 6 total_sales = scoops_sold * price_per_scoop, 7 temperature = round(runif(50, 15, 35), 1), 8 shop = sample(c("Shop A", "Shop B"), 50, replace = TRUE)) 9head(df)
  • 第1行:使用 set.seed() 函数确保每次运行代码时生成相同的随机数。
  • 第2行:使用 tibble() 函数创建名为 df 的新数据框。
  • 第3行:创建 flavor 列,通过 sample() 函数为50行分配随机口味,可选口味包括 "Vanilla"、"Chocolate"、"Strawberry" 和 "Cookies and cream"。
  • 第4行:创建 scoops_sold 列,生成50个1到6之间的随机整数,表示售出的冰淇淋勺数。
  • 第5行:使用 runif() 函数生成50个1到5之间的随机数值作为每勺价格,并用 round() 函数保留两位小数。
  • 第6行:创建 total_sales 列,通过将 scoops_sold 与 price_per_scoop 相乘得到总销售额。
  • 第7行:创建 temperature 列,表示购买时的室外气温,使用 runif() 生成15到35之间的随机值,并用 round() 保留一位小数。
  • 第8行:创建 shop 列,通过 sample() 为50行随机分配 "Shop A" 或 "Shop B"。
  • 第9行:使用 head() 函数打印 df 数据框的前几行。

利用该数据集,我们通过以下代码创建一个散点图:

1chart <- ggplot(df) + 2 geom_point(aes(x = temperature, y = total_sales, 3 color = flavor, size = 2)) + 4 theme_bw() + 5 scale_color_manual(values = c("#0b7a75","#1DD3B0","#5DA9E9","#03045E")) 6chart
  • 第1行:创建名为 chart 的新对象,使用 <- 赋值操作符将其存储。初始化一个 ggplot 对象并传入 df 数据框,再用 + 操作符添加图层。
  • 第2行:使用 geom_point() 创建散点图,通过 aes() 将 temperature 映射到x轴,total_sales 映射到y轴。
  • 第3行:通过 color 参数按 flavor 分组,并将点的大小设为2。
  • 第4行:使用 theme_bw() 将默认灰度主题改为黑白风格。
  • 第5行:使用 scale_color_manual() 手动指定散点图的颜色。

上述图表展示了所有冰淇淋口味的总销售额情况。但如果希望为每种口味单独绘制相同图表,就需要对每个口味分别筛选数据并重复绘图,这需要编写循环或额外代码。借助分面功能,可以大大简化这一过程。

接下来,我们使用已创建的冰淇淋数据集,探索 facet_grid() 和 facet_wrap() 函数的用法。

使用 facet_grid() 函数进行分面

facet_grid() 函数根据一个或两个离散变量对数据进行划分,并生成子图。这些子图可通过公式 variable_name1~variable_name2 格式进行水平或垂直排列。

下面我们通过几个示例展示如何在 facet_grid() 中指定变量组合以生成子图。

  1. .~variable_name:使用 .~variable_name 公式时,特定变量的取值将分布在各列中。这样便于比较y轴位置,因为垂直坐标轴是对齐的。
1chart + 2 facet_grid(.~flavor)
  • 第1行:调用包含散点图的 chart 对象,并通过 + 操作符添加新图层。
  • 第2行:使用 facet_grid() 函数,配合 .~flavor 公式,将 flavor 变量的取值分布到各列。
  1. variable_name~.:使用 variable_name~. 公式可将特定变量的取值分布到各行中。这样便于比较x轴位置,因为水平坐标轴是对齐的。
1chart + 2 facet_grid(flavor~.)
  • 第2行:使用 facet_grid() 函数,配合 flavor~. 公式,将 flavor 变量的取值分布到各行。

注意:公式左侧的 . 加上 ~ 表示只有一个变量,且希望将其显示在x轴上;若右侧为 ~.,则表示变量显示在y轴上。

  1. variable_name1~variable_name2:使用 variable_name1~variable_name2 组合可将第一个变量的取值分布到行,第二个变量的取值分布到列。
1chart + 2 facet_grid(flavor ~ shop)
  • 第2行:使用 facet_grid() 函数,配合 flavor ~ shop 公式,将 flavor 变量的取值分布到行,shop 变量的取值分布到列。

使用 facet_wrap() 函数进行分面

接下来我们使用 facet_wrap() 函数创建一组面板图。该函数通常被认为优于 facet_grid(),因为它更节省屏幕空间,并能将一维的面板序列自动转换为二维布局。此外,facet_wrap() 不需要使用 . 符号。

我们使用 facet_wrap() 函数创建一组面板图:

1chart + 2 facet_wrap(~ flavor)
  • 第2行:使用 facet_wrap() 函数,配合 ~ flavor 公式,将 flavor 变量的取值分布到各个子图中。

如上所示,facet_wrap() 将一条长条形的面板带折叠为二维布局,在优化空间排布方面非常实用。

下面我们对比 facet_grid() 与 facet_wrap() 生成的面板图效果:

1charts

接下来,我们使用 facet_wrap() 函数,展示 df 数据框中 flavor 和 shop 变量所有唯一组合下,temperature 与 total_sales 之间的关系。

1chart + 2 facet_wrap(flavor ~ shop)
  • 第2行:使用 facet_wrap() 函数,基于 flavor 与 shop 的唯一组合创建子图。

我们还可以通过 ncol 与 nrow 参数控制 facet_wrap() 中子图的行列数量。

1chart + 2 facet_wrap(flavor ~ shop, nrow = 2, ncol = 4)
  • 第2行:在 facet_wrap() 函数中设置 nrow = 2 和 ncol = 4,使图表呈现为两行四列的布局。

针对连续变量的分面

要为连续变量创建分面图,需先将变量划分为若干区间(bin)。为此,ggplot2 提供了三个辅助函数:

  • cut_interval(x, n):将输入数据 x 划分为 n 个长度相等的区间。每个区间的宽度相同,因此覆盖的数值范围也一致。
  • cut_width(x, width):将输入数据 x 划分为指定宽度的区间。每个区间的宽度可自定义,从而实现特定范围内的灵活分组。
  • cut_number(x, n = 10):将输入数据 x 划分为 n 个区间,确保每个区间包含大致相等的数据点数量。各区间的宽度可能不同,以达到均衡分布。

现在,我们在现有数据框中新增一列 scoop_int,使用 cut_interval(x, n) 函数进行分组:

1df$scoop_int <- cut_interval(df$scoops_sold, 2) 2colnames(df) 3head(df)
  • 第1行:向 df 数据框添加新列 scoop_int,其值由 cut_interval() 函数决定,将 scoops_sold 列的值划分为两个等长区间。
  • 第2行:使用 colnames() 函数获取 df 数据框的列名。
  • 第3行:使用 head() 函数打印数据框的前几行。

接下来,我们使用 facet_wrap() 函数,根据 scoop_int 列将图形划分为多个子图:

1chart + 2 facet_wrap(~scoop_int)
  • 第2行:使用 facet_wrap() 函数,配合 ~scoop_int 公式,将 scoop_int 变量的取值分布到各子图中。

采用类似方法,我们也可以使用 cut_width() 与 cut_number() 函数生成分面图。

1charts

图表自定义

使用 patchwork 包组合图形