호쌤
호쌤 Just For Fun

[Python 데이터 시각화] 공적마스크 판매정보 지도시각화

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[Python 데이터 시각화] 공적마스크 판매정보 지도시각화≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

[Python 데이터 시각화] 공적마스크 판매정보 지도시각화

공공데이터포털에서는 코로나19 사태 이후 공적마스크 판매 정보를 OpenAPI 형태로 공개하고 있습니다. 이번 포스팅에서는 이 데이터를 requests 패키지를 활용하여 수집한 후 Folium 패키지를 통해 지도위에 시각화 하는 예제를 소개합니다.

이 예제는 더이상 유효하지 않습니다. 공적마스크 제도가 폐지된 이후 OpenAPI도 함께 문을 닫아서 더이상 동작하지 않습니다.

#01. 기본 준비 단계

1) OpenAPI 확인하기

좌표(위/경도) 기준 공적 마스크 판매정보 제공 서비스에서 공적마스크 판매 정보 API를 확인할 수 있다. 이 밖에도 주소기반, 위경도/주소기반 등 총 4개의 기능을 제공하는데 이 포스팅에서는 위,경도 기반 API를 활용하기로 한다.

api

자세한 API 레퍼런스는 https://app.swaggerhub.com/apis-docs/Promptech/public-mask-info/20200307-oas3#/에서 확인 가능하다.

api

2) 필요한 패키지 가져오기

1
2
3
4
5
6
import folium
import requests
import json
import numpy
import pandas as pd
from pandas import DataFrame

3) 위도, 경도 변수 준비하기

구글맵에서 위,경도 확인을 원하는 위치를 마우스 우클릭한다.

google

표시되는 메뉴에서 이곳이 궁금한가요?라는 메뉴를 클릭하면 화면 하단에서 위,경도를 확인할 수 있다.

google

1
2
3
lat = 37.504750
lng = 127.025401
m = 5000

4) 세션 생성

1
2
3
user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36"
session = requests.Session()
session.headers.update( {'User-agent': user_agent, 'referer': None} )

#02. 특정 위치 부근의 공적 마스크 판매 정보 조회

1) 접속 주소 준비하기

변수가 치환될 주소 템플릿

API 레퍼런스를 통해 요처엥 필요한 GET 파라미터가 lat(위도), lng(경도), m(거리,미터단위) 임을 확인하였다.

1
url_tpl = "https://8oi9s0nnth.apigw.ntruss.com/corona19-masks/v1/storesByGeo/json?lat={lat}&lng={lng}&m={m}"

최종 접속 주소 확인

주소 템플릿에 미리 준비한 변수를 적용하여 최종 URL을 확인한다.

1
2
url = url_tpl.format(lat=lat, lng=lng, m=m)
url
▶ 출력결과
1
'https://8oi9s0nnth.apigw.ntruss.com/corona19-masks/v1/storesByGeo/json?lat=37.50475&lng=127.025401&m=5000'

확인된 URL에 웹 브라우저로 접속하면 아래와 같이 JSON 데이터를 확인할 수 있다.

api

2) API를 통한 JSON 데이터 가져오기

OpenAPI를 통한 JSON 가져오기

URL에 접근하여 JSON 데이터를 String으로 가져온다.

1
2
3
r = session.get(url)
r.encoding = "utf-8"
r.text
▶ 출력결과
1
2
3
4
5
'{\n  "count": 778,\n  "stores": [{\n    "addr": "서울특별시 동작구 동작대로 7 1층 (사당동)",\n    "code": "12809721",\n    "created_at": "2020/04/25 10:40:00",\n    "lat": 37.4771747,\n    "lng": 126.9811278,\n    "name": "사당소화약국",\n    "remain_stat": "plenty",\n    "stock_at": "2020/04/24 13:14:00",\n    "type": "01"\n  }, {\n    "addr": "서울특별시 서초구 방배천로 5-3 1층 (방배동, 도양빌딩)",\n    "code": "12838101",\n    "created_at": "2020/04/25 10:40:00",\n    "lat": 37.4770623,\n    "lng": 126.9822612,\n    "name": "사당13번약국",\n    "remain_stat": "break",\n    "stock_at": "2020/04/24 08:55:00",\n    "type": "01"\n  }, 

... 생략 ...

, {\n    "addr": "서울특별시 성동구 성수일로 39 (성수동1가)",\n    "code": "11879661",\n    "created_at": "2020/04/25 10:40:00",\n    "lat": 37.5442422,\n    "lng": 127.0492652,\n    "name": "메디팜호정약국",\n    "remain_stat": "plenty",\n    "stock_at": "2020/04/25 09:17:00",\n    "type": "01"\n  }, {\n    "addr": "서울특별시 성동구 연무장7길 6 (성수동2가)",\n    "code": "11861231",\n    "created_at": "2020/04/25 10:40:00",\n    "lat": 37.5431569,\n    "lng": 127.0545405,\n    "name": "우당약국",\n    "remain_stat": "plenty",\n    "stock_at": "2020/04/24 13:58:00",\n    "type": "01"\n  }]\n}'

JSON을 딕셔너리로 변환

가져온 String 데이터를 딕셔너리로 변환한다.

1
2
mask_dict = json.loads(r.text)
mask_dict
▶ 출력결과
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{'count': 778,
 'stores': [{'addr': '서울특별시 동작구 동작대로 7 1층 (사당동)',
   'code': '12809721',
   'created_at': '2020/04/25 10:40:00',
   'lat': 37.4771747,
   'lng': 126.9811278,
   'name': '사당소화약국',
   'remain_stat': 'plenty',
   'stock_at': '2020/04/24 13:14:00',
   'type': '01'},

  ... 생략 ...

  {'addr': '서울특별시 성동구 연무장7길 6 (성수동2가)',
   'code': '11861231',
   'created_at': '2020/04/25 10:40:00',
   'lat': 37.5431569,
   'lng': 127.0545405,
   'name': '우당약국',
   'remain_stat': 'plenty',
   'stock_at': '2020/04/24 13:58:00',
   'type': '01'}]}

딕셔너리를 데이터프레임으로 변환

1
2
mask_df = DataFrame(mask_dict['stores'])
mask_df
▶ 출력결과
addr code created_at lat lng name remain_stat stock_at type
0 서울특별시 동작구 동작대로 7 1층 (사당동) 12809721 2020/04/25 10:40:00 37.477175 126.981128 사당소화약국 plenty 2020/04/24 13:14:00 01
1 서울특별시 서초구 방배천로 5-3 1층 (방배동, 도양빌딩) 12838101 2020/04/25 10:40:00 37.477062 126.982261 사당13번약국 break 2020/04/24 08:55:00 01
2 서울특별시 동작구 동작대로 25 4층 (사당동) 11816678 2020/04/25 10:40:00 37.478750 126.981358 새미약국 plenty 2020/04/24 08:52:00 01
3 서울특별시 서초구 효령로2길 4 (방배동) 12833771 2020/04/25 10:40:00 37.476338 126.986532 메디팜메트로약국 plenty 2020/04/25 08:56:00 01
4 서울특별시 서초구 효령로 12 1층 (방배동) 12843636 2020/04/25 10:40:00 37.476877 126.986748 강남사랑약국 plenty 2020/04/25 08:51:00 01
... ... ... ... ... ... ... ... ... ...
773 서울특별시 성동구 왕십리로 92 (성수동1가) 11861011 2020/04/25 10:40:00 37.545726 127.044985 신동아약국 plenty 2020/04/24 15:39:00 01
774 서울특별시 성동구 서울숲2길 47 (성수동1가) 12809527 2020/04/25 10:40:00 37.546335 127.044004 백두산약국 plenty 2020/04/25 08:12:00 01
775 서울특별시 성동구 왕십리로 94-2 1층 (성수동1가) 11860880 2020/04/25 10:40:00 37.546135 127.044930 수명약국 plenty 2020/04/25 08:35:00 01
776 서울특별시 성동구 성수일로 39 (성수동1가) 11879661 2020/04/25 10:40:00 37.544242 127.049265 메디팜호정약국 plenty 2020/04/25 09:17:00 01
777 서울특별시 성동구 연무장7길 6 (성수동2가) 11861231 2020/04/25 10:40:00 37.543157 127.054541 우당약국 plenty 2020/04/24 13:58:00 01

778 rows × 9 columns

3) 데이터 전처리

결측치 제거

마스크 재고 정보가 없는 약국이 간혹 있다. 공적마스크 판매를 하지 않는 경우이므로 수집결과에서 삭제한다.

1
mask_df.dropna(inplace=True)

파생변수 추가

API 레퍼런스를 확인하면 재고 상태를 의미하는 remain_stat 변수의 값이 5개의 문자열로 구분되고 이 값에 따라 수량의 범위가 결정된다. 또한 각각의 문자열에 따라 어떤 색상을 표시해야 하는지도 정의하고 있다.

remain_stat 재고 상태 표시 색상
plenty 100개 이상 녹색
some 30개 이상 100개미만 노랑색
few 2개 이상 30개 미만 빨강색
empty 1개 이하 회색
break 판매중지 검은색
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 마스크 수량을 의미하는 조건들을 리스트로 설정
conditions = [  (mask_df['remain_stat'] == 'plenty'),   # 100개 이상
                (mask_df['remain_stat'] == 'some'),     # 30개 이상 100개미만
                (mask_df['remain_stat'] == 'few'),      # 2개 이상 30개 미만
                (mask_df['remain_stat'] == 'empty'),    # 1개 이하
                (mask_df['remain_stat'] == 'break')]    # 판매중지

# 조건에 따라 부여될 상태값과 색상값
status = [ '100개 이상', '30개 이상 100개미만', '2개 이상 30개 미만', '1개 이하', '판매중지' ]
color = [ 'green', 'orange', 'red', 'gray', 'black']

# 조건에 따른 열 추가하기
mask_df['status'] = numpy.select(conditions, status)
mask_df['color'] = numpy.select(conditions, color)

mask_df
▶ 출력결과
addr code created_at lat lng name remain_stat stock_at type status color
0 서울특별시 동작구 동작대로 7 1층 (사당동) 12809721 2020/04/25 10:40:00 37.477175 126.981128 사당소화약국 plenty 2020/04/24 13:14:00 01 100개 이상 green
1 서울특별시 서초구 방배천로 5-3 1층 (방배동, 도양빌딩) 12838101 2020/04/25 10:40:00 37.477062 126.982261 사당13번약국 break 2020/04/24 08:55:00 01 판매중지 black
2 서울특별시 동작구 동작대로 25 4층 (사당동) 11816678 2020/04/25 10:40:00 37.478750 126.981358 새미약국 plenty 2020/04/24 08:52:00 01 100개 이상 green
3 서울특별시 서초구 효령로2길 4 (방배동) 12833771 2020/04/25 10:40:00 37.476338 126.986532 메디팜메트로약국 plenty 2020/04/25 08:56:00 01 100개 이상 green
4 서울특별시 서초구 효령로 12 1층 (방배동) 12843636 2020/04/25 10:40:00 37.476877 126.986748 강남사랑약국 plenty 2020/04/25 08:51:00 01 100개 이상 green
... ... ... ... ... ... ... ... ... ... ... ...
773 서울특별시 성동구 왕십리로 92 (성수동1가) 11861011 2020/04/25 10:40:00 37.545726 127.044985 신동아약국 plenty 2020/04/24 15:39:00 01 100개 이상 green
774 서울특별시 성동구 서울숲2길 47 (성수동1가) 12809527 2020/04/25 10:40:00 37.546335 127.044004 백두산약국 plenty 2020/04/25 08:12:00 01 100개 이상 green
775 서울특별시 성동구 왕십리로 94-2 1층 (성수동1가) 11860880 2020/04/25 10:40:00 37.546135 127.044930 수명약국 plenty 2020/04/25 08:35:00 01 100개 이상 green
776 서울특별시 성동구 성수일로 39 (성수동1가) 11879661 2020/04/25 10:40:00 37.544242 127.049265 메디팜호정약국 plenty 2020/04/25 09:17:00 01 100개 이상 green
777 서울특별시 성동구 연무장7길 6 (성수동2가) 11861231 2020/04/25 10:40:00 37.543157 127.054541 우당약국 plenty 2020/04/24 13:58:00 01 100개 이상 green

753 rows × 11 columns

#03. 지도 시각화

1) 지도 객체 생성

지도의 중심이 되는 위도와 경도를 설정

1
2
3
# zoom_start: 배율 1~22
map_osm = folium.Map(location=[lat, lng], zoom_start=15)
map_osm

map01

2) 지도객체에 마커 추가

데이터 프레임의 행 수 만큼 반복하면서 지정

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
for i in mask_df.index:
    x = mask_df.loc[i, 'lat']
    y = mask_df.loc[i, 'lng']
    name = mask_df.loc[i, 'name']
    status = mask_df.loc[i, 'status']
    color = mask_df.loc[i, 'color']

    # 중간 확인을 위해 값을 출력한다.
    print(x, y, name, status, color)
    
    # HTML을 사용한 팝업
    tag_tpl = "<div style='white-space: nowrap'><b>%s</b><br>%s</div>"
    tag = tag_tpl % (name, status)
    popup_html = folium.Popup(tag, parse_html=False)

    # 마커 객체 생성
    mk = folium.Marker([x, y], popup=popup_html, icon=folium.Icon(color=color))

    mk.add_to(map_osm)  # 마커 객체를 지도에 추가함
▶ 출력결과
1
2
3
4
5
6
7
8
9
10
11
12
13
37.4771747 126.9811278 사당소화약국 100개 이상 green
37.4770623 126.9822612 사당13번약국 판매중지 black
37.47875 126.9813575 새미약국 100개 이상 green
37.4763378 126.9865321 메디팜메트로약국 100개 이상 green
37.4768774 126.9867478 강남사랑약국 100개 이상 green
37.4762829 126.9875282 나은약국 100개 이상 green
37.4797056 126.9984853 경희정산한약국 판매중지 black
37.4778415 127.0003899 비타민약국 30개 이상 100개미만 orange
... 생략 ...
37.5463345 127.0440042 백두산약국 100개 이상 green
37.5461351 127.0449303 수명약국 100개 이상 green
37.5442422 127.0492652 메디팜호정약국 100개 이상 green
37.5431569 127.0545405 우당약국 100개 이상 green
1
map_osm
▶ 출력결과

map02

Rating:

크리에이티브 커먼즈 라이선스 ITPAPER(호쌤,쭈쌤)에 의해 작성된 ≪[Python 데이터 시각화] 공적마스크 판매정보 지도시각화≫은(는) 크리에이티브 커먼즈 저작자표시-비영리-동일조건변경허락 4.0 국제 라이선스에 따라 이용할 수 있습니다.
이 라이선스의 범위 이외의 이용허락을 얻기 위해서는 leekh4232@gmail.com으로 문의하십시오.

comments powered by Disqus