如何制作带音效的动态图表?—— 在 R 中完成所有的操作

如何制作带音效的动态图表?—— 在 R 中完成所有的操作

接下来一段时间我们的教程主题就是动态图表绘制啦!在这一系列教程中我将会介绍一些绘制动态图表的方法。当然绘制图表知识目的,更重要的是绘图过程中的数据处理。这些也是我希望大家能够掌握的。

视频讲解

在前两天的教程中我主要介绍了三种绘制动态图表的方法:

  1. 绘制一系列的静态图,然后使用终端命令把静态图合成动图;
  2. 使用 gganimate 绘制基于 ggplot2 的动态图表;
  3. 使用 plotly 绘制基于 HTML 的动态图表;

今天我们的教程会继续介绍如何使用 gganimate 绘制动态图表,另外本文的最后会介绍一个非常有意思的东西,就是如果直接使用 R 语言为你的动态图表添加音效。下面我们开始吧!

加载 R 包

首先可以从知识星球下载附件,附件里有本案例需要的示例数据,这个示例数据是新冠肺炎疫情省份分布的历史数据,我们先导入需要的一些 R 包:

1
2
3
4
5
6
7
library(readxl)
library(tidyverse)
library(gganimate)
library(ggplot2)
library(plotly)
library(lubridate)
library(scales)

你也可以使用 pacman 的 p_load() 函数一行导入这些 R 包:

1
pacman::p_load(readxl, tidyverse, gganimate, ggplot2, plotly, lubridate, scales)

不过我不喜欢这种方式,因为这种导入方式隐藏了加载了包加载过程中产生的信息,例如 tidyverse 这个包加载的时候会告诉你加载它的同时会加载哪些相关的包以及检查包间的函数名称冲突。我们偏个题目,p_load() 这个函数并不什么高级的函数,我们看一下它的源码,在 R 里面直接运行函数名称(不带后面的括号)会打印出函数的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
pacman::p_load
#> function (..., char, install = TRUE, update = getOption("pac_update"),
#> character.only = FALSE)
#> {
#> if (!missing(char)) {
#> packages <- char
#> }
#> else if (character.only) {
#> packages <- eval(match.call()[[2]])
#> }
#> else {
#> packages <- as.character(match.call(expand.dots = FALSE)[[2]])
#> }
#> if (length(packages) == 0) {
#> return(invisible())
#> }
#> if (is.null(update)) {
#> update <- FALSE
#> }
#> if (update) {
#> p_update()
#> }
#> loaded <- sapply(packages, p_load_single, install = install)
#> if (!all(loaded)) {
#> failed <- packages[!loaded]
#> warning("Failed to install/load:\n", paste(failed, collapse = ", "))
#> }
#> return(invisible(loaded))
#> }
#> <bytecode: 0x7fbb54484378>
#> <environment: namespace:pacman>

通过阅读可以发现加载 R 包是这句实现的:

1
#> loaded <- sapply(packages, p_load_single, install = install)

就是这个函数还调用了 p_load_single 函数,我们再看看这个函数的源码:

1
2
pacman::p_load_single
#> Error: 'p_load_single' is not an exported object from 'namespace:pacman'

出错了,错误信息说 p_load_single 不是 pacman 的 名称空间中的导出对象,这个我之前在群里介绍过,只有包名称空间中 export 的对象才能使用 :: 操作符加载,那如果这个对象在包的源码中定义了,又没有在 namespace 文件中 export,那么我们可以使用 ::: 加载:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
pacman:::p_load_single
#> function (package, install = TRUE, ...)
#> {
#> if (suppressMessages(suppressWarnings(require(package, character.only = TRUE)))) {
#> return(TRUE)
#> }
#> if (install) {
#> p_install(package, character.only = TRUE, ...)
#> return(suppressMessages(require(package, character.only = TRUE)))
#> }
#> return(FALSE)
#> }
#> <bytecode: 0x7fbb5448bae0>
#> <environment: namespace:pacman>

注意到没:suppressMessages(suppressWarnings(require(package, character.only = TRUE))) 这个函数最后使用的是 require 函数加载 R 包,同时使用 suppressMessages() 和 suppressWarnings() 把加载过程中产生的信息和警告都隐藏了,这也就是我们看不到加载信息的原因。

这里就产生了另一个知识点,require() 函数和 library() 函数都能加载 R 包,这两个有什么区别?

1
2
3
4
is.logical(require(ggplot2))
#> [1] TRUE
is.logical(library(ggplot2))
#> [1] FALSE

是不是很清晰了,require(ggplot2) 会返回一个逻辑对象表示是否调用成功,library(ggplot2) 则不会。所以 require 函数通常用于函数编程中,因为它产生的逻辑信息可以用于判断语句。

导入数据

再回到正题,我们使用 readxl 包的 read_xlsx 函数导入我们的示例数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
prov <- read_xlsx('省级数据.xlsx')
prov
#> # A tibble: 1,186 x 6
#> 省份 日期 确诊 疑似 治愈 死亡
#> <chr> <dttm> <dbl> <dbl> <dbl> <dbl>
#> 1 湖北 2020-01-12 00:00:00 41 0 0 1
#> 2 湖北 2020-01-13 00:00:00 41 0 0 1
#> 3 湖北 2020-01-14 00:00:00 41 0 0 1
#> 4 湖北 2020-01-15 00:00:00 41 0 0 2
#> 5 湖北 2020-01-16 00:00:00 45 0 0 2
#> 6 湖北 2020-01-17 00:00:00 62 0 0 2
#> 7 湖北 2020-01-18 00:00:00 121 0 0 3
#> 8 上海 2020-01-19 00:00:00 0 0 0 0
#> 9 云南 2020-01-19 00:00:00 0 0 0 0
#> 10 北京 2020-01-19 00:00:00 2 0 0 0
#> # … with 1,176 more rows

绘制静态图

在绘制动态图表之前,我们先绘制一幅静态图表,由于湖北的确诊人数实在是太多了,所以就不绘制湖北的了:

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

上面的绘图语句中我使用了 ggrepel::geom_label_repel 图层,这个图层的特点是可以绘制互补重叠的文本标签。

可以使用 plotly 包的 ggplotly() 函数把 ggplot2 图表转成 HTML 控件:

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

绘制动态图

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

注意到这里我使用的是 transition_reveal,这个函数可以使得图表是数据存量绘制的,这和之前使用的 transition_manual 是不一样的,transition_manual 绘制的图表是使用数据增量绘制的。

animate 函数默认使用的 renderer 是 gifski_renderer(),顾名思义这个渲染器生成的动态图表是 GIF 格式的,如果我们想生成 MP4 文件,可以使用 av 包的 av_renderer 作为渲染器:

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

为动态图表添加音效

av 包中的 av_capture_graphics 函数可以在合成静态图的同时插入音频。

首先我在网上找了个音频文件:665.mp3,我们把这个文件读入查看音频中的信息:

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

len 就是这个音频的长度,107s,本来我是想一秒对应一帧图片,所以我使用 file_renderer 渲染器生成了 107 张静态图:

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

生成的这些静态图会被保存到 pic 文件夹中,如果提示你没有这个文件夹,可以自己在工作目录上创建一个。

把静态图绘制出来可以使用 cowplot 包:

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

最后我们制作带音效的 MP4 视频文件:

代码去哪了?

代码可以加入我的知识星球后从知识星球下载附件获取~
要了解如何加入我的知识星球,可以阅读关于界面或者添加我的微信咨询。

注意到这里为什么我要设定 framerate = 8 呢,这个参数表示每秒钟播放的帧数为 8 帧,这样,107 帧才能在 15s 内播放完成。

这个视频是这样的:

是不是非常有意思呢!以后靠着 R 语言就能成为朋友圈最靓的仔了!

知识星球附件链接:https://t.zsxq.com/rb2BQj2

#

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×