Разбор большого XML в dataframe в R - PullRequest
0 голосов
/ 27 декабря 2018

У меня есть большие файлы XML, которые я хочу превратить в кадры данных для дальнейшей обработки в R и других программах.Все это делается в macOS.

Каждый ежемесячный XML имеет размер около 1 ГБ, имеет 150 тыс. Записей и 191 переменную.В конце концов, мне могут не понадобиться полные 191 переменная, но я бы хотел сохранить их и принять решение позже.

Доступ к XML-файлам можно получить здесь (прокрутите список до конца для ежемесячногомол, в несжатом виде нужно смотреть на «dming» XML)

Я добился определенного прогресса, но обработка больших файлов занимает слишком много времени (см. ниже)

XML выглядит так:

<ROOT>
 <ROWSET_DUASDIA>
  <ROW_DUASDIA NUM="1">
   <variable1>value</variable1>
   ...
   <variable191>value</variable191>
  </ROW_DUASDIA>
  ...
  <ROW_DUASDIA NUM="150236">
   <variable1>value</variable1>
   ...
   <variable191>value</variable191>
  </ROW_DUASDIA>
 </ROWSET_DUASDIA>
</ROOT>

Надеюсь, это достаточно ясно.Это мой первый раз, когда я работаю с XML.

Я посмотрел здесь много ответов и фактически сумел перевести данные в фрейм данных, используя меньшую выборку (используя ежедневный XML вместо ежемесячных)и xml2.Вот что я сделал

library(xml2) 

raw <- read_xml(filename)

# Find all records
dua <- xml_find_all(raw,"//ROW_DUASDIA")

# Create empty dataframe
dualen <- length(dua)
varlen <- length(xml_children(dua[[1]]))
df <- data.frame(matrix(NA,nrow=dualen,ncol=varlen))

# For loop to enter the data for each record in each row
for (j in 1:dualen) {
  df[j, ] <- xml_text(xml_children(dua[[j]]),trim=TRUE)
}

# Name columns
colnames(df) <- c(names(as_list(dua[[1]])))

Я думаю, что это довольно элементарно, но я также довольно плохо знаком с R.

В любом случае, это хорошо работает с ежедневными данными (записи 4-5k), ноэто вероятно слишком неэффективно для 150k записей, и на самом деле я ждал пару часов, и это еще не закончилось.Конечно, мне нужно будет запускать этот код только раз в месяц, но я бы все же хотел его улучшить.

Я пытался превратить элементы для всех записей в список, используя функцию as_list в xml2так что я мог бы продолжить с plyr, но это также заняло слишком много времени.

Заранее спасибо.

Ответы [ 2 ]

0 голосов
/ 28 декабря 2018

Хотя нет гарантии лучшей производительности для больших XML-файлов, пакет («old school») XML поддерживает компактный обработчик фрейма данных xmlToDataFrame для плоских XML-файлов, подобных вашим.Любые недостающие узлы, доступные в других братьях и сестрах, приводят к NA для соответствующих полей.

library(XML)

doc <- xmlParse("/path/to/file.xml")
df <- xmlToDataFrame(doc, nodes=getNodeSet(doc, "//ROW_DUASDIA"))

Вы даже можете загружать ежедневные почтовые индексы, распаковывать необходимые XML-файлы и анализировать их во фрейме данных, если большие ежемесячные XML-файлы создают проблемы с памятью.Например, ниже извлекаются ежедневные данные за декабрь 2018 года в список фреймов данных, которые должны быть связаны строкой в ​​конце.Процесс даже добавляет поле DDate .Метод обернут в tryCatch из-за отсутствия последовательных дней или из-за других проблем с URL или почтовым индексом.

dec_urls <- paste0(1201:1231)
temp_zip <- "/path/to/temp.zip"
xml_folder <- "/path/to/xml/folder"

xml_process <- function(dt) {      
  tryCatch({
    # DOWNLOAD ZIP TO URL
    url <- paste0("ftp://ftp.aduanas.gub.uy/DUA%20Diarios%20XML/2018/dd2018", dt,".zip")
    file <- paste0(xml_folder, "/dding2018", dt, ".xml")

    download.file(url, temp_zip)
    unzip(temp_zip, files=paste0("dding2018", dt, ".xml"), exdir=xml_folder)
    unlink(temp_zip)           # DESTROY TEMP ZIP

    # PARSE XML TO DATA FRAME
    doc <- xmlParse(file)        
    df <- transform(xmlToDataFrame(doc, nodes=getNodeSet(doc, "//ROW_DUASDIA")),
                    DDate = as.Date(paste("2018", dt), format="%Y%m%d", origin="1970-01-01"))
    unlink(file)               # DESTROY TEMP XML

    # RETURN XML DF
    return(df)
  }, error = function(e) NA)      
}

# BUILD LIST OF DATA FRAMES
dec_df_list <- lapply(dec_urls, xml_process)

# FILTER OUT "NAs" CAUGHT IN tryCatch
dec_df_list <- Filter(NROW, dec_df_list)

# ROW BIND TO FINAL SINGLE DATA FRAME
dec_final_df <- do.call(rbind, dec_df_list)
0 голосов
/ 27 декабря 2018

Вот решение, которое обрабатывает весь документ одновременно, а не читает каждую из 150 000 записей в цикле.Это должно обеспечить значительное повышение производительности.

Эта версия также может обрабатывать случаи, когда количество переменных в записи отличается.

library(xml2)
doc<-read_xml('<ROOT>
 <ROWSET_DUASDIA>
              <ROW_DUASDIA NUM="1">
              <variable1>value1</variable1>
              <variable191>value2</variable191>
              </ROW_DUASDIA>
              <ROW_DUASDIA NUM="150236">
              <variable1>value3</variable1>
              <variable2>value_new</variable2>
              <variable191>value4</variable191>
              </ROW_DUASDIA>
              </ROWSET_DUASDIA>
              </ROOT>')

#find all of the nodes/records
nodes<-xml_find_all(doc, ".//ROW_DUASDIA")

#find the record NUM and the number of variables under each record
nodenum<-xml_attr(nodes, "NUM")
nodeslength<-xml_length(nodes)

#find the variable names and values
nodenames<-xml_name(xml_children(nodes))
nodevalues<-trimws(xml_text(xml_children(nodes)))

#create dataframe
df<-data.frame(NUM=rep(nodenum, times=nodeslength), 
       variable=nodenames, values=nodevalues, stringsAsFactors = FALSE)

#dataframe is in a long format.  
#Use the function cast, or spread from the tidyr to convert wide format
#      NUM    variable    values
# 1      1   variable1    value1
# 2      1 variable191    value2
# 3 150236   variable1    value3
# 4 150236   variable2 value_new
# 5 150236 variable191    value4

#Convert to wide format
library(tidyr)
spread(df, variable, values)
...