2. 문제 파악

2.1 데이터 전처리

본격적인 문제 파악에 앞서 데이터 전처리 작업을 진행하겠습니다. 먼저, 필요한 라이브러리를 불러오고 환경설정을 해줍니다.

from IPython.core.display import display, HTML
display(HTML("<style>.container {width:80% !important;}</style>"))
%matplotlib inline

import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
from scipy import stats
import warnings
warnings.filterwarnings('ignore')

mpl.rcParams['figure.figsize'] = (8,6)  #시각화 figure default 설정
mpl.rcParams['font.family'] = 'NanumGothic' #폰트 디폴트 설정
mpl.rcParams['font.size'] = 20    #폰트 사이즈 디폴트 설정
plt.rcParams['axes.unicode_minus'] = False
%config InlineBackend.figure_format='retina' # 그래프 글씨 뚜렷

수집한 csv파일을 데이터프레임으로 불러와 확인해보겠습니다. 데이터에는 총 1704개의 인스턴스와 43개의 컬럼으로 구성되어 있습니다. 전체 컬럼 중 앞의 6개 열은 '연번', '조사일자', '호선', '역번호', '역명', '구분'이며 이를 제외한 나머지는 5시 30분부터 23시 30분까지 30분 단위의 시간 간격입니다. 

# csv 파일을 불러와 데이터프레임에 저장
df = pd.read_csv('서울교통공사_지하철혼잡도정보_20211231.csv', encoding = 'cp949')

전체 컬럼 중에서 '연번', '역번호'는 필요가 없기 때문에 삭제하도록 하겠습니다. 그리고 '조사일자'에서 '토요일'과 '일요일' 값은 '평일'값과 통일성을 주기 위해 모두 '주말'로 변경하겠습니다. 

# 불필요한 '연번', '역번호' 컬럼 제거
df = df.drop(['연번', '역번호'], axis = 1)
# '조사일자'의 '토요일'과 '일요일'은 주말로 변경
df.replace('토요일', '주말', inplace = True)
df.replace('일요일', '주말', inplace = True)

2.2 1호선 혼잡도 분석

전처리 작업을 마쳤으니 본격적으로 분석을 진행 해보겠습니다. 우선, 어떤 내용을 확인하고 싶은지 구체화하여 정리해보고 이에 따라 데이터를 정리해보겠습니다. 최적의 지하철 통학 시간대를 결정하기 위해서는 시간대별 지하철 혼잡도를 파악해야 합니다. 이때, 정확한 분석을 위해서는 데이터를 범주에 따라 그룹화하여 살펴보아야 합니다.

첫 번째로 '평일'과 '주말'에 따른 그룹입니다. 사실 '요일'별로 혼잡도를 파악하는 것이 보다 정확한 분석이 되겠지만 현재 가지고 있는 데이터에서는 '평일'과 '주말' 정도로 데이터가 수집되어 있습니다. '평일'과 '주말' 지하철 이용객의 수가 각각 다를 수 있기 때문에 분리해서 분석하도록 하겠습니다.

두 번째로 '호선'('역')에 따른 그룹입니다. 원본 데이터는 1호선부터 8호선까지 모든 호선의 데이터를 포함하고 있습니다. 이 중 제가 이용하는 1호선과 2호선만 따로 분리해서 확인해야 할 필요가 있습니다. 이때 '호선' 뿐만 아니라 실재 이용하는 '역'에 대해서만 데이터를 필터링하여 살펴보겠습니다.

세 번째로 '방향'에 따른 그룹입니다. 원본 데이터를 보면 같은 역이라도 방향에 따라 '상선/하선', '외선/내선'으로 구분되어 있습니다. 집에서 학교를 갈 때와 학교에서 집으로 올 때 각각 이용하는 방향이 다르기 때문에 이 부분도 분석에서 구분되어야 할 중요한 요소일 것입니다.

먼저, 원본 데이터 중 1호선에 대한 데이터를 따로 분리하여 df_1에 저장하겠습니다. 이때 이용하는 역을 필터링하여 필요한 역만 추출하겠습니다.

# 1호선 역명 확인
df[df['호선'] == 1]['역명'].unique()

# 2호선 역명 확인
df[df['호선'] == 2]['역명'].unique()
# 전체 역 중 실제 이용하는 역을 리스트화
station = ['이대', '아현', '충정로', '시청', '종각', '종로3가','종로5가', '동대문', '동묘앞', '신설동', '제기동', '청량리', '회기']

# 1호선 중 실제 이용하는 역만 필터링하여 새로운 데이터프레임 생성
df_1 = df[(df['호선'] == 1) & (df['역명'].isin(station))]

그리고 데이터를 '시간(조사일자)'과 '방향(구분)'에 따라 그룹화하도록 하겠습니다. 그러면 아래와 같이 '주말/평일' 그리고 '상선/하선'에 따라 데이터가 그룹화되어 정리될 것입니다. 

# 요일(조사일자), 방향(구분)을 기준으로 그룹화
df_1 = df_1.groupby(['조사일자', '구분']).mean()

공간 상 일부 컬럼이 생략되었습니다

그룹화 한 데이터를 각 행별로 분리해서 따로 저장하겠습니다. 나중에 통학 시간대를 분석하기 위한 단계에서 조금 더 작업이 수월해질 수 있기 때문입니다. 그러면 아래와 같이 'df_wd_up_1(주중-상선-1)'과 같은 데이터 프레임이 총 4개가 생성될 것입니다.

(왜 그랬는지 모르겠지만 이때부터 '평일'이라는 단어를 '주중'이라고 표현하였습니다. 아마 '주말'과 통일성 있는 단어를 사용하려다 보니 바뀌게 된 것 같은데 이때 데이터에 있는 '평일'이라는 용어를 바꿨어야 했는데 그러지 못했습니다. 제 불찰이지만 너그러이 이해해주시면 감사하겠습니다.)

df_wd_up_1 = pd.DataFrame(df_1.iloc[2,:]) #주중 1호선 상선 혼잡도 
df_wd_down_1 = pd.DataFrame(df_1.iloc[3,:]) #주중 1호선 하선 혼잡도 

df_we_up_1 = pd.DataFrame(df_1.iloc[0,:]) #주말 1호선 상선 혼잡도 
df_we_down_1 = pd.DataFrame(df_1.iloc[1,:])#주말 1호선 하선 혼잡도
df_we_down_1.T.iloc[:,:16]

2.3 1호선 혼잡도 시각화

앞서 그룹화한 데이터를 살펴보게 되면 1) 조건(날짜, 방향)에 따라 혼잡도 차이가 있는 것으로 보이고 또한 2)시간대별로도 혼잡도 차이가 있는 것으로 파악됩니다. 하지만 너무 많은 수치가 있고 수치간 scale차이가 크지가 않다보니 명확하게 차이가 있다고 말하기가 어렵다고 느껴집니다. 이번에는 위의 표 데이터들을 시각적으로 확인할 수 있도록 그래프로 그려보겠습니다. '주중'과 '주말'에 대한 데이터 각각에 대해 막대 그래프를 그려보았고 방향을 label로 하여 막대를 2개씩 표현하였습니다. 말로 설명하기 어려운데 아래 그래프를 직접 보면 이해가 수월할 것입니다. 

### 주중/주말 1호선 평균 혼잡도 그래프 ###
plt.figure(figsize = (20, 15)) #subplot 2개를 그리기 위한 크기 지정

## 주중 1호선 평균 혼잡도 그래프 ##

index = np.arange(len(df_wd_up_1.index)) #가로축 index 개수만큼 array 생성
x1 = df_wd_up_1.index 
ya = df_wd_up_1.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
yb = df_wd_down_1.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
mean1 = ((np.mean(df_wd_up_1.values) + np.mean(df_wd_down_1.values)) / 2).round(1) # 주중 상선/하선 혼잡도 평균

plt.subplot(2,1,1) # 1X2 subplot의 첫번째 그래프
p1 = plt.bar(index - 0.2, ya, color = '#1f77b4', alpha = 0.8, width = 0.4) # 상선에 대한 바그래프
p2 = plt.bar(index + 0.2, yb, color = '#1f77b4', alpha = 0.4, width = 0.4) # 하선에 대한 바그래프

plt.legend((p1[0], p2[0]), ('상선', '하선'), loc = 'upper right', fontsize=20) # 범례 지정

plt.xticks(index, x1, rotation = 60) #xticks 레이블 60도 회전
plt.title('주중 1호선 평균 혼잡도')
plt.xlabel('시간')
plt.ylabel('혼잡도(%)')
plt.ylim(0, 60) # y축 범위 지정

plt.axhline(34, color='grey', linestyle=':', linewidth=2) # 만석 기준점(34%) 수평선 표시
plt.text(-2, 35, '만석 기준점 = 34.0%') # 만석 기준점(34%) 레이블 표시

plt.axhline(mean1, color='#d62728', linestyle='--', linewidth=2) # 혼잡도 평균 수평선 표시
plt.text(-2, mean1 + 1, f'평균 = {mean1}%') # 혼잡도 평균 레이블 표시

## 주말 1호선 평균 혼잡도 그래프 ##

index = np.arange(len(df_we_up_1.index)) #가로축 index 개수만큼 array 생성
x1 = df_we_up_1.index
ya = df_we_up_1.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
yb = df_we_down_1.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
mean2 = ((np.mean(df_we_up_1.values) + np.mean(df_we_down_1.values)) / 2).round(1) # 주중 상선/하선 혼잡도 평균

plt.subplot(2,1,2) # 1X2 subplot의 두번째 그래프
# plt.bar(x2, y2, color = '#2ca02c')
p1 = plt.bar(index - 0.2, ya, color = '#1f77b4', alpha = 0.8, width = 0.4) # 상선에 대한 바그래프
p2 = plt.bar(index + 0.2, yb, color = '#1f77b4', alpha = 0.4, width = 0.4) # 하선에 대한 바그래프

plt.legend((p1[0], p2[0]), ('상선', '하선'), loc = 'upper right', fontsize=20) # 범례 지정

plt.xticks(index, x1, rotation = 60) #xticks 레이블 60도 회전
plt.title('주말 1호선 평균 혼잡도')
plt.xlabel('시간')
plt.ylabel('혼잡도(%)')
plt.ylim(0, 60) # y축 범위 지정

plt.axhline(34, color='grey', linestyle=':', linewidth=2) # 만석 기준점(34%) 수평선 표시
plt.text(-2, 35, '만석 기준점 = 34.0%') # 만석 기준점(34%) 레이블 표시

plt.axhline(mean2, color='#d62728', linestyle='--', linewidth=2) # 혼잡도 평균 수평선 표시
plt.text(-2, mean2 + 1, f'평균 = {mean2}%') # 혼잡도 평균 레이블 표시

plt.tight_layout()
plt.show()

2.4 1호선 혼잡도 분석 결과

1호선 혼잡도 그래프를 살펴보면 다음과 같은 사실을 확인할 수 있습니다.

1) '주중'과 '주말' 지하철 혼잡도 정도(양)의 차이가 존재하는 것으로 보입니다. '평균 혼잡도'에 있어서 '주중', '주말' 각각 21.1%, 17.5%로 3.6%p 차이가 있습니다. 또한, 만석 기준점인 34.0%를 기준으로 했을 때 '주중'에는 기준점을 상회하는 시간대가 다수(하루 전체 중 총 2시간 정도)있지만 '주말'에는 모든 시간대에서 기준점 아래인 것을 확인할 수 있습니다. 따라서, '주말'보다 '주중' 지하철 이용 승객이 평균적으로 더 많은 편이고 그에 따라 지하철 내부가 훨씬 더 혼잡하다고 정리할 수 있습니다.

2) '주중'과 '주말' 지하철 혼잡도 분포의 차이가 있다는 것을 확인할 수 있습니다. '주중'에는 주로 출근 시간대(7시 30분 ~ 9시)와 퇴근 시간대(17시 ~ 18시 30분)에 혼잡도가 높은 편이며 반대로 '주말'에는 점심과 저녁 사이(12시 ~ 18시) 사이에 혼잡도가 높은 편입니다. 이는 '주중'과 '주말' 사람들의 생활 양식에 차이가 있기 때문에 생기는 차이라고 생각됩니다.

3) '주중'과 '주말' 각각 '방향'에 따라 지하철 혼잡도의 차이가 있는 것으로 보입니다. 먼저 '주중' 그래프 살펴보면 아침 시간대에 '하선'이 '상선'보다 혼잡도가 더 높고 반대로 저녁 시간대에는 '상선'이 '하선'보다 혼잡도가 더 높습니다. 이 점은 그래프를 그려보기 전에는 파악하지 못했던 점인데 굉장히 흥미로운 인사이트라고 여겨집니다. 그래프의 중요성을 깨달을 수 있는 기회였고 비록 이번 분석에서는 이 부분에 대해 확장해서 다루지는 않았지만 추후에 기회가 되면 '방향'에 따라 혼잡도 차이가 발생하는 이유에 대해 분석해보도록 하겠습니다.

2.5 2호선 혼잡도 분석

2호선에 대한 분석은 1호선과 동일하기 때문에 설명은 생략하도록 하겠습니다.

station = ['이대', '아현', '충정로', '시청', '종각', '종로3가','종로5가', '동대문', '동묘앞', '신설동', '제기동', '청량리', '회기']

# 2호선 중 실제 이용하는 역만 필터링하여 새로운 데이터프레임 생성
df_2 = df[(df['호선'] == 2) & (df['역명'].isin(station))]

# 요일(조사일자), 방향(구분), 2호선을 기준으로 그룹화
df_2 = df_2.groupby(['조사일자', '구분', '호선']).mean()

df_wd_in_2 = pd.DataFrame(df_2.iloc[2,:])
df_wd_out_2 = pd.DataFrame(df_2.iloc[3,:])

df_we_in_2 = pd.DataFrame(df_2.iloc[0,:])
df_we_out_2 = pd.DataFrame(df_2.iloc[1,:])

2.6 2호선 혼잡도 시각화

2호선에 대한 시각화 또한 1호선과 동일하기 때문에 설명은 생략하도록 하겠습니다.

### 주중/주말 2호선 평균 혼잡도 그래프 ###
plt.figure(figsize = (20, 15)) #subplot 2개를 그리기 위한 크기 지정

## 주중 2호선 평균 혼잡도 그래프 ##

index = np.arange(len(df_wd_up_1.index)) #가로축 index 개수만큼 array 생성
x1 = df_wd_in_2.index 
ya = df_wd_in_2.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
yb = df_wd_out_2.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
mean1 = ((np.mean(df_wd_in_2.values) + np.mean(df_wd_out_2.values)) / 2).round(1) # 주중 상선/하선 혼잡도 평균

plt.subplot(2,1,1) # 1X2 subplot의 첫번째 그래프
p1 = plt.bar(index - 0.2, ya, color = '#2ca02c', alpha = 0.8, width = 0.4) # 상선에 대한 바그래프
p2 = plt.bar(index + 0.2, yb, color = '#2ca02c', alpha = 0.4, width = 0.4) # 하선에 대한 바그래프

plt.legend((p1[0], p2[0]), ('내선', '외선'), loc = 'upper right', fontsize=20) # 범례 지정

plt.xticks(index, x1, rotation = 60) #xticks 레이블 60도 회전
plt.title('주중 2호선 평균 혼잡도', fontweight = 'semibold')
plt.xlabel('시간')
plt.ylabel('혼잡도(%)')
plt.ylim(0, 90) # y축 범위 지정

plt.axhline(34, color='grey', linestyle=':', linewidth=2) # 만석 기준점(34%) 수평선 표시
plt.text(-2, 35, '만석 기준점 = 34.0%') # 만석 기준점(34%) 레이블 표시

plt.axhline(mean1, color='#d62728', linestyle='--', linewidth=2) # 혼잡도 평균 수평선 표시
plt.text(-2, mean1 - 3, f'평균 = {mean1}%') # 혼잡도 평균 레이블 표시


## 주말 2호선 평균 혼잡도 그래프 ##

index = np.arange(len(df_we_up_1.index)) #가로축 index 개수만큼 array 생성
x1 = df_we_in_2.index
ya = df_we_in_2.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
yb = df_we_out_2.values.flatten() # 데이터프레임의 2차원 value값을 1차원 배열로 변환
mean2 = ((np.mean(df_we_in_2.values) + np.mean(df_we_out_2.values)) / 2).round(1) # 주중 상선/하선 혼잡도 평균

plt.subplot(2,1,2) # 1X2 subplot의 두번째 그래프
# plt.bar(x2, y2, color = '#2ca02c')
p1 = plt.bar(index - 0.2, ya, color = '#2ca02c', alpha = 0.8, width = 0.4) # 상선에 대한 바그래프
p2 = plt.bar(index + 0.2, yb, color = '#2ca02c', alpha = 0.4, width = 0.4) # 하선에 대한 바그래프

plt.legend((p1[0], p2[0]), ('내선', '외선'), loc = 'upper right', fontsize=20) # 범례 지정

plt.xticks(index, x1, rotation = 60) #xticks 레이블 60도 회전
plt.title('주말 2호선 평균 혼잡도', fontweight = 'semibold')
plt.xlabel('시간')
plt.ylabel('혼잡도(%)')
plt.ylim(0, 70) # y축 범위 지정

plt.axhline(34, color='grey', linestyle=':', linewidth=2) # 만석 기준점(34%) 수평선 표시
plt.text(-2, 35, '만석 기준점 = 34.0%') # 만석 기준점(34%) 레이블 표시

plt.axhline(mean2, color='#d62728', linestyle='--', linewidth=2) # 혼잡도 평균 수평선 표시
plt.text(-2, mean2 + 1, f'평균 = {mean2}%') # 혼잡도 평균 레이블 표시

plt.tight_layout()
plt.show()

2.7 2호선 혼잡도 분석 결과

2호선 혼잡도 그래프를 살펴보면 다음과 같은 사실을 확인할 수 있습니다.

1) 1호선 보다 2호선의 혼잡도가 높은 편입니다. 평균 혼잡도를 비교했을 때 1호선은 21.1%, 17.5%인데 반해 2호선은 30.8%, 24.3%로 각각 9.7%p, 6.8%p 더 높습니다. 두 노선의 열차 칸 수와 수용량이 동일하기 때문에 혼잡도가 높다는 것은 그만큼 하루 평균 더 많은 승객이 탑승했다고 해석할 수 있습니다. 특히, 주중 2호선 8시 시간대에 혼잡도가 90% 가까이 육박하는 것을 볼 수 있는데 이는 지하철 수용량 인원인 160명에 가까운 인원이 타고 있다는 것을 의미합니다. 1호선의 평균 혼잡도의 최대치가 60%가 안 된다는 점과 비교했을 때 지하철 2호선의 출근 시간은 정말 혼잡하다라고 할 수 있습니다. 물론 지금의 분석은 2호선 전체 중 일부 구간(이대~시청)이기 때문에 전체를 일반화할 수는 없습니다. 다만, 제 경우에 이 시간대만큼은 가능한 피하는 것이 원활한 통학에 유리할 것으로 보입니다.

2) '주중'과 '주말' 지하철 혼잡도 분포의 차이가 있다는 것을 확인할 수 있습니다. '주중'이 '주말' 보다 혼잡도가 더 높은데 이는 1호선에서도 볼 수 있는 패턴이었습니다. 다만, 1호선과 비교했을 때 2호선의 경우 '주말' 저녁 시간대(18시~23시)에도 혼잡도가 높은 편이고 낮시간대에서 그다지 떨어지지 않는 양상을 보이고 있습니다. 이는 주말 저녁에 혼잡도가 급격히 떨어지는 1호선과는 다른 모습이라고 할 수 있습니다.  정확한 분석을 위해서는 하락율을 계산해서 비교해 보아야 겠지만 육안 상으로도 혼잡도가 계속 유지되고 있는 것을 확인할 수 있습니다. 2호선의 경우 저녁 시간대에도 혼잡도가 높은 것은 강남, 신촌 등 서울의 주요 유흥 지역들이 2호선에 위치해 있기 때문이라고 생각됩니다. 기회가 된다면 이 부분에 대해서도 추후에 다루어 보도록 하겠습니다.

+ Recent posts