"버거 지수"는 진짜 도시의 발전 수준을 반영할까?

by Hyeshik on Dec 26, 2014

2015년 1월 26일 1차 개정

밐폭도 @Godtsune_miku

"한 도시의 발전 수준은 (버거킹의 개수+맥도날드의 개수+KFC의 개수)/롯데리아의 개수를 계산하여 높게 나올수록 더 발전된 도시라고 할 수 있다"

https://twitter.com/godtsune_miku/status/513648274406789120

사람이 적은 곳일 수록 롯데리아의 개수가 많은 것 같긴 한데.. 진짜로 그럴려나?

자 그럼..

$\xi=\frac {B+M+K} {L}$ 을 계산해 보자. 시군구 단위로. ($\xi$는 버거의 옆모습을 닮아서..ㅋ)

일단 준비~

In [1]:
import urllib.request
import json
import pandas as pd
import bs4

버거킹 매장 데이터를 받아와서 분석한다.

아주 쓰기 좋게 JSON으로 딱 제공해 준다. 만세 'ㅁ'/

In [2]:
response = urllib.request.urlopen('http://www.burgerking.co.kr/api/store/searchmap/empty/?areacd=')
bgk_data = json.loads(response.read().decode('utf-8'))
bgk_tbl = pd.DataFrame(bgk_data)
bgk_tbl.head()
Out[2]:
AllHour CloseCleaning ClosePeakSeason CloseWeekday Delivery DriveThrough Morning NewAddr NewAddr2 OpenTime PhoneNumber PointX PointY StoreNM StoreSQ
0 N 22:00 N N N 서울특별시 금천구 가산디지털1로 168 우림라이온스밸리 A동 9:00 02-853-0332 37.479965 126.882637 가산디지털점 1
1 N / 금, 토: 11:00~24:00 22:30 N N N 서울특별시 금천구 디지털로 10길 9 현대아울렛 6층 11:00 02-2136-9962 37.477620 126.889053 가산현대아울렛점 2
2 N 23:30 N N N 서울특별시 강서구 양천로 559 이마트3층 (가양동) 10:00 02-3664-0221 37.558193 126.861816 가양이마트점 3
3 Y 셋째주 월요일 02:00~08:00 Close N N Y 서울특별시 서초구 잠원로 69 02-595-9042 37.509400 127.007309 강남NC점 11
4 Y 둘째주 월요일 02:00~08:00 Close N N Y 서울특별시 서초구 사평대로 371 영풍빌딩1층 (반포동) 02-517-0236 37.504570 127.023590 강남교보점 4

다른 건 그닥 필요 없고, 주소만 구단위까지 추린다.

In [3]:
bgk_locs = pd.DataFrame(bgk_tbl['NewAddr'].apply(lambda v: v.split()[:2]).tolist(),
                        columns=('d1', 'd2'))
bgk_locs.head()
Out[3]:
d1 d2
0 서울특별시 금천구
1 서울특별시 금천구
2 서울특별시 강서구
3 서울특별시 서초구
4 서울특별시 서초구

광역단체 이름이 제대로 돼 있나 확인.

In [4]:
bgk_locs['d1'].unique()
Out[4]:
array(['서울특별시', '서울시', '경기도', '수원시', '인천광역시', '강원도', '충청남도', '충남', '대전광역시',
       '충청북도', '부산광역시', '울산광역시', '대구광역시', '경북', '경상북도', '경남', '경상남도',
       '전라남도', '전남', '광주광역시', '광주시', '전라북도', '전북', '제주특별자치도'], dtype=object)

줄여 쓴 이름들은 풀어 써준다.

In [5]:
d1_aliases = """서울시:서울특별시 충남:충청남도 강원:강원도 경기:경기도 충북:충청북도 경남:경상남도 경북:경상북도
전남:전라남도 전북:전라북도 제주도:제주특별자치도 제주:제주특별자치도 대전시:대전광역시 대구시:대구광역시 인천시:인천광역시
광주시:광주광역시 울산시:울산광역시"""
d1_aliases = dict(aliasset.split(':') for aliasset in d1_aliases.split())
bgk_locs['d1'] = bgk_locs['d1'].apply(lambda v: d1_aliases.get(v, v))
In [6]:
bgk_locs['d1'].unique()
Out[6]:
array(['서울특별시', '경기도', '수원시', '인천광역시', '강원도', '충청남도', '대전광역시', '충청북도',
       '부산광역시', '울산광역시', '대구광역시', '경상북도', '경상남도', '전라남도', '광주광역시', '전라북도',
       '제주특별자치도'], dtype=object)

..! 수원시가 있다. =.= 고치자..

In [7]:
bgk_locs[bgk_locs['d1'] == '수원시']
Out[7]:
d1 d2
101 수원시 영통구
In [8]:
bgk_locs.iloc[101] = ['경기도', '수원시']

시군구 단위는 제대로 돼 있나 보자.

In [9]:
bgk_locs['d2'].unique()
Out[9]:
array(['금천구', '강서구', '서초구', '강남구', '광진구', '관악구', '구로구', '용산구', '동작구',
       '영등포구', '중구', '강동구', '양천구', '송파구', '성북구', '은평구', '마포구', '서대문구',
       '도봉구', '종로구', '중랑구', '동대문구', '과천시', '광명시', '광주시', '파주시', '부천시',
       '성남시', '군포시', '수원시', '용인시', '안산시', '여주군', '오산시', '의정부시', '이천시',
       '고양시', '안양시', '하남시', '남구', '부평구', '연수구', '계양구', '홍천군', '원주시', '당진시',
       '아산시', '천안시', '서구', '유성구', '청주시', '청원군', '동구', '부산진구', '해운대구',
       '수성구', '달서구', '북구', '상주시', '경산시', '구미시', '안동시', '포항시', '거제시', '창원시',
       '김해시', '진주시', '순천시', '여수시', '무안군', '광산구', '군산시', '익산시', '전주시', '제주시'], dtype=object)

오오케이. 대충 잘 돼 있군~

In [10]:
B = bgk_locs.apply(lambda r: r['d1'] + ' ' + r['d2'], axis=1).value_counts()
B.head()
Out[10]:
서울특별시 강남구    20
서울특별시 서초구    10
서울특별시 송파구     7
경기도 수원시       7
경기도 성남시       7
dtype: int64

B 계산이 끝났다.

맥도날드 매장 데이터를 받아와서 분석한다.

맥도날드는 JSON은 따로 없고 그냥 HTML 파싱을 해야한다. 여러 번 검색한 것을 합치면 겹칠 수 있으니까 점포 이름도 같이 모아서 중첩 검사를 한다.

In [11]:
MCDONALDS_URL = 'http://www.mcdonalds.co.kr/www/kor/findus/district.do?sSearch_yn=Y&skey=2&pageIndex={page}&skeyword={location}'
In [12]:
def search_mcdonalds_stores_one_page(location, page):
    response = urllib.request.urlopen(
        MCDONALDS_URL.format(location=urllib.parse.quote(location.encode('utf-8')), page=page))
    mcd_data = response.read().decode('utf-8')
    soup = bs4.BeautifulSoup(mcd_data)
    
    ret = []
    for storetag in soup.findAll('dl', attrs={'class': 'clearFix'}):
        storename = storetag.findAll('a')[0].contents[-1].strip()
        storeaddr = storetag.findAll('dd', attrs={'class': 'road'})[0].contents[0].split(']')[1]
        storeaddr_district = storeaddr.split()[:2]
        ret.append([storename] + storeaddr_district)

    return pd.DataFrame(ret, columns=('store', 'd1', 'd2')) if ret else None

# 여러 페이지를 쭉 찾아서 안 나올 때 까지 합친다.
def search_mcdonalds_stores(location):
    from itertools import count
    
    found = []
    for pg in count():
        foundinpage = search_mcdonalds_stores_one_page(location, pg+1)
        if foundinpage is None:
            break
        found.append(foundinpage)

    return pd.concat(found)
In [13]:
search_mcdonalds_stores('전라북도').head()
Out[13]:
store d1 d2
0 군산 나운DT점 전라북도 군산시
1 이마트 군산점 전라북도 군산시
2 익산영등DT점 전라북도 익산시
3 익산원광대점 전라북도 익산시
4 전주덕진DT점 전라북도 전주시

이제 전체 지역에 대해서 찾아서 모두 합침. 시도명은 버거킹 목록을 이용.

In [14]:
found = []
for distr in bgk_locs['d1'].unique():
    found.append(search_mcdonalds_stores(distr))
mcd_tbl = pd.concat(found)
In [16]:
mcd_tbl['store'].value_counts().head()
Out[16]:
양주휴게소DT     2
가산디지털점      1
대구희망DT점     1
대동점         1
양재SK DT점    1
dtype: int64

양주휴게소DT가 두 번 들어가 있다. (지도 확인해 보면 완전히 같은 위치에 있음) 제거하자.!

In [17]:
mcd_tbl = mcd_tbl.drop_duplicates(subset=['store'])
In [18]:
M = mcd_tbl.apply(lambda r: r['d1'] + ' ' + r['d2'], axis=1).value_counts()
M.head()
Out[18]:
경기도 수원시      13
경상남도 창원시     12
서울특별시 강남구    12
대구광역시 달서구     9
경기도 고양시       9
dtype: int64

오! 버거킹과 달리 맥도날드는 수원이 가장 많다. 창원도 눈에 띄게 많다.

KFC 매장 데이터를 받아와서 분석한다.

KFC는 시도를 먼저 선택하고 군구를 선택해서 검색한다.

In [19]:
kfc_dists = "강원 경기 경남 경북 광주 대구 대전 부산 서울 울산 인천 전남 전북 제주 충남 충북".split()
In [20]:
KFC_DISTSEARCH_URL = 'http://www.kfckorea.com/store/store_addr_search.asp?addr_div=gugun&sido={location}'

def kfc_search_subdists(location):
    response = urllib.request.urlopen(
        KFC_DISTSEARCH_URL.format(location=urllib.parse.quote(location.encode('utf-8'))))
    kfc_data = response.read().decode('utf-8')
    soup = bs4.BeautifulSoup(kfc_data)
    return list(filter(None, [tag.attrs['value'] for tag in soup.findAll('option')]))
In [21]:
kfc_alldist = [(d, subd) for d in kfc_dists for subd in kfc_search_subdists(d)]
kfc_alldist[:5], len(kfc_alldist)
Out[21]:
([('강원', '강릉시'), ('강원', '고성군'), ('강원', '동해시'), ('강원', '삼척시'), ('강원', '속초시')],
 251)

다행히도 그 다음 검색은 JSON 리턴이다! 'o'/

In [22]:
KFC_STORESEARCH_URL = ('http://www.kfckorea.com/store/store_search.asp?sales_24_yn_=&'
                       'sales_wifi_yn_=&sales_order_group_yn_=&sales_park_yn_=&sales_subway_yn_=&'
                       'sales_mart_in_yn_=&searchFlag=0&addr_div1={div1}&addr_div2={div2}&keyword=')

def kfc_search_stores_in_dist(d1, d2):
    response = urllib.request.urlopen(
        KFC_STORESEARCH_URL.format(div1=urllib.parse.quote(d1.encode('utf-8')),
                                   div2=urllib.parse.quote(d2.encode('utf-8'))))
    return json.loads(response.read().decode('utf-8'))['store']
In [23]:
found = []
for d1, d2 in kfc_alldist:
    found.extend(kfc_search_stores_in_dist(d1, d2))
kfc_tbl = pd.DataFrame(found)
In [24]:
kfc_tbl.head()
Out[24]:
addr_div1 addr_div2 clean_day map_url1 map_url2 new_addr1 old_addr1 phone post_no sales_time shop_code shop_location shop_name shop_seq shop_use_div use_area
0 강원 강원 37.76419798981757 128.87763186572403 강원도 강릉시 솔올로 25 현진빌딩 1층 (교동) 강원도 강릉시 교동 1882-3 현진빌딩 1층 033)642-6332 210-100 10:00 ~ 22:00 1410723 교동주공 3단지 308동 옆 강릉교동 162 Y A
1 강원 강원 37.52217245343172 129.11533830315352 강원도 동해시 한섬로 111-7, 현진롯데시네마타운107호~108호 (천곡동) 강원 동해시 천곡동 863번지 현진롯데시네마타운 107호~108호 033)531-4956 240-812 10:00 ~ 21:30 1410680 시청로터리 롯데시네마 1층 동해 8 Y A
2 강원 강원 10:00~22:00 37.2039550 128.8370300 강원도 고한읍 고한리 산289-6 하이원리조트 밸리스키 하우스 강원 정선군 고한읍 고한리 산289-6 하이원리조트 밸리스키 하우스 3층 033)591-9749 233-901 10:00~22:00 (11월 23 일 개점, 겨울시즌 영업중) 1410696 하이원리조트 벨리스키하우스 3층 하이원리조트 29 Y A
3 강원 강원 37.863814444176135 127.71834104991627 강원도 춘천시 경춘로 2341 이마트 춘천점 1층 푸드코트 內 강원도 춘천시 온의동 511번지 이마트 춘천점 1층 푸드코트 內 033)252-3369 200-938 10:00~22:00 1410714 이마트 춘천점 1층 푸드코트 내 춘천이마트 152 Y A
4 강원 강원 37.87899033651872 127.72646272309694 강원도 춘천시 중앙로67번길 4 (중앙로2가) 강원 춘천시 중앙로2가 31 033)243-9646 200-042 (주중) 11:00~21:00 / (주말) 10:00~22:00 1410621 명동 닭갈비골목 인근 국민은행 맞은편 춘천1 95 Y A

간단히 완성! 이제 시군구까지 점포 수 센다.

In [26]:
kfc_locs = pd.DataFrame(kfc_tbl['old_addr1'].apply(
    lambda v: v.replace(' ', ' ').replace(' ', ' ').replace('광주 광역', '광주광역').split()[:2]).tolist(),
              columns=('d1', 'd2'))
kfc_locs['d1'].unique()
Out[26]:
array(['강원도', '강원', '경기', '경기도', '경남', '경북', '광주광역시', '광주', '대구광역시', '대구',
       '대전시', '대전', '대전광역시', '부산시', '부산광역시', '부산', '서울특별시', '서울시', '서울',
       '울산', '인천시', '인천', '인천광역시', '전북', '전라북도', '제주', '충남', '충청북도', '충북'], dtype=object)

아.. KFC도 버거킹처럼 줄여 쓴 주소와 풀어 쓴 주소가 섞여있다.

In [28]:
d1_aliases = """서울시:서울특별시 충남:충청남도 강원:강원도 경기:경기도 충북:충청북도 경남:경상남도 경북:경상북도
전남:전라남도 전북:전라북도 제주도:제주특별자치도 제주:제주특별자치도 대전시:대전광역시 대구시:대구광역시 인천시:인천광역시
광주시:광주광역시 울산시:울산광역시 광주:광주광역시 대구:대구광역시 대전:대전광역시 부산:부산광역시 부산시:부산광역시
인천:인천광역시 서울:서울특별시 울산:울산광역시"""
d1_aliases = dict(aliasset.split(':') for aliasset in d1_aliases.split())
kfc_locs['d1'] = kfc_locs['d1'].apply(lambda v: d1_aliases.get(v, v))
kfc_locs['d1'].unique()
Out[28]:
array(['강원도', '경기도', '경상남도', '경상북도', '광주광역시', '대구광역시', '대전광역시', '부산광역시',
       '서울특별시', '울산광역시', '인천광역시', '전라북도', '제주특별자치도', '충청남도', '충청북도'], dtype=object)
In [29]:
kfc_locs['d2'].unique()
Out[29]:
array(['강릉시', '동해시', '정선군', '춘천시', '고양시', '과천시', '광명시', '구리시', '김포시',
       '남양주시', '부천시', '성남시', '수원시', '안산시', '안양시', '오산시', '용인시', '의정부시',
       '이천시', '평택시', '하남시', '화성시', '거제시', '김해시', '양산시', '창원시', '경산시',
       '포항시', '서구', '남구', '달서구', '북구', '수성구', '중구', '유성구', '금정구', '기장군',
       '동래구', '부산진구', '사상구', '해운대구', '강남구', '강동구', '강북구', '강서구', '관악구',
       '광진구', '구로구', '금천구', '노원구', '도봉구', '동대문구', '동작구', '마포구', '서대문구',
       '서초구', '성동구', '성북구', '송파구', '양천구', '영등포구', '용산구', '종로구', '중랑구',
       '계양구', '남동구', '부평구', '연수구', '전주시', '서귀포시', '천안시', '청주시'], dtype=object)

다행히도 시군구는 줄여쓰거나 오타내거나 그런 경우가 없다.

In [30]:
K = kfc_locs.apply(lambda r: r['d1'] + ' ' + r['d2'], axis=1).value_counts()
K.head()
Out[30]:
서울특별시 강남구    10
서울특별시 서초구     7
경기도 수원시       7
경기도 성남시       5
서울특별시 용산구     5
dtype: int64

잠깐 정리..

이제 롯데리아를 해야 하는데.. 계속 하기는 좀 지루하니까 B, M, K만 테이블로 모아 보자.

In [31]:
BMK = pd.DataFrame({'B': B, 'M': M, 'K': K}).fillna(0)
BMK['total'] = BMK.sum(axis=1)
BMK = BMK.sort('total', ascending=False)
BMK.head(10)
Out[31]:
B K M total
서울특별시 강남구 20 10 12 42
경기도 수원시 7 7 13 27
서울특별시 서초구 10 7 7 24
경기도 성남시 7 5 8 20
충청북도 청주시 5 5 8 18
경상남도 창원시 3 3 12 18
경기도 고양시 4 3 9 16
서울특별시 송파구 7 4 5 16
대구광역시 달서구 2 3 9 14
서울특별시 강동구 3 4 7 14
In [32]:
from matplotlib import pyplot as plt
from matplotlib import rcParams, style
style.use('ggplot')
rcParams['font.size'] = 12

일단은 매장 수 비교.

In [33]:
plt.figure(figsize=(4, 3))
BMK.sum(axis=0).iloc[:3].plot(kind='bar')
Out[33]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f2c5a150048>

맥도날드가 많고, 버거킹, KFC는 비슷하다.

In [34]:
import scipy.stats

서로서로 매장수 비교를 해 볼까~

In [35]:
fig = plt.figure(figsize=(9, 3))

def plot_nstores(b1, b2, label1, label2):
    plt.scatter(BMK[b1] + np.random.random(len(BMK)),
                BMK[b2] + np.random.random(len(BMK)),
                edgecolor='none', alpha=0.75, s=6, c='black')
    plt.xlim(-1, 15)
    plt.ylim(-1, 15)
    plt.xlabel(label1)
    plt.ylabel(label2)
    
    r = scipy.stats.pearsonr(BMK[b1], BMK[b2])
    plt.annotate('r={:.3f}'.format(r[0]), (10, 12.5))

ax = fig.add_subplot(1, 3, 1)
plot_nstores('B', 'M', 'Burger King', "McDonald's")

ax = fig.add_subplot(1, 3, 2)
plot_nstores('B', 'K', 'Burger King', 'KFC')

ax = fig.add_subplot(1, 3, 3)
plot_nstores('M', 'K', "McDonald's", 'KFC')

plt.tight_layout()

버거킹과 KFC 매장수가 상당히 연관성이 높아서, 매장 배치 전략이 비슷한 것 같다. 버거킹과 맥도날드는 이 셋중에 가장 다르다. 맥도날드의 DT매장들 배치를 보면 이해가 간다.

이 그림을 보면, 버거킹은 일부 지역에 굉장히 집중하는 것처럼 보이는데, 집중도를 비교해 보자.

In [36]:
plt.figure(figsize=(4, 3))
for col, label in [('B', 'Burger King'), ('K', 'KFC'), ('M', "McDonald's")]:
    cumulv = np.cumsum(sorted(BMK[col], reverse=True)) / BMK[col].sum()
    plt.plot(cumulv, label='{} ({})'.format(label, int(BMK[col].sum())))
plt.legend(loc='best')
plt.xlabel('Number of districts (si/gun/gu)')
plt.ylabel('Cumulative fraction')
Out[36]:
<matplotlib.text.Text at 0x7f2c58190e10>

맥도날드가 가장 매장이 고루 흩어져 있지만, 이건 매장 개수가 많은 것으로 해석할 수도 있어서, 결론을 내리자면 좀 더 자세히 봐야한다. KFC와 버거킹은 매장수가 거의 같으니, 버거킹이 좀 더 일부 시군구에 집중되어 있음을 볼 수 있다.

롯데리아 데이터 가져오기

자.. 그럼 다시 돌아가서 롯데리아로~~

롯데리아는 페이지 크기를 늘리면 한 방에 가져올 수 있다.

In [37]:
LOTTERIA_URL = 'http://www.lotteria.com/Shop/Shop_Ajax.asp'
LOTTERIA_VALUES = {
    'Page': 1, 'PageSize': 2000, 'BlockSize': 2000,
    'SearchArea1': '', 'SearchArea2': '', 'SearchType': "TEXT",
    'SearchText': '', 'SearchIs24H': '', 'SearchIsWifi': '',
    'SearchIsDT': '', 'SearchIsHomeService': '', 'SearchIsGroupOrder': '',
    'SearchIsEvent': ''}
LOTTERIA_HEADERS = {
    'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:12.0) Gecko/20100101',
    'Host': 'www.lotteria.com',
    'Accept': 'text/html, */*; q=0.01',
    'Accept-Language': 'en-us,en;q=0.5',
    'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
    'X-Requested-With': 'XMLHttpRequest',
    'Referer': 'http://www.lotteria.com/Shop/Shop_List.asp?Page=1&PageSize=2000&BlockSize=2000&Se'
               'archArea1=&SearchArea2=&SearchType=TEXT&SearchText=&SearchIs24H=&SearchIsWifi=&Se'
               'archIsDT=&SearchIsHomeService=&SearchIsGroupOrder=&SearchIsEvent=',
}
In [38]:
postdata = urllib.parse.urlencode(LOTTERIA_VALUES).encode('utf-8')
req = urllib.request.Request(LOTTERIA_URL, postdata, LOTTERIA_HEADERS)
response = urllib.request.urlopen(req)
ltr_data = response.read().decode('utf-8')
soup = bs4.BeautifulSoup(ltr_data)
In [44]:
found = []
for tag in soup.findAll('tr', {'class': 'shopSearch'}):
    subtag = [tag.findAll('td', {'style': 'padding-right:10px;'}
                          )[i].contents[0].contents[0]
              for i in (0, 1)]
    found.append([subtag[0]] + subtag[1].replace('광주 광역', '광주광역').split()[:2])
ltr_tbl = pd.DataFrame(found, columns=('storename', 'd1', 'd2'))
ltr_tbl.head()
Out[44]:
storename d1 d2
0 서울대 서울 관악구
1 센텀프라자빌딩 부산 해운대구
2 광주농성D/T 광주 서구
3 김포구래 경기 김포시
4 롯데아울렛동부산1층 부산 기장군
In [45]:
ltr_tbl['d1'].unique()
Out[45]:
array(['서울', '부산', '광주', '경기', '강원', '충북', '전남', '경북', '대구', '세종', '경남',
       '충남', '대전', '울산', '제주', '인천', '전북', '대전시', '광주광역시', '충남시', '청북'], dtype=object)

충남시, 청북 (..) 고치자.

In [46]:
d1_aliases = """강원:강원도 충북:충청북도 부산:부산광역시 경기:경기도 전남:전라남도 경북:경상북도
대구:대구광역시 서울:서울특별시 세종:세종특별자치시 경남:경상남도 충남:충청남도 대전:대전광역시
울산:울산광역시 제주:제주특별자치도 인천:인천광역시 전북:전라북도 광주:광주광역시 대전시:대전광역시
충남시:충청남도 청북:충청북도"""
d1_aliases = dict(aliasset.split(':') for aliasset in d1_aliases.split())
ltr_tbl['d1'] = ltr_tbl['d1'].apply(lambda v: d1_aliases.get(v, v))
ltr_tbl['d1'].unique()
Out[46]:
array(['서울특별시', '부산광역시', '광주광역시', '경기도', '강원도', '충청북도', '전라남도', '경상북도',
       '대구광역시', '세종특별자치시', '경상남도', '충청남도', '대전광역시', '울산광역시', '제주특별자치도',
       '인천광역시', '전라북도'], dtype=object)

자.. 이제 시군구 체크. 제발.. >_<

In [47]:
ltr_tbl['d2'].unique()
Out[47]:
array(['관악구', '해운대구', '서구', '김포시', '기장군', '원주시', '청주시', '가평군', '나주시금천면',
       '안산시', '안동시', '여수시', '장성군장성읍', '수성구', '사상구', '강남구', '봉화군', '광명시',
       '연기면', '하동군', '수원시', '이천시', '금남면', '거제시', '천안시', '광주시', '동구', '중구',
       '성북구', '고양시', '양평군', '제주시', '송파구', '김천시', '고령군', '전주시', '남동구',
       '수영구', '창원시', '양산시', '남양주시', '강릉시', '연제구', '구미시', '고흥군고흥읍', '진주시',
       '용인시', '마포구', '순창군', '영등포구', '부천시', '북구', '성남시', '남구', '김해시', '달서구',
       '논산시연무읍', '사하구', '평택시', '영주시', '임실군임실읍', '기장군정관면', '유성구', '시흥시',
       '익산시', '창녕군남지읍', '서귀포시', '광진구', '용산구', '당진시', '강동구', '영천시', '완주군',
       '순천시', '화성시', '담양군', '부산진구', '함안군', '강북구', '연수구', '음성군', '서산시',
       '부안군', '구로구', '의정부시', '성주군', '은평구', '평창군', '중랑구', '아산시', '화천군',
       '하남시', '문경시', '안양시', '강진군', '경산시', '부여군', '양주시', '성동구', '장흥군',
       '군산시', '달성군', '경주시', '오산시', '대덕구', '포항시', '도봉구', '포천시', '계양구',
       '여주시', '동두천시', '부평구', '동작구', '목포시', '춘천시', '양천구', '종로구', '진구',
       '단양군', '속초시', '남원시', '금천구', '정선군', '동해시', '무안군', '노원구', '과천시',
       '광산구', '철원군', '무주군', '괴산군', '강서구', '파주시', '충주시', '영광군', '서초구',
       '청양군', '동래구', '광양시', '진천군', '아산신', '구리시', '안성시', '청원군', '인제군',
       '완도군', '금정구', '제천시', '공주시', '밀양시', '함양군', '영암군', '동대문구', '울주군',
       '서대문구', '군포시', '통영시', '의왕시', '을주군', '구로', '화순군', '정읍시', '원주시지정면',
       '태백시', '조치원읍', '영덕군', '칠곡군', '계룡시', '보령시', '논산시', '고창군', '서천군',
       '영월군', '청도군', '횡성군', '예천군', '옥천군', '양구군', '합천군', '종로2가', '창녕군',
       '사천시', '강화군', '울진군', '삼척시', '해남군', '고성군', '태안군', '연천군', '나주시',
       '남해군', '금산군', '홍천군', '영동군', '증평군', '영도구', '김제시', '홍성군', '거창군',
       '예산군', '보은군', '상주시'], dtype=object)

띄어쓰기 빠진 것이 제법 있다. 따로 따로 분리해주자.

In [48]:
d2_aliases = """나주시금천면:나주시 장성군장성읍:장성군 고흥군고흥읍:고흥군 기장군정관면:기장군
창녕군남지읍:창녕군 임실군임실읍:임실군 원주시지정면:원주시 진구:부산진구 논산시연무읍:논산시"""
d2_aliases = dict(aliasset.split(':') for aliasset in d2_aliases.split())
ltr_tbl['d2'] = ltr_tbl['d2'].apply(lambda v: d2_aliases.get(v, v))
ltr_tbl['d2'].unique()
Out[48]:
array(['관악구', '해운대구', '서구', '김포시', '기장군', '원주시', '청주시', '가평군', '나주시',
       '안산시', '안동시', '여수시', '장성군', '수성구', '사상구', '강남구', '봉화군', '광명시',
       '연기면', '하동군', '수원시', '이천시', '금남면', '거제시', '천안시', '광주시', '동구', '중구',
       '성북구', '고양시', '양평군', '제주시', '송파구', '김천시', '고령군', '전주시', '남동구',
       '수영구', '창원시', '양산시', '남양주시', '강릉시', '연제구', '구미시', '고흥군', '진주시',
       '용인시', '마포구', '순창군', '영등포구', '부천시', '북구', '성남시', '남구', '김해시', '달서구',
       '논산시', '사하구', '평택시', '영주시', '임실군', '유성구', '시흥시', '익산시', '창녕군',
       '서귀포시', '광진구', '용산구', '당진시', '강동구', '영천시', '완주군', '순천시', '화성시',
       '담양군', '부산진구', '함안군', '강북구', '연수구', '음성군', '서산시', '부안군', '구로구',
       '의정부시', '성주군', '은평구', '평창군', '중랑구', '아산시', '화천군', '하남시', '문경시',
       '안양시', '강진군', '경산시', '부여군', '양주시', '성동구', '장흥군', '군산시', '달성군',
       '경주시', '오산시', '대덕구', '포항시', '도봉구', '포천시', '계양구', '여주시', '동두천시',
       '부평구', '동작구', '목포시', '춘천시', '양천구', '종로구', '단양군', '속초시', '남원시',
       '금천구', '정선군', '동해시', '무안군', '노원구', '과천시', '광산구', '철원군', '무주군',
       '괴산군', '강서구', '파주시', '충주시', '영광군', '서초구', '청양군', '동래구', '광양시',
       '진천군', '아산신', '구리시', '안성시', '청원군', '인제군', '완도군', '금정구', '제천시',
       '공주시', '밀양시', '함양군', '영암군', '동대문구', '울주군', '서대문구', '군포시', '통영시',
       '의왕시', '을주군', '구로', '화순군', '정읍시', '태백시', '조치원읍', '영덕군', '칠곡군',
       '계룡시', '보령시', '고창군', '서천군', '영월군', '청도군', '횡성군', '예천군', '옥천군',
       '양구군', '합천군', '종로2가', '사천시', '강화군', '울진군', '삼척시', '해남군', '고성군',
       '태안군', '연천군', '남해군', '금산군', '홍천군', '영동군', '증평군', '영도구', '김제시',
       '홍성군', '거창군', '예산군', '보은군', '상주시'], dtype=object)

아.. 역시 매장이 많아서 오류도 많다. 다시 확인.

In [49]:
ltr_tbl[ltr_tbl['d2'].apply(lambda v: v[-1] not in '시군구')]
Out[49]:
storename d1 d2
19 세종첫마을 세종특별자치시 연기면
23 홈플러스세종 세종특별자치시 금남면
409 아산탕정 충청남도 아산신
547 오류동역 서울특별시 구로
662 홈플러스조치원 세종특별자치시 조치원읍
739 조치원 세종특별자치시 조치원읍
879 종각역 서울특별시 종로2가
945 개봉역 서울특별시 구로

오류는 고치고, 세종는 시군구 단위가 없으므로 세종시로 지정.

In [50]:
d2_aliases = """연기면:세종시 금남면:세종시 조치원읍:세종시 아산신:아산시 구로:구로구
종로2가:종로구"""
d2_aliases = dict(aliasset.split(':') for aliasset in d2_aliases.split())
ltr_tbl['d2'] = ltr_tbl['d2'].apply(lambda v: d2_aliases.get(v, v))
ltr_tbl['d2'].unique()
Out[50]:
array(['관악구', '해운대구', '서구', '김포시', '기장군', '원주시', '청주시', '가평군', '나주시',
       '안산시', '안동시', '여수시', '장성군', '수성구', '사상구', '강남구', '봉화군', '광명시',
       '세종시', '하동군', '수원시', '이천시', '거제시', '천안시', '광주시', '동구', '중구', '성북구',
       '고양시', '양평군', '제주시', '송파구', '김천시', '고령군', '전주시', '남동구', '수영구',
       '창원시', '양산시', '남양주시', '강릉시', '연제구', '구미시', '고흥군', '진주시', '용인시',
       '마포구', '순창군', '영등포구', '부천시', '북구', '성남시', '남구', '김해시', '달서구', '논산시',
       '사하구', '평택시', '영주시', '임실군', '유성구', '시흥시', '익산시', '창녕군', '서귀포시',
       '광진구', '용산구', '당진시', '강동구', '영천시', '완주군', '순천시', '화성시', '담양군',
       '부산진구', '함안군', '강북구', '연수구', '음성군', '서산시', '부안군', '구로구', '의정부시',
       '성주군', '은평구', '평창군', '중랑구', '아산시', '화천군', '하남시', '문경시', '안양시',
       '강진군', '경산시', '부여군', '양주시', '성동구', '장흥군', '군산시', '달성군', '경주시',
       '오산시', '대덕구', '포항시', '도봉구', '포천시', '계양구', '여주시', '동두천시', '부평구',
       '동작구', '목포시', '춘천시', '양천구', '종로구', '단양군', '속초시', '남원시', '금천구',
       '정선군', '동해시', '무안군', '노원구', '과천시', '광산구', '철원군', '무주군', '괴산군',
       '강서구', '파주시', '충주시', '영광군', '서초구', '청양군', '동래구', '광양시', '진천군',
       '구리시', '안성시', '청원군', '인제군', '완도군', '금정구', '제천시', '공주시', '밀양시',
       '함양군', '영암군', '동대문구', '울주군', '서대문구', '군포시', '통영시', '의왕시', '을주군',
       '화순군', '정읍시', '태백시', '영덕군', '칠곡군', '계룡시', '보령시', '고창군', '서천군',
       '영월군', '청도군', '횡성군', '예천군', '옥천군', '양구군', '합천군', '사천시', '강화군',
       '울진군', '삼척시', '해남군', '고성군', '태안군', '연천군', '남해군', '금산군', '홍천군',
       '영동군', '증평군', '영도구', '김제시', '홍성군', '거창군', '예산군', '보은군', '상주시'], dtype=object)

됐다. 이제 마지막 시도별 매장 수 계산!

In [51]:
L = ltr_tbl.apply(lambda r: r['d1'] + ' ' + r['d2'], axis=1).value_counts()
L.head()
Out[51]:
경상남도 창원시    29
경기도 수원시     28
충청북도 청주시    22
경기도 고양시     22
충청남도 천안시    22
dtype: int64

오. 롯데리아는 역시 강남이 상위권에 없다. 천안과 청주가 앞으로 튀어올랐다.

마지막으로 행정구역 이름을 위키백과 리스트와 다시 맞춰보자.

In [52]:
distr_latlon = pd.read_table('../../../../p/tiny/2014-12/burgerindex/latlon/lonlat.csv')
distr_latlon.head()
Out[52]:
d1 d2 area population density lat lon
0 경기도 가평군 843.04 58540 69.439173 37.831540 127.509883
1 서울특별시 강남구 39.50 569499 14417.696203 37.517236 127.047325
2 서울특별시 강동구 24.60 489655 19904.674797 37.530125 127.123762
3 강원도 강릉시 1040.07 219067 210.627169 37.751853 128.876057
4 서울특별시 강북구 23.60 343912 14572.542373 37.639610 127.025657
In [53]:
distr_latlon.index = distr_latlon.apply(lambda r: r['d1'] + ' ' + r['d2'], axis=1)
In [54]:
bgt = pd.DataFrame({'B': B, 'M': M, 'K': K, 'L': L}).fillna(0)
bgt = pd.merge(distr_latlon, bgt, how='outer', left_index=True, right_index=True)
bgt.head()
Out[54]:
d1 d2 area population density lat lon B K L M
강원도 강릉시 강원도 강릉시 1040.07 219067 210.627169 37.751853 128.876057 0 1 6 1
강원도 고성군 강원도 고성군 664.19 30802 46.375284 38.380129 128.467439 NaN NaN NaN NaN
강원도 동해시 강원도 동해시 180.01 95850 532.470418 37.524719 129.114292 0 1 3 0
강원도 삼척시 강원도 삼척시 1185.80 72431 61.081970 37.449868 129.165206 0 0 1 0
강원도 속초시 강원도 속초시 105.25 84568 803.496437 38.207015 128.591849 0 0 3 1

행정구역 정보가 없는데 버거 매장은 있는 지역을 찾아 봄.

In [55]:
bgt[np.isnan(bgt['area'])]
Out[55]:
d1 d2 area population density lat lon B K L M
경기도 여주군 NaN NaN NaN NaN NaN NaN NaN 1 0 0 0
울산광역시 을주군 NaN NaN NaN NaN NaN NaN NaN 0 0 1 0
충청북도 천안시 NaN NaN NaN NaN NaN NaN NaN 0 0 2 0
충청북도 청원군 NaN NaN NaN NaN NaN NaN NaN 1 0 3 0

흠.. 이름이 잘못 기록되거나 바뀐 것이 제법 있다. 고쳐주자.

In [56]:
bgidx_cols = ['B', 'K', 'L', 'M']
bgt.loc['경기도 여주시', bgidx_cols] += bgt.loc['경기도 여주군', bgidx_cols]
bgt.loc['울산광역시 울주군', bgidx_cols] += bgt.loc['울산광역시 을주군', bgidx_cols]
bgt.loc['충청남도 천안시', bgidx_cols] += bgt.loc['충청북도 천안시', bgidx_cols]
bgt.loc['충청북도 청주시', bgidx_cols] += bgt.loc['충청북도 청원군', bgidx_cols] # 2014년 7월 1일 통합.
bgt = bgt[~np.isnan(bgt['area'])].fillna(0)
In [57]:
bgt.head()
Out[57]:
d1 d2 area population density lat lon B K L M
강원도 강릉시 강원도 강릉시 1040.07 219067 210.627169 37.751853 128.876057 0 1 6 1
강원도 고성군 강원도 고성군 664.19 30802 46.375284 38.380129 128.467439 0 0 0 0
강원도 동해시 강원도 동해시 180.01 95850 532.470418 37.524719 129.114292 0 1 3 0
강원도 삼척시 강원도 삼척시 1185.80 72431 61.081970 37.449868 129.165206 0 0 1 0
강원도 속초시 강원도 속초시 105.25 84568 803.496437 38.207015 128.591849 0 0 3 1

오오. 드디어 버거지수 계산의 순간

이제 모든 데이터가 갖춰졌으니 모아서 계산해보자!

우선, pseudocount같은 것이 필요할 지 보기 위해 롯데리아가 하나도 없고 다른 버거 체인이 있는 시군구가 있는지 확인해 본다.

In [58]:
bgt[(bgt['L'] == 0) & (bgt['B'] + bgt['M'] + bgt['K'] > 0)]
Out[58]:
d1 d2 area population density lat lon B K L M

다행히도 없다. 롯데리아도 하나도 없는 곳은?

In [59]:
bgt[bgt['L'] == 0]
Out[59]:
d1 d2 area population density lat lon B K L M
강원도 고성군 강원도 고성군 664.19 30802 46.375284 38.380129 128.467439 0 0 0 0
강원도 양양군 강원도 양양군 628.68 28196 44.849526 38.075392 128.618850 0 0 0 0
경상남도 산청군 경상남도 산청군 794.59 35239 44.348658 35.415588 127.873498 0 0 0 0
경상남도 의령군 경상남도 의령군 482.95 30965 64.116368 35.322190 128.261658 0 0 0 0
경상북도 군위군 경상북도 군위군 614.15 25377 41.320524 36.242835 128.572770 0 0 0 0
경상북도 영양군 경상북도 영양군 815.11 18666 22.899977 36.666656 129.112401 0 0 0 0
경상북도 울릉군 경상북도 울릉군 72.82 10398 142.790442 37.484417 130.905800 0 0 0 0
경상북도 의성군 경상북도 의성군 1175.89 59608 50.691816 36.352658 128.697005 0 0 0 0
경상북도 청송군 경상북도 청송군 842.45 27067 32.128910 36.435904 129.057108 0 0 0 0
인천광역시 동구 인천광역시 동구 7.05 78496 11134.184397 37.473818 126.643338 0 0 0 0
인천광역시 옹진군 인천광역시 옹진군 164.30 18328 111.552039 37.213889 126.178333 0 0 0 0
전라남도 곡성군 전라남도 곡성군 547.37 32482 59.341944 35.281955 127.291918 0 0 0 0
전라남도 구례군 전라남도 구례군 443.01 27698 62.522291 35.202495 127.462653 0 0 0 0
전라남도 보성군 전라남도 보성군 663.16 49495 74.635081 34.771456 127.079894 0 0 0 0
전라남도 신안군 전라남도 신안군 653.13 45687 69.950852 34.827332 126.101074 0 0 0 0
전라남도 진도군 전라남도 진도군 420.32 34181 81.321374 34.486871 126.263485 0 0 0 0
전라남도 함평군 전라남도 함평군 392.77 37502 95.480816 35.065940 126.516552 0 0 0 0
전라북도 장수군 전라북도 장수군 533.64 23740 44.486920 35.647277 127.521136 0 0 0 0
전라북도 진안군 전라북도 진안군 788.94 27828 35.272644 35.791730 127.424836 0 0 0 0

의외로 상당히 많다. 롯데리아는 전국에 다 있는 줄 알았는데. -O-; 얘네들은 나중에 NaN으로 처리해서 다른 색깔로 표시.

그나저나 인천 동구에는 왜 버거킹, 롯데리아, KFC, 맥도날드가 하나도 없을까? 인구밀도도 1만이 넘는데.. 이상하다. --> 찾아보니 롯데리아는 구 경계에 딱 붙어서 남구에 3개가 있다.

In [60]:
bgt['BMK'] = bgt['B'] + bgt['M'] + bgt['K']
bgt['BgIdx'] = bgt['BMK'] / bgt['L']
bgt = bgt.sort('BgIdx', ascending=False)
bgt.head(10)
Out[60]:
d1 d2 area population density lat lon B K L M BMK BgIdx
서울특별시 강남구 서울특별시 강남구 39.50 569499 14417.696203 37.517236 127.047325 20 10 9 12 42 4.666667
서울특별시 서초구 서울특별시 서초구 47.04 431131 9165.199830 37.483712 127.032411 10 7 6 7 24 4.000000
서울특별시 종로구 서울특별시 종로구 23.90 177543 7428.577406 37.572950 126.979358 2 5 6 6 13 2.166667
강원도 홍천군 강원도 홍천군 1817.94 70264 38.650340 37.696952 127.888683 2 0 1 0 2 2.000000
서울특별시 서대문구 서울특별시 서대문구 17.60 336649 19127.784091 37.579116 126.936779 3 2 5 4 9 1.800000
서울특별시 중구 서울특별시 중구 9.96 137861 13841.465863 37.564091 126.997940 6 3 8 3 12 1.500000
인천광역시 중구 인천광역시 중구 110.60 93550 845.840868 37.473734 126.621480 2 2 6 5 9 1.500000
서울특별시 동작구 서울특별시 동작구 16.36 407973 24937.224939 37.512402 126.939252 2 3 6 4 9 1.500000
서울특별시 양천구 서울특별시 양천구 17.40 506684 29119.770115 37.516872 126.866399 3 1 7 6 10 1.428571
서울특별시 동대문구 서울특별시 동대문구 14.20 374277 26357.535211 37.574368 127.040019 1 1 5 5 7 1.400000

오오.. 역시 강남구, 서초구 버거지수는 유난히 튄다. 신기하다. +_+

홍천군이 3위! 그것은.. 오션월드와 대명비발디에 있는 버거킹 때문이다.

B+M+K와 롯데리아 매장 수 비교해 볼까?

In [61]:
rcParams['font.family'] = 'NanumGothic'

plt.figure(figsize=(5, 5))
r = lambda: np.random.random(len(bgt))
plt.scatter(bgt['L'] + r(), bgt['BMK'] + r(), s=6, c='black', edgecolor='none', alpha=0.6)
plt.xlabel('롯데리아')
plt.ylabel('버거킹+맥도날드+KFC')
plt.xlim(0, 45)
plt.ylim(0, 45)
plt.gca().set_aspect(1)

# 추세선 그린다.
trendfun = np.poly1d(np.polyfit(bgt['L'], bgt['BMK'], 1))
trendx = np.linspace(0, 45, 2)
plt.plot(trendx, trendfun(trendx))

# 튀는 점 몇 개는 이름도 표시한다.
tolabel = bgt[(bgt['L'] > 17) | (bgt['BMK'] >= 14)]
for idx, row in tolabel.iterrows():
    label_name = idx.split()[1][:-1]
    plt.annotate(label_name, (row['L'], row['BMK']))

음.. 역시 이렇게 보니까 강남, 서초는 대놓고 차이가 너무 많이 난다.

In [62]:
bgt.head()
Out[62]:
d1 d2 area population density lat lon B K L M BMK BgIdx
서울특별시 강남구 서울특별시 강남구 39.50 569499 14417.696203 37.517236 127.047325 20 10 9 12 42 4.666667
서울특별시 서초구 서울특별시 서초구 47.04 431131 9165.199830 37.483712 127.032411 10 7 6 7 24 4.000000
서울특별시 종로구 서울특별시 종로구 23.90 177543 7428.577406 37.572950 126.979358 2 5 6 6 13 2.166667
강원도 홍천군 강원도 홍천군 1817.94 70264 38.650340 37.696952 127.888683 2 0 1 0 2 2.000000
서울특별시 서대문구 서울특별시 서대문구 17.60 336649 19127.784091 37.579116 126.936779 3 2 5 4 9 1.800000

이제 지도에 표시해보자

미리 디자인해 놓은 블록모양 지도에서 좌표를 계산해서 옆에 붙인다. 지명이 길면 블록모양 지도 만들 때 번거로우므로 짧은 이름으로 매치한다.

In [63]:
def short_distr(name):
    wide, narrow = name.split()
    if narrow.endswith('구'):
        return wide[:2] + (narrow[:-1] if len(narrow) > 2 else narrow)
    elif narrow == '고성군': # 고성군은 강원도, 경상남도에 있다.
        return '고성({})'.format({'강원도': '강원', '경상남도': '경남'}[wide])
    else:
        return narrow[:-1]

bgt['shortname'] = list(map(short_distr, bgt.index))
bgt.head()
Out[63]:
d1 d2 area population density lat lon B K L M BMK BgIdx shortname
서울특별시 강남구 서울특별시 강남구 39.50 569499 14417.696203 37.517236 127.047325 20 10 9 12 42 4.666667 서울강남
서울특별시 서초구 서울특별시 서초구 47.04 431131 9165.199830 37.483712 127.032411 10 7 6 7 24 4.000000 서울서초
서울특별시 종로구 서울특별시 종로구 23.90 177543 7428.577406 37.572950 126.979358 2 5 6 6 13 2.166667 서울종로
강원도 홍천군 강원도 홍천군 1817.94 70264 38.650340 37.696952 127.888683 2 0 1 0 2 2.000000 홍천
서울특별시 서대문구 서울특별시 서대문구 17.60 336649 19127.784091 37.579116 126.936779 3 2 5 4 9 1.800000 서울서대문

블록 지도 데이터 읽어온다.

In [68]:
blockpositions = pd.read_csv('../../../../../p/tiny/2014-12/burgerindex/blockmap-positions.csv', names=range(15))
blockpositions.head()
Out[68]:
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14
0 NaN NaN NaN NaN NaN NaN 철원 화천 양구 고성(강원) NaN NaN NaN NaN NaN
1 NaN NaN NaN NaN NaN 서울도봉 서울노원 연천 포천 속초 NaN NaN NaN NaN NaN
2 NaN NaN NaN 파주 고양 서울강북 서울성북 동두천 양주 인제 양양 NaN NaN NaN NaN
3 NaN 강화 김포 광명 서울은평 서울서대문 서울종로 의정부 남양주 가평 춘천 NaN NaN NaN NaN
4 NaN 인천서구 부천 안양 서울강서 서울마포 서울중구 서울동대문 서울중랑 구리 홍천 강릉 NaN NaN NaN

x, y 좌표 테이블로 변환해서 버거 데이터랑 합친다.

In [69]:
flatrows = []
for y, colcities in blockpositions.iterrows():
    for x, city in colcities.iteritems():
        if isinstance(city, str):
            flatrows.append((x, y, city))

blockpositions_tbl = pd.DataFrame(flatrows, columns=('x', 'y', 'city')).set_index('city').sort_index()
bgtb = pd.merge(bgt, blockpositions_tbl, how='left', left_on='shortname', right_index=True)
bgtb.head()
Out[69]:
d1 d2 area population density lat lon B K L M BMK BgIdx shortname x y
서울특별시 강남구 서울특별시 강남구 39.50 569499 14417.696203 37.517236 127.047325 20 10 9 12 42 4.666667 서울강남 6 7
서울특별시 서초구 서울특별시 서초구 47.04 431131 9165.199830 37.483712 127.032411 10 7 6 7 24 4.000000 서울서초 6 6
서울특별시 종로구 서울특별시 종로구 23.90 177543 7428.577406 37.572950 126.979358 2 5 6 6 13 2.166667 서울종로 6 3
강원도 홍천군 강원도 홍천군 1817.94 70264 38.650340 37.696952 127.888683 2 0 1 0 2 2.000000 홍천 10 4
서울특별시 서대문구 서울특별시 서대문구 17.60 336649 19127.784091 37.579116 126.936779 3 2 5 4 9 1.800000 서울서대문 5 3

혹시 위치가 할당되지 않은 곳이 있는지 확인한다.

In [70]:
bgtb[bgtb['x'].apply(np.isnan)]
Out[70]:
d1 d2 area population density lat lon B K L M BMK BgIdx shortname x y

없다. 모든 지역이 다 위치가 잡혔으니 이제 그림을 그린다!

In [71]:
from matplotlib import rcParams
from matplotlib import cm, colors, _cm
rcParams['font.family'] = 'NanumBarunGothic'

BgIdx가 NaN이면 색을 제대로 칠하기 힘들기 때문에, 롯데리아가 없는 곳은 그냥 0으로 처리한다.

In [72]:
bgtb['BgIdx'] = bgtb['BgIdx'].fillna(0)

시도간 경계 그림 데이터 (열심히) 만든다.

In [73]:
BORDER_LINES = [
    [(3, 2), (5, 2), (5, 3), (9, 3), (9, 1)], # 인천
    [(2, 5), (3, 5), (3, 4), (8, 4), (8, 7), (7, 7), (7, 9), (4, 9), (4, 7), (1, 7)], # 서울
    [(1, 6), (1, 9), (3, 9), (3, 10), (8, 10), (8, 9),
     (9, 9), (9, 8), (10, 8), (10, 5), (9, 5), (9, 3)], # 경기도
    [(9, 12), (9, 10), (8, 10)], # 강원도
    [(10, 5), (11, 5), (11, 4), (12, 4), (12, 5), (13, 5),
     (13, 4), (14, 4), (14, 2)], # 충청남도
    [(11, 5), (12, 5), (12, 6), (15, 6), (15, 7), (13, 7),
     (13, 8), (11, 8), (11, 9), (10, 9), (10, 8)], # 충청북도
    [(14, 4), (15, 4), (15, 6)], # 대전시
    [(14, 7), (14, 9), (13, 9), (13, 11), (13, 13)], # 경상북도
    [(14, 8), (16, 8), (16, 10), (15, 10),
     (15, 11), (14, 11), (14, 12), (13, 12)], # 대구시
    [(15, 11), (16, 11), (16, 13)], # 울산시
    [(17, 1), (17, 3), (18, 3), (18, 6), (15, 6)], # 전라북도
    [(19, 2), (19, 4), (21, 4), (21, 3), (22, 3), (22, 2), (19, 2)], # 광주시
    [(18, 5), (20, 5), (20, 6)], # 전라남도
    [(16, 9), (18, 9), (18, 8), (19, 8), (19, 9), (20, 9), (20, 10)], # 부산시
]
In [74]:
def draw_blockcolormap(tbl, datacol, vmin, vmax, whitelabelmin, cmapname, gamma, datalabel, dataticks):
    cmap = colors.LinearSegmentedColormap(cmapname + 'custom',
                      getattr(_cm, '_{}_data'.format(cmapname)), gamma=gamma)
    cmap.set_bad('white', 1.)

    mapdata = tbl.pivot(index='y', columns='x', values=datacol)
    masked_mapdata = np.ma.masked_where(np.isnan(mapdata), mapdata)

    plt.figure(figsize=(9, 16))
    plt.pcolor(masked_mapdata, vmin=vmin, vmax=vmax, cmap=cmap,
               edgecolor='#aaaaaa', linewidth=0.5)

    # 지역 이름 표시
    for idx, row in tbl.iterrows():
        annocolor = 'white' if row[datacol] > whitelabelmin else 'black'

        # 광역시는 구 이름이 겹치는 경우가 많아서 시단위 이름도 같이 표시한다. (중구, 서구)
        if row['d1'].endswith('시') and not row['d1'].startswith('세종'):
            dispname = '{}\n{}'.format(row['d1'][:2], row['d2'][:-1])
            if len(row['d2']) <= 2:
                dispname += row['d2'][-1]
        else:
            dispname = row['d2'][:-1]

        # 서대문구, 서귀포시 같이 이름이 3자 이상인 경우에 작은 글자로 표시한다.
        if len(dispname.splitlines()[-1]) >= 3:
            fontsize, linespacing = 12, 1.2
        else:
            fontsize, linespacing = 14, 1.03

        plt.annotate(dispname, (row['x']+0.5, row['y']+0.5), weight='bold',
                     fontsize=fontsize, ha='center', va='center', color=annocolor,
                     linespacing=linespacing)

    # 시도 경계 그린다.
    for path in BORDER_LINES:
        ys, xs = zip(*path)
        plt.plot(xs, ys, c='black', lw=2)

    plt.gca().invert_yaxis()
    plt.gca().set_aspect(1)

    plt.axis('off')
    
    cb = plt.colorbar(shrink=.1, aspect=10)
    cb.set_label(datalabel)
    cb.set_ticks(dataticks)

    plt.tight_layout()

자, 이제 긴장되는 버거지수 지도 그리는 순간!

In [75]:
draw_blockcolormap(bgtb, 'BgIdx', 0, 3, 1.42, 'Blues', 0.75, '버거지수', np.arange(0, 3.1, 0.5))
plt.savefig('bmap-burgerindex.pdf')

그렸다! 제법 멋지게 나온다. 이제 따로 따로 보자. 인구 대비 롯데리아 수는?

In [76]:
bgtb['Lp10T'] = bgtb['L'] / bgtb['population'] * 10000
draw_blockcolormap(bgtb, 'Lp10T', 0, 1, 0.45, 'YlGn', 1, '1만명당 롯데리아 점포수', np.arange(0, 1.1, 0.2))
plt.savefig('bmap-lotteria.pdf')

인구 대비 버거킹+맥도날드+KFC 수는?

In [77]:
bgtb['BMKp10T'] = bgtb['BMK'] / bgtb['population'] * 10000
draw_blockcolormap(bgtb, 'BMKp10T', 0, 1, 0.45, 'YlGn', 1, '1만명당 버거킹+맥도날드+KFC 점포수', np.arange(0, 1.1, 0.2))
plt.savefig('bmap-bmkshops.pdf')