joinでテーブル結合(2).R

要約

・left_join()だけでも覚えて帰ってください
・一列ずつ処理しよう
・突き当らない行も確認しようね

データセット

> x
 NO SEX AGE ShoCo Kibo
1  1  男  26     1    2
2  2  男  28     3    6
3  3  女  27     4    3
4  4  男  25     5    1
5  5  女  24     5    4
6  6  女  29     3    1

> y
 ShoCo ShoMei
1     1 総務部
2     2 経理部
3     3 営業部
4     4 設計部
5     5 生産部

 xは20代社員を対象にした転属希望調査の結果で、NO・性別(SEX)・年齢(AGE)・現在の所属部署コード(ShoCo)・希望する転属先の部署コード(Kibo)で構成されている。
 一方、yは部署マスタで、所属部署コード(ShoCo)・部署名(ShoMei)で構成されている。
 xを左テーブル、yを右テーブルとして、所属コードをキーとしてjoinして、各join方法による出力の違いを実際に見てみる。
 ここでは簡便のために両テーブルに同じ名前のShoCo列を作成し、引数byによる指定は省略した。キーとなる所属コードが別の名前の列に記載されている場合には、前回記事のやり方に従って指定すればよい。
 また、xには部署マスタにない部署コードである6をわざと入れてみた。6=その他、と想定してもいいし、退職希望とみなしてもいい。
 なんでそんな設定にしたんだとか聞かない。

left_join()とright_join()

> left_join(x, y)
Joining, by = "ShoCo"
 NO SEX AGE ShoCo Kibo ShoMei
1  126     1    2 総務部
2  228     3    6 営業部
3  327     4    3 設計部
4  425     5    1 生産部
5  524     5    4 生産部
6  629     3    1 営業部

> right_join(x, y)
Joining, by = "ShoCo"
 NO  SEX AGE ShoCo Kibo ShoMei
1  126     1    2 総務部
2  228     3    6 営業部
3  327     4    3 設計部
4  425     5    1 生産部
5  524     5    4 生産部
6  629     3    1 営業部
7 NA <NA>  NA     2   NA 経理部

 どちらも出力の最初に、どの列を用いて結合したかが by ="ShoCo"として出てきている。
 xのShoCo列は現在の所属コードを示しており、現在の所属コードのみではあるが全ての値が部署マスタyを用いて部署名に変換され、ShoMei列に格納されている。
 一方、right_join()では、右テーブルにはあって左テーブルにはないShoCo=2については7行目に列が作成されて、左テーブルにあった列に欠損値(NA)が格納されている。
 left/right_joinでは左/右テーブルを基準にするので、もう一方のテーブルに値がなければそこは欠損値を格納して処理する。
 例えば、left_join()で左テーブルのKiboについても部署マスタで部署名に変換してみる。このときKibo列には右テーブルにない6という値が存在している。

> left_join(x, y, by = c("Kibo" = "ShoCo"))
 NO SEX AGE ShoCo Kibo ShoMei
1  126     1    2 経理部
2  228     3    6   <NA>
3  327     4    3 営業部
4  425     5    1 総務部
5  524     5    4 設計部
6  629     3    1 総務部

 左テーブルが基準となるので、左テーブルにある6の値は残しつつ、右テーブルには6がないので欠損値が格納されている。

 なお、同時には左テーブルのShoCo列とKibo列を処理することはできない。例えば、パイプ処理を使って順次処理することになるが、親切にもShoCoを処理した結果はShoMei.x列、Kibo列の処理結果はShoMei.yと列名が一意になるよう自動で命名してくれている。

#同時にやろうとするとエラー
> left_join(x, y, by = c("ShoCo" = "ShoCo", "Kibo" = "ShoCo"))
エラー: Join columns must be unique.
x Problem at position 2.
Run `rlang::last_error()` to see where the error occurred.
> 

#順次処理で回避
> left_join(x, y, by = "ShoCo") %>% left_join(., y, c("Kibo" = "ShoCo"))
 NO SEX AGE ShoCo Kibo ShoMei.x ShoMei.y
1  126     1    2   総務部   経理部
2  228     3    6   営業部     <NA>
3  327     4    3   設計部   営業部
4  425     5    1   生産部   総務部
5  524     5    4   生産部   設計部
6  629     3    1   営業部   総務部 

  ここまでleft/right_join()と書いたのだけれど、正直left_join()だけ覚えればよいと思う。なぜなら、left_join(x, y) とright_join(y, x)は同じだからだ。長方形の面積は縦×横でも横×縦でも同じでしょ?

> right_join(y, x)
Joining, by = "ShoCo"
 ShoCo ShoMei NO SEX AGE Kibo
1     1 総務部  126    2
2     3 営業部  228    6
3     3 営業部  629    1
4     4 設計部  327    3
5     5 生産部  425    1
6     5 生産部  524    4

 厳密にはそんなことはない。左テーブルにある列から順番に現れるので、left_join(x, y)とでは列の順番が異なって出力される。それに引きずられてNO列に格納された値の順序も変わっている。
 ただし、優先されるテーブルは同じになるので、この場合には先ほどのright_join(x, y)では現れた、欠損値が存在する行というのが存在しない。
 でもまあ、left_join()だけで十分だよねえ。

inner_join()

 inner_join()では、left/right_join()と異なり、xとyで突き当らない行(欠損値があるものとして処理される行)は自動的に省略される。
 キー列をShoCoにした場合には、突き当らないキーがないleft_join(x, y)と結果は同じになる。
 一方、左のキー列をKiboにした場合には、left_join()では突き当らないKibo=6がある列(男・28歳)が省略されていることが分かる。

 比較のために、left_join()を使って同様の結果を出力させてみた。join結果としてShoMeiがNAにならないもの(is.na() == F)でfilter()するということだ。裏を返せば、filter(is.na() == T)とすることで突き当らなかった行の確認ができるということになる。これは(当然ながら)inner_join()ではできないことだ。

> inner_join(x, y)
Joining, by = "ShoCo"
 NO SEX AGE ShoCo Kibo ShoMei
1  126     1    2 総務部
2  228     3    6 営業部
3  327     4    3 設計部
4  425     5    1 生産部
5  524     5    4 生産部
6  629     3    1 営業部

> inner_join(x, y, by = c("Kibo" = "ShoCo"))
 NO SEX AGE ShoCo Kibo ShoMei
1  126     1    2 経理部
2  327     4    3 営業部
3  425     5    1 総務部
4  524     5    4 設計部
5  629     3    1 総務部

> left_join(x, y, by = c("Kibo" = "ShoCo")) %>% filter(is.na(ShoMei) == F)
 NO SEX AGE ShoCo Kibo ShoMei
1  126     1    2 経理部
2  327     4    3 営業部
3  425     5    1 総務部
4  524     5    4 設計部
5  629     3    1 総務部

> left_join(x, y, by = c("Kibo" = "ShoCo")) %>% filter(is.na(ShoMei) == T)
 NO SEX AGE ShoCo Kibo ShoMei
1  228     3    6   <NA>

outer_join()

 簡単に言えば、左も右も全残しだ。
 左テーブルはKibo列をキーにすると、男・28歳のKibo=6が突き当らないのでShoMeiに欠損値が入る。
 一方、右テーブルのShoCo=5は左テーブルのKibo欄に値が見当たらないので突き当らない。つまり生産部への転属希望はないということだ。(注:生産部に対する悪意はありません。)
 このように突き当らない値をまとめて確認したりするのには便利かもしれない。

> full_join(x, y, by = c("Kibo" = "ShoCo"))
 NO  SEX AGE ShoCo Kibo ShoMei
1  126     1    2 経理部
2  228     3    6   <NA>
3  327     4    3 営業部
4  425     5    1 総務部
5  524     5    4 設計部
6  629     3    1 総務部
7 NA <NA>  NA    NA    5 生産部

まとめ

 joinのいいところは、mutate()などの列の結合と違って、予め行数をそろえておかなくても良いところだ。
 一方、それぞれのテーブルが不完全でも何となく結合できてしまうので、上述のように、あえて突き当らない値を出力して確認することも覚えておくと役立ちそうだ。

今日はここまで。

この記事が気に入ったらサポートをしてみませんか?