2015-12-10 16 views
13

chcę znaleźć najlepszą „R drogę” spłaszczyć się dataframe, który wygląda tak:Jak spłaszczyć ramkę danych R zawierającą listy?

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

tak będzie skonstruowany tak:

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 

Przykładowy kod, aby wygenerować dataframe źródłowy :

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="")) 

Wierzę, że potrzebuję kombinacji rbind i unlist? Każda pomoc będzie wielce ceniona. - Tim

+1

Jak duże są twoje "prawdziwe" dane (czy wydajność jest problemem?) – Heroka

+2

cSplit() z pakietu splitstackshape byłby dobrym rozwiązaniem. – jazzurro

+0

Na twoim przykładzie 'df [2: 3] <- lapply (df [, 2: 3], function (x) do.call (rbind, lapply (x," [", 1: 3))) wydaje się być fajny początek. – nicola

Odpowiedz

9

tu inny sposób zasady 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 Funkcja pomocnika ea do pracy

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

zawsze testować swój kod

f(df$TREAT) 

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

zastosować go

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

A następnie dodaj jeszcze jedną 'do.call (data.frame, ...)' na tym. Ich "lista" została teraz "spłaszczona" do "macierzy", ale liczba kolumn jest wciąż 3. – A5C1D2H2I1M1N2O1R2T1

10

Oto rozwiązanie wykorzystujące bazę R, akceptując wektory o dowolnej długości na liście i nie trzeba określać kolumn kolumny zawierającej ramkę danych, którą chcesz zwinąć. Część rozwiązania została wygenerowana przy użyciu odpowiedzi 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) 
    } 
})) 

jako R 3.2 jest lengths zastąpić sapply(x, length) również stosowane

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 
} 
})) 

danych:

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

Jest usunięte odpowiedź tutaj, który wskazuje, że „splitstackshape” mogą być wykorzystane do tego . Może, ale usunięta odpowiedź wykorzystała złą funkcję. Zamiast tego powinien użyć funkcji listCol_w. Niestety, w swojej obecnej postaci funkcja ta nie jest wektoryzowana między kolumnami, więc musisz zagnieżdżać wywołania do listCol_w dla każdej kolumny, która wymaga spłaszczenia.

Oto podejście:

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 

Zauważ, że fill = NA została określona, ​​ponieważ domyślnie fill = NA_character_, które w przeciwnym razie byłyby przymuszania wszystkie wartości do charakteru.


Inną alternatywą byłoby użycie transpose z "data.table". Oto możliwa implementacja (wygląda przerażająco, ale korzystanie z tej funkcji jest łatwe). Korzyści są następujące: (1) możesz określić kolumny do spłaszczenia, (2) możesz zdecydować, czy chcesz upuścić oryginalną kolumnę, czy nie, i (3) jest ona szybka.

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[] 
} 

Użycie byłoby ...

Keeping oryginalne kolumny:

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 

Dropping oryginalne kolumny:

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 

Zobacz this gist dla porównania z innymi proponowanymi rozwiązaniami.

Powiązane problemy