web-dev-qa-db-ja.com

Rデータフレームに行を追加する方法

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

私はまた、関数cappend、およびmergeを成功せずに試みました。何か提案がありましたら教えてください。

103
Gyan Veda

提案した3つの解決策をベンチマークしましょう。

# 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と思われる。

29
Julián Urbano

あなたが単にdata.frameのサイズを前もって知らないと仮定します。数行、あるいは数百万になることもあります。動的に成長するある種のコンテナーが必要です。 SOでの私の経験とそれに関連するすべての回答を考慮して、4つの異なる解決策があります。

  1. rbindlistからdata.frameへ

  2. data.tableの高速set操作を使用し、必要に応じて手動でテーブルを2倍にします。

  3. RSQLiteを使用して、メモリに保持されているテーブルに追加します。

  4. data.frame data.frameを格納するためにカスタム環境(参照セマンティクスを持つ)を拡張して使用することができるので、戻り時にコピーされることはありません。

これは、少数の追加行と多数の追加行の両方に対するすべてのメソッドのテストです。各メソッドには3つの機能が関連付けられています。

  • first_elementを入れて適切なバッキングオブジェクトを返すcreate(first_element)

  • テーブルの最後にelementを追加するappend(object, element)objectで表される)。

  • access(object)は、挿入されたすべての要素とともにdata.frameを取得します。

data.frameへのrbindlist

それはとても簡単で簡単なことです。

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 +手動でテーブルを2倍にします。

テーブルの実際の長さを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. answer のコピー&ペーストを似たようなスレッドにまとめたものです。

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

テストスイート:

便宜上、間接呼び出しでそれらすべてをカバーするために1つのテスト関数を使用します。 (私がチェックした:関数を直接呼び出す代わりに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)

Timings for adding n=10 rows

Timings for n=100 rowsTimings for n=1000 rows

1E5行の場合(測定はIntel(R)Core(TM)i7-4710HQ CPU @ 2.50 GHz):

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ベースの解決法のように見えますが、大きなデータではある程度のスピードを取り戻していますが、data.table +手動の指数関数的成長にはほど遠いものです。違いはほぼ2桁です。

概要

かなり少ない数の行(n <= 100)を追加することがわかっている場合は、最も簡単な解決方法を使用してください。 data.frameは事前入力されていません。

それ以外の場合はdata.table::setを使用してdata.tableを指数関数的に大きくします(例:私のコードを使用)。

12
Adam Ryczkowski

1から5までの数を持つベクトル '点'を取りましょう。

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

もし私たちがベクトルの中のどこかに数6を追加したいのなら、下記のコマンドが便利になるかも

i)ベクトル

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

ii)テーブルの

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

コマンドappendは3つの引数を取ります。

  1. 変更するベクトル/列.
  2. 変更されたベクトルに含まれる値.
  3. 下付き文字。その後に値が追加されます。

簡単です…!いずれかの場合に謝罪...!

2

Purrr、tidyr、dplyrで更新

この質問はすでに(6年)前の日付であるため、答えには新しいパッケージのtidyrとpurrrに関する解決策がありません。それで、これらのパッケージを使って作業している人々のために、私は前の答えに解決策を加えたいと思います。

Purrrとtidyrの最大の利点は、読みやすさの向上です。 purrrはlapplyをより柔軟なmap()ファミリーに置き換えます。tidyrは非常に直感的なメソッドadd_rowを提供します - ちょうどそれが言うことをします:)

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

この解決策は読むのが短く直感的で、比較的速いです:

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

それはほぼ線形的にスケーリングするので、1e5行の場合、パフォーマンスは次のようになります。

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 

これにより、@ Adam Ryczkowskiによるベンチマークでdata.tableの次に2番目にランク付けされます(プラセボを無視する場合)。

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
2
Agile Bean

より一般的な解決策は次のようになります。

    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
1
Pisca46