16 min read

data.table自带例子注解

加载data.table包后,运行example(“data.table”)可以获得data.table自带的例子代码。本文对代码的意思进行了中文注释,以深入理解data.table的特性。

data.table入门介绍,见这里。相关中文翻译,见本博客日志:

其实本部分内容在早期日志data.table学习笔记5中翻译了一部分,没有翻译完。本次作为一个新的日志,全新翻译了一次。

library(data.table)

1.生成数据集

生成DF和DT数据集,类型分别为data frame和data.table。二者本质上均为list类型。

DF = data.frame(x = rep(c("b", "a", "c"), each = 3),
                y = c(1, 3, 6),
                v = 1:9)
DT = data.table(x = rep(c("b", "a", "c")
                        , each = 3),
                y = c(1, 3, 6),
                v = 1:9)
DF
##   x y v
## 1 b 1 1
## 2 b 3 2
## 3 b 6 3
## 4 a 1 4
## 5 a 3 5
## 6 a 6 6
## 7 c 1 7
## 8 c 3 8
## 9 c 6 9
DT
##    x y v
## 1: b 1 1
## 2: b 3 2
## 3: b 6 3
## 4: a 1 4
## 5: a 3 5
## 6: a 6 6
## 7: c 1 7
## 8: c 3 8
## 9: c 6 9
identical(DF$a, DT$a)
## [1] TRUE
is.list(DF)
## [1] TRUE
is.list(DT)
## [1] TRUE

2.行操作

DT[2]                          # 提取第二行数据
##    x y v
## 1: b 3 2
DT[3:2]                        # 提取第三行和第二行数据
##    x y v
## 1: b 6 3
## 2: b 3 2
DT[order(x)]                   # 根据x升序对DT进行排序
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
## 4: b 1 1
## 5: b 3 2
## 6: b 6 3
## 7: c 1 7
## 8: c 3 8
## 9: c 6 9
DT[order(x), ]                 # 这样写也可以,不如上一行简洁
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
## 4: b 1 1
## 5: b 3 2
## 6: b 6 3
## 7: c 1 7
## 8: c 3 8
## 9: c 6 9
DT[y>2]                        # 提取所有y>2 的行
##    x y v
## 1: b 3 2
## 2: b 6 3
## 3: a 3 5
## 4: a 6 6
## 5: c 3 8
## 6: c 6 9
DT[y>2 & v>5]                 # 提取满足条件:y> 2 并且 v> 5的行
##    x y v
## 1: a 6 6
## 2: c 3 8
## 3: c 6 9
DT[!2:4]                      #提取除2, 3,4行外的其他行
##    x y v
## 1: b 1 1
## 2: a 3 5
## 3: a 6 6
## 4: c 1 7
## 5: c 3 8
## 6: c 6 9
DT[-(2:4)]                   #同上
##    x y v
## 1: b 1 1
## 2: a 3 5
## 3: a 6 6
## 4: c 1 7
## 5: c 3 8
## 6: c 6 9

3.列操作

DT[, v]                      #提取列名为v的该列所有值,返回一个向量
## [1] 1 2 3 4 5 6 7 8 9
DT[, list(v)]                #提取列名为v的该列所有值,返回一个data.table
##    v
## 1: 1
## 2: 2
## 3: 3
## 4: 4
## 5: 5
## 6: 6
## 7: 7
## 8: 8
## 9: 9
DT[, .(v)]                  #同上,.()是list()的缩写
##    v
## 1: 1
## 2: 2
## 3: 3
## 4: 4
## 5: 5
## 6: 6
## 7: 7
## 8: 8
## 9: 9
DT[, sum(v)]                #列v所有值的和,返回一个向量
## [1] 45
DT[, .(sum(v))]             #同上,返回一个data.table
##    V1
## 1: 45
DT[, .(sv=sum(v))]          #同上,但列被命名为sv
##    sv
## 1: 45
DT[, .(v, v*2)]             #返回一个data.table,包括两列:v和V2
##    v V2
## 1: 1  2
## 2: 2  4
## 3: 3  6
## 4: 4  8
## 5: 5 10
## 6: 6 12
## 7: 7 14
## 8: 8 16
## 9: 9 18

4.对部分行进行列操作

譬如,仅对2,3行v列进行求和操作。

DT[2:3, sum(v)]
## [1] 5
DT[2:3, .(sum(v))]
##    V1
## 1:  5
DT[2:3, .(sv=sum(v))] 
##    sv
## 1:  5
DT[2:5, cat(v, "\n")]
## 2 3 4 5
## NULL

5.通过data frame方式选择列

DT[, 2]               #通过数字方式选择列,返回一个data.table
##    y
## 1: 1
## 2: 3
## 3: 6
## 4: 1
## 5: 3
## 6: 6
## 7: 1
## 8: 3
## 9: 6
col_num = 2
DT[, ..col_num]       #..后跟随变量表示该变量名不是DT中的某一列,它存储的值为DT中的列名。
##    y
## 1: 1
## 2: 3
## 3: 6
## 4: 1
## 5: 3
## 6: 6
## 7: 1
## 8: 3
## 9: 6
DT[["y"]]             #同DT[, y],返回向量,但读取速度更快
## [1] 1 3 6 1 3 6 1 3 6

6.分组操作

DT[, sum(v), by=x]              #根据x进行分组,每组求v列的和
##    x V1
## 1: b  6
## 2: a 15
## 3: c 24
DT[, sum(v), keyby=x]           #同上,但输出结果根据x排序
##    x V1
## 1: a 15
## 2: b  6
## 3: c 24
DT[, sum(v), by=x][order(x)]    #同上,通过链式表达式操作
##    x V1
## 1: a 15
## 2: b  6
## 3: c 24

7.快速读取行

通过二级索引形式,通过二分法快速查询。

DT["a", on = "x"] # 获取x列中值为a的所有行,跟DT[x == "a"]结果一致,但是对列x建立主键,二分法搜索,更快
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
DT["a", on = .(x)] #更加方便的形式,不用写双引号
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
DT[.("a"), on = .(x)] #同上
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
DT[x == "a"] #同上,然而针对单个==内部进行了优化,速度同上
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
DT[x != "b" | y != 3]  #没有进行优化,仍然采用向量扫描提取子集
##    x y v
## 1: b 1 1
## 2: b 6 3
## 3: a 1 4
## 4: a 3 5
## 5: a 6 6
## 6: c 1 7
## 7: c 3 8
## 8: c 6 9
DT[.("b", 3), on = c("x", "y")]  # 对两列x和y设置二级索引,利用二分法查找,速度快
##    x y v
## 1: b 3 2
DT[.("b", 3), on = .(x, y)]  #同上, .()代替
##    x y v
## 1: b 3 2
DT[.("b", 1:2), on = c("x", "y")]  #没有匹配返回NA,y中没有2,因此返回的v值为NA
##    x y  v
## 1: b 1  1
## 2: b 2 NA
DT[.("b", 1:2), on = c("x", "y"), nomatch = NULL] #没有匹配的行,不返回值
##    x y v
## 1: b 1 1
DT[.("b", 1:2), on = c("x", "y"), roll = Inf]   # 没有匹配,返回上一行值
##    x y v
## 1: b 1 1
## 2: b 2 1
DT[.("b", 1:2), on = .(x, y), roll = -Inf] #没有匹配,返回下一行值
##    x y v
## 1: b 1 1
## 2: b 2 2
DT["b", sum(v * y), on = "x"]  # 对于所有x=="b"的行, 列v与y相乘,求和,注意返回的是向量
## [1] 25

8.综合小练习

DT[x != "a", sum(v), by = x]  # 提取x不等于"a"的所有行,根据x进行分组,计算每一组v列数值之和
##    x V1
## 1: b  6
## 2: c 24
DT[!"a", sum(v), by = .EACHI, on = "x"] # 同上,.EACHI 代表x列中每一类元素
##    x V1
## 1: b  6
## 2: c 24
DT[c("b", "c"), sum(v), by = .EACHI, on = "x"] #同上
##    x V1
## 1: b  6
## 2: c 24
DT[c("b", "c"), sum(v), by = .EACHI, on = .(x)] #同上
##    x V1
## 1: b  6
## 2: c 24

9.数据集连接、合并等操作

X = data.table(x = c("c", "b"),
               v = 8:7,
               foo = c(4, 2))  #创建一个新的数据集
X
##    x v foo
## 1: c 8   4
## 2: b 7   2
DT[X, on = "x"] # 把DT并入x,根据X中的x列,优先并入DT中其他列,X中的剩余列在最后,如果有跟DT重复的列名,加i.作为前缀;加i指明了这一列来自X
##    x y v i.v foo
## 1: c 1 7   8   4
## 2: c 3 8   8   4
## 3: c 6 9   8   4
## 4: b 1 1   7   2
## 5: b 3 2   7   2
## 6: b 6 3   7   2
X[DT, on = "x"] # 把X并入DT,根据DT中的x列,优先并入X中其他列,DT中的剩余列在最后,如果有跟X重复的列名,加i.作为前缀;加i指明了这一列来自DT,不匹配的用NA填充
##    x  v foo y i.v
## 1: b  7   2 1   1
## 2: b  7   2 3   2
## 3: b  7   2 6   3
## 4: a NA  NA 1   4
## 5: a NA  NA 3   5
## 6: a NA  NA 6   6
## 7: c  8   4 1   7
## 8: c  8   4 3   8
## 9: c  8   4 6   9
X[DT, on = "x", nomatch = NULL]  #屏蔽不匹配的行
##    x v foo y i.v
## 1: b 7   2 1   1
## 2: b 7   2 3   2
## 3: b 7   2 6   3
## 4: c 8   4 1   7
## 5: c 8   4 3   8
## 6: c 8   4 6   9
DT[!X, on = "x"]  #不合并数据集
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
DT[X, on = c(y = "v")] #通过DT中的y列与X中的v列进行连接
##       x y  v i.x foo
## 1: <NA> 8 NA   c   4
## 2: <NA> 7 NA   b   2
DT[X, on = "y==v"]  #同上
##       x y  v i.x foo
## 1: <NA> 8 NA   c   4
## 2: <NA> 7 NA   b   2
DT[X, on = .(y <= foo)] # 只要DT中y列的值小于等于X列中foo值,两行就连接在一起;注意再返回的结果,y列实际是X中foo列的值
##    x y v i.x i.v
## 1: b 4 1   c   8
## 2: b 4 2   c   8
## 3: a 4 4   c   8
## 4: a 4 5   c   8
## 5: c 4 7   c   8
## 6: c 4 8   c   8
## 7: b 2 1   b   7
## 8: a 2 4   b   7
## 9: c 2 7   b   7
DT[X, on = "y<=foo"] #同上
##    x y v i.x i.v
## 1: b 4 1   c   8
## 2: b 4 2   c   8
## 3: a 4 4   c   8
## 4: a 4 5   c   8
## 5: c 4 7   c   8
## 6: c 4 8   c   8
## 7: b 2 1   b   7
## 8: a 2 4   b   7
## 9: c 2 7   b   7
DT[X, on = c("y<=foo")] #同上
##    x y v i.x i.v
## 1: b 4 1   c   8
## 2: b 4 2   c   8
## 3: a 4 4   c   8
## 4: a 4 5   c   8
## 5: c 4 7   c   8
## 6: c 4 8   c   8
## 7: b 2 1   b   7
## 8: a 2 4   b   7
## 9: c 2 7   b   7
DT[X, on = c("y>=foo")] #只要DT中y列的值大于等于X列中foo值,两行就连接在一起;注意再返回的结果,y列实际是X中foo列的值
##    x y v i.x i.v
## 1: b 4 3   c   8
## 2: a 4 6   c   8
## 3: c 4 9   c   8
## 4: b 2 2   b   7
## 5: b 2 3   b   7
## 6: a 2 5   b   7
## 7: a 2 6   b   7
## 8: c 2 8   b   7
## 9: c 2 9   b   7
DT[X, on = .(x, y <= foo)] #依据共有x列(即x==x)和y<=foo两个条件,把DT并入X中;
##    x y v i.v
## 1: c 4 7   8
## 2: c 4 8   8
## 3: b 2 1   7
DT[X, .(x, y, x.y, v), on = .(x, y >= foo)] # 当X是一个data.table时,x.y 指的是DT中的y;i.x值得时X中的x
##    x y x.y v
## 1: c 4   6 9
## 2: b 2   3 2
## 3: b 2   6 3
DT[X, on = "x", mult = "first"]  #返回每个组的第一行
##    x y v i.v foo
## 1: c 1 7   8   4
## 2: b 1 1   7   2
DT[X, on = "x", mult = "last"] #返回每个组的最后一行
##    x y v i.v foo
## 1: c 6 9   8   4
## 2: b 6 3   7   2
DT[X, sum(v), by = .EACHI, on = "x"] #基于连接后数据集,依据x分组评估每个组的v列值之和
##    x V1
## 1: c 24
## 2: b  6
DT[X, sum(v) * foo, by = .EACHI, on = "x"] #原理同上,依据x分组评估每个组的v列值之和与foo之积
##    x V1
## 1: c 96
## 2: b 12
DT[X, sum(y) * foo, on = .(x, v >= v), by = .EACHI]  #连接条件:DT与X中x列相等,DT中v列大于X中v列
##    x v V1
## 1: c 8 36
## 2: b 7 NA
DT[X, on = .(x)]
##    x y v i.v foo
## 1: c 1 7   8   4
## 2: c 3 8   8   4
## 3: c 6 9   8   4
## 4: b 1 1   7   2
## 5: b 3 2   7   2
## 6: b 6 3   7   2
DT[X, on = .(x, v >= v)]
##    x  y v foo
## 1: c  3 8   4
## 2: c  6 8   4
## 3: b NA 7   2

10.设置和使用主键

kDT = copy(DT)  #深层拷贝
setkey(kDT, x)   # 设置主键为x列
setkeyv(kDT, "x") # 同上v表示向量
v = "x"
setkeyv(kDT, v) #同上
#key(kDT) <- v 已经废弃
haskey(kDT) #判断是否存在主键
## [1] TRUE
key(kDT)  #列出主键
## [1] "x"
kDT["a"]  # 根据x==a进行过滤
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
kDT["a", on = "x"]  #更明晰的写法
##    x y v
## 1: a 1 4
## 2: a 3 5
## 3: a 6 6
kDT[!"a", sum(v), by = .EACHI] #依据x列中元素分组(不包括"a",每一组求v列值之和
##    x V1
## 1: b  6
## 2: c 24
setkey(kDT, x, y) #设置多列作为主键
setkeyv(kDT, c("x", "y"))   #同上
kDT[.("a", 3:6)]  # 根据x和y列进行过滤
##    x y  v
## 1: a 3  5
## 2: a 4 NA
## 3: a 5 NA
## 4: a 6  6

11.特殊符号

主要包括:.N (行数), .SD (所有列), .SDcols (指定列), .I (行序列), .GRP (分组序列)等。

DT[.N]  #.N总行数。返回最后一行
##    x y v
## 1: c 6 9
DT[, .N] #返回总行数
## [1] 9
DT[, .N, by = .(x)] #返回每一组行数
##    x N
## 1: b 3
## 2: a 3
## 3: c 3
DT[, .SD, .SDcols = x:y]  #.SD表示选择的特定列,DT数据集中从x到y列之间的所有列,包括x和y
##    x y
## 1: b 1
## 2: b 3
## 3: b 6
## 4: a 1
## 5: a 3
## 6: a 6
## 7: c 1
## 8: c 3
## 9: c 6
DT[, .SD, .SDcols = !x:y]   #选择特定的列,剔除DT数据集中从x到y列之间的所有列,包括x和y
##    v
## 1: 1
## 2: 2
## 3: 3
## 4: 4
## 5: 5
## 6: 6
## 7: 7
## 8: 8
## 9: 9
DT[, .SD, .SDcols = patterns('^[xv]')] # 通过模式匹配选择特定的列。返回列名中包括x或者v的数据集。可以模式匹配是个好东西!
##    x v
## 1: b 1
## 2: b 2
## 3: b 3
## 4: a 4
## 5: a 5
## 6: a 6
## 7: c 7
## 8: c 8
## 9: c 9
DT[, .SD[1]] #返回第一行
##    x y v
## 1: b 1 1
DT[, .SD[1], by = x] #返回每一组中的第一行
##    x y v
## 1: b 1 1
## 2: a 1 4
## 3: c 1 7
DT[, c(.N, lapply(.SD, sum)), by = x] #根据x分组。获得每一个分组的行数,以及y和v列之和
##    x N  y  v
## 1: b 3 10  6
## 2: a 3 10 15
## 3: c 3 10 24
DT[, .I[1], by = x] #定义每一行的顺序编号。每一组中第一行的顺序编号
##    x V1
## 1: b  1
## 2: a  4
## 3: c  7
DT[, grp := .GRP, by = x] #定义每一组顺序编号
DT
##    x y v grp
## 1: b 1 1   1
## 2: b 3 2   1
## 3: b 6 3   1
## 4: a 1 4   2
## 5: a 3 5   2
## 6: a 6 6   2
## 7: c 1 7   3
## 8: c 3 8   3
## 9: c 6 9   3
DT[, grp := .GRP, keyby = x] #x排好序,再定义组号
DT
##    x y v grp
## 1: a 1 4   1
## 2: a 3 5   1
## 3: a 6 6   1
## 4: b 1 1   2
## 5: b 3 2   2
## 6: b 6 3   2
## 7: c 1 7   3
## 8: c 3 8   3
## 9: c 6 9   3
X[, DT[.BY, y, on = "v"], by = v] # .BY 接受v分组后两个值(8,7),对DT进行过滤,获取y值。返回一个data.table,其中v来自X,V1来自DT中的y。
##    v V1
## 1: 8  3
## 2: 7  1

12.通过reference方式增加/更新/删除列

DT[, z := 42L]  #通过reference 方式增加z列,值为42
DT
##    x y v grp  z
## 1: a 1 4   1 42
## 2: a 3 5   1 42
## 3: a 6 6   1 42
## 4: b 1 1   2 42
## 5: b 3 2   2 42
## 6: b 6 3   2 42
## 7: c 1 7   3 42
## 8: c 3 8   3 42
## 9: c 6 9   3 42
DT[, z := NULL] #删除z列
DT["a", v := 42L, on = "x"] #对列x中值为a的行,修改列v的值为42
DT["b", v2 := 84L, on = "x"] #对列x中值为b的行,修改列v的值为84
DT[, m := mean(v), by = x][] #根据x分组,求每组v列均值,作为新列m
##    x y  v grp v2  m
## 1: a 1 42   1 NA 42
## 2: a 3 42   1 NA 42
## 3: a 6 42   1 NA 42
## 4: b 1  1   2 84  2
## 5: b 3  2   2 84  2
## 6: b 6  3   2 84  2
## 7: c 1  7   3 NA  8
## 8: c 3  8   3 NA  8
## 9: c 6  9   3 NA  8

13.高级用法

DT = data.table(
  x = rep(c("b", "a", "c"), each = 3),
  v = c(1, 1, 1, 2, 2, 1, 1, 2, 2),
  y = c(1, 3, 6),
  a = 1:9,
  b = 9:1
)
DT[, sum(v), by = .(y %% 2)] # by中也可以指定表达式。 %%-余数,分组0和1
##    y V1
## 1: 1  9
## 2: 0  4
DT[, sum(v), by = .(bool = y %% 2)] #同上,改变分组列名为bool
##    bool V1
## 1:    1  9
## 2:    0  4
DT[, .SD[2], by = x] #获得每个分组的第二行
##    x v y a b
## 1: b 1 3 2 8
## 2: a 2 3 5 5
## 3: c 2 3 8 2
DT[, tail(.SD, 2), by = x] #获得每个分组的最后两行
##    x v y a b
## 1: b 1 3 2 8
## 2: b 1 6 3 7
## 3: a 2 3 5 5
## 4: a 1 6 6 4
## 5: c 2 3 8 2
## 6: c 2 6 9 1
DT[, lapply(.SD, sum), by = x] #对每个分组,每一列求和
##    x v  y  a  b
## 1: b 3 10  6 24
## 2: a 5 10 15 15
## 3: c 5 10 24  6
DT[, .SD[which.min(v)], by = x] #每个分组中v最小值多在行集合
##    x v y a b
## 1: b 1 1 1 9
## 2: a 1 6 6 4
## 3: c 1 1 7 3
DT[, list(MySum = sum(v),
          MyMin = min(v),
          MyMax = max(v)),
   by = .(x, y %% 2)]       # 统计每个组合(x和y%%2)的v列和,最大和最小值
##    x y MySum MyMin MyMax
## 1: b 1     2     1     1
## 2: b 0     1     1     1
## 3: a 1     4     2     2
## 4: a 0     1     1     1
## 5: c 1     3     1     2
## 6: c 0     2     2     2
DT[, .(a = .(a), b = .(b)), by = x] #这个有意思,a和b列的属性是列表
##    x     a     b
## 1: b 1,2,3 9,8,7
## 2: a 4,5,6 6,5,4
## 3: c 7,8,9 3,2,1
DT[, .(seq = min(a):max(b)), by = x] # 返回的seq列值:b-1:9, a-4:6, c-7:3
##     x seq
##  1: b   1
##  2: b   2
##  3: b   3
##  4: b   4
##  5: b   5
##  6: b   6
##  7: b   7
##  8: b   8
##  9: b   9
## 10: a   4
## 11: a   5
## 12: a   6
## 13: c   7
## 14: c   6
## 15: c   5
## 16: c   4
## 17: c   3
DT[, sum(v), by = x][V1 < 20]  #根据x分组,对v求和,然后对新数据,筛选和小于20的行
##    x V1
## 1: b  3
## 2: a  5
## 3: c  5
DT[, sum(v), by = x][order(-V1)]  #对新数据集降序排列
##    x V1
## 1: a  5
## 2: c  5
## 3: b  3
DT[, c(.N, lapply(.SD, sum)), by = x]  #汇总每个分组的行数,每一列的和
##    x N v  y  a  b
## 1: b 3 3 10  6 24
## 2: a 3 5 10 15 15
## 3: c 3 5 10 24  6
DT[, {
  tmp <- mean(y)
  
  .(a = a - tmp, b = b - tmp)
}, by = x] # 这个高级,可以自定义小函数
##    x          a          b
## 1: b -2.3333333  5.6666667
## 2: b -1.3333333  4.6666667
## 3: b -0.3333333  3.6666667
## 4: a  0.6666667  2.6666667
## 5: a  1.6666667  1.6666667
## 6: a  2.6666667  0.6666667
## 7: c  3.6666667 -0.3333333
## 8: c  4.6666667 -1.3333333
## 9: c  5.6666667 -2.3333333
DT[, plot(a, b), by = x] # 也可以直接画图,牛

## Empty data.table (0 rows and 1 cols): x
DT[, c(.(y = max(y)), lapply(.SD, min)), by = rleid(v), .SDcols = v:b] #这个也很复杂 rleid函数功能类似.GRP
##    rleid y v y a b
## 1:     1 6 1 1 1 7
## 2:     2 3 2 1 4 5
## 3:     3 6 1 1 6 3
## 4:     4 6 2 3 8 1