大家好,我是宋骁
首先感谢转发我上一篇文章的同学,如今此号关注量勇超100,这是我仅凭一己之力难以达到的。所以你们的每一个转发、点赞我都十分感激。如果你们在数据方面有困难,可以留言联系我。谢谢你们!
众所周知,R语言有三种主流操作语言rbase
,极乐净土,data.table
。
初学者肯定头大无比,为何同样的数据操作任务有三个重复的轮子呢?呃这大概就是开源的副作用吧。
rbase
使用大量讨厌的$
符号和难受的语法。这大大影响了代码可读性。
dplyr
领衔的极乐净土宗。由于语法简单,加上喜获COPSS奖的Hadley教主光环加成,获得大量簇拥。但是近来因为函数语法的随意性为人所诟病。所以画画图,做个小demo之类的用它就OK了,大工程可能还是不太行。
data.table
则一直不声不响,虽然实力超群,但名气始终无法和实力相匹配。还不小心背了个难学的锅。
最近在实习的工作中,我大量使用了R语言data.table
包。之前其实并不熟悉,自己真正上手了才发现太哇塞了。而且学起来非常简单。我真的搞不懂说它难学的是什么心态。data.table
的语法和pandas
很像,都是基于[ ]
方括号操作符的。dt则依赖于简洁的海象操作符:=
对列进行原位修改(所以logo上是一只海象)。
data.table的海象logo
学data.table
的三个理由是
- 肉眼可见的极速(C语言编写的)
- 语法简洁。和
pandas
之间容易迁移。此外Python也有datatable
包。 - 轻。不依赖任何其他R包
我们来盘点一下data.table
中的特殊操作符。
.SD
, .SDcols
.SD
是指数据中的子集,具体功能是对列进行筛选,可以配合by
一起使用。.SDcols
可以选择列的子集。
library(data.table)
# 生成一个数据
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 v y a b
#> 1: b 1 1 1 9
#> 2: b 1 3 2 8
#> 3: b 1 6 3 7
#> 4: a 2 1 4 6
#> 5: a 2 3 5 5
#> 6: a 1 6 6 4
#> 7: c 1 1 7 3
#> 8: c 2 3 8 2
#> 9: c 2 6 9 1
DT[ , lapply(.SD, mean), by = x] # 对除了 x 之外的列求均值
#> x v y a b
#> 1: b 1.000000 3.333333 2 8
#> 2: a 1.666667 3.333333 5 5
#> 3: c 1.666667 3.333333 8 2
DT[ , lapply(.SD, mean), by = x,.SDcols=c('v','y')] # 选择列
#> x v y
#> 1: b 1.000000 3.333333
#> 2: a 1.666667 3.333333
#> 3: c 1.666667 3.333333
library(data.table)
# 生成一个数据
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 v y a b
#> 1: b 1 1 1 9
#> 2: b 1 3 2 8
#> 3: b 1 6 3 7
#> 4: a 2 1 4 6
#> 5: a 2 3 5 5
#> 6: a 1 6 6 4
#> 7: c 1 1 7 3
#> 8: c 2 3 8 2
#> 9: c 2 6 9 1
DT[ , lapply(.SD, mean), by = x] # 对除了 x 之外的列求均值
#> x v y a b
#> 1: b 1.000000 3.333333 2 8
#> 2: a 1.666667 3.333333 5 5
#> 3: c 1.666667 3.333333 8 2
DT[ , lapply(.SD, mean), by = x,.SDcols=c('v','y')] # 选择列
#> x v y
#> 1: b 1.000000 3.333333
#> 2: a 1.666667 3.333333
#> 3: c 1.666667 3.333333
.N
, .I
.N
和.I
应当合起来讲。.N
类似nrow()
函数,即返回每组的长度,也就是最大行号。而.I
类似seq_len(nrow(x))
,就是返回行号。
DT[, .I[1], by=x]
#> x V1
#> 1: b 1
#> 2: a 4
#> 3: c 7
DT[, .I, by=x] # 输出每组的 index 行号
#> x I
#> 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[, .N, by=x] # 输出每组的行数
#> x N
#> 1: b 3
#> 2: a 3
#> 3: c 3
DT[, .I[-2], by=x] # 方括号内可以选择每组内的行数
#> x V1
#> 1: b 1
#> 2: b 3
#> 3: a 4
#> 4: a 6
#> 5: c 7
#> 6: c 9
DT[, .I[1], by=x]
#> x V1
#> 1: b 1
#> 2: a 4
#> 3: c 7
DT[, .I, by=x] # 输出每组的 index 行号
#> x I
#> 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[, .N, by=x] # 输出每组的行数
#> x N
#> 1: b 3
#> 2: a 3
#> 3: c 3
DT[, .I[-2], by=x] # 方括号内可以选择每组内的行数
#> x V1
#> 1: b 1
#> 2: b 3
#> 3: a 4
#> 4: a 6
#> 5: c 7
#> 6: c 9
.GRP
.GRP
生成分组序号,在根据多变量分组的时候很有用。
DT[, grp := .GRP, by=.(x,v)] # grp 变量将每一组标上序号
DT
#> x v y a b grp
#> 1: b 1 1 1 9 1
#> 2: b 1 3 2 8 1
#> 3: b 1 6 3 7 1
#> 4: a 2 1 4 6 2
#> 5: a 2 3 5 5 2
#> 6: a 1 6 6 4 3
#> 7: c 1 1 7 3 4
#> 8: c 2 3 8 2 5
#> 9: c 2 6 9 1 5
DT[, grp := .GRP, by=.(x,v)] # grp 变量将每一组标上序号
DT
#> x v y a b grp
#> 1: b 1 1 1 9 1
#> 2: b 1 3 2 8 1
#> 3: b 1 6 3 7 1
#> 4: a 2 1 4 6 2
#> 5: a 2 3 5 5 2
#> 6: a 1 6 6 4 3
#> 7: c 1 1 7 3 4
#> 8: c 2 3 8 2 5
#> 9: c 2 6 9 1 5
.BY
.BY
则代表by
分组的每一组名称。主要可以用于分面绘图(参考ggplot2::facet_wrap
)时的标题名称。比如说我们使用鸢尾花数据,将种类作为分组变量画分面散点图:
iris = as.data.table(iris)
par(mfrow=c(2,2))
iris[, plot(Sepal.Length ~ Sepal.Width,
main = do.call(paste, c('Species:', .BY))), by = Species]
iris = as.data.table(iris)
par(mfrow=c(2,2))
iris[, plot(Sepal.Length ~ Sepal.Width,
main = do.call(paste, c('Species:', .BY))), by = Species]
.EACHI
.EACHI
用于i
为列表或数据框时(即使用另一个数据框选择行),DT[i, j, by=.EACHI]
能够根据i的每一行分组(grouping by each i.)。如下所示:
X = data.table(x = c(1,1,1,2,2,5,6), y = 1:7, key = "x")
Y = data.table(x = c(2,6), z = letters[2:1], key = "x")
X[Y,]
#> x y z
#> 1: 2 4 b
#> 2: 2 5 b
#> 3: 6 7 a
X[Y, .N, by=.EACHI]
#> x N
#> 1: 2 2
#> 2: 6 1
X = data.table(x = c(1,1,1,2,2,5,6), y = 1:7, key = "x")
Y = data.table(x = c(2,6), z = letters[2:1], key = "x")
X[Y,]
#> x y z
#> 1: 2 4 b
#> 2: 2 5 b
#> 3: 6 7 a
X[Y, .N, by=.EACHI]
#> x N
#> 1: 2 2
#> 2: 6 1
总结
今天讲的只是data.table一小部分,初学小白最好的教材是data.table自带的帮助文档和小品文。其他的都可以忽略了。戳文末“原文链接”查看小品文cran在线版。
数据操纵(data manipulation)几乎是每个数据分析者的必需本领。这要求数据分析者掌握一套对结构化数据能够作任何灵活操作的工具。data.table
几乎完美满足了这个要求。它功能的多样性和运行速度、稳定性都令人震惊。而data.table
的特殊符号对结构化数据的一些特征进行了高度抽象,可以直接把它们当做函数调用。如果玩熟练可以大大增加数据分析效率。人生苦短,我学data.table
。