Estoy trabajando con varios archivos XML que quiero comparar, cada uno con aproximadamente 200-300 diferentes 'xml: ids'. Digamos que hay tres archivos que contienen el siguiente xml: ids:

file1.xml

<?xml version="1.0" encoding="UTF-8"?>
<div>
<p xml:id= "F23_1b">1</p>
<p xml:id= "F54_34a">3</p>
</div>

file2.xml

<?xml version="1.0" encoding="UTF-8"?>
<div>
<p xml:id= "F23_1b">7</p>
<p xml:id= "F54_34a">8</p>
<p xml:id= "F54_63d">12</p>
</div>

file3.xml

<?xml version="1.0" encoding="UTF-8"?>
<div>
<p xml:id= "F143_32a">5</p>
<p xml:id= "F175_23c">6</p>
<p xml:id= "F95_1a">14</p>
<p xml:id= "F89_9d">15</p>
</div>

Ahora mi objetivo es comparar estos diferentes archivos con respecto a a) los xml actuales: id yb) sus respectivos valores (ver tabla a continuación). Comencé usando el paquete XML / XPath de R para crear una lista para cada archivo:

file1 <- xmlTreeParse("file1.xml", useInternalNodes = T)
a <- xpathSApply(file1, "//*[@xml:id]", xmlGetAttr, "xml:id")

file2 <- xmlTreeParse("file1.xml", useInternalNodes = T)
a <- xpathSApply(file1, "//*[@xml:id]", xmlGetAttr, "xml:id")

file3 <- xmlTreeParse("file1.xml", useInternalNodes = T)
a <- xpathSApply(file1, "//*[@xml:id]", xmlGetAttr, "xml:id")

Ahora, en un segundo paso, me gustaría combinar los resultados en un marco de datos pero, y este es mi problema principal, las listas no comparten la misma longitud. Al principio pensé que podría buscar la lista más larga y agregar 'valores vacíos' para xml: identificadores que están presentes en él pero no en los más cortos, pero rápidamente me di cuenta de que este enfoque ignoraría los identificadores que solo existen en el listas más cortas

Al final, me encantaría tener un marco de datos que se pueda exportar fácilmente (a .csv), similar a esta tabla:

|------------||-----------||-----------||-----------|
|  xml:ids   ||   file1   ||   file2   ||   file3   |
|------------||-----------||-----------||-----------|
|------------||-----------||-----------||-----------|
|  F23_1b    ||     1     ||     7     ||    NULL   |
|------------||-----------||-----------||-----------|
|  F54_34a   ||     3     ||     8     ||    NULL   |
|------------||-----------||-----------||-----------|
|  F54_63d   ||   NULL    ||    12     ||    NULL   |
|------------||-----------||-----------||-----------|
|  F143_32a  ||   NULL    ||    NULL   ||     5     |
|------------||-----------||-----------||-----------|
|  F175_23c  ||   NULL    ||    NULL   ||     6     |
|------------||-----------||-----------||-----------|
|  F95_1a    ||   NULL    ||    NULL   ||     14    |
|------------||-----------||-----------||-----------|
|  F89_9d    ||   NULL    ||    NULL   ||     15    |
|------------||-----------||-----------||-----------|   

¿Tienes alguna sugerencia sobre mi problema?

r xml
0
macright 7 mar. 2018 a las 05:37

3 respuestas

La mejor respuesta

Si usa xml2 y ronronea, podría verse algo así como

library(tidyverse)
library(xml2) 


xml_data <- sprintf('file%s.xml', 1:3) %>%    # make filepaths
    map_df(~read_xml(.x) %>%    # iterate over filenames; read xml
               xml_find_all('//p') %>%    # select p nodes
               map_df(function(.y) {   # iterate over nodes and combine to data frame of...
                   list(file = basename(.x),    # the filename, 
                        id = xml_attr(.y, 'id'),    # the id attribute, and
                        value = as.integer(xml_text(.y)))    # the node value.
               }))

xml_data
#> # A tibble: 9 x 3
#>   file      id       value
#>   <chr>     <chr>    <int>
#> 1 file1.xml F23_1b       1
#> 2 file1.xml F54_34a      3
#> 3 file2.xml F23_1b       7
#> 4 file2.xml F54_34a      8
#> 5 file2.xml F54_63d     12
#> 6 file3.xml F143_32a     5
#> 7 file3.xml F175_23c     6
#> 8 file3.xml F95_1a      14
#> 9 file3.xml F89_9d      15

Si realmente quieres extenderlo a una forma amplia, desde aquí es bastante típico:

xml_data %>% 
    mutate(file = sub('.xml$', '', file)) %>% 
    spread(file, value)
#> # A tibble: 7 x 4
#>   id       file1 file2 file3
#>   <chr>    <int> <int> <int>
#> 1 F143_32a    NA    NA     5
#> 2 F175_23c    NA    NA     6
#> 3 F23_1b       1     7    NA
#> 4 F54_34a      3     8    NA
#> 5 F54_63d     NA    12    NA
#> 6 F89_9d      NA    NA    15
#> 7 F95_1a      NA    NA    14
2
alistaire 7 mar. 2018 a las 03:13

Aquí hay una solución que utiliza xml2 y dplyr::full_join:

 # Read XML files
 library(xml2);
 fn <- paste0("file", 1:3, ".xml");
 files <- lapply(fn, read_xml);

 # Extract node attributes and values, store as data.frame
 lst <- lapply(files, function(x)
     cbind.data.frame(
         id = xml_attr(xml_children(x), "id"),
         val = as.numeric(xml_text(xml_children(x))),
         stringsAsFactors = F))

 # Outer full join on all data.frame's in list
 df <- Reduce(function(x, y) dplyr::full_join(x, y, by = "id"), lst)
 colnames(df)[2:ncol(df)] <- fn;
 df;
 #        id file1.xml file2.xml file3.xml
 #1   F23_1b         1         7        NA
 #2  F54_34a         3         8        NA
 #3  F54_63d        NA        12        NA
 #4 F143_32a        NA        NA         5
 #5 F175_23c        NA        NA         6
 #6   F95_1a        NA        NA        14
 #7   F89_9d        NA        NA        15

Explicación: Leer archivos XML con xml2::read_xml; extraer atributos y valores de nodo con xml_attr y xml_text, respectivamente, y almacenar como list de data.frame s; realice una unión externa completa en data.frame s en list.

0
Maurits Evers 7 mar. 2018 a las 03:09

No sé qué tan flexible eres en tu elección de tecnología, pero aquí hay una solución en XSLT 3.0

<xsl:variable name="doc1" select="doc('file1.xml')"/>
<xsl:variable name="doc2" select="doc('file2.xml')"/>
<xsl:variable name="doc3" select="doc('file3.xml')"/>
<xsl:merge>
  <xsl:merge-source for-each-source="($doc1, $doc2, doc3)" select=".//p[@xml:id]">
    <xsl:merge-key select="@xml:id" sort-before-merge="yes"/>
  </xsl:merge-source>
  <xsl:merge-action>
    <tr>
      <td>{current-merge-key()}</td>
      <xsl:for-each select="($doc1, $doc2, doc3)">
         <td>{(current-merge-group()[(/) is current()], 'NA')[1]}</td>
      </xsl:for-each>
    </tr>
  </xsl:merge-action>
</xsl:merge>

No probado. Fácilmente generalizado a N documentos de entrada.

0
Michael Kay 7 mar. 2018 a las 08:47