优雅的循环迭代和泛函数编程-purr packages 和 map 函数
一、背景
多数情况下,我们经常在R语言编程中,使用循环处理各种数据,已达到必要的结果。在R语言中,写循环的修仙道路:手动for循环—apply函数族—purr包的泛函数式编程。
关于purrr 与 apply 族:
- purrr 提供了更多的一致性、规范性和便利性,更容易记住和使用。
- 速度来说,apply 族稍微快可以忽略不计的一丢丢。
基于 purrr 包的泛函式循环迭代的核心思想及常用操作:
- 循环迭代,就是将一个函数依次应用(映射)到序列的每一个元素上。
- 常用操作:
- map():依次应用一元函数到一个序列的每个元素上,基本等同 lapply()
- map2():依次应用二元函数到两个序列的每对元素上
- pmap():应用多元函数到多个序列的每组元素上,可以实现对数据框逐行迭代
- map 系列默认返回列表型
- 可根据想要的返回类型添加后缀:_int, _dbl, _lgl, _chr, _df
- 可以接着对返回的数据框df做行/列合并:_dfr, _dfc
- 如果只想要函数依次作用的过程,而不需要返回结果,改用 walk 系列即可
- 所应用的函数,有 purrr公式风格简写(匿名函数),支持一元,二元,多元函数
- map_* 系列函数
- 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 按列合并为一个数据框
二、基本概念
(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 操作
注意:
- 使用管道的好处是:提高程序可读性,避免引入不必要的中间变量。
- 数据经过管道默认传递给函数的第一个参数(表现为省略);若在非第一个参数处使用该数据,用 “
.
” 代替,这使得管道作用更加强大和灵活。
三、基于purr的匿名函数编程(泛函数式)
在序列上做循环迭代(应用函数),经常需要自定义函数,但有些简单的函数也用 function 定义,就会显得麻烦。所以,purrr 包提供了对 purrr 风格公式(匿名函数)的支持。
在上述描述中,purrr 包实现迭代循环是用 map(.x, .f)
,.f
是要应用的函数。如果想用匿名函数来写.f
,并将.f
应用在序列 .x
上(也就是将匿名函数.f
和序列 .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系列函数解读
4.1 map函数
map(.x, .f, ...)
map_*(.x, .f, ...)
其中,.x
为序列;.f
为一元函数,或 purrr 风格公式(匿名函数);...
可以设置函数 .f
的其它参数。
示例1:计算每列的均值
- 返回列表
# 依次将 mean() 函数,应用到第1列,第2列,...
> df <- iris[,1:4]
> map(df, mean)
# $Sepal.Length
# [1] 5.843333
#
# $Sepal.Width
# [1] 3.057333
#
# $Petal.Length
# [1] 3.758
#
# $Petal.Width
# [1] 1.199333
注解:
- df 是数据框(特殊的列表),作为序列其元素依次是:df[[1]], df[[2]], … 所以,map(df, mean) 相当于依次计算:mean(df[[1]]), mean(df[[2]]), …
- 返回向量
> map_dbl(df, mean)
# Sepal.Length Sepal.Width Petal.Length Petal.Width
# 5.843333 3.057333 3.758000 1.199333
- 使用多个参数
mean()函数还有其它参数,如 na.rm,若上述计算过程需要设置忽略缺失值
> map_dbl(df, mean, na.rm=TRUE)
# Sepal.Length Sepal.Width Petal.Length Petal.Width
# 5.843333 3.057333 3.758000 1.199333
- purr 风格公式(匿名函数)
> map_dbl(df, ~mean(.x, na.rm=TRUE))
# Sepal.Length Sepal.Width Petal.Length Petal.Width
# 5.843333 3.057333 3.758000 1.199333
示例2:批量读取数据文件并合并(列名相同)
files = list.files("datas/", pattern = "xlsx", full.names = TRUE)
df = map_dfr(files, read_xlsx) # 批量读取+按行堆叠合并
注解
- files 获取 datas 文件夹下所有 .xlsx 文件的路径,若嵌套只需设置参数 recursive = TRUR;
- map_dfr(files, read_xlsx) 依次将 read_xlsx() 函数应用到各个文件路径上,即依次读取数据,返回结果是数据框,同时“r”表示再做按行合并,一步到位。若需要设置 read_xlsx() 的其它参数,只需在后面设置即可。
示例3:批量建模
- 根据分类变量对数据进行分组,对每组分别建模,再提取模型信息
> df <- mtcars %>% select(mpg, cyl, wt)
> head(df)
# mpg cyl wt
# Mazda RX4 21.0 6 2.620
# Mazda RX4 Wag 21.0 6 2.875
# Datsun 710 22.8 4 2.320
# Hornet 4 Drive 21.4 6 3.215
# Hornet Sportabout 18.7 8 3.440
# Valiant 18.1 6 3.460
- 嵌套列表列:按照某列分组,并形成嵌套的数据结构
> df <- df %>% group_nest(cyl)
> df
# # A tibble: 3 × 2
# cyl data
# <dbl> <list<tibble[,2]>>
# 1 4 [11 × 2]
# 2 6 [7 × 2]
# 3 8 [14 × 2]
> df$data[[1]]
# # A tibble: 11 × 2
# mpg wt
# <dbl> <dbl>
# 1 22.8 2.32
# 2 24.4 3.19
# 3 22.8 3.15
# 4 32.4 2.2
# 5 30.4 1.62
# 6 33.9 1.84
# 7 21.5 2.46
# 8 27.3 1.94
# 9 26 2.14
# 10 30.4 1.51
# 11 21.4 2.78
- 建模
> df <- df %>% mutate(# 序列参数名作为匿名函数的参数model=map(data, ~lm(mpg~wt, data = .x)), # 分组建模pred=map(model, predict) # 计算每个样本的预测值)
> df
# # A tibble: 3 × 4
# cyl data model pred
# <dbl> <list<tibble[,2]>> <list> <list>
# 1 4 [11 × 2] <lm> <dbl [11]>
# 2 6 [7 × 2] <lm> <dbl [7]>
# 3 8 [14 × 2] <lm> <dbl [14]>
- 模型基本信息
> df$model %>% map(summary)
# [[1]]
#
# Call:
# lm(formula = mpg ~ wt, data = .x)
#
# Residuals:
# Min 1Q Median 3Q Max
# -4.1513 -1.9795 -0.6272 1.9299 5.2523
#
# Coefficients:
# Estimate Std. Error t value Pr(>|t|)
# (Intercept) 39.571 4.347 9.104 7.77e-06 ***
# wt -5.647 1.850 -3.052 0.0137 *
# ---
# Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
# Residual standard error: 3.332 on 9 degrees of freedom
# Multiple R-squared: 0.5086, Adjusted R-squared: 0.454
# F-statistic: 9.316 on 1 and 9 DF, p-value: 0.01374
#
#
# [[2]]
#
# Call:
# lm(formula = mpg ~ wt, data = .x)
#
# Residuals:
# 1 2 3 4 5 6 7
# -0.1250 0.5840 1.9292 -0.6897 0.3547 -1.0453 -1.0080
#
# Coefficients:
# Estimate Std. Error t value Pr(>|t|)
# (Intercept) 28.409 4.184 6.789 0.00105 **
# wt -2.780 1.335 -2.083 0.09176 .
# ---
# Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
# Residual standard error: 1.165 on 5 degrees of freedom
# Multiple R-squared: 0.4645, Adjusted R-squared: 0.3574
# F-statistic: 4.337 on 1 and 5 DF, p-value: 0.09176
#
#
# [[3]]
#
# Call:
# lm(formula = mpg ~ wt, data = .x)
#
# Residuals:
# Min 1Q Median 3Q Max
# -2.1491 -1.4664 -0.8458 1.5711 3.7619
#
# Coefficients:
# Estimate Std. Error t value Pr(>|t|)
# (Intercept) 23.8680 3.0055 7.942 4.05e-06 ***
# wt -2.1924 0.7392 -2.966 0.0118 *
# ---
# Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
#
# Residual standard error: 2.024 on 12 degrees of freedom
# Multiple R-squared: 0.423, Adjusted R-squared: 0.3749
# F-statistic: 8.796 on 1 and 12 DF, p-value: 0.01179
> df$model %>% map(summary) %>% map_dbl("r.squared")
# 用列表的元素名做 map 相当于提取该元素
# [1] 0.5086326 0.4645102 0.4229655> df %>% unnest(c(data, pred))
# 解除嵌套
# # A tibble: 32 × 5
# cyl mpg wt model pred
# <dbl> <dbl> <dbl> <list> <dbl>
# 1 4 22.8 2.32 <lm> 26.5
# 2 4 24.4 3.19 <lm> 21.6
# 3 4 22.8 3.15 <lm> 21.8
# 4 4 32.4 2.2 <lm> 27.1
# 5 4 30.4 1.62 <lm> 30.5
# 6 4 33.9 1.84 <lm> 29.2
# 7 4 21.5 2.46 <lm> 25.7
# 8 4 27.3 1.94 <lm> 28.6
# 9 4 26 2.14 <lm> 27.5
# 10 4 30.4 1.51 <lm> 31.0
# # … with 22 more rows
4.2 map2()函数:依次应用二元函数到两个序列的每对元素上
map2(.x, .y .f, ...)
map2_*(.x, .y, .f, ...)
其中,.x
为序列1,.y
为序列2,.f
为要应用的二元函数,或 purrr 风格公式(匿名函数),...
可设置函数 .f
的其它参数。
示例:计算BMI指数
> height = c(1.58, 1.76, 1.64)
> weight = c(52, 73, 68)
> bmi = function(h, w) w/h^2
> map2_dbl(height, weight, bmi)
# [1] 20.83000 23.56663 25.28257# purr风格公式(匿名函数)
> map2_dbl(height, weight, ~.y / .x^2)
# [1] 20.83000 23.56663 25.28257
说明:
- 序列1其元素为:height[[1]], height[[2]], …
- 序列2其元素为:weight[[1]], weight[[2]], …
- 所以,map2_dbl(height, weight, cal_BMI) 相当于依次计算:cal_BMI(height[[1]], weight[[1]]), cal_BMI(height[[2]], weight[[2]]), …
> df = tibble(height = height, weight = weight)
> df %>% mutate(bmi=map2_dbl(height, weight, bmi))
# # A tibble: 3 × 3
# height weight bmi
# <dbl> <dbl> <dbl>
# 1 1.58 52 20.8
# 2 1.76 73 23.6
# 3 1.64 68 25.3# purrr 风格公式(匿名函数)
> df %>% mutate(bmi=map2_dbl(height, weight, ~.y/.x^2))
# # A tibble: 3 × 3
# height weight bmi
# <dbl> <dbl> <dbl>
# 1 1.58 52 20.8
# 2 1.76 73 23.6
# 3 1.64 68 25.3
4.3 pmap(): 应用多元函数到多个序列的每组元素上,可以实现对数据框逐行迭代
pmap(.l, .f, ...)
pmap_*(.l, .f, ...)
其中,.l
为数据框,.f
为要应用的多元函数,...
可设置函数 .f
的其它参数
注:.f
是几元函数,对应数据框 .l
有几列,.f
将依次在数据框 .l
的每一行上进行迭代。
示例5: 分别生成不同数量不同均值、标准差的正态分布随机数。
> df <- tibble(n = c(1,3,5),mean = c(5,10,-3),sd = c(1,5,10)
)
> pmap(df, rnorm)
说明:这里的 rnorm(n, mean, sd) 是三元函数,pmap(df, rnorm) 相当于将三元函数 rnorm() 依次应用到数据框 df 的每一行上,即依次执行:rnorm(1, 5, 1), rnorm(3, 10, 5), rnorm(5, -3, 10)
- 特别注意,这里 df 中的列名,必须与 rnorm() 函数的参数名相同(列序随便)。若要避免这种局限,可以使用 purrr 风格公式写法:
pmap(df, ~ rnorm(..1, ..2, ..3)) # 结果同上(略), 或者简写为 pmap(df, ~ rnorm(...))
优雅的循环迭代和泛函数编程-purr packages 和 map 函数相关推荐
- python手机版怎么用-如何优雅的在手机上进行Python编程
原标题:如何优雅的在手机上进行Python编程 很多人都在学习Python,但是我们往往在清香于忙碌工作的同时的时候,很少有空余时间去学习py.今天就给大家推荐一个运行在android手机上的开发软件 ...
- python循环输入字典_python - 使用'for'循环迭代字典
使用'for'循环迭代字典 .values Python如何识别它只需要从中读取密钥 字典? 关键是Python中的一个特殊词吗? 或者只是一个 变量? 这不仅仅是.values循环. 这里重 ...
- python中循环迭代语句_python条件与循环-循环
1 while语句 while用于实现循环语句,通过判断条件是否为真,来决定是否继续执行. 1.1 一般语法 语法如下: while expression: suite_to_repeat 1.2 计 ...
- 阿里开发者们的第13个感悟:工程师需要在循环迭代中成长
2015年12月20日,云栖社区上线.2018年12月20日,云栖社区3岁. 阿里巴巴常说"晴天修屋顶". 在我们看来,寒冬中,最值得投资的是学习,是增厚的知识储备. 所以社区特别 ...
- java当中有关循环的代码_有关Java循环的内容,编程中还是比较常用的,下面分享给大家几个循环的示例代码,练习一下。1、循环输出1到100之间所有能被3或能被4整除的数。pack...
有关Java循环的内容,编程中还是比较常用的,下面分享给大家几个循环的示例代码,练习一下. 1.循环输出1到100之间所有能被3或能被4整除的数. package com.hz.loop02; /** ...
- 手机上有没有学python的软件-如何优雅的在手机上进行Python编程
原标题:如何优雅的在手机上进行Python编程 很多人都在学习Python,但是我们往往在清香于忙碌工作的同时的时候,很少有空余时间去学习py.今天就给大家推荐一个运行在android手机上的开发软件 ...
- 增强型的for循环linkedlist_38. 为什么千万别用for循环迭代LinkedList
今天晚上7点时候,想着每天的8点健身还早,突然想起来以前的一个知识点说千万别用for循环迭代LinkedList,效率奇低,今天就想着来写个测试例子并分析原理: 代码测试与现象 哈哈哈,首先还是先上源 ...
- js循环/迭代/遍历有多少方法
js循环/迭代/遍历有多少方法 JavaScript中存在着很多循环的方法 常见的有for,while,do while,for in等, ES5中的forEach, ES6的for of , jqu ...
- 手机python代码写好了怎么运行-如何优雅的在手机上进行Python编程
原标题:如何优雅的在手机上进行Python编程 很多人都在学习Python,但是我们往往在清香于忙碌工作的同时的时候,很少有空余时间去学习py.今天就给大家推荐一个运行在android手机上的开发软件 ...
最新文章
- 如何为ORACLE表空间创建大容量数据文件
- jQuery的Accordion插件
- JavaScript教程之DOM和BOM
- java rsa 公钥加密_java – 使用公钥进行RSA解密
- webwork在freemarker中使用iterator
- 10-30-010-安全简介-Kafka 安全机制
- el-table——可合并单元格的表格
- 已安装过matplotlib但提示ModuleNotFoundError: No module named ‘matplotlib‘的解决方法
- H5横竖屏的两种解决方法
- 超级保镖计算机管理系统
- Sublime Text 3在行前插入递增数字序号的方法
- 打开和设置IDEA欢迎界面
- 辞旧迎新:祝您阖家幸福安康,万事如意
- 一个学习小组有5个人,每个人有三门课的考试成绩。求全组分科的平均成绩和各科总平均成绩。
- R数据分析当中的化整为零(Split-Apply-Combine)策略
- Leetcode 1235. Maximum Profit in Job Scheduling (python)
- jk触发器的异步置位端和异步复位端的表示方法
- aiwi游戏里的忍者神龟
- PHP如何开发医疗HIS系统短信通知
- 深度玄学-实战开发步骤