2011-05-09 64 views
29

Vừa có cuộc trò chuyện với đồng nghiệp về điều này, và chúng tôi nghĩ rằng nó đáng để xem những gì mọi người trong SO đất phải nói. Giả sử tôi có một danh sách với các phần tử N, trong đó mỗi phần tử là một vectơ có độ dài X. Bây giờ giả sử tôi muốn biến đổi nó thành một tệp dữ liệu. Như với hầu hết mọi thứ trong R, có nhiều cách để lột da mèo, chẳng hạn như as.dataframe, sử dụng gói plyr, kết hợp do.call với cbind, phân bổ trước DF và điền vào, và các cách khác.Danh sách hiệu quả nhất cho phương pháp data.frame?

Vấn đề được trình bày là những gì xảy ra khi N hoặc X (trong trường hợp của chúng tôi là X) trở nên cực kỳ lớn. Có một phương pháp làm da mèo nào đáng chú ý hơn khi hiệu quả (đặc biệt là về mặt bộ nhớ) là của bản chất?

Trả lời

26

Từ một data.frame đã là một danh sách và bạn biết rằng mỗi phần tử danh sách là chiều dài tương tự (X), điều nhanh nhất có lẽ sẽ chỉ cập nhật classrow.names thuộc tính:

set.seed(21) 
n <- 1e6 
x <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) 
x <- c(x,x,x,x,x,x) 

system.time(a <- as.data.frame(x)) 
system.time(b <- do.call(data.frame,x)) 
system.time({ 
    d <- x # Skip 'c' so Joris doesn't down-vote me! ;-) 
    class(d) <- "data.frame" 
    rownames(d) <- 1:n 
    names(d) <- make.unique(names(d)) 
}) 

identical(a, b) # TRUE 
identical(b, d) # TRUE 

Cập nhật - đây là ~ 2x nhanh hơn tạo d:

system.time({ 
    e <- x 
    attr(e, "row.names") <- c(NA_integer_,n) 
    attr(e, "class") <- "data.frame" 
    attr(e, "names") <- make.names(names(e), unique=TRUE) 
}) 

identical(d, e) # TRUE 

cập nhật 2 - tôi cho về tiêu thụ bộ nhớ. Bản cập nhật cuối cùng tạo hai bản sao của e. Sử dụng chức năng attributes làm giảm chỉ có một bản sao.

set.seed(21) 
f <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) 
f <- c(f,f,f,f,f,f) 
tracemem(f) 
system.time({ # makes 2 copies 
    attr(f, "row.names") <- c(NA_integer_,n) 
    attr(f, "class") <- "data.frame" 
    attr(f, "names") <- make.names(names(f), unique=TRUE) 
}) 

set.seed(21) 
g <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) 
g <- c(g,g,g,g,g,g) 
tracemem(g) 
system.time({ # only makes 1 copy 
    attributes(g) <- list(row.names=c(NA_integer_,n), 
    class="data.frame", names=make.names(names(g), unique=TRUE)) 
}) 

identical(f,g) # TRUE 
+2

Để lại "có lẽ" ra khỏi câu trả lời và nó đúng. Nó cũng đúng nếu bạn thực hiện một hàm bằng cách sử dụng các cuộc gọi đó và thay thế cheat của việc biết n bằng lệnh chiều dài. Hàm mới của bạn tương đương với data.frame() sau khi xóa tất cả các kiểm tra mở rộng. Vì vậy, nếu bạn biết chắc chắn bạn đang giao cuộc gọi đầu vào phù hợp thì chỉ cần làm những gì Josh đề nghị cho tốc độ. Nếu bạn không chắc chắn thì data.frame an toàn hơn và do.call (data.frame, x)) là nhanh nhất tiếp theo (đủ kỳ quặc). – John

+2

Xem 'plyr :: quickdf' để biết chính xác chức năng này. – hadley

+0

@hadley: 'plyr :: quickdf' không cung cấp chính xác chức năng này; cụ thể là nó không tạo tên cột duy nhất. 'plyr ::: make_names' chỉ thay thế các tên còn thiếu và không có' unique = 'arg như' base :: make.names'. –

10

Điều này dường như cần một đề xuất data.table cho hiệu quả cho bộ dữ liệu lớn là bắt buộc. Đáng chú ý setattr bộ bằng cách tham khảo và không sao chép

library(data.table) 
set.seed(21) 
n <- 1e6 
h <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) 
h <- c(h,h,h,h,h,h) 
tracemem(h) 

system.time({h <- as.data.table(h) 
      setattr(h, 'names', make.names(names(h), unique=T))}) 

as.data.table, tuy nhiên không tạo một bản sao.


Edit - không có phiên bản sao chép

Sử dụng @ đề nghị setattr(h,'class','data.frame') mà sẽ chuyển đổi sang data.frame bằng cách tham khảo (không có bản sao) MatthewDowle của

set.seed(21) 
n <- 1e6 
i <- list(x=rnorm(n), y=rnorm(n), z=rnorm(n)) 
i <- c(i,i,i,i,i,i) 
tracemem(i) 

system.time({ 
    setattr(i, 'class', 'data.frame') 
    setattr(i, "row.names", c(NA_integer_,n)) 

    setattr(i, "names", make.names(names(i), unique=TRUE)) 

}) 
+1

setattr (h, "class", "data.frame") phải là ngay lập tức, không có bản sao nào cả. –

+0

@MatthewDowle - Như là 'setattr (h," class "," data.table ")';) (Rất cool, BTW). –

+0

@ JoshO'Brien Thật vậy :) Chỉ nhận ra trong vài ngày qua rằng '? Setattr' nói rằng' x' phải là 'data.table' (nhờ bình luận về datatable-help). 'setattr' thực sự có ý định làm việc trên bất cứ thứ gì. Sẽ sửa chữa docu. Nó cũng trả về đầu vào của nó, vì vậy bạn có thể kết hợp '[i, j, by]' sau đó nếu cần (nói nếu bạn bọc nó thành một bí danh: 'setDT (DF) [i, j, by]'). –

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