본문 바로가기
데이터 시각화

공공데이터포탈 Open API 활용법 - 국내 시.도별 코로나19 확진자 발생 현황 데이터 시각화 ( Python, Pandas, Plotly )

by 맑은안개 2021. 3. 15.

Image by S. Hermann & F. Richter from Pixabay

공공데이터포탈 Open API 활용

국내 시.도별 코로나19 확진자 발생 현황 데이터 시각화

공공데이터포털은 국내 각 기관이 다루는 데이터를 통합하여 사용하기 편리하게 제공하는 포털사이트이다.  코로나19(COVID-19)와 관련한 데이터도 제공한다.

  이번 블로그에서는 국내 코로나19 현황 데이터를 사용하여 일별 국내 총 확진자 추이, 국내 시.도별 확진자 추이 정보를 시각화 라이브러리, Plotly를 사용하여 차트로 표현해 본다.

 

배워 볼 것

- 공공데이터포털 OpenAPI 사용방법 

- 데이터 전처리

- 데이터 시각화 ( Bar, Pie, Map(공간정보) )

 

개발 환경

- Python 3.9

- Pandas 1.2.0

- plotly 4.14.3

- requests 2.25.1

- beautifulsoup4 4.9.3

- pytz 2020.5

 

공공데이터포털 ( 바로가기 )

- 회원 가입 후 인증키 발급 확인

- 다음의 서비스를 활용신청 한다.

! 주의사항

- 활용신청 일로부터 2~3시간이 지나면 사용된다고 설명되어 있으나 많게는 24시간이 지나야 서비스를 활용할 수 있다. 

 

라이브러리 임포트

import json # 공간정보 geojson파일 로드
import requests # 공공데이터포탈 API 호출

from bs4 import BeautifulSoup as bs # 
from datetime import datetime, date
# data
import pandas as pd
#visualization
import plotly.graph_objects as go
import plotly.express as px

import warnings # Supress warnings 
warnings.filterwarnings('ignore')

 

공공데이터포털 샘플 결과 확인 

- 활용신청 24시간 후 정상 인증 되었는지 확인하기 위해 해당 서비스를 테스트 해본다.

공공데이터포털 상세기능 정보 - 테스트

결과

테스트 - 결과

함수 생성

# 현재시간을 YYYYMMDD 형태로 리턴
def get_now(date_format='%Y%m%d'):
    import pytz
    return datetime.now(tz=pytz.timezone('Asia/Seoul')).strftime(date_format)
    
def get_korea_covid_data(start_dt=None, end_dt=None, num_of_rows=100, page_no=1, data_type='html.parser'):
    
    if start_dt is None:
        start_dt = get_now()
    
    if end_dt is None:
        end_dt = get_now()

    # print(f'page_no {page_no}, num_of_rows {num_of_rows}, start_dt {start_dt}, end_dt {end_dt}')

    end_point = "http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19SidoInfStateJson"
    params = {
        "ServiceKey": requests.utils.unquote("Insert your secret key", encoding='utf-8'), # 1
        "numOfRows": num_of_rows,
        "pageNo": page_no,
        "startCreateDt": start_dt,
        "endCreateDt": end_dt
    }

    resp = requests.get(end_point, params=params) #2

    data = None
    if resp.status_code == 200:
        data = bs(resp.text, data_type) #3

    return data

#4
def convert_list(item):
    _rs = []
    if item:
       for data in list(item):
           _rs.append(data.text)
    return _rs      

#1 `get_korea_covid_data` 함수의 `ServiceKey`에 공공데이터포털에서 발급된 인증키를 넣는다.

#2 `requests` 라이브러리를 사용하여 http 요청처리 한다. 

#3 http응답메세지가 정상인 경우 `beautilfulsoup`로 데이터를 읽어 객체화 한다.

#4 `convert_list` 함수는 DataFrame으로 변환하기 위해 각 `item`데이터의 컬럼을 리스트로 저장한다.

 

데이터 조회

data = get_korea_covid_data(start_dt='20210101', end_dt='20210315', page_no=current_page_no, data_type='lxml', num_of_rows=num_of_rows) 

위에서 얻은 데이터를 `convert_list`함수 처리한 결과를 보면 다음과 같다.

items = data.find_all('items')[0]

_template = []
for item in list(items):
	print(convert_list(item))
    _template.append(convert_list(item))    

결과

..
['2021-01-30 9:31:34.34', '36', '931', '울산', '蔚山', 'Ulsan', '2', '861', '34', '1', '1', '81.17', '7330', '2021년 01월 30일 00시', 'NULL']
['2021-01-30 9:31:34.34', '13', '1097', '대전', '大田', 'Daejeon', '2', '934', '150', '1', '1', '74.42', '7329', '2021년 01월 30일 00시', 'NULL']
['2021-01-30 9:31:34.34', '17', '1766', '광주', '光州', 'Gwangju', '33', '1369', '380', '32', '1', '121.23', '7328', '2021년 01월 30일 00시', 'NULL']
['2021-01-30 9:31:34.34', '48', '3792', '인천', '仁川', 'Incheon', '15', '3412', '332', '15', '0', '128.28', '7327', '2021년 01월 30일 00시', 'NULL']
['2021-01-30 9:31:34.34', '207', '8298', '대구', '大邱', 'Daegu', '8', '7961', '130', '8', '0', '340.57', '7326', '2021년 01월 30일 00시', 'NULL']
['2021-01-30 9:31:34.34', '93', '2737', '부산', '釜山', 'Busan', '20', '2256', '388', '18', '2', '80.22', '7325', '2021년 01월 30일 00시', 'NULL']
['2021-01-30 9:31:34.34', '323', '24061', '서울', '首尔', 'Seoul', '154', '19594', '4142', '150', '2', '247.2', '7324', '2021년 01월 30일 00시', '2021-02-08 16:04:34.34']
['2021-01-30 9:31:34.34', '1414', '77848', '합계', '合计', 'Total', '456', '67121', '9313', '421', '35', '150.15', '7323', '2021년 01월 30일 00시', '2021-02-08 16:06:05.05']
['2021-01-29 9:36:10.10', '3', '2698', '검역', '隔離區', 'Lazaretto', '9', '1979', '716', '0', '9', '-', '7322', '2021년 01월 29일 00시', 'NULL']
['2021-01-29 9:36:10.10', '0', '519', '제주', '济州', 'Jeju', '1', '495', '24', '1', '0', '77.38', '7321', '2021년 01월 29일 00시', 'NULL']
..

DataFrame으로 변환하기 위해 적합한 형태로 만들어졌다. 여기에 컬럼 정보를 매핑해야 하므로 컬럼을 정의한다.

columns = [
    'createdt',
    'deathcnt',
    'defcnt',
    'gubun',
    'gubuncn',
    'gubunen',
    'incdec',
    'isolclearcnt',
    'isolingcnt',
    'localocccnt',
    'overflowcnt',
    'qurrate',
    'seq',
    'stdday',
    'updatedt',
]  

DataFrame 생성

df = pd.DataFrame(_template, columns=columns)

df.head(5)

# OUTPUT
createdt	deathcnt	defcnt	gubun	gubuncn	gubunen	incdec	isolclearcnt	isolingcnt	localocccnt	overflowcnt	qurrate	seq	stdday	updatedt
0	2021-03-15 09:55:40.206	4	3056	검역	隔離區	Lazaretto	7	2596	456	0	7	-	8253	2021년 03월 15일 00시	null
1	2021-03-15 09:55:40.206	1	609	제주	济州	Jeju	3	575	33	3	0	90.79	8252	2021년 03월 15일 00시	null
2	2021-03-15 09:55:40.206	14	2438	경남	庆南	Gyeongsangnam-do	31	2185	239	31	0	72.53	8251	2021년 03월 15일 00시	null
3	2021-03-15 09:55:40.206	72	3373	경북	庆北	Gyeongsangbuk-do	3	3191	110	3	0	126.69	8250	2021년 03월 15일 00시	null
4	2021-03-15 09:55:40.206	8	904	전남	全南	Jeollanam-do	1	827	69	0	1	48.48	8249	2021년 03월 15일 00시	null

 

데이터 전처리

- 중복 행 확인 ( 결과: 중복 없음 )

# 전체 행 중복 확인
df[_df.duplicated()]

# seq 컬럼 중복 확인
df[_df.duplicated(['seq'])]

- 데이터 형변환

int_cols = ['deathcnt', 'defcnt', 'incdec', 'isolclearcnt', 'isolingcnt', 'localocccnt', 'overflowcnt']
for col in int_cols:
    df[col] = df[col].astype(int) #1

df['qurrate'] = df['qurrate'].astype(float) #2

df['createdt'] = df['createdt'].apply(lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S.%f')) #3
df['dt'] = df['stdday'].apply(lambda x: datetime.strptime(x, '%Y년 %m월 %d일 %H시')) #4

#1 `int_cols`에 정의된 컬럼을 int형으로 변환한다.

#2 비율 컬럼인 `qurrate`을 float형으로 변환한다.

#3 생성일시 `createdt`를 datetime형으로 변환한다.

#4 기준일시 `stdday`를 신규 컬럼 `dt`에 datetime형으로 변환하며 넣는다. 

 

- 불필요 데이터 삭제

df.drop(columns=['gubuncn', 'gubunen', 'seq', 'stdday', 'updatedt'], inplace=True)
df.head(5)

# OUTPUT
createdt	deathcnt	defcnt	gubun	incdec	isolclearcnt	isolingcnt	localocccnt	overflowcnt	qurrate	dt
0	2021-03-15 09:55:40.206	4	3056	검역	7	2596	456	0	7	0.00	2021-03-15
1	2021-03-15 09:55:40.206	1	609	제주	3	575	33	3	0	90.79	2021-03-15
2	2021-03-15 09:55:40.206	14	2438	경남	31	2185	239	31	0	72.53	2021-03-15
3	2021-03-15 09:55:40.206	72	3373	경북	3	3191	110	3	0	126.69	2021-03-15
4	2021-03-15 09:55:40.206	8	904	전남	1	827	69	0	1	48.48	2021-03-15

df.dtypes

# OUTPUT
createdt        datetime64[ns]
deathcnt                 int32
defcnt                   int32
gubun                   object
incdec                   int32
isolclearcnt             int32
isolingcnt               int32
localocccnt              int32
overflowcnt              int32
qurrate                float64
dt              datetime64[ns]
dtype: object

 

데이터 시각화

1. 서울 지역 확진자 추이

df_seoul = df[df.gubun == '서울']

fig = go.Figure([
    go.Bar(x=df_seoul.dt, y=df_seoul.overflowcnt, name='해외유입', text=df_seoul.overflowcnt, marker_color='red'),
    go.Bar(x=df_seoul.dt, y=df_seoul.localocccnt, name='국내발생', text=df_seoul.localocccnt, marker_color='blue'),
])
fig.update_layout(width=1000, barmode='stack')
fig.show()

- `stack` 으로 해외유입, 국내발생 Bar 차트 출력

서울 지역 확진자 추이

2. 시.도 별 확진자 발생 추이 비교 - Line chart

charts = []
for region in list(sorted_df.gubun.unique()):
    _df = df[df.gubun == region]
    charts.append(go.Scatter(x=_df.dt, y=_df.localocccnt, name=_df.iloc[0].gubun))

fig = go.Figure(charts)
fig.update_layout(
    title=dict(
        text='시.도',
        x=0.5
    )
)
fig.update_layout(width=1300, height=600)
fig.show()

(Plotly 라이브러리에선 Line 차트를 Scatter 함수로 표현한다.)

시.도 별 확진자 발생 추이 비교

3. 현재일 기준 전체 시.도 확진자 발생 현황 - Bar chart

- 확진자 가 많은 지역순으로 차트를 출력한다.

- 현재일은 현재 데이터 중 최신일을 기준으로 한다. 

- 확진자 수 기준 정렬을 하기 위해 `sort_values` 를 사용한다.

- 데이터 행 중 `검역`, `합계` 는 제외 한다.

sorted_df = df[df.dt == df.dt.max()][(df.gubun != '검역') & (df.gubun != '합계')].sort_values('localocccnt', ascending=False)
sorted_df.head(5)

# OUTPUT
createdt	deathcnt	defcnt	gubun	incdec	isolclearcnt	isolingcnt	localocccnt	overflowcnt	qurrate	dt
9	2021-03-15 09:55:40.205	517	26157	경기	161	23355	2285	161	0	197.41	2021-03-15
17	2021-03-15 09:55:40.203	407	30061	서울	112	27526	2128	112	0	308.84	2021-03-15
2	2021-03-15 09:55:40.206	14	2438	경남	31	2185	239	31	0	72.53	2021-03-15
14	2021-03-15 09:55:40.204	56	4726	인천	18	4423	247	18	0	159.87	2021-03-15
8	2021-03-15 09:55:40.205	42	2033	강원	10	1820	171	10	0	131.97	2021-03-15
max_date = sorted_df.dt.iloc[0]
fig = px.bar(
    sorted_df, 
    x=sorted_df.gubun, 
    y=sorted_df.localocccnt, 
    title='시.도', 
    text='localocccnt', 
    color='localocccnt', 
    color_continuous_scale=px.colors.sequential.Redor,
    labels={'localocccnt':'확진자 수'}
)
fig.update_layout(
    title=dict(
        text=f"<b>국내 시.도 확진자 발생 현황</b><br>집계일자: {max_date}", 
        x=0.5,        
    ),
    xaxis_title='시.도', 
    yaxis_title='확진자 수', 
    bargap=0.3,     
)
fig.show()

현재일 기준 전체 시.도 확진자 발생 현황 - Bar chart

4. 현재일 기준 전체 시.도 확진자 발생 현황 - Pie chart

fig = px.pie(
    sorted_df, 
    values='localocccnt', 
    names='gubun', 
    title=f"<b>국내 시.도 확진자 발생 현황</b><br>집계일자: {max_date}",
)
fig.update_traces(textposition='inside', textinfo='percent+label')
fig.show()

현재일 기준 전체 시.도 확진자 발생 현황 - Pie chart

 

2편에 이어서 Plotly의 choropleth_mapbox 차트를 사용하여 공간정보 시각화에 대해 알아본다.

 

2021.03.18 - [데이터 시각화] - 국내 시.도별 코로나 19 확진 정보 - Plotly 공간정보 ( 단계구분도 - choropleth map )

 

국내 시.도별 코로나 19 확진 정보 - Plotly 공간정보 ( 단계구분도 - choropleth map )

국내 코로나19 정보를 Plotly의 공간정보 표현 API인 Choropleth map API를 사용하여 공간정보를 표시해본다. 데이터는 이전 블로그에서 사용한 sorted_df 를 사용할 것이므로 미리 준비하도록 하자. 2021.03.

youngwonhan-family.tistory.com

 

반응형