使用基于ggplot2的包ggalluvial绘制桑基图(冲积图)

介绍ggalluvial包,一种基于ggplot2的扩展工具,用于绘制桑基图(冲积图)。该文解释了冲积图的基本概念如轴、冲积层、地层、水流等,并详细描述了两种数据格式——宽格式和长格式,以及如何使用ggalluvial的各种功能绘制不同类型的数据。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 使用基于ggplot2的包ggalluvial绘制桑基图(冲积图)

参考:https://corybrunson.github.io/ggalluvial/articles/ggalluvial.html

ggalluvial 是ggplot2的拓展,用于在 tidyverse 框架中产生冲积图(或桑基图)。该包的设计和功能受 alluvial 包的启发,并从许多用户的反馈中受益。本简介包括如下内容:

  • 定义在命名方案和文件中使用的冲积地块的基本组成部分(轴axis、冲积层alluvium、地层stratum、矿脉lode、水流flow),

  • 描述ggalluvial可以识别的冲积数据结构,

  • 说明了新的数据和几何函数,还有

  • 展示了该主题的一些流行变体以及如何制作它们。

与大多数冲积图和相关图不同,由ggalluvial 生成的图是由数据集和统计转换唯一确定的。这篇博客文章将详细介绍二者的区别。

还有许多其他资源可以在R中可视化分类数据,包括几种更基本的绘图类型,当数据的结构不符合冲积图的要求时,它们可能更准确地向观众传递比例信息。特别地,可以查看Michael Friendly的vcd和vcdExtra软件包(PDF)来了解各种基于统计的分类数据可视化技术,Hadley Wickham的**productplots软件包**以及Haley Jeppson和Heike Hofmann的后继者ggmosaic软件包可用于产品或者马赛克图,以及Nicholas Hamilton的ggtern软件包用于三元坐标。其他相关的程序包如下所述。

1.1 冲积图(Alluvial plots)

此处是一个典型的冲积图:

img

下一节将详细说明该图像的元素如何对底层数据集的信息进行编码。现在,我们用这张图片作为一个参考点来定义一个典型冲积图的以下元素:

  • **轴(axis )**是一个维度(变量),数据沿着它在固定的水平位置垂直分组。上面的图像使用了三个分类轴:Class、Sex和Age。
  • 每个轴上的分组被描绘成不透明的块,称为地层(strata。例如,Class轴包含4个层次:123Crew
  • 被称为**冲积(alluvia)**的水平(x-)样条横跨地块的宽度。在这个图中,每个冲积层对应于每个轴变量的固定值,由其在轴上的垂直位置表示,以及由其填充颜色表示的Survived变量的固定值。
  • 在相邻轴线之间的冲积层片段是水流(flow)
  • 冲积层在**矿脉处(lodes)**与地层相交。上述图中没有显示矿脉,但可以推断为填充的矩形,这些矩形在图的每一端延伸流过地层的流动,或连接中心地层两侧的流动。

正如下一节的示例所演示的那样,将这些元素中的哪些合并到冲积图中取决于底层数据的结构方式以及创建者希望该图传达什么信息。

1.2 冲积图数据(Alluvial data)

ggalluvial可以识别两种形式的“冲积数据alluvial data", 在以下几个小节中详细处理,但基本上对应于分类重复测量数据的“宽”和“长”格式。另外表格形式(tablular或者array)是多种分类维度数据存储的流行方式,例如TitanicUCBAdmissions数据集。为了符合数据整洁原则和ggplot2转化,ggalluvial接受tabular输入; base::data.frame()可以将array转化为可接受的data frame。

补充:

  • Titanic数据集:数据集为1912年泰坦尼克号沉船事件中一些船员的个人信息以及存活状况。包括乘客编号、所在船舱等级、姓名、性别、兄弟姐妹数量、父母和子女数量、票号、票价、座位号和乘客登船码头等。参考:https://www.heywhale.com/mw/dataset/58a940107159a710d916aefb
  • UCBAdmissions数据集:伯克利研究生院性别歧视数据。原数据六个院系中,一个严重「歧视」男生,三个轻微「歧视」男生。另外还有两个院系轻微「歧视」女生,某种程度上削弱了数据的戏剧性。参考:https://zhuanlan.zhihu.com/p/30446806
1.2.1 Alluvia (wide) format

宽(wide)的格式反映了冲积图的视觉安排,但“不扭曲”: 每一行对应一组观测,每个变量取特定值,每个变量有自己的列。另外的1列表示每行的数量,例如队列中观测单位的数量,可用于控制**地层(strata) 高度。基本上,宽格式由每冲积层一行(one row per alluvium)**组成。这是base::as.data.frame()转换频率表的格式,例如三维UCBAdmissions数据集:

head(as.data.frame(UCBAdmissions), n = 12)
##       Admit Gender Dept Freq
## 1  Admitted   Male    A  512
## 2  Rejected   Male    A  313
## 3  Admitted Female    A   89
## 4  Rejected Female    A   19
## 5  Admitted   Male    B  353
## 6  Rejected   Male    B  207
## 7  Admitted Female    B   17
## 8  Rejected Female    B    8
## 9  Admitted   Male    C  120
## 10 Rejected   Male    C  205
## 11 Admitted Female    C  202
## 12 Rejected Female    C  391
is_alluvia_form(as.data.frame(UCBAdmissions), axes = 1:3, silent = TRUE)
## [1] TRUE

这种形式来自ggalluvial的第一版本,其是根据alluvial的使用建立的。用户可以定义任意数量的轴数目, stat_alluvium() and stat_stratum() 能够以一致的方式来识别:

  • stat_alluvium() : 计算冲积层与地层的交点。
  • stat_stratum() : 计算每个轴中地层的中心和高度。
ggplot(as.data.frame(UCBAdmissions),
       aes(y = Freq, axis1 = Gender, axis2 = Dept)) +
  geom_alluvium(aes(fill = Admit), width = 1/12) +
  geom_stratum(width = 1/12, fill = "black", color = "grey") +
  geom_label(stat = "stratum", aes(label = after_stat(stratum))) +
  scale_x_discrete(limits = c("Gender", "Dept"), expand = c(.05, .05)) +
  scale_fill_brewer(type = "qual", palette = "Set1") +
  ggtitle("UC Berkeley admissions and rejections, by sex and department")
img

这些图的一个重要特征是垂直轴的意义: 地层之间没有插入空隙,因此图片的总高度反映了观测的累积量。由ggalluvial 形成的图片(在某种程度上)符合ggplot2的“图形语法”原则,这阻止了用户生成“自由浮动”的可视化,比如这里展示的桑基图。ggalluvial参数和现有的ggplot2功能也可以生成并行集合的图片,这里使用HairEyeColor数据集https://corybrunson.github.io/ggalluvial/articles/ggalluvial.html#fn5:

ggplot(as.data.frame(HairEyeColor),
       aes(y = Freq,
           axis1 = Hair, axis2 = Eye, axis3 = Sex)) +
  geom_alluvium(aes(fill = Eye),
                width = 1/8, knot.pos = 0, reverse = FALSE) +
  scale_fill_manual(values = c(Brown = "#70493D", Hazel = "#E2AC76",
                               Green = "#3F752B", Blue = "#81B0E4")) +
  guides(fill = FALSE) +
  geom_stratum(alpha = .25, width = 1/8, reverse = FALSE) +
  geom_text(stat = "stratum", aes(label = after_stat(stratum)),
            reverse = FALSE) +
  scale_x_continuous(breaks = 1:3, labels = c("Hair", "Eye", "Sex")) +
  coord_flip() +
  ggtitle("Eye colors of 592 subjects, by sex and hair color")
## Warning: `guides(<scale> = FALSE)` is deprecated. Please use `guides(<scale> =
## "none")` instead.
## Warning in to_lodes_form(data = data, axes = axis_ind, discern =
## params$discern): Some strata appear at multiple axes.

## Warning in to_lodes_form(data = data, axes = axis_ind, discern =
## params$discern): Some strata appear at multiple axes.

## Warning in to_lodes_form(data = data, axes = axis_ind, discern =
## params$discern): Some strata appear at multiple axes.
img

(警告是因为“头发Hair"和"眼睛Eye"轴都含有”Brown“值)

这种形式很功能对于很多应用时有用的,将在未来的版本中保留。它们还涉及一些明显偏离ggplot2规范的情况。

  • axis[0-9]*的位置美学是非标准的:它们不是一组显式参数,而是基于正则表达式模式的一系列参数;至少需要一个,但不是特定的一个。
  • stat_alluvium()忽略group美学的任何参数;相反,statAlluvium$compute_panel()使用group来链接对应于同一冲积层的内部转换数据集的行。
  • stat_stratum()(由geom_text()调用)产生的stratum变量在执行统计转换之前不可用,必须使用after_stat()恢复。
  • 必须手动纠正水平轴(使用scale_x_discrete()scale_x_continuous()),以反映标识轴的隐式分类变量。

此外,对于每个冲积层来说,fill等格式美学是必要的;例如,它们不能根据每个轴的值从一个轴改变到另一个轴。这意味着,尽管它们可以再现平行集的分支树结构,但这种格式和功能不能自然地产生带有配色方案的冲积图,比如这里的那些(“控制颜色”),它们在每个轴上都被“重置”。

1.2.2 Lodes (long) format

ggalluvial识别的长格式每个lode包含一行(one row per lode),可以理解为将dplyr格式的数据集的轴列“gathering”(在dplyr的意义上)或将microsoftexcel格式的数据集的轴列“pivoting”(在microsoftexcel的意义上)到一个键-值对的列中,将轴编码为键,地层编码为值。这种格式需要一个额外的索引列,将对应于一个共同队列的行连接起来,即单个冲积层的矿脉:

UCB_lodes <- to_lodes_form(as.data.frame(UCBAdmissions),
                           axes = 1:3,
                           id = "Cohort")
head(UCB_lodes, n = 12)
##    Freq Cohort     x  stratum
## 1   512      1 Admit Admitted
## 2   313      2 Admit Rejected
## 3    89      3 Admit Admitted
## 4    19      4 Admit Rejected
## 5   353      5 Admit Admitted
## 6   207      6 Admit Rejected
## 7    17      7 Admit Admitted
## 8     8      8 Admit Rejected
## 9   120      9 Admit Admitted
## 10  205     10 Admit Rejected
## 11  202     11 Admit Admitted
## 12  391     12 Admit Rejected
is_lodes_form(UCB_lodes, key = x, value = stratum, id = Cohort, silent = TRUE)
## [1] TRUE

在宽(alluvia)和长(lodes)格式之间转换数据的函数包括几个有助于保存辅助信息的参数。参见help("alluvial-data")获取示例。

相同的属性和geom可以使用不同的位置美学来接收这种格式的数据,同样也适用于ggalluvial:

  • x:“键”变量,表示行对应的轴,将沿水平轴排列;
  • stratum,由轴变量x所表示的“值”;而且
  • alluvium,将单个冲积层的各行连接起来的索引方案。

高度可以从轴到轴变化,允许用户生成像这里(https://imgur.com/gallery/gI5p7)展示的凹凸图在这些情况下,地层所包含的信息并不比冲积层多,而且常常没有绘制出来。为了方便起见,stat_alluvium()stat_flow()将接受xalluvium的参数,即使没有给出参数stratum。例如,我们可以按地区对Refugees数据集中的国家进行分组,以便比较不同规模的难民数量:

data(Refugees, package = "alluvial")
country_regions <- c(
  Afghanistan = "Middle East",
  Burundi = "Central Africa",
  `Congo DRC` = "Central Africa",
  Iraq = "Middle East",
  Myanmar = "Southeast Asia",
  Palestine = "Middle East",
  Somalia = "Horn of Africa",
  Sudan = "Central Africa",
  Syria = "Middle East",
  Vietnam = "Southeast Asia"
)
Refugees$region <- country_regions[Refugees$country]
ggplot(data = Refugees,
       aes(x = year, y = refugees, alluvium = country)) +
  geom_alluvium(aes(fill = country, colour = country),
                alpha = .75, decreasing = FALSE) +
  scale_x_continuous(breaks = seq(2003, 2013, 2)) +
  theme_bw() +
  theme(axis.text.x = element_text(angle = -30, hjust = 0)) +
  scale_fill_brewer(type = "qual", palette = "Set3") +
  scale_color_brewer(type = "qual", palette = "Set3") +
  facet_wrap(~ region, scales = "fixed") +
  ggtitle("refugee volume by country and region of origin")
img

该格式允许我们沿着同一冲积层从轴到轴的变化分配图片属性,这对重复测量数据集很有用。这需要为每个流生成单独的图形对象,就像在geom_flow()中实现的那样。下面的图片使用了一组学生在几个学期的课程的学术课程的变化。由于geom_flow()默认调用stat_flow()(见下一个例子),我们用stat_alluvium()覆盖它,以便跟踪所有学期的每个学生:

data(majors)
majors$curriculum <- as.factor(majors$curriculum)
ggplot(majors,
       aes(x = semester, stratum = curriculum, alluvium = student,
           fill = curriculum, label = curriculum)) +
  scale_fill_brewer(type = "qual", palette = "Set2") +
  geom_flow(stat = "alluvium", lode.guidance = "frontback",
            color = "darkgray") +
  geom_stratum() +
  theme(legend.position = "bottom") +
  ggtitle("student curricula across several semesters")
img

地层高度y未指定,因此每一行给定单位高度。这个例子展示了ggalluvial 处理丢失数据的一种方法。另一种方法是设置参数na.rm=TRUE。丢失的数据处理(具体来说,地层的顺序)也取决于stratum变量是字符变量还是因子/数字变量。

最后,lode格式为我们提供了聚合相邻轴之间的流的选项,当相邻轴之间的转换是最重要的时候,这可能是合适的。我们可以通过兰德美国生命小组(RAND American Life Panel)进行的流感疫苗接种调查数据来证明这一选择。数据,包括三个调查中每个问题的一个问题,已被汇总的回应档案(response profile):每个“subject”(对应到alluvium)实际上代表了一个队列的受试者,他们以相同的方式回答所有三个问题,每个队列的大小(映射到y)被记录在“频率freq”中。

data(vaccinations)
vaccinations <- transform(vaccinations,
                          response = factor(response, rev(levels(response))))
ggplot(vaccinations,
       aes(x = survey, stratum = response, alluvium = subject,
           y = freq,
           fill = response, label = response)) +
  scale_x_discrete(expand = c(.1, .1)) +
  geom_flow() +
  geom_stratum(alpha = .5) +
  geom_text(stat = "stratum", size = 3) +
  theme(legend.position = "none") +
  ggtitle("vaccination survey responses at three points in time")
img

这个图忽略了轴与轴之间的流动之间的连续性。这种“无记忆”地块产生了一个不那么混乱的图,其中最多有一个流从一个轴上的每个地层流向下一个轴上的每个地层,但代价是能够在整个图片上跟踪每个队列。

如果你想设置每个柱子中每个模块的高度限制,可以使用`geom_text()`函数的`position`参数来调整标签的位置。具体来说,你可以使用`position_stack(vjust = 0.5)`来将标签垂直居中,或者使用`position_stack(vjust = 1)`来将标签放置在模块的顶部。以下是修改代码的示例: ``` ggplot(plotData, aes(x = x, stratum = stratum, alluvium = Cohort, fill = stratum, label = stratum)) + scale_x_discrete(expand = c(0, 0)) + scale_y_discrete(expand = c(0, 3)) + geom_flow(width = 1/8) + geom_stratum(alpha = .9, width = 1/10) + geom_text(stat = "stratum", size = 3, color = "black", position = position_stack(vjust = 0.5)) + # 将标签垂直居中 scale_fill_manual(values = c(cell_type_cols, cell_type_cols)) + xlab("") + ylab("") + theme_bw() + theme(panel.grid = element_blank(), panel.border = element_blank(), axis.line = element_blank(), axis.ticks = element_blank(), axis.text = element_blank()) + ggtitle("") + guides(fill = FALSE) ``` 如果你想将标签放置在模块的顶部,可以将`vjust`参数设置为1,如下所示: ``` geom_text(stat = "stratum", size = 3, color = "black", position = position_stack(vjust = 1)) # 将标签放置在模块的顶部 ``` 另外,如果你想限制每个模块的高度,可以在`aes()`函数中使用`ymin`和`ymax`参数来指定每个模块的最小值和最大值。例如,你可以将`ymax`参数设置为0.5,将每个模块的高度限制为0.5个单位,如下所示: ``` ggplot(plotData, aes(x = x, stratum = stratum, alluvium = Cohort, fill = stratum, label = stratum, ymin = 0, ymax = 0.5)) + scale_x_discrete(expand = c(0, 0)) + scale_y_continuous(expand = c(0, 0.5), limits = c(0, 3)) + # 将y轴的限制设置为0到3 geom_flow(width = 1/8) + geom_stratum(alpha = .9, width = 1/10) + geom_text(stat = "stratum", size = 3, color = "black", position = position_stack(vjust = 0.5)) + scale_fill_manual(values = c(cell_type_cols, cell_type_cols)) + xlab("") + ylab("") + theme_bw() + theme(panel.grid = element_blank(), panel.border = element_blank(), axis.line = element_blank(), axis.ticks = element_blank(), axis.text = element_blank()) + ggtitle("") + guides(fill = FALSE) ``` 注意,如果你使用了`ymin`和`ymax`参数来限制每个模块的高度,你需要将`scale_y_discrete()`函数改为`scale_y_continuous()`函数,并在`scale_y_continuous()`函数中设置`limits`参数。
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值