Growth Marketing Bootcamp · Day 16
다중회귀분석 실습 & 군집 분석(K-Means) 입문
광고비로 판매량을 예측하고, 리드를 핫/워밍/콜드로 자동 분류하기
Part 1. 다중회귀분석 실습 — 광고비로 판매량 예측하기
어제 개념을 배웠으니 오늘은 바로 실습입니다. 데이터셋은 Advertising 데이터로, TV · 라디오 · 신문 광고비 지출이 제품 판매량에 얼마나 영향을 주는지 분석합니다. 목표는 딱 하나: "앞으로 어떤 채널에 예산을 쓰면 판매량이 가장 많이 오를까?"를 모델로 예측하는 것입니다.
STEP 0 라이브러리 불러오기
Day16.ipynbPython
import pandas as pd from sklearn.model_selection import train_test_split from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
라이브러리별 역할
- pandas: 데이터를 엑셀 표처럼 다루는 도구.
df라는 변수에 데이터를 담아 처리합니다. - train_test_split: 데이터를 "공부용"과 "시험용"으로 나눠주는 함수. 시험 문제를 미리 알려주면 진짜 실력을 알 수 없듯이, 모델도 학습 데이터로는 평가할 수 없습니다.
- LinearRegression: 선형 회귀 모델. y = ax + b 형태의 관계를 찾아줍니다.
- MAE · MSE · R²: 모델이 얼마나 틀렸는지 측정하는 성적표.
STEP 1–2 데이터 불러오기 & 확인
Day16.ipynbPython
# 1. 데이터 불러오기 url = "https://www.statlearning.com/s/Advertising.csv" df = pd.read_csv(url) if "Unnamed: 0" in df.columns: df = df.drop(columns=["Unnamed: 0"]) # 2. 데이터 확인 print("데이터 크기:", df.shape) # (200, 4) df.head() df.describe()
함수 설명
- df.shape: (행 수, 열 수)를 반환. (200, 4)라면 200개 데이터, 4개 변수.
- df.head(): 첫 5행만 미리 봄. 데이터가 제대로 들어왔는지 확인용.
- df.describe(): 평균, 표준편차, 최솟·최댓값 등 기초 통계. TV 예산 평균 약 147,000달러, 판매량 평균 약 14,000개를 이 단계에서 파악합니다.
- "Unnamed: 0" 제거: CSV에 불필요한 행 번호가 컬럼으로 들어오는 경우에 자주 보이는 패턴. 있으면 지워주세요.
| 컬럼 | 내용 |
|---|---|
TV | TV 광고 예산 (단위: 1,000달러) |
radio | 라디오 광고 예산 |
newspaper | 신문 광고 예산 |
sales | 제품 판매량 (단위: 1,000개) ← 우리가 예측할 값 |
STEP 3 독립변수(X)와 종속변수(y) 분리
Day16.ipynbPython
X = df[["TV", "radio", "newspaper"]] # 독립변수: 우리가 조절할 수 있는 것 y = df["sales"] # 종속변수: 우리가 예측하고 싶은 것
수식으로 이해하기
β₀(절편): 광고를 전혀 안 해도 나오는 기본 판매량
β₁, β₂, β₃: 각 채널 광고비 1단위 증가 시 판매량 변화량
ε(엡실론): 모델이 설명하지 못하는 나머지 오차
sales = β₀ + β₁×TV + β₂×radio + β₃×newspaper + εβ₀(절편): 광고를 전혀 안 해도 나오는 기본 판매량
β₁, β₂, β₃: 각 채널 광고비 1단위 증가 시 판매량 변화량
ε(엡실론): 모델이 설명하지 못하는 나머지 오차
STEP 4 학습용 / 테스트용 데이터 분리
Day16.ipynbPython
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, # 전체의 20%를 테스트용으로 random_state=42 # 결과를 고정 (팀원과 같은 숫자 쓰면 동일 결과) )
왜 데이터를 나눌까요?
시험 문제를 미리 알려주고 시험을 보면 공부를 잘 한 건지 알 수 없습니다. 모델도 마찬가지로, 학습에 쓴 데이터로 평가하면 진짜 예측 능력을 알 수 없습니다. 200개 중 160개(80%)로 학습하고, 나머지 40개(20%)로 평가합니다.
시험 문제를 미리 알려주고 시험을 보면 공부를 잘 한 건지 알 수 없습니다. 모델도 마찬가지로, 학습에 쓴 데이터로 평가하면 진짜 예측 능력을 알 수 없습니다. 200개 중 160개(80%)로 학습하고, 나머지 40개(20%)로 평가합니다.
STEP 5–6 모델 학습 & 예측
Day16.ipynbPython
# 5. 모델 학습 model = LinearRegression() model.fit(X_train, y_train) # "이 데이터로 공부해" # 6. 예측 y_pred = model.predict(X_test) # 테스트 X 넣어서 sales 예측
STEP 7 성능 평가 & 결과 해석 ★
이 단계가 가장 중요합니다. 모델이 "얼마나 잘 맞히는지"와 "어떤 채널이 판매량에 가장 큰 영향을 주는지"를 확인합니다.
Day16.ipynbPython
# 성능 지표 계산 mae = mean_absolute_error(y_test, y_pred) mse = mean_squared_error(y_test, y_pred) rmse = mse ** 0.5 r2 = r2_score(y_test, y_pred) # 회귀계수 출력 print("절편(intercept):", round(model.intercept_, 3)) for col, coef in zip(X.columns, model.coef_): print(f" - {col}: {round(coef, 3)}") print("MAE :", round(mae, 3)) print("RMSE:", round(rmse, 3)) print("R² :", round(r2, 3))
출력 결과
절편: 2.979 | TV: 0.045 | radio: 0.189 | newspaper: 0.003
절편: 2.979 | TV: 0.045 | radio: 0.189 | newspaper: 0.003
7-1. 회귀계수 해석 — "어느 채널이 효율적인가?"
| 계수 | 값 | 해석 |
|---|---|---|
| 절편 β₀ | 2.979 | 광고 없이도 기대되는 기본 판매량 약 2,979개 |
| TV β₁ | 0.045 | TV 광고비 1,000달러 증가 → 판매량 약 45개 증가 |
| radio β₂ | 0.189 | 라디오 광고비 1,000달러 증가 → 판매량 약 189개 증가 |
| newspaper β₃ | 0.003 | 신문 광고비 증가 → 판매량 거의 변화 없음 |
마케터 인사이트: 라디오 계수(0.189)가 TV(0.045)보다 약 4.2배 높습니다. 같은 금액을 써도 라디오가 훨씬 효율적이라는 뜻입니다. 신문 광고 예산을 줄여 라디오에 몰아주는 것이 데이터상 최적 전략입니다.
7-2. 평가 지표 해석 — "모델이 얼마나 잘 맞히는가?"
MAE
1.461
예측이 실제 판매량과 평균 약 1,461개 차이. 판매량 평균(14,000개) 대비 약 10% 오차 → 꽤 정확
RMSE
1.782
큰 오차에 더 많은 벌점 부여. MAE와 차이가 작아 모델이 안정적임을 뜻함
R²
0.899
광고비 데이터만으로 판매량 변동의 89.9% 설명 가능. 마케팅 분석에서 0.7 이상이면 우수
세 지표 한눈에 비교
- MAE: "평균적으로 얼마나 틀렸나?" → 이상치에 덜 민감, 낮을수록 좋음
- RMSE: "큰 실수에 더 많은 벌점" → 이상치 탐지에 유용, MAE와 비교해서 봄
- R²: "내 모델이 데이터를 얼마나 설명하나?" → 1에 가까울수록 좋음. 변수를 많이 넣을수록 올라가므로 단독으로만 믿으면 안 됨.
STEP 8 광고 예산 시나리오로 미래 예측
Day16.ipynbPython
scenarios = pd.DataFrame({ "시나리오": ["A (현재)", "B (라디오 집중)", "C (TV 집중)"], "TV": [100, 50, 200], "radio": [20, 80, 10], "newspaper": [30, 10, 10] }) X_scenarios = scenarios[["TV", "radio", "newspaper"]] scenarios["예상 판매량"] = model.predict(X_scenarios).round(2)
| 시나리오 | TV | radio | newspaper | 예상 판매량 |
|---|---|---|---|---|
| A (현재) | 100 | 20 | 30 | 11,313개 |
| B (라디오 집중) | 50 | 80 | 10 | 20,379개 ★ |
| C (TV 집중) | 200 | 10 | 10 | 13,845개 |
핵심 인사이트: 예산 총액이 같아도 어디에 쓰느냐에 따라 결과가 크게 달라집니다. 라디오에 집중 투자한 B안이 현재(A안) 대비 약 80% 판매량이 더 많습니다. 이것이 바로 회귀분석을 마케팅에 써야 하는 이유입니다.
Part 2. 군집 분석(Clustering) 개요
회귀분석은 "숫자를 예측"했다면, 군집 분석은 "비슷한 것끼리 자동으로 묶는" 작업입니다. 정답(레이블) 없이 데이터 스스로 패턴을 찾는 비지도 학습(Unsupervised Learning)입니다.
마케터에게 왜 필요한가?
리드 수백 명을 직관으로 분류하는 건 한계가 있습니다. "이 사람은 진지한 리드 같아 보여"라는 주관적 판단 대신, 알고리즘이 데이터를 보고 비슷한 사람끼리 자동으로 묶어줍니다. 리드 세그멘테이션, 콘텐츠 개인화, 채널 최적화 모두 여기서 출발합니다.
리드 수백 명을 직관으로 분류하는 건 한계가 있습니다. "이 사람은 진지한 리드 같아 보여"라는 주관적 판단 대신, 알고리즘이 데이터를 보고 비슷한 사람끼리 자동으로 묶어줍니다. 리드 세그멘테이션, 콘텐츠 개인화, 채널 최적화 모두 여기서 출발합니다.
K-Means 알고리즘이란?
오늘 사용할 알고리즘입니다. K개의 그룹으로 나눠달라고 미리 알려주면, 중심점을 반복적으로 옮겨가며 최적의 군집을 찾습니다.
K-Means 알고리즘 작동 흐름: 4단계를 수렴할 때까지 반복
군집 분석 시작 전 반드시 알아야 할 7가지
코드를 짜기 전에 이 개념들을 이해하지 않으면, 코드를 실행해도 왜 이렇게 하는지 모른 채 넘어가게 됩니다.
1
K-Means는 수치형 데이터만 다룰 수 있다
K-Means는 내부적으로 두 점 사이의 거리를 계산합니다. "마케터"와 "개발자" 사이의 거리는 잴 수 없으니, 텍스트를 숫자로 바꾸는 인코딩(Encoding)이 먼저 필요합니다.
2
스케일링(정규화)은 공정한 비교를 위해 필요하다
체류시간은 0~300초, CTA클릭은 0~1 범위입니다. 그대로 넣으면 체류시간이 압도적으로 큰 영향을 미칩니다. StandardScaler로 모든 컬럼의 범위를 평균 0, 표준편차 1로 통일합니다.
3
K(클러스터 수)는 알고리즘이 알아서 정하지 않는다
"몇 개 그룹으로 나눌지"는 사람이 직접 정해야 합니다. 이를 돕기 위해 엘보우 방법(Elbow Method)을 씁니다. K를 늘리면서 오차(Inertia) 감소폭이 급격히 줄어드는 "팔꿈치" 지점을 최적 K로 선택합니다.
4
군집 결과의 해석은 사람이 한다
알고리즘은 "클러스터 0", "클러스터 1" 같은 번호만 붙입니다. "이 그룹은 핫 리드", "이 그룹은 콜드 리드"라고 이름 짓고 마케팅 전략을 연결하는 것은 마케터의 몫입니다.
5
결측값(빈칸)은 반드시 처리해야 한다
K-Means는 빈칸이 있으면 계산을 거부합니다. 오늘 실습에서는 가장 단순한 방법인 평균값으로 채우기를 사용합니다.
6
군집 분석은 비지도 학습이다
정답 없이 데이터가 스스로 패턴을 찾습니다. 따라서 군집 결과를 "맞다/틀리다"로 평가할 수 없고, "클러스터 내부는 얼마나 비슷한가"와 "클러스터 간 차이는 얼마나 뚜렷한가"로 판단합니다.
7
어떤 컬럼을 넣느냐가 결과를 결정한다
행동 데이터(체류시간, 스크롤, CTA클릭)만 넣으면 행동 패턴 기준으로, 폼 데이터(직무, 회사규모)만 넣으면 속성 기준으로 나뉩니다. 오늘 실습에서는 둘 다 포함합니다.
Part 3. 군집분석 실습 — 리드 30명을 자동 분류하기
가상의 리드 30명 데이터를 이용합니다. 컬럼은 크게 두 종류입니다.
- 폼 데이터: 직무, 회사규모, 관심주제, 도입시기, 현재상황, 연락수단
- 행동 데이터: 유입채널, 체류시간_초, 스크롤깊이_%, CTA클릭, 폼제출, 재방문, 후속메시지반응
실습 01 라이브러리 & 데이터 불러오기
Day16.ipynbPython
import pandas as pd import matplotlib.pyplot as plt from sklearn.preprocessing import LabelEncoder, StandardScaler from sklearn.cluster import KMeans from sklearn.decomposition import PCA # Colab 한글 폰트 설정 !apt-get install -y fonts-nanum -qq plt.rcParams['font.family'] = 'NanumGothic' # 리드 데이터 30명 딕셔너리로 직접 입력 후 DataFrame 변환 df = pd.DataFrame(data) print(df.shape) # (30, 13)
새로 등장한 라이브러리
- LabelEncoder: 텍스트 카테고리를 숫자로 변환. 알파벳/가나다 순으로 0, 1, 2... 배정.
- StandardScaler: 모든 컬럼의 범위를 평균 0, 표준편차 1로 통일.
- KMeans: K-Means 군집 분석 모델.
- PCA: 12개 차원 → 2개로 압축해서 2차원 그래프로 시각화할 때 사용.
실습 02 데이터 탐색 (EDA)
분석 전에 데이터의 전체적인 모양을 파악하는 단계입니다. 이 단계를 생략하면 이상한 데이터가 들어간 줄도 모르고 분석을 진행하게 됩니다.
Day16.ipynbPython
# 2-1. 데이터 타입 & 결측값 확인 df.info() # → Non-Null Count가 모두 30 = 빈칸 없음 # → object 7개 (텍스트, 나중에 인코딩 필요), int64 6개 # 2-2. 수치형 컬럼 기초 통계 num_cols = ['체류시간_초', '스크롤깊이_%', 'CTA클릭', '폼제출', '재방문', '후속메시지반응'] df[num_cols].describe() # 2-3. 카테고리형 분포 확인 for col in ['직무', '현재상황', '유입채널', '도입시기']: print(df[col].value_counts()) # 2-4. 주요 컬럼 시각화 (2행 3열 = 6개 그래프) fig, axes = plt.subplots(2, 3, figsize=(15, 8))
EDA에서 발견한 것들: 마케터가 12명으로 가장 많고, 검색(SEO) 유입이 절반(12명)입니다. 도입 시기를 보면 1개월 이내 + 1~3개월이 전체 63%로, 빠른 도입을 고려하는 리드가 많습니다. 체류시간은 30초~280초로 편차가 크고, 이처럼 값의 분포가 넓을수록 군집 분석이 의미 있는 그룹을 발견할 가능성이 높습니다.
실습 03 전처리 = 인코딩 + 스케일링 ★
인코딩 → 스케일링 → K-Means 순서로 전처리합니다
Day16.ipynbPython
# 3-1. 인코딩 (텍스트 → 숫자) df_processed = df.copy() # 원본 보존! cat_cols = ['직무', '회사규모', '관심주제', '도입시기', '현재상황', '연락수단', '유입채널'] label_encoders = {} # 나중에 복원할 때 쓸 딕셔너리 for col in cat_cols: le = LabelEncoder() df_processed[col] = le.fit_transform(df_processed[col]) label_encoders[col] = le # 변환 확인 (가나다 순: 개발자=0, 기획자=1, 대표=2, 마케터=3, 영업=4) label_encoders["직무"].inverse_transform([0,1,2,3,4])
주의: 인코딩한 숫자 간 대소 관계는 아무 의미가 없습니다. 마케터(3)가 개발자(0)보다 크다는 뜻이 아닙니다. 단지 구분을 위한 번호입니다. 더 정밀한 머신러닝을 위해서는 One-Hot Encoding을 써야 하지만, 오늘 실습에서는 Label Encoding을 사용합니다.
Day16.ipynbPython
# 3-2. 스케일링 (범위 통일) feature_cols = ['직무', '회사규모', '관심주제', '도입시기', '현재상황', '유입채널', '체류시간_초', '스크롤깊이_%', 'CTA클릭', '폼제출', '재방문', '후속메시지반응'] X = df_processed[feature_cols] scaler = StandardScaler() X_scaled = scaler.fit_transform(X)
스케일링 결과 확인
- 체류시간_초 스케일링 전: 30초 ~ 280초 (범위 250)
- 체류시간_초 스케일링 후: -1.34 ~ 1.68 (범위 약 3)
- CTA클릭 스케일링 전: 0 ~ 1 (범위 1)
- CTA클릭 스케일링 후: -1.14 ~ 0.88 (범위 약 2)
- → 두 컬럼 모두 비슷한 범위로 통일되어 알고리즘이 동등하게 취급합니다.
실습 04 최적 K 찾기 — 엘보우 방법
Day16.ipynbPython
# K를 1~10으로 늘리면서 각 K의 Inertia(오차 합) 계산 inertia_list = [] for k in range(1, 11): kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(X_scaled) inertia_list.append(kmeans.inertia_) print(f" K={k} | Inertia: {kmeans.inertia_:.1f}") # 엘보우 그래프 그리기 plt.plot(range(1, 11), inertia_list, 'bo-', linewidth=2) plt.axvline(x=3, color='red', linestyle='--', label='권장 K=3') plt.show()
Inertia란?
- 각 데이터가 자신이 속한 클러스터 중심점과 얼마나 멀리 떨어져 있는지의 합계입니다.
- K가 클수록 Inertia는 항상 줄어듭니다. (K=30이면 각자가 클러스터가 되어 0이 됨)
- 감소폭이 급격히 줄어드는 "팔꿈치" 지점이 최적 K입니다. 이번 실습에서는 K=3이 해당합니다.
실습 05 K-Means 모델 학습
Day16.ipynbPython
K = 3 kmeans = KMeans(n_clusters=K, random_state=42, n_init=10) kmeans.fit(X_scaled) # 각 리드에 클러스터 번호 배정 df['클러스터'] = kmeans.labels_ print(df['클러스터'].value_counts()) # 클러스터 0: 12명 / 클러스터 1: 11명 / 클러스터 2: 7명
예상 결과 (처음 10명):
마케터 (245초, CTA클릭 O, 폼제출 O) → 클러스터 0 (핫 리드)
대표/창업자 (60초, CTA클릭 X, 폼제출 X) → 클러스터 1 (콜드 리드)
기획자 (130초, CTA클릭 O, 폼제출 X) → 클러스터 2 (워밍 리드)
마케터 (245초, CTA클릭 O, 폼제출 O) → 클러스터 0 (핫 리드)
대표/창업자 (60초, CTA클릭 X, 폼제출 X) → 클러스터 1 (콜드 리드)
기획자 (130초, CTA클릭 O, 폼제출 X) → 클러스터 2 (워밍 리드)
실습 06 결과 시각화 (PCA 산점도 + 막대그래프)
Day16.ipynbPython
# 6-1. PCA로 12차원 → 2차원 압축 후 산점도 pca = PCA(n_components=2, random_state=42) X_2d = pca.fit_transform(X_scaled) colors = ['#E05C5C', '#4A90D9', '#52A97A'] for i in range(K): mask = df['클러스터'] == i plt.scatter(X_2d[mask, 0], X_2d[mask, 1], c=colors[i], label=f'클러스터 {i}', alpha=0.8, s=100) plt.legend() plt.show() # 6-2. 클러스터별 주요 지표 막대그래프 비교 fig, axes = plt.subplots(1, 3, figsize=(14, 5)) df.groupby('클러스터')['체류시간_초'].mean().plot(kind='bar', ax=axes[0]) df.groupby('클러스터')['스크롤깊이_%'].mean().plot(kind='bar', ax=axes[1]) df.groupby('클러스터')['CTA클릭'].mean().plot(kind='bar', ax=axes[2]) plt.show()
PCA(주성분 분석)란?
- 12개 컬럼 = 12차원 공간. 사람이 볼 수 있는 건 2차원까지입니다.
- PCA는 정보 손실을 최소화하면서 12차원을 2차원으로 압축합니다.
- 시각화 결과에서 색깔별 점들이 뚜렷하게 분리될수록 군집 분석이 잘 된 것입니다.
- 경계 근처의 점은 두 클러스터의 특성을 동시에 갖는 애매한 리드입니다.
군집 분석 결과 — 클러스터별 행동 지표 및 프로필 요약
시각화 해석 방법:
클러스터 0이 체류시간 · 스크롤 · CTA 클릭 모두 높으면 → 고관여도 "핫 리드" 그룹
클러스터 1이 모든 지표에서 낮으면 → 저관여도 "콜드 리드" 그룹
클러스터 2가 중간 어딘가에 위치하면 → "워밍 리드" 그룹
이 차이가 클수록 군집 분석이 의미 있는 결과를 낸 것입니다.
클러스터 0이 체류시간 · 스크롤 · CTA 클릭 모두 높으면 → 고관여도 "핫 리드" 그룹
클러스터 1이 모든 지표에서 낮으면 → 저관여도 "콜드 리드" 그룹
클러스터 2가 중간 어딘가에 위치하면 → "워밍 리드" 그룹
이 차이가 클수록 군집 분석이 의미 있는 결과를 낸 것입니다.
오늘의 핵심 정리
| 주제 | 핵심 내용 |
|---|---|
| 다중회귀분석 | 광고비(X) → 판매량(y) 예측. 라디오 계수(0.189)가 TV(0.045)의 4배 이상. 예산 배분만으로 판매량 80% 차이. |
| 군집 분석 개념 | 정답 없이 비슷한 것끼리 묶는 비지도 학습. K는 사람이 직접 결정해야 함. |
| 전처리 순서 | 결측값 처리 → 인코딩(텍스트→숫자) → 스케일링(범위 통일) → K-Means |
| 엘보우 방법 | K를 늘리며 Inertia를 그래프로 그려서 감소폭이 급격히 줄어드는 지점을 최적 K로 선택 |
| PCA 시각화 | 12차원 → 2차원 압축. 색깔별 점 분리가 뚜렷할수록 군집이 잘 된 것 |
다음 시간 예고: 클러스터별 상세 프로파일링(핫/워밍/콜드 리드 정의 + 마케팅 전략) 및 이커머스 고객 데이터 군집 분석 실습
'부트캠프' 카테고리의 다른 글
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 35일차_260428 (0) | 2026.04.28 |
|---|---|
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 34일차_260427 (0) | 2026.04.27 |
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 31일차_260422 (1) | 2026.04.22 |
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 30일차_260421 (0) | 2026.04.21 |
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 29일차_260420 (0) | 2026.04.20 |