あらまし

fuzzyjoinパッケージについて知ったのは r-wakalang で質問させていただいたことが縁になります。yutannihilation=サンにパッケージを教えていただいたのですが、本パッケージ、なぜか使ってみた系記事すらあまり引っかからないとのこと。これも縁で私が書こうと思っていたのですが、多忙から曖昧な関係にしていたのをこの気に成仏させようというのが今回の記事の背景です。本記事は Rアドベントカレンダー 23日目となります。

fuzzyjoin package

fuzzyjoinパッケージはR前処理の心の友であるdplyrの機能を補うパッケージです。 CRANに挙がっている一行パッケージ内容紹介には “Join Tables Together on Inexact Matching” とあり、完全には一致しないIDをキーにして、データをjoinするためのパッケージだとわかります。

完全に一致しないIDとはどのようなものでしょうか。例えば誤記などがあるでしょう。fuzzyjoinパッケージには代表的なスペルミスデータを集めたmisspellingsデータが含まれています (abboutとaboutなど)。このようなスペルミスをkeyにしてjoinすると、考えているのとは違うデータしか得られないことでしょう。 fuzzyjoinパッケージの関数には上記のようなミススペルのためだけではなく、目的に応じて以下のような関数が用意されています。

interval_hoge_join, regex_hoge_joinについてはyutannihilation=サンが以前の記事、 fuzzyjoinパッケージでいい感じにjoin で例をあげてくれているので、これらの紹介についてはリンクを張って済ませます。

hoge部分にはdplyrでよく使うようなinner, left, right, full, semi, antiが入ります。

見てるだけじゃ全くわからんのでそれぞれ具体的に見ていきましょう。 余裕がなかったのでパッケージのexamplesばっかりで申し訳ないです。。。

実例

difference_hoge_join

何はともあれパッケージを読み込んでおきましょう。未インストールの人はinstall.packages()しておきましょう。

library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(DT)
library(fuzzyjoin)
library(sessioninfo)

今回はexampleがinner_joinで統一されているためinner_joinばかりを例にあげますが、必要なjoinに適宜置き換えてください。はじめに紹介するのはdifference_inner_joinです。以下のように、例にはみんな大好きirisデータを使います。

data(iris)
datatable(iris)

今回はSepal.Lengthkeyにして、新しい変数Typejoinすることを考えます。TypeデータはSepal.Lengthに対応したデータですが、Typeデータを格納しているデータフレームに格納されているSepal.Lengthは以下のように大雑把な値でしか得られていないとします。

sepal_lengths <- data_frame(Sepal.Length = c(5, 6, 7), Type = 1:3)
sepal_lengths
## # A tibble: 3 x 2
##   Sepal.Length  Type
##          <dbl> <int>
## 1            5     1
## 2            6     2
## 3            7     3

通常のjoinではこのようなデータの結合には対応できませんが、ここで “difference_hoge_join: 数値データ対象。値にちょっとしたブレがある時に使う。” の出番です。

通常のdplyr::inner_join()と全く同じノリで書いて行けるので違和感がなくていいですね。 max_distには許容できる値のブレの大きさを記述します。今回は0.5とします。適当なところを切り出して表示してみましょう。

temp <- iris %>% difference_inner_join(sepal_lengths, max_dist = .5)
## Joining by: "Sepal.Length"
datatable(temp[50:69, ])

ブレの大きさの範囲内に入った値でTypejoinされていることがわかります。大まかには他の関数もこのノリで使っていけます。

distance_hoge_join

別の指標に基づきデータをつなぐこともできます。次に示すのは “distance_hoge_join:複数列の距離に基づきデータをつなぎたい。” です。例はirisが続投です。

data(iris)
datatable(iris)

さっきのsepal_lengthsに似ていますが、データが新規のtypeではなくSepal.Widthになっている点に注目です。

sepal_lengths <- data_frame(Sepal.Length = c(5, 6, 7),
                            Sepal.Width = 1:3)

datatable(sepal_lengths)

距離をどのような指標で計算するかについてはmethodで指定することになっています。デフォルト設定はeuclideanです。結果を見るに、2変数のユークリッド距離が近い組み合わせでjoinされるようです。

temp <- iris %>% distance_inner_join(sepal_lengths, max_dist = 2)
## Joining by: c("Sepal.Length", "Sepal.Width")
datatable(temp[50:59, ])

stringdist_hoge_join

次に “stringdist_hoge_join: 文字列をコサイン距離などに基づいて操作したい。” を使ってみましょう。データはggplot2パッケージに含まれているdiamondsデータを利用します。

library(ggplot2)
data(diamonds)

ダイアモンドのカットについて新しくtypeというデータを作り、素のdiamondsデータにマージすることを考えます。しかし、diamondsデータのkeyに使う変数cut内の要素名がいまいち思い出せず曖昧になってしまったので新しいデータのkeyにはだいたいこんな名前 (approximate_name) という名前をつけておき、要素名も雑に入れておきます。雑さ加減がmaxなのでPremiumっぽい要素名が2つあったり、VeryGoodが2つある上にtypeも別だったりとハチャメチャですがあまり気にしないことにします。

d <- data_frame(approximate_name = c("Idea", "Premiums", "Premioom",
                                     "VeryGood", "VeryGood", "Faiir"),
                type = 1:6)
datatable(d)

で、これを普通のinner_joinで処理しようとしても当然だめなわけです。曖昧ですしね。

diamonds %>% inner_join(d, by = c(cut = "approximate_name"))
## Warning: Column `cut`/`approximate_name` joining factor and character
## vector, coercing into character vector
## # A tibble: 0 x 11
## # ... with 11 variables: carat <dbl>, cut <chr>, color <ord>,
## #   clarity <ord>, depth <dbl>, table <dbl>, price <int>, x <dbl>,
## #   y <dbl>, z <dbl>, type <int>

fuzzyjoinは曖昧さを許容します。文字列の距離をどのような指標で計算するかについてはmethodで指定することになっています。デフォルト設定が何なのかパッケージのexsamplesには乗っていませんでしたが、 stringdist packagestringdist-methodsを見ろとあったのでその設定どおりだとするとoptimal string alignment (OSA) ではないかと思われます。

temp <- diamonds %>% stringdist_inner_join(d, by = c(cut = "approximate_name"))
datatable(temp[, c(1:4, 11:12)])
## Warning in instance$preRenderHook(instance): It seems your data is too
## big for client-side DataTables. You may consider server-side processing:
## https://rstudio.github.io/DT/server.html