R语言教程-R编程9

函数进阶

函数式编程

多个数据自变量的pmap类函数

但是对于列表、数据框等多个自变量则不能自动进行向量化处理。 purrr包的pmap类函数支持对多个列表、数据框、向量等进行向量化处理。 pmap不是将多个列表等作为多个自变量, 而是将它们打包为一个列表。 所以, map2(x, y, f)用pmap()表示为pmap(list(x, y), f)。

在确知输出类型时可以用pmap_chr(), pmap_dbl()等变种, 在不需要输出结果时可以用pwalk()。

# 将三个列表中的对应项用c()函数连接
library(purrr)
x <- list(ID=101, name="李明")
y <- list(ID=102, name="张聪")
z <- list(ID=103, name="王国")
x
y
z
pmap(list(x, y, z), c)
# $ID
# [1] 101
#
# $name
# [1] "李明"
#
# $ID
# [1] 102
#
# $name
# [1] "张聪"
#
# $ID
# [1] 103
#
# $name
# [1] "王国"
#
# $ID
# NULL
#
# $name
# NULL

pmap()除了输入一个列表和要并行执行的函数以外, 也可以输入一个数据框, 对数据框的每一行执行函数。

d <- tibble::tibble(
x = 101:103,
y = c("李明", "张聪", "王美"),
z = c("男", "男", "女")
)
d
pmap_chr(d, function(x,y, z) paste(x,y,z, sep=':'))
# pmap_chr(d, function(...) paste(..., sep=':'))

也可以写为:pmap_chr(d, function(...) paste(..., sep=":"))其中的三个点代表d中的所有参数,当要写的参数较多时,可以这样使用。

pmap()和其它的map()类函数有一个区别是, 因为将输入数据打包在一个列表中, 而列表元素是有变量名的, 这样就可以将列表变量名取为要调用的函数的自变量名, 使得对输入列表中各元素的每个成员调用函数时, 可以带有对应的形参名调用。

mean()函数可以计算去掉最小、最大一部分后的平均值, mean(x, trim)用trim选项控制两端分别去掉的比例, 但是trim选项必须是标量。 用map_dbl()解决方法如下:

set.seed(4)
x1 <- rcauchy(1000)
trimss <- c(0.05, 0.1, 0.2, 0.3, 0.4)
# map_dbl(trimss, ~ mean(x=x1, trim=.x))
# trim=.x也可写为..1
map_dbl(trimss, function(x) mean(x=x1, trim=x))

可以用pmap()的列表元素名自动对应到调用函数形参名的方法:

pmap_dbl(list(trims = trimss), mean, x = x1)

或者

pmap_dbl(list(trims=trimss), ~ mean(x1))

相当于写了一个循环函数,求rcauchy(1000)函数产生的1000个随机数中,求取两端分别去掉0.05, 0.1, 0.2, 0.3, 0.4比例的数据后,求出的平均值。

pmap()的变种有ivoke_map(.f, .x, …), 其中.f是一个元素为函数名的字符型向量, .x是列表, .x的每个元素是.f列表中对应的函数的参数。 如:


创建 tibble 的另一种方法是使用 tribble() 函数,tribble 是 transposed tibble(转置 tibble) 的缩写。
tribble() 是定制化的,可以对数据按行进行编码:列标题由公式(以 ~ 开头)定义,数据条目以逗号分隔,这样就可以用易读的方式对少量数据进行布局
会加一条注释(以 # 开头的行)来明确指出标题行的位置

两种写法的对比:

写法1:tribble

tc <- tribble(
~ t1, ~ '第二列',
'学生1', 890,
'学生2', 240
)
tc

写法2:tibble

tc2 <- tibble(
t1=c('学生1', '学生2'),
'第二列'=c(890,240)
)
tc2

两种写法的结果相同。

library(tibble)
library(dplyr)
library(purrr)
sim <- tribble(
~f, ~params,
"runif", list(min = -1, max = 1),
"rnorm", list(sd = 5),
"rpois", list(lambda = 10)
)
sim %>%
# mutate==>dplyr; invoke_map==>purrr
mutate(sim = invoke_map(f, params, n = 10))
# 创建新的一列tibble数据,列名为sim,sim的每一个元素与f, params的数据对应

这里利用了tibble类型的高级功能: 某一列可以是列表类型, 然后列表元素也可以是列表、向量等复合类型。

purrr包中reduce类函数

reduce函数
purrr包的reduce函数把输入列表(或向量)的元素逐次地用给定的函数进行合并计算。

library(purrr)
# 累加
reduce(1:4, `+`)
# 等于sum(1:4)

reduce可以对元素为复杂类型的列表进行逐项合并计算。

多个集合的交集的问题。 下面的例子产生了4个集合, 然后反复调用intersect()求出了交集:

set.seed(7)
x <- replicate(4, sample(1:5, size = 5, replace = T), simplify = F)
x
# intersect为取交集
intersect(intersect(intersect(x[[1]], x[[2]]), x[[3]]), x[[4]])

与上面的结果相同:

reduce(x, intersect)

也可以用magrittr包的%>%符号写成:

x[[1]] %>% intersect(x[[2]]) %>% intersect(x[[3]]) %>% intersect(x[[4]])

还可以写成循环:

y <- x[[1]]
for (i in 2:4) y <- intersect(y, x[[i]])
y

以上这几种写法中使用reduce函数最为简单直观。
泛函的好处是需要进行的变换或计算是作为参数输入的, 只要输入其它函数就可以改变要做的计算, 比如, 变成求各个集合的并集:

reduce(x, union)

reduce()支持...参数, 所以可以给要调用的函数额外的自变量或选项。

可以用选项.init给出合并初值, 在通用程序中使用reduce()时应该提供此选项, 这样如果输入了零长度数据, 可以有一个默认的返回值; 输入非零长度数据时, 此初值作为第一个元素之前的值参与计算, 所以一定要取为与要进行的运算一致的值, 比如连加的初始值自然为0, 连乘积的初始值自然为1, 多个集合交集的初始值为全集(所有参与运算的各个集合应为此初值的子集), 等等。

reduce2函数
其中的第二个参数y可以自定义运算的方式与方法

reduce2(x, y, f)中的x是要进行连续运算的数据列表或向量, 而y是给这些运算提供不同的参数。 如果没有.init初始值, f仅需调用length(x)-1次, 所以y仅需要有length(x)-1个元素; 如果有.init初始值, f需要调用length(x)次, y也需要与x等长。

accumulate函数

对于加法, R的sum()函数可以计算连加, 而cumsum()函数可以计算逐步的连加。如:

cat('1到5累加结果:\n')
sum(1:5)
cat('-----\n', '逐步的累加结果\n', sep = '')
cumsum(1:5)

purrr::reduce()将连加推广到了其它的二元运算, purrr::accumulate()则类似cumsum()的推广。

例如,对前面例子中的4个集合, 计算逐步的并集, 结果的第一项保持原来的第一项不变:

accumulate(x, union)

将上述结果简化显示:

# 使用unique函数去除重复值,保留唯一值,使用sort函数排序
accumulate(x, union) %>%
map(~ sort(unique(.x), decreasing = T))

purrr包中使用示性函数的泛函

示性函数 : 返回逻辑向量的函数称为示性函数
R中有许多is.xxx函数都是示性函数(predicate functions)。 示性函数本身不是泛函(用函数作为参数的函数), 但是它们可以作为泛函的输入。

purrr包提供了如下的以示性函数函数为输入的泛函:

  • some(.x, .p),对数据列表或向量.x的每一个元素用.p判断, 只要至少有一个为真,结果就为真; every(.x, .p)some类似,但需要所有元素的结果都为真结果才为真。 这些函数与any(map_lgl(.x, .p))all(map_lgl(.x, .p))类似, 但是只要在遍历过程中能提前确定返回值就提前结束计算, 比如some只要遇到一个真值就不再继续判断, every只要遇到一个价值就不再继续判断。
  • detect(.x, .p)返回数据.x的元素中第一个用.p判断为真的元素值, 而detect_index(.x, .p)返回第一个为真的下标值。
  • keep(.x, .p)选取数据.x的元素中用.p判断为真的元素的子集; discard(.x, .p)返回不满足条件的元素子集。

例如,判断数据框中有无因子类型的列:

# 作为参数输入的函数只需要函数名,不用加括号
some(d.class, is.factor)

判断数据框是否完全由数值型列组成:

every(d.class, is.numeric)

返回向量中的第一个超过100的元素的值:

detect(c(1, 5, 77, 105, 99, 123), ~ . >= 100)

返回向量中的第一个超过100的元素的下标:

detect_index(c(1, 5, 77, 105, 99, 123), ~ .x >= 100)
# 返回所有满足条件的元素下标
cat('-----\n', '所有大于等于100的元素的下标:\n', sep='')
which(c(1, 5, 77, 105, 99, 123) >= 100)

下面的例子筛选出数据框的数值型列, 并用map_dbl求每列的平方和:

d.class %>% 
keep(is.numeric) %>%
map_dbl(~ sum(.^2))

从数据框(或列表)中选一部分满足某种条件的子集进行变换是常用的做法, 所以map提供了map_if()和modify_if()变种, 允许输入一个示性函数, 对满足条件的子集才应用输入的变换函数进行处理, 输入数据中其它元素原样返回。
map_if返回列表, modify_if返回与输入数据相同类型的输出。 例如, 将数据框中数值型列除以100, 其它列保持不变:

# modify_if函数的第一个参数为输入数据,第二个参数为判断条件/函数,第三个参数为对判断条件为真的数据的处理
d.class100 <- modify_if(d.class, is.numeric, ~ ./100)

也可以写为:

d.class100 <- modify_if(d.class, is.numeric,`/`, 100)

基本R的Find函数与detect作用类似, Position与detect_index作用类似, Filter函数与keep作用类似。