Преобразование вложенного списка в фрейм данных
Цель состоит в том, чтобы преобразовать вложенный список, который иногда содержит отсутствующие записи, во фрейм данных. Пример структуры, когда отсутствуют записи:
str(mylist)
List of 3
$ :List of 7
..$ Hit : chr "True"
..$ Project: chr "Blue"
..$ Year : chr "2011"
..$ Rating : chr "4"
..$ Launch : chr "26 Jan 2012"
..$ ID : chr "19"
..$ Dept : chr "1, 2, 4"
$ :List of 2
..$ Hit : chr "False"
..$ Error: chr "Record not found"
$ :List of 7
..$ Hit : chr "True"
..$ Project: chr "Green"
..$ Year : chr "2004"
..$ Rating : chr "8"
..$ Launch : chr "29 Feb 2004"
..$ ID : chr "183"
..$ Dept : chr "6, 8"
Если пропущенных записей нет, список можно преобразовать во фрейм данных с помощью data.frame(do.call(rbind.data.frame, mylist))
, Однако, если записи отсутствуют, это приводит к несоответствию столбца. Я знаю, что есть функции для объединения фреймов данных несоответствующих столбцов, но я пока не нашел такой, которая могла бы быть применена к спискам. Идеальный результат - вести запись 2 с NA для всех переменных. Надеюсь на некоторую помощь.
Изменить, чтобы добавить dput(mylist)
:
list(structure(list(Hit = "True", Project = "Blue", Year = "2011",
Rating = "4", Launch = "26 Jan 2012", ID = "19", Dept = "1, 2, 4"), .Names = c("Hit",
"Project", "Year", "Rating", "Launch", "ID", "Dept")), structure(list(
Hit = "False", Error = "Record not found"), .Names = c("Hit",
"Error")), structure(list(Hit = "True", Project = "Green", Year = "2004",
Rating = "8", Launch = "29 Feb 2004", ID = "183", Dept = "6, 8"), .Names = c("Hit",
"Project", "Year", "Rating", "Launch", "ID", "Dept")))
6 ответов
Вы также можете использовать (по крайней мере v1.9.3) из rbindlist
в data.table
пакет:
library(data.table)
rbindlist(mylist, fill=TRUE)
## Hit Project Year Rating Launch ID Dept Error
## 1: True Blue 2011 4 26 Jan 2012 19 1, 2, 4 NA
## 2: False NA NA NA NA NA NA Record not found
## 3: True Green 2004 8 29 Feb 2004 183 6, 8 NA
Вы можете создать список data.frames:
dfs <- lapply(mylist, data.frame, stringsAsFactors = FALSE)
Затем используйте один из них:
library(plyr)
rbind.fill(dfs)
или быстрее
library(dplyr)
rbind_all(dfs)
В случае dplyr::rbind_all
Я удивлен, что он решает использовать ""
вместо NA
за отсутствующие данные. Если вы удалите stringsAsFactors = FALSE
, ты получишь NA
но за счет предупреждения... Так suppressWarnings(rbind_all(lapply(mylist, data.frame)))
было бы уродливым, но быстрым решением.
Я только что разработал решение для этого вопроса, которое применимо здесь, поэтому я также приведу его здесь:
tl <- function(e) { if (is.null(e)) return(NULL); ret <- typeof(e); if (ret == 'list' && !is.null(names(e))) ret <- list(type='namedlist') else ret <- list(type=ret,len=length(e)); ret; };
mkcsv <- function(v) paste0(collapse=',',v);
keyListToStr <- function(keyList) paste0(collapse='','/',sapply(keyList,function(key) if (is.null(key)) '*' else paste0(collapse=',',key)));
extractLevelColumns <- function(
nodes, ## current level node selection
..., ## additional arguments to data.frame()
keyList=list(), ## current key path under main list
sep=NULL, ## optional string separator on which to join multi-element vectors; if NULL, will leave as separate columns
mkname=function(keyList,maxLen) paste0(collapse='.',if (is.null(sep) && maxLen == 1L) keyList[-length(keyList)] else keyList) ## name builder from current keyList and character vector max length across node level; default to dot-separated keys, and remove last index component for scalars
) {
cat(sprintf('extractLevelColumns(): %s\n',keyListToStr(keyList)));
if (length(nodes) == 0L) return(list()); ## handle corner case of empty main list
tlList <- lapply(nodes,tl);
typeList <- do.call(c,lapply(tlList,`[[`,'type'));
if (length(unique(typeList)) != 1L) stop(sprintf('error: inconsistent types (%s) at %s.',mkcsv(typeList),keyListToStr(keyList)));
type <- typeList[1L];
if (type == 'namedlist') { ## hash; recurse
allKeys <- unique(do.call(c,lapply(nodes,names)));
ret <- do.call(c,lapply(allKeys,function(key) extractLevelColumns(lapply(nodes,`[[`,key),...,keyList=c(keyList,key),sep=sep,mkname=mkname)));
} else if (type == 'list') { ## array; recurse
lenList <- do.call(c,lapply(tlList,`[[`,'len'));
maxLen <- max(lenList,na.rm=T);
allIndexes <- seq_len(maxLen);
ret <- do.call(c,lapply(allIndexes,function(index) extractLevelColumns(lapply(nodes,function(node) if (length(node) < index) NULL else node[[index]]),...,keyList=c(keyList,index),sep=sep,mkname=mkname))); ## must be careful to translate out-of-bounds to NULL; happens automatically with string keys, but not with integer indexes
} else if (type%in%c('raw','logical','integer','double','complex','character')) { ## atomic leaf node; build column
lenList <- do.call(c,lapply(tlList,`[[`,'len'));
maxLen <- max(lenList,na.rm=T);
if (is.null(sep)) {
ret <- lapply(seq_len(maxLen),function(i) setNames(data.frame(sapply(nodes,function(node) if (length(node) < i) NA else node[[i]]),...),mkname(c(keyList,i),maxLen)));
} else {
## keep original type if maxLen is 1, IOW don't stringify
ret <- list(setNames(data.frame(sapply(nodes,function(node) if (length(node) == 0L) NA else if (maxLen == 1L) node else paste(collapse=sep,node)),...),mkname(keyList,maxLen)));
}; ## end if
} else stop(sprintf('error: unsupported type %s at %s.',type,keyListToStr(keyList)));
if (is.null(ret)) ret <- list(); ## handle corner case of exclusively empty sublists
ret;
}; ## end extractLevelColumns()
## simple interface function
flattenList <- function(mainList,...) do.call(cbind,extractLevelColumns(mainList,...));
Исполнение:
## define data
mylist <- list(structure(list(Hit='True',Project='Blue',Year='2011',Rating='4',Launch='26 Jan 2012',ID='19',Dept='1, 2, 4'),.Names=c('Hit','Project','Year','Rating','Launch','ID','Dept')),structure(list(Hit='False',Error='Record not found'),.Names=c('Hit','Error')),structure(list(Hit='True',Project='Green',Year='2004',Rating='8',Launch='29 Feb 2004',ID='183',Dept='6, 8'),.Names=c('Hit','Project','Year','Rating','Launch','ID','Dept')));
## run it
df <- flattenList(mylist);
## extractLevelColumns():
## extractLevelColumns(): Hit
## extractLevelColumns(): Project
## extractLevelColumns(): Year
## extractLevelColumns(): Rating
## extractLevelColumns(): Launch
## extractLevelColumns(): ID
## extractLevelColumns(): Dept
## extractLevelColumns(): Error
df;
## Hit Project Year Rating Launch ID Dept Error
## 1 True Blue 2011 4 26 Jan 2012 19 1, 2, 4 <NA>
## 2 False <NA> <NA> <NA> <NA> <NA> <NA> Record not found
## 3 True Green 2004 8 29 Feb 2004 183 6, 8 <NA>
Моя функция более мощная, чем data.table::rbindlist()
с 1.9.6, в том смысле, что он может обрабатывать любое количество уровней вложенности и векторов различной длины в разных ветвях В связанном вопросе моя функция корректно выравнивает список OP в data.frame, но data.table::rbindlist()
не удается с "Error in rbindlist(jsonRList, fill = T) : Column 4 of item 16 is length 2, inconsistent with first column of that item which is length 1. rbind/rbindlist doesn't recycle as it already expects each item to be a uniform list, data.frame or data.table"
,
Вот решение, которое преобразует любой вложенный / нечетный список в фрейм данных. не работает во многих случаях, особенно для списка списков. Поэтому мне пришлось создать что-то лучше, чем
rbindlist
.
rbindlist.v2 <- function(l)
{
l <- l[lapply(l, class) == "list"]
df <- foreach(element = l, .combine = bind_rows, .errorhandling = 'remove') %do%
{df = unlist(element); df = as.data.frame(t(df)); rm(element); return(df)}
rm(l)
return(df)
}
Для больших списков вы можете ускорить процесс, заменив
%do%
к
%dopar%
. Это было то, что мне было нужно в моем случае.
Альтернатива @ishonest:
df <- purrr::map_dfr(l,function(y){
y[[1]]
})
вот несколько других методов, которые зависят от того, как вложены списки:
список списков
df <- purrr::map_dfr(r,function(x){
unlist(x)
})
если вложенный список более сложный, где некоторые элементы являются списками:
format_json_list <- function(r){
purrr::map_dfr(r,function(x){
#Base object, e.g. Vessel info
b <- x[[1]]
# object's events, e.g. paces Vessel visited
df <- purrr::map_dfr(x[[2]],function(y){
v <- y[[1]]
p <- y[2:length(y)]
dplyr::bind_cols(v,p)
})
dplyr::bind_cols(b,df)
})
}
для некоторых сложных json дублирование имен переменных может стать проблемой. Одним из исправлений является указание именования. Этот код ниже представляет собой жесткое кодирование имен. Я считаю, что это можно сделать динамичным.
purrr::map_dfr(vo, function(vessels){
if(is.list(vessels)){
vinfo <- purrr::map_dfr(vessels, function(vessel){
if(!is.list(vessel)){
#print(vessel)
vessel
}
}) %>% dplyr::rename_all(~ paste0("Vessel.", .))
calinfo <- purrr::map_dfr(vessels$Callings, function(calling){
if(is.list(calling)){
call <- purrr::map_dfr(calling, function(call){
if(!is.list(call)){
call
}
})
callport <- purrr::map_dfr(calling$Port, function(port){
if(!is.list(port)){
port
}
}) %>% dplyr::rename_all(~ paste0("Port.", .))
dplyr::bind_cols(call, callport)
}
}) %>% dplyr::rename_all(~ paste0("Calling.", .))
bind_cols(vinfo, calinfo)
}
}, .id ="Vessel" )
})
И если тебе нравитсяpurrr
:
> te <- list(structure(list(Hit = "True", Project = "Blue", Year = "2011",
Rating = "4", Launch = "26 Jan 2012", ID = "19", Dept = "1, 2, 4"), .Names = c("Hit", "Project", "Year", "Rating", "Launch", "ID", "Dept")), structure(list(
Hit = "False", Error = "Record not found"), .Names = c("Hit",
"Error")), structure(list(Hit = "True", Project = "Green", Year = "2004",
Rating = "8", Launch = "29 Feb 2004", ID = "183", Dept = "6, 8"), .Names = c("Hit", "Project", "Year", "Rating", "Launch", "ID", "Dept")))
> str(te)
List of 3
$ :List of 7
..$ Hit : chr "True"
..$ Project: chr "Blue"
..$ Year : chr "2011"
..$ Rating : chr "4"
..$ Launch : chr "26 Jan 2012"
..$ ID : chr "19"
..$ Dept : chr "1, 2, 4"
$ :List of 2
..$ Hit : chr "False"
..$ Error: chr "Record not found"
$ :List of 7
..$ Hit : chr "True"
..$ Project: chr "Green"
..$ Year : chr "2004"
..$ Rating : chr "8"
..$ Launch : chr "29 Feb 2004"
..$ ID : chr "183"
..$ Dept : chr "6, 8"
> purrr::map_dfr(te,as_tibble)
# A tibble: 3 × 8
Hit Project Year Rating Launch ID Dept Error
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 True Blue 2011 4 26 Jan 2012 19 1, 2, 4 NA
2 False NA NA NA NA NA NA Record not found
3 True Green 2004 8 29 Feb 2004 183 6, 8 NA