iBetter Books
수정

Ch 07. 데이터 합치기 (join, bind)

실무에서 분석에 필요한 정보가 하나의 파일에 모두 들어있는 경우는 드뭅니다. 고객 정보는 한 테이블에, 주문 내역은 다른 테이블에, 제품 정보는 또 다른 테이블에 나뉘어 있습니다. 이 테이블들을 연결하는 것이 join입니다.

실습 데이터 준비

직원과 부서 정보를 가진 두 개의 tibble을 만듭니다.

library(tidyverse)

employees <- tibble(
  emp_id   = c(1, 2, 3, 4, 5),
  name     = c("김철수", "이영희", "박민준", "정수진", "최동현"),
  dept_id  = c(10, 20, 10, 30, 20),
  salary   = c(3500, 4200, 3100, 5000, 3800)
)

departments <- tibble(
  dept_id   = c(10, 20, 30, 40),
  dept_name = c("개발팀", "영업팀", "인사팀", "마케팅팀")
)

employees
departments
# A tibble: 5 × 4
  emp_id name    dept_id salary
   <dbl> <chr>     <dbl>  <dbl>
1      1 김철수       10   3500
2      2 이영희       20   4200
3      3 박민준       10   3100
4      4 정수진       30   5000
5      5 최동현       20   3800

# A tibble: 4 × 2
  dept_id dept_name
    <dbl> <chr>    
1      10 개발팀   
2      20 영업팀   
3      30 인사팀   
4      40 마케팅팀 

departments에는 dept_id = 40인 마케팅팀이 있지만 employees에는 마케팅팀 직원이 없습니다. 이 차이가 join 종류에 따라 어떻게 결과에 영향을 주는지 살펴봅니다.

left_join(): 왼쪽 테이블 기준

가장 많이 쓰는 join입니다. 왼쪽 테이블의 모든 행을 유지하고, 오른쪽 테이블에서 일치하는 행을 붙입니다. 일치하는 행이 없으면 NA로 채웁니다.

employees %>% left_join(departments, by = "dept_id")
# A tibble: 5 × 5
  emp_id name    dept_id salary dept_name
   <dbl> <chr>     <dbl>  <dbl> <chr>    
1      1 김철수       10   3500 개발팀   
2      2 이영희       20   4200 영업팀   
3      3 박민준       10   3100 개발팀   
4      4 정수진       30   5000 인사팀   
5      5 최동현       20   3800 영업팀   

직원 5명이 모두 유지되고, 각 직원의 부서 이름이 추가되었습니다. 마케팅팀은 직원이 없으니 결과에 나타나지 않습니다.

inner_join(): 양쪽 모두 있는 것만

두 테이블 모두에 일치하는 행만 결과에 포함됩니다.

# 부서가 없는 직원 추가
employees_extra <- employees %>%
  add_row(emp_id = 6, name = "윤서연", dept_id = 99, salary = 3300)

employees_extra %>% inner_join(departments, by = "dept_id")
# A tibble: 5 × 5
  emp_id name    dept_id salary dept_name
   <dbl> <chr>     <dbl>  <dbl> <chr>    
1      1 김철수       10   3500 개발팀   
2      2 이영희       20   4200 영업팀   
3      3 박민준       10   3100 개발팀   
4      4 정수진       30   5000 인사팀   
5      5 최동현       20   3800 영업팀   

dept_id = 99인 윤서연은 부서 정보가 없어 결과에서 제외됩니다.

full_join(): 양쪽 모두 유지

양쪽 테이블의 모든 행을 결합합니다. 일치하지 않는 부분은 NA로 채웁니다.

employees %>% full_join(departments, by = "dept_id")
# A tibble: 6 × 5
  emp_id name    dept_id salary dept_name
   <dbl> <chr>     <dbl>  <dbl> <chr>    
1      1 김철수       10   3500 개발팀   
2      2 이영희       20   4200 영업팀   
3      3 박민준       10   3100 개발팀   
4      4 정수진       30   5000 인사팀   
5      5 최동현       20   3800 영업팀   
6     NA NA           40     NA 마케팅팀 

직원이 없는 마케팅팀(dept_id = 40)도 결과에 나타납니다. 직원 정보 열은 NA입니다.

right_join()

오른쪽 테이블 기준입니다. left_join()의 테이블 순서를 바꾼 것과 같습니다. 실무에서는 left_join()을 더 많이 씁니다.

# 아래 두 코드는 동일한 결과
employees %>% right_join(departments, by = "dept_id")
departments %>% left_join(employees, by = "dept_id")

join 키 열 이름이 다를 때

두 테이블에서 같은 정보를 가진 열의 이름이 다를 수 있습니다.

orders <- tibble(
  order_id    = c(101, 102, 103),
  customer_id = c(1, 2, 1),
  amount      = c(50000, 32000, 71000)
)

customers <- tibble(
  id   = c(1, 2, 3),
  name = c("김철수", "이영희", "박민준")
)

# orders의 customer_id와 customers의 id를 연결
orders %>% left_join(customers, by = c("customer_id" = "id"))
# A tibble: 3 × 4
  order_id customer_id amount name  
     <dbl>       <dbl>  <dbl> <chr> 
1      101           1  50000 김철수
2      102           2  32000 이영희
3      103           1  71000 김철수

semi_join()과 anti_join(): 필터링 join

이 두 가지는 열을 추가하지 않고 행을 필터링하는 용도입니다.

semi_join()은 오른쪽 테이블에 일치하는 행이 있는 왼쪽 행만 반환합니다.

# departments에 있는 dept_id를 가진 직원만
employees %>% semi_join(departments, by = "dept_id")

anti_join()은 반대입니다. 오른쪽 테이블에 일치하는 행이 없는 왼쪽 행만 반환합니다.

# departments에 없는 dept_id를 가진 직원 (불량 데이터 탐지)
employees_extra %>% anti_join(departments, by = "dept_id")
# A tibble: 1 × 4
  emp_id name    dept_id salary
   <dbl> <chr>     <dbl>  <dbl>
1      6 윤서연       99   3300

존재하지 않는 부서에 배정된 직원을 찾아냈습니다. 데이터 품질 검증에 자주 씁니다.

bind_rows(): 행 방향으로 합치기

같은 구조의 두 테이블을 위아래로 쌓습니다.

data_2023 <- tibble(
  year = 2023,
  month = 1:3,
  sales = c(1200, 1350, 980)
)

data_2024 <- tibble(
  year = 2024,
  month = 1:3,
  sales = c(1500, 1680, 1120)
)

bind_rows(data_2023, data_2024)
# A tibble: 6 × 3
   year month sales
  <dbl> <int> <dbl>
1  2023     1  1200
2  2023     2  1350
3  2023     3   980
4  2024     1  1500
5  2024     2  1680
6  2024     3  1120

두 테이블의 열 구조가 다르면 없는 열은 NA로 채웁니다.

bind_cols(): 열 방향으로 합치기

두 테이블을 옆으로 붙입니다. 행 수가 같아야 합니다. 공통 키가 없어도 붙이므로 행 순서가 맞는지 반드시 확인해야 합니다.

info  <- tibble(name = c("Alice", "Bob", "Charlie"))
score <- tibble(math = c(85, 72, 91), english = c(78, 88, 69))

bind_cols(info, score)
# A tibble: 3 × 3
  name     math english
  <chr>   <dbl>   <dbl>
1 Alice      85      78
2 Bob        72      88
3 Charlie    91      69

join 종류 요약

함수 결과
left_join 왼쪽 전체 + 오른쪽 일치 부분
right_join 오른쪽 전체 + 왼쪽 일치 부분
inner_join 양쪽 모두 일치하는 행만
full_join 양쪽 모두 전체 (불일치는 NA)
semi_join 오른쪽에 일치하는 왼쪽 행만 (열 추가 없음)
anti_join 오른쪽에 없는 왼쪽 행만 (열 추가 없음)

테이블을 연결하는 방법을 익혔습니다. 마지막으로 데이터의 형태 자체를 바꾸는 방법을 배웁니다.