个性化阅读
专注于IT技术分析

关于在R中使用函数的教程!

本文概述

在上一篇文章中, 你介绍了R语言控制流程, 循环或循环结构的一部分。在随后的文章中, 你了解了更多有关如何通过使用apply()函数系列来避免循环的函数, 这些函数以重复的方式作用于复合数据。这篇文章将从R程序员的角度向你介绍函数的概念, 并将说明函数在R代码中的作用范围。

该帖子将涵盖以下主题:

  • 什么是函数?
  • R中的函数
  • R中最受欢迎的函数是什么?
  • 用户定义函数(UDF)
  • 如何在RStudio中看到R函数?
  • 调用其他脚本中定义的R函数
  • R中的嵌套函数调用
  • 参数及其默认值
  • R中的匿名函数
  • R中的函数和函数编程

(要练习, 请尝试R课程中的srcmini的Writing Functions。)

什么是函数?

在编程中, 你可以使用函数来合并要重复使用的指令集, 或者由于它们的复杂性而更好地独立于子程序并在需要时调用。函数是为执行指定任务而编写的一段代码;它可以或不能接受参数或参数, 并且可以或不能返回一个或多个值。

现在那是多么通用!

实际上, 从数学到计算机科学, “函数”有几种可能的正式定义。通常, 其参数构成输入, 其返回值构成输出。

在这里, 你将使用一个简单的定义去掉数学上的限制:”每个输入与一个输出恰好相关的属性”。你将看到有些函数可对某些输入值进行操作, 可能会产生多个结果, 具体取决于它们的内部构造方式。

R中的函数

存在许多用于定义和表达函数, 子例程, 过程, 方法等的术语, 但是出于本文的目的, 你将忽略这种区别, 这通常是语义上的, 让人联想到其他较旧的编程语言。你通常将每个结构表示为”函数”, 主要是因为在R中, 我们只有…函数!

(对于受惊的读者, 这是一个链接:语义)。

在R中, 根据基础文档, 你可以使用以下结构定义函数


function (arglist)  {body}

大括号之间的代码是函数的主体。

请注意, 通过使用内置函数, 你唯一需要担心的是如何有效地传递正确的输入参数(arglist)和管理返回值(如果有)。

R中最受欢迎的函数是什么?

现在, 鉴于R中函数和库的数量众多, 你如何确定自己要学习和掌握的函数和对象?而且由于许多函数出现在不同的程序包(库)中, 你是否还不知道要使用哪些库?

提示:请在srcmini的R软件包初学者指南中详细了解R软件包和库之间的区别。

借助数据科学, 你会发现有人已经考虑过这一点:

  • 通过基于下载跟踪对函数进行排名, 尽管不是很及时;要么
  • 通过按类别列出函数;要么
  • 通过进一步创建和征集备忘单;最后,
  • 通过设计类似于Google的包依赖关系算法来找出最重要的包依赖关系。

到目前为止, 你仅了解到有很多R函数以多种程序包的形式组织, 最困难的工作是正确确定要传递的参数(参数或args)以及如何处理其返回值。 。

因此, 更多地了解函数内部工作的最佳方法是编写自己的函数。

用户定义函数(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;稍后你将详细了解!

但是请注意, 此n与我们在函数体内使用的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仅在必要时(例如, 在引用该参数的情况下)抱怨, 而没有提供值。你将在关于参数的部分中阅读有关此内容的更多信息。

因此, 你已经看到了”作用域”或变量可见性的第一个示例。

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文件)中。

也许在玩了一点之后, 你认为值得开发自己的函数库!

赞(0)
未经允许不得转载:srcmini » 关于在R中使用函数的教程!

评论 抢沙发

评论前必须登录!