跳至主要内容

R 实用工具教程

了解在 R 编程语言中用于数据结构操作、嵌套列表、正则表达式,以及处理时间和日期的多种实用函数。
· 14分钟 阅读

使用 AI 探索

在本教程中,您将学习 R 编程语言中一些简单而常用的函数和工具。我们将先讨论一些与数学相关的函数,然后再看看与 R 的数据结构更密切相关的函数,例如操作嵌套列表、正则表达式、时间和日期。

数学函数

先来看几段使用多个数学函数的代码。

您将先定义两个向量,分别为 x 和 y,它们包含正数和负数。

x <- c(1.1, -2.3, -4.5)
y <- c(2.4,-44, -2.2)
print(x)
[1]  1.1 -2.3 -4.5
print(y)
[1]   2.4 -44.0  -2.2

让我们对这两个向量 x 和 y 进行如下操作:

  • 取其绝对值,
  • 四舍五入到 0 位小数,
  • 分别求和,
  • 最后,计算二者的平均值。
mean(c(sum(round(abs(x))),sum(round(abs(y)))))
27.5

不妨把上面这行代码拆开,逐个函数仔细看看。

  • abs() 函数会将向量 x 和 y 的元素取为正值(绝对值)。

例如,对 x 和 y 应用 abs() 后,您会得到如下全为正数的结果。

abs(x)
  1. 1.1
  2. 2.3
  3. 4.5
abs(y)
  1. 2.4
  2. 44
  3. 2.2
  • 接下来是 round() 函数。它用于对输入数值进行四舍五入。该函数可接受一个额外参数,用于指定要保留的小数位数。一般情况下,round() 会四舍五入到 0 位小数。

例如,将 round() 应用于向量 x 和 y 后,输出如下:

round(x)
  1. 1
  2. -2
  3. -4
round(y)
  1. 2
  2. -44
  3. -2
  • sum() 函数会计算向量或矩阵元素之和。例如,如果传入一个行向量,函数会返回该行向量所有元素之和(标量)。

此处将向量 x 和 y 传给了 sum()。因此,R 会分别计算各自的元素之和,并返回标量结果。

sum(abs(round(x)))
7
sum(abs(round(y)))
 48
  • 最后,mean()(平均值)函数会计算算术平均数,即将一组数值相加后除以该组数值的个数。

在此例中,传入 mean() 的是一个长度为 2 的行向量,包含 7 和 48。因此其均值为这两个数的和除以元素个数(即 2)。

mean(c(7,48))
27.5

这还不算难,对吧?

现在进入本教程下一个更有趣的部分!

数据结构相关函数

现在来看看一些数据结构,如列表和向量。您将学习对 list 数据结构进行操作、反转一个 list,以及如何在列表与向量之间互相转换。

下面的函数是一个 list(),也可以说是一个 list of lists(列表的列表),也称为嵌套列表。它创建一个元素列表,这些元素可以是逻辑值、数值和字符串。

在下面的示例中,一个列表中包含三个子列表,分别为 log、ch 和 int_vec。它们分别对应逻辑、数值和字符串/字符类型的 R 对象。

list_define <- list(log = TRUE, ch = "hello_datacamp", int_vec = sort(rep(seq(8,2, by = -2), times = 2)))
list_define
$log
TRUE
$ch
'hello_datacamp'
$int_vec
  1. 2
  2. 2
  3. 4
  4. 4
  5. 6
  6. 6
  7. 8
  8. 8
  • log 是一个逻辑值,TRUE 或 FALSE
  • ch 是字符字符串 hello_datacamp
  • int_vec 是一个数值序列。

由于 log 和 ch 比较直观,我们重点看看 int_vec。

int_vec = sort(rep(seq(8,2, by = -2), times = 2))
int_vec
  1. 2
  2. 2
  3. 4
  4. 4
  5. 6
  6. 6
  7. 8
  8. 8

我们一步步理解上面的表达式。

seq 函数按降序生成一个从 8 到 2 的数列。

seq() 的语法为:seq(x1,x2, by = y)

前两个参数 x1 和 x2 指定序列的范围,即从哪里开始、到哪里结束。by 参数指定每一步的增量或减量。

例如,下面这行代码将生成一个从 100 到 200、步长为 20 的序列。

seq(100,200, by = 20)
  1. 100
  2. 120
  3. 140
  4. 160
  5. 180
  6. 200

在我们的示例中,序列函数会输出从 8 到 2(包含端点)、步长为 -2 的序列,得到长度为 4 的向量。

a = seq(8,2, by = -2)
a
  1. 8
  2. 6
  3. 4
  4. 2

接着理解 rep 函数。

它可以使用 times 参数重复输入参数(通常是向量或列表),times 接受整数,表示要重复的次数。rep 接受两个参数:输入对象和要重复(复制)的次数。

将 rep 应用于我们的示例(长度为 4 的向量),会得到长度为 8 的输出向量。

b = rep(a, times = 2)

如果您想重复向量或列表的每个元素(而不是整个向量),可以用 each 参数作为 times 的替代。

使用 times 与 each 的显著区别是元素出现的模式不同。

rep(a, each = 2)
  1. 8
  2. 8
  3. 6
  4. 6
  5. 4
  6. 4
  7. 2
  8. 2

最后是 sort() 函数。它是一个用途直观且通用的排序函数,可用于对向量或列表等多种数据结构排序。不仅限于数值,也可用于逻辑值和字符。默认按升序排序。

让我们把 rep 的输出传给 sort,得到最终结果。

sort(b)
  1. 2
  2. 2
  3. 4
  4. 4
  5. 6
  6. 6
  7. 8
  8. 8

很好!您用这样简单的方式就搞定了列表 list_define 中的长表达式 int_vec。

接下来,我们查看列表 list_define 的内容,这里将用到 str() 函数。R 中的 str() 可用于显示 R 对象的结构。

str(list_define)
List of 3
 $ log    : logi TRUE
 $ ch     : chr "hello_datacamp"
 $ int_vec: num [1:8] 2 2 4 4 6 6 8 8

来看看一些很实用的 R 表达式:

  • is 系列函数可用于检查数据结构的类型,返回逻辑值,这在处理条件语句时很有用。
is.list(list_define) #如果参数是列表则返回 true。
TRUE

而如果传入的是向量(非列表),则会返回 FALSE,如下所示。

is.list(c(1,2,3)) #传入向量,因此返回 false。
FALSE
  • 在 R 中,将向量转换为列表非常简单。使用 as 函数配合 .list(),并将向量作为参数传入即可完成转换。
vec_to_list <- as.list(c(1,2,3))
is.list(vec_to_list) #用 is.list() 验证
TRUE
  • 反过来,可以使用 unlist 将列表展开为向量。R 会通过“压平”整个列表结构来完成转换,并最终输出单个向量。
list_to_vec <- unlist(vec_to_list)
list_to_vec
  1. 1
  2. 2
  3. 3

类似于 is.list(),您可以使用 is.vector() 判断给定参数是否为向量。

is.vector(list_to_vec)
TRUE

让我们把大列表 list_define 转换为向量。需要注意的是,向量只能包含单一数据类型或 R 对象。因此,logical 和 numerical 值都会被转换为字符串。

unlist(list_define)
log
'TRUE'
ch
'hello_datacamp'
int_vec1
'2'
int_vec2
'2'
int_vec3
'4'
int_vec4
'4'
int_vec5
'6'
int_vec6
'6'
int_vec7
'8'
int_vec8
'8'

在进入下一主题前,看看 append() 和 rev() 函数。

  • 顾名思义,append() 函数可将两个或多个向量或列表追加到已有或新建的向量或列表中。

我们在 list_define 上试试 append()。您会发现列表元素数量从 3 变为 6,因为我们把同一个列表追加到了自身。

str(append(list_define, list_define))
List of 6
 $ log    : logi TRUE
 $ ch     : chr "hello_datacamp"
 $ int_vec: num [1:8] 2 2 4 4 6 6 8 8
 $ log    : logi TRUE
 $ ch     : chr "hello_datacamp"
 $ int_vec: num [1:8] 2 2 4 4 6 6 8 8
  • 最后,使用 R 中的 rev() 函数反转(改变顺序)list_define 列表。
str(rev(list_define))
List of 3
 $ int_vec: num [1:8] 2 2 4 4 6 6 8 8
 $ ch     : chr "hello_datacamp"
 $ log    : logi TRUE

正则表达式

很多人觉得正则表达式比较难学。然而它不仅在 R 中至关重要,在 Python 等多种编程语言中也同样重要。包括 R 在内的许多语言都内置了正则表达式能力。

正则表达式应用广泛,尤其在需要对文本数据进行预处理时非常有用。它也常用于各种自然语言处理(NLP)问题。

它还被用于查询搜索引擎和文本编辑器中。

正则表达式(也称 regex、regexp 或 rational operators)是一串定义搜索模式的字符序列。通常,这些搜索模式被各种字符串搜索算法用于查找模式、查找并替换模式,或筛选匹配的模式。

我们先通过 grep() 和 grepl() 函数来入门。

为简单起见,先定义一个长度为 5 的行向量 animals_regex,后续我们将在其上应用正则模式。

animals_regex <- c('cat','dog','cheetah','lion','mice')

先来了解 grepl()。grepl() 返回逻辑值:若字符串匹配模式则为 TRUE,否则为 FALSE。

下面是 grepl() 的简单直观语法:第一个参数是要匹配的模式,第二个参数是要在其中查找或筛选模式的字符串或输入。暂时可以忽略其余参数。grepl(pattern, x, ignore.case = FALSE, perl = FALSE, fixed = FALSE, useBytes = FALSE) 让我们用 grepl() 找出上面五种动物名称中包含字母 c 的。

此处我们要找的是包含 c 的动物名,因此模式就是 c 本身。

grepl(pattern = 'c', x = animals_regex)
  1. TRUE
  2. FALSE
  3. TRUE
  4. FALSE
  5. TRUE

从上面的输出可见,cat、cheetah 和 mice 中含有 c,因此在向量 animals_regex 的这些位置返回 TRUE,而不匹配模式 c 的则返回 FALSE。

我们再找找以 c 开头的元素,而不仅仅是名称中包含 c 的。

要实现这一点,只需在要查找的模式开头使用脱字符号 ^。

grepl(pattern = '^c', x = animals_regex) #只有 cat 和 cheetah 以 `c` 开头。
  1. TRUE
  2. FALSE
  3. TRUE
  4. FALSE
  5. FALSE

由此可见,只有 cat 和 cheetah 以 c 开头,因此仅这些位置返回 TRUE。

与 ^ 类似,$ 可用于要查找的模式末尾,以匹配以该模式结尾的元素。要找出以 n 结尾的动物,只需使用 n 后跟 $。

grepl(pattern = 'n$', x = animals_regex) #只有 lion 以 `n` 结尾。
  1. FALSE
  2. FALSE
  3. FALSE
  4. TRUE
  5. FALSE

注意:想了解更多正则表达式内容,可在 jupyter notebook 代码单元中输入 ?regex,即会弹出 regex 文档。另外,您也可以查看这个资源,它提供了设计搜索模式的工具,并支持在输入字符串上测试。

  • 与 grepl() 类似,grep() 函数返回的不是逻辑值,而是匹配给定模式的向量/矩阵元素的索引。

grep() 的语法与 grepl() 完全相同:grep(pattern, x, ignore.case = FALSE, perl = FALSE, value = FALSE, fixed = FALSE, useBytes = FALSE, invert = FALSE)。让我们用与上面相同的例子,这次应用 grep()!

grep(pattern = 'c', x = animals_regex)
  1. 1
  2. 3
  3. 5

如您所料,上面输出的是 cat、cheetah 和 mice 的索引,而不是 TRUE/FALSE。基本就是这样!

我们再用 which 来比较 grep() 和 grepl()。which() 会返回逻辑对象中为 TRUE 的索引。

把这些联系起来:由于 grepl() 能返回逻辑对象,我们可以将其结果传给 which(),从而得到与 grep() 类似的输出。

which(grepl(pattern = 'c', x = animals_regex))
  1. 1
  2. 3
  3. 5

与 grepl() 一样,grep() 也能处理不同类型的正则模式。

如果用 grep() 查找 animals_regex 向量中以 n 结尾的元素,预期输出为 4,因为只有 lion 以 n 结尾,如下所示。

grep(pattern = 'n$', x = animals_regex)
4

干得好!

您已经学会了正则表达式的一些基础用法,例如如何从向量中过滤出匹配给定模式的元素。不过,R 的能力远不止于模式匹配。它还有很多相关函数,其中 sub() 就是一个。

sub() 函数不是筛选匹配的模式,而是将匹配项替换为其他字符串。我们更深入地理解一下!

  • sub() 主要接受三个输入参数:
    • pattern:您要匹配的正则表达式,
    • replacement:用于替换匹配元素的值,
    • x:要应用正则的输入向量字符串。

其语法为:sub(pattern, replacement, x, ignore.case = FALSE, perl = FALSE,fixed = FALSE, useBytes = FALSE)。这次我们仍然使用与上面相同的示例来理解其功能!

sub(pattern = 'c', replacement = 'a', x = animals_regex)
  1. 'aat'
  2. 'dog'
  3. 'aheetah'
  4. 'lion'
  5. 'miae'

从上面的输出可以看到:cat 被替换为 aat,cheetah 被替换为 aheetah,mice 被替换为 miae。这些元素上的模式都成功匹配并替换了。

另外请注意,sub() 只会查找字符串中的第一个匹配项。这意味着如果一个字符串中有两个 c,只有第一个会被替换为 a,第二个保持不变。

如果您希望替换字符串中所有匹配项,请使用 gsub(),这超出了本教程的范围!

在进入下个主题前,我们再试一个有趣的表达式。

这次将使用 |(或)运算符,它会尝试匹配任何一个已定义的模式,若匹配则替换为 -。请记住,由于这里使用的是 gsub(),它会替换字符串中每一个匹配项。

gsub(pattern = 'c|d|l', replacement = '-', x = animals_regex) #包含 `c`、`d`、`l` 的动物名将被替换为 `-`。
  1. '-at'
  2. '-og'
  3. '-heetah'
  4. '-ion'
  5. 'mi-e'

接下来进入今天教程的最后一个主题:时间与日期!

时间与日期

时间和日期信息在很多场景中都很有用。比如,假设您在做计算机视觉相关问题,想知道算法运行的 FPS(每秒帧数)。在这种用例中,您可以使用时间对象来衡量算法的处理速度。对于时间序列预测、季节性研究等问题,R 的潜力也能得到充分发挥。

先来个入门示例,用 R 打印今天的日期,只需一个简单命令 Sys.Date()。这里的 Sys 指系统,表示返回系统日期。

Sys.Date()

很简单,对吧?

R 的时间和日期属于 Date 对象,或者说其数据类型为 Date。您可以用 class 函数(参见R 中的数据类型教程)来验证。

与日期函数类似,还有 time() 函数会返回系统当前时间,实际上它会同时返回时间和日期。

Sys.time()
[1] "2020-02-04 02:05:04 IST"

创建日期对象

您已经学会了获取当前日期和时间。现在来看看如何通过传入一个字符串来创建其他日期的对象。

要创建 1993 年 5 月 10 日的日期对象,使用以下语法:

date_may <- as.Date('1993-05-10') #将字符字符串转换为日期对象
date_may

class(date_may)
'Date'

需要注意的一点是,R 的 Date 函数默认要求日期格式为 YYYY-MM-DD。如果将年与月或日互换,会报错。我们来试试!

date_may <- as.Date('05-1993-10') #R 默认遵循 ISO 日期格式
Error in charToDate(x): character string is not in a standard unambiguous format
Traceback:


1. as.Date("05-1993-10")

2. as.Date.character("05-1993-10")

3. charToDate(x)

4. stop("character string is not in a standard unambiguous format")

好在,您可以通过传入 format 参数显式指定格式并进行自定义。

date_may <- as.Date('05-1993-10', format = '%m-%Y-%d')

as.Date() 可以接受不同的日期格式,但最终会将其转换回 ISO 日期格式。打印 date_may 即可看到。

date_may

date_may <- as.Date('05-10-1993', format = '%m-%d-%Y')
date_may

日期运算

如果能对 R 中的日期对象进行加减等数学运算,是不是很棒?

我们给 date_may 加 1,您会看到日期会增加一天。

date_may + 1

很好,如上所示,加 1 后日期从 1993 年 5 月 10 日变为 1993 年 5 月 11 日。类似地,您也可以从日期中减去 1。

再比如,假设您想计算自己与年长兄弟姐妹的出生日期之间的时间差。

elder_sib <- as.Date('1989-03-21')
date_may - elder_sib
Time difference of 1511 days

结语

恭喜您完成本教程。

本教程是初学者了解 R 中各类实用函数的良好起点。作为练习,您可以进一步学习正则表达式,它应用广泛,在清洗或预处理文本数据时非常强大。

如对本教程有任何疑问,欢迎在下方评论区留言。

如果您想进一步学习 R,请参加 DataCamp 的 Intermediate R 课程,并查看我们的 Using Functions in R 教程。

主题