구글 코랩에서 실행하기

2.2 스마트카드 데이터 분석#

  • 대중교통 이용자들의 승하차 정보를 담고 있는 스마트카드 데이터는 도시 교통 패턴을 이해하는 데 핵심적인 자료이다.

  • 본 섹션에서는 수도권의 버스 및및 지하철 승하차 데이터를 활용하여 기본적인 분석 및 시각화를 수행한다.

  • 본 분석에서 사용하는 데이터는 정류소 위치는 실제 위치지만 이용내역은 실제 데이터에 Noise를 입힌 가상의 데이터이다.

  • 본 섹션에서는 교통약자에 집중하여 분석을 수행합니다. 교통약자란 누구일까요? 스마트카드 데이터에서 해당 정보를 추출할 수 있을까?

연구 질문 (Research Question)#

  • 교통약자들의 이동 패턴은 일반 사람들과 비교해서 어떻게 다를까?

import pandas as pd
import os
from datetime import datetime, timedelta
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 1
----> 1 import pandas as pd
      2 import os
      3 from datetime import datetime, timedelta

ModuleNotFoundError: No module named 'pandas'
# 시각화 라이브러리
import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
import platform

# 운영체제별 한글 폰트 설정
system = platform.system()
if system == 'Windows':
    plt.rcParams['font.family'] = 'Malgun Gothic'
    font_name = 'Malgun Gothic'
elif system == 'Darwin':  # macOS
    plt.rcParams['font.family'] = 'AppleGothic'
    font_name = 'AppleGothic'
else:  # Linux
    plt.rcParams['font.family'] = 'DejaVu Sans'
    font_name = 'DejaVu Sans'

plt.rcParams['axes.unicode_minus'] = False  # 마이너스 기호 깨짐 방지

# seaborn 스타일 적용
sns.set(font=font_name, rc={"axes.unicode_minus": False})

# 그래프 크기 기본 설정
plt.rcParams['figure.figsize'] = (10, 6)

2.2.1 데이터 읽기#

def load_trip_data(date):
    """
    특정 날짜의 TCD 데이터를 불러오는 함수
    
    Args:
        date (str): 날짜 문자열 (예: '20240923')
    
    Returns:
        pd.DataFrame: 데이터프레임
    """
    file_path = f'../data/smartcard/TCD_{date}_modify.parquet'
    df = pd.read_parquet(file_path)
    return df

# 날짜 설정 및 데이터 로드
date = '20240923'
df = load_trip_data(date)
print("=" * 50)
print("📊 스마트카드 승하차 이력 데이터 (TCD) 정보")
print("=" * 50)
print(f"- 데이터 크기: {df.shape[0]:,}건의 승하차 이력")
print(f"- 컬럼 수: {df.shape[1]}개")
print("- 설명: 대중교통(버스, 지하철) 이용자들의 승차/하차 정보를 담고 있는 교통카드 이용 내역")
print("- 포함 정보: 승하차 시간, 정류장/역 정보, 요금, 이동거리, 환승정보, 이용자 구분 등")
print("- 주요 컬럼:")
print("  * 교통카드 사용자 구분 코드: 1(일반), 2(어린이), 3(청소년), 4(경로), 5(장애인)")
print("  * 환승 건수: 해당 통행에서의 환승 횟수")
print("  * 총 통행 거리: 미터 단위")
print("  * 총 소요 시간: 초 단위")
print("=" * 50)
==================================================
📊 스마트카드 승하차 이력 데이터 (TCD) 정보
==================================================
- 데이터 크기: 770,455건의 승하차 이력
- 컬럼 수: 123개
- 설명: 대중교통(버스, 지하철) 이용자들의 승차/하차 정보를 담고 있는 교통카드 이용 내역
- 포함 정보: 승하차 시간, 정류장/역 정보, 요금, 이동거리, 환승정보, 이용자 구분 등
- 주요 컬럼:
  * 교통카드 사용자 구분 코드: 1(일반), 2(어린이), 3(청소년), 4(경로), 5(장애인)
  * 환승 건수: 해당 통행에서의 환승 횟수
  * 총 통행 거리: 미터 단위
  * 총 소요 시간: 초 단위
==================================================

정류소 정보 불러오기#

# STTN 파일 불러오기 (20240923 데이터만)
def load_sttn_data(date):
    """
    특정 날짜의 STTN 데이터를 불러오는 함수
    
    Args:
        date (str): 날짜 문자열 (예: '20240923')
    
    Returns:
        pd.DataFrame: 정류장 데이터프레임
    """
    file_path = f'../data/smartcard/STTN_{date}.parquet'
    sttn_df = pd.read_parquet(file_path)
    return sttn_df

# STTN 데이터 로드
sttn_df = load_sttn_data(date)

# 필요한 컬럼들의 고유값만 남기기
sttn_unique = sttn_df[['정류장 ID', '정류장 명칭', '정류장 ARS번호', '정류장 X 좌표', 
                       '정류장 Y 좌표', '시도코드', '시도명', '시군구코드', '시군구명', 
                       '읍면동코드', '읍면동명']].drop_duplicates()

sttn_unique
정류장 ID 정류장 명칭 정류장 ARS번호 정류장 X 좌표 정류장 Y 좌표 시도코드 시도명 시군구코드 시군구명 읍면동코드 읍면동명
0 3100001 동울산우체국 ~ 35.51093 129.4297 31 울산광역시 31170 동구 3117010400 전하동
1 3100002 청량초등학교 ~ 35.48747 129.2983 31 울산광역시 31710 울주군 3171026221 청량읍
2 3100003 청량초등학교 ~ 35.48757 129.29838 31 울산광역시 31710 울주군 3171026221 청량읍
3 3100004 화봉쌍용예가 ~ 35.5938 129.3676 31 울산광역시 31200 북구 3120012500 화봉동
4 3100005 화봉휴먼시아2단지 ~ 35.59398 129.3677 31 울산광역시 31200 북구 3120012500 화봉동
... ... ... ... ... ... ... ... ... ... ... ...
129824 8019 남창 ~ 35.418539 129.28289 31 울산광역시 31710 울주군 3171025624 온양읍
129825 8020 망양 ~ 35.456522 129.287872 31 울산광역시 31710 울주군 3171025623 온양읍
129826 8021 덕하 ~ 35.493964 129.303064 31 울산광역시 31710 울주군 3171026224 청량읍
129827 8022 개운포 ~ 35.507907 129.321188 31 울산광역시 31140 남구 3114011000 상개동
129828 8023 태화강 ~ 35.53844 129.353792 31 울산광역시 31140 남구 3114010600 삼산동

128554 rows × 11 columns

print("=" * 50)
print("🚏 정류장 정보 데이터 (STTN) 정보")
print("=" * 50)
print(f"- 데이터 크기: {sttn_unique.shape[0]:,}개의 고유 정류장")
print(f"- 컬럼 수: {sttn_unique.shape[1]}개")
print("- 설명: 대중교통 정류장 및 지하철역의 위치 정보와 행정구역 정보를 담고 있는 데이터")
print("- 포함 정보: 정류장 ID, 명칭, 좌표, 행정구역코드, 행정구역명 등")
print("- 주요 컬럼:")
print("  * 정류장 ID: 각 정류장의 고유 식별번호")
print("  * 정류장 X/Y 좌표: 위경도 좌표 (WGS84)")
print("  * 시도/시군구/읍면동: 행정구역 코드 및 명칭")
print("- 활용: TCD 데이터의 승하차 정류장 정보와 매핑하여 공간분석 수행")
print("=" * 50)
==================================================
🚏 정류장 정보 데이터 (STTN) 정보
==================================================
- 데이터 크기: 128,554개의 고유 정류장
- 컬럼 수: 11개
- 설명: 대중교통 정류장 및 지하철역의 위치 정보와 행정구역 정보를 담고 있는 데이터
- 포함 정보: 정류장 ID, 명칭, 좌표, 행정구역코드, 행정구역명 등
- 주요 컬럼:
  * 정류장 ID: 각 정류장의 고유 식별번호
  * 정류장 X/Y 좌표: 위경도 좌표 (WGS84)
  * 시도/시군구/읍면동: 행정구역 코드 및 명칭
- 활용: TCD 데이터의 승하차 정류장 정보와 매핑하여 공간분석 수행
==================================================

2.2.2 O-D 데이터 만들기#

  • 현재의 데이터는 O-D 데이터라고는 할 수 없습니다. 개인의 통행이 여러번에 걸쳐서 발생하기 때문에 개별 Trip 마다 컬럼의 수가 달라지는 문제가 있습니다.

  • 이 문제를 어떻게 해결할 수 있을까요?

  • 완결성 있는 정류소 단위의 출발지-목적지 데이터를 만들어 봅시다.

  • 출발지 정류소 ID | 교통카드 사용자 구분 코드 | 출발시간 | 도착시간 | 도착지 정류소 ID | 환승횟수 | 총 통행 거리 | 총 탑승 시간 | 총 소요 시간 컬럼을 포함해서 만들어보세요

from datetime import timedelta

# '시작'과 '종료'로 시작하는 컬럼 추출
start_end_cols = [col for col in df.columns if col.startswith(('시작', '종료'))]

# 기본 컬럼 리스트
base_cols = [
    "교통카드 사용자 구분 코드",
    "환승 건수",
    "총 통행 거리",
    "총 탑승 시간", 
    "총 소요 시간"
]

# 모든 컬럼 합치기
all_cols = start_end_cols + base_cols

# 원하는 컬럼만 정제
df_refined = df[all_cols].rename(columns={
    "시작 승차 역 ID": "출발지 정류소 ID",
    "종료 하차 역 ID": "도착지 정류소 ID",
    "환승 건수": "환승횟수"
})

print(df_refined.head())
           시작 승차 일시 시작 교통수단 코드 시작 교통수단 구분 코드 시작 승차 노선 ID 출발지 정류소 ID 시작 승차 금액  \
0  20240923221246.0        202             T         106       1853        0   
1  20240923124248.0        203             T         208       2826        0   
2  20240923081625.0        203             T         208       2823        0   
3  20240923163554.0        202             T         106       1855        0   
4  20240923160512.0        203             T         208       2826        0   

           종료 하차 일시 종료 교통수단 코드 종료 교통수단 구분 코드 종료 하차 노선 ID 도착지 정류소 ID  \
0  20240923222933.0        202             T         106       1857   
1  20240923125036.0        203             T         208       2827   
2  20240923082515.0        203             T         208       2827   
3  20240923164257.0        202             T         106       1856   
4  20240923162457.0        203             T         208       2821   

  교통카드 사용자 구분 코드 환승횟수 총 통행 거리 총 탑승 시간 총 소요 시간  
0              1    0    8100    1007  1007.0  
1              4    0    1000     468   468.0  
2              4    0    3500     530   530.0  
3              2    0    1100     423   423.0  
4              1    0    5300    1185  1185.0  
# 문자열에서 뒤의 ".0" 제거
df_refined["출발시간"] = (
    df_refined["시작 승차 일시"]
    .astype(str)                      # 문자열로 변환
    .str.replace(r"\.0$", "", regex=True)  # 뒤의 ".0" 제거
    .pipe(pd.to_datetime, format="%Y%m%d%H%M%S", errors="coerce")  # datetime 변환
)

df_refined["도착시간"] = (
    df_refined["종료 하차 일시"]
    .astype(str)
    .str.replace(r"\.0$", "", regex=True)
    .pipe(pd.to_datetime, format="%Y%m%d%H%M%S", errors="coerce")
)

print(df_refined[["시작 승차 일시", "출발시간", "종료 하차 일시", "도착시간"]].head())
           시작 승차 일시                출발시간          종료 하차 일시                도착시간
0  20240923221246.0 2024-09-23 22:12:46  20240923222933.0 2024-09-23 22:29:33
1  20240923124248.0 2024-09-23 12:42:48  20240923125036.0 2024-09-23 12:50:36
2  20240923081625.0 2024-09-23 08:16:25  20240923082515.0 2024-09-23 08:25:15
3  20240923163554.0 2024-09-23 16:35:54  20240923164257.0 2024-09-23 16:42:57
4  20240923160512.0 2024-09-23 16:05:12  20240923162457.0 2024-09-23 16:24:57
# 컬럼명 통일을 위한 매핑 딕셔너리
column_mapping = {
    "시작 승차 일시": "출발 승차 일시",
    "시작 교통수단 코드": "출발 교통수단 코드", 
    "시작 교통수단 구분 코드": "출발 교통수단 구분 코드",
    "시작 승차 노선 ID": "출발 승차 노선 ID",
    "시작 승차 금액": "출발 승차 금액",
    "종료 하차 일시": "도착 하차 일시",
    "종료 교통수단 코드": "도착 교통수단 코드",
    "종료 교통수단 구분 코드": "도착 교통수단 구분 코드", 
    "종료 하차 노선 ID": "도착 하차 노선 ID"
}

# 컬럼명 변경
df_refined = df_refined.rename(columns=column_mapping)

# 변경된 컬럼명 확인
df_refined.columns
Index(['출발 승차 일시', '출발 교통수단 코드', '출발 교통수단 구분 코드', '출발 승차 노선 ID', '출발지 정류소 ID',
       '출발 승차 금액', '도착 하차 일시', '도착 교통수단 코드', '도착 교통수단 구분 코드', '도착 하차 노선 ID',
       '도착지 정류소 ID', '교통카드 사용자 구분 코드', '환승횟수', '총 통행 거리', '총 탑승 시간', '총 소요 시간',
       '출발시간', '도착시간'],
      dtype='object')

2.2.3 기초 통계 분석#

  • 시간대별 승하차 패턴을 분석해봅시다. 언제 가장 이용량이 많을까요?

  • 비교통약자, 어린이, 경로, 장애인별로 평균탑승시간 및 평균이동거리, 환승횟수수를 시각화 해보고 해석해 봅시다.

2.2.4 공간 분석#

  • 스마트카드 데이터에는 위치 정보 (위도 및 경도)가 존재하지 않습니다.

  • 어떻게 하면 스마트카드 데이터를 공간상에 시각화 할 수 있을까요? 아래 작업을 수행해 봅시다.

    • 승차량이 많은 지역을 읍면동 단위로 공간상에 나타내봅시다

    • 하차량이 많은 지역을 읍면동 단위로 공간상에 나타내봅시다

## 1. 연산효율화를 위해 출발지-도착지 단위로 df_refined aggregation. 출발정류소ID, 도착정류소 ID, 교통카드 사용자 구분 코드, cnt

## 2. df_refined랑 sttn_unique merge. key 값은 df_refined: (출발지 정류소 ID, 도착지 정류소 ID), sttn_unique: 정류장 ID

## 3. 공간분석을 위한 데이터 생성. 승차량 기준 geodataframe 및 하차량 기준 geodataframe 생성 

df_refined
출발 승차 일시 출발 교통수단 코드 출발 교통수단 구분 코드 출발 승차 노선 ID 출발지 정류소 ID 출발 승차 금액 도착 하차 일시 도착 교통수단 코드 도착 교통수단 구분 코드 도착 하차 노선 ID 도착지 정류소 ID 교통카드 사용자 구분 코드 환승횟수 총 통행 거리 총 탑승 시간 총 소요 시간 출발시간 도착시간
0 20240923221246.0 202 T 106 1853 0 20240923222933.0 202 T 106 1857 1 0 8100 1007 1007.0 2024-09-23 22:12:46 2024-09-23 22:29:33
1 20240923124248.0 203 T 208 2826 0 20240923125036.0 203 T 208 2827 4 0 1000 468 468.0 2024-09-23 12:42:48 2024-09-23 12:50:36
2 20240923081625.0 203 T 208 2823 0 20240923082515.0 203 T 208 2827 4 0 3500 530 530.0 2024-09-23 08:16:25 2024-09-23 08:25:15
3 20240923163554.0 202 T 106 1855 0 20240923164257.0 202 T 106 1856 2 0 1100 423 423.0 2024-09-23 16:35:54 2024-09-23 16:42:57
4 20240923160512.0 203 T 208 2826 0 20240923162457.0 203 T 208 2821 1 0 5300 1185 1185.0 2024-09-23 16:05:12 2024-09-23 16:24:57
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
770450 20240923151149.0 533 B 41002019 4160070 2800 20240923160141.0 582 B 41382002 4118191 1 2 24657 1826 2992.0 2024-09-23 15:11:49 2024-09-23 16:01:41
770451 20240923134139.0 582 B 41213002 4100527 1350 20240923135016.0 582 B 41213002 4100415 1 0 3221 517 517.0 2024-09-23 13:41:39 2024-09-23 13:50:16
770452 20240923082721.0 202 T 106 1851 1400 20240923084049.0 202 T 106 1854 1 0 4200 808 808.0 2024-09-23 08:27:21 2024-09-23 08:40:49
770453 20240923155132.0 202 T 106 1854 1400 20240923160420.0 202 T 106 1852 1 0 3200 768 768.0 2024-09-23 15:51:32 2024-09-23 16:04:20
770454 20240923165823.0 582 B 41221003 4115022 1350 20240923170307.0 582 B 41221003 4115021 1 0 918 284 284.0 2024-09-23 16:58:23 2024-09-23 17:03:07

770455 rows × 18 columns