2015-01-26 14 views
5

tôi có một danh sách với cấu trúc ví dụ sau:Flatten một danh sách với phức tạp cấu trúc lồng nhau

> 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 

Chú ý rằng danh sách section2 chứa các yếu tố được đặt tên rows. Chúng đại diện cho nhiều bản ghi. Những gì tôi có là một danh sách lồng nhau trong đó một số phần tử ở cấp cơ sở và những phần tử khác là nhiều bản ghi lồng nhau cho cùng một quan sát. Tôi muốn đầu ra sau trong một định dạng 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 

yếu tố gốc cấp nên cư hàng đầu tiên, trong khi row yếu tố nên có hàng riêng của họ. Là một biến chứng thêm, số lượng biến trong các mục nhập row có thể thay đổi.

+0

Tại sao bạn muốn kết quả mong muốn này? Điều đó có vẻ như một định dạng dữ liệu bất tiện để làm việc. – A5C1D2H2I1M1N2O1R2T1

+0

Tôi đang thực hiện một yêu cầu xà phòng trả về một bảng html với một cấu trúc rất lồng nhau trong một danh sách lồng nhau. Tôi không chắc chắn lý do tại sao bạn nghĩ rằng đầu ra mong muốn là bất tiện. Nó tạo lại bảng html ở định dạng data.frame và điền vào các giá trị NA trong đó một mục mở rộng nhiều hàng. – Zelazny7

+0

Bạn có thể cung cấp thêm một hoặc hai trường hợp thử nghiệm vì bạn đã thêm tiền thưởng vào trường hợp này chưa.Bạn đề cập đến rằng bạn đang tìm kiếm một giải pháp "chung", vì vậy sẽ rất tốt nếu có khả năng biết được những kịch bản nào khác cần được tính toán. – A5C1D2H2I1M1N2O1R2T1

Trả lời

3

Đây là cách tiếp cận chung. Nó không cho rằng bạn sẽ chỉ có ba hàng; nó sẽ làm việc với tuy nhiên nhiều hàng bạn có. Và nếu một giá trị bị thiếu trong cấu trúc lồng nhau (ví dụ: var1 không tồn tại đối với một số danh sách phụ trong phần 2), thì mã trả về đúng NA cho ô đó.

Ví dụ: nếu chúng ta sử dụng các dữ liệu sau:

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

Cách tiếp cận chung là sử dụng tan để tạo ra một dataframe bao gồm thông tin về cấu trúc lồng nhau, và sau đó dcast để nhào nặn nó thành định dạng mà bạn mong muốn.

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

Ý tưởng chính của giải pháp này là làm phẳng tất cả các danh sách phụ ngoại trừ các danh sách con có tên 'hàng'. Điều này có thể được thực hiện bằng cách tạo một ID duy nhất cho mỗi phần tử danh sách (được lưu trữ trong z) và sau đó yêu cầu tất cả các phần tử trong một hàng duy nhất phải có cùng một ID (được lưu trữ trong z2; phải viết một hàm đệ quy để đi qua lồng nhau) danh sách). Sau đó, z2 có thể được sử dụng để nhóm các phần tử thuộc cùng một hàng. Danh sách kết quả có thể được chuyển đổi thành dạng ma trận sử dụng stri_list2matrix từ gói stringi và sau đó được chuyển đổi thành một khung dữ liệu.

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

Kể từ khi vấn đề của bạn không được xác định rõ khi hàng có cấu trúc phức tạp (ví dụ: nếu mỗi hàng trong test chứa danh sách test`, làm thế nào nên hàng bị ràng buộc với nhau. Ngoài ra những gì nếu hàng trong cùng một bảng có khác nhau cấu trúc?), giải pháp sau đây phụ thuộc vào các hàng là danh sách các giá trị.

Điều đó nói rằng, tôi đoán rằng trong trường hợp chung, danh sách của bạn test sẽ chứa hoặc giá trị, danh sách các giá trị, hoặc danh sách các hàng (nơi hàng là danh sách các giá trị). Ngoài ra, nếu hàng không phải lúc nào cũng được gọi là "hàng" thì giải pháp này vẫn hoạt động.

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

Điều này bắt đầu tương tự như câu trả lời của tiffany, nhưng phân kỳ sau một chút.

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 
Các vấn đề liên quan