Cách nối các hàng vào khung dữ liệu R


121

Tôi đã xem xét StackOverflow, nhưng tôi không thể tìm thấy giải pháp cụ thể cho vấn đề của mình, liên quan đến việc nối các hàng vào khung dữ liệu R.

Tôi đang khởi tạo một khung dữ liệu 2 cột trống, như sau.

df = data.frame(x = numeric(), y = character())

Sau đó, mục tiêu của tôi là lặp qua một danh sách các giá trị và trong mỗi lần lặp, thêm một giá trị vào cuối danh sách. Tôi bắt đầu với đoạn mã sau.

for (i in 1:10) {
    df$x = rbind(df$x, i)
    df$y = rbind(df$y, toString(i))
}

Tôi cũng đã cố gắng định chức năng c, appendmergekhông thành công. Xin vui lòng cho tôi biết nếu bạn có bất cứ đề nghị.


2
Tôi không cho là biết R được sử dụng như thế nào, nhưng tôi muốn bỏ qua dòng mã bổ sung cần thiết để cập nhật các chỉ số trên mỗi lần lặp và tôi không thể dễ dàng phân bổ trước kích thước của khung dữ liệu vì tôi không không biết cuối cùng nó sẽ mất bao nhiêu hàng. Hãy nhớ rằng phần trên chỉ là một ví dụ về đồ chơi có thể tái tạo được. Dù bằng cách nào, cảm ơn vì đề xuất của bạn!
Gyan Veda

Câu trả lời:


115

Cập nhật

Không biết bạn đang cố gắng làm gì, tôi sẽ chia sẻ thêm một gợi ý: Định vị trước các vectơ thuộc loại bạn muốn cho mỗi cột, chèn giá trị vào các vectơ đó và sau đó, ở cuối, hãy tạo data.frame .

Tiếp tục với Julian's f3(đã phân bổ trước data.frame) là lựa chọn nhanh nhất cho đến nay, được định nghĩa là:

# pre-allocate space
f3 <- function(n){
  df <- data.frame(x = numeric(n), y = character(n), stringsAsFactors = FALSE)
  for(i in 1:n){
    df$x[i] <- i
    df$y[i] <- toString(i)
  }
  df
}

Đây là một cách tiếp cận tương tự, nhưng cách tiếp cận data.frameđược tạo ở bước cuối cùng.

# Use preallocated vectors
f4 <- function(n) {
  x <- numeric(n)
  y <- character(n)
  for (i in 1:n) {
    x[i] <- i
    y[i] <- i
  }
  data.frame(x, y, stringsAsFactors=FALSE)
}

microbenchmarktừ gói "microbenchmark" sẽ cung cấp cho chúng tôi thông tin chi tiết toàn diện hơn system.time:

library(microbenchmark)
microbenchmark(f1(1000), f3(1000), f4(1000), times = 5)
# Unit: milliseconds
#      expr         min          lq      median         uq         max neval
#  f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176     5
#  f3(1000)  149.417636  150.529011  150.827393  151.02230  160.637845     5
#  f4(1000)    7.872647    7.892395    7.901151    7.95077    8.049581     5

f1()(cách tiếp cận bên dưới) cực kỳ kém hiệu quả vì tần suất nó gọi data.framevà bởi vì việc phát triển các đối tượng theo cách đó nói chung là chậm trong R. đã f3()được cải thiện nhiều do việc phân bổ trước, nhưng data.framebản thân cấu trúc có thể là một phần của nút thắt cổ chai ở đây. f4()cố gắng vượt qua nút thắt cổ chai đó mà không ảnh hưởng đến cách tiếp cận bạn muốn thực hiện.


Câu trả lời ban đầu

Đây thực sự không phải là một ý tưởng hay, nhưng nếu bạn muốn làm theo cách này, tôi đoán bạn có thể thử:

for (i in 1:10) {
  df <- rbind(df, data.frame(x = i, y = toString(i)))
}

Lưu ý rằng trong mã của bạn, có một vấn đề khác:

  • Bạn nên sử dụng stringsAsFactorsnếu bạn muốn các ký tự không bị chuyển đổi thành thừa số. Sử dụng:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)

6
Cảm ơn! Điều đó giải quyết vấn đề của tôi. Tại sao điều này "thực sự không phải là một ý kiến ​​hay"? Và x và y được trộn theo cách nào trong vòng lặp for?
Gyan Veda

5
@ user2932774, Việc phát triển một đối tượng theo cách này theo cách này là vô cùng kém hiệu quả trong R. Một cải tiến (nhưng vẫn không nhất thiết là cách tốt nhất) sẽ là phân bổ trước data.framekích thước tối ưu mà bạn mong đợi và thêm các giá trị vào bằng cách [trích xuất / thay thế.
A5C1D2H2I1M1N2O1R2T1

1
Cảm ơn, Ananda. Tôi thường đi với phân bổ trước, nhưng tôi không đồng ý rằng đây thực sự không phải là một ý kiến ​​hay. Nó phụ thuộc vào tình hình. Trong trường hợp của tôi, tôi đang xử lý dữ liệu nhỏ và giải pháp thay thế sẽ tốn nhiều thời gian hơn để viết mã. Thêm vào đó, đây là mã thanh lịch hơn so với mã cần thiết để cập nhật các chỉ số số để lấp đầy các phần thích hợp của khung dữ liệu được phân bổ trước trên mỗi lần lặp. Chỉ tò mò, "cách tốt nhất" để hoàn thành nhiệm vụ này theo ý kiến ​​của bạn là gì? Tôi đã nghĩ rằng phân bổ trước sẽ là tốt nhất.
Gyan Veda

2
@ user2932774, thật tuyệt. Tôi cũng đánh giá cao quan điểm của bạn - tôi cũng không bao giờ thực sự làm việc với các bộ dữ liệu lớn. Điều đó nói rằng, nếu tôi chuẩn bị viết một hàm hoặc một cái gì đó, tôi thường sẽ dành thêm một chút nỗ lực để cố gắng chỉnh sửa mã để có được tốc độ tốt hơn bất cứ khi nào có thể. Xem bản cập nhật của tôi để biết ví dụ về sự khác biệt tốc độ khá lớn.
A5C1D2H2I1M1N2O1R2T1

1
Chà, đó là một sự khác biệt rất lớn! Cảm ơn bạn đã chạy mô phỏng đó và dạy tôi về gói microbenchmark. Tôi chắc chắn đồng ý với bạn rằng thật tuyệt khi nỗ lực thêm. Trong trường hợp cụ thể của tôi, tôi đoán tôi chỉ muốn một cái gì đó nhanh và bẩn trên một số mã mà tôi có thể không bao giờ phải chạy lại. :)
Gyan Veda

34

Hãy chuẩn ba giải pháp được đề xuất:

# use rbind
f1 <- function(n){
  df <- data.frame(x = numeric(), y = character())
  for(i in 1:n){
    df <- rbind(df, data.frame(x = i, y = toString(i)))
  }
  df
}
# use list
f2 <- function(n){
  df <- data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
  for(i in 1:n){
    df[i,] <- list(i, toString(i))
  }
  df
}
# pre-allocate space
f3 <- function(n){
  df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE)
  for(i in 1:n){
    df$x[i] <- i
    df$y[i] <- toString(i)
  }
  df
}
system.time(f1(1000))
#   user  system elapsed 
#   1.33    0.00    1.32 
system.time(f2(1000))
#   user  system elapsed 
#   0.19    0.00    0.19 
system.time(f3(1000))
#   user  system elapsed 
#   0.14    0.00    0.14

Giải pháp tốt nhất là phân bổ trước không gian (như dự định trong R). Giải pháp tốt nhất tiếp theo là sử dụng list, và giải pháp tồi tệ nhất (ít nhất là dựa trên các kết quả thời gian này) dường như là rbind.


Cảm ơn! Mặc dù tôi không đồng ý với đề nghị của Ananda. Việc tôi muốn các ký tự được chuyển đổi thành các mức của một hệ số hay không sẽ phụ thuộc vào những gì tôi muốn làm với đầu ra. Mặc dù tôi đoán rằng với giải pháp bạn đề xuất, cần phải đặt stringAsFactors thành FALSE.
Gyan Veda

Cảm ơn vì mô phỏng. Tôi nhận ra rằng phân bổ trước là tốt nhất về tốc độ xử lý, nhưng đó không phải là yếu tố duy nhất mà tôi xem xét khi đưa ra quyết định mã hóa này.
Gyan Veda

1
Trong f1, bạn bối rối khi gán chuỗi cho vector số x. Dòng chính xác là:df <- rbind(df, data.frame(x = i, y = toString(i)))
Eldar Agalarov

14

Giả sử bạn chỉ đơn giản là không biết trước kích thước của data.frame. Nó cũng có thể là một vài hàng, hoặc một vài triệu. Bạn cần phải có một số loại thùng chứa, phát triển linh hoạt. Xem xét kinh nghiệm của tôi và tất cả các câu trả lời liên quan trong SO, tôi đưa ra 4 giải pháp riêng biệt:

  1. rbindlist vào data.frame

  2. Sử dụng thao tác data.tablenhanh setvà ghép nối nó bằng cách nhân đôi bảng theo cách thủ công khi cần thiết.

  3. Sử dụng RSQLitevà thêm vào bảng được lưu trong bộ nhớ.

  4. data.framekhả năng của riêng mình để phát triển và sử dụng môi trường tùy chỉnh (có ngữ nghĩa tham chiếu) để lưu trữ data.frame, do đó nó sẽ không bị sao chép ngược lại.

Đây là một thử nghiệm của tất cả các phương pháp cho cả số lượng nhỏ và lớn các hàng được nối thêm. Mỗi phương thức có 3 chức năng được liên kết với nó:

  • create(first_element) trả về đối tượng sao lưu thích hợp với first_element đưa vào.

  • append(object, element)gắn elementvào cuối bảng (được đại diện bởiobject ).

  • access(object)nhận được data.framevới tất cả các phần tử được chèn.

rbindlist vào data.frame

Điều đó khá dễ dàng và dễ hiểu:

create.1<-function(elems)
{
  return(as.data.table(elems))
}

append.1<-function(dt, elems)
{ 
  return(rbindlist(list(dt,  elems),use.names = TRUE))
}

access.1<-function(dt)
{
  return(dt)
}

data.table::set + nhân đôi bàn thủ công khi cần thiết.

Tôi sẽ lưu trữ độ dài thực của bảng trong một rowcountthuộc tính.

create.2<-function(elems)
{
  return(as.data.table(elems))
}

append.2<-function(dt, elems)
{
  n<-attr(dt, 'rowcount')
  if (is.null(n))
    n<-nrow(dt)
  if (n==nrow(dt))
  {
    tmp<-elems[1]
    tmp[[1]]<-rep(NA,n)
    dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
    setattr(dt,'rowcount', n)
  }
  pos<-as.integer(match(names(elems), colnames(dt)))
  for (j in seq_along(pos))
  {
    set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
  }
  setattr(dt,'rowcount',n+1)
  return(dt)
}

access.2<-function(elems)
{
  n<-attr(elems, 'rowcount')
  return(as.data.table(elems[1:n,]))
}

SQL nên được tối ưu hóa để chèn bản ghi nhanh, vì vậy ban đầu tôi rất hy vọng RSQLite giải pháp

Về cơ bản, đây là bản sao và dán câu trả lời của Karsten W. trên chủ đề tương tự.

create.3<-function(elems)
{
  con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
  RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
  return(con)
}

append.3<-function(con, elems)
{ 
  RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
  return(con)
}

access.3<-function(con)
{
  return(RSQLite::dbReadTable(con, "t", row.names=NULL))
}

data.framemôi trường tùy chỉnh + bổ sung hàng của riêng.

create.4<-function(elems)
{
  env<-new.env()
  env$dt<-as.data.frame(elems)
  return(env)
}

append.4<-function(env, elems)
{ 
  env$dt[nrow(env$dt)+1,]<-elems
  return(env)
}

access.4<-function(env)
{
  return(env$dt)
}

Bộ thử nghiệm:

Để thuận tiện, tôi sẽ sử dụng một chức năng kiểm tra để bao gồm tất cả chúng bằng cách gọi gián tiếp. (Tôi đã kiểm tra: sử dụng do.callthay vì gọi trực tiếp các hàm không làm cho mã chạy lâu hơn có thể đo được).

test<-function(id, n=1000)
{
  n<-n-1
  el<-list(a=1,b=2,c=3,d=4)
  o<-do.call(paste0('create.',id),list(el))
  s<-paste0('append.',id)
  for (i in 1:n)
  {
    o<-do.call(s,list(o,el))
  }
  return(do.call(paste0('access.', id), list(o)))
}

Hãy xem hiệu suất của n = 10 lần chèn.

Tôi cũng đã thêm các chức năng 'giả dược' (có hậu tố 0) không thực hiện bất kỳ điều gì - chỉ để đo lường chi phí của thiết lập thử nghiệm.

r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)

Thời gian để thêm n = 10 hàng

Thời gian cho n = 100 hàng Thời gian cho n = 1000 hàng

Đối với hàng 1E5 (các phép đo được thực hiện trên CPU Intel (R) Core (TM) i7-4710HQ @ 2,50GHz):

nr  function      time
4   data.frame    228.251 
3   sqlite        133.716
2   data.table      3.059
1   rbindlist     169.998 
0   placebo         0.202

Có vẻ như sulution dựa trên SQLite, mặc dù lấy lại một số tốc độ trên dữ liệu lớn, nhưng không bằng data.table + tăng trưởng theo cấp số nhân thủ công. Sự khác biệt gần như là hai bậc của độ lớn!

Tóm lược

Nếu bạn biết rằng bạn sẽ thêm một số lượng hàng khá nhỏ (n <= 100), hãy tiếp tục và sử dụng giải pháp đơn giản nhất có thể: chỉ cần gán các hàng cho data.frame bằng cách sử dụng ký hiệu ngoặc và bỏ qua thực tế rằng data.frame là không được điền trước.

Đối với mọi thứ khác, hãy sử dụng data.table::setvà phát triển data.table theo cấp số nhân (ví dụ: sử dụng mã của tôi).


2
Lý do SQLite chậm là trên mỗi INSERT INTO, nó phải REINDEX, là O (n), trong đó n là số hàng. Điều này có nghĩa là chèn vào cơ sở dữ liệu SQL một hàng tại một thời điểm là O (n ^ 2). SQLite có thể rất nhanh, nếu bạn chèn toàn bộ data.frame cùng một lúc, nhưng nó không phải là cách tốt nhất để phát triển từng dòng.
Julian Zucker

5

Cập nhật với purrr, slimr & dplyr

Vì câu hỏi đã có niên đại (6 năm), câu trả lời đang thiếu một giải pháp với các gói mới hơn, gọn gàng hơn. Vì vậy, đối với những người làm việc với các gói này, tôi muốn thêm một giải pháp cho các câu trả lời trước đó - tất cả đều khá thú vị, đặc biệt.

Ưu điểm lớn nhất của purrr và slimr là IMHO dễ đọc hơn. purrr thay thế lapply bằng họ map () linh hoạt hơn, slimr cung cấp phương thức add_row siêu trực quan - chỉ thực hiện những gì nó nói :)

map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })

Giải pháp này ngắn gọn và trực quan để đọc và tương đối nhanh:

system.time(
   map_df(1:1000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
   0.756   0.006   0.766

Nó chia tỷ lệ gần như tuyến tính, vì vậy đối với hàng 1e5, hiệu suất là:

system.time(
  map_df(1:100000, function(x) { df %>% add_row(x = x, y = toString(x)) })
)
   user  system elapsed 
 76.035   0.259  76.489 

điều này sẽ làm cho nó xếp thứ hai ngay sau data.table (nếu bạn bỏ qua giả dược) trong điểm chuẩn của @Adam Ryczkowski:

nr  function      time
4   data.frame    228.251 
3   sqlite        133.716
2   data.table      3.059
1   rbindlist     169.998 
0   placebo         0.202

Bạn không cần phải sử dụng add_row. Ví dụ: map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }).
user3808394

@ user3808394 cảm ơn, đó là một sự thay thế thú vị! nếu ai đó muốn tạo khung dữ liệu từ đầu, thì khung dữ liệu của bạn ngắn hơn, do đó giải pháp tốt hơn. trong trường hợp bạn đã có dataframe, giải pháp của tôi tất nhiên là tốt hơn.
Agile Bean

Nếu bạn đã có khung dữ liệu, bạn sẽ làm bind_rows(df, map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }))thay vì sử dụng add_row.
user3808394

2

Cho phép lấy một 'điểm' vectơ có các số từ 1 đến 5

point = c(1,2,3,4,5)

nếu chúng ta muốn thêm số 6 vào bất kỳ đâu bên trong vectơ thì lệnh dưới đây có thể hữu ích

i) Vectơ

new_var = append(point, 6 ,after = length(point))

ii) các cột của bảng

new_var = append(point, 6 ,after = length(mtcars$mpg))

Lệnh appendcó ba đối số:

  1. vectơ / cột được sửa đổi.
  2. giá trị được bao gồm trong vectơ đã sửa đổi.
  3. một chỉ số con, sau đó các giá trị sẽ được thêm vào.

đơn giản...!! Xin lỗi nếu có ...!


1

Một giải pháp chung chung hơn có thể là như sau.

    extendDf <- function (df, n) {
    withFactors <- sum(sapply (df, function(X) (is.factor(X)) )) > 0
    nr          <- nrow (df)
    colNames    <- names(df)
    for (c in 1:length(colNames)) {
        if (is.factor(df[,c])) {
            col         <- vector (mode='character', length = nr+n) 
            col[1:nr]   <- as.character(df[,c])
            col[(nr+1):(n+nr)]<- rep(col[1], n)  # to avoid extra levels
            col         <- as.factor(col)
        } else {
            col         <- vector (mode=mode(df[1,c]), length = nr+n)
            class(col)  <- class (df[1,c])
            col[1:nr]   <- df[,c] 
        }
        if (c==1) {
            newDf       <- data.frame (col ,stringsAsFactors=withFactors)
        } else {
            newDf[,c]   <- col 
        }
    }
    names(newDf) <- colNames
    newDf
}

Hàm expandDf () mở rộng một khung dữ liệu với n hàng.

Ví dụ:

aDf <- data.frame (l=TRUE, i=1L, n=1, c='a', t=Sys.time(), stringsAsFactors = TRUE)
extendDf (aDf, 2)
#      l i n c                   t
# 1  TRUE 1 1 a 2016-07-06 17:12:30
# 2 FALSE 0 0 a 1970-01-01 01:00:00
# 3 FALSE 0 0 a 1970-01-01 01:00:00

system.time (eDf <- extendDf (aDf, 100000))
#    user  system elapsed 
#   0.009   0.002   0.010
system.time (eDf <- extendDf (eDf, 100000))
#    user  system elapsed 
#   0.068   0.002   0.070

0

Giải pháp của tôi gần giống với câu trả lời ban đầu nhưng nó không hiệu quả với tôi.

Vì vậy, tôi đã đặt tên cho các cột và nó hoạt động:

painel <- rbind(painel, data.frame("col1" = xtweets$created_at,
                                   "col2" = xtweets$text))
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.