2015-10-31 11 views
17

Für eine Hausaufgabe versuche ich eine XML-Datei in einen Datenrahmen in R zu konvertieren. Ich habe viele verschiedene Dinge ausprobiert, habe im Internet nach Ideen gesucht, war aber erfolglos . Hier ist mein Code so weit:R: XML-Daten in Datenrahmen umwandeln

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) 

Die Ausgabe, die ich bekomme, ist wie ein riesiger Vektor von Zahlen. Ich versuche, die Daten in einem Datenrahmen zu organisieren, aber ich weiß nicht, wie ich meinen Code richtig anpassen kann, um das zu erhalten.

+3

Ich würde xml2 verwenden, wenn auch nur weil Hadley durchweg sehr hochwertige und nützliche R-Pakete produziert. Es wird wahrscheinlich reibungsloser laufen und besser dokumentiert werden. Unterschiede unten in Readme: https://github.com/hadley/xml2 –

+2

Fair genug. Xml2, nach der GitHub-Seite, und meine kurzen Erfahrungen, hat eine einfachere Schnittstelle. R wurde von R-Core-Entwicklern geschrieben und ist von Macken durchsetzt, während Hadley oft die Einfachheit gegenüber der Leistungsbilanz richtig macht und alles sauber und ordentlich macht. –

Antwort

20

Es kann nicht sein, wie ausführliche als XML Paket aber xml2 nicht die Speicherverluste haben, und ist die Laser-konzentrierten sich auf die Datenextraktion. Ich benutze trimws das ist ein wirklich jüngsten Zusatz zu R Core.

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 

UPDATE

Ich würde prbly das letzte Bit nun auf diese Weise tun:

library(tidyverse) 

strsplit(vals, "[[:space:]]+") %>% 
    map_df(~as_data_frame(as.list(setNames(., cols)))) %>% 
    mutate(area_name=labs) 
+0

Aufräumen ist ein Tippfehler. Auch as_data_frame scheint kein exportierter Gegenstand mehr zu sein. – userJT

+1

Tippfehler behoben, aber Sie sollten tatsächlich sowohl 'tibble' als auch 'dplyr' überprüfen, bevor Sie "nicht exportierte" Anweisungen machen. – hrbrmstr

+0

@hrbrmstr danke das sieht nach einer wirklich guten Lösung aus. Ich bin ein komplettes noob in 'xml', also verallgemeinert Ihr Code zu anderen' xml' Dateien? Gibt es zum Beispiel in jedem xml ein 'record'? 'recs <- xml_find_all (pg," // record ")' –

2

Hier ist, was ich gefunden habe. Es entspricht der olive oil csv file, die auch auf der gleichen Seite verfügbar ist. Sie zeigen X als den ersten Spaltennamen, aber ich sehe es nicht in der XML, also habe ich es nur manuell hinzugefügt.

Es wird wahrscheinlich am besten sein, es in Abschnitte aufzuteilen, und dann den endgültigen Datenrahmen zusammenzusetzen, sobald alle Teile vorliegen. Wir können auch die [.XML* Shortcuts für XPath und die anderen [[ Convenience Accessor-Funktionen verwenden.

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() 
4

Große Antworten oben! Wenn Sie zu einem späteren Zeitpunkt mit einem komplexen XML-Code konfrontiert werden, der einen R-Import erfordert, sollten Sie das XML-Dokument mit XSLT (eine deklarative deklarative Programmiersprache, die XML-Inhalte in verschiedene Endbenutzer-Anforderungen umwandelt) neu strukturieren. Dann verwenden Sie einfach Rs xmlToDataFrame() Funktion aus dem XML-Paket.

Leider verfügt R über kein dediziertes XSLT-Paket, das auf allen Betriebssystemen auf CRAN-R verfügbar ist. Das gelistete SXLT scheint ein Linux-Paket zu sein und kann nicht unter Windows verwendet werden. Siehe unbeantwortete SO-Fragen here und here. Ich verstehe @hrbrmstr (oben) unterhält eine GitHub XSLT project. Nichtsdestoweniger unterhalten fast alle Universalsprachen XSLT-Prozessoren, einschließlich Java, C#, Python, PHP, Perl und VB.

Unten ist die Open-Source-Python-Route und weil das XML-Dokument ziemlich nuanciert ist, werden zwei XSLTs verwendet (natürlich können XSLT-Gurus sie zu einem kombinieren, aber ich versuchte es nicht.

FIRST XSLT (unter Verwendung eines 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> 

SECOND 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 (mit lxml Modul)

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) 

Ausgabe

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 
    ... 

(leichte Bereinigung auf erster Platte wird als ein zusätzlicher Raum benötigt wurde hinzugefügt, nachdem "na" in xml doc, so wurden arachidic und eicosenoic nach vorne verschoben)

+0

Jetzt gibt es das 'xslt' Paket. Ich habe es selbst nicht versucht, aber es gibt keinen Hinweis darauf, dass es keine plattformübergreifenden Plattformen sind. https://cran.r-project.org/web/packages/xslt/index.html. (Scheint nichts mit https://github.com/hrbrmstr/xslt zu tun) –

+0

@ Aurèle - Endlich! Danke für die Nachricht. Ich werde diese xml2-Erweiterung testen. – Parfait