두 개의 표, 하나의 키
실제 데이터가 표 하나에만 담기는 경우는 드뭅니다. 고객은 한 데이터 프레임에, 주문은 다른 데이터 프레임에 들어 있고, "고객마다 얼마를 썼는가?"라는 질문에는 둘 다 필요합니다. 이들을 연결하는 것이 키 입니다: 각 표에 존재하며 같은 개체를 식별하는 열이죠. 키로 표를 결합하는 것을 조인(SQL의 용어) 또는 병합(base R의 용어)이라고 합니다. 같은 연산입니다.
이 페이지의 나머지 부분에서 사용할 표 한 쌍입니다. 고객 5번은 주문을 했지만 고객 표에는 없고, 고객 2번과 4번은 한 번도 주문하지 않았다는 점을 눈여겨보세요:
불일치는 의도한 것입니다 - 조인이 매칭되지 않은 행을 어떻게 처리하느냐가 바로 조인 종류를 가르는 기준이기 때문입니다.
merge(): 기본은 내부 조인
merge(x, y, by = "key")는 키가 같은 행을 매칭해 열을 이어 붙입니다. 기본적으로 양쪽 표에 모두 존재하는 키만 남깁니다 - 내부 조인입니다:
세 행이 돌아옵니다. Ana는 두 번 나타납니다 - 주문이 두 건이고, 조인은 매칭되는 쌍 마다 출력 행을 하나씩 만듭니다. Ben과 Dana는 사라졌고(주문 없음), 고객 5번의 정체불명 주문도 사라졌습니다(고객 없음). 내부 조인은 양쪽 표에서 매칭되지 않은 행을 조용히 버립니다 - 완전한 쌍만 중요할 때는 정확히 옳고, 그렇지 않을 때는 조용한 데이터 손실 버그입니다.
왼쪽, 오른쪽, 완전 조인: all.x, all.y, all
매칭되지 않은 행을 유지하려면 어느 표의 행이 신성불가침인지 지정하세요. all.x = TRUE는 첫 번째 표의 모든 행을 유지합니다 - 왼쪽 조인 이며 실무에서 가장 많이 쓰입니다:
이제 Ben과 Dana가 amount에 NA를 달고 살아남습니다 - "이 고객은 존재하지만 매칭되는 주문이 없다"는 뜻이죠. 이 NA들은 쓰레기가 아니라 정보입니다: is.na(result$amount)가 곧 한 번도 주문하지 않은 고객 목록입니다(결측값 문서에서 다루는 법을 설명합니다). 나머지 변형은 같은 아이디어를 다른 쪽으로 향하게 한 것입니다: all.y = TRUE는 두 번째 표의 모든 행을 유지하고(오른쪽 조인 - 정체불명 고객 5번의 주문이 name에 NA를 달고 살아남습니다), all = TRUE는 양쪽 모두를 유지합니다(완전 조인).
이렇게 자문하며 고르세요: 어느 쪽 행을 잃어서는 안 되는가? 마스터 표를 참조 데이터로 보강한다면 - 왼쪽 조인, 마스터 표를 먼저. 양방향을 감사한다면 - 완전 조인입니다.
서로 다른 키 이름: by.x와 by.y
표들이 이름 규칙에서 일치하는 경우는 드뭅니다 - 한쪽은 id, 다른 쪽은 customer_id이죠. 이름을 바꾸지 말고 merge()에 두 이름을 알려 주세요:
출력은 키에 대해 첫 번째 표의 이름을 유지합니다. 관련해서, 두 표가 키가 아닌 열 이름을 공유하면(둘 다 date가 있는 경우 등) merge는 date.x와 date.y처럼 접미사를 붙입니다 - 읽기 나쁘니 바로 이름을 바꾸세요.
dplyr의 조인들
dplyr은 조인 종류마다 전용 동사를 제공하므로, 의도가 플래그 인자가 아니라 함수 이름에 드러납니다(정적 예제입니다. 샌드박스는 base R만 실행합니다):
library(dplyr)
left_join(customers, orders, by = "id")
inner_join(customers, orders, by = "id")
full_join(customers, orders, by = "id")
left_join(customers, orders, by = c("id" = "customer_id")) # different names
가독성 외에도 left_join()은 첫 번째 표의 행 순서를 보존하고(merge()는 키로 재정렬합니다) 다대다 매칭에 대해 큰 소리로 경고합니다 - 동사 가족은 dplyr 입문을 참고하세요.
과소평가된 멤버는 anti_join() 입니다: 두 번째 표에 매칭이 없는 첫 번째 표의 행을 반환합니다 - 열은 추가하지 않고 남은 행만 줍니다:
anti_join(customers, orders, by = "id") # customers who never ordered
"어떤 행이 매칭에 실패했는가?"라는 질문은 데이터 정제에서 끊임없이 등장하며(매칭 안 된 ID, 고아 레코드, 실패한 조회), base R에서는 customers[!(customers$id %in% orders$id), ]가 필요한 일을 anti_join()은 한 번의 호출로 해냅니다.
행 쌓기: rbind()
조인은 두 표의 열 을 결합합니다. 대신 같은 종류 의 행 묶음이 둘 있다면 - 1월 주문과 2월 주문처럼 - rbind()로 쌓습니다:
요구 조건은 엄격합니다: 두 데이터 프레임의 열 이름이 같아야 합니다(순서는 상관없습니다 - rbind는 이름으로 매칭합니다). 열이 빠지거나 더 있으면 NA로 채워지는 게 아니라 오류가 납니다. 묶음마다 열 구성이 어긋났다면 dplyr의 bind_rows()가 더 관대합니다 - 이름으로 정렬하고 빈 자리를 NA로 채웁니다.
중복 키 폭발
전형적인 조인 사고: 고유하다고 믿었던 키가 양쪽 표 모두에서 고유하지 않은 경우입니다. 매칭마다 서로 짝지어지며 행이 배로 늘어납니다:
두 행과 두 행을 조인하면 네 행이 나옵니다 - 각 x가 각 y와 짝지어지죠. 실제 데이터에서는 이런 식으로 1만 행짜리 표가 300만 행이 되고 매출 합계가 두 배가 됩니다. merge()는 이 일을 조용히 합니다. 방어책은 습관입니다: 조인 전에 고유하다고 가정한 키를 확인하고(anyDuplicated(customers$id)가 0이어야 합니다), 조인 후에는 nrow()가 예상과 맞는지 점검하세요.
핵심 정리
merge(x, y, by = "key")는 내부 조인이며, 매칭되지 않은 행은 조용히 사라집니다.all.x = TRUE(왼쪽),all.y = TRUE(오른쪽),all = TRUE(완전)는 매칭되지 않은 행을NA로 채워 유지합니다.- 키 이름이 다르면 base R에서는
by.x/by.y, dplyr에서는by = c("a" = "b")를 씁니다. - dplyr은 조인마다 이름을 붙입니다. 매칭에 실패한 행을 주는
anti_join()이 과소평가된 도구입니다. rbind()는 형태가 같은 표를 쌓습니다. 열은 이름으로 일치해야 합니다.- 중복 키는 조용히 행을 늘립니다 - 앞에서는
anyDuplicated(), 뒤에서는nrow()를 확인하세요.
다음 주제: pivot_longer()와 pivot_wider()로 넓은 형식과 긴 형식 사이를 오가며 재구조화하기.
자주 묻는 질문
R에서 두 데이터 프레임을 어떻게 병합하나요?
공통 키 열과 함께 merge(x, y, by = "key")를 사용합니다. 기본값은 내부 조인이라서 키가 양쪽 데이터 프레임 모두에 있는 행만 남습니다. 왼쪽 조인은 all.x = TRUE, 오른쪽 조인은 all.y = TRUE, 완전 조인은 all = TRUE를 추가하세요.
R에서 왼쪽 조인은 어떻게 하나요?
base R: merge(x, y, by = "key", all.x = TRUE)는 x의 모든 행을 유지하고 매칭되는 값이 없는 y 열은 NA로 채웁니다. dplyr: left_join(x, y, by = "key") - 결과는 같지만 x의 원래 행 순서까지 보존합니다. merge()는 그렇지 않습니다.
키 열의 이름이 서로 다를 때 데이터 프레임을 어떻게 병합하나요?
merge()에 두 이름을 모두 알려 줍니다: merge(x, y, by.x = "id", by.y = "customer_id"). dplyr에서 대응되는 표현은 left_join(x, y, by = c("id" = "customer_id"))이고, 최신 헬퍼를 쓰면 by = join_by(id == customer_id)입니다.
R에서 merge와 rbind의 차이는 무엇인가요?
결합하는 방향이 다릅니다. merge()는 두 표의 행을 키로 매칭해 열을 합칩니다 - 조인이죠. rbind()는 한 표의 행을 다른 표 아래에 쌓으며, 두 표의 열 이름이 같아야 합니다. 열이 동일한 월별 파일에는 rbind()가, 고객과 주문 결합에는 merge()가 필요합니다.