구글 코랩에서 실행하기
|
2.1 수도권 생활이동 데이터 분석#
2.1.1 활용 데이터#
통행수요는 특정지역에서 출발(Trip Production)하거나, 특정지역으로 도착하는 통행(Trip Attraction) 형태로 존재할 수도 있고, 혹은 더 세분화 한다면 출발지-목적지 단위의 통행수요(Origin-Destination (OD) Travel Demand)로 나타낼 수 있다.
최근 많은 사람들이 스마트폰을 사용하고 따라서 통신사 기지국 기반으로 세밀한 통행패턴이 수집 가능하며, O-D 단위의 통행수요 데이터를 구득하기가 수월해졌다. 따라서 본 교재에서는 O-D 단위의 통행 데이터를 주로 활용하는 실습을 진행할 예정이다.
본 예제에서는 서울시에서 제공하는 수도권 생활이동 데이터를 활용하였다.
2.1.2 데이터 읽기 및 간단한 전처리#
간단한 예제를 위해 서울시에서 제공하는 수도권 생활이동 데이터 중, 2024년 3월 27일 하루치의 데이터만을 예제로 사용하였다.
원활한 실습을 위해 모든 데이터는 Google Drive에 업로드 하였으며,
gdown패키지를 사용해 데이터를 다운로드 받은 후 로드하는 방식을 사용하였다.
import gdown
import pandas as pd
import numpy as np
import os
import topojson as tp # 공간정보를 간소화 해주는 패키지 (분석속도 & 결과물 용량에 영향을 미침)
import geopandas as gpd
import pydeck as pdk
---------------------------------------------------------------------------
ModuleNotFoundError Traceback (most recent call last)
Cell In[1], line 1
----> 1 import gdown
2 import pandas as pd
3 import numpy as np
ModuleNotFoundError: No module named 'gdown'
def download_and_read_parquet(file_id, output_path="../data/chp2_od_data_2.parquet"):
try:
# Google Drive에서 파일 다운로드
gdown.download(id=file_id, output=output_path, quiet=False)
# Parquet 파일을 DataFrame으로 읽기
df = pd.read_parquet(output_path)
# 임시 파일 삭제 (데이터 용량이 매우 큰 경우 사용)
# os.remove(output_path) # 다운로드 받은 데이터를 삭제하고 싶지 않을 때는 해당 라인을 주석처리
return df
except Exception as e:
print(f"오류 발생: {e}")
return None
# 파일 불러오기
file_id = "1uJX9MuNX2J6J5SpE-RQy05XPsae6Fg4x" # 구글 드라이브에 업로드 된 파일의 ID
df = download_and_read_parquet(file_id)
Downloading...
From: https://drive.google.com/uc?id=1uJX9MuNX2J6J5SpE-RQy05XPsae6Fg4x
To: e:\book\urban-mobility-simulation\data\chp2_od_data_2.parquet
100%|██████████| 56.6M/56.6M [00:05<00:00, 11.3MB/s]
인터넷 환경으로 인해 파일을 불러오는게 느리다면, github에서 clone 할 때 가지고 온 data 폴더에 있는 파일을 직접 열 수 있다.
아래 코드를 실행해 보자.
df = pd.read_parquet("../data/chp2_od_data.parquet")
데이터 자동 로딩 함수를 아래와 같이 구현할 수도 있다. 매번 데이터를 다운로드하는 것은 시간이 오래 걸리므로, 로컬에 파일이 있는지 먼저 확인하고 없을 때만 구글 드라이브에서 다운로드하는 함수를 만들어보자.
이 함수는 다음과 같이 동작합니다:
먼저 지정된 경로에 파일이 있는지 확인
파일이 있으면 로컬 파일을 불러옴
파일이 없으면 구글 드라이브에서 자동으로 다운로드 후 불러옴
이렇게 하면 처음에는 다운로드가 필요하지만, 그 다음부터는 로컬 파일을 빠르게 불러올 수 있습니다.
def load_or_download_parquet(file_id, file_path="../data/chp2_od_data.parquet"):
"""
로컬 파일이 존재하면 불러오고, 없으면 구글 드라이브에서 다운로드하는 함수
Args:
file_id (str): 구글 드라이브 파일 ID
file_path (str): 로컬 파일 경로
Returns:
pandas.DataFrame: 불러온 데이터프레임
"""
if os.path.exists(file_path):
print(f"로컬 파일이 존재합니다: {file_path}")
df = pd.read_parquet(file_path)
print("로컬 파일을 성공적으로 불러왔습니다.")
return df
else:
print(f"로컬 파일이 존재하지 않습니다. 구글 드라이브에서 다운로드합니다.")
df = download_and_read_parquet(file_id, file_path)
if df is not None:
print("구글 드라이브에서 파일을 성공적으로 다운로드하고 불러왔습니다.")
return df
# 함수를 사용하여 데이터 불러오기
file_id = "1uJX9MuNX2J6J5SpE-RQy05XPsae6Fg4x"
df = load_or_download_parquet(file_id)
로컬 파일이 존재합니다: ../data/chp2_od_data.parquet
로컬 파일을 성공적으로 불러왔습니다.
df
| O_ADMDONG_CD | D_ADMDONG_CD | ST_TIME_CD | CNT | MOVE_DIST | MOVE_TIME | |
|---|---|---|---|---|---|---|
| 0 | 11110515 | 11110515 | 0 | 25.54 | 151.875714 | 1683.637143 |
| 1 | 11110515 | 11110515 | 1 | 79.29 | 104.120000 | 1210.596667 |
| 2 | 11110515 | 11110515 | 2 | 103.07 | 175.205714 | 360.190000 |
| 3 | 11110515 | 11110515 | 3 | 16.30 | 239.946667 | 2441.596667 |
| 4 | 11110515 | 11110515 | 4 | 34.73 | 215.720000 | 860.990000 |
| ... | ... | ... | ... | ... | ... | ... |
| 3792913 | 52800410 | 41590360 | 11 | 2.56 | 69512.350000 | 4087.830000 |
| 3792914 | 52800420 | 11350570 | 14 | 3.50 | 66976.630000 | 6354.770000 |
| 3792915 | 52800420 | 11440630 | 9 | 3.48 | 63894.260000 | 3725.740000 |
| 3792916 | 52800420 | 41273610 | 17 | 2.33 | 69839.840000 | 1864.100000 |
| 3792917 | 52800420 | 41590253 | 15 | 3.72 | 50118.480000 | 3850.300000 |
3792918 rows × 6 columns
컬럼의 명세는 아래와 같다. 행정동 단위의 O-D 통행량 및 통행시간 정보를 제공해주고 있다. 더 상세하게는 내/외국인 구분, 국적, 이동목적과 같은 정보도 제공을 해준다.
순번 |
영문 컬럼명 |
컬럼 설명 |
NULL 여부 |
NULL 대체값 |
형식 |
규칙 |
데이터 허용범위 |
비고 |
|---|---|---|---|---|---|---|---|---|
1 |
O_ADMDONG_CD |
출발 행정동 |
X |
- |
STRING |
- |
- |
행안부 8자리 코드체계 |
2 |
D_ADMDONG_CD |
도착 행정동 |
X |
- |
STRING |
- |
- |
행안부 8자리 코드체계 |
3 |
ST_TIME_CD |
출발 시간 |
X |
- |
STRING |
- |
- |
7-9시/17시-19시는 20분단위, 그 외 1시간 단위 |
4 |
FNS_TIME_CD |
도착 시간 |
X |
- |
STRING |
- |
- |
7-9시/17시-19시는 20분단위, 그 외 1시간 단위 |
5 |
IN_FORN_DIV_NM |
내/외국인 구분 |
X |
- |
STRING |
- |
- |
내국인, 단기외국인, 장기외국인 |
6 |
FORN_CITIZ_NM |
국적 |
X |
- |
STRING |
- |
- |
- |
7 |
MOVE_PURPOSE |
이동 목적 |
X |
- |
STRING |
- |
- |
1: 출근, 2 : 등교, 3: 귀가, 4: 쇼핑, 5: 관광, 6: 병원, 7: 기타 |
8 |
MOVE_DIST |
평균 이동 거리(m) |
X |
- |
DOUBLE |
- |
- |
- |
9 |
MOVE_TIME |
평균 이동 시간(분) |
X |
- |
DOUBLE |
- |
- |
- |
10 |
CNT |
이동인구 수 |
X |
- |
DOUBLE |
(18,2) |
- |
- |
11 |
ETL_YMD |
기준 년월 일 |
X |
- |
STRING |
yyyyMMdd |
데이터 기준 당일 |
- |
2.1.3 Basic visualization#
통행량 데이터의 경우 출발지/도착지에 대한 공간정보 및 통행시간 정보가 포함된 데이터이다.
도시/교통 분야의 데이터는 이처럼 시간과 공간의 정보를 동시에 가지고 있는 ‘시공간 데이터’인 경우가 많으며 이를 분석하기 위한 기초적인 분석기술 및 핵심 패키지를 이해하는 것이 중요하다.
공간데이터를 분석하기 위한 Python 패키지로는 대표적으로
geopandas가 있으며, 시각화를 위한 패키지로는folium,plotly,pydeck이 있다.위의 키워드로하여 인터넷 검색을 해보면 다양한 튜토리얼 및 예제 코드가 있으므로 시간을 할애해서 공부하는 것을 추천하며, 본 책에서는 상세히 다루지 않는다.
아래는 내가 추천하는 시간을 할애해서 공부하면 좋은 자료들이다.
수도권 생활이동 데이터의 경우 행정동 단위로 데이터가 존재하기 때문에 공간상에 시각화를 하기 위해서는 행정동의 geometry 정보를 담고 있는 데이터가 추가로 필요하다.
본 실습에서는 vuski/admdongkor 에서제공하는 행정동 경계 데이터를 활용하여 간단한 시각화를 수행한다
def load_or_download_geojson(file_id, output_path="../data/chp2_HangJeongDong_ver20230101.geojson"):
# 파일이 이미 존재하는지 확인
if os.path.exists(output_path):
print(f"파일이 이미 존재합니다: {output_path}")
try:
df = gpd.read_file(output_path, driver='GeoJSON')
print("기존 파일을 성공적으로 로드했습니다.")
return df
except Exception as e:
print(f"기존 파일 로드 중 오류 발생: {e}")
print("파일을 다시 다운로드합니다...")
try:
# Google Drive에서 파일 다운로드
gdown.download(id=file_id, output=output_path, quiet=False)
# GeoJSON 파일을 GeoDataFrame으로 읽기
df = gpd.read_file(output_path, driver='GeoJSON')
# 임시 파일 삭제 (데이터 용량이 매우 큰 경우 사용)
# os.remove(output_path) # 다운로드 받은 데이터를 삭제하고 싶지 않을 때는 해당 라인을 주석처리
return df
except Exception as e:
print(f"오류 발생: {e}")
return None
#### 행정경계 데이터 불러오기
file_id = "1u8V4h-yUef0g-RJ445wsBgHXpMyUj7ih" # 구글 드라이브에 업로드 된 파일의 ID
gdf_adm = load_or_download_geojson(file_id)
# gdf_adm = gpd.read_file('../data/chp2_HangJeongDong_ver20230101.geojson', driver='GeoJSON')
파일이 이미 존재합니다: ../data/chp2_HangJeongDong_ver20230101.geojson
기존 파일을 성공적으로 로드했습니다.
#### 서울, 경기, 인천 지역만 추출
gdf_adm = gdf_adm[gdf_adm['sidonm'].isin(['서울특별시', '경기도', '인천광역시'])].reset_index(drop=True)
#### 유동인구와 일치하는 행정동코드 컬럼(`adm_cd2`)을 숫자형태로 변환
gdf_adm['adm_cd2'] = pd.to_numeric(gdf_adm['adm_cd2'])
#### Divide by 100 and create the new column 'ADMDONG_CD'
gdf_adm['ADMDONG_CD'] = gdf_adm['adm_cd2'] / 100
#### Convert the 'ADMDONG_CD' column to integer
gdf_adm['ADMDONG_CD'] = (gdf_adm['ADMDONG_CD']).astype(int)
#### name, sgg 컬럼만 남기기
gdf_adm = gdf_adm[['adm_nm','ADMDONG_CD','geometry']].copy()
gdf_adm.plot()
<Axes: >
#### 행정동 데이터 기하구조 간소화
simplified_geometry = tp.Topology(gdf_adm, toposimplify=.005).to_gdf()
simplified_geometry.plot()
<Axes: >
# Interactive Map
simplified_geometry.explore(tiles = 'CartoDB positron')
#### 출발 행정동 및 시간단위로 통행량 aggregation
df_agg = df.groupby(['O_ADMDONG_CD', 'ST_TIME_CD']).agg({'CNT': 'sum'}).reset_index()
df_agg
| O_ADMDONG_CD | ST_TIME_CD | CNT | |
|---|---|---|---|
| 0 | 11110515 | 0 | 61.31 |
| 1 | 11110515 | 1 | 133.12 |
| 2 | 11110515 | 2 | 150.33 |
| 3 | 11110515 | 3 | 64.69 |
| 4 | 11110515 | 4 | 87.28 |
| ... | ... | ... | ... |
| 53544 | 52800410 | 15 | 3.50 |
| 53545 | 52800420 | 9 | 3.48 |
| 53546 | 52800420 | 14 | 3.50 |
| 53547 | 52800420 | 15 | 3.72 |
| 53548 | 52800420 | 17 | 2.33 |
53549 rows × 3 columns
# 1. 먼저 pandas의 merge 함수를 사용하여 df_agg를 기준으로 병합
merged_df = df_agg.merge(simplified_geometry,
left_on='O_ADMDONG_CD',
right_on='ADMDONG_CD',
how='inner')
# 2. 병합 결과를 GeoDataFrame으로 변환
vis_gdf = gpd.GeoDataFrame(merged_df, geometry='geometry', crs=simplified_geometry.crs)
# 시간대 1(새벽 1-2시)의 출발 통행량을 지도로 시각화
# - column='CNT': 통행량(CNT) 컬럼을 기준으로 색상 표시
# - legend=True: 범례 표시
# - cmap='Reds': 빨간색 계열 색상맵 사용 (값이 클수록 진한 빨간색)
# - tiles='CartoDB positron': 밝은 배경의 지도 타일 사용
# - style_kwds: 지도 스타일 설정 (불투명도 1, 경계선 두께 0.5)
vis_gdf[vis_gdf['ST_TIME_CD'] == 1].explore(column='CNT',
legend=True,
cmap='Reds',
tiles='CartoDB positron',
style_kwds={'fillOpacity': 1, 'weight': 0.5})
2.1.4 Exercise#
GPT와 같은 AI 툴을 적극 활용해 문제를 풀어봅시다. 다만, 출력된 코드를 보고 이해하고, 코드가 실행되지 않는다면 어디서 실행되지 않는지 파악하고 올바르게 수정할 수 있어야 합니다.
[1] 통행 수요 데이터 분석 및 시각화#
2.1 Section에서 불러온 2024년 3월 27일 통행 데이터를 가지고 아래와 같은 분석을 수행해보자.
시간에 따른 통행량을 그래프로 시각화 해보자. 몇시에 통행이 가장 많으며, 그 양은 얼마인가?
공간에 따른 통행량의 분포를 읍면동 단위로 시각화 해보자. 어느 지역에서 출발 통행량(Trip Production)이 가장 많으며 어느 지역에서 도착 통행량 (Trip Attraction)이 가장 많은가?
통행의 시공간적 분포를 함께 분석해보자. 가장 효과적인 시각화 방안은 무엇일까?
출발통행, 도착통행이 아닌, 출발지-목적지간의 통행을 시각화 해보자. O-D pairs의 수가 지나치게 많다면 이를 효과적으로 시각화 할 수 있을까? 어떤 패키지나 소프트웨어를 활용하면 좋을지 고민해보자.
구글 코랩에서 실행하기