[R 데이터분석 with 샤이니] 데이터 분석 어플리케이션 개발하기 2 - app.R
## 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