前言

最近写论文的时候又一次用到了R。这次我是对Java有一定程度了解后再次转向R,才真正认识到R这门语言在统计编程和数据可视化领域的优雅和快速。

首先可以看一段Java的stream代码:

redisUtils.opsForHashValues(Const.COOP_PREFIX.getInfo() + msg.getBlogId(), msg.getFromId().toString()).stream().
map(userStr -> redisUtils.readValue(userStr, UserEntityVo.class)).
filter(user -> !fromId.equals(user.getId())).
forEach(user -> {
msg.setToOne(user.getId());
rabbitTemplate.convertAndSend(
CoopRabbitConfig.WS_TOPIC_EXCHANGE,
CoopRabbitConfig.WS_BINDING_KEY + user.getServerMark(),
msg);
});

这段代码大概就是将缓存的数据读取出来,反序列化成Java的对象,然后进行筛选,然后把筛选出的每个对象做一次消息的发送。在这里"->"这个箭头就是Java的lambda表达式,它支持了Java的函数式编程。这和R有什么关系呢?

函数式编程往往是和链式调用结合的,能让代码可读性得到增强。在些R的画图案例之前,先执行这段R代码:

c('tidyverse', 'stargazer', 'plm', 'sandwich', 'lmtest', 'ggpubr', 'showtext', 'rticles',
'maps', 'see','bookdown') |>
lapply(\(pkg) {
if (system.file(package = pkg) == '') {
install.packages(pkg)
}
library(pkg, character.only = TRUE)
})

这里把lapply()这个base R的函数换成purrr::map()也是可以的(我发现Hadley Wickham似乎有某种代码洁癖喜欢重写很多本来可以将就用的api)。这段逻辑也很简单,构造一个数组,数组里是一些R的常用包,对于这些包依次迭代(依次就是lapply函数的作用),对于已经安装的就加载,没有安装的就安装后加载。这里的"|>"就是R官方native的管道符号,是最近R 4.1.0更新加入的。通过"|>"可以将数组传入到lapply函数的第一个参数位置,第一个参数由此就可以省略了。lapply函数第二个参数是一个FUN,也就是一个函数,这里直接传入了一个匿名函数,去判断系统安装这个包没。从这里也能看出R和Java的异同:Java不管是任何方法都不可能直接将一个方法作为方法的参数,只能通过匿名实现类这种方式模拟出函数式编程的效果,而R这种函数式语言当然是可以的,现在的新语言比如Golang也是可以直接往函数里传一个函数的。尽管在R4.1.0以前,"|>"一般是被magrittr这个第三方库使用"%>%"去实现,但是我相信大多数人写这段代码时都会写成:

lapply(c('tidyverse', 'stargazer', 'plm', 'sandwich', 'lmtest', 'ggpubr', 'showtext', 'rticles', 'maps', 'see','bookdown'), function(pkg) {
if (system.file(package = pkg) == '') {
install.packages(pkg)
}
library(pkg, character.only = TRUE)
showtext_auto()
})

从上面这段代码看不到任何函数式编程的感觉,或者说没有了管道符号的加持代码显得不美。不过这也是没办法的事情,因为R原生就不支持lambda风格的管道符,但是为了写一些基于base R的代码就把magrittr这个包导进来显得很狼狈。可以说R 4.1.0推出的"|>"符号会大幅度优美化这个语言。

绘图代码

使用R绘图,如果不出意外应该还是基于ggplot2去作图,除了初学者以外现在国际主流应该是不推荐使用诸如plot(data, x, y)这种函数了。在绘图前一般需要对数据进行处理,基于函数式编程当然是使用dplyr。

直接画图

例如我需要画一幅折线图,其中这张图有三条折线,三条折线都是来源与这张表的三个变量,需要三个变量随时间的变化趋势:

表格类似于这个样子:

Year HighTech HighLab HighCap
1998 48.37 32.96 29.26
1999 51.84 31.74 32.06
2000 55.57 29.61 36.55
2001 55.82 30.59 36.91

代码为:

read_csv("data/tech-lab.csv") |>
ggplot() +
geom_line(aes(Year, HighTech / 100, col = "高技术人力密集度")) +
geom_line(aes(Year, HighLab / 100, col = "高劳力密集度")) +
geom_line(aes(Year, HighCap / 100, col = "高资本密集度")) +
ylab("百分位点") +
xlab("年份") +
scale_y_continuous(labels = scales::percent) +
scale_x_continuous(breaks = seq(1998, 2021, 3)) +
labs(col = "类型")

这里我将"%>%"替换成了"|>",这两者当然是有区别的,但是现在不推荐写"%>%"了。实际上purrr官网已经不推荐写"%>%"管道符,它注定会逐渐退出历史舞台。首先通过read_csv()将磁盘的文件读入内存,然后用"|>"传递给ggplot()的第一个参数,所以ggplot()也就可以空参了。每一个geom_line()函数其实就是一个图层,熟悉ps应该了解这个概念。三个geom_line()其实就是三个图层叠加在一起。aes()中需要指定x轴和y轴放什么变量,col其实是colour的缩写(也可以是color的缩写),ylab和xlab指定了横坐标和纵坐标,scale_y_continuous和scale_x_continuous指定y轴和x轴显示规则,最终生成的图片如下:

数据处理后画图

当然很多时候我们可能希望玩的更优雅一些,能不能先加工一波数据然后再画图,其实也是一样的道理:

State Export Inport FDI Year
A国 48.37 32.96 29.26 1999
B国 51.84 31.74 32.06 2000
C国 55.57 29.61 36.55 2001
D国 55.82 30.59 36.91 2002
data |>
filter(State %in% ASEAN, !is.na(Export), !is.na(Inport), !is.na(FDI)) |>
select(State, Export, Inport, FDI, Year) |>
group_by(Year) |>
mutate(expSum = sum(Export), inpSum = sum(Inport), fdiSum = sum(FDI)) |>
ggplot() +
geom_line(aes(Year, expSum, col = '进口')) +
geom_line(aes(Year, inpSum, col = '出口')) +
geom_line(aes(Year, fdiSum, col = '投资')) +
ylab("数额(千美元)") +
xlab("年份") +
scale_x_continuous(breaks = seq(1981, 2019, 3)) +
labs(col = "地区")

这里希望画一幅东协国家进口、出口和投资的数额随年份变化的时间序列图。于是首先将数据data使用"|>"传入到filter()的第一个参数,后面的参数就指定筛选规则:国家在东协且三个核心变量不为缺失值。然后选取State, Export, Inport, FDI, Year这几列,其他列在这幅图不考虑,然后根据年份group_by()分组,然后使用mutate()将每年的总进口、出口、和投资生成三个新的变量列。这一步做完,其实新列每个国家都是相同的,理论上我们只需要挑选出东协任意国再进行绘图。但是ggplot不需要显式处理这步,它自己会帮你做了。接下来将数据传下去让ggplot()绘图。这里看出每一步都使用"|>"的效果就是每一个函数的第一个参数都不需要写,非常的优雅。图片如下:

画个地图

有时候也希望玩一些高级操作,比如画个地图。画地图有个坑就是地图本质是依赖经纬度数据,一定要注意这个地图的经纬度数据有没有问题(一些众所周知的担忧)。

world <- map_data("world") |>
filter(region != "Antarctica")
data1993 <- data |>
filter(Year == 1993, !is.na(Export)) |>
select(State, Export, Year) |>
mutate(百分比 = (Export / sum(Export)) * 100) data2016 <- data |>
filter(Year == 2016, !is.na(Export)) |>
select(State, Export, Year) |>
mutate(百分比 = (Export / sum(Export)) * 100) data2016 <- data2016 |>
filter(data2016$State %in% data1993$State) data1993 <- data1993 |>
filter(data1993$State %in% data2016$State) suppressMessages(bind_cols(data1993$State, data2016$百分比 - data1993$百分比)) |>
rename(地区 = ...1, 百分比变动 = ...2) |>
ggplot(aes(map_id = 地区)) +
geom_map(aes(fill = 百分比变动), map = world) +
scale_fill_distiller(palette = "Set2",direction= 1) +
expand_limits(x = world$long, y = world$lat) +
xlab("经度") +
ylab("纬度")

这里做的并不像之前的图那么优雅,因为这个图的数据复杂一些。map_data("world")这个数据对于中国边界的经纬度数据是由一些问题的,如果要出版的话要尤为注意:

总结

由此,在R 4.1.0推出原生管道符以后,R的语法实际上已经无懈可击了。正如GraalVM也支持R一样可以看出顶级开发者对R的重视。R若仅仅是画图功能也不显得出众,结合R Markdown以后才是极大提升了生产力。我比较喜欢这种布局,将各个章节分散在不同的.rmd文件中:

---
title: ''
author:
- C
header-includes:
- \usepackage{lscape}
- \usepackage{ctex}
papersize: 'a4'
geometry: 'margin=1.75in'
keywords:
- A
- B
indent: true
output:
bookdown::pdf_document2:
latex_engine: xelatex
fig_caption: yes
number_sections: yes
toc_depth: 2
toc: yes
bibliography: bibliography.bib
--- ```{r, include=FALSE}
c('tidyverse',
'stargazer',
'plm',
'sandwich',
'lmtest',
'ggpubr',
'showtext',
'rticles',
'maps',
'see',
'bookdown') |>
lapply(\(pkg) {
if (system.file(package = pkg) == '') {
install.packages(pkg)
}
library(pkg, character.only = TRUE)
})
showtext_auto()
data <- read_csv('data/data.csv')
ASEAN <- c('Malaysia', 'Indonesia', 'Thailand', 'Philippines', 'Singapore', 'Vietnam', 'Brunei', 'Laos', 'Myanmar', 'Cambodia')
\``` ```{r child = ' 00-abstract.Rmd '}
\``` \newpage
```{r child = ' 01-intro.Rmd '}
\``` \newpage
```{r child = ' 02-review.Rmd '}
\``` \newpage
# 参考文献

最新文章

  1. Discuz 网站移至 Ubuntu 14.04.4 LTS VPS 配置
  2. Vmware快速安装linux虚拟机(SUSE)
  3. chromiun 学习《一》
  4. DWZ的选择带回功能无法带回第一个value中的值
  5. virutalbox虚拟机硬盘扩容
  6. C++实现的哈希搜索
  7. Java泛型:类型擦除
  8. Hbiernate关联排序问题
  9. 本地存储和cookies之间的区别
  10. Oracle EBS BOM模块常用表结构
  11. 自己动手用原生实现 bind/call/apply
  12. 点评cat系列-服务器开发环境部署
  13. psql 存储过程
  14. 【cf849ABC】
  15. 20155201 网络攻防技术 实验八 Web基础
  16. P3911 最小公倍数之和
  17. pbft流程深层分析和解释(转)
  18. Verilog笔记.三段式状态机
  19. 解决ASP.NET MVC4中使用Html.DropDownListFor显示枚举值默认项问题
  20. php 类继承

热门文章

  1. andriod升级保错问题归类
  2. Installing Jupyter
  3. vue 中 表单数据为数组,v-for 循环表单数据
  4. jdk下载及配置
  5. 2021 icpc 沈阳 I 【分式线性变换的保交比性】
  6. allure环境配置生成测试报告
  7. vue+elementUI 在表格中动态增加与删除行
  8. JSONObject和JSONArray的区别
  9. GraalVM, Native Image, Java on Truffle, LLVM runtime, WebAssembly, JavaScript and Node.js关系是什么
  10. gin websocket