第 20 章 函数式编程1

20.1 简单回顾

        list(a = 1, b = "a")   # list
c(a = 1, b = 2)     # named vector
data.frame(a = 1, b = 2)     # data frame
tibble(a = 1, b = 2)     # tibble

20.2 向量化运算

a <- c(2, 4, 3, 1, 5, 7)

for()循环，让向量的每个元素乘以2

for (i in 1:length(a)) {
print(a[i] * 2)
}
## [1] 4
## [1] 8
## [1] 6
## [1] 2
## [1] 10
## [1] 14

a * 2
## [1]  4  8  6  2 10 14

for (i in 1:length(a)) {
if (a[i] > 2)
print(a[i])
}
## [1] 4
## [1] 3
## [1] 5
## [1] 7

a[a > 2]
## [1] 4 3 5 7

20.3 多说说列表

a_list <- list(
num = c(8, 9),
log = TRUE,
cha = c("a", "b", "c")
)
a_list
## $num ## [1] 8 9 ## ##$log
## [1] TRUE
##
## $cha ## [1] "a" "b" "c" 要想访问某个元素，可以这样 a_list["num"] ##$num
## [1] 8 9

a_list$num ## [1] 8 9 在tidyverse里，还可以用 a_list %>% pluck(1) ## [1] 8 9 或者 a_list %>% pluck("num") ## [1] 8 9 20.4 列表 vs 向量 假定一向量 v <- c(-2, -1, 0, 1, 2) v ## [1] -2 -1 0 1 2 我们对元素分别取绝对值 abs(v) ## [1] 2 1 0 1 2 如果是列表形式，abs函数应用到列表中就会报错 lst <- list(-2, -1, 0, 1, 2) abs(lst) ## Error in abs(lst): non-numeric argument to mathematical function 报错了。用在向量的函数用在list上，往往行不通。 再来一个例子：我们模拟了5个学生的10次考试的成绩 exams <- list( student1 = round(runif(10, 50, 100)), student2 = round(runif(10, 50, 100)), student3 = round(runif(10, 50, 100)), student4 = round(runif(10, 50, 100)), student5 = round(runif(10, 50, 100)) ) exams ##$student1
##  [1] 62 81 73 95 79 69 86 95 67 54
##
## $student2 ## [1] 63 60 65 52 78 52 73 80 72 82 ## ##$student3
##  [1] 61 96 91 69 92 75 91 72 85 84
##
## $student4 ## [1] 58 57 60 91 55 94 99 99 71 98 ## ##$student5
##  [1] 90 61 58 82 65 55 50 87 76 87

mean(exams)
## [1] NA

?mean()

list(
student1 = mean(exams$student1), student2 = mean(exams$student2),
student3 = mean(exams$student3), student4 = mean(exams$student4),
student5 = mean(exams$student5) ) ##$student1
## [1] 76.1
##
## $student2 ## [1] 67.7 ## ##$student3
## [1] 81.6
##
## $student4 ## [1] 78.2 ## ##$student5
## [1] 71.1

20.5 purrr

map(exams, mean)
## $student1 ## [1] 76.1 ## ##$student2
## [1] 67.7
##
## $student3 ## [1] 81.6 ## ##$student4
## [1] 78.2
##
## $student5 ## [1] 71.1 哇，短短一句话，得出了相同的结果。 20.5.1 map函数 map()函数的第一个参数是list或者vector， 第二个参数是函数 函数 f 应用到list/vector 的每个元素 于是输入的 list/vector 中的每个元素，都对应一个输出 最后，所有的输出元素，聚合成一个新的list 整个过程，可以想象 list/vector 是生产线上的盒子，依次将里面的元素，送入加工机器。 函数决定了机器该如何处理每个元素，机器依次处理完毕后，结果打包成list，最后送出机器。 在我们这个例子，mean() 作用到每个学生的成绩向量， 调用一次mean(), 返回一个数值，所以最终的结果是五个数值的列表。 map(exams, mean) ##$student1
## [1] 76.1
##
## $student2 ## [1] 67.7 ## ##$student3
## [1] 81.6
##
## $student4 ## [1] 78.2 ## ##$student5
## [1] 71.1

exams %>% map(mean)
## $student1 ## [1] 76.1 ## ##$student2
## [1] 67.7
##
## $student3 ## [1] 81.6 ## ##$student4
## [1] 78.2
##
## $student5 ## [1] 71.1 20.5.2 map函数家族 如果希望返回的是数值型的向量，可以这样写map_dbl() exams %>% map_dbl(mean) ## student1 student2 student3 student4 student5 ## 76.1 67.7 81.6 78.2 71.1 map_dbl()要求每个输出的元素必须是数值型 如果每个元素是数值型，map_dbl()会聚合所有元素构成一个原子型向量 如果希望返回的结果是数据框 exams %>% map_df(mean) ## # A tibble: 1 × 5 ## student1 student2 student3 student4 student5 ## <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 76.1 67.7 81.6 78.2 71.1 是不是很酷？是不是很灵活 20.5.3 小结 事实上，map函数 • 第一个参数是向量或列表（数据框是列表的一种特殊形式，因此数据框也是可以的） • 第二个参数是函数，这个函数会应用到列表的每一个元素，比如这里map函数执行过程如下 ： 具体为，exams有5个元素，一个元素装着一个学生的10次考试成绩， 运行map(exams, mean)函数后， 首先取出exams第一个元素exams$student1(它是向量)，然后执行 mean(exams$student1), 然后将计算结果存放在列表result中的第一个位置result1上； 做完第一个学生的，紧接着取出exams第二个元素exams$student2，执行 mean(exams$student2), 然后将计算结果存放在列表result中的第一个位置result2上； 如此这般，直到所有学生都处理完毕。我们得到了最终结果—一个新的列表result 当然，我们也可以根据需要，让map返回我们需要的数据格式, purrr也提供了方便的函数，具体如下 我们将mean函数换成求方差var函数试试， exams %>% map_df(var) ## # A tibble: 1 × 5 ## student1 student2 student3 student4 student5 ## <dbl> <dbl> <dbl> <dbl> <dbl> ## 1 186. 121. 136. 383. 225. 20.5.4 额外参数 将每位同学的成绩排序，默认的是升序。 map(exams, sort) ##$student1
##  [1] 54 62 67 69 73 79 81 86 95 95
##
## $student2 ## [1] 52 52 60 63 65 72 73 78 80 82 ## ##$student3
##  [1] 61 69 72 75 84 85 91 91 92 96
##
## $student4 ## [1] 55 57 58 60 71 91 94 98 99 99 ## ##$student5
##  [1] 50 55 58 61 65 76 82 87 87 90

sort(exams$student1, decreasing = TRUE) ## [1] 95 95 86 81 79 73 69 67 62 54 map很人性化，可以让函数的参数直接跟随在函数名之和 map(exams, sort, decreasing = TRUE) ##$student1
##  [1] 95 95 86 81 79 73 69 67 62 54
##
## $student2 ## [1] 82 80 78 73 72 65 63 60 52 52 ## ##$student3
##  [1] 96 92 91 91 85 84 75 72 69 61
##
## $student4 ## [1] 99 99 98 94 91 71 60 58 57 55 ## ##$student5
##  [1] 90 87 87 82 76 65 61 58 55 50

20.5.5 匿名函数

my_fun <- function(x){
x - mean(x)
}

exams %>% map_df(my_fun)
## # A tibble: 10 × 5
##   student1 student2 student3 student4 student5
##      <dbl>    <dbl>    <dbl>    <dbl>    <dbl>
## 1   -14.1     -4.7    -20.6     -20.2    18.9
## 2     4.90    -7.7     14.4     -21.2   -10.1
## 3    -3.10    -2.70     9.4     -18.2   -13.1
## 4    18.9    -15.7    -12.6      12.8    10.9
## 5     2.90    10.3     10.4     -23.2    -6.10
## 6    -7.10   -15.7     -6.60     15.8   -16.1
## # … with 4 more rows

function(x) x - mean(x)

exams %>%
map(function(x) x - mean(x))
## $student1 ## [1] -14.1 4.9 -3.1 18.9 2.9 -7.1 9.9 18.9 ## [9] -9.1 -22.1 ## ##$student2
##  [1]  -4.7  -7.7  -2.7 -15.7  10.3 -15.7   5.3  12.3
##  [9]   4.3  14.3
##
## $student3 ## [1] -20.6 14.4 9.4 -12.6 10.4 -6.6 9.4 -9.6 ## [9] 3.4 2.4 ## ##$student4
##  [1] -20.2 -21.2 -18.2  12.8 -23.2  15.8  20.8  20.8
##  [9]  -7.2  19.8
##
## $student5 ## [1] 18.9 -10.1 -13.1 10.9 -6.1 -16.1 -21.1 15.9 ## [9] 4.9 15.9 还可以更加偷懒，用~代替function()，但代价是参数必须是规定的写法，比如.x exams %>% map(~ .x - mean(.x)) ##$student1
##  [1] -14.1   4.9  -3.1  18.9   2.9  -7.1   9.9  18.9
##  [9]  -9.1 -22.1
##
## $student2 ## [1] -4.7 -7.7 -2.7 -15.7 10.3 -15.7 5.3 12.3 ## [9] 4.3 14.3 ## ##$student3
##  [1] -20.6  14.4   9.4 -12.6  10.4  -6.6   9.4  -9.6
##  [9]   3.4   2.4
##
## $student4 ## [1] -20.2 -21.2 -18.2 12.8 -23.2 15.8 20.8 20.8 ## [9] -7.2 19.8 ## ##$student5
##  [1]  18.9 -10.1 -13.1  10.9  -6.1 -16.1 -21.1  15.9
##  [9]   4.9  15.9

exams %>% map(~ . - mean(.))
## $student1 ## [1] -14.1 4.9 -3.1 18.9 2.9 -7.1 9.9 18.9 ## [9] -9.1 -22.1 ## ##$student2
##  [1]  -4.7  -7.7  -2.7 -15.7  10.3 -15.7   5.3  12.3
##  [9]   4.3  14.3
##
## $student3 ## [1] -20.6 14.4 9.4 -12.6 10.4 -6.6 9.4 -9.6 ## [9] 3.4 2.4 ## ##$student4
##  [1] -20.2 -21.2 -18.2  12.8 -23.2  15.8  20.8  20.8
##  [9]  -7.2  19.8
##
## \$student5
##  [1]  18.9 -10.1 -13.1  10.9  -6.1 -16.1 -21.1  15.9
##  [9]   4.9  15.9

~ 告诉 map() 后面跟随的是一个匿名函数，. 对应函数的参数，可以认为是一个占位符，等待传送带的student1、student2到student5 依次传递到函数机器。

exams %>%
map_int(~ length(.[. > 80]))
## student1 student2 student3 student4 student5
##        4        1        6        5        4

• 直接传递
map(.x, mean, na.rm = TRUE)
• 匿名函数
map(.x,
funciton(.x) {
mean(.x, na.rm = TRUE)
}
)
• 使用 ~
function(.x) {
.x *2
}
# Equivalent
~ .x * 2
map(.x,
~ mean(.x, na.rm = TRUE)
)

function(x)  x^2 + 5

~ .x^2 + 5

~ .^2 + 5

:::

20.6 在dplyr函数中的运用map

20.6.1 在Tibble中

Tibble本质上是向量构成的列表，因此tibble也适用map。假定有tibble如下

tb <-
tibble(
col_1 = c(1, 2, 3),
col_2 = c(100, 200, 300),
col_3 = c(0.1, 0.2, 0.3)
)

map()中的函数f，可以作用到每一列

map_dbl(tb, median)
## col_1 col_2 col_3
##   2.0 200.0   0.2

palmerpenguins::penguins %>%
map_int(~ sum(is.na(.)))
##           species            island    bill_length_mm
##                 0                 0                 2
##     bill_depth_mm flipper_length_mm       body_mass_g
##                 2                 2                 2
##               sex              year
##                11                 0

20.6.2 在col-column中

tibble(
x = list(1, 2:3, 4:6)
) %>%
mutate(l = purrr::map_int(x, length))
## # A tibble: 3 × 2
##   x             l
##   <list>    <int>
## 1 <dbl [1]>     1
## 2 <int [2]>     2
## 3 <int [3]>     3

tibble(
x = c(3, 5, 6)
) %>%
mutate(r = purrr::map(x, ~rnorm(.x, mean = 0, sd = 1)))
## # A tibble: 3 × 2
##       x r
##   <dbl> <list>
## 1     3 <dbl [3]>
## 2     5 <dbl [5]>
## 3     6 <dbl [6]>

mtcars %>%
group_by(cyl) %>%
nest() %>%
mutate(model = purrr::map(data, ~ lm(mpg ~ wt, data = .))) %>%
mutate(result = purrr::map(model, ~ broom::tidy(.))) %>%
unnest(result)
## # A tibble: 6 × 8
## # Groups:   cyl [3]
##     cyl data     model  term        estimate std.error
##   <dbl> <list>   <list> <chr>          <dbl>     <dbl>
## 1     6 <tibble> <lm>   (Intercept)    28.4      4.18
## 2     6 <tibble> <lm>   wt             -2.78     1.33
## 3     4 <tibble> <lm>   (Intercept)    39.6      4.35
## 4     4 <tibble> <lm>   wt             -5.65     1.85
## 5     8 <tibble> <lm>   (Intercept)    23.9      3.01
## 6     8 <tibble> <lm>   wt             -2.19     0.739
## # … with 2 more variables: statistic <dbl>,
## #   p.value <dbl>

20.7 延伸阅读

2、看手册?purrr::modify()， 思考下它与map()的区别

exams %>% map(~ . - mean(.))

exams %>% modify(~ . - mean(.))
exams %>% as_tibble() %>% map(~ . - mean(.))

exams %>% as_tibble() %>% modify(~ . - mean(.))

3、他们的区别哪里？函数能否互换？

mtcars %>% map_chr(typeof)
mtcars %>% map_lgl(is.double)
mtcars %>% map_int(n_unique)
mtcars %>% map_dbl(mean)

4、练习