[빅데이터] 소비자 특성 분석

구글 코랩 환경에서 데이터 분석을 진행하였으며, 사용한 데이터의 링크는 여길 눌러 들어가라


각 열(Column)에 대한 간단한 분석을 통해 이 CSV 파일에서 데이터 준비 과정을 아래 항목들을 고려한 데이터 분석 준비 과정을 설명하겠다.

1. ID (고객 식별 번호)

  • 유형: 고유 값 (Unique Identifier)
  • 활용: 분석에 활용되지 않는 데이터로 제외하거나, 고객 개별 분석에 사용 가능.

2. Year_Birth (출생 연도)

  • 유형: 숫자형 데이터 (숫자 데이터)
  • 활용: 분석에서는 ‘나이’를 계산하는 데 사용 가능. 2024년 기준으로 나이를 계산할 수 있습니다.
  • 데이터 준비: 나이 = 2024 - Year_Birth로 새로운 열 추가 가능.

3. Education (학력 수준)

  • 유형: 범주형 데이터 (Categorical 데이터)
  • 활용: 학력 별로 소비 패턴을 분석할 수 있으므로 가변수(dummy variables)로 변환 추천.
  • 데이터 준비: 학력 유형을 더미 변수로 변환 가능 (예: “Graduation” → 1, 나머지 → 0).

4. Marital_Status (결혼 상태)

  • 유형: 범주형 데이터 (Categorical 데이터)
  • 활용: 결혼 상태에 따른 소비 패턴 분석 가능.
  • 데이터 준비: 결혼 상태에서 여러 상태(Single, Divorced 등)를 더미 변수로 변환.

5. Income (연간 소득)

  • 유형: 숫자형 데이터
  • 활용: 소득과 구매량/소비 패턴의 상관관계를 분석할 때 사용.
  • 데이터 준비: 결측값 확인 필요. 결측값이 있으면 중위수(또는 평균)로 대체하기 적합.

6. Kidhome, Teenhome (어린 자녀 수, 청소년 자녀 수)

  • 유형: 숫자형 데이터
  • 활용: 자녀 수와 각 제품군 구매 패턴의 관계를 분석할 수 있음.
  • 데이터 준비: 두 변수를 합쳐 ‘자녀 수’ 열로 통합할 수 있음 (Kidhome + Teenhome).

7. Dt_Customer (고객 등록 날짜)

  • 유형: 날짜 데이터 (Datetime)
  • 활용: 고객과의 관계 기간을 계산할 수 있으므로 분석 가능. 예를 들어 가입 후 시간이 지날수록 더 많이 소비하는지 확인 가능.
  • 데이터 준비: 고객 등록 날짜와 분석 기준 날짜(현재 시점) 간의 차이로, 변수 생성 가능 (예: Days_since_signup = 현재날짜 - Dt_Customer).

8. Recency (최근 구매 일수)

  • 유형: 숫자형 데이터
  • 활용: 고객이 마지막으로 구매한 후 지난 시간을 나타냄. 고객의 재구매 주기 또는 이탈 가능성 분석에 유용.
  • 데이터 준비: 그대로 사용할 수 있으며, 값을 버킷화(Binning)하여 고객 집단화 가능 (예: 최근 고객, 장기 미구매 고객 등).

9~15. MntWines, MntFruits, MntMeatProducts, MntFishProducts, MntSweetProducts, MntGoldProds (제품 구매 금액)

  • 유형: 숫자형 데이터
  • 활용: 고객이 특정 제품에 얼마나 소비하는지 보여줌. 상품별 매출을 분석하거나 총 소비 금액을 분석해 상위 고객 분류 가능.
  • 데이터 준비:
    • 각 제품군 구매 금액 데이터를 이용해 전체 지출 금액을 계산 가능.
    • Total_Spent = MntWines + MntFruits + MntMeatProducts + MntFishProducts + MntSweetProducts + MntGoldProds.
    • 또한 실루엣 분석(cluster analysis) 등을 위해 백분율로 정규화(normalization) 가능.

16~19. NumDealsPurchases, NumWebPurchases, NumCatalogPurchases, NumStorePurchases (구매 경로별 구매 횟수)

  • 유형: 숫자형 데이터
  • 활용: 고객이 어떤 경로(할인, 웹, 카탈로그, 매장)를 선호하는지 파악 가능.
  • 데이터 준비: 각 경로별 구매 횟수 데이터를 더해 총 구매 횟수로 활용 가능 (Total_Purchases = NumDealsPurchases + NumWebPurchases + NumCatalogPurchases + NumStorePurchases).

20. NumWebVisitsMonth (월별 웹사이트 방문 횟수)

  • 유형: 숫자형 데이터
  • 활용: 고객의 온라인 활동도를 측정할 수 있으며, 웹 방문과 웹 구매 연관성 분석 가능.
  • 데이터 준비: 값이 높은 고객은 리뷰어(re-engagement 대상)로 분류 가능. 평균 방문 횟수와 비교 가능.

21~25. AcceptedCmp3, AcceptedCmp4, AcceptedCmp5, AcceptedCmp1, AcceptedCmp2 (캠페인 참여 여부)

  • 유형: 이진형 데이터 (0 또는 1)
  • 활용: 고객의 캠페인 참여 성향을 파악할 수 있으며, 마케팅 캠페인에 대한 반응도를 분석 가능.
  • 데이터 준비: 캠페인 응답 횟수를 통해 총 캠페인 수락 횟수 변수 생성 가능 (Total_Accepted_Campaigns = AcceptedCmp1 + AcceptedCmp2 + AcceptedCmp3 + AcceptedCmp4 + AcceptedCmp5).

26. Complain (불만 제기 여부)

  • 유형: 이진형 데이터 (0 또는 1)
  • 활용: 고객 불만 여부를 기준으로 이탈 위험 분석 가능.
  • 데이터 준비: 불만을 제기한 고객 리스트를 따로 관리하거나 분석 가능.

27~28. Z_CostContact, Z_Revenue (고정 비용 및 고정 수익)

  • 유형: 숫자형 데이터
  • 활용: 이 변수는 고정적으로 ‘3’(같은 값)으로 유지되므로 분석에서 제외 가능.

29. Response (최근 캠페인에 대한 응답 여부)

  • 유형: 이진형 데이터 (0 또는 1)
  • 활용: 최근 캠페인의 고객 응답 여부를 나타내므로, 캠페인 효과 분석에 사용 가능.
  • 데이터 준비: 캠페인 성과 평가 및 응답하지 않은 고객들에게 재활동 권장 분석 가능.

데이터 분석 준비 사항

  1. 데이터 정제: 결측값 확인 및 처리 (특히 소득 데이터에서 확인할 것).
  2. 새로운 변수 생성: 나이, 총 구매 금액, 총 자녀 수, 등록 이후 경과 시간 등 파생 변수 생성.
  3. 범주형 변수 변환: Marital_StatusEducation 등의 변수는 더미 변수로 변환.
  4. 수치형 데이터: 필요시 Recency, 구매 횟수 등을 구간화하여 분석 가능.
  5. 특정 변수 제외: 고정된 값(Z_CostContact, Z_Revenue) 및 식별용 변수 ID는 분석에서 제외 가능.

데이터를 준비하고 적절한 처리 과정을 거친 후, 더 심도 있는 통계 분석 또는 머신러닝 모델링 등에 활용할 수 있을 것이다.


먼저 다운받은 데이터 파일을 코랩 환경에 올리고, 다음의 코드를 작성해주고 실행하자.

# 필요한 라이브러리 불러오기
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import pandas as pd

# 파일 경로 수정 필요 (Drive에서 실제 파일 경로)
file_path = '/content/marketing_campaign.csv'

# 파일 불러오기 (구분자가 탭으로 되어 있어 sep='\t' 사용)
df = pd.read_csv(file_path, sep='\t')

# 데이터 확인
df.head()

데이터 확인 및 간단한 정보 출력

데이터를 확인하고, 각 열에 대한 간단한 통계를 구할 수 있다.

# 데이터의 첫 5행 확인
df.head()

# 데이터 요약 정보 확인
df.info()

# 각 열에 대한 기본 통계 확인
df.describe()
  1. 데이터 개요
  • 총 2,240개의 행과 29개의 열이 있다.
  • 대부분의 열은 정수형(int) 또는 부동 소수점(float) 데이터를 포함하고 있으며, Education, Marital_Status, Dt_Customer는 문자열(object) 데이터다.

2. 기본 통

  • 소득(Income): 평균 52,247, 표준편차 25,173으로 매우 다양한 소득 분포를 보인다.
  • 연령(Year_Birth): 평균 출생 연도는 1968년이며, 1950년대 중반부터 1980년대 초반까지 고르게 분포되어 있다.
  • 자녀 관련 변수: 집에 자녀가 없거나(대다수), 1~2명 있는 경우가 많다.
  • 캠페인 응답(Response): 마지막 캠페인에 대한 응답률은 14.9%로, 캠페인 수락율이 낮은 편이다.

3. 누락된 데이터

  • Income 열에서 24개의 결측값이 존재한다. 이를 처리할 방법(평균 대체, 삭제 등)을 결정해야 한다.

4. 결측값 확인 및 처리

데이터에 결측값이 있는지 확인하고 처리한다.

# 결측값 확인
missing_values = df.isnull().sum()
print(missing_values)

# 결측값 처리 (예: 'Income'의 결측값을 평균으로 대체)
df['Income'].fillna(df['Income'].mean(), inplace=True)

결측값 처리

  • Income 컬럼의 결측값 24개를 평균 소득으로 대체했다.

5. 데이터 정규화 (나이 계산)

출생연도(Year_Birth)를 사용해 나이를 계산하고, 각 제품군의 지출 금액(MntWines, MntMeatProducts 등)을 합산해 총 구매 금액을 도출한다.

# 출생 연도를 이용해 나이 계산 (2024년 기준)
df['Age'] = 2024 - df['Year_Birth']

# 'Dt_Customer'를 날짜 형식으로 변환
df['Dt_Customer'] = pd.to_datetime(df['Dt_Customer'], format='%d-%m-%Y')

# 데이터 정제 후 기본 확인
df[['Year_Birth', 'Age', 'Dt_Customer']].head()

나이 계산 및 날짜 변환

  • 출생 연도를 기준으로 나이를 계산했을 때, 예시 결과는 다음과 같다.
    • 1957년생은 2024년 기준 67세
    • 1954년생은 70세
    • 1965년생은 59세
    • 1984년생은 40세
    • 1981년생은 43세
  • 고객 등록 날짜(Dt_Customer)는 날짜 형식으로 변환되었다.

6. 시각화 예시 고객 나이 분포

import matplotlib.pyplot as plt

# 나이 분포 시각화
plt.figure(figsize=(10,6))
plt.hist(df['Age'], bins=20, color='blue', edgecolor='black')
plt.title('Customer Age Distribution')
plt.xlabel('Age')
plt.ylabel('Number of Customers')
plt.show()

나이 분포 시각화

  • 히스토그램을 통해 고객들의 나이 분포를 확인한 결과, 고객들의 나이는 주로 30대에서 60대 사이에 분포하고 있었다.

7. 캠페인 응답 분석

# 각 캠페인에 대한 응답률 분석
campaign_columns = ['AcceptedCmp1', 'AcceptedCmp2', 'AcceptedCmp3', 'AcceptedCmp4', 'AcceptedCmp5']
response_rates = df[campaign_columns].mean() * 100  # 각 캠페인의 수락 비율(%)

# 출력
print("캠페인별 응답률(%)")
print(response_rates)

캠페인 응답률 분석

  • 각 캠페인에 대한 응답률(수락률)은 대체로 낮았다.
    • AcceptedCmp1: 약 6.4%
    • AcceptedCmp2: 약 1.3%
    • AcceptedCmp3: 약 7.3%
    • AcceptedCmp4: 약 7.5%
    • AcceptedCmp5: 약 7.3%

8. 구매 경로별 웹/스토어 평균 구매량

# 경로별 평균 구매량
purchase_channels = ['NumWebPurchases', 'NumCatalogPurchases', 'NumStorePurchases']
average_purchases = df[purchase_channels].mean()

# 출력
print("경로별 평균 구매량:")
print(average_purchases)

경로별 평균 구매량

  • 각 경로에서의 평균 구매 횟수는 다음과 같다.
    • 웹 구매: 약 4.2회
    • 카탈로그 구매: 약 2.7회
    • 매장 구매: 약 5.9회

9. 소득과 소비 분석 (소득에 따른 와인 소비)

# 소득과 와인 소비 간의 관계 시각화
plt.figure(figsize=(10,6))
plt.scatter(df['Income'], df['MntWines'], alpha=0.5)
plt.title('Income vs Wine Spending')
plt.xlabel('Income')
plt.ylabel('Wine Spending')
plt.show()

소득과 와인 소비 관계 시각화

소득과 와인 소비 간의 산점도를 통해 소득이 높을수록 와인 소비 금액이 증가하는 경향을 볼 수 있다. 하지만 100,000 이상의 소득에서는 와인 소비가 급격히 증가하지 않으며, 대부분의 고객은 낮은 소득 범위에 위치하고 있다. 몇몇 매우 높은 소득의 예외적인 값들도 관찰된다.

1. T-검정 (두 그룹 간 평균 비교)

T-검정은 두 그룹 간의 평균 차이가 유의미한지 테스트할 때 사용된다. 여기서는 캠페인에 응답한 고객과 응답하지 않은 고객의 평균 소득을 비교할 수 있다.

캠페인 응답 여부에 따른 소득 비교 (T-검정)

from scipy import stats

# 캠페인에 응답한 고객과 응답하지 않은 고객의 소득 데이터 분리
income_responded = df[df['Response'] == 1]['Income']
income_not_responded = df[df['Response'] == 0]['Income']

# T-검정 수행
t_stat, p_value = stats.ttest_ind(income_responded, income_not_responded)

print(f"T-statistic: {t_stat}")
print(f"P-value: {p_value}")

if p_value < 0.05:
    print("두 그룹 간 소득의 차이가 통계적으로 유의미하다.")
else:
    print("두 그룹 간 소득의 차이가 통계적으로 유의미하지 않다.")

T-검정 결과

  • T-statistic: 6.336 (T값이 크다)
  • P-value: 2.83e-10 (매우 작은 값, 일반적으로 유의 수준 0.05보다 훨씬 작다)

이 결과는 캠페인에 응답한 그룹과 응답하지 않은 그룹 간의 소득 차이가 통계적으로 유의미하다는 것을 의미한다. 즉, 캠페인에 응답한 고객의 소득이 더 높거나 낮다는 것을 말할 수 있다.

2. 회귀분석 (독립 변수와 종속 변수 간의 관계 분석)

회귀분석은 여러 독립 변수가 종속 변수에 미치는 영향을 분석할 때 사용된다. 여기서는 고객의 나이, 소득, 자녀 수 등이 와인 구매에 미치는 영향을 분석할 수 있다.

여러 변수와 와인 소비 간의 관계 분석 (회귀분석)

import statsmodels.api as sm

# 독립 변수(나이, 소득, 자녀 수 등)와 종속 변수(와인 소비) 설정
X = df[['Age', 'Income', 'Kidhome', 'Teenhome']]
y = df['MntWines']

# 상수항 추가 (회귀분석에서 절편 포함)
X = sm.add_constant(X)

# 회귀모델 적합
model = sm.OLS(y, X)
results = model.fit()

# 회귀분석 결과 요약 출력
print(results.summary())

R-squared (결정계수): 0.410 (41%)

  • 모델이 와인 소비의 41%를 설명할 수 있음을 의미한다. 나머지 59%는 다른 요인에 의해 설명될 수 있다.

독립변수 (Age, Income, Kidhome, Teenhome):

  • Income: 회귀 계수는 0.006으로, 소득이 1 단위 증가할 때 와인 소비가 0.006 단위 증가한다는 것을 나타낸다. P-value는 0.000으로 유의미하다.
  • Kidhome: 회귀 계수는 -189.03으로, 집에 자녀가 있을 때 와인 소비가 평균적으로 189 단위 줄어든다는 것을 보여준다. 이는 매우 유의미한 결과(P-value = 0.000)다.
  • AgeTeenhome은 P-value가 유의미하지 않으므로 와인 소비에 큰 영향을 미치지 않는 변수로 해석할 수 있다.

3. 선형 회귀 분석 (단순 선형 회귀)

선형 회귀 분석은 독립 변수와 종속 변수 간의 직선적 관계를 모델링하는 방법이다. 여기서는 소득이 와인 구매에 미치는 영향을 직선적으로 분석할 수 있다.

소득이 와인 소비에 미치는 영향 (단순 선형 회귀)

from sklearn.linear_model import LinearRegression
import numpy as np

# 독립 변수(Income)와 종속 변수(MntWines) 설정
X = df[['Income']].values
y = df['MntWines'].values

# 선형 회귀 모델 적합
model = LinearRegression()
model.fit(X, y)

# 회귀 계수 및 절편 출력
print(f"회귀 계수: {model.coef_[0]}")
print(f"절편: {model.intercept_}")

# 예측 값 시각화
import matplotlib.pyplot as plt

plt.scatter(X, y, color='blue', alpha=0.5)
plt.plot(X, model.predict(X), color='red', linewidth=2)
plt.title('Income vs Wine Spending (Linear Regression)')
plt.xlabel('Income')
plt.ylabel('Wine Spending')
plt.show()

회귀 계수: 0.00775

  • 소득이 1 단위 증가할 때 와인 소비가 약 0.00775 단위 증가한다.

절편: -101.19

  • 소득이 0일 때 와인 소비가 음수(실제로는 이 상황이 존재할 수 없다)로 나타나지만, 이는 데이터의 경향을 설명하기 위한 절편 값이다.

좀더 자세히 말하자면 회귀 분석 결과에서 절편 값이 -101.19로 나왔다. 이 절편 값은 소득(Income)이 0일 때, 예상되는 와인 소비(MntWines)가 -101.19라는 의미다.

그러나 실제로 소득이 0일 때 와인 소비가 음수로 나오는 것은 현실적으로 불가능한 결과다. 이는 회귀분석에서 절편이 논리적으로 해석되지 않는 경우일 수 있고, 회귀 모델에서의 직선이 데이터의 경향을 설명하는 과정에서 나타난 값이다. 이는 현실적으로 불가능한 값이지만, 회귀 직선의 경향을 설명하기 위한 수학적 결과일 뿐이다. 실제로 중요한 부분은 소득이 와인 소비에 미치는 영향이며, 이 영향은 양수(소득이 증가할수록 와인 소비도 증가)로 확인되었다. 절편 값은 이 회귀 모델의 전반적인 예측 성능에 큰 영향을 주지 않는다.

회귀 분석 결과를 바탕으로 다른 변수들과의 비교 분석을 진행해 보겠다. 이미 확인한 모델은 나이(Age), 소득(Income), 자녀 수(Kidhome), 청소년 수(Teenhome)와인 소비(MntWines) 간의 관계를 살펴본 결과였다. 이제 이와 비교할 수 있는 다른 변수를 추가하거나 기존 변수를 대체하여 와인 소비에 어떤 영향을 미치는지 확인할 것이다.

다음과 같은 변수를 추가적으로 분석해 비교해볼 수 있다.

  1. 다른 소비 지표: 와인 소비뿐만 아니라 고기 제품 소비(MntMeatProducts), 과일 제품 소비(MntFruits)와 같은 다른 소비 변수에 대한 분석을 통해 소비 패턴을 비교
  2. 다른 구매 채널: 웹 구매(NumWebPurchases), 매장 구매(NumStorePurchases) 등 채널별 구매 행동을 분석하여 비교
  3. 캠페인 수락: 캠페인 수락(AcceptedCmp1~5)과 와인 소비 간의 관계를 분석

1. 다른 소비 변수와의 비교 (고기 제품 소비)

소득과 고기 제품 소비 간의 관계를 분석하여 소득이 고기 제품 구매에 미치는 영향을 비교해볼 수 있다.

# 고기 제품 소비에 대한 회귀 분석
X_meat = df[['Age', 'Income', 'Kidhome', 'Teenhome']]
y_meat = df['MntMeatProducts']

# 상수항 추가
X_meat = sm.add_constant(X_meat)

# 회귀 모델 적합
model_meat = sm.OLS(y_meat, X_meat)
results_meat = model_meat.fit()

# 회귀 분석 결과 요약
print(results_meat.summary())

R-squared: 0.456 (45.6%)
모델이 고기 제품 소비의 45.6%를 설명할 수 있다. 나머지 54.4%는 다른 요인에 의해 설명된다.

  • Income: 소득이 1 단위 증가할 때 고기 제품 소비가 0.0043 단위 증가한다. 이는 매우 유의미하며(P-value = 0.000), 소득이 고기 제품 소비에 긍정적인 영향을 미친다고 볼 수 있다.
  • Kidhome: 자녀가 있을 때 고기 제품 소비가 약 102.37 단위 줄어든다. 이는 매우 유의미한 결과(P-value = 0.000)로, 자녀가 많을수록 고기 제품 소비가 줄어든다.
  • Teenhome: 청소년이 있을 경우 고기 제품 소비가 약 114.98 단위 줄어든다(P-value = 0.000). 자녀가 있는 가정은 고기 소비가 현저히 줄어든다.
  • Age: 나이는 유의미한 영향을 미치지 않는 것으로 보인다.(P-value = 0.774).

2. 캠페인 수락과의 비교

캠페인 수락 여부가 와인 소비에 미치는 영향을 분석할 수 있다. 캠페인을 수락한 고객들이 더 많이 소비하는지 비교한다.

# 캠페인 수락 여부와 와인 소비 간의 관계
X_cmp = df[['Age', 'Income', 'Kidhome', 'Teenhome', 'AcceptedCmp1', 'AcceptedCmp2', 'AcceptedCmp3', 'AcceptedCmp4', 'AcceptedCmp5']]
y_cmp = df['MntWines']

# 상수항 추가
X_cmp = sm.add_constant(X_cmp)

# 회귀 모델 적합
model_cmp = sm.OLS(y_cmp, X_cmp)
results_cmp = model_cmp.fit()

# 회귀 분석 결과 요약
print(results_cmp.summary())

R-squared: 0.555 (55.5%)
이 모델은 와인 소비의 55.5%를 설명할 수 있다. 캠페인 수락 여부가 와인 소비에 중요한 영향을 미칠 가능성이 높다.

  • Income: 소득이 1 단위 증가할 때 와인 소비가 0.0034 단위 증가한다(P-value = 0.000). 소득은 와인 소비에 유의미한 영향을 미친다.
  • AcceptedCmp3, AcceptedCmp4, AcceptedCmp5: 캠페인을 수락한 고객은 와인 소비가 각각 78.91, 225.29, 277.32 단위씩 더 높다. 이는 P-value가 0.000으로 매우 유의미하다. 특히 AcceptedCmp4AcceptedCmp5가 와인 소비에 가장 큰 영향을 미친다.
  • Kidhome: 자녀가 있을 때 와인 소비가 85.22 단위 줄어든다.(P-value = 0.000).
  • Teenhome: 청소년이 있을 경우 와인 소비가 43.77 단위 줄어든다.(P-value = 0.000).

3. 웹 구매와 매장 구매의 비교

웹 구매와 매장 구매 간의 소비 패턴을 비교 분석해볼 수 있다.

# 웹 구매와 매장 구매에 대한 회귀 분석
X_channel = df[['Age', 'Income', 'Kidhome', 'Teenhome', 'NumWebPurchases', 'NumStorePurchases']]
y_channel = df['MntWines']

# 상수항 추가
X_channel = sm.add_constant(X_channel)

# 회귀 모델 적합
model_channel = sm.OLS(y_channel, X_channel)
results_channel = model_channel.fit()

# 회귀 분석 결과 요약
print(results_channel.summary())

R-squared: 0.555 (55.5%)
이 모델은 와인 소비의 55.5%를 설명할 수 있으며, 웹 구매와 매장 구매가 와인 소비에 중요한 영향을 미칠 가능성이 있다.

  • NumWebPurchases: 웹 구매가 1회 증가할 때 와인 소비가 29.38 단위 증가한다.(P-value = 0.000). 이는 와인 소비가 웹 구매를 통해 증가하는 경향을 보여준다.
  • NumStorePurchases: 매장 구매가 1회 증가할 때 와인 소비가 32.79 단위 증가한다.(P-value = 0.000). 매장 구매 역시 와인 소비에 매우 중요한 영향을 미친다.
  • Income: 소득이 1 단위 증가할 때 와인 소비가 0.0034 단위 증가한다.(P-value = 0.000).

이제 데이터 모델링을 통해 예측 모델을 만들고, 데이터를 바탕으로 패턴을 분석하여 향후 결과를 예측할 수 있지만. 예측 파트는 생략하고 데이터를 학습하고 예측가능한 머신러닝 모델을 구축해 보겠다. 특히, 와인 소비(MntWines)를 예측하는 모델을 만들어보겠다.

단계

  1. 데이터 전처리
  2. 데이터 분할 (훈련/테스트 세트)
  3. 다양한 머신러닝 알고리즘 적용 (예: 선형 회귀, 랜덤 포레스트, XGBoost 등)
  4. 모델 성능 평가 (R², MSE, RMSE 등)

1. 데이터 전처리

먼저, 결측값을 처리하고 필요한 변수들을 선택하여 데이터 전처리를 한다.

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

# 필요한 변수 선택 (나이, 소득, 자녀 수 등)
X = df[['Age', 'Income', 'Kidhome', 'Teenhome', 'NumWebPurchases', 'NumStorePurchases']]

# 종속 변수: 와인 소비 (MntWines)
y = df['MntWines']

# 데이터 분할 (훈련 데이터와 테스트 데이터로 나눔)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

# 데이터 스케일링 (필요할 경우)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

2. 머신러닝 모델 적용

몇 가지 대표적인 머신러닝 모델을 적용해볼 수 있다. 선형 회귀(Linear Regression), 랜덤 포레스트(Random Forest), 그리고 XGBoost를 사용할 수 있다. 그 결과를 비교해보겠다.

2.1. 선형 회귀(Linear Regression)

from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score, mean_squared_error

# 선형 회귀 모델 학습
lin_reg = LinearRegression()
lin_reg.fit(X_train_scaled, y_train)

# 예측
y_pred_lin = lin_reg.predict(X_test_scaled)

# 성능 평가
print(f"Linear Regression R²: {r2_score(y_test, y_pred_lin)}")
print(f"Linear Regression MSE: {mean_squared_error(y_test, y_pred_lin)}")

Linear Regression:

  • : 0.566 (56.6%)
  • MSE: 46,662.83
  • 선형 회귀 모델은 데이터의 56.6%를 설명하며, MSE는 46,662로 나타났다.

2.2. 랜덤 포레스트(Random Forest)

from sklearn.ensemble import RandomForestRegressor

# 랜덤 포레스트 모델 학습
rf_reg = RandomForestRegressor(n_estimators=100, random_state=42)
rf_reg.fit(X_train, y_train)

# 예측
y_pred_rf = rf_reg.predict(X_test)

# 성능 평가
print(f"Random Forest R²: {r2_score(y_test, y_pred_rf)}")
print(f"Random Forest MSE: {mean_squared_error(y_test, y_pred_rf)}")

Random Forest:

  • : 0.644 (64.4%)
  • MSE: 38,275.57
  • 랜덤 포레스트 모델이 가장 높은 성능을 보였다. 데이터의 64.4%를 설명하며, MSE는 38,275로 가장 낮았다.

2.3. XGBoost

from xgboost import XGBRegressor

# XGBoost 모델 학습
xgb_reg = XGBRegressor(n_estimators=100, random_state=42)
xgb_reg.fit(X_train, y_train)

# 예측
y_pred_xgb = xgb_reg.predict(X_test)

# 성능 평가
print(f"XGBoost R²: {r2_score(y_test, y_pred_xgb)}")
print(f"XGBoost MSE: {mean_squared_error(y_test, y_pred_xgb)}")

XGBoost:

  • : 0.602 (60.2%)
  • MSE: 42,743.44
  • XGBoost 모델도 나쁘지 않은 성능을 보였으며, 데이터의 60.2%를 설명하고 MSE는 42,743다.

선형 회귀는 가장 기본적인 모델로, R² 값이 다른 모델들에 비해 낮지만, 데이터에 직선적인 관계가 있을 때는 유용할 수 있다

랜덤 포레스트 모델이 가장 좋은 성능을 보였으며, R² 값이 가장 높고 MSE 값이 가장 낮다. 이 모델은 데이터를 가장 잘 설명하고 있으며, 와인 소비를 예측하는 데 적합한 모델로 보인다.

XGBoost는 랜덤 포레스트보다는 성능이 약간 낮았지만, 여전히 좋은 결과를 보였다. 이 모델은 약간 더 복잡한 모델이기 때문에 데이터에 따라 성능 차이가 날 수 있다.


다음 단계

특성 중요도(Feature Importance): 랜덤 포레스트나 XGBoost 모델에서 어떤 특성이 가장 중요한지 분석할 수 있다.

랜덤 포레스트 하이퍼파라미터 튜닝: 랜덤 포레스트 모델의 하이퍼파라미터(예: 트리의 개수, 최대 깊이 등)를 조정해 성능을 향상시킬 수 있다.

교차검증(Cross-validation): 교차검증을 통해 모델의 일반화 성능을 평가하고, 과적합(overfitting)을 방지할 수 있다.

다음은 랜덤포레스트 하이퍼파라미터 튜닝 코드다.

from sklearn.model_selection import GridSearchCV

# 하이퍼파라미터 그리드 설정
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [10, 20, 30],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4]
}

# 그리드 서치 실행
grid_search = GridSearchCV(estimator=rf_reg, param_grid=param_grid, cv=3, n_jobs=-1, verbose=2)
grid_search.fit(X_train, y_train)

# 최적의 파라미터와 성능 출력
print(f"Best Parameters: {grid_search.best_params_}")
print(f"Best R² Score: {grid_search.best_score_}")

최적의 하이퍼파라미터

  • max_depth: 10
    트리의 최대 깊이를 10으로 설정한 것이 가장 좋은 성능을 보여주었다. 이는 트리가 너무 깊어져서 과적합(overfitting)되는 것을 방지하면서도, 적절한 복잡도를 유지할 수 있음을 의미한다.
  • min_samples_leaf: 1
    리프 노드에 최소한 1개의 샘플이 있어야 한다는 의미다. 과적합을 방지하는 데 유용하다.
  • min_samples_split: 2
    노드를 분할할 때 최소한 2개의 샘플이 있어야 한다. 기본 설정이며, 더 많은 샘플이 필요할 때 분할이 이루어지므로, 모델의 유연성을 유지하게 해준다.
  • n_estimators: 300
    300개의 결정 트리를 사용한 것이 최적이다. 트리의 수가 많아질수록 모델의 안정성이 향상되며, 일정 수준 이상에서는 더 많은 트리를 사용해도 성능 향상이 미미해진다.

최적의 R² 성능

  • Best R² Score: 0.649
    하이퍼파라미터 튜닝을 통해 R² 값이 0.649로 개선되었다. 이는 데이터의 약 64.9%를 설명할 수 있음을 의미하며, 모델 성능이 이전에 비해 향상되었다는 것을 보여준다. 이 결과는 튜닝된 랜덤 포레스트 모델이 현재 데이터에 대해 가장 적합한 예측 모델임을 나타낸다.

성능을 더 높이기 위해서 몇 가지 방법이 있다. 모델 성능을 개선하기 위한 방법에는 하이퍼파라미터 튜닝, 특성 엔지니어링, 모델 변경 및 앙상블 방법 등이 포함된다.

1. 하이퍼파라미터 튜닝의 추가 최적화

기존에 사용한 하이퍼파라미터 그리드가 충분히 다양하지 않을 수 있다. 더 다양한 하이퍼파라미터 범위를 실험하여 성능을 개선할 수 있다. 랜덤 서치(RandomizedSearchCV)를 통해 더 많은 조합을 탐색하거나 그리드 범위를 확장할 수 있다.

랜덤 서치로 더 넓은 하이퍼파라미터 탐색

from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import randint

# 더 넓은 범위의 하이퍼파라미터 설정
param_dist = {
    'n_estimators': randint(100, 1000),
    'max_depth': randint(5, 50),
    'min_samples_split': randint(2, 20),
    'min_samples_leaf': randint(1, 10)
}

# 랜덤 서치 실행
random_search = RandomizedSearchCV(estimator=rf_reg, param_distributions=param_dist, n_iter=100, cv=3, n_jobs=-1, random_state=42)
random_search.fit(X_train, y_train)

# 최적 파라미터 출력
print(f"Best Parameters: {random_search.best_params_}")
print(f"Best R² Score: {random_search.best_score_}")

최적의 하이퍼파라미터

  • max_depth: 29
  • min_samples_leaf: 2
  • min_samples_split: 10
  • n_estimators: 445

Best R² Score: 0.647

  • 데이터의 약 64.7%를 설명할 수 있다.

2. 특성 엔지니어링(Feature Engineering)

특성 엔지니어링은 모델 성능을 향상시킬 수 있다. 모델이 보다 잘 학습할 수 있도록 새로운 파생 변수를 추가하거나, 기존 특성을 변형할 수 있다. 예를 들어, Income 변수를 로그 스케일로 변환하거나, Age를 구간화할 수 있다.

새로운 파생 변수 추가

# 소득을 로그 변환하여 추가
df['log_Income'] = np.log1p(df['Income'])

# 나이를 구간화 (예: 10년 단위로)
df['Age_Group'] = pd.cut(df['Age'], bins=[20, 30, 40, 50, 60, 70, 80, 90], labels=['20s', '30s', '40s', '50s', '60s', '70s', '80s'])

# 새로운 변수를 포함한 데이터로 다시 학습
X_new = df[['log_Income', 'Kidhome', 'Teenhome', 'NumWebPurchases', 'NumStorePurchases']]
X_train_new, X_test_new, y_train_new, y_test_new = train_test_split(X_new, y, test_size=0.3, random_state=42)

# 스케일링 및 모델 학습
X_train_scaled_new = scaler.fit_transform(X_train_new)
X_test_scaled_new = scaler.transform(X_test_new)

rf_reg.fit(X_train_scaled_new, y_train_new)
y_pred_new = rf_reg.predict(X_test_scaled_new)

# 성능 평가
print(f"R² Score with New Features: {r2_score(y_test_new, y_pred_new)}")

R² Score with New Features: 0.647

새로운 파생 변수를 추가한 후에도 성능은 크게 향상되지 않았다.

3. 더 복잡한 모델 사용

랜덤 포레스트 외에도 더 복잡한 모델을 사용할 수 있다. 예를 들어, Gradient Boosting Machines(GBM), CatBoost, LightGBM 같은 모델들은 트리 기반 모델보다 더 복잡하고 좋은 성능을 발휘할 수 있다.

LightGBM 적용

import lightgbm as lgb

# LightGBM 모델 설정
lgbm_reg = lgb.LGBMRegressor(n_estimators=1000, learning_rate=0.05, random_state=42)

# 학습
lgbm_reg.fit(X_train_scaled, y_train)

# 예측 및 성능 평가
y_pred_lgbm = lgbm_reg.predict(X_test_scaled)
print(f"LightGBM R² Score: {r2_score(y_test, y_pred_lgbm)}")
print(f"LightGBM MSE: {mean_squared_error(y_test, y_pred_lgbm)}")

R² Score: 0.573

  • LightGBM의 결정계수는 57.3%로, 랜덤 포레스트에 비해 성능이 낮다.

MSE: 45,929

  • 평균 제곱 오차(MSE)가 비교적 높아, 예측 오차가 더 크다.

4. 앙상블(Ensemble) 방법 사용

앙상블 방법은 여러 모델을 결합하여 성능을 향상시킬 수 있다. 앙상블 방식으로는 배깅(Bagging), 부스팅(Boosting), 또는 스태킹(Stacking)을 사용할 수 있다.

스태킹(Stacking) 앙상블

from sklearn.ensemble import StackingRegressor
from sklearn.linear_model import Ridge

# 모델 구성
estimators = [
    ('rf', RandomForestRegressor(n_estimators=100, random_state=42)),
    ('xgb', XGBRegressor(n_estimators=100, random_state=42))
]

# 스태킹 모델
stacking_reg = StackingRegressor(estimators=estimators, final_estimator=Ridge())

# 학습 및 성능 평가
stacking_reg.fit(X_train_scaled, y_train)
y_pred_stack = stacking_reg.predict(X_test_scaled)

print(f"Stacking Regressor R² Score: {r2_score(y_test, y_pred_stack)}")
print(f"Stacking Regressor MSE: {mean_squared_error(y_test, y_pred_stack)}")

R² Score: 0.653

  • 여러 모델을 결합한 스태킹 앙상블 모델이 가장 높은 성능을 보였다. 데이터의 약 65.3%를 설명할 수 있으며, 이 값이 가장 높은 R² 값이다.

MSE: 37,287

  • MSE가 가장 낮으며, 예측 오차도 가장 적다. 스태킹 모델이 와인 소비 예측에서 가장 적합한 모델로 보인다.

5. 데이터 증대(Data Augmentation)

데이터가 충분히 많지 않다면, 데이터 증대 기법을 통해 데이터를 인위적으로 늘려 모델이 더 많은 패턴을 학습할 수 있도록 할 수 있다. 예를 들어, 변수 간의 상호작용 항을 추가하거나, 폴리노미얼 피처(Polynomial Features)를 추가하여 모델이 더 복잡한 관계를 학습할 수 있다.

글에서는 생략 했지만, 모델 성능을 올리기 위해 다른 방법을 써보았지만, 초기 Ridge를 최종 추정기로 한 스태킹 모델이 이미 좋은 성능을 보였기 때문에, 이를 기반으로 추가적인 성능 개선 작업을 진행해볼 것이다. 모델의 세부 튜닝교차검증을 적용하거나 특성 엔지니어링을 조금 더 시도하는 방법을 사용할 수 있다. 교차검증과 특성 엔지니어링을 모두 해본 결과 교차 검증이 더 좋은 결과를 보였기에, 다음은 교차검증에 관한 내용만 넣었다.

1. 교차검증(Cross-validation) 강화

스태킹 모델의 일반화 성능을 더 정확하게 평가하기 위해 교차검증을 적용하는 것이 좋다. 교차검증은 모델이 과적합되지 않도록 돕고, 더 안정적인 성능을 보장할 수 있다.

교차검증 적용

from sklearn.model_selection import cross_val_score

# 교차검증으로 성능 평가 (R² 스코어)
cv_scores = cross_val_score(stacking_reg, X_train_scaled, y_train, cv=5, scoring='r2')

# 교차검증 결과 출력
print(f"Cross-validated R² Scores: {cv_scores}")
print(f"Mean Cross-validated R² Score: {cv_scores.mean()}")

Cross-validated R² Scores: [0.619, 0.695, 0.620, 0.675, 0.617]

  • 개별 fold에서의 R² 값은 모두 0.62에서 0.70 사이로 안정적인 성능을 보였다.

Mean Cross-validated R² Score: 0.645

  • 교차검증을 통한 평균 R² 값은 0.645로, 매우 높은 성능을 보여주고 있다. 이 결과는 모델이 과적합되지 않았으며, 새로운 데이터에도 안정적인 성능을 보일 가능성이 높다는 것을 의미한다.

2. 특성 엔지니어링(Feature Engineering)

기존의 변수 외에도 새로운 파생 변수를 추가해 모델이 더 나은 패턴을 학습하도록 할 수 있다. 소득(Income) 같은 변수를 로그 변환하거나, 특정 변수들 간의 상호작용 항을 추가하는 방식으로 성능을 높일 수 있다.

로그 변환 및 상호작용 항 추가

import numpy as np

# 소득의 로그 변환
df['log_Income'] = np.log1p(df['Income'])

# 나이와 자녀 수의 상호작용 변수 추가
df['Age_Kidhome_Interaction'] = df['Age'] * df['Kidhome']

# 새로운 변수를 포함한 특성 집합
X_new = df[['log_Income', 'Age_Kidhome_Interaction', 'NumWebPurchases', 'NumStorePurchases']]

# 데이터 분할 및 스케일링
X_train_new, X_test_new, y_train_new, y_test_new = train_test_split(X_new, y, test_size=0.3, random_state=42)
scaler = StandardScaler()
X_train_scaled_new = scaler.fit_transform(X_train_new)
X_test_scaled_new = scaler.transform(X_test_new)

# 스태킹 모델 학습 및 예측
stacking_reg.fit(X_train_scaled_new, y_train_new)
y_pred_new = stacking_reg.predict(X_test_scaled_new)

# 성능 평가
print(f"R² Score with New Features: {r2_score(y_test_new, y_pred_new)}")
print(f"MSE with New Features: {mean_squared_error(y_test_new, y_pred_new)}")

R² Score: 0.633

  • 새로운 특성을 추가한 후의 R² 값은 0.633으로 약간 성능이 하락했다. 이는 추가된 특성들이 모델의 성능을 크게 향상시키지는 못했음을 의미한다.

MSE: 39,442

  • MSE는 39,442로 나타났으며, 예측 오차가 약간 개선되었다. 하지만, 이 또한 크게 향상된 것은 아니다.

시각화 설명

  • 각 폴드에 대해 얻은 R² 점수를 선으로 연결하여, 각 폴드별 성능 변화를 시각적으로 확인할 수 있다.
  • 평균 R² 값은 빨간색 점선으로 나타내어, 폴드별 성능과 평균 성능을 비교할 수 있다.
import matplotlib.pyplot as plt
import numpy as np

# 교차검증 결과
cv_scores = [0.61939862, 0.69506811, 0.61971235, 0.67497058, 0.61734525]
mean_cv_score = np.mean(cv_scores)

# 교차검증 결과 시각화
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(cv_scores) + 1), cv_scores, marker='o', linestyle='-', color='b', label='R² Scores per fold')
plt.axhline(y=mean_cv_score, color='r', linestyle='--', label=f'Mean R² Score: {mean_cv_score:.3f}')

# 시각적 요소 추가
plt.title('Cross-validated R² Scores per Fold')
plt.xlabel('Fold')
plt.ylabel('R² Score')
plt.ylim(0.6, 0.7)
plt.xticks(range(1, len(cv_scores) + 1))
plt.legend()
plt.grid(True)
plt.show()

폴드별 R² 점수

  • 각 폴드에서 계산된 R² 점수가 시각화되어 있으며, 폴드 2폴드 4에서 상대적으로 높은 성능을 보였고, 폴드 1, 폴드 3, 폴드 5는 상대적으로 낮은 성능을 보였다.
  • 이로 인해 데이터 샘플에 따라 성능 변동이 존재함을 확인할 수 있다.

평균 R² 점수

  • 빨간 점선으로 표시된 평균 R² 점수0.645로, 전반적으로 모델이 안정적인 성능을 보여준다.
  • 일부 폴드에서 성능이 더 높거나 낮을 수 있지만, 평균적으로는 일관된 예측 성능을 유지하고 있다.

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤