데이터분석/R

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

버섯도리 2022. 7. 23. 17:49

## 10-4 서울시 아파트 실거래 애플리케이션 만들기

# Step 1 : 라이브러리 불러오기

library(shiny); library(devtools); library(sf); library(purrr); library(dplyr); library(DT)
library(rgdal); library(lattice); library(latticeExtra); library(lubridate)
library(ggplot2); library(ggfortify); library(ggrepel); library(showtext)
library(leaflet); library(leaflet.extras); library(raster)
library(mapview); library(mapedit); library(grid)

# Step 2 : 힌글 글꼴 설정하기

require(showtext)
font_add_google(name='Nanum Gothic', regular.wt=400, bold.wt=700)
showtext_auto()
showtext_opts(dpi=112)

# Step 3 : 데이터 불러오기

setwd(dirname(rstudioapi::getSourceEditorContext()$path)) # shinyapps.io 서버에 배포할 때는 주석 처리
grid <- st_read("./01_code/sigun_grid/seoul.shp")
bnd <- st_read("./01_code/sigun_bnd/seoul.shp")
load("./06_geodataframe/06_apt_price.rdata")
load("./07_map/07_kde_high.rdata")
load("./07_map/07_kde_hot.rdata")

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

load("./01_code/circle_marker/circle_marker.rdata")
circle.colors <- sample(x=c("red","green","blue"), size = 1000, replace = TRUE)

# Step 5 : 그리드 필터링하기

grid <- as(grid, "Spatial"); grid <- as(grid, "sfc")
grid <- grid[which(sapply(st_contains(st_sf(grid), apt_price), length) > 0)]

# Step 6 : 사용자 화면 만들기

ui <- fluidPage(
  fluidRow(
    column(9, selectModUI("selectmap"), div(style = "hegith:45px")),
    column(3,
           sliderInput("range_time", "Construction Year", sep = "", min = 1960, max = 2021, value = c(1970,2020)),
           sliderInput("range_area", "Area", sep = "", min = 0, max = 350, value = c(0,200))
    )
  ),
  tabsetPanel(
    tabPanel("Chart",
             column(4, h5("Price Range", align = "center"),
                    plotOutput("density", height = 300)),
             column(4, h5("Price Trends", align = "center"),
                    plotOutput("regression", height = 300)),
             column(4, h5("PCA", align = "center"),
                    plotOutput("pca", height = 300)),
    ),
    tabPanel("Table", DT::dataTableOutput("table"))
  )
)

# Step 7 : 서버 만들기

server <- function(input, output, session) {

  #---# 필터링 by 슬라이더
  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
  })
  
  #---# 지도 그리기
  m <- leaflet() %>%
    addTiles(options = providerTileOptions(minZoom = 9, maxzoom = 18)) %>%
    addPolygons(data = bnd, weight = 3, stroke = T, color = "red", fillOpacity = 0) %>%
    addRasterImage(raster_high,
                   colors = colorNumeric(c("blue", "green", "yellow", "red"), values(raster_high)
                                         , na.color = "transparent"), opacity = 0.4, group = "2021 최고가") %>%
    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")

  g_sel <- callModule(selectMod, "selectmap", m)
  
  #---# 반응 결과 필터링
  rv <- reactiveValues(intersect=NULL, selectgrid=NULL)
  observe({
    gs <- g_sel()
    rv$selectgrid <- st_sf(grid[as.numeric(gs[which(gs$selected == TRUE), "id"])])
    if(length(rv$selectgrid) > 0) {
      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
    }
  })

  #---# 차트 그리기 1 : 확률 밀도 함수
  output$density <- renderPlot({
    if (nrow(rv$intersect) == 0)
      return(NULL)
    
    max_all <- density(apt_sel()$py) ; max_all <- max(max_all$y)
    max_sel <- density(rv$sel$py) ; max_sel <- max(max_sel$y)
    plot_high <- max(max_all, max_sel)
    avg_all <- mean(apt_sel()$py)
    avg_sel <- mean(rv$sel$py)
    
    plot(stats::density(apt_sel()$py), xlab=NA, ylab=NA, ylim=c(0, plot_high),
         col="blue", lwd=3, main=NA)
    abline(v = avg_all, lwd=2, col="blue", lty=2)
    text(avg_all + avg_all*0.13, plot_high*0.1,
         sprintf("%.0f", avg_all), srt=0.2, col="blue")
    lines(stats::density(rv$sel$py), ylim=c(0, plot_high), col="red", lwd=3, main=NA)
    abline(v = avg_sel, lwd=2, col="red", lty=2)
    text(avg_sel + avg_sel*0.13, plot_high*0.3,
         sprintf("%.0f", avg_sel), srt=0.2, col="red")
  })
  
  #---# 차트 그리기 2 : 회귀 분석
  output$regression <- renderPlot({
    if (nrow(rv$intersect) == 0)
      return(NULL)
    
    all <- aggregate(apt_sel()$py, by=list(apt_sel()$ym), mean)
    sel <- aggregate(rv$sel$py, by=list(rv$sel$ym), mean)
    
    fit_all <- lm(all$x ~ all$Group.1)
    fit_sel <- lm(sel$x ~ sel$Group.1)
    coef_all <- round(summary(fit_all)$coefficients[2], 1) * 365
    coef_sel <- round(summary(fit_sel)$coefficients[2], 1) * 365
    
    grob_all <- grobTree(textGrob(paste0("All : ", coef_all), x=0.05,
                                  y=0.84, hjust=0, gp=gpar(col="blue", fontsize=13)))
    grob_sel <- grobTree(textGrob(paste0("Sel : ", coef_sel), x=0.05,
                                  y=0.95, hjust=0, gp=gpar(col="red", fontsize=16, fontface="bold")))
    
    gg <- ggplot(sel, aes(x=Group.1, y=x, group=1)) +
      theme(axis.text.x = element_text(angle = 90)) +
      stat_smooth(method = 'lm', colour = 'dark grey', linetype = "dashed") +
      geom_line(color = "red", size = 1.5) + xlab("month") + ylab("price") +
      theme_bw()
    gg + geom_line(data = all, aes(x=Group.1, y=x, group=1), color = "blue", size = 1.5) +
      annotation_custom(grob_all) +
      annotation_custom(grob_sel)
  })  

  #---# 차트 그리기 3 : 주성분 분석
  output$pca <- renderPlot({
    if (nrow(rv$intersect) == 0)
      return(NULL)
    
    pca_01 <- aggregate(list(rv$sel$con_year, rv$sel$floor, rv$sel$py, rv$sel$area),
                        by=list(rv$sel$apt_nm), mean)
    colnames(pca_01) <- c("apt_nm", "new", "floor", "price", "area")
    m <- prcomp(~ new + floor + price + area, data = pca_01, scale = T)
    
    autoplot(m, size = NA, loadings.label = T, loadings.label.size = 4) +
      geom_label_repel(aes(label = pca_01$apt_nm), size = 3, alpha = 0.7, family="Nanum Gothic")
  })
  
  #---# 테이블 그리기
  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'))
  )
}

# Run the application 
shinyApp(ui = ui, server = server)

 

 

https://wygddp-meongtae.shinyapps.io/Doit_R_Shiny/

 

 

 

 

 

 

 

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