[ggplot2] grouping 원리

2025. 12. 31. 18:59·Data Science/Visualization

0. 개요 (Overview)

`group`은 무엇을 묶고, 어떻게 작동할까요?

ggplot2를 사용하다 보면 언젠가는 grouping 때문에 헷갈리는 순간이 한 번쯤은 오게 된다고 생각합니다.

group은 ggplot2의 미적 요소(aesthetic) 중 하나로, 어떤 관측치들을 하나의 그래픽 객체(선, 면, 상자 등)로 묶어 표현할지를 결정하는 데 사용됩니다.


1. ggplot2의 기본 grouping 규칙

ggplot2에서는 `group`을 명시적으로 지정하지 않으면 다음과 같은 규칙을 따릅니다.

그래프에 사용된 모든 이산형(discrete) 변수들의 상호작용(interaction)을
기본 group으로 자동 설정합니다.

 

예를 들어 다음 코드를 살펴보겠습니다.

diamonds |> 
  ggplot(aes(x = cut)) +
  geom_bar()

`cut`은 이산형 변수이므로, ggplot2는 내부적으로 cut 값이 같은 행들끼리 데이터를 묶어 각 막대를 하나씩 그립니다.

즉, 코드에 `group`을 명시적으로 작성하지 않았지만, 내부적으로 다음과 같은 grouping이 적용된 것입니다.

diamonds |> 
  ggplot(aes(x = cut)) +
  geom_bar(aes(group = cut))

그 결과 `cut`의 각 수준(level)마다 하나의 막대가 생성됩니다.


2. [예시] 선 그래프에서 다양한 grouping 설명

grouping은 특히 선 그래프를 그릴 때 더욱 헷갈릴 수 있습니다. 

이를 설명하기 위해 아래와 같은 게임 데이터가 존재한다고 생각해 봅시다.

library(tidyverse)

user <- expand_grid(
  user_id = str_c("user_", 1:5),
  date = ymd(20240101) + 0:6
) |> 
    group_by(user_id) |> 
    mutate(
        level = floor(runif(1, min=10, max=200)) + cumsum(floor(runif(7, 0, 21)))
    ) |> 
    ungroup()
  • 유저는 5명이며
  • 각 유저는 7일 동안의 기록을 가지고 있고
  • level은 시간에 지남에 따라 증가하도록 설정 

grouping 방식에 따른 차이를 설명하겠습니다.

 

1. group = 1

ggplot(user, aes(date, level)) + geom_line(aes(group=1))

이 경우 모든 관측치는 하나의 그룹으로 묶입니다. 즉, 하나의 선을 그리기 위해 전체 데이터를 모두 사용하게 됩니다.

geom_line()은 기본적으로 x축 값의 순서에 따라 점들을 연결합니다.
따라서 date 값을 기준으로 정렬된 후, 모든 관측치가 하나의 연속된 선으로 이어지게 됩니다.

그 결과, 서로 다른 user_id에 속한 값들까지도 모두 하나의 선으로 연결되며, 위와 같이 지그재그 형태의 단일 선 그래프가 그려집니다.

 

2. group = user_id

ggplot(user, aes(date, level)) + geom_line(aes(group = user_id))

이번에는 group을 user_id로 지정하였습니다. 이 경우 ggplot2는 user_id 값이 같은 관측치들끼리 하나의 그룹으로 묶습니다.

즉, 하나의 선을 그리기 위해 user_id가 동일한 데이터만 사용하게 됩니다.

데이터에는 총 5명의 유저가 존재하므로, 각 유저마다 하나의 선이 생성되어 총 5개의 선 그래프가 그려집니다.

geom_line()은 여전히 x축(date)의 값 순서에 따라 점들을 연결하므로, 각 유저의 날짜별 level 변화가 시간 흐름에 따라 자연스럽게 표현됩니다.

이 방식은 유저별 성장 추이처럼 개체별 변화를 보고 싶을 때 가장 일반적으로 사용됩니다.

 

3. group = date

ggplot(user, aes(date, level)) + geom_line(aes(group = date))

이번에는 group을 date로 지정해 보겠습니다.

이 경우 ggplot2는 같은 날짜에 해당하는 관측치들끼리 하나의 그룹으로 묶습니다.

즉, 하나의 선을 그리기 위해 같은 date 값을 가진 여러 유저의 level 값이 사용됩니다.

그 결과, 각 날짜마다 하나의 선이 만들어지고 x축의 동일한 날짜 위치에서 여러 유저의 level 값이 수직에 가까운 선으로 연결됩니다.

이러한 그래프는 시각적으로 보면 다소 어색하게 느껴질 수 있으며, 대부분의 경우 의도하지 않은 grouping 결과인 경우가 많습니다.

4. group을 지정하지 않으면?

ggplot(user, aes(date, level)) + geom_line()

이번에는 group을 명시적으로 지정하지 않은 경우입니다.

이때 ggplot2는 기본 grouping 규칙을 적용합니다.

앞서 설명한 것처럼, ggplot2는 그래프에 사용된 이산형(discrete) 변수들의 조합을 기본 group으로 자동 설정합니다.

하지만 이 예제에서 사용한 date는 연속형 변수이며, level 역시 연속형 변수입니다.

즉, 그래프에 사용된 이산형 변수가 존재하지 않습니다.

그 결과 ggplot2는 모든 관측치를 하나의 그룹으로 간주(group = 1)하여 처리합니다.


3. [예시] Boxplot에서 grouping

먼저 boxplot에서도 grouping이 어떻게 동작하는지 살펴보겠습니다.

iris |> ggplot(mapping=aes(x=Species, y=Sepal.Length)) + geom_boxplot()

이 코드에서는 group을 명시적으로 지정하지 않았지만, 각 품종(Species)별로 boxplot이 하나씩 그려집니다.

시각화에 사용된 변수 중 Species는 범주형(discrete) 변수이고,
ggplot2의 기본 규칙에 따라 범주형 변수는 자동으로 grouping 기준에 포함되기 때문입니다.

 

반면 아래 코드를 살펴봅시다.

mpg |> ggplot(mapping=aes(x=year, y=hwy)) + geom_boxplot()

의도는 year(연도)별로 hwy(고속도로 연비)에 대한 boxplot을 그리는 것입니다.

하지만 실제 결과를 보면, 연도별로 구분되지 않고 하나의 boxplot만 그려집니다.

이는 grouping의 기본 원리를 알지 못하면 쉽게 놓칠 수 있는 부분입니다.

 

이 코드에서 사용된 변수들을 보면,

  • year는 연속형 변수
  • hwy 역시 연속형 변수

즉, 시각화에 사용된 변수 중 범주형 변수가 존재하지 않습니다.

따라서 ggplot2는 기본 규칙에 따라 모든 관측치를 하나의 그룹으로 간주하며, `group=1`이 자동으로 적용됩니다.

 

그 결과, 연도별로 구분되지 않고 전체 데이터를 하나의 분포로 요약한 boxplot이 그려지게 됩니다.

만약 연도별로 박스플롯을 구분하기 위해서는 명시적으로 `group = year`를 기입해야합니다.

mpg |> 
  ggplot(aes(x = year, y = hwy)) +
  geom_boxplot(aes(group = year))

 


4. x축과 grouping

두 코드의 차이는 무엇일까요?

p1 <- ggplot(mpg, aes(displ, cty)) + geom_boxplot(aes(group = floor(displ)))
p2 <- ggplot(mpg, aes(factor(floor(displ)), cty)) + geom_boxplot()

두 코드 모두 displ(배기량)을 정수 단위로 묶어 cty(도시 연비)의 분포를 보고자 하는 코드입니다.

하지만 결과는 조금 다르게 나타납니다.

 

첫번째 p1의 경우 하나의 boxplot을 계산하기 위해 floor(displ) 값이 같은 데이터들을 묶어 사용합니다.

하지만 중요한 점은, x축은 여전히 연속형 변수 displ이라는 점입니다.

따라서 boxplot의 위치는 정수값이 아니라, 각 그룹에 속한 관측치들의 displ 값이 위치한 연속형 x축 좌표 위에 배치됩니다.

이 때문에 boxplot이 정수 중앙에 정확히 놓이지 않고, 어딘가 어긋난 위치에 그려지게 됩니다.

 

두번째 p2의 경우 x축 자체가 factor(floor(displ))로 지정되어 정수 단위의 범주형 축이 생성됩니다. 

또한 factor(floor(displ))는 범주형 변수이므로 ggplot2는 자동으로 group = factor(floor(displ))로 설정합니다.

그 결과, boxplot은 정수값(1, 2, 3, …)의 중앙에 정확히 배치되고 시각적으로도 의도에 맞는 결과를 얻게 됩니다.

'Data Science > Visualization' 카테고리의 다른 글

ggplot2에서 geom_rect의 투명도(alpha)가 적용되지 않는 이유와 해결법  (0) 2025.03.20
[Visualization with R] 1. Bar Chart (막대 그래프)  (0) 2023.09.07
[R] Scatterplot Matrix (산점도행렬)  (0) 2023.09.01
[R] ggplot2 Visualization  (0) 2023.03.29
'Data Science/Visualization' 카테고리의 다른 글
  • ggplot2에서 geom_rect의 투명도(alpha)가 적용되지 않는 이유와 해결법
  • [Visualization with R] 1. Bar Chart (막대 그래프)
  • [R] Scatterplot Matrix (산점도행렬)
  • [R] ggplot2 Visualization
임파카
임파카
[ML & Statistics] 모바일 버전에서 수식 오류가 있어 PC 환경에서 접속하는 것을 권장합니다.
  • 임파카
    무기의 스탯(Stat)
    임파카
  • 전체
    오늘
    어제
    • Study (151)
      • Data Science (45)
        • Modeling (18)
        • Manipulation (21)
        • Visualization (5)
      • Statistics (60)
        • Mathmetical Statistics (54)
        • Categorical DA (1)
      • Web Programming (17)
      • Marketing (0)
      • AI (26)
        • Machine Learning (16)
        • Deep Learning (10)
      • 활동 및 프로젝트 (3)
  • 인기 글

  • hELLO· Designed By정상우.v4.10.5
임파카
[ggplot2] grouping 원리
상단으로

티스토리툴바