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


121

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成功せず。何か提案があれば教えてください。


2
私はRがどのように使用されているのかを知っているとは思いませんが、反復ごとにインデックスを更新するために必要となる追加のコード行を無視したかったので、データフレームのサイズを簡単に事前に割り当てることはできません。最終的にいくつの行が必要になるかわかりません。上記は再現可能なことを意図したおもちゃの例にすぎないことを覚えておいてください。いずれにせよ、あなたの提案に感謝します!
Gyan Veda

回答:


115

更新

あなたが何をしようとしているのかわからないので、もう1つの提案を共有します。各列に必要なタイプのベクトルを事前に割り当て、それらのベクトルに値を挿入し、最後にを作成します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

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

コードには他に1つの問題があることに注意してください。

  • stringsAsFactors文字を因子に変換しないようにする場合に使用する必要があります。使用する:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)

6
ありがとう!これで問題は解決しました。なぜこれが「本当に良い考えではない」のですか?そして、どのようにしてforループでxとyが混合されますか?
Gyan Veda

5
@ user2932774、Rでこの方法でオブジェクトを拡張することは信じられないほど非効率的です。改善(ただし、必ずしも最良の方法であるとは限りません)は、予期するdata.frame最終的なサイズのを事前に割り当て、[抽出/置換で値を追加することです。
A5C1D2H2I1M1N2O1R2T1 2013

1
ありがとう、アナンダ。私は通常、事前割り当てを行いますが、これは本当に良い考えではないことに同意しません。状況によります。私の場合、私は小さなデータを扱っており、別の方法ではコーディングに時間がかかります。さらに、これは数値インデックスを更新して、すべての反復で事前に割り当てられたデータフレームの適切な部分を満たすために必要なコードと比較して、より洗練されたコードです。好奇心旺盛ですが、あなたの意見では、このタスクを達成するための「最善の方法」は何ですか?事前割り当てが最善だと思っていたでしょう。
Gyan Veda

2
@ user2932774、かっこいいです。私もあなたの視点に感謝します-大きなデータセットを扱うことはほとんどありません。とはいえ、関数や何かを書く場合、通常は可能な限り速度を上げるためにコードを微調整するために少し余分な労力を費やします。かなり大きな速度の違いの例については、私の更新を参照してください。
A5C1D2H2I1M1N2O1R2T1 2013

1
おっと、それは大きな違いです!そのシミュレーションを実行し、マイクロベンチマークパッケージについて教えてくれてありがとう。その余分な努力をするのはいいことだと私は間違いなくあなたに同意します。私の特定のケースでは、私は二度と実行する必要がないかもしれないいくつかのコードに素早く簡単なものが欲しかったと思います。:)
Gyan Veda

34

提案された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


ありがとう!私はアナンダの提案に同意しませんが。文字を因子のレベルに変換するかどうかは、出力で何をしたいかによって異なります。あなたが提案するソリューションでは、stringsAsFactorsをFALSEに設定する必要があると思いますが。
Gyan Veda

シミュレーションをありがとう。事前割り当ては処理速度の点で最良であることを理解していますが、このコーディングを決定する際に考慮した唯一の要素ではありません。
Gyan Veda

1
f1では、数値ベクトルxに文字列を割り当てることで混乱しました。正しい行は次のとおりです。df <- rbind(df, data.frame(x = i, y = toString(i)))
エルダーAgalarov

14

data.frameのサイズが事前にわからない場合を考えてみましょう。数行または数百万になることもあります。動的に成長する何らかのコンテナが必要です。SOでの私の経験と関連するすべての回答を考慮して、4つの異なるソリューションが用意されています。

  1. rbindlist data.frameに

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

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

  4. data.frameカスタム環境(参照セマンティクスを持つ)を拡張して使用し、data.frameを格納して、戻り時にコピーされないようにするの独自の機能。

以下は、追加された行の数が少ない場合と多数の場合のすべてのメソッドのテストです。各メソッドには3つの関数が関連付けられています。

  • create(first_element)これは、プットインで適切なバッキングオブジェクトを返します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 +必要に応じて手動でテーブルを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.の回答をコピーして貼り付けたものです。

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)

n = 10行を追加するタイミング

n = 100行のタイミング n = 1000行のタイミング

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ベースのソリューションのように見えますが、大きなデータである程度の速度は回復しますが、data.table +手動での指数関数的な増加にはほど遠いものです。違いはほぼ2桁です!

概要

かなり少ない行数(n <= 100)を追加することがわかっている場合は、先に進んで最も簡単な解決策を使用します。ブラケット表記を使用して行をdata.frameに割り当てるだけで、data.frameが事前入力されていません。

それ以外の場合はすべてdata.table::set、data.tableを指数関数的に使用して拡張します(たとえば、私のコードを使用します)。


2
SQLiteが遅い理由は、各INSERT INTOでREINDEXを実行する必要があるためです。これはO(n)で、nは行数です。これは、一度に1行をSQL​​データベースに挿入するとO(n ^ 2)になることを意味します。data.frame全体を一度に挿入すると、SQLiteは非常に高速になる可能性がありますが、1行ずつ成長させるのは最適ではありません。
ジュリアンザッカー2017年

5

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

を使用する必要はありませんadd_row。次に例を示します map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) })
user3808394

@ user3808394ありがとう、それは興味深い代替手段です!誰かが最初からデータフレームを作成したい場合は、あなたの方が短いので、より良い解決策です。あなたがすでにデータフレームを持っている場合、私の解決策はもちろんより優れています。
アジャイルBean

すでにデータフレームがある場合はbind_rows(df, map_dfr(1:1e5, function(x) { tibble(x = x, y = toString(x)) }))、を使用する代わりに行いますadd_row
user3808394

2

1から5までの番号を持つベクトル 'point'を取りましょう

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. 添え字、その後に値が追加されます。

シンプル... !! 何かお詫び申し上げます...!


1

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

    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

0

私の解決策は元の回答とほとんど同じですが、うまくいきませんでした。

だから、私は列に名前を付けました、そしてそれは機能します:

painel <- rbind(painel, data.frame("col1" = xtweets$created_at,
                                   "col2" = xtweets$text))
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.