R语言data.table实战
Liang
2020-05-05
1. 用data.table创建数据集
通常情况,我们用data.frame创建一个数据集时,可以使用下面的语法。
df<-data.frame(a=c('A','B','C','A','A','B'),b=rnorm(6))
对于data.table来说,创建一个数据集是和data.frame同样语法。
library(data.table)
dt = data.table(a=c('A','B','C','A','A','B'),b=rnorm(6))
但是他们的类型是不同的
class(df)
## [1] "data.frame"
class(dt)
## [1] "data.table" "data.frame"
如果是外部文件可以使用fread函数读入,也可以使用 nrow 参数来决定读取多少行:
library(data.table)
mydata <- fread("some_kind_of_data.csv")
mydata <- fread("some_kind_of_data.csv", nrows = 10)
2. 用data.table进行查询
由于data.table对用户使用上是希望和data.frame的操作尽量相似,所以适用于data.frame的查询方法基本都适用于data.table,同时data.table自己具有的一些特性,提供了自定义keys来进行高效的查询。
dt = data.table(a=c('A','B','C','A','A','B'),b=rnorm(6))
# 取第二行的数据
dt[2, ]
## a b
## 1: B -1.100145
# 不加,也可以
dt[2]
## a b
## 1: B -1.100145
# 取a列的值
# 是一个list
dt$a
## [1] "A" "B" "C" "A" "A" "B"
# 取a列中值为B的行
dt[a == "B", ]
## a b
## 1: B -1.100145
## 2: B 1.959228
# 取a列中值为B的行的判断
dt[, a=="B"]
## [1] FALSE TRUE FALSE FALSE FALSE TRUE
上面的操作,不管是用索引值,== 和 $ 都是data.frame操作一样的。下面我们取data.table特殊设计的keys来查询。
# 设置a列为索引列
setkey(dt, a)
# 打印dt对象,发现数据已经按照a列字母对应ASCII码值进行了排序。
dt
## a b
## 1: A 1.4520446
## 2: A -2.4933079
## 3: A -0.7583275
## 4: B -1.1001446
## 5: B 1.9592276
## 6: C 0.8833778
按照自定义的索引进行查询。
# 取a列中值为B的行
dt["B"]
## a b
## 1: B -1.100145
## 2: B 1.959228
# 取a列中值为B的行,并保留第一行
dt["B",mult="first"]
## a b
## 1: B -1.100145
从上面的代码测试中我们可以看出,在定义了keys后,我们要查询的时候就不用再指定列了,默认会把方括号中的第一位置留给keys,作为索引匹配的查询条件。从代码的角度,又节省了一个变量定义的代码。同时,可以用mult参数,对数据集增加过滤条件,让代码本身也变得更高效。如果查询的值,不是索引列包括的值,则返回NA。
通过列名选择列是,是否需要打双引号是一个值得考虑的问题,因为两者各有优劣。 data.table 支持打双引号的原生 R 的做法:
data1 <- mydata[, c("columnA", "columnB", "columnC", "columnD")]
但也可以使用不打双引号,这样会更方便(就好像 tidyverse 里面的 select),只不过需要用到 list:
data1 <- mydata[, list(columnA, columnB, columnC, columnD)]
# 可以使用一个点代替 list:
data1 <- mydata[, .(columnA, columnB, columnC, column)]
在 data.table 的中括号里,.() 就是 list()的简写形式。
如果你想使用一个已经存在的列名向量,比如:
mycols <- c("columnA", "columnB", "columnC", "columnD")
直接套用是行不通的:mydata[, mycols],而是需要在这个列名向量前面加两个点:
data1 <- mydata[, ..mycols]
借用命令行里面 .. 的含义去理解,可以认为是从括号里面的命名空间上升到全局变量了。
3. 对data.table对象进行增、删、改操作
给data.table对象增加一列,可以使用这样的格式 data.table[, colname := var1]。
# 增加1列,列名为c
dt[,c:=b+2]
dt
## a b c
## 1: A 1.4520446 3.4520446
## 2: A -2.4933079 -0.4933079
## 3: A -0.7583275 1.2416725
## 4: B -1.1001446 0.8998554
## 5: B 1.9592276 3.9592276
## 6: C 0.8833778 2.8833778
# 增加2列,列名为c1,c2
dt[,`:=`(c1 = 1:6, c2 = 2:7)]
dt
## a b c c1 c2
## 1: A 1.4520446 3.4520446 1 2
## 2: A -2.4933079 -0.4933079 2 3
## 3: A -0.7583275 1.2416725 3 4
## 4: B -1.1001446 0.8998554 4 5
## 5: B 1.9592276 3.9592276 5 6
## 6: C 0.8833778 2.8833778 6 7
# 增加2列,第2种写法
dt[,c('d1','d2'):=list(1:6,2:7)]
dt
## a b c c1 c2 d1 d2
## 1: A 1.4520446 3.4520446 1 2 1 2
## 2: A -2.4933079 -0.4933079 2 3 2 3
## 3: A -0.7583275 1.2416725 3 4 3 4
## 4: B -1.1001446 0.8998554 4 5 4 5
## 5: B 1.9592276 3.9592276 5 6 5 6
## 6: C 0.8833778 2.8833778 6 7 6 7
给data.table对象删除一列时,就是给这列赋值为空,使用这样的格式 data.table[, colname := NULL]。我们继续使用刚才创建的dt对象。
# 删除c1列
dt[,c1:=NULL]
dt
## a b c c2 d1 d2
## 1: A 1.4520446 3.4520446 2 1 2
## 2: A -2.4933079 -0.4933079 3 2 3
## 3: A -0.7583275 1.2416725 4 3 4
## 4: B -1.1001446 0.8998554 5 4 5
## 5: B 1.9592276 3.9592276 6 5 6
## 6: C 0.8833778 2.8833778 7 6 7
# 同时删除d1,d2列
dt[, c('d1', 'd2'):=NULL]
dt
## a b c c2
## 1: A 1.4520446 3.4520446 2
## 2: A -2.4933079 -0.4933079 3
## 3: A -0.7583275 1.2416725 4
## 4: B -1.1001446 0.8998554 5
## 5: B 1.9592276 3.9592276 6
## 6: C 0.8833778 2.8833778 7
修改data.table对象的值,就是通过索引定位后进行值的替换,通过这样的格式 data.table[condition, colname := 0]。我们继续使用刚才创建的dt对象。
dt[,b:=30]
dt
## a b c c2
## 1: A 30 3.4520446 2
## 2: A 30 -0.4933079 3
## 3: A 30 1.2416725 4
## 4: B 30 0.8998554 5
## 5: B 30 3.9592276 6
## 6: C 30 2.8833778 7
# 对a列值为B的行,c2列值值大于3的行,的b列赋值为100
dt[a=='B' & c2>3, b:=100]
dt
## a b c c2
## 1: A 30 3.4520446 2
## 2: A 30 -0.4933079 3
## 3: A 30 1.2416725 4
## 4: B 100 0.8998554 5
## 5: B 100 3.9592276 6
## 6: C 30 2.8833778 7
# 还有另一种写法
dt[,b:=ifelse(a=='B' & c2>3,50,b)]
dt
## a b c c2
## 1: A 30 3.4520446 2
## 2: A 30 -0.4933079 3
## 3: A 30 1.2416725 4
## 4: B 50 0.8998554 5
## 5: B 50 3.9592276 6
## 6: C 30 2.8833778 7
4. data.table的分组计算
基于data.frame对象做分组计算时,要么使用apply函数自己处理,要么用plyr包的分组计算功能。对于data.table包本身就支持了分组计算,很像SQL的group by这样的功能,这是data.table包主打的优势。
比如,按a列分组,并对b列按分组求和。
dt = data.table(a=c('A','B','C','A','A','B'),b=rnorm(6))
# 对整个b列数据求和
dt[,sum(b)]
## [1] 1.853691
# 按a列分组,并对b列按分组求和
dt[, sum(b), by =a]
## a V1
## 1: A -0.157817
## 2: B 1.464485
## 3: C 0.547023
setkey(DT, colA, colB),可以使得检索和分组更加快速。同时设置两个key变量的方式,也是可以的。
key(data) #检查该数据集key是什么?
haskey(data) #检查是否有Key
attributes(data)
mygroup= group_by(try,gender,buy_online)
from_dplyr<-summarize(mygroup,mean=mean(new_car)) #dplyr用两步
from_data_table<-try[,.(mean=mean(new_car)),by=.(gender,buy_online)] #data.table用一步
dplyr:先用group_by设置分组,然后利用summarize求平均,mean=mean();
data.table,在try数据集中,通过by=.(x,y)来分组,而且可以设定x/y两种分组,来求new_car的平均值。
mydata[,.(sum(Ozone,na.rm=T),sd(Ozone,na.rm=T))] #求和、求标准差操作
DT[,list(MySum=sum(v),
MyMin=min(v),
MyMax=max(v)),
by=.(x)]
多种方式混合,而且代码编译上也会有很多不同之处。DT数据集按照x分组,然后计算v变量的和、最小值、最大值。
5. 多个data.table的连接操作
在操作数据的时候,经常会出现2个或多个数据集通过一个索引键进行关联,而我们的算法要把多种数据合并到一起再进行处理,那么这个时候就会用的数据的连接操作,类似关系型数据库的左连接(LEFT JOIN)。
举个例子,学生考试的场景。按照ER设计方法,我们通常会按照实体进行数据划分。这里存在2个实体,一个是学生,一个是成绩。学生实体会包括,学生姓名等的基本资料,而成绩实体会包括,考试的科目,考试的成绩。
假设有6个学生,分别参加A和B两门考试,每门考试得分是不一样的。
# 学生
student <- data.table(id=1:6,name=c('Dan','Mike','Ann','Yang','Li','Kate'))
student
## id name
## 1: 1 Dan
## 2: 2 Mike
## 3: 3 Ann
## 4: 4 Yang
## 5: 5 Li
## 6: 6 Kate
# 分别参加A和B两门考试
score <- data.table(id=1:12,stuId=rep(1:6,2),score=runif(12,60,99),class=c(rep('A',6),rep('B',6)))
score
## id stuId score class
## 1: 1 1 83.47343 A
## 2: 2 2 79.11669 A
## 3: 3 3 90.85995 A
## 4: 4 4 94.58636 A
## 5: 5 5 77.45066 A
## 6: 6 6 61.55506 A
## 7: 7 1 61.27800 B
## 8: 8 2 63.52879 B
## 9: 9 3 82.44424 B
## 10: 10 4 82.00565 B
## 11: 11 5 71.84342 B
## 12: 12 6 64.20929 B
通过学生ID,把学生和考试成绩2个数据集进行连接。
# 设置score数据集,key为stuId
setkey(score,"stuId")
# 设置student数据集,key为id
setkey(student,"id")
# 合并两个数据集的数据
student[score,nomatch=NA,mult="all"]
## id name i.id score class
## 1: 1 Dan 1 83.47343 A
## 2: 1 Dan 7 61.27800 B
## 3: 2 Mike 2 79.11669 A
## 4: 2 Mike 8 63.52879 B
## 5: 3 Ann 3 90.85995 A
## 6: 3 Ann 9 82.44424 B
## 7: 4 Yang 4 94.58636 A
## 8: 4 Yang 10 82.00565 B
## 9: 5 Li 5 77.45066 A
## 10: 5 Li 11 71.84342 B
## 11: 6 Kate 6 61.55506 A
## 12: 6 Kate 12 64.20929 B
这种方法和tidyverse的方法很不一样。
mult参数 mult参数是用来控制i匹配到的哪一行的返回结果默认情况下会返回该分组的所有元素 返回匹配到键值所在列(V2列)所有行中的第一行
nomatch参数用于控制,当在i中没有到匹配数据的返回结果,默认为NA,也能设定为0。0意味着对于没有匹配到的行将不会返回。
6.一些问题
data.table取列时,可以用data[,1,with=FALSE]取data的第一列,相对于对数据框的操作
在data.table行操作跟data.frame很像,可以data[1,]就可以获得第一行的数据,同时也可以用,data[1]来获得行信息,这个是data.table特有的。
除了行,就是列的问题了。在data.table操作列,真的是费劲,常规来看,data[,.(x)] 还有 data$x,如果有很多名字很长的指标,data.table中如果按列进行遍历呢?data[,1]是不行的[其实是可以的],选中列的方式是用列名。
dt = data.table(a=c('A','B','C','A','A','B'),b=rnorm(6))
dt[, 1]
## a
## 1: A
## 2: B
## 3: C
## 4: A
## 5: A
## 6: B
dt[, 1, with=FALSE]
## a
## 1: A
## 2: B
## 3: C
## 4: A
## 5: A
## 6: B
最后一次修改于 2020-05-05