type 如何將行附加到R數據框




r table用法 (4)

我瀏覽過StackOverflow,但找不到針對我的問題的解決方案,它涉及將行附加到R數據框。

我正在初始化一個空的2列數據框,如下所示。

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

然後,我的目標是迭代值列表,並在每次迭代中將值附加到列表的末尾。 我從下面的代碼開始。

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

我也嘗試了函數cappendmerge但沒有成功。 如果您有任何建議,請告訴我。


更通用的解決方案可能是以下內容。

    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
}

extendDf()函數用n行擴展數據幀。

舉個例子:

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

更新

不知道你在做什麼,我會再分享一個建議:為每一列預先分配所需類型的向量,向這些向量中插入值,最後創建data.frame

繼續使用Julian的f3 (預分配的data.frame )作為迄今為止最快的選項,定義如下:

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

這裡有一個類似的方法,但是其中data.frame被創建為最後一步。

# 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)
}

來自“microbenchmark”軟件包的microbenchmark將為我們提供比system.time更全面的洞察力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() (下面的方法)效率非常低,因為它調用data.frame並且由於這種方式的增長對像在R中通常較慢, f3()由於預分配而大大改進,但data.frame結構本身可能成為這裡瓶頸的一部分。 f4()試圖繞過這個瓶頸而不會損害你想要採取的方法。

原始答案

這真的不是一個好主意,但如果你想這樣做,我想你可以嘗試:

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

請注意,在您的代碼中,還有一個問題:

  • 如果您希望字符不能轉換為因素,則應該使用stringsAsFactors 。 使用: df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)

讓我們對提出的三種解決方案進行基準測試

# 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

最好的解決方案是預先分配空間(如R中所預期的那樣)。 次最好的解決方案是使用list ,而最糟糕的解決方案(至少基於這些時序結果)似乎是rbind


假設你事先根本不知道data.frame的大小。 它可以是幾行,或幾百萬。 你需要有一些容器,它可以動態增長。 考慮到我的經驗和所有相關的答案,所以我有四種不同的解決方案:

  1. rbindlist到data.frame

  2. 使用data.table的快速set操作,並在需要時手動將表加倍。

  3. 使用RSQLite並追加到內存中保存的表中。

  4. data.frame自己能夠增長和使用自定義環境(具有引用語義)來存儲data.frame,因此不會在返回時被複製。

以下是對大量附加行的所有方法的測試。 每種方法都有3個相關的功能:

  • create(first_element)返回create(first_element)的適當的後台對象。

  • append(object, element)element附加到表格的末尾(用object表示)。

  • access(object)獲取所有插入元素的data.frame

rbindlist到data.frame

這很簡單直接:

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 +在需要時手動將表加倍。

我將在rowcount屬性中存儲表的真實長度。

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應該針對快速記錄插入進行優化,所以我最初對RSQLite解決方案寄予厚望

這基本上是複制和粘貼Karsten W.在類似的線程上回答 。

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.frame自己的行追加+自定義環境。

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

測試套件:

為了方便起見,我將使用一個測試函數以間接調用來覆蓋它們。 (我檢查:使用do.call而不是直接調用函數不會使代碼運行時間更長)。

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

讓我們看看n = 10插入的性能。

我還添加了一個“安慰劑”功能(後綴為0 ),它們不會執行任何操作 - 只是為了測量測試設置的開銷。

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

對於1E5行(在Intel(R)Core(TM)i7-4710HQ CPU @ 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

它看起來像基於SQLite的sulution,儘管在大數據上恢復了一些速度,卻遠不及data.table +手動指數級增長。 差異幾乎是兩個數量級!

概要

如果您知道您將追加相當少量的行(n <= 100),請繼續並使用最簡單的解決方案:使用括號表示將行分配給data.frame,並忽略data.frame沒有預先填充。

對於其他的東西,使用data.table::set並以指數形式增長data.table(例如,使用我的代碼)。





rows