# data.table{#Data-Manipulation-datatable}
data.table包是我数据处理最常用的R包,是我目前觉得最好用的数据处理包,大部分我需要用到的功能集成在包里,不需要很多的依赖包。我简单接触过python,julia两种语言,并没有深入比较,所以我这个好用的印象仅仅是个人感受。
data.table包是我用了较长一段时间tidyverse系列后发现的“数据处理包”。已经忘记最初是什么吸引了我,我猜测可能是“大数据处理利器”之类的标签吸引了我,因为我喜欢“快”。但是和大部分人可能不同的是,初次接触时,语法的“怪异”并没有给我带来多少麻烦,因为我本来就没有编程基础以及很深的R语言基础。
所以我死记硬背data.table里一些常用用法,尤其喜欢拿Excle的一些用法参照,去实现Excle上面的部分操作,从读取、增、改、删除、筛选、计算列等常规操作入手。慢慢熟悉data.table语法之后,将会享受data.table带来的便利,其简洁的语法以及高效的计算速度(相比tidyverse系列)。
另外,Python中也有该包,目前正在积极开发中,期待ing,毕竟python也是很好用,在不同需求下选择不同的语言实现功能。
官方关于data.table的基础介绍请参阅:
https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html
data.table 优势:
- 速度快
- 内存效率高
- API生命周期管理好
- 语法简洁
>本文会照搬很多官方关于data.table的demo
## 基础介绍{#datatable:description}
本部分从data.table安装,内置的案例查看,到data.table的句式语法,实现基础行列筛选和聚合计算。
1.安装
安装详细信息请参考[the Installation wiki](https://github.com/Rdatatable/data.table/wiki/Installation),有关于不同系统安装首次以及相关说明。
```{r eval=FALSE}
install.packages("data.table")
# latest development version:
data.table::update.dev.pkg()
```
2.使用说明
通过以下代码查看内置的使用案例。
```{r eval=FALSE}
library(data.table)
example(data.table)
```
### 读取数据
在我实际工作中接触的数据大部分以数据库,csv,Excel等形式存在,并且CSV格式数据较少。但是data.table包读取数据的`fread`函数仅接受CSV格式。如果是Excel格式文件,需要通过如`readxl`,`openxlsx`等包读入后转换为`data.table`格式数据。
fread 函数可以直接读取CSV格式文件,无论是本地文件或者在线文件,如下所示:
>案例中使用的数据集是R包`nycflights13`带的flights数据集。
```{r}
library(data.table)
input 、 =、<=分别代表大于、小于、等于、不等于、大于等于、小于等于。常用的逻辑运算符 &、|、!等。
```{r eval=FALSE}
#单条件筛选
filghts[year == 2014] #筛选year==2014
#多条件筛选 用 & 链接
flights[ year == 2014 & month == 6]
# | 相当于中文条件或
flights[ month == 5 | month == 6]
# %in% 类似sql中in用法
flights[month %in% c(1,3,5,7,9)]
# %between% 类似sql中between and 用法
flights[month %between% c(1,7)]
```
#### j列操作
数据集较大、字段较多时,由于无效信息较多可以做适当精选,这时需要我们筛选列。与sql中的select用法一致,即保留想要的字段。
.()或list()是data.table中的比较特殊的实现列筛选的用法。常规数字索引,字符向量索引同样有效。
```{r}
#注意前面的. .()
flights[,.(year,month,day,dep_delay,carrier,origin)]
# flights[,list(year,month,day,dep_delay,carrier,origin)] same above
# not run
# flights[,1:3]
# not run
# flights[,c('year','month','day')]
```
setcolorder函数可以调整列的顺序,将常用的字段信息排在前面可以用过该函数实现。
```{r}
# not run
# setcolorder(x = flights,neworder = c( "month","day","dep_delay" ,"arr_delay","carrier" ))
# 按照指定列顺序排序 其余字段保持不变,不是建立副本,是直接修改了flights 数据的列顺序
```
- 常规计算
根据最开始的Excel透视表截图,我们想要获得如截图一样的结果该怎么实现呢?代码如下:
```{r,eval=FALSE}
flights[year==2014 & month==6,.(求和项distance=sum(distance),平均距离=mean(distance)),by=.(origin,dest)]
```
在i的位置做筛选,j的位置做计算,by指定分组字段。在j的位置可以做各种各样的计算,R中自带的函数,或者是自己定义的函数。
```{r}
myfun 6)] #by里面可以做计算
head(dt)
```
### 行列筛选总结
行筛选在 i 的位置上进行, 列筛选在 j 的位置上进行;data.table中j的位置比较灵活多变,但是i的位置大部分时候都是进行条件筛选。我们通过上述的行列筛选已经大概知道data.table中i,j的用法。也就是我们常规数据清洗过程中的数据筛选过程,筛选符合要求的数据记录。
```{r}
dt =15,.(year,month,day,dep_delay,carrier,origin)]
head(dt)
```
## 常规操作{#datatable:usage}
### 行筛选
上文已经大致讲过行筛选,但是行筛选使用有一定的技巧,涉及到运算的快慢。主要是逻辑条件的设置,交集并集之间的差异。除了上文中的关系运算筛选,逻辑运算筛选除外,data.table中还有几个常用的筛选函数。
- 数字向量筛选
%in%用法与 sql 中 in 用法类似。
```{r eval=FALSE}
# 筛选 %in%
flights[ hour %in% seq(1,24,2) ]
```
- 字符向量筛选
%chin%用法与 %in% 类似,但仅仅针对字符。
```{r eval=FALSE}
# 字符筛选
flights[ origin %chin% c('JFK','LGA')]
# not run 同上 %chin% 对字符速度筛选速度更快
#flights[ origin %in% c('JFK','LGA')]
```
- between 筛选
该函数的新特性矢量化挺实用。
```{r}
#between 函数参数
#between(x, lower, upper, incbounds=TRUE, NAbounds=TRUE, check=FALSE)
X Note: DT[a > 4, b := c] is different from DT[a > 4][, b := c]
### 排序
当我们清洗数据时,我们需要将数据框排序,我们可以使用`setorder`或`setorderv`函数实现排序。函数是`data.table`包的函数,比base R 中的`order`函数要节省内存。
注意:按照函数文档说法:Note that queries like x[order(.)] are optimised internally to use data.table's fast order。即x[order(.)]这样的用法会被优化为data.table的排序方法。
```{r}
set.seed(45L)
DT = data.table(A=sample(3, 10, TRUE),
B=sample(letters[1:3], 10, TRUE), C=sample(10))
setorder(DT, A, -B) #将DT按照A、B排序 A 升序,-B降序
# 和上面同样的效果 但是函数变成 setorderv
setorderv(DT, c("A", "B"), c(1, -1))
```
## 常用函数{#datatable:function}
常用函数指我们常用功能的函数,如排名、排序、非重复计数、判断、表连接、长宽转换等功能。
### 特殊符号
.SD,.BY,.N,.I,.NGRP和.GRP,.SDcols等,只能用在 j 的位置,.N 可以用在 i 的位置。
如果想要记住用法需要自己多尝试练习,对于我来说.N使用较多。
```{r}
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
X = data.table(x=c("c","b"), v=8:7, foo=c(4,2))
X
# 用在i的位置
DT[.N] #取DT最后一行,.N 计数函数
DT[,.N] #DT 共有多少行记录 返回一个整数
DT[, .N, by=x] #分组计数
DT[, .SD, .SDcols=x:y] # 选择x 到y 列
#DT[, .SD, .SDcols=c("x","y")] 与上面不一样
DT[, .SD[1]] #取第一行
DT[, .SD[1], by=x] #按x列分组后
DT[, c(.N, lapply(.SD, sum)), by=x] #按照x分组后 行数计数和每列求和
```
### 排序函数
`frank`和`frankv`函数参数如下:
```{r eval=FALSE}
frank(x, ..., na.last=TRUE, ties.method=c("average",
"first", "last", "random", "max", "min", "dense"))
frankv(x, cols=seq_along(x), order=1L, na.last=TRUE,
ties.method=c("average", "first", "random",
"max", "min", "dense"))
```
官方案例,如下所示:
```{r}
# on vectors
x = c(4, 1, 4, NA, 1, NA, 4)
# NAs are considered identical (unlike base R)
# default is average
frankv(x) # na.last=TRUE
frankv(x, na.last=FALSE)
# on data.table
DT = data.table(x, y=c(1, 1, 1, 0, NA, 0, 2))
frankv(DT, cols="x") # same as frankv(x) from before
frankv(DT, cols="x", na.last="keep")
frankv(DT, cols="x", ties.method="dense", na.last=NA)
frank(DT, x, ties.method="dense", na.last=NA) # equivalent of above using frank
```
* frankv在排序时,NA被认为是一样的,基础base R 中认为不一样.
```{r}
x 2L, x, x - 1L)
fifelse(x > 2L,fifelse(x >= 4L,x + 1L,x),x-1L)
```
- fcase
与sql中的case when,与dplyr中的`case_when()`函数用法相似。相比fifelse相比,嵌套更加方便。
```{r}
x = 1:10
fcase(
x < 5L, 1L,
x > 5L, 3L
)
# not run 两种函数实现方式
fifelse(x > 5,fifelse(x >8,2,1),0)
fcase(
x > 8,2,
x > 5,1,
default = 0
)
```
### 交集 差集 合并
相当于base R 中 union(),intersect(),setdiff() 和setequal() 功能.all参数控制如何处理重复的行,和SQL中不同的是,data.table将保留行顺序.
```{r eval=FALSE}
fintersect(x, y, all = FALSE)
fsetdiff(x, y, all = FALSE)
funion(x, y, all = FALSE)
fsetequal(x, y, all = TRUE)
x % setorderv(cols=c('area','grouping'),na.last = TRUE))
```
通过上述计算,发现计算结果与Excel透视表一样。
- cube
观察`cube()`计算结果与`rollup()`差异,发现`cube()`聚合层次更多。
```{r}
cube(DT,j = sum(value),by = c("area","store_type"),id = TRUE)
```
- groupingsets
根据需要指定指定聚合的层次。
```{r}
# 与本例中rollup 结果一致
groupingsets(DT,j = sum(value),by = c("area","store_type"),sets = list('area',c("area","store_type"), character()),id = TRUE)
# 与本例中cube 结果一致
groupingsets(DT,j = sum(value),by = c("area","store_type"),sets = list('area',c("area","store_type"),"store_type", character()),id = TRUE)
```
> groupingsets: sets参数,用list()包裹想要聚合的字段组合,最后character(),加上该部分相当于不区分层级全部聚合,用法类似sql中"()".
> SELECT brand, size, sum(sales) FROM items_sold GROUP BY GROUPING SETS ((brand), (size), ());
[comment]: <> (This is a comment, it will not be included)
### rleid
该函数根据分组生成长度列。
即将0011001110111101类似这种分组成1 1 2 2 3 3 4 4 4 5 6 6 6 6 7 8。在特定时候是很便捷的一个函数。如在计算股票连续上涨或下跌天数时。
```{r}
rleid(c(0,0,1,1,0,0,1,1,1,0,1,1,1,1,0,1))
```
用法:
```{r eval=FALSE}
rleid(..., prefix=NULL)
rleidv(x, cols=seq_along(x), prefix=NULL)
```
```{r}
DT = data.table(grp=rep(c("A", "B", "C", "A", "B"), c(2,2,3,1,2)), value=1:10)
rleid(DT$grp) # get run-length ids
rleidv(DT, "grp") # same as above
rleid(DT$grp, prefix="grp") # prefix with 'grp'
```
### shift
向前或向后功能,通俗来说就是向前或向后移动位置。
示例如下:
```{r}
x = 1:5
# lag with n=1 and pad with NA (returns vector)
shift(x, n=1, fill=NA, type="lag")
```
其中参数n控制偏移量,n正负数和type的参数相对应。, n=-1 and type='lead' 与 n=1 and type='lag'效果相同。
在data.table上使用:
```{r}
DT = data.table(year=2010:2014, v1=runif(5), v2=1:5, v3=letters[1:5])
cols = c("v1","v2","v3")
anscols = paste("lead", cols, sep="_")
DT[, (anscols) := shift(.SD, 1, 0, "lead"), .SDcols=cols]
```
例如求某人连续消费时间间隔天数时:
```{r}
DT = data.table(dates =lubridate::ymd(c(20210105,20210115,20210124,20210218,20210424)))
DT[,newdate:=shift(dates)]
DT
```
通过构造新列newdate,然后将两列相减`dates-newdate`即可得到每次购物间隔天数。
### J
J 是`.()`,`list()`等的别名。`SJ`是排序连接,`CJ`是交叉连接。
用法:
```{r eval=FALSE}
# DT[J(...)] # J() only for use inside DT[...]
# DT[.(...)] # .() only for use inside DT[...]
# DT[list(...)] # same; .(), list() and J() are identical
SJ(...) # DT[SJ(...)]
CJ(..., sorted=TRUE, unique=FALSE) # DT[CJ(...)]
```
- CJ
我喜欢用`CJ()`函数创建笛卡尔积表。例如在商品运营中,时常需要将门店和商品形成笛卡尔积表,相比起`dplyr::full_join()` ,`data.table::merge.data.table(allow.cartesian = TRUE )`,`CJ`更加方便快捷。
```{r}
# CJ usage examples
CJ(c(5, NA, 1), c(1, 3, 2)) # sorted and keyed data.table
# do.call(CJ, list(c(5, NA, 1), c(1, 3, 2))) # same as above
# CJ(c(5, NA, 1), c(1, 3, 2), sorted=FALSE) # same order as input, unkeyed
```
- SJ
SJ : Sorted Join. The same value as J() but additionally setkey() is called on all columns in the order they were passed to SJ. For efficiency, to invoke a binary merge rather than a repeated binary full search for each row of i.
## 小技巧{#datatable:tips}
### 用{}抑制中间过程输出
默认只返回未命名花括号中定义的最后一个对象。
```{r}
dt