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() 中指定变量组合以生成子图。
.~variable_name:使用.~variable_name公式时,特定变量的取值将分布在各列中。这样便于比较y轴位置,因为垂直坐标轴是对齐的。
1chart + 2 facet_grid(.~flavor)
- 第1行:调用包含散点图的
chart对象,并通过+操作符添加新图层。 - 第2行:使用
facet_grid()函数,配合.~flavor公式,将flavor变量的取值分布到各列。
variable_name~.:使用variable_name~.公式可将特定变量的取值分布到各行中。这样便于比较x轴位置,因为水平坐标轴是对齐的。
1chart + 2 facet_grid(flavor~.)
- 第2行:使用
facet_grid()函数,配合flavor~.公式,将flavor变量的取值分布到各行。
注意:公式左侧的
.加上~表示只有一个变量,且希望将其显示在x轴上;若右侧为~.,则表示变量显示在y轴上。
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