sklearn 라이브러리의 train_test_split()
함수를 사용하지 않고 Holdout Test를 위해 데이터를 분할하는 다양한 방법을 알아보자.
개요 일반적으로 예측 모델링을 위해 데이터세트를 2개 이상으로 분할하고자 하는 경우 특별한 사유가 있지 않은 이상 sklearn 라이브러리의 train_test_split()
함수를 활용한다. 하지만 평가 데이터세트가 별도의 변수로 구분이 되어있거나 데이터 분석 시험(ADP, ProDS, DATA 등)의 실기시험에서 출제의 용이성과 문제 난이도 조절을 위해 별도의 조치가 되어있는 경우는 단순 Pandas 객체 기반의 데이터 핸들링을 통해 해결한다. 이와 관련하여 다양한 사례를 알아보자.
실습 유형별로 Pandas 데이터세트 분할에 대해 알아보도록 하자.
단순 필터링형 데이터세트를 분할하기위해 별도의 변수가 준비된 경우가 많으며 변수명도 “Xgrp”인 경우가 대부분이다. 다음과 같이 데이터를 준비하자.
1 2 3 4 df1 = pd.DataFrame(dict(col1 = range(5 ), col2 = range(100 , 105 ), Xgrp = ["train" ] * 3 + ["test" ] * 2 )) df1
col1
col2
Xgrp
0
0
100
train
1
1
101
train
2
2
102
train
3
3
103
test
4
4
104
test
별다른 기술없이 단순 필터링으로 해결할 수 있다.
1 2 3 4 df1_train = df1.loc[df1["Xgrp" ] == "train" , ] df1_test = df1.loc[df1["Xgrp" ] == "test" , ] len(df1_train), len(df1_test) (3 , 2 )
배수 처리형 고객ID 같은 특정 식별자 변수에 있는 자연수를 대상으로 (자연수)n의 배수이면 test, n의 배수가 아니면 train으로 할당하는 방식이다. n의 배수에 해당하는 숫자는 그렇지 않은 다른 자연수보다 상대적으로 작기 때문에 test 세트로 할당되는 경우가 많다.
다음과 같이 “df2”객체를 준비하자.
1 2 3 df2 = pd.DataFrame(dict(ID = range(1 , 6 ), value = range(100 , 105 ))) df2
ID
value
0
1
100
1
2
101
2
3
102
3
4
103
4
5
104
나눗셈의 나머지를 구하기 위해 %
연산자를 사용할 수 있고 다음은 “ID”변수의 값이 5의 배수가 아닌 행은 “df2_train” 객체에 할당하고 “ID”변수의 값이 5의 배수인 행은 “df2_test” 객체에 할당하는 예제이다.
1 2 3 4 df2_train = df2.loc[df2["ID" ] % 5 != 0 , ] df2_test = df2.loc[df2["ID" ] % 5 == 0 , ] len(df2_train), len(df2_test) (4 , 1 )
단순 숫자 추출형 이 유형은 데이터 분할을 위해 참고하는 변수에 문자와 숫자가 섞여있으며 비교적 쉽게 숫자를 추출할 수 있는 경우이다.
1 2 3 4 df3 = pd.DataFrame(dict(ID = range(1 , 12 ), value = range(100 , 111 ))) df3["ID" ] = "lot_" + df3["ID" ].astype("str" ).str.zfill(3 ) df3
ID
value
0
lot_001
100
1
lot_002
101
2
lot_003
102
3
lot_004
103
4
lot_005
104
5
lot_006
105
6
lot_007
106
7
lot_008
107
8
lot_009
108
9
lot_010
109
10
lot_011
110
단순 “lot_” 문자를 제거한 경우가 “ID_1”, 숫자를 제외한 나머지 문자를 정규표현식(regular expression)을 활용하여 제거한 것이 “ID_2”, 밑줄(_)을 기준으로 문자열을 분리하고 두 번째 원소들만 취한 경우가 “ID_3” 변수이다. 그리고 “ID_1”, “ID_2”, “ID_3” 의 경우 숫자가 아니기 때문에 숫자로 변환해야 하는데 이를 실시한 것이 “ID_num” 변수이다.
1 2 3 4 5 df3["ID_1" ] = df3["ID" ].str.replace("lot_" , "" ) df3["ID_2" ] = df3["ID" ].str.replace("[^0-9]" , "" , regex = True ) df3["ID_3" ] = df3["ID" ].str.split("_" , expand = True )[1 ] df3["ID_num" ] = df3["ID_1" ].astype("int" ) df3
ID
value
ID_1
ID_2
ID_3
ID_num
0
lot_001
100
001
001
001
1
1
lot_002
101
002
002
002
2
2
lot_003
102
003
003
003
3
3
lot_004
103
004
004
004
4
4
lot_005
104
005
005
005
5
5
lot_006
105
006
006
006
6
6
lot_007
106
007
007
007
7
7
lot_008
107
008
008
008
8
8
lot_009
108
009
009
009
9
9
lot_010
109
010
010
010
10
10
lot_011
110
011
011
011
11
최종적으로 수치형으로 변환된 “ID_num”변수를 기준으로 3의 배수 여부에 따라 데이터를 분리하자면 다음과 같다.
1 2 3 4 df3_train = df3.loc[df3["ID_num" ] % 3 != 0 , ] df3_test = df3.loc[df3["ID_num" ] % 3 == 0 , ] len(df3_train), len(df3_test) (8 , 3 )
고급 숫자 추출형 이 유형은 .str
접근자의 .extract()
메서드와 정규표현식을 어느정도 사용할 수 있어야 하는 유형인데 만약 시험문제에서 이런 유형이 나온다면 처참한 정답률을 보일 것 같은 유형이다.
1 2 3 df4 = pd.DataFrame(dict(ID = ["lotA81023" , "lotB0021" , "order_329145" , "order_12932_Z3" , "re-498" ], value = range(100 , 105 ))) df4
ID
value
0
lotA81023
100
1
lotB0021
101
2
order_329145
102
3
order_12932_Z3
103
4
re-498
104
“ID” 변수를 보면 감이 오겠지만 특정 구분자를 통한 분리, 특정 문자열 제거를 통한 숫자 추출이 어려운 상태이다. 예를 들어 추출해야되는 숫자의 패턴이 세자리 이상으로 연속된 숫자라고 했을 경우 다음과 같이 코드를 작성할 수 있다.
1 2 3 df4["ID_1" ] = df4["ID" ].str.extract("([0-9]{3,})" ) df4["ID_num" ] = df4["ID_1" ].astype("int" ) df4
ID
value
ID_1
ID_num
0
lotA81023
100
81023
81023
1
lotB0021
101
0021
21
2
order_329145
102
329145
329145
3
order_12932_Z3
103
12932
12932
4
re-498
104
498
498
1 2 3 4 df4_train = df4.loc[df4["ID_num" ] % 5 != 0 , ] df4_test = df4.loc[df4["ID_num" ] % 5 == 0 , ] (len(df4_train), len(df4_test))