본문으로 바로가기

6개월 전쯤에 지도 데이터 시각화 시도해본 적이 있는데 그 때 성공하지 못한 기억이 나네요..

그 때 직접 제 데이터에 적합한 지도데이터 (GeoJSON)을 다운받는 과정도 쉽지 않았고, 제가 가지고 있는 데이터에 연결시키는 과정도 잘 몰라 많이 헤맸습니다. 어찌저찌해서 주변 사람이 결국 도와준 기억이 나네요..

 

하지만 이번에는 AI SCHOOL 수업에서 받은 폴더에 GeoJSON 파일이 있어 어렵지 않게 다운받고 연결시키는 방법도 배우게 되었습니다!

이번 기회로 저도 많이 배우고 있고 성장해나가는 것 같아 기분이 좋네요ㅎㅎ

그럼 설명 시작해보도록 하겠습니다.

 


1. 데이터 준비 및 라이브러리 설치

1.1) 데이터 준비

[지도 데이터 준비]

- 가장 먼저 해야 할 일은 내가 가진 데이터셋에 맞는 지도 데이터를 다운받는 것이다.

- 지도 데이터 : https://github.com/southkorea/southkorea-maps

 

- 우리는 서울시 내 '구' 데이터를 가지고 있기에 '구'만 있는 데이터를 다운받으면 된다.

- 더 상세한 데이터를 사용하고 싶을 경우에는 provinces, municipalities, submunicipalities 데이터를 활용하면 된다.

 

[내 데이터 준비: Google Maps Geocoding API를 활용해서 위도, 경도 받아오기]

- 이 방식은 cholopleth보다 CircleMarker로 표현할 때 자주 사용된다.

- '구', '동' 단위로 표현할 것이 아니라 특정 장소로 표시할 때 따로 위도, 경도를 받아와야 한다.

- 내가 가진 데이터가 특정 '구'가 아닌 'ㅇㅇ경찰서' 이런 식으로 나와있을 때 내 데이터에 맞는 장소의 위도, 경도를 직접 받아오는 방법이다.

- 먼저 'ㅇㅇ경찰서'라고 적혀있을 때 크롤링해서 들고오는 방식이 가능하도록 정확한 명칭으로 바꿔주어야 한다.

station_name = []
for name in df['관서명']:
	station_name.append('서울'+name[:-1]+'경찰서')

- c.f., 필요에 따라 내가 지도에 표현할 데이터 수치들 Scaling 해주기

   - 특별히 수치 차이가 크기 나지 않는 경우에는 Scaling 해주는 것이 좋다.

 

 

- googlemaps geocoding을 하기 위해서는 구글맵스 API가 필요하다.

   - 구글맵스 API key 받기 (영문) : https://developers.google.com/maps/documentation/geocoding/get-api-key (https://goo.gl/mU5NYK

   - 구글맵스 API key 받기 (국문) : https://goo.gl/P4dbxU

 

- 그러면 API 키를 받고 라이브러리를 설치한다.

!pip install googlemaps==4.6.0
import googlemaps
gmaps = googlemaps.Client(key='API 키 입력')

 

- 어떻게 나오는지 확인해보면, 아래와 같이 출력되는 것을 확인할 수 있다.

map_info = gmaps.geocode('서울강남경찰서', language='ko')
map_info

 

- 이제 위도, 경도를 받아오자.

lat = []
lng = []
for name in df['경찰서']:
    # gmaps.geocode('한글 주소', language="ko")로 위도/경도, 우편번호까지 알 수 있음
    tmpMap = gmaps.geocode(name) 
    # 배열 형태( [~] )로 들어오기 때문에 [0]으로 호출
    tmpLoc = tmpMap[0].get('geometry') 
    # dict(tmpLoc)의 데이터는 dict['key값'] 로 value 호출
    lat.append(tmpLoc['location']['lat']) 
    lng.append(tmpLoc['location']['lng'])

df['lat'] = lat
df['lng'] = lng

df.head()


# 경도 & 위도 값으로 주소값 가져오기
# for name in df['경찰서']:
#     gmaps.reverse_geocode((longitude 값, latitude 값), language="ko")

 

 

1.2) 라이브러리 설치

- folium 라이브러리를 활용하여 지도에 시각화

!pip install folium==0.5.0.
import folium
import json

geo_path = 'skorea_municipalities_geo_simple.json'
 #'r': default이기에 따로 써주지 않음
geo_str = json.load(open(geo_path, encoding='utf-8'))

 

- GeoJSON 데이터 파일 활용하며, JSON에서의 객체는 python의 dict(딕셔너리) 형태로 나타내진다.

geo_str['features'][0]['id']   # >'강동구'

- 이렇게 코드를 작성하면 '강동구'가 출력된다.

 

 

- 만약, 데이터 구조가 너무 복잡해서 더 편하게 파악하고 싶다면 pyprnt의 prnt 함수를 사용하면 된다.

이는 JSON이나 dict를 구조를 파악하기 용이하게 그림으로 프린트해주는 함수이다.

from pyprnt import prnt
# 출력 결과가 깨져보일 경우 width 값을 조정 (ex. 60, 70, etc.)
prnt(geo_str, truncate=True, width=80)

 

 


2. 데이터 지도 시각화 (choropleth, circlemarker)

2.1) 먼저 map 함수가 잘 출력되는지 확인

# 편의를 위해 사용했지만 map 변수는 이미 지정된 변수이기에 다른 이름으로 적어주는 것을 권장
map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles='Stamen Toner') 
# tiles : 지도 타입 (default type or "Stamen Terrain" or "Stamen Toner")
# location : 초기 지도 center 위치
map

 

 

2.2) choropleth를 활용한 데이터 시각화

- map.choropleth(geo_data, data, columns, fill_color, key_on)

- map.choropleth 함수에서 준고정된 parameter가 있고, 개별적인 데이터에 맞게 변경해주어야 하는 parameter가 있다.

   - 준고정 : geo_data=geo_str , fill_color='PuRd' , key_on='feature.id'

   - 변경 : data=df['지도에 표현할 데이터'] ,

             columns=[ 'geo_str에서 매칭시켜줄 군 데이터', '지도에 표현할 데이터']

map = folium.Map(location=[37.5502, 126.982], zoom_start=11, tiles='Stamen Toner')

map.choropleth(geo_data = geo_str, # 서울시 행정구역별 polygon drawing, 준고정
               
               # 우리의 데이터에 맞게 설정해주어야 함 (data, columns)
               # columns의 index 1에 적혀있는 데이터 다시 한 번 적어주면 됨
               data = gu_df['살인'], # 시각화의 대상이 될 데이터
               # gu_df.index: '구별'열이 따로 없고 index열에 있기 때문에 .index로 아니였으면 gu_df['구별']
               columns = [gu_df.index, gu_df['살인']], # 1) df의 index 칼럼을 가져와 인식하고
               
               #PuRd, YlGnBu <- color brewer (http://colorbrewer2.org/) 
               #: ‘BuGn’, ‘BuPu’, ‘GnBu’, ‘OrRd’, ‘PuBu’, ‘PuBuGn’, ‘PuRd’, ‘RdPu’, ‘YlGn’, ‘YlGnBu’, ‘YlOrBr’, and ‘YlOrRd’
               # 준고정
               fill_color = 'PuRd', 
               # GeoJSON 규약을 따름, json 파일(지도 데이터)의 "feature" type의 "id" 에 매칭된다
               # 준고정
               key_on = 'feature.id') 

# key_on: Variable in the GeoJSON file to bind the data to. 
# legend_name = "칼라 범주 이름"
# Must always start with 'feature' and be in JavaScript objection notation. 
# Ex: 'feature.id' or 'feature.properties.statename'.
map

 

2.3) CircleMarker를 활용한 데이터 시각화

- 시각화하고자 하는 장소의 위도, 경도 데이터를 가졌다면 CircleMarker로 데이터를 시각화할 수 있다.

ap = folium.Map(location=[37.5502, 126.982], zoom_start=11)

# range(len(df.index))처럼 할 필요 없이 바로 df.index 를 순회하여 record 자체에 접근할 수 있음
for n in df.index: 
    # CircleMarker는 choropleth와 달리 하나씩 동그라미를 그려줘야 함
    folium.CircleMarker ([df['lat'][n], df['lng'][n]],   # 또는 [df.at[n,'lat'], df.at[n,'lng']]
                          radius=df['점수'][n]*0.5,   # circle 의 크기를 결정
                          color='#3186cc', fill=True, fill_color='#3186cc').add_to(map)   # add_to(map) 필수

map

# folium.Circle()의 radius는 meter 단위
# folium.CircleMarker의 radius는 pixel 단위

 

2.4) choropleth, CircleMarker 함께 데이터 시각화

map = folium.Map(location=[37.5502, 126.982], zoom_start=11)

map.choropleth(geo_data = geo_str,
               data = crime_ratio['전체발생비율'],
               columns = [crime_ratio.index, crime_ratio['전체발생비율']],
               fill_color = 'PuRd', #PuRd, YlGnBu
               key_on = 'feature.id')

for n in df.index:
    folium.CircleMarker([df['lat'][n], df['lng'][n]], 
                        radius=df['점수'][n]*0.7, # 0.5 -> 0.7
                        color='#3186cc', fill=True, fill_color='#3186cc').add_to(map)
    
map

 


3. 상세 버전의 GeoJSON 활용

- 보통 지도에 그리는 데 서울이 아닌 지도에 그리기 위해서는 코드를 직접 설정해서 그 지역에 해당하는 코드를 들고와야한다.

- 전국 데이터가 들어있는 곳에서 서울 내 지역만 모으고 데이터에 나타내보겠다.

- 먼저 코드번호로 확인해보겠다. 서울 내 지역이 어떤 코드 형태를 가지고 있는지 확인해보면 '11'로 시작하는 것을 알 수 있다.

- 그러면 startswith() 함수를 사용해서 서울에 해당되는 리스트만 뽑아온다.

geo_path = 'skorea-2018-municipalities-geo.json'
geo_str = json.load(open(geo_path, encoding='utf-8'))

in_seoul = [] # 서울 내 지역만 모을 리스트

for feature in geo_str['features']:
    # code : 법정동 코드 (앞에 5자리는 시/도, 뒤에 5자리는 구)
    if feature['properties']['code'].startswith('11'): # 서울 내 지역의 경우 code가 11로 시작 (11010~11250)
        in_seoul.append(feature)
        
geo_str['features'] = in_seoul

 

 

- 지금은 단순화한 지도로 그렸지만, 더 상세한 지도에 나타내면 다음과 같이 된다.

map = folium.Map(location=[37.5502, 126.982], zoom_start=11)

map.choropleth(geo_data = geo_str,
               data = crime_ratio['전체발생비율'],
               columns = [crime_ratio.index, crime_ratio['전체발생비율']],
               fill_color = 'PuRd', 
               key_on = 'feature.properties.name')

for n in df.index:
    folium.CircleMarker([df['lat'][n], df['lng'][n]], 
                        radius=df['점수'][n]*0.7, 
                        color='#3186cc', fill=True, fill_color='#3186cc').add_to(map)
map

 

- 저장하는 방법은 다음과 같다.

# Map to csv file
map.to_csv('processed_data.csv', encoding='utf-8') # 혹은 euc-kr or cp949

# DF to csv file
df.to_csv('processed_data.csv', encoding='utf-8') # 혹은 euc-kr or cp949