使用管道操作符提高代码简洁性
在编写R语言代码时,有时候需要对一个变量进行一系列的运算,例如对于一个同时包含数值列和字符串列的数据框,如果要计算所有数值列之间的相关系数,一般要分两步,第一步首先筛选数据框中的数值列,第二步计算数值列之间的相关系数。
x=data.frame(x1=c(1,34,22),x2=c(4,6,29),x3=c('a','b','c'))
- 1
假设我们需要计算上数据框x中数值列的相关系数,通常有两种做法:
- 分步进行
x_num=x[,unlist(lapply(x,function(i){is.numeric(i)==TRUE}))]
cor(x_num)
- 1
- 2
## x1 x2
## x1 1.0000000 0.2262448
## x2 0.2262448 1.0000000
- 1
- 2
- 3
在分步进行的方法中,我们需要保存每一步产生的中间结果,用于下一步的计算,对于包含多步骤连续运算的计算问题,分步进行将会变得非常繁琐。
- 嵌套函数
cor(x[,unlist(lapply(x,function(i){is.numeric(i)==TRUE}))])
- 1
## x1 x2
## x1 1.0000000 0.2262448
## x2 0.2262448 1.0000000
- 1
- 2
- 3
以上展示了嵌套函数的写法,嵌套函数通过逐层向外扩充表示对数据进行从内到外的不断依次计算,通常对于一两步连续运算,嵌套函数的写法还可以接受,但是超过三步的运算,如果不断向外嵌套,对于他人阅读理解代码以及代码的整洁性是非常不理想的,此外,嵌套写法不利于代码的修改。
以上两种写法是初学者在学习R语言执行多步计算时经常采用的写法,一种更为简洁明了、便于维护的写作方式便是今天讲的管道操作符。如果有人之前用过dplyrdplyr这个程序包,想必已经对于管道操作符的高效有了一定的了解和掌握。事实上,dplyrdplyr中管道符的实现是依赖于另一个函数包magrittrmagrittr,此处主要讲解该软件包中对于管道操作符的一些常用用法。
magrittrmagrittr包含四种管道操作符”%>%”,”%T>%”,”%<>%”,”%$%”,其中最重要最常用的是第一个”%>%”操作符。管道操作符是通过一种流程式的书写方式来表达一系列依次进行的运算操作,逻辑清晰且书写简便,下面通过一系列实际例子讲解管道操作符”%>%”的使用。
管道操作符”%>%”的基本用法
library(magrittr)
x[,unlist(lapply(x,function(i){is.numeric(i)==TRUE}))]%>%cor()
- 1
- 2
## x1 x2
## x1 1.0000000 0.2262448
## x2 0.2262448 1.0000000
- 1
- 2
- 3
注意到上式中管道操作符的写法”%>%cor()”,事实上,管道操作符的实质就是将左边表达式(前一步运算)返回的结果默认地传给后续要执行的函数,因此管道操作符的实质可以用如下表达式清晰地表示为:
管道操作符:x%>%f1()%>%f2()
嵌套表达式:f2(f1(x))
分步书写: res_1=x
res_2=f1(res_1)
res_3=f2(res_2)
- 1
- 2
- 3
- 4
- 5
可以看出,使用管道操作符具有逻辑清晰,书写方便的优点,此外管道操作符的表达方式允许我们方便地增加或删除其中一步的运算。
管道操作符中‘.’号的用法
在使用管道操作符时,默认地实际上是将左边表达式的结果作为右边函数的第一个参数传入的,如果右边函数只有一个参数,那自然无需考虑,但对于右边函数有多个参数,我们并不希望表达式的结果作为第一个参数时,就需要考虑更换默认的参数位置了。如何更换呢?这就需要了解‘.’在管道操作符中的用法。‘.’表达的即是左边表达式返回的结果,请观察下例:
x[,unlist(lapply(x,function(i){is.numeric(i)==TRUE}))]%>%cor(.)
- 1
## x1 x2
## x1 1.0000000 0.2262448
## x2 0.2262448 1.0000000
- 1
- 2
- 3
注意到,在右边函数中有一个‘.’,这表示的即是左边筛选的数据框结果,对于右边函数只有一个参数或者只需要将表达式作为第一个参数传入的情形,我们可以不用显示地将这个‘.’号写出来,但对于需要更改表达式结果作为函数其他位置的参数或者需要将表达式结果的其他形式作为函数参数的情形,则需要使用‘.’号来显示地表达上一表达式返回的结果。下面通过实例分别讲解这两种情况:
– 将表达式结果作为其他位置的参数传入
假设需要通过sum函数计算一系列向量的和,通常我们会制定na.rm参数来设定是否忽略缺失值。如果我们需要根据前面表达式的结果(TRUE or FALSE)来决定是否忽略缺失值NA,便会遇到一个问题,na.rm参数并不是sum函数的第一个参数,为了正确地执行,我们需要修改表达式参数的默认位置,这一需求便可通过‘.’来实现
TRUE%>%sum(c(1,2,3,NA),na.rm = .)
- 1
## [1] 6
- 1
可以看到,通过显示地制定‘.’的位置,我们可以更改参数的默认位置,‘.’号可以在函数中出现在任意多次任意位置,表示其他参数设定需要依赖该结果。
– 将表达式结果的其他形式作为函数参数
第二种用法实际上和第一种用法是一致的,在此单一列出只是为了更清楚的展示。借助tidyr中的fill函数来说明这一用法。tidyr中的fill函数可以用来根据前一行的值填充后一行的缺失值。
x=data.frame(x1=c(1,NA,3),x2=c(12.3,34,NA))
x
- 1
- 2
## x1 x2
## 1 1 12.3
## 2 NA 34.0
## 3 3 NA
- 1
- 2
- 3
- 4
这种填充缺失值的方法对于一系列时间上连续的采样样本应用较为合适。使用tidyr时,需要指定哪一列需要填充,如果我们需要填充函数的所有列,则需要在传入所有的列名称。由于列名称是数据框的某一属性,因此此时我们的函数既需要表达式的结果(即数据框)作为函数的第一个参数,又需要表达式结果的其他属性或者变形作为函数的另外参数,这一问题的解决可以依靠‘.’号来实现。
library(tidyr)
x%>%fill(names(.))
- 1
- 2
## x1 x2
## 1 1 12.3
## 2 1 34.0
## 3 3 34.0
- 1
- 2
- 3
- 4
更为具体的,可以写成
x%>%fill(.,names(.))
- 1
## x1 x2
## 1 1 12.3
## 2 1 34.0
## 3 3 34.0
- 1
- 2
- 3
- 4
以上便是‘.’号的用法。