Menu

R에서 데이터 재구조화: pivot_wider와 pivot_longer (넓은 형식 vs 긴 형식)

넓은 형식과 긴 형식, 그리고 둘 사이를 변환하는 방법: tidyr의 pivot_longer()와 pivot_wider(), 예전 이름인 spread/gather, 그리고 base R의 reshape().

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

넓은 형식 vs 긴 형식: 같은 데이터, 두 가지 형태

모든 재구조화 문제는 하나의 구분으로 귀결되므로 직접 눈으로 봅시다. 아래는 작은 데이터셋 하나 - 두 도시의 분기별 매출 - 를 두 형태로 만든 것입니다:

정보는 같지만 기하 구조가 다릅니다. 넓은 형식 은 도시마다 한 행, 분기마다 한 열을 가지며 분기가 열 이름 안에 들어 있습니다. 긴 형식 은 관측(도시 × 분기)마다 한 행을 가지며, 분기가 평범한 열로 내려오고 모든 측정값이 하나의 sales 열에 담깁니다.

어느 형태도 "정답"은 아닙니다 - 섬기는 주인이 다를 뿐입니다. 넓은 형식은 사람이 읽고 스프레드시트가 내보내는 형태입니다: 조밀하고 한눈에 비교됩니다. 긴 형식은 도구가 소비하는 형태이고, 둘 사이를 오가는 것을 피벗(또는 재구조화 - 같은 말입니다)이라고 합니다.

분석에서 긴 형식이 이기는 이유

넓은 형식이 "분기"라는 개념에 무슨 짓을 했는지 보세요: 변수 하나를 열 이름 으로 갈가리 찢었습니다. 열 이름으로는 아무것도 계산할 수 없습니다. 긴 형식에서는 quarter가 다시 데이터가 되고, 그룹 분석 도구 상자의 모든 기능이 켜집니다:

  • aggregate(sales ~ quarter, ...)group_by(quarter) - 넓은 형식에서는 분기마다 별도 열이라 손으로 처리해야 하므로 불가능합니다(그룹 요약은 모두 긴 형식 데이터를 전제합니다).
  • ggplot2 을 미적 요소에 매핑합니다: aes(x = quarter, y = sales, color = city)가 동작하려면 그 세 열이 존재해야 합니다. 넓은 데이터를 그리는 것은 끊임없는 싸움이지만, 긴 데이터를 그리는 것은 한 줄입니다.
  • Q3를 추가하는 것은 을 추가하는 일이라 스키마가 바뀌지 않습니다. 반면 넓은 형식은 열이 늘어나므로 하류의 모든 단계가 그 사실을 새로 배워야 합니다.

실용적인 규칙은 이렇습니다: 긴 형식으로 저장·분석하고, 넓은 형식으로 제시하라. 그래서 아래의 두 변환이 그토록 자주 쓰입니다 - 데이터는 스프레드시트에서 넓은 형식으로 도착해 분석을 위해 길어지고, 보고서의 최종 표를 위해 다시 넓어집니다.

넓은 형식 → 긴 형식: pivot_longer()

tidyr 패키지(tidyverse 안에서 dplyr의 형제)가 현대적 재구조화를 담당합니다. 여기의 예제는 정적입니다 - 샌드박스는 base R만 실행합니다. 길게 만들려면 세 가지를 결정해야 하고, 각각 인자 하나에 대응합니다:

library(tidyr)

long <- pivot_longer(wide,
    cols      = c(Q1, Q2),      # which columns to stack
    names_to  = "quarter",      # new column that receives the old NAMES
    values_to = "sales"         # new column that receives the cell VALUES
)

하나씩 짚어 봅시다: cols는 해체할 열을 지정합니다(Q1:Q2 범위 문법이나 starts_with("Q") 같은 헬퍼도 동작합니다 - 열이 스무 개쯤 될 때 유용합니다). 선택된 각 셀이 한 행이 되고, 셀이 있던 열 이름은 quarter로, 숫자 자체는 sales로 들어갑니다. cols에 나열되지 않은 열(여기서는 city)은 식별자로 취급되어 행마다 반복됩니다. 결과는 위에서 출력한 long 데이터 프레임과 정확히 같습니다.

긴 형식 → 넓은 형식: pivot_wider()

반대 방향에는 두 가지 결정이 필요합니다 - 새 열 이름이 어디서 오는지, 그리고 무엇으로 채울지:

wide <- pivot_wider(long,
    names_from  = quarter,   # distinct values here become new columns
    values_from = sales      # these values fill the cells
)

quarter의 서로 다른 값(Q1, Q2)이 각각 열이 되고, 각 셀은 해당 도시와 분기에 맞는 행의 sales 값으로 채워집니다. 나머지 열(city)은 행 식별자 역할을 하며, 서로 다른 조합마다 출력 행이 하나씩 생깁니다. 어떤 도시에 특정 분기가 없으면 그 셀은 NA가 됩니다(values_fill 인자로 다른 값을 대신 넣을 수 있습니다. 예: 개수 데이터에는 values_fill = 0).

오래된 튜토리얼에서는 이전 세대도 만나게 됩니다: spread()pivot_wider(), gather()pivot_longer()입니다 - tidyr 1.0 이후 대체되었고 여전히 동작하지만, 알아보는 정도 이상으로 배울 가치는 없습니다.

base R에는 reshape()가 있습니다 - 솔직한 경고

base R도 패키지 없이 이 일을 할 수 있습니다. reshape()인데, 워낙 혼란스럽기로 유명해서 과거에는 공식 문서조차 사과문을 실었을 정도입니다. 종단 연구 용어(idvar, timevar, direction)를 중심으로 설계되어 인자 이름이 일상적인 데이터에 어색하게 대응되고, 쓸 때마다 다들 다시 잊어버립니다. 동작하기는 합니다:

출력 이름을 보세요: sales.Q1, sales.Q2 - 값 열의 이름이 각각에 붙어 버렸습니다. 급할 때, 의존성이 전혀 없는 환경에서, 또는 옛 코드에서 마주칠 때는 괜찮습니다. 하지만 1년에 한 번 이상 데이터를 재구조화한다면 install.packages("tidyr")가 솔직한 권고입니다 - 데이터 가공 작업 중에서 base R의 도구가 아껴 주는 의존성보다 실제로 더 큰 비용을 치르는 유일한 영역입니다.

넓힐 때의 중복 키 함정

넓히기는 식별자와 이름의 각 조합이 하나 의 값을 지목한다고 가정합니다. Lima에 Q1 행이 두 개라면 단 하나뿐인 Q1 셀에는 어느 숫자가 들어가야 할까요? pivot_wider()는 추측하지 않습니다: 경고(values are not uniquely identified)를 내고 셀에 리스트 열 을 넣습니다 - 리스트가 중첩된 데이터 프레임이 되어 그다음에 하려는 작업이 깨집니다.

그 경고를 보면 values_fn이라는 탈출구부터 찾지 말고 중복이 있는지 물으세요. 대개는 데이터의 단위가 생각보다 잘게 쪼개져 있거나(분기가 아니라 일 단위 행 - 그렇다면 먼저 요약한 뒤 넓히세요) 상류의 조인이 행을 중복시킨 경우입니다. values_fn = mean(또는 sum)은 중복을 어떤 집계로 합쳐야 하는지 소리 내어 말할 수 있을 때에만 정당한 해결책입니다. reshape()는 여기서 더 나쁩니다: 첫 번째 중복만 조용히 유지하고 나머지를 버리며, 쉽게 지나칠 수 있는 경고만 남깁니다.

핵심 정리

  • 넓은 형식: 변수가 열 이름에 숨어 있고 읽기용입니다. 긴 형식: 관측마다 한 행이며 분석과 ggplot2용입니다.
  • 긴 형식으로 저장·분석하고, 넓은 형식으로 제시하세요.
  • pivot_longer(cols, names_to, values_to)는 열을 행으로 쌓고, 나열하지 않은 열은 반복되는 식별자가 됩니다.
  • pivot_wider(names_from, values_from)은 행을 열로 펼치고, 빈 자리는 NA로 채웁니다.
  • spread()/gather()는 옛 이름이고, base R의 reshape()는 동작하지만 어색하기로 유명합니다.
  • 식별자와 이름 쌍이 중복되면 넓히기가 모호해집니다 - 경고를 잠재우지 말고 먼저 요약하세요.

다음 주제: 파일에서 실제 데이터 읽어 오기 - read.csv()와 그 날카로운 모서리들.

자주 묻는 질문

R에서 넓은 형식과 긴 형식 데이터의 차이는 무엇인가요?

같은 데이터, 다른 형태입니다. 넓은 형식은 한 변수를 열들로 펼칩니다(예: 분기마다 열 하나). 대상마다 한 행이며 사람이 읽기 좋게 만들어졌습니다. 긴 형식은 그것을 행으로 쌓습니다. 관측마다 한 행이며 이름 열과 값 열을 두어 그룹 분석과 ggplot2에 적합합니다.

R에서 넓은 형식 데이터를 긴 형식으로 어떻게 바꾸나요?

tidyr의 pivot_longer(df, cols = Q1:Q4, names_to = "quarter", values_to = "sales")는 선택한 열들을 새 열 두 개로 쌓습니다. 기존 열 이름은 names_to 열로, 셀 값은 values_to 열로 들어갑니다.

R에서 긴 형식 데이터를 넓은 형식으로 어떻게 바꾸나요?

tidyr의 pivot_wider(df, names_from = quarter, values_from = sales)는 행을 열로 펼칩니다. names_from의 서로 다른 값이 각각 열이 되고 대응하는 values_from 항목으로 채워집니다. 없는 조합은 NA가 됩니다.

R에서 spread와 gather는 무엇으로 대체되었나요?

tidyr 1.0(2019)에서 pivot_wider()spread()를, pivot_longer()gather()를 대체했습니다. 예전 함수도 여전히 동작하며 오래된 튜토리얼에서 볼 수 있지만, 새 코드는 인자가 더 명확하고 기능이 많은 pivot_* 쌍을 써야 합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기