dplyrパッケージの*_join()
は、キーとなる列の値が完全に一致する必要があります。しかし、世の中のデータはそんなにきれいに一致はしていなくて、微妙に表記が違うとか微妙に値がずれているみたいなことがよくあります。
そんなときは、fuzzyjoinパッケージです。
fuzzyjoinには以下の4種類のjoinがあります。
difference_*_join()
: 値の差が許容値以内のものは同じとみなしてjoinstringdist_*_join()
: 文字列距離でjoinregex_*_join()
: 片方のデータの正規表現でもう片方の文字列をマッチさせてjoin(これはたぶん名前から予想するのと違う)distance_*_join()
: 幾何学的な距離でjoingeo_*_join()
: 地理的な距離でjoininterval_*_join()
: 値の範囲でjoingenome_*_join()
:interval_*_join()
の遺伝子版
stringdist_*_join()
はvignetteに詳しいので、ここでは、意外と便利そうなinterval_*_join()
と、ちょっとわかりづらいregex_*_join()
について取り上げます。geo_*_join()
は今だとsfでもっと楽にできるのかな?と思ったけど分からなかった。
interval_*_join()
interval_*_join()
は、値の範囲が重なるデータをjoinしたい、というときに便利です。
x1 <- data.frame(id1 = 1:3, start = c(1, 5, 10), end = c(3, 7, 15)) x2 <- data.frame(id2 = 1:3, start = c(2, 4, 16), end = c(4, 8, 20))
interval_*_join()
を使うには、Bioconductor上にあるIRangeというパッケージが必要です。まずはBioconductor - Installの指示に従ってBioconductorを使う準備をしましょう。biocLite()
を実行すればIRangeパッケージは自動でインストールされるはずです。
準備ができたらさっそくinterval_inner_join()
を使ってみましょう。
library(fuzzyjoin) interval_inner_join(x1, x2) #> Joining by: c("start", "end") #> id1 start.x end.x id2 start.y end.y #> 1 1 1 3 1 2 4 #> 2 2 5 7 2 4 8
1行目は[1, 3]
という区間と[2,4]
という区間、2行目は[2, 5]
という区間と[2, 4]
という区間に重なりがあるためjoinの対象となっています。
これは、数値だけでなく日付型にも使うことができます。上で説明しませんでしたが、by
には区間の始点の列と終点の列を指定します。指定しない場合は、start
という列とend
という列がそれぞれ使われます(なので同名の列がデータにないとエラーになる)。
library(dplyr, warn.conflicts = FALSE) x3 <- mutate_at(x1, vars(-id1), funs(date = Sys.Date() + .)) x4 <- mutate_at(x2, vars(-id2), funs(date = Sys.Date() + .)) interval_inner_join(x3, x4, by = c("start_date", "end_date")) #> id1 start.x end.x start_date.x end_date.x id2 start.y end.y start_date.y #> 1 1 1 3 2017-11-24 2017-11-26 1 2 4 2017-11-25 #> 2 2 5 7 2017-11-28 2017-11-30 2 4 8 2017-11-27 #> end_date.y #> 1 2017-11-27 #> 2 2017-12-01
Interval
にも使えると嬉しいんですが、それはちょっとまた別なのかもしれません。
regex_*_join()
例えば、こういうデータがあるとします。
data <- data.frame( id = c("uri", "u_ribo", "uribo", "hoxo_m", "hoxo-m", "hoxo_m"), venue = c("GitHub", "Twitter", "Qiita", "Twitter", "GitHub", "Qiita"), stringsAsFactors = FALSE )
お分かりのように、uri
とu_ribo
とuribo
というのは同一人物です。ですが、IDに微妙にゆれがあり、このままではうまくjoinすることができません。
そんなとき、あらかじめ正規表現を用意しておけば、その正規表現を使ってデータをjoinすることができる、というのがregex_*_join()
です。
catalog <- data.frame( name = c("uribo", "hoxo-m"), role = c("Chief Globalization Officer", "President"), regex = c("u_?ri(bo)?", "hoxo[_\\-]m"), stringsAsFactors = FALSE ) regex_inner_join(data, catalog, by = c(id = "regex")) #> id venue name role regex #> 1 uri GitHub uribo Chief Globalization Officer u_?ri(bo)? #> 2 u_ribo Twitter uribo Chief Globalization Officer u_?ri(bo)? #> 3 uribo Qiita uribo Chief Globalization Officer u_?ri(bo)? #> 4 hoxo_m Twitter hoxo-m President hoxo[_\\-]m #> 5 hoxo-m GitHub hoxo-m President hoxo[_\\-]m #> 6 hoxo_m Qiita hoxo-m President hoxo[_\\-]m
あらかじめ正規表現の列を作っておくというのがやや面倒ですが、まあ手軽に使うには便利そうですね(ちゃんとやるなら、case_when()
なりrecode()
なりであらかじめ共通のIDに変換してからやるべきだと思います)。
まとめ
stringdist_*_join()
しか知らなかったんですが、意外と便利っぽいです。