data.frame hàng thành một danh sách


123

Tôi có data.frame mà tôi muốn chuyển đổi thành danh sách theo hàng, nghĩa là mỗi hàng sẽ tương ứng với các phần tử danh sách của chính nó. Nói cách khác, tôi muốn có một danh sách miễn là data.frame có các hàng.

Cho đến nay, tôi đã giải quyết vấn đề này theo cách sau, nhưng tôi đang tự hỏi liệu có cách nào tốt hơn để tiếp cận vấn đề này không.

xy.df <- data.frame(x = runif(10),  y = runif(10))

# pre-allocate a list and fill it with a loop
xy.list <- vector("list", nrow(xy.df))
for (i in 1:nrow(xy.df)) {
    xy.list[[i]] <- xy.df[i,]
}

Câu trả lời:


164

Như thế này:

xy.list <- split(xy.df, seq(nrow(xy.df)))

Và nếu bạn muốn xy.dftên hàng là tên của danh sách đầu ra, bạn có thể thực hiện:

xy.list <- setNames(split(xy.df, seq(nrow(xy.df))), rownames(xy.df))

4
Lưu ý rằng, sau khi sử dụng splitmỗi phần tử có kiểu data.frame with 1 rows and N columnsthay vìlist of length N
Karol Daniluk

Tôi chỉ nói thêm rằng nếu bạn sử dụng, splitbạn có thể nên làm drop=Tnếu không thì mức ban đầu của bạn cho các yếu tố sẽ không giảm
Denis

51

Eureka!

xy.list <- as.list(as.data.frame(t(xy.df)))

1
Chăm sóc để chứng minh làm thế nào để sử dụng áp dụng?
Roman Luštrik

3
unlist(apply(xy.df, 1, list), recursive = FALSE). Tuy nhiên, giải pháp của flodel hiệu quả hơn so với sử dụng applyhoặc t.
Arun

11
Vấn đề ở đây là tchuyển đổi data.famethành a matrixđể các phần tử trong danh sách của bạn là vectơ nguyên tử, không phải là danh sách như OP yêu cầu. Nó thường không phải là vấn đề cho đến khi của bạn xy.dfchứa các loại hỗn hợp ...
Calimo

2
Nếu bạn muốn lặp lại các giá trị, tôi không khuyến khích apply. Nó thực sự chỉ là một vòng lặp for được thực hiện trong R. lapplythực hiện vòng lặp trong C, nhanh hơn đáng kể. Định dạng danh sách các hàng này thực sự thích hợp hơn nếu bạn thực hiện nhiều vòng lặp.
Liz Sander

1
Thêm bình luận khác từ tương lai, một applyphiên bản là.mapply(data.frame, xy.df, NULL)
alexis_laz

15

Nếu bạn muốn hoàn toàn lạm dụng data.frame (như tôi làm) và muốn giữ chức năng $, có một cách là chia data.frame của bạn thành một dòng data.frame được tập hợp trong danh sách:

> df = data.frame(x=c('a','b','c'), y=3:1)
> df
  x y
1 a 3
2 b 2
3 c 1

# 'convert' into a list of data.frames
ldf = lapply(as.list(1:dim(df)[1]), function(x) df[x[1],])

> ldf
[[1]]
x y
1 a 3    
[[2]]
x y
2 b 2
[[3]]
x y
3 c 1

# and the 'coolest'
> ldf[[2]]$y
[1] 2

Nó không chỉ là thủ dâm trí tuệ, mà còn cho phép 'chuyển đổi' data.frame thành danh sách các dòng của nó, giữ chỉ mục $ có thể hữu ích để sử dụng thêm với lapply (giả sử hàm bạn chuyển đến lapply sử dụng $ indexation này)


Làm thế nào để chúng tôi đặt chúng trở lại với nhau một lần nữa? Biến một danh sách các data.frames thành một data.frame?
Aaron McDaid

4
@AaronMcDaid Bạn có thể sử dụng do.call và rbind: df == do.call ("rbind",
ldf

@AaronMcDaid Hoặc data.table :: rbindlist (). Nếu khung dữ liệu ban đầu của bạn lớn, tốc độ tăng sẽ đáng kể.
Empiromancer,

8

Một giải pháp hiện đại hơn chỉ sử dụng purrr::transpose:

library(purrr)
iris[1:2,] %>% purrr::transpose()
#> [[1]]
#> [[1]]$Sepal.Length
#> [1] 5.1
#> 
#> [[1]]$Sepal.Width
#> [1] 3.5
#> 
#> [[1]]$Petal.Length
#> [1] 1.4
#> 
#> [[1]]$Petal.Width
#> [1] 0.2
#> 
#> [[1]]$Species
#> [1] 1
#> 
#> 
#> [[2]]
#> [[2]]$Sepal.Length
#> [1] 4.9
#> 
#> [[2]]$Sepal.Width
#> [1] 3
#> 
#> [[2]]$Petal.Length
#> [1] 1.4
#> 
#> [[2]]$Petal.Width
#> [1] 0.2
#> 
#> [[2]]$Species
#> [1] 1

8

Tôi đã làm việc này hôm nay cho data.frame (thực sự là data.table) với hàng triệu quan sát và 35 cột. Mục tiêu của tôi là trả về một danh sách data.frame (data.tables), mỗi cái có một hàng. Đó là, tôi muốn chia mỗi hàng thành một data.frame riêng biệt và lưu trữ chúng trong một danh sách.

Đây là hai phương pháp tôi nghĩ ra nhanh hơn khoảng 3 lần so split(dat, seq_len(nrow(dat)))với tập dữ liệu đó. Dưới đây, tôi chuẩn ba phương pháp trên tập dữ liệu 7500 hàng, 5 cột ( iris lặp lại 50 lần).

library(data.table)
library(microbenchmark)

microbenchmark(
split={dat1 <- split(dat, seq_len(nrow(dat)))},
setDF={dat2 <- lapply(seq_len(nrow(dat)),
                  function(i) setDF(lapply(dat, "[", i)))},
attrDT={dat3 <- lapply(seq_len(nrow(dat)),
           function(i) {
             tmp <- lapply(dat, "[", i)
             attr(tmp, "class") <- c("data.table", "data.frame")
             setDF(tmp)
           })},
datList = {datL <- lapply(seq_len(nrow(dat)),
                          function(i) lapply(dat, "[", i))},
times=20
) 

Điều này trả lại

Unit: milliseconds
       expr      min       lq     mean   median        uq       max neval
      split 861.8126 889.1849 973.5294 943.2288 1041.7206 1250.6150    20
      setDF 459.0577 466.3432 511.2656 482.1943  500.6958  750.6635    20
     attrDT 399.1999 409.6316 461.6454 422.5436  490.5620  717.6355    20
    datList 192.1175 201.9896 241.4726 208.4535  246.4299  411.2097    20

Mặc dù sự khác biệt không lớn như trong thử nghiệm trước của tôi, nhưng setDFphương pháp thẳng nhanh hơn đáng kể ở tất cả các cấp độ phân phối các lần chạy với max (setDF) <min (split) và attrphương pháp này thường nhanh hơn gấp đôi.

Phương thức thứ tư là phương pháp cực trị, là một lapplydanh sách lồng nhau đơn giản , trả về một danh sách lồng nhau. Phương pháp này minh họa chi phí xây dựng data.frame từ một danh sách. Hơn nữa, tất cả các phương pháp tôi đã thử với data.framehàm này gần như chậm hơn so với các data.tablekỹ thuật.

dữ liệu

dat <- vector("list", 50)
for(i in 1:50) dat[[i]] <- iris
dat <- setDF(rbindlist(dat))

6

Có vẻ như phiên bản hiện tại của purrrgói (0.2.2) là giải pháp nhanh nhất:

by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out

Hãy cùng so sánh các giải pháp thú vị nhất:

data("Batting", package = "Lahman")
x <- Batting[1:10000, 1:10]
library(benchr)
library(purrr)
benchmark(
    split = split(x, seq_len(.row_names_info(x, 2L))),
    mapply = .mapply(function(...) structure(list(...), class = "data.frame", row.names = 1L), x, NULL),
    purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out
)

Kết quả:

Benchmark summary:
Time units : milliseconds 
  expr n.eval   min  lw.qu median   mean  up.qu  max  total relative
 split    100 983.0 1060.0 1130.0 1130.0 1180.0 1450 113000     34.3
mapply    100 826.0  894.0  963.0  972.0 1030.0 1320  97200     29.3
 purrr    100  24.1   28.6   32.9   44.9   40.5  183   4490      1.0

Ngoài ra, chúng ta có thể nhận được kết quả tương tự với Rcpp:

#include <Rcpp.h>
using namespace Rcpp;

// [[Rcpp::export]]
List df2list(const DataFrame& x) {
    std::size_t nrows = x.rows();
    std::size_t ncols = x.cols();
    CharacterVector nms = x.names();
    List res(no_init(nrows));
    for (std::size_t i = 0; i < nrows; ++i) {
        List tmp(no_init(ncols));
        for (std::size_t j = 0; j < ncols; ++j) {
            switch(TYPEOF(x[j])) {
                case INTSXP: {
                    if (Rf_isFactor(x[j])) {
                        IntegerVector t = as<IntegerVector>(x[j]);
                        RObject t2 = wrap(t[i]);
                        t2.attr("class") = "factor";
                        t2.attr("levels") = t.attr("levels");
                        tmp[j] = t2;
                    } else {
                        tmp[j] = as<IntegerVector>(x[j])[i];
                    }
                    break;
                }
                case LGLSXP: {
                    tmp[j] = as<LogicalVector>(x[j])[i];
                    break;
                }
                case CPLXSXP: {
                    tmp[j] = as<ComplexVector>(x[j])[i];
                    break;
                }
                case REALSXP: {
                    tmp[j] = as<NumericVector>(x[j])[i];
                    break;
                }
                case STRSXP: {
                    tmp[j] = as<std::string>(as<CharacterVector>(x[j])[i]);
                    break;
                }
                default: stop("Unsupported type '%s'.", type2name(x));
            }
        }
        tmp.attr("class") = "data.frame";
        tmp.attr("row.names") = 1;
        tmp.attr("names") = nms;
        res[i] = tmp;
    }
    res.attr("names") = x.attr("row.names");
    return res;
}

Bây giờ caompare với purrr:

benchmark(
    purrr = by_row(x, function(v) list(v)[[1L]], .collate = "list")$.out,
    rcpp = df2list(x)
)

Các kết quả:

Benchmark summary:
Time units : milliseconds 
 expr n.eval  min lw.qu median mean up.qu   max total relative
purrr    100 25.2  29.8   37.5 43.4  44.2 159.0  4340      1.1
 rcpp    100 19.0  27.9   34.3 35.8  37.2  93.8  3580      1.0

điểm chuẩn trên một tập dữ liệu nhỏ của 150 hàng không có ý nghĩa nhiều như không ai nhận thấy bất kỳ sự khác biệt trong micro và nó không quy mô
David Arenburg

4
by_row()hiện đã chuyển đếnlibrary(purrrlyr)
MrHopko

Và ngoài việc có trong purrrlyr, nó sắp không được dùng nữa. Hiện nay có nhiều phương pháp khác kết hợp tidyr :: yến, dplyr :: đột biến purrr :: map để đạt được kết quả tương tự
Mike Stanley

3

Một số tùy chọn khác:

Với asplit

asplit(xy.df, 1)
#[[1]]
#     x      y 
#0.1137 0.6936 

#[[2]]
#     x      y 
#0.6223 0.5450 

#[[3]]
#     x      y 
#0.6093 0.2827 
#....

Với splitrow

split(xy.df, row(xy.df)[, 1])

#$`1`
#       x      y
#1 0.1137 0.6936

#$`2`
#       x     y
#2 0.6223 0.545

#$`3`
#       x      y
#3 0.6093 0.2827
#....

dữ liệu

set.seed(1234)
xy.df <- data.frame(x = runif(10),  y = runif(10))

2

Cách tốt nhất cho tôi là:

Dữ liệu mẫu:

Var1<-c("X1",X2","X3")
Var2<-c("X1",X2","X3")
Var3<-c("X1",X2","X3")

Data<-cbind(Var1,Var2,Var3)

ID    Var1   Var2  Var3 
1      X1     X2    X3
2      X4     X5    X6
3      X7     X8    X9

Chúng tôi gọi là BBmiscthư viện

library(BBmisc)

data$lists<-convertRowsToList(data[,2:4])

Và kết quả sẽ là:

ID    Var1   Var2  Var3  lists
1      X1     X2    X3   list("X1", "X2", X3") 
2      X4     X5    X6   list("X4","X5", "X6") 
3      X7     X8    X9   list("X7,"X8,"X9) 

1

Một cách khác là chuyển đổi df thành ma trận, sau đó áp dụng lappychức năng áp dụng danh sách trên nó:ldf <- lapply(as.matrix(myDF), function(x)x)


1

Một giải pháp thay thế khác bằng cách sử dụng library(purrr)(có vẻ nhanh hơn một chút trên data.frame lớn)

flatten(by_row(xy.df, ..f = function(x) flatten_chr(x), .labels = FALSE))

3
`by_row ()` hiện đã chuyển sang `thư viện (purrrlyr) '
MrHopko 17/08/17

1

Giống như @flodel đã viết: Điều này chuyển đổi khung dữ liệu của bạn thành một danh sách có cùng số phần tử với số hàng trong khung dữ liệu:

NewList <- split(df, f = seq(nrow(df)))

Bạn có thể bổ sung thêm một hàm để chỉ chọn những cột không phải là NA trong mỗi phần tử của danh sách:

NewList2 <- lapply(NewList, function(x) x[,!is.na(x)])

0

Các by_rowchức năng từ purrrlyrgói sẽ làm việc này cho bạn.

Ví dụ này chứng minh

myfn <- function(row) {
  #row is a tibble with one row, and the same number of columns as the original df
  l <- as.list(row)
  return(l)
}

list_of_lists <- purrrlyr::by_row(df, myfn, .labels=FALSE)$.out

Theo mặc định, giá trị trả về từ myfnđược đưa vào một cột danh sách mới trong df được gọi .out. Ở $.outcuối câu lệnh trên ngay lập tức chọn cột này, trả về một danh sách các danh sách.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.