本文引用的所有信息均为公开信息,仅代表作者本人观点,与就职单位无关。
本文概览
R 语言在数据可视化方面有很长时间的积累,除了内置的基础作图系统 graphics (R Core Team 2021)和栅格作图系统 grid(Murrell 2002),以及衍生出来的代表作 lattice (Sarkar 2008)和 ggplot2 (Wickham 2016),更加易用、便携、交互的网页图形逐渐形成新的主流。移动终端设备的大规模普及,探索性数据分析和可视化需求越来越强烈,得益于现代硬件设施和前端技术的落地,交互式网页图形逐渐成为数据展示中的标配。图形种类繁多,交互式图形的种类不比静态的少,本文亦无意全面罗列,而是以散点图为例,详细介绍几个常见 R 包的使用。
本文将主要介绍 R 语言绘制交互式网页图形的扩展包,综合考虑了使用权限,图形种类,接口成熟度等方面因素,挑选了 plotly、 ggiraph、 scatterD3、 apexcharter 和 echarts4r 等几个 R 包,见表1。R 语言还有一些专门化的可视化扩展包,比如绘制交互网络图的visNetwork ,绘制交互地图的leaflet 等,更多详见Ryan Hafen 收集整理的交互式图形展览网站。
R 包 | 简介 | 维护者 | 协议 |
---|---|---|---|
plotly(Sievert et al. 2021) | Create Interactive Web Graphics via plotly.js | Carson Sievert | MIT + file LICENSE |
ggiraph(Gohel and Skintzos 2022) | Make ggplot2 Graphics Interactive | David Gohel | GPL-3 |
echarts4r(Coene 2022) | Create Interactive Graphs with Echarts JavaScript Version 5 | John Coene | Apache License (>= 2.0) |
scatterD3(Barnier et al. 2021) | D3 JavaScript Scatterplot from R | Julien Barnier | GPL (>= 3) |
ggplot2(Wickham et al. 2022) | Create Elegant Data Visualisations Using the Grammar of Graphics | Thomas Lin Pedersen | MIT + file LICENSE |
apexcharter(Perrier and Meyer 2022) | Create Interactive Chart with the JavaScript ApexCharts Library | Victor Perrier | MIT + file LICENSE |
plotly
目前为止,plotly 是笔者使用次数最多的交互式网页图形制作工具,它同时提供了 Python 语言和 R 语言版本 plotly (Sievert 2020)。支持丰富的图形,足可应付大部分场景,以 MIT 协议开源,可以商用,能够导出 SVG/PDF 格式矢量图形,也可以配合 R Shiny 应用,英文文档也很全,还有配套书籍,接口还算稳定。美中不足的地方是 plotly 依赖很重,去掉 dplyr 的依赖就好了,画图就画图嘛,没必要引入那么多数据操作的扩展包!
library(plotly)
plot_ly(
data = iris,
# 横轴变量
x = ~Sepal.Width,
# 纵轴变量
y = ~Sepal.Length,
# 分类变量
color = ~Species,
# 调色板:RColorBrewer 包内置的调色板都支持
colors = "Set2",
# 图形种类:散点图
type = "scatter",
# 显示模式:散点,读者不妨试试 "markers+lines"
mode = "markers",
# 散点的样式
marker = list(
# 圆形
symbol = "circle",
# 如果有 size 变量,则映射到圆的直径,当然还可以映射到面积 area
sizemode = "diameter",
# 散点大小
size = 15,
# 圆的边界宽度为 2 和颜色为白色
line = list(width = 2, color = "#FFFFFF"),
# 圆的透明度,注意同一位置的圆点重合后颜色会加深
opacity = 0.8
),
text = ~ paste0(
"萼片宽度:", Sepal.Width, "<br>",
"萼片长度:", Sepal.Length
),
hoverinfo = "text"
) |>
layout(
title = "鸢尾花数据",
# 添加横轴标题,去掉水平线,坐标轴刻度值保留一位小数,如果单位是百分比,则为 .1%
xaxis = list(title = "萼片宽度", showgrid = FALSE, tickformat = ".1f"),
# 添加纵轴标题,去掉垂直线
yaxis = list(title = "萼片长度", showgrid = FALSE, tickformat = ".1f"),
# 取消拖拽和局部缩放
dragmode = FALSE,
# 设置图例标题并加粗
legend = list(title = list(text = "<b> 种类 </b>")),
# 按照下 bottom 左 left 上 top 右 right 的顺序设置图形边空
margin = list(b = 50, l = 50, t = 35, r = 0)
) |>
config(
toImageButtonOptions = list(
# 保存图片格式
format = "svg",
# 图片宽度
width = 800,
# 图片高度
height = 600,
# 图片文件名
filename = paste("iris", Sys.Date(), sep = "_")
)
)
散点图更多详细设置见文档,此处不一一展示,建议读者直接使用上述代码在 R 控制台里运行起来,并且逐行注释并运行试试看效果,多试几次,对规律的理解会更加深刻,这样即使是其它图形种类也得心应手。散点图主要用来展示变量关系,既是散点,它一般会有大小、颜色、透明度、边界等属性。同样地,是坐标轴就会有横纵轴标签、刻度标签、刻度单位、背景网格线等,是图例就会有标题、位置、方向等,是文本就会有样式、字族、颜色、大小,是线就会有类型、宽度、颜色等。
Python 版本和 R 语言版本不要同时使用,以免 plotly 库版本不同带来冲突。另一个值得一提的是移除 plotly 图形右上方的工具条,可以添加全局的 CSS 设置。
.modebar { display: none !important; }
不管是连续型还是离散型的调色板,数量都是 8-12 个有限值,一旦超出数量会触发警告:
Warning message: In RColorBrewer::brewer.pal(n = 20, name = "Set2") : n too large, allowed maximum for palette Set2 is 8 Returning the palette you asked for with that many colors
不过,plotly 还是会通过插值方式返回足够多的色块,读者也可以尝试使用
viridis
、plasma
、magma
或inferno
调色板,它们既可以当连续的也可以当离散的用。
plotly 支持调用 RColorBrewer 包所有内置的调色板,图 3 是 RColorBrewer 包内置的所有调色板,可供快速查阅和对比渲染效果。
上面提到 plotly 是支持集成到 R Shiny 应用中的,下面举个简单的例子,用热力图展示 faithful 数据集里喷发时间和等待时间的关系 1。
library(shiny)
library(KernSmooth)
ui <- fluidPage(
titlePanel("老忠实间歇泉喷发规律"),
sidebarLayout(
sidebarPanel(
sliderInput("x_bins",
"水平方向的窗宽:",
min = 0,
max = 1,
value = 0.7
),
sliderInput("y_bins",
"垂直方向的窗宽:",
min = 1,
max = 10,
value = 7
)
),
mainPanel(
plotly::plotlyOutput("heatmap")
)
)
)
server <- function(input, output) {
output$heatmap <- plotly::renderPlotly({
den <- bkde2D(x = faithful, bandwidth = c(input$x_bins, input$y_bins))
plotly::plot_ly(x = den$x1, y = den$x2, z = den$fhat) |>
plotly::add_heatmap() |>
plotly::layout(
xaxis = list(showgrid = F, title = "喷发时间(分钟)"),
yaxis = list(showgrid = F, title = "等待时间(分钟)")
) |>
plotly::config(displayModeBar = FALSE)
})
}
shinyApp(ui = ui, server = server)
ggiraph
从这扩展包 ggiraph 的名字就不难想到它与 ggplot2 有某种关联,实际上,它是真的有关联,目标就是制作交互式的 ggplot2 图形,在 ggvis 睡醒之前,它是最贴近 ggplot2 语法风格的!下面用一个示例先简略介绍 ggplot2,还是借用 Edgar Anderson 收集的鸢尾花数据,如图 5 所示。
library(ggplot2)
# 提供数据映射关系
ggplot(data = iris, aes(x = Sepal.Width, y = Sepal.Length, color = Species)) +
# 添加散点图层
geom_point() +
# 添加分组的线性回归趋势
geom_smooth(method = "lm", formula = y~x, se = FALSE) +
# 设置坐标轴标签
labs(x = "萼片长度", y = "萼片宽度", color = "种类") +
# 设置经典的黑白主题,还可以设置已安装的系统字体,比如 Noto Sans
theme_bw(base_size = 13, base_family = "Noto Sans") +
# 设置单独的标题字体
theme(title = element_text(family = "Noto Serif CJK SC"))
接下来看 ggiraph 如何绘制散点图的,tooltip 和 data_id 是交互图特有的,数据到图形的映射方式是一样的,仅仅将 geom_point()
换成 geom_point_interactive()
,geom_smooth()
换成 geom_smooth_interactive()
,就是在这些统计、几何图层函数加了后缀 _interactive而已!
library(ggiraph)
gg_point <- ggplot(data = iris, aes(x = Sepal.Width, y = Sepal.Length, color = Species)) +
geom_point_interactive(aes(tooltip = Species, data_id = Sepal.Width)) +
geom_smooth_interactive(method = "lm", formula = y ~ x, se = FALSE) +
labs(x = "萼片长度", y = "萼片宽度", color = "种类") +
theme_bw(base_size = 13, base_family = "Noto Sans") +
theme(title = element_text(family = "Noto Serif CJK SC"))
girafe(ggobj = gg_point)
scatterD3
Julien Barnier 开发的scatterD3 包基于鼎鼎大名的 D3 图形库,在散点图方面的渲染效果非常好,支持原生 SVG 矢量图形导出。
类似前面 plotly 的介绍,下面是一个完整的散点图示例,如图8 所示
# 加载 R 包
library(scatterD3)
scatterD3(
# 数据集
data = iris,
# 横轴变量
x = Sepal.Length,
# 纵轴变量
y = Sepal.Width,
# 分类变量
col_var = Species,
# 分类调色板 Tableau
colors = "schemeTableau10",
# 散点的大小
point_size = 200,
# 散点的透明度
point_opacity = 0.7,
# 鼠标悬停处散点的大小
hover_size = 4,
# 鼠标悬停处散点的透明度
hover_opacity = 1,
# 横轴标题
xlab = "Sepal Length",
# 纵轴标题
ylab = "Sepal Width",
# 图例标题
col_lab = "Species",
# 坐标轴字体大小
axes_font_size = "160%",
# 图例字体大小
legend_font_size = "16px",
# 提示符出现左上
tooltip_position = "top left",
# 聚类椭圆
ellipses = TRUE,
# 去掉图形上的导出按钮
menu = FALSE
)
apexcharter
apexcharter 是 apexcharts.js 的 R 接口,apexcharts.js 构建于 SVG 之上,也原生支持矢量图形导出。
# 加载 R 包
library(apexcharter)
apex(
data = iris,
aes(x = Sepal.Width, y = Sepal.Length, fill = Species),
type = "scatter"
) |>
# 设置调色板
ax_colors(RColorBrewer::brewer.pal(n = 3, name = "Set2")) |>
# 散点的透明度
ax_fill(opacity = 0.7) |>
# 显示图例
ax_legend(show = TRUE) |>
# 标题
ax_title(text = "散点图") |>
# 副标题
ax_subtitle(text = "鸢尾花数据集")
也可以采用一一指定类别和颜色的映射关系设置调色板,用下述代码替换 ax_colors()
所在行。
ax_colors_manual(list(
"setosa" = "#66C2A5",
"versicolor" = "#FC8D62",
"virginica" = "#8DA0CB"
))
echarts4r
最后,提一下 John Coene 开发的 echarts4r 包,它将 Apache ECharts 引入 R 语言社区。Apache ECharts 是百度出品的前端开源框架,在 2013 年6月30日发布 1.0.0 版本,目前已经版本迭代到 5.2.2 了,Li et al. (2018) 号称在图形库、交互性和渲染性能等方面都比较好。百度自助BI报表分析和可视化数据大屏制作工具 Sugar 也是采用 Apache ECharts,这算得上是 Apache ECharts 成熟的一种展示。echarts4r 和 plotly 一样都严重依赖 dplyr,不喜欢净土的慎选 echarts4r,此外,只有 John Coene 一个人在维护 R 包,Apache ECharts 5 还没支持到位,有跑路风险,前车之鉴是recharts包。吐槽完了,接着折腾,还是以 iris 数据集为例绘制散点图,效果如图12和图13,代码如下
library(echarts4r)
iris |>
group_by(Species) |>
e_charts(x = Sepal.Width) |>
e_scatter(serie = Sepal.Length, bind = Species, symbol_size = 10) |>
# 设置调色板 Set2
e_color(color = RColorBrewer::brewer.pal(n = 3, name = "Set2")) |>
e_tooltip(
trigger = "item",
# 定制悬浮内容
# params.name 取自 bind 变量
formatter = htmlwidgets::JS("
function(params){
return('种类: <strong>' + params.name +
'</strong><br />萼片宽度: ' + params.value[0] +
'<br />萼片长度: ' + params.value[1])
}
")
) |>
e_x_axis(
# 横轴标题
name = "萼片宽度",
# 横轴标题的位置,居中
nameLocation = "center",
# 横轴标题和坐标轴的距离
nameGap = 25,
# 横轴刻度值的展示精度
formatter = e_axis_formatter("decimal", digits = 1),
# 横轴最小值
min = 2,
# 横轴类型,数值型
type = 'value',
# 横轴名称的文本样式,加粗
nameTextStyle = list(fontWeight = 'bold')
) |>
# 设置纵轴,参数含义同上
e_y_axis(
name = "萼片长度", nameLocation = "center", nameGap = 35,
formatter = e_axis_formatter("decimal", digits = 1),
min = 4, type = 'value',
nameTextStyle = list(fontWeight = 'bold')
) |>
# 添加图标题
e_title("鸢尾花数据") |>
# 添加右上角缩放图形按钮
e_toolbox_feature("dataZoom") |>
# 添加右上角下载图片按钮
e_toolbox_feature(feature = "saveAsImage", title = "保存图片")
使用的方式上与前面介绍过的 R 包 plotly 等有些不太一样,连分组散点图画起来都比较费劲,关键是三份材料对照学习,其一是函数帮助文档,其二是 echarts4r 包文档,其三是 Apache ECharts 官方文档,所幸文档比较全,一点一点调试,积累积累也就好了,不然,画个散点图都能这么费劲,绝对可以劝退很多人。非常亮眼的地方在于鼠标悬停在散点上时,能感受到如丝般顺滑,也不枉来回折腾一趟!如果读者也想体验一下,一定要把上面的代码复制到 R 控制台里运行,话说千遍,不如一干!
如果对效果没啥要求,就是看看,倒也简单,四行代码即可!
iris |>
group_by(Species) |>
e_charts(Sepal.Width) |>
e_scatter(Sepal.Length)
哈哈,前后一对比,你就知道开发者给的示例和真正要用的之间的差距了吧!
函数 e_color()
中的color 选项有一个默认的调色板。
echarts_colors <- c(
"#5470c6", "#91cc75", "#fac858",
"#ee6666", "#73c0de", "#3ba272",
"#fc8452", "#9a60b4", "#ea7ccc"
)
scales::show_col(colours = echarts_colors)
还有一个调整调色板的办法,调函数 e_theme()
设置新的配色主题,比如 "vintage"
,即用
e_theme(name = "vintage")
替换
e_color(color = RColorBrewer::brewer.pal(n = 3, name = "Set2"))
效果见图16。
值得注意,目前,echarts4r 对统计图形的支持十分有限,分组线性回归尚且做不到,echarts4r官网对此也有示例说明。
iris |>
group_by(Species) |>
e_charts(x = Sepal.Width) |>
e_scatter(serie = Sepal.Length, bind = Species, symbol_size = 10) |>
# 添加回归
e_lm(Sepal.Length ~ Sepal.Width, name = "线性回归") |>
# 设置调色板 Set2
e_color(color = RColorBrewer::brewer.pal(n = 3, name = "Set2"))
如何选择
当然,除了上面介绍的这些,还有很多可以绘制交互式图形的 R 包,如rAmCharts4、highcharter和 rbokeh 等。笔者相信以后还会有越来越多的、甚至更好的 R 包出现,但是无论静态还是动态的交互图形,使用的套路会趋同—都宣称是「图形语法」家族,比如 Python 社区的 matplotlib、plotnine 和 seaborn,阿里的G2 等,太多太多,不再一一介绍。
library(rbokeh)
figure() %>%
ly_points(Sepal.Length, Sepal.Width,
data = iris,
color = Species, glyph = Species,
hover = list(Sepal.Length, Sepal.Width)
)
rAmCharts4 和 highcharter 分别依赖商业的图形库amCharts 4和highcharts,有一定版权风险,rbokeh的维护似乎已经中断,因此,不推荐使用。
某些 R 包的接口使用起来比较复杂,或者某些高级的图形需要自定义,对于这种情况,已存在一些 R 包来填补 Gap。比如 David Hodge 开发的simplevis 包就试图简化 R 包 ggplot2 和 leaflet 的接口,这对于新手或不愿意花时间去学习的人也许是件好事,可以直接拿起锤子去锤钉子,只是遇到锤子太轻,还需归来再读书! 还有的 R 包函数接口命名很糟糕,可能在设计上存在一些问题,比如 highcharter。两句格言共勉:
There are only two hard things in Computer Science: cache invalidation and naming things.(在计算机科学里只有两件困难的事:缓存和命名。)
— Phil Karlton
hard problem needs hard thinking and hard working.
— 于利前
从官网的文档来看,apexcharter 目前支持的图形种类还比较少,不过 JavaScript 库的迭代速度向来比较快,以后应该不是大问题。最后,笔者想到的问题是图形库本身的渲染速度、跨浏览器兼容性和未来的规划,因在这些方面所知甚少,不敢班门弄斧。
Python 语言
无论是 Plotly 还是 Apache ECharts 都提供有 Python 接口,分别是plotly和pyecharts,而且星赞数量远超 R 接口,这主要是两个数量级不对等的社区差异造成的。社区庞大成熟可以推动开发自行运转,而不依赖具体的一两个人或公司,局限会少一些。对使用者来说,遇到问题可以求助的对象多一些,甚至绝大部分问题仅需放狗搜索即可解决。除了开源社区、受欢迎程度,还有一个重要的因素需要考虑,就是上下游配套工具的情况,在做内部的数据产品方面,搭配 R Shiny 还是相当不错的,笔者比较熟悉 R 语言社区的情况,同时也相信 Python 社区有很好的框架可以做。
图18 展示 Python 版 Plotly 的绘图效果,数据和图形还是一样的,鸢尾花数据集 iris 按花种类分组做散点图和线性回归,展示数据相关性,线性回归用到 statsmodels 模块。读者可以和之前的图5对比看看,主要因为笔者对 Python 不太熟悉,做的比较粗糙,可能效果不及 R 语言版本,若有读者来改进,不甚感激。
import plotly.express as px
px.scatter(
px.data.iris(),
x="sepal_width",
y="sepal_length",
color="species",
trendline="ols",
template="simple_white",
labels={
"sepal_length": "萼片长度 (cm)",
"sepal_width": "萼片宽度 (cm)",
"species": "种类",
},
title="Edgar Anderson 的鸢尾花数据",
color_discrete_sequence=px.colors.qualitative.Set2
)
环境信息
在 RStudio IDE 内编辑本文的 Rmarkdown 源文件,用 blogdown (Xie, Hill, and Thomas 2017) 构建网站,Hugo 渲染 knitr 之后的 Markdown 文件,得益于 blogdown 对 Rmarkdown 格式的支持,图、表和参考文献的交叉引用非常方便,省了不少文字编辑功夫。文中使用了多个 R 包,为方便复现本文内容,下面列出详细的环境信息:
xfun::session_info(packages = c(
"knitr", "rmarkdown", "blogdown",
"plotly", "scatterD3", "echarts4r",
"apexcharter", "ggplot2", "ggiraph"
), dependencies = FALSE)
## R version 4.2.0 (2022-04-22)
## Platform: x86_64-apple-darwin17.0 (64-bit)
## Running under: macOS Big Sur/Monterey 10.16
##
## Locale: en_US.UTF-8 / en_US.UTF-8 / en_US.UTF-8 / C / en_US.UTF-8 / en_US.UTF-8
##
## Package version:
## apexcharter_0.3.1 blogdown_1.10 echarts4r_0.4.3 ggiraph_0.8.2
## ggplot2_3.3.6 knitr_1.39 plotly_4.10.0 rmarkdown_2.14
## scatterD3_1.0.1
##
## Pandoc version: 2.18
##
## Hugo version: 0.98.0