楼主: luorongjin
13558 2

[学习分享] R语言知识介绍---自定义函数 [推广有奖]

  • 1关注
  • 6粉丝

已卖:651份资源

副教授

6%

还不是VIP/贵宾

-

威望
0
论坛币
7111 个
通用积分
12.2590
学术水平
7 点
热心指数
19 点
信用等级
5 点
经验
912 点
帖子
301
精华
0
在线时间
941 小时
注册时间
2010-4-25
最后登录
2024-12-27

楼主
luorongjin 发表于 2016-5-25 18:12:48 |AI写论文

+2 论坛币
k人 参与回答

经管之家送您一份

应届毕业生专属福利!

求职就业群
赵安豆老师微信:zhaoandou666

经管之家联合CDA

送您一个全额奖学金名额~ !

感谢您参与论坛问题回答

经管之家送您两个论坛币!

+2 论坛币
大家好,今天给大家分享下R语言中自定义函数的知识点,后续将陆续推出R语言各个模块的知识点:
1 一个例子
  1. 格式:myfuntion<-function(arg1,arg2,…){
  2.                                  statements
  3.                                  return(object)
  4.           }
复制代码
例子:
  1. > mystats<-function(x,parametric=TRUE,print=FALSE){
  2.   if (parametric) {
  3. center<-mean(x);spread<-sd(x)
  4. } else {
  5. center<-median(x);spread<-mad(x)
  6. }
  7. if (print & parametric) {
  8. cat("mean=",center,"\n","sd=",spread,"\n")
  9. } else if (print & !parametric) {
  10. cat("median=",center,"\n","\bmad=",spread,"\n")
  11. }
  12. result<-list(center=center,spread=spread)
  13. return(result)
  14. }
复制代码
2 函数的组成部分

1)  函数体:body(),函数的代码

2)  形式参数列表:formals(),控制函数调用的参数列表

3)  环境:environment(),函数的变量所在位置的“地图”

  1. > f<-function(x) x^2
  2. > f
  3. function(x) x^2
  4. > formals(f)
  5. $x
  6. > body(f)
  7. x^2
  8. > environment(f)
  9. <environment: R_GlobalEnv>
复制代码

注意:函数都有上述三个组成部分,有一个例外:原语函数(如:sum等),它是直接用.Primitive()调用C语言代码,并且不包含R语言代码。因此它们的formals()、body()、environment()都是NULL


3 词法作用域1)概论:

        词法作用域进行符号的值的查找,是基于在函数创建时是如何嵌套的,而不是它们

在调用时如何嵌套的。 有了词法作用域,你不需要知道函数是怎么被调用的,以及在哪里查找变量的值。你只需要看看函数的定义即可。

2)四个基本原则:

   名字屏蔽、函数和变量、全新的开始状态、动态查找

1)名字屏蔽:

a)如果一个名字在函数中没有定义,那么R语言将向上一个层次查找。

   b)如果一个函数内部定义了另一个函数,也适用同样的规则: 首先,查看当前函数的内部,然后是这个函数被定义的环境,然后继续向上,以此类推,一直到全局环境,然后,再查找其它已经加载的包

   c同样的规则也适用于闭包——由其它函数创建的函数


例子:

  1. a)
  2. > x<-2
  3. > g<-function(){
  4. y<-1
  5. c(x,y)
  6. }
  7. >
  8. > g()
  9. [1] 2 1

  10. b)
  11. > x<-1
  12. > h<-function(){
  13. y<-2
  14. i<-function(){
  15. z<-3
  16. c(x,y,z)
  17. }
  18. i()
  19. }
  20. >
  21. > h()
  22. [1] 1 2 3

  23. c)
  24. > j<-function(x){
  25. y<-2
  26. function(){
  27. c(x,y)
  28. }
复制代码
2)函数和变量

对于函数,规则有一点点调整。 如果很明显你要的是函数(例如,f(3)),那么在这样的语境中,R语言在搜索时,将忽略那些不是函数的对象。

  1. > l<-function(x) x + 1
  2. > m<-function(){
  3. l<-function(x) x * 2
  4. l(10)
  5. }
  6. > m()
  7. [1] 20
复制代码

3)全新的开始状态

         函数每次调用时,一个新的环境就会被创建出来,随后函数会在该环境中执行。函数无法报告它上一次被调用时发生了什么,因为每次调用都是完全独立的。

  1. > j<-function(){
  2. if(!exists("a")){
  3. a<-1
  4. } else {
  5. a<-a + 1
  6. }
  7. print(a)
  8. }
  9. >
  10. > j()
  11. [1] 1
  12. > j()
  13. [1] 1
  14. 说明:每次调用时,都会创建一个先的环境,a都不存在
复制代码

4)动态查找

词法作用域决定了去哪里查找值,而不是决定在什么时候查找值。 R语言在函数运行时查找值,而不是在函数创建时查找值。这意味着,函数的输出是可以随着它所处的环境外面的对象,而发生变化的。

  1. <span lang="EN-US" style="font-size:12.0pt;font-family:
  2. &quot;Lucida Console&quot;;mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
  3. background:#E1E2E5;mso-font-kerning:0pt">> f<-function()x

  4. <span lang="EN-US" style="font-size:12.0pt;font-family:
  5. &quot;Lucida Console&quot;;mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
  6. background:#E1E2E5;mso-font-kerning:0pt">> x<-15

  7. <span lang="EN-US" style="font-size:12.0pt;font-family:
  8. &quot;Lucida Console&quot;;mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
  9. background:#E1E2E5;mso-font-kerning:0pt">> f()

  10. <span lang="EN-US" style="font-size:12.0pt;font-family:
  11. &quot;Lucida Console&quot;;mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:black;
  12. background:#E1E2E5;mso-font-kerning:0pt">[1] 15

  13. <span lang="EN-US" style="font-size:12.0pt;font-family:
  14. &quot;Lucida Console&quot;;mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
  15. background:#E1E2E5;mso-font-kerning:0pt">> x<-20

  16. <span lang="EN-US" style="font-size:12.0pt;font-family:
  17. &quot;Lucida Console&quot;;mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:blue;
  18. background:#E1E2E5;mso-font-kerning:0pt">> f()

  19. <span lang="EN-US" style="font-size:12.0pt;font-family:
  20. &quot;Lucida Console&quot;;mso-fareast-font-family:宋体;mso-bidi-font-family:宋体;color:black;
  21. background:#E1E2E5;mso-font-kerning:0pt">[1] 20

  22. <p class="MsoNormal" style="margin-left:31.5pt;text-indent:-31.5pt;mso-char-indent-count:
  23. -3.0"><span style="font-family:宋体;mso-ascii-font-family:Calibri;mso-ascii-theme-font:
  24. minor-latin;mso-fareast-font-family:宋体;mso-fareast-theme-font:minor-fareast;
  25. mso-hansi-font-family:Calibri;mso-hansi-theme-font:minor-latin">说明:环境不同函数中的对象值也不同。这不同于对象在函数体内,若对象在函数体内,每次执行函数都会产生一个新的环境,函数体中的变量将会重置;若对象在函数体外,执行函数时全局变量环境没有改变,所以还是会受到全局环境的影响。
复制代码

4 所有的操作都是函数调用

   万事万物都是对象;发生的所有事情都是函数调用。


  1. 如下两个表达等价:
  2. > x<-10
  3. > y<-5
  4. > x+y
  5. [1] 15
  6. > `+`(x,y)
  7. [1] 15

  8. > for(i in 1:2) print(i)
  9. [1] 1
  10. [1] 2
  11. > `for`(i,1:2,print(i))
  12. [1] 1
  13. [1] 2

  14. > sapply(1:5,`+`,3)
  15. [1] 4 5 6 7 8
  16. > sapply(1:5,"+",3)
  17. [1] 4 5 6 7 8
  18. 说明:第一个是称为+的对象的值,第二个是一个包含字符+的字符串。 第二个版本可以起作用,是因为lapply可以输入一个函数的名称而不是函数本身:如果你读过lapply函数的代码,那么你可以看到第一行使用了match.fun()函数通过名字来找到函数。
复制代码
一个更有用的应用是把lapply()或sapply与取子集操作组合起来:以下两个等价
  1. > x <- list(1:3, 4:9, 10:12)
  2. > sapply(x, "[", 2)
  3. [1]  2  5 11
  4. > sapply(x, function(x) x[2])
  5. [1]  2  5 11
复制代码
5 函数参数1)通过位置、全名来设定参数

当调用一个函数时,你可以通过参数的位置,或者通过完整的名称或者部分的名称,来匹配参数。 参数匹配的顺序是:首先是精确的名称匹配(完美匹配),然后通过前缀匹配,最后通过位置匹配。

  1. > f<-function(abcdef,bcde1,bcde2){
  2. +     list(a=abcdef,b1=bcde1,b2=bcde2)
  3. + }
  4. > str(f(1,2,3))
  5. List of 3
  6. $ a : num 1
  7. $ b1: num 2
  8. $ b2: num 3
  9. > str(f(2,3,abcdef=1))
  10. List of 3
  11. $ a : num 1
  12. $ b1: num 2
  13. $ b2: num 3
  14. > str(f(2,3,a=1))
  15. List of 3
  16. $ a : num 1
  17. $ b1: num 2
  18. $ b2: num 3
  19. > str(f(1,3,b=1))
  20. Error in f(1, 3, b = 1) : argument 3 matches multiple formal arguments
复制代码

一般来说,你只希望使用位置匹配来匹配排在前面的一、两个参数;它们是最常用的,大多数用户都知道它们是什么。要避免使用位置匹配来匹配较少使用的参数,并且仅使用具有可读性的缩写形式来对参数进行部分匹配。 (如果你正在为一个包编写代码,你想要把它发布在CRAN上,那么不能使用部分匹配,而必须使用完整的名字。) 命名的参数应该总是排在未命名的参数后面。 如果一个函数使用了...,则...之后列出的参数都必须使用它们的全名。


2)给定一个参数列表来调用函数
  1. > args<-list(1:10,na.rm=TRUE)
  2. > do.call(mean,args)
  3. [1] 5.5
复制代码
3)默认参数与缺失参数

a函数定义的时候设定初始值

  1. > f<-function(a=1,b=2){
  2. c(a,b)
  3. }
  4. > f()
  5. [1] 1 2
复制代码

b)默认参数由另一个参数创建

  1. > g<-function(a=1,b=a*2){
  2. c(a,b)
  3. }
  4. > g(,2)
  5. [1] 1 2
  6. > g(10)
  7. [1] 10 20
复制代码

c)默认参数在函数内部创建的变量来定义

  1. > h<-function(a=1,b=d){
  2. d<-(a + 1) ^ 2
  3. c(a,b)
  4. }
  5. > h()
  6. [1] 1 4
  7. > h(10)
  8. [1]  10 121
复制代码

默认参数在函数内部进行计算。 这意味着,如果表达式依赖于当前环境,那么结果将是变化的,取决于你是否使用了默认值或显式地提供了一个值。

  1. > f<-function(x=ls()){
  2. a<-1
  3. x
  4. }
  5. > f()
  6. [1] "a" "x"
  7. > f(ls())
  8. [1] "add"    "adders" "f"   
  9. 说明:在全局环境下:add、adders和f已经存在。
复制代码


4)延迟计算

默认情况下,R语言函数的参数是延迟计算的。仅当实际用到这些参数的时候,它们才会被计算出来。

  1. > f <- function(x){
  2. 10
  3. }
  4. > f(stop("This is an error!"))
  5. [1] 10
  6. > f <- function(x){
  7. force(x)
  8. 10
  9. + }
  10. > f(stop("This is an error!"))
  11. Error in force(x) : This is an error!
  12. 说明:第一段代码没有用到x参数,所以没有报错;第二段代码force了一下,就算不用到也执行了,由于调用函数的时候没有对x赋值,所以报错了。
复制代码

延迟性在if中的作用:


以下的第二个语句将仅在第一个语句为TRUE时才会被计算。如果不是这样的话,那么该语句会返回一个错误,因为NULL > 0是一个长度为0的逻辑向量,而不是if的一个有效输入。

  1. > x<-NULL
  2. > if(!is.null(x)&&x>0){}
复制代码
6 特殊调用1)中缀函数

大多数R中的函数是前缀操作符:函数的名称排在参数的前面。你还可以创建中缀函数,函数名位于它的参数之间,比如+或-。所有用户创建的中缀函数都必须以%开始和结束,R预定义了这些中缀函数:%%、%*%、%/%、%in%、%o%、%x%。

不需要%的内置中缀操作符的完整列表为:


::, :::, $, @, ?, *, /, +, -, >, >=, <, <=, ==, !=, !,&, &&, |, ||, ~, <-, <<-

  1. > `%+%` <- function(a, b) paste(a, b, sep = "")
  2. > "new" %+% " string"
  3. [1] "new string"
  4. 说明:在创建函数时,你必须把这个名字放在重音符"`"里面,因为它是一个特殊的名字。
复制代码

中缀函数的名字比普通R函数更加灵活:它们可以包含任何字符序列(当然,除了%以外)。 你需要在定义函数的时候,对任何特殊字符进行转义,而不是在调用的时候进行转义

  1. > `% %` <- function(a, b) paste(a, b)
  2. > `%'%` <- function(a, b) paste(a, b)
  3. > `%/\\%` <- function(a, b) paste(a, b)
  4. > "a" % % "b"
  5. [1] "a b"
  6. > "a" %'% "b"
  7. [1] "a b"
  8. > "a" %/\% "b"
  9. [1] "a b"
复制代码
2)替换函数

替换函数的行为表现得好像它们可以就地修改(译者注:modify in place,即修改立即生效,直接作用在被修改的对象上)参数,并且它们都拥有特别的名字xxx<-。 它们通常有两个参数(x和值),虽然它们可以有更多参数,但是它们必须返回修改过的对象。 例如,下面的函数允许你修改向量的第二个元素:

  1. > `second<-`<-function(x,value){
  2. x[2]<-value
  3. x
  4. }
  5. > x<-1:10
  6. > second(x)<-5
  7. > x
  8. [1]  1  5  3  4  5  6  7  8  9 10
复制代码

R计算赋值语句second(x) <- 5时,它注意到左手边的<-不是一个简单的名称,因此它寻找一个命名为second<-的函数来进行替换操作。


注意:我之所以说它们"表现得好像"可以就地修改参数,是因为实际上它们创建了一个修改后的副本。 我们可以使用pryr::address()来查看,找到底层对象的内存地址。

  1. > library(pryr)
  2. > x<-1:10
  3. > address(x)
  4. [1] "0x165f4f90"
  5. > second(x)<-6
  6. > address(x)
  7. [1] "0x1b2c4b10"  #地址不同
复制代码

把替换和取子集操作结合起来:

  1. > x<-c(a=1,b=2,c=3)
  2. > names(x)
  3. [1] "a" "b" "c"
  4. > names(x)[2]<-"two"
  5. > names(x)
  6. [1] "a"   "two" "c"
  7. 相当于写了如下代码(R实质):
  8. `*tmp*` <- names(x)
  9. `*tmp*`[2] <- "two"
  10.   names(x) <- `*tmp*`
复制代码

7 返回值1)语法:

retrun()语句

f <-function(x, y) {

if (!x) return(y)

# 这里是复杂的处理过程

}

2)保护机制

R语言提供了保护机制,让你避免一类副作用:大多数R对象具有修改时复制(copy-on-modify)的语义。所以修改函数参数不会改变原始值:


  1. > f<-function(x){
  2. x$a<-2
  3. x
  4. }
  5. > x<-list(a=1)
  6. > f(x)
  7. $a
  8. [1] 2
复制代码
3)返回不可见的值

invisible(x)

强制可见:(a<-2)

8 退出时执行

  on.exit()


注意:如果你在一个函数中调用多个on.exit()函数,那么请务必设置add = TRUE。 不幸的是,在on.exit()中add的默认值是add = FALSE,这样每次你运行它的时候,它都会覆盖已有的退出(exit)表达式。 由于on.exit()的特殊实现方式,因此无法创建一个默认设置为add = TRUE的变种函数,所以使用它的时候必须小心。


二维码

扫码加我 拉你入群

请注明:姓名-公司-职位

以便审核进群资格,未注明则拒绝

关键词:自定义函数 自定义 R语言 Arguments argument R语言 函数

已有 1 人评分论坛币 学术水平 热心指数 信用等级 收起 理由
mklyzl + 1 + 1 + 1 + 1 精彩帖子

总评分: 论坛币 + 1  学术水平 + 1  热心指数 + 1  信用等级 + 1   查看全部评分

沙发
luorongjin 发表于 2016-5-25 18:19:36
动态查询部分下的代码贴的时候没有检查,出现了乱码:现更改如下:
(4)动态查找
词法作用域决定了去哪里查找值,而不是决定在什么时候查找值。 R语言在函数运行时查找值,而不是在函数创建时查找值。这意味着,函数的输出是可以随着它所处的环境外面的对象,而发生变化的。

> f<-function()x
> x<-15
> f()
[1] 15
> x<-20
> f()
[1] 20
说明:环境不同函数中的对象值也不同。这不同于对象在函数体内,若对象在函数体内,每次执行函数都会产生一个新的环境,函数体中的变量将会重置;若对象在函数体外,执行函数时全局变量环境没有改变,所以还是会受到全局环境的影响。

藤椅
mklyzl 发表于 2018-9-29 15:36:57
看了很多书,都没有讲这么深刻的

您需要登录后才可以回帖 登录 | 我要注册

本版微信群
加好友,备注cda
拉您进交流群
GMT+8, 2025-12-25 05:42