R将XML(复杂结构)转换为数据帧

时间:2020-01-02 14:04:15

标签: r xml dataframe

我正在尝试使用R将具有复杂节点结构的XML转换为数据帧。这是XML文件的简短示例:

<products>
    <product>
        <id>1</id>
        <data>
            <data_value>
                <number>12345</number>
                <city>London</city>
            </data_value>
        </data>
        <attributes>
            <p_attribute>
                <name>Name_1</name>
                <value>Value_1</value>
            </p_attribute>
            <p_attribute>
                <name>Name_2</name>
                <value>Value_2</value>
            </p_attribute>
        </attributes>
    </product>
    <product>
        <id>2</id>
        <data>
            <data_value>
                <number>98765</number>
                <city>London</city>
            </data_value>
        </data>
        <attributes>
            <p_attribute>
                <name>Name_9</name>
                <value>Value_9</value>
            </p_attribute>
            <p_attribute>
                <name>Name_8</name>
                <value>Value_8</value>
            </p_attribute>
        </attributes>
    </product>
</products>

当我尝试将此文件转换为数据框时,我使用以下代码(XML库)

library(XML)
doc=xmlParse("file.xml")
xmldf=xmlToDataFrame(nodes = getNodeSet(doc, "//product"))

然后,最终结果是可以在下面看到的数据框:

  id        data                 attributes
1  1 12345London Name_1Value_1Name_2Value_2
2  2 98765London Name_9Value_9Name_8Value_8

如何获得不同的数据框架,从而消除XML文件的复杂结构,以获得类似的结果?

  id number   city name.1 value.1 name.2 value.2
1  1  12345 London Name_1 Value_1 Name_2 Vlaue_2
2  2  98765 London Name_9 Value_9 Name_8 Value_8

2 个答案:

答案 0 :(得分:5)

我不太熟悉XML软件包,但更多地使用了xml2软件包。它适合tidyverse软件包,因此可以与我将在此处使用的基于purrr的方法配合使用。对于每个<product>节点,我正在调用一个函数,该函数提取其所有子ID,数字,城市,名称和值节点,并提取其文本。我按产品进行操作是因为我想为每个对象获取一个小的数据框,以确保所有ID与名称和值节点保持在一起,从而允许它们具有不同的长度。最后,map_dfr按行绑定数据帧列表。

library(tidyr)
library(purrr)
library(xml2)


products <- read_xml("text.xml") %>%
  xml_find_all("//product")

prod_df <- map_dfr(products, function(p_node) {
  list(".//id", ".//number", ".//city", ".//name", ".//value") %>%
    set_names(stringr::str_extract, "\\w+") %>%
    map(~xml_find_all(p_node, .)) %>%
    map(xml_text) %>%
    as_tibble()
})

prod_df
#> # A tibble: 4 x 5
#>   id    number city   name   value  
#>   <chr> <chr>  <chr>  <chr>  <chr>  
#> 1 1     12345  London Name_1 Value_1
#> 2 1     12345  London Name_2 Value_2
#> 3 2     98765  London Name_9 Value_9
#> 4 2     98765  London Name_8 Value_8

我个人建议使用这种格式,尤其是因为您可能为不同的产品使用不同数量的名称/值对。但是,如果您确实需要宽格式,则可以为每个产品的子级标记一个观察号,然后重塑形状。

prod_df %>%
  dplyr::group_by(id, number, city) %>%
  dplyr::mutate(obs = dplyr::row_number()) %>%
  pivot_wider(names_from = obs, values_from = c(name, value), names_sep = ".")
#> # A tibble: 2 x 7
#> # Groups:   id, number, city [2]
#>   id    number city   name.1 name.2 value.1 value.2
#>   <chr> <chr>  <chr>  <chr>  <chr>  <chr>   <chr>  
#> 1 1     12345  London Name_1 Name_2 Value_1 Value_2
#> 2 2     98765  London Name_9 Name_8 Value_9 Value_8

答案 1 :(得分:0)

您好,JCMendes您可以使用tidyverse解决该问题

不幸的是,这不是很可扩展,我也建议您使用长数据

 x <- xmldf %>% 
    mutate(number = data %>% str_extract("[:digit:]{1,}"),
           city = data %>% str_extract("[:alpha:]{1,}"),
           characterss = str_split(attributes,"(?=[[:upper:]])"),
           name = characterss %>% map(keep,str_detect,"Name"),
           value= characterss %>% map(keep,str_detect,"Value")) %>%
    select(-attributes,-data,-characterss) %>%
    unnest(name) %>%
    unnest(value) %>% 
    group_by(id, number, city) %>% 
    dplyr::mutate(obs = dplyr::row_number()) %>%
    pivot_wider(names_from = obs, values_from = c(name, value), names_sep = ".")