purrr

purrr包是Hadley Wickham大神编写的高级函数编程语言包,它提供了大量的类map函数,使用函数式编程的思想,致力于减少R中的循环操作

map族函数

单参数计算

map族函数输入的数据类型是向量,函数将向量中的每一个元素带入运算,返回的也是一个具有同样长度、同样变量名的向量,返回的向量类型由函数的后缀决定。

map() # 返回一个列表(list) map_lgl() # 返回一个逻辑型向量 map_int() # 返回一个整数型向量 map_dbl() # 返回双精度数值向量 map_chr() # 返回字符串向量 map_dfc() # 返回数据框(column bind) map_dfr() # 返回数据框(row bind)

多参数计算

purrr中的map族函数提供了较apply族函数更为友好的数据输入形式,便于进行多参数操作

map() # Apply a function to each element (类似于apply的操作) map2() # Apply a function to pairs of elements (这个是2个输入参数的函数) pmap() # Apply a function to groups of elements (多参数形式) lmap() # Apply function to each list-element of a list or vector (类似于lapply) imap() # Apply .f to each element of a list or vector and its index. (对索引和内容同时操作) invoke_map() # Run each function in a list (每个元素使用不同的函数)

这些函数使用的示意图如下所示:

purrr_1.png

walk族函数

当我们使用函数的目的是向屏幕提供输出或将文件保存到磁盘——重要的是操作过程而不是返回值,我们应该使用游走函数,而不是map函数。

比如:

  1. x = list(1, "a", 3)
  2. x %>%
  3. walk(print)
  4. #> [1] 1
  5. #> [1] "a"
  6. #> [1] 3

一般来说,walk()函数不如walk2()pwalk()实用。例如有一个图形列表和一个文件名向量,那么我们就可以使用pwalk()将每个文件保存到相应的磁盘位置:

  1. library(ggplot2)
  2. plots = mtcars %>%
  3. split(.$cyl) %>%
  4. map(~ggplot(., aes(mpg, wt)) + geom_point())
  5. paths = stringr::str_c(names(plots), ".pdf")
  6. pwalk(list(paths, plots), ggsave, path = tempdir())
  7. #> Saving 7 x 5 in image
  8. #> Saving 7 x 5 in image
  9. #> Saving 7 x 5 in image

list的处理操作

FILTER LISTS

pluckchuck

pluckchuck可以代替繁琐的[[]]操作,使list元素的选取更加简洁

  1. > obj1 <- list("a", list(1, elt = "foo"))
  2. > obj2 <- list("b", list(2, elt = "bar"))
  3. > x <- list(obj1, obj2)
  4. > x
  5. [[1]]
  6. [[1]][[1]]
  7. [1] "a"
  8. [[1]][[2]]
  9. [[1]][[2]][[1]]
  10. [1] 1
  11. [[1]][[2]]$elt
  12. [1] "foo"
  13. [[2]]
  14. [[2]][[1]]
  15. [1] "b"
  16. [[2]][[2]]
  17. [[2]][[2]][[1]]
  18. [1] 2
  19. [[2]][[2]]$elt
  20. [1] "bar"
  21. > pluck(x, 1)
  22. [[1]]
  23. [1] "a"
  24. [[2]]
  25. [[2]][[1]]
  26. [1] 1
  27. [[2]]$elt
  28. [1] "foo"
  29. > pluck(x, 1, 2)
  30. [[1]]
  31. [1] 1
  32. $elt
  33. [1] "foo"
  34. > pluck(x, 1, 2, "elt")
  35. [1] "foo"
  36. > chuck(x, 1)
  37. [[1]]
  38. [1] "a"
  39. [[2]]
  40. [[2]][[1]]
  41. [1] 1
  42. [[2]]$elt
  43. [1] "foo"
  44. > chuck(x, 1)
  45. [[1]]
  46. [1] "a"
  47. [[2]]
  48. [[2]][[1]]
  49. [1] 1
  50. [[2]]$elt
  51. [1] "foo"

pluck和chuck的区别:当选取的元素不存在时,pluck返回NULL,而chuck会报错

条件选取

keep函数选取符合某一条件的元素
discard函数相反,选取不符合某一条件的元素
compact函数则去掉所有为空的元素

  1. > x <- list(1,2,3) %>% keep(function(x)(x>2))
  2. > x
  3. [[1]]
  4. [1] 3
  5. > x <- list(1,2,3) %>% discard(function(x)(x>2))
  6. > x
  7. [[1]]
  8. [1] 1
  9. [[2]]
  10. [1] 2
  11. > x <- list(1,2,3,NULL) %>% compact()
  12. > x
  13. [[1]]
  14. [1] 1
  15. [[2]]
  16. [1] 2
  17. [[3]]
  18. [1] 3

head_whiletail_while

  1. > pos <- function(x) x >= 0
  2. > head_while(5:-5, pos)
  3. [1] 5 4 3 2 1 0
  4. > pos <- function(x) x < 0
  5. > tail_while(5:-5, pos)
  6. [1] -1 -2 -3 -4 -5

SUMMARISE LISTS

everysome

判断元素是否满足条件

  1. > y <- list(0:10, 5.5)
  2. > y %>% every(is.numeric)
  3. [1] TRUE
  4. > y %>% every(is.integer)
  5. [1] FALSE
  6. > y %>% some(is.integer)
  7. [1] TRUE

has_element

判断是否包含某一对象

  1. > x <- list(1:10, 5, 9.9)
  2. > x %>% has_element(1:10)
  3. [1] TRUE
  4. > x %>% has_element(3)
  5. [1] FALSE

detectdetect_index

寻找匹配的第一个元素

  1. > is_even <- function(x) x %% 2 == 0
  2. > 3:10 %>% detect(is_even)
  3. [1] 4
  4. > 3:10 %>% detect_index(is_even)
  5. [1] 2

vec_depth

计算list的深度

  1. > x <- list(
  2. + list(),
  3. + list(list()),
  4. + list(list(list(1)))
  5. + )
  6. > vec_depth(x)
  7. [1] 5
  8. > x %>% map_int(vec_depth)
  9. [1] 1 2 4

TRANSFORM LISTS

modify

modify同样也是对列表或者向量中的元素进行函数运算,但是与map家族函数相比存在一些区别:

map家族函数往往返回一个特定类型的对象(数值,逻辑) modify函数返回的对象与输入对象的类型相一致 也就是说,modify是“真正”的对输入对象进行计算操作

  1. > iris %>%
  2. modify_if(is.factor, as.character) %>%
  3. str()
  4. 'data.frame': 150 obs. of 5 variables:
  5. $ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
  6. $ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
  7. $ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
  8. $ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
  9. $ Species : chr "setosa" "setosa" "setosa" "setosa" ...

modify函数的变形与map家族函数的使用是类似的

RESHAPE LISTS

flatten

去除list的一层层级关系,相当于将list压缩扁平化,但是得到的对象的类型是稳定可预测的(可以自己设置结果的类型)

  1. > x <- rerun(2, sample(3))
  2. > x
  3. [[1]]
  4. [1] 2 1 3
  5. [[2]]
  6. [1] 3 1 2
  7. > x %>% flatten()
  8. [[1]]
  9. [1] 2
  10. [[2]]
  11. [1] 1
  12. [[3]]
  13. [1] 3
  14. [[4]]
  15. [1] 3
  16. [[5]]
  17. [1] 1
  18. [[6]]
  19. [1] 2
  20. > x %>% flatten_int()
  21. [1] 2 1 3 3 1 2

transpose

置换列表的索引顺序,其实就是将二级索引和一级索引的层级调换

  1. > x <- rerun(3, x = runif(1), y = runif(3))
  2. > x
  3. [[1]]
  4. [[1]]$x
  5. [1] 0.9489699
  6. [[1]]$y
  7. [1] 0.09894665 0.34629673 0.61291285
  8. [[2]]
  9. [[2]]$x
  10. [1] 0.9344982
  11. [[2]]$y
  12. [1] 0.2195651 0.5119154 0.9527276
  13. [[3]]
  14. [[3]]$x
  15. [1] 0.730148
  16. [[3]]$y
  17. [1] 0.3331638 0.9099907 0.4129223
  18. > transpose(x)
  19. $x
  20. $x[[1]]
  21. [1] 0.9489699
  22. $x[[2]]
  23. [1] 0.9344982
  24. $x[[3]]
  25. [1] 0.730148
  26. $y
  27. $y[[1]]
  28. [1] 0.09894665 0.34629673 0.61291285
  29. $y[[2]]
  30. [1] 0.2195651 0.5119154 0.9527276
  31. $y[[3]]
  32. [1] 0.3331638 0.9099907 0.4129223

JOIN (TO) LISTS

prepend

appendsplice函数共同实现list的合并

  1. > x <- as.list(1:2)
  2. > x %>% append("a")
  3. [[1]]
  4. [1] 1
  5. [[2]]
  6. [1] 2
  7. [[3]]
  8. [1] "a"
  9. > x %>% prepend("a")
  10. [[1]]
  11. [1] "a"
  12. [[2]]
  13. [1] 1
  14. [[3]]
  15. [1] 2
  16. > x %>% prepend(list("a", "b"), before = 2)
  17. [[1]]
  18. [1] 1
  19. [[2]]
  20. [1] "a"
  21. [[3]]
  22. [1] "b"
  23. [[4]]
  24. [1] 2
  25. > splice(x, "a", "b")
  26. [[1]]
  27. [1] 1
  28. [[2]]
  29. [1] 2
  30. [[3]]
  31. [1] "a"
  32. [[4]]
  33. [1] "b"

WORK WITH LISTS

array_brancharray_tree

这两个函数的用法相同,会强制把数组转化成list

  1. x <- array(1:12, c(2, 2, 3))
  2. > array_branch(x, 2)
  3. [[1]]
  4. [,1] [,2] [,3]
  5. [1,] 1 5 9
  6. [2,] 2 6 10
  7. [[2]]
  8. [,1] [,2] [,3]
  9. [1,] 3 7 11
  10. [2,] 4 8 12
  11. > array_branch(x, 3)
  12. [[1]]
  13. [,1] [,2]
  14. [1,] 1 3
  15. [2,] 2 4
  16. [[2]]
  17. [,1] [,2]
  18. [1,] 5 7
  19. [2,] 6 8
  20. [[3]]
  21. [,1] [,2]
  22. [1,] 9 11
  23. [2,] 10 12

总结

purrr包作为tidyverse的核心R包之一,提供了一些更加强大的编程工具,优化函数式编程的操作,对于数据分析的高效快速处理是十分重要的。

purrr_2.png
purrr_3.png