dplyrが1.1.0になった。これにより、Rで非等価結合 - もうカツ丼はいいよなで少し触れていたdplyrによる非等価結合が正式にサポートされた。
記事で取り上げていた例なら次のように簡潔な記述で結合できるようになった。
# データの準備 library(dplyr) car <- tibble( car_model = c("CarA", "CarB", "CarC"), car_price = c(20000, 30000, 50000) ) boat <- tibble( boat_model = c("Boat1", "Boat2", "Boat3"), boat_price = c(10000, 40000, 60000) ) # car_price > boat_priceを条件とした非等価結合 car %>% left_join( boat, by = join_by(car_price > boat_price) ) ## # A tibble: 4 × 4 ## car_model car_price boat_model boat_price ## <chr> <dbl> <chr> <dbl> ## 1 CarA 20000 Boat1 10000 ## 2 CarB 30000 Boat1 10000 ## 3 CarC 50000 Boat1 10000 ## 4 CarC 50000 Boat2 40000
また、上記のような単純な不等号を使った非等価結合以外にrolling join、overlap joinができるようになっている。これによっておそらく時系列データなどがかなり扱いやすくなるのではないかと思われる。
Rolling join
これは不等号を使った非等価結合であるが、結合の結果を最も「近い」1件に限定する(タイが存在すると1件になるとは限らないが、その場合の挙動は後述)。
例のデータならCarC
に対応する部分の結合結果が1行に減る。
car %>% left_join( boat, by = join_by(closest(car_price > boat_price)) # 条件をclosest()で囲む ) ## # A tibble: 3 × 4 ## car_model car_price boat_model boat_price ## <chr> <dbl> <chr> <dbl> ## 1 CarA 20000 Boat1 10000 ## 2 CarB 30000 Boat1 10000 ## 3 CarC 50000 Boat2 40000
Overlap join
結合条件に範囲を使うもの。連続値を離散値に変換するような場合に便利だろう。
price_class <- tibble( class = c("A", "B", "C"), lower = c(10000, 20000, 50000), upper = c(19999, 49999, 100000) ) car %>% left_join( price_class, by = join_by(between(car_price, lower, upper)) # 条件をbetween()で記述する ) ## # A tibble: 3 × 5 ## car_model car_price class lower upper ## <chr> <dbl> <chr> <dbl> <dbl> ## 1 CarA 20000 B 20000 49999 ## 2 CarB 30000 B 20000 49999 ## 3 CarC 50000 C 50000 100000
Overlap joinは条件に指定した変数の値次第では複数の行にマッチしてしまい、行が増える可能性がある。それが意図しないものだった場合は困るので、複数行にマッチするケースが存在したらエラーを返すようにすることもできる。
price_class <- tibble( class = c("A", "B", "C"), lower = c(10000, 20000, 50000), upper = c(20000, 50000, 100000) # CarAとCarCが複数のclassに属する ) car %>% left_join( price_class, by = join_by(between(car_price, lower, upper)), multiple = "error" # 複数のマッチがある場合にエラーを返すようにする。 ) ## Error in `left_join()`: ## ! Each row in `x` must match at most 1 row in `y`. ## ℹ Row 1 of `x` matches multiple rows.
複数行マッチ時のwarning
等価結合の場合とrolling joinの場合、左辺のテーブルの特定の行に右辺のテーブルの複数行がマッチして結果の行が増えてしまうことがあり得るが、それは一般的に意図しないものであろう、ということでそういう場合に警告が出るようになった。
df1 <- tibble(id = c(1, 2, 3), x = c("a", "b", "c")) df2 <- tibble(id = c(1, 1, 2, 3), y = c("w", "x", "y", "z")) left_join(df1, df2, by = "id") ## Warning in left_join(df1, df2, by = "id"): Each row in `x` is expected to match at most 1 row in `y`. ## ℹ Row 1 of `x` matches multiple rows. ## ℹ If multiple matches are expected, set `multiple = "all"` to silence this ## warning. ## # A tibble: 4 × 3 ## id x y ## <dbl> <chr> <chr> ## 1 1 a w ## 2 1 a x ## 3 2 b y ## 4 3 c z
ちょっと気を利かせすぎな気もするが、実際このようなケースは意図せず生じた場合に検出が難しく間違いにつながることがしばしばある。私も何度かハマった経験がある。なのでデフォルトで警告が出るくらいのが良いのだろう。
警告は先程も使用した引数multiple
に"all"
を指定することでoffにできる。
left_join(df1, df2, by = "id", multiple = "all")
Cross join (交差結合)
他に結合関係の変更として、今まではleft_join(x, y, by = character())
というあまり直感的ではない書き方をする必要があった交差結合がcross_join()
によってできるようになった(by = character()
を指定して交差結合を行うと、cross_join()
を使うよう警告が表示される)。
cross_join(car, boat) ## # A tibble: 9 × 4 ## car_model car_price boat_model boat_price ## <chr> <dbl> <chr> <dbl> ## 1 CarA 20000 Boat1 10000 ## 2 CarA 20000 Boat2 40000 ## 3 CarA 20000 Boat3 60000 ## 4 CarB 30000 Boat1 10000 ## 5 CarB 30000 Boat2 40000 ## 6 CarB 30000 Boat3 60000 ## 7 CarC 50000 Boat1 10000 ## 8 CarC 50000 Boat2 40000 ## 9 CarC 50000 Boat3 60000
その他
- dplyr 1.1.0での変更点はおそらくこの記事dplyr 1.1.0 is coming soonが現状だとよくまとまっている。一般的には
summarise()
で.by
による一時的なグループ化ができるようになったことあたりが役立つのではないかと思う。 - dbplyr 2.3.0においてはまだ
join_by()
はサポートされていない。Pull request(Support `join_by()` by mgirlich · Pull Request #1074 · tidyverse/dbplyr · GitHub)が上がっているのでそのうち対応されるだろうと思う。備えよう。