data.tableの参照によって行を削除する方法は?


150

私の質問は、参照による割り当てとでのコピーに関連していdata.tableます。同様に、参照によって行を削除できるかどうかを知りたい

DT[ , someCol := NULL]

知りたい

DT[someRow := NULL, ]

この関数が存在しない理由は十分にあると思います。そのため、以下のように、通常のコピーアプローチに代わる優れた方法を指摘できます。特に、example(data.table)の私のお気に入りを使用すると、

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
#      x y v
# [1,] a 1 1
# [2,] a 3 2
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

このdata.tableから最初の行を削除したいとします。私はこれができることを知っています:

DT <- DT[-1, ]

我々は、オブジェクトのコピー(そのN場合、約3 * Nのメモリを必要としているので、しかし、多くの場合、我々は、それを回避したいことがありobject.size(DT)ここで指摘したように。今、私が見つかりました。set(DT, i, j, value)私はここのように設定し、特定の値(に方法を知っている:すべてのセット行1と2、列2と3の値は0)

set(DT, 1:2, 2:3, 0) 
DT
#      x y v
# [1,] a 0 0
# [2,] a 0 0
# [3,] a 6 3
# [4,] b 1 4
# [5,] b 3 5
# [6,] b 6 6
# [7,] c 1 7
# [8,] c 3 8
# [9,] c 6 9

しかし、どうすれば最初の2行を消去できますか?している

set(DT, 1:2, 1:3, NULL)

DT全体をNULLに設定します。

私のSQLの知識は非常に限られているので、皆さんは私に言っています:data.tableがSQLテクノロジーを使用している場合、SQLコマンドに相当するものはありますか

DELETE FROM table_name
WHERE some_column=some_value

data.table?


17
data.table()SQLのさまざまな操作とへのさまざまな引数の間に類似点を描くことができるほどSQLテクノロジを使用しているとは思いませんdata.table。私にとって、「テクノロジー」への言及data.tableは、それがSQLデータベースのどこかにあることを幾分示唆していますが、AFAIKはそうではありません。
追跡

1
チェイスありがとう。ええ、私はsqlの類推はワイルドな推測だったと思います。
Florian Oswald、

1
多くの場合、行を保持するためのフラグを定義してDT[ , keep := .I > 1]、その後の操作のためにサブセットを定義するだけで十分です。DT[(keep), ...]おそらくsetindex(DT, keep)、このサブセットの速度ですらあります。万能薬ではありませんが、ワークフローの設計上の選択として検討する価値があります。これらの行をすべてメモリから削除しますか、それとも除外しますか?答えはユースケースによって異なります。
MichaelChirico

回答:


125

良い質問。data.table参照による行はまだ削除できません。

data.tableご存知のように、列ポインタのベクトルをオーバーアロケーションするため、参照によってを追加および削除できます。計画は、行に対して同様のことを行い、高速insertおよびを許可することですdelete。行の削除はmemmove、Cで使用され、削除された行の後に(すべての列の)アイテムを一杯にします。テーブルの中央にある行を削除することは、SQLなどの行ストアデータベースと比較すると、依然として非効率的です。SQLは、テーブル内の行がどこにあっても、行の高速な挿入と削除に適しています。しかし、それでも、行を削除せずに新しいラージオブジェクトをコピーするよりもはるかに高速です。

一方、列ベクトルが過剰に割り当てられるため、行が最後に瞬時に挿入(および削除)される可能性があります。たとえば、成長する時系列。


問題として提出されています:参照により行を削除します


1
@マシュー・ダウレこれに関するニュースはありますか?
statquant

15
@statquant 37のバグを修正し、fread最初に終了する必要があると思います。その後はかなり高いです。
Matt Dowle 2013

15
@MatthewDowleよろしくお願いします。
statquant

1
@rbatt正解。 DT[b<8 & a>3]新しいdata.tableを返します。とを追加delete(DT, b>=8 | a<=3)DT[b>=8 | a<=8, .ROW:=NULL]ます。後者の利点は、他の特徴と組み合わせることになる[]で行番号などiに参加し、iそしてroll恩恵を受ける[i,j,by]最適化。
Matt

2
@charliealpha更新なし。貢献を歓迎します。喜んでご案内いたします。Cのスキルが必要です-繰り返しになりますが、私は喜んでご案内します。
Matt Dowle 2017

29

メモリ使用をインプレース削除と同様にするために私が取ったアプローチは、一度に列をサブセット化して削除することです。適切なC memmoveソリューションほど高速ではありませんが、ここで気にするのはメモリの使用だけです。このようなもの:

DT = data.table(col1 = 1:1e6)
cols = paste0('col', 2:100)
for (col in cols){ DT[, (col) := 1:1e6] }
keep.idxs = sample(1e6, 9e5, FALSE) # keep 90% of entries
DT.subset = data.table(col1 = DT[['col1']][keep.idxs]) # this is the subsetted table
for (col in cols){
  DT.subset[, (col) := DT[[col]][keep.idxs]]
  DT[, (col) := NULL] #delete
}

5
+1優れたメモリ効率の良いアプローチ。したがって、理想的には、参照によって行のセットを削除する必要がありますが、実際にはそうは思いませんでした。memmoveギャップを埋めるために一連のs である必要がありますが、それで問題ありません。
Matt Dowle 2014年

これは関数として機能しますか、または関数とリターンでの使用はメモリのコピーを作成することを強制しますか?
russellpierce 2014

1
data.tablesは常に参照であるため、関数で機能します。
vc273 14

1
ありがとう、いいもの。少し高速化するために(特に多くの列で)変更DT[, col:= NULL, with = F]しますset(DT, NULL, col, NULL)
Michele

2
イディオムと警告の変更に照らして更新 "with = FALSEと:=は、2014年10月にリリースされたv1.9.4で廃止されました。:=のLHSを括弧で囲んでください。例:DT [、(myVar):= sum(b) 、by = a]を使用して、変数myVarに保持されている列名に割り当てます。他の例については、? ':='を参照してください。2014年に警告されたように、これは警告です。
フランク

6

@ vc273の回答と@Frankのフィードバックに基づく作業関数を次に示します。

delete <- function(DT, del.idxs) {           # pls note 'del.idxs' vs. 'keep.idxs'
  keep.idxs <- setdiff(DT[, .I], del.idxs);  # select row indexes to keep
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs]); # this is the subsetted table
  setnames(DT.subset, cols[1]);
  for (col in cols[2:length(cols)]) {
    DT.subset[, (col) := DT[[col]][keep.idxs]];
    DT[, (col) := NULL];  # delete
  }
   return(DT.subset);
}

そしてその使用例:

dat <- delete(dat,del.idxs)   ## Pls note 'del.idxs' instead of 'keep.idxs'

ここで、「dat」はdata.tableです。1.4M行から14,000行を削除すると、私のラップトップでは0.25秒かかります。

> dim(dat)
[1] 1419393      25
> system.time(dat <- delete(dat,del.idxs))
   user  system elapsed 
   0.23    0.02    0.25 
> dim(dat)
[1] 1404715      25
> 

PS。私はSOが初めてなので、@ vc273のスレッドにコメントを追加できませんでした:-(


(col):=の変更された構文を説明するvcの回答の下でコメントしました。"delete"という名前の関数があるのは奇妙なことですが、保持するものに関連する引数です。ところで、一般的に、自分のデータの薄暗さを示すよりも、再現可能な例を使用することをお勧めします。たとえば、質問からDTを再利用できます。
フランク

なぜ参照によってそれを行うのかわかりませんが、後で代入データを使用します<
skan

1
@skan、その割り当てでは、元のdata.tableをサブセット化することによって作成された変更済みのdata.tableを指すように「dat」を割り当てます。<-割り当てでは、戻りデータのコピーは行われず、新しい名前が割り当てられるだけです。 リンク
ヤルノP.17年

@フランク、私はあなたが指摘した奇妙さのために関数を更新しました。
ヤルノP.17年

わかりました。ここでは、再現可能な例の代わりにコンソール出力を表示することは推奨されないことに注意する価値があると思うので、コメントは残しておきます。また、単一のベンチマークはそれほど有益ではありません。サブセット化にかかる時間も測定した場合、それはより有益です(ほとんどの人は、かかる時間を直感的に知らないため、コンプにかかる時間ははるかに短いため)。とにかく、これが悪い答えであることを示唆するつもりはありません。私は賛成者の一人です。
フランク

4

代わりに、またはNULLに設定しようとして、NAに設定してみてください(最初の列のNAタイプに一致)

set(DT,1:2, 1:3 ,NA_character_)

3
ええ、それはうまくいくと思います。私の問題は、大量のデータがあり、それらの行を削除するためにDTをコピーする必要なしに、NAでそれらの行を正確に削除したいということです。とにかくあなたのコメントをありがとう!
Florian Oswald、

4

トピックはまだ多くの人々(私を含む)を面白くしています。

そのことについて何?と以前に説明したコードassignを置き換えるために使用しましたglovalenv。元の環境をキャプチャする方が適切ですが、少なくともglobalenvメモリ効率が高く、参照による変更のように機能します。

delete <- function(DT, del.idxs) 
{ 
  varname = deparse(substitute(DT))

  keep.idxs <- setdiff(DT[, .I], del.idxs)
  cols = names(DT);
  DT.subset <- data.table(DT[[1]][keep.idxs])
  setnames(DT.subset, cols[1])

  for (col in cols[2:length(cols)]) 
  {
    DT.subset[, (col) := DT[[col]][keep.idxs]]
    DT[, (col) := NULL];  # delete
  }

  assign(varname, DT.subset, envir = globalenv())
  return(invisible())
}

DT = data.table(x = rep(c("a", "b", "c"), each = 3), y = c(1, 3, 6), v = 1:9)
delete(DT, 3)

明確にするために、これは参照によって(に基づいてaddress(DT); delete(DT, 3); address(DT))削除されるわけではありませんが、ある意味で効率的かもしれません。
フランク

1
いいえ、違います。これは動作をエミュレートし、メモリ効率に優れています。だから私は言った:それはのように機能します。しかし、厳密に言うと、アドレスが変更されたということです。
JRR

3

ここに私が使用したいくつかの戦略があります。.ROW関数が来ると思います。以下のこれらのアプローチはどれも高速ではありません。これらは、サブセットまたはフィルタリングを少し超えたいくつかの戦略です。私はちょうどデータをクリーンアップしようとしているdbaのように考えてみました。上記のように、data.tableの行を選択または削除できます。

data(iris)
iris <- data.table(iris)

iris[3] # Select row three

iris[-3] # Remove row three

You can also use .SD to select or remove rows:

iris[,.SD[3]] # Select row three

iris[,.SD[3:6],by=,.(Species)] # Select row 3 - 6 for each Species

iris[,.SD[-3]] # Remove row three

iris[,.SD[-3:-6],by=,.(Species)] # Remove row 3 - 6 for each Species

注:.SDは、元のデータのサブセットを作成し、j以降のdata.tableでかなりの作業を実行できるようにします。https://stackoverflow.com/a/47406952/305675を参照してください。ここで、アイリスをSepal Lengthで注文し、指定されたSepal.Lengthを最小として、すべてのSpeciesの上位3つ(Sepal Lengthで)を選択し、すべての付随データを返します。

iris[order(-Sepal.Length)][Sepal.Length > 3,.SD[1:3],by=,.(Species)]

上記のアプローチはすべて、行を削除するときにdata.tableを順番に並べ替えます。data.tableを転置して、転置された列になっている古い行を削除または置き換えることができます。':= NULL'を使用して転置行を削除すると、後続の列名も削除されます。

m_iris <- data.table(t(iris))[,V3:=NULL] # V3 column removed

d_iris <- data.table(t(iris))[,V3:=V2] # V3 column replaced with V2

data.frameをdata.tableに転置する場合、元のdata.tableから名前を変更し、削除した場合はクラス属性を復元することができます。転置されたdata.tableに ":= NULL"を適用すると、すべての文字クラスが作成されます。

m_iris <- data.table(t(d_iris));
setnames(d_iris,names(iris))

d_iris <- data.table(t(m_iris));
setnames(m_iris,names(iris))

あなたはキーの有無にかかわらず行うことができる重複した行を削除したいかもしれません:

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]     

d_iris[!duplicated(Key),]

d_iris[!duplicated(paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)),]  

'.I'を使用して増分カウンターを追加することもできます。次に、重複するキーまたはフィールドを検索し、カウンターでレコードを削除することにより、それらを削除できます。これは計算コストがかかりますが、削除する行を印刷できるため、いくつかの利点があります。

d_iris[,I:=.I,] # add a counter field

d_iris[,Key:=paste0(Sepal.Length,Sepal.Width,Petal.Length,Petal.Width,Species)]

for(i in d_iris[duplicated(Key),I]) {print(i)} # See lines with duplicated Key or Field

for(i in d_iris[duplicated(Key),I]) {d_iris <- d_iris[!I == i,]} # Remove lines with duplicated Key or any particular field.

行に0またはNAを入力し、iクエリを使用してそれらを削除することもできます。

 X 
   x v foo
1: c 8   4
2: b 7   2

X[1] <- c(0)

X
   x v foo
1: 0 0   0
2: b 7   2

X[2] <- c(NA)
X
    x  v foo
1:  0  0   0
2: NA NA  NA

X <- X[x != 0,]
X <- X[!is.na(x),]

これは実際には(参照による削除について)の質問には答えません。またt、data.frameで使用することは通常、良い考えではありません。str(m_iris)すべてのデータが文字列/文字になっていることを確認してください。ところで、d_iris[duplicated(Key), which = TRUE]カウンター列を作らずに行番号を取得することもできます。
フランク

1
はい、そうです。質問には特に答えません。しかし、参照による行の削除にはまだ公式の機能やドキュメントがありません。多くの人々は、まさにそれを行うための一般的な機能を探してこの投稿にアクセスするでしょう。行を削除する方法に関する質問に答えるだけの投稿を作成できます。スタックオーバーフローは非常に便利で、質問に対する回答を正確に保つ必要性を私は本当に理解しています。時々、しかし、私はSOがこの点で少しファシストであるかもしれないと思います...しかし多分それには正当な理由があるかもしれません。
rferrisx

説明してくれてありがとう。今のところ、ここでの議論は、この場合混乱する人にとっては十分な道標だと思います。
フランク
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.