Самый эффективный способ извлечения данных из больших файлов XML в R

У меня есть несколько больших (~ 10 ГБ и растет каждую неделю), которые мне нужно преобразовать из XML в фреймворк в R для анализа. Структура XML следующая (с несколькими записями и несколькими дополнительными элементами полей на запись):

<recordGroup>
  <records>
    <record>
      <recordId>123442</recordId>
      <reportingCountry>PT</reportingCountry>
      <date>2020-02-20</date>
      <field>
        <fieldName>Gender</fieldName>
        <fieldValue>F</fieldValue>
      </field>
      <field>
        <fieldName>Age</fieldName>
        <fieldValue>57</fieldValue>
      </field>
      <field>
        <fieldName>ClinicalSymptoms</fieldName>
        <fieldValue>COUGH</fieldValue>
        <fieldValue>FEVER</fieldValue>
        <fieldValue>O</fieldValue>
        <fieldValue>RUNOS</fieldValue>
        <fieldValue>SBREATH</fieldValue>
      </field>
    </record>
  </records>
</recordGroup>

Я пытался найти наиболее эффективный способ извлечения данных и преобразования их в data.frame, однако одна из основных проблем заключается в том, что файлы довольно большие, и как XML, так и XML2 сталкиваются с проблемами, на обработку которых уходит часы. Моя текущая стратегия использует xmlEventParse с использованием приведенного ниже кода, но это кажется еще более неэффективным.

value_df <- data.frame(recordId = as.character(), vardf = as.character(), value = as.character())
nvar <- 0

xmlEventParse(xmlDoc_clean,
              list(
                startElement = function (name, attrs) {
                  tagName <<- name
                },
                text = function (x) {
                  if (nchar(x) > 0) {
                    if (tagName == "recordId") {
                      rec <<- x
                    } else
                    if (tagName == "fieldName") {
                      var_f <<- x
                    } else {
                      if (tagName == 'fieldValue') {
                        v <- x
                         nvar <<- nvar + 1
                       value_df[nvar, 1:3] <<- c(rec, var_f, v)
                      }
                    }
                  }
                },
                endElement = function (name) {
                  if (name == 'record') {
                    print(nvar)
                  }
                }
              ))

Я пробовал XML2 (проблемы с памятью), XML (проблемы с памятью также при стандартном парсинге DOM), а также собирался попытаться использовать XMLSchema, но не смог заставить его работать. И XML, и XML2 работают, если файлы разделены.

Буду признателен за любые советы по повышению эффективности, поскольку файлы, с которыми я работаю, становятся больше каждую неделю. Я использую R на Linux-машине.


person Gakku    schedule 29.12.2020    source источник
comment
Вы видели здесь обновление в ответе? stackoverflow.com/questions/33446888 /   -  person ibilgen    schedule 29.12.2020
comment
@ibilgen Спасибо, да - я пробовал аналогичные подходы, но пытаюсь найти наиболее эффективный способ, так как мои наборы данных очень большие и на анализ с XML2 может уйти несколько часов. Я еще не понял, как использовать XSLT, который упоминается в другом ответе на этот вопрос.   -  person Gakku    schedule 30.12.2020
comment
См. R: xmlEventParse с большим вводом XML с различными узлами и преобразованием во фрейм данных. В принятом решении используется xmlEventParse.   -  person Parfait    schedule 30.12.2020
comment
@Parfait, подход с использованием веток, похоже, также работает и выглядит немного быстрее, чем использование обработчиков. Я продолжу работать над этим и опубликую код, надеюсь, может быть полезен другим.   -  person Gakku    schedule 30.12.2020


Ответы (2)


Когда проблема с памятью, подумайте о жестком диске. В частности, рассмотрите возможность создания большой CSV-версии извлеченных проанализированных данных XML с итеративными вызовами добавления через write.csv в прогоне xmlEventParse:

# INITIALIZE EMPTY CSV WITH EMPTY ROW
csv <- file.path("C:", "Path", "To", "Large.csv")
fileConn <- file(csv); writeLines(paste0("id,tag,text"), fileConn); close(fileConn)

i <- 0
doc <- file.path("C:", "Path", "To", "Large.xml")
output <- xmlEventParse(doc,
                        list(startElement=function(name, attrs){
                          if(name == "recordId") {i <<- i + 1}
                          tagName <<- name
                        }, text=function(x) {
                          if(nchar(trimws(x)) > 0) {
                            write.table(data.frame(id=i, tag=tagName, text=x), 
                                        file=csv, append=TRUE, sep=",", 
                                        row.names=FALSE, col.names=FALSE)
                          }
                        }),
                        useTagName=FALSE, addContext=FALSE)

Вывод

Очевидно, что для правильной миграции строк / столбцов потребуется дальнейшая обработка данных. Но теперь вы можете читать большие CSV-файлы с помощью множества инструментов или фрагментов.

id,tag,text
1,"recordId","123442"
1,"reportingCountry","PT"
1,"date","2020-02-20"
1,"fieldName","Gender"
1,"fieldValue","F"
1,"fieldName","Age"
1,"fieldValue","57"
1,"fieldName","ClinicalSymptoms"
1,"fieldValue","COUGH"
1,"fieldValue","FEVER"
1,"fieldValue","O"
1,"fieldValue","RUNOS"
1,"fieldValue","SBREATH"
person Parfait    schedule 29.12.2020
comment
Спасибо - я пробовал аналогичный подход, но не смог. Ваш пример работает очень хорошо, но запись в csv, похоже, требует времени. Ему удалось сделать около 100 записей за минуту. Я попытался преобразовать ваш код для создания фрейма данных, который работает немного быстрее, но все же довольно медленно (10 000 записей за 3,5 минуты - мне нужно пройти 1,5 миллиона). У меня такое чувство, что я должен смириться с тем, что все, что меньше часа, - достаточно хорошо! - person Gakku; 30.12.2020
comment
Оперативная память обычно быстрее, чем чтение с диска. Но избегайте увеличения объема большого фрейма данных в памяти. Это решение пытается избежать увеличения объема оперативной памяти. Перезагрузите компьютер и избегайте запуска других приложений и IDE, таких как RStudio, и запустите это в командной строке с помощью RScript. Храните XML или CSV не на внешних или сетевых дисках, а на жестком диске. - person Parfait; 30.12.2020

В конце концов, самый быстрый подход, который я нашел, был следующий:

  1. Разделите файлы XML на более мелкие части, используя XML2. У меня есть ›100 ГБ ОЗУ на сервере, над которым я работаю, поэтому я могу распараллелить этот процесс, используя foreach с 6 рабочими, но пробег зависит от того, сколько ОЗУ доступно.
  2. Функция разделения файлов возвращает data.frame с расположением разделенных файлов.
  3. Обработайте XML-файлы меньшего размера в foreach цикле - на этот раз можно использовать все ядра, поэтому я использовал 12 рабочих. При обработке используется XML2, поскольку я обнаружил, что это самый быстрый способ. Первоначально извлеченные данные имеют длинный формат, но затем я конвертирую в широкий формат в цикле.
  4. Цикл связывает и выводит разные фреймы данных в один большой фрейм данных. Последний шаг - использовать fwrite для сохранения файла csv. Это кажется наиболее эффективным способом.

При таком подходе я могу обработать XML-файл размером 2,6 ГБ за 6,5 минут.

В конце концов, я добавлю код, но он довольно конкретный, поэтому нужно немного обобщить.

person Gakku    schedule 09.01.2021
comment
Как вы разделили файлы XML с помощью XML2? Я столкнулся с теми же проблемами, но пока не нашел удовлетворительного решения. - person an_ja; 21.05.2021