搜索
查看: 672|回复: 0

[R] 慧美——R for data science 第16章 使用purrr实现迭代

[复制链接]

39

主题

40

帖子

289

积分

中级会员

Rank: 3Rank: 3

积分
289
发表于 2018-11-29 14:36:49 | 显示全部楼层 |阅读模式
R for data science   第16章  使用purrr实现迭代
16.1 简介
  • 减少重复代码主要有 3 个好处。
    • 更容易看清代码的意图
    • 更容易对需求变化作出反应。当要修改代码时,只需要在一处进行修改即可,无须对所 有复制粘贴的代码都进行修改。
    • 更容易减少程序 bug,因为每行代码都被多次使用。

  • 减少重复代码
    • 函数是减少重复代码的一种工具的一种工具,其减少重复代码的方法是,先识别出代码中的重复模式,然后将其提取出来,成为更容易修改和重用的独立部分。
    • 另一种工具是迭代,在于可以对多个输入执行同一种处理,比如对多个列或多个数据集进行同样的操作

  • 本章将介绍两种重要的迭代方式:命令式编程和函数式编程。
    • 对于命令式编 程,我们将介绍 for 循环和 while 循环
    • 函数式编程则可以将这些重复代码提取出来,使每个普通的 for 循环都可以通过 函数来完成。一旦掌握函数式编程的使用方法,那么你就可以通过更少的代码和更容易的 方式解决很多常见的迭代问题,出错的概率也会更小


16.2 for循环
  • 每个 for 循环都包括 3 个部分。
    • 输出:
      • output <- vector("double", length(x)) 在开始循环前,你必须为输出结果分配足够的空间。这对循环效率非常重要,如果在每 次迭代中都使用 c() 来保存循环的结果,那么 for 循环的速度就会特别慢
      • 创建给定长度的空向量的一般方法是使用 vector() 函数,该函数有两个参数:向量类型("logical"、"integer"、"double"、"character" 等)和向量的长度。

    • 序列:i in seq_along(df)
      • 这部分确定了使用哪些值来进行循环:每一轮 for 循环都会赋予 i 一个来自于 seq_along(df) 的不同的值。

    • 循环体utput[] <- median(df[])
      • 这部分就是执行具体操作的代码。它们会重复运行,每次运行都使用一个不同的 i值。第一次迭代运行的是output[[1]] <- median(df[[1]]),第二次迭代运行的是output[[2]] <- median[[2]]



16.3 for循环的变体
  • 在基础 for 循环之上有 4 种变体。
    • 修改现有对象,而不是创建新对象。
      • 可以使用类似的循环来修改列表或数据框,要记住使用 [[,而不是 [。所有 for 循环中使用的都是 [[。

    • 使用名称或值进行迭代,而不是使用索引。
    • 处理未知长度的输出。
    • 处理未知长度的序列。

  • 对向量进行循环的基本方式有3种
    • 通过for (i in seq_along(xs))使用数值索引进行循环,并使用x[]提取出相应的值。
    • 使用元素进行循环:for (x in xs)
    • 使用名称进行循环:for (nm in names(xs))。这种方式会给出一个名称,你可以使用这 个名称和 x[[nm]] 来访问元素的值。如果想要在图表标题或文件名中使用元素名称,那 么你就应该使用这种方式。

  • 如果想要创建命名的输出向量,请一定按照如下方式进行命名
    results<-vector("list", length(x))
    names(results) <-names(x)
  • 使用数值索引进行循环是最常用的方式,因为给定位置后,就可以提取出元素的名称和值
    for(iinseq_along(x)) {
          name<-names(x)[]
          value<-x[]
    }
  • 使用了 unlist() 函数将一个向量列表转换为单个向量。
    • 生成一个很长的字符串。
      • 不要使用 paste() 函数将每次迭代的结果与上一 次连接起来,而应该将每次迭代结果保存在字符向量中,然后再使用 paste(output, collapse = "") 将这个字符向量组合成一个字符串。

    • 生成一个很大的数据框。
      • 不要在每次迭代中依次使用 rbind() 函数,而应该将 每次迭代结果保存在列表中,再使用 dplyr::bind_rows(output) 将结果组合成数据框。


  • 未知的序列长度
    • 不知道输入序列的长度应该使用 while 循环。
    • while 循环只需要 2 个部分:条件和循环体
      while(condition) {
         # 循环体
      }
    • while 循环也比 for 循环更常用,因为任何 for 循环都可以使用 while 循环重新实现,但不是所有 while 循环都能使用 for 循环重新实现
      for(iinseq_along(x)) {
         # 循环体
      }
      # 等价于
      i<-1
      while(i<=length(x)) {
      # 循环体
      i<-i+1}

16.4 for循环与函数式编程
  • 使用 purrr 函数代替 for 循环的目的是将常见的列表处理问题分解为独立的几个部分。
    • 对于列表中的单个元素,你能找到解决问题的方法吗?如果找到了解决方法,那么你就可以使用 purrr 将这种方法扩展到列表中的所有元素。
    • 如果你面临的是一个非常复杂的问题,那么如何将其分解为几个可行的子问题,然后循 序渐进地解决,直至完成最终的解决方案?使用 purrr,你可以解决很多子问题,然后再 通过管道操作将这些问题的结果组合起来。


16.5 映射函数
  • 先对向量进行循环、然后对其每个元素进行一番处理,最后保存结果。这种模式太普遍 了,因此 purrr 包提供了一个函数族来替你完成这种操作。每种类型的输出都有一个相应 的函数:
    • map() 用于输出列表;
    • map_lgl() 用于输出逻辑型向量;
    • map_int() 用于输出整型向量;
    • map_dbl() 用于输出双精度型向量;
    • map_chr() 用于输出字符型向量。

  • 每个函数都使用一个向量作为输入,并对向量的每个元素应用一个函数,然后返回和输入 向量同样长度(同样名称)的一个新向量。向量的类型由映射函数的后缀决定。
  • R基础包
    • lapply() 函数与 map 函数的功能基本相同,差别在于 map() 函数与 purrr 包中的其他函 数是一致的,而且可以对 .f 使用快捷方式。
    • R 基础包中的 sapply() 函数是对 lapply() 的包装,可以自动简化输出。
    • vapply() 函数是 sapply() 的一种安全替代方式,因为前者可以提供额外的参数来定义类型。vapply() 的唯一缺点是输入量较大:vapply(df, is.numeric, logical(1)) 等价于 map_lgl(df, is.numeric)。vapply() 胜于 purrr 中的映射函数的一点是,它可以生成矩阵,而映射函数只能生成向量。


16.6 对操作失败的处理
  • safely() 是一个修饰函数(副词),它接受一个函数(动词),对其进行修改并返回修改后的函数。这样一来,修改后的函数就不会抛出错误。相反,它总是会返回由以下两个元素组成的一个列表。
    • result 原始结果。如果出现错误,那么它就是 NULL。
      error 错误对象。如果操作成功,那么它就是 NULL。


  • purrr 还提供了另外两个有用的修饰函数。
    • 与 safely() 类似,possibly() 函数也总是会成功返回。它比 safely() 还要简单一些,因为可以设定出现错误时返回一个默认值
    • quietly() 函数与 safely() 的作用基本相同,但前者的结果中不包含错误对象,而是包 含输出、消息和警告


16.7 多参数映射
  • purrr提供了 pmap() 函数,它可以将一个列表作为参数。如果你想生成均值、标准差和样本数量都不相同的正态分布,那么就可以使用这个函数
  • 如果没有为列表的元素命名,那么 pmap() 在调用函数时就会按照位置匹配。这样做比较容易出错,而且会让代码的可读性变差,因此最好使用命名参数

16.8 游走函数
  • 如果调用函数的目的是利用其副作用,而不是返回值时,那么就应该使用游走函数,而不是映射函数。通常来说,使用这个函数的目的是在屏幕上提供输出或者将文件保存到磁盘——重要的是操作过程,而不是返回值。
  • walk()、walk2() 和 pwalk() 都会隐式地返回 .x,即第一个参数。这使得它们非常适用于管道操作。

16.9 for循环的其他模式
  • 预测函数
    • 一些函数可以与返回 TRUE 或 FALSE 的预测函数一同使用。
    • keep() 和 discard() 函数可以分别保留输入中预测值为 TRUE 和 FALSE 的元素


  • some() 和 every() 函数分别用来确定预测值是否对某个元素为真以及是否对所有元素为真

  • detect() 函数可以找出预测值为真的第一个元素,detect_index() 函数则可以返回这个元素的位置

  • head_while() 和 tail_while() 分别从向量的开头和结尾找出预测值为真的元素

  • 归约与累计
    • 对于一个复杂的列表,有时你想将其归约为一个简单列表,方式是使用一个函数不断将两个元素合成一个。如果想要将两表间的一个 dplyr 操作应用于多张表,那么这种方法是非常适合的。
    • 或者你想要找出一张向量列表中的向量间的交集
    • reduce() 函数使用一个“二元”函数(即具有两个基本输入的函数),将其不断应用于一个列表,直到最后只剩下一个元素为止。累计函数与归约函数很相似,但前者会保留所有中间结果。你可以使用它来实现累计求和







上一篇:慧美——R for data science 第15章 向量
下一篇:生信技能书
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|小黑屋|生信技能树 ( 粤ICP备15016384号  

GMT+8, 2019-9-18 20:29 , Processed in 0.031110 second(s), 26 queries .

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.