2023-10-30 38th Class
Business Model
#️⃣ 마크 존슨 (2008) 연구의 비즈니스 모델 구성 요소
- 고객 가치 제안: 고객을 위해 가치를 창출할 방법
- 수익 공식: 고객에게 가치를 제공하면서 기업을 위한 가치를 창출하는 방법
- 핵심 자원: 목표 고객에게 제안한 가치를 제공하는 데 필요한 인력, 기술, 제품, 설비, 브랜드 등의 자산
- 핵심 절차: 규칙, 지표, 관행과 더불어 수익성있는 방식으로 고객 가치 제안을 달성하는 일을 반복적이며 대규모로 하는 방법
#️⃣ 알렉산더 오스터왈더 (2010)의 비즈니스 모델 캔버스 (9’ BLOCK)
- 고객 세그먼트: 고객은 누구인가? -> 매스 마켓, 틈새시장, 멀티 사이드 시장 등
- 가치제안: 핵심 가치는 무엇인가? -> 우수한 성능, 개인화, 탁월한 디자인 등
- 채널: 어떻게 가치를 제안하고 전달할 것인가?
- 고객 관계: 고객들과 어떤 관계를 맺을 것인가? -> 개별 지원, 셀프 서비스, 헌신적 지원-PB, 자동화 서비스 등
- 수익원: 어떻게 수익을 창출할 것인가? -> 물품 판매, 대여료, 중개수수료, 가입비, 광고 등
- 핵심 자원: 가지고있는 핵심 자원은 무엇인가? -> 물적, 인적, 지적, 재무적 자원
- 핵심 활동: 어떤 핵심 활동을 수행해야 하나? -> 생산, 문제 해결, 플랫폼/네트워크
- 핵심 파트너십: 누가 우리의 핵심 파트너인가? -> 전략적 동맹, 경쟁자들의 전략적 파트너십, 조인트 벤처, 안정적 공급을 위한 ‘구매자-공급자 관계’
- 비즈니스 구조: 이 비즈니스를 유지하기 위한 비용 구조는 어떠한가? 고정비, 규모의 경제, 변동비, 범위의 경제
특징
- 경쟁력과 지속성을 보유한 비즈니스 모델
- 경쟁력: 명확한 고객 가치 제안, 수익 메커니즘
- 지속성: 선순환 구조, 모방 불가능
- 명확한 고객 가치 제안
- 고객의 니즈를 충족하고 문제를 해결하는 방안을 제시하는 것
- 상품의 서비스화(정수기, 구독), 면도기-면도날(프린터-토너), 역면도기-면도날(고급 승용차 부가 서비스), 프리미엄(스마트폰 게임)
- 분할(넷플릭스), 양면 시장(유튜브)
- 선순환 구조
- 모방불가능성
Team Project 1 - 데이터 분석 프로젝트
데이터셋
Credit Card Customers
https://www.kaggle.com/datasets/sakshigoyal7/credit-card-customers
주제
신용카드 이탈 고객 예측 모델 및 타겟 마케팅 대응 전략
개요
- 신용카드 이탈 고객 데이터 분석
- 신용카드 이탈 고객 예측 머신러닝 모델 제작
- 이탈 고객 예측 모델을 활용한 이탈위험고객 파악 및 대응 전략 (타겟 마케팅)
EDA
#️⃣ 변수 분석
shape = (10127, 20)
['Attrition_Flag', 'Customer_Age', 'Gender', 'Dependent_count', 'Education_Level', 'Marital_Status',
'Income_Category', 'Card_Category', 'Months_on_book', 'Total_Relationship_Count', 'Months_Inactive_12_mon',
'Contacts_Count_12_mon', 'Credit_Limit', 'Total_Revolving_Bal', 'Avg_Open_To_Buy', 'Total_Amt_Chng_Q4_Q1',
'Total_Trans_Amt', 'Total_Trans_Ct', 'Total_Ct_Chng_Q4_Q1', 'Avg_Utilization_Ratio']
종속 변인 (dependent variables, y)
Attrition_Flag (1)
범주형 변수 (categorical variables)
Gender, Education_Level, Marital_Status, Income_Category, Card_Category (5)
수치형 변수 (continuous variables)
Customer_Age, Dependent_count, Months_on_book, Total_Relationship_Count, Months_Inactive_12_mon, Contacts_Count_12_mon, Credit_Limit, Total_Revolving_Bal, Avg_Open_To_Buy, Total_Amt_Chng_Q4_Q1, Total_Trans_Amt, Total_Trans_Ct, Total_Ct_Chng_Q4_Q1, Avg_Utilization_Ratio (14)
#️⃣ 데이터 분포
[1] dependent variable (y)
- 유지고객과 이탈고객의 비율이 imbalanced
- Autogluon에서 Over Sampling 및 Under Sampling test시 원본 데이터셋이 성능지표가 더 높게 나왔음
- Oversampling시 1627건을 8500건에 맞추는 과정에서 feature 개수 대비 records가 부족하여 overfitting이 일어나게 되었을 것 이라 예상하였음
따라서 오버나, 언더 샘플링은 하지 않고 원본을 그대로 사용하기로 결정하였음
[2] categorical variables
- 유지, 이탈 고객은 전반적으로 유사한 데이터 분포를 보임
- 1번 차트: 여성의 이탈이 많음
- 3번 차트: 기혼 대비 미혼의 이탈 비율이 높음
- 4번 차트: 자사 고객풀의 가장 큰 portion은 수입 40K 미만인 고객으로 전체의 35%에 해당
~40K 고객의 이탈수가 가장 많음 - 5번 차트: Blue(최저) 등급의 고객 이탈수도 가장 많음
수입이 적고, 고객 등급이 낮을 수록 이탈하나? 라는 생각이 들었음
수입 구간별 카드 카테고리 히트맵
전체 | 이탈 |
---|---|
- 하지만 데이터를 확인하면 수입이 높을 수록 등급이 높은 고객의 비율은 올라가긴 하나,
- 모든 수입군에서 Blue, Silver 등급의 N수가 절대 다수
이탈, 유지 고객 별 고객 등급 비율
행 레이블 | Blue | Silver | Gold | Platinum | 총합계 |
---|---|---|---|---|---|
Attrited | 1519(0.93) | 82(0.05) | 21(0.01) | 5(0.003) | 1627 |
Existing | 7917(0.93) | 473(0.05) | 95(0.01) | 15(0.001) | 8500 |
총합계 | 9436 | 555 | 116 | 20 | 10127 |
고객 등급별 총 추정 손실액
Attrited Customer | 추정 손실 합계 : Total_Trans_Amt |
---|---|
Blue | 4,487,376 |
Silver | 401,774 |
Gold | 122,678 |
Platinum | 23,779 |
총합계 | 5,035,607 |
- 고객 등급별 총 추정 손실액에서도 Blue, Silver 군의 손실이 가장 크기때문에
수입이 낮고, 등급이 낮은 고객을 특정하기보다는
전반적으로 ‘등급이 낮은 고객의 이탈’이 매출 감소에 영향을 끼친 것으로 보는 것이 타당
[3] 수치형 변수
- 대부분은 유사한 분포를 띔
- 3째줄 2번째의 차트는 유지고객과 이탈고객별로 카드 거래 건수의 도수분포표
- 이 도수 분포표를 보면 ‘유지고객에 비해 이탈고객의 카드거래 건수가 적은 것’을 확인 가능
- 이탈고객의 최빈 구간: 1년에 30~50건 결제 구간 -> 약 3~4건/월
- 카드거래가 활발하지 않다는 것은 Active 유저가 아니라는 의미 (주사용 카드가 아님)
이후의 분석은 액티브 유저 여부와 관련된 지표들을 중점으로 이탈 고객을 상세 분석하기로 함
#️⃣ 상세 지표 분석
이 부분은 영찬님이 맡아서 진행, 분석내용 요약은 아래와 같음
주요 분석
-
고객 분석 - 계좌
- 이탈 고객은 유지 고객보다 상대적으로 더 적은 개수의 계좌를 보유하는 경향을 보임
-
고객 분석 - 문의
- 이탈 고객은 상대적으로 유지 고객보다 더 많이 문의하는 경향을 보임
- 유지 고객은 적게 문의하는 영역에 상대적으로 더 많이 분포
- 문의사항이 많다는 현상은 이탈고객이 될 수 있는 전조현상으로 추정해 볼 수 있음 (불만표시)
-
고객 분석 - 신용 한도
- 이탈 고객 중 여성의 신용 한도가 남성에 비해 극단적으로 낮고, 밀집되어있음
- 유지 고객의 경우 신용 한도가 낮을 수록 이용률이 높아지는 경향을 보임
-
고객 분석 - 1분기 대비 4분기 이용 금액 (이탈 고객)
- 왼쪽의 소액결제 고객과, 오른쪽의 고액 결제 금액 고객으로 양분화
- 대부분의 데이터 포인트들이 y축 기준 1 이하에 분포하고있어, 연초 대비 연말에 거래 금액이 더 낮아 스팟성 고객의 가능성이 높음
-
고객 분석 - 총 거래 건수 (이탈 고객)
- 이탈 고객은 총 이용 건수가 낮은 곳에 대체로 분포됨
- 이탈 고객은 1분기 대비 4분기에 이용건수가 대체로 감소함
- 그러나 이용건수가 높은 고객이면 이용률 증가도 나타나는 경향성을 띔
Data Preprocessing
#️⃣ 수치형 변수 상관관계 분석 및 공선성 제거
수치형 변수 Corr | 신용한도와 잔여한도 |
---|---|
- Avg_Utilization_Ratio 와 상관계수가 1이고, Credit_Limit와 완전공선성을 띄는 Avg_Open_To_Buy 제거
- Avg_Open_To_Buy 제거 후 기존에 해당 컬럼과 상관계수가 높았던 Avg_Utilization_Ratio는 피처로 사용
#️⃣ 범주형 변수 인코딩
df['Attrition_Flag'] = df['Attrition_Flag'].apply(lambda x: 0 if x == 'Existing Customer' else 1)
# Label Encoding
gender = {'M': 0, 'F': 1}
df['Gender']=df['Gender'].map(gender)
marital_status = {'Married': 1,'Single': 2, 'Divorced': 3}
df['Marital_Status'] = df['Marital_Status'].map(marital_status)
education_level = {'Uneducated': 1,'High School': 2, 'Graduate': 3, 'College': 4, 'Post-Graduate': 5, 'Doctorate': 6}
df['Education_Level'] = df['Education_Level'].map(education_level)
income_cat = {'Less than $40K': 1,'$40K - $60K': 2, '$60K - $80K': 3, '$80K - $120K': 4, '$120K +': 5}
df['Income_Category'] = df['Income_Category'].map(income_cat)
card_cat = {'Blue': 1, 'Silver': 2, 'Gold': 3, 'Platinum': 4}
df['Card_Category'] = df['Card_Category'].map(card_cat)
- 종속변수(y), 명목변수(nominal), 서열변수(ordinal) 총 3가지 중에서
- y -> 이분형 종속 변수로 existing: 0, attrited: 1
- nomial -> 원핫 인코딩
- ordinal -> 라벨 인코딩 하였음
#️⃣ 범주형 변수 결측치 대체: KNN
[1] Unknown 결측치 결정 여부
범주형 변수 중 Education_Level, Marital_Status, Income_Category에는 Unknown이라는 값이 있었음
이 Unknown은 미응답으로, 결측치로 간주할 것인지 여부에 대해 고민하였음
-
scenario 1 : 개인정보 제공에 응답하지 않는 고객이 이탈 혹은 inactive 유저와 연관이 있을 수도 있기 때문에
미응답도 응답의 한가지 유형으로 보아야 함
(“니가 보낸 DM을 읽고 나서 답이 없는 게 내 답이야” - IVE의 Kitsch 가사 중…) -
scenario 2: 미응답이더라도, 사용금액, 성별, 나이 등 다른 개인 정보를 통해 어느정도 미응답 내용을 유추 가능
scenario 2를 선택하였고, 결측치로 간주하여 데이터 처리를 하기로 결정
[2] 결측치 처리 방식
결측치는 drop, replace 2가지 경우의 수가 있는데, 결정을 위해 데이터 Count를 해봄
a. 항목 별 미응답자 수
col name | num of nan |
---|---|
Education_Level | 1519 |
Marital_Status | 749 |
Income_Category | 1112 |
항목별로 미응답자 수가 다름
b. 1개라도 Unknwon인 행 모음 => 3046 명
unknown_rows = df[(df['Education_Level'] == 'Unknown') | (df['Marital_Status'] == 'Unknown') | (df['Income_Category'] == 'Unknown')]
print(unknown_rows.shape)
'''
(3046, 19)
'''
전체 10127건 중 3046건이나 결측치로 간주해 제거하는 것은 부적절하다고 판단함
c. 전부 미응답인 행 모음 => 7명
tot_unknown_rows = df[(df['Education_Level'] == 'Unknown') & (df['Marital_Status'] == 'Unknown') & (df['Income_Category'] == 'Unknown')]
print(tot_unknown_rows.shape)
'''
(7, 19)
'''
전부 미응답한 고객은 7명밖에 되지 않아 결측치 제거 혹은 대체 어떤 방법을 선택하더라도
모델에 그렇게 큰 영향을 줄 것으로 보이지 않음
d. 유지고객, 이탈고객
- 대부분의 고객이 응답
- 유지고객과 이탈고객의 미응답자 비율이 비슷 (종속 변인 별 미응답자 비율이 동일한 양상)
- 따라서 제거보다는 결측치 대체가 유리할 것으로 판단
KNN Impute
# imputing instance
imputer = KNNImputer(n_neighbors=7)
cols_to_impute = ['Marital_Status', 'Education_Level', 'Income_Category']
# Split the data and fit
X = df.drop(['Attrition_Flag'], axis=1)
y = df['Attrition_Flag']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)
X_train[cols_to_impute]=imputer.fit_transform(X_train[cols_to_impute])
X_test[cols_to_impute]=imputer.transform(X_test[cols_to_impute])
train_tot = pd.concat([X_train, y_train], axis=1)
test_tot = pd.concat([X_test, y_test], axis=1)
train_tot.sort_index(ascending=True, inplace=True)
test_tot.sort_index(ascending=True, inplace=True)
df2 = pd.concat([train_tot, test_tot], axis=0)
df2.sort_index(ascending=True, inplace=True)
print(f"{df.shape = }, {df2.shape = }")
# KNN 예측값 실수를 반올림하여 정수화
for col in cols_to_impute:
df2[col] = np.round(df2[col]).astype('int')
df2[cols_to_impute].head()
rsult
Marital_Status | Education_Level | Income_Category |
---|---|---|
0 | 1 | 2 |
1 | 2 | 3 |
2 | 1 | 3 |
3 | 1 | 2 |
4 | 1 | 1 |
- 결측치는 train 70%, stratify=True 로 KNN Fitting
- 결측치의 예측값은 1.1240 등 소수점으로 나오는 상태에서 반올림하여 정수로 바꿈
#️⃣ 데이터 정규화: MinMax Scaling
Model | Accuracy | AUC | Recall | Prec. | F1 | Kappa | MCC | |
---|---|---|---|---|---|---|---|---|
std | Light Gradient Boosting Machine | 0.9923 | 0.9989 | 0.9723 | 0.9796 | 0.9759 | 0.9714 | 0.9714 |
minmax | Light Gradient Boosting Machine | 0.9924 | 0.9991 | 0.9723 | 0.9802 | 0.9762 | 0.9717 | 0.9717 |
robust | Light Gradient Boosting Machine | 0.9914 | 0.9985 | 0.9619 | 0.9843 | 0.973 | 0.9679 | 0.9679 |
- AutoML 라이브러리인 Pycaret으로 테스트 진행
- 성능이 가장 좋았던 방법은 MinMax Scaling
- 원핫 인코딩한 변수와 종속변수를 제외한 모든 변수에 MinMax Scaling 적용
처음에는 미처 확인을 못하고 원핫 인코딩된 변수까지 스케일링을 했음
나중에 확인해보니 0, 1 값이 -0.8 … 이런 값이 나와서 뭔가 잘못됨을 눈치채고😱
원핫인코딩 된 변수는 0, 1로 남을 수 있도록, 나머지 변수에만 스케일링을 적용했음
#️⃣ 최종 데이터 Features & 종속변수 y
-
기존 변수
- Customer_Age, Dependent_count, Education_Level, Income_Category, Card_Category, Months_on_book, Total_Relationship_Count, Months_Inactive_12_mon, Contacts_Count_12_mon, Credit_Limit, Total_Revolving_Bal, Total_Amt_Chng_Q4_Q1, Total_Trans_Amt, Total_Trans_Ct, Total_Ct_Chng_Q4_Q1, Avg_Utilization_Ratio (16)
-
원핫 인코딩
- Gender_Male, Marital_Status_Married, Marital_Status_Single, Marital_Status_Divorced (4)
-
종속 변수
- Attrition_Flag (1)