25 函数式编程和数据框列表列 您所在的位置:网站首页 样本与样本观测值区别 25 函数式编程和数据框列表列

25 函数式编程和数据框列表列

2023-04-29 21:10| 来源: 网络整理| 查看: 265

25 函数式编程和数据框列表列 25.1 函数式编程介绍

R支持类(class)和方法(method), 实际提供了适用于多种自变量的通用函数(generic function,或称泛型函数), 不同自变量类型调用该类特有的方法, 但函数名可以保持不变。 这可以支持一定的面向对象编程方式。

R也支持函数式编程, 但不是专门的函数式编程语言。 R语言的设计主要用函数求值来进行运算; R的用户主要使用函数调用来访问R的功能。

按照函数式编程的要求, 函数应该是“第一级对象”, 可以将函数对象绑定到变量名上面, 可以在列表等结构中保存多个函数, 可以在函数内定义函数, 可以用函数作为函数的自变量, R函数满足这样的要求。

函数式编程的目的是提供可理解、可证明正确的软件。 R虽然带有函数式编程语言特点, 但并不强求使用函数式编程规范。 典型的函数式编程语言如Haskel, Lisp的运行与R的显式的、顺序的执行方式相差很大。

25.1.1 纯函数

函数式编程要求每个函数必须功能清晰、定义确切, 最好是所谓“纯函数”。 R并不是专门的函数式编程语言, 专门的函数式编程语言提供了定义纯函数的功能。 纯函数需要满足如下条件:

没有副作用。调用一个函数对后续运算没有影响, 不管是再次调用此函数还是调用其它函数。 这样,用全局变量在函数之间传递信息就是不允许的。 其它副作用包括写文件、打印、绘图等, 这样的副作用对函数式要求破坏不大。 函数返回值包含了函数执行的所有效果。

不受外部影响。函数返回值只依赖于其自变量及函数的定义。 函数定义仅由对所有可能的自变量值确定返回值来确定, 不依赖于任何外部信息(也就不能依赖于全局变量与系统设置值)。 在专用的函数式编程语言中, 函数定义返回值的方式是隐含地遍历所有可能的参数值给出返回值, 而不是用过程式的计算来修改对象的值。

不受赋值影响。 函数定义不需要反复对内部对象(所谓“状态变量”)赋值或修改。

R的函数一般不能修改实参的值, 这有助于实现纯函数的要求。 但是,如果多个函数之间用全局变量传递信息, 就不能算是纯函数。 像options()函数这样修改全局运行环境的功能会破坏函数式要求。 尽可能让自己的函数不依赖于options()中的参数。

如果函数对相同的输入可以有不同的输出当然不是纯函数, 例如R中的随机数函数(sample(), runif(), rnorm等)。

与具体硬件、软件环境有关的一些因素也破坏纯函数要求, 如不同的硬件常数、精度等。 调用操作系统的功能对函数式要求破坏较大。 减少赋值主要需要减少循环,可以用R的向量化方法解决。

一个R函数是否满足纯函数要求不仅要看函数本身, 还要看函数内部调用的其它函数是否纯函数。

R不是专用的函数式编程语言, 但可以采用函数式编程的范式, 将大多数函数写成纯函数, 将有副作用的单独写在少数的函数中。

25.1.2 副作用和运行环境恢复

如果函数除了输出之外还在其它方面影响了运行环境, 这样的函数就不是纯函数。 所有画图函数(plot等)、输出函数(cat, print, save等)都是这样的函数。 这些对运行环境的改变叫做副作用(side effects)。 又比如,library()函数会引入新的函数和变量, setwd(), Sys.setenv(), Sys.setlocale()会改变R运行环境, options(), par()会改变R全局设置。 自定义R函数中如果调用了非纯函数也就变成了非纯函数。 编程中要尽量控制副作用而且要意识到副作用的影响, 尤其是全局设置与全局变量的影响。

有些函数不可避免地要修改运行环境, 比如当前工作目录(用setwd())、R运行选项(用options())、绘图参数(用par())等, 如果可能的话, 在函数结束运行前, 应该恢复对运行环境的修改。 为此,可以在函数体的前面部分调用on.exit()函数, 此函数的参数是在函数退出前要执行的表达式或复合表达式。 此函数可以多次调用, 一般做法是函数体内部每次更改运行环境处调用一次on.exit(), 预先设定退出前恢复。 这需要使用add=TRUE选项。

例如, 绘图的函数中经常需要用par()修改绘图参数, 这会使得后续程序出错。 为此,可以在函数开头保存原始的绘图参数, 函数结束时恢复到原始的绘图参数。 如

f ", typeof(.x))) |> unname() ## [1] "name ==> character" "sex ==> integer" "age ==> double" ## [4] "height ==> double" "weight ==> double"

输入数据没有元素名的演示:

dl summarise(across( where(is.numeric), \(x) sum(x^2) )) ## # A tibble: 1 × 3 ## age height weight ## ## 1 3409 74305. 199436.

从数据框(或列表)中选一部分满足某种条件的子集进行变换是常用的做法, 所以map提供了map_if()和modify_if()变种, 允许输入一个示性函数, 对满足条件的子集才应用输入的变换函数进行处理, 输入数据中其它元素原样返回。 map_if返回列表, modify_if返回与输入数据相同类型的输出。 例如, 将数据框中数值型列除以100, 其它列保持不变:

modify_if(d.class, is.numeric, `/`, 100) |> head() ## # A tibble: 6 × 5 ## name sex age height weight ## ## 1 Alice F 0.13 0.565 0.84 ## 2 Becka F 0.13 0.653 0.98 ## 3 Gail F 0.14 0.643 0.9 ## 4 Karen F 0.12 0.563 0.77 ## 5 Kathy F 0.12 0.598 0.845 ## 6 Mary F 0.15 0.665 1.12

基本R的Find函数与detect作用类似, Position与detect_index作用类似, Filter函数与keep作用类似。

25.2.5 基本R的函数式编程支持

使用purrr包的泛函的好处是用法风格一致, 有许多方便功能。 对于少量的使用泛函的需求, 在不想使用purrr包的情况下, 可以使用基本R中的类似功能。

基本R的apply函数可以对矩阵的每行或每列进行计算, 或对多维向量的某个维度进行计算。 参见12.6。

基本R中的integrate、uniroot、optim、omptimize等函数也需要输入函数, 但主要用于数学计算, 与一般的函数式编程关系不大。

基本Rlapply函数用输入的函数对数据的每个元素进行变换,格式为

lapply(X, FUN, ...)

其中X是一个列表或向量, FUN是一个函数(可以是有名或无名函数), 结果也总是一个列表, 结果列表的第\(i\)个元素是将X的第\(i\)个元素输入到FUN中的返回结果。 ...参数会输入到FUN中。 这与purrr::map()功能类似。

sapply与lapply函数类似, 但是sapply试图简化输出结果为向量或矩阵, 在不可行时才和lapply返回列表结果。 如果X长度为零,结果是长度为零的列表; 如果FUN(X[i])都是长度为1的结果, sapply()结果是一个向量; 如果FUN(X[i])都是长度相同且长度大于1的向量, sapply()结果是一个矩阵, 矩阵的第\(i\)列保存FUN(X[i])的结果。 因为sapply()的结果类型的不确定性, 在自定义函数中应慎用。

vapply()函数与sapply()函数类似, 但是它需要第三个参数即函数返回值类型的例子,格式为

vapply(X, FUN, FUN.VALUE, ...)

其中FUN.VALUE是每个FUN(X[i])的返回值的例子, 要求所有FUN(X[i])结果类型和长度相同。

例如,求d.class每一列类型的问题,用lapply,写成:

lapply(d.class, typeof) ## $name ## [1] "character" ## ## $sex ## [1] "integer" ## ## $age ## [1] "double" ## ## $height ## [1] "double" ## ## $weight ## [1] "double"

lapply的结果总是列表。 sapply会尽可能将结果简化为向量或矩阵,如:

sapply(d.class, typeof) ## name sex age height weight ## "character" "integer" "double" "double" "double"

或使用vapply():

vapply(d.class, typeof, "") ## name sex age height weight ## "character" "integer" "double" "double" "double"

vapply可以处理遍历时每次调用的函数返回值不是标量的情形, 结果为矩阵, purrr包的map_dbl等只能处理调用的函数返回值是标量的情形。

R提供了 Map, Reduce, Filter, Find, Negate, Position等支持函数式编程的泛函。

Map()与purrr::map、purrr::pmap功能类似, 以一个函数作为参数, 可以对其它参数的每一对应元素进行变换, 结果为列表。

例如, 对数据框d, 如下的程序可以计算每列的平方和:

d


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有