Что мне делать с функцией, которая не принимает вектор? Ошибка: `x` должен быть строкой длиной 1 - PullRequest
0 голосов
/ 21 июня 2020

Я пытаюсь использовать пакет xml2 для чтения многих каналов подкастов. Я хочу иметь возможность рассчитать 75-й процентиль для продолжительности каждого подкаста в серии и многие аналогичные показатели (например, частоту выпусков). Я часто использую data.table. Я хочу продолжать его использовать. Каждый раз, когда я вызываю аргумент read_ xml для чтения URL-адресов в столбце, я получаю эту ошибку:

Error: `x` must be a string of length 1

Я могу заставить его работать, если обрабатываю только одну строку, но это противоречит цели.

Позвольте привести простой пример. Вот список только моих подкастов со статистикой, но в реальной жизни я подписываюсь на> 100 подкастов во многих областях.

library(data.table)
library(xml2)
statml.opml <- read_xml(x = "https://player.fm/farrelbuch/statistics-ml.opml")
statml.items <- xml_find_all(x = statml.opml, "/opml/body/outline")
xml_structure(statml.opml)
statml.dt <- data.table(podcast = xml_attr(statml.items, "text"), url = xml_attr(statml.items, "xmlUrl"))

Я начинаю с чтения файла opml, который предоставляет мой агрегатор подкастов. Спасибо player.fm. . Затем я получаю список каждого канала и, глядя на структуру, вижу, что мне нужно извлечь из каждого канала. В итоге я получаю таблицу data.table, в которой есть имя каждого подкаста и его URL.

statml.dt[1, url]
pod1 <- read_xml(x = "https://podcasts.files.bbci.co.uk/p02nrss1.rss")

xml_structure(pod1)
xml_children(pod1)


xml_find_all(x = pod1, "/rss/channel/item/itunes:duration")
xml_text(xml_find_all(x = pod1, "/rss/channel/item/itunes:duration"))

list(xml_text(xml_find_all(x = read_xml(x = "https://podcasts.files.bbci.co.uk/p02nrss1.rss"), "/rss/channel/item/itunes:duration")))

Так что я могу легко отобразить только один URL-адрес и прочитать xml по этому URL-адресу. xml_find_all получит все элементы с тегами itunes: duration, а xml_text выделит фактическую продолжительность времени и отбросит все теги. Можно преобразовать в список раз, который должен позволить сохранить его в столбце data.table.

Посмотрите, что происходит, когда я пробую эти простые строки кода для быстрого добавления столбцов по ссылке, используя: =. Вы увидите, что все работает хорошо, если я установил i = 1 (другими словами, я работаю только с первой строкой и первой строкой). Но, увы, если я оставлю поле пустым, чтобы он работал со всеми строками, или даже если я установил для i значение 1: 2, операция завершится с ошибкой около x, это должна быть строка 1.

statml.dt[,times:=list(xml_text(xml_find_all(x = read_xml(url), "/rss/channel/item/itunes:duration")))]
statml.dt[1,times:=list(xml_text(xml_find_all(x = read_xml(url), "/rss/channel/item/itunes:duration")))]
statml.dt[,hereIam:=list(read_xml(url))]
statml.dt[1,hereIam:=list(read_xml(url))]

Как получить аргумент для работы с каждой строкой таблицы data.table, если столбец значений не ожидается?

Ответы [ 2 ]

1 голос
/ 21 июня 2020

Самый "data.table" способ сделать это, я думаю, - использовать by. Если в вашей таблице еще нет идентификатора строки, вы можете создать его с помощью statml.dt[, rowid:=.I]. Как только вы его получите, вы можете просто вставить by='rowid' в свой оператор (например, statml.dt[,hereIam:=list(read_xml(url)), by='rowid']).

Когда вы это сделаете, указанные вами функции будут выполняться в каждой строке таблицы data.table индивидуально, поскольку каждая строка представляет собой отдельную группу.

1 голос
/ 21 июня 2020

Vectorize(somefunc) преобразует невекторизованную функцию somefunc из функции, которая принимает не более одного аргумента, в функцию, принимающую вектор.

Vectorize(somefunc) возвращает функцию , который вы затем используете в следующем вызове. Легко как пре- Vectorize функцию, так и встроенную.

func1 <- function(x) { stopifnot(length(x) == 1L); 2*x; }

data.table(a=1:2)[, b := func1(a) ]
# Error in func1(a) : length(x) == 1L is not TRUE

data.table(a=1:2)[, b := Vectorize(func1)(a) ][]
#    a b
# 1: 1 2
# 2: 2 4

func1_n <- Vectorize(func1)
data.table(a=1:2)[, b := func1_n(a) ][]
#    a b
# 1: 1 2
# 2: 2 4

Когда у вас есть более сложные логические c, которые необходимо выполнить (например, более одного вызова функции на элемент вектора), обычно лучше использовать функцию сортировки, либо анонимную (встроенную), либо предопределено, даже если временно. Оттуда используйте lapply или sapply:

data.table(a=1:2)[, b := lapply(a, func1)][]
#    a b
# 1: 1 2
# 2: 2 4
str(data.table(a=1:2)[, b := lapply(a, func1)])
# Classes ‘data.table’ and 'data.frame':    2 obs. of  2 variables:
#  $ a: int  1 2
#  $ b:List of 2
#   ..$ : num 2
#   ..$ : num 4
#  - attr(*, ".internal.selfref")=<externalptr> 
str(data.table(a=1:2)[, b := sapply(a, func1)])
# Classes ‘data.table’ and 'data.frame':    2 obs. of  2 variables:
#  $ a: int  1 2
#  $ b: num  2 4
#  - attr(*, ".internal.selfref")=<externalptr> 

Обратите внимание, что метод lapply выглядит так, как будто он генерирует «простой столбец», но lapply всегда возвращает a list, это просто происходит так, как можно подумать. Если вы знаете, что ваша функция всегда будет возвращать «скаляр» (который в R на самом деле является вектором длины 1), вы можете использовать sapply или, возможно, vapply(a, func1, numeric(1)).

...