본문 바로가기
데이터분석/R

[R 데이터분석 with 샤이니] 데이터 분석 어플리케이션 개발하기

by 버섯도리 2022. 7. 22.

> ## 10-1 반응형 지도 만들기

> # Step 1 : 데이터 불러오기

> library(sf)
> setwd(dirname(rstudioapi::getSourceEditorContext()$path))
> load("./06_geodataframe/06_apt_price.rdata") # 아파트 실거래 데이터
> bnd <- st_read("./01_code/sigun_bnd/seoul.shp") # 서울시 경계선
Reading layer `seoul' from data source `D:\1_Study\1_BigData\01_R\02_Doit_R_Shiny\01_code\sigun_bnd\seoul.shp' using driver `ESRI Shapefile'
Simple feature collection with 1 feature and 1 field
Geometry type: POLYGON
Dimension:     XY
Bounding box:  xmin: 126.7645 ymin: 37.42899 xmax: 127.1835 ymax: 37.70146
Geodetic CRS:  GCS_unknown
> load("./07_map/07_kde_high.rdata") # 최고가 래스터 이미지
> load("./07_map/07_kde_hot.rdata") # 급등 지역 래스터 이미지

> # Step 2 : 마커 클러스터링 설정

> load("./01_code/circle_marker/circle_marker.rdata") # 마커 클러스터링 함수(javascript avg.fomula)
> circle.colors <- sample(x=c("red","green","blue"), size = 1000, replace = TRUE)

> # Step 3 : 반응형 지도 만들기

> library(purrr)
> library(leaflet)
> library(raster)
leaflet() %>%
+   #---# 기본 앱 설정 : 오픈스트리트맵
+   addTiles(options = providerTileOptions(minZoom = 9, maxzoom = 18)) %>%
+   #---# 서울시 외곽 경계선
+   addPolygons(data = bnd, weight = 3, color = "red", fillOpacity = 0) %>%
+   #---# 최고 지역 KDE
+   addRasterImage(raster_high,
+                  colors = colorNumeric(c("blue", "green", "yellow", "red"), values(raster_high)
+                                        , na.color = "transparent"), opacity = 0.4, group = "2021 최고가") %>%
+   #---# 급등지역 KDE
+   addRasterImage(raster_hot,
+                  colors = colorNumeric(c("blue", "green", "yellow", "red"), values(raster_hot)
+                                        , na.color = "transparent"), opacity = 0.4, group = "2021 급등지") %>%
+   #---# 레이어 스위치 메뉴
+   addLayersControl(baseGroups = c("2021 최고가", "2121 급등지"),
+                    options = layersControlOptions(collapsed = FALSE)) %>%
+   #---# 마커 클러스터링
+   addCircleMarkers(data = apt_price, lng = unlist(map(apt_price$geometry,1)),
+                    lat = unlist(map(apt_price$geometry,2)), radius = 10, stroke = FALSE,
+                    fillOpacity = 0.6, fillColor = circle.colors, weight = apt_price$py,
+                    clusterOptions = markerClusterOptions(iconCreateFunction=JS(avg.formula)))
Warning messages:
1: In showSRID(uprojargs, format = "PROJ", multiline = "NO", prefer_proj = prefer_proj) :
  Discarded ellps WGS 84 in Proj4 definition: +proj=merc +a=6378137 +b=6378137 +lat_ts=0 +lon_0=0 +x_0=0 +y_0=0 +k=1 +units=m +nadgrids=@null +wktext +no_defs +type=crs
2: In showSRID(uprojargs, format = "PROJ", multiline = "NO", prefer_proj = prefer_proj) :
  Discarded datum World Geodetic System 1984 in Proj4 definition
3: In wkt(projfrom) : CRS object has no comment
4: In wkt(pfrom) : CRS object has no comment
5: In rgdal::rawTransform(projfrom, projto, nrow(xy), xy[, 1], xy[,  :
  Using PROJ not WKT2 strings
6: In wkt(pfrom) : CRS object has no comment
7: In rgdal::rawTransform(projfrom, projto, nrow(xy), xy[, 1], xy[,  :
  Using PROJ not WKT2 strings
8: In rgdal::rawTransform(projto_int, projfrom, nrow(xy), xy[, 1],  :
  Using PROJ not WKT2 strings
9: In wkt(pfrom) : CRS object has no comment
10: In rgdal::rawTransform(projfrom, projto, nrow(xy), xy[, 1], xy[,  :
  Using PROJ not WKT2 strings

>

> ## 10-2 지도 애플리케이션 만들기

> # Step 1 : 그리드 필터링하기

> grid <- st_read("./01_code/sigun_grid/seoul.shp") # 서울시 1km 그리드
Reading layer `seoul' from data source `D:\1_Study\1_BigData\01_R\02_Doit_R_Shiny\01_code\sigun_grid\seoul.shp' using driver `ESRI Shapefile'
Simple feature collection with 694 features and 2 fields
Geometry type: MULTIPOLYGON
Dimension:     XY
Bounding box:  xmin: 126.7645 ymin: 37.42899 xmax: 127.1835 ymax: 37.70146
Geodetic CRS:  GCS_unknown
> grid <- as(grid, "Spatial"); grid <- as(grid, "sfc")
> grid <- grid[which(sapply(st_contains(st_sf(grid), apt_price), length) > 0)] # 필터링
plot(grid)


> # Step 2 : 반응형 지도 모듈화하기

> m <- leaflet() %>%
+   #---# 기본 앱 설정 : 오픈스트리트맵
+   addTiles(options = providerTileOptions(minZoom = 9, maxzoom = 18)) %>%
+   #---# 서울시 외곽 경계선
+  addPolygons(data = bnd, weight = 3, stroke = T, color = "red", fillOpacity = 0) %>%
+   #---# 최고 지역 KDE
+   addRasterImage(raster_high,
+                  colors = colorNumeric(c("blue", "green", "yellow", "red"), values(raster_high)
+                                        , na.color = "transparent"), opacity = 0.4, group = "2021 최고가") %>%
+   #---# 급등지역 KDE
+   addRasterImage(raster_hot,
+                  colors = colorNumeric(c("blue", "green", "yellow", "red"), values(raster_hot)
+                                        , na.color = "transparent"), opacity = 0.4, group = "2021 급등지") %>%
+   #---# 레이어 스위치 메뉴
+   addLayersControl(baseGroups = c("2021 최고가", "2121 급등지"),
+                    options = layersControlOptions(collapsed = FALSE)) %>%
+   #---# 마커 클러스터링
+   addCircleMarkers(data = apt_price, lng = unlist(map(apt_price$geometry,1)),
+                    lat = unlist(map(apt_price$geometry,2)), radius = 10, stroke = FALSE,
+                    fillOpacity = 0.6, fillColor = circle.colors, weight = apt_price$py,
+                    clusterOptions = markerClusterOptions(iconCreateFunction=JS(avg.formula))) %>%
+   #---# 그리드
+   leafem::addFeatures(st_sf(grid), layerId = ~seq_len(length(grid)), color = "grey")
Warning messages:
1: In wkt(projfrom) : CRS object has no comment
2: In wkt(pfrom) : CRS object has no comment
3: In rgdal::rawTransform(projfrom, projto, nrow(xy), xy[, 1], xy[,  :
  Using PROJ not WKT2 strings
4: In wkt(pfrom) : CRS object has no comment
5: In rgdal::rawTransform(projfrom, projto, nrow(xy), xy[, 1], xy[,  :
  Using PROJ not WKT2 strings
6: In rgdal::rawTransform(projto_int, projfrom, nrow(xy), xy[, 1],  :
  Using PROJ not WKT2 strings
7: In wkt(pfrom) : CRS object has no comment
8: In rgdal::rawTransform(projfrom, projto, nrow(xy), xy[, 1], xy[,  :
  Using PROJ not WKT2 strings
> # ---> 이렇게 만든 m은 자바스크립트를 포함하는 htmlwidgets라고 하며 샤이니에서 불러와서 사용할 수 있다.
> m


> # Step 3,4 : 애플리케이션 구현하기

> library(shiny)
> install.packages("mapedit")
> library(mapedit)
> library(dplyr)

> ui <- fluidPage(
+   selectModUI("selectmap"), # 지도 입력 모듈. 지도에서 특정 지점이 선택될 때 입력값을 서버로 전달
+   textOutput("sel")
+ )
> server <- function(input, output, session) {
+   df <- callModule(selectMod, "selectmap", m) # 입력 결과를 처리하여 다시 화면으로 전달하는 출력 모듈
+   output$sel <- renderPrint({
+     if (nrow(df() > 0)) {
+       df()[1]
+     }
+   })
+ }
> shinyApp(ui, server)

Listening on http://127.0.0.1:5504


>

> ## 10-3 반응형 지도 애플리케이션 완성하기

> # Step 1 : 사용자 인터페이스 설정하기

> library(DT)
> ui <- fluidPage(
+   fluidRow(
+     column(9, selectModUI("selectmap"), div(style = "hegith:45px")),
+     column(3,
+            sliderInput("range_area", "전용면적", sep = "", min = 0, max = 350, value = c(0,200)),
+            sliderInput("range_time", "건축연도", sep = "", min = 1960, max = 2020, value = c(1980,2020))
+     ),
+     column(12, dataTableOutput("table"), div(style = "height:200px"))
+     )
+ )

> # Step 2 : 반응식 설정하기

> server <- function(input, output, session) {
+   apt_sel <- reactive({
+     apt_sel <- subset(apt_price,
+                       con_year >= input$range_time[1] & con_year <= input$range_time[2] &
+                         area >= input$range_area[1] & area <= input$range_area[2])
+     apt_sel
+   })
+   
+   # Step 3 : 지도 입출력 모듈 설정하기
+   
+   g_sel <- callModule(selectMod, "selectmap", m)
+   
+   # Step 4 : 선택에 따른 반응 결과 저장하기
+   
+   #---# 반응 초기값 설정(NULL)
+   rv <- reactiveValues(intersect=NULL, selectgrid=NULL)
+   #---# 반응 결과(rv:reactive value) 저장
+   observe({
+     gs <- g_sel()
+     rv$selectgrid <- st_sf(grid[as.numeric(gs[which(gs$selected == TRUE), "id"])])
+     if(length(rv$selectgrid) > 0) {

+     # 특정 그리드를 선택하면 해당 그리드 내 정보를 추출한 다음 rv$sel에 저장한다.

+     # st_drop_geometry()로 불필요한 공간 정보는 제거한다.

+       rv$intersect <- st_intersects(rv$selectgrid, apt_sel())
+       rv$sel <- st_drop_geometry(apt_sel()[apt_sel()[unlist(rv$intersect[1:10]),],])
+     } else {
+       rv$intersect <- NULL
+     }
+   })
+   
+   # Step 5 : 반응 결과 렌더링
+   
+   output$table <- DT::renderDataTable({
+     dplyr::select(rv$sel, ymd, addr_1, apt_nm, price, area, floor, py) %>%
+       arrange(desc(py))
+   }, extension = 'Buttons',
+   options = list(dom = 'Bfrtip', scrollY = 300, scrollCollapse = T, paging = TRUE, buttons = c('excel'))
+   )
+ }

> # Step 6 : 애플리케이션 실행하기

> shinyApp(ui, server)

Listening on http://127.0.0.1:5504



 

 

 

 

 

 

출처 : 김철민, ⌜공공데이터로 배우는 R 데이터분석 with 샤이니⌟, 이지스퍼블리싱, 2022