[실전 머신러닝] XGBoost:커플 성사 여부 예측하기 ❷

부스팅 모델 중 가장 유명한 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/데싸노트

Leave a Reply

©2020 GoldenRabbit. All rights reserved.
상호명 : 골든래빗 주식회사
(04051) 서울특별시 마포구 양화로 186, 5층 512호, 514호 (동교동, LC타워)
TEL : 0505-398-0505 / FAX : 0505-537-0505
대표이사 : 최현우
사업자등록번호 : 475-87-01581
통신판매업신고 : 2023-서울마포-2391호
master@goldenrabbit.co.kr
개인정보처리방침
배송/반품/환불/교환 안내