data.table本质上是一个list,它们的列包含的元素个数都相同。
1、数据的读写
1.1数据读入--fread
选其常用的参数如下:
fread(input,na.strings="NA", file, stringsAsFactors=FALSE,encoding="unknown", ...)
- input 入的文件对象,
fread
函数可以自动判断分隔符类型,自动判断首行是否是列标题,同时默认读入时字符型变量不会变为因子型。也可也从网页读取数据 - na.strings,对NA的解释;
- file文件路径,再确保没有执行shell命令时很有用,也可以在input参数输入;
- stringsASFactors是否转化字符串为因子,
- encoding,默认”unknown”,其它可能”UTF-8”或者”Latin-1”,不是用来重新编码的,而是允许处理的字符串在本机编码;
- showProgress = T 显示进度条
- integer64 当数据列中有大于2 ^ 31的整数,可能会丢失精度
- quote 对带双引号的字符添加转义,在R中正常显示,但是输出时,可能会引起成倍的双引号,因此建议使用
fwrite
时,设置该参数quote = FALSE
1.2数据写入--fwrite
fwrite(x, file = "", append = FALSE, na = "", row.names = FALSE, col.names = TRUE,logicalAsInt = FALSE, ...)
- x,比如data.frame和data.table等R的对象;
- file,输出文件名,““意味着直接输出到操作台;
- append,如果TRUE,在原文件的后面添加;默认删除原来文件的数据,重新存储。
- na,na值的表示,默认”“;
- row.names,是否写出行名,因为data.table没有行名,所以默认FALSE;
- col.names ,是否写出列名,默认TRUE,如果没有定义,并且append=TRUE和文件存在,那么就会默认使用FALSE;
- logicalAsInt,逻辑值作为数字写出还是作为FALSE和TRUE写出;
2. 数据行列筛选 — i 参数
对于数据的处理,data.table
包提供了一个非常简洁的通用格式:DT[i,j,by]
,
意思是: 对于数据集DT
,选取子集行i
, 通过by
分组计算j
。i
设定数据的选取条件,j
设定结果的计算方式,by
设定数据的分组情况。通过这个,我们可以在一行代码中很方便地完成处理过程。首先需要把数据变为data.table类型
library(data.table)
library(magrittr)
mtcars_dt <- data.table(mtcars) # 也可以强制转化 as.data.table()
行筛选:–i参数
- 直接采用逻辑语句(慢)
- 使用主键(会对筛选的数据进行重排序–适中)
- 二级索引(不重排序,按原数据的顺序排序–快)
2.1 直接采用逻辑语句(慢)
mtcars_dt <- data.table(mtcars) # 一共32行,11列
#直接采用逻辑语句(慢)
mtcars_dt[cyl==8 ]
mtcars_dt[cyl==8 & carb==4]
mtcars_dt[cyl %in% c("8","6")]# 等价 mtcars_dt[cyl %in% c(8,6)]
2.1.2 设置/获取/使用主键(适中)
函数 | 说明 |
---|---|
setkey(x,V1) |
该函数可以对一个data.table按照某一列进行排序,排序之后,这个data.table 对象会被标记为排过序了, 由于不会在内存中复制被排序的data.table对象,所以非常高效. |
setkey(x,V1,V2,V3, ...) |
等价于setkeyv(x,c("V1","V2",...) ) |
setkeyv(x,c("V1","V2",...) ) |
可以按照多列一起排序.先根据V1排序,然后根据V2排序, 以此类推. |
table() |
查看内存中所有data.table 的详细信息,包括key键, |
setkey(x,V1)
传入列名作为参数,不需要引号, 而setkeyv(x,c("V1","V2",...) )
传入一个字符型的向量, 这两个函数都没有返回值, 直接对data.table进行更新操作,和:=
类似每一个table.table中只有一组key键, 像普通的data.frame, 每一行有且只有一个行名且行名具有唯一性, 行名可以看做是data.frame的索引. 而data.table可以利用主键进行索引, 且可以对多列设置主键,且主键不强调唯一性. 也就是说,不同列的主键可以是一样的。既然行可以通过主键排序,那么排序的时候,具有同样主键的一些行,会被排在一起。
在data.table里,操作符”:=“和所有的以set开头函数(比如setkey,setorder,setname等)一样,它们都会更新输入的原数据。
#使用主键(会对筛选的数据进行重排序--适中) mtcars_dt <- data.table(mtcars) setkey(mtcars_dt,cyl,carb)# 设置key键 mtcars_dt #可以看出已经排序了 tables() # 查看内存中所有data.table 的详细信息,包括key键 iris_dt = data.table(iris) tables() # setkeyv(mtcars_dt,c("cyl","carb"))#先设置的cyl,carb列为主键 mtcars_dt[.(8,4)] # 等价 mtcars_dt[cyl==8 & carb==4] tables() # 查看内存中所有data.table 的详细信息
2.3 二级索引(indices)
(快)
a) 什么是二级索引? 二级索引和主键有什么区别?
- 前面介绍了
setkey
和setkeyv
的使用,每一次使用setkey
, data.table 对象都会在内存里被重新排序, 时间复杂度是O(nlogn)
. 而setindex
和setindexv
则不会对data.table对象进行重新排序, 它只会计算某列的顺序,将这个顺序向量保存在一个额外的,叫做index的属性里面。 此外,一个data.table对象只能有一组key, 但是可以有多个二级索引(indices). 二级索引的时间复杂度是O(logn)
函数 | 说明 |
---|---|
setindex(x,V1) |
设置索引—即将V1列设置为该data.table的二级索引 等价 setindexv(x,c("V1")) |
setindex(x,V1,V2,...) |
设置多列索引—即将V1,V2列设置为该data.table的二级索引 等价 setindexv(x,c("V1","V2",...)) |
setindexv(x,c("V1","V2",...)) |
以字符的方式设置索引 |
setindex(x, NULL) |
删除所有的二级索引。 |
indices(x) |
获取data.table对象的所有二级索引, 如果该data.table没有二级索引,那么返回NULL。 |
# 设置和获取二级索引
mtcars_dt <- data.table(mtcars)
setindex(mtcars_dt,cyl,carb) # 等价于 setindexv(mtcars_dt,c("cyl","carb"))
names(attributes(mtcars_dt)) # 查询data.table 的属性
indices(mtcars_dt)
mtcars_dt # 注意并没有对数据进行重新排序, 而setkey会对数据进行重新排序
setindexv(mtcars_dt,c("vs", "am", "gear","carb"))
indices(mtcars_dt)
b)为什么使用二级索引
原因是对一个data.table重新排序成本太高, 除非你要进行大量选子集的操作,建议使用setkey 来提取子集. 我们想快速地提取子集(subset)同时又不必重新排序,此时二级索引就派上用处了.
2.4 二级索引的快速使用 — 参数on
上面我们都在讲先设置二级索引,然后在进行操作,这样能不能合成一步?—-于是参数on
诞生了, 参数on
使得语法更简洁,并且能自动创建并重用二级索引
参数on
- 通过创建索引进行subset。每次都能节省setindex()的时间。
- 通过检查属性,可以简单地重用已经存在的二级索引。
- 参数on必须是一个字符型的向量。
- 注意参数on也可以用来指定主键。事实上,为了更佳的可读性,我们鼓励在参数on里面指定主键。
参数
on
它不会把这个二级索引自动创建为data.table的一个属性。需要额外设置参数才可以(设verbose = TRUE
).mtcars_dt[.(1),on="am"]#同时满足cyl=8,gear=4的行 ## 等价的备选方案 -- mtcars_dt[list(1), on = "am"] mtcars_dt[.(8,4),on=c("cyl","carb")]#同时满足cyl=8,gear=4的行 ## 注意 on="am"表明我们要基于am这一列进行过滤,过滤的条件是am == 1, 1必须放置.()里面 ## 原因是 am是numeric类型,如果不是numeric类型,就不用这样做 iris_dt = data.table(iris) iris_dt["setosa",on="Species"] #等价的备选方案 iris_dt[.("setosa"),on="Species"] iris_dt[list("setosa"),on="Species"] # 选择 Species =setosa 或者 Species =virginica iris_dt[.(c("setosa","virginica")) ,on="Species"] iris_dt[.(c("setosa","virginica"),c(3.0)) ,on=c("Species","Sepal.Width")] # 注意小心有NA行,原因在于Petal.Length列没有等于5.1的值 iris_dt[.(c("setosa","virginica"),c(5.1)) ,on=c("Species","Petal.Length")] ####
2.5 自动索引 — 只支持操作符 == 和 %in%
回顾一下,我们先学习如何通过主键使用快速二分法搜索进行subset。接着,我们学习了使用二级索引,它带来更好的效果,而且语法也更简洁。 等等,有没有更好的方法?
有!优化R的原生语法,使用内置的索引。这样我们毋需使用新的语法,就能得到同样的效果。 这就是自动索引。 目前,它只支持操作符 == 和 %in% 。而且只对一列起作用。某一列会被自动创建为索引,并且作为data.table的属性保存起来。这跟参数on不同,参数on会每次创建一个临时索引,所以才会被叫做“二级索引”。
iris_dt[Petal.Length == 5.1, ]
iris_dt[Petal.Length %in% c(5.1,5.2), ]
注意, 以上都是针对i
参数设置,因此可以同j
参数, by
参数同时使用
还有, 在进行行筛选时,尽量打个逗号,区分i
和j
3 列筛选:– j参数
3.1 .()
格式 == 等价list()
用.()
来包围列名,和list()
等价
# 列筛选 --方法一, 直接输入列名
mtcars_dt[, .(mpg, cyl, hp)] %>% head()
mtcars_dt[, c("mpg", "cyl", "hp")]# 返回的都是data.table
mtcars_dt[, c("mpg", "cyl", "hp"), with =F]
# 列筛选 --方法二 如果没用list()或者.()包围列名 ,则返回的是向量(这不是我们想要的结果),如下
mtcars_dt[, c(mpg, cyl, hp)] %>% head()
# 列筛选 --方法三, 可以把列名改写成对应的数字列,但返回的是data.table类型
mtcars_dt[, c(1, 2, 4)] %>% head()
mtcars_dt[, c(1, 2, 4)] %>% class()
# 列筛选 --方法四, 要排除列名怎么办,可以用数字
mtcars_dt[, -c(1, 2, 4)] %>% head() # 等价
mtcars_dt[, c("mpg", "cyl", "hp") :=NULL] # 等价
mtcars_dt[, -c("mpg", "cyl", "hp"), with = F] %>% head() # 等价
mtcars_dt[, !c("mpg", "cyl", "hp"), with = F] %>% head() # 等价
# 下面只能用冒号(:)链接,不然会报错
mtcars_dt[, mpg:disp, with = FALSE] %>% head() # 等价
mtcars_dt[,-(mpg:disp), with = FALSE] %>% head() # 等价
3.2 在j参数上可以进行计算:
# 返回按列计算的值
mtcars_dt[,.(sum(mpg),mean(cyl))]
# 当列的长度不一的时候,会循环对齐
mtcars_dt[,.(mpg,mean(cyl))] %>% head()
# 还可以输入多个表达式,用花括号括起来即可
mtcars_dt[,{print(mpg);plot(disp)}]
# {} 还有帮助我们隐藏一些过度中间变量
mtcars_dt[,.(x = cyl^2 +1, y = cyl^2 +2)] %>% head() # 这样cyl^2计算了两次,这样效率慢
mtcars_dt[,{temp = cyl^2; .(x = temp+1,y=temp+2)}] %>% head()
关键词.SD
和.SDcol
的作用
## 对data.table的每一列进行计算
mtcars_dt[,sapply(.SD, function(x){sum(is.na(x))/.N})]
## 也可以指定列进行计算
mtcars_dt[,sapply(.SD, function(x){sum(is.na(x))/.N}),.SDcol = c("A","B","C")]
iris_dt = data.table(iris)
col_names = colnames(iris_dt)
##### 在数据框中把指定的列转换为因子列
## 方法一 ---- 这种方法已被弃用 with=F, 推荐使用方法2
# col_names 为一个字符向量
# iris_dt[, col_names := lapply(.SD, function(x)as.factor(x)),.SDcols = col_names,with=F]
iris_dt[,col_names,with=F] # 选择以col_names变量的内容的列
## 方法二
## (col_names):= 代表的是字符串向量,如果只使用col_names则表示列名为col_names的变量
iris_dt[, (col_names) := lapply(.SD, function(x)as.factor(x)) , .SDcols = col_names]
iris_dt[,("Sepal.Length"):=as.factor(Sepal.Length)] #同上
## 如果左边用数字并打上() 代表第几列,指定第几列的操作
iris_dt[,(2:3):=Species] # 把第2:3列的值进行更新,即第2:3列都为Species列
3.3 选取子集
选取子集仍然采用subeset
函数,语法格式为:subset(x, subset, select)
,x是data.table对象,subset是行满足条件,select是列满足条件
# 用 .() 来包围列名,和list() 等价,都返回data.table数据类型,只要参数 j 返回一个list,这个list的每一个元素都会被转换成结果data.table的一列
subset(mtcars_dt,cyl==8,select =c('mpg','cyl','disp'))
# 等价
mtcars_dt[cyl==8,.(mpg,cyl,disp)]
# 等价
mtcars_dt[cyl==8,list(mpg,cyl,disp)]
## 注意 ,没用list()或者.()包围列名 ,则返回的是向量,如下
mtcars_dt[,c(mpg,cyl,hp)]
3.4 对列进行排序
- 排序采用
setorder
函数,输入待排序的列名,默认升序,降序列名前加-
注意:升序,降序都是按assic 值的大学排序
setorder(mtcars_dt,mpg,-hp) #注意,这里直接对原数据进行了重排序,这点和R很多函数不一样, #几乎等价 mtcars_dt[order(cyl,-hp)] #这个就基本符合R语言规律,不改变原数据。 # 还可以使用key键排序,这样会改变原数据
3.5 对列进行增删变量
都是直接对原数据进行修改,无需重复赋值
3.5.1 添加变量有三种语法格式:
DT[i, LHS:=RHS, by=...] #适用单变量添加,等号(=)前面为新的变量名,没有写明变量名,自动为V+
DT[i, c("LHS1","LHS2") := list(RHS1, RHS2), by=...] #双变量添加
DT[i, `:=`(LHS1=RHS1,LHS2=RHS2, ...), by=...] #多变量添加,注意`:=`
# 有条件的添加变量
mtcars_dt <- data.table(mtcars)
mtcars_dt[, cyl1:= 1*(cyl <= 6)]
mtcars_dt[, cyl2:= ifelse(cyl <=6, 1,0)]
mtcars_dt[, cyl_mpg:= ifelse(cyl <=6, mpg,cyl)]# 当cyl小于6时,则用mpg的数据,大于6时,则用cyl值保持不变
3.5.2 删除变量 — 变量:=NULL
# 这里发现一个bug,数据集被改动了,但是rstudio中的变量的维度没变,但是数据集确实改变了
mtcars_dt[,`:=`(mpg1=1/mpg,new=cyl+gear)]#增加变量mpg1 与new变量
mtcars_dt[,mpg1:=NULL] #删除变量mpg1 也可以mtcars_dt[,`:=`(mpg1=NULL,new=NULL)],也可以用 mtcars_dt[, c("mpg") :=NULL]
# 如果要删除多列时,可用以下方法,
mtcars_dt[, c("mpg", "cyl", "hp") :=NULL] # 注意用 := 会直接更改原数据
mtcars_dt[, -c("mpg", "cyl", "hp"), with = F] # 这个会产生一个副本(或者叫拷贝)
mtcars_dt[, !c("mpg", "cyl", "hp"), with = F] # 同上,产生拷贝
mtcars_dt[, -c(1, 2, 4)] # 同上,产生拷贝
# 删除多列
col = c("mpg", "cyl", "hp")
mtcars_dt[, col :=NULL] # 这个只能删除列名为col的变量
mtcars_dt[, (col) :=NULL] # 能删除以col为变量的内容作为mtcars的列名,
# 上面等价mtcars_dt[, c("mpg", "cyl", "hp") :=NULL]
# 下面只能有冒号(:)链接,不然会报错
mtcars_dt[, mpg:disp, with = FALSE] %>% head() # 等价
mtcars_dt[,-(mpg:disp), with = FALSE] %>% head() # 等价
理解数据存储以及变量名 。比如:数字,字符串 ,矩阵的内容,都会直接存在内存中,把数字3赋值给变量a,就相当于a指向数字3,改变a不会对内存进行修改,只是指针发生了改变,
操作符“:=”会更新原数据。操作符“:=”对输入的数据进行浅度复制,只是一份指向列的指针向量的复制,在内存里,数据不是真的被复制了。他会随着指针指向的对象变化而变化,
不更新原数据用函数 copy()。函数 copy() 对输入参数进行深度复制,因此对副本做的所有更新操作,都不会对原数据生效。简单理解:就是在内存中创建了一个和原来一模一样的数据.
4. 分组汇总--by参数
4.1 分组汇总只需在by
指定分组变量,在j
指定计算函数即可
#按cyl与vs分组,对统计分组下的mpg均值,disp的总和,分组数据个数num,共返回cyl,vs,以及分组下的三个新变量
mtcars_dt[,.(mean_mpg = mean(mpg),num=length(mpg),sum_disp = sum(disp)),by=.(cyl,vs)]
.2 data.table有一个特殊的变量.N
可以直接计算分组的观测值个数。
- 当参数j里面只有一列,我们可以省略 .(),如下:mtcars_dt[,.N,by=.(cyl,vs)]
- 当参数by里面只有一列,我们可以省略 .(),如下:mtcars_dt[,.N,by=cyl]
mtcars_dt[,.(mean_mpg = mean(mpg),.N),by=.(cyl,vs)]
4.3 by参数还有接受表达式:
# 可以对要分组的列进行表达式判断,按其真假进行分组
mtcars_dt[,.(mean_mpg = mean(mpg),.N),by=.(cyl>5,vs)]
# 也可以用函数进行简单的分组
mtcars_dt[,.(hp),by=sign(cyl-6)]
4.4 .SD
和.SDcol
关键词
对j参数进行计算时,必须分别对每列指定 mean() 函数吗 ?
如上面的例子,要对分组变量进行计算难道都要指定每一列?可以采用.SD
函数,它常和.SDcols
函数联合使用。
.SD
是经过i
和by
处理之后剩下的那部分数据集, 本质是一个data.table
对象.- 另一种理解,
.SD
代表行经过i
筛选后, 除了by
参数指定的列以外的所有列组成的新的data.table, 是原数据的子集 - 首先,对
i
参数进行筛选, 然后把by
参数指定的那一列会被提前到首列, 其余列被按by
指定的列进行分组组成新的data.table
对象, 分成多少组,就有多少个data.table
对象, 这里的多个data.table
对象出现的顺序是按照by
指定列值出现的顺序 .SD
只能在位置j中使用, 且只能在:=
右边使用- 由于
.SD
默认包含用于分组的所有列。我们需要指定列进行计算 —–可用关键词.SDcol
.SDcols
指定.SD
包括哪些列. eg:.SDcols = c("disp","hp")
, 则.SD
从默认的所有列改为只包含disp
和hp
这两列。我们也可以使用
-
或者!
来移除列。比如:我们指定!(colA:colB)
或者-(colA:colB)
表示移除从colA
到colB
的所有列。library(data.table) mtcars_dt = data.table::as.data.table(mtcars) mtcars_dt[, .SD, by=cyl] # 注意此时cyl 从第二列变成了第一列,故by指定的列提前到第一列,其余列顺序保持不变 mtcars_dt[, print(.SD), by=cyl] # 注意了多个data.table对象, 每一个对象是按照cyl值的先后顺序输出的 mtcars_dt[, lapply(.SD[, 1:2, with=F], mean), by=cyl] #.SD代表除了by指定的所有列组成的新data.table, 并选择新的data.table的前两列进行计算 # 按cyl vs变量进行分组后,对其余的每一列求均值 mtcars_dt[,lapply(.SD, mean),by=.(cyl,vs)] # 按cyl分组以后对其余变量求和 mtcars_dt[,lapply(.SD, sum),by=.(cyl)] # 如果要对大量的变量做聚合计算,可以使用.SD函数,和.SDcols函数。 # 默认的,.SD函数指对所有变量进行计算 mtcars_dt[, lapply(.SD, mean)] #只对某些特定的列 结合.SDcols参数 mtcars_dt[, lapply(.SD, mean), .SDcols = c("cyl", "vs")]#只对cyl, vs列进行计算 # 对多个变量实现多个统计指标计算 mtcars_dt[, sapply(.SD, function(x) c(mean=mean(x), median=median(x)))]
4.5 如果要对子集进行分组统计怎么办?,即对选出的i进行分组统计.
可用by=列名即可
允许按每一个已知i的子集分组,在使用by=.EACHI时需要设置键值
mtcars_dt[cyl==4 | cyl==8,mean(mpg),by = cyl]
5分组汇总--keyby参数
和上面的by参数有什么不同?
data.table本身就被设计成能保持原数据的顺序。在一些情况下,必须保持原来的顺序。但是,有时我们希望自动根据分组的变量排序。使用keyby
参数,使用参数keyby
自动将引用的列设置为主键
如何按照分组的变量排序
mtcars_dt <- data.table(mtcars) # 未设置主键时,分组变量cyl,vs是按原来mtcars_dt数据集中的顺序排序的 mtcars_dt[,.(mean_mpg = mean(mpg),.N),by=.(cyl,vs)] #设置主键 ,把cyl,vs列设置主键--会根据分组的变量排序。 mtcars_dt[,.(mean_mpg = mean(mpg),.N),keyby=.(cyl,vs)]# 注意这里keyby里面不能进行降序排列,若在vs前面加一个负号,这等于在变量vs乘-1 # 等价 mtcars_dt[,.(mean_mpg = mean(mpg),.N),by=.(cyl,vs)][order(cyl,vs)]
6. chaining表达式
我们可以一个接一个地添加表达式,做一系列操作,就像这样:
1,DT[...][...][...]。
2,DT[...
][...
][...
]
比如要提取前面几行就可以DT[…][1:5]
以上就是data.table的基本操作,后续更新一些本报的一些基本概念和其他函数
7总结
data.table的语法形式是:
DT[i, j, by]
指定参数i:
* 类似于data.frame,我们可以subset行,除非不需要重复地使用 DT$,既然我们能将列当做变量来引用。
* 我们可以使用order()排序。为了得到更快速的效果,order()函数内部使用了data.table的快速排序。
我们可以通过参数i做更多的事,得到更快速的选取和连结。
指定参数j:
* 以data.table的形式选取列:DT[, .(colA, colB)]。
* 以data.frame的形式选取列:DT[, c("colA", "colB"), with=FALSE]。
* 按列进行计算:DT[, .(sum(colA), mean(colB))]。
* 如果需要:DT[, .(sA =sum(colA), mB = mean(colB))]。
* 和i共同使用:DT[colA > value, sum(colB)]。
指定参数by:
* 通过by,我们可以指定列或表达式,进行分组。参数j可以很灵活地配置参数i和by实现强大的功能。
* by可以指定多个列,也可以指定表达式。
* 我们可以用 keyby,对分组的结果自动排序。
* 我们可以在参数j中指定 .SD 和 .SDcols,对复数的列进行操作。例如:
1.把函数fun 应用到所有 .SDcols指定的列上,同时对参数by指定的列进行分组:
DT[, lapply(.SD, fun), by=., .SDcols=...]。
2.返回每组册前两行:DT[, head(.SD, 2), by=.]。
3.三个参数联合使用:DT[col > val, head(.SD, 1), by=.]。
小提示: 只要j返回一个list,这个list的每个元素都会是结果data.table的一列。
https://youngspring1.github.io/2016/2016-03-13-datatable1/
https://youngspring1.github.io/2016/2016-03-21-datatable2/
https://youngspring1.github.io/2016/2016-03-22-datatable3/