R数据处理|基础篇(一)
先用几个问题检验一下你是否需要看这篇文章
高效处理数据R包 dplyr
提取数据到底有哪些方法
使用 $ 提取数据模糊匹配
数据框提取数据时,有时候得到的是数据框,有时候是向量,这种数据自动降维是怎么回事
管道操作函数
在使用R做数据分析的一个完整的过程包括数据的获取,数据的前期处理,之后才是使用“整齐”的数据来套用模型得出结论。本专题旨在系统地讲述使用R语言完成前期的数据处理,英文叫tidy data,将“脏”数据洗干净。比如处理掉一些会影响结果准确度的数据,提取出我们想要的内容,或将整个数据集或多个数据集融合、改变成我们想要的形式。这个过程往往是数据处理上最繁荣琐碎的过程,可能在这里耗费的时间要过多真正 fit model 所需要的时间。
除了通用的处理之外,本专题还会专门针对缺失值处理、时间处理、读写数据等方面,单独进行介绍。
在数据处理上,R语言本身自带这一些操作方法,但是有三个主要的缺陷
用法不够简洁易记
在处理大量数据时,速度太慢
完成有些需求过于麻烦,或者无法达成。
但是这是R的开源好处就体现出来了,用户开发了几个高效处理数据的R包,它们不仅提高了运行速度,而且代码简明易记,如果熟练掌握,可以成为处理数据的一大利器。
主要的大数据处理包可以分为两个派别
一个是大神Hadley的一系列数据处理包,主要有reshape2 dplyr tidyr 包(虽然Hadley最出名的不是这些,而是ggplot2绘图包,但是这些包在数据分析上也都是首屈一指的)。
另一个是data.table包。
本专题讲述时,将前者和R自带基础函数一起讲述,data.table包会单独开出一个部分来讲。
本文使用R语言进行数据处理,不仅包括R自带的函数,而且包括其他高效处理数据的R包。这个部分共两篇文章,使用基础函数和Hadley写的一系列数据处理包,来完成整个数据处理的基本需求。
本文目录如下
数据框建立
提取
根据坐标和行列名提取单个数值
提取整行或整列
根据逻辑值判断提取
提取的衍生物(为什么这么说会在后面介绍)
修改
排序
删除
插入
增添
载入R包
本文只需要使用dplyr包
library(dplyr) # 高速处理数据,取代R自带的一些函数,代码简单易记
数据框建立
# 数据框建立
name1 <- c("Bob","Mary","Jane","Kim")
name2 <- c("Bob","Mary","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","ok","no")
df1 <- data.frame(name1,weight,height)
rownames(df1) <- letters[1:4] # 赋予行名
df2 <- data.frame(name2,birth,accept)
上面是通过建立向量再组合来创建数据框,日常使用中还有两种常见获得数据框的方法
用as.data.frame(a) 这个函数将矩阵转换成数据框
通过读取文件来获得数据框
提取
提取看起来简单,但是方法却多而杂乱,在这里我将做一个总结
从大方向上来看是分为三个角度
根据坐标位置来提取
根据行名列名来提取
使用逻辑值判断提取
一二两种放在一起说,都是使用[ ]实现提取,[ ]中的内容分为以下5种
[ ]中放两个维度的坐标、或者行名和列名,提取单个点或多个点
[ ]中放一个两列的矩阵,可以把每行看成一个坐标x y,用于提取多个点,点的个数和矩阵的行数相等
两个维度中只指定一个维度(两个参数中有一个是空着的,即逗号一边什么都不加),提取行或列
只有一位维度,即[ ]中只有一个数或者列名,提取列(提取列比行多这种方法)
使用$指定列名提取列(这也是提取列多出来的方法)
下面有这几种提取方法的实例,看完实例,我会拓展一些更深层次的数据结构。
# 通过[]选择坐标,确定点
df1[2,3]
# 使用向量确定多个点
df1[c(2,4),c(1,3)]
# 通过[]选择行列名
df1["a","weight"]
df1["a",2] # 混搭
# 通过矩阵提取多个点,一列指定行,一列指定列
df1[cbind(c(1,2,1),3:1)]
df1[cbind(c("a","b"), c("weight","height"))]
# 通过[]少选择一个维度,整行整列地选择(这是比选择行多的一种方法)
df1[2,] # 取行
df1[,3] # 取列
df1[c(2,3),] # 取多行
df1[,c(2,3)] # 取多列
df1["a",]
df1[,"weight"]
df1[3] # 只接一个数字,选择列
df1[c(2,3)] # 使用向量则选择多列
df1$weight # 通过$选择列名名字来选择列
# 列名放在[]中选取列
df1["weight"]
df1[c("weight","height")] # 使用向量选择多列
select(df2,accept) # dplyr包中函数,等价于 df2$accept
在选择行和列上有必要进一步说明,关于取出来的仍是数据框还是向量的问题
这是提取数据是否同时进行降维的问题。即使是只有一列的数据框,它仍是二维的,只有变成向量才算是一维的。上面涉及到的多种提取行列的方法,默认得到的结果是数据框还是向量是不一样的,但是二者之间是有办法相互转化的。
# 取行和取列默认不一样
class(df1[,3]) # "numeric"
class(df1[2,]) # "data.frame"
# 另外三种
class(df1[3]) # "data.frame"
class(df1["weight"]) # "data.frame"
class(df1$weight) # "numeric"
上面输出的两种结果中
numeric代表这样提取得到的是一个向量
data.frame代表这样提取得到的仍是一个数据框
对于取两个坐标[ ]的方法来说,drop参数可以调整输出结果
# drop=T是降维的意思
class(df1[,3,drop=F]) # "data.frame"
class(df1[2,,drop=T]) # "list"
# 取行用drop也不能转化为向量,那就用unlist
class(unlist(df1[2,])) # "numeric"
对于只接受一个维度参数的方法来说[[ ]]相当于$,可以实现降维
class(df1[["weight"]]) # "numeric"
class(df1[[2]]) # "numeric"
# 注意:[[]]不能作用到选取多列的情况上,因为多列谈不上降维
df1[[c("weight","height")]] # 报错
df1[[1:3]] # 报错
其他方面
1.关于[[ ]]和$等关于降维方面的使用,读者可以自行在list和matrix上试。使用下面一条命令查看帮助文档
help(`[`) # R语言一切都是函数
2.模糊匹配
df1$wei # 前面匹配就可以选出那一列
# 但是必须是唯一匹配
df1$weig <- 1:4 # 新增加一列weig
df1$wei # 匹配出两个都满足(weigth和weig),则返回NULL
是否支持模糊匹配也可以用参数exact调整
df1[["hei"]] # 默认不支持模糊匹配
df1[["hei",exact=F]] # 支持
df1["hei"]; df1["hei",exact=F] # 单个[]加了也不支持
模糊匹配在函数参数使用上也有体现
seq(1,10,length.out=10)
seq(1,10,len=10)
3.如果指定的数字不是整数,会自动向下取整
(i <- 3.999999999) # 返回4,因为当前保留的位数不够
# 如果想要改变保留位数,可以用options(digits = 10)修改
# 不过内存中存储的还是3.999999999
(1:5)[i] # 3
通过逻辑值判断选取
# 通过判断语句
df2[df2$accept=="no"|name2=="Bob",]
subset(df2,accept=="no") # R自带提取函数
filter(df2,accept=="no"|name2=="Bob") # dplyr包提取函数
上面这种通过判断来选取的方式,有一个背后的原理,我们拿[]来说,这是通过逻辑值向量进行的选取
df2$accept=="no"|name2=="Bob"
# TRUE FALSE FALSE TRUE
# 先生成等长的逻辑向量,放在行的位置
# 列不写代表所有列都要
# 而只有对应TRUE的行才会被提取出来
df2[df2$accept=="no"|name2=="Bob",]
# 相当于
df2[c(T,F,F,T),]
知道了这个原理之后,我们就可以更加灵活地提取数据,因为[ ]中放置的不一定必须是本数据框中的变量,只要是等长逻辑值向量就可以了(注意最后的逗号不要忘记写就好)
df2[df1$weight>50,]
提取衍生物
我们使用过程中可能涉及到的,修改数据框,按照行或列进行排序,删除某行某列,插入某行某列。这些如果都单独来学,会感觉命令非常多、非常乱,但如果看成是提取的衍生物,则不需要花多少精力就能掌握。因为这些其实都是使用了提取的方法,只是各自加入了自己的原理,就包装成了另外一种东西。我们一项一项来看:
修改
这里修改数据框其实只是把需要修改的地方用提取的方法提取出来,再赋个值,就实现了对原数据框的更改,基本上没有什么新东西。
df1[2,3] <- 160;df1
df2$accept[df2$accept=="ok"] <- "yes"
df2 # 数据框发生了改变
我们会发现,这里每次使用数据框中的一列时,都要使用$,非常麻烦,可以使用within函数避免这样的麻烦
df2 <- data.frame(name2,birth,accept,stringsAsFactors=F)
within(df2,{accept[accept=="ok"]<-"yes"
name2[name2=="Bob"]<-"BOB"})
当其他时候使用数据框中的列进行计算时,如果不想用$,可以使用attach函数
df2 <- data.frame(name2,birth,accept,stringsAsFactors=F)
attach(df2)
2*weight
detach(df2)
用这种方法一定要注意最后detach,否则会搞乱命名空间,由于容易忘记,一般不推荐这种方法,我们还可以使用with函数
with(df2,{a <- weight*2
a^3})
删除
不想要哪行,就在哪行用负数,其实相当于提取了其他行,组成了一个新的数据框。根据这个原理,使用逻辑值向量也可以实现删除行列。
df1[-c(2,3),]
df1[,c(T,F,T)]
排序
R语言使用基础函数处理向量、数据框等,都不能直接对其本身修改,无论是改变一个值,还是排序,删除某一列,得到的都不再是原来的数据框了。所以排序其实就是一个按顺序提取的过程。
R中自带的方法是使用order函数,生成大小顺序的索引
df1_order_row <- df1[order(df1$weight),]
# 我们可以拆分来看
order(df1$weight) # 3 4 1 2
# 上面的结果意味着第三个是最小的,最先被提取出来,放在第一行,接下来是第4个第二小……
# 先按体重,再按身高排序(当体重一样时)
df1_order_row <- df1[order(c(df1$weight,df1$height)),]
# 按照列名对列排序也是一样
df1_order_col <- df1[,order(colnames(df1))]
# 使用dplyr包对行更方便、高效地处理
arrange(df1,weight,height)
插入
由于上面说 49 30855 49 15290 0 0 1170 0 0:00:26 0:00:13 0:00:13 3600的特性,R语言没有办法在原有向量或数据框中插入内容,唯一的方法就是按照要插入的位置,将数据框分开,即提取出dfa和dfb,再用二者和要加入的内容拼接。这种方法非常繁琐,尤其是当要在很多地方插入的情况下,本人目前没有找到更好的方法,如果读者有好的方法可以在评论区留言。
下一节,我们会介绍以下内容
数据框合并
拼接合并
merge合并
计算并增加行列
汇总计算
分组计算
融合重铸
融合重铸的应用
拆分合并列
参考资料
1.想要快速入门dplyr包,可以看网上的教程,都大同小异,很多都是转载的同一篇,这里随便贴一个 http://blog.163.com/zzz216@yeah/blog/static/16255468420147179438149/
2.一般看完上面的这种教程,就能处理大部分问题了,如果想更系统地了解这个包(dplyr),可以看一看官方帮助文档。说实话,有很多特别好的功能都不为人所知。
专栏信息
专栏主页:Data Analysis https://zhuanlan.zhihu.com/Data-AnalysisR专栏目录:目录 https://zhuanlan.zhihu.com/p/25780082
文末彩蛋
这次讲管道操作,一种数据处理中非常便捷的使用方法,主要基于 magrittr 包,里面有如下几个函数
%>% %T>% %$% %<>%
最常用的是第一个 %>% ,如果你载入Hadley的包,就自动可以使用这个函数,不用另外加载magrittr包,如果想了解其他函数再载入这个包,查看文档来学习,或者看这里链接 http://blog.fens.me/r-magrittr/
管道操作除了这个包,还有 pipeR 包,这是另外一个系统,有兴趣的可以去学一下,这里简要介绍一下 %>% 函数
library(dplyr)
name2 <- c("Bob","Mary","Kim","Jane")
weight <- c(60,65,45,55)
height <- c(170,165,140,135)
accept <- c("no","ok","ok","no")
df <- data.frame(name2,weight,height,accept)
# 下面两个操作等价
select(df,weight:accept)
df %>% select(weight:accept)
所以 %>% 的作用在于把前面的内容放到后面函数中,作为第一个参数。使用这个符号的好处有
使代码更加易读
减少中间变量
我们可以对比一下操作
df %>% select(starts_with("w")) %>% `*`(2) %>%
unlist() %>% matrix(nrow=2) %>% colMeans() %>% plot()
plot(colMeans(matrix(unlist(2*select(df,starts_with("w"))),
nrow=2)))
w <- select(df,starts_with("w"))
v <- unlist(w*2)
m <- matrix(v,nrow=2)
plot(colMeans(m))
以上三块内容实现的是相同的目的,我们通过管道函数从前往后读一下
拿到数据集,先提取首字母是w的列,乘2,转化为向量,使用这个向量创建一个2行的矩阵,对矩阵每一列求均值,最后画个图
我们在再去看一下第二种,括号一层套一层,是不是可读性差了很多?而且之后如果想加操作还要再往外套
第三种则可读性提高了一些(但是还是没有从前往后读顺畅),但是中间加了很多没有必要的变量,代码就会很冗余。
以上是最基本也是最常用的方法,下面展示一些其他用法,更深入的可以去看包的帮助文档
# 一次使用多个函数计算
df$weight %>% {c(min(.), mean(.), max(.))} %>% floor
# 想作为函数第二个参数时,可以用.代替
2 %>% head(df,.)
# 将得到的结果赋值
a <- df %>% .$name2 %>% grep("a",.)
Dwzb , R语言中文社区专栏作者,厦门大学统计专业学生。
知乎专栏:Data Analysis
https://zhuanlan.zhihu.com/Data-AnalysisR
微信回复关键字即可学习
回复 R R语言快速入门免费视频
回复 统计 统计方法及其在R中的实现
回复 用户画像 民生银行客户画像搭建与应用
回复 大数据 大数据系列免费视频教程
回复 可视化 利用R语言做数据可视化
回复 数据挖掘 数据挖掘算法原理解释与应用
回复 机器学习 R&Python机器学习入门