接下来的例子中,我们会继续使用前面介绍过的产品信息样本和统计数据来演示数据
筛选和统计的基本方法。
例如,选取所有 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 维数
组。这种汇总数据的方法可能比较麻烦,尤其是需要对多列进行分组的时候。主要是因为
数组一般是高维的,难以表示,进一步的操作不够灵活。在本章的后续部分,你会学到许
多不同的方法来简化分组汇总工作。