5.1 抽象化

5.1 抽象化

R是一个好东西的主要原因是它是一门语言.而语言的魅力便是抽象.在R语言中,函数便是实现抽象的方式.假如我们想要把1到3的整数反复两次.这是一个简单的命令:

c(1:3, 1:3)

如今假如想要反复这些数字六次或者六十次.用函数来抽象这个操作就变得有意义了.其实,这样的抽象已经有前人做了:

rep(1:3, 6)  

rep()能够完毕我们上面的任务和其它一些类似的任务.

我们在做一个新的任务.我们有两个向量;我们想生成一个新的向量,首先将第一个向量反复到第二个向量的长度,然后第二个向量反复到第一个向量的长度.一个向量被反复到的长度假设小于它本身的长度意味着仅仅须要这个向量的前边的那一部分.使用rep()函数能够轻松地将其抽象到一个函数中:

repeat.xy <- function(x, y)
{
 c(rep(x, length=length(y)), rep(y, length=length(x)))
}

repeat.xy()如今就能够在R中被使用了.

repeat.xy(1:4, 6:16)  

这样的非常轻易地写出一个函数好像就意味着我们能够非常自然地从仅仅使用R升级到用R编程.

除了能够抽象操作,函数还凝结着智慧.π大约是 3.1415926535897932384626433832795028841971693993751058209749445
923078便是智慧.

函数:

circle.area <- function(r) pi * r ^ 2 

智慧与抽象兼而有之–它能够帮你算出不论什么你想知道的园的面积(约等于).

这里并非一个纯粹讨论R语言结构的地方,而是一个评论我们上边讲的两个函数细节的地方.repeat.xy()的主体被一对大括号包围着而circle.area()没有.函数的主体应该是一个简单地表达式.大括号将主体内的表达式转换成一个单独的(组合)表达式.当函数的主体仅仅有一条命令的时候,大括号是可选的.大括号也被应用在循环,分支和推断的结构中.

理想的情况是每一个函数通过让人easy理解的输入输出完毕一个被明白定义的任务.刚開始学习的人通常都是用一个函数来完毕全部的事情.一个普遍来讲总是更好的方法是:写非常多的小巧的函数,然后有一个更大的函数调用这些小的函数来完毕全部事情.一步步将任务拆分能够让我们清楚地知道什么是真正应该去做的.并且当程序出现bug时能够让我们的调试变得更简单.使用小函数普及程度更高.

R语言抽象的一个瑰宝是函数入參的默认值.比方,sd()中的入參na.rm的默认值是FALSE.假设你的需求正好是FALSE,你在调用sd()的时候便不须要改动na.rm的值.假设你希望丢掉缺失值,你在调用sd()的时候增加參数na.rm=TRUE.假如你编写了自己的函数而仅仅仅仅是改变函数默认入參,那么你可能就不会感激函数提供给你的抽象性了.

函数最总返回一个结果给你,而函数的返回值差点儿证明着自己的存在.函数中最后的一条语句被定义为返回值.然而非常多函数并没有服从这样的机制,可是return()会强制返回你想返回的.

函数的其它一些影响是它会有一个或者多个负作用.一个负作用便是除了会返回结果外,还会改变代码系统.R的哲学是将这些负作用集中在少数几个我们明白知道的并且希望对系统造成影响的函数中(比方print(),plot(),rm()).
这里写图片描写叙述
R函数处理的事物是对象.R有丰富的对象类型.表 5.1展示了一些重要的对象类型.

你也许会注意到每一个原子类型都有一个可能存在的值–NA(Not Available),这被称作缺失值,一些初次接触R的使用者花费了大量的时间努力去避免NAs.对于0的首次出现,他们也许也会这样做.然而NA是一个对你非常有价值的好东西.当你的数据中存在缺失值的时候你一般是不会高兴的,可是生活有NA总比没有好.

R的设计理想是nothing is important.我们再读一次”nothing” is important.向量的长度能够为0.这是另外一个愚蠢的设计可是被证明是难以置信地实用–这么说来并非愚蠢的设计啦.我们并不常常去处理一个不存在的事物,所以一些情况下,这是个问题–我们将会在轮回8,轮回8.1.15看到演示样例.

对象的大多数价值在于处理它们的属性.非常多属性改变着R和使用者对它们的认知.通常大多数情况下,对象的一个属性都会有自己的名字.决定着对象的方向的属性是类别.表5.2列举了少许非常重要的由属性决定的对象.

刚開始学习的人的一个普遍问题是把数据框和矩阵混为一谈.它们看起来好像一样.可是它们确实是不一样的.在轮回8.2.37将会为你展示它们为什么不同.

“vector”在R中有非常多含义:
1. 一个原子对象(和列表相反),这也许是最普遍的使用方法.
2. 一个没有属性的对象(除了可能名称).这是被is.vector()和as.vector()影响的定义.
3. 一个能够有随意长度的对象(包含列表).

非常明显第一个定义和第三个定义看起来是矛盾的,它们的究竟属于哪一个仅仅有通过代码的上下文才干够清楚.当我们讨论向量跟矩阵的差别时,第二个定义就派上用场了.

单词”list”在R中有一个专业的含义–一个能够包含不同类型,包含自己本身的长度可伸缩的对象.有时这个单词也会被用到非专业的地方,比方在”search list”或者”argument list”.

并非全部的函数都被平等地被制造出来.它们能够被随意地分为三种类型.

下边是一个无名函数:

apply(x, 2, function(z) mean(z[z > 0])) 

这个函数充当了apply()的第三个參数,它是如此短暂以至于我们都不是必需给它命名.

这些函数仅仅在一个特定的场合被使用,这些就是你的一次性函数.

然而对于一些对你来说确实珍贵的函数.本来它们就是一次性的而你却重写它们让它们变得更加抽象.诚然,你可能非常希望一个文件或者R包能够引入你的珍贵实用的函数.

从无名函数的样例中我们能够看到,一个函数也能够是另外一个函数的入參.在R中,函数和向量或者矩阵一样都是对象.你能够把函数想象成数据.

一个全新级别的抽象是一个函数的返回值是另外一个函数. 经验分布函数就是一个样例:

> mycumfun <- ecdf(rnorm(10))
> mycumfun(0)
[1] 0.4   

仅仅要你写了一个函数返回还有一个函数的这样的代码,你就能够直接去下一个轮回了.

在第二轮回(12页)我们短暂地分析了do.call().一些人对这个函数甚是迷惑.这是没有必要且不幸的–实际上它是一个非常easy但又非常强大的函数.普通情况下我们都是通过函数的的函数名外和一个”入參列表”来调用这个函数,这样非常easy:

sample(x=10, size=5)  

do.call()函数同意你以一个真正的列表来提供函数的入參:

do.call("sample", list(x=10, size=5))

有时在调用一个函数时能看到详细的执行情况是非常实用的.函数被调用的时候会建立一个环境,当这个被调用的函数再去调用其它函数时,系统也会为这些其它函数建立自己的环境.因此内存里会有一个环境栈随程序的执行而伸缩.

让我们定义一些函数吧:

ftop <- function(x)
{
# time 1
x1 <- f1(x)
# time 5
ans.top <- f2(x1)
# time 9
ans.top
}

f1 <- function(x)
{
# time 2
ans1 <- f1.1(x)
# time 4
ans1
}

f2 <- function(x)
{
# time 6
ans2 <- f2.1(x)
# time 8
ans2
}

然后我们进行调用:

# time 0
ftop(myx)
# time 10  

图 5.1向我们展示了这次调用随着时间栈中的环境怎么变化的.注意在ftop(),f1(),f2()的环境中都有一个x.x在ftop()中被称作myx(或者可能是x的副本),在f1()也是这样.可是f2()中的x就有些不同了.

当我们调试代码的时候,我们将会研究这个栈特定时刻的情况.比方,假设一个代码当一个bug出如今f2.1,我们就要查找在time 7附近的栈的情形.
这里写图片描写叙述

R语言有丰富的对象类型.这是R长处的一部分.这些对象的一些是语言本身的元素–函数调用,表达式等等.这提供了一种非常强大的抽象形式–在语言上计算.尽管差点儿全部的新人对语言的元素混淆看起来是十分的令人费解的,然而非常多人对这样的观点十分迟钝.

原文地址:https://www.cnblogs.com/zhchoutai/p/7388685.html