혼합형그래프 축 조정 및 범례 표기하기

개요

이번 글에서는 선그래프와 막대그래프를 하나의 도표에 결합하여 데이터를 시각화하는 방법을 소개합니다. 이를 통해 두 가지 데이터를 한눈에 비교할 수 있습니다. 예제에서는 막대그래프로 서울특별시의 주민등록인구 수를, 선그래프로 전국에서 서울의 인구 비중을 표현해 보겠습니다. 그래프의 값이 큰 차이를 보일 경우 두 데이터를 효과적으로 표시하는 방법과 자동으로 생성되지 않는 범례를 추가하는 방법도 함께 알아보겠습니다.

데이터 준비하기

필요한 패키지를 로드한 뒤, 데이터를 적절히 가공합니다. ggplot2 패키지를 활용해 막대그래프와 선그래프를 결합한 시각화를 구현할 예정입니다.

# 패키지 로드
library(readxl)
library(tidyverse)
library(ggplot2)
library(showtext)
library(scales)

# 차트 글꼴 설정
font_add("kopub", "C:/Users/.../AppData/Local/Microsoft/Windows/Fonts/KoPub Dotum Medium.ttf")
showtext_auto()
showtext_opts(dpi=300)

theme.size = 12
text.size = theme.size / .pt

1995년부터 2023년까지의 시도별 주민등록인구 데이터를 사용합니다. 데이터를 로드하고, 주민등록인구 수를 만 명 단위로 변환합니다. 이후 전국 대비 서울의 인구 비중을 계산하며, 그래프를 간소화하기 위해 특정 연도만 추출합니다.

# 데이터 로드
data <- read_xlsx("데이터/주민등록인구_시도_1995-2023.xlsx")

# 데이터 가공
data <- data %>% 
  rename(시도 = 행정구역별,
         주민등록인구수 = `계 (명)`) %>% 
  filter(시도 != "전국") %>% 
  mutate(시점 = as.character(시점),
         주민등록인구수 = as.numeric(주민등록인구수) / 10000) %>% 
  group_by(시점) %>% 
  mutate(비율 = 주민등록인구수 / sum(주민등록인구수, na.rm = T)) %>% 
  filter(시도 == "서울특별시",
         시점 %in% as.character(seq(1997, 2023, by = 5)))

# 데이터 확인
head(data)
## # A tibble: 6 × 4
## # Groups:   시점 [6]
##   시도       시점  주민등록인구수  비율
##   <chr>      <chr>          <dbl> <dbl>
## 1 서울특별시 1997           1034. 0.221
## 2 서울특별시 2002           1021. 0.212
## 3 서울특별시 2007           1019. 0.207
## 4 서울특별시 2012           1020. 0.200
## 5 서울특별시 2017            986. 0.190
## 6 서울특별시 2022            943. 0.183

막대그래프와 선그래프 그리기

서울특별시 주민등록인구 수를 막대그래프로, 인구 비중을 선그래프로 표시합니다. 두 값의 차이가 크기 때문에 선그래프가 잘 보이지 않는 문제가 있습니다. 이 문제를 해결하기 위해 선그래프 위치를 조정해 줘야 합니다.

data %>% 
  ggplot(aes(x = 시점)) +
  geom_col(aes(y = 주민등록인구수)) +
  geom_line(aes(y = 비율,
                group = 1),
            linewidth = 0.4) +
  geom_point(aes(y = 비율),
             size = 3) +
  scale_x_discrete(name = "") +
  scale_y_continuous(name = "",
                     expand = expansion(mult = c(0, 0.2)),
                     labels = comma_format()) +
  theme_minimal(base_size = theme.size, base_family = "kopub") +
  theme(
    axis.line = element_line(linewidth = 0.5, color = "gray10"),
    axis.ticks = element_line(linewidth = 0.1, color = "gray10"),
    
    panel.grid.minor = element_blank(),
    
    axis.text = element_text(color = "gray10")
    )

스케일 보정하기

막대그래프의 끝부분과 선그래프가 적절히 조화를 이루도록, 두 데이터의 평균값을 활용해 위치를 계산하여 선그래프의 위치를 조정할 수 있습니다. 먼저, 주민등록인구 수 평균을 비율 평균으로 나눈 값을 계산합니다. 이렇게 계산된 값은 막대그래프와 선그래프의 스케일 차이를 보정하는 데 사용되며, 막대그래프의 끝부분 근처에서 선그래프가 나타날 가능성이 높아집니다. 하지만 선그래프가 막대그래프의 70% 정도 높이에 위치하는 것이 더 효과적이라고 판단하여, 이 값에 추가로 0.7을 곱해 coeff 변수에 저장합니다. 그다음, 이 coeff 변수를 선그래프와 포인트그래프의 y 값에 곱해 위치를 조정합니다.

coeff <- mean(data$주민등록인구수)/mean(data$비율) * 0.7

data %>% 
  ggplot(aes(x = 시점)) +
  geom_col(aes(y = 주민등록인구수),
           width = 0.7) +
  geom_line(aes(y = 비율 * coeff,
                group = 1),
            linewidth = 0.4) +
  geom_point(aes(y = 비율 * coeff),
             size = 3) +
  scale_x_discrete(name = "") +
  scale_y_continuous(name = "주민등록인구 수(만 명)",
                     expand = expansion(mult = c(0, 0.2)),
                     labels = comma_format()) +
  theme_minimal(base_size = theme.size, base_family = "kopub") +
  theme(
    axis.line = element_line(linewidth = 0.5, color = "gray10"),
    axis.ticks = element_line(linewidth = 0.1, color = "gray10"),
    
    panel.grid.minor = element_blank(),
    
    axis.text = element_text(color = "gray10")
    )

이중축 추가 및 텍스트 표기하기

선그래프와 막대그래프가 서로 다른 단위를 사용하므로, 주민등록인구 비율에 해당하는 보조축을 추가합니다. scale_y_continuoussec.axis를 추가하여 주민등록인구 비율을 나타내는 보조축을 생성했습니다. 이 축은 값을 coeff로 나눠 원래의 스케일로 복원하여 표시합니다. 그리고 geom_text를 사용해 주민등록인구 수와 비율 값을 각각 막대그래프와 선그래프 위에 텍스트로 표시합니다.

coeff <- mean(data$주민등록인구수)/mean(data$비율) * 0.7

data %>% 
  ggplot(aes(x = 시점)) +
  geom_col(aes(y = 주민등록인구수),
           width = 0.7) +
  geom_text(aes(y = 주민등록인구수, label = comma(주민등록인구수, accuracy = 1)),
            vjust = -1,
            family = "kopub",
            size = text.size) +
  geom_line(aes(y = 비율 * coeff,
                group = 1),
            linewidth = 0.4) +
  geom_point(aes(y = 비율 * coeff),
             size = 3) +
  geom_text(aes(y = 비율 * coeff, label = percent(비율, accuracy = .1, suffix = "")),
            vjust = -1,
            family = "kopub",
            size = text.size) +
  scale_x_discrete(name = "") +
  scale_y_continuous(name = "주민등록인구 수(만 명)",
                     expand = expansion(mult = c(0, 0.2)),
                     labels = comma_format(),
                     sec.axis = sec_axis(~./coeff, name = "주민등록인구 비율(%)",
                        labels = scales::percent_format(suffix = ""))) +
  theme_minimal(base_size = theme.size, base_family = "kopub") +
  theme(
    axis.line = element_line(linewidth = 0.5, color = "gray10"),
    axis.ticks = element_line(linewidth = 0.1, color = "gray10"),
    
    panel.grid.minor = element_blank(),
    
    axis.text = element_text(color = "gray10")
    )

범례 표기하기

색상을 설정하고 범례를 추가하여 그래프를 더욱 명확하게 만들겠습니다. 색상은 scale_fill_manual 함수의 values 매개변수를 이용해 지정하고, 범례는 geom_col에서 aes(fill = "주민등록인구 수")를 지정해 추가합니다. 선그래프도 동일하게 설정해 각 그래프의 의미를 쉽게 구분할 수 있도록 합니다. 또한, 범례 제목은 표시하지 않고 범례를 하단에 배치하여 그래프가 깔끔하게 보이도록 조정합니다.

coeff <- mean(data$주민등록인구수)/mean(data$비율) * 0.7

data %>% 
  ggplot(aes(x = 시점)) +
  geom_col(aes(y = 주민등록인구수, fill = "주민등록인구 수"),
           width = 0.7) +
  geom_text(aes(y = 주민등록인구수, label = comma(주민등록인구수, accuracy = 1)),
            vjust = -1,
            family = "kopub",
            size = text.size) +
  geom_line(aes(y = 비율 * coeff,
                group = 1,
                color = "주민등록인구 비율"),
            linewidth = 0.4) +
  geom_point(aes(y = 비율 * coeff,
                 color = "주민등록인구 비율"),
             shape = 21,
             size = 3,
             fill = "white") +
  geom_text(aes(y = 비율 * coeff, label = percent(비율, accuracy = .1, suffix = "")),
            vjust = -1,
            family = "kopub",
            size = text.size) +
  scale_fill_manual(values = "#CD5988") +
  scale_color_manual(values = "#21272F") +
  scale_x_discrete(name = "") +
  scale_y_continuous(name = "주민등록인구 수(만 명)",
                     expand = expansion(mult = c(0, 0.2)),
                     labels = comma_format(),
                     sec.axis = sec_axis(~./coeff, name = "주민등록인구 비율(%)",
                        labels = scales::percent_format(suffix = ""))) +
  theme_minimal(base_size = theme.size, base_family = "kopub") +
  theme(
    axis.line = element_line(linewidth = 0.5, color = "gray10"),
    axis.ticks = element_line(linewidth = 0.1, color = "gray10"),
    
    panel.grid.minor = element_blank(),
    
    axis.text = element_text(color = "gray10"),
    
    legend.title = element_blank(),
    legend.position = "bottom"
    )

이번 글에서 다룬 방법은 값의 차이가 큰 두 데이터를 한 그래프에 효과적으로 표현하거나, 혼합형 그래프에 범례를 추가할 때 활용할 수 있습니다. 데이터를 시각적으로 더 명확하고 직관적으로 전달해야 할 때, 이러한 기법을 적용해 보세요!