개발일지

데이터분석 5주차 숙제 - 📄 통신사 고객 데이터를 이용한 종합적 데이터 분석 본문

강의/데이터분석

데이터분석 5주차 숙제 - 📄 통신사 고객 데이터를 이용한 종합적 데이터 분석

MotherCarGasoline 2022. 2. 6. 23:31

여러분들이 1주차부터 5주차까지 배운 모든 내용을 이용해 아래의 데이터를

여러분의 역량이 되는 한 최대한 분석해봅시다.

 

👻

Q1. 탐색적 데이터 분석을 통해 각종 차트로 시각화해봅시다.

Q2. 특정 기준을 선정하여 K-means로 군집을 시도해봅시다.

Q3. 2주차에서 배운 분류 모델 중 하나를 선정하여 고객 이탈을 예측해봅시다.

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

df = pd.read_csv('Sparta_CodingClub_Telco_Customer.csv')
df
 
df.info()
 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 21 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   customerID        7043 non-null   object 
 1   gender            7043 non-null   object 
 2   SeniorCitizen     7043 non-null   int64  
 3   Partner           7043 non-null   object 
 4   Dependents        7043 non-null   object 
 5   tenure            7043 non-null   int64  
 6   PhoneService      7043 non-null   object 
 7   MultipleLines     7043 non-null   object 
 8   InternetService   7043 non-null   object 
 9   OnlineSecurity    7043 non-null   object 
 10  OnlineBackup      7043 non-null   object 
 11  DeviceProtection  7043 non-null   object 
 12  TechSupport       7043 non-null   object 
 13  StreamingTV       7043 non-null   object 
 14  StreamingMovies   7043 non-null   object 
 15  Contract          7043 non-null   object 
 16  PaperlessBilling  7043 non-null   object 
 17  PaymentMethod     7043 non-null   object 
 18  MonthlyCharges    7043 non-null   float64
 19  TotalCharges      7043 non-null   object 
 20  Churn             7043 non-null   object 
dtypes: float64(1), int64(2), object(18)
memory usage: 1.1+ MB
 

 


Churn : 고객 이탈 여부, 종속 변수. customerID : 고객의 고유한 ID. gender : 고객 성별. SeniorCitizen : 고객이 노약자인가 아닌가. Partner : 고객에게 파트너가 있는지 여부(결혼 여부). Dependents : 고객의 부양 가족 여부. tenure : 고객이 회사에 머물렀던 개월 수. PhoneService : 고객에게 전화 서비스가 있는지 여부. MultipleLines : 고객이 여러 회선을 사용하는지 여부. InternetService : 고객의 인터넷 서비스 제공업체. OnlineSecurity : 고객의 온라인 보안 여부. OnlineBackup : 고객이 온라인 백업을 했는지 여부. DeviceProtection : 고객에게 기기 보호 기능이 있는지 여부. TechSupport : 고객이 기술 지원을 받았는지 여부. StreamingTV : 고객이 스트리밍TV을 가지고 있는지 여부. StreamingMovies : 고객이 영화를 스트리밍하는지 여부. Contract : 고객의 계약기간. PaperlessBilling : 고객의 종이 없는 청구서 수신 여부(모바일 청구서). PaymentMethod : 고객의 결제 수단. MonthlyCharges : 매월 고객에게 청구되는 금액. TotalCharges : 고객에게 청구된 총 금액.

 
df['customerID'].nunique()
 
7043
 
df.describe()
 

여기서 잘 관찰을 하시다가 이상한 점을 찾아내셔야 하는데, TotalCharges라는 열이 현재 object(문자열) 데이터로 취급이 되고 있습니다. 문자열 데이터를 수치형 데이터로 변환을 시도합니다.

 
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'])
 
 
 

그러면 에러가 발생하는데 " "라는 값이 존재하고 있어서 변환이 불가능하다는 의미입니다. " " 값을 0 으로 바꿔주는 작업을 진행하겠습니다.

 
 
df['TotalCharges'] = df['TotalCharges'].replace({" ": 0})
 
 
df['TotalCharges'] = pd.to_numeric(df['TotalCharges'])
df
 
 
df.info()
 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 21 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   customerID        7043 non-null   object 
 1   gender            7043 non-null   object 
 2   SeniorCitizen     7043 non-null   int64  
 3   Partner           7043 non-null   object 
 4   Dependents        7043 non-null   object 
 5   tenure            7043 non-null   int64  
 6   PhoneService      7043 non-null   object 
 7   MultipleLines     7043 non-null   object 
 8   InternetService   7043 non-null   object 
 9   OnlineSecurity    7043 non-null   object 
 10  OnlineBackup      7043 non-null   object 
 11  DeviceProtection  7043 non-null   object 
 12  TechSupport       7043 non-null   object 
 13  StreamingTV       7043 non-null   object 
 14  StreamingMovies   7043 non-null   object 
 15  Contract          7043 non-null   object 
 16  PaperlessBilling  7043 non-null   object 
 17  PaymentMethod     7043 non-null   object 
 18  MonthlyCharges    7043 non-null   float64
 19  TotalCharges      7043 non-null   float64
 20  Churn             7043 non-null   object 
dtypes: float64(2), int64(2), object(17)
memory usage: 1.1+ MB
 
 

TotalCharges의 자료형이 object에서 float64로 바뀐 것을 확인할 수 있습니다.

 
 
df.describe()
 
 
 
 

지속 회원과 탈퇴 회원의 통계랑 비교

 
df.groupby("Churn")["customerID"].count()
 
Churn
No     5174
Yes    1869
Name: customerID, dtype: int64
 
 
df.Churn.value_counts().plot(kind='pie', y='Churn', figsize=(5, 5), autopct='%1.0f%%')
 
 
 

데이터에서 지속 회원의 수는 5,174명이며 탈퇴 회원은 1,869명입니다.

 
customer_stay = df[['MonthlyCharges','tenure']][df['Churn'] == 'No'] # 탈퇴 안한 회원들의 매월청구금액,개월수 평균값들
customer_stay.describe()
 
 
 
 
 
customer_end = df[['MonthlyCharges','tenure']][df['Churn'] == 'Yes'] # 지속하는 회원들의 매월청구금액,개월수 평균값들
customer_end.describe()
 

탈퇴 회원일수록 월 사용료가 평균적으로 더 높았습니다. 탈퇴 회원에 대해서 회원 기간(tenure)의 분포를 살펴봅시다. matplotlib을 사용해서 히스토그램을 작성합니다.

 

plt.hist(customer_end["tenure"])
 
 
코드를 실행하면 히스토그램이 표시됩니다. 회원 기간이 10개월 이내인 고객이 많고, 10개월 이상의 고객 수는 거의 차이가 나지않거나 일정한 것을 알 수 있습니다.
 

클러스터링

 
 
from sklearn.cluster import KMeans 
from sklearn.preprocessing import MinMaxScaler
 
 
 
# 이탈한 고객의 경우의 MonthlyCharges와 tenure열 필터링
monthlyp_and_tenure = df[['MonthlyCharges','tenure']][df['Churn'] == 'Yes']
monthlyp_and_tenure
 
 
 

자, 이제 두 개의 열에 대해서 클러스터링을 해줄 것입니다.
그런데 5강에서 클러스터링 전에 전체 열에 대해서 값에 정규화를 해주므로서 특정 열의 값에 좌지우지되지 않도록 해준 것 기억나시나요?
그리고 4강에서 사실 정규화 방법에는 여러가지 방법이 있다고 코드스니펫을 통해 안내를 드린 적이 있습니다.

해당 코드스니펫의 링크는 다음과 같았는데요.

https://mkjjo.github.io/python/2019/01/10/scaler.html

 

해당 링크를 가보시면 알겠지만, 정규화 방법 중에는 MinMaxScaler라는 방법 또한 존재합니다.

 
 
scaler = MinMaxScaler()
monthly_and_tenure_standardized = pd.DataFrame( scaler.fit_transform(monthlyp_and_tenure) )
monthly_and_tenure_standardized.columns = ['MonthlyCharges','tenure']

monthly_and_tenure_standardized
 
 

 

K-means를 통해 3개의 클러스터를 만듭니다.

 
 
kmeans = KMeans(n_clusters=3, random_state=42).fit(monthly_and_tenure_standardized)
monthly_and_tenure_standardized['cluster'] = kmeans.labels_

monthly_and_tenure_standardized
 
 
 
 
monthly_and_tenure_standardized.groupby("cluster").count()
 
 
 

그룹1가 다른 그룹에 비해 수가 두 배 정도 많고, 그룹0이 그룹2보다는 많지만 크게 차이나지 않습니다.
클러스터링은 튜터님의 정답코드와 출력이 다르게 나온다 랜덤이기 때문에

 
 
monthly_and_tenure_standardized.groupby("cluster").mean()
 
 
 
 

결과를 보면 그룹 0은 월 사용료와 이용 기간 둘 다 짧습니다. 그룹 2는 월 사용료도 높고, 사용 기간 또한 길었구요. 그룹 1는 월 사용료는 높았지만, 사용 기간은 짧았습니다. (앞서 확인했듯이 가장 많은 수의 이탈 고객에 해당됩니다.) 그룹의 특징을 알면 그룹별로 다른 캠페인을 사용할 수 있습니다.

 

이제 시각화를 진행해봅시다.
강의에서 스포츠센터 고객 데이터에서는 클러스터링 시각화를 위해서 5차원을 2차원으로 줄이기 위해서 차원 축소를 사용하였지만,
현재는 특성이 2차원밖에 없으므로 굳이 차원 축소를 사용할 필요가 없습니다.
각각의 특성이 x축과 y축이 되어줄 것이기 때문입니다.

 
 
# 군집 결과에 대한 산점도
fig, ax = plt.subplots(figsize=(13,8))
plt.scatter( monthly_and_tenure_standardized['MonthlyCharges'], monthly_and_tenure_standardized['tenure'], c=monthly_and_tenure_standardized['cluster'])

plt.title('Clustering churned users by monthly Charges and tenure')
plt.xlabel('Monthly Charges')
plt.ylabel('Tenure')
plt.show()
 
 
 

월간 요금이 낮았던 대부분의 이탈 사용자는 매우 빠른 시간 내로 이탈했습니다. 이들은 더 낮은 금액이나 가성비가 좋은 업체로 빠르게 이동하는 것을 바랬을 수 있습니다. 이탈 사용자 중 사용 기간이 길고 낮은 가격을 가진 경우의 이탈 사용자는 매우 적습니다.

초록이 빠른 이탈자 = 가격대비 만족을 못한건가

 

전처리

 
 
 
del df["customerID"]
 
 
 
df.info()
 
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7043 entries, 0 to 7042
Data columns (total 20 columns):
 #   Column            Non-Null Count  Dtype  
---  ------            --------------  -----  
 0   gender            7043 non-null   object 
 1   SeniorCitizen     7043 non-null   int64  
 2   Partner           7043 non-null   object 
 3   Dependents        7043 non-null   object 
 4   tenure            7043 non-null   int64  
 5   PhoneService      7043 non-null   object 
 6   MultipleLines     7043 non-null   object 
 7   InternetService   7043 non-null   object 
 8   OnlineSecurity    7043 non-null   object 
 9   OnlineBackup      7043 non-null   object 
 10  DeviceProtection  7043 non-null   object 
 11  TechSupport       7043 non-null   object 
 12  StreamingTV       7043 non-null   object 
 13  StreamingMovies   7043 non-null   object 
 14  Contract          7043 non-null   object 
 15  PaperlessBilling  7043 non-null   object 
 16  PaymentMethod     7043 non-null   object 
 17  MonthlyCharges    7043 non-null   float64
 18  TotalCharges      7043 non-null   float64
 19  Churn             7043 non-null   object 
dtypes: float64(2), int64(2), object(16)
memory usage: 1.1+ MB
 
df
 
 
gender_map = {"Female" : 0, "Male": 1}
yes_no_map = {"Yes" : 1, "No" : 0}

df["gender"] = df["gender"].replace(gender_map)
df["Partner"] = df["Partner"].replace(yes_no_map)
df["Dependents"] = df["Dependents"].replace(yes_no_map)
df["PhoneService"] = df["PhoneService"].replace(yes_no_map)
df["PaperlessBilling"] = df["PaperlessBilling"].replace(yes_no_map)
df["Churn"] = df["Churn"].replace(yes_no_map)
 

 

현재 타입이 object이면서 값의 종류가 2개(Yes와 No)밖에 없는 경우에 대해서 전부 1과 0으로 변환.

 
 
df
 
 
 

바차트

 

fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(ncols=2, nrows=2)
fig.set_size_inches(10, 10)

sns.countplot(x='gender', hue="Churn", data=df, ax=ax1).set_title('Demographic Variables: gender')
sns.countplot(x='SeniorCitizen', hue="Churn", data=df, ax=ax2).set_title('Demographic Variables: SeniorCitizen')
sns.countplot(x='Partner', hue="Churn", data=df, ax=ax3).set_title('Demographic Variables: Partner')
sns.countplot(x='Dependents', hue="Churn", data=df, ax=ax4).set_title('Demographic Variables: Dependents')

plt.tight_layout()
 
 
 

Boxplot

 
 
figure, (ax1, ax2) = plt.subplots(1, 2, figsize= (8, 6))
sns.boxplot(x = 'Churn',  y = 'TotalCharges', data = df, ax=ax1)
sns.boxplot(x = 'Churn',  y = 'MonthlyCharges', data = df, ax=ax2)
 
 
 

더미형 변수 추가

 
 
final_df = pd.get_dummies(df)
final_df
 
 
 
final_df.columns
 
Index(['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'tenure',
       'PhoneService', 'PaperlessBilling', 'MonthlyCharges', 'TotalCharges',
       'Churn', 'MultipleLines_No', 'MultipleLines_No phone service',
       'MultipleLines_Yes', 'InternetService_DSL',
       'InternetService_Fiber optic', 'InternetService_No',
       'OnlineSecurity_No', 'OnlineSecurity_No internet service',
       'OnlineSecurity_Yes', 'OnlineBackup_No',
       'OnlineBackup_No internet service', 'OnlineBackup_Yes',
       'DeviceProtection_No', 'DeviceProtection_No internet service',
       'DeviceProtection_Yes', 'TechSupport_No',
       'TechSupport_No internet service', 'TechSupport_Yes', 'StreamingTV_No',
       'StreamingTV_No internet service', 'StreamingTV_Yes',
       'StreamingMovies_No', 'StreamingMovies_No internet service',
       'StreamingMovies_Yes', 'Contract_Month-to-month', 'Contract_One year',
       'Contract_Two year', 'PaymentMethod_Bank transfer (automatic)',
       'PaymentMethod_Credit card (automatic)',
       'PaymentMethod_Electronic check', 'PaymentMethod_Mailed check'],
      dtype='object')
 

각종 머신 러닝 모델을 이용한 학습

 
 
from sklearn.model_selection import train_test_split
from sklearn.metrics import precision_score, recall_score, f1_score, accuracy_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LogisticRegression
 
 
 
X = final_df.drop(['Churn'], axis=1)
y = final_df['Churn']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
 
 
 
def get_metrics( model ):
    y_pred = model.predict(X_test)
    y_actual = y_test 
    print()
    print('-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*')
    print()
    print('테스트 데이터에 대한 정확도 :' , accuracy_score(y_actual, y_pred)*100 , '%' )
    print()
    f1score = f1_score(y_actual,y_pred)
    precision = precision_score(y_actual,y_pred)
    recall = recall_score(y_actual,y_pred)
    score_dict = { 'f1_score':[f1score], 'precision':[precision], 'recall':[recall]}
    score_frame = pd.DataFrame(score_dict)
    print(score_frame)
 
 
lr = LogisticRegression(C=10000, penalty='l2')
lr.fit(X_train, y_train) 
print('훈련 데이터에 대한 정확도 :',lr.score(X_train,y_train)*100,'%')
get_metrics(lr)
 
훈련 데이터에 대한 정확도 : 80.33368832090876 %

-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

테스트 데이터에 대한 정확도 : 82.11497515968772 %

   f1_score  precision    recall
0      0.64   0.685015  0.600536
/usr/local/lib/python3.7/dist-packages/sklearn/linear_model/_logistic.py:818: ConvergenceWarning: lbfgs failed to converge (status=1):
STOP: TOTAL NO. of ITERATIONS REACHED LIMIT.

Increase the number of iterations (max_iter) or scale the data as shown in:
    https://scikit-learn.org/stable/modules/preprocessing.html
Please also refer to the documentation for alternative solver options:
    https://scikit-learn.org/stable/modules/linear_model.html#logistic-regression
  extra_warning_msg=_LOGISTIC_SOLVER_CONVERGENCE_MSG,
 
 
 
훈련 데이터에 대한 정확도 : 81.06141285055023 %

-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*

테스트 데이터에 대한 정확도 : 80.34066713981547 %

   f1_score  precision    recall
0   0.58841       0.66  0.530831
 
 
 
 

정답링크

https://colab.research.google.com/drive/1bc1kpJsFp8ix9WEr3bMR-S6zB7P8QrpX?usp=sharing

 

Sparta CodingClub 5강 - 통신사 고객 데이터를 이용한 종합적 데이터 분석

Colaboratory notebook

colab.research.google.com

 

 

잘모르겠다~~ 파이썬 기초를 배우고 다시 복습하는 수 밖에 없다~
Comments