본문 바로가기
  • plotly로 바로쓰는 동적시각화 in R & 파이썬
데이터 전처리

일별(일간) 데이터를 주별(주간) 데이터로 만들기 in R

by 아참형인간 2022. 6. 18.
timeperiod_week.knit

사용데이터 : https://2stndard.tistory.com/68

lubridate와 tsibble를 사용하는 기간별 합계값 구하기 - 주별 데이터

이전 포스트에서는 lubridatezoo를 사용하여 일간 데이터를 월간 데이터로 변환하는 방법을 알아보았다. 앞서도 언급했지만 우리가 사용하는 데이터는 주로 숫자나 문자로 이루어져 있고 대량의 데이터가 저장되어 있는 DB나 다른 데이터 소스에서 데이터를 추출할 때는 csv 파일이나 엑셀 파일 등으로 추출하고 R에서 불러들이는 것이 일반적인데 이 경우 흔히 날짜 데이터를 문자열 형태로 불러 읽어들이는 경우도 많다. 앞선 포스트에서와 같이 월별 데이터로 변환할 때는 문자열 형태로 설정된 날짜 데이터도 이 문자열을 잘 다루면 연별, 월별로 그룹화하여 변환하는 것이 가능하다. 하지만 주별 데이터로 변환하는 것은 문자열 형태의 날짜 데이터에서는 매우 어렵다. 그렇다면 주별 데이터는 어떻게 변환할 것인가?

주별 데이터도 앞선 월별 데이터와 거의 유사한 방식으로 전환이 가능하다.

주별 데이터의 변환에도 앞서 사용했던 코로나19의 일별 데이터인 ’df_covid19’를 사용하는데 주별 데이터는 좀 많으니 다음과 같이 정제하여 사용하도록 하겠다.

df_covid19_by_period <- df_covid19 |> 
  ## 한국 데이터와 각 대륙별 데이터만을 필터링
  filter(iso_code %in% c('KOR', 'OWID_ASI', 'OWID_EUR', 'OWID_OCE', 'OWID_NAM', 'OWID_SAM', 'OWID_AFR')) |>
  ## 읽은 데이터의 마지막 데이터에서 100일전 데이터까지 필터링
  filter(date >= as.Date('2021-06-01')) |>
  ## 국가명을 한글로 변환
  mutate(location = case_when(
    location == 'South Korea' ~ '한국', 
    location == 'Asia' ~ '아시아', 
    location == 'Europe' ~ '유럽', 
    location == 'Oceania' ~ '오세아니아', 
    location == 'North America' ~ '북미', 
    location == 'South America' ~ '남미', 
    location == 'Africa' ~ '아프리카')) |>
  ## 국가 이름의 순서를 설정 
  mutate(location = fct_relevel(location, '한국', '아시아', '유럽', '북미', '남미', '아프리카', '오세아니아')) |>
  ## 날짜로 정렬
  arrange(date)

head(df_covid19_by_period, 10) 
## # A tibble: 10 x 67
##    iso_code continent location date       total_cases new_cases new_cases_smoot~
##    <chr>    <chr>     <fct>    <date>           <dbl>     <dbl>            <dbl>
##  1 OWID_AFR <NA>      아프리카 2021-06-01     4850455     10120           11355.
##  2 OWID_ASI <NA>      아시아   2021-06-01    51481216    206374          242658.
##  3 OWID_EUR <NA>      유럽     2021-06-01    47255809     47249           52319 
##  4 OWID_NAM <NA>      북미     2021-06-01    39073831     37619           30353.
##  5 OWID_OCE <NA>      오세아~  2021-06-01       68669        43             145.
##  6 OWID_SAM <NA>      남미     2021-06-01    28957718    158438          140167 
##  7 KOR      Asia      한국     2021-06-01      141476       677             542 
##  8 OWID_AFR <NA>      아프리카 2021-06-02     4864277     13822           11300 
##  9 OWID_ASI <NA>      아시아   2021-06-02    51693192    211976          231289.
## 10 OWID_EUR <NA>      유럽     2021-06-02    47300174     44365           50056 
## # ... with 60 more variables: total_deaths <dbl>, new_deaths <dbl>,
## #   new_deaths_smoothed <dbl>, total_cases_per_million <dbl>,
## #   new_cases_per_million <dbl>, new_cases_smoothed_per_million <dbl>,
## #   total_deaths_per_million <dbl>, new_deaths_per_million <dbl>,
## #   new_deaths_smoothed_per_million <dbl>, reproduction_rate <dbl>,
## #   icu_patients <dbl>, icu_patients_per_million <dbl>, hosp_patients <dbl>,
## #   hosp_patients_per_million <dbl>, weekly_icu_admissions <dbl>, ...

앞선 포스트에서는 월별 합계를 구했지만 이번에는 주단위로 평균값을 구해보도록 하겠다.

lubridate의 year(), week()를 사용

’date’형의 열을 주별로 그룹화하기 위해서는 lubridateweek()를 사용할 수 있다. week()는 매개변수로 지정된 벡터의 해당 년도 주차 수를 반환하는 함수이다. 하지만 week()만을 사용하면 매년 같은 주차 데이터들은 같은 주차 수를 반환하기 때문에 주차의 시계열적 변화를 보기 위해서는 반드시 년을 반환하는 year()를 같이 사용해주어야 한다. ’df_covid19_by_period’에서 연별, 주차별 한국의 신규확진자의 합은 다음과 같이 구할 수 있다.

library(lubridate)

df_covid19_by_period |>
  group_by(년 = year(df_covid19_by_period$date), 주 = week(df_covid19_by_period$date), location) |>
  summarise(신규확진자수 = mean(new_cases, na.rm = T)) |>
  filter(location == '한국')
## # A tibble: 56 x 4
## # Groups:   년, 주 [56]
##       년    주 location 신규확진자수
##    <dbl> <dbl> <fct>           <dbl>
##  1  2021    22 한국             684.
##  2  2021    23 한국             572.
##  3  2021    24 한국             483.
##  4  2021    25 한국             507.
##  5  2021    26 한국             680 
##  6  2021    27 한국             971.
##  7  2021    28 한국            1386 
##  8  2021    29 한국            1527.
##  9  2021    30 한국            1582.
## 10  2021    31 한국            1514.
## # ... with 46 more rows

이 중에 한국의 월별 데이터를 선 그래프로 그린다면 다음과 같이 그릴 수 있겠다. 여기서 하나 주의할 것은 앞서 사용한 year()week()는 결과값이 문자열로 리턴된다는 것이다. 그래서 paste0()를 사용하면 앞서 언급한 대로 1주, 10주, 11주, 12주, 2주와 같이 나타난다.

df_covid19_by_period |>
  group_by(년 = year(df_covid19_by_period$date), 주 = week(df_covid19_by_period$date), location) |>
  summarise(신규확진자수 = mean(new_cases, na.rm = T)) |>
  filter(location == '한국') |>
  ggplot(aes(x = paste0(년, '-', 주), y = 신규확진자수)) +
  geom_line(aes(group = location)) +
  theme(axis.text.x = element_text(angle = 90)) +
  labs(x = '연월')

이러한 현상을 방지하기 위해서는 1~9주차에 해당하는 주차 수를 두 자리 정수 형태로 바꾸어 준다. 이를 위해서 sprintf()를 사용하는데 이 함수는 원래 C언어에서 사용되는 함수로 R에서도 지원된다. 이 방법은 앞선 월간 데이터에서도 사용될 수 있지만 월간 데이터에서는 ‘Date’ 형으로 전환해서도 사용이 가능하다.

df_covid19_by_period |>
  group_by(년 = year(df_covid19_by_period$date), 주 = week(df_covid19_by_period$date), location) |>
  summarise(신규확진자수 = mean(new_cases, na.rm = T)) |>
  filter(location == '한국') |>
  mutate(주 = sprintf('%02d', 주)) |>
  ggplot(aes(x = paste0(년, '-', 주), y = 신규확진자수)) +
  geom_line(aes(group = location)) +
  theme(axis.text.x = element_text(angle = 90)) +
  labs(x = '연월')

lubridate의 floor_date()를 사용

앞선 예에서 year()week()를 사용하면 문자열이 반환되고 이를 다시 적절히 변환하여 사용해야 한다는 불편함이 있다. 이보다는 앞서 사용했던 floor_date()를 사용해서 주간 단위로 그룹화하고 이에 대해 합계를 적용하면 앞의 예보다는 다소 코드가 쉬워질 수 있다.

floor_date()는 주어진 시간 주기에 맞는 마지막 날을 리턴하는 함수로 이 날짜를 기준으로 그룹화하면 주 단위로 연산이 가능하다. 앞서에서는 월 단위로 그룹화하기 위해 매월 말일 단위로 그룹화하기 위해 ‘month’ 매개변수를 주었고 주 단위로 그룹화하기 위해서는 ‘week’ 매개변수를 설정한다.

df_covid19_by_period |>
  group_by(연월 = floor_date(date, 'week'), location) |>
  summarise(신규확진자수 = mean(new_cases, na.rm = T))  |>
  filter(location == '한국')
## # A tibble: 55 x 3
## # Groups:   연월 [55]
##    연월       location 신규확진자수
##    <date>     <fct>           <dbl>
##  1 2021-05-30 한국             671.
##  2 2021-06-06 한국             532.
##  3 2021-06-13 한국             468.
##  4 2021-06-20 한국             560.
##  5 2021-06-27 한국             716.
##  6 2021-07-04 한국            1137.
##  7 2021-07-11 한국            1415 
##  8 2021-07-18 한국            1557.
##  9 2021-07-25 한국            1563.
## 10 2021-08-01 한국            1596.
## # ... with 45 more rows

이 데이터를 사용하여 선 그래프를 다음과 같이 그릴 수 있다. 다음에서는 date_labels의 월 포맷을 ’%W’로 설정함으로써 주차 수를 표기하였다.

df_covid19_by_period |>
  group_by(연월 = floor_date(date, 'month'), location) |>
  summarise(신규확진자수 = mean(new_cases, na.rm = T))  |>
  filter(location == '한국') |>
  ggplot(aes(x = 연월, y = 신규확진자수)) +
  geom_line(aes(group = location)) + 
  scale_x_date(date_breaks = '2 weeks', date_labels = '%y년 %W주') + 
  scale_y_continuous(labels = scales::comma) +
  theme(axis.text.x = element_text(angle = 90))

tsibble의 yearmonth()를 사용

앞의 두 예를 보면 주별 데이터를 만드는데 lubridate 패키지의 함수들을 사용하였다. lubridate는 R에서 시간 단위 연산에 매우 훌륭한 패키지임은 두말할 나위없지만 그 외에도 많이 사용되는 패키지들이 있다. 그중 최근 많이 사용되는 패키지가 tsibble이다. 이 tsibble 패키지에서 제공하는 yearweek()를 사용하면 연도와 주수를 반환하기 때문에 주단위로 연산하는데 매우 편리하게 사용할 수 있다. 또 yearmonth()를 사용하면 월간 데이터도 쉽게 산출이 가능하다.

if (!require(tsibble)) {
  install.packages('tsibble')
  library(tsibble)
}

df_covid19_by_period |>
  group_by(연월 = yearweek(date), location) |>
  summarise(신규확진자수 = mean(new_cases, na.rm = T))  |>
  filter(location == '한국')
## # A tibble: 55 x 3
## # Groups:   연월 [55]
##        연월 location 신규확진자수
##      <week> <fct>           <dbl>
##  1 2021 W22 한국             640.
##  2 2021 W23 한국             519.
##  3 2021 W24 한국             462.
##  4 2021 W25 한국             581.
##  5 2021 W26 한국             746.
##  6 2021 W27 한국            1193 
##  7 2021 W28 한국            1437.
##  8 2021 W29 한국            1566.
##  9 2021 W30 한국            1548 
## 10 2021 W31 한국            1635.
## # ... with 45 more rows

위의 데이터를 사용하여 선 그래프를 다음과 같이 그릴 수 있다. 다만 yearweek()의 결과는 ‘Date’ 형이 아니기 때문에 ggplot2에서 제공하는 scale_x_date()를 사용할 수 없고 tsibble 패키지에서 제공하는 scales_x_yearweek()을 사용해야 한다.

df_covid19_by_period |>
  group_by(연월 = yearweek(date), location) |>
  summarise(신규확진자수 = mean(new_cases, na.rm = T))  |>
  filter(location == '한국') |>
  ggplot(aes(x = 연월, y = 신규확진자수)) +
  geom_line(aes(group = location)) + 
  scale_x_yearweek(date_breaks = '2 weeks', date_labels = '%y년 %W주') +
  scale_y_continuous(labels = scales::comma) +
  theme(axis.text.x = element_text(angle = 90))

댓글