[Project] Property Price Prediction

Doyun’s Journey
Doyun’s Lab
Published in
21 min readAug 28, 2020

Subject : Predicting real estate prices in Korea with various variables

Language : R

Data : ‘직방’ 데이터

  • train.csv : Apartment transaction data (1.6 million)
  • school.csv : Elementary, middle and high school information (1,100)
  • subways.csv : Subway information (400)

1. Data preprocessing

# library
library(tidyverse)
# ------------------------------------------------------------------# 파일 불러오기
setwd("C:\\r_temp\\직방")
train <- read_csv("직방.csv")
subway <- read_csv("Subways.csv")
school <- read_csv("Schools.csv")
seoul <- read.csv("서울.csv", header = T)
# ------------------------------------------------------------------# 칼럼명 설정
colnames(train) <- c("번호", "키", "아파트id", "도시", "거래년월", "거래일", "입주년도", "실평수",
"층", "위도", "경도", "법적주소", "주차용량", "세대수", "건축물수", "가장높은층",
"가장낮은층", "난방방법", "난방연료", "방id", "평수", "같은방id가구수", "방수",
"화장실수", "입구구조", "실거래가", "실거래가로그", "평수앞자리",
"실평수1", "실평수대")
head(train)
colnames(subway) <- c("역id", "위도", "경도", "호선", "법적주소")
head(subway)
colnames(school) <- c("학교코드", "위도", "경도", "학교등급", "운영타입", "학교타입", "성", "설립일", "법적주소")head(school)# ------------------------------------------------------------------# 변수 추가# 실거래가 로그값 추가
train$실거래가로그 <- log(train$실거래가)
# 평수 앞자리 추가
area <- c()
for (a in train$평수) {
if(nchar(as.integer(a)) == 2) {
area <- c(area, str_sub(a, 1, 1))}
if(nchar(as.integer(a)) == 3) {
area <- c(area, str_sub(a, 1, 2))}
}
train$평수앞자리 <- area# 실평수를 3.3 으로 나눈 값 추가
train$실평수1 <- train$실평수/3.3
# 실평수대 추가
area1 <- c()
for (a in train$실평수1) {
if(nchar(as.integer(a)) == 1) {
area1 <- c(area1, 1)}
if(nchar(as.integer(a)) == 2) {
area1 <- c(area1, paste0(str_sub(a, 1, 1), 0))}
}
train$실평수2 <- area1# ------------------------------------------------------------------# 파일 저장
write.csv(train, "직방.csv")
# ------------------------------------------------------------------# 서울 구명 추가
s <- subset(train, train$도시 == 1)
gu <- c()
for (code in s$법적주소) {
if(str_sub(code, 1, 5) == 11110) {
gu <- c(gu, "종로구")}
if(str_sub(code, 1, 5) == 11140) {
gu <- c(gu, "중구")}
if(str_sub(code, 1, 5) == 11170) {
gu <- c(gu, "용산구")}
if(str_sub(code, 1, 5) == 11200) {
gu <- c(gu, "성동구")}
if(str_sub(code, 1, 5) == 11215) {
gu <- c(gu, "광진구")}
if(str_sub(code, 1, 5) == 11230) {
gu <- c(gu, "동대문구")}
if(str_sub(code, 1, 5) == 11260) {
gu <- c(gu, "중랑구")}
if(str_sub(code, 1, 5) == 11290) {
gu <- c(gu, "성북구")}
if(str_sub(code, 1, 5) == 11305) {
gu <- c(gu, "강북구")}
if(str_sub(code, 1, 5) == 11320) {
gu <- c(gu, "도봉구")}
if(str_sub(code, 1, 5) == 11350) {
gu <- c(gu, "노원구")}
if(str_sub(code, 1, 5) == 11380) {
gu <- c(gu, "은평구")}
if(str_sub(code, 1, 5) == 11410) {
gu <- c(gu, "서대문구")}
if(str_sub(code, 1, 5) == 11440) {
gu <- c(gu, "마포구")}
if(str_sub(code, 1, 5) == 11470) {
gu <- c(gu, "양천구")}
if(str_sub(code, 1, 5) == 11500) {
gu <- c(gu, "강서구")}
if(str_sub(code, 1, 5) == 11530) {
gu <- c(gu, "구로구")}
if(str_sub(code, 1, 5) == 11545) {
gu <- c(gu, "금천구")}
if(str_sub(code, 1, 5) == 11560) {
gu <- c(gu, "영등포구")}
if(str_sub(code, 1, 5) == 11590) {
gu <- c(gu, "동작구")}
if(str_sub(code, 1, 5) == 11620) {
gu <- c(gu, "관악구")}
if(str_sub(code, 1, 5) == 11650) {
gu <- c(gu, "서초구")}
if(str_sub(code, 1, 5) == 11680) {
gu <- c(gu, "강남구")}
if(str_sub(code, 1, 5) == 11710) {
gu <- c(gu, "송파구")}
if(str_sub(code, 1, 5) == 11740) {
gu <- c(gu, "강구")}
}
s$구명 <- gu
write.csv(s, "서울.csv")
# ------------------------------------------------------------------# 용적률 추가
home$용적률 <- as.integer((home$실평수/home$평수)*100)
# ------------------------------------------------------------------# 세대당 주차용량 추가
home$세대당주차 <- round(home$주차용량/home$세대수, digits = 2L)
# ------------------------------------------------------------------# 서울 부산 분리
busan <- subset(train, train$도시 == 0)
# ------------------------------------------------------------------# 결측치 확인
a <- list()
for (i in 1:length(train)){
a[[i]] <- sum(is.na(train[i]))
}
# 주차용량 : 91813 / 가장높은층 : 9 / 가장낮은층 : 9 / 방수 : 691 / 화장실수 : 691 /
# 입구구조 : 13892 / 난방방법 : 2017 / 난방연료 : 9667 /
# 결측치 제거
home <- na.omit(train)
b <- list()
for (i in 1:length(home)){
b[[i]] <- sum(is.na(home[i]))
}
# ------------------------------------------------------------------
  • NA값은 na.omit 함수를 이용하여 제거

> Added variable

  • 실거래가 자연로그 값
  • 실평수
  • 실평수대 (ex. 10평대, 20평대 …)
  • 지역구명
  • 용적률 (전용면적 / (배타적 사용 영역 + 공용 공간 영역))
  • 세대당 주차용량 (주차용량 / 세대수)

2. Data basic analysis and visualization

### 위의 시각화 부분 중 빠진 코드가 존재할 수 있습니다.
# 평균거래가
# 도시별 평균거래가
p <- home %>%
group_by(도시) %>%
summarise(평균거래가 = mean(실거래가))
ggplot(p, aes(x = 도시, y = 평균거래가, fill = 도시)) +
geom_bar(stat = "identity")
# ------------------------------------------------------------------# 난방방법별 평균거래가
p1 <- home %>%
group_by(난방방법) %>%
summarise(평균거래가 = mean(실거래가)) %>%
arrange(desc(평균거래가))
ggplot(p1, aes(x = 난방방법, y = 평균거래가, fill = 난방방법)) +
geom_bar(stat = "identity")
# district (지역난방) 방식이 가장 비싸다.
# ------------------------------------------------------------------# 난방연료별 평균거래가
p2 <- home %>%
group_by(난방연료) %>%
summarise(평균거래가 = mean(실거래가)) %>%
arrange(desc(평균거래가))
ggplot(p2, aes(x = 난방연료, y = 평균거래가, fill = 난방연료)) +
geom_bar(stat = "identity")
# cogeneration 연료가 가장 비싸다.
# ------------------------------------------------------------------# 입구구조별 평균거래가
p3 <- home %>%
group_by(입구구조) %>%
summarise(평균거래가 = mean(실거래가)) %>%
arrange(desc(평균거래가))
ggplot(p3, aes(x = 입구구조, y = 평균거래가, fill = 입구구조)) +
geom_bar(stat = "identity")
# stairway 구조가 가장 비싸다,
# ------------------------------------------------------------------# 구별 평균거래가
p4 <- seoul %>%
group_by(구명) %>%
summarise(평균거래가 = mean(실거래가)) %>%
top_n(5, wt = 평균거래가) %>%
arrange(desc(평균거래가))
p5 <- seoul %>%
group_by(구명) %>%
summarise(평균거래가 = mean(실거래가)) %>%
top_n(-5, wt = 평균거래가) %>%
arrange(desc(-평균거래가))
install.packages("gridExtra")
library(gridExtra)
p4_1 <- ggplot(p4, aes(reorder(구명, -평균거래가), y = 평균거래가, fill = 구명)) +
geom_bar(stat = "identity") +
xlab("구명")
p5_1 <- ggplot(p5, aes(reorder(구명, -평균거래가), y = 평균거래가, fill = 구명)) +
geom_bar(stat = "identity") +
xlab("구명")
grid.arrange(p4_1, p5_1)
# 강남구, 서초구, 용산구 순서로 비싸다.
# 도봉구, 노원구, 금천구 순서로 싸다.
# ------------------------------------------------------------------# 실거래가 히스토그램
ggplot(home, aes(x = 실거래가, fill = ..count..)) +
geom_histogram(binwidth = 10000000)
ggplot(home, aes(x = 실거래가로그, fill = ..count..)) +
geom_histogram(binwidth = 0.5)
ggplot(home, aes(x = 난방방법, y = 실거래가, fill = 난방방법)) +
geom_boxplot(alpha=0.3)
# ------------------------------------------------------------------#아파트 분양가 상승률
apartment <- seoul[,c(4,6,27)]
a <- apartment %>%
group_by(아파트id, 거래년월) %>%
summarise(평균거래가 = mean(실거래가))
# 최초 거래년월과 최근 거래년월 추출
b <- a %>%
group_by(아파트id) %>%
filter(거래년월 == max(거래년월) | 거래년월 == min(거래년월))
# 최초 거래년월과 최근 거래년월 둘 중에 하나만 있는 행 탐색
b %>%
group_by(아파트id) %>%
summarise(n = n()) %>%
arrange(desc(-n))
b <- as.data.frame(b)
which(b$아파트id == 316)
which(b$아파트id == 22241)
which(b$아파트id == 36912)
which(b$아파트id == 37467)
which(b$아파트id == 38419)
# 하나만 있는 행 삭제
b <- b[-c(259, 4882, 5215, 5238, 5243), ]
library(reshape2)
first <- b[!duplicated(b$아파트id),]
recent <- b[duplicated(b$아파트id),]
colnames(first) <- c("아파트id", "최초거래", "최초거래가")first$최근거래 <- recent$거래년월
first$최근거래가 <- recent$평균거래가
fir_rec <- first
fir_rec$변동 <- (fir_rec$최근거래가 - fir_rec$최초거래가)
head(fir_rec)
fir_rec %>% arrange(desc(-변동))ggplot(fir_rec, aes(x = 변동, fill = ..count..)) +
geom_histogram(binwidth = 10000000)
# 대부분 집값이 0원 ~ 1억 사이의 상승률을 보인다.
# ------------------------------------------------------------------# 세대당 주차 히스토그램
ggplot(home, aes(x = 세대당주차, fill = ..count..)) +
geom_histogram(binwidth = 0.3)
ggplot(home, aes(x = 세대당주차, y = 실거래가)) +
geom_bar(stat = "identity")
# 세대당주차는 1 ~ 1.3 대가 많이 분포한다
# ------------------------------------------------------------------# 용적률 히스토그램
ggplot(home, aes(x = 용적률, fill = ..count..)) +
geom_histogram(binwidth = 1)
ggplot(home, aes(x = 용적률, y = 실거래가)) +
geom_bar(stat = "identity")
# 용적률은 75~80% 대가 많이 분포한다
# ------------------------------------------------------------------# 실평수 히스토그램
ggplot(home, aes(x = 실평수1, fill = ..count..)) +
geom_histogram(binwidth = 5)
# 25평대가 제일 많이 분포한다
#거래일자 Date 형태로 변환하기
#년월(월초, 월중, 월말)기준
category <- str_split_fixed(string = seoul$거래일,pattern="~",2)
seoul$거래년월일 <- paste0(seoul$거래년월,data.frame(category)[,1])
seoul$거래년월일 <- as.Date(seoul$거래년월일, format= "%Y%m%d")
buydate <- seoul %>%
group_by(거래년월일, 구명) %>%
summarise(카운트=n()) %>%
arrange(desc(카운트))
ggplot(buydate, aes(x = 거래년월일, y = 카운트, group = 구명, colour = 구명)) +
geom_line() +
facet_wrap(~ 구명) +
coord_cartesian(ylim=c(0, 1500))
# ------------------------------------------------------------------#년월 기준
category <- str_split_fixed(string = seoul$거래일,pattern="~",2)
seoul$거래년월일 <- paste0(seoul$거래년월,1)
seoul$거래년월일 <- as.Date(seoul$거래년월일, format= "%Y%m%d")
buydate <- seoul %>%
group_by(거래년월일, 구명) %>%
summarise(카운트=n())
ggplot(buydate, aes(x = 거래년월일, y = 카운트, group = 구명, colour = 구명)) +
geom_line() +
facet_wrap(~ 구명) +
coord_cartesian(ylim=c(0, 1500))
#-------------------------------------------------------------------
#구별 평균거래액의 시대별 추이
평균거래가 <- seoul %>%
group_by(거래년월일, 구명) %>%
summarise(평균액 = mean(실거래가))
ggplot(평균거래가, aes(x = 거래년월일, y = 평균액, group = 구명, colour = 구명)) +
geom_line() +
facet_wrap(~ 구명)
###어느 지역이나 평수가 클수록 비싸고, 작을수록 싸다. 따라서 이걸 공통변수로 보고평균거래액 추이를 그림.

3. Correlation analysis

# 상관분석
cor.test(train$실거래가, train$실평수)
cor.test(train$실거래가, train$층)
cor.test(train$실거래가, train$세대수)
cor.test(train$실거래가, train$건축물수)
cor.test(train$실거래가, train$평수)
# ------------------------------------------------------------------num <- c(home[,4], home[,5], home[,8], home[,9], home[,13], home[,14], home[,15], home[,16],
home[,17], home[,21], home[,22], home[,23], home[,24], home[,26], home[,31], home[,32])
num <- as.data.frame(num)
x <- cor(num)
library(corrplot)
corrplot(x)
# 학교수 vs 거래가
km <- read.csv("면적.csv")
sch_gu <- seoul_school %>%
group_by(구명) %>%
summarise(학교수 = n())
seo_gu <- seoul %>%
group_by(구명) %>%
summarise(실거래가평균 = mean(실거래가))
sch_gu$면적당학교수 <- sch_gu$학교수/km$면적sch_gu %>%
arrange(-sch_gu$면적당학교수)
cor.test(sch_gu$면적당학교수, seo_gu$실거래가평균)
# 학교 수와 거래가는 음의 관계
# ------------------------------------------------------------------# 지하철수 vs 거래가
sub_gu <- seoul_subway %>%
group_by(구명) %>%
summarise(지하철수 = n()) %>%
arrange((구명))
sub_gu$면적당지하철수 <- sub_gu$지하철수/km$면적sub_gu %>%
arrange(-sub_gu$면적당지하철수)
cor.test(sub_gu$면적당지하철수, seo_gu$실거래가평균)
# 지하철 수와 거래가는 양의 관계

4. Modeling

attach(home)
ad <- lm(실거래가 ~ 실평수 + 층 + 주차용량 + 세대수 + 건축물수 +
가장높은층 + 가장낮은층 + 평수 + 같은방id가구수 +
방수 + 화장실수 + 난방방법 + 입구구조 + 도시 +
용적률 + 세대당주차)
summary(ad)
require(MASS)
anova(ad)
par(mfrow = c(2, 2))
plot(ad)

4. Conclusion

  • 서울의 아파트들은 보통 학교와 가까이 있는 모습을 보인다
  • 거래는 3월과 10월에 활발하고, 1월과 12월에 저조하다
  • 보통 최초 거래일에 비해 집 값이 상승하는 모습을 보인다
  • 강동구, 마포구 등은 안정적으로 집 값이 오른다
  • 구별 거래가는 지하철 수에 영향을 받는다고 할 수 있다.

--

--