728x90
항만 대기시간 예측 및 운영시스템 개선 을 위한 첫번째 전처리 단계인, 항구별 대기율 산정에 관한 내용을 담고 있다.
1. 사용 데이터
데이터명 | 사용 목적 | merge 기준 컬럼 |
PORT MIS 입출항 현황 | 입출항 데이터 사용 > 서비스시간 산출 | 호출부호, 선박명 ,입항횟수_횟수, 입항횟수_연도 |
PORT MIS 시설사용허가현황 | 접안대기, 출항대기 데이터 파악 및 대기시간 산출 |
2. 전처리 설계
2.1. 선박 입출항 관련 서비스 지표
대기율 (WR) = TW/TS
TW : 대기시간 (Waiting Time)
TS : 서비스시간 (Service Time)
선박 입출항 관련 서비스 지표인 대기율(대기시간/서비스시간) 을 기준으로 대기율이 가장 높은 항구를 선정한다.
22년 기준 물동량이 가장 많은 항구인 부산, 인천, 광양, 울산을 대상으로 진행한다.
2.2. 대기율 산출 프로세스
선박 입출항 프로세스 : 입항 > 투묘&양묘 > 선석 접안 > 이안 > 출항 대기시간 : 선박이 입항에서 선석 접안 전까지 대기한 시간 서비스시간 : 선박이 선석 접안 후 양적하 등 서비스를 마치고 선석을 떠나기 전까지의 시간 |
이슈 : 접안, 이안, 출항 등 명확한 시점에 대한 데이터 부재 > 따라서, 4가지 경우로 나누어 서비스 시간을 산출했다.
대기시간 산출 방법 : 시설사용허가 중 사용 목적명이 '대기시간'인 지정일시(FROM ~ TO) 시간
서비스시간 산출 방법 : merge 후, 출항대기와 접안대기가 발생한 경우, 발생하지 않은 경우로 나누어 서비스시간 산출 방식을 다르게 함
- 입항 > 접안 > 출항
- 입항일시를 접안일시로 파악, 출항 - 입항 = 서비스 시간
- 입항 > 접안 > 출항대기 > 출항
- 출항 대기(처음) - 입항 = 서비스 시간
- 입항 > 접안 대기 > 접안 > 출항
- 출항 - 접안 대기 일시(마지막 시점) = 서비스 시간
- 입항 > 접안 대기 > 접안 > 출항대기 > 출항
- 출항 대기 (처음) - 접안 대기 일시(마지막 시점) = 서비스 시간
3. 항구별 대기율 산정_전처리
각각 항구별로 2022년 입출항, 시설사용허가 데이터를 merge 해서 사용했으며, 부산의 경우 약 8만건의 row 값을 확인했다.
예시코드는 대기율이 가장 높은 울산항 기준이며, 3년치 데이터(14만건) 를 불러와서 전처리한 코드이다.
3.0. 데이터 불러오기
- 시설사용허가현황 : 월별로 분리되어있어, 개별 파일을 불러온 후 이중 for 문(연도, 월)으로 concat > 컬럼명 지정
import pandas as pd
# 데이터 파일이 저장된 디렉토리 경로
directory = '../data/울산'
# 데이터프레임을 저장할 리스트 생성
dfs = []
# 202001부터 202212까지의 월을 대상으로 반복문 실행
for year in range(2020, 2023):
for month in range(1, 13):
# 파일 경로 생성
file_path = f'{directory}/울산_시설_{year}{month:02d}.xlsx'
# 엑셀 파일을 읽어와 데이터프레임 생성 후 리스트에 추가
try:
df = pd.read_excel(file_path)
dfs.append(df)
except FileNotFoundError:
print(f"파일을 찾을 수 없습니다: {file_path}")
# 모든 데이터프레임을 하나로 합치기
result_df = pd.concat(dfs, ignore_index=True)
combined_df = result_df.copy() # copy()
# 제목 삭제
combined_df = combined_df.iloc[3:]
# 컬럼명 지정
combined_df.columns = ['순번', '호출부호', '입항횟수_연도', '입항횟수_횟수', '시설사용횟수', '신고톤수', '선박명', '선사/대리점_코드',
'선사/대리점명', '신청시설_코드', '신청시설_번호', '신청시설명', '신청일시(FROM)',
'신청일시(TO)', '지정시설_코드', '지정시설_번호', '지정시설명', '지정일시(FROM)',
'지정일시(TO)', '사용목적명', '예보일시', '허가유무']
- 입출항 현황 데이터 : 1년단위로 구분 > 반복문 하나로 concat > 컬럼명 지정
directory = '../data/울산'
dfs = []
# 20,21,22 - 연도별
for year in range(2020, 2023):
file_path_ship = f'{directory}/울산_입출항_{year}.xlsx'
# Read Excel file and append DataFrame to the list
try:
df_ship = pd.read_excel(file_path_ship)
dfs.append(df_ship)
except FileNotFoundError:
print(f"파일을 찾을 수 없습니다: {file_path_ship}")
# concat
dfs = pd.concat(dfs, ignore_index=True)
dfs = dfs.iloc[11:] # 상단 11행 삭제 _ 제목 부분
# 컬럼명 변경
dfs.columns = ['항명', '호출부호', '선박명', '입항횟수_연도', '입항횟수_횟수', '구분', '외내', '입출', '총톤수', '입항일시',
'출항일시', 'CIQ수속일자', '수리일시', '항해구분', 'MRN 번호', '계선장소_코드', '계선장소_숫자', '계선장소명',
'차항지', '전출항지', '선박용도', '외항:한국인선원수/내항:해기사선원수', '외항:외국인선원수/내항:보통선원수',
'승객', '예선', '도선', '부선호출부호1', '부선호출부호2']
3.1. 컬럼 제거 및 결측치 확인
- 컬럼 제거
## 불필요한 컬럼 제거
# 시설사용허가현황
columns_to_drop = ['신청시설_코드', '신청시설_번호', '신청시설명', '신청일시(FROM)', '신청일시(TO)', '선사/대리점_코드', '선사/대리점명']
combined_df.drop(columns=columns_to_drop, inplace=True)
# 입출항 현황
columns_to_drop_dfs = ['외내', 'CIQ수속일자', 'MRN 번호', '차항지', '전출항지','외항:한국인선원수/내항:해기사선원수','외항:외국인선원수/내항:보통선원수', '승객', '예선', '도선', '부선호출부호1', '부선호출부호2']
dfs.drop(columns=columns_to_drop_dfs,inplace=True)
- 상세처리
- 입출 : 출항만 사용(같은 행 중복)
- 시설사용허가현황 : 미허가 행 제외
combined_df = combined_df[combined_df['허가유무'] != '미허가']
dfs = dfs[dfs['입출'] == '출항']
3.2. 데이터 병합(Merge)
- 기준 컬럼 타입 변경
combined_df['입항횟수_횟수'] = pd.to_numeric(combined_df['입항횟수_횟수'], errors='coerce')
combined_df['입항횟수_연도'] = pd.to_numeric(combined_df['입항횟수_연도'], errors='coerce')
dfs['입항횟수_횟수'] = dfs['입항횟수_횟수'].astype(float)
dfs['입항횟수_연도'] = dfs['입항횟수_연도'].astype(float)
combined_df['입항횟수_횟수'] = combined_df['입항횟수_횟수'].astype(float)
combined_df['입항횟수_연도'] = combined_df['입항횟수_연도'].astype(float)
- 데이터 병합
- 호출부호, 선박명, 입항횟수_횟수, 입항횟수_연도 기준, 입출항 데이터 기준으로 left merge
merged_left = pd.merge(dfs, combined_df, on= ['호출부호','선박명','입항횟수_횟수','입항횟수_연도'], how='left') # 입출항, 시설사용허가 left merge
merged_left
3.3 대기시간, 서비스시간 산출
- 중복 제거
# 중복된 행, unique값만 남기기
unique_values = merged_left.drop_duplicates()
- 접안대기, 출항대기 컬럼 생성(사용목적명에 따라)
# copy
temp = unique_values.copy()
# 데이터 타입으로 변경 , 잘못된 값 제거(6311 인덱스) - 형식 불일치 값 (3022-10-02 1건)
temp['지정일시(TO)'] = pd.to_datetime(temp['지정일시(TO)'], errors='coerce')
temp['지정일시(FROM)'] = pd.to_datetime(temp['지정일시(FROM)'], errors='coerce')
# 접안 대기시간,출항 대기시간 계산
temp['접안_대기시간'] = pd.NaT
temp['출항_대기시간'] = pd.NaT
temp.loc[temp['사용목적명'] == '접안대기', '접안_대기시간'] = temp['지정일시(TO)'] - temp['지정일시(FROM)']
temp.loc[temp['사용목적명'] == '출항대기', '출항_대기시간'] = temp['지정일시(TO)'] - temp['지정일시(FROM)']
- Service_Time_End 생성
- 출항대기시간 null 값인 경우에는, Service_Time_End 값이 '출항일시' 값
- 사용목적명이 '출항대기'일 경우에 Service_Time_End 값이 '지정일시(FROM)' 값
import numpy as np
# 'Service_Time_End' 열 생성
temp['Service_Time_End'] = np.nan
# '사용목적명'이 '출항대기'인 경우 'Service_Time_End' 값 할당
temp.loc[temp['사용목적명'] == '출항대기', 'Service_Time_End'] = temp['지정일시(FROM)']
# '출항_대기시간'이 null인 경우 'Service_Time_End' 값 할당
temp.loc[temp['출항_대기시간'].isnull(),'Service_Time_End'] = temp['출항일시']
- Anchor(= 접안대기), Service_Time_Start 생성
temp['Anchor'] = np.nan
# '사용목적명'이 '접안대기'인 경우 'Anchor' 값 할당
temp.loc[temp['사용목적명'] == '접안대기', 'Anchor'] = temp['지정일시(TO)']
# Anchor(=접안대기)가 null 인 경우, 입항 일시 값을 service time start로
temp['Service_Time_Start'] = np.where(temp['Anchor'].isnull(), temp['입항일시'], temp['Anchor'])
# 문자열을 datetime으로 변환
temp['Service_Time_End'] = pd.to_datetime(temp['Service_Time_End'])
temp['Service_Time_Start'] = pd.to_datetime(temp['Service_Time_Start'])
temp['Service_Time'] = temp['Service_Time_End'] - temp['Service_Time_Start']
- 대기율 산출
waiting = temp.copy()
# 접안_대기시간이 null 값인 경우 대기 0
waiting['접안_대기시간'].fillna(0, inplace = True)
# 데이터프레임에서 '접안_대기시간'과 'Service_Time' 컬럼을 timedelta 형식으로 변환
waiting['접안_대기시간'] = pd.to_timedelta(waiting['접안_대기시간'])
waiting['Service_Time'] = pd.to_timedelta(waiting['Service_Time'])
# timedelta 값에서 일(day)로 변환
waiting['접안_대기시간_NumDays'] = waiting['접안_대기시간'].dt.days
waiting['Service_Time_NumDays'] = waiting['Service_Time'].dt.days
# 'Service_Time' 컬럼에서 'days', 'hours', 'minutes'를 분리하여 새로운 열로 추가
waiting['Service_Time_일'] = waiting['Service_Time'].dt.days
waiting['Service_Time_시간'] = waiting['Service_Time'].dt.components.hours
waiting['Service_Time_분'] = waiting['Service_Time'].dt.components.minutes
# 'Service_Time'을 분으로 변환하여 'Service_Time_분' 열에 추가
waiting['Service_Time_분'] = waiting['Service_Time_일'] * 24 * 60 + waiting['Service_Time_시간'] * 60 + waiting['Service_Time_분']
# '접안_대기시간'을 분으로 변환하여 '접안_대기시간_분' 열에 추가
waiting['접안_대기시간_분'] = waiting['접안_대기시간_일'] * 24 * 60 + waiting['접안_대기시간_시간'] * 60 + waiting['접안_대기시간_분']
waiting['대기율'] = waiting['접안_대기시간_분'] / waiting['Service_Time_분']
해당 과정을 항구별로 각각 진행한다. (입항일시가 출항일시와 다른 경우에는 이상치로 판정하고 추가로 제거해주었다.)
4. 항구별 대기율 최종(2022)
- 울산 0.143032
- 부산 0.018844
- 인천 0.110807
- 광양 0.123489
728x90
'Project > Ship Waiting Time Prediction' 카테고리의 다른 글
PORTMIS 대기시간 예측 - 예측 결과 활용(대기비용, 온실가스배출량) (3) | 2023.12.05 |
---|---|
PORT MIS 대기시간 예측 - 3. 결측치 처리(시계열 데이터, 중앙값) (0) | 2023.11.30 |
PORT MIS 대기시간 예측 - 2. 입출항 기준 데이터셋 재구성(2차 전처리) (2) | 2023.11.23 |