2015-01-26 3 views
5

Ho una lista con la seguente struttura esempio:Appiattire una lista con complessa struttura annidata

> dput(test) 
structure(list(id = 1, var1 = 2, var3 = 4, section1 = structure(list(
    var1 = 1, var2 = 2, var3 = 3), .Names = c("var1", "var2", 
"var3")), section2 = structure(list(row = structure(list(var1 = 1, 
    var2 = 2, var3 = 3), .Names = c("var1", "var2", "var3")), 
    row = structure(list(var1 = 4, var2 = 5, var3 = 6), .Names = c("var1", 
    "var2", "var3")), row = structure(list(var1 = 7, var2 = 8, 
     var3 = 9), .Names = c("var1", "var2", "var3"))), .Names = c("row", 
"row", "row"))), .Names = c("id", "var1", "var3", "section1", 
"section2")) 


> str(test) 
List of 5 
$ id  : num 1 
$ var1 : num 2 
$ var3 : num 4 
$ section1:List of 3 
    ..$ var1: num 1 
    ..$ var2: num 2 
    ..$ var3: num 3 
$ section2:List of 3 
    ..$ row:List of 3 
    .. ..$ var1: num 1 
    .. ..$ var2: num 2 
    .. ..$ var3: num 3 
    ..$ row:List of 3 
    .. ..$ var1: num 4 
    .. ..$ var2: num 5 
    .. ..$ var3: num 6 
    ..$ row:List of 3 
    .. ..$ var1: num 7 
    .. ..$ var2: num 8 
    .. ..$ var3: num 9 

Si noti che l'elenco section2 contiene elementi denominati rows. Questi rappresentano più record. Quello che ho è un elenco annidato in cui alcuni elementi sono al livello di root e altri sono più record annidati per la stessa osservazione. Desidero il seguente output in un formato data.frame:

> desired 
    id var1 var3 section1.var1 section1.var2 section1.var3 section2.var1 section2.var2 section2.var3 
1 1 2 4    1    2    3    1    4    7 
2 NA NA NA   NA   NA    NA    2    5    8 
3 NA NA NA   NA   NA    NA    3    6    9 

elementi a livello Root devono popolare la prima fila, mentre row elementi dovrebbero avere le proprie righe. Come ulteriore complicazione, il numero di variabili nelle voci row può variare.

+0

Perché vuoi questo output desiderato? Sembra un formato dati scomodo con cui lavorare. – A5C1D2H2I1M1N2O1R2T1

+0

Sto eseguendo una richiesta soap che restituisce una tabella html con una struttura molto annidata in un elenco annidato. Non sono sicuro del motivo per cui pensi che l'output desiderato sia inopportuno. Ricrea la tabella html nel formato data.frame e riempie i valori NA in cui una voce si estende su più righe. – Zelazny7

+0

Puoi fornire uno o due ulteriori casi di test da quando hai aggiunto una taglia a questo.Hai detto che stai cercando una soluzione "generale", quindi sarebbe bene sapere potenzialmente quali altri scenari dovrebbero essere considerati. – A5C1D2H2I1M1N2O1R2T1

risposta

3

Ecco un approccio generale. Non presuppone che avrai solo tre righe; funzionerà con tutte le righe che hai. E se nella struttura nidificata manca un valore (ad esempio var1 non esiste per alcuni sotto-elenchi in section2), il codice restituisce correttamente un NA per quella cella.

E.g. se usiamo i seguenti dati:

test <- structure(list(id = 1, var1 = 2, var3 = 4, section1 = structure(list(var1 = 1, var2 = 2, var3 = 3), .Names = c("var1", "var2", "var3")), section2 = structure(list(row = structure(list(var1 = 1, var2 = 2), .Names = c("var1", "var2")), row = structure(list(var1 = 4, var2 = 5), .Names = c("var1", "var2")), row = structure(list(var2 = 8, var3 = 9), .Names = c("var2", "var3"))), .Names = c("row", "row", "row"))), .Names = c("id", "var1", "var3", "section1", "section2")) 

L'approccio generale è quello di utilizzare fusione per creare un dataframe che comprende informazioni sulla struttura annidata, e poi dcast alla muffa nel formato desiderato.

library("reshape2") 

flat <- unlist(test, recursive=FALSE) 
names(flat)[grep("row", names(flat))] <- gsub("row", "var", paste0(names(flat)[grep("row", names(flat))], seq_len(length(names(flat)[grep("row", names(flat))])))) ## keeps track of rows by adding an ID 
ul <- melt(unlist(flat)) 
split <- strsplit(rownames(ul), split=".", fixed=TRUE) ## splits the names into component parts 
max <- max(unlist(lapply(split, FUN=length))) 
pad <- function(a) { 
    c(a, rep(NA, max-length(a))) 
} 
levels <- matrix(unlist(lapply(split, FUN=pad)), ncol=max, byrow=TRUE) 

## Get the nesting structure 
nested <- data.frame(levels, ul) 
nested$X3[is.na(nested$X3)] <- levels(as.factor(nested$X3))[[1]] 
desired <- dcast(nested, X3~X1 + X2) 
names(desired) <- gsub("_", "\\.", gsub("_NA", "", names(desired))) 
desired <- desired[,names(flat)] 

> desired 
    ## id var1 var3 section1.var1 section1.var2 section1.var3 section2.var1 section2.var2 section2.var3 
## 1 1 2 4    1    2    3    1    4    7 
## 2 NA NA NA   NA   NA   NA    2    5    8 
## 3 NA NA NA   NA   NA   NA    3    6    9 
1

L'idea centrale di questa soluzione è di appiattire tutti gli sotto-elenchi tranne gli sotto-elenchi denominati 'riga'. Ciò potrebbe essere fatto creando un ID univoco per ciascun elemento dell'elenco (memorizzato in z) e quindi richiedendo che tutti gli elementi all'interno di una singola "riga" debbano avere lo stesso ID (archiviato in z2; dovevano scrivere una funzione ricorsiva per attraversare il nidificato elenco). Quindi, è possibile utilizzare z2 per raggruppare elementi appartenenti alla stessa riga. L'elenco risultante può essere convertito nella matrice utilizzando stri_list2matrix dal pacchetto stringi e quindi convertito in un frame di dati.

utest <- unlist(test) 
z <- relist(seq_along(utest),test) 

recurse <- function(L) { 
    if (class(L)!='list') return(L) 
    b <- names(L)=='row' 
    L.b <- lapply(L[b],function(k) relist(rep(k[[1]],length(k)),k)) 
    L.nb <- lapply(L[!b],recurse) 
    c(L.b,L.nb) 
} 

z2 <- unlist(recurse(z)) 

library(stringi) 
desired <- as.data.frame(stri_list2matrix(split(utest,z2))) 
names(desired) <- names(z2)[unique(z2)] 

desired 
#  id var1 var3 section1.var1 section1.var2 section1.var3 section2.row.var1 
# 1 1 2 4    1    2    3     1 
# 2 <NA> <NA> <NA>   <NA>   <NA>   <NA>     2 
# 3 <NA> <NA> <NA>   <NA>   <NA>   <NA>     3 
# section2.row.var1 section2.row.var1 
# 1     4     7 
# 2     5     8 
# 3     6     9 
0

Poiché il problema non è ben definito quando le righe sono complesse strutture (cioè se ogni riga test contenuta nell'elenco test`, come si dovrebbe righe essere legati insieme. Anche se le righe della stessa tabella hanno differenti strutture?), la seguente soluzione dipende dalle righe che sono un elenco di valori.

Detto questo, sto cercando di indovinare che nel caso generale, la vostra lista test sarà contenere né valori, elenchi di valori, o elenchi di righe (dove file sono elenchi di valori). Inoltre, se le righe non vengono sempre chiamate "riga", questa soluzione funziona ancora.

temp <- lapply(test, 
       function(x){ 
        if(!is.list(x)) 
         # x is a value 
         return(x) 
        # x is a lis of rows or values 
        out <- do.call(cbind,x) 
        if(nrow(out)>1){ 
         # x is a list of rows 
         colnames(out)<-paste0(colnames(out),'.',rownames(out)) 
         rownames(out)<-rep_len(NA,nrow(out)) 
        } 
        return(out) 
       }) 

# a function that extends a matrix to a fixt number of rows (n) 
# by appending rows of NA's 
rowExtend <- function(x,N){ 
       if((!is.matrix(x))){ 
        out<-do.call(rbind,c(list(x),as.list(rep_len(NA,N - 1)))) 
        colnames(out) <- "" 
        out 
       }else if(nrow(x) < N) 
        do.call(rbind,c(list(x),as.list(rep_len(NA,N - nrow(x))))) 
       else 
        x 
      } 

# calculate the maximum number of rows 
.nrows <- sapply(temp,nrow) 
.nrows <- max(unlist(.nrows[!sapply(.nrows,is.null)])) 

# extend the shorter rows 
(temp2<-lapply(temp, rowExtend,.nrows)) 

# calculate new column namames 
newColNames <- mapply(function(x,y) { 
         if(nzchar(y)[1L]) 
          paste0(x,'.',y) 
         else x 
         }, 
         names(temp2), 
         lapply(temp2,colnames)) 


do.call(cbind,mapply(`colnames<-`,temp2,newColNames)) 

#> id var1 var3 section1.var1 section1.var2 section1.var3 section2.row.var1 section2.row.var2 section2.row.var3 
#> 1 2 4 1    2    3    1     4     7     
#> NA NA NA NA   NA   NA   2     5     8     
#> NA NA NA NA   NA   NA   3     6     9     
0

Questo inizia in modo simile alla risposta di Tiffany, ma diverge un po 'in seguito.

library(data.table) 

# flatten the first level 
flat = unlist(test, recursive = FALSE) 

# compute max length 
N = max(sapply(flat, length)) 

# pad NA's and convert to data.table (at this point it will *look* like the right answer) 
dt = as.data.table(lapply(flat, function(l) c(l, rep(NA, N - length(l))))) 

# but in reality some of the columns are lists - check by running sapply(dt, class) 
# so unlist them 
dt = dt[, lapply(.SD, unlist)] 
# id var1 var3 section1.var1 section1.var2 section1.var3 section2.row section2.row section2.row 
#1: 1 2 4    1    2    3   1   4   7 
#2: NA NA NA   NA   NA   NA   2   5   8 
#3: NA NA NA   NA   NA   NA   3   6   9