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") nlibrary("jsonlite")nlibrary("magrittr")nlibrary("plyr")nlibrary("rlist")nhomefeed<-fromJSON("E:/git/DataWarehouse/File/homefeed.json",simplifyVector=FALSE)n
homefeed本身結構非常複雜,已經超越了現有結構化數據處理函數的能力範圍,plyr、dplyr、tidyr什麼的統統都束手無策了。
sapply(homefeed,length)npaging data attached_info n 3 144 1n
這是homefeed對象的第一級節點結構,可以看到我們的課程信息存在data中,data一共有144條記錄。
myresult<-homefeed %>% `[[`(2);length(myresult);names(myresult)n[1] 144nNULLn
以上提取除了data這個對象,接下來所有的工作都將圍繞著myresult來進行的。
myresult[[1]] #提取第一個list對象nunlist(myresult[[1]]) #將第一個list對象展開成普通向量nlength(myresult[[1]]) #第一個list對象的長度nnames(unlist(myresult[[1]])) #展開之後的所有對象名稱n
因為myresult裡面有144個子list,分別代表144個課程,每一個子list(課程)的所有子孫節點一共是53個,所有的信息展開之後應該是一個144*53的大矩陣(或者數據框)。
列表展開
fulldata<-myresult %>% lapply(unlist) %>% do.call(rbind,.) %>% as.data.frame()n
這份數據集將所有的課程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")nfulldata<-fulldata%>% .[,useful]ndim(fulldata)n[1] 144 17n
篩選之後,剩餘數據集是一個144行,17列的數據框。但是預覽數據會發現,其中有些行記錄值明顯不對,也就是有個別記錄串列啦!!!
這是為什麼呢,還記得我們預覽第一條記錄的時候是長度是53,可是這麼展開列表的時候結果卻是75,很詭異吧,我猜是這144個課程屬性信息長度不等,有些課程是53個屬性,有些會更多。具體情況如何,我們用一個循環自己查看下!
num<-c()nfor (i in myresult){nnum<-c(num,unlist(i) %>%length())n}nplyr::count(num)n x freqn1 53 133n2 64 10n3 75 1n
果然,144個記錄中,只有133個是53條屬性信息,10個是64條信息,還有1個是75條信息,我們展開的列表是75列,說明函數按照子列表中長度最大的列進行展開與合併的。
接下來怎麼辦呢,那麼笨辦法只能將53、64和75條信息的不同子list分隔成三個不同的列表對象,然後分別展開。
myresult1=myresult2=myresult3=list()nfor (i in 1:length(myresult)){ nif (unlist(myresult[i]) %>%length()==53) {n myresult1<-c(myresult1,myresult[i])n } else if(unlist(myresult[i]) %>%length()==64) {n myresult2<-c(myresult2,myresult[i])n } else {n myresult3<-c(myresult3,myresult[i]) n }n}nlength(myresult1)n[1] 133nlength(myresult2)n[1] 10nlength(myresult3)n[1] 1n
運行以上代碼之後,我們獲取了三個新列表。
使用以下函數分別將三個列表中平鋪,然後縱向合併,最後選擇我們需要的重要信息列。
myfulldata1<-myresult1 %>% lapply(unlist) %>% do.call(rbind,.) %>% as.data.frame() %>% select(useful)nmyfulldata2<-myresult2 %>% lapply(unlist) %>% do.call(rbind,.) %>% as.data.frame() %>% select(useful)nmyfulldata3<-myresult3 %>% lapply(unlist) %>% do.call(rbind,.) %>% as.data.frame() %>% .[,useful]nmyfulldata<-rbind(myfulldata1,myfulldata2,myfulldata3)n
OK,完美,得到了一份非常規整的數據集,甚至都沒有什麼缺失值。這個數據集可以放心的作為其他分析數據源或者存入資料庫啦。
可是不覺得以上步驟有些繁瑣嘛~簡單方法當然有啦,任坤大大開發的rlist是專門針對R語言list結構數據處理的,其中封裝了很多功能強大的列表操作函數,使得在R語言中操作列表就像使用dplyr操作data.frame那樣簡單。
library("rlist")nlibrary("pipeR")n
rlist的使用還是有一定難度的,因為涉及到一些非結構化數據以及遞歸的操作,今天只涉及其中一個函數,即list.map()
list.map(.data, expr)n
只有兩個參數,第一個是數據框,第二個是匿名函數。(就跟python中的lambda差不多一個意思,沒有函數名的無頭函數)。
live.seats.taken <-myresult %>% list.map(live$seats$taken) %>% unlist() nlive.fee.original_price <-myresult %>% list.map(live$fee$original_price) %>% unlist() nlive.review.count <-myresult %>% list.map(live$review$count) %>% unlist() nlive.review.score <-myresult %>% list.map(live$review$score) %>% unlist() nlive.speaker.member.name <-myresult %>% list.map(live$speaker$member$name) %>% unlist() nlive.speaker.member.gender<-myresult %>% list.map(live$speaker$member$gender) %>% unlist() nlive.subject <-myresult %>% list.map(live$subject) %>% unlist() nmydatamap<-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)n
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官方文檔中的案例數據源已經不可用了,不過第二篇裡面提供了自造案例數據,可以作為練習使用。
在線課程請點擊文末原文鏈接:
Hellobi Live | 9月12日 R語言可視化在商務場景中的應用
往期案例數據請移步本人GitHub:https://github.com/ljtyduyu/DataWarehouse/tree/master/File推薦閱讀:
※UFO長啥樣?--Python數據分析來告訴你
※中國有嘻哈丨數據分析誰是押韻轟炸機
※Infovis的圖形推理(譯)
※excel怎麼把一欄數據分別複製到其他欄?