私は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))
}
私はまた、関数c
、append
、およびmerge
を成功せずに試みました。何か提案がありましたら教えてください。
提案した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
と思われる。
あなたが単にdata.frameのサイズを前もって知らないと仮定します。数行、あるいは数百万になることもあります。動的に成長するある種のコンテナーが必要です。 SOでの私の経験とそれに関連するすべての回答を考慮して、4つの異なる解決策があります。
rbindlist
からdata.frameへ
data.table
の高速set
操作を使用し、必要に応じて手動でテーブルを2倍にします。
RSQLite
を使用して、メモリに保持されているテーブルに追加します。
data.frame
data.frameを格納するためにカスタム環境(参照セマンティクスを持つ)を拡張して使用することができるので、戻り時にコピーされることはありません。
これは、少数の追加行と多数の追加行の両方に対するすべてのメソッドのテストです。各メソッドには3つの機能が関連付けられています。
first_element
を入れて適切なバッキングオブジェクトを返すcreate(first_element)
。
テーブルの最後にelement
を追加するappend(object, element)
(object
で表される)。
access(object)
は、挿入されたすべての要素とともに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,]))
}
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)
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を指数関数的に大きくします(例:私のコードを使用)。
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つの引数を取ります。
簡単です…!いずれかの場合に謝罪...!
この質問はすでに(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
より一般的な解決策は次のようになります。
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