한국 아파트 매매 분석 및 시각화
highcharter
국토교통부 실거래가 데이터
ko_apt.RData
Chapter 1.
# data handling
library(dplyr)
library(lazyeval)
library(tidyr)
library(data.table)
library(lubridate)
# chapter 1
library(budongsan) # devtools::install_github("keepcosmos/budongsan")
library(highcharter)
library(purrr)
library(viridis)
# chapter 2
library(XML)
library(stringr)
library(ggplot2)
library(leaflet)
library(jsonlite)
budongsan
패키지에 내장돼 있는 데이터입니다.molit.rt.{type}.{year}
함수를 통해서 특정 데이터를 불러올 수 있습니다.type
:
apt
: 아파트sh
: 단독 / 다가구rh
: 연립 / 다세대year
: 2006 ~ 2016ko_apt_all <- molit.rt.get("apt")
load("ko_apt.RData")
knitr::kable(head(ko_apt_all[, 1:9]))
region | zipcode | housing.estate | net.area | day | trading.price | floor | build.year | street |
---|---|---|---|---|---|---|---|---|
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 35.64 | 1~10 | 38000 | 3 | 1982 | 개포로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 45.26 | 1~10 | 51000 | 4 | 1982 | 개포로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 49.56 | 11~20 | 70000 | 1 | 1982 | 선릉로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 49.56 | 11~20 | 70000 | 4 | 1982 | 선릉로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 50.64 | 1~10 | 65700 | 3 | 1982 | 개포로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 50.64 | 1~10 | 67000 | 2 | 1982 | 개포로 |
seoul_apt <- data.table(molit.rt.get("apt", "서울"))
seoul_apt_buy <- seoul_apt[deal.type == "매매" , ]
knitr::kable(head(seoul_apt_buy[, 1:9]))
region | zipcode | housing.estate | net.area | day | trading.price | floor | build.year | street |
---|---|---|---|---|---|---|---|---|
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 35.64 | 1~10 | 38000 | 3 | 1982 | 개포로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 45.26 | 1~10 | 51000 | 4 | 1982 | 개포로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 49.56 | 11~20 | 70000 | 1 | 1982 | 선릉로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 49.56 | 11~20 | 70000 | 4 | 1982 | 선릉로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 50.64 | 1~10 | 65700 | 3 | 1982 | 개포로 |
서울특별시 강남구 개포동 | 141 | 개포주공 1단지 | 50.64 | 1~10 | 67000 | 2 | 1982 | 개포로 |
seoul_apt_buy_date <- seoul_apt_buy %>%
group_by(year, month) %>%
summarise(mean = mean(trading.price),
lower = quantile(trading.price)[2],
upper = quantile(trading.price)[4]) %>%
mutate(date = paste(year, ".", month, ".01", sep = ""))
seoul_apt_buy_date$date <- ymd(seoul_apt_buy_date$date)
df <- seoul_apt_buy_date %>%
mutate(tmpstmp = datetime_to_timestamp(date),
color_m = colorize(mean, viridis(10, option = "B")),
color_m = hex_to_rgba(color_m, 0.65))
knitr::kable(head(df))
year | month | mean | lower | upper | date | tmpstmp | color_m |
---|---|---|---|---|---|---|---|
2006 | 1 | 27003.20 | 16700 | 32700 | 2006-01-01 | 1.136074e+12 | rgba(244,131,20,0.65) |
2006 | 2 | 25819.76 | 16000 | 32000 | 2006-02-01 | 1.138752e+12 | rgba(168,46,93,0.65) |
2006 | 3 | 29134.20 | 17000 | 35800 | 2006-03-01 | 1.141171e+12 | rgba(252,255,164,0.65) |
2006 | 4 | 27583.18 | 16600 | 34400 | 2006-04-01 | 1.143850e+12 | rgba(249,173,25,0.65) |
2006 | 5 | 26953.20 | 16900 | 33000 | 2006-05-01 | 1.146442e+12 | rgba(228,94,46,0.65) |
2006 | 6 | 25031.76 | 15500 | 31000 | 2006-06-01 | 1.149120e+12 | rgba(57,12,92,0.65) |
dfcolyrs <- seoul_apt_buy_date %>%
select(year, mean) %>%
group_by(year) %>%
summarise(mean = mean(mean)) %>%
ungroup() %>%
mutate(color_y = colorize(mean, viridis(10, option = "B")),
color_y = hex_to_rgba(color_y, 0.65)) %>%
select(-mean)
df_dfcolyrs <- left_join(df, dfcolyrs, by = "year")
lsseries2_bu <- df_dfcolyrs %>%
group_by(year) %>%
do(data = .$mean, color = "transparent", enableMouseTracking = FALSE, color2 = first(.$color_y)) %>%
mutate(name = year) %>%
list.parse3()
## Warning: 'list.parse3' is deprecated.
## Use 'list_parse' instead.
## See help("Deprecated")
motion_1 <- highchart() %>%
hc_chart(polar = TRUE) %>%
hc_plotOptions(series = list(marker = list(enabled = FALSE), animation = TRUE, pointIntervalUnit = "month")) %>%
hc_legend(enabled = FALSE) %>%
hc_title(text = "Animated Spiral") %>%
hc_xAxis(type = "datetime", min = 0, max = 365 * 24 * 36e5, labels = list(format = "{value:%B}")) %>%
hc_tooltip(headerFormat = "{point.key}", xDateFormat = "%B", pointFormat = " {series.name}: {point.y}") %>%
hc_add_series_list(lsseries2_bu) %>%
hc_chart(events = list(load = JS("
function() {
console.log('ready');
var duration = 16 * 1000
var delta = duration/this.series.length;
var delay = 1000;
this.series.map(function(e){
setTimeout(function() {
e.update({color: e.options.color2, enableMouseTracking: true});
e.chart.setTitle({text: e.name})
}, delay)
delay = delay + delta;
});
}
")))
polar line chart
로 그렸습니다.JS
를 통해 2006년부터 2016년까지 연도별로 시간에 따라 차트가 추가됩니다.프레임 새로고침
해야합니다. motion_1
KATA <- ko_apt_all[trading.price > 0 & floor > 0, c("net.area", "trading.price", "floor","year", "deal.type")]
KATA[net.area < 100, size := "30평 이하"]
KATA[net.area >= 100, size := "30평 이상"]
names(KATA) <- tolower(names(KATA))
KATA_1 <- KATA %>%
group_by(year, floor, size) %>%
summarise(price_sp = round(mean(trading.price), 0)) %>%
mutate(price = price_sp*ifelse(size == "30평 이상", -1, 1)) %>%
select(-price_sp)
KATA_2 <- KATA_1 %>%
group_by(year) %>%
spread(floor, price) %>%
mutate_each(funs_(interp(~replace(., is.na(.), 0)))) %>%
gather(floor, price, 3:82)
knitr::kable(head(KATA_2))
year | size | floor | price |
---|---|---|---|
2006 | 30평 이상 | 1 | -28915 |
2006 | 30평 이하 | 1 | 11425 |
2007 | 30평 이상 | 1 | -24148 |
2007 | 30평 이하 | 1 | 10350 |
2008 | 30평 이상 | 1 | -27416 |
2008 | 30평 이하 | 1 | 11410 |
KATA_series <- KATA_2 %>%
group_by(size, floor) %>%
do(data = list(sequence = .$price)) %>%
ungroup() %>%
group_by(size) %>%
do(data = .$data) %>%
mutate(name = size) %>%
list_parse()
xaxis <- list(categories = sort(as.numeric(unique(KATA_2$floor))),
reversed = FALSE, tickInterval = 2,
labels = list(step = 2))
maxpop <- max(abs(KATA_1$price))
motion_2 <- highchart() %>%
hc_title(text = "층별 아파트 평균 매매가") %>%
hc_chart(type = "bar") %>%
hc_motion(enabled = T, labels = 2006:2016, series = c(0:1), loop = TRUE , updateInterval = 1) %>%
hc_add_series_list(KATA_series) %>%
hc_plotOptions(series = list(stacking = "normal"),
bar = list(groupPadding = 0, pointPadding = 0, borderWidth = 0)) %>%
hc_tooltip(shared = TRUE) %>%
hc_yAxis(labels = list(formatter = JS("function(){ return Math.abs(this.value) /10000 + '억'; }")),
tickInterval = 100000,
min = -maxpop,
max = maxpop) %>%
hc_xAxis(xaxis,
rlist::list.merge(xaxis, list(opposite = TRUE, linkedTo = 0))) %>%
hc_tooltip(shared = FALSE,
formatter = JS(
"function () { return '<b>' + this.series.name + ', floor ' + this.point.category + '</b><br/>' +
'price: ' + Highcharts.numberFormat(Math.abs(this.point.y), 0);}"
)
)
bar chart
로 그렸습니다.Motion_1
차트와 비슷하게 JS 함수를 이용했습니다.loop = True
함수를 통해 자동적으로 반복되게 만들었습니다. motion_2
korea_apt <- list(
na = ko_apt_all[grepl("세종특별자치시", ko_apt_all$region), ],
Gyeonggi = ko_apt_all[grepl("경기도", ko_apt_all$region), ],
North_Jeolla = ko_apt_all[grepl("전라북도", ko_apt_all$region), ],
South_Gyeongsang = ko_apt_all[grepl("경상남도", ko_apt_all$region), ],
South_Jeolla = ko_apt_all[grepl("전라남도", ko_apt_all$region), ],
Busan = ko_apt_all[grepl("부산광역시", ko_apt_all$region), ],
North_Gyeongsang = ko_apt_all[grepl("경상북도", ko_apt_all$region), ],
Sejong = ko_apt_all[grepl("세종특별자치시", ko_apt_all$region), ],
Daejeon = ko_apt_all[grepl("대전광역시", ko_apt_all$region), ],
Ulsan = ko_apt_all[grepl("울산광역시", ko_apt_all$region), ],
Incheon = ko_apt_all[grepl("인천광역시", ko_apt_all$region), ],
Gangwon = ko_apt_all[grepl("강원도", ko_apt_all$region), ],
South_Chungcheong = ko_apt_all[grepl("충청남도", ko_apt_all$region), ],
Jeju = ko_apt_all[grepl("제주특별자치도", ko_apt_all$region), ],
North_Chungcheong = ko_apt_all[grepl("충청북도", ko_apt_all$region), ],
Seoul = ko_apt_all[grepl("서울특별시", ko_apt_all$region), ],
Daegu = ko_apt_all[grepl("대구광역시", ko_apt_all$region), ],
Gwangju = ko_apt_all[grepl("광주광역시", ko_apt_all$region), ]
)
SVG maps
https://www.amcharts.com/svg-maps/는 다양한 맵차트에 폴리곤형식의 base data를 주는 오픈소스입니다.mapdata <- get_data_from_map(download_map_data("countries/kr/kr-all"))
glimpse(mapdata)
## Observations: 18
## Variables: 20
## $ hc-group <chr> "admin1", "admin1", "admin1", "admin1", "admin1", ...
## $ hc-middle-x <dbl> 0.93, 0.81, 0.51, 0.35, 0.72, 0.89, 0.32, 0.50, 0....
## $ hc-middle-y <dbl> 0.70, 0.72, 0.51, 0.46, 0.32, 0.43, 0.67, 0.73, 0....
## $ hc-key <chr> "kr-4194", "kr-kg", "kr-cb", "kr-kn", "kr-2685", "...
## $ hc-a2 <chr> "NU", "KG", "CB", "KN", "SJ", "PU", "NG", "SJ", "T...
## $ labelrank <chr> "20", "6", "6", "6", "6", "9", "6", "9", "9", "6",...
## $ hasc <chr> "-99", "KR.KG", "KR.CB", "KR.KN", "KR.KJ", "KR.PU"...
## $ woe-id <chr> "-99", "2345969", "2345964", "2345967", "2345972",...
## $ country <chr> "South Korea", "South Korea", "South Korea", "Sout...
## $ longitude <chr> "126.592", "127.205", "127.081", "128.217", "126.8...
## $ latitude <chr> "34.1079", "37.2562", "35.679", "35.3192", "34.816...
## $ alt-name <chr> NA, "Kyonggi-do|Keiki-do|Kyunggi", "Chollabuk-Do|C...
## $ fips <chr> NA, "KS13", "KS03", "KS20", "KS18", "KS10", "KS14"...
## $ postal-code <chr> NA, "KG", "CB", "KN", NA, "PU", NA, "SJ", "TJ", "U...
## $ name <chr> NA, "Gyeonggi", "North Jeolla", "South Gyeongsang"...
## $ type-en <chr> NA, "Province", "Province", "Province", "Metropoli...
## $ woe-name <chr> NA, "Gyeonggi-do", "Jeollabuk-do", "Gyeongsangnam-...
## $ woe-label <chr> NA, "Kyeongki-Do, KR, South Korea", "Jeollabuk-Do,...
## $ type <chr> NA, "Do", "Do", "Do", "Gwangyeoksi", "Gwangyeoksi"...
## $ region <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "Incheon",...
DT::datatable(korea)
hcmap_1 <- hcmap("countries/kr/kr-all",
data = korea,
value = "value",
joinBy = c("hc-key", "code"),
name = "아파트 평균가",
dataLabels = list(enabled = TRUE, format = '{point.name}'),
borderColor = "#FAFAFA", borderWidth = 0.1,
tooltip = list(valueDecimals = 2, valuePrefix = "", valueSuffix = " 만원"))
hcmap_1
노른자 땅(서울)
을 보기 위해서는 50000~60000만원만 남기고 클릭해제하면 됩니다.zoomType
함수를 통해서 확대가 가능합니다. (드래그 해보세요)hcmap_2 <- hcmap("countries/kr/kr-all",
data = korea,
value = "value",
joinBy = c("hc-key", "code"),
name = "평균가",
dataLabels = list(enabled = TRUE, format = '{point.name}'),
borderColor = "#FAFAFA",
borderWidth = 1,
tooltip = list(valueDecimals = 0,valuePrefix = "", valueSuffix = " 만원")) %>%
hc_colorAxis(dataClasses = color_classes(c(10000, 15000, 20000, 25000, 30000, 50000, 60000)),
minColor = "#FFFFFF", maxColor = "#434348", type = "logarithmic") %>%
hc_legend(layout = "vertical", align = "right", floating = TRUE, valueDecimals = 0, valueSuffix = "만원") %>%
hc_chart(zoomType = "xy", panKey = "shift")
hcmap_2
Chapter 2.
budongsan
패키지의 단점은 제작자가 2016년 11월 이후에 업데이트를 안한다는 것입니다.REST
(Representational State Transfer) 제공합니다.API
의 기본적인 사용방법은 원하는 데이터를 Query
형태로 불러들여 JSON
형태로 읽어주면 됩니다.api_url <- "http://openapi.molit.go.kr/OpenAPI_ToolInstallPackage/service/rest/RTMSOBJSvc/getRTMSDataSvcAptTradeDev?LAWD_CD="
service_key <- "OM9kzkoOD0ZxX3nsqQZ9BfhTIl7e%2FNMFLDoKTCifJlcb%2B2wzMqYFtQ1%2FfO68g5fmoGfa7LW8w1IzCI7AnxCX0w%3D%3D"
#서울시 지역코드
locCode <-c("11110","11140","11170","11200","11215","11230","11260","11290","11305","11320",
"11350","11380","11410","11440","11470","11500","11530","11545","11560","11590",
"11620","11650","11680","11710","11740")
locCode_nm <-c("종로구","중구","용산구","성동구","광진구","동대문구","중랑구","성북구","강북구","도봉구",
"노원구","은평구","서대문구","마포구","양천구","강서구","구로구","금천구","영등포구","동작구",
"관악구","서초구","강남구","송파구","강동구")
datelist <-c("201701","201702","201703")
urllist <- list()
cnt <-0
for(i in 1:length(locCode)){
for(j in 1:length(datelist)){
cnt = cnt + 1
urllist[cnt] <- paste0(api_url,locCode[i],"&DEAL_YMD=",datelist[j],"&numOfRows=1000","&serviceKey=",service_key)
}
}
total<-list()
for(i in 1:length(urllist)){
item <- list()
item_temp_dt<-data.table()
raw.data <- xmlTreeParse(urllist[i], useInternalNodes = TRUE,encoding = "utf-8")
rootNode <- xmlRoot(raw.data)
items <- rootNode[[2]][['items']]
size <- xmlSize(items)
for(j in 1:size){
item_temp <- xmlSApply(items[[j]],xmlValue)
item_temp_dt <- data.table( price = item_temp[1],
con_year = item_temp[2],
year = item_temp[3],
street = item_temp[4],
dong = item_temp[11],
aptnm = item_temp[17],
month = item_temp[18],
dat = item_temp[19],
area = item_temp[21],
bungi = item_temp[22],
floor = item_temp[24],
gu_code = locCode[((j-1)%/%12)+1],
gu = locCode_nm[((j-1)%/%12)+1]
)
item[[j]]<-item_temp_dt
}
total[[i]] <- rbindlist(item)
}
budongsan
패키지와 비슷한 형태로 핸들링했습니다. seoul_apt_api_2017 <- rbindlist(total)
seoul_apt_api_2017 <- na.omit(seoul_apt_api_2017)
seoul_apt_api_2017$price <- as.numeric(gsub(",", "",seoul_apt_api_2017$price))
hcboxplot(x = seoul_apt_api_2017$price, var = seoul_apt_api_2017$gu,
name = "Length", color = "#2980b9") %>% hc_add_theme(hc_theme_sandsignika())
2017년 서울의 아파트 매매가격을 본 결과 25개의 자치구가 큰 차이가 없을을 볼 수 있습니다. 예를들어 강남구
의 아파트 매매가가 다른 곳과 크게 다르지 않습니다.
아파트 금액을 이용해 시각화할 아이디어를 찾다가 부동산이나 복덕방에 있는 친근한 사진이 생각났습니다.
leaflet
패키지를 이용하기 위해서는 지구의 x, y축 좌표 즉 위경도 좌표가 필요합니다.다음지도 API
http://apis.map.daum.net/를 이용해서 주소
를 좌표
로 변환시켰습니다.동
별로 아파트 매매가격의 평균을 이용해서 표시했습니다. seoul_apt_api_2017_map <- seoul_apt_api_2017 %>% group_by(dong) %>% summarise(n = n(), mean = round(mean(price), 0))
seoul_apt_api_2017_map <- as.data.table(seoul_apt_api_2017_map)
for(i in 1:nrow(seoul_apt_api_2017_map)){
post <- paste("서울특별시", seoul_apt_api_2017_map[i, ]$dong, sep = " ")
# post <- iconv(post, from = "cp949", to = "UTF-8") # win version
post_url <- URLencode(post)
url <- paste0("https://apis.daum.net/local/geo/addr2coord?apikey=1d21e7ebdcb92cd633f79cc7c93a7667&q=",
post_url,"&output=json")
url_query <- readLines(url)
url_json <- fromJSON(paste0(url_query, collapse = ""))
x_point <- mean(url_json$channel$item$point_x)
y_point <- mean(url_json$channel$item$point_y)
seoul_apt_api_2017_map[i, x_ := x_point]
seoul_apt_api_2017_map[i, y_ := y_point]
seoul_apt_api_2017_map[i, n_ := i]
}
DT::datatable(seoul_apt_api_2017_map)
seoul_apt_api_2017_map_purr <- seoul_apt_api_2017_map %>%
mutate(level = cut(mean, c(0, 50000, 100000, 200000, 500000), labels = c("~ 5억", "5억 ~ 10억", "10억 ~ 20억", "20억 ~ 50억")))
seoul_apt_api_2017_map_purr_split <- split(seoul_apt_api_2017_map_purr, seoul_apt_api_2017_map_purr$level)
seoul_leaf <- leaflet(width = "100%") %>% addTiles()
seoul_leaf_ <- names(seoul_apt_api_2017_map_purr_split) %>%
walk( function(df) {
seoul_leaf <<- seoul_leaf %>%
addMarkers(data = seoul_apt_api_2017_map_purr_split[[df]],
lng = ~x_, lat = ~y_,
popup = ~as.character(dong),
label = ~as.character(mean),
labelOptions = labelOptions(noHide = T, textsize = "15px", direction = 'auto'),
group = df,
clusterOptions = markerClusterOptions(removeOutsideVisibleBounds = F))
})
seoul_leaf <- seoul_leaf %>%
addLayersControl(
overlayGroups = names(seoul_apt_api_2017_map_purr_split),
options = layersControlOptions(collapsed = FALSE)
) %>% addMiniMap(toggleDisplay = TRUE)
leaflet
패키지가 가끔식 R의 Markdown 과 호환상 충돌을 일으킬 때가 있습니다.RPubs
에 올려놓았습니다. https://rpubs.com/J-Park/KATA_leaflet seoul_leaf