본 데이터는 국토교통부 실거래가 공개시스템의 2018.10.01 ~ 2019.09.30의 데이터 입니다.
전반적인 데이터를 전처리, 및 시각화 해보았습니다.
또한 Random Forest 모델을 적용해 지역별(구 기준), 건물의 층별 1평당 거래가격을 예측해보는 회귀모델을 생성 해보았습니다.
부동산114라는 키워드로 포털사이트 뉴스정보를 크롤링하여 워드클라우드를 만들어 보았습니다.
본 데이터의 참고사항은 다음과 같습니다.
회귀 모델을 만들기에 앞서 데이터 업로드 및 탐색적 자료 분석(EDA)를 해보았습니다. 컬럼의 종류, 컬럼의 자료형, 각 컬럼의 간단한 요약을 살펴보았습니다.
탐색적 데이터 분석을 통해 어떠한 방향으로 데이터 분석을 해야할 지에 대해 생각해보았습니다.
# 필요한패키지
library(dplyr) # data handling
library(ggplot2) # data visualization
library(stringr) # data handling
library(reshape2) # data handling
library(randomForest) # randomForest model
library(rvest) # web crawling
library(KoNLP) # elemental analysis
library(wordcloud) # wordcloud
library(RColorBrewer) # color
전반적인 데이터를 파악해보았을때 컬럼은 총 12개이며, 전용면적은 10.78m² ~ 317.36m², 건축년도는 1965 ~ 2019년, 층은 지하2 ~ 67층 까지로 나타났습니다.
# 데이터 업로드
raw_property_df <- read.csv("property_181001_190930.csv", fileEncoding = "euc-kr")
# 데이터확인
head(raw_property_df)
## 시군구 번지 본번 부번 단지명
## 1 서울특별시 강남구 개포동 655-2 655 2 개포2차현대아파트(220)
## 2 서울특별시 강남구 개포동 658-1 658 1 개포6차우성아파트1동~8동
## 3 서울특별시 강남구 개포동 658-1 658 1 개포6차우성아파트1동~8동
## 4 서울특별시 강남구 개포동 658-1 658 1 개포6차우성아파트1동~8동
## 5 서울특별시 강남구 개포동 658-1 658 1 개포6차우성아파트1동~8동
## 6 서울특별시 강남구 개포동 658-1 658 1 개포6차우성아파트1동~8동
## 전용면적... 계약년월 계약일 거래금액.만원. 층 건축년도 도로명
## 1 77.75 201906 18 134,500 4 1988 언주로 103
## 2 67.28 201905 4 124,000 4 1987 언주로 3
## 3 79.97 201906 15 141,000 5 1987 언주로 3
## 4 79.97 201908 28 155,000 4 1987 언주로 3
## 5 79.97 201909 2 157,000 2 1987 언주로 3
## 6 79.97 201909 10 151,500 5 1987 언주로 3
# 데이터 인덱스, 컬럼 파악
dim(raw_property_df)
## [1] 45994 12
# 자료형 파악
str(raw_property_df)
## 'data.frame': 45994 obs. of 12 variables:
## $ 시군구 : Factor w/ 316 levels "서울특별시 강남구 개포동",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ 번지 : Factor w/ 3908 levels "01월 01일","01월 03일",..: 2803 2814 2814 2814 2814 2814 2797 2797 2797 569 ...
## $ 본번 : int 655 658 658 658 658 658 652 652 652 12 ...
## $ 부번 : int 2 1 1 1 1 1 0 0 0 2 ...
## $ 단지명 : Factor w/ 4658 levels "(1-102)","(1101-1)",..: 280 281 281 281 281 281 284 284 284 285 ...
## $ 전용면적... : num 77.8 67.3 80 80 80 ...
## $ 계약년월 : int 201906 201905 201906 201908 201909 201909 201901 201906 201908 201906 ...
## $ 계약일 : int 18 4 15 28 2 10 12 8 3 5 ...
## $ 거래금액.만원.: Factor w/ 2437 levels "10,000","10,050",..: 322 232 387 475 484 456 743 484 602 827 ...
## $ 층 : int 4 4 5 4 2 5 12 6 11 14 ...
## $ 건축년도 : int 1988 1987 1987 1987 1987 1987 1984 1984 1984 2004 ...
## $ 도로명 : Factor w/ 5559 levels " ","18","37",..: 3654 3663 3663 3663 3663 3663 262 262 262 269 ...
# 데이터 요약
summary(raw_property_df)
## 시군구 번지 본번
## 서울특별시 노원구 상계동: 1767 17 : 277 Min. : 0.0
## 서울특별시 양천구 신정동: 979 22 : 211 1st Qu.: 170.0
## 서울특별시 노원구 중계동: 966 19 : 208 Median : 465.0
## 서울특별시 송파구 잠실동: 841 688 : 207 Mean : 563.7
## 서울특별시 구로구 구로동: 824 13 : 202 3rd Qu.: 785.0
## 서울특별시 관악구 봉천동: 792 1013 : 191 Max. :6040.0
## (Other) :39825 (Other):44698
## 부번 단지명 전용면적... 계약년월
## Min. : 0.000 현대 : 397 Min. : 10.78 Min. :201810
## 1st Qu.: 0.000 두산 : 352 1st Qu.: 59.61 1st Qu.:201903
## Median : 0.000 한신 : 313 Median : 81.95 Median :201906
## Mean : 6.303 신동아 : 288 Mean : 77.52 Mean :201892
## 3rd Qu.: 1.000 삼성래미안: 278 3rd Qu.: 84.97 3rd Qu.:201907
## Max. :2837.000 파크리오 : 269 Max. :317.36 Max. :201909
## (Other) :44097
## 계약일 거래금액.만원. 층 건축년도
## Min. : 1.00 60,000 : 707 Min. :-2.000 Min. :1965
## 1st Qu.: 8.00 90,000 : 456 1st Qu.: 4.000 1st Qu.:1993
## Median :16.00 50,000 : 365 Median : 8.000 Median :2001
## Mean :16.12 40,000 : 357 Mean : 9.283 Mean :2000
## 3rd Qu.:24.00 70,000 : 338 3rd Qu.:13.000 3rd Qu.:2007
## Max. :31.00 80,000 : 338 Max. :67.000 Max. :2019
## (Other):43433
## 도로명
## 올림픽로 435 : 269
## 올림픽로 135 : 184
## 올림픽로 99 : 183
## 금하로 816 : 172
## 아리수로50길 50: 164
## 가재울미래로 2 : 160
## (Other) :44862
탐색적 데이터 분석 단계에서 확인 했을 때 데이터셋에서 모델을 만드는데 필요한 컬럼들을 생성 및 삭제를 진행했고, 어떠한 컬럼을 적용해야 하는지에 대해 시각화를 해보았습니다.
데이터의 처리에서는 시군구 컬럼의 분할, 번지,부번,도로명 등 다소 필요없는 컬럼들을 삭제, 자료형 변환을 통해 전반적인 data.frame을 수정해보았습니다.
# 필요한 컬럼만 추출
raw_property_df <- cbind(raw_property_df$시군구,raw_property_df[,5:11])
# 시구동 분할
colname <- c("시", "구", "동")
mutate_df <- colsplit(raw_property_df[,1], " ", colname)
property_df <- cbind(mutate_df, raw_property_df[,2:8])
colnames(property_df) <- c("city", "gu", "dong", "apt_name", "use_area", "contract_ym", "contract_day", "transaction_cost", "floor", "architecture_year")
# ","제거 및 거래금액 컬럼을 숫자형으로 변환
property_df$transaction_cost <- property_df$transaction_cost %>%
as.character() %>%
str_replace_all(",", "") %>%
as.numeric()
# 1평당 거래가격(cost_pyeong) 컬럼 생성
property_df <- mutate(property_df, cost_pyeong = property_df$transaction_cost / property_df$use_area *3.3)
# 거래량(count) 컬럼 생성
property_df <- mutate(property_df, count = 1)
# 자료형 factor로 변환
property_df$city <- property_df$city %>% as.factor()
property_df$gu <- property_df$gu %>% as.factor()
property_df$dong <- property_df$dong %>% as.factor()
property_df$contract_ym <- property_df$contract_ym %>% as.factor()
property_df$architecture_year <- property_df$architecture_year %>% as.factor()
# 자료형 확인
str(property_df)
## 'data.frame': 45994 obs. of 12 variables:
## $ city : Factor w/ 1 level "서울특별시": 1 1 1 1 1 1 1 1 1 1 ...
## $ gu : Factor w/ 25 levels "강남구","강동구",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ dong : Factor w/ 314 levels "가락동","가리봉동",..: 9 9 9 9 9 9 9 9 9 9 ...
## $ apt_name : Factor w/ 4658 levels "(1-102)","(1101-1)",..: 280 281 281 281 281 281 284 284 284 285 ...
## $ use_area : num 77.8 67.3 80 80 80 ...
## $ contract_ym : Factor w/ 12 levels "201810","201811",..: 9 8 9 11 12 12 4 9 11 9 ...
## $ contract_day : int 18 4 15 28 2 10 12 8 3 5 ...
## $ transaction_cost : num 134500 124000 141000 155000 157000 ...
## $ floor : int 4 4 5 4 2 5 12 6 11 14 ...
## $ architecture_year: Factor w/ 54 levels "1965","1966",..: 23 22 22 22 22 22 19 19 19 39 ...
## $ cost_pyeong : num 5709 6082 5818 6396 6479 ...
## $ count : num 1 1 1 1 1 1 1 1 1 1 ...
데이터 시각화에서는 다음과 같이 데이터를 시각화 해보았습니다.
최근 1년간 거래에 따르면 서울시의 1평당 평균 거래금액은 약 3292만원으로 나타났습니다.
구 단위에서는 강남구 약 6656만원으로 1위, 서초구 약5532만원으로 2위, 가장낮은 구는 도봉구 약 1849만원으로 나타났습니다.
# 구별 거래금액의 평균
gu_cost_data <- property_df %>%
group_by(gu) %>%
summarise(mean_cost_pyoeng = mean(cost_pyeong)) %>%
arrange(desc(mean_cost_pyoeng)) %>%
as.data.frame()
ggplot(data = gu_cost_data, aes(x = reorder(gu,mean_cost_pyoeng), y = mean_cost_pyoeng,
fill = mean_cost_pyoeng)) +
xlab("구") +
ylab("거래금액평균") +
geom_bar(stat = "identity") +
coord_flip() +
theme_bw()
거래량을 층별로 구분지어 보았을때 1~5층이 가장 많은 거래량이 발생한 것으로 나타났습니다.
또한 고층으로 갈수록 거래량이 감소하는 형태를 보이고 있는데 이는 사람들이 고층보다 저층을 선호한다는 것으로는 보기 어렵습니다. 그 이유는 기존의 아파트는 보통 약 15층 정도로 많이 건설되었고, 최근에 고층아파트들이 늘어나는 추세이기 때문에 이 부분은 최근 1년 데이터만으로는 판단하기 어렵고 수년, 수십년전부터 데이터를 종합하여 고층건물의 비율 과 그 거래량의 증가, 감소를 살펴보아야 판단가능한 부분이라고 생각합니다.
# 층 그룹화
property_df$floor_group <- ifelse(property_df$floor <= 0, "지하",
ifelse(property_df$floor <= 5,"1~5층",
ifelse(property_df$floor <= 10,"6~10층",
ifelse(property_df$floor <= 15,"11~15층",
ifelse(property_df$floor <= 20,"16~20층",
ifelse(property_df$floor <= 25,"21~25층",
ifelse(property_df$floor <= 30,"26~30층","31층이상")))))))
# 층별 거래량
floor_group_ggplot <- property_df %>%
group_by(floor_group) %>%
summarise(trade_count = sum(count)) %>%
arrange(desc(trade_count)) %>%
as.data.frame()
ggplot(data = floor_group_ggplot, aes(x = reorder(floor_group,trade_count), y = trade_count ,
fill = trade_count )) +
geom_bar(stat = "identity") +
coord_flip() +
theme_bw()
건축년도별 거래량, 거래금액평균을 살펴보았을때 건축년도가 거래금액의 책정에 영향을 미치긴 어렵다는 판단입니다. 그래프를 살펴보았을때 거래량은 2004년건축, 거래금액평균은 1982년건축이 가장 높은것으로 나왔습니다. 이는 건축년도가 가격에 많은 영향을 미치기 보다는 다른 변수가 가격결정의 요인이 되는 것으로 보여집니다.
# 건축년도별 거래량
architecture_year_count_ggplot <- property_df %>%
group_by(architecture_year) %>%
summarise(trade_count = sum(count)) %>%
arrange(desc(trade_count)) %>%
as.data.frame()
# 건축년도별 거래금액평균
architecture_year_cost_ggplot <- property_df %>%
group_by(architecture_year) %>%
summarise(mean_cost_pyeong = mean(cost_pyeong)) %>%
arrange(desc(mean_cost_pyeong)) %>%
as.data.frame()
ggplot(data = architecture_year_count_ggplot, aes(x = architecture_year,
y = trade_count ,
fill = trade_count )) +
geom_bar(stat = "identity") +
coord_flip() +
theme_bw()
ggplot(data = architecture_year_cost_ggplot, aes(x = architecture_year,
y = mean_cost_pyeong ,
fill = mean_cost_pyeong )) +
geom_bar(stat = "identity") +
coord_flip() +
theme_bw()
구별 거래량을 살펴보았을 때 노원구 1위, 송파구 2위, 강남구 3위로 나타났습니다.
월별 거래량을 살펴보았을 때 6~8월 여름시즌에 거래량이 높고 11~1월 겨울시즌에 거래량이 낮은 형태를 볼 수 있습니다. 이러한 데이터를 기반으로 계절에 따라 마케팅 수단을 달리 취하여 부동산시장을 활발하게 할 수 있을 것입니다.
# 구별 거래량
gu_trade_count <- property_df %>%
group_by(gu) %>%
summarise(trade_count = sum(count)) %>%
as.data.frame()
ggplot(data = gu_trade_count, aes(x = gu, y = trade_count, fill = trade_count)) +
geom_bar(stat = "identity") +
coord_flip() +
theme_bw()
# 월별 거래량
month_trade_count <- property_df %>%
group_by(contract_ym) %>%
summarise(trade_count = sum(count)) %>%
as.data.frame()
ggplot(data = month_trade_count, aes(x = contract_ym , y = trade_count, fill = trade_count)) +
geom_bar(stat = "identity") +
coord_flip() +
theme_bw()
전용면적별 거래량을 보았을때 21 ~ 30평이 18720개로 1위, 11 ~ 20평이 17351로 2위를 기록했습니다. 10평 미만평수를 제외한 비교적 작은 평수에서 잦은 상당히 많은 거래량이 발생하게 되는데, 이는 가족의 형태와 관련이 있다고 생각합니다. 집을 구할때 신혼부부나 어린 아이가 있는 가족이 비교적 작은 집에 계약을 하게 되는데 아이를 출산하여 가족구성원이 늘어나거나 아이가 자라 더 큰집이 필요로 하기에 집을 매매하는 경향이 있다고 예상해 볼 수 있습니다. 또한 이들이 젊은 부부라고 가정했을때 이들의 이직이나 보유자산 등 정리되지 않은 불완전한 상황을 고려하여 잦은 이사를 할 수 있다고 예상해 볼 수 있습니다. 그에 반해 평수가 높아질 수록 거래량이 급격하게 줄어들게 되는데 높은 평수의 아파트 증가율 과 그 거래량의 상관관계를 고려해보야 할 문제이지만, 어느정도 생활의 안정화가 되어 잦은 이사를 하지 않는 것으라고 생각해 볼 수 있을 것 같습니다.
# 전용면적별 그룹화
property_df$use_area_group <- ifelse(property_df$use_area <= 33, "1~10평",
ifelse(property_df$use_area <= 66,"11~20평",
ifelse(property_df$use_area <= 99,"21~30평",
ifelse(property_df$use_area <= 132,"31~40평",
ifelse(property_df$use_area <= 165,"41~50평",
ifelse(property_df$use_area <= 198,"51~60평","61평이상"))))))
# 전용면적별 거래량
use_area_trade_count <- property_df %>%
group_by(use_area_group) %>%
summarise(trade_count = sum(count)) %>%
arrange(desc(trade_count)) %>%
as.data.frame()
ggplot(data = use_area_trade_count, aes(x = use_area_group, y = trade_count ,
fill = trade_count )) +
geom_bar(stat = "identity") +
coord_flip() +
theme_bw()
모델은 독립변수인 구, 층 으로 종속변수인 평당거래금액을 회귀하는 randomForest 모델을 사용했습니다. importance와 varImpPlot을 살펴보았을때 아파트가 어디구에 위치해있는가가 가격을 결정하는대에 가장큰 영향을 주었고, 층이라는 변수는 구에 비해 매우 낮은 영향력을 보였습니다.
# data 생성
df <- property_df
# train/test sampling
training_sampling <- sort(sample(1:nrow(df), nrow(df) * 0.7 ))
test_sampling <- setdiff(1:nrow(df),training_sampling)
# traning_set, test_set
training_set <- df[training_sampling,]
test_set <- df[test_sampling,]
# randomForest model
rf_m <- randomForest(cost_pyeong ~ gu + floor , data = training_set)
# importance
rf_importance <- randomForest(cost_pyeong ~ gu + floor , data = training_set, importance = TRUE)
importance(rf_m)
## IncNodePurity
## gu 52152744984
## floor 2531446154
varImpPlot(rf_m)
# predict cost
rf_p <- predict(rf_m, newdata = test_set, type = "class")
# predict dataframe 생성
predict_df <- cbind(cost_pyeong = test_set$cost_pyeong, p_cost_pyeong = rf_p) %>%
as.data.frame()
# 오차 컬럼 생성
predict_df <- mutate(predict_df, error = abs(cost_pyeong - p_cost_pyeong))
mean(predict_df$error)
## [1] 812.6121
# 요약
summary(predict_df$error)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 0.041 265.672 569.856 812.612 1062.093 11135.577
# error density ggplot
predict_df$error <- predict_df$error %>% as.numeric()
ggplot (data=predict_df, aes (x= error)) +
geom_density() +
ggtitle("density") +
theme_bw()
# Root Mean Squared Error
RMSE <- function(error) { sqrt(mean(error^2)) }
RMSE(predict_df$error)
## [1] 1181.257
# Mean Absolute Error
mae <- function(error) { mean(abs(error)) }
mae(predict_df$error)
## [1] 812.6121
위와 같이 전반적인 데이터를 처리 및 시각화해보고 randomforest를 사용하여 회귀모델을 만들어보아 예측해보았습니다. 아쉽게도 모델의 예측수준은 평균절대오차는 812.6120947 만원으로 낮은 성능을 보였습니다. 또한 오차의 최솟값은 0.0411663 만원, 최댓값은1.113557710^{4} 만원으로 약 1억이 넘는 수준이었습니다. 이를 통해 알 수 있는 것은 아파트의 매매가격을 정하는 데에는 구, 층, 건축년도 외에 많은 외부 변수와 환경요인이 작용한다는 것이었습니다. 부동산 데이터는 최근 이슈인 분양가 상한제와 같은 정부 정책, 전망, 역세권, 풍수지리와 같은 지리적인 요인에 많은 영향을 받습니다. 또한 직장이 밀집해 있는 지역, 학군, 재건축 여부와 같은 변수들 또한 가격에 크게 영향을 주는 것으로 보였습니다. 단편적인 예로 1982년 건축된 강남구 개포동 개포주공1단지의 경우 평균 평당 매매금액이 약 1억 3천만원으로 나왔습니다. 표면적으로는 터무니없이 비싼 가격이라고 생각되지만 이 아파트는 재건축 예정이고 분당선 구룡역이 도보권에 속해있어 교통이 편리한 점을 고려하면 다소 납득이 가는 금액이라고 생각할 수 있습니다. 이와 같이 부동산 정보는 다양한 변수들이 작용합니다. 이에 관한 과거의 사례분석, 지리적요인, 법률적 요인을 축적하여 데이터화한다면 더욱 신뢰성있는 빅데이터를 구축하고 이를 기반으로 고객에게 정확한 정보를 전달할 수 있다고 생각합니다.
웹 크롤링을 사용하여 부동산114라는 키워드의 뉴스들의 형태소를 분석하여 자주나오는 단어들로 wordcloud를 만들어 보았습니다.
page_list <- c(1:3)
news_text_list <- c()
for (page in page_list) {
estate_url <- paste0("https://search.naver.com/search.naver?&where=news&query=%EB%B6%80%EB%8F%99%EC%82%B0%20114&sm=tab_pge&sort=0&photo=0&field=0&reporter_article=&pd=0&ds=&de=&docid=&nso=so:r,p:all,a:all&mynews=0&cluster_rank=17&start=",page,"&refresh_start=0")
estate_html <- read_html(estate_url)
estate_urls <- estate_html %>%
html_nodes(".type01") %>%
html_nodes("a") %>%
html_attr("href") %>%
unique()
estate_urls <- estate_urls[grep("naver.com",estate_urls)]
news_text <- lapply(estate_urls, function(news_page){
news_page %>%
read_html() %>%
html_nodes("#articleBodyContents._article_body_contents") %>%
html_text()
})
news_text <- news_text %>%
unlist() %>%
str_replace_all("flash 오류를 우회하기 위한 함수 추가","") %>%
str_replace_all("function _flash_removeCallback","") %>%
str_replace_all("동영상 뉴스","") %>%
str_replace_all("\\W"," ") %>%
str_replace_all("[[A-Za-z]]"," ") %>%
str_replace_all("\\d"," ") %>%
str_replace_all(" "," ") %>%
unique()
news_text_list <- append(news_text_list,news_text)
Sys.sleep(20)
}
word.freq <- paste0(news_text_list[1:12],collapse = " ")
nouns <- KoNLP::extractNoun(word.freq)
nouns <- nouns[nchar(nouns) >= 2]
wordcount <- table(unlist(nouns))
df.word <- as.data.frame(wordcount, stringsAsFactors = FALSE)
df.word <- rename(df.word, word = Var1, freq = Freq)
word.freq <- df.word %>%
filter(freq >= 2) %>%
arrange(desc(freq))
wordcloud::wordcloud(words = word.freq$word, freq = word.freq$freq,
min.freq = 2, max.words = 200,
random.order = FALSE, rot.per = 0.1,
scale= c(5,0.5),
colors = brewer.pal(8, "Dark2"))