R数据处理|data.table篇(三)
本文为data.table包介绍最后一篇,前两篇链接如下 R数据处理|data.table篇(一) - 知乎专栏 R数据处理|data.table篇(二) - 知乎专栏 本文主要讲解data.table包中一些比较不常用的函数,还有data.table包高效的深层原理。下面是本文目录
其他函数具体举例子讲述以下函数 copy setnames setDT ?setDF rleid rowid tables tstrsplit copy 复制一个数据框 name1 <- c("Bob","Mary","Jane","Kim") name2 <- c("Bob","Kim","Jane") weight <- c(60,65,45,55) height <- c(170,165,140,135) birth <- c("1990-1","1980-2","1995-5","1996-4") accept <- c("no","ok","no") library(data.table) dft <- data.table(name1,weight,height,accept) dtt <- copy(dft) 这种复制不同于直接用 <- 赋值,在本专题的后面我会专门讲一下R语言的深复制 setnames 修改列名 setnames(dtt,letters[1:4]) colnames(dtt)<-letters[2:5] # 也可以实现列名的修改 setnames(dtt,"c","C") # 修改特定列名 setnames(dtt,1:2,c("m","n")) setDF 将data.table转化为data.frame setDF(dtt) class(dtt) # "data.frame" # setDT 将data.frame转化为data.table setDT(dtt) class(dtt) # "data.table" "data.frame" rleid # 可以接在by后面,每次连续作为一组 dft = data.table(x=rep(c("b","a","c"),each=3),v=c(1,1,2,2),y=c(1,3,6),a=1:9,b=9:1) rleid(dft$v) # 返回一个和原向量等长的向量,值与其一一对应。值从1开始,原向量从头往后看,值不变则仍为1,变一次加1 dft[,.N,by=rleid(v)] # 根据上面形成的向量分组(每次连续相同的值为一组) rowid 一个组合出现第几次就显示为几 DT = data.table(x=c(20,10,30,20),y=c("a","b","b"),z=1:6) DT rowid(DT$x) # 1,2 rowidv(DT,cols="x") # 同上 rowid(DT$x,prefix="group") # 数字前面加"group" # 返回 ?"group1" "group1" "group2" "group1" "group2" "group2" rowid(DT$x,DT$y) # 多列组合看重复 # 返回1,1 rowidv(DT,cols=c("x","y")) # 同上 DT[,.(N=seq_len(.N)),by=.(x,y)]$N # 上面相当于做了这样的事 # 应用 dcast(DT,x ~ rowid(x,prefix="group"),value.var="z") # 将x为10的两个z值放在同一行,x为20的放在同一行.... tables tables() # 返回当前所有的datatable,并展示数据集行列数、大小、列名、key等信息 tstrsplit name <- 1:3 dates <- c("2016-3-4","2016-3-14","2016-3-24") nd <- data.table(name,dates) strsplit(dates,"-") tstrsplit(dates,"-") # 好像把strsplit得到的结果转置了一样 nd[,c("year","month","day"):=tstrsplit(dates,"-")] # 实现拆分 改进了的函数%chin%替代了%in% fsort替代了sort chmatch替代了match,两个参数返回和前者等长的向量,是前者每一个元素在后者中的索引 chorder或者chgroup代替order,返回一个向量,排列顺序为:最小值在向量中的索引,第二小的... duplicated替代duplicated unique替代unique,另有uniqueN直接计算去重之后的个数 上面改进是功能相同,只是运行速度有所提高。下面列举的函数是不仅在运行速度上,而且在功能上也根据data.table包的特性做了一些增强 集合操作函数 增加了all参数,控制重复值。基础函数只能返回去重之后的结果 函数变化:union intersect setdiff setequal 前面都加了一个f 基础函数作用于两个向量,data.table中函数作用于两个data.table数据框,而且列名需要相同 x <- data.table(a=c(1,4,4)) y <- data.table(a=c(2,5)) x y fintersect(x,y) ? ? ? ? ? ?# 返回相交部分并去重 fintersect(x,y,all=TRUE) ?# 相交,保留重复值 fsetdiff(x,y) ? ? ? ? ? ? ?# x中有y中没有的,去重 fsetdiff(x,all=TRUE) ? ?# 保留重复值 funion(x,y) ? ? ? ? ? ? ? ?# 并集,去重 funion(x,all=TRUE) ? ? ?# 保留重复值 fsetequal(x,y) ? ? ? ? ? ? # 返回一个F,二者不完全相等 rank frank比rank函数速度更快,而且增加参数ties.method参数的一种取值”dense”,即当有两个值相等并列第二时,让二者都为2,之后的数排名不是第4,而是3,这样结果数值不会发生跳跃 x = c(2,5,NA,4) frank(x) # 自动将NA当成最大的了 frank(x,na.last=F) # 自动将NA当成最小的 frank(x,na.last="keep") # NA仍然是NA frank(x,ties.method = "min") frank(x,ties.method = "dense") DT = data.table(x,2)) frank(DT,cols="x") 滞后 shift函数,参数如下
y <- x <- 1:5 xy <- data.table(x,y) shift(x,n=1,fill=NA,type=”lag”) shift(x,n=1:2,fill=0,type=”lag”) xy[,(c(“a”,”b”)):=shift(.SD,”lead”)][] # 添加两列shift(xy,type=”lag”,give.names=T) xy[,shift(.SD,”lead”,give.names = T)][] # 自动生成名字 上下合并数据框 使用rbindlist函数,先将数据框转化为list再进行合并 DT1 = data.table(A=1:3,B=letters[1:3]) DT2 = data.table(A=4:5,B=letters[4:5]) DT3 = data.table(B=letters[4:5],A=4:5) DT4 = data.table(B=letters[4:5],C=factor(1:2)) l1 = list(DT1,DT2) l2 = list(DT1,DT3) l3 = list(DT1,DT4) rbindlist(l1) rbindlist(l1,idcol=T) # 多出一列,对数据框分组(来自不同数据框) rbindlist(l2) # 不同列名直接合并 rbindlist(l2,use.names=T) # 将相同列名的合并在一起 rbindlist(l3) # 不同列名直接合并 rbindlist(l3,fill=T) # 选择相同列名合并,不匹配的填入NA options设置在控制台中输入options()会打印出一个list,这是当前的options设置值,比如显示保留几位小数等。加载data.table包之后,这里新增了一些data.table专用的参数,可以用下面的命令查看 ops <- options() # ops就是一个list,参数和值的一一对应 # ops$ ?这样输入在rstudio中就会自动提示后面的参数 # 由于data.table专用参数都是以datatable为前缀,使用我们输入时可以这样 # ops$datatable. ?这样输入提示的会都是以datatable为前缀的参数,当然当你打出da的时候就已经差不多全是data.table的参数了 ops$datatable.print.nrows # 查看这个参数,返回100 getOption("datatable.print.topn") # 也可以这样查看,返回5 我们拿打印行数来举例子,看这样两个参数datatable.print.topn和datatable.print.nrows
d <- data.table(a=1:200,b=2:201) d # 200行数据自动只输出前5行和后5行 op <- options(datatable.print.topn=10) # 设置打出前10行和后10行 d # 打出前10行和后10行 options(op) # 恢复默认值5 f <- data.table(a=1:50,b=2:51) f # 50行全打了出来 op <- options(datatable.print.nrows = 30) # 设置行数超过30行时就省略打出 f # 只打出前5行和后5行 options(op) # 恢复默认值100 下面我们再深入一点讲解options设置的内部运行机制 上面打印的参数设置其实调用了print函数,options里面设置的参数被print函数自动调用 ?print.data.table # 可以查看打印data.table的函数的帮助文档,发现函数参数设置如下 print(x,? ?topn=getOption("datatable.print.topn"),? ? ? ? ?# default: 5 ? ?nrows=getOption("datatable.print.nrows"),? ? ? ?# default: 100 ? ?class=getOption("datatable.print.class"),?# default: FALSE ? ?row.names=getOption("datatable.print.rownames"),# default: TRUE ? ?quote=FALSE,...) # 所以我们之前在options里面设置的参数都在这里被调用 # 所以我们也可以直接使用print函数来实现和options设置相同的功能 print(d) print(d,topn=10) print(f) print(f,nrows=30) 性能之Secondary indices and auto indexing上面我们提到setkey设置键值方便以后提取,但是它会自动按照键将整个数据框排序,这是是非常耗费时间的。我们可以选择用setindex函数省去这部分时间,同时不损失提取效率。 下面我们首先来介绍一下index的创建和查询,以及index和判断提取的关系。 dft <- data.table(name1,accept) setindex(dft,name1) # 设置按照name1列来索引,但不进行排序 names(attributes(dft)) # 多出了属性index indices(dft) # 查看现有的index,"name1" setindex(dft,accept) # 增加一个index indices(dft) # "name1" ?"accept" setindex(dft,NULL) # 去掉index dft[name1=="Bob"] # 用==判断提取 indices(dft) # 自动生成index为name1 dft[weight==45] # 这样之后就有两个index了 setindex(dft,NULL) # 去掉index dft[.(60),on="weight"] # 使用on判断提取 indices(dft) # 不会创建index 我们会发现使用==进行提取时就已经自动创建了index,所以一般没有必要提前用setindex去设置 那么创建index有什么好处呢?主要是运行速度上的问题,我们来看一下实例 set.seed(1L) dt = data.table(x = sample(1e5L,1e7L,TRUE),y = runif(100L)) print(object.size(dt),units = "Mb") # 114.4 Mb system.time(ans <- dt[.(988L),on="x"]) # 有一定的时间消耗,多次运行这条命令,实现消耗几乎没有区别 system.time(ans <- dt[x == 989L]) # 时间消耗与使用on基本相同 system.time(ans <- dt[x == 1L]) # 几乎没有时间消耗 system.time(ans <- dt[.(988L),on="x"]) # 这时使用on也不耗费了 system.time(ans <- dt[y == 989L]) # 有较大时间消耗 system.time(ans <- dt[y == 9]) # 几乎没有时间消耗 setindex(dt,NULL) system.time(ans <- dt[x == 1L]) # 仍有一定的时间消耗 # 看普通数据框 df = data.frame(x = sample(1e5L,y = runif(100L)) system.time(ans <- df[df$x == 1L,]) # 时间消耗比较小,但是每次运行时间相同 我们可以看到,使用==提取创建了index耗费了一些时间后,第二次提取就几乎不耗费时间了,而用on提取每次都要创建index。 下面我们来看一下设置index的耗时,和index与key的对比 dt = data.table(x = sample(1e5L,y = runif(100L)) head(dt) system.time(setindex(dt,x)) # 0.28 setindex(dt,NULL) # 这样删除之后再重新加,时间不变 system.time(setindex(dt,x)) # 0.28 # setkey system.time(setkey(dt,x)) # setkey多了排序,时间要长一些,0.72 setkey(dt,NULL) head(dt) # 即使删除后,依然按照x排序 system.time(setkey(dt,x)) # 因为排序仍然保留,所以再重新加时间缩短了非常多,0.03 system.time(setkey(dt,y)) # 时间还是很多 system.time(setkey(dt,x)) # 因为按y排序,x被打乱了,所以这一次时间也延长了 dt = data.table(x = sample(1e5L,y = runif(100L)) system.time(dt[x==2]) # 有一定的时间消耗 setkey(dt,x) system.time(dt[x==2]) # 几乎不耗费时间 system.time(dt[.(1),on="x"]) # 几乎不耗费时间 总结一下
我们也可以通过设置options参数来禁止index的使用,主要有两个参数
浅复制和深复制(shallow vs deep copy)使用R语言基础函数进行数据处理时,常常默认使用的是深复制的方法,当处理数据集较大时,运行速度就会很慢,data.table在一些地方使用了浅复制,极大提高了运行效率。不过浅复制也会有一些副作用,本节后面会进行介绍。 浅复制和深复制的区别 比如我们要修改一个数据框中某一列的值,用R基础函数的[]处理,其实处理之后得到的数据框已经完全不是最初的数据框本身,它是把原有数据框复制出一个完整的备份,再在这个备份上进行修改,修改的过程中,还可能多次复制,这样的复制不仅极大增加了运行时间,同时也非常消耗内存。这就是所谓的深复制。 而data.table在处理的时候,会使用改变后的新值,而其他没改变的内容还是用原来那些,没有重新复制出来使用。虽然也是一个新的数据框,但是只是新创建了一个指针,指向原有的内容。这样不需要把大量数据全部复制一遍,会大大缩短运行时间,这就是浅复制。 而浅复制有一个弊端,就是新数据框合旧数据框都指向同一个内容,只要在一个数据框中把这个内容改变,另外的数据框也会受到影响。这就是copy函数存在的意义,这样深复制一下可以让两个数据框之间互不影响。下面我们用具体的例子来解释 使用函数来判断数据框的复制 R语言中可以用tracemem函数来跟踪一个变量名指向的地址。地址是变量名指向的内容的存放位置,如果改变数据框时地址发生变化,说明在其他位置复制出了一个一模一样的数据框,新的数据框则使用新产生的那个。因为每次复制数据框,都要分配给它一个新的地址来储存,所以我们可以通过地址变化的次数来反映数据框被复制的次数。 tracemem函数作用在一个变量名上,如果这个变量名指向的地址发生改变,就会print出一条信息。 DF <- data.frame(ID = c("b",a = 1:6,b = 7:12,c = 13:18) # 先测试基础函数的复制情况 tracemem(DF) # 打印出此时地址 "<0000000002F25938>" DF$c <- 18:13 # 修改数据框,打印出三条更改信息,说明这个过程中,数据框被复制了三次 DF$c[DF$ID == "b"] <- 15:13 # 这样改变则复制了四次 untracemem(DF) # 结束检测 接下来我们测试一下data.table DT <- as.data.table(DF) tracemem(DT) DT[,c:=18:13] DT["b",c:=15:13,on="ID"] untracemem(DT) 修改的过程中一次信息都没有print出来,说明没有进行过一次深复制,这是data.table处理高效的原因之一。 浅复制的副作用 上面我们已经说明了data.table的处理方式是浅复制,下面我们用例子说明浅复制中相互影响带来的负面影响。 DT <- data.table(ID = c("b",c = 13:18) DD <- DT[,c:=18:13][] DT;DD # 二者相同 DT["b",on="ID"] DT;DD # 二者仍相同,说明改变DT的同时也改变了DD rm(DT,DD) # 删除变量重新试验 使用copy函数实现复制,不影响原来数据框 DT = data.table(ID = c("b",c = 13:18) assign_DT <- DT copy_DT ?<- copy(DT) DT;assign_DT;copy_DT # 此时三者一样 DT[,c:=18:13] # 改变其中一个 DT;assign_DT # 通过普通赋值符号产生的数据框也跟着改变了 copy_DT # 通过copy深复制才没有被影响 rm(DT,assign_DT,copy_DT) 也可以用address函数检查地址,而不用试验(通过地址来检查各个对象是否改变)
专栏信息专栏主页:Data Analysis(https://zhuanlan.zhihu.com/Data-AnalysisR) 文末彩蛋这里分享几个Rstudio文本编辑快捷键
本文上面有很多相似的代码都是这样快速复制得到的,这样快速复制一个备份来调整参数,有时再上下调整一下位置,非常方便
Dwzb?,?R语言中文社区专栏作者,厦门大学统计专业学生。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |