본문 바로가기

프로젝트들

[All about 따릉이 EDA, 번외] 데이터에 없는 따릉이 대여소의 지역구 데이터 얻기

이번 글에서는, 데이터에는 없는 지역구에 대한 데이터를 얻는 방법을 다룬다.
기본적으로, 대여소명을 활용하고, 다음의 과정을 거친다.

  • 공개된 대여소 데이터로 지역구 데이터를 얻는다.
  • 대여소명과 카카오맵 rest api를 통해 얻는 방법에 대해 다룬다.
    • 이 과정 중에, 카카오맵 rest api 사용법도 간략히 살펴보게 된다.
  • 결과적으로 {대여소 이름: 지역구} 인 dictionary를 얻는다.
    예를 들어 아래와 같다.
{'MCM 본사 직영점 앞': '강남구',
 '교보타워 버스정류장(신논현역 3번출구 후면)': '강남구',
 '논현역 7번출구': '강남구',
 '신영 ROYAL PALACE 앞': '강남구',
 '압구정 한양 3차 아파트': '강남구',
 '압구정역 2번 출구 옆': '강남구',
 '압구정파출소 앞': '강남구',
 '청담동 맥도날드 옆(위치)': '강남구',
 '청담역(우리들병원 앞)': '강남구',
 '학동로 래미안 아파트 앞': '강남구',
 '현대고등학교 건너편': '강남구'}

1. 대여소 정보 데이터를 통해, 대여소의 지역구 데이터 얻기.

첫 번째 방법은, 공공 데이터 포탈에 있는 서울특별시 공공자전거 대여소 정보.csv 를 먼저 활용하는 거다.
여기서 다운받을 수 있다.
나는 19.5.31 에 올라온 데이터를 받았다.

import pandas as pd
rentals = pd.read_csv("data/서울특별시 공공자전거 대여소 정보.csv")
rentals.head()
  구명 대여소ID 대여소번호 대여소명 대여소 주소 거치대수 위도 경도
0 강남구 ST-777 2301 현대고등학교 건너편 서울특별시 강남구 압구정로 134현대고등학교 건너편 10 37.524071 127.021790
1 강남구 ST-787 2302 교보타워 버스정류장(신논현역 3번출구 후면) 서울특별시 강남구 봉은사로 지하 102교보타워 버스정류장 10 37.505581 127.024277
2 강남구 ST-788 2303 논현역 7번출구 서울특별시 강남구 학동로 지하 102논현역 7번출구 15 37.511517 127.021477
3 강남구 ST-789 2304 신영 ROYAL PALACE 앞 서울특별시 강남구 언주로 626신영 ROYAL PALACE앞 10 37.512527 127.035835
4 강남구 ST-790 2305 MCM 본사 직영점 앞 서울특별시 강남구 언주로 734MCM 본사 직영점 앞 10 37.520641 127.034508

여기에는 대여소별 지역구에 대한 정보가 있다.
즉, 현대고등학교 건너편강남구 에 속해있다는 정보를 알 수 있다.

len(set(rentals['대여소명']))
1460

1460개에 대한 정보가 있다고 한다.
19년도 5월 데이터이니, 해당 날짜 기준 대여소만 있고, 아무래도 이전에 폐쇄된 대여소 정보는 없을 것이다.

그리고, 이제 내가 사용할 데이터프레임을 가져와보자.

df = pd.read_pickle('data/dataframes/2017-2018.pkl')
df.head()
  대여일자 대여시간 대여소번호 대여소 대여구분코드 성별 연령대코드 이용건수 운동량 탄소량 이동거리(M) 이동시간(분) 요일
0 2017-01-01 0 230 영등포구청역 1번출구 정기권 F ~10대 1 31.270000 0.39 1680 155 2017 1 1 6
1 2017-01-01 0 315 신한은행 안국역지점 옆 정기권 F 20대 1 47.320000 0.55 2390 15 2017 1 1 6
2 2017-01-01 0 328 탑골공원 앞 정기권 F 20대 1 57.919998 0.52 2250 13 2017 1 1 6
3 2017-01-01 0 175 홍연2교옆 정기권 F 20대 1 133.289993 1.53 6600 43 2017 1 1 6
4 2017-01-01 0 817 삼각지역 4번출구 앞 정기권 F 20대 1 33.880001 0.32 1380 8 2017 1 1 6

내 데이터프레임에는, 지역구에 대한 정보가 없다. 근데 나는 지역구를 여기다가 추가하고싶다. 이게 문제의 시작이다.

이 데이터프레임에는 대여소 종류가 몇 개인지 확인해보면,

len(set(df['대여소']))
1514

1514개란다. 이 데이터프레임은 2017-2018년의 데이터를 담고있는데, 19년도 5월 데이터의 대여소개수가 1460개인 것과 비교하면, 이 데이터프레임에는 최소 54개 정도의 대여소가 없다.

위에서 로드한 대여소 데이터를 데이터프레임에 대여소 이름을 기준으로 매핑하면, 얼마만큼의 데이터가 손실되는지 보자.

before = df.shape[0]
after = pd.merge(df, rentals[['대여소번호', '구명']], on='대여소번호', how='inner').shape[0]

print("매핑하기 이전:", before)
print("매핑하기 이후:", after)
매핑하기 이전: 10183216
매핑하기 이후: 5909173
(before - after) / before * 100
41.971445955776645

약 40% 의 손실이 나니.. 그냥 무시하고 갈 수가 없다.

이에, 대여소 데이터 뿐이 아니라, 별도의 써드파티 api 를 이용하여 지역구 데이터를 얻어오기로 해보자.


2. 카카오맵 rest api 를 이용해서 대여소의 지역구 얻어오기

아이디어는 이렇다.

  1. 우리는 데이터프레임에서 각 대여소의 지역구를 얻고 싶은데, 현재 지역구가 없다.
  2. 대여소 데이터에 일부 대여소의 지역구 정보가 있지만, 없는 것도 많다.
  3. 하지만, 대여소 이름을 보면 어느정도 지역을 추론할 수 있는데, 대여소 이름을 카카오 지도에서 검색하여 주소를 알아낼 수 있다.
  4. 해당 주소에서 지역구 값만 가져오면 된다.

얼마나 잘될지는 모르겠지만, 일단 해보자.

2.1) Kakao rest api 사용법 알아보기

kakao developers 공식 docu 에 있는 내용을 토대로 먼저, 카카오 rest api 사용해보자.
아래를 보기 전에, 아래와 같은 사항이 준비되어있어야 한다.

  1. kakao developers 에서 계정 생성 후, 내 어플리케이션 만들기.
  2. 내 어플리케이션의 REST API key 를 찾을 것.
    • 애플리케이션 - 설정 - 일반에 가면 볼 수 있다.
my_rest_api_key = "여기에 자신의 어플리케이션 REST API key를 입력해준다."
import requests

url = "https://dapi.kakao.com/v2/local/search/keyword.json"
headers = {"Authorization": "KakaoAK " + my_rest_api_key}
params = {"query": "영등포구청역 1번출구"}
res = requests.get(url=url, params=params, headers=headers)

import pprint as pp
pp.pprint(res.json())
{'documents': [{'address_name': '서울 영등포구 당산동3가 375',
                'category_group_code': '',
                'category_group_name': '',
                'category_name': '스포츠,레저 > 자전거,싸이클 > 자전거대여소',
                'distance': '',
                'id': '1911311374',
                'phone': '',
                'place_name': '영등포구청역 1번출구 대여소',
                'place_url': 'http://place.map.kakao.com/1911311374',
                'road_address_name': '서울 영등포구 당산로 111-2',
                'x': '126.896217493004',
                'y': '37.5246335974008'},
               {'address_name': '서울 영등포구 당산동3가',
                'category_group_code': '',
                'category_group_name': '',
                'category_name': '교통,수송 > 지하철,전철',
                'distance': '',
                'id': '10642080',
                'phone': '',
                'place_name': '영등포구청역 2호선 1번출구',
                'place_url': 'http://place.map.kakao.com/10642080',
                'road_address_name': '',
                'x': '126.896285209314',
                'y': '37.5247489851369'}],
 'meta': {'is_end': True,
          'pageable_count': 2,
          'same_name': {'keyword': '영등포구청역 1번출구',
                        'region': [],
                        'selected_region': ''},
          'total_count': 2}}

보면, res['documents']list 형식으로, 검색결과를 받아오는 것을 알 수 있다.

검색결과 데이터 중 가장 첫 번째의 결과의 adderss_name 에 지역구가 들어있을 것이다.

result = res.json()['documents'][0]
result['address_name']
'서울 영등포구 당산동3가 375'

지역구는 아래와 같이 추출할 수 있다.

result['address_name'].split()[1]
'영등포구'

2.2) {대여소 이름: 지역구} 를 반환하는 함수 만들기.

def get_region_from_rental_number(rental_names, default_data=False, verbose=True, process_unit=300):
    total = len(rental_names)
    print("total_size :", total)
    print("-------------------")

    d = {}

    # 먼저 대여소 데이터에서 얻을 수 있는 데이터는 얻자.
    if default_data:
        try: 
            rentals = pd.read_csv("data/서울특별시 공공자전거 대여소 정보.csv")

            for i, row in rentals.iterrows():
                d[row['대여소명']] = row['구명']
        except e:
            print(e)
            print("'서울특별시 공공자전거 대여소 정보.csv' 를 'data/' 내에 넣어주세요.")

    # 대여소 이름을 카카오맵 rest api query에 보내, 지역구를 받자.
    fail, success = 0, 0
    fail_list = set()
    for i, rental_name in enumerate(rental_names, 1):

        # 진행속도 출력
        if verbose and i % process_unit == 0:
            print("%d / %d (%d%%) processed.." %(i, total, float(i)/total*100))

        # 이미 있으면 스킵.
        if rental_name in d:
            continue

        # api 에 쿼리 날리기.
        params = {"query": "서울 " + rental_name}
        res = requests.get(url=url, params=params, headers=headers)
        result = res.json()['documents']

        if result:
            region = result[0]['address_name'].split()[1]
            d[rental_name] = region
            success += 1
        else:
            # 실패했을 경우, 다음으로 한번 더 시도해본다.
            if len(rental_name.split()) >= 2:
                params = {"query": "서울 " + "".join(rental_name.split()[:-1])}
                res = requests.get(url=url, params=params, headers=headers)
                result = res.json()['documents']

                if result:
                    region = result[0]['address_name'].split()[1]
                    d[rental_name] = region
                    success += 1
                    continue

            # 그래도 실패한 경우.
            fail_list.add(rental_name)
            fail += 1         

    print("sucess: %d(%.1f%%)" %(success, float(success)/(fail+success)*100))
    print("fail: %d(%.1f%%)" %(fail, float(fail)/(fail+success)*100))

    # dictionary, fail_list 반환
    return d, fail_list

테스트 해보자.

rental_names = df['대여소'][:10]
get_region_from_rental_number(rental_names)
total_size : 10
-------------------
sucess: 10(100.0%)
fail: 0(0.0%)

({' 영등포구청역 1번출구': '영등포구',
  ' 신한은행 안국역지점 옆': '종로구',
  ' 탑골공원 앞': '종로구',
  ' 홍연2교옆': '서대문구',
  ' 삼각지역 4번출구 앞': '용산구',
  ' 연신내역 5번출구150M 아래': '은평구',
  ' 연신내역 3번출구 인근': '은평구',
  ' 서울역사박물관 앞': '종로구',
  ' 낙원상가 옆': '종로구',
  ' 종로3가역 15번출구 앞': '종로구'},
 set())

잘 뽑히는걸 확인할 수 있다.

3. 함수 사용하여 {대여소: 지역구} 만들고, 피클라이즈 하기.

이제 만들어놓은 함수로, 지역구 데이터를 데이터프레임에 추가하자.

rental_names = set(df['대여소'].dropna())

region, fail_list = get_region_from_rental_number(rental_names, default_data=True)
total_size : 1513
-------------------
300 / 1513 (19%) processed..
600 / 1513 (39%) processed..
900 / 1513 (59%) processed..
1200 / 1513 (79%) processed..
1500 / 1513 (99%) processed..
sucess: 1481(99.6%)
fail: 6(0.4%)
fail_list
{' 구로구배드민턴실내체육관 앞',
 ' 구일우성(아) 육교 밑',
 ' 센서텍㈜',
 '8.삼호@ 2동 ( 간선도로)',
 '위트콤공장',
 '이동정비'}

일부 실패한 애들은, 카카오맵(https://map.kakao.com/) 에서 직접 찾아서 입력해주자.

region[' 구로구배드민턴실내체육관 앞'] = '구로구'
region[' 구일우성(아) 육교 밑'] = '구로구'
region[' 센서텍㈜'] = '강서구'
region['8.삼호@ 2동 ( 간선도로)'] = '강남구'
region['위트콤공장'] = '서초구'

region 을 살짝 살펴보면, 아래와 같은 포맷이다.

i = 0
region_temp = {}
for k, v in region.items():
    region_temp[k] = v
    i += 1
    if i > 10:
        break
pp.pprint(region_temp)
{'MCM 본사 직영점 앞': '강남구',
 '교보타워 버스정류장(신논현역 3번출구 후면)': '강남구',
 '논현역 7번출구': '강남구',
 '신영 ROYAL PALACE 앞': '강남구',
 '압구정 한양 3차 아파트': '강남구',
 '압구정역 2번 출구 옆': '강남구',
 '압구정파출소 앞': '강남구',
 '청담동 맥도날드 옆(위치)': '강남구',
 '청담역(우리들병원 앞)': '강남구',
 '학동로 래미안 아파트 앞': '강남구',
 '현대고등학교 건너편': '강남구'}
import pickle

with open('data/rental_region.pkl', 'wb') as f:
    pickle.dump(region, f)

이제 pickled 된 rental_region.pkl 을 필요한 데이터프레임에서 활용하면 된다.


이 글에 대한 노트북은 여기서 다운받을 수 있다.