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 pddf = 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 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()
굳이 안해도 되지만 “환승역구분” 변수를 이진변수인 “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 StandardScalerarr_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 )