数据预处理

本节主要对表数据(data.frame)进行详解,包括增、删、查、改等单表操作,以及表之间数据关联的操作,并包含一些向量化计算的内容。 此外还会涉及到空间数据中坐标系的转换问题。

为了方便,称 data.frame 类型的数据,统称为“表数据”,这里“表”指数据类型,而不是文件格式。

单个表数据的操作

上节已经讲到,用列表示的一个或更多的数据种类,每行包含一个唯一的数据实体。 因此,对表数据的操作将从行、列两个角度进行展开。 我们将单个表数据的操作归为“创建、增加、查询、修改、删除”五个类型。

创建

创建一个表,是使用 data.frame() 函数进行的。创建表时,需要指定这个表里面有哪些列,每一列有哪些数据。例如我们使用如下语句创建一个表

函数 data.frame() 具有如下参数:

参数名类型含义默认值
...变长参数每一列的值。可以指定参数名,也可以不指定。如果指定参数名,则使用参数名作为列名。 
row.namesinteger/character/vector(integer/character)指定行名NULL
check.rowslogical检查列名是否符合语法、是否存在重复等问题FALSE

需要指出的是:

查询

查询一行或者一列,可以使用与查看矩阵相同的方法,即使用 [] 运算符。 可以像矩阵一样,在 [] 中使用整数、序列、整型向量、逻辑型向量或者省略行列索引其中之一来获取数据。

对于具有列名或行名的矩阵或表数据,可以通过指定其行名、列名来获取数据

如果只取某一列的所有值,可以使用运算符 $ 获取这一列的值,例如

还可以使用运算符 [[ ]],中间既可以填写列索引,也可以填写列名,例如

以上只是对数据简单的查找。能否像数据库一样,给数据中指定一些条件,筛选出符合条件的数据呢?答案是可以的,而且有多种方式可以做到。这里介绍三种。

第一种是直接使用逻辑判断表达式,得到一组逻辑类型的向量,根据该向量获取对应位置上的元素。例如提取 demo.suspectgender 列为 M 的行,可以这样操作

第二种是稍微有些多此一举,使用 which() 函数。这个函数接收一个逻辑类型的向量作为参数(如果不是向量,也会被转换成向量),返回这些向量中值为 TRUE 的元素的位置。例如

第三种是使用一个强大的 dplyr 包,该包提供了 selectfilter 两个函数,可以大大简化对数据的查找。 其中,select 函数偏向于获取列,filter 函数偏向于获取行,基本对应了 SQL 语句中 selectwhere 两个子句,但提供了更多的功能。

以下是一些 select 函数的示例

starts_with 和 ends_with 是 dplyr 提供的判断字符串开头和结尾的两个函数,

可以看到,该函数可以接收多种类型的参数:字符串、字符串向量、:表示的序列,以及一些函数返回值。

以下是一些 filter 函数的使用示例

可见,该函数还是主要接收一个逻辑类型的向量作为筛选依据,可以看作是对第一种数据检索方式的简化。 该函数也可以接收多个条件,多个条件之间是使用 & 运算符连接的。 该函数还可以在列之间进行比较,也可以对列做一些运算后比较。 该函数还支持一些函数,用于方便进行数据检索:

当然这两个函数可以进行嵌套

分组查询

在 R 语言中,还可以对数据进行分组查询,相当于 SQL 中的 group by 子句。实现这一操作有几种方式。

一种是使用 dplyr 包中提供的 group_by() 函数,该函数给数据指定一个分组依据,但不对数据进行分组处理。使用 summarise()summarize() 函数进行分组处理。 使用一次 group_by() 函数,数据中增加一层分组条件;使用一次 summarise() 函数,数据中减少一次分组条件。

这里用到了 mean()length() 两个函数,其作用分别是计算平均数和向量长度。

这种方式就非常类似于 SQL 语句的方式,使用起来也非常方便。

另一种是使用 R 自带的 aggregate 函数,对数据进行聚合。例如

该函数具有如下参数:

参数名类型含义默认值
xdata.frame要处理的数据 
bylist分组依据 
FUNfunction聚合函数。将每一列传入到函数的第一个参数中。 
...logical聚合函数的额外参数 

这里涉及到两个类型 listfunction ,即列表类型和函数类型。

列表类型

列表类型,有点类似于其他编程语言的字典类型(C++ 中对应 Map 类型),是以键值对方式存在的数据。 其中值必须有,但键名不一定必须有。当键名有时,可以比较类似于字典;当键名没有时,比较类似于任意列表。 因此该类型的使用非常灵活,可以和 JSON 数据对应。

创建列表类型变量的方式和创建表数据的方式很像,使用如下方式

事实上,data.frame 类型只是一个特殊的 list 类型,特殊在不同键的值具有相同的长度。

aggregrate 函数中,如果 by 参数传入的列表只有几个键值对,那么就按照这几个条件进行分组。多个条件分组的示例如下

函数类型

R 中,函数也是一个变量,即函数类型的变量(有点 JavaScript 那味了),但是 () 运算符只能作用于函数类型的变量上。

创建函数类型的变量使用 function 关键字,将最后一条运行的语句的结果作为返回值。例如,创建一个计算二次函数值的变量

R 语言中的函数无需使用 return 指明返回值, return() 也是一个函数,但其只起到控制函数流程的作用。

R 语言中也提供了丰富的函数,用于进行简单的统计计算,包括 mean(), sd(), var(), min(), max(), median(), length(), range(), quantile(), fivenum() 等。 这些函数可以直接使用。

判断语句

在之前可以看到,我们调用 mean 作为统计函数的时候,如果遇到字符串或逻辑值,其统计结果是有问题的。这时就需要我们使用自定义的函数,对不同类类型的列进行处理

这里使用到了 if else 关键字进行函数流程的控制。 if 关键字后面使用 () 包含一个逻辑表达式表示条件,当该条件成立时执行 {} 中的语句;不成立时,如果有 else 则执行 else{} 的语句。

循环语句

与其它语言类似,R 语言中也有 for while 两种循环语句。这里顺便介绍一下。

for 语句的用法是

这里的 print() 函数将变量进行输出。

while 语句的用法是

需要指出的是,R 语言执行 for while 循环的效率比较低,因此不建议大量使用循环语句,推荐使用向量化计算方法,即将循环对象作为一个向量进行整体计算。 如果一定要对每一个值单独进行处理,在 R 中也有更高效的方法。

增加

对数据增加一列,可以使用 $ 运算符或者 [[]] 运算符,只要这一列的名字没有在数据中出现,直接赋值即可。新增的列出现在最后面。例如

我们也可以将一些计算值加入到列中,例如计算 BMI 指数

以上这些操作,都可以使用一个函数 transform() 完成,该函数的用法类似于 select filter 两个函数,在后面可以直接指定新列的值

增加列的操作是比较简单的。对于增加行的操作,由于不同列的数据类型不同,因此比较复杂。

如果有行名,可以使用 [行名,列名] 的方式,指定一个新行名的索引,填入数据即可。 建议分别赋值,可保证列不改变。

如果没有行名,可以使用函数 nrow() 计算以下当前的行数 ,使用 [r+1,列名] 的方式填入数据。

还可以使用 rbind() 函数增加一行,使用方法如下

但该函数效率较低,应尽量避免使用。同样地,R 中也有一个 cbind 函数,其用法和 rbind 类似,只是用于增加列,但该函数效率也比较低,因此不推荐使用。

修改

该部分所说的改,包括对名、类型、值的修改。

修改表数据列名和行名的方法和矩阵是类似的,都是使用 row.names()col.names() 两个函数进行的。值得注意的是,不一定一次要全部修改。例如

修改类型,特指修改列的类型,方法还是使用 as 族函数,通过对一列进行整体操作,例如

修改值的方法和修改类型的方法基本一样,例如如果给 age 整体增 1,可以使用以下方式

当然也可以对某一个值单独进行赋值,例如将 王五is.married 列的值修改为 T

与增操作类似,一般不对行进行整体的改操作。

删除一行或者一列的方法,等价于选择另外一些行或者列。例如如果要删除 children.count 这一列,或者删除 children.count 这一列的值小于 1 的,只需要选择其他列,即可。 而如果列比较多,要删的列只有一两个,进行查询操作并不是很方便。此时可以使用负整数索引进行删除。例如

此外,如果知道列名, transform 函数也可以用于删除列,只需要将该列的值设置为 NULL 即可

多个表的操作

很多时候,不同部分的数据是分别收集的,会形成多个表数据。对于这种情况,在数据处理时,就需要将两张表进行连接。 使用原生 R 的语句可以做到这一点,但是会比较麻烦。这里还是主要介绍 dplyr 包中提供的方法。

先创建一个新的数据集,使用的数据是 R 中自带的,主要是关于美国各州的一些情况。此外还需要用到一个自带的表数据 USArrests

由于 data.frame 是一个特殊的 list,创建 data.frame 时也可以直接传入 list 数据,该 list 中的每一个值的要求,和对其他字段的要求相同。

下面我们需要将这两个表进行关联。如果两个数据是一一对应的,可以直接使用 cbind() 函数。 但这与这种数据,显然不是,就需要使用进行连接操作,使用到的函数主要有四种:

这几个函数接受的参数是一样的。

这些函数中,前两个参数显然就是要连接的两张表,第一个参数是左表,第二个参数是右表。参数 by 就是连接条件。 该参数可以有多种形式:

连接后的表包含了两张表的所有字段,如果字段有重名,那么会在字段后面加上后缀,默认是 .x .y ,但是也可以通过 suffix 参数进行控制。这里具体就不再进行演示了。

向量化计算

之前提到,R 语言中执行循环的效率是比较低的,提高效率的方法就是向量化计算,即以向量为单位进行计算。 如果向量之间各元素的操作相同,且比较简单,那么可以直接使用向量运算来进行,这是最为推荐的方式。 但是如果向量计算的操作比较复杂,这时就需要用到 apply 族函数了。这些函数主要包括以下几个:

例如,我们现在有一个 的距离矩阵(这里使用 dist 函数生成,也可以从外部导入),我们需要找到每一列中排名第 20 的距离值。就可以使用 apply 函数进行计算。

该函数具有如下参数:

参数名类型含义默认值
Xmatrix要处理的数据 
MARGINinteger处理的维度。取 1 时,表示对每一行进行处理。取 2 时,表示对每一列进行处理。 
FUNfunction执行函数。根据 MARGIN 将每一行或每一列作为第一个参数传入。 
...logical执行函数的额外参数。 

函数在返回时,每一次执行函数的结果进行组合。如果执行的结果是数量,则组合成一个向量。如果执行的结果是向量,则作为一列组合成一个矩阵。

在第一节中,我们读取了一个表数据,但是有些字段应该是浮点数,系统判断成了字符串类型;有些是整数,系统默认为浮点数。 对于这种情况,我们就可以使用 lapply 对其进行转换

如果我们要取 PURCHASE UNEMPLOY PROF 三列中各自最大的 6 个值,可以使用如下方法

可见 lapply 处理的是列表,返回的也是列表。可以使用 sapply 对输出结果进行自动简化,生成更易于观察的结果

对于 vapply 这里就不再过多介绍了,其效果和 lapply 类似,只是接收一个向量。

空间数据操作

对于空间数据的操作,可以分成两类:

  1. 对属性进行操作。这个操作方法和之前所讲的对表数据的操作方法相同,只是操作的对象是 @data 插槽中的值。
  2. 对几何进行操作。这类操作有很多,相交、合并、投影……这里就不一一介绍。但这里主要讲一个操作——坐标系转换。

坐标系转换

坐标系转换,在 ArcGIS 中称作“投影”,就是把空间数据在一个坐标系下的坐标转换为另一种坐标系下的坐标。常用的有几种坐标:

这一操作主要使用 rgdal 包中的 spTransform 进行实现。

该函数接收两个参数,第一个参数是要转换的 Spatial*DataFrame 数据;第二个参数是转换到的坐标系,是 CRS() 函数返回的对象。 函数 CRS() 接收一个 Proj4 格式的字符串,不同坐标系对应的 Proj4 字符串可在 EPSG 官网上进行查询。

关于其他空间操作,详见《R 语言空间数据处理与分析实践教程》。