2015-12-10 17 views
13

Tôi muốn tìm "R cách" tốt nhất để san bằng một dataframe trông như thế này:Làm cách nào để làm phẳng khung dữ liệu R chứa danh sách?

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

Vì vậy, nó sẽ được cấu trúc như thế này:

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 

Ví dụ mã để tạo ra dataframe nguồn :

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

Tôi tin rằng tôi cần sự kết hợp giữa rbind và không công khai? Mọi sự trợ giúp sẽ rất được trân trọng. - Tim

+1

Dữ liệu 'thực' của bạn lớn đến mức nào (hiệu suất có phải là vấn đề không?) – Heroka

+2

cSplit() từ gói splitstackshape sẽ là một lựa chọn tốt. – jazzurro

+0

Với ví dụ của bạn 'df [2: 3] <- lapply (df [, 2: 3], hàm (x) do.call (rbind, lapply (x," [", 1: 3)))' có vẻ là một khởi đầu tốt đẹp. – nicola

Trả lời

9

Dưới đây là một cách khác trong cơ sở 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="")) 

Tạo ea chức năng helper để làm công việc

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

Luôn luôn kiểm tra mã của bạn

f(df$TREAT) 

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

Áp dụng nó

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

Và, sau đó thêm một 'do.call (data.frame, ...)' vào đây. Danh sách 'list' của họ giờ đã được" làm phẳng "thành một' ma trận', nhưng số cột vẫn là 3. – A5C1D2H2I1M1N2O1R2T1

10

Đây là giải pháp sử dụng cơ sở R, chấp nhận vectơ có độ dài bất kỳ bên trong danh sách của bạn và không cần chỉ định cột nào của khung dữ liệu bạn muốn thu gọn. Một phần của giải pháp được tạo ra bằng cách sử dụng câu trả lời 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) 
    } 
})) 

Tính đến R 3.2 có lengths để thay thế sapply(x, length) là tốt,

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

dữ liệu sử dụng:

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ó một câu trả lời đã xóa ở đây chỉ ra rằng "splitstackshape" có thể được sử dụng cho điều này . Nó có thể, nhưng câu trả lời đã xóa sử dụng chức năng sai. Thay vào đó, nó nên sử dụng hàm listCol_w. Thật không may, trong hình thức hiện tại của nó, chức năng này không được vectorized trên các cột, vì vậy bạn sẽ cần phải lồng các cuộc gọi đến listCol_w cho mỗi cột cần được làm phẳng.

Dưới đây là cách tiếp cận:

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 

Lưu ý rằng fill = NA đã được xác định bởi vì nó mặc định là fill = NA_character_, mà nếu không sẽ ép buộc tất cả các giá trị để nhân vật.


Một giải pháp thay thế khác là sử dụng transpose từ "data.table". Đây là một thực hiện có thể (trông đáng sợ, nhưng sử dụng chức năng là dễ dàng). Lợi ích là (1) bạn có thể chỉ định các cột để làm phẳng, (2) bạn có thể quyết định xem bạn muốn thả cột ban đầu hay không, và (3) nó nhanh.

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

Cách sử dụng sẽ là ...

Giữ cột gốc:

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 

Thả cột gốc:

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 

Xem this gist để so sánh với các giải pháp khác được đề xuất.

Các vấn đề liên quan