ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • R 지도 시각화를 이용한 서울시 로또 명당 살펴보기
    잡다R 2019. 12. 11. 15:39

     

     지난 시간에는 Rselenium을 이용해서 로또 1등 배출점을 긁어오는 작업을 했습니다. 그러면 이어서 1등 배출점에 대한 지도를 그려볼게요. 지도는 어떤 지도를 그리는 게 좋을까 생각하다가 서울시만 따로 뽑아서 그리기로 결정했습니다. 서울시 자치구별 로또 1등 당첨자 수는 몇 명인지 확인해보고, 로또 맛집이 어디인지 확인해보도록 하겠습니다!

     먼저 지난 시간에 저장하고 끝냈던 데이터를 가져와 보겠습니다.

    store.csv
    0.15MB

    store <- read.csv("store.csv", header = TRUE)

     살펴보고싶다면 이렇게

    dim(store) 
    str(store) 
    summary(store)

     Rselenium을 이용해서 웹크롤링을 해온 데이터입니다. 뿌듯하네요. 하지만 이 데이터 만으로 시각화를 진행하기에는 많이 부족합니다. 지도를 그리기 위해서는 지역별 id와 지리정보파일이 필요합니다. 이 두 데이터를 가져오는 작업은 아래 블로그를 참고하시면 되겠습니다. 좋은 글 정말 감사드립니다.

    • [R visualization] R을 이용한 서울시 지도 시각화 with ggplot2 ggmap raster rgeos maptools rgdal packages (블로그 바로가기) / 작성자 : clueseven 님
    • 최대한 친절하게 쓴 R로 지도에 점 찍고, 선 긋고, 색칠하기 (블로그 바로가기) / 작성자 : kini 님

     

     그럼 지역명, 지역 id, 지리정보파일을 하나로 뭉쳐 지도를 그리기 위한 작업을 진행하겠습니다. 먼저 store 데이터 프레임에서 서울시 자치구별 정보를 가져오겠습니다. store의 location column에는 1등 배출점의 지역정보가 시군구 구분으로 들어 있습니다. 저희는 서울시 소재의 배출점만 필요로 하기 때문에 주소에서 '서울'이 들어있는 정보만 따로 빼오겠습니다. grepl() 함수를 이용하면 원하는 글자가 포함된 데이터를 필터링할 수 있습니다.

    library(dplyr) 
    seoul <- store %>% 
      filter( grepl("서울", location))

     그 다음에는 자치구별 이름만 빼내주겠습니다. 아래의 코드를 진행해주기만 하면 됩니다.

    pattern <- regexpr("[[:alpha:]]{1,}구", seoul$location) 
    seoul$gu <- regmatches(seoul$location, pattern) 
    seoul %>% head
    ## name n location 
    ## 1 스파 35 서울 노원구 상계동 666-3 주공10단지종합상가111 
    ## 2 잠실매점 10 서울 송파구 신천동 7-18번지 잠실역 8번출구 앞 가판 
    ## 3 제이복권방 10 서울 종로구 종로5가 58번지 평창빌딩 1층 103호 
    ## 4 갈렙분식한식 9 서울 중랑구 망우동 490-13번지 
    ## 5 버스판매소 9 서울 영등포구 영등포동4가 440번지 신세계앞 
    ## 6 가판점(신문) 8 서울 영등포구 당산동6가 331-1번지 
    ## gu 
    ## 1 노원구 
    ## 2 송파구 
    ## 3 종로구 
    ## 4 중랑구 
    ## 5 영등포구 
    ## 6 영등포구

     이건 뭔가 당황하시는 분들을 위해 간략하게 설명 드리자면 먼저 [[:alpha:]]{1,}는 텍스트 정규표현입니다. [[:alpha:]]는 모든 텍스트를 의미하고, {1,}는 바로 앞의 정규표현이 1회 이상 반복된 경우를 의미합니다. 둘을 합치면 '1회 이상의 텍스트'라는 뜻인데 뒤에 '구'가 붙었으니 [[:alpha:]]{1,}구는 중구, 종로구, 영등포구와 같이 구로 끝나는 텍스트를 의미합니다.

     regexpr()와 regmatches()는 원하는 표현만 추출하거나 배제하는데 쓰이는 데이터 추출 듀오입니다. regexpr()로 원하는 표현을 적어주면 regmatches()를 통해 해당 표현만 따로 빼올 수 있습니다. 한 마디로 location에서 OO구로 된 표현을 빼오는 작업인거죠. regexpr() 함수는 원하는 표현이 첫 번째로 나타나는 것을 찾아내지만, 앞에 g만 하나 더 붙여서 gregexpr() 함수를 사용하면 모든 해당 표현에 대해서 추출합니다. 그렇게 되면 '구'로 끝나는 단어 중 중구, 종로구처럼 서울시 자치구별 표현 뿐만 아니라 2번 행의 잠실매점의 location에서 '8번출구'라는 표현도 같이 나오겠네요. 어찌 됐든 구로 끝나는 표현이 분명하니까요. 그런 표현 방식이 필요할 때도 있겠지만, 지금과 같은 상황에서는 에러를 일으킬 게 뻔합니다. 따라서 상황에 맞게 사용하시면 되겠습니다.

     설명은 여기까지 하고 다시 데이터를 보겠습니다. 자치구별 이름은 잘 뺐으니 id를 입력해보죠. 서울은 자치구별 고유 id가 있다고 합니다. 지도를 그리기 위해서는 그 id가 필요합니다. 그리고 고유 id는 인터넷에 잘 정리가 되어있기 때문에 파일을 받아서 불러오도록 합니다. 시군구 id가 2자리 수인 데이터도 있고 5자리인 데이터도 있던데, 저희가 사용할 지리정보파일에서는 5자리를 사용하기 때문에 5자리 id코드를 받아오시면 됩니다.

    seoul_id <- read.csv("seoul_id.csv", header = TRUE) 
    seoul_id %>% head
    ## 시군구명 id 
    ## 1 종로구 11110 
    ## 2 중구 11140 
    ## 3 용산구 11170 
    ## 4 성동구 11200 
    ## 5 광진구 11215 
    ## 6 동대문구 11230

     서울 자치구별 id를 받아온 seoul_id를 seoul 데이터에 left_join 해주도록 할게요. 그 전에 오류를 최대한 피해주기 위해서 column명을 영어로 바꿔주겠습니다. rename() 함수를 사용하도록 하겠습니다.

    seoul_id <- rename(seoul_id, "gu" = "시군구명") 
    seoul <- left_join(seoul, seoul_id, by = "gu") 
    seoul %>% head
    ## name n location 
    ## 1 스파 35 서울 노원구 상계동 666-3 주공10단지종합상가111 
    ## 2 잠실매점 10 서울 송파구 신천동 7-18번지 잠실역 8번출구 앞 가판 
    ## 3 제이복권방 10 서울 종로구 종로5가 58번지 평창빌딩 1층 103호 
    ## 4 갈렙분식한식 9 서울 중랑구 망우동 490-13번지 
    ## 5 버스판매소 9 서울 영등포구 영등포동4가 440번지 신세계앞 
    ## 6 가판점(신문) 8 서울 영등포구 당산동6가 331-1번지 
    ## gu id 
    ## 1 노원구 11350 
    ## 2 송파구 11710 
    ## 3 종로구 11110 
    ## 4 중랑구 11260 
    ## 5 영등포구 11560 
    ## 6 영등포구 11560

     seoul에 자치구별 id까지 데이터가 잘 들어간 것을 확인할 수 있습니다. 여기서 서울시 자치구별 로또 1등 당첨자가 몇 명인지 확인할 수 있는 seoul_sum이라는 데이터 프레임을 만들어보겠습니다. 상호명과 상세주소는 일단 생각하지 않도록 하겠습니다. 구역 이름과 id, 자치구별 로또 1등 당첨 횟수만 아래와 같이 준비해주세요.

    seoul_sum <- seoul %>% 
      group_by(id, gu) %>% 
      summarise(sum_n = sum(n)) 
      
    seoul_sum %>% head
    ## # A tibble: 6 x 3 
    ## # Groups: id [6] 
    ## id gu sum_n 
    ## <int> <chr> <int> 
    ## 1 11110 종로구 50 
    ## 2 11140 중구 55 
    ## 3 11170 용산구 18 
    ## 4 11200 성동구 30 
    ## 5 11215 광진구 28 
    ## 6 11230 동대문구 34

    자치구 id는 잘 병합을 했습니다. 그럼 다음으로는 지리정보를 추가해줄게요. 좌표에 지도를 그려준 지리정보파일, '.shp파일'을 불러주기 위해 raster 라이브러리를 불러옵니다.

    library(raster) 
    map_shape <- shapefile("SIG_201703/TL_SCCO_SIG.shp")

    가져온 지리정보자료를 ggplot2 fortify() 함수를 이용해서 data frame 형태로 바꿔줍니다.

    library(ggplot2) 
    map <- fortify(map_shape, region = "SIG_CD") 
    str(map)
    ## 'data.frame': 1337742 obs. of 7 variables: 
    ## $ long : num 127 127 127 127 127 ... 
    ## $ lat : num 37.6 37.6 37.6 37.6 37.6 ... 
    ## $ order: int 1 2 3 4 5 6 7 8 9 10 ... 
    ## $ hole : logi FALSE FALSE FALSE FALSE FALSE FALSE ... 
    ## $ piece: Factor w/ 858 levels "1","2","3","4",..: 1 1 1 1 1 1 1 1 1 1 ... 
    ## $ id : chr "11110" "11110" "11110" "11110" ... 
    ## $ group: Factor w/ 5043 levels "11110.1","11140.1",..: 1 1 1 1 1 1 1 1 1 1 ...

     데이터를 확인하고 싶으면 str(map)을 확인해보세요. 이 파일이 어디에 쓰이는 물건인지는 아마 QGIS 쓰시는 분들이 자세하게 알고 계실 듯 합니다. 궁금하다면 위에 알려드린 블로그에 들어가서 한번 확인해보세요. 짧게 설명드리면 위도와 경도를 이용해서 지도를 그린 파일이고, 시군구별 id도 들어 있습니다. 데이터가 워낙 크기 때문에 원활한 사용을 위해 서울시의 25개 자치구만 뽑아내겠습니다. id가 11740 이하면 서울 지역입니다.

    map$id <- as.numeric(map$id) 
    seoul_map <- map[map$id <= 11740,]

     seoul_map에 서울지역 정보만 담아왔다면, 앞서 만든 seoul_sum과 합쳐줍니다.

    M <- merge(seoul_map, seoul_sum, by = "id")

     이렇게 하면 지역명, 자치구별 id, 지도를 그릴 수 있는 지리정보파일까지 모두 하나의 데이터로 합쳤습니다. 지도를 그릴 수 있는 최소한의 작업이 끝났으니 한번 지도를 그려볼까요?

    ggplot() + 
      geom_polygon(data = M, 
                   aes(x = long, 
                       y = lat, 
                       group = group, 
                       fill = sum_n), 
                   color = "white") 

     

     음.. 그리긴 그렸는데 뭔가 이쁘지 않게 나옵니다. theme과 color를 손봐서 좀 더 이쁘게 바꿔볼게요.

     

     

     잘 그려졌는데 여전히 뭔가 허전하네요. 가독성이 떨어지는 것 같아요. 어디가 어디 구인지 잘 모르겠습니다. 가독성을 높여주기 위해서 지도에 자치구명을 적어줘야겠습니다. 자치구명은 좌표의 중앙점을 찾아서 대입해줘야 합니다. 요 좌표에 따른 자치구별 이름을 삽입하는 파일은 구글링으로 찾을 수 없어서.. 제가 일일이 좌표 설정을 했습니다. gu_name이라는 csv파일을 만들었죠. 글씨를 입히는 방법은 좌표 설정한 파일을 불러와서 geom_text()에 써주면 됩니다.

     

    gu_name <- read.csv("gu_name.csv", header = TRUE) 
    
    # *주의* 순서가 중요할 수 있음! 
    ggplot() + 
      geom_polygon(data = M, 
                   aes(x = long, 
                       y = lat, 
                       group = group, 
                       fill = sum_n), 
                   color = "white") + 
      scale_fill_gradient(low = "#FBCF61", 
                          high = "#00CC99", 
                          space = "Lab", 
                          guide = "colourbar") + 
      labs(fill = "로또 1등 당첨자 수") + 
      theme_void() + 
      theme(legend.position = c(.15, .85)) +
      geom_text(data = gu_name, 
                aes(x = long, 
                    y = lat, 
                    label = paste(gu, sum_n, sep = "\n")))

     

     좋아요! 가독성이 한껏 좋아졌습니다. 한번 살펴보도록 하죠. 송파구와 노원구가 각각 68, 62회씩 1등 당첨자가 나왔네요. 그렇다면 서울시 로또 맛집은 송파구에 있다고 말하면 될까요? 결론부터 말하자면 그렇게 말하는 건 통계적으로 이건 잘못된 해석입니다. 이렇게 생각해보겠습니다. A지점에는 1등 당첨자가 100명이 나왔고, B지점에서는 10명이라면, A지점이 B지점보다 좋은 로또 명당이라고 말할 수 있을까요?

     돈은 많을 수록 좋고, 수능 등급은 낮을 수록 좋습니다. 어떤 분야에서는 단순히 숫자가 높고 낮고에 따라서 좋다 안 좋다를 쉽게 말할 수 있습니다. 하지만 '비율'로 얘기해야 분야도 많습니다. 이를테면 범죄율(인구 천명당 범죄발생건수)이나, (1인당)GDP 같은 것 말이죠. 상대적인 차이를 고려해야 한다면 우리는 '비율'을 봐야합니다. A지점의 로또 판매량이 10만장인데 비해 B지점의 판매량은 20장이라면, 1등 당첨 확률은 A지점에서는 0.1%인 반면, B지점에서는 50%나 됩니다! 따라서 진짜 명당은 B지점이라고 말할 수 있는 것이죠.

     우리가 진짜 로또 명당을 가리기 위해서는 각 판매점 별 판매량을 알아야합니다. 그래서 판매점 별 1등 당첨 비율을 비교하는 것이 확실한 방법이죠. 하지만 가장 큰 문제는 판매점 당 판매량에 대한 데이터를 구할 수 없다는 것입니다. 흑흑. 인터넷 어디에도 로또 판매점 별 판매량을 보여주는 데이터가 존재하지 않더라구요. 그래서 우리는 판매량이 아닌 다른 척도로 로또 명당을 찾아볼까 합니다. 바로 '인구'를 통해서 입니다.

     서울시 자치구별 인구 수를 나타내는 데이터는 서울열린데이터광장(바로가기)에서 받으실 수 있습니다. 저는 2019년도 3사분기 서울 인구 수를 사용하겠습니다.

    library(readxl) 
    seoul_pop <- read_xls("Report.xls", col_names = TRUE) 
    
    # 앞에 row 3줄은 필요없고, column은 자치구 및 자치구별 전체 인구 수를 나타내는 컬럼만 뽑아냅니다. 
    seoul_pop <- seoul_pop[-c(1:3),c(2,4)] 
    
    # rename을 이용해서 column명을 고치고 데이터에 자치구별 인구 수를 병합하겠습니다. 
    seoul_pop <- rename(seoul_pop, gu = "자치구", pop = "인구...4") 
    M <- left_join(M, seoul_pop, by = "gu") 
    gu_name <- left_join(gu_name, seoul_pop, by = "gu")
    # 인구를 나타내는 pop의 값이 factor가 아닌 숫자형으로 변환해줍니다. 
    M$pop <- as.numeric(M$pop)

     병합하고 지도를 그려보면 아래와 같습니다.

     

     자치구별 로또 1등 빈도를 나타낸 위에 지도와 쉽게 비교해보기 위해 색상을 똑같이 했습니다. 어떠신가요? 두 그림이 얼추 비슷하구나 하는 느낌이 들지 않으신가요? 비교 척도를 정도껏 잘 설정한 것 같습니다. 그렇다면 인구 대비 1등 당첨자가 가장 많은 구는 어디일까? 빨리 확인해보겠습니다.

    # 인구 대비 로또 1등 당첨 비율을 먼저 구해주고 
    M <- M %>% 
      mutate(ratio = round(sum_n / pop * 100000)) 
    gu_name <- left_join(gu_name, M[,c("gu", "ratio")], by = "gu") 
    
    # 지도를 그려줍니다. 
    ggplot() + 
      geom_polygon(data = M, 
                   aes(x = long, 
                       y = lat, 
                       group = group, 
                       fill = ratio), 
                   color = "white") + 
      geom_text(data = gu_name, 
                aes(x = long, 
                    y = lat, 
                    label = paste(gu, ratio, sep = "\n"))) + 
      scale_fill_gradient(low = "#FFDFD3", 
                          high = "#D291BC", 
                          space = "Lab", 
                          guide = "colourbar") + 
      labs(fill = "인구 십만명당 로또 당첨자 수\n(단위 : 명)") + 
      theme_void() + 
      theme(legend.position = c(.2, .85))

     

     

     오옹 로또 당첨을 비율로 따지면 중구랑 종로구가 롯도 맛집이네요. 아마 종로구와 중구는 기업이 밀집되어 있는 지역이라 상대적으로 경제활동인구가 많다는 점이 이러한 결과를 나타낸 요인으로 작용한 듯 싶습니다.

    ## name n location 
    ## 1 제이복권방 10 서울 종로구 종로5가 58번지 평창빌딩 1층 103호

     이렇게 되면 서울시 로또 맛집은 종로구의 제이복권방이 되겠습니다! 여기까지 R 지도시각화를 이용한 로또 명당을 알아봤습니다. 양심없지만 로또 명당을 찾는 분들이나 지도 시각화를 해보시려는 분들께 도움이 된 글이었으면 좋겠습니다 ㅋㅋ 

    :)

     

     

Designed by Tistory.