0%

R 语言入门与实践(1):非均匀骰子项目

最近媳妇为了她自己的事业,说要学习 R 语言。权且无论其是否能够坚持,至少初心还是好的。尽管雄心勃勃,但对于一个没有任何编程基础的人来说,要学习一门编程语言还是难度较大。为了降低她的入门难度,同时从实际行动上表达我对她事业的支持,我也和她一起学习 R 语言。

该系列文章是《R 语言入门与实践》一书的学习笔记,尽管这本书比较老了,但是非常基础,比较适合没有编程经验的读者学习 R 语言。

R 基础

数据科学的基石是存储大量数据并根据需求随时调用数据的能力。对于每天跟数据打交道的人来说,编程是一项必须的技能。图形用户界面简单易用,但也有着根本的局限性,因为它束缚了 好的数据分析 应该具备的以下三个属性:

  • 可再现性:能够完全再现之前某个分析结果的能力
  • 自动化:当数据发生改变时,能够快速更新分析结果的能力
  • 沟通:代码仅仅是一段文本,十分利于沟通

R 语言的传统强项是建模与作图,但是本书将 R 语言视为一门纯粹的编程语言,而不是统计软件。在使用 R 之前,首先需要安装 RRStudio

本章的项目是要模拟一对骰子,并利用这对骰子生成随机数字。要模拟一对骰子,你必须将每个骰子的特征提取出来。

R 的用户界面

在正式的 R 编程之前,首先需要学习 RStudio 的使用。RStudio 是 R 语言的开发工具,方便我们进行 R 程序的开发调试。如下是在 RStudio 的控制台界面输入一段代码(命令):

1
2
3
> 1 + 1
[1] 2
>

这里输出结果 2 之前的 [1] 表示其为输出结果中的第一个值。当返回结果有多个值,R 可能需要多行来显示,并且用括号内嵌数字来标示每一行的起始数值在整个返回结果中的序号:

1
2
3
4
5
6
7
> 1:100
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
[19] 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
[37] 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
[55] 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
[73] 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
[91] 91 92 93 94 95 96 97 98 99 100
  • 当输入的代码不完整时,控制台提示符会显示一个 +,表示等待继续输入代码。
  • R 解释器会忽略这些以 # 开头的代码行,所以在 R 语言中可以使用 # 开启注释
  • 输入 Ctrl-C 可以终止控制台中 R 代码的执行

不同于 C 语言这种静态编程语言,R 语言是一种动态编程语言,这就意味着你在运行 R 代码时,编译操作会由 R 自动完成。

对象

在 R 中,保存数据就是将数据存储到 R 对象中:要创建一个 R 对象,先确定一个名称,再使用赋值符号(<-)将数据赋值给它。此时 R 会生成一个具有该名称的 R 对象,并将赋值符号后面的数值存储在其中。每次创建一个 R 对象后,该对象就会显示在 RStudio 的环境面板上。

1
2
3
> die <- 1:6
> die
[1] 1 2 3 4 5 6

R 对象命名规范:

  • 不能以数字开头
  • 一些特殊符号不能使用,例如 ^、!、$、@、+、-
  • R 对象名称区分大小写

同一个对象如果被多次赋值,那么 R 会自动覆盖存储在该对象中的信息。可以使用 ls 查看已经命名了哪些 R 对象。

如果在一个运算中涉及两个或两个以上的向量,R 会将这些向量排成一行并执行一系列单独的运算。如果两个向量的长度不相等,R 会在较短的向量上重复,直到其长度与较长向量相同,然后再执行运算。需要注意,此时短向量本身不会产生任何实质性的影响。如果长向量的长度值不是短向量长度值的整数倍,那么 R 在返回运算结果的同时也会返回一条警告消息。R 的这种行为称为向量循环,可帮助 R 执行元素方式运算

1
2
3
4
> die
[1] 1 2 3 4 5 6
> die * die
[1] 1 4 9 16 25 36
1
2
3
4
5
6
7
8
9
10
11
> 1:2
[1] 1 2
> 1:4
[1] 1 2 3 4
> die + 1:2
[1] 2 4 4 6 6 8
> die + 1:4
[1] 2 4 6 8 6 8
Warning message:
In die + 1:4 :
longer object length is not a multiple of shorter object length

R 语言同样支持传统的矩阵乘法,只是在你需要使用矩阵乘法时,必须做出请求,例如使用 %*% 来执行内乘法,使用 %o% 执行外乘法,使用 t 命令执行矩阵转置乘法,使用 det 命令获取矩阵的行列式。

函数

R 中函数的使用方法非常简单,只需要使用函数名称并在其后的括号中键入相应的数据即可。

1
2
3
4
> round(3.15)
[1] 3
> factorial(3)
[1] 6

传递到函数中的数据被称为该函数的参数。参数可以是原始数据、R 对象、甚至是另一个 R 函数的返回结果。

1
2
> round(mean(die))
[1] 4

R 中提供了 sample 函数,可以模拟投骰子。sample 函数有两个参数:一个名为 x 的向量和一个名为 size 的数字。sample 的作用就是从向量 x 中抽取 size 个元素并返回。

1
2
3
4
5
6
> sample(x = die, size = 1)
[1] 6
> sample(x = die, size = 1)
[1] 2
> sample(x = die, size = 1)
[1] 5

在调用函数时,可以设置将哪个数据对象赋值给该函数中的哪个参数,方法是将这个数据对象的名称与参数用等号连接起来。在给有着多个参数的函数设置参数值时尤为重要:将数据对象明确指定给某个参数名称,可以避免错误传递数据。

在调用一个包含多个参数的函数时,从第二个或者第三个参数开始,就应该写出每个参数的名称。因为第一个参数的含义通常指向是明确的,但是要记住函数的第三、第四个参数的含义,就比较困难。而且详细写出函数名称可以防止出现错误。如果没有写出参数名称,那么 R 会按照顺序将你的值与函数的参数匹配。如果详细写出参数名称,R 会始终将某个值与其参数名称相匹配,而无论其参数顺序如何。

1
2
3
4
5
6
> sample(die, 1)
[1] 6
> sample(1, die)
Error in sample.int(x, size, replace, prob) : 'size'参数不对
> sample(size = 1, x = die)
[1] 2

可以使用 args 函数查看某个函数的所有参数名称:

1
2
3
> args(sample)
function (x, size, replace = FALSE, prob = NULL)
NULL

函数的参数可以有默认值,这些参数被称为可选参数。可选参数如果没有被明确赋值,就会使用其默认值。

可放回抽样

在使用 sample 函数时,如果将 size 设置为 2,那么几乎可以模拟一对骰子。但是这种方法有个问题,两个骰子的点数不可能一模一样。因为 sample 默认使用的是不可放回抽样。如果额外设定参数 replace = TRUE,那么就可以实现可放回抽样。

1
2
3
4
5
6
7
8
9
10
11
12
> sample(size = 2, x = die)
[1] 1 6
> sample(size = 2, x = die)
[1] 2 3
> sample(size = 2, x = die)
[1] 2 4
> sample(size = 2, x = die, replace=TRUE)
[1] 1 1
> sample(size = 2, x = die, replace=TRUE)
[1] 3 6
> sample(size = 2, x = die, replace=TRUE)
[1] 2 2

可放回抽样法是创建独立随机样本的一种简单方法。

1
2
3
4
5
6
7
> dice = sample(size = 2, x = die, replace=TRUE)
> dice
[1] 4 5
> dice
[1] 4 5
> sum(dice)
[1] 9

编写自定义函数

接下来编写一个函数 roll,用来投掷虚拟的骰子。R 函数其实就是一种不同类型的 R 对象,它们包含的不是数据,而是代码。任何一个 R 函数都包含 3 个部分:

  • 函数名
  • 函数主体
  • 参数集合

如下将投掷骰子的过程模拟成一个函数:

1
2
3
4
5
roll <- function() {
die <-1:6
dice <- sample(die, size = 2, replace = TRUE)
sum(dice)
}
  • function 的作用是将大括号内的所有代码都构建成一个函数
  • 大括号内的每一行代码都进行了缩进,目的是提高代码的可读性
  • 利用 function 构建函数之后,可以将其输出保存到某个 R 对象中。这个对象就是构建的新函数
  • 使用对象名称 + () 即可调用函数(假设没有参数)

函数的运行结果就是最后一行代码(逻辑上)的运行结果。如果最后一行不返回任何值,整个函数也不会返回任何值。

创建自定义函数后,R 对待它的方式与 R 中其他函数没有任何区别。

参数

如下定义的函数包含了 bones 这个参数。那么调用该函数时也需要传递对应的参数:

1
2
3
4
5
6
roll2 <- function(bones) {
dice = sample(bones, size = 2, replace = TRUE)
sum(dice)
}

roll2(bones = 1:4)

此时调用 roll2 时如果不提供参数,就会出现错误。我们可以为该函数提供一个初始默认值:

1
2
3
4
5
6
roll2 <- function(bones = 1:6) {
dice = sample(bones, size = 2, replace = TRUE)
sum(dice)
}

roll2(bones = 1:4)

自定义函数中可以设置任意数量的参数,只要在 function 后的括号列表中列出它们的名称即可,并用逗号隔开。

脚本

可以通过 R 脚本创建代码草稿。在 RStudio 中,使用 File->New File->R Script 来创建一个新的 R 脚本。

想要自动运行脚本中的某一行代码,只需要单击 Run 按钮即可。R 默认会运行鼠标光标所在的那一行代码,如果在脚本中选中了一整段代码,R 就会运行这段代码。通过 Source 按钮,可以运行脚本中的所有代码。

RStudio 还内置了创建函数的工具,只需要将 R 脚本中想转换为函数的代码段选中,使用 Code->Extract Function 即可提取到函数。它会自动检查代码段中没有定义的变量,并将它们设置为函数参数。

R 包与帮助文档

接下来让这对骰子投出大点数的概率稍高于投出小点数的概率。在对这对骰子加权之前,首先需要确保这对骰子原本是均匀的。有两个工具可以实现这一点:重复(repetition)和可视化(visualization)。这也是数据科学领域最有用的两个工具。

R 中的 replicate 函数可以重复某一动作,qplot 可以将结果可视化。qplot 并不是 R 自带的函数,它来自一个独立的 R 包。

R 包

很多开发者已经利用 R 设计了可以帮助人们分析数据的工具,这些工具提供给大家无偿使用,只需要下载对应的 R 包即可。一个 R 包就是一些有用的函数、帮助文档和数据集的集合。在加载 R 包之后,就可以在 R 代码中使用这个 R 包所提供的函数了。R 包的作者们构成了庞大的 R 社区(很多 R 包作者同时也是非常活跃的数据科学家);许多已经写好的、针对某些常见的数据科学任务可以拿来即用。

R 基础包是指 每次打开 R 之后,R 默认就加载的所有函数。这些函数构成了 R 语言的基石,不需要加载任何 R 包就可以使用它们。

想要安装某个 R 包,首先需要在计算机中安装这个包,然后在当前的 R 会话中加载它。安装 R 包最简单的方法是利用 install.packages 函数。打开 R 并在命令行中输入以下命令:

1
install.packages("包名称")

R 会在 CRAN 上的所有 R 包集合中搜索具有该名称的包,搜索成功后则会下载并将其安装到本地计算机的 R 包文件夹内。本地安装完成之后,在以后的 R 会话中要使用该包,只需加载即可,无需重新下载安装。也可以同时安装多个 R 包,只需要使用连接函数 c 将这些包的名称连接起来即可:

1
install.packages(c("ggplot2", "reshape2", "dplyr"))

通过如下方式在 R 会话中加载 R 包(这里包名称的双引号是可选的):

1
library(包名称)

library 命令的效果是,在当前 R 会话被关闭之前,该包中的所有函数、数据集和帮助文档都可以被拿来即用。如果想查看当前 R 库中存储了哪些 R 包,可以运行如下代码:

1
library()
1
2
3
4
5
6
7
8
9
10
11
Packages in library ‘/Library/Frameworks/R.framework/Versions/4.3-arm64/Resources/library’:

base The R Base Package
boot Bootstrap Functions (Originally by Angelo Canty for S)
class Functions for Classification
cluster "Finding Groups in Data": Cluster Analysis Extended Rousseeuw
et al.
codetools Code Analysis Tools for R
compiler The R Compiler Package
datasets The R Datasets Package
......

可以看到,library() 还会显示 R 库的实际文件路径,即包含所有 R 包的文件夹位置。

许多 R 包并没有在 CRAN 上托管,要安装这些 R 包,可以借助 devtools 这个 R 包。它提供了 install_github, install_gitorious, install_bitbucket, install_url 等函数。它们类似于 install.packages,但是安装源不同。

只安装和加载自己需要的 R 包可以减轻 R 软件的负载,也使得 R 的使用更加迅捷。同时也可以在不更新 R 的情况下,只更新某个单独的 R 包。

安装并加载 ggplot2 包

如下安装并加载 ggplot2

1
2
> install.packages("ggplot2")
> library("ggplot2")

接下来就可以使用该包中的函数了,例如:

1
2
3
4
5
6
> qplot
function (x, y, ..., data, facets = NULL, margins = FALSE, geom = "auto",
xlim = c(NA, NA), ylim = c(NA, NA), log = "", main = NULL,
xlab = NULL, ylab = NULL, asp = NA, stat = deprecated(),
position = deprecated())
......

qplot 的含义是快速绘图(quick plot),如果把两个长度相同的数值交给 qplot,它会绘制出一副散点图,它将第一个向量作为一组 x 值,将第二个向量作为一组 y 值。如下是一个示例:

1
2
3
x <- c(-1, 0, 1, 2, 2.1, 2.2)
y <- x^3
qplot(x, y)

这个例子使用了 c 函数来创建数值向量(c 代表 concatenate)。

在描述两个变量之间的关系时,散点图是非常有用的工具。而直方图则可以用来可视化单一变量的分布情况:对于这个变量中的每一个值 x,直方图会展示有多少数据点落在这个值内。

1
2
x <- c(1, 2, 2, 2, 3, 3)
qplot(x, binwidth = 1)

replicate 函数提供了快速重复运行一段 R 代码的快捷方法。如果需要使用它,需要提供你想要重复运行的次数,已经想要重复运行的 R 代码。

1
2
> replicate(3, 1 + 1)
[1] 2 2 2

接下来使用 qplot 模拟骰子投递过程:

1
2
3
4
5
6
7
roll2 <- function(bones = 1:6) {
dice = sample(bones, size = 2, replace = TRUE)
sum(dice)
}

rolls = replicate(10000, roll2())
qplot(rolls, binwidth = 1)

得到如下结果:

帮助页面

每个 R 函数都有自己的帮助页面,只需要键入问号并加上函数的名称既可以查看该函数的帮助页面:

1
2
> ?qplot
> ?sample

如果一个函数来自某个 R 包,但并没有安装或加载该 R 包,那么使用上述方式并不能获得该函数的帮助页面。

一个帮助页面通常包含以下几个部分:

  • 函数描述
  • 使用方法
  • 参数
  • 相关细节
  • 返回值
  • 另请参阅
  • 代码示例

如果忘记了函数的确切名称,又想找到这个函数的帮助页面,可以使用关键词搜索,即在关键词之前键入两个问号:

1
2
> ??log
> ??plot

通过阅读 sample() 函数的帮助文档,可以得知其通过 prob 参数可以调整元素的权重:

1
2
3
4
5
6
7
roll2 <- function(bones = 1:6) {
dice = sample(bones, size = 2, replace = TRUE, prob=c(1/8, 1/8, 1/8, 1/8, 1/8, 3/8))
sum(dice)
}

rolls = replicate(10000, roll2())
qplot(rolls, binwidth = 1)

此时新的 点数和 模拟图如下所示:

Reference