global.R
library(tidyverse)
library(glue)
library(leaflet)
library(plotly)
library(sass)
library(shiny)
library(shiny.fluent)
library(shiny.router)
# library(statdata)
library(waffle)
library(gt)
library(ggtextures)
# 카드 제작 함수 ----------------------------------
function(title, content, size = 12, style = "") {
makeCard <-div(
class = glue::glue("card ms-depth-8 ms-sm{size} ms-xl{size}"),
style = style,
Stack(
tokens = list(childrenGap = 5),
Text(variant = "large", title, block = TRUE),
content
)
)
}
# 페이지 제작 함수 ----------------------------------
function (title, subtitle, contents) {
makePage <-tagList(div(
class = "page-title",
span(title, class = "ms-fontSize-32 ms-fontWeight-semibold", style =
"color: #323130"),
span(subtitle, class = "ms-fontSize-14 ms-fontWeight-regular", style =
"color: #605E5C; margin: 14px;")
),
contents)
}
# 1. Dataset ----
# gender <- statdata::gender
read_csv("www/025원시_성별.csv") %>%
gender <- set_names("성별")
server.R
function(input, output, session) {
server <-
# 제어 결과 필터링 리액티브 데이터프레임 ----------------------------
reactive({
filtered_gender <-
if (isTRUE(input$maleOnly)) 1 else 0
maleOnlyVal <-
if(maleOnlyVal == 1) {
filtered_gender <-%>%
gender filter(성별 == "남")
else {
}
gender
}
})
reactive({
count_gender <-filtered_gender() %>%
count(성별, name = "명수")
})
# 1. 데이터셋 보이기 ----
$datasetId <- render_gt(
outputexpr = filtered_gender(),
height = px(600),
width = px(600)
)
# 2. 기술통계량 ----
$gender_stat_gt <- render_gt(
outputfiltered_gender() %>%
count(성별, name = "명수") %>%
mutate(성비 = glue::glue("{명수 / sum(명수) * 100} %")) %>%
gt::gt()
)
# 3. 시각화 ----------------------------------------------------------
## 3.1. 막대그래프 ----------------------------------------------------------
$barPlotId <- renderPlot({
outputcount_gender() %>%
ggplot(aes(x = 성별, y=명수)) +
geom_col(width = 0.3, fill = "midnightblue") +
scale_y_continuous(limits = c(0,10), labels = scales::number_format(accuracy = 1)) +
labs(x = "성별",
y = "명수",
title = "중학교 성별 범주형 데이터") +
theme_bw(base_family = "NanumGothic")
})
## 3.2. 원 그래프 ----------------------------------------------------------
$piePlotId <- renderPlot({
outputcount_gender() %>%
ggplot(aes(x = "", y=명수, fill = 성별)) +
geom_bar(width = 1, stat = "identity", color = "white") +
coord_polar("y", start = 0) +
geom_text(aes(label = glue::glue("{성별} : {명수}")),
position = position_stack(vjust = 0.5),
family = "NanumGothic",
size = 10) +
theme_void(base_family = "NanumGothic") +
scale_fill_viridis_d() +
theme(legend.position = "bottom") +
labs(title = "중학교 성별 범주형 데이터")
})
## 3.3. 와플 그래프 ----------------------------------------------------------
$wafflePlotId <- renderPlot({
outputcount_gender() %>%
ggplot(aes(fill = 성별, values=명수)) +
geom_waffle(n_rows = 6, size = 0.33, colour = "white") +
scale_fill_manual(name = NULL,
values = c("#BA182A", "#FF8288"),
labels = c("남자", "여자")) +
coord_equal() +
theme_void(base_family = "NanumGothic")
})
## 3.4. 이미지 그래프 ----------------------------------------------------------
$imagePlotId <- renderPlot({
output
count_gender() %>%
mutate(image = if_else( str_detect(성별, "남"), list(magick::image_read_svg("https://raw.githubusercontent.com/tidyverse-korea/pkg_doc/master/fig/man-svgrepo-com.svg")),
list(magick::image_read_svg("https://raw.githubusercontent.com/tidyverse-korea/pkg_doc/master/fig/woman-svgrepo-com.svg")))
%>%
) ggplot(aes(x = 성별, y=명수, image = image)) +
geom_isotype_col() +
# scale_fill_manual(name = NULL,
# values = c("#BA182A", "#FF8288"),
# labels = c("남자", "여자")) +
theme_bw(base_family = "NanumGothic") +
scale_y_continuous(limits = c(0,10), labels = scales::number_format(accuracy = 1))
})
# Main 산출물 -------------------------------------------------------
$visualization <- renderUI({
output
Stack(
tokens = list(childrenGap = 10),
horizontal = TRUE,
makeCard("막대 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("barPlotId"))),
makeCard("원 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("piePlotId"))),
makeCard("와플 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("wafflePlotId"))),
makeCard("이미지 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("imagePlotId")))
)
})
}
ui.R
# 제어를 위한 필터 ---------------------------------
Stack(
filters <-
tokens = list(childrenGap = 10),
Toggle.shinyInput("maleOnly", value = TRUE, label = "남성만?")
)
# UI 페이지 ----------------------------------------------------------
fluentPage(
ui <-$style(".card { padding: 28px; margin-bottom: 28px; }"),
tagsStack(
tokens = list(childrenGap = 10), horizontal = TRUE,
makeCard("데이터 탐색", filters, size = 4, style = "max-height: 320px"),
makeCard("데이터셋", gt_output(outputId = "datasetId"), size = 8, style = "max-height: 320px; overflow: auto")
),uiOutput("visualization"),
Stack(
tokens = list(childrenGap = 10), horizontal = TRUE,
makeCard("기술통계", gt_output(outputId = "gender_stat_gt"), size = 8, style = "max-height: 320px")
) )
style.css
-container {
.grid: grid;
display-template-columns: 320px 1fr;
grid-template-rows: 54px 1fr 45px;
grid-template-areas: "header header" "sidenav main" "footer footer";
grid: 100vh;
height
}
.header {-area: header;
grid-color: #fff;
background padding: 6px 0px 6px 10px;
: flex;
display
}
.main {-area: main;
grid-color: #faf9f8;
background padding-left: 40px;
-right: 32px;
padding-width: calc(100vw - 400px);
max-height: calc(100vh - 100px);
max: auto;
overflow
}
.footer {-area: footer;
grid-color: #f3f2f1;
background padding: 12px 20px;
}
.sidenav {-area: sidenav;
grid-color: #fff;
background padding: 25px;
}
body {-color: rgba(225, 223, 221, 0.2);
background-height: 611px;
min: 0;
margin
}
-title {
.page: 52px 0px;
padding
}
.card {: #fff;
background padding: 28px;
-bottom: 28px;
margin-radius: 2px;
border-clip: padding-box;
background
}
.title {: 0px 14px 0px 14px;
padding: #737373;
color margin: 6px 0px 6px 10px;
-left: 1px solid darkgray;
border: 220px;
width
}
.logo {: 44px;
height }
global.R
# * 팩키지 ----------------------------------
library(tidyverse)
library(glue)
library(leaflet)
library(plotly)
library(sass)
library(shiny)
library(shiny.fluent)
library(shiny.router)
# library(statdata)
library(waffle)
library(gt)
library(ggtextures)
# * 도움 함수(Helper Function) ----------------------------------
## * 카드 제작 함수 ----------------------------------
function(title, content, size = 3, style = "") {
makeCard <-div(
class = glue::glue("card ms-depth-8 ms-sm{size} ms-xl{size}"),
style = style,
Stack(
tokens = list(childrenGap = 5),
Text(variant = "large", title, block = TRUE),
content
)
)
}
## * 페이지 제작 함수 ----------------------------------
function (title, subtitle, contents) {
makePage <-tagList(div(
class = "page-title",
span(title, class = "ms-fontSize-32 ms-fontWeight-semibold", style =
"color: #323130"),
span(subtitle, class = "ms-fontSize-14 ms-fontWeight-regular", style =
"color: #605E5C; margin: 14px;")
),
contents)
}
# * 데이터셋 ----------------------------------
# gender <- statdata::gender
read_csv("www/025원시_성별.csv") %>%
gender <- set_names("성별")
# * 홈페이지 ----------------------------------
makeCard(
card1 <-"오픈 통계 팩키지를 소개합니다.",
div(
Text("코로나19로 인해 심화된 디지털 불평등과 디지털 경제 전환(Digital Transformation)으로 인해 시민의 필수 역량이 된 Data Literacy 역량 강화를 위해 컴퓨터를 처음 접하거나 데이터 작업이 처음인 대한민국 국민 누구나 쉽게 통계 팩키지를 접할 수 있도록 하고 데이터 프로그래밍 역량도 수준별로 강화할 수 있도록 새로운 형태의 블록 통계 분석을 포함한 초중등 오픈 통계 팩키지 개발을 금번 컨트리뷰션 아카데미 프로그램을 빌어 Tidyverse Korea 멘토와 멘티가 함께 2021년 새로운 여행을 함께 떠나가고자 합니다.")
))
makeCard(
card2 <-"관련 정보",
div(
Text("공학 소프트웨어 현황: https://r2bit.com/onboard/tong.html", block = TRUE),
Text("오픈 통계 팩키지 기여: https://r2bit.com/onboard/ojt.html", block = TRUE),
Text("한국 R 컨퍼런스: https://use-r.kr/", block = TRUE)
))
? Text
makePage(
home_page <-"다함께 참여하고 활용하는 오픈 소스 소프트웨어",
"R 사용자회 + Open Up = 오픈 통계 팩키지",
div(card1, card2)
)
# 제어를 위한 필터 ---------------------------------
Stack(
filters <-
tokens = list(childrenGap = 10),
Toggle.shinyInput("maleOnly", value = TRUE, label = "남성만?")
)
# analysis 페이지 ----------------------------------------------------------
makePage(
analysis_page <-"단변량 데이터셋",
"범주형 변수",
div(
Stack(
tokens = list(childrenGap = 10), horizontal = TRUE,
makeCard("데이터 탐색", filters, size = 4, style = "max-height: 320px"),
makeCard("데이터셋", gt_output(outputId = "datasetId"), size = 8, style = "max-height: 320px; overflow: auto")
),uiOutput("visualization"),
Stack(
tokens = list(childrenGap = 10), horizontal = TRUE,
makeCard("기술통계", gt_output(outputId = "gender_stat_gt"), size = 8, style = "max-height: 320px")
)
)
)
# UI Structure - Layout ----------------------------------------------------------
tagList(
header <-img(src = "logo.jpg", width = "100px", class = "logo"),
div(Text(variant = "xLarge", "오픈 통계 팩키지"), class = "title"),
CommandBar(
items = list(
CommandBarItem("초등", "Upload"),
CommandBarItem("중등", "Upload"),
CommandBarItem("고등", "Upload")
),farItems = list(
CommandBarItem("Grid view", "Tiles", iconOnly = TRUE),
CommandBarItem("Info", "Info", iconOnly = TRUE)
),style = list(width = "100%")))
navigation <- Nav(
navigation <-groups = list(
list(links = list(
list(name = 'Home', url = '#!/', key = 'home', icon = 'Home'),
list(name = '범주형', url = '#!/other', key = 'analysis', icon = 'AnalyticsReport'),
list(name = 'bit2r', url = 'https://r2bit.com/', key = 'bit2r', icon = 'GitGraph'),
list(name = 'R 컨퍼런스', url = 'http://use-r.kr', key = 'rconf', icon = 'WebAppBuilderFragment')
))
),initialSelectedKey = 'home',
styles = list(
root = list(
height = '100%',
boxSizing = 'border-box',
overflowY = 'auto'
)
)
)
Stack(
footer <-horizontal = TRUE,
horizontalAlign = 'space-between',
tokens = list(childrenGap = 20),
Text(variant = "medium", "Built with Shiny and Fluent UI", block=TRUE),
Text(variant = "medium", nowrap = FALSE, "If you'd like to learn more, reach out to us at admin@r2bit.com"),
Text(variant = "medium", nowrap = FALSE, "All rights reserved.")
)
function(mainUI){
layout <-div(class = "grid-container",
div(class = "header", header),
div(class = "sidenav", navigation),
div(class = "main", mainUI),
div(class = "footer", footer)
)
}
# router -------------------------------------------------------------
make_router(
router <-route("/", home_page),
route("other", analysis_page))
# Add shiny.router dependencies manually: they are not picked up because they're added in a non-standard way.
::addResourcePath("shiny.router", system.file("www", package = "shiny.router"))
shiny file.path("shiny.router", "shiny.router.js")
shiny_router_js_src <- shiny::tags$script(type = "text/javascript", src = shiny_router_js_src) shiny_router_script_tag <-
server.R
function(input, output, session) {
server <-
$server(input, output, session)
router
# 제어 결과 필터링 리액티브 데이터프레임 ----------------------------
reactive({
filtered_gender <-
if (isTRUE(input$maleOnly)) 1 else 0
maleOnlyVal <-
if(maleOnlyVal == 1) {
filtered_gender <-%>%
gender filter(성별 == "남")
else {
}
gender
}
})
reactive({
count_gender <-filtered_gender() %>%
count(성별, name = "명수")
})
# 1. 데이터셋 보이기 ----
$datasetId <- render_gt(
outputexpr = filtered_gender(),
height = px(600),
width = px(600)
)
# 2. 기술통계량 ----
$gender_stat_gt <- render_gt(
outputfiltered_gender() %>%
count(성별, name = "명수") %>%
mutate(성비 = glue::glue("{명수 / sum(명수) * 100} %")) %>%
gt::gt()
)
# 3. 시각화 ----------------------------------------------------------
## 3.1. 막대그래프 ----------------------------------------------------------
$barPlotId <- renderPlot({
outputcount_gender() %>%
ggplot(aes(x = 성별, y=명수)) +
geom_col(width = 0.3, fill = "midnightblue") +
scale_y_continuous(limits = c(0,10), labels = scales::number_format(accuracy = 1)) +
labs(x = "성별",
y = "명수",
title = "중학교 성별 범주형 데이터") +
theme_bw(base_family = "NanumGothic")
})
## 3.2. 원 그래프 ----------------------------------------------------------
$piePlotId <- renderPlot({
outputcount_gender() %>%
ggplot(aes(x = "", y=명수, fill = 성별)) +
geom_bar(width = 1, stat = "identity", color = "white") +
coord_polar("y", start = 0) +
geom_text(aes(label = glue::glue("{성별} : {명수}")),
position = position_stack(vjust = 0.5),
family = "NanumGothic",
size = 10) +
theme_void(base_family = "NanumGothic") +
scale_fill_viridis_d() +
theme(legend.position = "bottom") +
labs(title = "중학교 성별 범주형 데이터")
})
## 3.3. 와플 그래프 ----------------------------------------------------------
$wafflePlotId <- renderPlot({
outputcount_gender() %>%
ggplot(aes(fill = 성별, values=명수)) +
geom_waffle(n_rows = 6, size = 0.33, colour = "white") +
scale_fill_manual(name = NULL,
values = c("#BA182A", "#FF8288"),
labels = c("남자", "여자")) +
coord_equal() +
theme_void(base_family = "NanumGothic")
})
## 3.4. 이미지 그래프 ----------------------------------------------------------
$imagePlotId <- renderPlot({
output
count_gender() %>%
mutate(image = if_else( str_detect(성별, "남"), list(magick::image_read_svg("https://raw.githubusercontent.com/tidyverse-korea/pkg_doc/master/fig/man-svgrepo-com.svg")),
list(magick::image_read_svg("https://raw.githubusercontent.com/tidyverse-korea/pkg_doc/master/fig/woman-svgrepo-com.svg")))
%>%
) ggplot(aes(x = 성별, y=명수, image = image)) +
geom_isotype_col() +
# scale_fill_manual(name = NULL,
# values = c("#BA182A", "#FF8288"),
# labels = c("남자", "여자")) +
theme_bw(base_family = "NanumGothic") +
scale_y_continuous(limits = c(0,10), labels = scales::number_format(accuracy = 1))
})
# Main 산출물 -------------------------------------------------------
$visualization <- renderUI({
output
Stack(
tokens = list(childrenGap = 10),
horizontal = TRUE,
makeCard("막대 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("barPlotId"))),
makeCard("원 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("piePlotId"))),
makeCard("와플 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("wafflePlotId"))),
makeCard("이미지 그래프", div(style="max-height: 500px; overflow: auto", plotOutput("imagePlotId")))
)
})
}
ui.R
# UI 페이지 ----------------------------------------------------------
fluentPage(
ui <-layout(router$ui),
$head(
tags$link(href = "style.css", rel = "stylesheet", type = "text/css"),
tags
shiny_router_script_tag
) )
데이터 과학자 이광춘 저작
kwangchun.lee.7@gmail.com