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 |
오른쪽에 없는 왼쪽 행만 (열 추가 없음) |
테이블을 연결하는 방법을 익혔습니다. 마지막으로 데이터의 형태 자체를 바꾸는 방법을 배웁니다.