Py) 전처리 - ML 군집분석모델용 - 01

Py) 전처리 - ML 군집분석모델용 - 01

Python으로 머신러닝(ML) 군집분석모델에 들어갈 데이터를 전처리하는 방법을 알아보자.


※ 본 내용에서 사용하는 Korea_Busan_metro_stn_hour_gio_2023_utf8.csv, Korea_Busan_metro_stn_info_210226_utf8.csv 파일은 별도로 다운로드 받아야 한다.
Korea_Busan_metro_stn_hour_gio_2023_utf8.csv 다운받기 [클릭]
Korea_Busan_metro_stn_info_210226_utf8.csv 다운받기 [클릭]
※ 상기 데이터는 공공데이터 포털의 부산교통공사_시간대별 승하차인원부산교통공사_도시철도역사정보 데이터를 가공한 데이터이다.

개요

실무에서 주어지는 데이터는 바로 머신러닝 모델에 넣기 곤란한 경우가 많다. 그리고 이러한 데이터를 머신러닝 모델에 넣기 위해서는 전처리가 필요하다. 이번 게시물에서는 머신러닝 군집분석(clustering)에 들어갈 데이터를 전처리하는 방법을 알아본다.

여기서 부산교통공사의 지하철 승하차 데이터를 사용하려고 하며 환승역이 아닌 역에 대한 주중시간의 승차 데이터만 사용하려고 한다.

본 전처리 예제는 다음의 게시물의 내용을 모두 이해하고난 후에 도전하기 바란다.

그리고 다음과 같은 내용을 다룬다.

대분류 소분류 여부
데이터유형 단일 데이터
다중 데이터 O
일반 전처리 문자 데이터 핸들링 O
숫자 데이터 핸들링 O
시간 데이터 핸들링
필터링 O
정렬
피보팅
그룹 연산 O
축 연산
특정 변수 제거 O
데이터 병합 조인 연산
이어붙이기 O
이상치 처리 극단치 확인
극단치 제거
극단치 대치
결측치 확인
결측치 제거
결측치 대치
수치형 데이터 처리 단순 데이터 변환
특수 지표 생성
구간화
표준화 O
범주형 데이터 처리 단순 데이터 변환
특수 지표 생성
레이블 인코딩
원핫 인코딩
데이터 분할 단순 분할
단순 표본추출
층화 표본추출

데이터 준비

부산교통공사 지하철 승하차 데이터를 사용하여 데이터 전처리를 실습하려고 하며 제공되는 데이터는 인코딩이 “utf-8”이 아니기 때문에 먼저 인코딩을 변환해야 하는데 이 때 사용된 코드는 다음과 같다.

1
2
3
4
5
6
7
8
9
df = pd.read_csv("Korea_Busan_metro_stn_hour_gio_2023.csv", 
encoding = "CP949")
df.to_csv("Korea_Busan_metro_stn_hour_gio_2023_utf8.csv",
index = False, encoding = "utf-8")

df = pd.read_csv("Korea_Busan_metro_stn_info_210226.csv",
sep = "\t", encoding = "utf-16")
df.to_csv("Korea_Busan_metro_stn_info_210226_utf8.csv",
index = False, encoding = "utf-8")

상기 내용에 대한 좀 더 자세한 내용은 다음의 게시물을 참고하기 바란다.
Py) 기초 - 파일 인코딩 확인

“Korea_Busan_metro_stn_hour_gio_2023_utf8.csv” 의 각 변수명과 설명은 다음과 같다.

변수명 설명
역번호 지하철 역 번호
역명 지하철 역 이름
년월일 날짜 (년-월-일 형식)
요일 요일
구분 구분 (승차 또는 하차)
합계 총 승객 수
01시-02시 오전 1시부터 2시까지의 승객 수
02시-03시 오전 2시부터 3시까지의 승객 수
03시-04시 오전 3시부터 4시까지의 승객 수
04시-05시 오전 4시부터 5시까지의 승객 수
05시-06시 오전 5시부터 6시까지의 승객 수
06시-07시 오전 6시부터 7시까지의 승객 수
07시-08시 오전 7시부터 8시까지의 승객 수
08시-09시 오전 8시부터 9시까지의 승객 수
09시-10시 오전 9시부터 10시까지의 승객 수
10시-11시 오전 10시부터 11시까지의 승객 수
11시-12시 오전 11시부터 낮 12시까지의 승객 수
12시-13시 낮 12시부터 오후 1시까지의 승객 수
13시-14시 오후 1시부터 2시까지의 승객 수
14시-15시 오후 2시부터 3시까지의 승객 수
15시-16시 오후 3시부터 4시까지의 승객 수
16시-17시 오후 4시부터 5시까지의 승객 수
17시-18시 오후 5시부터 6시까지의 승객 수
18시-19시 오후 6시부터 7시까지의 승객 수
19시-20시 오후 7시부터 8시까지의 승객 수
20시-21시 오후 8시부터 9시까지의 승객 수
21시-22시 오후 9시부터 10시까지의 승객 수
22시-23시 오후 10시부터 11시까지의 승객 수
23시-24시 오후 11시부터 자정까지의 승객 수
24시-01시 자정부터 오전 1시까지의 승객 수

그리고 “Korea_Busan_metro_stn_info_210226_utf8.csv” 의 각 변수명과 설명은 다음과 같다.

변수명 설명
역번호 지하철역 번호
역사명 지하철역명
노선번호 지하철 노선 번호
노선명 지하철 노선명
영문역사명 지하철역 영문명
한자역사명 지하철역 한자명
환승역구분 환승역 여부 및 구분
환승노선번호 환승 가능한 다른 노선의 번호
환승노선명 환승 가능한 다른 노선의 이름
역위도 지하철역 위도 좌표
역경도 지하철역 경도 좌표
운영기관명 지하철역 운영 기관명
역사도로명주소 지하철역 도로명 주소
역사전화번호 지하철역 전화번호
데이터기준일자 데이터 기준 일자

이제 “Korea_Busan_metro_stn_hour_gio_2023_utf8.csv”, “Korea_Busan_metro_stn_info_210226_utf8.csv” 데이터를 다운받아 불러온다.
Korea_Busan_metro_stn_hour_gio_2023_utf8.csv 다운받기 [클릭]
Korea_Busan_metro_stn_info_210226_utf8.csv 다운받기 [클릭]

1
2
3
4
import pandas as pd

df = pd.read_csv("data/Korea_Busan_metro_stn_hour_gio_2023_utf8.csv")
df.iloc[:3, :10]
역번호 역명 년월일 요일 구분 합계 01시-02시 02시-03시 03시-04시 04시-05시
0 95 다대포해수욕장 2023-01-01 승차 6311 0 0 0 9
1 95 다대포해수욕장 2023-01-01 하차 5839 2 0 0 0
2 95 다대포해수욕장 2023-01-02 승차 4264 0 0 0 18

변수 개수가 많은데 “.columns” 어트리뷰트로 확인해보면 다음과 같다.

1
2
3
4
5
6
7
df.columns
## Index(['역번호', '역명', '년월일', '요일', '구분', '합계', '01시-02시', '02시-03시', '03시-04시',
## '04시-05시', '05시-06시', '06시-07시', '07시-08시', '08시-09시', '09시-10시',
## '10시-11시', '11시-12시', '12시-13시', '13시-14시', '14시-15시', '15시-16시',
## '16시-17시', '17시-18시', '18시-19시', '19시-20시', '20시-21시', '21시-22시',
## '22시-23시', '23시-24시', '24시-01시'],
## dtype='object')

그래서 다음과 같이 변수명을 변경해본다.

1
2
3
ls_col_hrs = ["h" + str(h).zfill(2) for h in range(1, 25)]
df.columns = ["stn_no", "stn_nm", "date", "wday", "type", "cnt"] + ls_col_hrs
df.iloc[:3, :10]
stn_no stn_nm date wday type cnt h01 h02 h03 h04
0 95 다대포해수욕장 2023-01-01 승차 6311 0 0 0 9
1 95 다대포해수욕장 2023-01-01 하차 5839 2 0 0 0
2 95 다대포해수욕장 2023-01-02 승차 4264 0 0 0 18

물론 직접 입력을 해서 변수명을 변경해도 되지만 문자형 데이터 핸들링을 위해 상기와 같이 코드를 작성하였다.
이제 “Korea_Busan_metro_stn_info_210226_utf8.csv” 데이터를 불러온다.

1
2
df_stn = pd.read_csv("data/Korea_Busan_metro_stn_info_210226_utf8.csv")
df_stn.iloc[:2, :8]
역번호 역사명 노선번호 노선명 영문역사명 한자역사명 환승역구분 환승노선번호
0 95 다대포해수욕장 S2601 부산도시철도 1호선 Dadaepo Beach 多大浦海水浴場 일반역 NaN
1 96 다대포항역 S2601 부산도시철도 1호선 Dadaepo Harbor 多大浦港 일반역 NaN

전처리

데이터 병합

여기서 각 역사의 “환승역” 정보를 추출하여 환승역은 제외하려고 한다. 그래서 “역번호”와 “환승역구분” 변수만 추출한다.

1
2
df_stn_sub = df_stn[["역번호", "환승역구분"]].copy()
df_stn_sub.head()
역번호 환승역구분
0 95 일반역
1 96 일반역
2 97 일반역
3 98 일반역
4 99 일반역

“환승역구분” 변수에는 어떤 값이 있는지 확인해보자.

1
2
3
4
5
df_stn_sub["환승역구분"].value_counts()
## 환승역구분
## 일반역 102
## 환승역 12
## Name: count, dtype: int64

굳이 안해도 되지만 “환승역구분” 변수를 이진변수인 “is_trns”로 변환해보자. 그리고 “환승역구분” 변수는 제거한다.

1
2
df_stn_sub["is_trans"] = (df_stn_sub["환승역구분"] == "환승역") + 0
df_stn_sub = df_stn_sub.drop(columns = "환승역구분")

이제 승하차 데이터와 환승역 여부 데이터가 있는 두 객체 “df”와 “df_stn_sub”를 병합한다.
※ “df_join” 객체의 변수가 너무 많아 결과값 출력을 특이하게 처리했다.

1
2
df_join = pd.merge(df, df_stn_sub, left_on = "stn_no", right_on = "역번호")
df_join.iloc[:3, list(range(8)) + [df_join.shape[1] - 1]]
stn_no stn_nm date wday type cnt h01 h02 is_trans
0 95 다대포해수욕장 2023-01-01 승차 6311 0 0 0
1 95 다대포해수욕장 2023-01-01 하차 5839 2 0 0
2 95 다대포해수욕장 2023-01-02 승차 4264 0 0 0

필터링

주말 데이터는 제거하고, 환승역도 제거하고, 승차 데이터만 추출한다.

1
2
3
4
5
df_join_sub = df_join.loc[~df_join["wday"].isin(["토", "일"]), ]
df_join_sub = df_join_sub.loc[df_join_sub["is_trans"] == 0, ]
df_join_sub = df_join_sub.loc[df_join_sub["type"] == "승차", ]
df_join_sub = df_join_sub.drop(columns = ["stn_nm", "is_trans", "type"])
df_join_sub.iloc[:3, :8]
stn_no date wday cnt h01 h02 h03 h04
2 95 2023-01-02 4264 0 0 0 18
4 95 2023-01-03 4431 1 1 0 27
6 95 2023-01-04 4631 0 0 0 18

역별 주차별 승차 데이터 취합

이 시점에서 군집분석을 위한 데이터 핸들링 방법이 여러갈래로 나뉠 수 있다. 이 게시물에서는 역별 주차별로 승차 데이터를 취합하고 해당 데이터를 역별로 또 한번 취합하여 군집분석을 위한 데이터를 만들어보려고 한다. 이를 위해 주차 계산을 월요일을 기준으로 누적합을 하여 주차를 구분하고자 한다.

1
2
3
4
df_join_sub = df_join_sub.reset_index(drop = True)
df_join_sub["is_Mon"] = (df_join_sub["wday"] == "월") + 0
df_join_sub["week_cnt"] = df_join_sub["is_Mon"].cumsum()
df_join_sub.iloc[:3, list(range(8)) + [df_join_sub.shape[1] - 1]]
stn_no date wday cnt h01 h02 h03 h04 week_cnt
0 95 2023-01-02 4264 0 0 0 18 1
1 95 2023-01-03 4431 1 1 0 27 1
2 95 2023-01-04 4631 0 0 0 18 1

이제 역별 주차별로 승차 데이터를 취합하기 위해 불필요한 변수를 제거한다.

1
2
df_join_sub = df_join_sub.drop(columns = ["date", "wday", "is_Mon"])
df_join_sub.iloc[:3, list(range(8)) + [df_join_sub.shape[1] - 1]]
stn_no cnt h01 h02 h03 h04 h05 h06 week_cnt
0 95 4264 0 0 0 18 66 114 1
1 95 4431 1 1 0 27 54 144 1
2 95 4631 0 0 0 18 63 139 1

이제 역별 주차별 승차 데이터를 취합한다.

1
2
df_g = df_join_sub.groupby(["stn_no", "week_cnt"]).sum().reset_index()
df_g.iloc[:3, :10]
stn_no week_cnt cnt h01 h02 h03 h04 h05 h06 h07
0 95 1 22672 4 5 0 109 299 685 2004
1 95 2 22452 0 0 0 92 295 723 1943
2 95 3 22825 0 0 4 95 314 716 1880

이제 역별 승차데이터의 주차별 최대값, 최소값, 평균값을 산출해본다. 물론 표준편차 같은 다른 통계량을 활용하는 것도 좋은 방법이다. 그리고 여기서 변수명을 특수하게 처리하는데 이는 .groupby().agg() 메서드 때문에 2중 인덱스가 생성되기 때문이며 해당 부분을 처리하기 위함이다.

1
2
3
4
df_g2 = df_g.drop(columns = "week_cnt").groupby("stn_no").agg(["min", "max", "mean"])
df_g2 = df_g2.reset_index()
df_g2.columns = ["stn_no"] + ['_'.join(map(str, t)) for t in df_g2.columns.to_list()][1:]
df_g2.iloc[:3, :8]
stn_no cnt_min cnt_max cnt_mean h01_min h01_max h01_mean h02_min
0 95 18892 34896 25529.519231 0 26 1.346154 0
1 96 13437 21070 18129.153846 0 8 0.653846 0
2 97 20002 27454 25155.076923 0 7 0.711538 0

정규화

이제 역별 데이터가 준비되었으니 정규화를 진행한다. 정규화를 위해 StandardScaler() 클래스를 사용하며 군집분석의 경우 학습/평가 데이터셋으로 분리할 필요가 없고 일회성으로 사용할 수 있기 때문에 fit_transform() 메서드를 사용하며, 여기서 역번호(stn_no)까지 정규화 하지 않도록 주의한다.

1
2
3
4
5
from sklearn.preprocessing import StandardScaler

arr_g2_nor = StandardScaler().fit_transform(df_g2.iloc[:, 1:])
df_g2_nor = pd.DataFrame(arr_g2_nor, columns = df_g2.columns[1:])
df_g2_nor.iloc[:3, :8]
cnt_min cnt_max cnt_mean h01_min h01_max h01_mean h02_min h02_max
0 -0.419547 -0.255062 -0.454305 0.0 4.224201 0.999101 0.0 -0.206897
1 -0.709056 -0.726680 -0.756644 0.0 0.248992 -0.165565 0.0 -0.623414
2 -0.360637 -0.508916 -0.469603 0.0 0.028147 -0.068510 0.0 0.070781

“stn_no” 변수를 한 번에 가장 앞에 붙이기 위해 pd.concat() 함수를 사용하여 조금 특이하게 코드를 작성했다.

1
2
df_model_nor = pd.concat([df_g2[["stn_no"]], df_g2_nor], axis = 1)
df_model_nor.iloc[:3, :8]
stn_no cnt_min cnt_max cnt_mean h01_min h01_max h01_mean h02_min
0 95 -0.419547 -0.255062 -0.454305 0.0 4.224201 0.999101 0.0
1 96 -0.709056 -0.726680 -0.756644 0.0 0.248992 -0.165565 0.0
2 97 -0.360637 -0.508916 -0.469603 0.0 0.028147 -0.068510 0.0

마무리

적당히 저장하면 끝.

1
df_model_nor.to_csv("model_clustering_busan_metro.csv", index = False)
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×