부스팅 모델 중 가장 유명한 XGBoos를 활용하여 커플 성사를 예측하고, 그리드 서치(Grid Search)로 하이퍼파라미터를 튜닝하여 더 나은 모델을 만드는 방법을 학습합니다.
총 4편으로 연재됩니다. 2편은 전처리 입니다.
전처리 : 결측치 처리
결측치를 처리해야 하니 항목별 결측치 비율을 확인하겠습니다.
data.isna( ) .mean( )
has_null 0.000000 gender 0.000000 age 0.011339 age_o 0.012413 race 0.007520 race_o 0.008713 importance_same_race 0.009429 importance_same_religion 0.009429 pref_o_attractive 0.010623 pref_o_sincere 0.010623 pref_o_intelligence 0.010623 pref_o_funny 0.011697 pref_o_ambitious 0.012772 pref_o_shared_interests 0.015397 attractive_o 0.025304 sincere_o 0.034256 intelligence_o 0.036524 funny_o 0.042970 ambitous_o 0.086178 shared_interests_o 0.128432 attractive_important 0.009429 sincere_important 0.009429 intellicence_important 0.009429 funny_important 0.010623 ambtition_important 0.011817 shared_interests_important 0.014443 attractive_partner 0.024111 sincere_partner 0.033063 intelligence_partner 0.035331 funny_partner 0.041776 ambition_partner 0.084984 shared_interests_partner 0.127357 interests_correlate 0.018859 expected_happy_with_sd_people 0.012055 expected_num_interested_in_me 0.785152 like 0.028646 guess_prob_liked 0.036882 met 0.044760 match 0.000000 dtype: float64
대부분의 변수에서 결측치가 보이나 대체로 5% 미만입니다. 우리가 이 장에서 사용할 XGBoost 알고리즘도 기본적으로 트리 베이스 모델이라서 결측치를 채우기는 까다롭지 않습니다.* ****데이터에 등장하지 않을 법한 임의의 숫자, 예를 들어 -99와 같은 숫자를 채워넣는 것으로 해당 사람은 해당 항목에 응답하지 않음을 나타내보겠습니다. 단, 중요도와 관련된 변수들은 결측치를 제거하는 방향으로 처리하겠습니다. 이유는 곧 진행할 피처 엔지니어링에서 중요도 X 점수로 계산을 하기 때문입니다. 평가 점수에 관한 변수는 무응답( 결측치)을 하나의 응답 종류로 간주하여 사용하기로 합니다.
그럼 우선 해당 변수들에 대해 dropna( ) 로 결측치를 제거하겠습니다.
data = data.dropna(subset=['pref_o_attractive', 'pref_o_sincere', 'pref_o_intelligence', 'pref_o_funny', 'pref_o_ambitious', 'pref_o_shared_interests','attractive_important', 'sincere_important', 'intellicence_important', 'funny_important', 'ambtition_important', 'shared_interests_important']) # 일부 변수에서 결측치 제거
그리고 나머지 변수들의 결측치는 -99로 채워넣겠습니다.
data = data.fillna(-99) # 남은 결측치는 -99로 대체
* 가령 -99로 결측치를 대체한다면, 선형모델에서는 해당 숫자가 아웃라이어로써 작용하겠지만 트리 모델에서는 결측치라는 사실 자체가 유 의미한 차이를 보인다면 -99를 분류하는 노드가 생겨날 겁니다.
전처리 : 피처 엔지니어링
이번 장에서는 피처 엔지니어링으로 다룰 내용이 꽤 있습니다. 가장 먼저 살펴볼 부분은 나이와 관련된 변수입니다. 데이터에 상대방 나이와 본인 나이가 있기 때문에, 이를 토대로 나이차가 얼마나 나는지를 계산할 수 있습니다. 여기에서 추가적로 고려해야 할 사항은 결측치입니다. 결측치를 -99로 채워넣었으므로 단순히 나이차를 계산해서는 안 됩니다. ‘알 수 없음’의 의미로 -99를 사용하겠습니다. 또 하나는 성별과 관련된 요인입니다. 단순한 나이 차이보다는 남자가 여자보다 많은지, 반대 경우인지도 고려하는 게 좋습니다.
여러 조건을 반영하여 계산해야 하니 함수로 만들고 각 조건에 알맞게 처리하겠습니다.
def age_gap(x): # 함수 정의
if x['age'] == -99: # age가 -99면
return -99 # -99 리턴
elif x['age_o'] == -99: # age_o가 -99면
return -99 # -99 리턴
elif x['gender'] == 'female': # gender가 female이면
return x['age_o'] - x['age'] # age_o에서 age를 뺀 값 리턴
else: # 나머지 경우는
return x['age'] - x['age_o'] # age에서 age_o를 뺀 값 리턴
❶ 남녀 중 한 명이라도 나이가 -99이면 -99를 반환합니다. ❷ 그렇지 않으면 남자가 연상이면 플러스값이, 여자가 연상이면 마이너스값이 반환됩니다.
정의한 age_gap( ) 함수를 데이터프레임에 적용시키겠습니다.
data['age_gap'] = data.apply(age_gap, axis=1) # age_gap 변수에 age_gap 함수 적용
남녀 중 어느 쪽이 더 나이가 많은지와 상관없이, 나이 차이 자체가 중요한 변수가 될 수도 있으므로, age_gap 변수에 절댓값을 취한 변수도 추가하겠습니다. 절댓값은 다음과 같이 abs( ) 를 사용하면 구할 수 있습니다.
data['age_gap_abs'] = abs(data['age_gap']) # 절댓값 적용
다음은 인종 데이터 관련 피처 엔지니어링입니다. 본인과 상대방의 인종이 같으면 1, 다르면 -1으로 처리합니다. 결측치는 -99를 반환하는 함수를 만들겠습니다.
def same_race(x): # 함수 정의
if x['race'] == -99: # race가 -99면
return -99 # -99 리턴
elif x['race_o'] == -99: # race_o가 -99면
return -99. # -99 리턴
elif x['race'] == x['race_o']: # race와 race_o가 같으면
return 1 # 1 리턴
else: # 나머지 경우는
return -1 # 1 리턴
그리고 위 함수를 적용하여 same_race라는 이름으로 새 변수를 만들겠습니다.
data['same_race'] = data.apply(same_race, axis=1)
# data를 same_race 함수에 적용하여 결과를 same_race 변수로 저장
인종과 관련된 변수로 importance_same_race도 있습니다. 동일 인종 여부가 얼마나 중요한지를 의미하기 때문에, 새로 구한 same_race 변수와 이 변수를 곱하여 새 변수를 만들겠습니다. 이 계산 결과는 동일 인종이면 양수, 아니면 음수이며 중요할수록 절댓값이 큽니다. 중요도가 0인 경우에는 결과값도 0이 나옵니다. 단, 여기서도 결측치가 있는 값은 -99를 반환하도록 함수를 만들겠습니다.
def same_race_point(x): # 함수 정의
if x['same_race'] == -99: # same_race가 -99면
return -99 # -99 리턴
else: # 나머지 경우는
return x['same_race'] * x['importance_same_race']
# same_race와 importance_same_race의 곱을 리턴
same_race_point( ) 함수를 적용하여 same_race_point라는 새 변수를 생성합니다.
data['same_race_point'] = data.apply(same_race_point, axis=1)
# data에 same_race_point 함수를 적용한 결과를 same_race_point 변수로 저장
그런데 왜 same_race에 1과 0 대신 1(인종이 같음)과 -1(인종이 다름)을 사용했을까요? 1과 0을 사용하면 importance_same_race와 곱하는 과정에서 인종 여부가 전혀 중요하지 않은 사람은 무조건 0이 나오게 되는데, 이것이 importance_same_race가 10인 사람이 동일 인종이 아닌 경우 취하게 되는 값(0)과 같아, 적절한 변별력을 갖기 어렵기 때문입니다. 아래 예시 표를 통해 살펴보겠습니다.
▼ same_race가 1과 0인 경우
❶ b는 인종 여부가 굉장히 중요한데, 동일 인종이 아니어서 0점을 받게 됐습니다. 반면 ❷ e는 동일 인종이지만 전혀 중요하지 않아서 역시 0점을 가지게 됩니다. ❷ e보다 ❶ b가 더 부정적인 상황인데, 이 둘이 같은 0점을 가져 변별력이 떨어집니다.
▼ same_race가 1과 -1인 경우
이번에는 ❶ b에서 -10이 나옵니다. 동일 인종임이 매우 중요한데 동일 인종이 아니므로 음수가 더욱 적절해보입니다. 반면 동일 인종 여부가 중요하지 않은 ❷ f는 0입니다. 딱히 좋을 것도 나쁠 것도 없다는 의미로 적절합니다. 1과 0을 사용했을 때보다 훨씬 변별력이 있고 합리적인 수치입니다.
마지막으로 attractive, sincere 등에 대한 평가/중요도 변수들을 다루겠습니다. 간단하게 평가 점수x중요도로 계산하여 새로운 변수를 만들 수 있습니다. 같은 계산을 여러 변수에 반복하므로 이번에도 함수로 만들겠습니다. 결측치는 역시나 -99를 반환합니다.
def rating(data, importance, score): # 함수 정의
if data[importance] == -99: # importance가 -99면
return -99 # -99 리턴
elif data[score] == -99: # score가 -99면
return -99 # -99 리턴
else: # 나머지 경우는
return data[importance] * data[score] # importance와 score의 곱을 리턴
매개변수가 3개입니다. 첫 번째 매개변수인 data는 데이터프레임을, importance와 score는 각각 중요도와 평가 변수를 받습니다. 이 함수로 여러 변수들을 계산할 겁니다. 그래서 importance와 score 매개변수를 입력했습니다(그래서 변수 별로 새 함수를 만들 필요 없이 호출할 때 중요도와 평가 항목만 바꿔주면 됩니다). 각각에 들어갈 컬럼명들을 리스트 형태로 취합하여 for문으로 일괄 계산하겠습니다.
for 문에 적용할 변수 이름을 직접 입력하여 리스트를 만들 수도 있지만, 중요도/평가 항목과 본인/상대방 항목 조합이 데이터프레임 안에 순서대로 나열되어 있어 인덱싱해 리스트를 더 쉽게 만들 수 있습니다. 예를 들어 상대방의 중요도에 대한 변수 이름을 다음과 같이 인덱싱할 수 있습니다.
data.columns[8:14] # 컬럼의 8자리부터 14 이전 자리까지 출력
Index(['pref_o_attractive', 'pref_o_sincere', 'pref_o_intelligence', 'pref_o_funny', 'pref_o_ambitious', 'pref_o_shared_interests'], dtype='object')
보시다시피 pref_o_xxx 형태로 된 변수 이름들만 모였습니다. 이 방법을 사용하여 총 4개 범주의 변수 이름 리스트를 만들겠습니다.
partner_imp = data.columns[8:14] # 상대방의 중요도
partner_rate_me = data.columns[14:20] # 본인에 대한 상대방의 평가
my_imp = data.columns[20:26] # 본인의 중요도
my_rate_partner = data.columns[26:32] # 상대방에 대한 본인의 평가
순서대로 상대방의 중요도, 본인에 대한 상대방의 평가, 본인의 중요도, 상대방에 대한 본인의 평가 변수들이 리스트로 만들어졌습니다.
여기서 하나 더 필요한 것은 계산된 값(평가 점수x중요도)을 받아줄 새 변수의 이름입니다. 이것도 리스트 형태로 직접 새 컬럼명들을 입력하여 만들겠습니다.
# 상대방 관련 새 변수 이름을 저장하는 리스트
new_label_partner = ['attractive_p', 'sincere_partner_p', 'intelligence_p', 'funny_p', 'ambition_p', 'shared_interests_p']
# 본인 관련 새 변수 이름을 저장하는 리스트
new_label_me = ['attractive_m', 'sincere_partner_m', 'intelligence_m', 'funny_m', 'ambition_m', 'shared_interests_m']
이제 ‘평가점수 × 중요도’를 계산하면 됩니다. 위 6개 리스트를 한 번에 3가지(새 변수 이름, 중요도, 평가) 리스트를 사용해 계산합니다. 즉, 새 변수 이름 = 중요도 변수 이름 × 평가 변수 이름이 되어야 하고, 이를 ‘본인 관련 새 변수’에서 한 번, ‘상대방 관련 새 변수’에서 또 한 번 시행해주면 됩니다.
💡 for문 in 뒤에 리스트를 2개 이상 사용하기
zip( ) 함수를 사용하면 for문에 리스트 3개를 동시에 사용할 수 있습니다. zip( ) 함수는 가변 인수를 받으므로 2개 이상의 객체(여기서는 리스트)를 받을 수 있습니다. zip( )을 사용해 리스트 3개를 순회하면서 원소를 반환하는 for문 예시 코드를 살펴봅시다.
for i,j,k in zip(new_label_partner, partner_imp, partner_rate_me):
print(i,' & ',j,' & ',k)
attractive_p & pref_o_attractive & attractive_o sincere_partner_p & pref_o_sincere & sincere_o intelligence_p & pref_o_intelligence & intelligence_o funny_p & pref_o_funny & funny_o ambition_p & pref_o_ambitious & ambitous_o shared_interests_p & pref_o_shared_interests & shared_interests_o
상대방에 관련 된 변수를 앞서 만든 rating( ) 함수를 사용하여 중요도 × 평가를 계산해봅시다.
for i,j,k in zip(new_label_partner, partner_imp, partner_rate_me): # 순회
data[i] = data.apply(lambda x: rating(x, j, k), axis=1) # ❶ rating함수적용결과를i변수에저장
❶ data 전체에 대해 apply( ) 를 활용하여 rating( ) 함수를 사용했습니다. rating( ) 함수의 매개변수는 각각 x = 데이터프레임, j = 중요도 변수 이름, k = 평가 변수입니다. 계산 결과는 중요도 × 평가 변수인 data[i]에 저장합니다. 여기에서 apply 안에 lambda x를 사용하면 해당 변수 i의 한 줄 한 줄의 데이터가 x로 받아져서 rating( ) 함수에 사용됩니다.
본인 관련된 변수들도 같은 방법으로 계산해줍니다.
for i,j,k in zip(new_label_me, my_imp, my_rate_partner):
data[i] = data.apply(lambda x: rating(x, j, k), axis=1)
3편은 모델링 및 평가, 경사하강법 이해하기 입니다.
==
삼성전자에 마케팅 직군으로 입사하여 앱스토어 결제 데이터를 운영 및 관리했습니다. 데이터에 관심이 생겨 미국으로 유학을 떠나 지금은 모바일 서비스 업체 IDT에서 데이터 사이언티스트로 일합니다. 문과 출신이 미국 현지 데이터 사이언티스트가 되기까지 파이썬과 머신러닝을 배우며 많은 시행착오를 겪었습니다. 제가 겪었던 시행착오를 덜어드리고, 머신러닝에 대한 재미를 전달하고자 유튜버로 활동하고 책을 집필합니다.
현) IDT Corporation (미국 모바일 서비스 업체) 데이터 사이언티스트
전) 콜롬비아 대학교, Machine Learning Tutor, 대학원생 대상
전) 콜롬비아 대학교, Big Data Immersion Program Teaching Assistant
전) 콜롬비아 대학교, M.S. in Applied Analytics
전) 삼성전자 무선사업부, 스마트폰 데이터 분석가
전) 삼성전자 무선사업부, 모바일앱 스토어 데이터 관리 및 운영
강의 : 패스트캠퍼스 〈파이썬을 활용한 이커머스 데이터 분석 입문〉
SNS : www.youtube.com/c/데싸노트