查看原文
其他

R数据处理|基础篇(一)

2017-04-11 dwzb 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机器学习入门 

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存