도찐개찐

[Python] folium(지도시각화) 본문

PYTHON/데이터분석

[Python] folium(지도시각화)

도개진 2022. 12. 26. 12:39

지도위에 데이터를 interactive하게 표현해 주는 대표적인 파이썬 지도 시각화 라이브러리 folium에 대해서 알아보고 그 사용법을 파헤쳐보도록 하겠습니다.

folium 개요

folium은 leaflet.js 기반으로 만들어진 Python 지도 시각화 라이브러리 입니다.

도큐먼트

folium을 사용하여 인터랙티브한 지도를 생성하고 마커를 추가하여 시각화하거나 원으로 범위를 표기하고 html 파일로 내보내기 등을 수행할 수 있습니다.

folium 설치

pip install folium으로 라이브러리를 설치할 수 있습니다.

!pip install folium

모듈 import

import folium

기본 좌표 설정

location에 위도, 경도 정보를 입력하여 입력한 위,경도 좌표를 기준으로 지도를 그릴 수 있습니다.

이때 zoom_start 정보를 지정하여 확대의 정도를 지정할 수 있습니다.

  • 참고: zoom_start의 범위는 최대 18 입니다.
# 위도
latitude = 37.394946
# 경도
longitude = 127.111104
m = folium.Map(location=[latitude, longitude],
               zoom_start=17, 
               width=750, 
               height=500
              )
m

마커 추가

  • location: 마커를 추가할 위도/경도 좌표를 입력 후
  • popup: 표기할 팝업 문구 지정 (마우스 클릭시 표기되는 문구)
  • tooltip: 표기할 툴팁 지정 (마우스 오버시 표기되는 문구)

마커를 생성 후 기존에 생성된 지도 m에 추가합니다.

folium.Marker([latitude, longitude],
              popup="판교역",
              tooltip="판교역 입구").add_to(m)
m

마커에 대한 스타일 변경도 가능합니다. 스타일 변경시 icon 파라미터에 folium.Icon(color=?, icon=?)을 지정합니다.

folium.Marker([latitude, longitude],
              popup="판교역",
              tooltip="판교역 입구", 
              icon=folium.Icon('red', icon='star'),
             ).add_to(m)
m

popup이나 tooltip에 다음과 같이 html 코드를 삽입하여 이미지를 표기하거나 심지어 YouTube 영상도 삽입할 수 있습니다.

folium.Marker([latitude, longitude],
              popup='<iframe width="560" height="315" src="https://www.youtube.com/embed/dpwTOQri42s" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>',
              tooltip="판교역 입구").add_to(m)
m

folium.CircleMarker([latitude, longitude],
                    color='tomato',
                    radius = 50, 
                    tooltip='판교역 상권').add_to(m)
m

import pandas as pd
df = pd.read_csv('소상공인시장진흥공단_상가(상권)정보_경기_202109.csv')
df.head(3)
상가업소번호상호명지점명상권업종대분류코드상권업종대분류명상권업종중분류코드상권업종중분류명상권업종소분류코드상권업종소분류명표준산업분류코드...구우편번호신우편번호동정보층정보호정보전화번호경도위도상권번호데이터기준일자012
20713599 눈높이러닝센타 NaN R 학문/교육 R13 학문교육기타 R13A01 학습지보급 P85503 ... 423010.0 14221.0 NaN NaN 2 02-2066-9109 126.858147 37.478530 NaN 2016-01-26
20642964 유니베라 구성대리점 D 소매 D16 화장품소매 D16A01 화장품판매점 G47813 ... 446525.0 16919.0 NaN 2 NaN 031-8005-7071 127.116575 37.294690 NaN 2021-10-22
24510829 엠아이케이21 NaN D 소매 D14 운동/경기용품소매 D14A01 운동/경기용품 G47631 ... 463937.0 13622.0 901 NaN 90 070-8699-8912 127.123291 37.340903 NaN 2016-01-26

3 rows × 42 columns

pd.Series(df.columns)
0        상가업소번호
1           상호명
2           지점명
3     상권업종대분류코드
4      상권업종대분류명
5     상권업종중분류코드
6      상권업종중분류명
7     상권업종소분류코드
8      상권업종소분류명
9      표준산업분류코드
10      표준산업분류명
11         시도코드
12          시도명
13        시군구코드
14         시군구명
15        행정동코드
16         행정동명
17        법정동코드
18         법정동명
19        PNU코드
20       대지구분코드
21        대지구분명
22        지번본번지
23        지번부번지
24         지번주소
25        도로명코드
26          도로명
27        건물본번지
28        건물부번지
29       건물관리번호
30          건물명
31        도로명주소
32        구우편번호
33        신우편번호
34          동정보
35          층정보
36          호정보
37         전화번호
38           경도
39           위도
40         상권번호
41      데이터기준일자
dtype: object
sub_df = df.loc[df['행정동명'].isin(['백현동', '정자동', '삼평동'])]
sub_df.head(3)
상가업소번호상호명지점명상권업종대분류코드상권업종대분류명상권업종중분류코드상권업종중분류명상권업종소분류코드상권업종소분류명표준산업분류코드...구우편번호신우편번호동정보층정보호정보전화번호경도위도상권번호데이터기준일자35102244
24527550 코리아세븐분당 정자3호점 D 소매 D03 종합소매점 D03A01 편의점 G47122 ... 463834.0 13612.0 NaN 1 NaN 031-718-9733 127.113508 37.362807 NaN 2016-01-27
28523933 처가방 NaN Q 음식 Q10 별식/퓨전요리 Q10A02 샤브샤브전문 I56111 ... 463420.0 13529.0 NaN NaN NaN 031-5170-1908 127.112071 37.392785 NaN 2016-11-17
20786763 백현 NaN L 부동산 L01 부동산중개 L01A01 부동산중개 L68221 ... 463887.0 13532.0 NaN 1 1 031-8016-8100 127.110756 37.389333 NaN 2017-10-02

3 rows × 42 columns

sub_df[['위도', '경도', '상호명']]
위도경도상호명35102244284678...514805515501515824516332516457
37.362807 127.113508 코리아세븐분당
37.392785 127.112071 처가방
37.389333 127.110756 백현
37.401265 127.108639 야쿤커피앤토스트판교점
37.368770 127.112015 해동검도
... ... ...
37.397736 127.111414 에세이스튜디오
37.362357 127.113513 아몽옷고치는전문집
37.384859 127.111408 티랩
37.392951 127.118687 우디크리빙
37.395832 127.113503 써브웨이

3322 rows × 3 columns

from folium.plugins import MarkerCluster


m = folium.Map(
    location=[latitude, longitude],
    zoom_start=15
)

coords = sub_df[['위도', '경도']]


marker_cluster = MarkerCluster().add_to(m)

for lat, long in zip(coords['위도'], coords['경도']):
    folium.Marker([lat, long], icon = folium.Icon(color="green")).add_to(marker_cluster)
m

서울 지도에서 행정 구역별 표시

import requests
import json

# 서울 행정구역 json raw파일(githubcontent)
r = requests.get('https://raw.githubusercontent.com/southkorea/seoul-maps/master/kostat/2013/json/seoul_municipalities_geo_simple.json')
c = r.content
seoul_geo = json.loads(c)

서울 지역의 구별 boundary 시각화

m = folium.Map(
    location=[37.559819, 126.963895],
    zoom_start=11, 
)

folium.GeoJson(
    seoul_geo,
    name='지역구'
).add_to(m)

m

tiles 옵션 변경을 통해 지도의 테마 변경

지도의 기본 테마(tiles)는 OpenStreetMap으로 설정되어 있는데, 이를 변경하여 다른 지도 테마를 적용할 수 있다.

Stamen Toner 적용시

m = folium.Map(
    location=[37.559819, 126.963895],
    zoom_start=11, 
    tiles='Stamen Toner'
)

folium.GeoJson(
    seoul_geo,
    name='지역구'
).add_to(m)

m

cartodbpositron 적용시

m = folium.Map(
    location=[37.559819, 126.963895],
    zoom_start=11, 
    tiles='cartodbpositron'
)

folium.GeoJson(
    seoul_geo,
    name='지역구'
).add_to(m)

m

서울시 자치구별 상권정보 시각화

seoul 데이터프레임에 소상공인시장진흥공단에서 제공하는 서울시 상권정보 csv 파일을 로드합니다.

seoul = pd.read_csv('소상공인시장진흥공단_상가(상권)정보_서울_202109.csv')

# 필요한 컬럼 정보만 가져옵니다
seoul = seoul[['시군구명', '상권업종대분류명', '상권업종중분류명', '위도', '경도']]
seoul
시군구명상권업종대분류명상권업종중분류명위도경도01234...325875325876325877325878325879
송파구 소매 의복의류 37.493054 127.147321
영등포구 소매 건강/미용식품 37.520613 126.907168
성동구 소매 취미/오락관련소매 37.566857 127.049018
동작구 음식 한식 37.487105 126.980952
종로구 음식 한식 37.572387 126.981794
... ... ... ... ...
마포구 부동산 부동산중개 37.557971 126.907290
은평구 소매 시계/귀금속소매 37.604195 126.936049
은평구 소매 애견/애완/동물 37.596790 126.905613
광진구 음식 커피점/카페 37.556004 127.085023
강북구 생활서비스 이/미용/건강 37.629348 127.017622

325880 rows × 5 columns

시각화 모듈을 import 하고 서울시의 업종별 개수를 시각화합니다.

import matplotlib.pyplot as plt
import seaborn as sns

# 한글 폰트 설정
plt.rcParams['font.family'] = 'NanumGothic'

plt.figure(figsize=(12, 30))
sns.countplot(y=seoul['상권업종중분류명'], order=seoul['상권업종중분류명'].value_counts().index)
plt.yticks(fontsize=12)
plt.title('서울시 업종별 개수')
plt.show()

한식 업종이 가장 많은 개수를 차지합니다. 커피점/카페 업종이 가장 많은 업종일 줄 알았는데요. 한식, 이미용/건강, 종합소매업 다음 4위를 차지하였습니다.

그럼 커피점/카페 업종은 자치구별로 어느 곳에 가장 많이 분포해 있는지 시각화해 보겠습니다.

 
plt.figure(figsize=(12, 10))
seoul.loc[seoul['상권업종중분류명'] == '커피점/카페'].groupby('시군구명')['상권업종대분류명'].count()\
                                                     .sort_values().plot(kind='barh', color='royalblue')
plt.yticks(fontsize=12)
plt.title('서울시 자치구별 커피점/카페 업종수')
plt.show()

강남구가 가장 많은 수를 차지하고 강북구는 가장 적은 수를 차지하였습니다.

이제 이를 folium 위에 시각화를 해보겠습니다.

seoul_coffee = seoul.loc[seoul['상권업종중분류명'] == '커피점/카페']
seoul_coffee
시군구명상권업종대분류명상권업종중분류명위도경도29109190200206...325715325754325788325791325878
종로구 음식 커피점/카페 37.583149 127.000171
도봉구 음식 커피점/카페 37.658728 127.034746
마포구 음식 커피점/카페 37.554599 126.929692
강서구 음식 커피점/카페 37.580941 126.813358
양천구 음식 커피점/카페 37.522055 126.842935
... ... ... ... ...
서대문구 음식 커피점/카페 37.557293 126.950705
금천구 음식 커피점/카페 37.468602 126.902047
마포구 음식 커피점/카페 37.540241 126.948240
강서구 음식 커피점/카페 37.532109 126.839080
광진구 음식 커피점/카페 37.556004 127.085023

19703 rows × 5 columns

m = folium.Map(
    location=[37.559819, 126.963895],
    zoom_start=12, 
    tiles='cartodbpositron'
)

folium.GeoJson(
    seoul_geo,
    name='지역구'
).add_to(m)

marker_cluster = MarkerCluster().add_to(m)

for lat, long in zip(seoul_coffee['위도'], seoul_coffee['경도']):
    folium.Marker([lat, long], icon = folium.Icon(color="green")).add_to(marker_cluster)

m

seoul_group_data = seoul.loc[seoul['상권업종중분류명'] == '커피점/카페'].groupby('시군구명')['상권업종중분류명'].count()
seoul_group_data
시군구명
강남구     2253
강동구      763
강북구      330
강서구     1048
관악구      593
광진구      771
구로구      596
금천구      444
노원구      534
도봉구      373
동대문구     499
동작구      526
마포구     1427
서대문구     704
서초구     1277
성동구      640
성북구      668
송파구     1125
양천구      543
영등포구     859
용산구      730
은평구      575
종로구     1082
중구       895
중랑구      448
Name: 상권업종중분류명, dtype: int64
 

choropleth 를 사용하여 시각화를 하면 업종 별 개수에 따른 색상의 차이를 두어 시각화를 해줍니다.

m = folium.Map(
    location=[37.559819, 126.963895],
    zoom_start=11, 
    tiles='cartodbpositron'
)

folium.GeoJson(
    seoul_geo,
    name='지역구'
).add_to(m)

m.choropleth(geo_data=seoul_geo,
             data=seoul_group_data, 
             fill_color='YlOrRd', # 색상 변경도 가능하다
             fill_opacity=0.5,
             line_opacity=0.2,
             key_on='properties.name',
             legend_name="지역구별 커피 업종 수"
            )
m

bins를 만들어 1/4, 2/4, 3/4분위수별로 구간을 나누어 시각화할 수 있습니다.

bins = list(seoul_group_data.quantile([0, 0.25, 0.5, 0.75, 1]))

m = folium.Map(
    location=[37.559819, 126.963895],
    zoom_start=11, 
    tiles='cartodbpositron'
)

folium.GeoJson(
    seoul_geo,
    name='지역구'
).add_to(m)

m.choropleth(geo_data=seoul_geo,
             data=seoul_group_data, 
             fill_color='YlOrRd', # 색상 변경도 가능하다
             fill_opacity=0.5,
             line_opacity=0.2,
             key_on='properties.name',
             legend_name="지역구별 커피 업종 수", 
             bins=bins
            )
m

Html 파일로 저장

저장은 save() 함수로 쉽게 html 파일로 저장할 수 있습니다.

 

출처

728x90

'PYTHON > 데이터분석' 카테고리의 다른 글

[Python] 선형회귀  (2) 2022.12.26
인공지능 개념  (0) 2022.12.26
[Python] plot 한글 사용  (0) 2022.12.26
[Python] 스파크(spark)  (0) 2022.12.26
[Python] 판다스(pandas)  (0) 2022.12.26
Comments