机器学习中常用的 R 数据结构





5.00/5 (2投票s)
如何使用 R 和机器学习解决现实世界问题。
编程语言中存在各种类型的数据结构,每种结构都有其优点和缺点,适用于特定任务。由于 R 是一种广泛用于统计数据分析的编程语言,因此其数据结构的设计都考虑到了这类工作。
在机器学习中最常用的 R 数据结构是向量、因子、列表、数组、矩阵和数据框。每种结构都针对特定的数据管理任务进行了优化,因此了解它们在您的 R 项目中将如何交互非常重要。在本文中,我们将介绍不同类型的 R 数据结构,并理解它们之间的异同。
本文摘录自 Packt 出版社的书籍《Machine Learning with R - Third Edition》,作者是 Brett Lantz。这本书将帮助您使用 R 和机器学习解决现实世界中的问题。
向量
最基本的 R 数据结构是 **向量**,它存储一组称为 **元素** 的有序值。一个向量可以包含任意数量的元素。但是,它的所有元素必须是同一类型;例如,一个向量不能同时包含数字和文本。要确定向量 v
的类型,请使用 typeof(v)
命令。
机器学习中常用的几种向量类型有:integer
(不带小数的数字)、double
(带小数的数字)、character
(文本数据)和 logical
(TRUE
或 FALSE
值)。还有两个特殊值:NA
,表示缺失值;NULL
,用于表示任何值的缺失。虽然这两个值可能看起来相似,但它们确实略有不同。NA
值是其他内容的占位符,因此长度为一,而 NULL
值是真正空的,长度为零。
手动输入大量数据会很繁琐,但可以使用 c()
合并函数创建简单的向量。还可以使用箭头 <-
运算符为向量命名。这是 R 的赋值运算符,用法类似于许多其他编程语言中的 =
赋值运算符。
例如,让我们构建一组包含三名医疗患者数据的向量。我们将创建一个名为 subject_name
的字符向量来存储三名患者的姓名,一个名为 temperature
的数值向量来存储每位患者的体温(华氏度),以及一个名为 flu_status
的逻辑向量来存储每位患者的诊断(如果患有流感则为 TRUE
,否则为 FALSE
)。如下面的代码所示,这三个向量是
> subject_name <- c("John Doe", "Jane Doe", "Steve Graves")> temperature
<- c(98.1, 98.6, 101.4)> flu_status <- c(FALSE, FALSE, TRUE)
存储在 R 向量中的值会保留其顺序。因此,可以通过患者在数据集中的位置来访问每位患者的数据,从 1 开始,然后在向量名称后使用方括号(即 [
和 ]
)提供该数字。例如,要获取患者 Jane Doe(第二位患者)的体温值,只需键入
> temperature[2][1] 98.6
R 提供了多种从向量中提取数据的方法。可以使用冒号运算符获取一系列值。例如,要获取第二位和第三位患者的体温,请键入
> temperature[2:3][1] 98.6 101.4
可以通过指定负数项来排除项。要排除第二位患者的体温数据,请键入
> temperature[-2][1] 98.1 101.4
指定一个逻辑向量来指示是否应包含每个项有时也很有用。例如,要包含前两个体温读数但排除第三个,请键入
> temperature[c(TRUE, TRUE, FALSE)][1] 98.1 98.6
正如您很快就会看到的那样,向量为许多其他 R 数据结构奠定了基础。因此,掌握各种向量操作对于使用 R 中的数据至关重要。
有关最新的 R 代码、问题跟踪和公共 wiki,请加入社区!
因子
如果您还记得第 1 章“**机器学习入门**”,则名义特征代表具有类别值的特征。虽然可以使用字符向量来存储名义数据,但 R 提供了一种专门用于此目的的数据结构。**因子** 是向量的一种特殊情况,仅用于表示分类或有序变量。在我们正在构建的医疗数据集中,我们可以使用因子来表示性别,因为它使用了两个类别:男性和女性。
为什么要使用因子而不是字符向量?因子的一个优点是类别标签只存储一次。计算机存储 1
、1
、2
,而不是存储 MALE
、MALE
、FEMALE
,这可以减少存储值所需的内存。此外,许多机器学习算法对名义特征和数值特征的处理方式不同。将分类变量编码为因子可确保 R 正确处理分类数据。
要从字符向量创建因子,只需应用 factor()
函数即可。例如
> gender <- factor(c("MALE", "FEMALE", "MALE"))> gender[1] MALE FEMALE MALELevels: FEMALE MALE
请注意,当显示 gender
因子时,R 打印了有关其级别的额外信息。级别包含因子可能取的类别集合,在此例中是 MALE
或 FEMALE
。
创建因子时,我们可以添加原始数据中可能未出现的其他级别。假设我们为血型创建了另一个因子,如下面的示例所示
> blood <- factor(c("O", "AB", "A"),
levels = c("A", "B", "AB", "O"))> blood[1] O AB ALevels: A B AB O
当我们定义 blood
因子时,我们使用 levels
参数指定了一个包含四种可能血型的附加向量。结果是,尽管我们的数据仅包含 O、AB 和 A 血型,但 blood
因子保留了所有四种类型,如输出所示。存储附加级别允许将来添加其他血型患者的可能性。它还确保如果我们创建血型表,我们将知道 B 型血存在,尽管它在我们的初始数据中未找到。
因子数据结构还允许我们包含有关名义变量类别顺序的信息,这提供了一种创建有序特征的方法。例如,假设我们有关于患者症状严重程度的数据,这些数据按严重程度递增编码,从轻度到中度再到重度。我们通过按所需顺序提供因子的级别来指示有序数据的存在,这些级别按升序从最低到最高排列,并将 ordered
参数设置为 TRUE
,如下所示
> symptoms <- factor(c("SEVERE", "MILD", "MODERATE"),
levels = c("MILD", "MODERATE", "SEVERE"), ordered = TRUE)
结果的 symptoms
因子现在包含有关请求顺序的信息。与我们之前的因子不同,该因子的级别由 <
符号分隔,以指示从 MILD
到 SEVERE
的顺序存在
> symptoms[1] SEVERE MILD MODERATELevels: MILD < MODERATE < SEVERE
有序因子的一个有用功能是逻辑测试可以按预期工作。例如,我们可以测试每个患者的症状是否比中度更严重
> symptoms > "MODERATE"[1] TRUE FALSE FALSE
能够对有序数据进行建模的机器学习算法将需要有序因子,因此请确保相应地编码数据。
列表
**列表** 是一种数据结构,与向量类似,它用于存储一组有序的元素。但是,向量要求所有元素都必须是同一类型,而列表则允许收集不同的 R 数据类型。由于这种灵活性,列表通常用于存储各种类型的输入和输出数据以及机器学习模型的配置参数集。
为了说明列表,让我们考虑我们一直在构建的医疗患者数据集,其中六个向量中存储着三名患者的数据。如果我们想显示第一位患者的所有数据,我们需要输入五个 R 命令
> subject_name[1][1] "John Doe"> temperature[1][1] 98.1>
flu_status[1][1] FALSE> gender[1][1] MALELevels: FEMALE MALE>
blood[1][1] OLevels: A B AB O> symptoms[1][1] SEVERELevels: MILD < MODERATE < SEVERE
如果我们将来还要查看患者的数据,而不是重新键入这些命令,列表允许我们将所有值分组到一个对象中,我们可以重复使用该对象。
类似于使用 c()
创建向量,列表是使用 list()
函数创建的,如下面的示例所示。一个显著的区别是,当构造列表时,序列中的每个组件都应该有一个名称。名称并非严格必需,但允许稍后按名称而不是按数字位置访问值。要创建包含第一位患者所有数据的命名组件的列表,请键入以下命令
> subject1 <- list(fullname = subject_name[1],
temperature = temperature[1],
flu_status = flu_status[1],
gender = gender[1],
blood = blood[1],
symptoms = symptoms[1])
这位患者的数据现在已收集到 subject1
列表中
> subject1$fullname[1] "John Doe"$temperature[1] 98.1$flu_status[1]
FALSE$gender[1] MALELevels: FEMALE MALE$blood[1] OLevels:
A B AB O$symptoms[1] SEVERELevels: MILD < MODERATE < SEVERE
请注意,值会标有我们在上一个命令中指定的名称。由于列表像向量一样保留顺序,因此可以使用数字位置访问其组件,如下所示,以获取 temperature
值
> subject1[2]$temperature[1] 98.1
对列表对象使用向量式运算符的结果是另一个列表对象,它是原始列表的子集。例如,前面的代码返回了一个包含单个 temperature
组件的列表。要将单个列表项返回为 **原生** 数据类型,请在选择列表组件时使用双括号([[
和 ]]
)。例如,以下命令返回一个长度为一的数值向量
> subject1[[2]][1] 98.1
为清楚起见,通常最好按名称访问列表组件,方法是将 $
和组件名称追加到列表名称,如下所示
> subject1$temperature[1] 98.1
与双括号表示法一样,此方法将列表组件以其原生数据类型(在此例中为长度为一的数值向量)返回。
通过指定名称向量,可以获取多个列表项。以下命令返回 subject1
列表的子集,其中仅包含 temperature
和 flu_status
组件
> subject1[c("temperature",
"flu_status")]$temperature[1] 98.1$flu_status[1] FALSE
可以使用列表(甚至是列表的列表)来构造整个数据集。例如,您可以考虑创建一个 subject2
和 subject3
列表,并将它们分组到一个名为 pt_data
的列表对象中。但是,以这种方式构造数据集足够常见,以至于 R 提供了一种专门用于此任务的数据结构。
数据框
迄今为止,机器学习中最重要 R 数据结构是 **数据框**,这是一种类似于电子表格或数据库的结构,因为它包含数据的行和列。用 R 的术语来说,数据框可以理解为向量或因子的列表,每个向量或因子都具有完全相同数量的值。现在,由于数据框实际上是向量类型对象的列表,因此它结合了向量和列表的方面。
让我们为我们的患者数据集创建一个数据框。使用我们之前创建的患者数据向量,data.frame()
函数将它们合并到一个数据框中
> pt_data <- data.frame(subject_name, temperature,
flu_status, gender, blood, symptoms,
stringsAsFactors = FALSE)
您可能会在前述代码中注意到一些新内容。我们包含了一个附加参数:stringsAsFactors = FALSE
。如果我们不指定此选项,R 将自动将每个字符向量转换为因子。
此功能有时有用,但有时也不必要。在这里,例如,subject_name
字段绝对不是分类数据,因为名称不是值的类别。因此,将 stringsAsFactors
选项设置为 FALSE
可让我们仅在对项目有意义的地方将字符向量转换为因子。
当我们显示 pt_data
数据框时,我们看到其结构与我们之前处理的数据结构大不相同
> pt_data subject_name temperature flu_status gender blood symptoms1 John Doe
98.1 FALSE MALE O SEVERE2 Jane Doe 98.6
FALSE FEMALE AB MILD3 Steve Graves 101.4 TRUE MALE
A MODERATE
与一维向量、因子和列表相比,数据框是二维的,并以矩阵格式显示。此特定数据框为每位患者数据向量都有一列,为每位患者都有一行。在机器学习的术语中,数据框的列是特征或属性,行是示例。
要提取整个列(向量)数据,我们可以利用数据框本质上是向量列表这一事实。与列表类似,提取单个元素的直接方法是按名称引用它。例如,要获取 subject_name
向量,请键入
> pt_data$subject_name[1] "John Doe" "Jane Doe" "Steve Graves"
同样,与列表类似,可以使用名称向量从数据框中提取多个列
> pt_data[c("temperature", "flu_status")] temperature flu_status1
98.1 FALSE2 98.6 FALSE3 101.4 TRUE
当我们按名称请求数据框中的列时,结果是一个数据框,其中包含指定列的所有数据行。命令 pt_data[2:3]
也会提取 temperature
和 flu_status
列。但是,按名称引用列会产生清晰且易于维护的 R 代码,如果数据框稍后重新排序,这些代码也不会中断。
要从数据框中提取特定值,可以使用访问向量中值的类似方法。但是,有一个重要的区别——因为数据框是二维的,所以必须指定所需的行和列。先指定行,然后是逗号,然后是以 [rows, columns]
格式指定的列。与向量一样,行和列都从一计数。
例如,要提取患者数据框中第一行第二列的值,请使用以下命令
> pt_data[1, 2][1] 98.1
如果您想要多于一行或一列数据,请指定指示所需行和列的向量。以下语句将从第一行和第三行以及第二列和第四列中提取数据
> pt_data[c(1, 3), c(2, 4)] temperature gender1 98.1 MALE3 101.4 MALE
要引用所有行或所有列,只需将行或列部分留空即可。例如,要提取第一列的所有行
> pt_data[, 1][1] "John Doe" "Jane Doe" "Steve Graves"
要提取第一行的所有列
> pt_data[1, ] subject_name temperature flu_status gender blood symptoms1
John Doe 98.1 FALSE MALE O SEVERE
并提取所有内容
> pt_data[ , ] subject_name temperature flu_status gender blood symptoms1
John Doe 98.1 FALSE MALE O SEVERE2 Jane Doe
98.6 FALSE FEMALE AB MILD3 Steve Graves 101.4
TRUE MALE A MODERATE
当然,按名称访问列比按位置访问更好,并且可以使用负号来排除行或列数据。因此,命令的输出
> pt_data[c(1, 3), c("temperature", "gender")] temperature gender1
98.1 MALE3 101.4 MALE
等同于
> pt_data[-2, c(-1, -3, -5, -6)] temperature gender1
98.1 MALE3 101.4 MALE
有时有必要在数据框中创建新列——例如,可能作为现有列的函数。例如,我们可能需要将患者数据框中的华氏温度读数转换为摄氏度。为此,我们只需使用赋值运算符将转换计算的结果分配给新列名,如下所示
> pt_data$temp_c <- (pt_data$temperature - 32) * (5 / 9)
为了确认计算是否有效,让我们将新的基于摄氏度的 temp_c
列与之前的华氏度 temperature
列进行比较
> pt_data[c("temperature", "temp_c")] temperature temp_c1
98.1 36.722222 98.6 37.000003 101.4 38.55556
并排查看,我们可以确认计算已正确完成。
要更熟悉数据框,请尝试使用患者数据集进行类似的练习,甚至更好的是,使用您自己项目中的数据。这些类型的操作对于我们将在后续章节中进行的许多工作至关重要。
矩阵和数组
除了数据框之外,R 还提供了以表格形式存储值的其他结构。**矩阵** 是一种数据结构,表示具有数据行和列的二维表。与向量一样,R 矩阵只能包含一种类型的数据,尽管它们最常用于数学运算,因此通常只存储数字。
要创建矩阵,只需将数据向量提供给 matrix()
函数,并提供一个指定行数(nrow
)或列数(ncol
)的参数。例如,要创建一个存储数字一到四的 2x2 矩阵,我们可以使用 nrow
参数将数据请求分成两行
> m <- matrix(c(1, 2, 3, 4), nrow = 2)> m [,1] [,2][1,] 1 3[2,] 2 4
这等同于使用 ncol = 2
产生的矩阵
> m <- matrix(c(1, 2, 3, 4), ncol = 2)> m [,1] [,2][1,] 1 3[2,] 2 4
您会注意到 R 首先加载了矩阵的第一列,然后才加载第二列。这称为 **列主序**,这是 R 加载矩阵的默认方法。
为了进一步说明这一点,让我们看看如果我们向矩阵添加更多值会发生什么。
使用六个值,请求两行会创建一个三列的矩阵
> m <- matrix(c(1, 2, 3, 4, 5, 6), nrow = 2)> m
[,1] [,2] [,3][1,] 1 3 5[2,] 2 4 6
请求两列会创建一个三行的矩阵
> m <- matrix(c(1, 2, 3, 4, 5, 6), ncol = 2)> m
[,1] [,2][1,] 1 4[2,] 2 5[3,] 3 6
与数据框一样,可以使用 [row, column]
表示法提取矩阵中的值。例如,m[1, 1]
将返回值 1
,而 m[3, 2]
将从 m
矩阵中提取 6。此外,还可以请求整行或整列
> m[1, ][1] 1 4> m[, 1][1] 1 2 3
与矩阵结构紧密相关的是 **数组**,它是一种多维数据表。矩阵具有值行和列,而数组具有行、列以及额外的多层值。