本文概述
- 什么是函数?
- R中的函数
- R中最受欢迎的函数是什么?
- 用户定义函数(UDF)
- 如何在RStudio中看到R函数?
- 调用其他脚本中定义的R函数
- R中的嵌套函数调用
- 函数参数及其默认值
- R中的匿名函数
- R中的函数和函数式编程
- 摘要
该帖子将涵盖以下主题:
- 什么是函数?
- R中的函数
- R中最受欢迎的函数是什么?
- 用户定义函数(UDF)
- 如何在RStudio中看到R函数?
- 调用其他脚本中定义的R函数
- R中的嵌套函数调用
- 参数及其默认值
- R中的匿名函数
- R中的函数和函数编程
什么是函数? 在编程中, 你可以使用函数来合并要重复使用的指令集, 或者由于它们的复杂性而更好地独立于子程序并在需要时调用。函数是为执行指定任务而编写的一段代码;它可以或不能接受参数或参数, 并且可以或不能返回一个或多个值。
现在那是多么通用!
实际上, 从数学到计算机科学, “ 函数” 有几种可能的正式定义。通常, 其参数构成输入, 其返回值构成输出。
在这里, 你将使用一个简单的定义去掉数学上的限制:” 每个输入与一个输出恰好相关的属性” 。你将看到有些函数可对某些输入值进行操作, 可能会产生多个结果, 具体取决于它们的内部构造方式。
R中的函数 存在许多用于定义和表达函数, 子例程, 过程, 方法等的术语, 但是出于本文的目的, 你将忽略这种区别, 这通常是语义上的, 让人联想到其他较旧的编程语言。你通常将每个结构表示为” 函数” , 主要是因为在R中, 我们只有…函数!
(对于受惊的读者, 这是一个链接:语义)。
在R中, 根据基础文档, 你可以使用以下结构定义函数
function (arglist){body}
大括号之间的代码是函数的主体。
请注意, 通过使用内置函数, 你唯一需要担心的是如何有效地传递正确的输入参数(arglist)和管理返回值(如果有)。
R中最受欢迎的函数是什么? 现在, 鉴于R中函数和库的数量众多, 你如何确定自己要学习和掌握的函数和对象?而且由于许多函数出现在不同的程序包(库)中, 你是否还不知道要使用哪些库?
提示:请在srcmini的R软件包初学者指南中详细了解R软件包和库之间的区别。
借助数据科学, 你会发现有人已经考虑过这一点:
- 通过基于下载跟踪对函数进行排名, 尽管不是很及时;要么
- 通过按类别列出函数;要么
- 通过进一步创建和征集备忘单;最后,
- 通过设计类似于Google的包依赖关系算法来找出最重要的包依赖关系。
因此, 更多地了解函数内部工作的最佳方法是编写自己的函数。
用户定义函数(UDF) 无论你是要完成特定任务, 还是不知道已经存在专用函数或库, 或者是因为当你花费大量时间寻找一些现有解决方案时, 你可能已经拥有了自己的解决方案, 因此你会在某个时候找到自己输入类似:
function.name <
- function(arguments)
{
computations on the arguments
some other code
}
因此, 在大多数情况下, 函数在关键字” function” 后的()中有一个名称, 其中一些自变量用作该函数的输入;主体, 即花括号{}中的代码, 你可以在其中执行计算;并可以具有一个或多个返回值(输出)。通过将指令函数(参数)” 分配” 给” 变量” function.name, 然后其余其余部分, 来定义与变量相似的函数。
请记住, 还有一些不带有名称的函数;这些被称为” 匿名函数” 。
确保你为函数选择的名称不是R保留字。例如, 这意味着你不想为自己的UDF选择现有函数的名称, 因为这会引起你很多麻烦, 因为R不会知道你指的是你最近定义的UDF还是现有函数已加载其中一个库。
避免这种情况的一种方法是使用帮助系统:是否通过输入{r eval = FALSE}获得了一些信息? OurFunctionName, 你最好不要使用该名称, 因为它已经被使用了。
请注意, 仍然可以为你自己的UDF使用现有函数的名称, 但不建议这样做;这将要求你将一个函数与另一个函数隐藏起来!
一旦在函数定义中定义了函数, 就可以在代码中的其他地方调用或使用它。你可以在下面的代码段中轻松地发现这一点, 在该代码段中, 你定义了一个计算参数平方的函数, 然后在为其参数赋值之后调用它:
需要一些注释来说明UDF的工作:
- 首先, 使用关键字function将函数定义为变量myFirstFun, 该函数还将接收n作为参数(无类型说明)。后者将存在于函数中。你使用了整数, 但是n也可以是向量, 矩阵或字符串:R可以为你很好地处理所有这些;
- 在代码段中, 调用函数时, 将其分配给变量m。本质上这不是必需的, 因为R总是会打印上一次完成的评估, 但是你这样做是为了清楚起见, 也许是因为你想稍后再使用该结果。但是, 如果你不这样做, R将在运行下一条命令时忘记此评估;
- 调用该函数时, 可以使用一个任意变量, 例如上面的代码块中的k, 并为其分配一个整数值。你这样做是为了说明变量不需要相同的名称(或相同的类型), 因为它是一个不同的对象。这意味着
- 你可能使用了相同的名称n;稍后你将详细了解!
你会看到k和n保持其初始定义的值。
如果你在上次调用之前未定义变量n, 则R会向你抛出错误, 如下所示:
提示:在尝试上面的代码块之前, 请记住要清理工作区:如果在RStudio中工作, 请在环境窗口中单击画笔, 或取消注释以下代码段的第一行, 否则R会记住以前的值。你可以通过先运行rm(list = ls())来实现。
或者, 要从工作空间中删除特定元素, 可以使用函数rm(x, y, z … )从环境中删除对象x, y, z。这些可以是变量, 数据集, 也可以是函数。
你应该得到以下错误:” myFirstFun(n)中的错误:找不到对象’ n'” 。请注意, 任何其他先前未定义的变量也会导致相同的错误。这是因为R执行的是惰性计算:它仅在以下情况下检查因此, 如果你将参数定义为字符k =’ a’ , 则会收到错误:n * n中的错误:二进制运算符的非数字参数。
这也意味着, 如果你在未传递第二个参数的情况下定义了第二个参数, 则R仅在必要时(例如, 在引用该参数的情况下)抱怨, 而没有提供值。你将在关于参数的部分中阅读有关此内容的更多信息。
因此, 你已经看到了” 作用域” 或变量可见性的第一个示例。
如上图所示, 函数的基本特征是内部使用的变量是局部的。这意味着, 例如, 它们的范围位于函数内部, 并且仅限于函数本身, 因此在函数主体外部不可见。
显然, 函数需要一种与外界通信的方法, 通常是通过一个或多个参数(“ 输入” )和函数返回给调用者的一个或多个值(称为” 输出” )。
在你的示例中, 函数返回值包含在变量m中。请注意, 由于函数中的所有对象都是本地对象, 因此它们不会显示在你的工作空间中。为了使它们可以从函数主体外部访问, 你需要使用return。
因此, 可以说R中的环境是嵌套的。它们以树形结构组织, 反映了R遇到符号时的运行方式。 R从下往上开始:当在当前函数环境中找不到符号时, 它将向上查找到全局环境的下一层。最终, 如果找不到该符号, R将给出错误。
在尝试截取函数中定义的变量时(例如在调试时)就是这种情况。如果脚本环境中存在具有相同名称的符号, 则会显示该符号。但是, 它不是函数中的变量:RStudio环境仍然不可见。
因此, 要检查函数中的变量, 可以使用打印语句来提供帮助。
如何在RStudio中看到R函数? 开发函数时, 你可以在RStudio环境中看到它。可视化其代码的一种简单方法是键入其名称而不带括号()。
当你退出Rstudio而不关闭函数脚本文件时, 并且在退出时保存了环境时, 你会在工作空间中的脚本文件中再次找到它, 而该脚本文件可能在你退出后就已经存在。
但是, 在开发稍大的项目时, 很可能你将函数编写为R脚本并将其保存在某处。
调用其他脚本中定义的R函数 也许你计划了一个实用程序函数库, 并希望从正在开发的另一个脚本中调用其中一个或多个。这是如何运作的?
首先, 请注意在R中加载和执行函数的简单方法。这可能在Rstudio控制台中不可见, 但是在任何R控制台中都可见。如果上面看到的函数代码片段myFirstFun已保存到R脚本文件中, 请说myIndepFun.R, 你可以使用命令source()加载该函数:
source("myIndepFun.R")
而且此命令也可以通过脚本运行。
但是, 你可能希望在脚本文件MyUtils.R中找到一个特定的函数, 例如myFirstFun, 该文件包含其他实用程序函数。
在这种情况下, “ 源” 命令将在调用函数exist()后找到该函数:
if(exists("myFirstFun", mode = "function"))
source("MyUtils.R")
如果拼写错误或忘记了如何调用文件, 则可以使用sapply()从目录中检索扩展名为.R的文件名及其全名的列表, 并说” / R / MyFiles” , 然后加载它们:
sapply(list.files(pattern="[.]R$", path="R/MyFiles/", full.names=TRUE), source);
R中的嵌套函数调用 函数中不需要return语句, 但是建议在函数执行多次计算时, 或者希望在函数主体外部访问值(而不是包含它的对象!)时使用return语句。如你所见, 后者不是默认行为。
注意, 顾名思义, 它具有结束函数执行并将控制权返回给调用它的代码的作用。
现在考虑以下参数:这些参数可以是任何类型, 并且在函数内部可以具有默认值。即使未将显式值传递给输出, 后者也会提供输出。最后, 你可以在一个函数内调用另一个函数。
让我们通过以下示例详细了解这些要点。
首先, 定义一个向量v, 将在以下内容中使用:
# Define a numeric vector `v` of 4 elements
v <
- c(1, 3, 0.2, 1.5, 1.7)# Define a matrix `M`
M <
- cbind( c(0.2, 0.9, 1), c(1.0, 5.1, 1), c(6, 0.2, 1), c(2.0, 9, 1))
然后, 显示一个示例函数, 该示例调用上面创建的第一个函数。请注意, 即使函数是用两个参数定义的, 也只能在调用中传递一个参数。这次你还使用return():
如果忘记了后者, 就像下面的代码块一样, 你将永远无法访问输出。
实际上, 如最后一条命令所示, 输出为NULL, 这仅仅是因为即使内部函数的返回值填充了向量u, 后者也仍然限制在第二个函数内, 因为它不返回任何值!
函数参数及其默认值 你已经看到在()中指定了函数参数。让我们看一系列示例, 以计算作为参数传递的值n的一些幂, 在参数管理上几乎没有变化:
在这种情况下, 你会看到如果同时指定了两个参数, 则函数将计算2 ^ 3 = 8。当你仅传递第一个参数n时, 该函数将使用默认值y = 2进行计算。如果省略参数, R会引发错误。取消注释行以查看错误!
在这里, 你指定第二个参数, 即指数作为值列表, 以计算指数小于或等于1的给定n的幂:
在这里, 仅指定n(代码段中的2)会使函数根据指定的指数列表计算所有幂。
以下是等效的:在这里你没有默认使用上面的值, 而是通过函数missing()对参数进行了if测试来检查其是否存在:
好的, 但是你可以做得更好!使用默认列表作为验证输入的用户输入的检查器。下面的MyFourthFun函数检查y值是否在列表中:如果是, 则代码将执行电源操作, 否则将执行默认操作或抛出错误:
请注意, 在上面的代码块中, 你只是添加到打印中以检查传递给它的输入值, 因为你将无法从工作区中看到这些值。因此, 取消注释这些即可进行检查!
第一个调用执行了预期的操作, 第二个调用未执行, 并且抱怨该指数不在列表中;第三个将使用默认指数, 第四个将使用两个默认值。
这个主题有很多可能的变化, 但是你已经拥有了这个精神!
R中的匿名函数 当你不给函数命名时, 就是在创建一个匿名函数。
这怎么可能?
这是因为在R中, 函数(实际上是任何对象)的求值无需将其或其结果分配给任何命名变量, 并且可以应用于任何标准R函数。
语法与上面看到的普通UDF略有不同, 因为现在你使用了不同的括号方法:
- 首先, 通常在关键字function之后使用()来表示对函数的调用:这可以指定参数, 例如x。
- 其次, 一对()包围着function(x)声明和主体;
- 第三, 在上一个构造之后, 你可以指定调用中传递的参数。
为什么或何时使用匿名函数?
如上面的语法所示, 你可以一口气完成所有工作:在一行语句中进行声明和调用。因此, 尽管在读取时不是透明的, 但它是独立的, 并且你使用它是因为你不想在当前脚本(或外部脚本)中的其他地方定义另一个函数:你正在处理一个简单的在需要时进行计算, 你可能不会在代码中的其他任何地方使用它, 因此不值得记住。
R中的函数和函数式编程 在不提及R是一种函数编程语言的关键事实的情况下, 如何结束这篇文章?
是的, 你没看错, 尽管人们通常将” 函数” 属性与诸如Scala之类的流行语言相关联。
这是权威Hadley Wickham在R上的帖子的链接, 他的话是” 你可以对向量执行的函数可以做任何事情:可以将它们分配给变量, 将它们存储在列表中, 将它们作为参数传递给其他函数, 创建它们内部函数, 甚至将它们作为函数的结果返回” 。
读物的一个引人入胜的地方是闭包的概念:这些是由函数编写的函数, 它们的主要用途是环境的可访问性。
如上所述, 潜在的棘手问题是函数终止其工作时变量的可见性或不可见性。闭包由函数及其环境以及数据组成, 因此可以访问调用方函数环境。
摘要 你已经看到函数是R中最重要的编程结构, 它实际上是一种函数语言。你可以自行开发称为” 用户定义函数(UDF)” 的函数。第一个示例向我们介绍了函数和跨环境的可变可见性或” 作用域” 的概念。
在实践中, 当你开发自己的函数时, 以下是一些有关如何避免范围界定问题和维护简洁代码的提示:
- 与从库中采购函数类似, 你可以使用source()函数加载和执行函数。
- 函数环境(变量, 其他嵌套函数)只能通过传递给-的参数以及获得的返回值来访问;
- 尽可能使用名称函数(或将其” 分配” 给名称)。这类似于命名变量。命名函数不允许使用return语句, 尽管后者的存在清楚地表明了该函数的出口位置。
- 匿名函数可能很有用, 但是如果你认为你将进行的工作不只是简单的计算, 并且打算再次使用该函数, 则只需创建一个新的命名函数即可;和,
- 本着同样的精神, 如果一个函数被重复使用并且具有通用性, 那么也许值得将其与类似的姐妹函数一起放入专用脚本(R文件)中。
推荐阅读
- Python time.sleep()函数
- PostgreSQL初学者指南
- Android PAI (PlayAutoInstall)预装APK 功能
- MTK Android O1平台预置apk
- Android 自定义View实现SegmentControlView(自定义多样式tablayout)
- Android For OpenCV的环境搭建
- Android广播机制的基本使用
- Android自定义多宫格解锁控件
- Android权限之动态权限