在第一节中,我们回顾了许多用于操作数据框的内置函数。然后,了解了 sqldf 扩展
包,它使得简单的数据查询和统计变得更简便。然而,两种方法都有各自的局限性。使用
内置函数可能既繁琐又缓慢,而相对于各式各样的 R 函数来说,SQL 又不够强大,所以用
sqldf 进行数据的汇总统计,也不容易。
data.table 包提供了一个加强版的 data.frame。它运行效率极高,而且能够处
理适合内存的大数据集,它通过 [ ] 实现了一种自然的数据操作语法。如果尚未安装,
请运行以下命令:
install.packages("data.table")
安装成功后,我们就可以加载它,并查看它都提供了些什么:
library(data.table)
##
## Attaching package: 'data.table'
## The following objects are masked from 'package:reshape2':
##
## dcast, melt
注 意 到, 前 面 加 载 的 reshape2 包 中 已 经 定 义 了 dcast( ) 和 melt( ) 。
data.table 包提供了加强版的 dcast( ) 和 melt( ),它们的功能更强大,性能更高,
内存使用也更高效。本节的后续部分我们将介绍它们。
创建一个 data.table 与创建一个 data.frame 非常相似:
dt <- data.table(x = 1:3, y = rnorm(3), z = letters[1:3])
d
## x y z
## 1: 1 -0.2469598 a
## 2: 2 1.4126529 b
## 3: 3 -1.2174483 c
通过 str( ) 查看它的结构:
str(dt)
## Classes 'data.table' and 'data.frame': 3 obs. of 3 variables:
## $ x: int 1 2 3
## $ y: num -0.247 1.413 -1.217
## $ z: chr "a" "b" "c"
## - attr(*, ".internal.selfref") = <externalptr>
很明显,dt 的类是 data.table 和 data.frame,这意味着 data.table 继承自
data.frame。换句话说,它继承了 data.frame 的一些行为,但是增强了其他部分。
data.table 的基本语法 dt[i,j,by]。简单来说,就是使用 i 选择行,用 by 分组,
然后计算 j。接下来我们就看看 data.table 具体继承了什么,增强了什么。
首先,仍然先载入产品数据。但这次我们使用 data.table 包中的 fread( )函数来
读取数据。fread( ) 函数非常快,内存效率很高,而且直接返回了 data.table:
product_info <- fread("data/product-info.csv")
product_stats <- fread("data/product-stats.csv")
product_tests <- fread("data/product-tests.csv")
toy_tests <- fread("data/product-toy-tests.csv")
如果查看 product_info,可以看到它的数据格式与 data.frame 几乎是一样:
product_info
## id name type class released
## 1: T01 SupCar toy vehicle yes
## 2: T02 SupPlane toy vehicle no
## 3: M01 JeepX model vehicle yes
## 4: M02 AircraftX model vehicle yes
## 5: M03 Runner model people yes
## 6: M04 Dancer model people no
再次查看它的结构:
str(product_info)
## Classes 'data.table' and 'data.frame': 6 obs. of 5 variables:
## $ id : chr "T01" "T02" "M01" "M02" ...
## $ name : chr "SupCar" "SupPlane" "JeepX" "AircraftX" ...
## $ type : chr "toy" "toy" "model" "model" ...
## $ class : chr "vehicle" "vehicle" "vehicle" "vehicle" ...
## $ released: chr "yes" "no" "yes" "yes" ...
## - attr(*, ".internal.selfref")=<externalptr>
与 data.frame 不同,如果对 data.table 只提供一个参数来构建子集的话,是选
择行而不是选择列:
product_info[1]
## id name type class released
## 1: T01 SupCar toy vehicle yes
product_info[1:3]
## id name type class released
## 1: T01 SupCar toy vehicle yes
## 2: T02 SupPlane toy vehicle no
## 3: M01 JeepX model vehicle yes
如果为 [ ] 提供负值,意味着排除对应记录(行),这与构建向量子集完全一致:
product_info[-1]
## id name type class released
## 1: T02 SupPlane toy vehicle no
## 2: M01 JeepX model vehicle yes
## 3: M02 AircraftX model vehicle yes
## 4: M03 Runner model people yes
## 5: M04 Dancer model people no
此外,data.table 也提供了许多特殊符号,它们是data.table 的重要组成部分。.N 是
最有用的符号之一,它表示在当前的分组中,对象的数目(即每组的行数)。有了这个符号,我们
就不必再用nrow(product_info) 计算行数。但是在 [ ] 中单独使用 .N 是指提取最后一行:
product_info[.N]
## id name type class released
## 1: M04 Dancer model people no
也可以轻松地选择第一和最后一行:
product_info[c(1, .N)]
## id name type class released
## 1: T01 SupCar toy vehicle yes
## 2: M04 Dancer model people no
在对 data.table 构建子集时,能够根据语义自动地计算表达式。也就是说,在选取列
时,可以直接使用列名,就像使用 subset( )、transform( ) 和 with( )一样。例如,
选择已经发布的产品时,我们可以直接使用 released 作为第 1 个参数来选择满足条件的行:
product_info[released == "yes"]
## id name type class released
## 1: T01 SupCar toy vehicle yes
## 2: M01 JeepX model vehicle yes
## 3: M02 AircraftX model vehicle yes
## 4: M03 Runner model people yes
方括号内的第 1 个参数是行筛选器,第 2 个则对筛选后的数据进行适当地计算(包括
选择某些列或执行某个表达式)。例如,我们可以直接使用 id 来表达 roduct_info$id,
这里提取满足 released 取值为 yes 的 id 列:
product_info[released == "yes", id]
## [1] "T01" "M01" "M02" "M03"
而选择 data.frame 的列的方式(“id”)在这里不起作用。如果把一个字符向量作为
第 2 个参数,就会得到这个字符向量本身,因为一个字符串就只是表示一个字符串,可以
试一下:
product_info[released == "yes", "id"]
## [1] "id"
为了避免这种情况,我们可以设定 with = FALSE。这样,第 2 个参数就可以根据字
符向量选择列,并且不论指定多少列,总是返回 data.table:
product_info[released == "yes", "id", with = FALSE]
## id
## 1: T01
## 2: M01
## 3: M02
## 4: M03
product_info[released == "yes", c("id", "name"), with = FALSE]
## id name
## 1: T01 SupCar
## 2: M01 JeepX
## 3: M02 AircraftX
## 4: M03 Runner
第 2 个参数也可以是表达式。例如,生成一张表,用于反映每种 type 和 class 的组
合中的 released 取值为 yes 的产品数量:
product_info[released == "yes", table(type, class)]
## class
## type people vehicle
## model 1 2
## toy 0 1
要注意是,给第 2 个参数提供 list(),它仍然会被转换为 data.table:
product_info[released == "yes", list(id, name)]
## id name
## 1: T01 SupCar
392 第 12 章 数据操作
## 2: M01 JeepX
## 3: M02 AircraftX
## 4: M03 Runner
此外,我们也可以轻松地生成一个替换原有列的新的 data.table:
product_info[, list(id, name, released = released == "yes")]
## id name released
## 1: T01 SupCar TRUE
## 2: T02 SupPlane FALSE
## 3: M01 JeepX TRUE
## 4: M02 AircraftX TRUE
## 5: M03 Runner TRUE
## 6: M04 Dancer FALSE
还可以利用原有列来创建新列,并生成新的 data.table:
product_stats[, list(id, material, size, weight, density = size / weight)]
## id material size weight density
## 1: T01 Metal 120 10.0 12.000000
## 2: T02 Metal 350 45.0 7.777778
## 3: M01 Plastics 50 NA NA
## 4: M02 Plastics 85 3.0 28.333333
## 5: M03 Wood 15 NA NA
## 6: M04 Wood 16 0.6 26.666667
为了简化,data.table 为 list( ) 提供了缩写.( ),即 .( ) 和 list( ) 是等价的:
product_info[, .(id, name, type, class)]
## id name type class
## 1: T01 SupCar toy vehicle
## 2: T02 SupPlane toy vehicle
## 3: M01 JeepX model vehicle
## 4: M02 AircraftX model vehicle
## 5: M03 Runner model people
## 6: M04 Dancer model people
product_info[released == "yes", .(id, name)]
## id name
## 1: T01 SupCar
## 2: M01 JeepX
## 3: M02 AircraftX
## 4: M03 Runner
通过提供排序索引,我们可以根据既定的要求对记录进行排序:
product_stats[order(size, decreasing = TRUE)]
## id material size weight
## 1: T02 Metal 350 45.0
## 2: T01 Metal 120 10.0
## 3: M02 Plastics 85 3.0
## 4: M01 Plastics 50 NA
## 5: M04 Wood 16 0.6
## 6: M03 Wood 15 NA
前面都是在构建子集之后,又创建了一个新的 data.table。data.table 包还提供
了一个对列进行原地赋值的符号:=。例如,product_stats 原始数据是这样的:
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
使用 :=,可以直接在 product_stats 中创建新列:
product_stats [, density := size / weight]
虽然并没有返回什么,但原始的 data.table 已经被修改了:
product_stats
## id material size weight density
## 1: T01 Metal 120 10.0 12.000000
## 2: T02 Metal 350 45.0 7.777778
## 3: M01 Plastics 50 NA NA
## 4: M02 Plastics 85 3.0 28.333333
## 5: M03 Wood 15 NA NA
## 6: M04 Wood 16 0.6 26.666667
使用 := 替换已有的列:
product_info[, released := released == "yes"]
product_info
## id name type class released
## 1: T01 SupCar toy vehicle TRUE
## 2: T02 SupPlane toy vehicle FALSE
## 3: M01 JeepX model vehicle TRUE
## 4: M02 AircraftX model vehicle TRUE
## 5: M03 Runner model people TRUE
## 6: M04 Dancer model people FALSE
data.table 包提供 := ,主要是因为原地修改的性能更高,避免了不必要的复制。