광역철도 수송 데이터 탐색적 분석 및 시각화

개요

  • 운행일자를 기준으로 2019년 10월 한 달 간 운행열차의 운행내역 데이터를 사용하여 정차역에 따른 승,하차,통과 인원 및 해당 역 별 지가지수를 파악하여 광역 철도 데이터의 탐색적 분석 및 공간 데이터 시각화 분석이 이루어짐.

분석 데이터

  • 일자별전철역승차인원 데이터
  • 여객역코드 데이터
  • 역 별 위치 데이터(위,경도)
  • 역세권 별 지가지수 데이터(2014~2017)

load python library

import pandas as pd
import pandas_profiling
pd.options.mode.chained_assignment = None
from pyproj import Proj, transform
import numpy as np
import matplotlib.pyplot as plt
import ipywidgets as widgets
from ipywidgets import interact, interact_manual, GridspecLayout
import ipywidgets
from collections import OrderedDict
import geopandas as gpd
from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
import warnings
warnings.filterwarnings(action='ignore')

load dataset

path = '/home/yubin90/pyWork/diamond/'
def stp_cols(df):
 cols = list(map(str.strip, df.columns.tolist()))
 df.columns = cols  
 return df

ride_acvm_df = pd.read_csv(path+'TB_COL_KR_TRVL_STNP_CLSF_RIDE_ACVM.csv', low_memory=False)
ride_acvm_df = stp_cols(ride_acvm_df)
ride_acvm_df = ride_acvm_df.drop(labels='AGGR_YMDHMS', axis=1)

trvl_stn_cd_df = pd.read_csv(path+'TB_COL_KR_TRVL_STN_CD.csv', low_memory=False)
trvl_stn_cd_df = stp_cols(trvl_stn_cd_df)
trvl_stn_cd_df = trvl_stn_cd_df.drop(labels='AGGR_YMDHMS', axis=1)
trvl_stn_cd_df = trvl_stn_cd_df.drop_duplicates(subset='STN_CD')
stn_loc_df = pd.read_csv(path+'stn_loc_no_dup.csv', encoding='cp949')

code mapping

#년월 생성
ride_acvm_df['YYMM'] = ride_acvm_df['RUN_DT'].astype(str).str[:-2]

# 역명코드 dict 생성
trvl_stn_cd_dict= dict(zip(trvl_stn_cd_df.STN_CD, trvl_stn_cd_df.KOR_STN_NM))

# dict mapping(역명 한글이름 매칭)
ride_acvm_df['KOR_STOP_STN'] = ride_acvm_df['STOP_STN'].map(trvl_stn_cd_dict)

역 위치 (위,경도) 매핑

ride_acvm_with_loc = pd.merge(ride_acvm_df,stn_loc_df, how='left', left_on='KOR_STOP_STN', right_on='STN_NM')
ride_acvm_with_loc
RUN_DTSTOP_STNSTLB_TRN_CLSF_CDUP_DN_DV_CDABRD_PRNBGOFF_PRNBNSTP_PRNBYYMMKOR_STOP_STNSTN_NMLATLNG
02019100139002800D1148302115849201910오송오송36.620098127.327582
12019100139002807D63014226902201910오송오송36.620098127.327582
220191001390028010D49127615201910오송오송36.620098127.327582
32019100139000237U194749378201910서울서울37.554073126.970702
42019100139000967U13327104545201910동대구동대구35.879437128.628784
.......................................
292772019103139002921U10660201910충주충주36.975892127.909136
292782019103139005561U0041201910춘양춘양36.937810128.919938
292792019103139006111U5036201910민둥산민둥산37.243701128.773657
292802019103139007031D0161201910북천북천35.111383127.883501
292812019103139007031U0039201910북천북천35.111383127.883501

29282 rows × 12 columns

역 별 승,하차, 통과 인원 수 상위 10

for_top_df = ride_acvm_df[['ABRD_PRNB', 'GOFF_PRNB', 'NSTP_PRNB', 'KOR_STOP_STN']]
#승차 top 10
sum_abrd_by_stn = pd.DataFrame(for_top_df.groupby('KOR_STOP_STN')['ABRD_PRNB'].sum())
sum_abrd_by_stn = sum_abrd_by_stn[sum_abrd_by_stn.index != '청량리']
sum_abrd_by_stn.sort_values('ABRD_PRNB', ascending=False).head(10)
ABRD_PRNB
KOR_STOP_STN
------
서울1527592
동대구748905
용산720647
대전654794
부산636205
수원503330
광명422479
영등포358667
오송270318
천안아산250665
#하차 top 10
sum_goff_by_stn = pd.DataFrame(for_top_df.groupby('KOR_STOP_STN')['GOFF_PRNB'].sum())
sum_goff_by_stn.sort_values('GOFF_PRNB', ascending=False).head(10)
GOFF_PRNB
KOR_STOP_STN
------
서울1545800
동대구754050
용산722747
대전656247
부산632395
수원508386
광명416549
영등포362172
청량리277838
오송261490
#통과 top 10
sum_nstp_by_stn = pd.DataFrame(for_top_df.groupby('KOR_STOP_STN')['NSTP_PRNB'].sum())
sum_nstp_by_stn.sort_values('NSTP_PRNB', ascending=False).head(10)
NSTP_PRNB
KOR_STOP_STN
------
대전3271135
광명3027384
동대구2769875
오송2181114
천안아산2039345
수원1731419
서울1632729
천안1445134
익산1362569
평택1353513
abrd_top = sum_abrd_by_stn.sort_values('ABRD_PRNB', ascending=False).head(10).index
abrd_top = abrd_top.tolist()
abrd_top

['서울', '동대구', '용산', '대전', '부산', '수원', '광명', '영등포', '오송', '천안아산']

공공데이터 추가

  • 한국감정원 월별 KTX역 역세권 지가지수(2014년 11월 ~ 2019년 7월)
    *지가지수 : 기준시점의 지수를 100으로 보았을 때, 기준시점 대비 가격상승분을 반영한 해당시점의 지수를 나타냄
  • 각 역 단위 실 주소 및 위,경도
land_value = pd.read_csv(path+'land_value_2017_2019.csv', encoding='cp949')
land_value[['YYMM', '오송']].head()
YYMM오송
0201701100.226
120170299.968
2201703100.988
3201704101.482
4201705102.830
#승차인원 상위 역 지가지수 변화 그래프
#land_value.set_index(land_value['YYMM'], inplace=True)
cols = ['YYMM'] + abrd_top
land_value_plt = land_value[cols]
land_value_plt['YYMM'] = land_value_plt['YYMM'].astype(str)

font = {'family' : 'nanumgothic', 'size':10}
plt.rc('font', **font) #font option
plt.rcParams["figure.figsize"] = (25,10)
land_value_plt.plot(x='YYMM', y=abrd_top)
plt.title('월별 역의 지가지수 흐름', fontsize=18)
plt.xlabel('Date', fontsize=18)
plt.ylabel('지가지수', fontsize=18)
plt.legend(fontsize=15, loc='best')

<matplotlib.axes._subplots.AxesSubplot at 0x7f16f307b2e8>

Text(0.5, 1.0, '월별 역의 지가지수 흐름')

Text(0.5, 0, 'Date')

Text(0, 0.5, '지가지수')

<matplotlib.legend.Legend at 0x7f16f30715f8>

2-1

stn_addr = pd.read_csv(path+'stn_addr_2016.csv', encoding='cp949')
stn_addr.head()
역명주소
0가수원대전 서구 가수원동 547-1
1가야부산 부산진구 백양대로 91
2각계충북 영동군 심천면 각계리
3감곡전북 정읍시 감곡면 유정리 196-1
4강경충남 논산시 강경읍 대흥리 32
#merge
ride_acvm_with_loc_addr = pd.merge(ride_acvm_with_loc, stn_addr, how='left', left_on='KOR_STOP_STN', right_on='역명')
ride_acvm_with_loc_addr.head()
RUN_DTSTOP_STNSTLB_TRN_CLSF_CDUP_DN_DV_CDABRD_PRNBGOFF_PRNBNSTP_PRNBYYMMKOR_STOP_STNSTN_NMLATLNG역명주소
02019100139002800D1148302115849201910오송오송36.620098127.327582오송충청북도 청원군 강외면 봉산리 370-1
12019100139002807D63014226902201910오송오송36.620098127.327582오송충청북도 청원군 강외면 봉산리 370-1
220191001390028010D49127615201910오송오송36.620098127.327582오송충청북도 청원군 강외면 봉산리 370-1
32019100139000237U194749378201910서울서울37.554073126.970702서울서울 용산구 동자동 43-205
42019100139000967U13327104545201910동대구동대구35.879437128.628784동대구대구 동구 동대구로 550(신암동)

지도 설정

  • 좌표계 포맷 변경
  • 역 별 위경도(WG84 -> UTM-K)
proj_UTMK = Proj(init='epsg:5178') # UTM-K(Bassel)
proj_WGS84 = Proj(init='epsg:4326') # Wgs84 경도/위도, GPS

def transform_w84_to_utmk(df):
 return pd.Series(transform(proj_WGS84, proj_UTMK, df['LNG'], df['LAT']), index=['LNG', 'LAT'])

ride_acvm_with_loc_addr[['LNG_utmk', 'LAT_utmk']] = ride_acvm_with_loc_addr.apply(transform_w84_to_utmk, axis=1)
ride_acvm_with_loc_addr.head()
Unnamed: 0RUN_DTSTOP_STNSTLB_TRN_CLSF_CDUP_DN_DV_CDABRD_PRNBGOFF_PRNBNSTP_PRNBYYMMKOR_STOP_STNSTN_NMLATLNG역명주소LNG_utmkLAT_utmk
002019100139002800D1148302115849201910오송오송36.620098127.327582오송충청북도 청원군 강외면 봉산리 370-19.847740e+051.846622e+06
112019100139002807D63014226902201910오송오송36.620098127.327582오송충청북도 청원군 강외면 봉산리 370-19.847740e+051.846622e+06
2220191001390028010D49127615201910오송오송36.620098127.327582오송충청북도 청원군 강외면 봉산리 370-19.847740e+051.846622e+06
332019100139000237U194749378201910서울서울37.554073126.970702서울서울 용산구 동자동 43-2059.534382e+051.950351e+06
442019100139000967U13327104545201910동대구동대구35.879437128.628784동대구대구 동구 동대구로 550(신암동)1.102084e+061.765044e+06

고속열차(KTX) 대상

  • 승차,하차,통과인원수 0이거나 음수인 경우 제외
ride_acvm_with_loc_addr = ride_acvm_with_loc_addr[ride_acvm_with_loc_addr['STLB_TRN_CLSF_CD'] == 0]
ride_fin_df = ride_acvm_with_loc_addr[(ride_acvm_with_loc_addr[['ABRD_PRNB','GOFF_PRNB', 'NSTP_PRNB']] != 0).all(axis=1)]
ride_fin_df = ride_fin_df[['RUN_DT', 'STOP_STN', 'UP_DN_DV_CD', 'ABRD_PRNB', 'GOFF_PRNB', 'NSTP_PRNB','YYMM','KOR_STOP_STN', 'LNG_utmk', 'LAT_utmk']]
ride_fin_df_cols = ['운행년월일', '정차역코드', '상하행구분', '승차인원수', '하차인원수', '통과인원수', '운행년월','역명', 'LNG_utmk', 'LAT_utmk']
ride_fin_df.columns = ride_fin_df_cols
updown_dict = {'U':'상행', 'D':'하행'}
ride_fin_df['상하행구분'] = ride_fin_df['상하행구분'].map(updown_dict)
ride_fin_df.head()
운행년월일정차역코드상하행구분승차인원수하차인원수통과인원수운행년월역명LNG_utmkLAT_utmk
0201910013900280하행1148302115849201910오송9.847740e+051.846622e+06
6201910013900047하행9681241819201910수원9.558390e+051.918382e+06
12201910013900685하행1208148201910창원1.100874e+061.696025e+06
13201910013900902상행980171666201910창원중앙1.109497e+061.694457e+06
16201910013900229상행2269723222201910광주송정9.355993e+051.682427e+06

지도 및 차트 생성

  • 해당 운행일자의 각 역 별 상-하행 구분에 따른 승차,하차,통과 인원 및 역의 지가지수 변동을 인터렉티브하게 표현함
widget_time = sorted(list(ride_fin_df['운행년월'].unique()))
widget_stn = list(ride_fin_df['역명'].unique())
widget_updown = ['하행', '상행']
widget_prnb_Value = ['승차인원수', '하차인원수', '통과인원수']
ride_acvm_gpd = gpd.GeoDataFrame(
 ride_fin_df, geometry=gpd.points_from_xy(ride_fin_df.LNG_utmk, ride_fin_df.LAT_utmk)
)
ride_acvm_gpd.head()
운행년월일정차역코드상하행구분승차인원수하차인원수통과인원수운행년월역명LNG_utmkLAT_utmkgeometry
0201910013900280하행1148302115849201910오송9.847740e+051.846622e+06POINT (984774.029 1846622.323)
6201910013900047하행9681241819201910수원9.558390e+051.918382e+06POINT (955838.951 1918382.084)
12201910013900685하행1208148201910창원1.100874e+061.696025e+06POINT (1100874.105 1696025.006)
13201910013900902상행980171666201910창원중앙1.109497e+061.694457e+06POINT (1109496.880 1694456.908)
16201910013900229상행2269723222201910광주송정9.355993e+051.682427e+06POINT (935599.278 1682426.726)
#base map loading

whole_map = pd.read_pickle(path+'whole_map.pkl')
whole_map.columns = ['geometry']

#b_time
w1 = widgets.Dropdown(
 options=widget_time,
 value=widget_time[0],
 description='운행년월 :',
 disabled=False,
)

#b_stn
w2 = widgets.Dropdown(
 options=widget_stn,
 value=widget_stn[0],
 description='역명 :',
 disabled=False,
)

#b_dir
w3 = widgets.Dropdown(
 options=widget_updown,
 value=widget_updown[0],
 description='상하행 :',
 disabled=False,
)

#want_view
w4 = widgets.Dropdown(
 options=widget_prnb_Value,
 value=widget_prnb_Value[0],
 description='기준인원 :',
 disabled=False,
)
def view(b_time = '', b_stn = '', b_dir = '', want_view = ''):
 if b_time=='All': 
return ride_acvm_gpd
 temp_df = ride_acvm_gpd
 temp_df = temp_df[temp_df['운행년월']==b_time]
 temp_df = temp_df[temp_df['역명']==b_stn]
 if temp_df.shape[0] == 0:
 return "데이터가 없습니다"

font = {'family' : 'NanumGothic', 'size':10}
plt.rc('font', **font) #font option
fig = plt.figure(figsize=(28,8), constrained_layout=False)    
gs = fig.add_gridspec(nrows=5, ncols=8, left=0.05, right=0.48, wspace=0.5)
ax1 = fig.add_subplot(gs[:5, :4]) #row, col
ax2 = fig.add_subplot(gs[:2, 4:8])  #row, col
ax3 = fig.add_subplot(gs[3:5, 4:8])

whole_map.plot(color='white', edgecolor='black', legend=True, ax=ax1, linewidth=0.2)
gpd.GeoDataFrame(temp_df).plot(column='역명', marker='*', color="r", markersize=40,legend=True, ax=ax1)

ax1.axes.get_xaxis().set_visible(False)
ax1.axes.get_yaxis().set_visible(False)
ax1.set_title('역 위치')

#1. 역명,기준연월, 상하행
df = temp_df[(temp_df['역명'] == b_stn) & (temp_df['상하행구분'] == b_dir)]
bar_1 = df[['운행년월일',want_view]]
bar_1.sort_index().plot.bar(x='운행년월일',y=want_view, ax=ax2, fontsize=10, color="orange", rot=90)
ax2.set_title('2019년 한달 간 기준인원 변화')

#2. 해당 역 지가지수
bar_2 = pd.DataFrame(land_value[['YYMM', b_stn]])
bar_2.sort_index().plot.bar(x='YYMM',y=b_stn, ax=ax3, fontsize=10, color="purple", rot=90)
ax3.set_ylim([80, 140])
ax3.set_title('17~19년 해당 역 지가지수 변화')
interact_manual(view, b_time=w1, b_stn=w2, b_dir=w3, want_view = w4)
#from IPython.display import Image
#Image('/home/yubin90/pyWork/diamond/widget_result.png')

<function main.view(b_time='', b_stn='', b_dir='', want_view='')>

2-2