Corona Shiny Project 6_2 - Chart Automation

Page content

공지

이번에 준비한 튜토리얼은 제 강의를 듣는 과거-현재-미래 수강생분들을 위해 준비한 자료이다. 많은 도움이 되기를 바란다

이번에 준비한 Tutorial 코로나 세계현황을 Shiny Dashboard로 만들어 가는 과정을 담았다.

I. Shiny Tutorial 소개

처음 shiny를 접하거나 shiny의 전체 튜토리얼이 궁금한 사람들을 위해 이전 글을 소개한다.

II. Shiny Project

현재 진행중인 프로젝트가 궁금하다면 아래를 확인해보자.

III. 데이터 전처리 리뷰

처음 이글을 접하는 사람들을 위해 전체적인 코드를 다시 정리하였다. 처음부터 shiny tutorial을 진행하는 것도 도움이 되지만, 우선 이번 강의의 목적은 Chart Automation을 어떻게 활용하는지가 우선이기 때문에 데이터셋은 전처리된 데이터셋을 다시 정리한다.

아래 코드대로 실행하면 되니, 큰 문제없이 따라와주기를 바란다.

library(rgdal)
## Loading required package: sp
## rgdal: version: 1.4-8, (SVN revision 845)
##  Geospatial Data Abstraction Library extensions to R successfully loaded
##  Loaded GDAL runtime: GDAL 2.4.2, released 2019/06/28
##  Path to GDAL shared files: /Library/Frameworks/R.framework/Versions/3.6/Resources/library/rgdal/gdal
##  GDAL binary built with GEOS: FALSE 
##  Loaded PROJ.4 runtime: Rel. 5.2.0, September 15th, 2018, [PJ_VERSION: 520]
##  Path to PROJ.4 shared files: /Library/Frameworks/R.framework/Versions/3.6/Resources/library/rgdal/proj
##  Linking to sp version: 1.3-2
library(readxl)
library(httr)
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(stringr)
library(tidyr)
library(shiny)
library(leaflet)

# Excel URL Copy
url <- 'https://www.ecdc.europa.eu/sites/default/files/documents/COVID-19-geographic-disbtribution-worldwide.xlsx'

# 파일 이름만 가져오기
getFileName <- basename(url)

# 지정된 디렉토리에 파일 다운로드 받기
download.file(url = url, destfile = getFileName)

# 지정된 디렉토리에서 파일 업로드 하기
corona <- read_excel(getFileName)

corona_date <- as.Date(corona$dateRep)

countries <- readOGR(dsn ="~/Desktop/ne_50m_admin_0_countries",
                     layer = "ne_50m_admin_0_countries",
                     encoding = "utf-8",use_iconv = T,
                     verbose = FALSE)

countries@data$POP_EST[ which(countries@data$POP_EST == 0)] = NA
countries@data$POP_EST <- as.numeric(as.character(countries@data$POP_EST)) / 1000000 %>% round(2)

corona %>% 
  select(dateRep, countriesAndTerritories, cases) %>% # 필요한 열만 추출한다. 
  group_by_at(vars(-cases)) %>% # cases를 제외하고 모두 그룹을 한다는 뜻이다. 
  dplyr::mutate(row_id=1:n()) %>% ungroup() %>%  # 그룹 인덱스를 만든다.
  spread(key=dateRep, value=cases) %>% # 
  select(-row_id) -> dataCases # 인덱스 제거 후 corona2로 저장한다. 

coronaConfirmed <- left_join(data.frame(countries = countries$NAME %>% as.character(), 
                                              Pop = countries$POP_EST %>% as.character() %>% as.numeric()), 
                             dataCases %>% 
                               rename(countries = countriesAndTerritories)) %>% 
                               filter(!is.na(countries))
## Joining, by = "countries"
## Warning: Column `countries` joining factor and character vector, coercing into
## character vector
coronaConfirmed[is.na(coronaConfirmed)] <- 0

countries2 <- merge(countries,
                    coronaConfirmed,
                    by.x = "NAME",
                    by.y = "countries",
                    sort = FALSE)
  • 처음 이글을 접한 사람들에게 여기에서 문제가 될만한 소스코드는 readOGR에 해당하는 지도객체를 가져오는 문제인데, 이 부분은 강사가 작업한 Corona Shiny Project 4 - Visusalization (Map Chart)에서 추가학습 할 것을 권한다.

  • countries2 객체로 데이터를 Merge 하였다.

이렇게 합쳐진 데이터의 데이터를 다시 보면 아래와 같다.

head(countries2@data) %>% select(NAME, Pop, contains("-"))
##          NAME      Pop 2019-12-31 2020-01-01 2020-01-02 2020-01-03 2020-01-04
## 241  Zimbabwe 13.80508          0          0          0          0          0
## 240    Zambia 15.97200          0          0          0          0          0
## 239     Yemen 28.03683          0          0          0          0          0
## 236   Vietnam 96.16016          0          0          0          0          0
## 235 Venezuela 31.30402          0          0          0          0          0
## 234   Vatican  0.00100          0          0          0          0          0
##     2020-01-05 2020-01-06 2020-01-07 2020-01-08 2020-01-09 2020-01-10
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          0          0          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-01-11 2020-01-12 2020-01-13 2020-01-14 2020-01-15 2020-01-16
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          0          0          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-01-17 2020-01-18 2020-01-19 2020-01-20 2020-01-21 2020-01-22
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          0          0          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-01-23 2020-01-24 2020-01-25 2020-01-26 2020-01-27 2020-01-28
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          2          0          0          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-01-29 2020-01-30 2020-01-31 2020-02-01 2020-02-02 2020-02-03
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          3          0          2          1
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-02-04 2020-02-05 2020-02-06 2020-02-07 2020-02-08 2020-02-09
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          1          1          0          2          1          1
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-02-10 2020-02-11 2020-02-12 2020-02-13 2020-02-14 2020-02-15
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          1          0          1          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-02-16 2020-02-17 2020-02-18 2020-02-19 2020-02-20 2020-02-21
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          0          0          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-02-22 2020-02-23 2020-02-24 2020-02-25 2020-02-26 2020-02-27
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          0          0          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-02-28 2020-02-29 2020-03-01 2020-03-02 2020-03-03 2020-03-04
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          0          0          0          0
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-03-05 2020-03-06 2020-03-07 2020-03-08 2020-03-09 2020-03-10
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          0          1          4          9          1
## 235          0          0          0          0          0          0
## 234          0          0          0          0          0          0
##     2020-03-11 2020-03-12 2020-03-13 2020-03-14 2020-03-15 2020-03-16
## 241          0          0          0          0          0          0
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          4          4          5          5          4          4
## 235          0          0          0          0         10          5
## 234          0          0          0          0          0          0
##     2020-03-17 2020-03-18 2020-03-19 2020-03-20 2020-03-21 2020-03-22
## 241          0          0          0          0          1          1
## 240          0          0          2          0          0          0
## 239          0          0          0          0          0          0
## 236          4          0         15          9          2          7
## 235         18          0          0          0          3          0
## 234          0          0          0          0          0          0
##     2020-03-23 2020-03-24 2020-03-25 2020-03-26 2020-03-27 2020-03-28
## 241          0          0          0          1          0          2
## 240          1          0          0          9          2          2
## 239          0          0          0          0          0          0
## 236         24          5         11         14          5         16
## 235          0         48          7         15          1         12
## 234          0          0          0          0          0          0
##     2020-03-29 2020-03-30 2020-03-31 2020-04-01 2020-04-02 2020-04-03
## 241          2          0          0          1          0          0
## 240         12          1          6          0          1          3
## 239          0          0          0          0          0          0
## 236         54          5          1          0          6          4
## 235          0          0         16          0          8          1
## 234          0          0          0          0          0          0
##     2020-04-04 2020-04-05 2020-04-06 2020-04-07 2020-04-08 2020-04-09
## 241          1          0          0          0          1          1
## 240          0          0          0          0          0          0
## 239          0          0          0          0          0          0
## 236          0          1          1          4          6          0
## 235          0          0          4         11          7          1
## 234          0          0          0          0          0          0
##     2020-04-10 2020-04-11 2020-04-12 2020-04-13 2020-04-14 2020-04-15
## 241          0          0          3          0          3          0
## 240          0          1          0          3          2          0
## 239          1          0          0          0          0          0
## 236          4          2          1          4          3          9
## 235          4          4          0          6          8          4
## 234          0          0          0          0          0          0
##     2020-04-16 2020-04-17
## 241          6          1
## 240          3          0
## 239          0          0
## 236          1          0
## 235          4          7
## 234          0          0

변환하고 합친 데이터가 이렇게 정렬된 것을 볼 수 있다. 참고로 위 데이터는 각 나라별 확진자 수 데이터를 보여주는 것이다.

이제 flexdashboard를 활용해서 지도 시각화를 진행해보자. 코드가 어려운 것은 아니다. 다만, reactive에 대한 개념이 없으면 문법이 달라서 조금 고생할 수 있다.

IV. 시각화 코드

(1) sliderInput

Inputs 탭에 아래와 같이 코드를 입력한다.

# to control speed, plus looping
sliderInput("dayRange", "Looping Animation:",
             min = min(corona_date), max = max(corona_date),
             value = c(median(corona_date)), step = 25, 
             animate = animationOptions(interval = 2000, loop = TRUE))

소스코드에 대한 설명은 다음과 같다.

  • dateRange : inputID이며, 날짜 데이터를 받는다.
  • Looping Animation: sliderInput 표시글이다.
  • min, max: 날짜의 범위를 최소값과 최대값을 나타낸다. corona_date는 데이터의 수집 날짜로 vector로 구성되어 있다.
  • value: 처음 ShinyApp이 실행되었을 때 나타내는 초기값이다.
  • step: 1~100까지 vector가 존재할 때, 건너뛰는 단위를 나타낸다. 무언가 극적인 변화를 보여줄 때, 쓰면 좋다. 강사는 25로 지정했다.
  • animate: 차트 애니메이션 작동 방식을 의미하며, step에 따라 데이터가 바뀐다. intervals 시간, loop는 자동으로 계속 step을 돌릴 것인지 TRUE 또는 FALSE에 따라 달라진다.

(2) 날짜 데이터 다루기

세계 인구 지도 차트 작성했을 때의 코드를 기억하였으면 좋겠다. 왜냐하면 많이 바뀌지 않기 때문이다.

# 색상, 범례에 사용할 것
mybins <- c(0,10,20,50,100,500,Inf)
mypalette <- colorBin(palette="YlOrBr", domain=countries@data$POP_EST, na.color="transparent", bins= mybins)

# 국가 클릭 시, 아래 내용으로 출력
mytext <- paste(
  "Country: ", countries@data$NAME_EN,"<br/>", 
  "Area:  ", countries@data$CONTINENT, "<br/>",
  "Population: ", round(countries@data$POP_EST, 2),
  sep="") %>%
  lapply(htmltools::HTML)

# 마지막으로 Mapping
leaflet(countries) %>% 
  addTiles()  %>% 
  setView( lat=20, lng=0 , zoom=4) %>% 
  addPolygons( 
    fillColor = ~mypalette(POP_EST), 
    weight = 2,
    opacity = 1,
    color = "white",
    dashArray = "3",
    fillOpacity = 0.7,
    highlight = highlightOptions(
      weight = 5,
      color = "#666",
      dashArray = "",
      fillOpacity = 0.7,
      bringToFront = TRUE),
    label = mytext,
    labelOptions = labelOptions(
      style = list("font-weight" = "normal", padding = "2px 7px"),
      textsize = "13px",
      direction = "auto"
    )
  ) %>%
  addLegend(pal=mypalette, values=~POP_EST, opacity=0.9, title = "Population (M)", position = "bottomleft" )

여기서 바꿔야 하는 코드는 무엇일까? 인구 대신 확진자 수 데이터만 대입하면 된다. 그런데 문제가 있다. 날짜가 바뀔때마다 확진수도 바뀌어야 한다. 그렇지 않은가? 그래야 의미가 있기 때문이다. 즉, 여기서 고민해야 하는 것은 프로세스이다.

순서대로 고민해보자.

  • 첫번째, 날짜가 바뀐다.
  • 두번째, 바뀐 날짜를 문자열 변수로 저장한다.
  • 세번째, 저장된 변수를 활용하여 데이터의 Column를 선택한다.

위와 같은 Workflow대로 작업하면 된다.

세번째의 개념을 좀더 돕고자 아래 소스코드를 예제로 들어본다.

indicator <- "2020-03-10"
data.frame(date = indicator,
           country = countries2$NAME, 
           Confirmed = countries2[[indicator]],
           Pop = countries2@data$Pop) %>% 
  arrange(desc(Confirmed)) %>% head(10)
##          date     country Confirmed       Pop
## 1  2020-03-10       Italy      1797 62.137802
## 2  2020-03-10       Spain       615 48.958159
## 3  2020-03-10        Iran       595 82.021564
## 4  2020-03-10      France       286 67.106161
## 5  2020-03-10     Germany       237 80.594017
## 6  2020-03-10     Denmark        75  5.605948
## 7  2020-03-10 Netherlands        56 17.084719
## 8  2020-03-10      Sweden        45  9.960487
## 9  2020-03-10 Switzerland        42  8.236303
## 10 2020-03-10     Belgium        39 11.491346

여기에서 주목해야 하는 코드는 countries2[[indicator]] 이 코드이다. 이번에는 corona_date의 값을 바꿔서 실제로 값이 들어오는 것처럼 적용한다. 이 때에는 max(corona_date)값을 가져온다.

indicator <- max(as.character(corona_date))
print(paste0("현재 날짜: ", indicator))
## [1] "현재 날짜: 2020-04-17"
data.frame(date = indicator,
           country = countries2$NAME, 
           Confirmed = countries2[[indicator]],
           Pop = countries2@data$Pop) %>% 
  arrange(desc(Confirmed)) %>% head(10)
##          date country Confirmed       Pop
## 1  2020-04-17   Spain      5183  48.95816
## 2  2020-04-17  Turkey      4801  80.84521
## 3  2020-04-17   Italy      3786  62.13780
## 4  2020-04-17  Russia      3448 142.25752
## 5  2020-04-17 Germany      3380  80.59402
## 6  2020-04-17  France      2641  67.10616
## 7  2020-04-17  Brazil      2105 207.35339
## 8  2020-04-17  Canada      1717  35.62368
## 9  2020-04-17    Iran      1606  82.02156
## 10 2020-04-17 Belgium      1236  11.49135

데이터가 수집된 4월 17일 기준으로 값이 바뀌는 것을 알 수 있다. 위와 같은 방법으로 모두 수정해주면 끝이다.

(3) 지도 차트 완성하기

이제 지도차트를 작성하는데, 이 때에는 renderLeaflet({}) 함수를 사용하는데, 그 이유는 reactive() 객체를 다루기 때문이다.

sliderInput() 에 입력되는 데이터를 받아서 사용하려면 reactive() 객체로 변환해야 해서 적용해야 하기 때문에 이 부분만 주의하면 된다. (일종의 shiny 문법이라 이해하였으면 좋겠다.)

날짜 데이터 다루는 방법을 shiny에 적용하면 아래 코드와 같다.

dayRange <- renderText({ as.character(input$dayRange) })
  indicator <- dayRange()
  print(indicator)

print(indicator)이 값은 step이 바뀔 때마다 다른 날짜가 주기적으로 입력될 것이다.

기존에 진행했던 소스코드 POP_EST를 찾아서, countries2[[indicator]]로 모두 수정한다. 그리고, mytext에서도 “NewCases: “, round(countries2[[indicator]], 0), sep=””) 코드를 추가하여 보완하도록 한다.

renderLeaflet({
  dayRange <- renderText({ as.character(input$dayRange) })
  indicator <- dayRange()
  print(indicator)

# 색상, 범례에 사용할 것
mybins <- c(0,10,20,50,100,500,Inf)
mypalette <- colorBin(palette="YlOrBr", domain=countries2[[indicator]], na.color="transparent", bins= mybins)

# Prepare the text for tooltips:
mytext <- paste(
  "Country: ", countries2@data$NAME_EN,"<br/>", 
  "Area:  ", countries2@data$CONTINENT, "<br/>",
  "Population: ", round(countries2@data$POP_EST, 2), "<br/>",
  "NewCases: ", round(countries2[[indicator]], 0),
  sep="") %>%
  lapply(htmltools::HTML)
  
  leaflet("map", data = countries2) %>% 
  addTiles()  %>% 
  setView(lat=20, lng=0 , zoom=4) %>% 
  addPolygons( 
    fillColor = ~mypalette(countries2[[indicator]]), 
    weight = 2,
    opacity = 1,
    color = "white",
    dashArray = "3",
    fillOpacity = 0.7,
    highlight = highlightOptions(
      weight = 5,
      color = "#666",
      dashArray = "",
      fillOpacity = 0.7,
      bringToFront = TRUE),
    label = mytext,
    labelOptions = labelOptions(
      style = list("font-weight" = "normal", padding = "2px 7px"),
      textsize = "13px",
      direction = "auto"
    )
  ) %>% 
  leaflet::addLegend(pal = mypalette, values=~countries2[[indicator]], opacity=0.9, title = "Population (M)", position = "bottomleft" )
  
})

Output은 유투브 영상을 통해 확인한다.

(4) 유투브 Sample

V. 결론

이제 최종버전만 남았다. 3개의 동적 대시보드를 하나로 합치는 작업만 남았는데, 이 부분은 수강생의 과제로 남겨둔다. 전체코드는 강사가 게시한 ShinyApp에서 확인하기를 바란다 (참고: 가급적 PC에서 보기를 바란다.)

이글을 읽는 사람들에게 작게나마 도움이 되기를 바란다.

Pray for Victims of Covid_19. Contribution to them with this tutorial.