Мне нужно базовое решение R для преобразования вложенного списка с разными именами в data.frame
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z=list('k'))
convert(mylist)
## returns a data.frame:
##
## a b z
## 1 2 <NULL>
## 3 NA <NULL>
## NA 5 <NULL>
## 9 NA <chr [1]>
Я знаю, что это можно легко сделать с помощью dplyr::bind_rows или data.table::rbindlist с fill = TRUE (хотя и не идеально, поскольку он заполняет столбец символов с помощью NULL, а не NA), но мне действительно нужно решение в базе R. Чтобы упростить проблему, это также хорошо с двухуровневый вложенный список, в котором нет списков третьего уровня, таких как
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))
convert(mylist)
## returns a data.frame:
##
## a b z
## 1 2 NA
## 3 NA NA
## NA 5 NA
## 9 NA k
Я пробовал что-то вроде
convert <- function(L) as.data.frame(do.call(rbind, L))
Это не заполняет NA и добавляет дополнительный столбец z
mylist это просто упрощенный пример. На самом деле я не мог предположить ни названия элементов подсписка (a, b и z в примере), ни длины подсписков (2, 1, 1, 2 в примере).
Вот предположения для ожидаемого data.frame и ввода mylist:
data.frame определяется максимальной длиной подсписков, которая может варьироваться от 1 до нескольких сотен. Нет явного источника информации о длине каждого подсписка (неизвестно, какие имена появятся или исчезнут в каком подсписке).
max(sapply(mylist, length)) <= 1000 ## ==> TRUEdata.frame определяется длиной mylist, которая может варьироваться от 1 до нескольких тысяч.
dplyr::between(length(mylist), 0, 10000) ## ==> TRUEdata.frame могут быть определены только по сути из mylistnumeric, character или list. Чтобы упростить задачу, рассмотрим только numeric и character.




Вы можете сделать что-то вроде следующего:
mylist <- list(list(a=1,b=2), list(a=3), list(b=5), list(a=9, z='k'))
convert <- function(mylist){
col_names <- NULL
# get all the unique names and create the df
for(i in 1:length(mylist)){
col_names <- c(col_names, names(mylist[[i]]))
}
col_names <- unique(col_names)
df <- data.frame(matrix(ncol=length(col_names),
nrow=length(mylist)))
colnames(df) <- col_names
# join data to row in df
for(i in 1:length(mylist)){
for(j in 1:length(mylist[[i]])){
df[i, names(mylist[[i]])[j]] <- mylist[[i]][names(mylist[[i]])[j]]
}
}
return(df)
}
df <- convert(mylist)
> df
a b z
1 1 2 <NA>
2 3 NA <NA>
3 NA 5 <NA>
4 9 NA k
У меня есть решение. Обратите внимание, что здесь используется только труба, и ее можно заменить на родную трубу и т. д.
mylist %>%
#' first, ensure that the 2nd level is flat,
lapply(. %>% lapply(FUN = unlist, recursive = FALSE)) %>%
#' replace missing vars with `NA`
lapply(function(x, vars) {
x[vars[!vars %in% names(x)]]<-NA
x
}, vars = {.} %>% unlist() %>% names() %>% unique()) %>%
do.call(what = rbind) %>%
#' do nothing
identity()
В {.} он предназначен для определения и оценки функции, образованной unlist, за которой следует names. В противном случае . %>% unlist() %>% names() просто определяет функцию, а не оценивает входные данные ..
Более короткое решение в базе R будет
make_df <- function(a = NA, b = NA, z = NA) {
data.frame(a = unlist(a), b = unlist(b), z = unlist(z))
}
do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
#> a b z
#> 1 1 2 <NA>
#> 2 3 NA <NA>
#> 3 NA 5 <NA>
#> 4 9 NA k
Обновлять
Более общее решение, использующее тот же метод, но не требующее конкретных имен, будет следующим:
build_data_frame <- function(obj) {
nms <- unique(unlist(lapply(obj, names)))
frmls <- as.list(setNames(rep(NA, length(nms)), nms))
dflst <- setNames(lapply(nms, function(x) call("unlist", as.symbol(x))), nms)
make_df <- as.function(c(frmls, call("do.call", "data.frame", dflst)))
do.call(rbind, lapply(mylist, function(x) do.call(make_df, x)))
}
Это позволяет
build_data_frame(mylist)
#> a b z
#> 1 1 2 <NA>
#> 2 3 NA <NA>
#> 3 NA 5 <NA>
#> 4 9 NA k
Мне нравится простота этого решения. Однако невозможно явно определить имена и порядок столбцов. Пожалуйста, смотрите обновление вопроса
@englealuze а, понятно. Проверьте мое обновление, которое содержит более общее решение.
Два вопроса: 1. как оцениваются объекты вызова unlist(*) внутри dflst? Я мог понять, что касается внешнего call("do.call", "data.frame", dflst)), вы конвертируете его в функцию и применяете к фактическим параметрам. Но я не мог понять, как оценивается внутренний вызов unlist(*). 2. В примере на справочной странице as.function используется alist. Когда я пытаюсь as.function(alist(a =, call('abs', as.symbol("a")))), он не генерирует истинную функцию. Я должен использовать as.function(c(a = NA, call('abs', as.symbol("a")))), как ты. Почему?
Объекты вызова внутри dflist не оцениваются до тех пор, пока do.call("data.frame", dflist) не будет запущен. Но так как это само находится в теле make_df, оно вызывается только тогда, когда вызывается make_df. В этот момент функция make_df эффективно function(a = NA, b = NA, z = NA) data.frame(a = unlist(a), b = unlist(b), z = unlist(z)) . Ни один из аргументов или вызовов в функции не оценивается до тех пор, пока make_df не будет вызван в последнем вызове lapply в последней строке.
Аргументы в alist не оцениваются, поэтому as.function(alist(a =, {abs(a)})), вероятно, делает то, что вы намеревались.
Спасибо. lapply используется чаще, чем sapply в nms <- unique(unlist(sapply(obj, names))). Если все подсписки имеют одинаковые имена и длину, sapply без упрощения возвращает матрицу и приводит к ошибке. Итак, lapply является более общим
@englealuze ты прав. Я обновлю.
Мы можем попробовать базовый код R ниже
subset(
Reduce(
function(...) {
merge(..., all = TRUE)
},
Map(
function(k, x) cbind(id = k, list2DF(x)),
seq_along(mylist), mylist
)
),
select = -id
)
который дает
a b z
1 1 2 NA
2 3 NA NA
3 NA 5 NA
4 9 NA k
Удобочитаемость этого чисто функционального подхода не имеет себе равных. Идея сокращения + слияния очень умна. Увидев это решение, я спрашиваю себя, почему я не сделал это таким образом. Я должен был сам придумать эту идею
Это решение пропускает 5 в 3-й строке в столбце
b(вместо этого назначается NA) и ошибочно ставит"k"в 4-й строке в столбцеb."k"должен быть на 4 месте вzстолбце. Также не могли бы вы объяснить использование.в первыхlapplyиvar = {.}?