Как получить образец заданного размера из большого XML-файла в R?

В отличие от простого чтения случайных строк, здесь необходимо сохранить структуру XML-файла, чтобы R мог прочитать ее в соответствующий data.frame.

Возможное решение - прочитать весь файл, а затем выбрать строки, но можно ли читать только необходимые фрагменты?

Образец из файла:

<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product>
    <sku>967190</sku>
    <productId>98611</productId>
...
    <listingId/>
    <sellerId/>
    <shippingRestrictions/>
  </product>
...

Количество строк для каждого «товара» не равно. Конечное количество записей до открытия файла неизвестно.

2
Anton Tarasenko 21 Дек 2013 в 17:33

2 ответа

Лучший ответ

Вместо чтения всего файла можно использовать синтаксический анализ событий с closure, который обрабатывает интересующие вас узлы. Чтобы добраться туда, я начну со стратегии случайной выборки из файла. Обрабатывайте записи по одной. Если i -я запись меньше или равна количеству n записей, которые необходимо сохранить, сохраните ее, в противном случае сохраните ее с вероятностью n / i. Это может быть реализовано как

i <- 0L; n <- 10L
select <- function() {
    i <<- i + 1L
    if (i <= n)
        i
    else {
        if (runif(1) < n / i)
            sample(n, 1)
        else
            0
    }
}

Который ведет себя так:

> i <- 0L; n <- 10L; replicate(20, select())
 [1]  1  2  3  4  5  6  7  8  9 10  1  5  7  0  1  9  0  2  1  0

Это говорит нам сохранить первые 10 элементов, затем мы заменяем элемент 1 элементом 11, элемент 5 элементом 12, элемент 7 элементом 13, затем отбрасываем 14-й элемент и т. Д. Замены становятся менее частыми, поскольку i становится намного больше, чем n .

Мы используем это как часть обработчика product, который предварительно выделяет пространство для интересующих нас результатов, затем каждый раз, когда встречается узел "продукт", мы проверяем, следует ли выбрать, и если да, добавляем его в наши текущие результаты в соответствующем месте

sku <- character(n)
product <- function(p) {
    i <- select()
    if (i)
        sku[[i]] <<- xmlValue(p[["sku"]])
    NULL
}

Обработчики 'select' и 'product' объединены с функцией (get), которая позволяет нам извлекать текущие значения, и все они помещаются в замыкание, так что у нас есть своего рода фабричный шаблон, который инкапсулирует переменные n, i и sku

sampler <- function(n)
{
    force(n)    # otherwise lazy evaluation could lead to surprises
    i <- 0L
    select <- function() {
        i <<- i + 1L
        if (i <= n) {
            i
        } else {
            if (runif(1) < n / i)
                sample(n, 1)
            else
                0
        }
    }

    sku <- character(n)
    product <- function(p) {
        i <- select()
        if (i)
            sku[[i]] <<- xmlValue(p[["sku"]])
        NULL
    }

    list(product=product, get=function() list(sku=sku))
}

И тогда мы готовы к работе

products <- xmlTreeParse("foo.xml", handler=sampler(1000))
as.data.frame(products$get())

Как только количество обрабатываемых узлов i станет большим по сравнению с n, оно будет линейно масштабироваться с размером файла, поэтому вы можете понять, достаточно ли хорошо он работает, начав с подмножеств исходный файл.

3
Martin Morgan 22 Дек 2013 в 18:19

Вот пример, основанный на предоставленном вами XML-файле.

xml <- '<?xml version="1.0" encoding="UTF-8"?>
<products>
  <product>
    <sku>967190</sku>
    <productId>98611</productId>
    <listingId/>
    <sellerId/>
    <shippingRestrictions/>
  </product>
  <product>
    <sku>967191</sku>
    <productId>98612</productId>
    <listingId/>
    <sellerId/>
    <shippingRestrictions/>
  </product>
  <product>
    <sku>967192</sku>
    <productId>98613</productId>
    <listingId/>
    <sellerId/>
    <shippingRestrictions/>
  </product>
</products>
'

# parse
p <- xmlParse(xml)
# get nodes
nodes <- xpathApply(p, '//product')
# return a random sample of notes
nodes[sample(seq_along(nodes), 2)]

Вот результат:

> nodes[sample(seq_along(nodes), 2)]
[[1]]
<product>
  <sku>967191</sku>
  <productId>98612</productId>
  <listingId/>
  <sellerId/>
  <shippingRestrictions/>
</product> 

[[2]]
<product>
  <sku>967190</sku>
  <productId>98611</productId>
  <listingId/>
  <sellerId/>
  <shippingRestrictions/>
</product>
1
Thomas 22 Дек 2013 в 15:54