본문 바로가기

전체 글

앞선 단계에서 데이터에 대한 수집과 간단한 처리 부분은 완료했다. 이제 이번 포스트에서는 본 프로젝트의 핵심인 H3의 사용의 시작과 쓰인 함수에 대한 소개를 하고자 한다.

 

H3는 Uber에서 개발한 육각형 그리드 시스템으로 파이썬으로 쉽게 그리드 시스템을 다룰 수 있다.

 

카일님의 블로그의 말을 빌리자면 육각형 그리드의 장점은 인접 타일 간의 거리가 같다는 점이 기존의 정사각형 그리드보다 강점을 지니며, 육각형 타일의 경우 재구성이 사각형 타일처럼 정확히 이루어지진 않으나, 약간의 회전과 함께 재구성이 가능하다.

(출처: https://zzsza.github.io/data/2019/03/31/uber-h3/)

 

서론은 줄이고 바로 H3 설치부터 다뤄보도록 한다.

 

0. H3 및 기타 패키지 설치

H3 패키지와 관련 함수들을 사용하기 위해 설치해야한 패키지의 목록은 다음과 같다.

- h3 

- geopandas

- geojson

- simplejson

- branca (LinearColormap에 쓰임)

 

각 패키지가 pip를 통해 바로 설치가 되면 좋겠지만, geopandas의 경우 pip를 통해 설치를 하면 설치 오류가 발생했고 관련 문제가 자주 일어나는 듯 보였다.

이 곳(https://codedragon.tistory.com/9556)의 글을 따라서 pip 설치가 아닌 whl파일을 직접 내려받아 로컬에서 설치를 진행하는 방식으로 진행해야 하며, geopandas를 설치하기 앞서 설치해야하는 패키지가 몇개 존재한다...

 

1. pyproj

2. Shapely

3. GDAL

4. Fiona

5. geopandas

 

https://www.lfd.uci.edu/~gohlke/pythonlibs/

각각의 패키지에 대한 whl파일은 위의 링크에서 내려받을 수 있다. 각각의 운영체제와 python의 버전에 맞게 내려받는다.

 

(whl 파일을 통한 설치 방법)

명령 프롬프트에서 pip install ~~~.whl의 명령을 통해 설치 파일을 내려 받은 whl파일로 지정하여 설치를 진행한다. 나같은 비전공자의 경우 cmd에서의 디렉토리 변경이 익숙치 않아 번거로우므로 워킹 디렉토리에 파일을 옮긴 다음 바로 pip install을 진행하는것이 편할 것이다ㅎㅎ

 

[예시]

(User 폴더에 whl파일 이동) -> (명령 프롬프트 실행) -> pip install geopandas-0.7.0-py2.py3-none-any.whl

(설치를 마무리한 뒤에는 whl파일을 삭제해도 무관하다.)

 

from h3 import h3
import pandas as pd
from tqdm import tqdm_notebook
import folium
from folium import GeoJson
import branca.colormap as cm
from geojson import Feature, FeatureCollection
import simplejson as json

본 단계에서 사용할 패키지들을 전부 import 했다.

위의 패키지를 전부 문제 없이 설치했다면 이렇게 라이브러리를 import해서 오류가 없는지 확인하고 다음 단계로 넘어가자.

 

1. H3 제공 주요 메서드

*프로젝트 중 설치한 패키지와 버전에 따라 메서드의 이름과 인자 이름이 다른 것을 확인하였으나, 공식 Documentation이 없어 이를 명확히할 수 없었다..!

 

아쉽게도 H3 패키지 자체에 시각화나 분석에 바로 쓸수 있는 메서드는 찾기 어렵다. h3패키지가 자체적으로 제공하는 주요 메서드들을 보면 다음과 같다.

 

1. geo_to_h3 : 위경도 좌표와 resolution을 받아 그 좌표가 속한 육각 타일의 id를 제공하는 메서드

2. h3_to_geo : 육각 타일 id를 받아 위경도 좌표 값으로 변환해주는 메서드

3. hex_ring : 육각 타일과 거리 k를 받아 거리가 k에 해당하는 타일의 id의 집합(set)을 반환하는 메서드

    ex. k=2 -> 12개 타일 반환

4. k_ring : 육각 타일과 거리 k를 받아 거리가 k이하인 타일의 id의 집합(set)을 반환하는 메서드

    ex. k=2 -> 1(거리=0) + 6(거리 =1) +12(거리=2) -> 총 19개 타일 반환

 

이런 주요 메서드를 보면 알 수 있듯이 h3 패키지가 분석을 자체적으로 도와주기보다는, 기존 geocode를 해당 위치와 resolution에 해당하는 타일 id를 반환해주고, 타일의 위치나 인접 타일에 대한 정보 정도를 다뤄주는 정도까지의 기능만 제공해준다,

 

즉 우리가 직접 육각 타일에 값을 부여하고, 그 결과를 시각화하는 작업은 직접 수행해야 된다는 것이다.

 

이를 위해 다음 단계에서 분석을 위한 함수를 직접 정의하는 과정을 다뤄보자!

 

2. H3 패키지 메서드 활용 함수

상술한 H3 패키지의 메서드를 이용해 분석단계에 활용한 함수들은 아래의 글에서 참고하였으며, 일부 수정을 거쳤다.

medium.com/better-programming/playing-with-ubers-hexagonal-hierarchical-spatial-index-h3-ed8d5cd7739d

 

1. counts_by_hexagon

def counts_by_hexagon(df, resolution):
    df = df[["lat","lng"]] # 데이터프레임에서 lat, lng만 가져와 처리
    
    df["hex_id"] = df.apply(lambda row: h3.geo_to_h3(row["lat"], row["lng"], resolution), axis = 1) 
    # h3.geo_to_h3 메서드를 통해 위경도 좌표가 속한 그리드의 id를 입력받아 hex_id 칼럼에 저장
    
    df_aggreg = df.groupby(by = "hex_id").size().reset_index() # hex_id를 기준으로 groupby하여, 행의 개수를 저장
    df_aggreg.columns = ["hex_id", "value"] # 칼럼이름을 각각 hex_id와 value로 변경
    
    df_aggreg["geometry"] =  df_aggreg.hex_id.apply(lambda x: {"type" : "Polygon",
                                                               "coordinates":[h3.h3_to_geo_boundary(x,geo_json=True)]})
    return df_aggreg

가장 처음으로 활용하게 되는 함수이다.

우선 위도('lat')와 경도('lng')값을 갖고 있는 데이터프레임을 인자로 받아 위경도 좌표값이 위치하는 타일의 id를 변환하여 'hex_id' 칼럼에 저장한다.

그 다음 hex_id 칼럼으로 groupby하여 size메서드를 통해 각 hex_id의 카운트 값을 계산하여 'value'칼럼으로 저장한다.

또한 이 타일을 시각화하기 위하여 'geometry'칼럼을 만들어 그곳에 geojson형태의 자료형을 입력해주었다. type은 'Polygon', 좌표값은 h3의 h3_to_geo_boundary 메서드를 활용하여 해당 타일을 이루는 6개의 점의 좌표값 리스트를 사용하였다.

 

2. sum_by_hexagon

def sum_by_hexagon(df,column, resolution): #count_by_hexagon과 유사하나, 특정 칼럼값을 지정하여, 타일별 그 값의 합을 연산
    
    df["hex_id"] = df.apply(lambda row: h3.geo_to_h3(row["lat"], row["lng"], resolution), axis = 1) 
    
    df_aggreg = df.groupby('hex_id')[column].sum().reset_index()# hex_id로 groupby한 뒤 지정한 칼럼의 sum을 계산
    df_aggreg.columns = ['hex_id','value']
    df_aggreg["geometry"] =  df_aggreg.hex_id.apply(lambda x: {"type" : "Polygon",
                                                               "coordinates":[h3.h3_to_geo_boundary(x,geo_json=True)]})
    return df_aggreg

위의 count_by_hexagon을 응용한 함수로, 위경도 값이 있는 데이터프레임에서 특정 value가 담긴 값을 타일별로 총합하여 반환하는 함수이다. count_by_hexagon에서 groupby 집계 방식만 바꿔주었다.

 

3. mean_by_hexagon

def mean_by_hexagon(df,column, resolution): #sum_by_hexagon과 유사하나 총합이 아닌, 평균으로 집계함
    
    df["hex_id"] = df.apply(lambda row: h3.geo_to_h3(row["lat"], row["lng"], resolution), axis = 1) 
    
    df_aggreg = df.groupby('hex_id')[column].mean().reset_index()#hex_id로 groupby하여 지정한 칼럼값의 평균으로 집계
    df_aggreg.columns = ['hex_id','value']
    df_aggreg["geometry"] =  df_aggreg.hex_id.apply(lambda x: {"type" : "Polygon",
                                                               "coordinates":[h3.h3_to_geo_boundary(x,geo_json=True)]})
    return df_aggreg

mean_by_hexagon은 마찬가지로 count_by_hexagon에서 groupby 집계 방식만 평균으로 바꾸어서 만들었다,

 

4. hexagons_dataframe_to_geojson

#hex_id와 value로 이루어진 데이터프레임을 geojson형태로 전환하는 함수
def hexagons_dataframe_to_geojson(df_hex, id='hex_id', value="value", file_output = None):
    list_features = []
    
    for i,row in df_hex.iterrows():
        feature = geojson.Feature(geometry = row["geometry"] , id=row[id], properties = {"value" : row[value]})
        list_features.append(feature)
        
    feat_collection = geojson.FeatureCollection(list_features)
    
    geojson_result = json.dumps(feat_collection)
    
    #optionally write to file
    if file_output is not None:
        with open(file_output,"w") as f:
            json.dump(feat_collection,f)
    
    return geojson_result

 

5. choropleth_map

# hex_id별 value가 입력된 데이터 프레임을 코로플레스 시각화 지도로 바꾸어주는 함수
def choropleth_map(df_aggreg,geojson_data, value='val',location =[37.65,126.865] ,border_color = 'black', fill_opacity = 0.7, initial_map = None, with_legend = True, kind = "filled_nulls"):
    #colormap시각화를 위한 중간값 m도출
    min_value = df_aggreg[value].min()
    max_value = df_aggreg[value].max()
    m = round ((min_value + max_value ) / 2 , 0)
    
    if initial_map is None:
        initial_map = folium.Map(location= location, zoom_start=12)
    
    # linear - 선형적인 관계를 나타냄 많고 적음을 나타내게 함
    # outlier - outlier를 찾기 위한 choropleth - 
    # filled_nulls - linear와 같으나, 0인 값을 회색으로 칠함.
    if kind == "linear":
        custom_cm = cm.LinearColormap(['green','yellow','red'], vmin=min_value, vmax=max_value)
    elif kind == "outlier":
        custom_cm = cm.LinearColormap(['blue','white','red'], vmin=min_value, vmax=max_value)
    elif kind == "filled_nulls":
        custom_cm = cm.LinearColormap(['gray','green','yellow','red'], 
                                      index=[0,min_value,m,max_value],vmin=min_value,vmax=max_value)
    geojson_data = geojson_data
    folium.GeoJson(geojson_data,style_function=lambda feature: {
            'fillColor': custom_cm(feature['properties']['value']),
            'color': border_color,
            'weight': 1,
            'fillOpacity': fill_opacity}
    ).add_to(initial_map)
    #add legend (not recommended if multiple layers)
    if with_legend == True:
        custom_cm.add_to(initial_map)    
    
    return initial_map

chropleth_map함수는 4번 hexagons_dataframe_to_geojson 함수로 만든 geojson 자료에 담긴 타일 정보를 folium 지도에 시각화하는 함수이다. 이름에 있는 것과 달리 본 함수는 folium의 Choropleth 메서드를 활용하는 것이 아닌, GeoJson 메서드를 활용하여 지도 위에 Polygon을 올린다. 인자로 받아주는 geojson_data에는 각 타일의 id와 그 위치가 담긴 'geometry'값이 있는데, 이를 참고하여 지도 위에 육각형 타일을 그리고, 각 타일에 대한 색깔은 branca의 LinearColormap 메서드를 활용하여 값의 크기에 비례하여 색을 결정한다.

 

이때, kind인자로 colormap으로 linear, outlier, filled_nulls 세 가지를 선택할 수 있다. linear은 기본적인 형태로 값이 작을수록 초록색, 클수록 빨간색을 띠는 colormap이며, outlier는 outlier가 아닌 일반적인 값은 흰색이고 값이 큰 부분과 작은 부분을 각각 빨간색과 파란색으로 채워준다. filled_nulls는 기본적으로 linear과 같은 colormap이나, 0인 부분을 회색으로 채워넣었다. (원래 코드에서는 다른 색이었는데 수정해 주었음.)

 

이외의 인자로는 Map의 시작위치를 설정하는 location, 타일들의 모서리의 색깔을 설정하는 border_color(없애고 싶은 경우 None), 타일의 불투명도를 설정하는 fill_opacity 등이 있다.

 

그 외에 인자로 설정할 수 있는 부분들이 함수에 존재하나, 본 프로젝트에서는 동일하게 사용될 인자는 따로 인자로 빼놓지 않았다.

 

6. get_neighbors, affect_neighbors

#hex_id값을 입력하면 인근 타일을 반환하는 코드. ring_size값에 따라 범위가 늘어남
def get_neighbors(h3_code,ring_size):
    return list(h3.hex_ring(h3_code,k=ring_size))

#한 그리드의 value값을 인접타일에 특정 비율만큼 추가하는 함수
def affect_neighbors(df, ratio):
    df = df.set_index('hex_id')
    df_2= df.copy()
    
    for h_id in tqdm(list(df.index)):
        neighbor_list = get_neighbors(h_id,1)
        for neighbor in neighbor_list:
            if neighbor in list(df_2.index):
                df_2.loc[neighbor,'value'] += df.loc[h_id,'value']*ratio
            else:
                df_2.loc[neighbor] = {'value':df.loc[h_id,'value']*ratio,'geometry':{"type" : "Polygon","coordinates":[h3.h3_to_geo_boundary(neighbor,geo_json=True)]}}
    return df_2.reset_index()

get_neighbors 함수는 하나의 타일 id에 대하여 인접한 타일의 id의 리스트를 반환하는 함수이다. ring_size를 인자로 설정하긴 했으나, 프로젝트 내에선 k=1로만 사용하였다.

affect_neighbors 함수는 타일 데이터가 담긴 데이터 프레임(df)과 비율(ratio)을 인자로 받아 각각의 타일에 대하여 인접한 타일에게 본 타일에 매핑된 값을 분배해주는 함수이다. 

 

다음 포스트에서 위 함수들을 활용하여 앞서 수집한 데이터를 타일에 매핑하여 분석해 볼 것이다.

댓글

본 단계에서 부터는 실질적으로 지리데이터를 갖고 서울시 전기차 충전소 분석을 진행하고자 한다.

이 글에서는 간단하게 아래의 두 가지 데이터만 활용해보고자 한다.

 

1. 서울시 전기차 충전소 현황 데이터

2. 서울시 행정동별 생활인구 데이터

 

두 자료가 각각의 전기차 충전의 공급 현황과 수요를 나타내는 데이터라 가정하며, 1번의 경우 앞선 포스트에서 수집 방법에 대해서 다루었으므로, 이번에는 '서울시 행정동별 생활 인구 데이터'를 다루어 보고자 한다.

 

1. 서울시 행정동별 생활인구 데이터 다루기

서울 열린 데이터 광장(http://data.seoul.go.kr/)에는 서울시의 행정동별 생활인구 데이터를 월별로 제공하고 있다. 

(자료 : http://data.seoul.go.kr/dataList/OA-14991/S/1/datasetView.do)

 

본 분석에서는 내국인에 대한 20년 1월 자료를 활용하였다. 최근 데이터를 사용하지 않은 것은 코로나 바이러스로 인한 생활인구 자료에 노이즈가 발생하였을 것을 우려했기 때문인데, 사실 큰 차이는 느껴지지 않았다.

서울시 행정동별 생활인구 데이터의 형태

자료를 보면 행정동별, 일별, 시간대별, 연령대별, 성별별 생활인구가 자세하게 기록되어있는 것을 볼 수 있다. 또한 행정동의 경우 동 이름이 아닌, 행정동 코드로 입력된 것을 확인할 수 있는데, 이를 매핑하기 위해 행정동 코드 자료 또한 구해야 한다. 행정동 코드는 위의 주소와 같은 페이지에서 구할 수 있다.

행정구역 코드 정보는 표시한 곳에서 내려받을 수 있다.

행정동 코드 정보를 얻었으니 우리는 pandas를 활용하여 행정동 코드를 행정동 이름으로 변경하는 작업을 해보고자 한다.

import pandas as pd
pop_df = pd.read_csv('LOCAL_PEOPLE_DONG_202001.csv')
hd_code = pd.read_csv('행정동코드.csv')

#행정동 코드를 key, 행정동명을 value로 하는 dictionary 정의
hd_dict = dict(zip(hd_code['행자부행정동코드'],hd_code['행정동명'])) 

#위에서 정의한 hd_dict를 참고하여 '행정동'이라는 열 생성
pop_df['행정동'] = pop_df['행정동코드'].replace(hd_dict) 

위 코드를 통하여 '행정동'이라는 이름의 열로 행정동명 데이터를 생성하였다. 다음으로는 본 데이터를 분석에 맞게 reshaping하는 작업이 필요하다. 

 

1. 본 분석에서는 생활인구를 크게 주간(10시~17시)과 야간(19시 ~07시)의 두 시간대로 나누어 진행한다. (출퇴근 시간대인 07시~10시와 17시~18시는 생활 인구가 크게 이동하는  제외하였다.) 행정동별로 해당 시간대별 평균 생활인구를 구한다.

 

2. 두 시간대의 행정동별 평균 생활인구수를 구하면, 야간대비 주간 생활인구 증가율을 계산한다.

 

3. 성별 및 연령대별 데이터가 나뉘어져 있는데, 전기차 수요와 보다 관련이 있는 25~54세에 해당하는 남성 및 여성의 생활 인구에 대해서만 다룬다.

 

위의 세 가지 작업을 실행하는 코드는 아래와 같다.

pop_df_day = pop_df[(pop_df['시간대구분']>=10)&(pop_df['시간대구분']<=17)]
pop_df_night = pop_df[(pop_df['시간대구분']>=19)|(pop_df['시간대구분']<=7)]
col_list = ['남자25세부터29세생활인구수','남자30세부터34세생활인구수','남자35세부터39세생활인구수','남자40세부터44세생활인구수',
            '남자45세부터49세생활인구수','남자50세부터54세생활인구수','여자25세부터29세생활인구수','여자30세부터34세생활인구수',
            '여자35세부터39세생활인구수','여자40세부터44세생활인구수','여자45세부터49세생활인구수','여자50세부터54세생활인구수']
            
#행정동별 groupby 후 각 연령대 자료 총계 계산
night = pop_df_night.groupby('행정동').mean()[col_list].sum(axis=1)
day = pop_df_day.groupby('행정동').mean()[col_list].sum(axis=1)

#야간대비 주간 증가율 계산
night_day = day/night

#새로운 데이터프레임 pop_df 생성
pop_df = pd.DataFrame(night_day,columns=['야간대비 주간생활인구 증가율']).reset_index()
pop_df.loc[:,'주간'] = list(day)
pop_df['야간'] = list(night)

위 작업을 통해 얻은 데이터는 아래와 같다.

행정동별 주야간 평균 생활인구와 증가율

이제 우리는 두가지 데이터 모두 전처리가 완료되었다. 이제 H3 라이브러리를 활용한 분석을 할 수 있으면 좋겠지만..

우리가 가진 데이터는 모두 위경도 자료가 매핑되어있지 않다. 따라서 우리는 각 데이터에 있는 주소 데이터를 활용해 위경도 값을 먼저 찾아줘야 한다.

 

2. googlemaps 라이브러리를 활용한 위경도 값 찾기

import googlemaps
from tqdm import tqdm_notebook

def latlng_getter(df, address_col):
    gmaps_key = 'YOUR-API-KEY'
    gmaps = googlemaps.Client(key=gmaps_key)
    for i in tqdm_notebook(range(len(df))):
        address = df.loc[i,address_col]
        json_obj = gmaps.geocode(address,language='ko')
        if type(json_obj) == list: #json obj가 리스트로 불려올 떄도, 그대로 dictionary로 불려올 떄도 있어서 둔 코드
            json_obj = json_obj[0]
        df.loc[i,'lat'] = json_obj['geometry']['location']['lat']
        df.loc[i,'lng'] = json_obj['geometry']['location']['lng']
    return df
    
pop_df = latlng_getter(pop_df,'행정동')
sttn_df = latlng_getter(sttn_df,'address')

위경도 값을 매핑하기 위하여 위와 같은 함수 latlng_getter를 정의하였다. 

googlemaps 라이브러리를 활용하여 googlemaps API에서 행단위로 선택한 열값(주소)을 검색하여 얻은 위경도 값을 각각 'lat', 'lng' 칼럼을 생성하여 부여하였다.

 

이번 분석을 통해 얻은 것

1. 공공데이터를 활용해 행정동별 생활인구를 다루어보았고, 행정동 코드 자료도 받아 이를 매핑하는 과정을 경험함.

 

댓글

첫번째 단계에서 해야할 작업은 서울시의 전기차 충전소 현황을 담은 자료를 구하는 것이었다. 

우리가 첫번째로 구할 수 있었던 자료는 전력 빅데이터 센터(https://bigdata.kepco.co.kr/)의 전기차 충전소 설치 현황 자료였다.

자료 : https://home.kepco.co.kr/kepco/BD/BDBAPP008/BDBAPP008.do?menuCd=FN33020108

 

이것만 갖고 본래 작업을 수행하려 했으나, 개수가 적고 특정지역에 비어있는 등의 문제가 있는 것을 확인하였고, 다른 충전소 자료를 탐색하여 전체 충전소에 대한 자료가 맞는지 확인해본 결과, 전기차 충전소 실시간 조회 서비스를 찾을 수 있었다. 여담으로 포스트 작성을 하면서 이곳(https://www.ev.or.kr/evmonitor)에서도 실시간 전기차 충전소 모니터링 서비스를 제공한다. 이곳의 자료도 수집하여 정보를 검토할 수도 있었을 것 같다.

 

주소: http://user.happecharger.co.kr/chargeView.do

happeCharger의 충전소 조회 서비스 화면

위 자료와 같이 지도에 충전소 위치에 마커를 표시하였고 좌측에 충전소 목록을 확인할 수 있다. 우리의 목표는 이 왼쪽의 박스다! 이것을 selenium을 활용한 크롤링을 하여 구하고자 한다. 사용한 코드는 아래와 같다.

 

from bs4 import BeautifulSoup
from selenium import webdriver
import time
import datetime
import pandas as pd
from tqdm import tqdm_notebook
browser = webdriver.Chrome(r'chromedriver.exe')
url = 'http://user.happecharger.co.kr/chargeView.do'
browser.get(url)

우선 관련 라이브러리를 import한 후, 웹드라이버를 구동시킨 다음 충전소 조회 서비스 페이지로 이동한다.

이제 데이터를 수집하는 페이지에 다다랐다. 하지만 우리는 이 단계에서 바로 데이터를 수집할 수 없다. 충전소 조회 설정을 해야하기 때문이다. 이 또한 코드 작업으로 자동화할 수도 있겠으나, 그 단계까지의 자동화가 필요한 작업도 아니기 때문에 웹드라이버 내에서 손수 클릭하여 검색설정을 바꾸었다..!

바꾼 설정은 1. 충전기 운영기관을 전부 체크 2. 지역명 - '전체'로 설정 두 가지다.

webdriver 내에서 조회 설정을 바꾼 결과

element_list = browser.find_elements_by_class_name('adressbox')
sttn_names = []
address_list = []
for element in tqdm_notebook(element_list):
    tmp = element.text.split('\n')
    sttn_names.append(tmp[0])
    address_list.append(tmp[1])
sttn_df = pd.DataFrame({'name':sttn_names,
                  'address':address_list})

 

 

 

 

 

 

이제 위의 코드로 자료 수집을 시작한다. 좌측 박스의 목록 하나하나는 html코드에서 class name이 'adressbox'로 되어있는 것을 확인했다. (왜 ad'd'ress가 아닐까!)

adressbox태그에는 이름과 주소가 \n으로 구분되어 있으므로 이를 split으로 구분한 다음, 각 list에 append하는 방식으로

수집을 진행하였다. 

그 다음 list 형태로 이름과 주소를 수집한 것을 데이터 프레임으로 저장한 후 그 결과를 확인해 보았다.

크롤링 첫번째 결과

위와 같이 충전소 이름(name)과 주소(address)가 잘 찍혀있는 것 처럼 보였다!

하지만..

서울에 위치한 충전소인데 서울이 아닌 주소들

자료 검토 결과(사실 이후 단계에서 확인한 것이지만) 주소들 중 서울이 아닌 주소들이 일부 발견되었다! 해당 문제에 대해서 탐구해본 결과 주소가 충전소가 위치한 소재지를 가리키는 것이 아니라, 해당 충전소를 관리 및 운영하는 곳의 주소가 적혀있는 것으로 판단되었다. 이에 대한 처리는 아래와 같이 진행하였다.

for i in tqdm_notebook(range(len(sttn_df))):
    if sttn_df.address[i][:2]!='서울':
        sttn_df.loc[i,'address'] = sttn_df.loc[i,'name']

데이터프레임 df의 address 열 값이 '서울'로 시작하지 않는 경우, 그 값을 그 행의 'name'열의 값으로 대체하는 작업을 통해 주소를 일단 충전소 이름으로 대체하는 과정을 통해 위 문제를 해결하였다.

 

 

하지만 이렇게 수집된 결과도 분석결과 이상함을 느껴, 첫번째 자료와 통합한 다음 오류를 직접 찾아보았다. 찾아본 결과 몇몇 충전소 주소가 한군데 주소에 똑같이 찍혀있는 등의 오류를 발견할 수 있었다! 이는 서비스쪽의 서버에서 보내는 단계에서의 오류에 해당하므로, 별 수 없이 수작업으로 수정하였다..! 

이 과정을 끝낸 후에야 데이터 수집 작업을 완전히 마무리할 수 있었다.

 

이 과정으로 얻은 것 두 가지

1. 실시간 조회 서비스 또한 크롤링으로 자료 수집이 가능하다!

2. 크롤링 결과를 안일하게 맹신하지 말자..! 귀찮더라도 잘 수집됐는지 두 번은 확인하자! (실제로 두 번 수정..)

 

다음 포스트에서는 이렇게 수집한 충전소 이름, 주소를 갖고 지리데이터 분석을 하는 기초 단계를 다루어 보고자 한다.

댓글

학회에서 진행했던 전기차 충전소 관련 프로젝트에 대해 정리해 보고 느꼈던 것들 생각한 것들을 남겨놓고자 한다. 

 

프로젝트의 주제는 '에너지저장장치(ESS)로서의 전기자동차 활용 및 보금을 위한 도심 내 충전인프라 최적화 방안 연구'라는 길고 멋진 이름이었지만, 분석의 핵심은 현재 서울의 전기차 충전소의 현황에 대한 분석과 우리가 추정한 수요에 따른 전기차 충전소 확충 지역 선별이었다.

 

프로젝트 전체적으로는 이것 외에도 다른 내용들이 있으나, 위의 두가지 목표를 위해 진행하였던 데이터 분석 과정을 중심으로 기록해 나가고자 한다.

 

파트를 나눠보면 아마 이와 같을 것이다.

 

1. selenium을 활용한 전기차 현황 자료 수집 

2. h3 라이브러리를 활용한 지리데이터 분석 - 1. H3 소개, 관련 라이브러리 설치 및 오픈소스 코드 공부

3. h3 라이브러리를 활용한 지리데이터 분석 - 2. 분석을 위한 추가적인 코드 작성

4. h3 라이브러리를 활용한 지리데이터 분석 - 3. 추가 분석을 위한 응용 코드와 분석 결과

 

정확히 이렇게 나뉘어질진 모르겠지만 이런식으로 포스트를 나누어서 작성하려고 한다.

댓글