R语言数据清洗实战——复杂数据结构与list解析

时间:2022-05-08
本文章向大家介绍R语言数据清洗实战——复杂数据结构与list解析,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

数据清洗从来都不是一件简单的事情!

使用httr包结合浏览器抓包工具进行网页数据抓取虽然非常方便,但是获取的数据后期处理工作量却非常庞大的。

因为大部分json数据包返回之后都会被转换为R语言中的非结构化数据类型——list。

也就是说,对于list数据结构的处理熟练程度,将会决定着你在数据清洗中所花费的时间与精力。

list数据结构本身即可简单也可复杂,当list中存在递归结构时,其处理难度就大大增加了。(不幸的是大部分json数据包都是递归结构的)

对于list数据结构的处理,你可以通过手动构造循环来处理(无论是自己书写显式的循环还是借助矢量化函数)。

当然也可以借助很成熟的第三方list操作包,其中rlist就是目前存在的非常优秀的用于处理list数据结构的扩展包。

以下是昨天使用httr包抓取的知乎live课程信息的json数据包,我会通过该份案例的清洗实战,来给大家演示list数据结构处理的一般流程,同时尝试引入新的rlist包(其实我们之前一节已经用过它的一个函数了,保存json的时候用过list.save,不知道大家还有印象不?)

导入json数据包:

library("dplyr") 
library("jsonlite")
library("magrittr")
library("plyr")
library("rlist")
homefeed<-fromJSON("E:/git/DataWarehouse/File/homefeed.json",simplifyVector=FALSE)

homefeed本身结构非常复杂,已经超越了现有结构化数据处理函数的能力范围,plyr、dplyr、tidyr什么的统统都束手无策了。

sapply(homefeed,length)
paging          data attached_info 
  3           144             1

这是homefeed对象的第一级节点结构,可以看到我们的课程信息存在data中,data一共有144条记录。

myresult<-homefeed %>% `[[`(2);length(myresult);names(myresult)
[1] 144NULL

以上提取除了data这个对象,接下来所有的工作都将围绕着myresult来进行的。

myresult[[1]]                  #提取第一个list对象
unlist(myresult[[1]])          #将第一个list对象展开成普通向量
length(myresult[[1]])          #第一个list对象的长度
names(unlist(myresult[[1]]))   #展开之后的所有对象名称

因为myresult里面有144个子list,分别代表144个课程,每一个子list(课程)的所有子孙节点一共是53个,所有的信息展开之后应该是一个144*53的大矩阵(或者数据框)。

列表展开

fulldata<-myresult %>% lapply(unlist)  %>% do.call(rbind,.)  %>% as.data.frame()

这份数据集将所有的课程list全部展开了,获取到了一个144*75的大数据框,但是其中有很多数据字段我们不需要的,或者说意义不大的。需要根据分析需要一点一点儿剔除掉。

找到课程的第一条——董明伟老师的Python课程,然后顺便通过浏览器定位到老师的知乎live主页,将live主页上到的信息与抓取到的信息进行对比匹配,我们可以找到那些对我们非常重要的课程信息。

比如课程参与人数,课程初始价格,折扣价格,课程评论人数,课程得分,作者,讲师签名,讲师性别,课程名称,课程描述等信息。

useful<-c("live.seats.taken","live.id","live.fee.original_price","live.fee.amount","live.review.count","live.review.score","live.speaker.member.name","live.speaker.member.headline","live.speaker.member.gender","live.speaker.description","live.subject","live.speaker_message_count","live.tags.name","live.tags.great_num","live.tags.score","live.tags.available_num","live.tags.live_num","live.liked_num")
fulldata<-fulldata%>% .[,useful]
dim(fulldata)
[1] 144  17

筛选之后,剩余数据集是一个144行,17列的数据框。但是预览数据会发现,其中有些行记录值明显不对,也就是有个别记录串行啦!!!

这是为什么呢,还记得我们预览第一条记录的时候是长度是53,可是这么展开列表的时候结果却是75,很诡异吧,我猜是这144个课程属性信息长度不等,有些课程是53个属性,有些会更多。具体情况如何,我们用一个循环自己查看下!

num<-c()
for (i in myresult){
num<-c(num,unlist(i) %>%length())
}
plyr::count(num)
   x freq
1 53  133
2 64   10
3 75    1

果然,144个记录中,只有133个是53条属性信息,10个是64条信息,还有1个是75条信息,我们展开的列表是75列,说明函数按照子列表中长度最大的列进行展开与合并的。

接下来怎么办呢,那么笨办法只能将53、64和75条信息的不同子list分隔成三个不同的列表对象,然后分别展开。

myresult1=myresult2=myresult3=list()
for (i in 1:length(myresult)){ 
if (unlist(myresult[i]) %>%length()==53) {
  myresult1<-c(myresult1,myresult[i])
  } else if(unlist(myresult[i]) %>%length()==64) {
  myresult2<-c(myresult2,myresult[i])
  } else {
  myresult3<-c(myresult3,myresult[i]) 
  }
}
length(myresult1)
[1] 133
length(myresult2)
[1] 10
length(myresult3)
[1] 1

运行以上代码之后,我们获取了三个新列表。

使用以下函数分别将三个列表中平铺,然后纵向合并,最后选择我们需要的重要信息列。

myfulldata1<-myresult1 %>% lapply(unlist)  %>% do.call(rbind,.)  %>% as.data.frame() %>% select(useful)
myfulldata2<-myresult2 %>% lapply(unlist)  %>% do.call(rbind,.)  %>% as.data.frame() %>% select(useful)
myfulldata3<-myresult3 %>% lapply(unlist)  %>% do.call(rbind,.)  %>% as.data.frame() %>% .[,useful]
myfulldata<-rbind(myfulldata1,myfulldata2,myfulldata3)

OK,完美,得到了一份非常规整的数据集,甚至都没有什么缺失值。这个数据集可以放心的作为其他分析数据源或者存入数据库啦。

可是不觉得以上步骤有些繁琐嘛~简单方法当然有啦,任坤大大开发的rlist是专门针对R语言list结构数据处理的,其中封装了很多功能强大的列表操作函数,使得在R语言中操作列表就像使用dplyr操作data.frame那样简单。

library("rlist")
library("pipeR")

rlist的使用还是有一定难度的,因为涉及到一些非结构化数据以及递归的操作,今天只涉及其中一个函数,即list.map()

list.map(.data, expr)

只有两个参数,第一个是数据框,第二个是匿名函数。(就跟python中的lambda差不多一个意思,没有函数名的无头函数)。

live.seats.taken          <-myresult %>% list.map(live$seats$taken) %>% unlist() 
live.fee.original_price   <-myresult %>% list.map(live$fee$original_price) %>% unlist() 
live.review.count         <-myresult %>% list.map(live$review$count) %>% unlist() 
live.review.score         <-myresult %>% list.map(live$review$score) %>% unlist() 
live.speaker.member.name  <-myresult %>% list.map(live$speaker$member$name) %>% unlist() 
live.speaker.member.gender<-myresult %>% list.map(live$speaker$member$gender) %>% unlist() 
live.subject              <-myresult %>% list.map(live$subject) %>% unlist() 
mydatamap<-data.frame(live.seats.taken,live.fee.original_price,live.review.count,live.review.score,live.speaker.member.gender,live.speaker.member.name,live.subject)

list.map可以通过简单的输入list内的元素路径(就像是提取数据框的列一样,只不过是多层而已),实现矢量化的提取和递归操作,将每一个子对象的相同元素一次全部提取出来。以上操作我们提取了一些重要课程信息变量。

最终的数据表非常规整,list.map可以帮你自动处理缺失值问题,避免了有些null值造成提取后对象的长度不等,进而无法实现数据框化。

如果你想获取详细的rlist操作技巧,可以查阅查阅rlist文档,本篇文章旨在提供处理复杂list的一般思路,最终的干净数据也会同步上传GitHub,如果你已经迫不及待的想要分析的话,可以自己去下载。

本文参考文献: https://renkun-ken.github.io/rlist-tutorial/Getting-started/Quick-overview.html http://blog.csdn.net/sinat_26917383/article/details/51123164

官方文档中的案例数据源已经不可用了,不过第二篇里面提供了自造案例数据,可以作为练习使用。

往期案例数据请移步本人GitHub: https://github.com/ljtyduyu/DataWarehouse/tree/master/File