▼오늘 배운 사항들
1. 다중회귀 분석 실습
0) 라이브러리 불러오기
1) 데이터 불러오기
2) 데이터 확인
3) 독립변수/종속변수 분리 - 입력
4) 학습용/테스트용 데이터 분리
5) 모델 학습
6) 예측
7) 성능 평가
- 회귀계수 해석
- 평가 지표 해석
8) 새 광고 예산으로 미래 예측
2. 군집 분석
3. 군집 분석 실습
Part 1. 다중회귀 분석 실습
머신러닝은 과거 데이터로 패턴을 배우고, 새 데이터에 적용하는 것
pov.마케터 : 과거 광고 집행 결과를 학습시켜서, 앞으로 어떤 광고에 얼마를 써야할지 예측하는 것
어제 선형회귀분석과 차이점
- X 변수 더 추가해보기
- train_test_spilt : 원래있던 데이터에서 20% 남겨놓고 학습에 쓰지 않고 평가할 때만 20%로 써보기
- 이전 강의에서는 전부 학습시켜서 원본과 비교가 어려웠으니
# 0. 라이브러리 열기
import pandas as pd
from sklearn.metrics import r2_score, mean_absolute_error, mean_squared_error
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
# 1. 데이터 불러오기
url = "https://www.statlearning.com/s/Advertising.csv"
df = pd.read_csv(url)
# 1-1. 인덱스성 불필요 컬럼 제거
df = df.drop(columns=['Unnamed: 0'])
# 2. 데이터 확인
df.describe() # TV의 표준편차가 큰 것으로 보아 대규모 캠페인을 진행한 것 같음
df.shape #(200,6)
# 3. 독립변수/종속변수 분리 - 데이터 입력
X = df[['TV', 'radio', 'newspaper']]
y = df['sales']
# 4. 학습용 데이터와 테스트용 데이터 분리
# 이유 : 시험문제 다 알려주고 시험보면 학생이 공부 잘했는지 안했는지 모름. 80% 는 공부하는 데 쓰고 나머지 20%는 성과 났는지 확인할 것
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2 , random_state=42) # test_size = 테스트 용 20% / random_state = 랜덤하게 돌리지 말기, 42는 기준
# X_train.shape (160,3)
# X_test.shape (40,3)
# 5. 모델 학습
model = LinearRegression()
model.fit(X_train, y_train)
# 6. 모델 예측
y_pred = model.predict(X_test)
y_pred
# 7. 성능 평가
# y_pred, y_test 값으로 비교해보면서 성능평가함
mae = mean_absolute_error(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = mse ** 0.5 #제곱근 즉, mse의 제곱근을 의미
r2 = r2_score(y_test, y_pred)
# 7-2. 출력값 명령어 잘 확인하기 ,round
print("절편: ", round(model.intercept_, 3))
print("TV 회귀계수: ", round(model.coef_[0], 3))
print("radio 회귀계수: ", round(model.coef_[1], 3))
print("newspaper 회귀계수: ", round(model.coef_[2], 3))
print("======================")
print("MAE (평균 절대 오차):", round(mae, 3))
print("RMSE (평균 제곱근 오차):", round(rmse, 3))
print("결정계수:", round(r2, 3))
# 8. 시나리오 생성 후 미래 예측
# tv 100, radio 20, news 10으로 예산 분배하면 prediciton이 다음처럼 나오는구나
시나리오 = pd.DataFrame({
"TV": [100, 150, 200],
"radio": [20, 30, 40],
"newspaper": [10, 20, 10] })
시나리오
# prediction이 y값
prediction = model.predict(시나리오)
prediction
시나리오['예상_판매량'] = prediction.round(2)
시나리오
-------------------------------------------
# 실습 9. 시나리오 미래 예측 응용
# 9-1. X 값 데이터 넣기
scenarios = pd.DataFrame({
"시나리오": ["A (현재)", "B (라디오 집중)", "C (TV 집중)"],
"TV": [100, 50, 200],
"radio": [20, 80, 10],
"newspaper": [30, 10, 10] })
# 9-2 변수 입력
# X 변수 입력값 : new_campaign
# X값만 넣은 이유는, y값을 구하기 위해서 predict가 y값이 된 것
new_campaign = scenarios[['TV', 'radio', 'newspaper']]
# y 값 예측 : 판매량 예측
predicted_sales = model.predict(new_campaign)
# 시나리오별 예상 판매량 값 확인하기
# 왜 시나리오 B가 예상 판매량이 제일 많이 나왔을까? radio의 회귀계수가 제일 높기 때문
scenarios['예상 판매량'] = predicted_sales.round(2)
scenarios


Part 2. 군집분석
- 선형회귀분석은 지도학습. 답이 있는 머신러닝
- 군집분석은 비지도학습으로 정답이 없는 데이터 끼리의 유사성만으로 그룹을 나누는 머신러닝 기법
[마케터에게 군집분석이 필요한 이유]
- 보통 리드/고객을 직관으로 분류하는데 데이터가 수백 수천개가 되면 직관으로는 한계에 부딪힘
- 데이터의 유사성만으로 스스로 비슷한 사람끼리 뭉치게 만드는 패턴을 찾아서 활용하는 것
= 수학적 유사성 기반 분류
[K-Means 알고리즘]
- 오늘 사용할 알고리즘
- 랜덤으로 뿌려서 데이터들과의 거리를 측정해서 가장 적은 수가 나올 때까지 모델 돌림
- 가장 적은 수가 되는 곳이 분류로 마무리 됨
- K 중심점을 몇 개로 정할 것인지가 키포인트
< K-Means 작동 흐름>
1단계: K개의 중심점을 데이터 안에 랜덤하게 배치한다
2단계: 각 데이터를 가장 가까운 중심점에 배정한다
3단계: 배정된 데이터들의 평균 위치로 중심점을 이동한다
4단계: 중심점이 더 이상 움직이지 않을 때까지 2-3단계를 반복한다
[K-Means key point]
- K-Means 는 수치형 데이터만 다룰 수 있으므로 인코딩 해줘야함
- 리드수집 시, '영업' / '마케터' 등 문자로 수집 시 오류나므로 숫자로 바꾸는 인코딩 필수
- Lable Encoding : 인코딩 전 직무(마케터) → 인코딩 후 : 0 /직무(기획자) → 1 등 - 스케일링(정규화)해서 공정한 비교 세팅해야함
- 컬러마다 숫자의 범위가 천차만별이기 때문에 비슷한 범위 안에서 통일해야함
- 통일하지 않을시, 숫자 범위가 큰 컬럼이 더 많은 영향력을 가져가기 때문
- 예를 들어 아래처럼 범위가 다양할 때
체류시간_초 : 10 ~ 300 (범위: 290)
스크롤깊이_% : 10 ~ 100 (범위: 90)
CTA클릭 : 0 ~ 1 (범위: 1)
직무(인코딩) : 0 ~ 4 (범위: 4)
- StandardScaler 사용 시
스케일링 전 스케일링 후
체류시간: 30 → -1.2
체류시간: 180 → 0.8
체류시간: 90 → -0.3 - K (클러스터 수) 몇 개의 그룹으로 나눌지는 직접 정해야함
- K를 너무 작게 하면 서로 다른 그룹이 하나로 뭉치고, K를 너무 크게 하면 아무 의미 없는 아주 작은 그룹이 많이 생깁
- 엘보우 방법 : k가 1,2,,,,10일 때 오차를 계산해서 오차 지점이 줄어드는 지점을 K로 가져갈 것
- 데이터파악 + 비즈니스 판단 2가지 함께 - 군집 결과 해석은 사람이 한다
- 알고리즘은 그룹만 지어주지 이름/특징을 알려주지 않음
- 숫자와 데이터를 보면서 특징을 파악하고 의미있는 이름을 붙이는 것은 분석가의 역할 - 결측치 있을 시 K-Means는 계산 불가
- 누락된 값은 평균값 채우기 사용할 것임 - 군집 분석은 비지도학습
- 군집분석은 비지도학습으로 정답이 없는 데이터 끼리의 유사성만으로 그룹을 나누는 머신러닝 기법 - 컬럼에 따라 클러스터 결과가 결정됨
- 행동데이터 -> 행동 패턴 / 폼데이터 -> 속성
Part 3. 군집분석 - 실습
1. 데이터 불러오기

2. 데이터 탐색
- info() : 결측치 없음 ▶ K-Means 돌릴 수 있겠구나, 만약 결측치 있으면 평균값으로 채워주기
- Dtype : object 전부 라벨링 해줘야겠구나 ▶ K-Means는 숫자형 데이터만 읽을 수 있으니까
2-1) 수치형 데이터 탐색 ▶ 스케일링 해야할 것 고르기 위함
: num_cols = [''] / df.describe()
2-2) 문자형 컬럼 분포 확인하기 ▶ 클러스터 사용하기 전에 카테고리마다 어떻게 분포되어있는지 눈으로 확인
: 비교적 빨리 도입하고 싶어하는 사람이 많고, 검색으로 들어온 사람이 많으며, 대표와 마케터가 대부분이구나 등을 알 수 있음
# 2-1. 수치형 데이터만 탐색 → 스케일링 해야할 것 고르기 위해서
num_cols = ['체류시간_초' , '스크롤깊이_%' , 'CTA클릭' , '폼제출' , '재방문', '후속메시지반응' ]
df[num_cols].describe().round(2)
# 2-2. 문자형 컬럼 분포 확인하기 → 클러스터 사용하기 전에 카테고리마다 어떻게 분포되어있는지 눈으로 확인해보기
for col in ['직무', '현재상황', '유입채널', '도입시기' ]:
print(f" \n<{col}>=====================") #\n : 줄바꿈 컬럼
print(df[col].value_counts())



2-3) 주요 컬럼 분포 시각화 ▶ 가시적으로 데이터 분포 현황 파악하기 위해서
- key1. bar그래프와 hist 그래프 차이점 : 문자형범주는 bar / 숫자범주는 hist
- 문자형은 bar 숫자형은 hist로 설정 → bar는 [].bar로 안되나? 아...axes[0,0] 이러면서 하나하나 하기 힘드니까? ↓
- 문자형은 value_count()로 숫자를 넣어줘야 이해함 (바로 전단계에서 value_counts()로 문자별 데이터 숫자로 계산해뒀음)
# 2-3. 주요 컬럼 분포 시각화 - 데이터 분포 현황 가시적으로
# key1. bar그래프와 hist 그래프 차이점 : 문자형범주는 bar / 숫자범주는 hist
fig, axes = plt.subplots(2,3 , figsize=(15,8))
# 문자형은 bar 숫자형은 hist로 설정 → bar는 [].bar로 안되나? 아...axes[0,0] 이러면서 하나하나 하기 힘드니까?
# 문자형은 value_count()로 숫자를 넣어줘야 이해한다 (윗단계에서 value_counts()로 문자별 데이터 숫자로 계산해뒀음)
df['직무'].value_counts().plot(kind='bar', ax=axes[0,0], color = 'orange')
df['현재상황'].value_counts().plot(kind='bar', ax=axes[0,1], color = 'orange')
df['유입채널'].value_counts().plot(kind='bar', ax=axes[0,2], color = 'orange')
# hist 그래프는 matlib에서 .hist로 바로 이해할 수 있음
df['체류시간_초'].hist(ax=axes[1,0], bins=10)
df['스크롤깊이_%'].hist(ax=axes[1,1], bins=10)
df['도입시기'].value_counts().plot(kind='bar', ax=axes[1,2])
axes[0,0].set_title('직무')
axes[0,1].set_title('현재상황')
axes[0,2].set_title('유입채널')
axes[1,0].set_title('체류시간_초')
axes[1,1].set_title('스크롤깊이_%')
axes[1,2].set_title('도입시기')
plt.tight_layout()
plt.show()

3. 데이터 전처리 - 인코딩
- LabelEncoding 사용
- #3-2의 fit 패턴분석 의미 : 마케터면 0, 기획자면 2 이런식으로 같은 값에 같은 숫자로 묶기
- .inverse_transform으로 딕셔너리 값 확인 가능(0은 개발자, 1은 기획자 등등)
- 텍스트 컬럼 필터링 → 인코딩용 df 카피 (원본 데이터 보존) → 컬럼별 인코딩 규칙 딕셔너리 생성 → for 반복문으로 인코딩하기 → 인코딩 딕셔너리에 넣기
# 3-0. 텍스트 컬럼 뽑기 (#df.head(1)로 보면서 문자열은 다 뽑기)
# 3-2 에서 df_processed[col]쓸거라서 list 만든것
cat_cols = ['직무', '회사규모', '관심주제', '도입시기', '현재상황', '연락수단', '유입채널']
# 3-0. 라벨 인코더 사용 - 원본 망가지지 않게 copy
df_processed = df.copy() # 원본 보존
# 3-1. 컬럼별 인코딩 규칙 정할 딕셔너리 만들기
label_encoders = {} # 딕셔너리 해놔야 나중에 인코딩 규칙 까먹어도 이거보고 떠올릴 수 있음
# 3-2. 반복문으로 컬럼별 인코딩 반복하기 : 반복문 안에 어떤 작업을 하냐면
for col in cat_cols:
# 3-3. LabelEncoder() 객체 만들기
le = LabelEncoder()
# 3-4. 이 컬럼의 문자열 값을 fit(패턴 분석(마케터면 0, 기획자면 2 이런식으로 같은 값에 같은 숫자로)해서) + transform (변환할 것)
df_processed[col] = le.fit_transform(df_processed[col]) # 인코더 쓸건데 거기에 들어있는 거 알아서 패턴분석해서 변환까지 해줘
# 3-5. 아까 만든 딕셔너리에 규칙 저장해두기
label_encoders[col] = le # 직무와 관련된(fit+transform된) 규칙이 알아서 들어가겠죠



3. 데이터 전처리 - 스케일링
- StandardScaler() 사용
- 인코딩과 마찬가지로 .fit_transform으로 패턴분석하고 변환까지 시키기
# 수치형 데이터 스케일링하기 (전부 수치화 했으니까 전부)
# 3-0. 연락수단 제외한 것만 담기 (실무에서 필요한 컬럼만 넣기)
feature_cols = ['직무','회사규모','관심주제','도입시기','현재상황','유입채널','체류시간_초','스크롤깊이_%','CTA클릭','폼제출','재방문','후속메시지반응']
X = df_processed[feature_cols]
# 3-1. 스케일링 시작
scaler = StandardScaler()
# 3-2. 스케일링 적용
X_scaled = scaler.fit_transform(X) # 전단계에서 만들었던 X 스케일링할 거니까 (X)
# 3-3. 스케일링 확인
print("스케일링 완료.")
print()
print(f" 체류시간_초 스케일링 전 범위: {df['체류시간_초'].min()}초 ~ {df['체류시간_초'].max()}초")
# [:,6] 모든 행에서 6번 열 (0부터 세니까 7번째 컬럼 의미)
print(f" 체류시간_초 스케일링 후 범위: {X_scaled[:, 6].min():.2f} ~ {X_scaled[:, 6].max():.2f}")
print()
print(f" CTA클릭 스케일링 전 범위: {df['CTA클릭'].min()} ~ {df['CTA클릭'].max()}")
print(f" CTA클릭 스케일링 후 범위: {X_scaled[:, 8].min():.2f} ~ {X_scaled[:, 8].max():.2f}")
# 앞으로 사용할 데이터
X_scaled # = 스케일링 된 데이터

4. 최적 K 찾기
- 엘보우 방법을 사용
- 각 오차의 합이 적게 걸리는 지점 (k를 1부터 10까지 돌려보고 젤 적은거 찾는)
- k=1,k=2 사이에 차이가 170정도..k=2,k=3 차이는 60.. 이러다 k=3과 k=4 차이가 제일 완만함 ▶ 최적의 k=3
- 왜 군집 3개로 했어요? 엘보우 방법으로 분석했을 때 (엘보우 시각화 그래프 첨부해서 근거 제시하기)
- 군집분석 실무에서는 보통 유료툴을 사용하거나 제일 흔히 쓰는 건 엘보우 방법을 사용하긴 함
# 4-1. 각 K에서의 오차를 저장할 빈 리스트 생성
# inertia : 군집에 있는 데이터들이 중심과 얼마나 퍼져있는지 나타내는 값 = 즉, 이 값이 작을수록 데이터가 중심점과 가까움
inertia_list = []
# 4-2. for 반복문 K를 1부터 10까지 반복
for k in range(1,11):
# 군집개수가 k개인 군집모델 돌리기
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) # n_cluster=k k를 range(1,11)까지 돌리라는 것 / n_init=10 찾을때까지 돌려야되는데 10번만 도전하라고 지정함
kmeans.fit(X_scaled) # 학습시키기
# 모델 돌릴 때 나온 (부산물 같은) inertia값 저장하기
inertia_list.append(kmeans.inertia_) # 모델 학습하고나서 나오는 속성(선형회귀모델의 coef_와 유사)
# inertia 값의 차이가 적은 (완만한) 부분 체크하기 = 그곳이 최적의 k
print(f" k = {k}, Inertia: {kmeans.inertia_}")
# 엘보우 그래프 그리기 -> inertia 데이터 넣는 곳 + 최적k 값 표시
plt.figure(figsize=(9, 5))
# 이곳에 inertia 데이터 넣기
plt.plot(range(1, 11), inertia_list, 'bo-', linewidth=2, markersize=9)
plt.xlabel('K (클러스터 수)', fontsize=12)
plt.ylabel('Inertia (오차 합계)', fontsize=12)
plt.title('엘보우 방법 - 최적 K 찾기', fontsize=14, fontweight='bold')
plt.xticks(range(1, 11))
plt.grid(True, alpha=0.3)
# 빨간선 = 최적의 k값으로 수정 가능
plt.axvline(x=3, color='red', linestyle='--', linewidth=1.5, label='권장 K=3')
plt.legend(fontsize=11)
plt.tight_layout()
plt.show()

5. K-Means 모델 학습
- 클러스터 컬럼에 들어간 0, 1, 2를 보고 군집분류 가능
- df_processed에 넣어야했던거 아닌가...
- ?? 왜 kmeans 돌려서 나온 부산물이 labels인거지? ▶ 아래 접은글
# 5-0. 최적의 k 설정
k = 3
# 5-1. KMeans 모델 학습
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
kmeans.fit(X_scaled)
# 5-2. 클러스터 라벨링을 새 컬럼으로 추가
df['클러스터'] = kmeans.labels_
df['클러스터'].value_counts()

6. 결과 시각화
- 참고
# 6-1. PCA 2D 산점도
k=3
pca = PCA(n_components=2, random_state=42)
X_2d = pca.fit_transform(X_scaled)
colors = ['#E05C5C', '#4A90D9', '#52A97A'] # 클러스터 0, 1, 2 색상
plt.figure(figsize=(9, 6))
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, edgecolors='white', linewidths=0.5
)
plt.title('리드 군집 분석 결과 (PCA 2D)', fontsize=14, fontweight='bold')
plt.xlabel('주성분 1 (가장 많은 정보를 담은 축)', fontsize=11)
plt.ylabel('주성분 2 (두 번째로 많은 정보를 담은 축)', fontsize=11)
plt.legend(fontsize=11)
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

[모델링에서 부산물이라는 단어의 의미]
Q. 왜 클러스터 번호를 부산물이라고 해? 모델링 결과라고 하면 되는거 아냐?
A. 데이터 분석 현장이나 개발 문법적 관점에서 굳이 '부산물'이라는 표현의 이유는
- 바로 나오는 결과(리턴값)가 아니여서
- 모델링 돌린 후 해당 모델을 살펴보았을 때 잔재되어있는 속성(내용)이라


마케터는 수학을 잘해야하고, 경제학 지식이 풍부할수록 잘하겠네
마케터 뿐만 아니라 개발자도 그런 것 같은데...
수학..물리학.. 어..윽...
