本节书摘来自华章出版社《机器学习与R语言(原书第2版)》一书中的第2章,第2.1节,美] 布雷特·兰茨(Brett Lantz) 著,李洪成 许金炜 李舰 译更多章节内容可以访问“华章计算机”公众号查看。
第2章
数据的管理和理解
任何机器学习项目初期的核心部分都是与管理和理解所收集的数据有关的。尽管你可能发现这些工作不像建立和部署模型那样令人有成就感(建立和部署模型阶段就开始看到了劳动的成果),但是忽视这些重要的准备工作是不明智的。
任何学习算法的好坏取决于输入数据的好坏。在很多情况下,输入数据是复杂的、凌乱的并且取自多种不同渠道和格式。因为这些复杂性,所以投入机器学习项目中的很大一部分精力要花在数据准备和探索中。
本章从3个方面来讨论这些主题。第一节讨论R用来存储数据的基本数据结构。学完这一节后,在你创建和管理数据集时,你就对这些数据结构非常熟悉了。第二节是实践,这节讨论从R中输入或者输出数据的几种常用函数。第三节通过探索一个真实世界数据集的例子来说明理解数据的方法。
学完本章后,你将理解:
基本的R数据结构以及如何使用它们来存储和提取数据。
如何把常见来源格式的数据导入R。
理解并可视化复杂数据的典型方法。
因为R考虑数据的方式定义了你应该考虑数据的方式,所以在进入数据准备工作之前,理解基本的R数据结构是很有帮助的。然而,如果你已经对R的数据结构很熟悉了,你完全可以跳过这部分,直接学习数据预处理部分。
2.1 R数据结构
在程序语言中有多种形式的数据结构,在应用到特定的任务时,它们各有优势和劣势。因为R是一个在统计数据分析中广泛运用的程序语言,所以R所用的数据结构的设计目的是使它易于处理这类工作。
在机器学习中经常使用的R数据结构是:向量、因子、列表、数组、矩阵和数据框。每种数据类型都针对一类具体的数据管理任务,所以知道它们如何与R项目相互交互是至关重要的。
2.1.1 向量
R的基本数据结构是向量。向量存储一组有序的值,称为元素。一个向量可以包含任意数量的元素。然而,所有的元素必须是一样的类型,比如,一个向量不能同时包含数字和文本。可以应用命令typeof(v)来确定向量v的类型。
在机器学习中常用的几种向量类型包括:integer(整型,没有小数的数字)、double (双精度浮点类型,即包含小数的数字)、character(字符型,文本数据)或者logical(逻辑型,取值为TRUE或者FALSE)。还有两个特殊的值:NULL,用来表明没有任何值;NA,用来表明一个缺失值。
有些R函数把integer和double类型的向量都报告为numeric,而其他函数则对这两种类型加以区别。因此,尽管所有double类型的向量是numeric类型,但并非所有numeric类型的向量是double类型。
手工输入大量的数据会单调乏味,但是一些简单的向量还是可以用组合函数c()来创建。向量也能通过使用箭头运算符“<-”来给它赋一个名字,这是R的赋值运算符,与很多其他程序语言中的赋值运算符“=”的使用方法差不多。
例如,我们构建多个向量来存储3个体检病人的诊断数据。创建一个字符向量subject_name,它包含3个病人的姓名;一个数值向量temperature,它包含每个病人的体温;以及一个逻辑向量flu_status,它包含每个病人的诊断情况(如果病人患有流感则取值为TRUE,否则为FALSE)。创建这3个向量的代码如下所示:
因为R中的向量有固有的顺序,所以其数据能通过计算向量中各个元素的序号来访问,序号是从1开始算起的,并且在向量名字的后面用方括号括起这个序号(例如,[和])。例如,为了获得温度向量中Jane Doe或者序号为2的病人的体温,只要简单地输入:
为了从向量中提取数据,R提供了各种方便的方法。一个范围内的值可以通过冒号(:)操作符获得。例如,为了获得Jane Doe 和Steve Graves 的体温,输入:
通过指定一个负的序号可以把该项排除在输出数据之外。要想排除Jane Doe的体温数据,输入:
最后,可以通过一个逻辑向量来标识每一项是否包含在内,有时候这也是很有用的。例如,需要包括前两个温度读数,但是排除第三个,就可以输入:
正如你将看到的,向量是很多其他R数据结构的基础。因此,了解不同类型的向量操作对在R中操作数据是很重要的。
下载例子代码
你能用你的账号在网站http://www.packtpub.com下载你已经购买的所有Packt出版的书的例子代码文件。如果你是在其他地方购买这本书的,你可以通过访问http://www.packtpub.com/support网站并且注册,通过电子邮件的形式把文件直接发给你。
本书第2版新增加了一个下载网站,你可以通过GitHub网站:
https://github.com/dataspelunking/MLwR/来获取本书的例子代码。登录本网站获取最新的R代码、事件追踪和公用wiki。请加入本社区。
2.1.2 因子
如果你回忆第1章的内容,用类别值来代表特征的属性称为名义属性。尽管可以用一个字符向量来存储名义属性数据,但R提供了一个数据结构专门来表示这种属性数据。因子是向量的一个特例,它单独用来标识分类或者有序变量。在前面构建的医学体检数据集中,可以用一个因子来表示性别(gender),因为它有两个类别:MALE和FEMALE。
为什么不用字符向量呢?使用因子的一个优势在于类别标签只存储一次。例如,不存储MALE、MALE、FEMALE,计算机只要存储1、1、2,这样可以减少存储同样信息所需要的内存容量。另外,许多机器学习算法用不同的方式来处理名义数据和数值数据。经常需要把变量编码为因子,这样R函数才能合理地处理分类数据。
因子不应该用来处理不是真正分类数据的字符向量。如果一个向量主要存储类似名字或标识字符串这样的唯一值,那么还是把它作为字符向量。
要把字符向量转换成因子,只需要应用factor()函数。例如:
注意,当John Doe和Jane Doe的性别数据显示出来后,R输出关于gender因子的额外的信息。变量levles(水平)由factor(因子)可能取的类别值组成,在这个例子中是MALE或者FEMALE。
当创建因子时,可以增加没有在数据中出现的其他水平。假设增加表示血型变量的另一个因子,如下所示:
注意,当我们为3个病人定义blood(血型)因子时,我们用levels参数来说明一个额外的向量,该向量给出了4个可能的血型。因此,即使数据仅包含O型、AB型和A型,但所有的4种血型和输出给出的blood因子存储在一起。存储额外的水平使得未来增加具有其他血型类型的数据成为可能。它也保证了尽管血型B没有记录在我们的数据中,但是当我们创建血型类型表时,我们知道类型B是存在的。
因子数据结构还允许包含关于名义变量类别的顺序信息,这给出了存储有序数据的方便方式。例如,假设我们有病人symptoms(症状)的数据,按照严重程度的水平升序排列:从MILD(不严重)、MODERATE(中等)到SEVERE(严重)。我们通过下述方式来呈现有序数据:以期望的顺序给出因子的level(水平),从最低到最高的升序方式来列出有序数据,并设置ordered参数的值为TURE。如下所示:
由此产生的symptoms因子现在就包含了我们需要的顺序信息。与之前的因子不同,这个因子的水平值由<符号分隔,它表明了从不严重到严重的序列顺序:
有序因子的一个有用的特性是进行你期望的逻辑测试工作。例如,可以检验病人的症状是否比moderate(中等)还严重。
能够对有序数据建模的机器学习算法将期望输入数据为有序因子,所以确保对你的数据进行相应的编码。
2.1.3 列表
列表是一个与向量类似的数据结构,因为它用来存储一个元素的有序集合。然而,向量要求所有元素都必须是同一种类型,列表允许收集不同类型的元素。由于这个灵活性,列表一直用于存储不同类型的输入和输出数据,以及机器学习模型所使用配置的结构参数的集合。
例如,考虑我们构建的体检病人的数据集,3个病人的数据存储在6个向量中。如果我们要显示关于John Doe(对象1)所有的数据,我们需要输入6条R命令:
显示一个病人的医疗数据看上去像是一个庞大的工程。列表结构使我们能够把所有病人的数据放到一个我们能够重复使用的对象中。
与使用c()创建一个向量类似,列表使用list()函数创建,如下面例子中所示。一个明显的不同是,当列表建立以后,序列中的每一个成分几乎都有一个名字。名字不是必需的,但是它使得接下来能够通过名字访问列表中的值,而不是通过位置序号。为了给第一个病人的所有数据创建一个含有名字成分的列表,输下面的代码:
现在病人的数据被收集到subject1列表中了。
注意,取值是由前面命令中指定的名字标识的。然而,列表也能用类似访问向量的方法来访问。为了获取temperature的值,采用下面的命令:
在列表对象上应用向量风格的运算符得到的结果是另一个列表对象,它是原始列表的一个子集。例如,上面的代码返回具有唯一temperature成分的一个列表。为了以简单数据类型返回一个单一的列表项,在尝试选取列表成分时应用双方括号([[和]]))。例如,下面代码返回一个长度为1的数值向量:
为了清晰起见,通过在列表对象名的后面附加一个$符号和值的名字来直接访问列表成分通常会更简单,例如:
与双方括号类似,它以简单数据类型返回列表成分(这里是长度为1的数值向量)。
通过名字来访问值的方式能保证检索正确的项,即使以后列表元素的顺序发生改变。
也可以通过指定一个名字的向量来获取列表中的多个列表项。下面返回subject1列表的一个子集,它包含temperature和flu_status成分:
整个数据集可以用列表和列表的列表来构建。例如,你可以考虑构建subject2和subject3列表,然后将它们组合为一个名为pt_data的单一列表对象。然而,以这种方式构建数据集是很常用的,所以R专门为这个任务提供了一种专用的数据结构(即数据框)。
2.1.4 数据框
到目前为止,机器学习中使用的最重要的R数据结构就是数据框。因为它既有行数据又有列数据,所以它是一个与电子表格或数据库相类似的结构。在R术语中,数据框定义为一个向量列表或者因子列表,每一列都有相同数量的值。因为数据框准确来说是一个向量类型的对象的列表,所以它结合了向量和列表两个方面的特点。
下面为前面用到的病人数据集构建一个数据框。这里我们使用前面创建的病人数据向量,data.frame()函数把它们组合成一个数据框:
你可能在上述代码中注意到一些新的东西。它加入了一个新的参数stringAsFactors= FALSE。如果不指定这个选项,R将自动把每个字符向量转化为因子。
这个特性有时候是有用的,但有时候又是不需要的。例如,这里,subject_name字段显然不是分类数据,因为姓名不是类别值。因此,只有在对项目有意义时,将stringsAsFactors选项设置为TRUE才能将字符转化成因子。
当我们显示pt_data数据框时,我们可以看到它的结构与先前使用的数据结构略有不同:
与一维向量、因子和列表相比,数据框是二维的,因此它显示为矩阵格式。在数据框中,病人的每个数据向量为一列,每个病人的数据是一行。用机器学习术语来讲,数据框的列代表特征或属性,行代表案例。
为了提取整列(即整个向量)数据,利用数据框就是向量列表这一事实。与列表相类似,提取一个单独元素最直接的方法是通过名字来引用它。例如,为了提取subject_name向量,输入如下命令:
与列表相类似,可以用名称向量从一个数据框中提取多列数据:
当我们通过这种方式访问数据框时,输出的结果还是一个数据框,它包含目标列所有行的数据。你也可以输入命令pt_data[2:3]来提取temperature和flu_status列。但是,通过名字访问列数据将产生清晰、容易维护的R代码,如果未来对数据框重新结构化代码也不会失效。
为了提取数据框中的值,我们可以用前面学过的访问向量中值的方法,但是有一个很重要的不同。因为数据框是二维的,所以它需要指定要提取数据的行和列。格式为[rows,colums],先指定行号,接着是一个逗号,再指定列号。和向量一样,行号和列号都是从1开始计数。
例如,为了提取病人数据框中第一行、第二列的值(John Doe的体温值),使用下面命令:
如果需要提取多于一行或者一列的数据,可以指定所需要数据的行号向量和列号向量。下面的语句将从第1、3行以及第2、4列中提取数据:
要提取所有行或者列,只要让行或者列的部分空白就行了。例如,提取第一列中所有行的数据:
提取第一行中所有列的数据,命令如下:
提取所有数据,命令如下:
前面学习的访问列表和向量中值的方法也可以用来提取数据框的行和列。例如,列数据除了能通过位置访问外,也能通过名称访问,并且负号也能用来排除特定行或者列的数据。因此,命令:
等价于:
为了熟练运用数据框,可以尝试用前面的病人数据来练习这些操作。或者,如果用你自己的数据集进行练习就更好了。这些操作类型对我们以后将学习的章节中的内容是很重要的。
2.1.5 矩阵和数组
除了数据框以外,R还提供了用于存储表格形式数据的专用数据结构。矩阵是一种表示行和列数据的二维表格数据结构。和向量类似,R矩阵能包含任何一种单一类型的数据,但是大多数情况下矩阵是用来做数学运算的,因此矩阵通常存储数值数据。
要想创建一个矩阵,仅需要向matrix()函数提供一个数据向量,紧跟着用一个参数指定行数(nrow)或者列数(ncol)。例如,要想创建一个2×2矩阵,用于存储1~4的数字,那么可以使用nrow参数要求将数据分为两行:
这与用ncol = 2产生的矩阵是等价的:
你将会注意到R先载入矩阵的第一列,然后载入第二列。这称为按列顺序,这是R载入矩阵的默认方法。
为了改变这种默认的设置,在创建矩阵时可以设置参数byrow = TRUE按照行载入矩阵。
为了进一步说明这个概念,观察当我们在矩阵中加入更多值以后会发生什么。
一共有6个值,要求2行将创建一个具有3列的矩阵:
类似地,要求2列将创建一个具有3行的矩阵:
与数据框一样,矩阵中的值也能用[rows,column]这样的方式来提取。例如,m[1, 1]
返回值1,m[3, 2]从矩阵m中提取值6。另外,也可以提取矩阵的整行或者整个列,例如:
与矩阵结构非常接近的是数组,它是一个多维数据表。矩阵含有值的行和列;一个数组包含值的行、列以及任意多层。尽管在后面的章节中我们偶尔会使用矩阵,但是数组的使用超出了本书的学习范围。