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.