data.frame แถวไปยังรายการ


123

ฉันมี data.frame ซึ่งฉันต้องการแปลงเป็นรายการตามแถวซึ่งหมายความว่าแต่ละแถวจะสอดคล้องกับองค์ประกอบรายการของมันเอง กล่าวอีกนัยหนึ่งคือฉันต้องการรายการที่มีความยาวเท่าที่ data.frame มีแถว

จนถึงตอนนี้ฉันได้แก้ไขปัญหานี้แล้วในลักษณะต่อไปนี้ แต่ฉันสงสัยว่ามีวิธีที่ดีกว่าในการแก้ไขปัญหานี้หรือไม่

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

คำตอบ:


164

แบบนี้:

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

และถ้าคุณต้องการxy.dfให้ชื่อแถวเป็นชื่อของรายการผลลัพธ์คุณสามารถทำได้:

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

4
โปรดทราบว่าหลังจากใช้splitแต่ละองค์ประกอบจะมีประเภทdata.frame with 1 rows and N columnsแทนlist of length N
Karol Daniluk

ฉันจะเพิ่มว่าถ้าคุณใช้splitคุณควรทำdrop=Tอย่างอื่นระดับดั้งเดิมของคุณสำหรับปัจจัยจะไม่ลดลง
เดนิส

51

ยูเรก้า!

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

1
สนใจสาธิตวิธีใช้สมัคร?
Roman Luštrik

3
unlist(apply(xy.df, 1, list), recursive = FALSE). อย่างไรก็ตามการแก้ปัญหา flodel เป็นมีประสิทธิภาพมากขึ้นกว่าการใช้หรือapply t
อรุณ

11
ปัญหาที่นี่คือการtแปลงdata.fameเป็น a matrixเพื่อให้องค์ประกอบในรายการของคุณเป็นเวกเตอร์อะตอมไม่ใช่รายการตามที่ OP ร้องขอ โดยปกติจะไม่มีปัญหาจนกว่าคุณจะxy.dfมีประเภทผสม ...
Calimo

2
applyหากคุณต้องการที่จะห่วงมากกว่าค่าที่ผมไม่แนะนำให้ จริงๆแล้วมันเป็นเพียง for loop ที่ใช้งานใน R. lapplyทำการวนลูปใน C ซึ่งเร็วกว่ามาก รูปแบบรายการของแถวนี้ดีกว่าหากคุณกำลังวนซ้ำมาก ๆ
Liz Sander

1
เพิ่มความคิดเห็นจากอนาคตอีกapplyเวอร์ชันคือ.mapply(data.frame, xy.df, NULL)
alexis_laz

15

หากคุณต้องการใช้ data.frame ในทางที่ผิดโดยสิ้นเชิง (เหมือนที่ฉันทำ) และต้องการเก็บฟังก์ชัน $ ไว้วิธีหนึ่งคือแยก data.frame ของคุณออกเป็น data.frame แบบบรรทัดเดียวที่รวบรวมในรายการ:

> 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

ไม่ใช่แค่การสำเร็จความใคร่ด้วยตนเองทางปัญญาเท่านั้น แต่ยังช่วยให้ 'แปลง' data.frame เป็นรายการของบรรทัดโดยเก็บค่าดัชนี $ ซึ่งจะเป็นประโยชน์สำหรับการใช้งานเพิ่มเติมกับ lapply (สมมติว่าฟังก์ชันที่คุณส่งผ่านไปยัง lapply จะใช้การจัดทำดัชนี $ นี้)


เราจะนำกลับมารวมกันอีกครั้งได้อย่างไร? เปลี่ยนรายการเป็นรายการdata.frameเดียวdata.frame?
Aaron McDaid

4
@AaronMcDaid คุณสามารถใช้ do.call และ rbind: df == do.call ("rbind", ldf)
random_forest_fanatic

@AaronMcDaid หรือ data.table :: rbindlist () หากกรอบข้อมูลเดิมของคุณมีขนาดใหญ่ความเร็วที่เพิ่มขึ้นจะมีมาก
Empiromancer

8

โซลูชันที่ทันสมัยกว่าใช้เฉพาะ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

วันนี้ฉันกำลังดำเนินการกับ data.frame (จริงๆคือ data.table) ที่มีการสังเกตนับล้านและ 35 คอลัมน์ เป้าหมายของฉันคือส่งคืนรายการ data.frames (data.tables) แต่ละรายการด้วยแถวเดียว นั่นคือฉันต้องการแยกแต่ละแถวออกเป็น data.frame แยกกันและเก็บไว้ในรายการ

นี่คือสองวิธีที่ฉันคิดขึ้นซึ่งเร็วกว่าsplit(dat, seq_len(nrow(dat)))ชุดข้อมูลนั้นประมาณ 3 เท่า ด้านล่างฉันเปรียบเทียบสามวิธีในแถว 7500 ชุดข้อมูล 5 คอลัมน์ ( ม่านตาซ้ำ 50 ครั้ง)

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
) 

ผลตอบแทนนี้

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

แม้ว่าความแตกต่างจะไม่มากเท่าในการทดสอบครั้งก่อนของฉัน แต่setDFวิธีการตรงนั้นเร็วกว่าอย่างมีนัยสำคัญในทุกระดับของการกระจายการวิ่งที่มีค่าสูงสุด (setDF) <นาที (แยก) และattrโดยทั่วไปวิธีนี้จะเร็วกว่าสองเท่า

วิธีที่สี่คือแชมป์เปี้ยนสุดขีดซึ่งเป็นการซ้อนแบบง่ายๆlapplyโดยส่งคืนรายการที่ซ้อนกัน วิธีนี้เป็นตัวอย่างต้นทุนในการสร้าง data.frame จากรายการ ยิ่งไปกว่านั้นวิธีการทั้งหมดที่ฉันลองใช้กับdata.frameฟังก์ชันนี้มีลำดับความสำคัญโดยประมาณช้ากว่าdata.tableเทคนิคโดยประมาณ

ข้อมูล

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

6

ดูเหมือนว่าpurrrแพ็คเกจ (0.2.2) เวอร์ชันปัจจุบันจะเป็นวิธีที่เร็วที่สุด:

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

ลองเปรียบเทียบโซลูชันที่น่าสนใจที่สุด:

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
)

Rsults:

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

นอกจากนี้เรายังได้ผลลัพธ์เดียวกันด้วย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;
}

ตอนนี้เปรียบเทียบกับpurrr:

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

ผล:

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

การเปรียบเทียบกับชุดข้อมูลขนาดเล็ก 150 แถวไม่สมเหตุสมผลมากนักเนื่องจากไม่มีใครสังเกตเห็นความแตกต่างในหน่วยไมโครวินาทีและไม่ได้ปรับขนาด
David Arenburg

4
by_row()ได้ย้ายไปที่library(purrrlyr)
MrHopko

และนอกเหนือจากการอยู่ใน purrrlyr แล้วก็กำลังจะเลิกใช้งาน ตอนนี้มีวิธีการอื่น ๆ รวม tidyr :: nest, dplyr :: mutate purrr :: map เพื่อให้ได้ผลลัพธ์เดียวกัน
Mike Stanley

3

ตัวเลือกเพิ่มเติมสองสามตัวเลือก:

กับ 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 
#....

ด้วยsplitและrow

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
#....

ข้อมูล

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

2

วิธีที่ดีที่สุดสำหรับฉันคือ:

ตัวอย่างข้อมูล:

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

เราเรียก BBmiscห้องสมุด

library(BBmisc)

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

และผลลัพธ์จะเป็น:

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

อีกทางเลือกหนึ่งคือการแปลง df เป็นเมทริกซ์จากนั้นใช้ list ใช้lappyฟังก์ชันทับ:ldf <- lapply(as.matrix(myDF), function(x)x)


1

อีกทางเลือกหนึ่งในการใช้library(purrr)(ซึ่งดูเหมือนจะเร็วกว่าเล็กน้อยใน data.frame ขนาดใหญ่)

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

3
`by_row ()` ได้ย้ายไปที่ 'library (purrrlyr) `
MrHopko

1

เช่นเดียวกับที่ @flodel เขียนว่า: สิ่งนี้จะแปลงดาต้าเฟรมของคุณเป็นรายการที่มีจำนวนองค์ประกอบเท่ากับจำนวนแถวในดาต้าเฟรม:

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

คุณสามารถเพิ่มฟังก์ชันเพื่อเลือกเฉพาะคอลัมน์ที่ไม่ใช่ NAในแต่ละองค์ประกอบของรายการ:

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

0

by_rowฟังก์ชั่นจากpurrrlyrแพ็กเกจจะทำเพื่อคุณ

ตัวอย่างนี้แสดงให้เห็น

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

โดยค่าเริ่มต้นค่ากลับมาจากการmyfnถูกใส่ลงไปใหม่คอลัมน์รายการใน DF .outที่เรียกว่า $.outในตอนท้ายของคำสั่งดังกล่าวทันทีเลือกคอลัมน์นี้กลับรายการแสดงรายการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.