Ch 08. 피벗과 재구성 (pivot_longer, pivot_wider)
같은 데이터라도 형태에 따라 분석과 시각화에 적합한 모습이 다릅니다. ggplot2는 세로로 긴 형태(tidy data)를 선호하지만, 엑셀 보고서는 가로로 넓은 형태가 읽기 편합니다. 두 형태를 자유롭게 전환하는 것이 pivot_longer()와 pivot_wider()의 역할입니다.
넓은 형태와 긴 형태
학생별 월간 점수 데이터를 예로 들겠습니다.
library(tidyverse)
# 넓은 형태 (wide format)
scores_wide <- tibble(
name = c("김철수", "이영희", "박민준"),
jan = c(85, 92, 78),
feb = c(88, 87, 82),
mar = c(91, 95, 75)
)
scores_wide
# A tibble: 3 × 4
name jan feb mar
<chr> <dbl> <dbl> <dbl>
1 김철수 85 88 91
2 이영희 92 87 95
3 박민준 78 82 75
이 형태는 한눈에 읽기 좋습니다. 그런데 ggplot2로 월별 추세를 그리거나 group_by(month)로 월별 평균을 내려면 아래와 같은 긴 형태가 필요합니다.
name month score
김철수 jan 85
김철수 feb 88
김철수 mar 91
이영희 jan 92
...
pivot_longer()로 넓은 → 긴 형태
pivot_longer()는 여러 열을 하나의 열로 모읍니다.
scores_long <- scores_wide %>%
pivot_longer(
cols = c(jan, feb, mar), # 모을 열
names_to = "month", # 열 이름이 들어갈 새 열
values_to = "score" # 값이 들어갈 새 열
)
scores_long
# A tibble: 9 × 3
name month score
<chr> <chr> <dbl>
1 김철수 jan 85
2 김철수 feb 88
3 김철수 mar 91
4 이영희 jan 92
5 이영희 feb 87
6 이영희 mar 95
7 박민준 jan 78
8 박민준 feb 82
9 박민준 mar 75
3행 4열이 9행 3열로 바뀌었습니다. 이 형태에서 월별 평균을 바로 구할 수 있습니다.
scores_long %>%
group_by(month) %>%
summarise(avg_score = mean(score))
# A tibble: 3 × 2
month avg_score
<chr> <dbl>
1 feb 85.7
2 jan 85
3 mar 87
cols 지정 방법
cols에는 여러 방식으로 열을 지정합니다.
# 열 이름으로
pivot_longer(scores_wide, cols = c(jan, feb, mar), ...)
# 제외할 열로 지정 (name 열 제외 = 나머지 모두)
pivot_longer(scores_wide, cols = -name, ...)
# starts_with 사용
pivot_longer(scores_wide, cols = starts_with("j"), ...)
# 범위로
pivot_longer(scores_wide, cols = jan:mar, ...)
실무에서는 cols = -name 패턴을 많이 씁니다. 열이 추가되어도 코드를 바꾸지 않아도 됩니다.
pivot_wider()로 긴 → 넓은 형태
pivot_wider()는 반대 방향입니다. 긴 형태를 넓게 펼칩니다.
scores_wide_again <- scores_long %>%
pivot_wider(
names_from = "month", # 열 이름이 될 열
values_from = "score" # 값이 될 열
)
scores_wide_again
# A tibble: 3 × 4
name jan feb mar
<chr> <dbl> <dbl> <dbl>
1 김철수 85 88 91
2 이영희 92 87 95
3 박민준 78 82 75
원래 형태로 돌아왔습니다. 보고서 출력이나 엑셀 저장 전에 pivot_wider()로 변환하면 읽기 좋은 형태가 됩니다.
실전 예제: 지역별 연도별 데이터
좀 더 복잡한 경우입니다. 지역별, 연도별 판매 데이터입니다.
sales <- tibble(
region = rep(c("서울", "부산", "대구"), each = 3),
year = rep(c(2022, 2023, 2024), times = 3),
sales = c(1200, 1350, 1500, 800, 870, 920, 650, 700, 780)
)
sales
# A tibble: 9 × 3
region year sales
<chr> <dbl> <dbl>
1 서울 2022 1200
2 서울 2023 1350
3 서울 2024 1500
4 부산 2022 800
5 부산 2023 870
6 부산 2024 920
7 대구 2022 650
8 대구 2023 700
9 대구 2024 780
이 긴 형태를 지역을 행으로, 연도를 열로 하는 넓은 형태로 변환합니다.
sales %>%
pivot_wider(
names_from = year,
values_from = sales
)
# A tibble: 3 × 4
region `2022` `2023` `2024`
<chr> <dbl> <dbl> <dbl>
1 서울 1200 1350 1500
2 부산 800 870 920
3 대구 650 700 780
지역별 연도별 매출 비교 표가 완성됩니다. 열 이름에 숫자가 오면 백틱(`)으로 감싸야 합니다.
separate()로 열 분리
하나의 열에 두 가지 정보가 합쳐져 있을 때 분리합니다.
combined <- tibble(
info = c("서울_2022", "부산_2023", "대구_2024"),
value = c(1200, 870, 780)
)
combined %>%
separate(info, into = c("region", "year"), sep = "_")
# A tibble: 3 × 3
region year value
<chr> <chr> <dbl>
1 서울 2022 1200
2 부산 2023 870
3 대구 2024 780
sep에는 구분자 문자열이나 정규표현식을 씁니다. 위치 기반 분리도 됩니다.
# 앞 2자리를 연도로, 나머지를 코드로
tibble(code = c("22SEOUL", "23BUSAN")) %>%
separate(code, into = c("year", "city"), sep = 2)
# A tibble: 2 × 2
year city
<chr> <chr>
1 22 SEOUL
2 23 BUSAN
unite()로 열 합치기
separate()의 반대입니다. 두 열을 하나로 합칩니다.
split_data <- tibble(
region = c("서울", "부산", "대구"),
year = c(2022, 2023, 2024)
)
split_data %>%
unite("region_year", region, year, sep = "_")
# A tibble: 3 × 1
region_year
<chr>
1 서울_2022
2 부산_2023
3 대구_2024
pivot_longer와 pivot_wider를 활용한 데이터 변환 흐름
실무에서 자주 등장하는 패턴입니다. 넓은 형태로 받아서 분석하고, 결과를 다시 넓은 형태로 출력합니다.
# 1. 넓은 형태로 시작
scores_wide
# 2. 긴 형태로 변환 → 분석
result <- scores_wide %>%
pivot_longer(-name, names_to = "month", values_to = "score") %>%
group_by(month) %>%
summarise(avg = mean(score), max = max(score), min = min(score))
result
# A tibble: 3 × 4
month avg max min
<chr> <dbl> <dbl> <dbl>
1 feb 85.7 92 82
2 jan 85 92 78
3 mar 87 95 75
# 3. 보고서용 넓은 형태로 출력
result %>%
pivot_wider(names_from = month, values_from = c(avg, max, min))
# A tibble: 1 × 9
avg_feb avg_jan avg_mar max_feb max_jan max_mar min_feb min_jan min_mar
<dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1 85.7 85 87 92 92 95 82 78 75
PART 03을 모두 마쳤습니다. tibble부터 시작해서 데이터를 읽고, 필터링하고, 결측치를 처리하고, 변환하고, 집계하고, 합치고, 형태를 바꾸는 전 과정을 다루었습니다.
이제 정제된 데이터로 시각화에 도전할 준비가 되었습니다. 다음 파트에서는 ggplot2로 데이터를 그림으로 표현합니다.