티스토리 뷰
이번 블로그는 파이썬으로 데이터주무르기 책의 chapter2.서울시 범죄 현황 분석에 대한 내용을 참조하여 서울 맛집 리스트를 지도에 나타내보는 것이다.
- 출처 : 파이썬으로 데이터 주무르기(ch.2 서울시 범죄 현황 분석)
나는 평소 술을 좋아하기 때문에 맛집에 대한 관심이 많았다. 매번 인터넷이나 지도 어플로 검색을 하다가 내가 공부한 것을 바탕으로 맛집 리스트를 지도에 표시해 보고 싶었다. 우선 내가 살고 있는 곳이 현재 서울이기 때문에 서울 지역 맛집만 지도에 표시해 보려고 한다.
Step 1. 필요한 라이브러리 import 및 api setting¶
우선 필요한 라이브러리를 불러와보자. 그 다음으로 위치에 대한 검색 결과 중 주소와 위도, 경도 정보를 제공하는 서비스가 구글에 있다. 바로 google maps api 이다. 그중에 주소 검색과 위도, 경도 정보 정도를 얻을 수 있는 Geocoding API를 선택하여 키 가져오기로 키를 가져오자.
# 라이브러리 로드
import os
import json
import csv
import requests
import folium
import googlemaps
import pandas as pd
import xlrd
import openpyxl
import seaborn as sns
import numpy as np
import platform
%matplotlib notebook
import matplotlib.pyplot as plt
import warnings
from matplotlib import font_manager, rc
# 주피터 에러 메시지 제거
warnings.filterwarnings(action='ignore')
# Plot 한글 지원
plt.rcParams['axes.unicode_minus'] = False
if platform.system() == 'Darwin':
rc('font', family='AppleGothic')
elif platform.system() == 'Windows':
path = "c:/Windows/Fonts/malgun.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
rc('font', family=font_name)
elif platform.system() == 'Linux':
path = "/usr/share/fonts/NanumGothic.ttf"
font_name = font_manager.FontProperties(fname=path).get_name()
plt.rc('font', family=font_name)
else:
print('Unknown system... sorry~~~~')
# Google map api key setting
gmaps_key = "받은 api key 입력"
gmaps = googlemaps.Client(key=gmaps_key)
Step 2. 데이터 불러오기¶
2-1. 맛집 데이터¶
인터넷에서 떠도는 전국_맛집_취합종합본 엑셀 파일을 내려 받아 read_excel로 데이터를 불러오자. 최근 버전에서 xlsx 파일을 열려면 engine='openpyxl'을 입력해야 오류가 나지 않고 xlsx 데이터를 불러올 수 있으니 참고하자. 내려받은 엑셀 파일에는 지역 별로 맛집 sheet가 나뉘어 정리되어 있다. 이번 시간엔 서울특별시만 지도에 표시해보자.
seoul_df = pd.read_excel("전국_맛집_취합종합본.xlsx",'서울특별시', engine = 'openpyxl')
seoul_df.head()
Unnamed: 0 | Unnamed: 1 | Unnamed: 2 | Unnamed: 3 | Unnamed: 4 | Unnamed: 5 | Unnamed: 6 | |
---|---|---|---|---|---|---|---|
0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | 지역 | 도시명 | 음식종류 | 대표메뉴 | 식당상호 | 포털 검색명 | 추천사유 |
4 | 서울특별시 | 강남구 | 육류 | 양꼬치 | 짱수양꼬치 | 수서동 양꼬치 | 양꼬치 집과 쌀국수집이 같이 붙어 있어 쌀국수도 같이 즐길 수 있어서 좋았어요 |
총 106개의 행과 7개의 columns로 이루어져 있는 걸 볼 수 있다. 컬럼명은 수정이 필요해 보이고 0 ~ 3 행 까지의 데이터는 필요가 없어보인다. 다음 단계에서 데이터 전처리를 해보자.
2-2. 서울시 주민등록 인구 통계¶
이번에는 서울열린데이터 광장에서 서울시 주민등록 인구에 대한 데이터를 불러오자. 이는 맛집과 지역에 따른 상관관계를 알아보기 위함이다.
- 서울열린데이터 광장: 서울시 주민등록인구 (구별) 통계
cencus_seoul = pd.read_csv('cencus_seoul.txt', sep='\t',thousands = ',')
cencus_seoul.head()
기간 | 자치구 | 세대 | 계 | 남자 | 여자 | 계.1 | 남자.1 | 여자.1 | 계.2 | 남자.2 | 여자.2 | 세대당인구 | 65세이상고령자 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2021.1/4 | 합계 | 4400403 | 9828094 | 4774856 | 5053238 | 9598484 | 4667524 | 4930960 | 229610 | 107332 | 122278 | 2.18 | 1567819 |
1 | 2021.1/4 | 종로구 | 74113 | 156567 | 75772 | 80795 | 147296 | 71644 | 75652 | 9271 | 4128 | 5143 | 1.99 | 27781 |
2 | 2021.1/4 | 중구 | 63746 | 133708 | 65312 | 68396 | 124552 | 60851 | 63701 | 9156 | 4461 | 4695 | 1.95 | 24767 |
3 | 2021.1/4 | 용산구 | 112881 | 243336 | 118120 | 125216 | 229013 | 110181 | 118832 | 14323 | 7939 | 6384 | 2.03 | 39575 |
4 | 2021.1/4 | 성동구 | 135883 | 298421 | 145358 | 153063 | 291906 | 142506 | 149400 | 6515 | 2852 | 3663 | 2.15 | 45968 |
이중에서 자치구, 계 컬럼만 가져오고 합계는 제거 하도록 한다.
cencus_seoul_gu = cencus_seoul.drop(['기간','세대','남자','여자','계.1','남자.1','여자.1','계.2',
'남자.2','여자.2','세대당인구','65세이상고령자'], axis=1)
cencus_seoul_gu = cencus_seoul_gu.drop(cencus_seoul.index[0])
cencus_seoul_gu.rename(columns = {'자치구':'도시명',
'계':'인구수'}, inplace=True)
cencus_seoul_gu.head()
도시명 | 인구수 | |
---|---|---|
1 | 종로구 | 156567 |
2 | 중구 | 133708 |
3 | 용산구 | 243336 |
4 | 성동구 | 298421 |
5 | 광진구 | 356191 |
Step 3. 데이터 전처리하기¶
3-2. 맛집 데이터 전처리¶
먼저 unnamed로 되어있는 컬럼명을 지정해보자. rename()으로 아래 코드와 같이 입력하면 출력결과에서 깔끔하게 바뀐 컬럼명이 나오게 된다.
seoul_df.rename(columns = {'Unnamed: 0':'지역',
'Unnamed: 1':'도시명',
'Unnamed: 2':'음식종류',
'Unnamed: 3':'대표 메뉴',
'Unnamed: 4':'식당상호',
'Unnamed: 5':'포털 검색명',
'Unnamed: 6':'추천사유'}, inplace=True)
seoul_df.head()
지역 | 도시명 | 음식종류 | 대표 메뉴 | 식당상호 | 포털 검색명 | 추천사유 | |
---|---|---|---|---|---|---|---|
0 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
1 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
2 | NaN | NaN | NaN | NaN | NaN | NaN | NaN |
3 | 지역 | 도시명 | 음식종류 | 대표메뉴 | 식당상호 | 포털 검색명 | 추천사유 |
4 | 서울특별시 | 강남구 | 육류 | 양꼬치 | 짱수양꼬치 | 수서동 양꼬치 | 양꼬치 집과 쌀국수집이 같이 붙어 있어 쌀국수도 같이 즐길 수 있어서 좋았어요 |
그리고 첫 행부터 3번째 행 까지는 Nan 이기 때문에 drop() 메서드를 이용하여 제거해 주면 아래와 같은 출력을 볼 수 있다.
seoul_df_raw = seoul_df.drop([0,1,2,3])
seoul_df_raw.head()
지역 | 도시명 | 음식종류 | 대표 메뉴 | 식당상호 | 포털 검색명 | 추천사유 | |
---|---|---|---|---|---|---|---|
4 | 서울특별시 | 강남구 | 육류 | 양꼬치 | 짱수양꼬치 | 수서동 양꼬치 | 양꼬치 집과 쌀국수집이 같이 붙어 있어 쌀국수도 같이 즐길 수 있어서 좋았어요 |
5 | 서울특별시 | 강남구 | 일식 | 돈까스 | 구찌라꾸 | 구찌라꾸 | 일본식 돈까스 맛집 까스류 뿐아니라 우동 덥밥 등 모든메뉴가 일품 |
6 | 서울특별시 | 강남구 | 중식 | 낙지짬뽕 | 안사부 | 안사부짬뽕 | 비주얼과 맛 모두 최고, 내용물을 보면 가성비 최고. 양과 재료, 맛과 비주얼 모두... |
7 | 서울특별시 | 강남구 | 한식 | 순대국 | 농민백암순대 | 농민백암순대 | 수요미식회에 소개된, 국물이 진하고 건더기가 가득한~!! 순대국 갑오브갑 |
8 | 서울특별시 | 강남구 | 한식 | 족발 | 뽕족 | 뽕족 | 비교불가한 부드러움이 특징인 족발로, 부드러운 족발을 좋아하신다면, 뽕족~!! |
이제는 음식 종류를 딕셔너리를 사용해서 한식/분식/일식/양식/중식으로 추가 전처리를 진행하자.
seoul_df_raw_dic = {
"가정식백반":"한식",
"고기":"한식",
"곱창":"한식",
"곱창,닭발":"한식",
"닭갈비":"한식",
"도가니":"한식",
"떡복이,튀김,핫도그":"분식",
"분식포차":"분식",
"삼겹살":"한식",
"쌈싸먹는 김치찌게, 삽겹살":"한식",
"아구찜, 닭볶음탕":"한식",
"양식":"양식",
"요리주점":"한식",
"육류":"한식",
"일본식 꼬치구이":"일식",
"일식":"일식",
"일식집":"일식",
"족발":"한식",
"중식":"중식",
"짬뽕":"중식",
"쭈꾸미/삼겹살":"한식",
"토소음식":"한식",
"퓨전한식":"한식",
"한식":"한식",
"한식/술집":"한식",
"해산물":"일식",
"해장국,찌개,불고기백반":"한식"
}
이렇게 만들어 놓은 딕셔너리를 새로 '음식종류_dic'컬럼을 만들어 넣어주자.
a = []
for i in range(4,106):
a.append(seoul_df_raw_dic[seoul_df_raw['음식종류'][i]])
seoul_df_raw['음식종류_dic'] = a
seoul_df_raw.head()
지역 | 도시명 | 음식종류 | 대표 메뉴 | 식당상호 | 포털 검색명 | 추천사유 | 음식종류_dic | |
---|---|---|---|---|---|---|---|---|
4 | 서울특별시 | 강남구 | 육류 | 양꼬치 | 짱수양꼬치 | 수서동 양꼬치 | 양꼬치 집과 쌀국수집이 같이 붙어 있어 쌀국수도 같이 즐길 수 있어서 좋았어요 | 한식 |
5 | 서울특별시 | 강남구 | 일식 | 돈까스 | 구찌라꾸 | 구찌라꾸 | 일본식 돈까스 맛집 까스류 뿐아니라 우동 덥밥 등 모든메뉴가 일품 | 일식 |
6 | 서울특별시 | 강남구 | 중식 | 낙지짬뽕 | 안사부 | 안사부짬뽕 | 비주얼과 맛 모두 최고, 내용물을 보면 가성비 최고. 양과 재료, 맛과 비주얼 모두... | 중식 |
7 | 서울특별시 | 강남구 | 한식 | 순대국 | 농민백암순대 | 농민백암순대 | 수요미식회에 소개된, 국물이 진하고 건더기가 가득한~!! 순대국 갑오브갑 | 한식 |
8 | 서울특별시 | 강남구 | 한식 | 족발 | 뽕족 | 뽕족 | 비교불가한 부드러움이 특징인 족발로, 부드러운 족발을 좋아하신다면, 뽕족~!! | 한식 |
맛집 지역 & 인구수 데이터 전처리¶
추가로 지역별 맛집 개수와, 인구수의 데이터를 합쳐 보자
seoul_df_area = seoul_df_raw.groupby('도시명').count()[['지역']]
seoul_df_area = seoul_df_area.reset_index()
seoul_df_area.head()
도시명 | 지역 | |
---|---|---|
0 | 강남구 | 10 |
1 | 강동구 | 4 |
2 | 강북구 | 3 |
3 | 강서구 | 6 |
4 | 광진구 | 6 |
cencus_seoul_gu.head()
도시명 | 인구수 | |
---|---|---|
1 | 종로구 | 156567 |
2 | 중구 | 133708 |
3 | 용산구 | 243336 |
4 | 성동구 | 298421 |
5 | 광진구 | 356191 |
seoul_merged_df = pd.merge(seoul_df_area, cencus_seoul_gu)
seoul_merged_df
도시명 | 지역 | 인구수 | |
---|---|---|---|
0 | 강남구 | 10 | 539538 |
1 | 강동구 | 4 | 468815 |
2 | 강북구 | 3 | 307537 |
3 | 강서구 | 6 | 582804 |
4 | 광진구 | 6 | 356191 |
5 | 구로구 | 11 | 426675 |
6 | 금천구 | 2 | 244564 |
7 | 노원구 | 1 | 522225 |
8 | 동대문구 | 1 | 352570 |
9 | 동작구 | 4 | 398205 |
10 | 마포구 | 1 | 378216 |
11 | 서초구 | 6 | 425103 |
12 | 성동구 | 2 | 298421 |
13 | 성북구 | 2 | 444295 |
14 | 송파구 | 1 | 667115 |
15 | 양천구 | 12 | 456019 |
16 | 영등포구 | 12 | 403070 |
17 | 용산구 | 1 | 243336 |
18 | 은평구 | 3 | 479607 |
19 | 종로구 | 3 | 156567 |
20 | 중구 | 1 | 133708 |
21 | 중랑구 | 5 | 396807 |
Step 4. Google Maps를 이용해서 주소와 위도, 경도 정보 얻기¶
google maps 를 사용해서 데이터에 있는 첫 번째 행이 식당상호 '짱수양꼬치'라는 단어를 검색해 보자. 그러면 formatted_address 항목에 주소가 나온다. lat와 lng에서 위도 경도 정보도 확인해 볼 수 있다. 아래에서 해볼 지도 시각화에서 유용하게 사용할 수 있는 정보이다.
tasty_name = []
for name in seoul_df_raw["식당상호"]:
tasty_name.append('서울'+ str(name))
그런데 위의 코드 결과를 보면 식당 상호만 나와있다. 그러면 구글 검색에서 주소가 제대로 나오지 않아 위 코드처럼 서울 ** 로 만들어야 한다. 아래 코드를 입력하여 출력하면 서울 다음에 맛집 상호명이 나온다.
tasty_addreess = []
tasty_lat = []
tasty_lng = []
for name in tasty_name:
tmp = gmaps.geocode(name, language='ko')
tasty_addreess.append(tmp[0].get("formatted_address"))
tmp_loc = tmp[0].get("geometry")
tasty_lat.append(tmp_loc['location']['lat'])
tasty_lng.append(tmp_loc['location']['lng'])
이제 위 코드처럼 google maps 코드에 만들어둔 이름을 이용해서 주소를 받아오자. 앞서 짱수양꼬치라는 맛집의 google maps의 출력결과에formatted_address를 get()함수를 이용해 주소를 받아오면 된다. 위의 결과를 얻었으니 각 맛집별 주소를 모두 얻었다.
seoul_df_raw['lat'] = tasty_lat
seoul_df_raw['lng'] = tasty_lng
위도와 경도를 얻었으니 seoul_df_raw에 위 코드를 입력해 넣어 출력하면 아래와 같은 결과를 얻을 수 있다.
seoul_df_raw.head()
지역 | 도시명 | 음식종류 | 대표 메뉴 | 식당상호 | 포털 검색명 | 추천사유 | 음식종류_dic | lat | lng | |
---|---|---|---|---|---|---|---|---|---|---|
4 | 서울특별시 | 강남구 | 육류 | 양꼬치 | 짱수양꼬치 | 수서동 양꼬치 | 양꼬치 집과 쌀국수집이 같이 붙어 있어 쌀국수도 같이 즐길 수 있어서 좋았어요 | 한식 | 37.485499 | 127.030815 |
5 | 서울특별시 | 강남구 | 일식 | 돈까스 | 구찌라꾸 | 구찌라꾸 | 일본식 돈까스 맛집 까스류 뿐아니라 우동 덥밥 등 모든메뉴가 일품 | 일식 | 37.487269 | 127.103126 |
6 | 서울특별시 | 강남구 | 중식 | 낙지짬뽕 | 안사부 | 안사부짬뽕 | 비주얼과 맛 모두 최고, 내용물을 보면 가성비 최고. 양과 재료, 맛과 비주얼 모두... | 중식 | 37.496673 | 127.031900 |
7 | 서울특별시 | 강남구 | 한식 | 순대국 | 농민백암순대 | 농민백암순대 | 수요미식회에 소개된, 국물이 진하고 건더기가 가득한~!! 순대국 갑오브갑 | 한식 | 37.503706 | 127.053037 |
8 | 서울특별시 | 강남구 | 한식 | 족발 | 뽕족 | 뽕족 | 비교불가한 부드러움이 특징인 족발로, 부드러운 족발을 좋아하신다면, 뽕족~!! | 한식 | 37.503170 | 127.052030 |
Step 5. Folium을 이용해 지도 시각화 하기¶
이제 위도와 경도를 다 얻었으니 Folium을 이용해 지도를 시각화해 보자. 많은 지도 시각화 도구가 있지만 여기서는 Folium 라이브러리를 다루도록 하자.
먼저 맛집의 위치를 확인한다. 그리고 Marker를 이용해서 맛집의 위치를 표시해 준다. tootip을 이용해 아래 코드를 입력하면 Marker 위에 맛집에 대한 정보가 나온다.
map = folium.Map(location=[37.5502, 126.982], zoom_start=11)
for n in seoul_df_raw.index:
folium.Marker(
[seoul_df_raw['lat'][n],seoul_df_raw['lng'][n]],
radius = 10,
color='#3186cc',
fill_color='#3186cc',
fill=True,
tooltip = ('<b>- 지역</b>: ' + seoul_df_raw['지역'][n] + " " + seoul_df_raw['도시명'][n] + '<br>' +
'<b>- 상호명</b>: ' + seoul_df_raw['식당상호'][n] + '<br>' +
'<b>- 대표메뉴</b>: ' + seoul_df_raw['대표 메뉴'][n] + '<br>' +
'<b>- 추천사유</b>: ' + seoul_df_raw['추천사유'][n])
).add_to(map)
map
Step.6 그래프를 이용해 서울 맛집의 인기 종류와 맛집 도시 분포 시각화해보기¶
이번엔 서울 맛집의 인기 있는 종류가 어떤 것인지 그래프로 표현해보려 한다.
서울시 음식종류에 따른 맛집 리스트¶
서울시의 음식종류에 따른 맛집 리스트를 파악해보자. 예상했던 바와 같이 한식이 86개로 가장 많은 비율을 차지하고 있다.
ordered_index = seoul_df_raw.value_counts('음식종류_dic').index
plt.figure(figsize=(10, 8))
ax1 = sns.countplot(x='음식종류_dic',data=seoul_df_raw, order=ordered_index)
plt.title('음식에 따른 서울시 맛집 Bar plot')
plt.xlabel('음식종류')
for p in ax1.patches:
ax1.annotate('{:.0f}개'.format(p.get_height()), (p.get_x() + 0.33, p.get_height() + 0.5))
서울시 도시별 맛집 수¶
이번에는 도시별 맛집 수를 파악해 보았다. 우선 영등포와 양천구와 구로구를 보면 회사들이 많아 사람들이 맛집리스트에 상위에 올리는 것을 확인할 수 있었다. 즉, 맛집은 밀집되어 있는 회사에 영향을 많이 받는 것을 볼 수 있다. 또한 강남구 역시 상위권을 차지하는데 이 역시 인구가 많아서 맛집 리스트에 오르기 쉬웠던 것으로 판단된다.
ordered_index = seoul_df_raw.value_counts('도시명').index
plt.figure(figsize=(10,8))
ax = sns.countplot(y="도시명", data=seoul_df_raw, order=ordered_index)
plt.title('도시별 맛집 수')
plt.xlabel('맛집수')
plt.ylabel('도시')
for p in ax.patches:
ax.annotate('{:.0f}개'.format(p.get_width()), (p.get_width(),p.get_y() + 0.5))
맛집과 인구수의 상관관계¶
이번에는 맛집과 인구수의 상관관계를 파악해보자. 인구수와 지역을 merge 시킨 데이터를 통해 다음과 같은 상관계수를 뽑아 보았다. 이를 해석해보니 강남구는 인구수에 비해 맛집이 많은 것으로 나왔으며 이는 거주인구대비 유동인구가 많음을 나타낸다. 그리고 동대문구와 노원구는 인구수 대비 맛집이 없는 것으로 판단되는데 이는 거주하는 인원에 비해 유동인구가 상당히 낮음을 알 수 있다. 이를 좀 더 확장시켜서 사고를 넓혀가보면 동대문구와 노원구는 조금 더 고령화가 되어 젊은 층이 맛집에 대한 데이터를 입력하기가 어려울 수 있다고 생각이 된다.
fp1 = np.polyfit(seoul_merged_df['인구수'], seoul_merged_df['지역'],1)
fp1
f1 = np.poly1d(fp1)
fx = np.linspace(100000, 700000, 100)
seoul_merged_df.set_index('도시명', inplace=True)
plt.figure(figsize=(10,8))
plt.scatter(seoul_merged_df['인구수'], seoul_merged_df['지역'], s=50)
plt.plot(fx, f1(fx), ls='dashed', lw=3, color='g')
for n in range(10):
plt.text(seoul_merged_df['인구수'][n]*1.02, seoul_merged_df['지역'][n]*0.98,
seoul_merged_df.index[n], fontsize=15)
plt.title("맛집의 인구수와 지역에 따른 상관 관계")
plt.xlabel('인구수')
plt.ylabel('지역')
plt.colorbar()
plt.grid()
plt.show()
총평¶
맛집 다니기가 취미어서, 서울에 맛집이 어디어디에 분포하는지 시각화를 해보았다. 추가로 해당 맛집의 지역이 인구에 어떤 영향을 끼치는지 알아보기 위해 인구 데이터도 추가하여 분석을 하게 되었다. 최초의 유동인구와 맛집의 상관관계를 고려하여 유동인구 데이터를 구하려고 했으나 유동인구 데이터 자체가 쉽게 구할 수 없어서 서울시 열린데이터 광장에서 주민등록상의 인구로 분석을 해보았다. 주민등록상의 인구와 맛집 수로 파악을 해본 결과 유동인구 데이터가 없어도 각 지역에 유동인구의 흐름을 어느정도는 볼 수 있었다.