1 두-테이블 동사 1 2 3

매일 데이터를 분석하는 현업작업에서 단일 데이터프레임을 갖고 작업하는 경우는 매우 드물다. 보통 데이터 통계분석에는 수많은 테이블이 동원된다. 이런 경우 여러 형태를 갖는 테이블을 자유로이 다룰 수 있는 능력도 필수적이다. dplyr에는 두 테이블 동사로 다음과 같은 세가지 유형을 지원한다.

  • Mutating Join: 두 테이블을 결합할 때, 두 테이블 간에 행이 매칭되는 경우 첫번째 테이블에 새로운 변수를 추가.
  • Filtering Join: 다른 테이블에 관측점이 매칭되냐 매칭되지 않냐에 근거하여 해당 테이블의 관측점을 필터링함.
  • 집합연산: 마치 집합원소처럼 데이터셋의 관측점을 집합연산으로 조합.

1.1 중요개념

Base R에는 동일한 개념이 필요하여 merge() 함수로 기능을 제공하였지만, dplyr 두-테이블 동사를 활용해서도 동일한 기능을 구현하는데 문제가 없다.

mergedplyr 두-테이블 동사

기존 Base R에서 많이 사용된 merge 함수와 비교하여 dplyr 두-테이블 동사를 사용하는 경우 장점은 다음과 같다.

  • 항상 행순서를 보존한다.
  • 구문이 직관적이다.
  • 관계형 데이터베이스, 스파크에도 적용할 수 있다.

테이블 두개를 결합할 경우 생각보다 다양한 조합의 수가 존재한다. 두 테이블을 병합할 때, 먼저 두 테이블 모두 공통된 변수가 존재해야 된다. 이를 키라고 부르는데, 두 테이블 모두 존재하기 때문에 명칭을 달리할 필요가 있다. 따라서, 기준 테이블의 키를 기본키(Primary key), 병합할 테이블의 키를 외래키(Foreign key)라고 부른다. 기본키와 외래키를 꼭 한 필드로 고정할 필요는 없고, 다수 필드를 묶어 기본키와 외래키를 정의하는 것도 가능하다.

2 변수(칼럼): mutate join 유형 병합(join)

데이블 두개를 병합하는데 사용되는 기본 병합(join) 동사는 4개가 있다: left_join(), right_join(), inner_join(), full_join() 이런 유형의 두 데이터프레임 동사는 데이터프레임의 형태가 변형(mutate)된다는 특징이 있어 이를 mutate join 동사라고 한다.

기본 병합

먼저 superheroes, publishers 데이터프레임을 생성한다.

2.1 inner_join()

inner_join()은 두 테이블 모두에 존재하는 행이 있는 경우 이를 병합하여 새로운 테이블을 생성시킨다.

inner_join(x,y) 를 수행하면 기준 테이블의 모든 행을 반환시키는데 조건은 상대 테이블에 매칭되는 행이 있는 경우다. 두 테이블 사이 다수가 매칭되면, 모든 매칭 조합이 반환된다. 대표적인 mutating join 사례다.

superheroes

name alignment gender publisher
Magneto bad male Marvel
Storm good female Marvel
Mystique bad female Marvel
Batman good male DC
Joker bad male DC
Catwoman bad female DC
Hellboy good male Dark Horse Comics

publishers

publisher yr_founded
DC 1934
Marvel 1939
Image 1992

semi-join(x = superheroes, y = publishers)

name alignment gender publisher yr_founded
Magneto bad male Marvel 1939
Storm good female Marvel 1939
Mystique bad female Marvel 1939
Batman good male DC 1934
Joker bad male DC 1934
Catwoman bad female DC 1934

inner join

2.2 left_join()

# A tibble: 7 x 5
  name     alignment gender publisher         yr_founded
  <chr>    <chr>     <chr>  <chr>                  <dbl>
1 Magneto  bad       male   Marvel                  1939
2 Storm    good      female Marvel                  1939
3 Mystique bad       female Marvel                  1939
4 Batman   good      male   DC                      1934
5 Joker    bad       male   DC                      1934
6 Catwoman bad       female DC                      1934
7 Hellboy  good      male   Dark Horse Comics         NA

superheroes

name alignment gender publisher
Magneto bad male Marvel
Storm good female Marvel
Mystique bad female Marvel
Batman good male DC
Joker bad male DC
Catwoman bad female DC
Hellboy good male Dark Horse Comics

publishers

publisher yr_founded
DC 1934
Marvel 1939
Image 1992

left_join(x = superheroes, y = publishers)

name alignment gender publisher yr_founded
Magneto bad male Marvel 1939
Storm good female Marvel 1939
Mystique bad female Marvel 1939
Batman good male DC 1934
Joker bad male DC 1934
Catwoman bad female DC 1934
Hellboy good male Dark Horse Comics NA

left join

2.3 right_join()

# A tibble: 7 x 5
  name     alignment gender publisher yr_founded
  <chr>    <chr>     <chr>  <chr>          <dbl>
1 Batman   good      male   DC              1934
2 Joker    bad       male   DC              1934
3 Catwoman bad       female DC              1934
4 Magneto  bad       male   Marvel          1939
5 Storm    good      female Marvel          1939
6 Mystique bad       female Marvel          1939
7 <NA>     <NA>      <NA>   Image           1992

superheroes

name alignment gender publisher
Magneto bad male Marvel
Storm good female Marvel
Mystique bad female Marvel
Batman good male DC
Joker bad male DC
Catwoman bad female DC
Hellboy good male Dark Horse Comics

publishers

publisher yr_founded
DC 1934
Marvel 1939
Image 1992

right_join(x = superheroes, y = publishers)

name alignment gender publisher yr_founded
Batman good male DC 1934
Joker bad male DC 1934
Catwoman bad female DC 1934
Magneto bad male Marvel 1939
Storm good female Marvel 1939
Mystique bad female Marvel 1939
NA NA NA Image 1992

right join

2.4 full_join()

# A tibble: 8 x 5
  name     alignment gender publisher         yr_founded
  <chr>    <chr>     <chr>  <chr>                  <dbl>
1 Magneto  bad       male   Marvel                  1939
2 Storm    good      female Marvel                  1939
3 Mystique bad       female Marvel                  1939
4 Batman   good      male   DC                      1934
5 Joker    bad       male   DC                      1934
6 Catwoman bad       female DC                      1934
7 Hellboy  good      male   Dark Horse Comics         NA
8 <NA>     <NA>      <NA>   Image                   1992

superheroes

name alignment gender publisher
Magneto bad male Marvel
Storm good female Marvel
Mystique bad female Marvel
Batman good male DC
Joker bad male DC
Catwoman bad female DC
Hellboy good male Dark Horse Comics

publishers

publisher yr_founded
DC 1934
Marvel 1939
Image 1992

full_join(x = superheroes, y = publishers)

name alignment gender publisher yr_founded
Magneto bad male Marvel 1939
Storm good female Marvel 1939
Mystique bad female Marvel 1939
Batman good male DC 1934
Joker bad male DC 1934
Catwoman bad female DC 1934
Hellboy good male Dark Horse Comics NA
NA NA NA Image 1992

full join

3 변수(칼럼): filtering join 유형 병합(join)

mutate join과 달리 filtering join은 해당 데이터프레임에 새로운 것이 추가되거나 해서 변형되는 것이 아니라 다른 데이터프레임을 참조해서 필터링된다는 점에서 차이가 난다.

filtering join

3.1 semi_join()

semi_join()은 두 데이터프레임에 공통된 것을 필터로 찾아서 반환시키는 역할을 수행한다.

# A tibble: 6 x 4
  name     alignment gender publisher
  <chr>    <chr>     <chr>  <chr>    
1 Magneto  bad       male   Marvel   
2 Storm    good      female Marvel   
3 Mystique bad       female Marvel   
4 Batman   good      male   DC       
5 Joker    bad       male   DC       
6 Catwoman bad       female DC       

superheroes

name alignment gender publisher
Magneto bad male Marvel
Storm good female Marvel
Mystique bad female Marvel
Batman good male DC
Joker bad male DC
Catwoman bad female DC
Hellboy good male Dark Horse Comics

publishers

publisher yr_founded
DC 1934
Marvel 1939
Image 1992

semi-join(x = superheroes, y = publishers)

name alignment gender publisher
Magneto bad male Marvel
Storm good female Marvel
Mystique bad female Marvel
Batman good male DC
Joker bad male DC
Catwoman bad female DC

semi join

3.2 anti_join()

anti_join()은 두 데이터프레임에 공통된 것을 필터로 찾아서 semi_join()과 정반대 역할을 수행한다. 즉, anti_join(x, y)은 필터링 조인으로 y 데이터프레임에 매칭되지 않지만 x 데이터프레임에 있는 칼럼만 반환한다.

# A tibble: 1 x 2
  publisher yr_founded
  <chr>          <dbl>
1 Image           1992

publishers

publisher yr_founded
DC 1934
Marvel 1939
Image 1992

superheroes

name alignment gender publisher
Magneto bad male Marvel
Storm good female Marvel
Mystique bad female Marvel
Batman good male DC
Joker bad male DC
Catwoman bad female DC
Hellboy good male Dark Horse Comics

anti_join(x = publishers, y = superheroes)

publisher yr_founded
Image 1992

anti join

4 관측점(행): 집합 연산자

집합 연산자

변수(칼럼) 두 데이터프레임 동사와 비교하여 관측점(행) 두 데이터프레임 동사는 집합 연산자로 알려져 있으며 변수명이 동일한 상태에서 관측점에 대한 다양한 연산작업을 수행할 수 있고, 교집합(intersect), 차집합(setdiff), 합집합(union) 연산자가 많이 사용된다.

먼저, x_df, y_df 두 데이터프레임을 정의해보자.

# A tibble: 3 x 3
  A     B         C
  <chr> <chr> <dbl>
1 a     t         1
2 b     u         2
3 c     v         3
# A tibble: 2 x 3
  A     B         C
  <chr> <chr> <dbl>
1 c     v         3
2 d     w         4

4.1 교집합(intersect)

교집합(intersect) 연산자는 한쪽 데이터프레임에서 다른 쪽 데이터프레임과 공통된 관측점을 추출할 때 사용된다.

교집합(intersect)

데이터프레임 x_df에서 데이터프레임 y_df 차집합(intersect) 연산을 가하게 되면 공통된 행만 추출하게 된다.

# A tibble: 1 x 3
  A     B         C
  <chr> <chr> <dbl>
1 c     v         3

4.2 차집합(setdiff)

차집합(setdiff)은 첫번재 데이터프레임과 두번째 데이터프레임 간의 공통된 부분을 제거하고 차이가 나는 관측점만 남기게 된다.

차집합(setdiff)

# A tibble: 2 x 3
  A     B         C
  <chr> <chr> <dbl>
1 a     t         1
2 b     u         2

4.3 합집합(union)

합집합(union)은 중복된 관측점은 제외시키고 x_dfy_df 둘 중 한 데이터프레임에 있는 관측점을 추출시키게 된다.

합집합(union)

# A tibble: 2 x 3
  A     B         C
  <chr> <chr> <dbl>
1 a     t         1
2 b     u         2

5 R과 SQL 비교

두 데이터프레임 동사와 SQL 구문을 비교하면 다음과 같다.

R SQL
inner_join() SELECT * FROM x JOIN y ON x.a = y.a
left_join() SELECT * FROM x LEFT JOIN y ON x.a = y.a
right_join() SELECT * FROM x RIGHT JOIN y ON x.a = y.a
full_join() SELECT * FROM x FULL JOIN y ON x.a = y.a
semi_join() SELECT * FROM x WHERE EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
anti_join() SELECT * FROM x WHERE NOT EXISTS (SELECT 1 FROM y WHERE x.a = y.a)
intersect(x,y) SELECT * FROM x INTERSECT SELECT * FROM y
union(x, y) SELECT * FROM x UNION SELECT * FROM y
setdiff(x, y) SELECT * FROM x EXCEPT SELECT * FROM y

6 사례: 레고 4

LEGO Database - The LEGO Parts/Sets/Colors and Inventories of every official LEGO set 캐글 데이터셋은 BOM(bill of material)을 이해할 수 있는 매우 좋은 데이터일 뿐만 아니라 데이터 정규화가 잘 되어 있어 앞서 학습한 dplyr 두 데이터프레임 동사를 연마하기도 좋다. 그러기 전에 먼저 데이터에 대한 관계도를 살표보자.

6.1 아이언맨과 슈퍼맨 BOM 비교

먼저, 아이언맨 가장 오래된 레고상품과 가장 최신 레고상품 BOM을 비교해보자. 이를 위해서 먼저 아이언맨을 sets에서 찾아낸다.

# A tibble: 2 x 5
  set_num name      year theme_id num_parts
  <chr>   <chr>    <dbl>    <dbl>     <dbl>
1 4526-1  Batman    2012      492        40
2 4529-1  Iron Man  2012      492        44

마블을 대표하는 아이언맨과 DC코믹스를 대표하는 배트맨의 부품을 상호비교해보자.

ironman superman

6.2 부품 공용화

해당 셋트에서 부품을 찾아내기 위해서는 inventories를 통해 inventory_parts, 그리고 마지막으로 parts 테이블을 합쳐야 해당 세트를 구성하는 부품을 찾아낼 수 있다.

  • 요약표
    • 배트맨 총 부품수: 20
    • 아이언맨 총 부품수: 27
    • 공통부품수: 10

즉, intersect() 함수를 통해 공통으로 사용되는 부품을 찾아내서 부품공용화를 이뤄낼 수 있다.

7 자기 자신과 병합(self-join)

자기 자신과 병합(self-join)을 하게 되면 위계(hierarchy)를 갖는 데이터를 쭉 펼칠 수 있는 형태 즉, 부모-자식(parent-child) 관계가 한 행에 위치할 수 있게 된다.

반지의 제왕(The Lord of the Rings)은 톨킨의 3부작 소설로 구성되어 있다. 데이터를 보게 되면 parent_id가 있고 id를 통해서 부모-자식 관계를 확인할 수 있다.

# A tibble: 4 x 3
     id name                       parent_id
  <dbl> <chr>                          <dbl>
1   566 The Lord of the Rings            561
2   567 The Fellowship of the Ring       566
3   568 The Two Towers                   566
4   569 The Return of the King           566

이와 같은 위계가 있는 데이터를 self join 즉, 자기자신과 병합하게 되면 데이터를 한 행에 부모-자식 관계를 명시적으로 나타낼 수 있게 된다.

# A tibble: 3 x 5
     id name_child             parent_id name_parent       parent_id_parent
  <dbl> <chr>                      <dbl> <chr>                        <dbl>
1   567 The Fellowship of the~       566 The Lord of the ~              561
2   568 The Two Towers               566 The Lord of the ~              561
3   569 The Return of the King       566 The Lord of the ~              561