对于家庭作业,我试图将一个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)
我得到的输出就像一个巨大的数字向量。我试图将数据组织到数据框中,但我不知道如何正确调整我的代码以获得它。
答案 0 :(得分:26)
它可能不像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)
答案 1 :(得分:7)
上面的好答案!对于未来的读者,只要您遇到需要R导入的复杂XML,就可以考虑使用XSLT(一种将XML内容处理成各种最终用途需求的专用声明性编程语言)重新构建XML文档。然后只需使用XML包中的R xmlToDataFrame()
函数。
不幸的是,R在所有操作系统上都没有CRAN-R上可用的专用XSLT软件包。列出的SXLT似乎是一个Linux软件包,无法在Windows上使用。请参阅未回答的SO问题here和here。我理解@hrbrmstr(上图)维护GitHub XSLT project。尽管如此,几乎所有通用语言都维护着XSLT处理器,包括Java,C#,Python,PHP,Perl和VB。
下面是开源Python路由,因为XML文档非常细微,所以正在使用两个XSLT(当然XSLT专家可以将它们组合成一个但是尝试过,因为我可能无法使它工作。
FIRST XSLT (使用recursive template)
<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()
<强> - [R 强>
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 doc中“na”之后添加了额外的空格,所以需要在第一条记录上进行轻微清理,因此arachidic
和eicosenoic
向前移动了 < / p>
答案 2 :(得分:2)
这是我想出的。它匹配同一页面上也可用的olive oil csv file。他们将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()
答案 3 :(得分:0)
对我来说,规范的答案是
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()