2014-10-03 24 views
8

L'obiettivo è convertire un elenco annidato che a volte contiene record mancanti in un frame di dati. Un esempio della struttura quando ci sono record mancanti è:Conversione dell'elenco nidificato in dataframe

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" 

Quando non c'è mancano registra l'elenco può essere convertito in un frame di dati utilizzando data.frame(do.call(rbind.data.frame, mylist)). Tuttavia, quando mancano i record, ciò provoca una mancata corrispondenza della colonna. So che ci sono funzioni per unire frame di dati di colonne non corrispondenti, ma devo ancora trovarne uno che possa essere applicato agli elenchi. Il risultato ideale terrebbe il record 2 con NA per tutte le variabili. Sperando in qualche aiuto.

Modifica per aggiungere 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"))) 

risposta

17

È inoltre possibile utilizzare (almeno v1.9.3) della rbindlist nel pacchetto 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 
+1

[1.9.4 è ora disponibile su CRAN] (http://cran.r-project.org/web/packages/data.table/index.html) (anche se potrebbe essere necessario un giorno in più affinché i binari rimanenti siano disponibili). – Arun

7

è possibile creare una lista di data.frames:

dfs <- lapply(mylist, data.frame, stringsAsFactors = FALSE) 

quindi utilizzare uno di questi:

library(plyr) 
rbind.fill(dfs) 

o il più veloce

library(dplyr) 
rbind_all(dfs) 

Nel caso di dplyr::rbind_all, sono sorpreso che scelga di utilizzare "" anziché NA per i dati mancanti. Se rimuovi stringsAsFactors = FALSE, riceverai NA ma al costo di un avviso ... Quindi suppressWarnings(rbind_all(lapply(mylist, data.frame))) sarebbe una soluzione brutta ma veloce.

+2

'rbind_all()' è obsoleto. Per favore usa 'bind_rows()' invece. – psychonomics

5

Ho appena sviluppato una soluzione per this question che è applicabile qui, quindi vi fornirò qui pure :

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,...)); 

Esecuzione:

## 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> 

La mia funzione è più potente di data.table::rbindlist() a partire dal 1.9.6, in quanto è in grado di gestire qualsiasi numero di livelli di nidificazione e lunghezze vettoriali diverse tra i rami. Nella domanda collegata, la mia funzione appiattisce correttamente l'elenco degli OP su un data.frame, ma data.table::rbindlist() non riesce con "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".

+0

Wow, finalmente ho trovato una soluzione per appiattire il tipo di elenco che sto affrontando. Grazie. – jcarlos

Problemi correlati