오늘은 통신 고객 데이터를 활용해 고객 이탈 여부를 분석하는 전체 과정을 복습했습니다. 데이터 구조 확인부터 결측값 처리, EDA와 시각화, 범주형 데이터 인코딩, 학습용 데이터 구성, 머신러닝 모델 학습과 평가까지 순서대로 정리합니다.
오늘의 학습 흐름
데이터 확인 → 결측값 처리 → EDA → 범주형 데이터 인코딩 → X와 y 분리 → 모델 생성 → 학습 → 예측 → 정확도 평가
1. 데이터 구조 확인: df.info()
df.info()는 데이터프레임의 전체 행과 열, 컬럼명, 각 컬럼의 데이터 타입, 결측값이 아닌 데이터의 개수 등을 확인하는 기능입니다.
고객 이탈 분석을 시작할 때 먼저 데이터가 어떤 구조로 되어 있는지 확인합니다. 특히 숫자로 처리해야 할 컬럼이 문자형으로 읽히지는 않았는지, 일부 컬럼에 값이 누락되어 있지는 않은지 살펴봅니다.
- 컬럼 정보: 컬럼명과 데이터 타입을 확인합니다.
- Non-Null Count: 각 컬럼에서 결측값이 아닌 데이터의 개수를 확인합니다.
- 결측값 확인: 전체 행 수보다 Non-Null Count가 적다면 해당 컬럼에 결측값이 있을 가능성이 있습니다.
기억할 점: df.info()만으로도 결측값 존재 여부를 추정할 수 있지만, 결측값의 개수를 정확히 확인할 때는 isnull().sum()을 사용하는 것이 편리합니다.
2. TotalCharges 컬럼의 문제점과 결측값 처리
고객 이탈 데이터의 TotalCharges는 총 누적 요금을 나타내는 컬럼입니다. 이 컬럼에는 숫자 외에 공백 문자열이 섞여 있을 수 있습니다.
공백 문자열이 포함되어 있으면 판다스가 해당 컬럼을 문자형으로 인식할 수 있습니다. 이후 숫자 계산이나 머신러닝 모델 학습에 사용하려면 숫자형으로 변환하는 전처리가 필요합니다.
2-1. 공백 데이터 탐색
pd.DataFrame.query()는 조건식을 이용해 원하는 행을 조회하는 기능입니다. TotalCharges 값이 공백 문자열인 행만 찾아 실제 데이터 상태를 확인할 수 있습니다.
이 단계의 목적은 숫자로 변환하기 전에 어떤 값이 문제를 일으키는지 먼저 파악하는 것입니다.
2-2. 1단계: 문자형 데이터를 숫자형으로 변환
pd.to_numeric()은 데이터를 숫자형으로 변환하는 함수입니다. 변환할 수 없는 값이 존재할 때 errors='coerce'를 설정하면 해당 값을 결측값(NaN)으로 처리합니다.
- to_numeric: 숫자형 데이터로 변환합니다.
- errors='coerce': 숫자로 변환할 수 없는 값을 결측값으로 처리합니다.
- 변환 후 info(): 컬럼의 데이터 타입이 변경되었는지 확인합니다.
공백 문자열은 그 자체로 숫자가 아니므로 숫자형 변환 과정에서 결측값으로 처리될 수 있습니다. 따라서 변환 후에는 결측값이 발생했는지 확인해야 합니다.
2-3. 2단계: 결측값이 포함된 행 제거
dropna()는 결측값이 포함된 행 또는 열을 제거하는 기능입니다. 기본 설정에서는 하나 이상의 결측값이 있는 행을 제거합니다.
ignore_index=True는 행을 제거한 뒤 인덱스를 0부터 다시 순서대로 부여합니다. 행을 삭제하면 기존 인덱스 번호가 중간에 비어 있을 수 있는데, 이 설정을 사용하면 연속된 인덱스로 정리할 수 있습니다.
주의: 결측값이 있는 행을 제거하면 분석에 사용할 수 있는 데이터 수도 줄어듭니다. 실제 분석에서는 결측값의 원인과 비율을 확인한 뒤 행 제거가 적절한지 판단해야 합니다.
3. EDA와 데이터 시각화
EDA(Exploratory Data Analysis)는 본격적인 모델 학습 전에 데이터의 분포와 특징, 변수 간 관계를 탐색하는 과정입니다.
이번 고객 이탈 분석에서는 고객 이탈 여부의 비율, 계약 형태별 이탈 고객 수, 가입 기간과 요금 분포 등을 그래프로 확인합니다.
3-1. Plotly Express
Plotly Express는 파이썬에서 대화형 그래프를 간편하게 만들 수 있는 시각화 인터페이스입니다. 그래프를 생성하면 마우스 올리기, 확대 등 다양한 방식으로 데이터를 탐색할 수 있습니다.
| 시각화 | 분석 목적 | 주요 문법 |
|---|---|---|
| 원형 그래프 | 고객 이탈 여부의 비율 확인 | px.pie() |
| 막대그래프 | 계약 형태별 고객 수 비교 | px.bar() |
| 히스토그램 | 가입 기간 및 요금의 분포 확인 | px.histogram() |
3-2. 원형 그래프: 고객 이탈 비율
고객 이탈 여부를 나타내는 Churn 컬럼의 항목별 비율을 확인합니다.
value_counts(normalize=True)는 각 항목의 비율을 계산합니다. 기본적인 value_counts()가 항목별 개수를 계산하는 것과 달리, normalize=True를 지정하면 전체에서 차지하는 비중을 확인할 수 있습니다.
reset_index()는 인덱스로 관리되는 항목을 일반 컬럼 형태로 바꾸어 데이터프레임으로 다루기 쉽게 합니다.
px.pie()는 원형 그래프를 생성합니다. names는 항목 이름, values는 그래프에 표시할 수치를 지정하는 역할을 합니다.
hole=0.3은 원형 그래프 가운데에 구멍을 만들어 도넛형 그래프로 표시하는 설정입니다.
3-3. 계약 형태와 고객 이탈: groupby()
고객의 계약 형태인 Contract와 이탈 여부인 Churn의 관계를 확인합니다. 계약 형태별로 이탈 고객과 유지 고객이 각각 몇 명인지 집계하면 계약 조건에 따른 고객 이탈 양상을 비교할 수 있습니다.
groupby()는 지정한 컬럼의 값에 따라 데이터를 그룹으로 나눕니다. 여러 컬럼을 기준으로 지정하면 조합별 그룹을 만들 수 있습니다.
- by: 그룹을 나눌 기준 컬럼을 지정합니다.
- size(): 각 그룹에 포함된 행의 개수를 계산합니다.
- reset_index(): 그룹 기준이 인덱스가 된 결과를 일반 컬럼으로 정리합니다.
- name='Count': 집계된 행 개수 컬럼에 Count라는 이름을 지정합니다.
여러 컬럼으로 그룹을 만들면 결과에 여러 단계의 인덱스가 생길 수 있습니다. 이를 멀티 인덱스라고 하며, reset_index()를 사용하면 일반적인 표 형태로 바꾸어 시각화하기 편리합니다.
3-4. 막대그래프: 계약 형태별 이탈 고객 비교
px.bar()는 범주별 수치를 비교하는 막대그래프를 생성합니다.
| 매개변수 | 역할 | 이번 분석에서의 의미 |
|---|---|---|
| x | 가로축에 표시할 컬럼 | Contract |
| y | 세로축에 표시할 수치 | Count |
| color | 색상으로 구분할 항목 | Churn |
| barmode='group' | 막대를 그룹별로 나란히 표시 | 유지 고객과 이탈 고객 비교 |
| text_auto | 막대 위에 수치 표시 | 고객 수 확인 |
| title | 그래프 제목 지정 | 분석 목적 표시 |
color='Churn'을 지정하면 고객 이탈 여부에 따라 막대 색상을 구분할 수 있습니다. 이를 통해 계약 유형별로 유지 고객과 이탈 고객의 수를 비교할 수 있습니다.
3-5. 히스토그램: 가입 기간(tenure)
tenure는 고객의 가입 기간을 나타내는 변수입니다. 가입 기간에 따른 이탈 분포를 확인하면 가입 초기 고객과 장기 이용 고객의 이탈 양상이 어떻게 다른지 탐색할 수 있습니다.
px.histogram()은 숫자 데이터가 어느 구간에 얼마나 분포하는지 확인할 때 사용하는 그래프입니다.
- x='tenure': 가입 기간을 가로축으로 사용합니다.
- color='Churn': 이탈 여부에 따라 분포를 색상으로 구분합니다.
- barmode='group': 이탈 여부별 막대를 나란히 표시합니다.
- update_traces(): 이미 생성된 그래프 요소의 표시 속성을 수정합니다.
- marker_line_color='black': 막대 테두리를 검은색으로 지정합니다.
- marker_line_width=1: 막대 테두리의 두께를 지정합니다.
3-6. 히스토그램: 월 요금(MonthlyCharges)
MonthlyCharges는 고객의 월 사용 요금을 나타냅니다. 이 컬럼의 분포를 고객 이탈 여부와 함께 확인하면 요금 구간별 고객 분포의 차이를 탐색할 수 있습니다.
시각화 방식은 가입 기간과 같습니다. x에 MonthlyCharges를 지정하고, color에 Churn을 지정해 이탈 여부별 분포를 비교합니다. 막대 테두리는 update_traces()로 설정할 수 있습니다.
3-7. 히스토그램: 총 누적 요금(TotalCharges)
TotalCharges는 고객이 누적해서 지불한 총 요금입니다. 이 컬럼은 앞에서 숫자형 변환과 결측값 처리를 진행했으므로, 정리된 데이터를 이용해 분포를 확인합니다.
가입 기간과 월 요금, 총 누적 요금을 각각 살펴보면 고객 이탈 여부와 관련된 데이터의 패턴을 비교할 수 있습니다. 다만 그래프에서 차이가 보인다고 해서 해당 변수가 이탈의 직접적인 원인이라고 단정할 수는 없습니다.
4. 다중공선성(Multicollinearity)
다중공선성은 독립변수들 사이에 높은 상관관계가 존재하는 현상입니다. 특히 선형 회귀 계수의 해석과 안정성에 영향을 줄 수 있습니다.
서로 비슷한 정보를 담고 있는 컬럼을 여러 개 사용하면 모델에서 각 변수의 개별적인 영향을 해석하기 어려워질 수 있습니다. 따라서 상관관계와 분석 목적을 확인하고, 필요한 경우 변수 선택이나 차원 축소 등의 방법을 검토합니다.
기억할 점: 컬럼 두 개가 비슷한 정보를 가진다는 이유만으로 무조건 삭제하지는 않습니다. 변수 간 관계와 모델의 종류, 예측 성능, 해석 목적을 함께 고려해야 합니다.
5. 인코딩(Encoding)과 데이터 전처리
머신러닝 모델은 입력 데이터의 형태에 따라 숫자형 데이터로 변환해야 하는 경우가 많습니다. 고객 이탈 데이터에는 계약 유형, 결제 방식, 인터넷 서비스 유형처럼 문자로 표현된 범주형 컬럼이 포함되어 있습니다.
5-1. 불필요한 컬럼 삭제
drop(columns=...)는 지정한 컬럼을 제거합니다. 이번 분석에서는 customerID가 고객을 식별하기 위한 컬럼이므로, 예측에 사용하지 않도록 제거합니다.
식별자 컬럼이 반드시 모든 분석에서 불필요한 것은 아니지만, 고객 고유번호 자체가 이탈을 예측하는 의미 있는 특성이 아니라면 입력 변수에서 제외하는 것이 일반적입니다.
5-2. 정답 컬럼 Churn 인코딩
map()은 지정한 대응 관계에 따라 값을 다른 값으로 바꾸는 기능입니다.
Churn 컬럼은 고객 이탈 여부를 나타내는 정답 컬럼입니다. No를 0, Yes를 1로 변환하면 이탈 여부를 숫자로 표현할 수 있습니다.
| 원래 값 | 변환 값 | 의미 |
|---|---|---|
| No | 0 | 고객 이탈 아님 |
| Yes | 1 | 고객 이탈 |
5-3. 범주형 컬럼 추출
select_dtypes()는 데이터 타입을 기준으로 컬럼을 선택하는 기능입니다. include에 문자형 타입을 지정하면 범주형 데이터로 처리할 컬럼을 추출할 수 있습니다.
범주형 컬럼을 먼저 추출하면 어떤 컬럼에 원-핫 인코딩을 적용할지 확인하기 쉽습니다.
5-4. 컬럼 이름과 리스트 자료형
columns는 데이터프레임의 컬럼 이름을 나타냅니다. 컬럼 이름을 추출하면 인덱스 형태의 객체로 관리할 수 있습니다.
tolist()는 해당 객체의 값을 파이썬 리스트로 변환합니다. 여러 컬럼 이름을 함수의 인자로 전달하거나 목록으로 관리할 때 유용합니다.
컬럼 추출, 컬럼 이름 확인, 리스트 변환은 각각 수행할 수도 있고, 필요한 작업을 연결해 한 줄로 작성할 수도 있습니다.
5-5. 원-핫 인코딩(One-Hot Encoding)
pd.get_dummies()는 범주형 컬럼의 각 범주를 별도의 숫자형 컬럼으로 변환하는 기능입니다.
예를 들어 계약 형태에 여러 종류가 있다면 각 계약 유형에 해당하는 컬럼을 만들고, 해당하는 유형에는 1, 해당하지 않는 유형에는 0을 부여하는 방식으로 표현할 수 있습니다.
- data: 변환할 데이터프레임을 지정합니다.
- columns: 원-핫 인코딩을 적용할 컬럼을 지정합니다.
- dtype=int: 생성되는 값을 정수형으로 지정합니다.
원-핫 인코딩을 마치면 문자형 범주가 숫자형 컬럼으로 바뀌어 머신러닝 모델의 입력으로 활용하기 쉬워집니다.
6. 학습용 데이터 X와 정답 데이터 y 분리
머신러닝 지도학습에서는 예측에 사용할 입력 데이터와 모델이 맞혀야 할 정답 데이터를 구분합니다.
| 구분 | 역할 | 고객 이탈 분석에서의 의미 |
|---|---|---|
| X_data | 입력 특성 데이터 | 고객의 계약 정보, 가입 기간, 요금 등 |
| y_data | 정답 데이터 | 고객 이탈 여부인 Churn |
X_data는 정답 컬럼을 제외한 입력 특성으로 구성합니다. y_data에는 모델이 예측해야 할 Churn 컬럼을 지정합니다.
정답 컬럼이 입력 데이터에 그대로 포함되면 모델이 정답 정보를 미리 사용하는 문제가 생길 수 있습니다. 따라서 X와 y를 분리할 때는 정답 컬럼이 X에 포함되지 않았는지 확인해야 합니다.
6-1. 정답 비율 확인
y_data의 항목별 비율을 확인하면 이탈 고객과 유지 고객의 데이터가 어느 정도 포함되어 있는지 알 수 있습니다.
한쪽 클래스의 비율이 매우 높고 다른 쪽 클래스의 비율이 낮다면 클래스 불균형이 존재할 수 있습니다. 이 경우 정확도만으로 모델 성능을 판단하기 어려울 수 있으므로 추가적인 평가 지표를 고려해야 합니다.
7. 로지스틱 회귀(Logistic Regression) 모델
로지스틱 회귀는 이름에 회귀가 포함되어 있지만, 이진 분류 문제에 널리 사용되는 모델입니다. 고객 이탈 여부처럼 두 가지 범주 중 하나를 예측하는 문제에 적용할 수 있습니다.
모델 학습 과정에서는 다음 순서를 따릅니다.
- 로지스틱 회귀 모델을 생성합니다.
- 학습 데이터를 이용해 fit()으로 모델을 학습합니다.
- 평가용 입력 데이터를 predict()에 전달해 예측 결과를 생성합니다.
- 예측 결과와 실제 정답을 비교해 모델 성능을 평가합니다.
8. SVM 모델
SVM(Support Vector Machine)은 데이터를 구분하는 경계를 찾는 머신러닝 기법입니다. 분류 문제에서 활용할 수 있으며, 데이터의 특성과 설정에 따라 예측 성능이 달라질 수 있습니다.
SVM 역시 모델 생성, fit()을 통한 학습, predict()를 통한 예측, 성능 평가 순서로 사용합니다.
로지스틱 회귀와 SVM을 같은 학습용·평가용 데이터로 비교하면 어떤 모델이 현재 데이터에서 더 좋은 결과를 내는지 확인할 수 있습니다. 다만 공정한 비교를 위해서는 동일한 데이터 분할과 적절한 전처리 조건을 유지해야 합니다.
9. 모델 평가: accuracy_score()
accuracy_score()는 분류 모델의 예측 결과 중 실제 정답과 일치한 비율을 계산합니다.
정확도가 높을수록 전체 예측 중 맞힌 비율이 높다는 의미입니다. 하지만 이탈 고객의 비율이 낮은 데이터에서는 모든 고객을 이탈하지 않는다고 예측해도 정확도가 높게 나올 수 있습니다.
따라서 고객 이탈 예측에서는 정확도뿐 아니라 정밀도(Precision), 재현율(Recall), F1-score, 혼동행렬(Confusion Matrix) 등의 지표도 함께 검토할 수 있습니다. 특히 이탈 고객을 얼마나 잘 찾아내는지가 중요하다면 재현율을 살펴보는 것이 도움이 됩니다.
10. 오늘의 전체 복습 순서
| 단계 | 학습 내용 | 핵심 목적 |
|---|---|---|
| 1 | df.info() | 컬럼, 데이터 타입, 결측값 여부 확인 |
| 2 | to_numeric(), dropna() | 숫자형 변환과 결측값 처리 |
| 3 | EDA, Plotly Express | 고객 이탈 분포와 변수 관계 탐색 |
| 4 | groupby(), size(), reset_index() | 계약 형태별 고객 수 집계 |
| 5 | drop(), map() | 불필요한 컬럼 삭제와 정답 인코딩 |
| 6 | select_dtypes(), tolist(), get_dummies() | 범주형 컬럼 추출과 원-핫 인코딩 |
| 7 | X_data, y_data | 입력 특성과 정답 분리 |
| 8 | Logistic Regression, SVM | 분류 모델 학습과 예측 |
| 9 | accuracy_score() | 예측 결과의 정확도 평가 |
11. 문법만 빠르게 복습하기
마지막으로 오늘 사용한 주요 문법을 표로 정리합니다. 실제 코드는 VS Code에서 확인하고, 아래 표로 함수와 매개변수의 역할을 복습합니다.
| 분류 | 문법 | 간단한 의미 |
|---|---|---|
| 데이터 확인 | df.info() | 컬럼명, 타입, Non-Null Count 확인 |
| 데이터 조회 | query() | 조건에 맞는 행 조회 |
| 형 변환 | pd.to_numeric() | 숫자형으로 변환 |
| 오류 처리 | errors='coerce' | 변환할 수 없는 값을 결측값으로 처리 |
| 결측값 처리 | dropna() | 결측값이 있는 행 또는 열 제거 |
| 인덱스 정리 | ignore_index=True | 새 인덱스를 순서대로 부여 |
| 비율 계산 | value_counts(normalize=True) | 항목별 비율 계산 |
| 데이터 정리 | reset_index() | 인덱스를 일반 컬럼으로 변환 |
| 그룹 집계 | groupby() | 기준 컬럼별 그룹 생성 |
| 행 개수 | size() | 그룹별 행 개수 계산 |
| 컬럼명 지정 | reset_index(name='Count') | 집계 결과 컬럼명 지정 |
| 원형 그래프 | px.pie() | 항목별 비율 시각화 |
| 막대그래프 | px.bar() | 범주별 수치 비교 |
| 히스토그램 | px.histogram() | 숫자 데이터 분포 시각화 |
| 그래프 색상 | color='Churn' | 이탈 여부별 색상 구분 |
| 그래프 수정 | update_traces() | 그래프 요소의 표시 속성 변경 |
| 컬럼 삭제 | drop(columns=[...]) | 지정한 컬럼 제거 |
| 값 변환 | map() | 대응 관계에 따라 값 변환 |
| 타입별 선택 | select_dtypes() | 데이터 타입에 맞는 컬럼 추출 |
| 컬럼 목록 | columns | 컬럼 이름 확인 |
| 리스트 변환 | tolist() | 값을 파이썬 리스트로 변환 |
| 원-핫 인코딩 | pd.get_dummies() | 범주형 데이터를 숫자형 컬럼으로 변환 |
| 모델 학습 | fit() | 학습 데이터로 모델 학습 |
| 모델 예측 | predict() | 학습한 모델로 결과 예측 |
| 모델 평가 | accuracy_score() | 예측 정확도 계산 |
마무리
고객 이탈 예측의 핵심은 모델을 바로 학습하는 것이 아니라, 먼저 데이터를 확인하고 올바른 형태로 전처리하는 것입니다. 공백 문자열을 숫자형으로 변환하고 결측값을 처리한 뒤, EDA로 데이터의 특징을 파악하고 범주형 컬럼을 인코딩해야 합니다.
그다음 입력 특성 X와 정답 y를 분리하고, 로지스틱 회귀와 SVM 같은 모델을 학습시켜 예측 결과를 평가합니다. 이 과정을 순서대로 이해하면 다른 분류 문제에도 동일한 분석 흐름을 적용할 수 있습니다.
오늘의 핵심: 데이터 확인 → 전처리 → 탐색과 시각화 → 인코딩 → 모델 학습 → 예측 → 평가