当前课程知识点:R语言数据分析 >  中部:执具 >  第6章 基础编程——用别人的包和函数讲述自己的故事 >  6.6 函数(II)

返回《R语言数据分析》慕课在线视频课程列表

6.6 函数(II)在线视频

下一节:7.1 向量与因子(I)

返回《R语言数据分析》慕课在线视频列表

6.6 函数(II)课程教案、知识点、字幕

大家好

欢迎来到《R语言数据分析》课程

咱们今天继续交流函数相关的内容

我们先来看几行代码

就是关于这个plot绘图的几行代码

比如说我们首先可以定义一个数值向量

这个等差数列1到100的交给x

然后再定义另外一个向量就是y <- 2x+10

那我们再x和y来cbind(x, y)一下

就相当于并列放在一起

作为列放一起

就形成一个矩阵

这个时候它的class是一个矩阵matrix

这时候要是我将这个xy交给plot的话

你发现它会自动生成一个

就绘制了一个曲线 [直线]

毫无疑问是条直线

这是我们plot(xy,...)的一个效果

接下来我们还是x和y还是不变

但是我现在拟合了一个关于y和x之间的函数

我通过这个lm=linear model来拟合它

然后拟合结果

交给这个my_model

结果my_model是一个 linear model

是一个线性模型

然后我想将我这个my_model

也交给这个plot进行绘制的话

你会发现它出了

4幅图

同样是plot

但是当我这个

接收的参数这个my_model

以及前面xy

它的class不一样的时候

一个是这个矩阵matrix

一个是线性模型lm

它不一样的时候

你发现这个plot的行为是不一样的

这个plot的行为是不一样的

同样一个接口

然后面对不同的对象的时候

有不同的行为

这其实引出我们今天想讲的主题是

多态的问题

泛型函数的问题

其实也是在这个面向对象里面一个重要特点

就是多态

针对不同的对象有不同的行为

那在R里面的体现的话就是

泛型函数

所谓泛型函数

假如我们用这个影视剧的话

比较通俗的话讲就是

见什么人说什么话

到什么山上唱什么歌

我对象不一样

然后我的方法是不一样

那泛型函数怎么定义

泛型函数其实应该是一组函数

我们说泛型函数它有个接口

接口是统一的

比如说我们现在定义一个函数

一个泛型函数的接口叫interface

就是我接口都叫这个名字

但是当我这个类不一样的时候

我接受参数所属类不一样的时候

然后我有不同的具体的函数

比如说我现在根据这个y

就是我这个参数里面其中一个参数是y

这个y的类型不一样

它的class不一样

我就分发到不同的

particular这个函数里面去

假如我这个y是classA的话

那接下来我调用谁

particular.classA()

假如我这个y的类型是classB的话

那我就要调用particular.classB()

所以我们看得出来点号.在R里面

它可以作为一个简单的字符来对待

但同时它有特殊的含义

比如说点号放在最前面

表示隐藏的一个变量

假如点号放到中间的话

现在我们这个泛型函数定义的时候

其实是一个后缀

用来匹配不同的分发的函数

也就说一旦调用这个interface的时候

它首先进入这个接口

single interface然后通过这个UseMethod()

我来分发到具体方法里面去

当然也可能匹配不到

比如说我这个y

既不属于classA也不属于classB

那它就直接调用这个particular.default()

.default

这其实就是一个

泛型函数定义的一个典型的方式

有一个接口函数

有不同的具体分发函数

当然一般来讲

我们的接口函数interface和

后面这个分发的函数particular

其实我们在很多情况之下二者都是一样

就说这里面是interface那这边也是interface

interface.classA

interface.classB

interface. default

当然因为我们这边为了展示这个详细分发过程

将它二者取值不一样

另外就是假如我这里面默认的情况之下

大部分都是第一个参数

看第一个参数的类别

当然我们这边是将第二个参数的类别

作为一个分发的一个具体的一个机制

就是参照第二个参数所属的类来进行分发

一旦我们定义这么一个泛型函数

或者说这么一组泛型函数的话

那我们怎么调用 我们举个例子

比如x赋值为1到10

y赋值为1到20

然后我现在将y的标签贴成classA

给y贴上标签贴成classA

这时候我调用这个interface(x, y)的时候

大家注意了

它首先是进入这个接口

然后进到这个具体的particular函数里面去是

particular.classA

particular.classA

假如我现在将这个y的标签贴成classB的话

它首先还是进入这个接口函数

这个single interface

但是它的分发函数是多少

大家注意了

应该是particular.classB

所以我们可以看得出来

当类标签不一样的时候贴上不同的标签

然后我的接口是一样的

都是interface(x, y)

但是我具体行为是不一样

一个是particular.classA

一个是particular.classB

这里面其实我们可以看得出来

确实就基本就实现了

针对不同的对象

然后有不同的行为

那我们需要补充一下

大家看这个y的话

应该是一个1到20的一个数值的向量

但这个时候我们发现它所属的类可以随便贴

可以贴成classA也可以贴成classB

你甚至可以贴成某个人的名字

或者贴成“我爱北京天安门”

这么一个字符串也可以贴给它

所以对于R来讲所谓的类

它并不是它本质上存储的一个[数据]类型

它只是一个标签而已

当然假如我这个y的类型

比如说classC的话

那这时候你再调用

这个interface(x, y)

毫无疑问

它就只能进到particular.default()里面去

或者说你撕掉这个标签

将class(y)赋值为多少呢

NULL

那这个时候interface(x, y)

同样 也只能跑到哪去

只能跑到default里面去

particular.default

这其实就是我们说的泛型函数

它的一个定义以及调用的过程

好 我们再看看我们前面代码

结合我们前面代码

看到一些那个泛型已经用到过一些泛型函数

比如这个加号

其实就是一个泛型函数

+就是一个泛型函数

我们前面在讲这个二元操作符的时候

已经讲到加号了

假如我们在没有加载ggplot2包之前

我们有多少个泛型函数

就加点后面有多少个

它可以 +.Date

也可以 +.POSIXt

都可以

假如我们一旦加载完这个ggplot2之后

通过这个library()这个函数加载ggplot2这个包

然后我们再看

它其实就多了一个泛型函数

就对后面这个gg

类标签为gg的这种对象我也能处理了

假如我们一旦有了这么一个意识之后

就发现其实所谓的加号也是一个泛型函数

我们再重新看一下以前的代码

因为我们在迷你案例里面也看到了

前面的管道操作符我们已经理解了

只是一个二元操作符而已

另外还有一个比较奇奇怪怪的是加号

加号前面是ggplot这是一个函数

然后后面这个geom_boxplot

又是另外一个函数调用的过程

我们都说1+1=2这很容易理解

但一个函数加另外一个函数

那就不知道是几个意思

但其实我们现在重新审视完这个加号之后

发现它无非是针对gg的对象

我自己定义的一些操作而已

因为函数就是个操作

那既然它是一个函数

既然是一种操作

我们就没有什么不可理解的

其实我们可以打开这个"+.gg"看一下

它也是一个二元操作符

接受这两个

其实接受这两个参数

这个参数的类就是gg

所以它对这个参数进行相应的操作

在我们那个ggplot2绘图里面就表现为一系列的

接下来做什么操作

有可能是图层的叠加

也有可能是一些坐标操作等等

也有可能是一些坐标操作等等

当然我们可以定义自己的加号

比如说我给定一个 +.onlyFirst

注意了加号后面一个点

表示我对一个onlyFirst这种类

我有一个自己的操作

同样它也是一个二元操作符

接收两个参数

我只将向量a和向量b的第一个元素相加

并且返回

比如说我a现在目前我可以赋值为1:5

那a加6:10的话

它应该结果就是7-9-11-13-15

在正常情况下是这样的

假如我现在将a的类贴成onlyFirst

那这个时候因为加号是一个泛型函数

我针对这个onlyFirst这种数据对象

这个a这个数据对象

它要调用 +.onlyFirst

这么一个函数

就分发到这个函数里面来

毫无疑问结果是只有7了

因为我这个函数只是返回两参数的

这两个向量的第一个元素之和

这是我们那个加号

我们重新审视了一下

相信我们认识完这个二元操作符

认识完这个所谓的泛型函数之后

对于我们前面那个迷你小案例

基本上就不会觉得这个代码再奇奇怪怪了

讲完了这个泛型函数之后

我想再讲函数的最后一个知识点

应该也是相对比较重要的

这时候我又重新将菲波那契摆出来了

还是以这个菲波那契数列来讲

讲什么呢 递归

因为我们在几乎所有教材里面

一旦讲到菲波那契数列的时候

基本上都是用来做

演示递归的过程

我们前面讲这个菲波那契数列的时候

比如说1 1 2 3 5 8

一直往下走

我们都怎么做的

通过循环

从这开始一直往下生长

就是从少到多增长的过程

但其实菲波那契数列

比如说我想知道1 2 3 4 5 6

这个长度为6的菲波那契数列

我想求出来的话

前面我们讲都是通过循环来顺着生长

但是我们可以这个思路其实可以倒过来

比如说我现在想求这个长度为6的

菲波那契数列f(6)

其实可以基于

这个长度为5的菲波那契数列来生成它

倒过来

长度为5的话

可以基于

长度为4的菲波那契数列的生长

然后接着往下转

也就是说倒过来生长也可以

要求长度为6的菲波那契数列

只要能求出长度为5的话

那长度为6的菲波那契数列怎么求

就把长度为5的菲波那契数列最后两个数相加

放在后面就可以

然后长度为5的话怎么求

只要求到长度为4就可以

这样的话就是层层递进 层层递进

这个菲波那契数列其实也可以生长出来

这面其实就是一个递归的方式

来实现这个菲波那契数列

当然讲这个具体如何实现菲波拉契数列之前

通过这个递归方式实现菲波拉契数列

在这讲这个之前

我们先简单温习一下什么叫递归

其实所谓递归即便我们没有学过编程的话

递归的思想其实我们早就在很多故事里面

其实已经有所体现了

一个非常典型的故事

从前有座山

山上有座庙

庙里有个老和尚

老和尚在给小和尚讲故事

故事讲的是

又是从前有座山

山上有座庙

又有个老和尚

老和尚在给小和尚讲故事

故事里面又有山又有庙

又有老和尚

老和尚又在给小和尚讲故事

其实就这样层层递进层层递进

仿佛是无穷无尽的

仿佛是无穷无尽的

这其实就是一个递归的过程

这就是一个递归的过程

当然我们可以通过R代码来实现一下

这个老和尚讲故事的这么递归的过程

我先定义一个函数

叫old_monk_story

表示老和尚讲故事

当然我这里接收一个参数

就第几个老和尚

比如刚开始是第一个老和尚

从前有座山这么第一个老和尚在讲故事

老和尚讲的故事是

这个再从前又个老和尚

那这个时候就是第二个老和尚

这个时候我们接受一个参数

就是它究竟这个递归有几层

递归层级作为我的参数传递过来

具体函数实现是什么呢

就是从前有一个老和尚在讲故事

这个从前有山我就不管它了

就是从前我们到这个从前我们得量化一下

就说我们说从前的时候只是400年前

这都是我们自己定义的

400年前多少

就是2018减掉400乘以我刚才这个参数

假如第二个老和尚的话就是2018减掉400乘以2

从前有个老和尚在讲故事

故事讲的是

再从前又有一个老和尚在讲故事

还调用我这个old_monk_story

所以这就嵌套的递归的

当然因为我们这里面

它其实一旦说到递归的时候

它其实都有一个触底的过程

咱们这个老和尚讲故事怎么触底

因为我们都知道

据说这个佛教是公元66年传到我国的

也就是说从前从前从前的从前

当然可一直往前从前

但这个从前一旦从前到66年之前了

那我认为佛教都没有

老和尚肯定也没有

所以假如我这个2018减掉400乘以我这个相应的

递归的层级

假如它是大于等于66的话

我认为这个故事都有可能成立

否则的话我就不再执行了

那这个时候我就回来

就是一旦讲完了故事之后

我就说这个老和尚的故事讲完了

这里面我们通过R代码实现了一下

这个老和尚讲故事

old_monk_story

那这个结果是

我们一旦执行这个old_monk_story

这个函数的话

我们看一下结果

400年前这个时候其实就是1618年

第一个老和尚在讲故事

故事讲的是

再从前就是1218年

第二个老和尚在讲故事

第二个老和尚讲故事再从前818年

第三个老和尚讲故事

再从前418年第四个老和尚讲故事

418年之前再往前就没有了

418再减400就是公元18年

那这个时候佛教都没有

就没有所谓的老和尚

那这个时候就得往回走了

就层层递进层层递进

然后现在往回走

其实我们可以看得出来

就是第四个老和尚故事讲完了

第三个老和尚故事讲完了

第二个老和尚故事讲完

第一个老和尚故事也讲完了

所以我们可以看得出来

这个递归它其实是层层递进的过程

然后逐层回归

层层《递》进 逐层回《归》

这个递归的含义出来了

它就是一个层层递进再逐层回归的过程

这就是我们所谓的递归的一个最基本的含义

那我们温习完这个递归基本含义之后

我们再来看看

通过递归怎么来实现这个菲波那契数列

我们定义一个函数叫fib

同样我们就是说这个长度为n的话

我就将它作为参数

递归我们刚刚讲有一个触底的过程

当n等于1的时候

我结果有多少就是1

假如是n的大于等于2的话

我就把这个

长度为n-1菲波那契数列拿过来

然后把它的最后两位求和

求和之后放在那后面

就combine一下

所以我们一看这里面其实用到

长度为n减1的菲波那契数列

就我们说倒过来生长的过程

倒过来层层递进的

我们一看

这里面其实就是一个递归的实现方式

说起来也非常简单

这里面我们需要稍微补充一下

我们前面讲这个菲波那契数列的时候

1-1-2-3-5-8等等

我们都是从第三个元素开始

是属于前两个元素之和

但我们这边用了一个讨巧的办法

其实我们除了n==1之外

按理说应该还n==2应该也得做一个触底

也得给一个定义的

但是我们用到tail这个函数

tail这个函数

tail()这个函数表示

取后面这个对象菲波那契数列

长度为n-1的菲波那契数列

最后两位

当我这个fib(1)的时候

也就是说长度为1的菲波那契数列

我交给它的时候

因为这个时候我长度是为1

但是我要取两个出来

取不出来

但对于R来讲

对这个tail这个函数来讲

它会怎么样

它就把这个1本身就返回就可以了

它不会说我要报一个错

本来应该要只有一个元素

我现在从里面取两个取不出来

不会报错

所以我从2开始

从第二个元素开始

其实我就会调用下面这个逻辑

可以通过下面这个逻辑生成

这其实就是通过递归的方式来实现

菲波那契数列

通过递归的方式实现斐波那契数列

以上就是我们关于函数的全部内容

本次课到此结束

谢谢大家

R语言数据分析课程列表:

上部:问道

-第1章 气象万千、数以等观

--第1章 气象万千、数以等观

--第1章 作业

-第2章 所谓学习、归类而已

--2.1 所谓学习、归类而已(I)

--2.2 所谓学习、归类而已(II)

--2.3 所谓学习、归类而已(III)

--2.4 所谓学习、归类而已(IV)

--第2章 作业

-第3章 格言联璧话学习

--第3章 格言联璧话学习

--第3章 作业

-第4章 源于数学、归于工程

--第4章 源于数学、归于工程

--第4章 作业

-讨论题

--如何发挥人工智能的头雁效应

中部:执具

-第5章 工欲善其事、必先利其器

--第5章 工欲善其事、必先利其器

--第5章 作业

-第6章 基础编程——用别人的包和函数讲述自己的故事

--6.1 编程环境

--6.2Mini案例

--6.3 站在巨人的肩膀上

--6.4 控制流

--6.5 函数(I)

--6.6 函数(II)

--第6章 作业

-第7章 数据对象——面向数据对象学习R语言

--7.1 向量与因子(I)

--7.2 向量与因子(II)

--7.3 矩阵与数组(I)

--7.4 矩阵与数组(II)

--7.5 列表与数据框(I)

--7.6 列表与数据框(II)

--第7章 作业

-第8章 人人都爱tidyverse

--第8章 人人都爱tidyverse

--第8章 作业

-第9章 最美不过数据框

--第9章 最美不过数据框

--第9章 作业

下部:博术

-第10章 观数以形

--10.1 一维数据空间(I)

--10.2 一维数据空间(II)

--10.3 二维数据空间

--10.4 高维数据空间

--第10章 作业

-第11章 相随相伴、谓之关联

--11.1 导引

--11.2 关联规则(I)

--11.3 关联规则(II)

--11.4 关联规则(III)

--第11章 作业

-第12章 既是世间法、自当有分别

--12.1 导引

--12.2 近邻法(I)

--12.3 近邻法(II)

--12.4 决策树(I)

--12.5 决策树(II)

--12.6 随机森林

--12.7 朴素贝叶斯

--12.8 逻辑斯蒂回归

--12.9 人工神经网络(I)

--12.10 人工神经网络(II)

--12.11 支持向量机

--第12章 作业

-第13章 方以类聚、物以群分

--13.1 导引

--13.2 划分方法

--13.3 层次方法

--第13章 作业

-第14章 庐山烟雨浙江潮

--第14章 庐山烟雨浙江潮

--第14章 作业

6.6 函数(II)笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。