ADsP NOTE검색

데이터 분석 / 7분 읽기

R 데이터 결합과 집계, 조인 뒤 행 수가 늘어나는 이유

조인 키가 오른쪽 표에서 중복되면 왼쪽의 한 행이 여러 행과 짝지어져 행 수와 합계가 늘어납니다. 결합 전 각 표의 한 행이 무엇인지 확인하고, 같은 키의 대응 수를 곱해 예상 행 수를 구한 뒤 실제 결과와 대조합니다.

자료 두 개를 합쳤는데 합계가 달라졌다면

주문 표의 금액을 더하면 12,000원인데 회원 정보를 붙인 뒤에는 17,000원이 나옵니다. 새 주문은 없었습니다. 이 상황에서 sum 함수부터 의심하면 원인을 놓치기 쉽습니다. 결합 과정에서 같은 주문이 두 번 나타났는지부터 확인합니다.

조인은 공통된 값으로 표의 행을 짝짓는 작업이고, 키는 그 짝을 찾는 기준 열입니다. 집계는 여러 행을 그룹별 합계나 평균 같은 값으로 요약합니다. 이름이 비슷한 데이터 가공 작업이지만 행 수가 변하는 이유는 서로 다릅니다.

주문과 회원은 가상 자료이며, 코드는 추가 패키지가 필요 없는 기본 R 함수만 사용합니다.

한 행의 의미를 적고 결합 키를 정합니다

orders <- data.frame(
  order_id = c("A1", "A2", "A3", "A4"),
  member_id = c("M1", "M1", "M2", "M3"),
  amount = c(2000, 3000, 5000, 2000)
)
members <- data.frame(
  member_id = c("M1", "M2"),
  region = c("서울", "부산")
)
sum(orders$amount)

주문 표는 한 행이 주문 한 건입니다. 회원 M1은 주문을 두 번 했으므로 회원 번호가 중복되어도 정상입니다. 회원 표는 한 행이 회원 한 명이며 이 예제에서는 회원 번호가 한 번씩만 나옵니다. 원래 주문 합계는 12,000원입니다.

어떤 열이 중복되었는지보다 그 표의 한 행이 무엇을 나타내는지가 먼저입니다. 주문 표에서 member_id 중복을 제거하면 정상 주문을 지울 수 있습니다. 회원 표에 지역 이력이 들어 있어 회원별 여러 행이 존재한다면 단순 회원 표와도 다릅니다. 그때는 현재 지역인지 주문 당시 지역인지 정하고 적용 기간까지 결합 조건에 반영해야 합니다.

내부 조인과 왼쪽 조인은 남기는 행이 다릅니다

inner <- merge(orders, members, by = "member_id")
left <- merge(orders, members, by = "member_id", all.x = TRUE)
nrow(inner)
nrow(left)
sum(inner$amount)
sum(left$amount)

기본 merge는 두 표에 모두 대응 값이 있는 행을 남깁니다. M3은 회원 표에 없으므로 내부 조인 결과는 3행이고 합계는 10,000원입니다. all.x = TRUE는 왼쪽 표인 orders의 행을 유지하는 왼쪽 조인입니다. 결과는 4행, 합계는 12,000원이며 M3의 region에는 NA가 들어갑니다.

기준내부 조인왼쪽 조인
대응 회원이 있는 주문남김남김
대응 회원이 없는 M3 주문제외남기고 오른쪽 값을 NA로 표시
현재 자료의 결과 행 수3행4행
현재 자료의 주문 금액 합계10,000원12,000원

왼쪽 조인이 언제나 원본과 같은 행 수라는 뜻은 아닙니다. 이 결과가 성립한 이유는 회원 표의 키가 유일하기 때문입니다. merge 결과의 기본 정렬 순서도 원래 주문 순서와 같다는 보장은 없습니다. 원래 순서가 필요하면 별도 행 번호를 붙여 두고 결합 후 다시 정렬합니다.

키 하나가 여러 행에 맞으면 모든 조합이 생깁니다

회원 표에 M1이 두 줄 있다고 가정하겠습니다. 지역 정보를 수정하다가 예전 행을 남긴 상황입니다. 아래는 오류의 영향을 확인하기 위한 자료이며 이력을 모두 삭제하라는 뜻은 아닙니다.

members_bad <- rbind(
  members,
  data.frame(member_id = "M1", region = "인천")
)
bad <- merge(orders, members_bad,
             by = "member_id", all.x = TRUE)
nrow(bad)
sum(bad$amount)

M1 주문은 A1과 A2 두 행입니다. 오른쪽에서도 M1이 두 행이므로 두 주문이 각각 서울과 인천에 연결됩니다. M1 부분만 2 × 2 = 4행이 됩니다. M2 한 행과 대응이 없는 M3 한 행을 더하면 전체 6행입니다. 금액은 M1의 2,000원과 3,000원이 한 번씩 더 세어져 17,000원이 됩니다.

같은 키의 대응 행은 곱하고, 서로 다른 키의 결과는 더합니다. 양쪽에서 각각 3행과 2행이면 그 키의 일치 결과는 6행입니다. 단, 왼쪽 조인에서 오른쪽 대응이 0행인 경우 왼쪽 행을 유지하므로 3 × 0 = 0으로 계산하면 틀립니다. 규칙의 적용 범위는 실제로 대응하는 행의 조합이고, 미대응 행은 조인 방식에 따라 별도로 처리합니다.

해결 전에 회원 번호별 행 수와 지역 값의 이유를 조사하세요. 완전히 같은 중복 행인지, 유효한 주소 이력인지, 여러 지역 소속인지에 따라 정리 방법이 다릅니다. 아무 행이나 첫째 것만 남기면 합계는 맞아 보여도 지역 분석이 틀릴 수 있습니다.

집계는 결합과 달리 요약할 단위를 새로 정합니다

회원별 주문 금액을 구할 때는 원래 주문 표에서 집계할 수 있습니다. 오른쪽 표를 붙일 필요가 없는 계산이라면 조인을 먼저 하지 않는 편이 결과를 점검하기 쉽습니다.

member_total <- aggregate(amount ~ member_id, data = orders, FUN = sum)
member_count <- aggregate(order_id ~ member_id, data = orders, FUN = length)
member_total
member_count

회원별 합계는 M1 5,000원, M2 5,000원, M3 2,000원입니다. 주문 건수는 각각 2, 1, 1건입니다. 식의 물결표 왼쪽은 요약할 값이고 오른쪽은 그룹을 나눌 기준입니다. 이번 자료에는 결측값이 없으며, order_id는 주문마다 하나씩 존재하므로 length로 행 수를 셀 수 있습니다.

이제 회원별 합계 표는 한 행이 주문이 아니라 회원입니다. 이 표의 금액 평균은 (5,000 + 5,000 + 2,000) / 3 = 4,000원입니다. 원래 주문 금액의 평균 12,000 / 4 = 3,000원과 다릅니다. 앞의 값은 회원당 총 구매액의 평균이고 뒤의 값은 주문 한 건당 금액의 평균입니다. 같은 amount라는 이름을 유지해도 행의 단위가 달라지면 통계량의 질문이 바뀝니다.

분석용 표에서는 행 수와 키별 대응 수, 합계를 확인합니다

분석 목적에 맞춰 데이터를 모은 데이터 마트를 만들 때는 한 행의 기준을 문장으로 남깁니다. 주문 한 건인지, 회원 한 명인지, 회원과 월의 조합인지 적는 것입니다. 회원별 지난달 구매액을 예측에 쓴다면 회원별 금액만 모았다는 설명으로는 부족하고 집계 기간과 기준 시점도 필요합니다. 주문 당시에는 알 수 없었던 다음 달 정보가 섞이지 않도록 확인합니다.

결측값 처리는 집계 전에 결정하세요. aggregate의 수식 방식은 기본적으로 사용 열에 결측이 있는 행을 제외할 수 있습니다. 그룹 열의 결측과 금액 열의 결측이 같은 문제도 아닙니다. 원래 행 수와 집계에 사용한 행 수를 대조하고 제외 이유를 기록합니다. na.rm 설정 하나로 모든 집계 함수와 입력 방식의 결측 규칙이 같아진다고 가정하지 마세요.

검산은 행 수, 키별 대응 수, 합계를 함께 봅니다. 이 예제에서 회원별 합계를 다시 더하면 원래 12,000원으로 돌아와야 합니다. 내부 조인으로 범위를 줄였다면 합계 감소가 의도한 조건인지 설명할 수 있어야 합니다. 매번 합계가 같아야 한다는 규칙 대신 어떤 행을 남기기로 했는지와 결과가 일치하는지를 확인합니다.

중복과 누락을 정리하기 전에는 데이터 품질 점검처럼 정상 기록과 오류를 구분하는 기준부터 남깁니다.

핵심 정리와 실수 점검

핵심 정리

  • 왼쪽 조인은 왼쪽 행을 유지하지만 오른쪽 중복 키 때문에 결과 행 수가 늘 수 있습니다.
  • 동일 키의 대응 조합 수는 양쪽 행 수의 곱입니다. 미대응 행은 조인 방식에 따라 따로 처리합니다.
  • 집계하면 한 행의 단위가 바뀌므로 합계뿐 아니라 평균의 분모도 다시 확인합니다.

놓치기 쉬운 지점

  • 주문 표의 회원 번호 중복을 오류라고 보고 정상 주문을 지우는 실수
  • 결합 뒤 합계 증가를 매출 증가로 해석하는 실수. 같은 주문이 반복됐는지 먼저 확인합니다.
  • 회원별 합계의 평균을 주문당 평균이라고 이름 붙이는 실수

확인할 내용

  • 각 표의 한 행이 나타내는 대상을 한 문장으로 쓴다.
  • 결합 키별 행 수를 양쪽 표에서 확인한다.
  • 미대응 행을 남길지 제외할지 정한다.
  • 결합과 집계 전후의 행 수, 키 수, 합계를 검산한다.

이해한 내용 확인하기

직접 작성한 학습용 문항입니다. 공식 기출문제가 아닙니다.

Q1. 왼쪽에 같은 키가 3행, 오른쪽에 같은 키가 2행 있습니다. 그 키의 내부 조인 결과는 몇 행인가요?

생각한 뒤 해설 보기

3 × 2 = 6행입니다. 양쪽의 대응 가능한 조합이 모두 만들어집니다.

Q2. 왼쪽에 K1이 3행이고 오른쪽에는 K1이 없습니다. 왼쪽 조인에서 K1은 몇 행 남나요?

생각한 뒤 해설 보기

3행입니다. 오른쪽 열은 NA로 채워집니다. 3 × 0 = 0이라는 계산은 왼쪽 조인의 미대응 행 유지 규칙을 빠뜨린 것입니다.

Q3. 주문 4건이 같은 회원에게 속합니다. 주문 표에서 회원 번호가 중복됐다는 이유만으로 3건을 지워도 되나요?

생각한 뒤 해설 보기

안 됩니다. 주문 표의 한 행은 주문 한 건이므로 회원 번호의 반복은 정상일 수 있습니다. 주문 키와 실제 주문 내용을 확인해야 합니다.

Q4. 회원 A는 100원짜리 주문 3건, 회원 B는 900원짜리 주문 1건이 있습니다. 주문당 평균과 회원별 총액의 평균은 각각 얼마인가요?

생각한 뒤 해설 보기

전체 금액 1,200원을 주문 4건으로 나눈 주문당 평균은 300원입니다. 회원별 총액 300원과 900원의 평균은 600원입니다. 분모가 주문 수인지 회원 수인지 다릅니다.

Q5. 오른쪽 키가 유일하고 모든 왼쪽 키에 대응합니다. 왼쪽 조인 후 행 수가 줄어드는 것이 정상인가요?

생각한 뒤 해설 보기

아닙니다. 이 조건에서는 각 왼쪽 행이 정확히 한 오른쪽 행과 연결되어 원래 왼쪽 행 수가 유지됩니다. 행 수가 달라졌다면 실제 키 중복이나 다른 필터 처리를 확인합니다.

객관식 유형 연습 →

더 궁금한 점

중복 키가 있으면 무조건 제거해야 하나요?

아닙니다. 주문 표에서는 한 회원의 여러 주문이 정상이고, 회원 이력 표에서는 변경 시점별 행이 필요할 수 있습니다. 한 행의 의미를 확인한 뒤 원하는 시점이나 집계 단위에 맞춰 결합 조건을 정해야 합니다.

참고 자료와 확인 범위

함께 다듬는 노트

이해되지 않는 설명이 있었나요?

틀린 계산, 어려운 표현, 다음에 읽고 싶은 주제를 알려주세요.

의견 보내기