用 R 写 循环 从低到高有三种境界:手动 for 循环,apply 函数族,purrr 包泛函式编程。
补充一点, 关于purrr 与 apply 族:purrr 提供了更多的一致性、规范性和便利性,更容易记住和使用。速度来说,apply 族稍微快可以忽略不计的一丢丢。
本篇来谈一谈用 purrr 包优雅地做循环迭代。
先总述一下 purrr 包做泛函式循环迭代的核心思想,以及将要介绍的常用操作:
循环迭代,就是将一个函数依次应用(映射)到序列的每一个元素上。
- map():依次应用一元函数到一个序列的每个元素上,基本等同 lapply()
- map2():依次应用二元函数到两个序列的每对元素上
- pmap():应用多元函数到多个序列的每组元素上,可以实现对数据框逐行迭代
- map 系列默认返回列表型,可根据想要的返回类型添加后缀:_int, _dbl, _lgl, _chr, _df, 甚至可以接着对返回的数据框df做行/列合并:_dfr, _dfc
- 如果只想要函数依次作用的过程,而不需要返回结果,改用 walk 系列即可
- 所应用的函数,有 purrr公式风格简写(匿名函数),支持一元,二元,多元函数
- purrr 包中的其它有用函数
下面结合实例具体展开。
先加载包:
library(tidyverse)
一. 预备知识
- 几个必要的概念
(1) 序列:姑且这么叫吧,即可根据位置或名字进行索引的数据结构,包括
- 原子向量(各个值都是同类型的,包括 6 种类型:logical、integer、double、character、complex、raw,其中 integer 和 double 也统称为numeric)
- 列表(各个值是不同类型的)
所谓循环迭代,就是依次在序列上做相同的操作。
(2) 泛函式编程:函数的函数称为泛函,在编程中表示函数作用在函数上,或者说函数包含其它函数作为参数。
循环迭代,本质上就是将一个函数依次应用(映射)到序列的每一个元素上。表示出来不就是泛函式:map(x, f)
(3) 管道:管道可以将数据从一个函数传给另一个函数,从而用若干函数构成的管道就能依次变换你的数据。例如:
x %>% f() %>% g() # 等同于 g(f(x))
使用管道的好处是:提高程序可读性,避免引入不必要的中间变量。
对该管道示例应该这样理解:
依次对数据进行若干操作:先对 x 进行 f 操作, 接着对结果进行 g 操作
注:数据经过管道默认传递给函数的第一个参数(表现为省略);若在非第一个参数处使用该数据,用 "." 代替,这使得管道作用更加强大和灵活。
2. 循环迭代返回类型的控制
map 系列函数都有后缀形式,以决定循环迭代之后返回的数据类型,这是 purrr 比 apply函数族更先进和便利的一大优势。常用后缀如下:
- map_chr(.x, .f): 返回字符型向量
- map_lgl(.x, .f): 返回逻辑型向量
- map_dbl(.x, .f): 返回实数型向量
- map_int(.x, .f): 返回整数型向量
- map_dfr(.x, .f): 返回数据框列表,再 bind_rows 按行合并为一个数据框
- map_dfc(.x, .f): 返回数据框列表,再 bind_cols 按列合并为一个数据框
3. purrr 风格公式(匿名函数)
在序列上做循环迭代(应用函数),经常需要自定义函数,但有些简单的函数也用 function 定义一番,毕竟是麻烦和啰嗦。所以,purrr 包提供了对 purrr 风格公式(匿名函数)的支持。
熟悉其它语言的匿名函数的话,很自然地就能习惯。
前面说了,purrr 包实现迭代循环是用 map(x, f),f 是要应用的函数,想用匿名函数来写它,它要应用在序列 x 上,就是要和序列 x 相关联,那么就限定用序列参数名关联好了,即将该序列参数名 作为匿名函数的参数使用:
- 一元函数:序列参数是 .x
比如,f(x) = x^2 + 1, 其 purrr 风格公式(匿名函数)就写为:~ .x ^ 2 + 1
- 二元函数:序列参数是 .x, .y
比如,f(x, y) = x^2 - 3 y, 其 purrr 风格公式(匿名函数)就写为:~ .x ^ 2 - 3 * .y
- 多元函数:序列参数是 ..1, ..2, ..3, 等
比如,f(x, y, z) = ln(x + y + z), 其 purrr 风格公式(匿名函数)就写为:~ log(..1 + ..2 + ..3)
注:所有序列参数,可以用 ... 代替,比如,sum(..1, ..2, ..3) 同 sum(...)
二. map(): 依次应用一元函数到一个序列的每个元素上
map(.x, .f, ...)
map_*(.x, .f, ...)
其中,.x 为序列
.f 为要应用的一元函数,或 purrr 风格公式(匿名函数)
... 可设置函数 .f 的其它参数
例1 计算 iris 前4列,每列的均值
即依次将 mean() 函数,应用到第1列,第2列,...
df = iris[, 1:4]
map(df, mean)