본문 바로가기

배운 책들 정리/공공데이터로 배우는 R 데이터 분석 with 샤이니

공공데이터 with 샤이니 9,10 - 샤이니 입문하기, 데이터 분석 app 개발하기

1. 샤이니 입문하기 CP 9

 

1) 샤이니 기본 구조

 

# 
# 01
# 샤이니 기본 구조
# install.packages("shiny")
library(shiny)
# ui
ui <- fluidPage("user interface")
# server
server <- function(input, output, session){}
# shinyApp
shinyApp(ui, server)
#

 

 

2) 샘플 실행

 

# 02
# 샤이니 샘플
library(shiny)
# 샘플 보기
runExample()
# "01_hello" 샘플 보기
runExample("01_hello")
# 샘플의 데이터 = 올드 페이스풀 간헐천
head(faithful)
# eruptions: 분출 시간
# waiting: 다음 분출 때까지 대기 시간
# 
# "02_text" 샘플 보기
runExample("02_text")
#

 

 

3) 01_Hello 샘플 만들기

 

# 03
# "01_hello" 샘플 만들기
library(shiny)
# ui
ui <- fluidPage(
  titlePanel("Number 1 sample"),
  sidebarLayout(
    sidebarPanel(
      sliderInput(inputId = "bins", # server의 input$bins 로 전달
                  label = "Number of bins",
                  min = 1, max = 50,
                  value = 15)
    ),
    mainPanel(
      plotOutput(outputId = "histplot") # 수정된 결과 output$histplot 을 ui에 출력
    )
  )
)
# server
server <- function(input, output, session){
  # 수정된 결과가 output$histplot 으로 저장
  output$histplot <- renderPlot({
    x <- faithful$waiting
    # length.out = 구간의 수 + 1
    bins <- seq(min(x), max(x), length.out = input$bins+1) # 입력받은 값으로 결과 수정
    hist(x, breaks = bins,
         col = "skyblue", border = "black",
         xlab = "Waiting (min)",
         main = "Histogram of waiting")
  })
}
# shinyApp
shinyApp(ui, server)
#

 

 

4) 입력 받기 _ input

 

# 04
# 연비를 입력하는 화면 만들기 => ui 만들기 실습
library(shiny)
# ui
ui <- fluidPage(
  sliderInput(inputId = "range",
              label = "Fuel efficiency",
              min = 0, max = 35,
              value = c(0, 10))
)
# server
server <- function(input, output, session){}
# shinyApp
shinyApp(ui, server)
#

 

 

5) 출력 하기 _ out put

 

# 05
# 연비를 입력해서 범위 값을 출력
library(shiny)
# ui
ui <- fluidPage(
  sliderInput(inputId = "range",
              label = "Fuel efficiency",
              min = 0, max = 35,
              value = c(0, 10)),
  # 범위 값 출력하는 화면
  textOutput("value")
)
# server
server <- function(input, output, session){
  output$value <- renderText(input$range[2] - input$range[1])
}
# shinyApp
shinyApp(ui, server)
#

 

 

6) 입출력 과정에서의 렌더링 함수의 중요성

 

# 06
# 연비를 입력해서 범위 값을 출력 오류가 있는 내용
# 렌더링 함수의 중요성
library(shiny)
# ui
ui <- fluidPage(
  sliderInput(inputId = "range",
              label = "Fuel efficiency",
              min = 0, max = 35,
              value = c(0, 10)),
  # 범위 값 출력하는 화면
  textOutput("value")
)
# server
server <- function(input, output, session){
  # renderText 함수 사용하지 않고 출력
  # 렌더링 함수를 사용해야 서버와 ui를 연결시켜줄 수 있음
  output$value <- (input$range[2] - input$range[1])
}
# shinyApp
shinyApp(ui, server)
#

 

에러메시지 발생

 

output$value <- (input$range[2] - input$range[1])

이 코드가 아래의 코드로 바뀌어야 함

output$value <- renderText(input$range[2] - input$range[1])

 

 

7) 반응식의 도입

 

# 07
# 연비를 입력해서 결과를 출력하는 화면 만들기
# 연비의 범위값에 해당하는 자동차 데이터를 출력
library(shiny)
# install.packages("DT")
library(DT)
library(ggplot2)
mpg <- mpg
# ui
ui <- fluidPage(
  sliderInput(inputId = "range",
              label = "Fuel efficiency",
              min = 0, max = 35,
              value = c(0, 10)),
  dataTableOutput("table")
)
# server
server <- function(input, output, session){
  # 반응식 = reactive()
  cty_sel = reactive({
    cty_sel = subset(mpg, cty >= input$range[1] &                      cty <= input$range[2])
    return(cty_sel)
  })
  output$table <- renderDataTable(cty_sel())
}
# shinyApp
shinyApp(ui, server)
#

 

지정 범위 값에 따라 달라지는 결과 창

 

8) 레이아웃 적용

 

# 레이아웃 설정
library(shiny)
# ui
ui <- fluidPage(
  fluidRow(
    # div(style =): html
    column(width = 9, div(style = "height:450px;
                          border: 4px solid red;"), "Width 9"),
    column(width = 3, div(style = "height:450px;
                          border: 4px solid blue;"), "Width 3"),
    column(width = 12, div(style = "height:450px;
                          border: 4px solid yellow;"), "Width 12")
  )
)
# server
server <- function(input, output, session){}
# shinyApp
shinyApp(ui, server)
#

 

 

 

2. 데이터 분석 어플리케이션 개발하기 CP 10

 

1) 라이브러리, 데이터 불러오기 : 반응형 지도 만들기

 

# 
# 
# 10_1
# 반응형 지도 만들기
# 7장 내용 기반
# 라이브러리
# install.packages("mapedit")
library(sf);library(leaflet);library(tidyverse);library(shiny);library(mapedit);library(DT)
# 데이터 불러오기
# 아파트 가격
load("./data/apt_price.rdata")
head(apt_price)
# 서울시 그리드 데이터
grid <- st_read("./data/sigun_grid/seoul.shp")
# 서울시 경계선 데이터
bnd <- st_read("./data/sigun_bnd/seoul.shp")
# 최고가 레스터 데이터
load("./data/raster_high.rdata")
raster_high
# 급등 지역 레스터 데이터
load("./data/raster_hot.rdata")

실거래, 경계선 데이터, 최고가 지역, 급등 지역, 그리드 데이터 불러오기

 

 

2) 마커 클러스터링 설정 : 반응형 지도 만들기

 

# 
# 마커 클러스터링 옵션 설정
# 상위 10%, 하위 10%, 나머지
q10 <- quantile(apt_price$py, probs = seq(.1, .9, by = .1))
# 상위 10% = 90%
pct_90 <- as.numeric(q10[9])
# 하위 10% = 10%
pct_10 <- as.numeric(q10[1])
# 마커 클러스터링 함수 불러오기
load("./data/circle_marker/circle_marker.rdata")
# 색상 설정
circle.colors <- c()

데이터 왜곡을 방지하기 위해 하위 10%, 상위 10%를 지정함.

그 후 등급별 차이를 직관적으로 보여줄 수 있게 마커에 표시할 색상을 circle.colors <- c(" ", " ", " ") 에서 설정.

 

 

3) 반응형 지도 만들기 : 반응형 지도 만들기

 

# 반응형 지도 만들기
leaflet() %>% 
  addTiles(options = providerTileOptions(minZoom = 9, maxZoom = 18)) %>% 
  # 서울시 경계선
  addPolygons(data = bnd,
              color = "gold",
              fill = NA,
              weight = 5) %>% 
  # 최고가 레스터 데이터
  addRasterImage(raster_high,
                 colors = colorNumeric(palette = c("blue","green","yellow","red"),
                                       domain = values(raster_high),
                                       na.color = "transparent"),
                 opacity = 0.4) %>% 
  # 급등 지역 레스터 데이터
  addRasterImage(raster_hot,
                 colors = colorNumeric(palette = c("blue", "green", "yellow", "red"),
                                       domain = values(raster_hot),
                                       na.color = "transparent"),
                 opacity = 0.4) %>% 
  # 체크 박스
  addLayersControl(baseGroups = c("High Price in 2021", "Hot Price in 2021"),
                   options = layersControlOptions(collapsed = F)) %>% 
  # 마커 클러스터링
  addCircleMarkers(data = apt_price,
                   # 경도
                   lng = unlist(map(apt_price$geometry,1)),
                   # 위도
                   lat = unlist(map(apt_price$geometry,2)),
                   fillColor = circle.colors,
                   weight = apt_price$py,
                   clusterOptions = markerClusterOptions(iconCreateFunction=JS(avg.formula)))
#

 

 

 

4) 데이터 불러오기부터 그리드 필터링까지 : 지도 애플리케이션 만들기

 

# 10_2
# 지도 애플리케이션 만들기
# 그리드 추가
# 라이브러리
library(sf);library(leaflet);library(tidyverse);library(shiny);library(mapedit);library(DT)
library(leafem)
# 데이터 불러오기
# 아파트 가격
load("./data/apt_price.rdata")
head(apt_price)
# 서울시 그리드 데이터
grid <- st_read("./data/sigun_grid/seoul.shp")
# 서울시 경계선 데이터
bnd <- st_read("./data/sigun_bnd/seoul.shp")
# 최고가 레스터 데이터
load("./data/raster_high.rdata")
raster_high
# 급등 지역 레스터 데이터
load("./data/raster_hot.rdata")
raster_hot
# 
# 그리드 데이터 sf형을 sp형으로 변환
grid <- as(grid, "Spatial")
# sfg: simple feature geometry
# sfc: simple feature columns (the coordinate)
# sf: simple feature columns + attribute (data.frame)
# 그리드 데이터를 sfc로 변환
grid <- as(grid, "sfc")
grid
# 아파트 가격 데이터와 그리드 데이터 합치기
# 데이터가 있는 그리드만 추출
grid <- grid[which(sapply(st_contains(st_sf(grid), apt_price),length) > 0)]
# 그리드 확인
plot(grid)

 

 

5) 파이프라인으로 마커클러스터링~그리드 추가해서 반응형 지도 모듈화하기 : 지도 애플리케이션 만들기

 

# 
# 마커 클러스터링 옵션 설정
# 상위 10%, 하위 10%, 나머지
q10 <- quantile(apt_price$py, probs = seq(.1, .9, by = .1))
# 상위 10% = 90%
pct_90 <- as.numeric(q10[9])
# 하위 10% = 10%
pct_10 <- as.numeric(q10[1])
# 마커 클러스터링 함수 불러오기
load("./data/circle_marker/circle_marker.rdata")
# 색상 설정
circle.colors <- c()
# 
# 반응형 지도 만들기
m <- leaflet() %>% 
  addTiles(options = providerTileOptions(minZoom = 9, maxZoom = 18)) %>% 
  # 서울시 경계선
  addPolygons(data = bnd,
              color = "gold",
              fill = NA,
              weight = 5) %>% 
  # 최고가 레스터 데이터
  addRasterImage(raster_high,
                 colors = colorNumeric(palette = c("blue","green","yellow","red"),
                                       domain = values(raster_high),
                                       na.color = "transparent"),
                 opacity = 0.4) %>% 
  # 급등 지역 레스터 데이터
  addRasterImage(raster_hot,
                 colors = colorNumeric(palette = c("blue", "green", "yellow", "red"),
                                       domain = values(raster_hot),
                                       na.color = "transparent"),
                 opacity = 0.4) %>% 
  # 체크 박스
  addLayersControl(baseGroups = c("High Price in 2021", "Hot Price in 2021"),
                   options = layersControlOptions(collapsed = F)) %>% 
  # 마커 클러스터링
  addCircleMarkers(data = apt_price,
                   # 경도
                   lng = unlist(map(apt_price$geometry,1)),
                   # 위도
                   lat = unlist(map(apt_price$geometry,2)),
                   fillColor = circle.colors,
                   weight = apt_price$py,
                   clusterOptions = markerClusterOptions(iconCreateFunction=JS(avg.formula))) %>% 
  # 그리드 추가
  addFeatures(st_sf(grid), layerId = ~seq_len(length(grid)), color = "gray")
#

 

 

6) 렌더링 함수 없이 실행하기 : 지도 애플리케이션 만들기

 

# 0310_app_3
# 그리드 선택할 수 있는 지도 웹 어플리케이션 만들기
# 렌더링 함수 없이 실습하여 확인
# 라이브러리
library(sf);library(leaflet);library(tidyverse);library(shiny);library(mapedit);library(DT);library(leafem)
# ui
ui <- fluidPage(
  # 그리드 선택 모듈
  selectModUI("selectmap"),
  "No response"
)
# server
server <- function(input, output, session){
  callModule(selectMod, "selectmap", m)
}
# shinyApp
shinyApp(ui, server)
#

 

 

7) 그리드 선택할 수 있는 어플리케이션 구현하기 : 지도 애플리케이션 만들기

 

# 그리드 선택할 수 있는 지도 웹 어플리케이션 만들기
# 렌더링 함수 없이 실습하여 확인
# 라이브러리
library(sf);library(leaflet);library(tidyverse);library(shiny);library(mapedit);library(DT);library(leafem)
# ui
ui <- fluidPage(
  # 그리드 선택 모듈
  selectModUI("selectmap"),
  "No response"
)
# server
server <- function(input, output, session){
  callModule(selectMod, "selectmap", m)
}
# shinyApp
shinyApp(ui, server)
#

 

 

 


 

3. 데이터 분석 어플리케이션 개발하기 CP 10 (0313 지난 시간 이어서)

 

 

1) 지난 시간 이어서 -> 라이브러리~클러스터링 함수까지 : 지도 애플리케이션 만들기

 

# 배포판 작성
# 10_1, 10_2, 10_3, 10_4, 10_5 종합적으로 작성 # 7장 내용 기반
# 반응형 지도 만들기
# 라이브러리
library(sp);library(sf);library(leaflet);library(tidyverse);library(raster);library(rgdal);library(tmap);library(mapedit);library(leafem);library(ggfortify);library(grid);library(shiny);library(DT)
# 2. 데이터 불러오기
# 아파트 가격
load("./data/apt_price.rdata")
# 서울시 그리드 데이터
grid <- st_read("./data/sigun_grid/seoul.shp")
# 서울시 경계선 데이터
bnd <- st_read("./data/sigun_bnd/seoul.shp")
# 최고가 레스터 데이터
load("./data/raster_high.rdata")
# 급등 지역 레스터 데이터
load("./data/raster_hot.rdata")
# 3. 그리드 데이터 전처리
# 그리드 데이터 sf형을 sp형으로 변환
grid <- as(grid, "Spatial")
# sfg: simple feature geometry
# sfc: simple feature columns (the coordinate)
# sf: simple feature columns + attribute (data.frame)
# 그리드 데이터를 sfc로 변환
grid <- as(grid, "sfc")
# 아파트 가격 데이터와 그리드 데이터 합치기
# 데이터가 있는 그리드만 추출
grid <- grid[which(sapply(st_contains(st_sf(grid), apt_price),length) > 0)]
# 4. 마커 클러스터
# 마커 클러스터링 옵션 설정
# 상위 10%, 하위 10%, 나머지
# q10 <- quantile(apt_price$py, probs = seq(.1, .9, by = .1))
# 상위 10% = 90%
pct_90 <- as.numeric(quantile(apt_price$py, probs = seq(.1, .9, by = .1))[9])
# 하위 10% = 10%
pct_10 <- as.numeric(quantile(apt_price$py, probs = seq(.1, .9, by = .1))[1])
# 마커 클러스터링 함수 불러오기
load("./data/circle_marker/circle_marker.rdata")
# 색상 설정
circle.colors <- c()

 

2) ui 설정하기 

 

# 5. 반응형 지도 만들기 => shiny application 내부
# 6. shiny application
# ui
ui <- fluidPage(
  # 상단: 좌 (반응형 지도)
  column(width = 9,
         selectModUI("selectmap"),
         div(style = "height:40px;")),
  # 상단: 우 (슬라이더-면적, 건축연도)
  column(width = 3,
         sliderInput(inputId = "range_area", # ID
                     "Area", # 제목
                     min = 0, max = 350, # 범위: 최소값, 최대값
                     value = c(0, 200)), # default 값
         sliderInput(inputId = "range_con", # ID
                     "Construction year", # 제목
                     min = 1960, max = 2021, # 범위: 최소값, 최대값
                     value = c(2000, 2021))), # default 값
  # 하단: 입력값에 따른 데이터테이블 형태로 데이터 출력
  column(width = 12,
         dataTableOutput(outputId = "table"),
         div(style = "height:200px;"))
)

 

3) 반응식 설정하기

 

# server
server <- function(input, output, session){
  # 반응식
  # A: 면적과 건축연도 범위에 해당하는 데이터 추출
  all <- reactive({
    all <- subset(apt_price, area >= input$range_area[1] & area <= input$range_area[2] & con_year >= input$range_con[1] & con_year <= input$range_con[2])
    return(all)})

 

4) 지도 입출력 모듈 설정하기 (서버와 이어짐)

 

# server
server <- function(input, output, session){
  # 반응식
  # A: 면적과 건축연도 범위에 해당하는 데이터 추출
  all <- reactive({
    all <- subset(apt_price, area >= input$range_area[1] & area <= input$range_area[2] & con_year >= input$range_con[1] & con_year <= input$range_con[2])
    return(all)})
  
  
  
  # B: 선택된 그리드 저장
  sel <- callModule(selectMod, "selectmap", 
                    leaflet() %>% 
                      addTiles(options = providerTileOptions(minZoom = 9, maxZoom = 18)) %>% 
                      # 서울시 경계선
                      addPolygons(data = bnd,
                                  color = "gold",
                                  fill = NA,
                                  weight = 5) %>% 
                      # 최고가 레스터 데이터
                      addRasterImage(raster_high,
                                     colors = colorNumeric(palette = c("blue","green","yellow","red"),
                                                           domain = values(raster_high),
                                                           na.color = "transparent"),
                                     opacity = 0.4) %>% 
                      # 급등 지역 레스터 데이터
                      addRasterImage(raster_hot,
                                     colors = colorNumeric(palette = c("blue", "green", "yellow", "red"),
                                                           domain = values(raster_hot),
                                                           na.color = "transparent"),
                                     opacity = 0.4) %>% 
                      # 체크 박스
                      addLayersControl(baseGroups = c("High Price in 2021", "Hot Price in 2021"),
                                       options = layersControlOptions(collapsed = F)) %>% 
                      # 마커 클러스터링
                      addCircleMarkers(data = apt_price,
                                       # 경도
                                       lng = unlist(map(apt_price$geometry,1)),
                                       # 위도
                                       lat = unlist(map(apt_price$geometry,2)),
                                       fillColor = circle.colors,
                                       weight = apt_price$py,
                                       clusterOptions = markerClusterOptions(iconCreateFunction=JS(avg.formula))) %>% 
                      # 그리드 추가
                      addFeatures(st_sf(grid), layerId = ~seq_len(length(grid)), color = "gray")
  )

 

5) 선택에 따른 반응 결과 저장하기

 

  # A 와 B 교집합
  # 초기 반응값 저장
  rv <- reactiveValues(intersect = NULL, selectgrid = NULL)
  # 반응 결과 저장
  observe({
    # grid selected => 선택된 그리드 저장
    gs <- sel()
    # 선택(selected == TRUE)된 그리드 ID를 숫자형으로 변환하여 그리드 데이터에서 가져오기(sf형으로 변환)
    rv$selectgrid <- st_sf(grid[as.numeric(gs[which(gs$selected == TRUE),"id"])])
    # 데이터가 있는 경우
    if(length(rv$selectgrid) > 0){
      # A & B 교집합
      rv$intersect <- st_intersects(rv$selectgrid, all())
      # geometry 삭제
      # rv$intersect 결과는 리스트
      rv$sel <- st_drop_geometry(apt_price[unlist(rv$intersect), ])
    } else{ # 데이터가 없는 경우
      rv$intersect <- NULL
    }
  })

 

6) 반응 결과 렌더링 후 실행까지

 

 # rv$sel => 데이터테이블로 저장(렌더링 함수 사용)
  output$table <- DT::renderDataTable({
    dplyr::select(rv$sel, 
                  # 열 선택
                  ymd, addr_1, apt_nm, price, area, floor, py) %>% 
      arrange(desc(py))},
    extensions = 'Buttons',
    options = list(dom = 'Bfrtrip',
                   scrollY = 300,
                   scrollCollapse = T,
                   paging = T,
                   buttons = c('excel'))
  )
}
# shinyApp
shinyApp(ui, server)
#

 

 


 

4. 데이터 분석 어플리케이션 개발하기 CP 10 (0313 새로운 파일로)

 

1) 데이터 불러오기부터 마커클러스터링까지

 

# 반응형 지도 만들기
# 7장 내용 기반
# 라이브러리
library(sf);library(leaflet);library(tidyverse);library(shiny);library(mapedit);library(DT)
# 데이터 불러오기
# 아파트 가격
load("./data/apt_price.rdata")
head(apt_price)
# 서울시 그리드 데이터
grid <- st_read("./data/sigun_grid/seoul.shp")
# 서울시 경계선 데이터
bnd <- st_read("./data/sigun_bnd/seoul.shp")
# 최고가 레스터 데이터
load("./data/raster_high.rdata")
raster_high
# 급등 지역 레스터 데이터
load("./data/raster_hot.rdata")
raster_hot
# 
# 마커 클러스터링 옵션 설정
# 상위 10%, 하위 10%, 나머지
q10 <- quantile(apt_price$py, probs = seq(.1, .9, by = .1))
# 상위 10% = 90%
pct_90 <- as.numeric(q10[9])
# 하위 10% = 10%
pct_10 <- as.numeric(q10[1])
# 마커 클러스터링 함수 불러오기
load("./data/circle_marker/circle_marker.rdata")
# 색상 설정
circle.colors <- c()
# 
# 반응형 지도 만들기
m1 <- leaflet() %>% 
  addTiles(options = providerTileOptions(minZoom = 9, maxZoom = 18)) %>% 
  # 서울시 경계선
  addPolygons(data = bnd,
              color = "gold",
              fill = NA,
              weight = 5) %>% 
  # 최고가 레스터 데이터
  addRasterImage(raster_high,
                 colors = colorNumeric(palette = c("blue","green","yellow","red"),
                                       domain = values(raster_high),
                                       na.color = "transparent"),
                 opacity = 0.4) %>% 
  # 급등 지역 레스터 데이터
  addRasterImage(raster_hot,
                 colors = colorNumeric(palette = c("blue", "green", "yellow", "red"),
                                       domain = values(raster_hot),
                                       na.color = "transparent"),
                 opacity = 0.4) %>% 
  # 체크 박스
  addLayersControl(baseGroups = c("High Price in 2021", "Hot Price in 2021"),
                   options = layersControlOptions(collapsed = F)) %>% 
  # 마커 클러스터링
  addCircleMarkers(data = apt_price,
                   # 경도
                   lng = unlist(map(apt_price$geometry,1)),
                   # 위도
                   lat = unlist(map(apt_price$geometry,2)),
                   fillColor = circle.colors,
                   weight = apt_price$py,
                   clusterOptions = markerClusterOptions(iconCreateFunction=JS(avg.formula)))
#
m1
#

 

 

2) 지도 어플리케이션 만들기

 

# 지도 애플리케이션 만들기
# 반드시 10_1 코드가 실행되어야 함
# 그리드 데이터 sf형을 sp형으로 변환
grid <- as(grid, "Spatial")
# sfg: simple feature geometry
# sfc: simple feature columns (the coordinate)
# sf: simple feature columns + attribute (data.frame)
# 그리드 데이터를 sfc로 변환
grid <- as(grid, "sfc")
grid
# 아파트 가격 데이터와 그리드 데이터 합치기
# 데이터가 있는 그리드만 추출
grid <- grid[which(sapply(st_contains(st_sf(grid), apt_price),length) > 0)]
# 그리드 확인
plot(grid)
# 그리드 추가 함수 라이브러리 준비 = addFeatures
library(leafem)
# 
# 반응형 지도 만들기
m2 <- m1 %>% 
  # 그리드 추가
  addFeatures(st_sf(grid), layerId = ~seq_len(length(grid)), color = "gray")
#

 

 

3) 그리드 선택할 수 있는 지도 웹 어플리케이션 만들기

 

# 그리드 선택할 수 있는 지도 웹 어플리케이션 만들기
# 렌더링 함수 없이 실습하여 확인
# 라이브러리
library(sf);library(leaflet);library(tidyverse);library(shiny);library(mapedit);library(DT);library(leafem)
# ui
ui <- fluidPage(
  # 그리드 선택 모듈 = 지도에서 선택된 내용이 서버로 전달
  selectModUI("selectmap"),
  "No response"
)
# server
server <- function(input, output, session){
  callModule(selectMod, "selectmap", m2)
}
# shinyApp
shinyApp(ui, server)
#

 

 

4) 렌더링 함수 및 결과 출력되는 부분 추가해서 그리드 선택할 수 있는 지도 app 만들기

 

# 그리드 선택할 수 있는 지도 웹 어플리케이션 만들기
# 렌더링 함수 추가 실습하여 확인
# 라이브러리
library(sf);library(leaflet);library(tidyverse);library(shiny);library(mapedit);library(DT);library(leafem)
# ui
ui <- fluidPage(
  # 그리드 선택 모듈 = 지도에서 선택된 내용이 서버로 전달
  selectModUI("selectmap"),
  # 결과 출력되는 부분 추가
  textOutput("sel")
)
# server
server <- function(input, output, session){
  df <- callModule(selectMod, "selectmap", m2)
  # 렌더링 함수 추가
  output$sel <- renderPrint({df()})
}
# shinyApp
shinyApp(ui, server)
#

 

5) 지도 app 완성하기

* ui 만들기

 

# 지도 웹 어플리케이션 완성하기
# 상단: 좌 (반응형 지도), 우 (슬라이더-면적, 건축연도)
# 하단: 입력값에 따른 데이터테이블 형태로 데이터 출력
# 반드시 10_1, 10_2 먼저 실행할 것
# 
# ui
ui <- fluidPage(
  # 상단: 좌 (반응형 지도)
  column(width = 9,
         selectModUI("selectmap"),
         div(style = "height:40px;")),
  # 상단: 우 (슬라이더-면적, 건축연도)
  column(width = 3,
         sliderInput(inputId = "range_area", # ID
                     "Area", # 제목
                     min = 0, max = 350, # 범위: 최소값, 최대값
                     value = c(0, 200)), # default 값
         sliderInput(inputId = "range_con", # ID
                     "Construction year", # 제목
                     min = 1960, max = 2021, # 범위: 최소값, 최대값
                     value = c(2000, 2021))), # default 값
  # 하단: 입력값에 따른 데이터테이블 형태로 데이터 출력
  column(width = 12,
         dataTableOutput(outputId = "table"),
         div(style = "height:200px;"))
)

 

* 서버 만들기 - 슬라이드 범위 선택 필터링

 

# server
server <- function(input, output, session){
  # 반응식
  # A: 면적과 건축연도 범위에 해당하는 데이터 추출
  all <- reactive({
    all <- subset(apt_price, area >= input$range_area[1] & area <= input$range_area[2] & con_year >= input$range_con[1] & con_year <= input$range_con[2])
    return(all)})

 

* 지도 데이터 불러오기

 

 # B: 선택된 그리드 저장
  # m2에 지도 정보가 담겨 있음.
  sel <- callModule(selectMod, "selectmap", m2)
  # A 와 B 교집합

 

 * 초기 반응 결과 필터링

 

  # 초기 반응값 저장
  rv <- reactiveValues(intersect = NULL, selectgrid = NULL)
  # 반응 결과 저장
  observe({
    # grid selected => 선택된 그리드 저장
    gs <- sel()
    # 선택(selected == TRUE)된 그리드 ID를 숫자형으로 변환하여 그리드 데이터에서 가져오기(sf형으로 변환)
    rv$selectgrid <- st_sf(grid[as.numeric(gs[which(gs$selected == TRUE),"id"])])
    # 데이터가 있는 경우
    if(length(rv$selectgrid) > 0){
      # A & B 교집합
      rv$intersect <- st_intersects(rv$selectgrid, all())
      # geometry 삭제
      # rv$intersect 결과는 리스트
      rv$sel <- st_drop_geometry(apt_price[unlist(rv$intersect), ])
    } else{ # 데이터가 없는 경우
      rv$intersect <- NULL
    }
  })

 

* 데이터 테이블 저장 후 실행까지

 

  
  # rv$sel => 데이터테이블로 저장(렌더링 함수 사용)
  output$table <- DT::renderDataTable({
    dplyr::select(rv$sel, 
                  # 열 선택
                  ymd, addr_1, apt_nm, price, area, floor, py) %>% 
      arrange(desc(py))},
    extensions = 'Buttons',
    options = list(dom = 'Bfrtrip',
                   scrollY = 300,
                   scrollCollapse = T,
                   paging = T,
                   buttons = c('excel'))
  )
}
# shinyApp
shinyApp(ui, server)
#

 

지도 앱 완성

 

 

6) 지도앱 배포판 전용 코드

 

# 배포판 작성
# 10_1, 10_2, 10_3, 10_4, 10_5 종합적으로 작성
# 1. 라이브러리 준비
library(sp);library(sf);library(leaflet);library(tidyverse);library(raster);library(rgdal);library(tmap);library(mapedit);library(leafem);library(ggfortify);library(grid);library(shiny);library(DT)
# 2. 데이터 불러오기
# 아파트 가격
load("./data/apt_price.rdata")
# 서울시 그리드 데이터
grid <- st_read("./data/sigun_grid/seoul.shp")
# 서울시 경계선 데이터
bnd <- st_read("./data/sigun_bnd/seoul.shp")
# 최고가 레스터 데이터
load("./data/raster_high.rdata")
# 급등 지역 레스터 데이터
load("./data/raster_hot.rdata")
# 3. 그리드 데이터 전처리
# 그리드 데이터 sf형을 sp형으로 변환
grid <- as(grid, "Spatial")
# sfg: simple feature geometry
# sfc: simple feature columns (the coordinate)
# sf: simple feature columns + attribute (data.frame)
# 그리드 데이터를 sfc로 변환
grid <- as(grid, "sfc")
# 아파트 가격 데이터와 그리드 데이터 합치기
# 데이터가 있는 그리드만 추출
grid <- grid[which(sapply(st_contains(st_sf(grid), apt_price),length) > 0)]
# 4. 마커 클러스터
# 마커 클러스터링 옵션 설정
# 상위 10%, 하위 10%, 나머지
# q10 <- quantile(apt_price$py, probs = seq(.1, .9, by = .1))
# 상위 10% = 90%
pct_90 <- as.numeric(quantile(apt_price$py, probs = seq(.1, .9, by = .1))[9])
# 하위 10% = 10%
pct_10 <- as.numeric(quantile(apt_price$py, probs = seq(.1, .9, by = .1))[1])
# 마커 클러스터링 함수 불러오기
load("./data/circle_marker/circle_marker.rdata")
# 색상 설정
circle.colors <- c()
# 5. 반응형 지도 만들기 => shiny application 내부
# 6. shiny application
# ui
ui <- fluidPage(
  # 상단: 좌 (반응형 지도)
  column(width = 9,
         selectModUI("selectmap"),
         div(style = "height:40px;")),
  # 상단: 우 (슬라이더-면적, 건축연도)
  column(width = 3,
         sliderInput(inputId = "range_area", # ID
                     "Area", # 제목
                     min = 0, max = 350, # 범위: 최소값, 최대값
                     value = c(0, 200)), # default 값
         sliderInput(inputId = "range_con", # ID
                     "Construction year", # 제목
                     min = 1960, max = 2021, # 범위: 최소값, 최대값
                     value = c(2000, 2021))), # default 값
  # 하단: 입력값에 따른 데이터테이블 형태로 데이터 출력
  column(width = 12,
         dataTableOutput(outputId = "table"),
         div(style = "height:200px;"))
)
# server
server <- function(input, output, session){
  # 반응식
  # A: 면적과 건축연도 범위에 해당하는 데이터 추출
  all <- reactive({
    all <- subset(apt_price, area >= input$range_area[1] & area <= input$range_area[2] & con_year >= input$range_con[1] & con_year <= input$range_con[2])
    return(all)})
  # B: 선택된 그리드 저장
  sel <- callModule(selectMod, "selectmap", 
                    leaflet() %>% 
                      addTiles(options = providerTileOptions(minZoom = 9, maxZoom = 18)) %>% 
                      # 서울시 경계선
                      addPolygons(data = bnd,
                                  color = "gold",
                                  fill = NA,
                                  weight = 5) %>% 
                      # 최고가 레스터 데이터
                      addRasterImage(raster_high,
                                     colors = colorNumeric(palette = c("blue","green","yellow","red"),
                                                           domain = values(raster_high),
                                                           na.color = "transparent"),
                                     opacity = 0.4) %>% 
                      # 급등 지역 레스터 데이터
                      addRasterImage(raster_hot,
                                     colors = colorNumeric(palette = c("blue", "green", "yellow", "red"),
                                                           domain = values(raster_hot),
                                                           na.color = "transparent"),
                                     opacity = 0.4) %>% 
                      # 체크 박스
                      addLayersControl(baseGroups = c("High Price in 2021", "Hot Price in 2021"),
                                       options = layersControlOptions(collapsed = F)) %>% 
                      # 마커 클러스터링
                      addCircleMarkers(data = apt_price,
                                       # 경도
                                       lng = unlist(map(apt_price$geometry,1)),
                                       # 위도
                                       lat = unlist(map(apt_price$geometry,2)),
                                       fillColor = circle.colors,
                                       weight = apt_price$py,
                                       clusterOptions = markerClusterOptions(iconCreateFunction=JS(avg.formula))) %>% 
                      # 그리드 추가
                      addFeatures(st_sf(grid), layerId = ~seq_len(length(grid)), color = "gray")
                    )
  # A 와 B 교집합
  # 초기 반응값 저장
  rv <- reactiveValues(intersect = NULL, selectgrid = NULL)
  # 반응 결과 저장
  observe({
    # grid selected => 선택된 그리드 저장
    gs <- sel()
    # 선택(selected == TRUE)된 그리드 ID를 숫자형으로 변환하여 그리드 데이터에서 가져오기(sf형으로 변환)
    rv$selectgrid <- st_sf(grid[as.numeric(gs[which(gs$selected == TRUE),"id"])])
    # 데이터가 있는 경우
    if(length(rv$selectgrid) > 0){
      # A & B 교집합
      rv$intersect <- st_intersects(rv$selectgrid, all())
      # geometry 삭제
      # rv$intersect 결과는 리스트
      rv$sel <- st_drop_geometry(apt_price[unlist(rv$intersect), ])
    } else{ # 데이터가 없는 경우
      rv$intersect <- NULL
    }
  })
  # rv$sel => 데이터테이블로 저장(렌더링 함수 사용)
  output$table <- DT::renderDataTable({
    dplyr::select(rv$sel, 
                  # 열 선택
                  ymd, addr_1, apt_nm, price, area, floor, py) %>% 
      arrange(desc(py))},
    extensions = 'Buttons',
    options = list(dom = 'Bfrtrip',
                   scrollY = 300,
                   scrollCollapse = T,
                   paging = T,
                   buttons = c('excel'))
  )
}
# shinyApp
shinyApp(ui, server)
#

 

 

 

일반적으로 해당 파일 및 data 파일 폴더만 클릭함.

하지만 상황에 따라 전체 클릭으로 확인하기도.. <-- 이 부분은 좀 더 자세히 확인하는걸로

 

* 핵심

  1. 렌더링 함수 : renderText
  2. 반응식을 왜 쓰는지 : 값을 입력했을 때 해당 되는 데이터를 출력하기 위해
  3. 마커 클러스터링을 왜 쓰는지 : 데이터 왜곡을 방지하기 위함
  4. 필터링 작업을 해야 하는 이유 : 데이터 오류 방지를 위해
  5. 배포하는 방법 : shiny Publishing

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형
LIST