Corona Shiny Project 6_2 - Chart Automation
공지
이번에 준비한 튜토리얼은 제 강의를 듣는 과거-현재-미래 수강생분들을 위해 준비한 자료이다. 많은 도움이 되기를 바란다
이번에 준비한 Tutorial 코로나 세계현황을 Shiny Dashboard로 만들어 가는 과정을 담았다.
I. Shiny Tutorial 소개
처음 shiny를 접하거나 shiny의 전체 튜토리얼이 궁금한 사람들을 위해 이전 글을 소개한다.
- shiny tutorial 01 - get started
- shiny tutorial 02 - Shiny Structure
- shiny tutorial 03 - HTML content
- shiny tutorial 04 - Shiny Layouts
- shiny tutorial 05 - Sharing Apps
- shiny tutorial 06 - shinydashboard
- shiny tutorial 07 - flexdashboard
- shiny tutorial 08 - HTML, CSS 적용
II. Shiny Project
현재 진행중인 프로젝트가 궁금하다면 아래를 확인해보자.
- Corona Shiny Project 1 - Get Data
- Corona Shiny Project 2 - Visusalization (Time Series)
- Corona Shiny Project 3 - Visusalization (Bubble Chart)
- Corona Shiny Project 4 - Visusalization (Map Chart)
- Corona Shiny Project 5 - Chart with DateInput
- Corona Shiny Project 6_1 - 데이터 전처리)
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.