Dataset Readme(Sample)



Wrangled Data


- 서울특별시 데이터셋



YongHun Suh

Date Generated: May 14, 2021
- Troubleshooter
- RGIS Ref



※ 본 문서는 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

전국 시군구 파일에서 시군구 코드를 기준으로 서울 지역만 추출하는 과정입니다.

seoulgu <- readOGR(dsn = "./data/shp/구",
                       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)로 좌표계를 정의해주고, 불러옴과 동시에 서울 행정동만 추출하였습니다.

seoulemd <- readOGR(dsn = "./data/shp/행정동", 
                    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)

pop.gu <- read_tsv("./data/Rawdata/population/20201stQGu.txt")[-c(1:3),]

추후에 셰이프 파일 형식으로도 사용할 수 있게 필드명 최대 허용 바이트 수를 넘지 않는 선에서 열 이름을 변경하였습니다.

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] <- pop.gu[j,i] %>% str_remove(',')
  }
  if(i>2){
    pop.gu[ , i] <- apply(pop.gu[ , i], 2,
                          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)

seoulgu.pop <- merge(seoulgu, pop.gu ,by.x ="SIG_KOR_NM", by.y = "GU")

tm_shape(seoulgu.pop) + tm_polygons()
#tm_shape(seoulgu.pop) + tm_polygons("총인구", id = "SIG_KOR_NM", palette = "RdBu")

3.2.4 정제 과정 - 동

구에서 정제한 방법과 유사하게 진행하였으며, 먼저 구분자(Delimiter)로 구성된 파일을 불러왔습니다.

pop.dong <- read_tsv("./data/Rawdata/population/20201stQDong.txt")[-c(1:3),]

추후에 셰이프 파일 형식으로도 사용할 수 있게 필드 최대 허용 바이트 수를 넘지 않는 선에서 열 이름을 변경하였습니다.

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] <- pop.dong[j,i] %>% str_remove(',')
  }
  if(i>3){
    pop.dong[ , i] <- apply(pop.dong[ , i], 2,
                          function(x) as.numeric(as.character(x)))
  }
  if(i==3){
    for(j in 1:length(pop.dong$`cap/hhd`)){
    pop.dong[j,i] <- pop.dong[j,i] %>% str_replace_all(pattern ="\\.",replacement = "·")
    }
  }
}

아래 과정도 조인 대상을 맞춰주는 과정으로, 중복된 동 이름을 알맞게 변환하는 과정입니다.

for(j in 1:length(pop.dong$EMD)){
      if(pop.dong$GU[j] == "관악구" && pop.dong$EMD[j] == "신사동"){
        pop.dong$EMD[j] <- "신사동(관악)"
      }else if(pop.dong$GU[j] == "강남구" && pop.dong$EMD[j] == "신사동"){
        pop.dong$EMD[j] <-  "신사동(강남)"
      }
}

다음은 공간자료의 속성정보로 넣어주는 과정입니다.

seoulemd.pop <- merge(seoulemd, pop.dong ,by.x ="ADM_DR_NM", by.y = "EMD")

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)
  seoulgu.pop@data[,i] <- as.numeric(seoulgu.pop@data[,i])
}

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)
  seoulemd.pop@data[,i] <- as.numeric(seoulemd.pop@data[,i])
}

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)

html.olive <- read_html("./data/Rawdata/oliveyoung/olive.html",
                        encoding = "UTF-8")

불러온 html에서 ‘매장명’과 ’주소’, ‘전화번호’, ’관심매장등록수’를 추출한 뒤, 하나의 데이터프레임을 만들었습니다.

library(tidyverse)
library(xml2)

name <- html_nodes(html.olive, css="a") %>% 
  str_remove('<a href="javascript:;">') %>%
  str_remove('</a>')

addr <- html_nodes(html.olive, css=".addr") %>% 
  str_remove('<p class="addr">') %>%
  str_remove('</p>')


ph.numb <- html_nodes(html.olive, css=".call") %>% 
  str_remove('<div class="call">') %>%
  str_remove('</div>')

noi <- html_nodes(html.olive, css=".fv_reShop_in") %>%
  html_nodes("span") %>% 
  as_list() %>% 
  unlist(use.names=FALSE) %>% 
  str_remove(',')

olive <- cbind(name,addr,ph.numb,noi) %>% 
  as.data.frame(stringsAsFactors=FALSE)

olive$noi <- as.integer(olive$noi)

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

위 데이터프레임을 바로 뒤에 언급할 지오코딩 코드를 사용하여 잘못된 주소를 바로잡았습니다.

olive$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("티지에스플러스(준비중)")

다음의 표에서 검색도 가능합니다.

library(DT)

datatable(olive, 
          caption = "서울특별시 올리브영 매장 목록",
          filter = "top",
          options = list(scrollX = TRUE)
          )

3.3.4 지오코딩

네이버 클라우드에서 제공하는 지오코딩 API 서비스를 사용하여 좌표를 부여하였습니다.
- 네이버 클라우드



앞서 언급하였듯이, 지오코딩 루프를 돌리면서 주소를 통하여 좌표를 잡지 못하는 데이터를 수정하는 방식으로 주소 무결성 또한 확인하였습니다.



API 키는 개인 키이기 때문에 가렸습니다.

library(httr)
library(XML)

#Client ID 설정 
clientId <- "API키"

#Client Secret 설정
clientSecret <- "비밀번호"



x <- vector(mode = "double", length(olive$addr))
y <- vector(mode = "double", length(olive$addr))

for (i in 1:length(olive$addr)){
  #print(i)
  apiResult <- httr::GET( 
    url = "https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode", 
    httr::add_headers(
      `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!")
    result <- rawToChar(apiResult$content)  
    Encoding(result) <- "UTF-8"
    list <- xmlToList(result)
    
    u <- as.numeric(list$addresses$x)
    v <- as.numeric(list$addresses$y)
    
    x[i] <- u
    y[i] <- v
    
    
  }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("몰라요")
  }
  
}

olive$x <- x
olive$y <- y

3.3.5 자료의 공간정보화

먼저 GCS(WGS84)를 사용하여 지오코딩으로 획득한 좌표를 사용하여 공간정보로 변환하였습니다. 또한 행정경계 파일과 좌표체계를 통일하여 공간연산이 가능하게 하였습니다.

library(sf)
library(rgdal)

crsgcs <- crs("+proj=longlat +datum=WGS84 +no_defs +ellps=WGS84
+towgs84=0,0,0 ")

oliveSP <- SpatialPointsDataFrame(olive[, 5:6], olive,proj4string = crsgcs) %>% spTransform(crs(seoulgu))

3.3.6 공간연산

구, 행정동당 올리브 영의 수를 연산하였습니다.

library(GISTools)
num.gu <- poly.counts(oliveSP, seoulgu)
num.emd <- poly.counts(oliveSP, seoulemd)

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차 배열의 자료를 두 행정구역 자료의 속성 테이블에 각각 넣어주었습니다.

seoulgu@data$num.gu <- num.gu
seoulemd@data$num.emd <- num.emd

3.3.7 올리브영 매장 분포 시각화

올리브영 매장 분포

library(tmap)
library(leafem)

tmap_mode("view")
tm1 <- tm_shape(oliveSP) + tm_dots()
lf <- tmap_leaflet(tm1)
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")