
大家好,好久没有更新过了,先在这里和大家说一声抱歉,再说一声新春快乐,新年伊始,我打算更频繁的做一些更新,写一些干货,希望对数据分析和机器学习爱好者从业者有些许的帮助。
今天的话题是data.table;很多朋友应该听过data.table,或者用过。我自己也和周围的朋友以及一些网友交流过,也去参与过一些meetup,但是感觉喜欢用并且在工作学习中经常用data.table的并不是太多,至少没办法和dplyr[1]相比。
和python不同,R语言自带data.frame类型,python没有内置的数据框类型,一般使用pandas模块。基本上所有的数据操作都可以用data.frame和R里面的其他内置数据类型和函数实现。如果在十年前,或者二十年前,新的数据操作软件包看起来不是那么的必要,可是随着大数据以及大计算的概念的火热,大家学习工作中会处理越来越大的数据,对计算时间也越来越敏感。所以R语言自带的data.frame已经难以满足广大R语言用户的需求了。一些第三方的数据处理软件包吸引了越来越多的注意,比如dplyr,data.table,SparkR[3], feather[4], fst[5]等等。这些工具包可能侧重数据处理中的不同阶段或者任务。比如fst侧重于数据框类型的读写效率,SparkR在R中提供了Spark的接口。
大家常常会把dplyr和data.table做比较。基于我有限的dplyr的使用经验,dplyr就像是一套招数繁杂的武功,你需要记住select(), mutate(), join(), distinct(), nrow(), sample_n() 等等一系列的函数,而且可能会有越来越多的特定功能的函数会被加入。也许在你每次学习到一个函数的时候,会获得一些成就感,但是我始终觉得这不是一种非常好的设计。相比之下,data.table的语法就简洁态度了,当然也有人觉得data.table的语法很奇怪。刚开始用data.table大概都会觉得它的语法奇怪,尤其是 `:=` 操作。对我来说data.table的另一个优势是性能/速度,这一点尤其重要。dplyr好像就是一套招式繁杂的武功,比如五岳剑派的剑法,data.table呢,好像是辟邪剑谱吧?(这个比喻貌似不是很恰当,不知道大家有什么看法)。
下面我们就来讲一讲data.table的使用,分为入门和高级两部分。这个文章里面我们先来讲讲入门,高级篇明天发。
首先要安装data.table,然后就可以使用了。
创建data.table对象
使用data.table我们需要创造一个data.table类型的对象。有不同的办法去创造一个data.table的对象;这里我们来看三种
1) 把data.frame对象转变成data.table对象,比如
iris_dt=data.table(iris)
2) 支持创造一个data.table对象,比如
data.table(x=c(1:10),y=c(1:10)^2)
3)使用data.table里面的fread函数读csv文件,可以直接返回data.table对象,当然也可以返回data.frame对象。
选择子集
我们可以从一个data.table里面选择一些行或者一些列来创造一个新的data.table对象,这种操作也是数据处理中经常遇到的,比如在建模的时候,需要把数据分成训练集和测试集(行),或者在变量选取的时候我们要选一些特定的特征(列)。
iris_dt=data.table(iris)
iris_dt_subset=iris_dt[1:50, .(Sepal.Length,Petal.Length,Species)]
head(iris_dt_subset)
Sepal.Length Petal.Length Species
1: 5.1 1.4 setosa
2: 4.9 1.4 setosa
3: 4.7 1.3 setosa
4: 4.6 1.5 setosa
5: 5.0 1.4 setosa
6: 5.4 1.7 setosa
注意我们选取列的时候的语法,
括号前面有一个点,这个点其实是list的别称,所以我们可以得到一样的结果使用iris_dt[1:50, list(Sepal.Length,Petal.Length,Species)]
列的名字没有引号,如果加了引号,那么结果变成如下
> iris_dt[1:5,.('Sepal.Length',Petal.Length,Species)]
V1 Petal.Length Species
1: Sepal.Length 1.4 setosa
2: Sepal.Length 1.4 setosa
3: Sepal.Length 1.3 setosa
4: Sepal.Length 1.5 setosa
5: Sepal.Length 1.4 setosa
添加新的列
比如我们想在iris_dt数据框上加上一列 max.Length=max(Sepal.Length, Petal.Length),我们需要使用:=操作符。
> iris_dt[,max.Length:=pmax(Sepal.Length,Petal.Length)]
> head(iris_dt)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species max.Length
1: 4.3 3.0 1.1 0.1 setosa 4.3
2: 4.4 2.9 1.4 0.2 setosa 4.4
3: 4.4 3.0 1.3 0.2 setosa 4.4
4: 4.4 3.2 1.3 0.2 setosa 4.4
5: 4.5 2.3 1.3 0.3 setosa 4.5
6: 4.6 3.1 1.5 0.2 setosa 4.6
排序
data.table的排序功能非常方便而且强大。比如我们想把iris_dt按Sepal.Length排序:
> setkey(iris_dt,Sepal.Length)
> head(iris_dt)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1: 4.3 3.0 1.1 0.1 setosa
2: 4.4 2.9 1.4 0.2 setosa
3: 4.4 3.0 1.3 0.2 setosa
4: 4.4 3.2 1.3 0.2 setosa
5: 4.5 2.3 1.3 0.3 setosa
6: 4.6 3.1 1.5 0.2 setosa
data.table的setkey()函数可以对一个data.table按照某一列进行排序,排序之后,这个data.table对象会被标记为排过序了,这个排序不会在内存中复制被排序的data.table对象,所以非常高效。
我们也可以对data.table按照几列一起来排序,这时需要使用setkeyv()函数:
> setkeyv(iris_dt,c('Sepal.Length','Sepal.Width'))
> head(iris_dt)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species max.Length
1: 4.3 3.0 1.1 0.1 setosa 4.3
2: 4.4 2.9 1.4 0.2 setosa 4.4
3: 4.4 3.0 1.3 0.2 setosa 4.4
4: 4.4 3.2 1.3 0.2 setosa 4.4
5: 4.5 2.3 1.3 0.3 setosa 4.5
6: 4.6 3.1 1.5 0.2 setosa 4.6
需要注意的是我们对列名加了引号。
分组操作
如何得到每一和类别的Sepal.Length的平均值呢?
iris_dt[,.(mean_sepal_length=mean(Sepal.Length)),by=Species]
Species mean_sepal_length
1: setosa 5.006
2: versicolor 5.936
3: virginica 6.588
上面这行代码可能看起来有点奇怪,首先我们看到在[ ] 里面有两个逗号。第一个逗号之前是用来选取行的,我们这里选取所有的行, 所以第一个逗号之前是空的,第二个逗号后面的by= x 会把数据按照x的不同值进行分组,然后我们对每一组数据进行两个逗号之间的操作,也就是计算Sepal.Length的平均值并且把这一列命名为mean_sepal_length。也许你会疑问为什么这里用=而不是:=,这是因为我们并没有在原始的iris_dt中加入新的一列,当我们用by关键字的时候,一个新的data.table对象被创造出来。另一个可能有的疑问是为什么要用 .() 来创造mean_sepal_length;如果把 .() 删除,那么这一行代码会报错。这可以理解为data.table的语法要求,使用by关键字,我们在创造新的有名字的列的时候需要用list()或者.();如果我们不需要对新的列命名,那我们可以把上面的代码简化:
> iris_dt[,mean(Sepal.Length),by=Species]
Species V1
1: setosa 5.006
2: versicolor 5.936
3: virginica 6.588
上面的例子只是按照Species 来分组,我们也可以用多列一起来分组,比如我们可以这样写:
> iris_dt[,mean(Sepal.Length),by=.(Species, Petal.Width)]
也可以这样写:
> iris_dt[,mean(Sepal.Length),by=c('Species', 'Petal.Width')]
我们一样可以同时创造几个不同的列,比如:
>iris_dt[,.(mean_sepal_length=mean(Sepal.Length),mean_petal_width=mean(Petal.Width)),by=Species]
Species mean_sepal_length mean_petal_width
1: setosa 5.006 0.246
2: versicolor 5.936 1.326
3: virginica 6.588 2.026
看到这里有没有觉得data.table非常简洁方便?
join
data.table提供两种不同的语法进行数据框的join操作:
类型 | 语法1 | 语法2 |
inner | X[Y,nomatch=0]或者 Y[X,nomatch=0] | merge(X,Y,all=FALSE) |
left | Y[X] | merge(X,Y,all.x=TRUE) |
right | X[Y] | merge(X,Y,all.y=TRUE) |
full | - | merge(X,Y,all=TRUE) |
在使用语法1的时候,我们需要先用setkey(v) 去设置data.table的key;用语法2,我们可以在merge函数里面指定用来join的列名。
>iris_summary=iris_dt[,.(mean_sepal_length=mean(Sepal.Length),mean_petal_width=mean(Petal.Width)),by=Species]
> iris_summary
Species mean_sepal_length mean_petal_width
1: setosa 5.006 0.246
2: versicolor 5.936 1.326
3: virginica 6.588 2.026
> setkey(iris_dt,Species)
> setkey(iris_summary,Species)
> head(iris_summary[iris_dt])
Species mean_sepal_length mean_petal_width Sepal.Length Sepal.Width
1: setosa 5.006 0.246 4.3 3.0
2: setosa 5.006 0.246 4.4 2.9
3: setosa 5.006 0.246 4.4 3.0
4: setosa 5.006 0.246 4.4 3.2
5: setosa 5.006 0.246 4.5 2.3
6: setosa 5.006 0.246 4.6 3.1
Petal.Length Petal.Width max.Length
1: 1.1 0.1 4.3
2: 1.4 0.2 4.4
3: 1.3 0.2 4.4
4: 1.3 0.2 4.4
5: 1.3 0.3 4.5
6: 1.5 0.2 4.6
References:
[1] https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html
[2] https://cran.r-project.org/web/packages/dplyr/dplyr.pdf
[3] https://spark.apache.org/docs/latest/sparkr.html
[4] https://github.com/wesm/feather
[5] http://www.fstpackage.org
欢迎关注微信公众号 —— 机器会学习




