library(tidyverse)
#> Warning: package 'ggplot2' was built under R version 4.5.2
#> Warning: package 'readr' was built under R version 4.5.2
library(nycflights13)19 조인(Joins)
19.1 소개
데이터 분석에 단일 데이터 프레임만 포함되는 경우는 드뭅니다. 일반적으로 많은 데이터 프레임이 있으며 관심 있는 질문에 답하려면 이들을 조인(join), 즉 결합해야 합니다. 이 장에서는 두 가지 중요한 조인 유형을 소개합니다:
- 변형 조인(Mutating joins): 다른 데이터 프레임의 일치하는 관측값에서 한 데이터 프레임에 새 변수를 추가합니다.
- 필터링 조인(Filtering joins): 다른 데이터 프레임의 관측값과 일치하는지 여부에 따라 한 데이터 프레임에서 관측값을 필터링합니다.
조인에서 두 데이터 프레임을 연결하는 데 사용되는 변수인 키(keys)에 대해 논의하는 것으로 시작하겠습니다. nycflights13 패키지의 데이터셋에 있는 키를 검사하여 이론을 공고히 한 다음, 그 지식을 사용하여 데이터 프레임을 조인하기 시작할 것입니다. 다음으로 행에 대한 조치에 초점을 맞춰 조인이 작동하는 방식에 대해 설명합니다. 기본 동등 관계보다 키를 일치시키는 더 유연한 방법을 제공하는 조인 제품군인 비동등 조인(non-equi joins)에 대한 논의로 마무리하겠습니다.
19.1.1 선수 지식
이 장에서는 dplyr의 조인 함수를 사용하여 nycflights13의 5가지 관련 데이터셋을 탐색합니다.
19.2 키(Keys)
조인을 이해하려면 먼저 두 테이블이 각 테이블 내의 키 쌍을 통해 어떻게 연결될 수 있는지 이해해야 합니다. 이 섹션에서는 두 가지 유형의 키에 대해 배우고 nycflights13 패키지의 데이터셋에서 두 가지 예를 모두 볼 것입니다. 또한 키가 유효한지 확인하는 방법과 테이블에 키가 없는 경우 수행할 작업을 배웁니다.
19.2.1 기본 키와 외래 키
모든 조인에는 기본 키와 외래 키라는 한 쌍의 키가 포함됩니다. 기본 키(Primary key) 는 각 관측값을 고유하게 식별하는 변수 또는 변수 집합입니다. 둘 이상의 변수가 필요한 경우 키를 복합 키(compound key) 라고 합니다. 예를 들어 nycflights13에서:
-
airlines는 각 항공사에 대한 두 가지 데이터인 항공사 코드와 전체 이름을 기록합니다. 두 글자 항공사 코드로 항공사를 식별할 수 있으므로carrier가 기본 키가 됩니다.airlines #> # A tibble: 16 × 2 #> carrier name #> <chr> <chr> #> 1 9E Endeavor Air Inc. #> 2 AA American Airlines Inc. #> 3 AS Alaska Airlines Inc. #> 4 B6 JetBlue Airways #> 5 DL Delta Air Lines Inc. #> 6 EV ExpressJet Airlines Inc. #> # ℹ 10 more rows -
airports는 각 공항에 대한 데이터를 기록합니다. 세 글자 공항 코드로 각 공항을 식별할 수 있으므로faa가 기본 키가 됩니다.airports #> # A tibble: 1,458 × 8 #> faa name lat lon alt tz dst #> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> #> 1 04G Lansdowne Airport 41.1 -80.6 1044 -5 A #> 2 06A Moton Field Municipal Airport 32.5 -85.7 264 -6 A #> 3 06C Schaumburg Regional 42.0 -88.1 801 -6 A #> 4 06N Randall Airport 41.4 -74.4 523 -5 A #> 5 09J Jekyll Island Airport 31.1 -81.4 11 -5 A #> 6 0A9 Elizabethton Municipal Airpo… 36.4 -82.2 1593 -5 A #> # ℹ 1,452 more rows #> # ℹ 1 more variable: tzone <chr> -
planes는 각 비행기에 대한 데이터를 기록합니다. 꼬리 번호로 비행기를 식별할 수 있으므로tailnum이 기본 키가 됩니다.planes #> # A tibble: 3,322 × 9 #> tailnum year type manufacturer model engines #> <chr> <int> <chr> <chr> <chr> <int> #> 1 N10156 2004 Fixed wing multi… EMBRAER EMB-145XR 2 #> 2 N102UW 1998 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> 3 N103US 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> 4 N104UW 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> 5 N10575 2002 Fixed wing multi… EMBRAER EMB-145LR 2 #> 6 N105UW 1999 Fixed wing multi… AIRBUS INDUSTR… A320-214 2 #> # ℹ 3,316 more rows #> # ℹ 3 more variables: seats <int>, speed <int>, engine <chr> -
weather는 출발 공항의 날씨에 대한 데이터를 기록합니다. 위치와 시간의 조합으로 각 관측값을 식별할 수 있으므로origin과time_hour가 복합 기본 키가 됩니다.weather #> # A tibble: 26,115 × 15 #> origin year month day hour temp dewp humid wind_dir #> <chr> <int> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> #> 1 EWR 2013 1 1 1 39.0 26.1 59.4 270 #> 2 EWR 2013 1 1 2 39.0 27.0 61.6 250 #> 3 EWR 2013 1 1 3 39.0 28.0 64.4 240 #> 4 EWR 2013 1 1 4 39.9 28.0 62.2 250 #> 5 EWR 2013 1 1 5 39.0 28.0 64.4 260 #> 6 EWR 2013 1 1 6 37.9 28.0 67.2 240 #> # ℹ 26,109 more rows #> # ℹ 6 more variables: wind_speed <dbl>, wind_gust <dbl>, …
외래 키(Foreign key) 는 다른 테이블의 기본 키에 해당하는 변수(또는 변수 집합)입니다. 예를 들어:
-
flights$tailnum은 기본 키planes$tailnum에 해당하는 외래 키입니다. -
flights$carrier는 기본 키airlines$carrier에 해당하는 외래 키입니다. -
flights$origin은 기본 키airports$faa에 해당하는 외래 키입니다. -
flights$dest는 기본 키airports$faa에 해당하는 외래 키입니다. -
flights$origin-flights$time_hour는 복합 기본 키weather$origin-weather$time_hour에 해당하는 복합 외래 키입니다.
이러한 관계는 Figure 19.1 에 시각적으로 요약되어 있습니다.
이 키들의 설계에서 좋은 특징을 발견할 수 있습니다. 기본 키와 외래 키는 거의 항상 동일한 이름을 가지고 있으며, 이는 곧 보게 되겠지만 조인 작업을 훨씬 쉽게 만들어 줄 것입니다. 반대 관계도 주목할 가치가 있습니다. 여러 테이블에서 사용되는 거의 모든 변수 이름은 각 장소에서 동일한 의미를 갖습니다. 예외는 단 하나뿐입니다. year는 flights에서는 출발 연도를 의미하고 planes에서는 제조 연도를 의미합니다. 이것은 실제로 테이블을 조인하기 시작할 때 중요해질 것입니다.
19.2.2 기본 키 확인
이제 각 테이블의 기본 키를 식별했으므로 실제로 각 관측값을 고유하게 식별하는지 확인하는 것이 좋습니다. 그렇게 하는 한 가지 방법은 기본 키를 count()하고 n이 1보다 큰 항목을 찾는 것입니다. planes와 weather는 모두 좋아 보입니다:
또한 기본 키에 결측값이 있는지 확인해야 합니다. 값이 누락되면 관측값을 식별할 수 없습니다!
planes |>
filter(is.na(tailnum))
#> # A tibble: 0 × 9
#> # ℹ 9 variables: tailnum <chr>, year <int>, type <chr>, manufacturer <chr>,
#> # model <chr>, engines <int>, seats <int>, speed <int>, engine <chr>
weather |>
filter(is.na(time_hour) | is.na(origin))
#> # A tibble: 0 × 15
#> # ℹ 15 variables: origin <chr>, year <int>, month <int>, day <int>,
#> # hour <int>, temp <dbl>, dewp <dbl>, humid <dbl>, wind_dir <dbl>, …19.2.3 대리 키(Surrogate keys)
지금까지 우리는 flights의 기본 키에 대해 이야기하지 않았습니다. 외래 키로 사용하는 데이터 프레임이 없기 때문에 여기서는 별로 중요하지 않지만, 다른 사람들에게 설명할 방법이 있다면 관측값으로 작업하기가 더 쉽기 때문에 고려해 볼 만합니다.
약간의 생각과 실험 끝에 우리는 함께 각 항공편을 고유하게 식별하는 세 가지 변수가 있다고 결정했습니다:
중복이 없으면 time_hour-carrier-flight가 자동으로 기본 키가 될까요? 확실히 좋은 시작이지만 보장하지는 않습니다. 예를 들어 고도와 위도는 airports에 대한 좋은 기본 키입니까?
고도와 위도로 공항을 식별하는 것은 분명히 나쁜 생각이며 일반적으로 변수 조합이 좋은 기본 키를 만드는지 여부를 데이터만으로는 알 수 없습니다. 그러나 항공편의 경우 time_hour, carrier, flight의 조합은 합리적으로 보입니다. 동일한 비행 편명을 가진 여러 항공편이 동시에 공중에 있다면 항공사와 고객에게 정말 혼란스러울 것이기 때문입니다.
그렇긴 해도 행 번호를 사용하는 간단한 숫자 대리 키를 도입하는 것이 더 나을 수 있습니다:
flights2 <- flights |>
mutate(id = row_number(), .before = 1)
flights2
#> # A tibble: 336,776 × 20
#> id year month day dep_time sched_dep_time dep_delay arr_time
#> <int> <int> <int> <int> <int> <int> <dbl> <int>
#> 1 1 2013 1 1 517 515 2 830
#> 2 2 2013 1 1 533 529 4 850
#> 3 3 2013 1 1 542 540 2 923
#> 4 4 2013 1 1 544 545 -1 1004
#> 5 5 2013 1 1 554 600 -6 812
#> 6 6 2013 1 1 554 558 -4 740
#> # ℹ 336,770 more rows
#> # ℹ 12 more variables: sched_arr_time <int>, arr_delay <dbl>, …대리 키는 다른 사람들과 소통할 때 특히 유용할 수 있습니다. 2013-01-03 오전 9시에 출발한 UA430을 보라고 말하는 것보다 2001번 항공편을 보라고 말하는 것이 훨씬 쉽습니다.
19.2.4 연습문제
Figure 19.1 에서
weather와airports사이의 관계를 그리는 것을 잊었습니다. 관계는 무엇이며 다이어그램에 어떻게 나타나야 합니까?weather에는 NYC의 3개 출발 공항에 대한 정보만 포함되어 있습니다. 미국 전역의 공항에 대한 날씨 기록이 포함되어 있다면flights와 어떤 추가 연결이 생길까요?weather의year,month,day,hour,origin변수는 거의 복합 키를 형성하지만 중복 관측값이 있는 시간이 하나 있습니다. 그 시간의 특별한 점을 파악할 수 있습니까?우리는 일년 중 며칠은 특별하고 평소보다 비행하는 사람이 적다는 것을 알고 있습니다(예: 크리스마스 이브와 크리스마스 당일). 해당 데이터를 데이터 프레임으로 어떻게 나타낼 수 있습니까? 기본 키는 무엇입니까? 기존 데이터 프레임과 어떻게 연결됩니까?
Lahman 패키지의
Batting,People,Salaries데이터 프레임 간의 연결을 설명하는 다이어그램을 그리세요.People,Managers,AwardsManagers간의 관계를 보여주는 다른 다이어그램을 그리세요.Batting,Pitching,Fielding데이터 프레임 간의 관계를 어떻게 특성화하시겠습니까?
19.3 기본 조인
이제 키를 통해 데이터 프레임이 어떻게 연결되는지 이해했으므로 조인을 사용하여 flights 데이터셋을 더 잘 이해할 수 있습니다. dplyr은 6가지 조인 함수를 제공합니다: left_join(), inner_join(), right_join(), full_join(), semi_join(), anti_join(). 모두 동일한 인터페이스를 가지고 있습니다: 한 쌍의 데이터 프레임(x와 y)을 취하고 데이터 프레임을 반환합니다. 출력의 행과 열 순서는 주로 x에 의해 결정됩니다.
이 섹션에서는 하나의 변형 조인 left_join()과 두 개의 필터링 조인 semi_join() 및 anti_join()을 사용하는 방법을 배웁니다. 다음 섹션에서는 이러한 함수가 어떻게 작동하는지, 그리고 나머지 inner_join(), right_join(), full_join()에 대해 정확히 배울 것입니다.
19.3.1 변형 조인(Mutating joins)
변형 조인을 사용하면 두 데이터 프레임의 변수를 결합할 수 있습니다. 먼저 키로 관측값을 일치시킨 다음 한 데이터 프레임에서 다른 데이터 프레임으로 변수를 복사합니다. mutate()와 마찬가지로 조인 함수는 오른쪽에 변수를 추가하므로 데이터셋에 변수가 많으면 새 변수가 보이지 않을 수 있습니다. 이 예제에서는 변수가 6개뿐인 좁은 데이터셋을 만들어 무슨 일이 일어나고 있는지 더 쉽게 볼 수 있도록 하겠습니다1:
flights2 <- flights |>
select(year, time_hour, origin, dest, tailnum, carrier)
flights2
#> # A tibble: 336,776 × 6
#> year time_hour origin dest tailnum carrier
#> <int> <dttm> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA
#> # ℹ 336,770 more rows네 가지 유형의 변형 조인이 있지만 거의 항상 사용하는 하나가 있습니다: left_join(). 출력이 항상 조인하는 데이터 프레임인 x와 동일한 행을 갖기 때문에 특별합니다2. left_join()의 주된 용도는 추가 메타데이터를 추가하는 것입니다. 예를 들어 left_join()을 사용하여 flights2 데이터에 전체 항공사 이름을 추가할 수 있습니다:
flights2 |>
left_join(airlines)
#> Joining with `by = join_by(carrier)`
#> # A tibble: 336,776 × 7
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA United Air Lines In…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA United Air Lines In…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA American Airlines I…
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 JetBlue Airways
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Delta Air Lines Inc.
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA United Air Lines In…
#> # ℹ 336,770 more rows또는 각 비행기가 출발할 때의 온도와 풍속을 알아낼 수 있습니다:
flights2 |>
left_join(weather |> select(origin, time_hour, temp, wind_speed))
#> Joining with `by = join_by(time_hour, origin)`
#> # A tibble: 336,776 × 8
#> year time_hour origin dest tailnum carrier temp wind_speed
#> <int> <dttm> <chr> <chr> <chr> <chr> <dbl> <dbl>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 39.0 12.7
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 39.9 15.0
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 39.0 15.0
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 39.0 15.0
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 39.9 16.1
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 39.0 12.7
#> # ℹ 336,770 more rows또는 어떤 크기의 비행기가 비행했는지:
flights2 |>
left_join(planes |> select(tailnum, type, engines, seats))
#> Joining with `by = join_by(tailnum)`
#> # A tibble: 336,776 × 9
#> year time_hour origin dest tailnum carrier type
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Fixed wing multi en…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA Fixed wing multi en…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Fixed wing multi en…
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 Fixed wing multi en…
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Fixed wing multi en…
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Fixed wing multi en…
#> # ℹ 336,770 more rows
#> # ℹ 2 more variables: engines <int>, seats <int>left_join()이 x의 행에 대한 일치 항목을 찾지 못하면 새 변수를 결측값으로 채웁니다. 예를 들어 꼬리 번호가 N3ALAA인 비행기에 대한 정보가 없으므로 type, engines, seats는 누락됩니다:
flights2 |>
filter(tailnum == "N3ALAA") |>
left_join(planes |> select(tailnum, type, engines, seats))
#> Joining with `by = join_by(tailnum)`
#> # A tibble: 63 × 9
#> year time_hour origin dest tailnum carrier type engines seats
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr> <int> <int>
#> 1 2013 2013-01-01 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 2 2013 2013-01-02 18:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 3 2013 2013-01-03 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 4 2013 2013-01-07 19:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> 5 2013 2013-01-08 17:00:00 JFK ORD N3ALAA AA <NA> NA NA
#> 6 2013 2013-01-16 06:00:00 LGA ORD N3ALAA AA <NA> NA NA
#> # ℹ 57 more rows이 장의 나머지 부분에서 이 문제로 몇 번 돌아올 것입니다.
19.3.2 조인 키 지정
기본적으로 left_join()은 두 데이터 프레임에 모두 나타나는 모든 변수를 조인 키로 사용합니다. 이를 소위 자연(natural) 조인이라고 합니다. 유용한 경험적 방법이지만 항상 작동하는 것은 아닙니다. 예를 들어 flights2를 전체 planes 데이터셋과 조인하려고 하면 어떻게 될까요?
flights2 |>
left_join(planes)
#> Joining with `by = join_by(year, tailnum)`
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier type manufacturer
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA <NA> <NA>
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA <NA> <NA>
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA <NA> <NA>
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA> <NA>
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL <NA> <NA>
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA <NA> <NA>
#> # ℹ 336,770 more rows
#> # ℹ 5 more variables: model <chr>, engines <int>, seats <int>, …조인이 tailnum과 year를 복합 키로 사용하려고 하기 때문에 누락된 일치 항목이 많이 발생합니다. flights와 planes 모두 year 열이 있지만 의미가 다릅니다. flights$year는 비행이 발생한 연도이고 planes$year는 비행기가 제조된 연도입니다. 우리는 tailnum으로만 조인하고 싶으므로 join_by()를 사용하여 명시적 사양을 제공해야 합니다:
flights2 |>
left_join(planes, join_by(tailnum))
#> # A tibble: 336,776 × 14
#> year.x time_hour origin dest tailnum carrier year.y
#> <int> <dttm> <chr> <chr> <chr> <chr> <int>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA 1999
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA 1998
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA 1990
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 2012
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL 1991
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA 2012
#> # ℹ 336,770 more rows
#> # ℹ 7 more variables: type <chr>, manufacturer <chr>, model <chr>, …year 변수는 출력에서 접미사(year.x 및 year.y)로 구분되어 변수가 x 또는 y 인수에서 왔는지 알려줍니다. suffix 인수로 기본 접미사를 재정의할 수 있습니다.
join_by(tailnum)은 join_by(tailnum == tailnum)의 줄임말입니다. 두 가지 이유로 이 더 긴 형식에 대해 아는 것이 중요합니다. 첫째, 두 테이블 간의 관계를 설명합니다. 키는 동일해야 합니다. 이것이 이러한 유형의 조인을 종종 동등 조인(equi join) 이라고 부르는 이유입니다. Section 19.5 에서 비동등 조인에 대해 배울 것입니다.
둘째, 각 테이블에서 서로 다른 조인 키를 지정하는 방법입니다. 예를 들어 flight2와 airports 테이블을 조인하는 방법에는 dest 또는 origin의 두 가지가 있습니다:
flights2 |>
left_join(airports, join_by(dest == faa))
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA George Bush Interco…
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA George Bush Interco…
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA Miami Intl
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 <NA>
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL Hartsfield Jackson …
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Chicago Ohare Intl
#> # ℹ 336,770 more rows
#> # ℹ 6 more variables: lat <dbl>, lon <dbl>, alt <dbl>, tz <dbl>, …
flights2 |>
left_join(airports, join_by(origin == faa))
#> # A tibble: 336,776 × 13
#> year time_hour origin dest tailnum carrier name
#> <int> <dttm> <chr> <chr> <chr> <chr> <chr>
#> 1 2013 2013-01-01 05:00:00 EWR IAH N14228 UA Newark Liberty Intl
#> 2 2013 2013-01-01 05:00:00 LGA IAH N24211 UA La Guardia
#> 3 2013 2013-01-01 05:00:00 JFK MIA N619AA AA John F Kennedy Intl
#> 4 2013 2013-01-01 05:00:00 JFK BQN N804JB B6 John F Kennedy Intl
#> 5 2013 2013-01-01 06:00:00 LGA ATL N668DN DL La Guardia
#> 6 2013 2013-01-01 05:00:00 EWR ORD N39463 UA Newark Liberty Intl
#> # ℹ 336,770 more rows
#> # ℹ 6 more variables: lat <dbl>, lon <dbl>, alt <dbl>, tz <dbl>, …이전 코드에서는 문자 벡터를 사용하여 조인 키를 지정하는 다른 방법을 볼 수 있습니다:
-
by = "x"는join_by(x)에 해당합니다. -
by = c("a" = "x")는join_by(a == x)에 해당합니다.
이제 join_by()가 존재하므로 더 명확하고 유연한 사양을 제공하기 때문에 선호합니다.
inner_join(), right_join(), full_join()은 left_join()과 동일한 인터페이스를 가집니다. 차이점은 유지하는 행입니다. 왼쪽 조인은 x의 모든 행을 유지하고, 오른쪽 조인은 y의 모든 행을 유지하고, 전체 조인은 x 또는 y의 모든 행을 유지하고, 내부 조인은 x와 y 모두에서 발생하는 행만 유지합니다. 나중에 이에 대해 더 자세히 다시 다룰 것입니다.
19.3.3 필터링 조인(Filtering joins)
짐작할 수 있듯이 필터링 조인의 주요 조치는 행을 필터링하는 것입니다. 세미 조인과 안티 조인의 두 가지 유형이 있습니다. 세미 조인(Semi-joins) 은 y에 일치하는 항목이 있는 x의 모든 행을 유지합니다. 예를 들어 세미 조인을 사용하여 airports 데이터셋을 필터링하여 출발 공항만 표시할 수 있습니다:
airports |>
semi_join(flights2, join_by(faa == origin))
#> # A tibble: 3 × 8
#> faa name lat lon alt tz dst tzone
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 EWR Newark Liberty Intl 40.7 -74.2 18 -5 A America/New_York
#> 2 JFK John F Kennedy Intl 40.6 -73.8 13 -5 A America/New_York
#> 3 LGA La Guardia 40.8 -73.9 22 -5 A America/New_York또는 목적지만:
airports |>
semi_join(flights2, join_by(faa == dest))
#> # A tibble: 101 × 8
#> faa name lat lon alt tz dst tzone
#> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 ABQ Albuquerque Internati… 35.0 -107. 5355 -7 A America/Denver
#> 2 ACK Nantucket Mem 41.3 -70.1 48 -5 A America/New_Yo…
#> 3 ALB Albany Intl 42.7 -73.8 285 -5 A America/New_Yo…
#> 4 ANC Ted Stevens Anchorage… 61.2 -150. 152 -9 A America/Anchor…
#> 5 ATL Hartsfield Jackson At… 33.6 -84.4 1026 -5 A America/New_Yo…
#> 6 AUS Austin Bergstrom Intl 30.2 -97.7 542 -6 A America/Chicago
#> # ℹ 95 more rows안티 조인(Anti-joins) 은 그 반대입니다. y에 일치하는 항목이 없는 x의 모든 행을 반환합니다. Section 18.3 의 주제인 데이터에 암시된 결측값을 찾는 데 유용합니다. 암시적으로 누락된 값은 NA로 표시되지 않고 부재로만 존재합니다. 예를 들어 일치하는 목적지 공항이 없는 항공편을 찾아 airports에서 누락된 행을 찾을 수 있습니다:
또는 planes에서 누락된 tailnum을 찾을 수 있습니다:
19.3.4 연습문제
(일년 전체 과정에서) 최악의 지연이 있는 48시간을 찾으세요.
weather데이터와 상호 참조하세요. 패턴을 볼 수 있습니까?-
이 코드를 사용하여 가장 인기 있는 10개 목적지를 찾았다고 상상해 보세요:
그 목적지로 가는 모든 항공편을 어떻게 찾을 수 있습니까?
모든 출발 항공편에 해당 시간의 날씨 데이터가 있습니까?
planes에 일치하는 기록이 없는 꼬리 번호의 공통점은 무엇입니까? (힌트: 하나의 변수가 문제의 약 90%를 설명합니다.)해당 비행기를 운항한 모든
carrier를 나열하는 열을planes에 추가하세요. 각 비행기는 단일 항공사에 의해 운항되기 때문에 비행기와 항공사 사이에 암시적인 관계가 있을 것이라고 기대할 수 있습니다. 이전 장에서 배운 도구를 사용하여 이 가설을 확인하거나 기각하세요.flights에 출발 및 도착 공항의 위도와 경도를 추가하세요. 조인 전이나 후에 열 이름을 바꾸는 것이 더 쉽습니까?-
목적지별 평균 지연을 계산한 다음
airports데이터 프레임에 조인하여 지연의 공간적 분포를 보여줄 수 있도록 하세요. 미국 지도를 그리는 쉬운 방법은 다음과 같습니다:airports |> semi_join(flights, join_by(faa == dest)) |> ggplot(aes(x = lon, y = lat)) + borders("state") + geom_point() + coord_quickmap()점의
size또는color를 사용하여 각 공항의 평균 지연을 표시하고 싶을 수 있습니다. 2013년 6월 13일에 무슨 일이 일어났습니까? 지연 지도를 그린 다음 Google을 사용하여 날씨와 상호 참조하세요.
19.4 조인은 어떻게 작동합니까?
이제 조인을 몇 번 사용했으므로 x의 각 행이 y의 행과 어떻게 일치하는지에 초점을 맞춰 조인이 작동하는 방식에 대해 자세히 알아볼 때입니다. Figure 19.2 에 표시된 아래 정의된 간단한 티블을 사용하여 조인의 시각적 표현을 소개하는 것으로 시작하겠습니다. 이 예제에서는 key라는 단일 키와 단일 값 열(val_x 및 val_y)을 사용하지만 아이디어는 모두 여러 키와 여러 값으로 일반화됩니다.
key 열은 배경색을 키 값에 매핑합니다. 회색 열은 따라오는 “값” 열을 나타냅니다.
Figure 19.3 는 시각적 표현의 기초를 소개합니다. x의 각 행과 y의 각 행에서 그려진 선 사이의 교차점으로 x와 y 사이의 모든 잠재적 일치를 보여줍니다. 출력의 행과 열은 주로 x에 의해 결정되므로 x 테이블은 수평이며 출력과 일치합니다.
특정 유형의 조인을 설명하기 위해 일치 항목을 점으로 나타냅니다. 일치 항목은 키, x 값, y 값을 포함하는 새 데이터 프레임인 출력의 행을 결정합니다. 예를 들어 Figure 19.4 는 키가 동일한 경우에만 행이 유지되는 내부 조인을 보여줍니다.
x의 각 행을 key 값이 같은 y의 행과 일치시킵니다. 각 일치 항목은 출력에서 하나의 행이 됩니다.
동일한 원칙을 적용하여 데이터 프레임 중 하나 이상에 나타나는 관측값을 유지하는 외부 조인(outer joins) 을 설명할 수 있습니다. 이러한 조인은 각 데이터 프레임에 추가 “가상” 관측값을 추가하여 작동합니다. 이 관측값에는 다른 키가 일치하지 않는 경우 일치하는 키와 NA로 채워진 값이 있습니다. 세 가지 유형의 외부 조인이 있습니다:
-
왼쪽 조인은
x의 모든 관측값을 유지합니다(Figure 19.5).x의 모든 행은y의NA행과 일치하도록 폴백할 수 있으므로 출력에 보존됩니다.Figure 19.5: x의 모든 행이 출력에 나타나는 왼쪽 조인의 시각적 표현. -
오른쪽 조인은
y의 모든 관측값을 유지합니다(Figure 19.6).y의 모든 행은x의NA행과 일치하도록 폴백할 수 있으므로 출력에 보존됩니다. 출력은 여전히 가능한 한x와 일치합니다.y의 모든 추가 행은 끝에 추가됩니다.Figure 19.6: y의 모든 행이 출력에 나타나는 오른쪽 조인의 시각적 표현. -
전체 조인은
x또는y에 나타나는 모든 관측값을 유지합니다(Figure 19.7).x와y모두NA폴백 행이 있으므로x와y의 모든 행이 출력에 포함됩니다. 다시 출력은x의 모든 행으로 시작하고 일치하지 않는 나머지y행이 뒤따릅니다.Figure 19.7: x와y의 모든 행이 출력에 나타나는 전체 조인의 시각적 표현.
외부 조인 유형이 어떻게 다른지 보여주는 또 다른 방법은 Figure 19.8 과 같은 벤 다이어그램을 사용하는 것입니다. 그러나 이것은 어떤 행이 보존되는지 기억을 되살릴 수는 있지만 열에서 무슨 일이 일어나는지 설명하지 못하기 때문에 훌륭한 표현은 아닙니다.
여기에 표시된 조인은 키가 같으면 행이 일치하는 소위 동등(equi) 조인입니다. 동등 조인은 가장 일반적인 유형의 조인이므로 일반적으로 동등 접두사를 생략하고 “동등 내부 조인”이 아닌 그냥 “내부 조인”이라고 말합니다. Section 19.5 에서 비동등 조인으로 다시 돌아올 것입니다.
19.4.1 행 일치
지금까지 x의 행이 y의 0개 또는 1개 행과 일치하면 어떻게 되는지 살펴보았습니다. 둘 이상의 행과 일치하면 어떻게 됩니까? 무슨 일이 일어나고 있는지 이해하기 위해 먼저 inner_join()에 초점을 맞추고 그림을 그려보겠습니다(Figure 19.9).
x의 행이 일치할 수 있는 세 가지 방법. x1은 y의 한 행과 일치하고, x2는 y의 두 행과 일치하며, x3은 y의 0개 행과 일치합니다. x에 3개 행이 있고 출력에 3개 행이 있지만 행 간에 직접적인 대응 관계는 없다는 점에 유의하세요.
x의 행에 대해 세 가지 가능한 결과가 있습니다:
- 아무것도 일치하지 않으면 삭제됩니다.
-
y의 1개 행과 일치하면 보존됩니다. -
y의 1개 이상의 행과 일치하면 각 일치 항목에 대해 한 번씩 복제됩니다.
원칙적으로 이는 출력의 행과 x의 행 사이에 보장된 대응 관계가 없음을 의미하지만 실제로는 거의 문제를 일으키지 않습니다. 그러나 행의 조합적 폭발을 일으킬 수 있는 특히 위험한 경우가 하나 있습니다. 다음 두 테이블을 조인한다고 상상해 보세요:
df1의 첫 번째 행은 df2의 한 행과만 일치하지만 두 번째와 세 번째 행은 모두 두 행과 일치합니다. 이것을 때때로 다대다(many-to-many) 조인이라고 하며 dplyr이 경고를 보내도록 합니다:
df1 |>
inner_join(df2, join_by(key))
#> Warning in inner_join(df1, df2, join_by(key)): Detected an unexpected many-to-many relationship between `x` and `y`.
#> ℹ Row 2 of `x` matches multiple rows in `y`.
#> ℹ Row 2 of `y` matches multiple rows in `x`.
#> ℹ If a many-to-many relationship is expected, set `relationship =
#> "many-to-many"` to silence this warning.
#> # A tibble: 5 × 3
#> key val_x val_y
#> <dbl> <chr> <chr>
#> 1 1 x1 y1
#> 2 2 x2 y2
#> 3 2 x2 y3
#> 4 2 x3 y2
#> 5 2 x3 y3의도적으로 이 작업을 수행하는 경우 경고가 제안하는 대로 relationship = "many-to-many"를 설정할 수 있습니다.
19.4.2 필터링 조인
일치하는 항목 수는 필터링 조인의 동작도 결정합니다. 세미 조인은 Figure 19.10 와 같이 y에 하나 이상의 일치 항목이 있는 x의 행을 유지합니다. 안티 조인은 Figure 19.11 와 같이 y에 0개의 행이 일치하는 x의 행을 유지합니다. 두 경우 모두 일치 항목의 존재 여부만 중요합니다. 몇 번 일치하는지는 중요하지 않습니다. 즉, 필터링 조인은 변형 조인처럼 행을 중복시키지 않습니다.
y의 값은 출력에 영향을 주지 않습니다.
y에 일치하는 항목이 있는 x의 행을 삭제합니다.
19.5 비동등 조인(Non-equi joins)
지금까지 x 키가 y 키와 같으면 행이 일치하는 동등 조인만 보았습니다. 이제 그 제한을 완화하고 한 쌍의 행이 일치하는지 확인하는 다른 방법에 대해 논의하겠습니다.
하지만 그렇게 하기 전에 위에서 만든 단순화를 다시 살펴봐야 합니다. 동등 조인에서 x 키와 y는 항상 동일하므로 출력에 하나만 표시하면 됩니다. keep = TRUE를 사용하여 dplyr에 두 키를 모두 유지하도록 요청할 수 있으며, 이는 아래 코드와 Figure 19.12 의 다시 그려진 inner_join()으로 이어집니다.
x |> inner_join(y, join_by(key == key), keep = TRUE)
#> # A tibble: 2 × 4
#> key.x val_x key.y val_y
#> <dbl> <chr> <dbl> <chr>
#> 1 1 x1 1 y1
#> 2 2 x2 2 y2x와 y 키를 모두 보여주는 내부 조인.
동등 조인에서 벗어나면 키 값이 종종 다르기 때문에 항상 키를 표시합니다. 예를 들어 x$key와 y$key가 같을 때만 일치시키는 대신 x$key가 y$key보다 크거나 같을 때마다 일치시켜 Figure 19.13 로 이어질 수 있습니다. dplyr의 조인 함수는 이 차이점 동등 조인과 비동등 조인을 이해하므로 비동등 조인을 수행할 때 항상 두 키를 모두 표시합니다.
x 키가 y 키보다 크거나 같아야 하는 비동등 조인. 많은 행이 여러 일치 항목을 생성합니다.
비동등 조인은 조인이 아닌 것이 무엇인지만 알려주고 무엇인지 알려주지 않기 때문에 특별히 유용한 용어는 아닙니다. dplyr은 4가지 특히 유용한 비동등 조인 유형을 식별하여 도움을 줍니다:
- 교차 조인(Cross joins) 은 모든 행 쌍을 일치시킵니다.
-
부등 조인(Inequality joins) 은
==대신<,<=,>,>=를 사용합니다. - 롤링 조인(Rolling joins) 은 부등 조인과 유사하지만 가장 가까운 일치 항목만 찾습니다.
- 중첩 조인(Overlap joins) 은 범위와 함께 작동하도록 설계된 특수한 유형의 부등 조인입니다.
이들 각각은 다음 섹션에서 자세히 설명합니다.
19.5.1 교차 조인(Cross joins)
교차 조인은 Figure 19.14 와 같이 모든 것을 일치시켜 행의 데카르트 곱(Cartesian product)을 생성합니다. 이는 출력이 nrow(x) * nrow(y)개의 행을 가짐을 의미합니다.
x의 각 행을 y의 모든 행과 일치시킵니다.
교차 조인은 순열을 생성할 때 유용합니다. 예를 들어 아래 코드는 가능한 모든 이름 쌍을 생성합니다. df를 자신에게 조인하므로 이를 때때로 자체 조인(self-join) 이라고 합니다. 교차 조인은 모든 행을 일치시킬 때 내부/왼쪽/오른쪽/전체 구분 없기 때문에 다른 조인 함수를 사용합니다.
df <- tibble(name = c("John", "Simon", "Tracy", "Max"))
df |> cross_join(df)
#> # A tibble: 16 × 2
#> name.x name.y
#> <chr> <chr>
#> 1 John John
#> 2 John Simon
#> 3 John Tracy
#> 4 John Max
#> 5 Simon John
#> 6 Simon Simon
#> # ℹ 10 more rows19.5.2 부등 조인(Inequality joins)
부등 조인은 <, <=, >=, >를 사용하여 가능한 일치 항목 집합을 제한합니다(Figure 19.13 및 Figure 19.15).
x의 키가 y의 키보다 작은 행에서 x가 y에 조인되는 부등 조인. 이것은 왼쪽 상단 모서리에 삼각형 모양을 만듭니다.
부등 조인은 매우 일반적이어서 의미 있는 특정 사용 사례를 생각해내기 어렵습니다. 작고 유용한 기술 중 하나는 모든 순열을 생성하는 대신 모든 조합을 생성하도록 교차 조인을 제한하는 데 사용하는 것입니다:
df <- tibble(id = 1:4, name = c("John", "Simon", "Tracy", "Max"))
df |> inner_join(df, join_by(id < id))
#> # A tibble: 6 × 4
#> id.x name.x id.y name.y
#> <int> <chr> <int> <chr>
#> 1 1 John 2 Simon
#> 2 1 John 3 Tracy
#> 3 1 John 4 Max
#> 4 2 Simon 3 Tracy
#> 5 2 Simon 4 Max
#> 6 3 Tracy 4 Max19.5.3 롤링 조인(Rolling joins)
롤링 조인은 부등식을 만족하는 모든 행을 얻는 대신 가장 가까운 행만 얻는 특수한 유형의 부등 조인입니다(Figure 19.16). closest()를 추가하여 모든 부등 조인을 롤링 조인으로 바꿀 수 있습니다. 예를 들어 join_by(closest(x <= y))는 x보다 크거나 같은 가장 작은 y와 일치하고 join_by(closest(x > y))는 x보다 작은 가장 큰 y와 일치합니다.
롤링 조인은 완벽하게 정렬되지 않는 두 개의 날짜 테이블이 있고 테이블 1의 날짜보다 이전(또는 이후)에 오는 테이블 2의 (예를 들어) 가장 가까운 날짜를 찾으려는 경우에 특히 유용합니다.
예를 들어 사무실의 파티 계획 위원회 책임자라고 상상해 보세요. 회사가 꽤 인색해서 개별 파티를 하는 대신 분기에 한 번만 파티를 엽니다. 파티가 언제 열릴지 결정하는 규칙은 조금 복잡합니다. 파티는 항상 월요일에 열리고, 많은 사람들이 휴가 중이므로 1월 첫째 주는 건너뛰고, 2022년 3분기의 첫 번째 월요일은 7월 4일이므로 일주일 미뤄야 합니다. 그 결과 다음과 같은 파티 날짜가 나옵니다:
이제 직원 생일 테이블이 있다고 상상해 보세요:
set.seed(123)
employees <- tibble(
name = sample(babynames::babynames$name, 100),
birthday = ymd("2022-01-01") + (sample(365, 100, replace = TRUE) - 1)
)
employees
#> # A tibble: 100 × 2
#> name birthday
#> <chr> <date>
#> 1 Kemba 2022-01-22
#> 2 Orean 2022-06-26
#> 3 Kirstyn 2022-02-11
#> 4 Amparo 2022-11-11
#> 5 Belen 2022-03-25
#> 6 Rayshaun 2022-01-11
#> # ℹ 94 more rows그리고 각 직원에 대해 생일 이전(또는 당일)에 오는 마지막 파티 날짜를 찾고 싶습니다. 롤링 조인으로 표현할 수 있습니다:
employees |>
left_join(parties, join_by(closest(birthday >= party)))
#> # A tibble: 100 × 4
#> name birthday q party
#> <chr> <date> <int> <date>
#> 1 Kemba 2022-01-22 1 2022-01-10
#> 2 Orean 2022-06-26 2 2022-04-04
#> 3 Kirstyn 2022-02-11 1 2022-01-10
#> 4 Amparo 2022-11-11 4 2022-10-03
#> 5 Belen 2022-03-25 1 2022-01-10
#> 6 Rayshaun 2022-01-11 1 2022-01-10
#> # ℹ 94 more rows하지만 이 접근 방식에는 한 가지 문제가 있습니다. 생일이 1월 10일 이전인 사람들은 파티를 얻지 못합니다:
이 문제를 해결하려면 중첩 조인이라는 다른 방식으로 문제에 접근해야 합니다.
19.5.4 중첩 조인(Overlap joins)
중첩 조인은 부등 조인을 사용하여 간격 작업을 더 쉽게 만드는 세 가지 도우미를 제공합니다:
-
between(x, y_lower, y_upper)는x >= y_lower, x <= y_upper의 줄임말입니다. -
within(x_lower, x_upper, y_lower, y_upper)는x_lower >= y_lower, x_upper <= y_upper의 줄임말입니다. -
overlaps(x_lower, x_upper, y_lower, y_upper)는x_lower <= y_upper, x_upper >= y_lower의 줄임말입니다.
그것들을 어떻게 사용할 수 있는지 알아보기 위해 생일 예제를 계속해 보겠습니다. 위에서 사용한 전략에는 한 가지 문제가 있습니다. 1월 1-9일 생일 이전에 파티가 없습니다. 따라서 각 파티가 걸쳐 있는 날짜 범위를 명시하고 초반 생일에 대해 특별한 경우를 만드는 것이 더 나을 수 있습니다:
parties <- tibble(
q = 1:4,
party = ymd(c("2022-01-10", "2022-04-04", "2022-07-11", "2022-10-03")),
start = ymd(c("2022-01-01", "2022-04-04", "2022-07-11", "2022-10-03")),
end = ymd(c("2022-04-03", "2022-07-11", "2022-10-02", "2022-12-31"))
)
parties
#> # A tibble: 4 × 4
#> q party start end
#> <int> <date> <date> <date>
#> 1 1 2022-01-10 2022-01-01 2022-04-03
#> 2 2 2022-04-04 2022-04-04 2022-07-11
#> 3 3 2022-07-11 2022-07-11 2022-10-02
#> 4 4 2022-10-03 2022-10-03 2022-12-31해들리(Hadley)는 데이터 입력에 끔찍하게 서툴러서 파티 기간이 겹치지 않는지 확인하고 싶었습니다. 이를 수행하는 한 가지 방법은 자체 조인을 사용하여 시작-종료 간격이 다른 것과 겹치는지 확인하는 것입니다:
parties |>
inner_join(parties, join_by(overlaps(start, end, start, end), q < q)) |>
select(start.x, end.x, start.y, end.y)
#> # A tibble: 1 × 4
#> start.x end.x start.y end.y
#> <date> <date> <date> <date>
#> 1 2022-04-04 2022-07-11 2022-07-11 2022-10-02이런, 겹치는 부분이 있으니 문제를 해결하고 계속해 보겠습니다:
이제 각 직원을 파티에 연결할 수 있습니다. 파티를 배정받지 못한 직원이 있는지 빨리 알아보고 싶기 때문에 unmatched = "error"를 사용하기에 좋은 곳입니다.
employees |>
inner_join(parties, join_by(between(birthday, start, end)), unmatched = "error")
#> # A tibble: 100 × 6
#> name birthday q party start end
#> <chr> <date> <int> <date> <date> <date>
#> 1 Kemba 2022-01-22 1 2022-01-10 2022-01-01 2022-04-03
#> 2 Orean 2022-06-26 2 2022-04-04 2022-04-04 2022-07-10
#> 3 Kirstyn 2022-02-11 1 2022-01-10 2022-01-01 2022-04-03
#> 4 Amparo 2022-11-11 4 2022-10-03 2022-10-03 2022-12-31
#> 5 Belen 2022-03-25 1 2022-01-10 2022-01-01 2022-04-03
#> 6 Rayshaun 2022-01-11 1 2022-01-10 2022-01-01 2022-04-03
#> # ℹ 94 more rows19.5.5 연습문제
-
이 동등 조인에서 키에 무슨 일이 일어나고 있는지 설명할 수 있습니까? 왜 다릅니까?
x |> full_join(y, join_by(key == key)) #> # A tibble: 4 × 3 #> key val_x val_y #> <dbl> <chr> <chr> #> 1 1 x1 y1 #> 2 2 x2 y2 #> 3 3 x3 <NA> #> 4 4 <NA> y3 x |> full_join(y, join_by(key == key), keep = TRUE) #> # A tibble: 4 × 4 #> key.x val_x key.y val_y #> <dbl> <chr> <dbl> <chr> #> 1 1 x1 1 y1 #> 2 2 x2 2 y2 #> 3 3 x3 NA <NA> #> 4 NA <NA> 4 y3 파티 기간이 다른 파티 기간과 겹치는지 찾을 때
join_by()에서q < q를 사용했습니다. 이유는 무엇입니까? 이 부등식을 제거하면 어떻게 됩니까?
19.6 요약
이 장에서는 변형 및 필터링 조인을 사용하여 한 쌍의 데이터 프레임에서 데이터를 결합하는 방법을 배웠습니다. 그 과정에서 키를 식별하는 방법과 기본 키와 외래 키의 차이점을 배웠습니다. 또한 조인이 작동하는 방식과 출력에 몇 개의 행이 있는지 파악하는 방법도 이해했습니다. 마지막으로 비동등 조인의 힘을 살짝 엿보고 몇 가지 흥미로운 사용 사례를 보았습니다.
이 장으로 개별 열과 티블에서 사용할 수 있는 도구에 중점을 둔 책의 “변형(Transform)” 파트가 마무리됩니다. 논리형 벡터, 숫자, 전체 테이블 작업을 위한 dplyr 및 기본 함수, 문자열 작업을 위한 stringr 함수, 날짜-시간 작업을 위한 lubridate 함수, 팩터 작업을 위한 forcats 함수에 대해 배웠습니다.
책의 다음 파트에서는 다양한 유형의 데이터를 깔끔한 형태로 R로 가져오는 방법에 대해 더 자세히 배울 것입니다.