接下来的例子中,我们会继续使用前面介绍过的产品信息样本和统计数据来演示数据

筛选和统计的基本方法。

例如,选取所有 type 为 toy 的行:

product_info[product_info$type == "toy", ]

## id name type class released

## 1 T01 SupCar toy vehicle yes

## 2 T02 SupPlane toy vehicle no

或者,所有 released 为 no 的行:

product_info[product_info$released == "no", ]

## id name type class released

## 2 T02 SupPlane toy vehicle no

## 6 M04 Dancer model people no

对列进行筛选,需要将第 1 个参数留空,并为第 2 个参数提供一个字符向量,这和对

矩阵构建子集的做法完全一样:

product_info[, c("id", "name", "type")]

## id name type

## 1 T01 SupCar toy

## 2 T02 SupPlane toy

## 3 M01 JeepX model

## 4 M02 AircraftX model

## 5 M03 Runner model

## 6 M04 Dancer model

除此之外,将数据框看作列表并进行筛选也是可行的。我们只需要在 [ ] 中提供一个

由选取的列名构成的字符向量(忽略逗号)即可:

product_info[c("id", "name", "class")]

## id name class

## 1 T01 SupCar vehicle

## 2 T02 SupPlane vehicle

## 3 M01 JeepX vehicle

## 4 M02 AircraftX vehicle

## 5 M03 Runner people

## 6 M04 Dancer people

同时按行和列对数据框进行筛选,需要分别向第 1 个和第 2 个参数提供向量来选择相

应的行和列:

product_info[product_info$type == "toy", c("name", "class", "released")]

## name class released

## 1 SupCar vehicle yes

## 2 SupPlane vehicle no

如果行筛选条件是基于特定列的值,上述的代码就显得很繁琐,特别是条件更加复

杂的时候。另一个内置的函数 subset( ) 可以简化代码,就如前面介绍的:

subset(product_info,

subset = type == "model" & released == "yes",

select = name:class)

## name type class

## 3 JeepX model vehicle

## 4 AircraftX model vehicle

## 5 Runner model people

subset( ) 函数使用了非标准计算,所以可以直接使用数据框的列,而不用输入很

多次 product_info ,因为这个表达式是在数据框的语义中被计算的。

类似地,也可以使用 with( ) 在数据框的语义中计算表达式,也就是说,可以直接

使用数据框的列名,而不必重复指定数据框:

with(product_info, name[released == "no"])

## [1] "SupPlane" "Dancer"

表达式的用法中,除了用于构建子集,还可以统计每列的各个可能值出现的频数。例

如,提取了一个 released 列取值为 yes 的子集,我们对这个子集的 type 列创建一个频

数统计表:

with(product_info, table(type[released == "yes"]))

##

## model toy

## 3 1

除了产品信息表,我们还有一张描述产品属性的统计表:

product_stats <- read_ _csv("data/product-stats.csv")

product_stats

## id material size weight

## 1 T01 Metal 120 10.0

## 2 T02 Metal 350 45.0

## 3 M01 Plastics 50 NA

## 4 M02 Plastics 85 3.0

## 5 M03 Wood 15 NA

## 6 M04 Wood 16 0.6

现在,思考一下如何获得尺寸最大的前 3 个产品的名字。一种方法是将 product_

stats 按尺寸降序排列,选择前 3 个记录的 id 值,并用 id 值筛选 product_info 的行:

top_3_id <- unlist(product_stats[order(product_stats$size, decreasing = TRUE),

"id"])[1:3]

product_info[product_info$id %in% top_3_id, ]

## id name type class released

## 1 T01 SupCar toy vehicle yes

## 2 T02 SupPlane toy vehicle no

## 4 M02 AircraftX model vehicle yes

尽管结果和预期一样,但是这种方式有些冗长。注意到,事实上 product_info 和

product_stats 是从不同方面描述相同的一系列产品。两个表格通过 id 列建立连接。

每个 id 值都是独一无二的,表示同一个产品。为了同时访问两个信息集,我们可以把这

两个表格合并成一个数据框,最简单的方法是使用 merge( ):

product_table <- merge(product_info, product_stats, by = "id")

product_table

## id name type class released material size weight

## 1 M01 JeepX model vehicle yes Plastics 50 NA

## 2 M02 AircraftX model vehicle yes Plastics 85 3.0

## 3 M03 Runner model people yes Wood 15 NA

## 4 M04 Dancer model people no Wood 16 0.6

## 5 T01 SupCar toy vehicle yes Metal 120 10.0

## 6 T02 SupPlane toy vehicle no Metal 350 45.0

现在创建了一个新的数据框,即共享 id 列的 product_table 和 product_info

的组合版本。实际上,即使对第 2 个表重新排序,两张表格仍然可以正确地合并。

有了这个组合版本,我们可以更简便地完成工作。举个例子,通过合并版本,在一张

表中就能根据任意一列排序数据框,而不需要人工处理另一张表:

product_table[order(product_table$size), ]

## id name type class released material size weight

## 3 M03 Runner model people yes Wood 15 NA

## 4 M04 Dancer model people no Wood 16 0.6

## 1 M01 JeepX model vehicle yes Plastics 50 NA

## 2 M02 AircraftX model vehicle yes Plastics 85 3.0

## 5 T01 SupCar toy vehicle yes Metal 120 10.0

## 6 T02 SupPlane toy vehicle no Metal 350 45.0

对于前面那个问题,我们可以直接使用这个合并表获得相同结果:

product_table[order(product_table$size, decreasing = TRUE), "name"][1:3]

## [1] "SupPlane" "SupCar" "AircraftX"

在合并后的数据框中,可以按某一列排序,再按另一列筛选记录。例如,先按产品重

量进行降序排列,再筛选 type 取值为 model 的所有记录:

product_table[order(product_table$weight, decreasing = TRUE),] [product_

table $type == "model",]

step2

## id name type class released material size weight

## 6 T02 SupPlane toy vehicle no Metal 350 45.0

## 5 T01 SupCar toy vehicle yes Metal 120 10.0

## 2 M02 AircraftX model vehicle yes Plastics 85 3.0

## 4 M04 Dancer model people no Wood 16 0.6

有时,某些列的取值可能是文本,将其转换为 R 的标准数据结构,可以更恰当地表示

数据。例如, product_info 中的 released 列是二元取值:yes 或 no,更适合用逻辑

向量表示。正如前面学到的,可以用 <- 修改列的值。然而,一般来说,更好的方法是生成

一个新的数据框,在此基础上调整原有列和添加新列,从而避免损坏原始数据。我们可以

使用 transform( ) 来完成:

transform(product_table,

released = ifelse(released == "yes", TRUE, FALSE),

density = weight / size)

## id name type class released material size weight

## 1 M01 JeepX model vehicle TRUE Plastics 50 NA

## 2 M02 AircraftX model vehicle TRUE Plastics 85 3.0

## 3 M03 Runner model people TRUE Wood 15 NA

## 4 M04 Dancer model people FALSE Wood 16 0.6

## 5 T01 SupCar toy vehicle TRUE Metal 120 10.0

## 6 T02 SupPlane toy vehicle FALSE Metal 350 45.0

## density

## 1 NA

## 2 0.03529412

## 3 NA

## 4 0.03750000

## 5 0.08333333

## 6 0.12857143

结果返回了一个新的数据框,其中 released 这一列被转换成了一个逻辑向量,同时

添加了一个新列 density。你可以验证一下 product_table 是不是被修改了。

同时,我们也注意到,transform( ) 的工作方式与 subset( ) 很相似。这是因为

两个函数都使用了非标准计算,从而允许我们直接将数据框的列名作为参数,不必每次都

在列名前输入 product_table$。

在前面的数据中,许多列中都有缺失值(用 NA 表示)。很多情形中,我们不希望数据

中出现任何缺失值。因此需要以某种方式处理它们。为了演示这些技术,我们需要载入另

一个包含缺失值的表。这个表是前面用的数据集的每件产品的质量、耐久性、防水性的测

试结果。我们将这些数据储存在 product_tests 中:

product_tests <- read_ _csv("data/product-tests.csv")

product_tests

## id quality durability waterproof

## 1 T01 NA 10 no

## 2 T02 10 9 no

## 3 M01 6 4 yes

## 4 M02 6 5 yes

## 5 M03 5 NA yes

## 6 M04 6 6 yes

注意到,quality 和 durability 两列中都存在缺失值(NA)。na.omit( ) 可以

剔除所有包含缺失值的行:

na.omit(product_tests)

## id quality durability waterproof

## 2 T02 10 9 no

## 3 M01 6 4 yes

## 4 M02 6 5 yes

## 6 M04 6 6 yes

还有一种方法,使用函数 complete.cases( ),它返回一个逻辑向量,表明某一行

是否完整(完整意味着这一行不包含缺失值):

complete.cases(product_tests)

## [1] FALSE TRUE TRUE TRUE FALSE TRUE

然后,使用这个逻辑向量筛选数据框。例如可以获得不含缺失值的行的 id 值:

product_tests[complete.cases(product_tests), "id"]

## [1] "T02" "M01" "M02" "M04"

此外也可以获得所有包含缺失值的行的 id:

product_tests[!complete.cases(product_tests), "id"]

## [1] "T01" "M03"

注意到, product_info、product_stats 和 product_tests 这 3 个数据框共

享同一个 id 列,因此,可以将它们按 id 列合并。不幸的是,没有可以合并多个数据框的

内置函数。我们只能一次合并两个现有的数据框,也就是逐个合并:

product_full <- merge(product_table, product_tests, by = "id")

product_full

## id name type class released material size weight quality

## 1 M01 JeepX model vehicle yes Plastics 50 NA 6

## 2 M02 AircraftX model vehicle yes Plastics 85 3.0 6

## 3 M03 Runner model people yes Wood 15 NA 5

## 4 M04 Dancer model people no Wood 16 0.6 6

## 5 T01 SupCar toy vehicle yes Metal 120 10.0 NA

## 6 T02 SupPlane toy vehicle no Metal 350 45.0 10

## durability waterproof

## 1 4 yes

## 2 5 yes

## 3 NA yes

## 4 6 yes

## 5 10 no

## 6 9 no

对于完全合并好的表,我们可以使用 tapply( ) 函数,它也是 apply 函数族的一个成

员,专门处理表格数据,使用某些方法根据某一列对另一列的数据进行统计。例如可以根

据 type 列计算 quality 列的均值,即每种 type 对应的 quality 的均值:

mean_quality1 <- tapply(product_full$quality,

list(product_full$type),

mean, na.rm = TRUE)

mean_quality1

## model toy

## 5.75 10.00

注意到,我们不仅提供了函数 mean ,还设定了 na.rm = TRUE,这就意味着忽

略 quality 中的缺失值。返回的结果看起来是一个数值向量,调用 str( ) 来看看它的结构:

str(mean_quality1)

## num [1:2(1d)] 5.75 10

## - attr(*, "dimnames")=List of 1

## ..$ : chr [1:2] "model" "toy"

实际上,这是个一维数组:

is.array(mean_quality1)

## [1] TRUE

tapply( ) 函数返回了一个数组,而不是一个简单的数值向量。因此可以方便地进

行多组操作。例如,计算每一对 type 和 class 组合的 quality 的均值:

mean_quality2 <- tapply(product_full$quality,

list(product_full$type, product_full$class),

mean, na.rm = TRUE)

mean_quality2

## people vehicle

## model 5.5 6

## toy NA 10

现在我们有了一个二维数组,可以通过两个参数来获取其中的值:

mean_quality2["model", "vehicle"]

## [1] 6

此外,也可以根据多列进行分组。在下面这段代码中,我们调用 with( ) 函数,避

免反复输入 product_full:

mean_quality3 <- with(product_full,

tapply(quality, list(type, material, released),

mean, na.rm = TRUE))

mean_quality3

## , , no

##

## Metal Plastics Wood

## model NA NA 6

## toy 10 NA NA

##

## , , yes

##

## Metal Plastics Wood

## model NA 6 5

## toy NaN NA NA

现在,我们得到了一个三维数组。尽管已经设置了 na.rm = TRUE ,但仍有许多

单元格是缺失值。这是因为该组没有数据:

str(mean_quality3)

## num [1:2, 1:3, 1:2] NA 10 NA NA 6 NA NA NaN 6 NA ...

## - attr(*, "dimnames") = List of 3

## ..$ : chr [1:2] "model" "toy"

## ..$ : chr [1:3] "Metal" "Plastics" "Wood"

## ..$ : chr [1:2] "no" "yes"

我们可以通过 3 个参数获取单元格中的值:

mean_quality3["model", "Wood", "yes"]

## [1] 5

总的来说,tapply( ) 根据 n 个特定变量对输入的数据框分组,并返回一个 n 维数

组。这种汇总数据的方法可能比较麻烦,尤其是需要对多列进行分组的时候。主要是因为

数组一般是高维的,难以表示,进一步的操作不够灵活。在本章的后续部分,你会学到许

多不同的方法来简化分组汇总工作。