날짜 다루기

개요

R에서 시계열 데이터(time-series data)를 이용해 도표를 작성할 때 날짜를 다루는 팁을 정리했습니다. 이번 포스팅에서는 국가별 코로나19 확진자 수 데이터를 활용해 날짜 처리 방법과 ggplot2를 활용한 시계열 그래프 작성법을 소개합니다. 데이터는 Our World in Data에서 가져온 데이터를 사용합니다.

날짜 데이터 처리의 어려움

날짜 데이터를 다루는 데 있어 다음과 같은 어려움이 있을 수 있습니다:

  • 다양한 형식: “2020-08-08”, “2020/08/08”, “20-08-08”, “2020년 8월 8일” 등 여러 형식이 있습니다
  • 순서 문제: 날짜 데이터를 문자형(character)으로 불러오면 정렬이 비정상적일 수 있습니다
  • 수학적 계산의 어려움: 날짜는 숫자형(numeric) 데이터와 달라서, 일반적인 ‘+’나’-’ 연산으로 기간을 계산할 수 없습니다

이 문제들을 해결하기 위해 lubridate 패키지를 활용할 수 있습니다. lubridate 공식 문서를 참고하세요.

데이터 준비 및 날짜 변환

이번에 사용하는 데이터는 국가별 코로나19 데이터로, 각 날짜별 신규 확진자 수(new_cases)를 포함하고 있습니다. 데이터의 주요 열은 다음과 같습니다:

  • date: 확진자가 보고된 날짜(문자형)
  • location: 국가명(예: South Korea)
  • new_cases: 해당 날짜의 신규 확진자 수

이 데이터를 이용해 우리나라의 신규 확진자 데이터를 필터링하고, 날짜를 처리해 보겠습니다.

# 패키지 로드
library(tidyverse)
library(lubridate)

# 데이터 불러오기
data <- read.csv("owid-covid-data.csv")

# 한국 데이터 필터링 및 열 선택
data_korea <- data %>% 
  filter(location=="South Korea") %>% 
  select(date, new_cases)

# 데이터 구조 확인
str(data_korea)
## 'data.frame':    1034 obs. of  2 variables:
##  $ date     : chr  "2020-01-22" "2020-01-23" "2020-01-24" "2020-01-25" ...
##  $ new_cases: num  NA 0 1 0 1 1 0 0 0 7 ...

이제 lubridateymd 함수를 사용하여 date 열을 문자형에서 날짜(Date) 클래스로 변환하겠습니다. 이 데이터는 2020년 1월 22일부터 2022년 11월 20일까지 한국의 신규 확진자 수 정보를 담고 있습니다. 신규 확진자 수가 적을 때는 0명, 많을 때는 621,317명까지도 나온 적이 있습니다.

# 날짜 변환
data_korea <- data_korea %>% 
  mutate(date=ymd(date))

# 데이터 요약
summary(data_korea)
##       date              new_cases     
##  Min.   :2020-01-22   Min.   :     0  
##  1st Qu.:2020-10-06   1st Qu.:   168  
##  Median :2021-06-21   Median :   967  
##  Mean   :2021-06-21   Mean   : 25733  
##  3rd Qu.:2022-03-06   3rd Qu.: 13004  
##  Max.   :2022-11-20   Max.   :621317  
##                       NA's   :1

x축 날짜 표시

x축에 날짜를 표시할 때는 scale_x_date를 사용합니다. 예를 들어, 한국의 신규 확진자 수를 나타내는 막대그래프는 아래와 같이 작성할 수 있습니다. 이 코드는 x축에 3개월 간격으로 날짜를 표시하고, 년-월 형식으로 라벨이 표시되도록 합니다.

ggplot(data=data_korea, aes(x=date, y=new_cases)) +
  geom_col() +
  scale_x_date(name="", 
               breaks=seq(ymd('2020-01-20'),ymd('2022-11-20'), by='3 month'), 
               date_labels="%Y-%m"
               ) +
  scale_y_continuous(name="신규 확진자 수(명)") +
  theme_bw()

한국의 신규 확진자 수 막대그래프

날짜 라벨 줄 바꿈 및 회전

날짜 라벨이 길어 겹치는 경우, 텍스트 줄 바꿈이나 회전을 통해 해결할 수 있습니다.

1. 라벨 줄 바꿈

labels 인자에 함수를 지정하여 날짜 형식을 변환합니다. 예를 들어, 년-월-일 형식을 년도와 월-일을 구분하여 줄 바꾸기를 할 수 있습니다.

ggplot(data=data_korea, aes(x=date, y=new_cases)) +
  geom_col() +
  scale_x_date(name="",
              breaks=seq(ymd('2020-01-20'),ymd('2022-11-20'), by='3 month'),
              labels=function(x) sub("-", "-\n", as.character(x)) # 년도 다음 대쉬 뒤 줄 바꿈
              ) + 
  scale_y_continuous(name="신규 확진자 수(명)") +
  theme_bw()

x축 텍스트 줄 바꾼 그래프

2. 라벨 회전

줄 바꿈 말고도, 텍스트를 회전하는 방법도 있습니다. theme 함수의 axis.text.x 옵션으로 라벨 각도를 설정할 수 있습니다.

ggplot(data=data_korea, aes(x=date, y=new_cases)) +
  geom_col() +
  scale_x_date(name="",
              breaks=seq(ymd('2020-01-20'),ymd('2022-11-20'), by='3 month'),
              ) + 
  scale_y_continuous(name="신규 확진자 수(명)") +
  theme_bw() +
  theme(
    axis.text.x=element_text(angle=45, hjust=1, vjust=1)
  )

x축 텍스트 45도 회전한 그래프

월 단위 데이터 처리

일 단위 시계열 데이터를 월 단위로 요약하고 싶다면, format 함수를 사용해 년월 열을 생성한 후 그룹화하여 집계하면 됩니다.

# 년월 열 생성 및 집계
data_korea_monthly <- data_korea %>%
  mutate(년월=format(date, "%Y-%m")) %>%
  group_by(년월) %>%
  summarise(new_cases=sum(new_cases, na.rm = TRUE))

# 데이터 확인
head(data_korea_monthly)
## # A tibble: 6 × 2
##   년월    new_cases
##   <chr>       <dbl>
## 1 2020-01        10
## 2 2020-02      3139
## 3 2020-03      6636
## 4 2020-04       988
## 5 2020-05       729
## 6 2020-06      1347

이후 scale_x_discrete을 사용해 x축을 설정하고, 라벨의 년도와 월을 줄 바꿈으로 표시할 수 있습니다.

ggplot(data_korea_monthly, aes(x=년월, y=new_cases)) +
  geom_col() +
  scale_x_discrete(name="",
                   labels = function(x) sub("-", "\n", x)
                   ) +
  scale_y_continuous(name="신규 확진자 수(명)") +
  theme_bw()

일 단위 데이터를 월 단위로 집계한 그래프

특정 월에만 년도 표시

1월에만 년도를 표기하고 싶다면, scale_x_discretelabels 인자를 활용하면 됩니다.

  • 대쉬 뒤 줄 바꿈: sub 함수 이용
  • 1월 해당 여부 파악: grepl 함수 이용
  • 1월 해당하지 않으면 년도 삭제: ifelse, sub 함수 이용
ggplot(data=data_korea_monthly, aes(x=년월, y=new_cases)) +
  geom_col() +
  scale_x_discrete(name="",
                   labels=function(x){
                     y=sub("-", "\n", x)
                     ifelse(!grepl(".*(01)$", y), sub("^\\d{4}", "", y), y)
                   }) +
  scale_y_continuous(name="신규 확진자 수(명)") +
  theme_bw()

1월에만 년도를 표시한 그래프

자잘한 수정

그래프를 좀 더 예쁘게 만들어 보겠습니다.

  • y축 숫자 단위 만 명으로 수정
  • 막대그래프가 x축과 맞닿도록 수정
  • 막대그래프 위에 숫자 표시
  • 글꼴 및 색상 변경
# 글꼴 변경
library(showtext)

font_add_google("Noto Sans KR", "noto")

showtext_auto()
showtext_opts(dpi=300)

theme.size = 10
geom.text.size = theme.size/.pt 

# 그래프 작성
ggplot(data=data_korea_monthly, 
       aes(x=년월, y=new_cases/10000, 
           label=scales::comma(new_cases/10000, accuracy=1))
       ) +
  geom_col(fill="#9AD1F5") +
  geom_text(size=geom.text.size, family="noto", vjust=-0.5) +
  scale_x_discrete(name="",
                   labels=function(x){
                     y=sub("-", "\n", x)
                     ifelse(!grepl(".*(01)$", y), sub("^\\d{4}", "", y), y)
                   }) +
  scale_y_continuous(name="신규 확진자 수(만 명)",
                     labels=scales::comma_format(),
                     expand=expansion(mult=c(0, 0.2))) +
  theme_bw(base_size=theme.size, base_family="noto")

한국 월별 코로나19 신규 확진자 수 그래프

위에서 소개한 방법을 활용하면 R에서 날짜 데이터를 효과적으로 처리하고, ggplot2를 이용해 가독성 높은 시각화를 제작할 수 있습니다. 코로나19 데이터를 활용해 날짜 데이터를 다루는 연습을 해보고, 다양한 데이터셋에 적용해 보세요. 날짜 데이터는 까다롭지만, 적절한 도구를 사용하면 쉽고 간편하게 다룰 수 있습니다. 이제 시계열 데이터를 활용한 멋진 그래프를 만들어 보세요!