혼합형그래프 축 조정 및 범례 표기하기
개요
이번 글에서는 선그래프와 막대그래프를 하나의 도표에 결합하여 데이터를 시각화하는 방법을 소개합니다. 이를 통해 두 가지 데이터를 한눈에 비교할 수 있습니다. 예제에서는 막대그래프로 서울특별시의 주민등록인구 수를, 선그래프로 전국에서 서울의 인구 비중을 표현해 보겠습니다. 그래프의 값이 큰 차이를 보일 경우 두 데이터를 효과적으로 표시하는 방법과 자동으로 생성되지 않는 범례를 추가하는 방법도 함께 알아보겠습니다.
데이터 준비하기
필요한 패키지를 로드한 뒤, 데이터를 적절히 가공합니다. 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_continuous에 sec.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"
)

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