データフレームを結合(マージ)する方法(内側、外側、左、右)


1233

2つのデータフレームがあるとします。

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2, 4, 6), State = c(rep("Alabama", 2), rep("Ohio", 1)))

df1
#  CustomerId Product
#           1 Toaster
#           2 Toaster
#           3 Toaster
#           4   Radio
#           5   Radio
#           6   Radio

df2
#  CustomerId   State
#           2 Alabama
#           4 Alabama
#           6    Ohio

データベーススタイル、つまりSQLスタイルの結合はどのように実行できますか?つまり、どうやって取得するのですか?

  • インナーには参加df1df2
    左表が右の表にキーが一致しているに戻る行のみ。
  • 外部結合df1df2
    右の表にキーが一致している左からレコードを結合し、両方のテーブルからすべての行を返します。
  • 左外部結合(又は単に左ジョイン)df1df2
    左テーブルからすべての行、右テーブルからの一致するキーを有する任意の行を返します。
  • 右の外部結合df1df2
    左テーブルからキーが一致する右側のテーブルのすべての行、および任意の行を返します。

追加クレジット:

SQLスタイルのselectステートメントを実行するにはどうすればよいですか?


4
stat545-ubc.github.io/bit001_dplyr-cheatsheet.html←この質問に対する私のお気に入りの回答
同型写像

RStudioによって作成および保守されたdplyrチートシートを使用したデータ変換には、dplyr rstudio.com/resources/cheatsheets
Arthur Yip

2
代わりにパンダのデータフレームのマージについて知りたい場合は、ここにリソースがあります
cs95 2018

回答:


1350

使用することでmerge機能し、そのオプションのパラメータを:

内部結合: merge(df1, df2) Rは共通の変数名でフレームを自動的に結合するため、これらの例で機能merge(df1, df2, by = "CustomerId")しますが、必要なフィールドのみに一致するように指定することをお勧めします。一致する変数の名前が異なるデータフレームで異なる場合は、by.xおよびby.yパラメータを使用することもできます。

外部結合: merge(x = df1, y = df2, by = "CustomerId", all = TRUE)

左アウター: merge(x = df1, y = df2, by = "CustomerId", all.x = TRUE)

右アウター: merge(x = df1, y = df2, by = "CustomerId", all.y = TRUE)

クロス結合: merge(x = df1, y = df2, by = NULL)

内部結合と同様に、 "CustomerId"を一致する変数としてRに明示的に渡すことをお勧めします。 ほとんどの場合、マージしたい識別子を明示的に述べるのが最善だと思います。入力data.framesが予期せず変更され、後で読みやすくなれば、より安全です。

byようなベクトルを与えることで、複数の列をマージできますby = c("CustomerId", "OrderId")

上のマージするカラム名が同じでない場合、あなたは、たとえばを指定することができ、by.x = "CustomerId_in_df1", by.y = "CustomerId_in_df2"どこCustomerId_in_df1の列の名前は、最初のデータフレーム内にあり、CustomerId_in_df2第二のデータフレームの列の名前です。(複数の列でマージする必要がある場合、これらはベクトルにすることもできます。)


2
@MattParker私はデータフレームに対する複雑なクエリのホスト全体にsqldfパッケージを使用していますが、自己クロス結合(つまり、data.frameクロス結合自体)を実行するために本当に必要でした...パフォーマンスの観点から比較するとどうなのか... 。???
ニコラスハミルトン

9
@ADP私はsqldfを実際に使用したことがないので、速度についてはわかりません。パフォーマンスが主要な問題である場合は、data.tableパッケージも調べる必要があります。これはまったく新しい結合構文のセットですが、ここで説明しているものよりも根本的に高速です。
マットパーカー

5
より明確に、説明して..... mkmanu.wordpress.com/2016/04/08/…–
Manoj Kumar

42
私にとって役立つマイナーな追加-複数の列を使用してマージする場合:merge(x=df1,y=df2, by.x=c("x_col1","x_col2"), by.y=c("y_col1","y_col2"))
Dileep Kumar Patchigolla

8
これはdata.table今では機能しますが、同じ機能がより速くなります。
marbel 16

222

これらの操作をSQLで表現できるGabor Grothendieckのsqldfパッケージをチェックアウトすることをお勧めします。

library(sqldf)

## inner join
df3 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              JOIN df2 USING(CustomerID)")

## left join (substitute 'right' for right join)
df4 <- sqldf("SELECT CustomerId, Product, State 
              FROM df1
              LEFT JOIN df2 USING(CustomerID)")

SQL構文は、同等のR構文よりも単純で自然であることがわかりました(ただし、これは私のRDBMSバイアスを反映しているだけかもしれません)。

結合の詳細については、Gaborのsqldf GitHubを参照してください。


198

内部結合にはdata.tableアプローチがあり、時間とメモリ効率が非常に高くなります(一部の大きなdata.framesには必要です)。

library(data.table)

dt1 <- data.table(df1, key = "CustomerId") 
dt2 <- data.table(df2, key = "CustomerId")

joined.dt1.dt.2 <- dt1[dt2]

mergedata.tablesでも機能します(汎用的でを呼び出すためmerge.data.table)。

merge(dt1, dt2)

stackoverflowに記載さ
れているdata.tabledata.tableマージ操作を実行する方法
外部キーのSQL結合をR data.table構文に
変換する大きなdata.frames Rをマージするための効率的な代替方法data.tableで
基本的な左外部結合を実行する方法Rで?

さらに別のオプションはjoinplyrパッケージにある機能です

library(plyr)

join(df1, df2,
     type = "inner")

#   CustomerId Product   State
# 1          2 Toaster Alabama
# 2          4   Radio Alabama
# 3          6   Radio    Ohio

オプションtypeinnerleftrightfull

From ?join:とは異なりmerge、[ join]は、使用される結合タイプに関係なく、xの順序を保持します。


8
言及のための1 plyr::join。マイクロベンチマークは、パフォーマンスがの約3倍であることを示していますmerge
Beasterfield 2013年

20
ただし、data.table両方よりもはるかに高速です。SOにも優れたサポートがあります。ここでは、data.tableライターやコントリビューターほど頻繁に質問に回答するパッケージライターは多くありません。
マーベル

1
データフレームのリストdata.tableをマージするための構文は何ですか?
Aleksandr Blekh

5
注意:dt1 [dt2]は右外部結合(「純粋な」内部結合ではない)であるため、dt1に一致する行がなくても、dt2からのすべての行が結果の一部になります。影響:dt1のキー値と一致しないdt2のキー値がある場合、結果として潜在的に不要な行ができます。
Rヨーダ

8
その場合は@RYodaのみ指定できますnomatch = 0L
David Arenburg、2015

181

Hadley Wickhamの素晴らしいdplyrパッケージを使用して結合を行うこともできます。

library(dplyr)

#make sure that CustomerId cols are both type numeric
#they ARE not using the provided code in question and dplyr will complain
df1$CustomerId <- as.numeric(df1$CustomerId)
df2$CustomerId <- as.numeric(df2$CustomerId)

結合の変更:df2の一致を使用してdf1に列を追加します

#inner
inner_join(df1, df2)

#left outer
left_join(df1, df2)

#right outer
right_join(df1, df2)

#alternate right outer
left_join(df2, df1)

#full join
full_join(df1, df2)

結合のフィルタリング:df1の行をフィルターで除外し、列を変更しない

semi_join(df1, df2) #keep only observations in df1 that match in df2.
anti_join(df1, df2) #drops all observations in df1 that match in df2.

16
なぜCustomerId数値に変換する必要があるのですか?このタイプの制限についてのドキュメント(plyrおよびの両方dplyr)での言及はありません。マージ列のcharacterタイプが(特にplyr)である場合、コードは正しく動作しませんか?何か不足していますか?
Aleksandr Blekh 2014年

semi_join(df1、df2、df3、df4)を使用して、残りの列と一致するdf1の観測のみを保持できますか?
Ghose Bishwajit

@GhoseBishwajit列の代わりに残りのデータフレームを意味すると想定して、df2、df3、df4でrbindを使用できます。たとえば、semi_join(df1、rbind(df2、df3、df4))
abhy3

はい、私はデータフレームを意味しました。ただし、特定の行で欠落しているものがあるため、同じ構造ではありません。4つのデータフレームについて、さまざまな国の4つの異なる指標(GDP、GNP GINI、MMR)に関するデータがあります。私は、4つの指標すべてについてそれらの国のみが存在するようにデータフレームを結合したいと考えています。
Ghose Bishwajit

86

これをやり直す良い例がいくつかあります R Wiki。ここでカップルを盗みます:

マージ方法

キーには同じ名前が付けられているため、内部結合を行う簡単な方法はmerge()です。

merge(df1,df2)

完全な内部結合(両方のテーブルのすべてのレコード)は、「all」キーワードで作成できます。

merge(df1,df2, all=TRUE)

df1とdf2の左外部結合:

merge(df1,df2, all.x=TRUE)

df1とdf2の右外部結合:

merge(df1,df2, all.y=TRUE)

「em」、「slap」、「em」を押し下げて、質問した他の2つの外部結合を取得できます。

下付きメソッド

添え字メソッドを使用した左側のdf1との左外部結合は次のようになります。

df1[,"State"]<-df2[df1[ ,"Product"], "State"]

外部結合の他の組み合わせは、左外部結合添え字の例を変換することで作成できます。(ええ、それは「読者の演習として残しておきます...」と言っているのと同じことです。)


4
「R Wiki」リンクが壊れています。
zx8754 2019

79

2014の新機能:

特に、一般的なデータ操作(ソート、フィルタリング、サブセット化、要約など)にも関心がある場合は、必ず以下を参照してください。 dplyrデータフレームでの作業を容易にするために設計されたさまざまな機能をすべて備えたをぜひご覧ください。その他の特定のデータベースタイプ。非常に手の込んだSQLインターフェース、さらに(ほとんどの)SQLコードを直接Rに変換する関数さえ提供します。

dplyrパッケージの4つの結合関連関数は(引用)です。

  • inner_join(x, y, by = NULL, copy = FALSE, ...):yに一致する値があるxからすべての行を返し、xとyからすべての列を返します
  • left_join(x, y, by = NULL, copy = FALSE, ...):xからすべての行を返し、xとyからすべての列を返します
  • semi_join(x, y, by = NULL, copy = FALSE, ...):xからの列のみを保持して、yに一致する値があるxからすべての行を返します。
  • anti_join(x, y, by = NULL, copy = FALSE, ...):xからの列のみを保持して、yに一致する値がないxからすべての行を返します

それはすべてですここでは詳細に。

列の選択はで行うことができますselect(df,"column")。それがあなたにとってsql()SQLっぽくないなら、SQLコードをそのまま入力できる関数があり、Rでずっと書いていたのと同じように指定した操作を実行します(詳細については、dplyr /データベースビネット)。たとえば、正しく適用された場合sql("SELECT * FROM hflights")、「hflights」dplyrテーブル(「tbl」)からすべての列が選択されます。


dplyrパッケージが過去2年間で獲得した重要性を考えると、間違いなく最良のソリューションです。
マルコフマガッリ

72

データセットを結合するためのdata.tableメソッドの更新。結合のタイプごとの例を以下に示します。2つのメソッドがあります。1つは[.data.tableサブセットの最初の引数として2 つ目のdata.tableを渡すときです。もう1つは、merge高速なdata.tableメソッドにディスパッチする関数を使用する方法です。

df1 = data.frame(CustomerId = c(1:6), Product = c(rep("Toaster", 3), rep("Radio", 3)))
df2 = data.frame(CustomerId = c(2L, 4L, 7L), State = c(rep("Alabama", 2), rep("Ohio", 1))) # one value changed to show full outer join

library(data.table)

dt1 = as.data.table(df1)
dt2 = as.data.table(df2)
setkey(dt1, CustomerId)
setkey(dt2, CustomerId)
# right outer join keyed data.tables
dt1[dt2]

setkey(dt1, NULL)
setkey(dt2, NULL)
# right outer join unkeyed data.tables - use `on` argument
dt1[dt2, on = "CustomerId"]

# left outer join - swap dt1 with dt2
dt2[dt1, on = "CustomerId"]

# inner join - use `nomatch` argument
dt1[dt2, nomatch=NULL, on = "CustomerId"]

# anti join - use `!` operator
dt1[!dt2, on = "CustomerId"]

# inner join - using merge method
merge(dt1, dt2, by = "CustomerId")

# full outer join
merge(dt1, dt2, by = "CustomerId", all = TRUE)

# see ?merge.data.table arguments for other cases

以下のベンチマークテストでは、ベースR、sqldf、dplyr、data.tableをテストします。
ベンチマークでは、キーなし/インデックスなしのデータセットをテストします。ベンチマークは50M-1行のデータセットで実行され、結合列には50M-2の共通値があるため、各シナリオ(内部、左、右、完全)をテストでき、結合はまだ簡単ではありません。これは、結合アルゴリズムを強調する結合のタイプです。タイミングはのようですsqldf:0.4.11dplyr:0.7.8data.table:1.12.0

# inner
Unit: seconds
   expr       min        lq      mean    median        uq       max neval
   base 111.66266 111.66266 111.66266 111.66266 111.66266 111.66266     1
  sqldf 624.88388 624.88388 624.88388 624.88388 624.88388 624.88388     1
  dplyr  51.91233  51.91233  51.91233  51.91233  51.91233  51.91233     1
     DT  10.40552  10.40552  10.40552  10.40552  10.40552  10.40552     1
# left
Unit: seconds
   expr        min         lq       mean     median         uq        max 
   base 142.782030 142.782030 142.782030 142.782030 142.782030 142.782030     
  sqldf 613.917109 613.917109 613.917109 613.917109 613.917109 613.917109     
  dplyr  49.711912  49.711912  49.711912  49.711912  49.711912  49.711912     
     DT   9.674348   9.674348   9.674348   9.674348   9.674348   9.674348       
# right
Unit: seconds
   expr        min         lq       mean     median         uq        max
   base 122.366301 122.366301 122.366301 122.366301 122.366301 122.366301     
  sqldf 611.119157 611.119157 611.119157 611.119157 611.119157 611.119157     
  dplyr  50.384841  50.384841  50.384841  50.384841  50.384841  50.384841     
     DT   9.899145   9.899145   9.899145   9.899145   9.899145   9.899145     
# full
Unit: seconds
  expr       min        lq      mean    median        uq       max neval
  base 141.79464 141.79464 141.79464 141.79464 141.79464 141.79464     1
 dplyr  94.66436  94.66436  94.66436  94.66436  94.66436  94.66436     1
    DT  21.62573  21.62573  21.62573  21.62573  21.62573  21.62573     1

あなたが使用して実行できる参加し、他の種類がありますので注意してくださいdata.table
- 参加のアップデート -あなたのメインテーブルに別のテーブルから値を検索したい場合は
- 総参加では -あなたがキーに集約したい場合は、あなたが持っていない参加していますすべての結合結果をマテリアライズする
- オーバーラップ結合 -範囲ごとにマージする場合
- ローリング結合 -前後にローリングすることで前後の行の値と一致できるようにする場合
- 非等結合 -場合結合条件が等しくない

再現するコード:

library(microbenchmark)
library(sqldf)
library(dplyr)
library(data.table)
sapply(c("sqldf","dplyr","data.table"), packageVersion, simplify=FALSE)

n = 5e7
set.seed(108)
df1 = data.frame(x=sample(n,n-1L), y1=rnorm(n-1L))
df2 = data.frame(x=sample(n,n-1L), y2=rnorm(n-1L))
dt1 = as.data.table(df1)
dt2 = as.data.table(df2)

mb = list()
# inner join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x"),
               sqldf = sqldf("SELECT * FROM df1 INNER JOIN df2 ON df1.x = df2.x"),
               dplyr = inner_join(df1, df2, by = "x"),
               DT = dt1[dt2, nomatch=NULL, on = "x"]) -> mb$inner

# left outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.x = TRUE),
               sqldf = sqldf("SELECT * FROM df1 LEFT OUTER JOIN df2 ON df1.x = df2.x"),
               dplyr = left_join(df1, df2, by = c("x"="x")),
               DT = dt2[dt1, on = "x"]) -> mb$left

# right outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all.y = TRUE),
               sqldf = sqldf("SELECT * FROM df2 LEFT OUTER JOIN df1 ON df2.x = df1.x"),
               dplyr = right_join(df1, df2, by = "x"),
               DT = dt1[dt2, on = "x"]) -> mb$right

# full outer join
microbenchmark(times = 1L,
               base = merge(df1, df2, by = "x", all = TRUE),
               dplyr = full_join(df1, df2, by = "x"),
               DT = merge(dt1, dt2, by = "x", all = TRUE)) -> mb$full

lapply(mb, print) -> nul

さまざまな列名を使用する方法を示す例も追加する価値はありon = ますか?
SymbolixAU 2016年

1
それは非エクイは、オペレータへの加入を追加すると私たちは1.9.8のリリースを待つことができる@Symbolix onのarg
jangorecki

別の考え; マージ中にキーを追加して結果に残すmerge.data.tableデフォルトのsort = TRUE引数があるというメモを追加する価値がありますか?これは、特にキーの設定を回避しようとしている場合は注意が必要です。
SymbolixAU 2016

1
Dupsがある場合、それらのほとんどが機能しないと誰も言っていないことに驚いています...
statquant

@statquantでデカルト結合をdata.table実行できますが、どういう意味ですか?もっと具体的に教えてください。
David Arenburg 2017年

32

0.4以降のdplyrは、を含むこれらすべての結合を実装しましたがouter_join0.4より前の最初のいくつかのリリースでは、提供していなかったことに注意する価値がありました。その後(そのようなコードはSO、Kaggleの回答、その期間のgithubで引き続き見つけることができます。したがって、この回答は依然として有用な目的に役立ちます。)outer_join

参加関連のリリースのハイライト

v0.5(2016年6月)

  • POSIXctタイプ、タイムゾーン、重複、異なる因子レベルの処理。エラーと警告の改善。
  • 重複する変数名が受け取る接尾辞を制御する新しい接尾辞引数(#1296)

v0.4.0(2015年1月)

  • 右結合と外部結合を実装する(#96)
  • 変更結合。別の行の一致する行から1つのテーブルに新しい変数を追加します。フィルタリング結合。一方のテーブルの観測値を、もう一方のテーブルの観測値と一致するかどうかに基づいてフィルタリングします。

v0.3(2014年10月)

  • 各テーブルの異なる変数でleft_joinできるようになりました:df1%>%left_join(df2、c( "var1" = "var2"))

v0.2(2014年5月)

  • * _join()は列名を並べ替えなくなりました(#324)

v0.1.3(2014年4月)

その問題におけるハドリーのコメントごとの回避策:

  • right_join(x、y)は、行の点ではleft_join(y、x)と同じですが、列の順序が異なります。select(new_column_order)で簡単に回避
  • outer_joinは基本的にunion(left_join(x、y)、right_join(x、y))です-つまり、両方のデータフレームのすべての行を保持します。

1
@Gregor:いいえ、削除しないでください。Rユーザーにとっては、結合機能が長年欠落していたことを知っておくことが重要です。そこにあるコードのほとんどには、回避策やアドホックの手動実装、またはインデックスのベクターによるアドホセリーが含まれているため、さらに悪いことに、これらのパッケージやすべての操作。毎週、SOでそのような質問を目にします。私たちは、今後何年にもわたって混乱を解消します。
smci 2016

@Gregorと尋ねた他の人:更新された、歴史的な変更と、この質問が尋ねられた数年前から何が欠けていたかを要約しています。これは、その期間のコードが主にハックされていたり、dplyr結合の使用を避けられてマージに失敗した理由を示しています。SOとKaggleの過去のコードベースを確認すると、採用の遅延と、ユーザーコードが非常に混乱していることがわかります。この回答がまだ不足している場合は、お知らせください。
smci 2018年

@Gregor:2014年半ばにそれを採用した私たちの人々は、最高の瞬間を選びませんでした。(2013年頃には以前の(0.0.x)リリースがあったと思いましたが、間違いはありませんでした。)とにかく、2015年までまだがらくたコードがたくさんありました。それが私がこれを投稿する動機となったのです。 Kaggle、github、SOで見つけたクラッド。
smci 2018年

2
はい、わかりました。あなたはそれをうまくやっていると思います。(私もアーリーアダプターでした。私はまだdplyr構文が好きですが、バックエンドからlazyevalへの変更によりrlang多くのコードが壊れてしまい、詳細を学ぶようdata.tableになりました。現在は主に使用していますdata.table。)
Gregor Thomas

@Gregor:興味深い、それをカバーするQ&A(あなたや他の誰か)を教えてくれませんか?plyr/ dplyr/ data.table/ tidyverseを採用するたびに、開始した年と、パッケージが当時とは対照的に、その当時の状態(胚)に大きく依存しているようです...
smci

25

それぞれ100万行までの2つのデータフレームを結合し、1つは2列、もう1つは20以下のデータフレームを結合すると、驚くほどmerge(..., all.x = TRUE, all.y = TRUE)高速であることがわかりましたdplyr::full_join()。これはdplyr v0.4で

マージには最大17秒、full_joinには最大65秒かかります。

しかし、私は通常、デフォルトで操作タスクにdplyrを使用するため、いくつかの食べ物です。


24

0..*:0..1カーディナリティを使用した左結合またはカーディナリティを使用した右結合の場合、結合子(テーブル)0..1:0..*の片側の列を0..1直接結合側(テーブル)に割り当てることができる0..*ため、まったく新しいデータの表。これには、ジョイニーのキー列をジョイナーに一致させ、それに応じてジョイナーの行をインデックス付け+順序付けする必要があります。

キーが単一の列である場合は、単一の呼び出しを使用match()してマッチングを実行できます。これは、この回答で取り上げるケースです。

OPに基づく例を次に示します。ただしdf2、ジョイナーで一致しないキーのケースをテストするために、IDが7の行を追加しました。これは実質的にdf1左結合df2です:

df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L)));
df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas'));
df1[names(df2)[-1L]] <- df2[match(df1[,1L],df2[,1L]),-1L];
df1;
##   CustomerId Product   State
## 1          1 Toaster    <NA>
## 2          2 Toaster Alabama
## 3          3 Toaster    <NA>
## 4          4   Radio Alabama
## 5          5   Radio    <NA>
## 6          6   Radio    Ohio

上記では、キー列が両方の入力テーブルの最初の列であるという仮定をハードコーディングしました。一般的に、これは不当な仮定ではありません。キー列を持つdata.frameがある場合、それがdata.frameの最初の列として設定されていないと、奇妙なことになるからです。最初に。そして、いつでも列を並べ替えてそのようにすることができます。この仮定の有利な結果は、キー列の名前をハードコーディングする必要がないことですが、ある仮定を別の仮定に置き換えるだけであると思います。精度は、整数インデックスのもう1つの利点であると同時に速度です。以下のベンチマークでは、競合する実装に一致するように文字列名のインデックス付けを使用するように実装を変更します。

単一の大きなテーブルに対して左結合したいテーブルがいくつかある場合、これは特に適切なソリューションだと思います。マージごとにテーブル全体を繰り返し再構築することは不必要で非効率的です。

一方、何らかの理由でこの操作を通じてジョイニーを変更しないでおく必要がある場合、このソリューションはジョイニーを直接変更するため、使用できません。ただし、その場合は、単にコピーを作成し、そのコピーに対してインプレース割り当てを実行できます。


補足として、私は複数列キーの可能なマッチングソリューションを簡単に調べました。残念ながら、私が見つけた唯一の一致する解決策は:

  • 非効率的な連結。例えばmatch(interaction(df1$a,df1$b),interaction(df2$a,df2$b))、またはと同じ考えpaste()
  • 非効率的なデカルト結合詞、例えば outer(df1$a,df2$a,`==`) & outer(df1$b,df2$b,`==`)
  • ベースR merge()および同等のパッケージベースのマージ関数。常に新しいテーブルを割り当ててマージされた結果を返すため、インプレースの割り当てベースのソリューションには適していません。

たとえば、異なるデータフレームで複数の列一致させ、結果として他の列を取得する2つの列を他の2つの列一致する、複数の列一致する、および最初にインプレースソリューションCombineを思いついたこの質問の重複を参照してください。Rの行数が異なる2つのデータフレーム


ベンチマーク

私は独自のベンチマークを実行して、インプレース割り当てアプローチがこの質問で提供されている他のソリューションとどのように比較されるかを確認することにしました。

テストコード:

library(microbenchmark);
library(data.table);
library(sqldf);
library(plyr);
library(dplyr);

solSpecs <- list(
    merge=list(testFuncs=list(
        inner=function(df1,df2,key) merge(df1,df2,key),
        left =function(df1,df2,key) merge(df1,df2,key,all.x=T),
        right=function(df1,df2,key) merge(df1,df2,key,all.y=T),
        full =function(df1,df2,key) merge(df1,df2,key,all=T)
    )),
    data.table.unkeyed=list(argSpec='data.table.unkeyed',testFuncs=list(
        inner=function(dt1,dt2,key) dt1[dt2,on=key,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2,key) dt2[dt1,on=key,allow.cartesian=T],
        right=function(dt1,dt2,key) dt1[dt2,on=key,allow.cartesian=T],
        full =function(dt1,dt2,key) merge(dt1,dt2,key,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    data.table.keyed=list(argSpec='data.table.keyed',testFuncs=list(
        inner=function(dt1,dt2) dt1[dt2,nomatch=0L,allow.cartesian=T],
        left =function(dt1,dt2) dt2[dt1,allow.cartesian=T],
        right=function(dt1,dt2) dt1[dt2,allow.cartesian=T],
        full =function(dt1,dt2) merge(dt1,dt2,all=T,allow.cartesian=T) ## calls merge.data.table()
    )),
    sqldf.unindexed=list(testFuncs=list( ## note: must pass connection=NULL to avoid running against the live DB connection, which would result in collisions with the residual tables from the last query upload
        inner=function(df1,df2,key) sqldf(paste0('select * from df1 inner join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        left =function(df1,df2,key) sqldf(paste0('select * from df1 left join df2 using(',paste(collapse=',',key),')'),connection=NULL),
        right=function(df1,df2,key) sqldf(paste0('select * from df2 left join df1 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from df1 full join df2 using(',paste(collapse=',',key),')'),connection=NULL) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    sqldf.indexed=list(testFuncs=list( ## important: requires an active DB connection with preindexed main.df1 and main.df2 ready to go; arguments are actually ignored
        inner=function(df1,df2,key) sqldf(paste0('select * from main.df1 inner join main.df2 using(',paste(collapse=',',key),')')),
        left =function(df1,df2,key) sqldf(paste0('select * from main.df1 left join main.df2 using(',paste(collapse=',',key),')')),
        right=function(df1,df2,key) sqldf(paste0('select * from main.df2 left join main.df1 using(',paste(collapse=',',key),')')) ## can't do right join proper, not yet supported; inverted left join is equivalent
        ##full =function(df1,df2,key) sqldf(paste0('select * from main.df1 full join main.df2 using(',paste(collapse=',',key),')')) ## can't do full join proper, not yet supported; possible to hack it with a union of left joins, but too unreasonable to include in testing
    )),
    plyr=list(testFuncs=list(
        inner=function(df1,df2,key) join(df1,df2,key,'inner'),
        left =function(df1,df2,key) join(df1,df2,key,'left'),
        right=function(df1,df2,key) join(df1,df2,key,'right'),
        full =function(df1,df2,key) join(df1,df2,key,'full')
    )),
    dplyr=list(testFuncs=list(
        inner=function(df1,df2,key) inner_join(df1,df2,key),
        left =function(df1,df2,key) left_join(df1,df2,key),
        right=function(df1,df2,key) right_join(df1,df2,key),
        full =function(df1,df2,key) full_join(df1,df2,key)
    )),
    in.place=list(testFuncs=list(
        left =function(df1,df2,key) { cns <- setdiff(names(df2),key); df1[cns] <- df2[match(df1[,key],df2[,key]),cns]; df1; },
        right=function(df1,df2,key) { cns <- setdiff(names(df1),key); df2[cns] <- df1[match(df2[,key],df1[,key]),cns]; df2; }
    ))
);

getSolTypes <- function() names(solSpecs);
getJoinTypes <- function() unique(unlist(lapply(solSpecs,function(x) names(x$testFuncs))));
getArgSpec <- function(argSpecs,key=NULL) if (is.null(key)) argSpecs$default else argSpecs[[key]];

initSqldf <- function() {
    sqldf(); ## creates sqlite connection on first run, cleans up and closes existing connection otherwise
    if (exists('sqldfInitFlag',envir=globalenv(),inherits=F) && sqldfInitFlag) { ## false only on first run
        sqldf(); ## creates a new connection
    } else {
        assign('sqldfInitFlag',T,envir=globalenv()); ## set to true for the one and only time
    }; ## end if
    invisible();
}; ## end initSqldf()

setUpBenchmarkCall <- function(argSpecs,joinType,solTypes=getSolTypes(),env=parent.frame()) {
    ## builds and returns a list of expressions suitable for passing to the list argument of microbenchmark(), and assigns variables to resolve symbol references in those expressions
    callExpressions <- list();
    nms <- character();
    for (solType in solTypes) {
        testFunc <- solSpecs[[solType]]$testFuncs[[joinType]];
        if (is.null(testFunc)) next; ## this join type is not defined for this solution type
        testFuncName <- paste0('tf.',solType);
        assign(testFuncName,testFunc,envir=env);
        argSpecKey <- solSpecs[[solType]]$argSpec;
        argSpec <- getArgSpec(argSpecs,argSpecKey);
        argList <- setNames(nm=names(argSpec$args),vector('list',length(argSpec$args)));
        for (i in seq_along(argSpec$args)) {
            argName <- paste0('tfa.',argSpecKey,i);
            assign(argName,argSpec$args[[i]],envir=env);
            argList[[i]] <- if (i%in%argSpec$copySpec) call('copy',as.symbol(argName)) else as.symbol(argName);
        }; ## end for
        callExpressions[[length(callExpressions)+1L]] <- do.call(call,c(list(testFuncName),argList),quote=T);
        nms[length(nms)+1L] <- solType;
    }; ## end for
    names(callExpressions) <- nms;
    callExpressions;
}; ## end setUpBenchmarkCall()

harmonize <- function(res) {
    res <- as.data.frame(res); ## coerce to data.frame
    for (ci in which(sapply(res,is.factor))) res[[ci]] <- as.character(res[[ci]]); ## coerce factor columns to character
    for (ci in which(sapply(res,is.logical))) res[[ci]] <- as.integer(res[[ci]]); ## coerce logical columns to integer (works around sqldf quirk of munging logicals to integers)
    ##for (ci in which(sapply(res,inherits,'POSIXct'))) res[[ci]] <- as.double(res[[ci]]); ## coerce POSIXct columns to double (works around sqldf quirk of losing POSIXct class) ----- POSIXct doesn't work at all in sqldf.indexed
    res <- res[order(names(res))]; ## order columns
    res <- res[do.call(order,res),]; ## order rows
    res;
}; ## end harmonize()

checkIdentical <- function(argSpecs,solTypes=getSolTypes()) {
    for (joinType in getJoinTypes()) {
        callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
        if (length(callExpressions)<2L) next;
        ex <- harmonize(eval(callExpressions[[1L]]));
        for (i in seq(2L,len=length(callExpressions)-1L)) {
            y <- harmonize(eval(callExpressions[[i]]));
            if (!isTRUE(all.equal(ex,y,check.attributes=F))) {
                ex <<- ex;
                y <<- y;
                solType <- names(callExpressions)[i];
                stop(paste0('non-identical: ',solType,' ',joinType,'.'));
            }; ## end if
        }; ## end for
    }; ## end for
    invisible();
}; ## end checkIdentical()

testJoinType <- function(argSpecs,joinType,solTypes=getSolTypes(),metric=NULL,times=100L) {
    callExpressions <- setUpBenchmarkCall(argSpecs,joinType,solTypes);
    bm <- microbenchmark(list=callExpressions,times=times);
    if (is.null(metric)) return(bm);
    bm <- summary(bm);
    res <- setNames(nm=names(callExpressions),bm[[metric]]);
    attr(res,'unit') <- attr(bm,'unit');
    res;
}; ## end testJoinType()

testAllJoinTypes <- function(argSpecs,solTypes=getSolTypes(),metric=NULL,times=100L) {
    joinTypes <- getJoinTypes();
    resList <- setNames(nm=joinTypes,lapply(joinTypes,function(joinType) testJoinType(argSpecs,joinType,solTypes,metric,times)));
    if (is.null(metric)) return(resList);
    units <- unname(unlist(lapply(resList,attr,'unit')));
    res <- do.call(data.frame,c(list(join=joinTypes),setNames(nm=solTypes,rep(list(rep(NA_real_,length(joinTypes))),length(solTypes))),list(unit=units,stringsAsFactors=F)));
    for (i in seq_along(resList)) res[i,match(names(resList[[i]]),names(res))] <- resList[[i]];
    res;
}; ## end testAllJoinTypes()

testGrid <- function(makeArgSpecsFunc,sizes,overlaps,solTypes=getSolTypes(),joinTypes=getJoinTypes(),metric='median',times=100L) {

    res <- expand.grid(size=sizes,overlap=overlaps,joinType=joinTypes,stringsAsFactors=F);
    res[solTypes] <- NA_real_;
    res$unit <- NA_character_;
    for (ri in seq_len(nrow(res))) {

        size <- res$size[ri];
        overlap <- res$overlap[ri];
        joinType <- res$joinType[ri];

        argSpecs <- makeArgSpecsFunc(size,overlap);

        checkIdentical(argSpecs,solTypes);

        cur <- testJoinType(argSpecs,joinType,solTypes,metric,times);
        res[ri,match(names(cur),names(res))] <- cur;
        res$unit[ri] <- attr(cur,'unit');

    }; ## end for

    res;

}; ## end testGrid()

これは、前に説明したOPに基づく例のベンチマークです。

## OP's example, supplemented with a non-matching row in df2
argSpecs <- list(
    default=list(copySpec=1:2,args=list(
        df1 <- data.frame(CustomerId=1:6,Product=c(rep('Toaster',3L),rep('Radio',3L))),
        df2 <- data.frame(CustomerId=c(2L,4L,6L,7L),State=c(rep('Alabama',2L),'Ohio','Texas')),
        'CustomerId'
    )),
    data.table.unkeyed=list(copySpec=1:2,args=list(
        as.data.table(df1),
        as.data.table(df2),
        'CustomerId'
    )),
    data.table.keyed=list(copySpec=1:2,args=list(
        setkey(as.data.table(df1),CustomerId),
        setkey(as.data.table(df2),CustomerId)
    ))
);
## prepare sqldf
initSqldf();
sqldf('create index df1_key on df1(CustomerId);'); ## upload and create an sqlite index on df1
sqldf('create index df2_key on df2(CustomerId);'); ## upload and create an sqlite index on df2

checkIdentical(argSpecs);

testAllJoinTypes(argSpecs,metric='median');
##    join    merge data.table.unkeyed data.table.keyed sqldf.unindexed sqldf.indexed      plyr    dplyr in.place         unit
## 1 inner  644.259           861.9345          923.516        9157.752      1580.390  959.2250 270.9190       NA microseconds
## 2  left  713.539           888.0205          910.045        8820.334      1529.714  968.4195 270.9185 224.3045 microseconds
## 3 right 1221.804           909.1900          923.944        8930.668      1533.135 1063.7860 269.8495 218.1035 microseconds
## 4  full 1302.203          3107.5380         3184.729              NA            NA 1593.6475 270.7055       NA microseconds

ここでは、2つの入力テーブル間で異なるスケールと異なるパターンのキーオーバーラップを試して、ランダムな入力データのベンチマークを行います。このベンチマークは、依然として単一列の整数キーの場合に制限されています。また、同じテーブルの左結合と右結合の両方でインプレースソリューションが機能することを保証するために、すべてのランダムテストデータは0..1:0..1基数を使用します。これは、2番目のdata.frameのキー列を生成するときに、最初のdata.frameのキー列を置換せずにサンプリングすることで実装されます。

makeArgSpecs.singleIntegerKey.optionalOneToOne <- function(size,overlap) {

    com <- as.integer(size*overlap);

    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- data.frame(id=sample(size),y1=rnorm(size),y2=rnorm(size)),
            df2 <- data.frame(id=sample(c(if (com>0L) sample(df1$id,com) else integer(),seq(size+1L,len=size-com))),y3=rnorm(size),y4=rnorm(size)),
            'id'
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            'id'
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkey(as.data.table(df1),id),
            setkey(as.data.table(df2),id)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf('create index df1_key on df1(id);'); ## upload and create an sqlite index on df1
    sqldf('create index df2_key on df2(id);'); ## upload and create an sqlite index on df2

    argSpecs;

}; ## end makeArgSpecs.singleIntegerKey.optionalOneToOne()

## cross of various input sizes and key overlaps
sizes <- c(1e1L,1e3L,1e6L);
overlaps <- c(0.99,0.5,0.01);
system.time({ res <- testGrid(makeArgSpecs.singleIntegerKey.optionalOneToOne,sizes,overlaps); });
##     user   system  elapsed
## 22024.65 12308.63 34493.19

上記の結果の対数-対数プロットを作成するコードをいくつか書きました。オーバーラップ率ごとに個別のプロットを生成しました。少し乱雑ですが、すべてのソリューションタイプと結合タイプを同じプロットで表すのが好きです。

スプライン補間を使用して、個々のpchシンボルで描画された各ソリューション/結合タイプの組み合わせの滑らかな曲線を表示しました。結合タイプは、pch記号によってキャプチャされます。内側と左側の山かっこを左と右の山かっこに使用し、ひし形を完全に使用します。ソリューションタイプは、凡例に示されている色でキャプチャされます。

plotRes <- function(res,titleFunc,useFloor=F) {
    solTypes <- setdiff(names(res),c('size','overlap','joinType','unit')); ## derive from res
    normMult <- c(microseconds=1e-3,milliseconds=1); ## normalize to milliseconds
    joinTypes <- getJoinTypes();
    cols <- c(merge='purple',data.table.unkeyed='blue',data.table.keyed='#00DDDD',sqldf.unindexed='brown',sqldf.indexed='orange',plyr='red',dplyr='#00BB00',in.place='magenta');
    pchs <- list(inner=20L,left='<',right='>',full=23L);
    cexs <- c(inner=0.7,left=1,right=1,full=0.7);
    NP <- 60L;
    ord <- order(decreasing=T,colMeans(res[res$size==max(res$size),solTypes],na.rm=T));
    ymajors <- data.frame(y=c(1,1e3),label=c('1ms','1s'),stringsAsFactors=F);
    for (overlap in unique(res$overlap)) {
        x1 <- res[res$overlap==overlap,];
        x1[solTypes] <- x1[solTypes]*normMult[x1$unit]; x1$unit <- NULL;
        xlim <- c(1e1,max(x1$size));
        xticks <- 10^seq(log10(xlim[1L]),log10(xlim[2L]));
        ylim <- c(1e-1,10^((if (useFloor) floor else ceiling)(log10(max(x1[solTypes],na.rm=T))))); ## use floor() to zoom in a little more, only sqldf.unindexed will break above, but xpd=NA will keep it visible
        yticks <- 10^seq(log10(ylim[1L]),log10(ylim[2L]));
        yticks.minor <- rep(yticks[-length(yticks)],each=9L)*1:9;
        plot(NA,xlim=xlim,ylim=ylim,xaxs='i',yaxs='i',axes=F,xlab='size (rows)',ylab='time (ms)',log='xy');
        abline(v=xticks,col='lightgrey');
        abline(h=yticks.minor,col='lightgrey',lty=3L);
        abline(h=yticks,col='lightgrey');
        axis(1L,xticks,parse(text=sprintf('10^%d',as.integer(log10(xticks)))));
        axis(2L,yticks,parse(text=sprintf('10^%d',as.integer(log10(yticks)))),las=1L);
        axis(4L,ymajors$y,ymajors$label,las=1L,tick=F,cex.axis=0.7,hadj=0.5);
        for (joinType in rev(joinTypes)) { ## reverse to draw full first, since it's larger and would be more obtrusive if drawn last
            x2 <- x1[x1$joinType==joinType,];
            for (solType in solTypes) {
                if (any(!is.na(x2[[solType]]))) {
                    xy <- spline(x2$size,x2[[solType]],xout=10^(seq(log10(x2$size[1L]),log10(x2$size[nrow(x2)]),len=NP)));
                    points(xy$x,xy$y,pch=pchs[[joinType]],col=cols[solType],cex=cexs[joinType],xpd=NA);
                }; ## end if
            }; ## end for
        }; ## end for
        ## custom legend
        ## due to logarithmic skew, must do all distance calcs in inches, and convert to user coords afterward
        ## the bottom-left corner of the legend will be defined in normalized figure coords, although we can convert to inches immediately
        leg.cex <- 0.7;
        leg.x.in <- grconvertX(0.275,'nfc','in');
        leg.y.in <- grconvertY(0.6,'nfc','in');
        leg.x.user <- grconvertX(leg.x.in,'in');
        leg.y.user <- grconvertY(leg.y.in,'in');
        leg.outpad.w.in <- 0.1;
        leg.outpad.h.in <- 0.1;
        leg.midpad.w.in <- 0.1;
        leg.midpad.h.in <- 0.1;
        leg.sol.w.in <- max(strwidth(solTypes,'in',leg.cex));
        leg.sol.h.in <- max(strheight(solTypes,'in',leg.cex))*1.5; ## multiplication factor for greater line height
        leg.join.w.in <- max(strheight(joinTypes,'in',leg.cex))*1.5; ## ditto
        leg.join.h.in <- max(strwidth(joinTypes,'in',leg.cex));
        leg.main.w.in <- leg.join.w.in*length(joinTypes);
        leg.main.h.in <- leg.sol.h.in*length(solTypes);
        leg.x2.user <- grconvertX(leg.x.in+leg.outpad.w.in*2+leg.main.w.in+leg.midpad.w.in+leg.sol.w.in,'in');
        leg.y2.user <- grconvertY(leg.y.in+leg.outpad.h.in*2+leg.main.h.in+leg.midpad.h.in+leg.join.h.in,'in');
        leg.cols.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.join.w.in*(0.5+seq(0L,length(joinTypes)-1L)),'in');
        leg.lines.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in-leg.sol.h.in*(0.5+seq(0L,length(solTypes)-1L)),'in');
        leg.sol.x.user <- grconvertX(leg.x.in+leg.outpad.w.in+leg.main.w.in+leg.midpad.w.in,'in');
        leg.join.y.user <- grconvertY(leg.y.in+leg.outpad.h.in+leg.main.h.in+leg.midpad.h.in,'in');
        rect(leg.x.user,leg.y.user,leg.x2.user,leg.y2.user,col='white');
        text(leg.sol.x.user,leg.lines.y.user,solTypes[ord],cex=leg.cex,pos=4L,offset=0);
        text(leg.cols.x.user,leg.join.y.user,joinTypes,cex=leg.cex,pos=4L,offset=0,srt=90); ## srt rotation applies *after* pos/offset positioning
        for (i in seq_along(joinTypes)) {
            joinType <- joinTypes[i];
            points(rep(leg.cols.x.user[i],length(solTypes)),ifelse(colSums(!is.na(x1[x1$joinType==joinType,solTypes[ord]]))==0L,NA,leg.lines.y.user),pch=pchs[[joinType]],col=cols[solTypes[ord]]);
        }; ## end for
        title(titleFunc(overlap));
        readline(sprintf('overlap %.02f',overlap));
    }; ## end for
}; ## end plotRes()

titleFunc <- function(overlap) sprintf('R merge solutions: single-column integer key, 0..1:0..1 cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,T);

R-merge-benchmark-single-column-integer-key-optional-one-to-one-99

R-merge-benchmark-single-column-integer-key-optional-one-to-one-50

R-merge-benchmark-single-column-integer-key-optional-one-to-one-1


キー列の数とタイプ、およびカーディナリティに関して、より強力な2つ目の大規模ベンチマークを次に示します。このベンチマークでは、3つのキー列を使用します。1つの文字、1つの整数、1つの論理列であり、カーディナリティに制限はありません(つまり、0..*:0..*)。(一般に、浮動小数点比較の複雑さのため、キー列を倍精度または複素数の値で定義することはお勧めできません。基本的に、生の型を使用することはなく、キー列にははるかに少ないため、これらの型をキーに含めていませんまた、情報のために、最初にPOSIXctキー列を含めることで4つのキー列を使用しようとしましたがsqldf.indexed、おそらく浮動小数点比較の異常のため、POSIXctタイプはソリューションでうまく機能しませんでした。削除しました。)

makeArgSpecs.assortedKey.optionalManyToMany <- function(size,overlap,uniquePct=75) {

    ## number of unique keys in df1
    u1Size <- as.integer(size*uniquePct/100);

    ## (roughly) divide u1Size into bases, so we can use expand.grid() to produce the required number of unique key values with repetitions within individual key columns
    ## use ceiling() to ensure we cover u1Size; will truncate afterward
    u1SizePerKeyColumn <- as.integer(ceiling(u1Size^(1/3)));

    ## generate the unique key values for df1
    keys1 <- expand.grid(stringsAsFactors=F,
        idCharacter=replicate(u1SizePerKeyColumn,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=sample(u1SizePerKeyColumn),
        idLogical=sample(c(F,T),u1SizePerKeyColumn,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+sample(u1SizePerKeyColumn)
    )[seq_len(u1Size),];

    ## rbind some repetitions of the unique keys; this will prepare one side of the many-to-many relationship
    ## also scramble the order afterward
    keys1 <- rbind(keys1,keys1[sample(nrow(keys1),size-u1Size,T),])[sample(size),];

    ## common and unilateral key counts
    com <- as.integer(size*overlap);
    uni <- size-com;

    ## generate some unilateral keys for df2 by synthesizing outside of the idInteger range of df1
    keys2 <- data.frame(stringsAsFactors=F,
        idCharacter=replicate(uni,paste(collapse='',sample(letters,sample(4:12,1L),T))),
        idInteger=u1SizePerKeyColumn+sample(uni),
        idLogical=sample(c(F,T),uni,T)
        ##idPOSIXct=as.POSIXct('2016-01-01 00:00:00','UTC')+u1SizePerKeyColumn+sample(uni)
    );

    ## rbind random keys from df1; this will complete the many-to-many relationship
    ## also scramble the order afterward
    keys2 <- rbind(keys2,keys1[sample(nrow(keys1),com,T),])[sample(size),];

    ##keyNames <- c('idCharacter','idInteger','idLogical','idPOSIXct');
    keyNames <- c('idCharacter','idInteger','idLogical');
    ## note: was going to use raw and complex type for two of the non-key columns, but data.table doesn't seem to fully support them
    argSpecs <- list(
        default=list(copySpec=1:2,args=list(
            df1 <- cbind(stringsAsFactors=F,keys1,y1=sample(c(F,T),size,T),y2=sample(size),y3=rnorm(size),y4=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            df2 <- cbind(stringsAsFactors=F,keys2,y5=sample(c(F,T),size,T),y6=sample(size),y7=rnorm(size),y8=replicate(size,paste(collapse='',sample(letters,sample(4:12,1L),T)))),
            keyNames
        )),
        data.table.unkeyed=list(copySpec=1:2,args=list(
            as.data.table(df1),
            as.data.table(df2),
            keyNames
        )),
        data.table.keyed=list(copySpec=1:2,args=list(
            setkeyv(as.data.table(df1),keyNames),
            setkeyv(as.data.table(df2),keyNames)
        ))
    );
    ## prepare sqldf
    initSqldf();
    sqldf(paste0('create index df1_key on df1(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df1
    sqldf(paste0('create index df2_key on df2(',paste(collapse=',',keyNames),');')); ## upload and create an sqlite index on df2

    argSpecs;

}; ## end makeArgSpecs.assortedKey.optionalManyToMany()

sizes <- c(1e1L,1e3L,1e5L); ## 1e5L instead of 1e6L to respect more heavy-duty inputs
overlaps <- c(0.99,0.5,0.01);
solTypes <- setdiff(getSolTypes(),'in.place');
system.time({ res <- testGrid(makeArgSpecs.assortedKey.optionalManyToMany,sizes,overlaps,solTypes); });
##     user   system  elapsed
## 38895.50   784.19 39745.53

上記と同じプロットコードを使用した結果のプロット:

titleFunc <- function(overlap) sprintf('R merge solutions: character/integer/logical key, 0..*:0..* cardinality, %d%% overlap',as.integer(overlap*100));
plotRes(res,titleFunc,F);

R-merge-benchmark-assorted-key-optional-many-to-many-99

Rマージベンチマーク-分類済みキー-オプション-多対多-50

R-merge-benchmark-assorted-key-optional-many-to-many-1


非常に素晴らしい分析ですが、スケールを10 ^ 1から10 ^ 6に設定するのは残念です。これらは非常に小さいセットなので、速度の違いはほとんど関係ありません。10 ^ 6から10 ^ 8は興味深いものです。
jangorecki

1
また、結合操作に対して無効にするクラス強制のタイミングをベンチマークに含めることも見つけました。
jangorecki

8
  1. 使用する merge関数を、SQLのselectステートメントに慣れているように、左テーブルまたは右テーブルの変数を選択できます(EX:Select a。* ...またはSelect b。* from .....)
  2. 新しく結合されたテーブルからサブセット化するコードを追加する必要があります。

    • SQL:- select a.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

    • R:- merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df1)]

同じ方法

  • SQL:- select b.* from df1 a inner join df2 b on a.CustomerId=b.CustomerId

  • R:- merge(df1, df2, by.x = "CustomerId", by.y = "CustomerId")[,names(df2)]


7

インナーは、すべての列に参加するために、あなたはまた、使用することができますfintersectからdata.table -packageやintersectからdplyrの代替として-package merge指定せずにby-columnsを。これにより、2つのデータフレーム間で等しい行が得られます。

merge(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
dplyr::intersect(df1, df2)
#   V1 V2
# 1  B  2
# 2  C  3
data.table::fintersect(setDT(df1), setDT(df2))
#    V1 V2
# 1:  B  2
# 2:  C  3

データの例:

df1 <- data.frame(V1 = LETTERS[1:4], V2 = 1:4)
df2 <- data.frame(V1 = LETTERS[2:3], V2 = 2:3)

5

結合を更新します。もう1つの重要なSQLスタイルの結合は、「更新結合」で、1つのテーブルの列が別のテーブルを使用して更新(または作成)されます。

OPのサンプルテーブルを変更しています...

sales = data.frame(
  CustomerId = c(1, 1, 1, 3, 4, 6), 
  Year = 2000:2005,
  Product = c(rep("Toaster", 3), rep("Radio", 3))
)
cust = data.frame(
  CustomerId = c(1, 1, 4, 6), 
  Year = c(2001L, 2002L, 2002L, 2002L),
  State = state.name[1:4]
)

sales
# CustomerId Year Product
#          1 2000 Toaster
#          1 2001 Toaster
#          1 2002 Toaster
#          3 2003   Radio
#          4 2004   Radio
#          6 2005   Radio

cust
# CustomerId Year    State
#          1 2001  Alabama
#          1 2002   Alaska
#          4 2002  Arizona
#          6 2002 Arkansas

年の列を無視して、顧客の状態をcust購入表に追加するsalesとします。ベースRを使用すると、一致する行を識別し、次に値をコピーできます。

sales$State <- cust$State[ match(sales$CustomerId, cust$CustomerId) ]

# CustomerId Year Product    State
#          1 2000 Toaster  Alabama
#          1 2001 Toaster  Alabama
#          1 2002 Toaster  Alabama
#          3 2003   Radio     <NA>
#          4 2004   Radio  Arizona
#          6 2005   Radio Arkansas

# cleanup for the next example
sales$State <- NULL

ここでわかるようにmatch、customerテーブルから最初に一致する行を選択します。


複数の列で結合を更新します。上記のアプローチは、単一の列のみで結合していて、最初の一致で満足している場合にうまく機能します。customerテーブルの測定年を販売年と一致させたいとします。

@bgoldstの回答が言及しているように、matchwith interactionはこの場合のオプションかもしれません。より簡単に言うと、data.tableを使用できます。

library(data.table)
setDT(sales); setDT(cust)

sales[, State := cust[sales, on=.(CustomerId, Year), x.State]]

#    CustomerId Year Product   State
# 1:          1 2000 Toaster    <NA>
# 2:          1 2001 Toaster Alabama
# 3:          1 2002 Toaster  Alaska
# 4:          3 2003   Radio    <NA>
# 5:          4 2004   Radio    <NA>
# 6:          6 2005   Radio    <NA>

# cleanup for next example
sales[, State := NULL]

ローリング更新結合。または、お客様が見つかった最後の状態を取得することもできます。

sales[, State := cust[sales, on=.(CustomerId, Year), roll=TRUE, x.State]]

#    CustomerId Year Product    State
# 1:          1 2000 Toaster     <NA>
# 2:          1 2001 Toaster  Alabama
# 3:          1 2002 Toaster   Alaska
# 4:          3 2003   Radio     <NA>
# 5:          4 2004   Radio  Arizona
# 6:          6 2005   Radio Arkansas

上記の3つの例はすべて、新しい列の作成/追加に重点を置いています。既存の列を更新/変更する例については、関連するR FAQを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.