Py) ML - 데이터 분할-02

Py) ML - 데이터 분할-02

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))
## (4, 1)
Your browser is out-of-date!

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

×