Эффективность скорости - R для петли

Следующий код используется для синтаксического анализа XML с целью извлечения такой информации, как узел, родительский элемент, тип и т. Д., Во фрейм данных. Он отлично работает для небольшого XML-файла со строками, но когда используется файл, содержащий более 25000 строк, его обработка занимает пару минут. Следовательно, я намерен оптимизировать код, чтобы он работал быстрее. Цель функции - прочитать любой XML-файл и сгенерировать данные в соответствии с требованиями фрейма данных.

Пример XML:

<?xml version="1.0" encoding="UTF-8"?>
<CATALOG>
   <PLANT id="1" required="false">
      <COMMON Source="NLM">Bloodroot</COMMON>
      <BOTANICAL>Aquilegia canadensis</BOTANICAL>
      <DATE>
         <Year>2013</Year>
      </DATE>
   </PLANT>
   <PLANT id="2" required="true">
      <COMMON Source="LNP">Columbine</COMMON>
      <BOTANICAL>Aquilegia canadensis</BOTANICAL>
      <DATE>
         <Year>2014</Year>
      </DATE>
   </PLANT>
</CATALOG>

Выход:

                      path      node                value  parent      type
1                  CATALOG   CATALOG                 NULL    NULL   element
2            CATALOG/PLANT     PLANT                 NULL CATALOG   element
3            CATALOG/PLANT        id                    1   PLANT attribute
4            CATALOG/PLANT  required                false   PLANT attribute
5     CATALOG/PLANT/COMMON    COMMON            Bloodroot   PLANT      text
6     CATALOG/PLANT/COMMON    Source                  NLM  COMMON attribute
7  CATALOG/PLANT/BOTANICAL BOTANICAL Aquilegia canadensis   PLANT      text
8       CATALOG/PLANT/DATE      DATE                 NULL   PLANT   element
9  CATALOG/PLANT/DATE/Year      Year                 2013    DATE      text
10           CATALOG/PLANT     PLANT                 NULL CATALOG   element
11           CATALOG/PLANT        id                    2   PLANT attribute
12           CATALOG/PLANT  required                 true   PLANT attribute
13    CATALOG/PLANT/COMMON    COMMON            Columbine   PLANT      text
14    CATALOG/PLANT/COMMON    Source                  LNP  COMMON attribute
15 CATALOG/PLANT/BOTANICAL BOTANICAL Aquilegia canadensis   PLANT      text
16      CATALOG/PLANT/DATE      DATE                 NULL   PLANT   element
17 CATALOG/PLANT/DATE/Year      Year                 2014    DATE      text

Фрагмент кода:

library(XML)
library(plyr)

## helper function of xPathApply
getValues <- function(x) {
  List <- list()

  # find all ancestors of a given node
  ancestorNames <- character()  
  ancestorNamesList <- xmlAncestors(x, fun = function(y) {
    ancestorNames <- c(ancestorNames, xmlName(y))})  
  pathName <- paste(ancestorNamesList, collapse = "/")

  # find the parent of a given node
  parentNode <- xmlParent(x)
  parentName <- "NULL"
  if(!is.null(parentNode)) {
    parentName <- xmlName(parentNode)
  } 

  if(inherits(x, "XMLInternalElementNode")) {
    # check if the value of the given node exists i.e. text
    if(length(xmlValue(x, recursive=FALSE)) != 0) {
      List <- append(List, list(path = pathName, node = xmlName(x), value = xmlValue(x, recursive=FALSE), parent = parentName, type = "text"))
    } else {
      List <- append(List, list(path = pathName, node = xmlName(x), value = "NULL", parent = parentName, type = "element"))      
    }
  }

  ## attributes
  if(!is.null(xmlAttrs(x))) {
    num.attributes = xmlSize(xmlAttrs(x))
    for (i in seq_len(num.attributes)) {
      # get the attribute name
      attributeName <- names(xmlAttrs(x)[i])
      # get the attribute value
      attributeValue <- xmlAttrs(x)[[i]]  

      List <- append(List, list(path = pathName, node = attributeName, value = attributeValue, parent = parentName, type = "attribute"))      
    }
  }

  return(List)
}

## recursive function 
visitNode <- function(node, xpath) {
  if (is.null(node)) {
    return()
  }

  # number of children of a node
  num.children <- xmlSize(node)

  bypass <- function(n = num.children) {
    if(num.children == 0) {
      xpathSApply(node, path = xpath, getValues)
    } else {
      return(num.children)
    }
  }

  # recursive call to visitNode 
  for (i in seq_len(num.children)) { 
    visitNode(node[[i]], xpath) 
  }   

  # add list type result to data frame
  if(is.list(result <- bypass())) {    
    dt <<- do.call(rbind.fill, lapply(result, data.frame)) 
  }
} 


# read XML data from the given file
xtree <- xmlParse("test.xml")

# retrieve the root of the XML
root <- xmlRoot(xtree)

# define data frame which is to hold the data interpreted from XML
dt <- data.frame(path = NA, node = NA, value = NA, parent = NA, type = NA)

# call to recursive function
visitNode(root, xpath <- "//node()")

dt

person user2877232    schedule 17.12.2014    source источник
comment
Основная неэффективность, которую я вижу, заключается в том, что объект List заранее не определяет размер. Использование c () для расширения списков может быть очень неэффективным. Использование sapply не излечит эту патологию. Посмотрите, List <- list(xmlSize(xmlAttrs(x)) ) и простая индексация List по i ускоряют работу.   -  person IRTFM    schedule 17.12.2014
comment
List[[length(List)+1]] неверно. Вероятно, это должно быть List[[i]]. Я пробовал это на образце xml, и он возвращает пустой список   -  person Rich Scriven    schedule 17.12.2014
comment
Пожалуйста, Ричард, приведи людей в предварительное измерение.   -  person IRTFM    schedule 17.12.2014
comment
Да, но непросто сказать, сколько атрибутов может иметь XML-документ.   -  person Rich Scriven    schedule 17.12.2014
comment
Я не могу использовать List [[i]], потому что я добавляю в список другие вещи, такие как элементы и текст перед циклом атрибутов.   -  person user2877232    schedule 17.12.2014
comment
Я бы использовал xmlApply(x, ...), где x xmlRoot(doc)   -  person Rich Scriven    schedule 17.12.2014
comment
Должен сказать, что я хотел помочь с вашими вопросами по xml, но они настолько неясны, что я расстраиваюсь и ухожу. Это почти то же самое, потому что вы не показываете никаких данных примера и желаемого результата. При работе с xml должны быть некоторые правила, потому что многие узлы совершенно разные, и поэтому результат этой функции может быть нежелательным. Если бы вы могли добавить немного контекста к этому вопросу, было бы здорово   -  person Rich Scriven    schedule 17.12.2014
comment
@RichardScriven Я обновил свой пост. Надеюсь, теперь все ясно. Часть извлечения атрибутов, которую я опубликовал ранее, - это небольшая часть этого кода, которая, на мой взгляд, недостаточно эффективна. Поскольку у меня есть весь опубликованный код, не могли бы вы дать несколько общих предложений по повышению эффективности?   -  person user2877232    schedule 17.12.2014


Ответы (1)


Я действительно хотел бы, чтобы в R была хорошая поддержка XSLT, но я не могу найти для нее отличный пакет. Другой стратегией было бы преобразование xml в более простой файл данных, который вы можете легко прочитать с помощью read.table или чего-то еще. Вы можете легко пройти его с xmlEventParse. Вот пользовательский обработчик, который, кажется, создает нужные вам данные.

getHandler<-function(file="", sep=",") {
    list(.startDocument = function(.state) {
           cat("path","node","value","parent","type", file=file, sep=sep)
           cat("\n", file=file, sep=sep, append=T)
           .state
    }, .startElement=function(name, atts, .state) {
       .state$path <- c(.state$path, name)
       cat(paste(.state$path, collapse="/"), name, NA, .state$path[length(.state$path)-1], "element", sep=sep, file=file, append=T)
       cat("\n",  file=file, append=T)
       if(!is.null(atts)) {
           cat(paste(paste(.state$path, collapse="/"), names(atts), atts, .state$path[length(.state$path)-1], "attribute", sep=sep, collapse="\n"), file=file, append=T)
           cat("\n",file=file, append=T)
       }
       .state
    }, .endElement=function(name, .state) {
       .state$path <- .state$path[-length(.state$path)]
       .state
    }, .text=function(value, .state) {
       value <- gsub("^\\s+|\\s+$", "", value)
       if(nchar(value)>0) {
           cat(paste(.state$path, collapse="/"), .state$path[length(.state$path)], value, .state$path[length(.state$path)-1], "text", sep=sep, file=file, append=T)
           cat("\n", file=file, append=T)
       }
       .state
    })
}

Так что это не совсем красиво, но в основном это просто создание строки с cat(). Затем мы можем использовать его с

zz <- xmlEventParse("test.xml",
    handlers = getHandler(), 
    state = list(path=character(0)), useDotNames=TRUE)

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

zz <- xmlEventParse("test.xml",
    handlers = getHandler(file="ok.txt", sep="\t"), 
    state = list(path=character(0)), useDotNames=TRUE)

который запишет данные с разделителями в файл с именем «ok.txt». Затем вы можете прочитать данные с помощью

read.table("ok.txt", sep="\t", header=T)

который возвращается

                      path      node                value  parent      type
1                  CATALOG   CATALOG                 <NA>           element
2            CATALOG/PLANT     PLANT                 <NA> CATALOG   element
3            CATALOG/PLANT        id                    1 CATALOG attribute
4            CATALOG/PLANT  required                false CATALOG attribute
5     CATALOG/PLANT/COMMON    COMMON                 <NA>   PLANT   element
6     CATALOG/PLANT/COMMON    Source                  NLM   PLANT attribute
7     CATALOG/PLANT/COMMON    COMMON            Bloodroot   PLANT      text
8  CATALOG/PLANT/BOTANICAL BOTANICAL                 <NA>   PLANT   element
9  CATALOG/PLANT/BOTANICAL BOTANICAL Aquilegia canadensis   PLANT      text
10      CATALOG/PLANT/DATE      DATE                 <NA>   PLANT   element
11 CATALOG/PLANT/DATE/Year      Year                 <NA>    DATE   element
12 CATALOG/PLANT/DATE/Year      Year                 2013    DATE      text
13           CATALOG/PLANT     PLANT                 <NA> CATALOG   element
14           CATALOG/PLANT        id                    2 CATALOG attribute
15           CATALOG/PLANT  required                 true CATALOG attribute
16    CATALOG/PLANT/COMMON    COMMON                 <NA>   PLANT   element
17    CATALOG/PLANT/COMMON    Source                  LNP   PLANT attribute
18    CATALOG/PLANT/COMMON    COMMON            Columbine   PLANT      text
19 CATALOG/PLANT/BOTANICAL BOTANICAL                 <NA>   PLANT   element
20 CATALOG/PLANT/BOTANICAL BOTANICAL Aquilegia canadensis   PLANT      text
21      CATALOG/PLANT/DATE      DATE                 <NA>   PLANT   element
22 CATALOG/PLANT/DATE/Year      Year                 <NA>    DATE   element
23 CATALOG/PLANT/DATE/Year      Year                 2014    DATE      text

Теперь строк больше, чем в вашем примере, но некоторые правила выбора были мне не так понятны.

Основная идея состоит в том, что xmlEventParse более эффективен, чем xmlParse, потому что ему не нужно загружать все дерево. Кроме того, используя cat() для дампа в файл, мне не нужно сразу беспокоиться об управлении памятью (но это не совсем то, что запись на диск - это тоже так здорово).

В любом случае, это еще один вариант, который стоит рассмотреть.

person MrFlick    schedule 17.12.2014