初心者也會用的 R 語言讀取 XML 資料分析實戰教學!(三)

前兩篇文章已經介紹過處理 XML 資料的前備知識以及 R 語言 XML 套件的常用功能,是時候磨刀上陣了!本文將採用政府開放資料中的台鐵、高鐵以及微軟 XML 教學檔案進行範例演練,內文中使用到相關的套件及語法將不在一一細述,請參考前兩篇文章的說明。

XML 資料來源:
1. 台鐵時刻表
2. 高鐵時刻表
3. 微軟提供的範例檔

用 R 讀取台鐵時刻表 XML

首先針對台鐵時刻表,運用剛介紹過的 XML Viewer 快速了解資料集的大致結構以及各附加屬性:

r-xml-tutorial-in-practice-2-3

(續)
r-xml-tutorial-in-practice-3-1

從上圖當中可以發現台鐵時刻表的 XML 節點結構並不複雜,基本上只有三層主要結構(taitrainlist->traininfo->timeinfo),但是比較特別的是個別元素的值都為空,所有的資訊都藏在屬性當中(有 @ 標記的資料)。

起手式運用 xmlParse 爬取檔案名稱為「 20160601.xml 」的 XML 資料並賦值給自訂變數 TaiTrainNodes ,使用編碼為 utf-8 :

TaiTrainNodes<-xmlParse(file="20160601.xml", encoding="UTF-8")

假如我們特別想要知道列車編號為 4023 的一系列行駛時間資訊,該怎麼做呢?

由於元素值為空,我們應該選擇 xmlGetAttr 而非 xmlValue 來取值,但因為有多個需要求值的節點,故搭配 xmlApply 來執行:

#此處賦值給自訂變數 Train4023Time
Train4023Time<-xmlApply(getNodeSet(TaiTrainNodes,"/TaiTrainList/TrainInfo[@Train=4023]/TimeInfo"), xmlAttrs)

結果得到一個 list 物件如下:

print(Train4023Time)
[[1]]
ARRTime DEPTime Order Route Station
“13:28:00″ “13:30:00″ “1″ “" “1808″

[[2]]
ARRTime DEPTime Order Route Station
“13:41:00″ “13:42:00″ “2″ “" “1805″

[[3]]
ARRTime DEPTime Order Route Station
“13:48:00″ “13:54:00″ “3″ “" “1804″

看起來還不錯對吧?

不過這還不是最理想的資料格式,許多情況下不論是為了做進一步的資料整理或者匯入資料庫,我們都會希望 R 中看到的是「 data.frame 」的格式,所以我們需要稍微再加工一下,先使用 unlist 將資料變為文字向量:

#此處賦值給自訂變數 Train4023Time_Unlist
Train4023Time_Unlist<-unlist(Train4023Time)

#使用 str() 函數檢視資料特徵
str(Train4023Time_Unlist)
Named chr [1:50] “13:28:00″ “13:30:00″ “1″ “" “1808″ “13:41:00″ “13:42:00″ “2″ “" “1805″ …
– attr(*, “names")= chr [1:50] “ARRTime" “DEPTime" “Order" “Route" …

接下來使用 matrix 函數將向量轉變為矩陣格式,我們需要確定矩陣的大小,這可以透過 nrow 以及 ncol 參數設置來達成,並藉由邏輯參數 byrow 來指定資料填入順序是由上往下或者由左而右,最後再以 as.data.frame 函數轉變資料格式。

由於在稍早資料整理的過程中已經知道有 5 個變數( ARRTime 、 DEPTime 等等),所以此處的範例採用限定矩陣欄位為 5 加上資料水平填入矩陣的方式重整:

#此處賦值給自訂變數 Train4023Time_Matrix
Train4023Time_Matrix<-as.data.frame(matrix(Train4023Time_Unlist, ncol=5, byrow=TRUE))

#重新賦予欄位名稱
colnames(Train4023Time_Matrix)<-c("ARRTime","DEPTime","Order","Route","Station")

#檢視 dataframe 結果如下:
head(Train4023Time_Matrix)
ARRTime DEPTime Order Route Station
1 13:28:00 13:30:00 1 1808
2 13:41:00 13:42:00 2 1805
3 13:48:00 13:54:00 3 1804
4 14:04:00 14:05:00 4 1002
5 14:09:00 14:10:00 5 1003
6 14:18:00 14:19:00 6 1005

搞定一個!

用 R 讀取高鐵時刻表 XML

看完台鐵 XML 的資料,你或許會想高鐵時刻表的格式應該差不多吧?先別這麼快下結論…。

我們採用上行時刻表當範例,先看看 XML Viewer 怎麼說:

r-xml-tutorial-in-practice-3-2

(續)
r-xml-tutorial-in-practice-3-3

(續)
r-xml-tutorial-in-practice-3-4

咦,你有沒有發現這份 XML 的結構有點莫名其妙?

為了不要浪費篇幅,我們就直接揭曉答案吧:這根本不是正常的 XML ,只不過是把 excel 檔案輸出為 excel XML 濫竽充數罷了!其實還滿讓人傻眼的……。

r-xml-tutorial-in-practice-3-5

言歸正傳,以 excel 表格為基底的 XML 能不能解析?有時候還是可以的,不過如果原本的資料不是方方正正的對齊,而是存在不規律的合併儲存格或是不明跳空的格子,就會大幅提高正確判讀的難度。

高鐵時刻表原本的資料其實是長這樣的:

r-xml-tutorial-in-practice-3-6

勉強一試,先讀取檔案名稱為「 TSR_to_North.xml 」的資料如下:

#此處賦值給自訂變數 TSRNodes
TSRNodes<-xmlParse(file="TSR_to_North.xml", encoding="UTF-8")

接著使用 xmlApply 加上 xmlValue 的方式取得元素中的數值, 其中 Xpath 路徑的「 * 」表示任意的元素,搭配全域符號「 // 」就等於是告訴 R 去索取有任意值的全部元素:

#此處賦值給自訂變數 TSRNodesCell
TSRNodesCell<-xmlApply(getNodeSet(TSRNodes,"//*"),xmlValue)

只能得到不規律的 list 如下:

> str(TSRNodesCell,list.len=5)
List of 9182
$ : chr “NPOI房昕田2015-08-25T05:39:11Z2014-12-04T16:31:20Z2016-02-17T02:39:12Z14.001320017040-1500FalseFalse台 灣 高 鐵 列 車"| __truncated__
$ : chr “NPOI房昕田2015-08-25T05:39:11Z2014-12-04T16:31:20Z2016-02-17T02:39:12Z14.00″
$ : chr “NPOI"
$ : chr “房昕田"
$ : chr “2015-08-25T05:39:11Z"

用 R 讀取微軟客戶訂單 XML

我們接著看到最後一個客戶訂單 XML ,這個文件的結構雖然較深但是樹狀結構良好,從 XML Viewer 可略知一二:

r-xml-tutorial-in-practice-3-8

r-xml-tutorial-in-practice-3-7

兩個主要的分支節點「 customers 」、「 orders 」分別紀錄客戶基本資料以及每筆訂單明細,這種格式回推到資料庫層級往往就是暗示著分支節點屬於不同的資料表,對於資料庫有些敏銳度的讀者應該馬上就想到可以在這份 XML 中尋找共通的 key 值把訂單與買家串連起來。

附帶一提,這個例子中的 key 值是 customerid 。

先前的演練當中是先將 XML 轉為 list 物件之後再經過整理變成 data.frame ,此處刻意採用另一種操作方式,試圖直接把 XML 轉為 data.frame ,讓我們來看看有何差別:

#讀取檔案名稱為 MS_XML.xml 的資料
#此處賦值給自訂變數 MSNodes
MSNodes<-xmlParse(file="MS_XML.xml", encoding="UTF-8")

#已知 Root 為最高級節點
#此處賦值給自訂變數 MSNodes_DF
MSNodes_DF<-xmlToDataFrame(nodes=getNodeSet(MSNodes,"//Root"))

檢視轉換後的 data.frame :

> str(MSNodes_DF)
‘data.frame': 1 obs. of 2 variables:
$ Customers: Factor w/ 1 level “Great Lakes Food MarketHoward SnyderMarketing Manager(503) 555-75552732 Baker Blvd.EugeneOR97403USAHungry Coyote Import StoreYo"| __truncated__: 1
$ Orders : Factor w/ 1 level “GREAL61997-05-06T00:00:001997-05-20T00:00:0023.35Great Lakes Food Market2732 Baker Blvd.EugeneOR97403USAGREAL81997-07-04T00:00:"| __truncated__: 1

仔細一看,雖然成功轉成了 data.frame ,卻只是粗糙地取用子節點作為變數,再把個別變數的所有數值塞進同一筆資料當中而已,這樣可是沒法做分析的。

一個簡單的解決方案就是執行多次的 xmlToDataFrame ,每一次針對一個特定的節點將其轉化為只具有一個變數的 data.frame,之後再將資料做欄位方向的合併,例如以下操作 orders 節點的例子:

#請注意 Xpath 路經的層級
#此處賦值給 4 個自訂變數
MSNodes_DF_Orders_ID<-xmlToDataFrame(nodes=getNodeSet(MSNodes,"//Root/Orders/Order/CustomerID"))
MSNodes_DF_Orders_Date<-xmlToDataFrame(nodes=getNodeSet(MSNodes,"//Root/Orders/Order/OrderDate"))
MSNodes_DF_Orders_ShipCity<-xmlToDataFrame(nodes=getNodeSet(MSNodes,"//Root/Orders/Order/ShipInfo/ShipCity"))
MSNodes_DF_Orders_ShipName<-xmlToDataFrame(nodes=getNodeSet(MSNodes,"//Root/Orders/Order/ShipInfo/ShipName"))

#此處賦值給自訂變數 MSNodes_DF_Orders
#運用 cbind 功能合併變數
MSNodes_DF_Orders<-cbind(MSNodes_DF_Orders_ID,MSNodes_DF_Orders_Date,MSNodes_DF_Orders_ShipCity,MSNodes_DF_Orders_ShipName)
#為變數重新命名
colnames(MSNodes_DF_Orders)<-c("ID","Date","City","Name")

#運用 head 函數檢視前幾筆資料
head(MSNodes_DF_Orders)
ID Date City Name
1 GREAL 1997-05-06T00:00:00 Eugene Great Lakes Food Market
2 GREAL 1997-07-04T00:00:00 Eugene Great Lakes Food Market
3 GREAL 1997-07-31T00:00:00 Eugene Great Lakes Food Market
4 GREAL 1997-07-31T00:00:00 Eugene Great Lakes Food Market
5 GREAL 1997-09-04T00:00:00 Eugene Great Lakes Food Market
6 GREAL 1997-09-25T00:00:00 Eugene Great Lakes Food Market

這樣一來就大功告成了!

系列文章:

初心者也會用的 R 語言讀取 XML 資料分析實戰教學!(一)

初心者也會用的 R 語言讀取 XML 資料分析實戰教學!(二)

更多 R 資料科學實戰:

* 統計R語言實作筆記系列 – 2D視覺化進階 GGPLOT()的基本架構(一)

* 統計R語言實作筆記系列 – 用 SHINY 套件極速打造你的商業智慧分析網站!

* 統計R語言實作筆記系列- 直線ABLINE()、曲線CURVE()與多邊形POLYGON()

* 統計R語言實作筆記系列 – 2D視覺化入門

* 統計R語言實作筆記系列 – R的字串處理:GREP套件包(GREP、GREPL、REGEXPR、GREGEXPR、REGEXEC)

(Visited 1,554 times, 8 visits today)

Wendell.Huang

科技公司嫌棄太活潑,消費品牌挑剔太沉悶..., 經常必須解釋自己在學什麼, 不小心就摔破對方眼鏡的跨領域玩家。

2 Comments

發表迴響

你的電子郵件位址並不會被公開。 必要欄位標記為 *