YOGYUI

공공데이터포털::코로나19 감염현황 데이터 조회 (REST API) 본문

Data Analysis/Data Engineering

공공데이터포털::코로나19 감염현황 데이터 조회 (REST API)

요겨 2021. 2. 22. 11:55
반응형

공공데이터포털에서 국내 코로나19 감염현황에 대한 데이터를 얻어보자 (OpenAPI 실습)

 

데이터 타이틀은 "보건복지부_코로나 19 감염_현황"이고 URL은 아래 링크를 참고

www.data.go.kr/data/15043376/openapi.do

메타데이터 정보

RESTful API로 호출하여 XML 포맷으로 데이터를 받아볼 수 있을 것 같다

 

1. 데이터 활용신청

활용신청

로그인 후 페이지 내 "활용신청" 버튼을 클릭 후 개발계정 신청서를 작성하자

OpenAPI 개발계정 활용신청서 작성

 

승인되면 다음과 같이 API가 활용가능한 것으로 디스플레이된다
(원래 사용하는 계정은 신청/활용건수가 너무 많아 포스팅을 위해 계정을 새로 하나 만들었다...)

활용 승인 완료

개발계정 상세보기로 가면 실제 API에서 사용해야 할 Key (일반 인증키)를 얻을 수 있다

Key값 (일반인증키)는 API 호출 시 필요하므로 메모장같은데 복사해두자

개발계정 상세보기

중요: 처리상태가 승인이라고 해서 API를 바로 사용할 수 있는게 아니더라... (SERVICE KEY IS NOT REGISTERED ERROR 응답만 주구장창 볼 수 있음)

API Key 미등록 에러 반환

공공데이터포털 공식 안내문에는 서버 동기화까지 1시간 정도가 소요된다고 하는데 (일부 데이터셋은 24시간 소요), 실상은 그렇지가 못한 것 같다

데이터포털 사이트 Q&A 게시판보면 Key Not Registered Error 관련 문의글이 넘쳐흐른다

일단 승인완료 후에 괜히 바로 들이대지 말고 맘편하게 하루 이상 기다린 뒤 개발을 시작해보도록 하자

 

만약 해당 데이터를 본인의 어플리케이션(웹 혹은 모바일 앱)에서 활용하고자 할 경우 '운영계정 신청'을 해줘야 한다

운영계정 신청

운영계정 신청 시 해당 데이터가 사용될 어플리케이션에서 어떤 방식으로 활용되는지에 대한 개요를 제출해야 한다 (사이트 URL, 웹/앱 스크린샷 및 description)

단순히 활용 계정으로만 신청할 경우 일일 트래픽은 1000건으로 제한되긴 하지만 간단하게 실습할 때는 문제없다 (운영계정으로 등록하면 일일트래픽은 10만건까지 확대)

 

참고문서 (word파일)을 보면 API에 대한 좀 더 상세한 정보를 확인할 수 있다

(다행히도 SSL 암호화는 안쓰네...)

API 상세 정보 문서 - 1
API 상세 정보 문서 - 2

만약 해당 API로 어플리케이션을 만들고자 할 경우, 평균 응답 시간 및 초당 최대 TR 등을 고려해야한다

2. Test

URL이랑 API 키가 있으니 API 호출 준비는 완료되었다
서비스 URL: http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19InfStateJson

API 호출 시 필요한 파라미터는 데이터 소개 페이지에 기재되어 있다

API 호출 파라미터

필수 인자는 발급받는 서비스 키 (ServiceKey)

 

Python으로 URL Request해보자 (HTTP GET method, requests 라이브러리 활용)

인증키가 UTF-8 URL 인코딩된 결과로 주어질 경우, 코드에서는 해당 문자열을 ASCii 문자열로 디코딩 후 파라미터로 입력해줘야 제대로 된 응답을 받을 수 있다

URL 관련 라이브러리에서 제공하는 unquote는 %xx 이스케이프 문자로 인코딩(UTF-8)된 문자를 단일 문자로 대체해준다 (예: '1%2B2%3D3' → '1+2=3')

(requests 라이브러리나, urllib라이브러리의 parse 모듈이나 구현 결과는 동일하다)

import requests
from urllib import parse

url = "http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19InfStateJson"
api_key_utf8 = "Your API Key form data.go.kr"
api_key_decode = requests.utils.unquote(api_key_utf8, encoding='utf-8')
# api_key_decode = parse.unquote(api_key_utf8, encoding='utf-8')

params={
    "ServiceKey": api_key_decode,
    "startCreateDt": 20200221,
    "endCreateDt": 20200221
}

response = requests.get(url, params=params)

테스트삼아 2021년 2월 21일 하루치의 데이터만 요청해봤다 (output text는 보기좋게 수정)

In [1]: print(response.text)
Out[1]:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
  <header>
    <resultCode>00</resultCode>
    <resultMsg>NORMAL SERVICE.</resultMsg>
  </header>
  <body>
    <items>
      <item>
        <accDefRate>1.3725500095</accDefRate>
        <accExamCnt>6411340</accExamCnt>
        <accExamCompCnt>6337984</accExamCompCnt>
        <careCnt>7919</careCnt>
        <clearCnt>77516</clearCnt>
        <createDt>2021-02-21 09:34:28.285</createDt>
        <deathCnt>1557</deathCnt>
        <decideCnt>86992</decideCnt>
        <examCnt>73356</examCnt>
        <resutlNegCnt>6250992</resutlNegCnt>
        <seq>426</seq>
        <stateDt>20210221</stateDt>
        <stateTime>00:00</stateTime>
        <updateDt>null</updateDt>
      </item>
    </items>
    <numOfRows>10</numOfRows>
    <pageNo>1</pageNo>
    <totalCount>1</totalCount>
  </body>
</response>

별다른 문제가 없다면 header의 resultCode는 00, resultMsg는 NORMAL SERVICE. 값을 가진다

body의 items 태그 안에 요청한 수만큼의 데이터가 하루 단위로 묶여있다

item 내부 각 태그들의 의미는 데이터포털에서 제공하는 명세서 (워드파일)를 참고

응답메시지 명세

3. Data Parsing

XML format으로 얻은 응답 문자열을 beautifulsoup 라이브러리를 사용해 파싱해보자

(일주일치 데이터를 얻은 뒤 각 아이템들의 속성 및 값들을 딕셔너리 형태로 변환한 뒤, pandas DataFrame으로 하나의 데이터셋으로 변환)

속성별로 데이터형식이 정수형 / 실수형 / Date형/ DateTime형이 나뉘므로 각 데이터별 형식을 담은 딕셔너리를 사전 정의해두고 iteration하면 깔끔하게 구현할 수 있다

import requests
import pandas as pd
from bs4 import BeautifulSoup
from datetime import date, datetime

def getCovid19Info(start_date: date, end_date: date):
    url = "http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19InfStateJson"
    api_key_utf8 = "Your API Key from data.go.kr"
    api_key_decode = requests.utils.unquote(api_key_utf8, encoding='utf-8')

    params={
        "ServiceKey": api_key_decode,
        "startCreateDt": int('{:04d}{:02d}{:02d}'.format(start_date.year, start_date.month, start_date.day)),
        "endCreateDt": int('{:04d}{:02d}{:02d}'.format(end_date.year, end_date.month, end_date.day)),
    }

    response = requests.get(url, params=params)
    return BeautifulSoup(response.text, "lxml")

convert_method = {
    'accdefrate': float,
    'accexamcnt': int,
    'accexamcompcnt': int,
    'carecnt': int,
    'clearcnt': int,
    'createdt': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S.%f'),
    'deathcnt': int,
    'decidecnt': int,
    'examcnt': int,
    'resutlnegcnt': int,
    'seq': int,
    'statedt': lambda x: datetime.strptime(x, '%Y%m%d'),
    'statetime': str,
    'updatedt': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S')
}


temp = getCovid19Info(date(2021, 2, 15), date(2021, 2, 21))
items = temp.find('items')
item_list = []
for item in items:
    item_dict = {}
    for tag in list(item):
        try:
            item_dict[tag.name] = convert_method[tag.name](tag.text)
        except Exception:
            item_dict[tag.name] = None
    item_list.append(item_dict)
df = pd.DataFrame(item_list)
In [2]: df
Out[2]: 
   accdefrate  accexamcnt  accexamcompcnt  ...    statedt  statetime updatedt
0    1.372550     6411340         6337984  ... 2021-02-21      00:00     None
1    1.371068     6390631         6314494  ... 2021-02-20      00:00     None
2    1.372585     6345992         6274876  ... 2021-02-19      00:00     None
3    1.372917     6303214         6232494  ... 2021-02-18      00:00     None
4    1.372719     6260567         6188157  ... 2021-02-17      00:00     None
5    1.374134     6213490         6136593  ... 2021-02-16      00:00     None
6    1.378793     6162859         6082713  ... 2021-02-15      00:00     None

[7 rows x 14 columns]

검사기준일을 기준으로 내림차순으로 정렬되어있는 것을 알 수 있다

4. 전체 데이터 불러오기

DataFrame으로 변환하는 구문을 함수화하고, 전체 데이터를 불러오는 함수도 따로 만들어보자

(2020년 1월 31일이 최초로 기록된 데이터이니, 2020년 1월 1일을 시작 날짜로 잡고 오늘 날짜까지 데이터를 불러오도록 구현)

import requests
import pandas as pd
from typing import Union
from bs4 import BeautifulSoup
from datetime import date, datetime

def getCovid19Info(start_date: Union[date, datetime], end_date: Union[date, datetime]):
    url = "http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19InfStateJson"
    api_key_utf8 = "Your API Key from data.go.kr"
    api_key_decode = requests.utils.unquote(api_key_utf8, encoding='utf-8')

    params={
        "ServiceKey": api_key_decode,
        "startCreateDt": int('{:04d}{:02d}{:02d}'.format(start_date.year, start_date.month, start_date.day)),
        "endCreateDt": int('{:04d}{:02d}{:02d}'.format(end_date.year, end_date.month, end_date.day)),
    }

    response = requests.get(url, params=params)
    elapsed_us = response.elapsed.microseconds
    print('Request Done, Elapsed: {} seconds'.format(elapsed_us / 1e6))
    return BeautifulSoup(response.text, "lxml")

def getCovid19DataFrame(start_date: Union[date, datetime], end_date: Union[date, datetime]) -> pd.DataFrame:    
    convert_method = {
        'accdefrate': float,
        'accexamcnt': int,
        'accexamcompcnt': int,
        'carecnt': int,
        'clearcnt': int,
        'createdt': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S.%f'),
        'deathcnt': int,
        'decidecnt': int,
        'examcnt': int,
        'resutlnegcnt': int,
        'seq': int,
        'statedt': lambda x: datetime.strptime(x, '%Y%m%d'),
        'statetime': str,
        'updatedt': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S')
    }
    
    temp = getCovid19Info(start_date, end_date)
    items = temp.find('items')
    item_list = []
    for item in items:
        item_dict = {}
        for tag in list(item):
            try:
                item_dict[tag.name] = convert_method[tag.name](tag.text)
            except Exception:
                item_dict[tag.name] = None
        item_list.append(item_dict)
    df = pd.DataFrame(item_list)
    return df

def getAllCovid19Data() -> pd.DataFrame:
    now = datetime.now()
    df = getCovid19DataFrame(date(2019, 1, 1), now)
    print("Today: {}\nLoaded {} Records".format(now.strftime('%Y-%m-%d'), df.shape[0]))
    return df
In [3]: df = getAllCovid19Data()
Request Done, Elapsed: 0.329607 second
Today: 2021-02-23
Loaded 424 Records

>> 2021년 2월 23일 기준으로 총 424 레코드 존재

>> 전체 데이터 불러오는데 330msec 정도 시간이 소요된다 (인터넷 환경에 따라 변동)

 

데이터프레임을 탐색해보면, 코로나 초기 (2020년 초반)에는 하루에도 여러번 데이터가 업데이트된 것을 알 수 있다 (~2020년 3월 1일까지의 데이터를 보면 statetime을 보면 하루 데이터가 오전 9시, 오후 4시 데이터로 두 차례로 나누어 집계)

In [4]: df[-60:][['createdt', 'decidecnt', 'statetime']]
Out[4]:
                   createdt  decidecnt statetime
364 2020-03-01 17:41:45.450       3736     16:00
365 2020-03-01 10:06:32.320       3526     09:00
366 2020-02-29 17:16:20.200       3150     16:00
367 2020-02-29 10:14:50.500       2931     09:00
368 2020-02-28 17:01:59.590       2337     16:00
369 2020-02-28 10:32:34.340       2022     09:00
370 2020-02-27 17:32:06.060       1766     16:00
371 2020-02-27 10:19:35.350       1595     09:00
372 2020-02-26 17:00:43.430       1261     16:00
373 2020-02-26 10:02:05.050       1146     09:00
374 2020-02-25 17:02:03.030        977     16:00
375 2020-02-25 10:00:40.400        893     09:00
376 2020-02-24 17:03:50.500        833     16:00
377 2020-02-24 10:17:36.360        763     09:00
378 2020-02-23 17:05:12.120        602     16:00
379 2020-02-23 10:22:25.250        556     09:00
380 2020-02-22 17:00:28.280        433     16:00
381 2020-02-22 09:59:46.460        346     09:00
382 2020-02-21 17:13:21.210        204     16:00
383 2020-02-21 17:12:33.330        204     16:00
384 2020-02-21 09:58:51.510        156     09:00
385 2020-02-20 17:12:33.330        104     16:00
386 2020-02-20 10:01:31.310         82     09:00
387 2020-02-19 17:00:13.130         51     16:00
388 2020-02-19 09:53:08.080         46     09:00
389 2020-02-18 16:42:00.000         31     16:00
390 2020-02-18 10:05:31.310         31     09:00
391 2020-02-18 10:04:38.380         31     09:00
392 2020-02-17 16:50:47.470         30     16:00
393 2020-02-17 09:53:13.130         30     09:00
394 2020-02-16 16:57:02.020         29     16:00
395 2020-02-16 10:04:04.040         29     09:00
396 2020-02-15 17:00:21.210         28     16:00
397 2020-02-15 10:00:11.110         28     09:00
398 2020-02-14 17:16:19.190         28     16:00
399 2020-02-14 09:50:49.490         28     09:00
400 2020-02-13 17:09:07.070         28     16:00
401 2020-02-13 09:50:45.450         28     09:00
402 2020-02-12 16:56:47.470         28     16:00
403 2020-02-12 10:05:49.490         28     09:00
404 2020-02-11 17:09:25.250         28     16:00
405 2020-02-11 09:50:10.100         28     09:00
406 2020-02-10 17:03:49.490         27     16:00
407 2020-02-10 09:56:41.410         27     09:00
408 2020-02-09 16:59:21.210         27     16:00
409 2020-02-09 09:58:22.220         25     09:00
410 2020-02-08 17:01:34.340         24     16:00
411 2020-02-08 17:01:12.120         24     16:00
412 2020-02-08 16:48:05.050         24     16:00
413 2020-02-08 16:00:22.220         24     16:00
414 2020-02-08 10:09:34.340         24     09:00
415 2020-02-07 17:20:45.450         24     16:00
416 2020-02-07 09:53:27.270         24     09:00
417 2020-02-06 09:09:49.490         23     09:00
418 2020-02-05 20:05:40.400         19     19:00
419 2020-02-04 23:56:31.310         18     09:00
420 2020-02-03 21:26:59.590          0     00:00
421 2020-02-03 14:41:17.170         15     09:00
422 2020-02-03 12:22:49.490          2     09:00
423 2020-01-31 17:47:33.330          0     18:00

그리고 2020년 3월 2일부터는 누적확진률(accdefrate), 누적 검사 수(accexamcnt), 누적 검사 완료수(accexamcompcnt), 치료 중 환자 수(carecnt), 결과 음성 수(resultnegcnt)가 집계되었음을 알 수 있다

In [5]: df[-65:-55][['createdt', 'accdefrate', 'accexamcnt', 'accexamcompcnt', 'carecnt', 'resutlnegcnt']]
Out[5]:
                   createdt  accdefrate  ...  carecnt  resutlnegcnt
359 2020-03-06 12:55:44.440    4.397235  ...   6134.0      136624.0
360 2020-03-05 10:15:12.120    4.622748  ...   5643.0      118965.0
361 2020-03-04 10:21:44.440    4.919986  ...   5255.0      102965.0
362 2020-03-03 10:46:53.530    5.329140  ...   4750.0       85484.0
363 2020-03-02 10:52:38.380    5.557315  ...   4159.0       71580.0
364 2020-03-01 17:41:45.450         NaN  ...      NaN           NaN
365 2020-03-01 10:06:32.320         NaN  ...      NaN           NaN
366 2020-02-29 17:16:20.200         NaN  ...      NaN           NaN
367 2020-02-29 10:14:50.500         NaN  ...      NaN           NaN
368 2020-02-28 17:01:59.590         NaN  ...      NaN           NaN

[10 rows x 6 columns]

누적확진자를 간단히 plot해보자

import matplotlib.pyplot as plt

plt.plot(df['createdt'], df['decidecnt'])

누적확진자 Plot

2020년 5월 근방에 삐죽 튀어나온 glitch가 하나 보인다

In [6]: df[303:311][['createdt', 'decidecnt']]
Out[6]:
                   createdt  decidecnt
303 2020-04-27 10:12:27.270      10738
304 2020-04-26 10:12:24.240      10728
305 2020-04-25 10:58:08.080      10718
306 2020-04-25 10:57:05.050      10718
307 2020-04-25 10:39:03.030      12801
308 2020-04-24 10:11:08.080      10708
309 2020-04-23 10:11:05.050      10702
310 2020-04-22 10:20:28.280      10694

해당 데이터는 2020년 4월 25일 오전 10시 39분에 기록된 데이터 (12801명)로, 이후 오전 10시 58분에 10718명으로 수정되었다 (4월 25일이 무슨 날이었는지는 모르겠는데, 3번이나 기록된 걸 보면 뭔가 전산상의 오류가 있었던 듯? 검색해봐도 뭔 일이 있었는지는 찾을 수가 없네...)

 

간단한 EDA 결과, 동일한 날짜에도 2차례 이상 레코드가 업데이트된 사례들이 있으므로, 동일 날짜에 대해서는 타임스탬프가 가장 최후인 데이터를 사용해야 할 것으로 보인다

 

5. 일일확진자 계산 (데이터 전처리)

간단한 데이터 전처리를 해보자 (누적확진자, column 이름은 decidecnt)

앞서 보았듯이 동일한 날짜에 대해서는 최종 업데이트된 값을 사용해야 하므로 다음과 같이 누적확진자 데이터를 우선 전처리해줘야 한다

df = getAllCovid19Data()

생성일자에서 년-월-일만 따로 문자열로 치환한 시리즈를 하나 만들자

date_str_series = df['createdt'].dt.strftime('%Y-%m-%d')
In [7]: date_str_series
Out[7]:
0      2021-02-23
1      2021-02-22
2      2021-02-21
3      2021-02-20
4      2021-02-19
   
419    2020-02-04
420    2020-02-03
421    2020-02-03
422    2020-02-03
423    2020-01-31
Name: createdt, Length: 424, dtype: object

pandas Series의 duplicated 메서드를 활용하면 0-index부터 순차적으로 시리즈 내에 중복된 데이터가 있는지 여부를 boolean으로 변환한 Series를 반환한다

In [8]: date_str_series.duplicated()
Out[8]:
0      False
1      False
2      False
3      False
4      False
 
419    False
420    False
421     True
422     True
423    False
Name: createdt, Length: 424, dtype: bool

이를 사용해서 중복된 적이 있는 레코드의 인덱스를 추출할 수 있다

duplicated_idx = date_str_series[date_str_series.duplicated()].index
In [9]: duplicated_idx
Out[9]:
Int64Index([ 67, 191, 306, 307, 331, 341, 365, 367, 369, 371, 373, 375, 377,
            379, 381, 383, 384, 386, 388, 390, 391, 393, 395, 397, 399, 401,
            403, 405, 407, 409, 411, 412, 413, 414, 416, 421, 422],
           dtype='int64')

예를 들어 duplicated_idx 안에 포함된 306, 307번 인덱스는 앞서 살펴본 2020년 4월 25일에 해당하는 데이터를 가리킨다

In [10]: df.iloc[304:309][['createdt', 'decidecnt']]
Out[10]:
                   createdt  decidecnt
304 2020-04-26 10:12:24.240      10728
305 2020-04-25 10:58:08.080      10718
306 2020-04-25 10:57:05.050      10718
307 2020-04-25 10:39:03.030      12801
308 2020-04-24 10:11:08.080      10708

305번 인덱스가 4월 25일에 측정된 '마지막' 레코드이며 306, 307번은 4월 25일에 측정된 첫번째, 두번째 레코드이며 갱신되지 않은 데이터라서 무시하면 된다

(이는 데이터가 측정 시간에 대해 내림차순으로 정렬되어 있기 때문에 나타나는 결과다)

따라서, duplicated_idx에 포함된 인덱스들은 데이터 전처리에서 무시해도 되는 결과로 생각할 수 있다

 

인덱스를 제외한 데이터프레임을 하나 따로 생성하자

df_process = df[~df.index.isin(duplicated_idx)]
In [11]: df.shape
Out[11]: (424, 14)

In [12]: df_process.shape
Out[12]: (387, 14)

In [13]: df_process.iloc[301:306]
Out[13]: 
     accdefrate  accexamcnt  accexamcompcnt  ...    statedt  statetime updatedt
303    1.811510    601660.0        592765.0  ... 2020-04-27      00:00     None
304    1.820508    598285.0        589286.0  ... 2020-04-26      00:00     None
305    1.829316    595161.0        585902.0  ... 2020-04-25      00:00     None
308    1.846462    589520.0        579920.0  ... 2020-04-24      00:00     None
309    1.865006    583971.0        573832.0  ... 2020-04-23      00:00     None

[5 rows x 14 columns]

중복된 데이터 중 일부인 306, 307번 인덱스 레코드가 제거됐다

 

이제 전처리된 데이터프레임의 누적확진자(decidecnt) 데이터를 통해 일일 신규 확진자를 계산해보자

(1-shift 시킨 ndarray와 뺄셈 연산만 해주면 끝)

import numy as np

v = df_process['decidecnt'].values
df_process['decide_new_daily'] = np.append(v[:-1] - v[1:], 0)
In [14]: df_process[['createdt', 'decide_new_daily']]
Out[14]:
                   createdt  decide_new_daily
0   2021-02-23 09:31:45.903               357
1   2021-02-22 09:30:56.472               332
2   2021-02-21 09:34:28.285               416
3   2021-02-20 09:40:36.883               448
4   2021-02-19 09:33:30.840               561
..                      ...               ...
417 2020-02-06 09:09:49.490                 4
418 2020-02-05 20:05:40.400                 1
419 2020-02-04 23:56:31.310                18
420 2020-02-03 21:26:59.590                 0
423 2020-01-31 17:47:33.330                 0

[387 rows x 2 columns]

In [15]: df_process.iloc[:10]['decide_new_daily']
Out[15]:
0    357
1    332
2    416
3    448
4    561
5    621
6    621
7    457
8    343
9    326
Name: decide_new_daily, dtype: int64

코로나 공식 웹페이지의 일일 신규확진자 자료와 비교하면 제대로 계산된 것을 알 수 있다

신규 확진자 수를 간단하게 플롯해보자

plt.plot(df_process['createdt'], df_process['decide_new_daily'])

일일 신규 확진자 Plot

으잉? 음수 값을 가진 데이터가 보인다

데이터를 다시 살펴보니 Time Stamp가 역전되어있는 레코드가 하나 있네...

In [16]: df_process.iloc[74:79][['statedt', 'decidecnt', 'decide_new_daily']]
Out[16]:
      statedt  decidecnt  decide_new_daily
75 2020-12-11      40786               688
76 2020-12-10      40098              1352
77 2020-12-08      38746              -686
78 2020-12-09      39432              1271
79 2020-12-07      38161               615

2020년 12월 8일과 2020년 12월 9일 레코드의 순서가 바뀌었다

따라서 데이터 전처리시에 timestamp 기반으로 한 번 sorting해주자

df = getAllCovid19Data()
date_str_series = df['createdt'].dt.strftime('%Y-%m-%d')
duplicated_idx = date_str_series[date_str_series.duplicated()].index
df_process = df[~df.index.isin(duplicated_idx)]
df_process = df_process.sort_values(by=['statedt'], ascending=False)

v = df_process['decidecnt'].values
df_process.loc[:, ('decide_new_daily')] = np.append(v[:-1] - v[1:], 0)

plt.plot(df_process['createdt'], df_process['decide_new_daily'])
In [17]: df_process.iloc[74:79][['statedt', 'decidecnt', 'decide_new_daily']]
Out[17]:
      statedt  decidecnt  decide_new_daily
75 2020-12-11      40786               688
76 2020-12-10      40098               666
78 2020-12-09      39432               686
77 2020-12-08      38746               585
79 2020-12-07      38161               615

수정 후 일일 신규 확진자 Plot

기초적인 수준의 데이터 탐색 및 데이터 전처리까지 해봤다

(포스트의 의도는 공공데이터포털에서 REST API로 데이터를 얻어보는 거였기 때문에 이정도로 마무리하자...)

 

[최종 코드]

import requests
import numpy as np
import pandas as pd
from typing import Union
from bs4 import BeautifulSoup
from datetime import date, datetime

def getCovid19Info(start_date: Union[date, datetime], end_date: Union[date, datetime]):
    url = "http://openapi.data.go.kr/openapi/service/rest/Covid19/getCovid19InfStateJson"
    api_key_utf8 = "Your API Key from data.go.kr"
    api_key_decode = requests.utils.unquote(api_key_utf8, encoding='utf-8')

    params={
        "ServiceKey": api_key_decode,
        "startCreateDt": int('{:04d}{:02d}{:02d}'.format(start_date.year, start_date.month, start_date.day)),
        "endCreateDt": int('{:04d}{:02d}{:02d}'.format(end_date.year, end_date.month, end_date.day)),
    }

    response = requests.get(url, params=params)
    elapsed_us = response.elapsed.microseconds
    print('Request Done, Elapsed: {} seconds'.format(elapsed_us / 1e6))
    
    return BeautifulSoup(response.text, "lxml")

def getCovid19DataFrame(start_date: Union[date, datetime], end_date: Union[date, datetime]) -> pd.DataFrame:    
    convert_method = {
        'accdefrate': float,
        'accexamcnt': int,
        'accexamcompcnt': int,
        'carecnt': int,
        'clearcnt': int,
        'createdt': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S.%f'),
        'deathcnt': int,
        'decidecnt': int,
        'examcnt': int,
        'resutlnegcnt': int,
        'seq': int,
        'statedt': lambda x: datetime.strptime(x, '%Y%m%d'),
        'statetime': str,
        'updatedt': lambda x: datetime.strptime(x, '%Y-%m-%d %H:%M:%S')
    }
    
    temp = getCovid19Info(start_date, end_date)
    items = temp.find('items')
    item_list = []
    for item in items:
        item_dict = {}
        for tag in list(item):
            try:
                item_dict[tag.name] = convert_method[tag.name](tag.text)
            except Exception:
                item_dict[tag.name] = None
        item_list.append(item_dict)
    df = pd.DataFrame(item_list)
    
    return df

def getAllCovid19Data(with_daily_decide: bool = True) -> pd.DataFrame:
    now = datetime.now()
    df = getCovid19DataFrame(date(2019, 1, 1), now)
    print("Today: {}\nLoaded {} Records".format(now.strftime('%Y-%m-%d'), df.shape[0]))
    
    if with_daily_decide:
        date_str_series = df['createdt'].dt.strftime('%Y-%m-%d')
        duplicated_idx = date_str_series[date_str_series.duplicated()].index
        df = df[~df.index.isin(duplicated_idx)]
        df = df.sort_values(by=['statedt'], ascending=False)
        v = df['decidecnt'].values
        df.loc[:, ('decide_new_daily')] = np.append(v[:-1] - v[1:], 0)
        
    return df

 

끝~!

반응형