Вычисление количества xmlchildren под каждым родительским узлом для списка в R

Я запрашиваю PubMED с длинным списком PMID, используя R. Поскольку entrez_fetch может делать только определенное число одновременно, я разбил свои ~2000 PMID на один список с несколькими векторами (каждый длиной около 500). Когда я запрашиваю PubMED, я извлекаю информацию из файлов XML для каждой публикации. В конце концов я хотел бы получить что-то вроде этого:

    Original.PMID     Publication.type
    26956987          Journal.article
    26956987          Meta.analysis
    26956987          Multicenter.study
    26402000          Journal.article
    25404043          Journal.article
    25404043          Meta.analysis

Каждая публикация имеет уникальный PMID, но может быть несколько типов публикаций, связанных с каждым PMID (как видно выше). Я могу запросить номер PMID из файла XML, и я могу получить типы публикации каждого PMID. У меня проблемы с повторением PMID x количество раз, чтобы каждый PMID ассоциировался с каждым типом публикации. Я могу сделать это, если у меня нет данных в списке с несколькими подсписками (например, если у меня есть 14 пакетов, каждый в качестве своего собственного фрейма данных), получая количество дочерних узлов от родительского узла PublicationType. Но я не могу понять, как это сделать в списке.

Мой код пока таков:

library(rvest)
library(tidyverse)
library(stringr)
library(regexr)
library(rentrez)
library(XML)

pubmed<-my.data.frame

into.batches<-function(x,n) split(x,cut(seq_along(x),n,labels=FALSE))
batches<-into.batches(pubmed.fwd$PMID, 14)
headings<-lapply(1:14, function(x) {paste0("Batch",x)})
names(batches)<-headings
fwd<-sapply(batches, function(x) entrez_fetch(db="pubmed", id=x, rettype="xml", parsed=TRUE))
trial1<-lapply(fwd, function(x) 
  list(pub.type = xpathSApply(x, "//PublicationTypeList/PublicationType", xmlValue),
  or.pmid = xpathSApply(x, "//ArticleId[@IdType='pubmed']", xmlValue)))

trial1 - это то, с чем у меня проблемы. Это дает мне список, где в каждом пакете у меня есть вектор для pub.type и вектор для or.pmid, но они разной длины.

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

trial1<-lapply(fwd, function(x) 
  list(childnodes = xpathSApply(xmlRoot(x), "count(.//PublicationTypeList/PublicationType)", xmlChildren)))

К сожалению, это просто говорит мне об общем количестве дочерних узлов для каждого пакета, а не для каждой публикации (или pmid).

2 ответа

Решение

Я бы разделил результаты XML на отдельные узлы Article и применил функции xpath для получения pmids и pubtypes.

pmids <- c(11677608, 22328765 ,11337471)
res <- entrez_fetch(db="pubmed", rettype="xml", id = pmids)
doc <- xmlParse(res)
x <-  getNodeSet(doc, "//PubmedArticle")
x1 <- sapply(x, xpathSApply, ".//ArticleId[@IdType='pubmed']", xmlValue)
x2 <- sapply(x, xpathSApply, ".//PublicationType", xmlValue)
data.frame( pmid= rep(x1, sapply(x2, length) ), pubtype = unlist(x2) )
      pmid                          pubtype
1 11677608                  Journal Article
2 11677608 Research Support, Non-U.S. Gov't
3 22328765                  Journal Article
4 22328765 Research Support, Non-U.S. Gov't
5 11337471                  Journal Article

Кроме того, NCBI говорит использовать метод HTTP POST, если используется более 200 UID. rentrez не поддерживает POSTing, но вы можете запустить его с помощью нескольких строк кода.

Во-первых, вам нужен вектор с тысячами идентификаторов Pubmed (6171 из таблицы микробного генома)

library(readr)
x <- read_tsv( "ftp://ftp.ncbi.nih.gov/genomes/GENOME_REPORTS/prokaryotes.txt", 
                na = "-", quote = "")
ids <- unique( x$`Pubmed ID` )
ids <- ids[ids < 1e9 & !is.na(ids)]

Разместите идентификаторы в NCBI, используя httr POST,

uri = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/epost.fcgi?"
response <- httr::POST(uri, body= list(id = paste(ids, collapse=","), db = "pubmed"))

Разобрать результаты по коду в entrez_post чтобы получить историю веб-поиска.

 doc  <-   xmlParse( httr::content(response, as="text", encoding="UTF-8") )
 result <- xpathApply(doc, "/ePostResult/*", xmlValue)
 names(result) <- c("QueryKey", "WebEnv")
 class(result) <- c("web_history", "list")

Наконец, извлеките до 10 тыс. Записей (или переберите retstart вариант если у вас больше 10К)

res <- entrez_fetch(db="pubmed", rettype="xml", web_history=result)
doc <- xmlParse(res)

Это займет всего секунду, чтобы работать на моем ноутбуке.

articles <- getNodeSet(doc, "//PubmedArticle")
x1 <- sapply(articles, xpathSApply, ".//ArticleId[@IdType='pubmed']", xmlValue)
x2 <- sapply(articles, xpathSApply, ".//PublicationType", xmlValue)

data_frame( pmid= rep(x1, sapply(x2, length) ), pubtype = unlist(x2) )
# A tibble: 9,885 × 2
       pmid                                  pubtype
      <chr>                                    <chr>
 1 11677608                          Journal Article
 2 11677608         Research Support, Non-U.S. Gov't
 3 12950922                          Journal Article
 4 12950922         Research Support, Non-U.S. Gov't
 5 22328765                          Journal Article
 ...

И последний комментарий. Если вы хотите одну строку на статью, я обычно создаю функцию, которая объединяет несколько тегов в список с разделителями и добавляет NA для отсутствующих узлов.

xpath2 <-function(x, ...){
    y <- xpathSApply(x, ...)
    ifelse(length(y) == 0, NA,  paste(y, collapse="; "))
}

data_frame( pmid = sapply(articles, xpath2, ".//ArticleId[@IdType='pubmed']", xmlValue),
            journal = sapply(articles, xpath2, ".//Journal/Title", xmlValue),
           pubtypes = sapply(articles, xpath2, ".//PublicationType", xmlValue))

# A tibble: 6,172 × 3
      pmid                 journal                                          pubtypes
     <chr>                   <chr>                                             <chr>
1 11677608                  Nature Journal Article; Research Support, Non-U.S. Gov't
2 12950922  Molecular microbiology Journal Article; Research Support, Non-U.S. Gov't
3 22328765 Journal of bacteriology Journal Article; Research Support, Non-U.S. Gov't
4 11337471         Genome research                                   Journal Article
...

Поскольку вероятный ArticleId уникален для каждой статьи, а PublicationType может быть более одного для каждой статьи, рассмотрите возможность итеративного создания фреймов данных вместо отдельных векторов.

В частности, используйте индексирование узлов, [#]через каждый узел PubmedArticle в XML-документе, так как это общий предок идентификатора и типа, а затем xpath для необходимых потомков. Ниже создается список информационных кадров равной длины fwd:

trial1 <- lapply(fwd, function(doc) {
  # RETRIEVE NUMBER OF ARTICLES PER EACH XML
  num_of_articles <- length(xpathSApply(doc, "//PubmedArticle"))

  # LOOP THROUGH EACH ARTICLE AND BIND XML VALUES TO DATAFRAME
  dfList <- lapply(seq(num_of_articles), function(i)
    data.frame(
     Original.PMID = xpathSApply(doc, paste0("//PubmedArticle[",i,"]/descendant::ArticleId[@IdType='pubmed']"), xmlValue),
     Publication.type = xpathSApply(doc, paste0("//PubmedArticle[",i,"]/descendant::PublicationTypeList/PublicationType"), xmlValue)
  ))

  # ROW BIND ALL DFS INTO ONE
  df <- do.call(rbind, dfList)
})

Для получения окончательного основного кадра данных во всех пакетах запустите do.call(rbind, ...) снова из цикла:

finaldf <- do.call(rbind, trial1)
Другие вопросы по тегам