R语言编程:基于tidyverse
上QQ阅读APP看书,第一时间看更新

0.3 R语言编程思想

0.3.1 面向对象

R语言是一种基于对象的编程语言,即在定义类的基础上,创建与操作对象,而数值向量、函数、图形等都是对象。Python的一切皆为对象也适用于R语言。

a = 1L
class(a)
## [1] “integer”
b = 1:10
class(b)
## [1] “integer”
f = function(x) x + 1
class(f)
## [1] “function”

早期的R语言和底层R语言中的面向对象编程是通过泛型函数来实现的,以S3类、S4类为代表。新出现的R6类更适合用来实现通常所说的面向对象编程,包括类、属性、方法、继承、多态等概念。

面向对象的内容是R语言编程的高级内容,本书不做具体展开,只在附录中提供一个用R6类面向对象编程的简单示例。

0.3.2 面向函数

笼统地来说,R语言的主要工作就是对数据应用操作。这个操作就是函数,包括R语言自带的函数,各种扩展包里的函数以及自定义的函数。

所以,使用R语言的大部分时间都是在与函数打交道,学会了使用函数,R语言也就学会了一半,很多人说R简单易学,也是因此。

代码中的函数是用来实现某个功能。很多时候,我们使用R语言自带的或来自其他包中的现成函数就够了。

那么,如何找到并使用现成函数解决自己想要解决的问题?比如想做线性回归,通过查资料知道是用R语言自带的lm()函数实现。那么先通过以下命令打开该函数的帮助,如图0.8所示:

?lm

图0.8 R函数的帮助页面

执行“?函数名”(若函数来自扩展包需要事先加载包),在Rstudio右下角窗口打开函数帮助界面,一般至少包括如下内容:

函数描述(Description);

函数语法格式(Usage);

函数参数说明(Arguments);

函数返回值(Value);

函数示例(Examples)。

先阅读函数描述、参数说明、返回值,再调试示例,我们就能快速掌握该函数的使用方法。

函数包含很多参数,常用参数往往只是前几个。比如lm()的常用参数如下所示。

formula:设置线性回归公式形式“因变量~自变量+自变量”。

data:提供数据(框)。

接下来使用自带的mtcars数据集演示,按照函数参数要求的对象类型提供实参:

head(mtcars)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1
model = lm(mpg ~ disp, data = mtcars)
summary(model)      # 查看回归汇总结果
##
## Call:
## lm(formula = mpg ~ disp, data = mtcars)
##
## Residuals:
##     Min      1Q  Median      3Q     Max
## -4.8922 -2.2022 -0.9631  1.6272  7.2305
##
## Coefficients:
##              Estimate Std. Error t value Pr(>|t|)
## (Intercept) 29.599855   1.229720  24.070  < 2e-16 ***
## disp        -0.041215   0.004712  -8.747 9.38e-10 ***
## ---
## Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ‘ 1
##
## Residual standard error: 3.251 on 30 degrees of freedom
## Multiple R-squared:  0.7183, Adjusted R-squared:  0.709
## F-statistic: 76.51 on 1 and 30 DF,  p-value: 9.38e-10

所有的R函数,即使是陌生的,也都可以这样来使用。

编程中一种重要的思维就是函数式思维,包括自定义函数(把解决某问题的过程封装成函数)和泛函式编程(把函数依次应用到一系列的对象上)。

如果找不到现成的函数解决自己的问题,那就需要自定义函数,R自定义函数的基本语法如下:

函数名 = function(输入1, ..., 输入n) {
...
return(输出)        # 若有多个输出, 需要打包成一个list
}

比如,想要计算很多圆的面积,就有必要把如何计算一个圆的面积定义成函数,需要输入半径,才能计算圆的面积:

AreaCircle = function(r) {
S = pi * r * r
return(S)
}

有了函数之后,再计算圆的面积,你只需要把输入给函数,它就能在内部进行相应处理,把你想要的输出结果返回给你。如果想批量计算圆的面积,按泛函式编程思维,只需要将该函数依次应用到一系列的半径上即可。

比如计算半径为5的圆的面积和批量计算半径为247的圆的面积,代码如下所示:

AreaCircle(5)
## [1] 78.53982
rs = c(2,4,7)
map_dbl(rs, AreaCircle)      # purrr包
## [1]  12.56637  50.26548 153.93804

定义函数就好比创造一个模具,调用函数就好比用模具批量生成产品。使用函数最大的好处就是将某个功能封装成模具,从而可以反复使用。这就避免了写大量重复的代码,程序的可读性也大大加强。

0.3.3 向量化编程

高级编程语言提倡向量化编程[2],说白了就是对一列数据、矩阵或多维数组中的数据同时做同样的操作,既提升程序效率又大大简化代码。


[2] 向量化编程中的向量,泛指向量、矩阵、多维数组。

向量化编程关键是要用整体考量的思维来思考和表示运算,这需要用到线性代数的知识,其实我觉得线性代数最有用的知识就是用向量、矩阵表示运算。

比如考虑n元一次线性方程组:

若从整体的角度来考量,可以引入矩阵和向量:

前面的n元一次线性方程组,可以向量化表示为:

可见,向量化表示大大简化了表达式。这放在编程中,就相当于本来用两层for循环才能表示的代码,简化为短短一行代码。

向量化编程其实并不难,关键是要转变思维方式:很多人学完C语言的“后遗症”,就是首先想到的总是使用for循环。想摆脱这种思维,可以调动头脑里的线性代数知识,尝试用向量、矩阵表示,长此以往,向量化编程思维就有了。

下面以计算决策树算法中的样本经验熵为例来演示向量化编程。

对于分类变量D表示第类数据所占的比例,则D的样本经验熵为:

其中,表示集合包含的元素个数。

在实际需求中,我们经常遇到要把数学式子变成代码,与前文所谈到的一样,首先你要看懂式子,用简单实例逐代码片段调试就能解决。

以著名的“西瓜书”(《机器学习》)中的西瓜分类数据中的因变量“好瓜”为例,表示是否为好瓜,取值为“是”和“否”:

y = c(rep(“是”, 8), rep(“否”, 9))
y
##  [1] “是” “是” “是” “是” “是” “是” “是” “是” “否” “否” “否” “否” “否”
## [14] “否” “否” “否” “否”

则D分为两类:为好瓜类,为坏瓜类。

从内到外先要计算,用向量化的思维同时计算,就是统计各分类的样本数,再除以总样本数:

table(y)                   # 计算各分类的频数, 得到向量
## y
## 否 是
##  9  8
p = table(y) / length(y)   # 向量除以标量
p
## y
##        否        是
## 0.5294118 0.4705882

继续代入公式计算,谨记R自带的函数天然就接受向量做输入参数:

log(p)                     # 向量取对数
## y
##         否         是
## -0.6359888 -0.7537718
p * log(p)                 # 向量乘以向量, 对应元素做乘法
## y
##         否         是
## -0.3366999 -0.3547161
- sum(p * log(p))          # 向量求和
## [1] 0.6914161

看着挺复杂的公式用向量化编程之后,核心代码只有两行:计算p和最后一行。这个实例虽然简单,但基本涉及所有常用的向量化操作:

向量与标量做运算;

向量与向量做四则运算;

把函数作用到向量。