※ 본 문서는 PC 환경에 최적화되어있습니다.
1 목차
구상 중입니다.
2 데이터셋 위치
모든 데이터셋은 ./data/Datasets/
디렉터리 안에 있습니다.
3 정제 데이터셋
데이터셋 랭글링 과정을 담았습니다.
3.1 행정경계 데이터 불러오기
먼저, 분석 스케일의 뼈대가 되는 행정경계 데이터를 불러왔습니다.
3.1.1 사용한 패키지
library(tidyverse)
library(sf)
library(rgdal)
library(raster)
library(tmap)
#library(geojsonio)
3.1.2 구
전국 시군구 파일에서 시군구 코드를 기준으로 서울 지역만 추출하는 과정입니다.
<- readOGR(dsn = "./data/shp/구",
seoulgu layer = "kr_si_gun-gu",
encoding = "UTF-8",
stringsAsFactors = FALSE,
verbose = FALSE) %>%
subset(SIG_CD <= 11740)
#seouldong <- readOGR(dsn = "./data/shp/행정동",
# layer = "HangJeongDong_ver20200401.ㅓ",
# encoding = "UTF-8",
# stringsAsFactors = FALSE,
# verbose = FALSE)# %>% subset(SIG_CD < 20000)
#sqlseoul <- "SELECT * FROM As features WHERE 'adm_cd' < '20000'"
#seouldong <- geojson_read("./data/shp/행정동/HangJeongDong_ver20200401.geojson", parse = FALSE, what = "sp", stringsAsFactors = FALSE)[c(1:425),]
3.1.3 행정동
좌표투영이 되어있지 않은 파일이라 구득 웹 사이트(국가공간정보포털 오픈마켓) 상에서 메타데이터를 확인하여, 지리원 표준 좌표계 중 하나인 ‘EPSG:5181’(중부원점, GRS80)로 좌표계를 정의해주고, 불러옴과 동시에 서울 행정동만 추출하였습니다.
<- readOGR(dsn = "./data/shp/행정동",
seoulemd layer = "Z_SOP_BND_ADM_DONG_PG",
encoding = "UTF-8",
stringsAsFactors = FALSE,
verbose = FALSE,
p4s = "+proj=tmerc +lat_0=38 +lon_0=127 +k=1 +x_0=200000 +y_0=500000 +ellps=GRS80 +units=m +no_defs")[c(1:424),] %>% spTransform(crs(seoulgu))
tmap_mode("view")
tm_shape(seoulemd) + tm_polygons() + tm_shape(seoulgu) + tm_polygons(alpha = 0.7)
3.2 서울시 인구
서울시 인구 자료를 정제하였으며, 2020년 1/4분기 자료입니다.
3.2.1 사용한 패키지
library(tidyverse) # 정제 필수 패키지
library(DT) # 동적 테이블 시각화
# 공간자료화 및 시각화
library(sf)
library(rgdal) # .rmd 상에서 필요
library(tmap)
library(raster)
library(leafem) # leaflet
3.2.2 자료의 출처
해당 자료는 서울시 주민등록인구 (구별) 통계 및 서울시 주민등록인구 (동별) 통계에서 구득하였습니다.
3.2.3 정제 과정 - 구
먼저 앞서 언급한 웹 페이지에서 구득한 구분자(Delimiter)로 구성된 파일을 불러왔습니다.
library(tidyverse)
<- read_tsv("./data/Rawdata/population/20201stQGu.txt")[-c(1:3),] pop.gu
추후에 셰이프 파일 형식으로도 사용할 수 있게 필드명 최대 허용 바이트 수를 넘지 않는 선에서 열 이름을 변경하였습니다.
names(pop.gu) <- c("quarter","GU","HousHld","TotlPop","Male","Female","Domestc",
"DomMale","DomFmle","Foreign","ForMale","ForFmle","cap/hhd","65+")
반복문과 조건문을 사용하여 문자열 열을 수치형 열로 바꿔주는 작업입니다.
천 단위마다 찍히는 콤마를 제거하고, 문자형 열을 제외한 나머지 열만 수치형으로 변환하였습니다.
for(i in 1:length(pop.gu)){
for(j in 1:length(pop.gu$`cap/hhd`)){
<- pop.gu[j,i] %>% str_remove(',')
pop.gu[j,i]
}if(i>2){
<- apply(pop.gu[ , i], 2,
pop.gu[ , i] function(x) as.numeric(as.character(x)))
} }
정제 결과물입니다.
library(DT)
datatable(pop.gu,
caption = "서울특별시 구별 인구 현황",
filter = "top",
options = list(scrollX = TRUE)
)
위의 결과물을 서울시 구 공간 데이터에 속성정보로 넣었습니다.
library(sf)
library(rgdal)
library(tmap)
<- merge(seoulgu, pop.gu ,by.x ="SIG_KOR_NM", by.y = "GU")
seoulgu.pop
tm_shape(seoulgu.pop) + tm_polygons()
#tm_shape(seoulgu.pop) + tm_polygons("총인구", id = "SIG_KOR_NM", palette = "RdBu")
3.2.4 정제 과정 - 동
구에서 정제한 방법과 유사하게 진행하였으며, 먼저 구분자(Delimiter)로 구성된 파일을 불러왔습니다.
<- read_tsv("./data/Rawdata/population/20201stQDong.txt")[-c(1:3),] pop.dong
추후에 셰이프 파일 형식으로도 사용할 수 있게 필드 최대 허용 바이트 수를 넘지 않는 선에서 열 이름을 변경하였습니다.
names(pop.dong) <- c("quarter","GU","EMD","HousHld","TotlPop","Male","Female","Domestc",
"DomMale","DomFmle","Foreign","ForMale","ForFmle","cap/hhd","65+")
반복-조건문을 사용하여 문자형을 수치형으로 변환하였고, 인구자료에는 시군구 코드가 없는 관계로 공간자료 속성정보에 조인(join) 대상을 맞춰주는 작업을 진행하였습니다. 아래는 온점(.)을 가운뎃점(·)으로 변환하는 과정입니다.
for(i in 1:length(pop.dong)){
for(j in 1:length(pop.dong$`cap/hhd`)){
<- pop.dong[j,i] %>% str_remove(',')
pop.dong[j,i]
}if(i>3){
<- apply(pop.dong[ , i], 2,
pop.dong[ , i] function(x) as.numeric(as.character(x)))
}if(i==3){
for(j in 1:length(pop.dong$`cap/hhd`)){
<- pop.dong[j,i] %>% str_replace_all(pattern ="\\.",replacement = "·")
pop.dong[j,i]
}
} }
아래 과정도 조인 대상을 맞춰주는 과정으로, 중복된 동 이름을 알맞게 변환하는 과정입니다.
for(j in 1:length(pop.dong$EMD)){
if(pop.dong$GU[j] == "관악구" && pop.dong$EMD[j] == "신사동"){
$EMD[j] <- "신사동(관악)"
pop.dongelse if(pop.dong$GU[j] == "강남구" && pop.dong$EMD[j] == "신사동"){
}$EMD[j] <- "신사동(강남)"
pop.dong
} }
다음은 공간자료의 속성정보로 넣어주는 과정입니다.
<- merge(seoulemd, pop.dong ,by.x ="ADM_DR_NM", by.y = "EMD")
seoulemd.pop
tm_shape(seoulemd.pop) + tm_polygons("TotlPop")
위에서 조인을 하면서 자연스럽게 구마다 존재하는 소계 항목(행)을 자연스럽게 제거한 결과물입니다.
datatable(seoulemd.pop@data,
caption = "서울특별시 동별 인구 현황",
filter = "top",
options = list(scrollX = TRUE)
)
3.2.5 정제 자료 저장
다음과 같이 ./data/Datasets/Population/
디렉터리에 데이터프레임을 저장하였고, ./data/Datasets/Population/shapefile/
디렉터리에 공간정보 또한 셰이프파일 포멧으로 저장하였습니다. 셰이프 파일 저장시의 for
문은 저장시 위에서 변환한 수치형의 필드를 오류가 발생하지 않도록 다시 한 번 명시하는 과정입니다.
# 서울시 구별 인구 데이터프레임
write_csv(pop.gu, path = "./data/Datasets/Population/20201stQGu.csv", append = FALSE)
# 서울시 동별 인구 데이터프레임
write_csv(seoulemd.pop@data, path = "./data/Datasets/Population/20201stQDong.csv", append = FALSE)
# 서울시 구별 인구 공간정보(폴리곤, 구)
for(i in 5:16){
#print(i)
@data[,i] <- as.numeric(seoulgu.pop@data[,i])
seoulgu.pop
}
writeOGR(seoulgu.pop, paste0(getwd(), "/data/Datasets/Population/shapefile"), "20201stQGu", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
# 서울시 동별 인구 공간정보(폴리곤, 행정동)
for(i in 6:17){
#print(i)
@data[,i] <- as.numeric(seoulemd.pop@data[,i])
seoulemd.pop
}
writeOGR(seoulemd.pop, paste0(getwd(), "/data/Datasets/Population/shapefile"), "20201stQDong", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
3.3 올리브영 매장
3.3.1 사용한 패키지
library(rvest) # html 크롤링시 사용
library(tidyverse) # 정제 필수 패키지
library(DT) # 동적 테이블 시각화
# API 호출 관련
library(httr)
library(xml2)
# 공간자료화 및 시각화
library(sf)
library(rgdal) # .rmd 상에서 필요
library(GISTools) # 폴리곤당 점 계산시 사용
library(tmap)
library(raster)
library(leafem) # leaflet
3.3.2 자료의 출처
해당 문서에서 정제한 올리브영 자료는 2020년 7월 19일 기준입니다.
해당 자료는 올리브영 매장안내에서 구득하였습니다.
올리브영 웹 페이지에 게시된 서울시 매장 목록을 사용할 목적으로 html 크롤링을 시도하였으나
위와 같이 스크롤을 모든 항목이 나타날 때까지 해야 되므로, 아래와 같이
스크롤을 하여 모든 항목을 불러온 뒤, 해당 요소를 ’Notepad++’를 사용하여 별도의 html 파일을 생성하였습니다.
3.3.3 정제 과정
먼저, 생성한 html을 불러왔습니다.
library(rvest)
<- read_html("./data/Rawdata/oliveyoung/olive.html",
html.olive encoding = "UTF-8")
불러온 html에서 ‘매장명’과 ’주소’, ‘전화번호’, ’관심매장등록수’를 추출한 뒤, 하나의 데이터프레임을 만들었습니다.
library(tidyverse)
library(xml2)
<- html_nodes(html.olive, css="a") %>%
name str_remove('<a href="javascript:;">') %>%
str_remove('</a>')
<- html_nodes(html.olive, css=".addr") %>%
addr str_remove('<p class="addr">') %>%
str_remove('</p>')
<- html_nodes(html.olive, css=".call") %>%
ph.numb str_remove('<div class="call">') %>%
str_remove('</div>')
<- html_nodes(html.olive, css=".fv_reShop_in") %>%
noi html_nodes("span") %>%
as_list() %>%
unlist(use.names=FALSE) %>%
str_remove(',')
<- cbind(name,addr,ph.numb,noi) %>%
olive as.data.frame(stringsAsFactors=FALSE)
$noi <- as.integer(olive$noi)
olive
head(olive)
name addr
1 은행사거리점 서울특별시 노원구 한글비석로 264 중계그랜드프라자 1층
2 중계역점 서울특별시 노원구 동일로 1335 (상계동)
3 노원역사거리점 서울특별시 노원구 노해로 480
4 노원점 서울특별시 노원구 상계로 65 105호,106호
5 홈플러스중계점 서울특별시 노원구 동일로204가길 12 홈플러스중계점 1층
6 상계보람점 서울특별시 노원구 한글비석로 471
ph.numb noi
1 02-938-9305 1278
2 02-930-2952 518
3 02-934-5123 2365
4 02-935-5290 1948
5 02-948-6960 526
6 02-930-2532 729
위 데이터프레임을 바로 뒤에 언급할 지오코딩 코드를 사용하여 잘못된 주소를 바로잡았습니다.
$addr[110] <- as.character("서울특별시 강동구 천호대로 997 지하 1층, 천호엔터식스 012호")
olive$addr[113] <- as.character("서울 종로구 송월길 99 경희궁자이 202동 2132호")
olive$addr[142] <- as.character("만리동2가 229-1")
olive$addr[156] <- as.character("서울 강남구 가로수길 5 1층 올리브영 가로수길입구점")
olive$addr[219] <- as.character("서울특별시 서초구 반포동 19-11")
olive$addr[290] <- as.character("서울특별시 동작구 동작대로 3")
olive$addr[368] <- as.character("서울특별시 강동구 성내로 89")
olive$name[368] <- as.character("티지에스플러스(준비중)") olive
다음의 표에서 검색도 가능합니다.
library(DT)
datatable(olive,
caption = "서울특별시 올리브영 매장 목록",
filter = "top",
options = list(scrollX = TRUE)
)
3.3.4 지오코딩
네이버 클라우드에서 제공하는 지오코딩 API 서비스를 사용하여 좌표를 부여하였습니다.
- 네이버 클라우드
앞서 언급하였듯이, 지오코딩 루프를 돌리면서 주소를 통하여 좌표를 잡지 못하는 데이터를 수정하는 방식으로 주소 무결성 또한 확인하였습니다.
API 키는 개인 키이기 때문에 가렸습니다.
library(httr)
library(XML)
#Client ID 설정
<- "API키"
clientId
#Client Secret 설정
<- "비밀번호"
clientSecret
<- vector(mode = "double", length(olive$addr))
x <- vector(mode = "double", length(olive$addr))
y
for (i in 1:length(olive$addr)){
#print(i)
<- httr::GET(
apiResult url = "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode",
::add_headers(
httr`X-NCP-APIGW-API-KEY-ID` = clientId,
`X-NCP-APIGW-API-KEY` = clientSecret
),query = list(
`query` = olive$addr[i]
)
)if (apiResult$status_code == "200"){
#print("ResultCode OK!")
<- rawToChar(apiResult$content)
result Encoding(result) <- "UTF-8"
<- xmlToList(result)
list
<- as.numeric(list$addresses$x)
u <- as.numeric(list$addresses$y)
v
<- u
x[i] <- v
y[i]
else if (apiResult$status_code == "400"){
}print(paste("Bad Request Exception in",olive$name[i]))
else if (apiResult$status_code == "500"){
}print(paste("Unexpected Error in",olive$name[i]))
else{
}print("몰라요")
}
}
$x <- x
olive$y <- y olive
3.3.5 자료의 공간정보화
먼저 GCS(WGS84)를 사용하여 지오코딩으로 획득한 좌표를 사용하여 공간정보로 변환하였습니다. 또한 행정경계 파일과 좌표체계를 통일하여 공간연산이 가능하게 하였습니다.
library(sf)
library(rgdal)
<- crs("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84
crsgcs +towgs84=0,0,0 ")
<- SpatialPointsDataFrame(olive[, 5:6], olive,proj4string = crsgcs) %>% spTransform(crs(seoulgu)) oliveSP
3.3.6 공간연산
구, 행정동당 올리브 영의 수를 연산하였습니다.
library(GISTools)
<- poly.counts(oliveSP, seoulgu)
num.gu <- poly.counts(oliveSP, seoulemd)
num.emd
head(num.gu)
0 1 2 3 4 5
15 22 12 11 14 8
head(num.emd)
0 1 2 3 4 5
1 2 0 1 0 1
위에서 연산한 1차 배열의 자료를 두 행정구역 자료의 속성 테이블에 각각 넣어주었습니다.
@data$num.gu <- num.gu
seoulgu@data$num.emd <- num.emd seoulemd
3.3.7 올리브영 매장 분포 시각화
올리브영 매장 분포
library(tmap)
library(leafem)
tmap_mode("view")
<- tm_shape(oliveSP) + tm_dots()
tm1 <- tmap_leaflet(tm1)
lf addMouseCoordinates(lf)
공간연산(구)
tm_shape(seoulgu) +
tm_polygons("num.gu", id = "SIG_KOR_NM", alpha = 0.7, title = '올리브영 매장 수 <br/>(구)') +
tm_shape(oliveSP) +
tm_dots()
공간연산(행정동)
tm_shape(seoulemd) +
tm_polygons("num.emd", id = "ADM_DR_NM", title = "올리브영 매장 수<br/>(행정동)") +
tm_shape(oliveSP) +
tm_dots()
3.3.8 정제 자료 저장
다음과 같이 ./data/Datasets/Oliveyoung/
디렉터리에 데이터프레임을 저장하였고, ./data/Datasets/Oliveyoung/shapefile/
디렉터리에 공간정보 또한 셰이프파일 포멧으로 저장하였습니다.
# 올리브영 데이터프레임
write_csv(olive, path = "./data/Datasets/Oliveyoung/Oliveyoung.csv", append = FALSE)
# 올리브영 셰이프(점)
writeOGR(oliveSP, paste0(getwd(), "/data/Datasets/Oliveyoung/shapefile"), "Oliveyoung", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
# 올리브영 점포 수 셰이프(폴리곤, 구)
writeOGR(seoulgu, paste0(getwd(), "/data/Datasets/Oliveyoung/shapefile"), "OliveGu", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
#올리브영 점포 수 셰이프(폴리곤, 행정동)
writeOGR(seoulemd, paste0(getwd(), "/data/Datasets/Oliveyoung/shapefile"), "OliveEMD", driver = "ESRI Shapefile",
layer_options = "encoding=UTF-8", overwrite_layer = TRUE)
#test <- read_csv("./data/Datasets/Oliveyoung/Oliveyoung.csv")