加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 大数据 > 正文

R数据处理|data.table篇(二)

发布时间:2020-12-14 03:10:18 所属栏目:大数据 来源:网络整理
导读:上文 我们讨论到使用data.table包完成数据分析框架,遗留如下几个问题 增添列,另外一种删除列的方法,修改数据框 计算或分组计算时,可不可以一次对所有列进行计算,而不需要再每一列都指定(.SD) DT中可不可以按照行名来提取(key) 普通合并数据框(改进了

上文我们讨论到使用data.table包完成数据分析框架,遗留如下几个问题

  • 增添列,另外一种删除列的方法,修改数据框

  • 计算或分组计算时,可不可以一次对所有列进行计算,而不需要再每一列都指定(.SD)

  • DT中可不可以按照行名来提取(key)

  • 普通合并数据框(改进了的函数,会在下一篇中介绍)

  • 融合重铸

本文介绍data.table包更深入的使用方法,顺带解决上述问题,下面是本文目录

  • key的使用

  • 融合重铸的深入使用和改进

  • 特殊符号如.N .SD := 等

  • 高效读写文件函数及参数解释

Key

数据集创建

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)

DT数据框的行名及key的介绍

之前提到过提取时没有使用行名提取这个方法,这是因为data.table没有行名,如果硬要说有,那就是1234,而且不能修改,也不能根据行名来做提取等操作。

rownames(dft) # "1" "2" "3" "4"
rownames(dft) <- letters[1:4]
rownames(dft) # "a" "b" "c" "d"
dft # 虽然上面rownames改过来了,但是这样输出发现还没有变
# dft["a","weight"] # 报错,无法根据行名提取
dft[1,"weight"] # 使用行数才可以


这不是data.table的漏洞,而是因为它有更强大的操作,根本就不需要使用行名。具体的使用方法是,把data.frame的行名当成一列读进去,通过设置key来指定该列为行名。这样做的好处是,不止可以指定这一列,任意一列都可以,还可以指定多列,我们下面来看一看。

ndt <- copy(dft) # 为了和原数据框对比,创建一个新的
setkey(ndt,name1)
ndt # 发现数据框自动按照name1这一列进行排序了
# 如果想去掉key,则setkey(ndt,NULL)

# 实现通过行名提取
ndt["Bob",weight] 
ndt["Bob","weight"]
ndt["Bob",2]
dft["Bob",2,on="name1"] # key 相当于使用了on
ndt["Bob"]

# 注意
setkey(ndt,weight)
ndt[60] # 认为是提取第60行
ndt[.(60)] # 下面两个才是提取weight为60的行
ndt[J(60)]

当我们把data.frame数据框转化成data.table时,默认抛弃行名,不过我们也可以用一个参数保留行名成为新的一列

df1 <- data.frame(weight,row.names = name1)
dt1 <- as.data.table(df1)
dt2 <- as.data.table(df1,keep.rownames=T) # 将原来数据框中的行名当成一列,列名为rn
dt1;dt2
as.data.table(df1,keep.rownames = "rownames") # 自己指定新增列的列名

设置key时还有另外一个函数setkeyv

name1 <- "weight"
setkey(ndt,name1) # 设置name1这一列为key 
ndt # 观察这里的排序和下面一个的区别
setkeyv(ndt,name1) # 设置name1这个变量指向的weight这一列为key 
ndt
setkey(ndt,name1,weight) # 设置两个key
setkeyv(ndt,c("name1","weight"))

这里注意到数据框会自动按照设置key的列进行排序

检查key的函数

haskey(ndt) # 返回TF值,检查是否有Key
key(ndt) # 检查它的key是什么

使用key来辅助计算

setkey(ndt,accept)
ndt["ok",sum(weight)] # 指定计算accept为ok的weight之和
ndt[c("ok","no"),sum(weight)] # 全部合在一起算
ndt[c("ok",sum(weight),by=.EACHI] # 分类算
ndt[accept,by=.EACHI] # 每一类计算完,保留所有行输出
ndt[unique(accept),by=.EACHI] # 只显示和类数相同的行
ndt[,by=accept] # 上面等价于分组计算

我们可以发现,key的作用相当于设定on参数。计算时使用key,再指定计算哪些行,by=.EACHI,可以实现和分组计算一样的功能。

设置多个key

# 设置多个key,可以对多列进行筛选
setkey(ndt,weight)
ndt[.("Kim",50:60)] # 不匹配的全部显示NA
ndt[.("Kim",50:60),nomatch=0] # 不匹配的删除掉
ndt[.("Kim",roll=T] # 没有信息的用上面的添
ndt[.("Kim",roll=Inf] # 和上一条一样
ndt[.("Kim",roll=-Inf] # 没有信息的用下面的添
ndt[!"Kim"]
ndt[!.("Kim",56)]

融合重铸

data.table包改写了reshape2包中的融合重铸功能。加载了data.table包之后,不需要加载reshape2包,但是它的所有用法都可以照常使用。而data.table不仅运行速度更快,而且增加了一些reshape2包没有的功能,melt和dcast函数都有改进。

melt

先来看一下两个包中的函数的帮助文档,在控制台输入 ?melt 会发现这两个包中都有这个函数,下面是函数参数

# reshape2包中处理data.frame的melt函数
#(在页面中点击melt.data.frame,这涉及到R语言中的泛函,我们以后会专门讲解)
# 现在理解就是当接的data是数据框时,使用 melt.data.frame这个函数
melt(data,id.vars,measure.vars,?variable.name = "variable",...,na.rm = FALSE,value.name = "value",?factorsAsStrings = TRUE)

# data.table包中的melt函数
melt(data,? ?variable.name = "variable",? ?...,variable.factor = TRUE,? ?value.factor = FALSE,? ?verbose = getOption("datatable.verbose"))

读者如果看过我的上一篇讲融合重铸的文章https://zhuanlan.zhihu.com/p/26123110会发现,这里的很多参数我都没有使用到,在这里我会用它们实现更多的功能。下面我们列出三个主要功能,并总结每一个功能中两个包的差异

1.得到的数据是否转化为factor的问题

  • reshape2中的factorsAsStrings参数可以控制value列是否为因子型,而variable列则自动为因子型

  • data.table中通过variable.factor和value.factor分别控制两列是否为因子型

  • 明显可以看出data.table的使用更灵活,更方便

2.多种数据类型融合问题

之前我们遇到的问题都是被融合的数据是同样的数据类型,比如都是整数或都是字符串,现在我们要面对的是很多不同数据类型的数据框。这里我们使用data.table包中例子里创建的数据框

set.seed(45)
DT <- data.table(
 ?i_1 = c(1:5,NA),?i_2 = c(NA,6,7,8,9,10),?f_1 = factor(sample(c(letters[1:3],TRUE)),?f_2 = factor(c("z","a","x","c","x"),ordered=TRUE),?c_1 = sample(c(letters[1:3],TRUE),?d_1 = as.Date(c(1:3,NA,4:5),origin="2013-09-01"),?d_2 = as.Date(6:1,origin="2012-01-01"))
DT[,l_1 := DT[,list(c=list(rep(i_1,sample(5,1)))),by = i_1]$c]
DT[,l_2 := DT[,list(c=list(rep(c_1,by = i_1]$c]

DT
# ? ?i_1 i_2 f_1 f_2 c_1 ? ? ? ?d_1 ? ? ? ?d_2 ? ? ? l_1 ? ? l_2
# 1: ? 1 ?NA ? b ? z ? b 2013-09-02 2012-01-07 ? ? ? 1,1 b,b,b
# 2: ? 2 ? 6 ? b ? a ?NA 2013-09-03 2012-01-06 2,2 ? NA,NA
# 3: ? 3 ? 7 ? c ? x ? a 2013-09-04 2012-01-05 ? ? ? ? 3 ? ? ? a
# 4: ? 4 ? 8 ? a ? c ? c ? ? ? <NA> 2012-01-04 ? ? 4,4,4 ? ? ? c
# 5: ? 5 ? 9 ?NA ? x ? b 2013-09-05 2012-01-03 5,5,5 ? b,b
# 6: ?NA ?10 ?NA ? x ? c 2013-09-06 2012-01-02 ? ? ? ?NA ? ? c,c

我们可以看到,这个数据框中各列的数据类型是不相同的,总结一下是这样

  • i前缀的列是数值型

  • f前缀的列是因子型

  • c前缀的列是字符型

  • d前缀的列是时间型

  • l前缀的列是列表

我们如果直接用一条命令保留前两列

melt(DT,id=c("i_1","i_2"))
# 或者
melt(DT,id=1:2)

其他列融合在一起,就会出现warning,强制转换数据类型。这时就需要我们能够选择相同类型的数据融合在一起,也就是相同的前缀融合在一起。两个包都是使用了measure.vars参数来控制要被融合的列

 
 

(细心的读者可能会注意到参数明明是id.vars,为什么我使用时就用了id和measure,这是参数的模糊匹配功能,只要提取前面的字符,不会发生歧义,就能代表这个参数)

上面四行代码两个包中都可以使用,不过data.table包中提供了选择列更好的方法

 
 

使用patterns指定,将所有以f开头的列融合在一列,d开头的列融合在一列,产生两列value。

特殊情况

  • 如果id和measure两个参数只指定一个,则默认将剩余所有列全部赋予未指定的那个参数上

  • 如果两个参数都不指定,则默认measure指定numeric/integer/logical类型数据,其余为id指定

3.新列命名

融合之后得到一列自动命名为variable和value,如果我想自己指定名字,就使用variable.name和value.name两个参数,在这个功能上,两个包没有区别

 
 

cast

我们还是用?dcast查看帮助文档,发现两个包中都有这个函数,其中reshape2包中还有acast函数,而data.table包中没有

# reshape2中的函数
dcast(data,formula,fun.aggregate = NULL,margins = NULL,?subset = NULL,fill = NULL,drop = TRUE,?value.var = guess_value(data))

# data.table中的函数
dcast(data,sep = "_",subset = NULL,? ?drop = TRUE,value.var = guess(data),51); font-size: medium; white-space: normal;">和melt一样,上一篇文章中我们没有深入了解函数的各种参数,这里我们通过这些参数来比较两个包中的重铸功能

1.两个包相同的参数margins/fill/drop/subset,相同的使用方法

  • margins=T或者字符串向量,可以对得到的矩阵的每行每列在使用该函数计算

  • fill当遇到缺失值时如何填充问题

  • drop是否要去除掉不匹配的行,即全是NA的行

  • subset提取结果的一部分进行展示

DT <- data.table(v1 = rep(1:2,each = 6),? ? ? ? ? ? ? ? v2 = rep(rep(1:3,2),each = 2),? ? ? ? ? ? ? ? v3 = factor(rep(c(1,3),6),levels=1:3),? ? ? ? ? ? ? ? v4 = rnorm(6))
# margins参数
dcast(DT,v1~v2,mean) # 生成2*3的矩阵,默认选择v4作为value计算
dcast(DT,mean,margins=T) # 3*4的矩阵,多出了对每行每列求的均值
dcast(DT,margins="v1") # 只每列求均值
dcast(DT,margins="v2") # 只每行求均值

# drop 参数
# v3因子型,2没有出现过,在融合时还会与其他列进行匹配,就会出现一行全是NA的情况
dcast(DT,v1+v3~v2,drop=F) 
dcast(DT,drop=T) 

# fill参数
dcast(DT,drop=F,fill=0) # 是NA的地方填充0

# subset参数
dcast(DT,mean)
dcast(DT,subset=.(v3==1)) # 挑选v3是1的出来计算
dcast(DT,subset=.(v1==1)) # 计算完挑选v1是1的出来
dcast(DT,subset=.(v2==1)) # 计算完挑选v2是1的出来
dcast(DT,subset=.(v1==1&v3==1)) # 挑选v3是1的出来计算,之后挑选v1是1的来展示

2.value.var参数
之前那篇讲reshape2包的文章中,value正好只有一列,但当有多列可以作为value时,如果不加以指定,计算的结果可能不是我们想要的

运行了上面代码的读者也会发现,每次输出结果时都会打印出如今选用的作为value的列是什么,我们如果想要自己指定哪一列作为value,就要用value.var参数

在这个参数的使用上,data.table包比reshape2包功能更强大

# 两个包都可以这样使用
DT <- data.table(v1 = rep(1:2,? ? ? ? ? ? ? ? v4 = rnorm(6))
dcast(DT,v1~v3,mean) # 默认使用v4来计算
dcast(DT,value.var="v2") # 指定v2作为value来计算
dcast(DT,value.var="v4") # 使用v4和默认的结果相同

下面是data.table包独有的功能

dcast(DT,value.var=c("v4","v2")) # v3中的元素分别和v2和v4组合,生成四列
dcast(DT,fun=list(sum,mean),value.var="v2") # 同时使用两种计算函数生成四列
dcast(DT,"v2")) # 二者结合,生成8列
dcast(DT,value.var=list("v4","v2")) # v4的使用sum,v2的使用mean,生成4列


特殊符号

添加、更新和删除 := 符号

这个符号可以实现在本身直接更改,而无需产生一个新的数据框,再赋值给原本相同的变量名

dft <- data.table(name1,accept)
dft[,u:=1] # 添加一个全是1的列
dft[,height:=1:4] # 更改height列
dft[,c("accept","height"):=.(1:4,2:5)] # 作用于多个列
dft[,`:=`(m=1:4,n=3:6)] # 使用:=函数的真正调用方式
dft[,weight:=NULL] # 删除weight列
dft[,c("m","n"):=NULL] # 删除多列
dft[2,height:=22][] # 只修改一个值,加一个[]返回得到的数据框

dft <- data.table(name1,accept)
dft["Bob",accept:="yes",on="name1"] # 通过逻辑判断修改
dft[,m:=mean(height),by=accept] # 增加一个列,这个列根据分组计算得出
# 注意一点
dft[name1=="Bob"][,height:=13][] # :=作用在提取之后的数据框,所以对原数据框没有改变
dft
# 使用一个指向字符串的变量作为新名称
a <- "aa"
dft[,a:=1][] # 使用a作为列名
dft[,(a):=2][] # 使用aa作为列名

.N

.N 代表行的数量,用by参数分组时则是每一组的行数量

dft[.N-1] # 返回倒数第二行
dft[,.N] # 返回数据框一共有几行(放在第二个参数位置表示计算并输出结果)
dft[,.N,by=accept] # 分组计算行数

.SD

.SD 代表整个数据框,用by参数分组时则是每一组的数据框

之前我们提到过,在DT中计算时输出的总是DT,但是如果我想返回多个矩阵怎么办,那就是使用嵌套list,像把矩阵压缩成一个元素一样,放在DT中。这里我们要用分组计算,返回矩阵。

下面这个例子是要对分组之后的每个数据框求covariance,计算得到的是列与列两两对应协方差矩阵。

dft[,cov(.SD[,-4)]),by=accept] # 矩阵被变成向量
l <- dft[,.(.(cov(.SD[,-4)]))),by=accept]
l[[1,2]]

这里解释一下连续嵌套两层list的理由

  • 第一个list是指对每一组计算出来的结果矩阵,用list压缩成为可以放在DT中的元素

  • 第二个list是将两个压缩了的元素整合在一起,使之成为一列。这一层中还可以加第二个元素,输出结果放在下一列展示,功能类似我们平时使用的计算多个结果

    l <- dft[,-4)])),.(cor(.SD[,by=accept]
    l <- dft[,sum(weight)),.(m=mean(weight),s=sum(weight)),by=accept] # 外层list功能类似这里

.SDcols

.SDcols 指定.SD 代表的数据框包括哪些列

 
 

.I

.I 表示(分组后)每一行在原数据框中是第几行

.GRP

如果不使用by参数,则为1。使用by,则是组的计数(第一组的值是1,第二组是2)

串联操作,避免多余中间变量

dft[weight>50][height>100][order(height)]

%between% 范围

# 以下6个等价
dft[weight>=50&weight<=60]
dft[weight %between%c(50,60)]
dft[weight %inrange%c(50,60)]
dft[weight %between% list(rep(50,4),rep(60,4))]
dft[between(weight,50,60)]
dft[inrange(weight,60)]

%like% 字符串中含有某个字符

dft[name1%like%"a"]


读写文件

data.table包中的fread和fwrite读写文件的速度非常快,可以处理.txt.csv.dat等文件,下面是最基本的使用方法

dataw <- data.table(a=1:10,b=2:11)
fwrite(dataw,"dataw.csv")
fwrite(dataw,"dataw.txt")
fwrite(dataw,"dataw.dat")

fread("dataw.csv") # 读取文件直接用字符串,赋予参数input
fread(file="dataw.csv") # 也可以赋予参数file

下面我们来详细讨论fread函数的各个参数

data = "A,B,C,Dn1,3,a,7n2,NAn2,8n"
cat(data) # 看一下这个数据长什么样
fread(data) # 第一行作为列名,生成一个data.table
str(fread(data))
str(fread(data,stringsAsFactors=T)) # stringsAsFactors默认F,自动将字符型转化为因子型
fread(data,col.names = letters[1:4]) # 改变列名
fread(data,verbose = T) # 显示一些信息
fread(data,data.table=F) # 生成data.frame
fread(data,showProgress = T) # 显示进度条
# data.table和showProgress都可以再options里面设置

fread(data) # header默认为"auto",自动第一行作为列名
fread(data,header=F) # header第一行不作为行名
data1 = "1,1,8n"
fread(data1) # 而当数据是这样时又不会自动将第一行作为列名
fread(data1,header=T) # 将第一行作为列名
fread(data1,header=T,check.names = T) # 检查,避免数字作为列名,避免两列名重复

# sep控制列与列之间用什么分隔。默认为"auto",会自动寻找 空格,; : | t 这些符号自动分列
# 当既有:又有空格时,当:很规整,若空格非常整齐,一个不落,:会被当成字符处理,否则就会把:当做分隔符
dataa1 = "a: b: c: dn1: 2: 3: 4n3: 4: 5:6" # 只有最后一个少空格
dataa2 = "a: b: c: dn1: 2: 3: 4n3: 4: 5: 6"
dataa3 = "a : b: c: dn1: 2: 3: 4n3: 4: 5: 6" # 前面加一个空格
fread(dataa1) # 按照:分,得到规整的数据
fread(dataa2) # 带有:
fread(dataa3) # 规整的数据
# 当改变sep的取值时
fread(data,sep="n") # 此时读成一列

# nrow控制读取第几行,默认-1全部读取,0取列名,1只取第一行
fread(data,nrow=0)
fread(data,nrow=1) # 取第一行,一般用这个来检测读进来的形式是否正确
fread(data,nrow=3) # 取前三行

# na.strings默认将,这样没有值的、"NA" "N/A" "null" 当成缺失值
fread(data,na.strings="a") # 将读进去的字符"a"当成缺失值
str(fread(data,na.strings=NULL)) # 不将那些转化为缺失值

# autostart自动选择合适的行开始
data1 = "i,can,do,itn A,8n"
data2 = "i,itn friends are kind ton A,8n"
fread(data1) # i,it 作为列名,(同时注意到,没有变成NA,因为这列是字符串)
fread(data2) # 自动从A,D开始读取,将其作为列名

# skip跳过前几列不读,默认是0,自动从autostart点开始读取
data2 = "i,itn friends are kind n A,8n"
fread(data2) # 从ABCD开始读
fread(data2,skip=2) # skip=0,2结果相同,ABCD作为列名
fread(data2,skip=3) # 自动不将第一列作为列名
data3 = "i,Dnbob,mary,and,janen1,8n"
fread(data3) # 从ABCD开始读
fread(data3,skip="bob") # 从bob这一行开始读,bob等作为列名

# select and drop
data = "A,8n"
fread(data,select="A") # 只取A这列
fread(data,select=c("A","B")) # 取AB列
fread(data,drop="A") # 取除了A这列

# colclasses
data = "A,8n"
fread(data)
str(fread(data,colClasses = c(A="character")))
str(fread(data,colClasses = c(A="character",B="double")))
str(fread(data,colClasses = list(character=c("A","B"))))
str(fread(data,colClasses = list(character=1:3)))
fread(data,colClasses = list(NULL=1:3)) # 只保留第四列


专栏信息

专栏主页:Data Analysis(https://zhuanlan.zhihu.com/Data-AnalysisR
专栏目录:目录(https://zhuanlan.zhihu.com/p/25780082

文末彩蛋

这里介绍R语言几个行名列名函数的联系和区别

  • colnames和names

  • rownames和row.names

这两对区别如下

  • names和colnames的区别在于,作用于数据框时二者相同,作用于矩阵时names无效

  • rownames和row.names基本上没差别

    a <- data.frame(a=1:4,b=letters[1:4])
    ma <- as.matrix(a)
    names(a) # "a" "b"
    colnames(a) # "a" "b"
    names(ma) # NULL
    colnames(ma) # "a" "b"
    rownames(a) # ?"1" "2" "3" "4"
    row.names(a) # "1" "2" "3" "4"
    rownames(ma) # NULL
    row.names(ma) # NULL


Dwzb?,?R语言中文社区专栏作者,厦门大学统计专业学生。
知乎专栏:Data?Analysis
https://zhuanlan.zhihu.com/Data-AnalysisR?



微信回复关键字即可学习

回复 R??????????? ??R语言快速入门免费视频?回复 统计??????? ??统计方法及其在R中的实现回复 用户画像 ??民生银行客户画像搭建与应用?回复 大数据??? ??大数据系列免费视频教程回复 可视化? ? ??利用R语言做数据可视化回复 数据挖掘 ? 数据挖掘算法原理解释与应用回复 机器学习 ??R&Python机器学习入门?

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读