2015-10-31 9 views
17

Per un compito a casa Sto tentando di convertire un file XML in un frame di dati in R. Ho provato molte cose diverse, e ho cercato idee su Internet ma non ho avuto successo . Ecco il mio codice finora:R: convertire i dati XML nel frame dati

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) 

L'output che ottengo è come un vettore gigante di numeri. Sto tentando di organizzare i dati in un frame di dati, ma non so come regolare correttamente il mio codice per ottenerlo.

+3

Vorrei usare xml2, se non altro perché Hadley produce costantemente pacchetti di R molto utili e di alta qualità. Probabilmente andrà più liscio e sarà meglio documentato. Differenze in fondo a readme: https://github.com/hadley/xml2 –

+2

abbastanza giusto. xml2, secondo la pagina GitHub, e le mie brevi esperienze, ha un'interfaccia più semplice. R è stato scritto da sviluppatori del core R, ed è pieno di stranezze, mentre Hadley spesso ottiene la semplicità e il bilanciamento del potere giusto, e rende tutto pulito e ordinato. –

risposta

20

Esso non può essere così dettagliato come il pacchetto XML ma xml2 non ha le perdite di memoria ed è laser focalizzato sull'estrazione dati. Io uso trimws che è una veramente aggiunta recente a 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

avrei prbly faccio l'ultimo bit in questo modo la società:

library(tidyverse) 

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

tidyvere è un errore di battitura. anche as_data_frame sembra non essere più oggetto esportato. – userJT

+1

corretti errori di battitura, ma in effetti si controllano sia 'tibble' che' dplyr' prima di rendere dichiarazioni "non esportate" in questo modo. – hrbrmstr

+0

@hrbrmstr ringrazia che sembra davvero un'ottima soluzione. Sono un noob completo in 'xml', quindi il tuo codice è generalizzato ad altri file' xml'? Ad esempio, c'è un "record" in ogni xml? 'recs <- xml_find_all (pg," // record ")' –

2

Ecco cosa mi è venuto in mente. Corrisponde allo olive oil csv file disponibile anche nella stessa pagina. Mostrano X come nome della prima colonna, ma non lo vedo nell'xml, quindi l'ho aggiunto manualmente.

Probabilmente sarà meglio suddividerlo in sezioni, quindi assemblare il frame di dati finale una volta che avremo tutte le parti. Possiamo anche utilizzare le scorciatoie [.XML* per XPath e le altre funzioni di accesso facilitato [[.

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

grandi risposte di cui sopra! Per i lettori futuri, ogni volta che si affronta un XML complesso che richiede l'importazione R, prendere in considerazione la ristrutturazione del documento XML utilizzando XSLT (un linguaggio di programmazione dichiarativo per scopi speciali che manipola il contenuto XML in varie esigenze di utilizzo finale). Quindi utilizzare semplicemente la funzione xmlToDataFrame() di R dal pacchetto XML.

Sfortunatamente, R non ha un pacchetto XSLT dedicato disponibile su CRAN-R su tutti i sistemi operativi. Lo SXLT elencato sembra un pacchetto Linux e non può essere utilizzato su Windows. Vedi domande SO senza risposta here e here. Capisco @hrbrmstr (sopra) mantiene un GitHub XSLT project. Tuttavia, quasi tutti i linguaggi generici mantengono i processori XSLT tra cui Java, C#, Python, PHP, Perl e VB.

Di seguito è la rotta Python open-source e poiché il documento XML è piuttosto sfumato, vengono utilizzati due XSLT (ovviamente i guru XSLT possono combinarli in uno solo, ma ho provato perché non avrei potuto farlo funzionare.

FIRST XSLT (utilizzando un 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> 

SECONDA 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 (utilizzando il modulo 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) 

uscita

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

(leggera pulizia sul primo disco è necessario come uno spazio extra è stato aggiunto dopo "na" in XML doc, quindi arachidic e eicosenoic sono stati spostati in avanti)

+0

Ora c'è il pacchetto 'xslt'. Non l'ho provato da solo, ma non vi è alcuna indicazione che non si tratti di piattaforme incrociate. https://cran.r-project.org/web/packages/xslt/index.html. (Sembra non correlato a https://github.com/hrbrmstr/xslt) –

+0

@ Aurèle - Finalmente! Grazie per il messaggio. Metterò alla prova questa estensione xml2. – Parfait