Я пытаюсь преобразовать XML со сложной структурой узлов в фрейм данных, используя R. Это краткий пример файла 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
1
JCMendes 2 Янв 2020 в 17:04

2 ответа

Лучший ответ

Я менее знаком с пакетом XML, но больше использовал пакет xml2. Он подходит для пакетов tidyverse, поэтому хорошо работает с подходом, основанным на purrr, который я буду использовать здесь. Для каждого узла <product> я вызываю функцию, которая извлекает все его дочерние узлы 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
5
camille 2 Янв 2020 в 15:01

Привет, 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 = ".")
0
Bruno 2 Янв 2020 в 15:58