[iMOD(2)] Data Cleaning 부터

Jiwon Kim
|2023. 7. 20. 15:28

당시 나는 eda의 e자도 모르는 사람이었기에 일단

데이콘, 캐글, 그리고 기타 책들을 참고하여 아주 기본적인 EDA process를 따라해보기로 했다. 

 

EDA의 첫 번째 과정은 Data Cleaning이다. 

 

이 이후에 여러 가지 데이터 분석 프로젝트를 해보고 나서 돌이켜 보니, 

정말 내가 만난 데이터 중에서 제일 힘든?  총체적 난국?인 data cleaning 작업이었던 것 같다. 

 

약 360만건의 요청이력(rows)와 26개의 features (columns)가 주어졌다. 

 

 

" Data cleaning이  쉽지 않았던 이유 "

 

 

<첫번째>

 

[배차상태]에만 배차불가/시뮬레이션취소/시스템취소/탑승취소/하차완료 5가지 종류가 있었으며,

이는 또 다시 [배차상태상세]로 나누어졌다. 

예를 들어,

배차불가 - 운행가능차량없음, 중복된 이용신청 / 시스템취소 - 결제승인실패, 결제인증실패, 관리자취소, 시스템오류, 탑승권생성ㅍ실패.....  더 많다. 

 

한 줄로 정리하면 배차상태상세까지 분류하면 총 19가지의 상황이 발생하였다는 소리이고,

사실 이 19가지의 User Experience가 어떤식으로 (시간순으로) 발생하는지도 알 수 없었다. 

 

 

<두번째>

 

"사용자의 명시적인 재호출과 사용자앱의 자동 재시도를 모두 포함하며, 반복 호출도 포함되어 있고,

같은 사용자가 도보로 단거리 이동하면서 재호출한것까지 두번, 세번씩 이력에 남았다는 것이다." 

즉, 사용자의 실제 이동수요(O/D) 한 건에 대한 정보를 뽑아내기가 힘들었다는 것이다. 

 

 

이에 대한 간단한 예시를 들어보자면 다음과 같다. 

 

1. A씨가 집에서 나오면서 버스를 불렀다. (이력1 기록)

2. 호출하고자 하는 장소로 나가면서 보니 안잡혀서, 아파트 단지 걸어나가면서 호출을 취소하고 재호출을 요청했다. (이력2 기록)

3. 배차를 받았는데 너무 돌아 오는 것 같다. 경로가 마음에 안들어서 재시도! (이력3 기록)

4. 정거장에 가서 다시 호출했다. 이번에는 배차차량을 기다린 후, 차에 탑승했고, 원하는 장소에 하차까지 마무리했다. (이력4 기록)

 

실제로 A씨의 O/D는 하나이지만, 이력이 4번이나 기록되었다. 

 


My Solution
 (1) 호출 및 배차 과정에 대한 UI/UX의 정리
 (2) 사용자의 실제 이동수요가 하나의 row로 정리될 수 있도록 이력자료를 압축하기

 

PART I 

 

WHAT : 호출 - 배차 - 하차 과정에 대한 User interface & experience 정리

WHY : 배차 상태와 배차 상태 상세 조합이 총 19가지이다.  이에 대한 이해가 부족하다면 서비스를 대하는 이용자에 대한 insight  도출이 힘들 것이라 생각

HOW : 운영자 측에서 제공한 "배차상태" 컬럼 상세설명을 참고한다. 서비스 이용자에게 interface로 작용하는 i-mod 어플을 직접 깔아 구동해본다. 두 개를 활용하여 flow chart를 정리해본다. 

 

PROCESS :

(1) I-MOD는 전용 어플을 통해 호출이 가능한데, 서비스 도중 한 번 변화가 있으면서 최종 버젼은 IMOD 2.0이 되었다. 당시 안내문을 보면 결제 시점의 변화에 따라 바뀌었다. 따라서 2.0 버젼을 토대로 플로우차트를 정리해 보았다. 

앱을 구동해보면서 유저는 '희망 도착지에 도달'이라는 task를 이루기 위해 크게 '호출 - 배차 - 하차완료' 3가지 과정을 거친다는 것을 알 수 있었다. 

 

유저의 화면을 토대로 유저 플로우를 정리해보고 크게 3단계로 나누어 보았다.

 

(2) 이력자료에서 [배차상태]와 [배차상태상세]의 세부 항목을 나열해보고, 유저 플로우와 연결지어보았다. 

 

* ['배차상태'] = '시뮬레이션취소'는 구동 화면에서 2단계인 '배차'단계에서 '호출취소' 버튼을 누른 경우이다. 

* 이용자가 '배차진행' 버튼을 눌러 배차가 완료되었는데 여러가지 이유로 배차를 취소할 수 있다. - '개인사정/다른교통수단이용/승차시간 부족/승차시간 지연' 은 본인의 의지로 취소하는 경우

- '관리자취소/중복신청취소'는 시스템 상 취소된 경우

- '미탑승'은 자의 또는 타의로 인해 노쇼한 경우 (배차 후 호출을 취소하지 또는 취소되지 않았는데 미탑승 시 자동 환불되며, 노쇼에 대한 페널티 부과)

* ['배차상태'] = '하차완료'만이 구동 화면 상 1단계부터 3단계를 모두 거친 경우이다. 

 

 

 

 

 

 

 

 

 

위 두 자료를 바탕으로 직접 정리해본 User Flow Chart!!

 

이 플로우 알아내려고 정말 고생...을 고생. ㅠㅠ

 

 

PART II

 

WHAT : 실제 이동수요( O/D ) 한 건으로 호출이력 정리하기

WHY : 원본 그대로의 자료가 필요한 부분도 있지만, 어느 장소(정류장)이 출발 또는 도착희망 장소로 수요가 높은지를 알아보려면 실 이동수요로 정리된 데이터가 따로 필요하다. 

HOW : 이용자의 호출이력을 직접 살펴보며 위의 플로우에 따라 이용자의 입장에서 imod 서비스를 어느 단계까지 이용할 수 있었는지 해석하고, 하나의 O/D 건으로 볼 수 있을지 기준을 세워 통일된 기준으로 정리한다. 

PROCESS :

 

2월 15일 / ID '1' 이용자의 호출이력 (첫 다섯줄)

 

0행부터 8행, 9건의 기록을 살펴봤을 때 (x,y)좌표가 거의 변하지 않는다. (37.508#,126.513#) 

이는 대략적으로 한 장소 부근에서 지속적으로 호출을 시도했음을 의미한다.

0행부터 8행까지 좌표가 조금씩 움직이는 것이 지도 상 어느 정도의 위치 변화인지는, 지도에 직접 plot해보면 알 수 있다.

 

오 생각보다 거리가 있어서 약간 신기했음 (위치 감각이 없어서 거의 완전 제자리 걸음일줄)

 

참고로 궁금해서 어딘지 구글맵에서 찾아보기도 했는데, 모빌리티 오퍼레이션 센터였다. 역시 user #1은 운행사의 test user이었나보다 ! 

 

아무튼 test user임을 잠시 잊어보자.

0~2행 (배차불가) - 3~4행 (탑승취소:승차시간부족) - 5행 (시뮬레이션취소) - 6행 (탑승취소:승차시간부족) - 7행 (하차완료)의 시행착오를 겪어 목적지에 도달하게 된 것이다.  

9~11행도 비슷한 시간, 장소에서 발생한 호출이력인 것으로 보인다. 

결국, 나는 이렇게 총 12행을 (맥락상) 하나의 이동수요로 여러 호출이력들을 세 묶음으로 묶어주었다. ( 0~7 / 8 / 9~11 )

 

이러한 관찰을 반영하여 "실제 이동수요 (O/D) 한 건"으로 묶은 table을 만드는 것이 내 목표인데, 이를 위해서는 연관된 여러 개의 이력들을 어떻게 하나의 이동수요로 묶을 것이냐에 대한 통일된 기준이 필요하다. (왜냐하면 call은 너무 많고, 내가 user 개개인의 호출 이력을 모두 살펴보며 이를 정리함은 불가능하기 때문이다.)

 

통일된 <기준>을 세우기 위한 방법...

(1) 첫번째 접근 : 호출 위치 (공간정보)로 묶어보기

 

이게 단순하게 "호출위치"만을 기준으로 grouping하는 작업이었다면 위치좌표를 바탕으로 묶어줄 수 있는 방법을 이미 발행된 논문을 reference 삼아 시도했을텐데, 현재 '호출위치'만 보고 grouping 할 것이 아니라 호출시간, 배차 상태, 배차 상태 상세 등의 통합적 고려가 필요하기 때문에 내가 이 table의 다른 column들을 보면서 새로운 기준을 세우기로 했다. 

 

(2) 두번째 접근 : 배차상태에 따라 묶어보기

'suc_time' (배차성공시각) 이력이 기록여부와 'suc_status' (배차상태) 이력을 비교해보았다. 원래대로라면, '하차완료'한 이력들은 당연히 'suc_time'이력이 존재해야 하지만 존재하지 않는 이력이 3,748건이 존재한다. 하지만 기록상의 오류가 있을 수 있으므로 섣불리 삭제하지 않았다. '배차불가'한 이력들은 정상적으로 모두 'suc_time' 이력이 존재하지 않았다. 기관자료 설명상 '탑승취소'한 이력들은 모두 배차 성공 후 탑승 취소라고 설명되어 있어 원래대로라면 모두 'suc_time'기록이 존재해야하지만, 존재하지 않는 이력들이 2055건 있었다. 이들도 마찬가지로 삭제하지는 않았다. 

갑자기 이렇게 'suc_time' 이력의 기록여부에 따른 배차상태를 따져본 결과,  'suc_time' 또는 'cancel_time' column으로는 실 이동수요 grouping을 판단하기 위한 기준으로 삼기는 어렵다는 결론을 내렸다.

 

(3) 세번째 접근 : 탑승정류장 (공간정보)에 따라 묶어보기 

 

다시 돌아가서,,,, 데이터프레임을 위에서부터 쭉 보면서 grouping을 위해 줄을 긋는다고 상상해보자. 

호출장소를 고려하지 못하는 경우에 공간정보에 따라 줄을 어디에 그을지 고민한 결과 가장 이상적인 방법은 

 board_station (호출 시 탑승 희망 장소)가 바뀔 때마다 다른 수요라고 생각해주는 것이다. (단, 이미 데이터가 시간별 - 유저별 오름차순 정렬되어 있음)

물론 내가 집에서 학교갈 때 a라는 장소에서 불렀다가 b라는 장소에서 부르기로 다르게 마음 먹었어도 통행 목적은 "집"에서 "학교"로 가는 등교이니까

같은 수요 아닌가? 라고 반박할 수 있다. 

 

(이 부분에 대해서는 나중에 추가적으로 해결했다. 해결방법은 위의 반박에서 a라는 장소와 b라는 장소 사이의 유클리드 거리가 짧을 때는 board_station이 달라도, 혹은 alight_station이 다른 호출이더라도 실제로는 하나의 수요에 의한 호출이었을거라고 판단하는 것이다.  최종 발표를 위하여 정리할 때에는 연속된 승차 희망 정류장 사이의 거리와 연속된 하차 희망 정류장 사이의 거리의 합을 새로운 피쳐로 추가한 후, 정규화 스케일링을 진행했다.)

여러 접근을 반복하면서 일차적으로 이동수요를 구분하기 위한 제일 근접한 방법은 바로 윗행의 희망 승차 장소와 비교하는 것이었다.

 

따라서 기존의 데이터프레임에서 trip number 정보를 저장할 수 있는, 새로운 'Trip' 열을 추가하여

윗행과 같은 'board_station' 값을 가졌다면     ---------- >    trip number를 윗행과 동일하게 부여

윗행과 다른 'board_station' 값을 가졌다면     ----------->    윗행의 trip number에 1을 더함               이 되도록 만들었다.

 

그리고 같은 trip number를 가진 행들 중 가장 마지막 행을 '진짜 수요'라고 생각하고 

마지막 행만 남기는 작업을 했더니, 

기존 3,660,643건의 호출 이력에서 288,947건의 호출이력들로 감축되었다.

 

(4) 네번째 접근 : 호출간격 (시간정보)에 따라 묶어보기

 

마지막 접근은 호출시간의 간격 차이에 따라 분류해주는 것이다. 

이 또한 데이터가 미리 시간별-유저별 오름차순 정렬되어 있는 상태에서 연속된 두 호출 사이의 호출 시각의 차이가 15분 이내라면

같은 이동 수요에 대해 시스템 오류 / 단순 변심 / 배차가 늦음 등의 여러가지 이유들 때문에 단순 반복 재호출을 시행한다고 판단하는 것이다. 

 

따라서 (3) 접근과 마찬가지로 기존의 데이터프레임에서 trip number 정보를 저장할 수 있는, 새로운 'Trip' 열을 추가하여

윗행과 15분 이내의 'call_time' 값을 가졌다면     ---------- >    trip number를 윗행과 동일하게 부여

윗행과 15분 이상의 'call_time' 값을 가졌다면     ----------->    윗행의 trip number에 1을 더함               이 되도록 만들 수 있었다.

 

'Project' 카테고리의 다른 글

[청년주거(2)] 문제정의  (0) 2023.07.31
[청년주거(1)] 현상의 파악  (0) 2023.07.30
[iMOD(4)] 지표 도출  (0) 2023.07.26
[iMOD(3)] 현상의 파악  (0) 2023.07.20
[iMOD(1)] 나의 첫 데이터 분석기  (0) 2023.06.29