[Project] Network Analysis of Apartment Price in Korea

Doyun’s Journey
Doyun’s Lab
Published in
9 min readAug 29, 2020

Subject : Expressing Korea’s Real Estate Network with Social Network

Language : R

Data : ‘한국감정원 아파트 지수’ Data

1. Data preprocessing

### 주제 - 전국 지역별 아파트 가격 지수의 네트워크 파악
### 기대효과 - 혼란스러운 부동산 시장의 흐름을 파악하여 부동산 시장의 변동에 대비

## Vertex : 각 지역
## Edge(Weight) : 각 지역별 아파트 가격 지수 변동의 상관계수
# 디렉토리 설정 및 라이브러리 불러오기
setwd("C:\\r_temp")
library(tidyverse)
# 데이터 불러오기
df <- read.csv("sna_data.csv", fileEncoding = "euc-kr", stringsAsFactors = F)
## 데이터 전처리
# 4개로 나뉘어져 있는 지역명 합치기
a <- subset(df, select=c('지.역','X','X.1','X.2'))
a <- unite(a, col="지역",지.역,X,X.1,X.2,sep="")
result <- data.frame(a, df[,5:268])
result <- result[2:221, ]

df <- t(result)
b <- df[1, ]
# 짝수 열만 불러오기 (지수만 가져오기 위함)
q <- seq(2, 264 , by = 2)
t <- data.frame()
for (a in q){
t <- rbind(t, df[q,])
}

# 열이름 지정
colnames(t) <- b

# 필요한 부분 불러오기
sna <- t[1:132,]

# 겹치는 "구"명 바꾸기
names(sna)[which(names(sna) == "중구")] <- c("서울중구", "인천중구", "부산중구", "대구중구", "대전중구", "울산중구")
names(sna)[which(names(sna) == "남구")] <- c("인천남구" , "부산남구", "대구남구", "광주남구", "울산남구", "포항남구")
names(sna)[which(names(sna) == "동구")] <- c("인천동구", "부산동구", "대구동구", "광주동구", "대전동구","울산동구")
names(sna)[which(names(sna) == "강서구")] <- c("서울강서구","부산강서구")
names(sna)[which(names(sna) == "북구")] <- c("부산북구", "대구북구","광주북구" ,"울산북구" ,"포항북구")
names(sna)[which(names(sna) == "서구")] <- c("인천서구", "부산서구", "대구서구" ,"광주서구" ,"대전서구")

# 불필요한 열 제거
sna$경부2권 <- NULL
sna$용인시 <- NULL
sna$수원시 <- NULL
sna$서해안권 <- NULL
sna$안산시 <- NULL
sna$동부1권 <- NULL
sna$동부2권 <- NULL
sna$경의권 <- NULL
sna$고양시 <- NULL
sna$강북지역 <-NULL
sna$도심권 <- NULL
sna$동북권 <- NULL
sna$서북권 <- NULL
sna$서남권 <- NULL
sna$동남권 <- NULL
sna$경기 <- NULL
sna$경부1권 <- NULL
sna$안양시 <- NULL
sna$성남시 <- NULL
sna$제주 <- NULL
sna$경원권 <- NULL
sna$인천 <- NULL
sna$부산 <- NULL
sna$중부산권 <- NULL
sna$동부산권 <- NULL
sna$서부산권 <- NULL
sna$대구 <- NULL
sna$광주 <- NULL
sna$대전 <- NULL
sna$울산 <- NULL
sna$경북 <- NULL
sna$경남 <- NULL
sna$충북 <- NULL
sna$충남 <- NULL
sna$전북 <- NULL
sna$전남 <- NULL
sna$강원 <- NULL
sna$창원시 <- NULL
sna$포항시 <- NULL
sna$천안시 <- NULL
sna$전주시 <- NULL
sna$청주시 <- NULL

# 최종 데이터 파일 저장
data <- data.frame(sna)

# 열별로 숫자형으로 바꿔주기
data <- as.data.frame(apply(data, 2, as.numeric))
  • Edge List 생성 전, 데이터프레임 전처리 해주는 과정을 거침.
# 열 개수 = 132개
nrow(data)
## [1] 132# Data가 2009년 1월부터 2019년 12월 데이터
# 4로 나눠주어 33개월(2년 9개월)씩 나누어 지수의 평균값 구하기
net <- t(data.frame(apply(data[1:33, ], 2, mean, na.rm = T), apply(data[34:66, ], 2, mean, na.rm = T),
apply(data[67:101, ], 2, mean, na.rm = T), apply(data[102:132, ], 2, mean, na.rm = T)))
net <- data.frame(net)

# Combination 조합
comb <- combn(colnames(net), 2) %>% t()

# 행이름 지정
rownames(net) <- c("one", "two", "three", "four")
net$강원.1 <- NULL

# 지수의 변동 파악
change <- data.frame(apply(net, 2, diff))

# 지수 변동 데이터프레임 생성
change <- t(change)
change <- data.frame(change)
colnames(change) <- c("a", "b", "c")

a <- data.frame(change$a)
b <- data.frame(change$b)
c <- data.frame(change$c)

colnames(a) <- "지수"
colnames(b) <- "지수"
colnames(c) <- "지수"

d <- data.frame(rbind(a, b, c))

nm <- data.frame(rownames(change))
ae <- data.frame(rbind(nm, nm, nm))

last <- data.frame(cbind(d, ae))
colnames(last) <- c("지수", "지역")

# 조합별로 상관계수 추출
price <- c()
for(i in 1:nrow(comb)){ # 조합 한줄씩 읽어오기
# i번째 조합의 첫번째 주식의 종가diff 추출
a <- last[last$지역 == comb[i, 1], "지수"]
b <- last[last$지역 == comb[i, 2], "지수"]
print(i)

price <- c(price, cor(a, b)) # 두 주식 correlation 산출 후 저장
}
# 최종 edge list 생성
edge <- as.data.frame(comb)
edge <- cbind(edge, price)
colnames(edge) <- c("source", "target", "weight")
edge <- na.omit(edge)

# edge list csv 파일 저장
write.csv(edge, "sna_edge.csv")
  • 조합별 상관계수를 가지는 Edge List 생성

2. Data visualization

  • 지역별 상관계수로 Heat map 생성
  • Network graph
# 상관계수 히트 맵 생성
netm <- as_adjacency_matrix(gg, attr="weight", sparse=F)
colnames(netm) <- V(gg)$name
rownames(netm) <- V(gg)$name

palf <- colorRampPalette(c("gold", "dark orange"))
heatmap(netm, Rowv = NA, Colv = NA, col = palf(100),
scale="none", margins=c(10,10) )
# Graph DataFrame 생성
g <- graph.data.frame(edge, directed = T)

# 상관계수 0.8 미만 삭제
gg <- delete.edges(g, E(g)[abs(weight) < 0.8])

# weight average cut
cut.off <- mean(edge$weight)
ggg <- delete_edges(g, E(g)[weight<cut.off])

# 엄청난 상관성을 보이는 것만 추출하여 시각화
ggs <- delete.edges(g, E(g)[abs(weight) < 0.99])

# weight 절대 값으로 변경 후, Graph DataFrame 생성
edge.abs <- edge
edge.abs$weight <- abs(edge$weight)
g.abs <- graph.data.frame(edge.abs, directed = T)
g.abs <- delete_edges(g.abs, E(g.abs)[weight<cut.off])
# 지역별로 다른 색 지정을 위해 벡터 생성
seoul <- c("종로구", "서울중구", "성동구", "광진구", "동대문구", "중랑구", "성북구",
"강북구", "도봉구", "노원구", "은평구", "서대문구", "마포구", "강남지역",
"양천구", "서울강서구", "구로구", "금천구", "영등포구", "동작구",
"관악구", "서초구", "강남구", "송파구", "강동구", "용산구")

gig <- c('과천시','만안구','동안구','수정구','중원구','분당구',
'군포시','의왕시','안성시','처인구','기흥구','수지구',
'장안구','권선구','팔달구','영통구','부천시','상록구',
'단원구','시흥시','광명시','화성시','오산시','평택시',
'남양주시','구리시','하남시','광주시','이천시','여주시','김포시',
'덕양구','일산동구','일산서구','파주시','포천시','동두천시','양주시', "의정부시")

ic <- c("인천중구","인천동구","인천남구","연수구","남동구","부평구","계양구","인천서구", "미추홀구")

busan <- c("부산중구","부산서구","부산동구","영도구","부산진구","부산남구",
"연제구","수영구","해운대구","금정구","동래구","기장군","부산북구", "부산강서구",
"사상구","사하구", "울산중구", "울산남구", "울산동구", "울산북구" ,"울주군")

daegu <- c("대구중구","대구동구","대구서구","대구남구","대구북구","수성구","달서구","달성군")

kwangju <- c("광주동구", "광주서구", "광주남구", "광주북구", "광산구")

daejun <- c("대전동구", "대전중구", "대전서구", "유성구", "대덕구")

gangwon <-c('춘천시','원주시','강릉시','동해시','태백시','속초시')

chunbuk <-c('삼척시','상당구','서원구','흥덕구','청원구','충주시','제천시','음성군')

chungnam <- c("동남구" ,"서북구", "공주시", "보령시", "서산시", "아산시", "논산시", "계룡시", "홍성군", "예산군", "당진시")

junbuk <- c("완산구", "덕진구", "군산시", "익산시", "정읍시" ,"남원시", "김제시")

junnam <- c("목포시", "여수시", "순천시", "나주시", "광양시", "무안군")

kb <- c("포항남구", "포항북구","경주시","김천시","안동시","구미시","영주시","영천시","상주시","문경시","경산시","칠곡군")
kn <- c("의창구","성산구","마산합포구","마산회원구","진해구","진주시","통영시","사천시","밀양시","김해시","거제시","양산시")
jeju <- c("제주시","서귀포시")

# 지역별로 Vertex 색 벡터 생성
colors <- ifelse(V(g)$name %in% seoul, "red",

ifelse(V(g)$name %in% gig, "#458B00",

ifelse(V(g)$name %in% ic, "blue",

ifelse(V(g)$name %in% busan, "purple",

ifelse(V(g)$name %in% daegu, "gold",

ifelse(V(g)$name %in% kwangju, "orange",

ifelse(V(g)$name %in% daejun, "skyblue",

ifelse(V(g)$name %in% gangwon, "beige",

ifelse(V(g)$name %in% chunbuk, "skyblue",

ifelse(V(g)$name %in% chungnam, "skyblue",

ifelse(V(g)$name %in% junbuk, "orange",

ifelse(V(g)$name %in% junnam, "orange",

ifelse(V(g)$name %in% kb, "gold",

ifelse(V(g)$name %in% kn, "purple", "black"))))))))))))))
# weight 값에 따라 색과 선굵기 구별해주는 벡터 생성
e.color <- ifelse(abs(E(g)$weight) > 0.8, "dark red", "skyblue")
e.width <- ifelse(abs(E(g)$weight) > 0.8, abs(E(gg)$weight*0.1), 0.5)
# 시각화
plot(g, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = log(degree(g)*0.2), edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA)
plot(g, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = log(degree(g)*0.2), edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_with_mds)
plot(g, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_as_star)
plot(g, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_as_tree)
# 시각화 2
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, edge.curved = 1, layout = layout.fruchterman.reingold)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, edge.curved = 1, layout = layout_with_mds)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_with_lgl)
plot(ggg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_as_tree)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_in_circle)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, edge.curved = 0, layout = layout_nicely)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_on_grid)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_on_sphere)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_randomly)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = log(degree(g)*0.2), edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, edge.curved = 2, layout = layout_with_gem)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_with_graphopt)
plot(gg, vertex.label.cex = 1, edge.width = abs(E(g)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_with_fr)
# Cluster
gD <- igraph::simplify(igraph::graph.data.frame(edge.abs, directed=FALSE))
lou <- cluster_louvain(gD)
plot(lou, gD, vertex.label = NA, vertex.size=5, edge.arrow.size = 0.0000002,
vertex.color = colors, edge.width = 0.05, edge.curved = 0.2)
# 시각화 2-1 (ppt)
plot(ggg, vertex.label.cex = 1, edge.width = 0.08,
vertex.size = 3, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA)
# tidygraph 객체 생성
ggg1<- tidygraph::as_tbl_graph(ggg) %>% activate(nodes) %>% mutate(label=name)
# tidygraph로 시각화 3
ggg1 %>%
ggraph(layout="tree") +
geom_edge_diagonal(alpha = .2, color='#8B8378') +
geom_node_point(size=0.001, color=colors) +
geom_node_text(aes(label = name), repel = TRUE, point.padding = unit(0.2, "lines"), colour=colors) +
theme_graph(background = 'white')
ggg1 %>%
ggraph(layout="eigen") +
geom_edge_diagonal(alpha = .2, color='#8B8378') +
geom_node_point(size=0.001, color=colors) +
geom_node_text(aes(label = name), repel = TRUE, point.padding = unit(0.2, "lines"), colour=colors) +
theme_graph(background = 'white')
## Warning in layout_with_eigen(graph, type = type, ev = eigenvector): g is
## directed. undirected version is used for the layout.
ggg1 %>%
ggraph(layout="graphopt") +
geom_edge_diagonal(alpha = .2, color='#8B8378') +
geom_node_point(size=0.001, color=colors) +
geom_node_text(aes(label = name), repel = TRUE, point.padding = unit(0.2, "lines"), colour=colors) +
theme_graph(background = 'white')
# 시각화 4
plot(ggs, vertex.label.cex = 1, edge.width = abs(E(ggs)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, edge.curved = 0, layout = layout_with_mds)
plot(ggs, vertex.label.cex = 1, edge.width = 1,
vertex.size = log(degree(g)*0.2), edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, edge.curved = 2, layout = layout_with_gem)
plot(ggs, vertex.label.cex = 1, edge.width = abs(E(ggs)$weight*0.1),
vertex.size = 2, edge.arrow.size = 0.000000000000001,
vertex.color = colors, vertex.label = NA, layout = layout_with_graphopt)

3. Interpretation of results

# 근접 중심성
# 단거리로 모든 노드에 접근이 가능
which.max(closeness(g.abs, mode = "all"))
## 단원구
## 45
which.max(closeness(g.abs, mode = "in"))## Warning in closeness(g.abs, mode = "in"): At centrality.c:2617 :closeness
## centrality is not well-defined for disconnected graphs
## 제주시
## 141
which.max(closeness(g.abs, mode = "out"))## Warning in closeness(g.abs, mode = "out"): At centrality.c:2617 :closeness
## centrality is not well-defined for disconnected graphs
## 종로구
## 1
# 연결 중심성
which.max(centralization.degree(ggg)$res)
## [1] 87V(ggg)$name[87]## [1] "대구중구"# 중개 중심성
# 노드 사이에 위치한 정도 (노드의 중요성)
# 최단 경로로 끼이게 되는 경우의 수로 산출
max(betweenness(g.abs))
## [1] 1265# 아이젠벡터 중심성
# 자신과 연결된 노드의 중요도
eigen_centrality(ggg)
# Diameter
get.diameter(g.abs)
## + 2/141 vertices, named, from 0024510:
## [1] 덕양구 부산남구
# 추이성
# 친구의 친구가 내 친구인 비율
transitivity(gg)
## [1] 0.8651398# 상호 호혜성
reciprocity(ggg)
## [1] 0dyad_census(ggg) # 한쪽으로 가는관계, edge가 없는 node쌍## $mut
## [1] 0
##
## $asym
## [1] 4889
##
## $null
## [1] 4981
# 네트워크 밀도
edge_density(ggg, loops = F) # 연결선수/가능한 연결선수
## [1] 0.2476697# 다른 노드로 정보를 전달할 때, 가장 효율적인 노드
which.min(apply(d, 1, sum))
## [1] 531# 다른 노드로부터 모든 정보를 수집할 때, 가장 효율적인 노드
which.min(apply(d, 2, sum))
## integer(0)# Degree 평균
mean(degree(ggg))
## [1] 69.34752# Vertex 수
length(V(ggg))
## [1] 141# Edge 수
length(E(ggg))
## [1] 4889

4. Conclusion

# 결론

## Degree Centrality
# 디그리 중심성이 가장 높은 지역은 대구광역시 중구이다. 수도권과 떨어져 있지만 대구광역시가 대표적인 부동산 투기 지역으로 인식된 적이 있어 높은 중심성을 보이는 것 같다 [대구지역은 지방보다는 수도권과 가격이 비슷한 모습(대구 수성구와 남구도 그렇다)]

## Closeness Centrality
# 근접 중심성이 가장 높은 지역은 안산시 단원구이다. 안산시 지역이 대부분 네트워크 그래프에서 수도권과 멀리 떨어져있는 모습을 볼 수 있는데, 안산 지역이 다른 수도권 지역에 비해 아파트 가격이 낮은 편이라 수도권과 지방 중간에 위치하는 모습을 보이는 것 같다.

## Betweenness Centrality
# 매개 중심성이 가장 높은 지역은 안산시 상록구이다. 상록구가 부동산 시장의 가격변동 흐름에 통제 능력을 갖고 있고 부동산 가격 정보를 많이 가지고 있다고 판단 할 수 있다.

## Eigenvector Centrality
# 아이겐벡터 중심성이 가장 높은 지역은 서울특별시 노원구이다. 노원구가 전국 아파트 가격흐름을 파악하는데 중요한 역할을 한다고 볼 수 있다.

## 추이성 - 86.8%로 나타났다.
### 아파트 부동산 시장의 흐름을 파악해보고 싶을 때, 종로구의 부동산 시장 흐름을 잘 파악해 보면 될것같다. 또한, 이 분석을 통해서 대구 지역의 부동산 가격을 파악할 수 있었고, 안산의 상록구와 단원구는 수도권 부동산 시장에서 떨어져 나와있는 것을 알 수 있었다. 마지막으로, 대부분 수도권은 수도권끼리 지방은 지방끼리 부동산 시장을 형성하는 것 같다.

--

--