[英]R: convert XML data to data frame
對於家庭作業,我試圖將 XML 文件轉換為 R 中的數據框。我嘗試了很多不同的方法,並且在互聯網上搜索了一些想法,但都沒有成功。 到目前為止,這是我的代碼:
library(XML)
url <- 'http://www.ggobi.org/book/data/olive.xml'
doc <- xmlParse(myUrl)
root <- xmlRoot(doc)
dataFrame <- xmlSApply(xmltop, function(x) xmlSApply(x, xmlValue))
data.frame(t(dataFrame),row.names=NULL)
我得到的輸出就像一個巨大的數字向量。 我試圖將數據組織到一個數據框中,但我不知道如何正確調整我的代碼以獲得它。
它可能不像XML
包那樣冗長,但xml2
沒有內存泄漏,並且專注於數據提取。 我用trimws
這是一個非常最近除了與R核心。
library(xml2)
pg <- read_xml("http://www.ggobi.org/book/data/olive.xml")
# get all the <record>s
recs <- xml_find_all(pg, "//record")
# extract and clean all the columns
vals <- trimws(xml_text(recs))
# extract and clean (if needed) the area names
labs <- trimws(xml_attr(recs, "label"))
# mine the column names from the two variable descriptions
# this XPath construct lets us grab either the <categ…> or <real…> tags
# and then grabs the 'name' attribute of them
cols <- xml_attr(xml_find_all(pg, "//data/variables/*[self::categoricalvariable or
self::realvariable]"), "name")
# this converts each set of <record> columns to a data frame
# after first converting each row to numeric and assigning
# names to each column (making it easier to do the matrix to data frame conv)
dat <- do.call(rbind, lapply(strsplit(vals, "\ +"),
function(x) {
data.frame(rbind(setNames(as.numeric(x),cols)))
}))
# then assign the area name column to the data frame
dat$area_name <- labs
head(dat)
## region area palmitic palmitoleic stearic oleic linoleic linolenic
## 1 1 1 1075 75 226 7823 672 NA
## 2 1 1 1088 73 224 7709 781 31
## 3 1 1 911 54 246 8113 549 31
## 4 1 1 966 57 240 7952 619 50
## 5 1 1 1051 67 259 7771 672 50
## 6 1 1 911 49 268 7924 678 51
## arachidic eicosenoic area_name
## 1 60 29 North-Apulia
## 2 61 29 North-Apulia
## 3 63 29 North-Apulia
## 4 78 35 North-Apulia
## 5 80 46 North-Apulia
## 6 70 44 North-Apulia
更新
我現在最好這樣做最后一點:
library(tidyverse)
strsplit(vals, "[[:space:]]+") %>%
map_df(~as_data_frame(as.list(setNames(., cols)))) %>%
mutate(area_name=labs)
上面的答案很好! 對於未來的讀者,無論何時您遇到需要 R 導入的復雜 XML,都可以考慮使用XSLT (一種特殊用途的聲明式編程語言,將 XML 內容處理為各種最終使用需求)來重新構建 XML 文檔。 然后簡單地使用來自 XML 包的 R 的xmlToDataFrame()
函數。
不幸的是,R 在所有操作系統的 CRAN-R 上都沒有可用的專用 XSLT 包。 列出的SXLT似乎是一個 Linux 包,不能在 Windows 上使用。 在此處和此處查看未回答的 SO 問題。 我知道@hrbrmstr(以上)維護一個GitHub XSLT 項目。 盡管如此,幾乎所有通用語言都維護 XSLT 處理器,包括 Java、C#、Python、PHP、Perl 和 VB。
下面是開源 Python 路線,因為 XML 文檔非常微妙,所以使用了兩個 XSLT(當然 XSLT 專家可以將它們組合成一個,但嘗試過,因為我可能無法讓它工作。
第一個 XSLT (使用遞歸模板)
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<!-- Identity Transform -->
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record/text()" name="tokenize">
<xsl:param name="text" select="."/>
<xsl:param name="separator" select="' '"/>
<xsl:choose>
<xsl:when test="not(contains($text, $separator))">
<data>
<xsl:value-of select="normalize-space($text)"/>
</data>
</xsl:when>
<xsl:otherwise>
<data>
<xsl:value-of select="normalize-space(substring-before($text, $separator))"/>
</data>
<xsl:call-template name="tokenize">
<xsl:with-param name="text" select="substring-after($text, $separator)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template match="description|variables|categoricalvariable|realvariable">
</xsl:template>
第二個 XSLT
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<!-- Identity Transform -->
<xsl:template match="records">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="record">
<record>
<area_name><xsl:value-of select="@label"/></area_name>
<area><xsl:value-of select="data[1]"/></area>
<region><xsl:value-of select="data[2]"/></region>
<palmitic><xsl:value-of select="data[3]"/></palmitic>
<palmitoleic><xsl:value-of select="data[4]"/></palmitoleic>
<stearic><xsl:value-of select="data[5]"/></stearic>
<oleic><xsl:value-of select="data[6]"/></oleic>
<linoleic><xsl:value-of select="data[7]"/></linoleic>
<linolenic><xsl:value-of select="data[8]"/></linolenic>
<arachidic><xsl:value-of select="data[9]"/></arachidic>
<eicosenoic><xsl:value-of select="data[10]"/></eicosenoic>
</record>
</xsl:template>
</xsl:stylesheet>
Python (使用 lxml 模塊)
import lxml.etree as ET
cd = os.path.dirname(os.path.abspath(__file__))
# FIRST TRANSFORMATION
dom = ET.parse('http://www.ggobi.org/book/data/olive.xml')
xslt = ET.parse(os.path.join(cd, 'Olive.xsl'))
transform = ET.XSLT(xslt)
newdom = transform(dom)
tree_out = ET.tostring(newdom, encoding='UTF-8', pretty_print=True, xml_declaration=True)
xmlfile = open(os.path.join(cd, 'Olive_py.xml'),'wb')
xmlfile.write(tree_out)
xmlfile.close()
# SECOND TRANSFORMATION
dom = ET.parse(os.path.join(cd, 'Olive_py.xml'))
xslt = ET.parse(os.path.join(cd, 'Olive2.xsl'))
transform = ET.XSLT(xslt)
newdom = transform(dom)
tree_out = ET.tostring(newdom, encoding='UTF-8', pretty_print=True, xml_declaration=True)
xmlfile = open(os.path.join(cd, 'Olive_py.xml'),'wb')
xmlfile.write(tree_out)
xmlfile.close()
電阻
library(XML)
# LOADING TRANSFORMED XML INTO R DATA FRAME
doc<-xmlParse("Olive_py.xml")
xmldf <- xmlToDataFrame(nodes = getNodeSet(doc, "//record"))
View(xmldf)
輸出
area_name area region palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
North-Apulia 1 1 1075 75 226 7823 672 na 60
North-Apulia 1 1 1088 73 224 7709 781 31 61 29
North-Apulia 1 1 911 54 246 8113 549 31 63 29
North-Apulia 1 1 966 57 240 7952 619 50 78 35
North-Apulia 1 1 1051 67 259 7771 672 50 80 46
...
(需要對第一個記錄進行輕微清理,因為在 xml 文檔中的“na”之后添加了一個額外的空間,因此arachidic
和eicosenoic
被向前移動)
這是我想出的。 它與同一頁面上的橄欖油 csv 文件相匹配。 他們將X
顯示為第一個列名,但我在 xml 中沒有看到它,所以我只是手動添加了它。
最好將其分成幾部分,然后在我們獲得所有部分后組裝最終的數據框。 我們還可以使用 XPath 的[.XML*
快捷方式,以及其他[[
便利訪問器函數。
library(XML)
url <- "http://www.ggobi.org/book/data/olive.xml"
## parse the xml document and get the top-level XML node
doc <- xmlParse(url)
top <- xmlRoot(doc)
## create the data frame
df <- cbind(
## get all the labels for the first column (groups)
X = unlist(doc["//record//@label"], use.names = FALSE),
read.table(
## get all the records as a character vector
text = xmlValue(top[["data"]][["records"]]),
## get the column names from 'variables'
col.names = xmlSApply(top[["data"]][["variables"]], xmlGetAttr, "name"),
## assign the NA values to 'na' in the records
na.strings = "na"
)
)
## result
head(df)
# X region area palmitic palmitoleic stearic oleic linoleic linolenic arachidic eicosenoic
# 1 North-Apulia 1 1 1075 75 226 7823 672 NA 60 29
# 2 North-Apulia 1 1 1088 73 224 7709 781 31 61 29
# 3 North-Apulia 1 1 911 54 246 8113 549 31 63 29
# 4 North-Apulia 1 1 966 57 240 7952 619 50 78 35
# 5 North-Apulia 1 1 1051 67 259 7771 672 50 80 46
# 6 North-Apulia 1 1 911 49 268 7924 678 51 70 44
## clean up
free(doc); rm(doc, top); gc()
對我來說,規范的答案是
doc<-xmlParse("Olive_py.xml")
xmldf <- xmlToDataFrame(nodes = getNodeSet(doc, "//record"))
不知何故隱藏在@Parfait 的答案中。
但是,如果某些節點有多個相同類型的子節點,這將失敗。 在這種情況下,提取器功能將解決問題:
示例數據
<?xml version="1.0" encoding="UTF-8"?>
<testrun duration="25740" footerText="Generated by IntelliJ IDEA on 11/20/19, 9:21 PM" name="All in foo">
<suite duration="274" locationUrl="java:suite://com.foo.bar.LoadBla" name="LoadBla"
status="passed">
<test duration="274" locationUrl="java:test://com.foo.bar.LoadBla/testReadWrite"
name="LoadBla.testReadWrite" status="passed">
<output type="stdout">ispsum ..</output>
</test>
</suite>
<suite duration="9298" locationUrl="java:suite://com.foo.bar.TestFooSearch" name="TestFooSearch"
status="passed">
<test duration="7207" locationUrl="java:test://com.foo.bar.TestFooSearch/TestFooSearch"
name="TestFooSearch.TestFooSearch" status="passed">
<output type="stdout"/>
</test>
<test duration="2091" locationUrl="java:test://com.foo.bar.TestFooSearch/testSameSearch"
name="TestFooSearch.testSameSearch" status="passed"/>
</suite>
</testrun>
代碼
require(XML)
require(tidyr)
require(dplyr)
node2df <- function(node){
# (Optinonally) read out properties of some optional child node
outputNodes = getNodeSet(node, "output")
stdout = if (length(outputNodes) > 0) xmlValue(outputNodes[[1]]) else NA
vec_as_df <- function(namedVec, row_name="name", value_name="value"){
data_frame(name = names(namedVec), value = namedVec) %>% set_names(row_name, value_name)
}
# Extract all node properties
node %>%
xmlAttrs %>%
vec_as_df %>%
pivot_wider(names_from = name, values_from = value) %>%
mutate(stdout = stdout)
}
testResults = xmlParse(xmlFile) %>%
getNodeSet("/testrun/suite/test", fun = node2df) %>%
bind_rows()
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.