2015-12-10 6 views
13

voglio trovare il miglior "modo R" per appiattire un dataframe che assomiglia a questo:Come appiattire il frame di dati R che contiene gli elenchi?

CAT COUNT  TREAT 
    A  1,2,3  Treat-a, Treat-b 
    B  4,5  Treat-c,Treat-d,Treat-e 

Così sarà strutturata in questo modo:

CAT COUNT1 COUNT2 COUNT3 TREAT1 TREAT2 TREAT3 
    A 1  2  3  Treat-a Treat-b NA 
    B 4  5  NA  Treat-c Treat-d Treat-e 

codice di esempio per generare il dataframe fonte :

df<-data.frame(CAT=c("A","B")) 
df$COUNT <-list(1:3,4:5) 
df$TREAT <-list(paste("Treat-", letters[1:2],sep=""),paste("Treat-", letters[3:5],sep="")) 

Credo di aver bisogno di una combinazione di rbind e unlist? Qualsiasi aiuto sarebbe molto apprezzato. - Tim

+1

Quanto sono grandi i dati "reali" (le prestazioni sono un problema?) – Heroka

+2

cSplit() dal pacchetto splitstackshape sarebbe una buona opzione. – jazzurro

+0

Con il tuo esempio 'df [2: 3] <- lapply (df [, 2: 3], function (x) do.call (rbind, lapply (x," [", 1: 3))) sembra un buon inizio – nicola

risposta

9

Ecco un altro modo di base r

df<-data.frame(CAT=c("A","B")) 
df$COUNT <-list(1:3,4:5) 
df$TREAT <-list(paste("Treat-", letters[1:2],sep=""),paste("Treat-", letters[3:5],sep="")) 

Creat funzione di supporto e bis per fare il lavoro

f <- function(l) { 
    if (!is.list(l)) return(l) 
    do.call('rbind', lapply(l, function(x) `length<-`(x, max(lengths(l))))) 
} 

testare sempre il codice di

f(df$TREAT) 

#   [,1]  [,2]  [,3]  
# [1,] "Treat-a" "Treat-b" NA  
# [2,] "Treat-c" "Treat-d" "Treat-e" 

Applicare

df[] <- lapply(df, f) 
df 

#  CAT COUNT.1 COUNT.2 COUNT.3 TREAT.1 TREAT.2 TREAT.3 
# 1 A  1  2  3 Treat-a Treat-b <NA> 
# 2 B  4  5  NA Treat-c Treat-d Treat-e 
+0

E, quindi aggiungere un altro 'do.call (data.frame, ...) 'su questo. La loro 'lista' è stata ora" appiattita "su una' matrice ', ma il numero di colonne è ancora 3. – A5C1D2H2I1M1N2O1R2T1

10

Ecco una soluzione che utilizza la base R, accetta i vettori di qualsiasi lunghezza all'interno dell'elenco e non è necessario specificare quali colonne del dataframe si desidera comprimere. Parte della soluzione è stata generata utilizzando la risposta this.

df2 <- do.call(cbind,lapply(df,function(x){ 
    #check if it is a list, otherwise just return as is 
    if(is.list(x)){ 
    return(data.frame(t(sapply(x,'[',seq(max(sapply(x,length))))))) 
    } else{ 
    return(x) 
    } 
})) 

Al R 3.2 c'è lengths sostituire sapply(x, length) pure,

df3 <- do.call(cbind.data.frame, lapply(df, function(x) { 
    # check if it is a list, otherwise just return as is 
    if (is.list(x)) { 
    data.frame(t(sapply(x,'[', seq(max(lengths(x)))))) 
    } else { 
    x 
} 
})) 

dati utilizzati:

df <- structure(list(CAT = structure(1:2, .Label = c("A", "B"), class = "factor"), 
    COUNT = list(1:3, 4:5), TREAT = list(c("Treat-a", "Treat-b" 
    ), c("Treat-c", "Treat-d", "Treat-e"))), .Names = c("CAT", 
"COUNT", "TREAT"), row.names = c(NA, -2L), class = "data.frame") 
4

C'è una risposta cancellato qui che indica che "splitstackshape" potrebbe essere utilizzato per questo . Può, ma la risposta cancellata ha usato la funzione sbagliata. Invece, dovrebbe utilizzare la funzione listCol_w. Sfortunatamente, nella sua forma attuale, questa funzione non è vettorizzata su colonne, quindi è necessario nidificare le chiamate a listCol_w per ogni colonna che deve essere appiattita.

Ecco il metodo:

library(splitstackshape) 
listCol_w(listCol_w(df, "COUNT", fill = NA), "TREAT", fill = NA) 
## CAT COUNT_fl_1 COUNT_fl_2 COUNT_fl_3 TREAT_fl_1 TREAT_fl_2 TREAT_fl_3 
## 1: A   1   2   3 Treat-a Treat-b   NA 
## 2: B   4   5   NA Treat-c Treat-d Treat-e 

Nota che fill = NA è stato specificato perchè default fill = NA_character_, che altrimenti costringere tutti i valori a carattere.


Un'altra alternativa sarebbe quella di utilizzare transpose da "data.table". Ecco una possibile implementazione (sembra spaventoso, ma l'utilizzo della funzione è semplice). I vantaggi sono che (1) è possibile specificare le colonne da appiattire, (2) è possibile decidere se si desidera eliminare la colonna originale o meno, e (3) è veloce.

flatten <- function(indt, cols, drop = FALSE) { 
    require(data.table) 
    if (!is.data.table(indt)) indt <- as.data.table(indt) 
    x <- unlist(indt[, lapply(.SD, function(x) max(lengths(x))), .SDcols = cols]) 
    nams <- paste(rep(cols, x), sequence(x), sep = "_") 
    indt[, (nams) := unlist(lapply(.SD, transpose), recursive = FALSE), .SDcols = cols] 
    if (isTRUE(drop)) { 
    indt[, (nams) := unlist(lapply(.SD, transpose), recursive = FALSE), 
     .SDcols = cols][, (cols) := NULL] 
    } 
    indt[] 
} 

L'utilizzo sarebbe ...

Mantenere colonne originali:

flatten(df, c("COUNT", "TREAT")) 
# CAT COUNT     TREAT COUNT_1 COUNT_2 COUNT_3 TREAT_1 TREAT_2 TREAT_3 
# 1: A 1,2,3   Treat-a,Treat-b  1  2  3 Treat-a Treat-b  NA 
# 2: B 4,5 Treat-c,Treat-d,Treat-e  4  5  NA Treat-c Treat-d Treat-e 

eliminazione di colonne originali:

flatten(df, c("COUNT", "TREAT"), TRUE) 
# CAT COUNT_1 COUNT_2 COUNT_3 TREAT_1 TREAT_2 TREAT_3 
# 1: A  1  2  3 Treat-a Treat-b  NA 
# 2: B  4  5  NA Treat-c Treat-d Treat-e 

Vedi this gist per un confronto con le altre soluzioni proposte.

Problemi correlati