Data Analysis

선형회귀 실습 (당뇨병 진행률 BMI, 광고비 기반 매출 예측)

김심슨 2025. 6. 17. 10:43

< BMI 값을 통한 당뇨병 진행률 예측 > 

사용 데이터 : sklearn.datasets.load_diabetes() (10개 특성을 가진 당뇨병 데이터셋) 

사용 알고리즘 : LinearRegression() - 단순 선형 회귀 

평가 방법 : 시각화를 통한 예측선과 실제값 비교 

시각화 : matplotlib를 통한 산점도 + 회귀선 

 

< 주요 라이브러리 분석 > 

import pandas as pd #데이터 프레임 형태로 조작할 수 있는 도구 (이번 예제에서 직접 사용은 안함)
import numpy as np #수치 계산용 (차원 변형 np.newaxis에 사용)
import matplotlib.pylot as plt #시각화 (그래프 출력)
from sklearn.linear_model import LinearRegression #선형회귀모델 
from sklearn.datasets import load_diabetes #당뇨병 예제 데이터셋 
from sklearn.model_selection import train_test_split #훈련용/테스트용 데이터 분할

scikit-learn의 예제 데이터셋은 기본적으로 numpy 배열 형태로 제공되기 때문에 head()가 안됨. 

data : 독립변수 (x)

target : 종속변수 (y)

feature_names : 변수이름 

DESCR : 데이터셋 설명 텍스트 

< 코드 뜯어먹기 > 

diabetes = load_diabetes()
X = diabetes.data #10개의 입력 특성(독립변수)
y = diabetes.target #예측하고자 하는 값 (종속변수, 당뇨병 진행률)

-> 데이터 로드 및 분리

 

X = X[:, np.newaxis, 2] #BMI특성만 선택 (2번 인덱스)

-> 배열 x에서 특정한 열 하나만 골라내서, 2차원 배열로 바꾸는 과정

예를 들어 원래 데이터가 아래처럼 생겼다고 할 때 

X = [
  [1, 2, 3],
  [4, 5, 6],
  [7, 8, 9]
]

이걸 numpy 배열이라고 생각하면 아래와 같이 표현됨 

array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

: -> 모든 행을 선택한다는의미 

np.newaxis -> 차원을 하나 더 만들어줌 (배열 모양을 조정)

2 : 2번 열을 선택 (0번부터 시작하므로 세 번째 열) 

 

실행 결과 : 

[[3],
 [6],
 [9]]

원래 (3,) 형태의 1차원 배열이었던 게 [[3],[6],[9]] 처럼 2차원 배열로 바뀌게 된다 

X[:, 2] → 모든 행의 2번 열(BMI)만 가져옴 → (442,) 형태

np.newaxis → 차원을 강제로 추가 → (442, 1) 형태로 바꿈

 

Q. 왜 2차원 배열로 바꿔야 하나? 

Scikit-learn 같은 머신러닝 모델에서는 데이터가 반드시 2차원 형태로 들어가야 하는 경우가 많음!

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
-> 전체 데이터의 20%는 테스트용, 80%는 훈련용으로 분리
model = LinearRegression()
model.fit(X_train, y_train)

-> 선형회귀 모델 객체 생성, 실제로 데이터를 이용해 기울기(a), 절편(b) 학습
print(f'기울기(slope) : {model.coef_[0]:.2f}') # 소수점 2째자리까지
print(f'절편(intercept) : {model.intercept_:.2f}') #소수점 2째자리까지

-------------------

기울기(slope) : 998.58
절편(intercept) : 152.00

coef_ : 회귀 직선의 기울기 (BMI가 1 증가할 때, 당뇨 진행률이 얼마나 증가하는가)

intercept_ : 절편 (BMI가 0일 때 예측값)

y_pred = model.predict(X_test) #예측

# 예측 결과 시각화
plt.rc('font', family='Malgun Gothic')
plt.scatter(X_test, y_test, color='blue', label='실제 데이터')
plt.plot(X_test, y_pred, color='red', label='예측(회귀) 선')
plt.xlabel('BMI(체질량 지수)')
plt.ylabel('당뇨병 진행률')
plt.title('선형 회귀 - 당뇨병 예측 (BMI)')
plt.legend()
plt.grid(True)
plt.show()

* 선의 기울기 : 상승추세 

빨간색 선은 우상향 추세. 즉 BMI(체질량 지수)가 증가할수록 당뇨병 진행률이 높아지는 경향이 있다. 

 

* 선 주변 데이터의 분포 : 직선에서 떨어져 흩어져 있음 

선형 회귀 모델이 데이터를 완벽하게 설명하지 못하지만 전반적인 경향성(추세)을 어느 정도 잘 나타내고 있음을 의미

 

* 선과 점 사이의 거리 (오차)

각 점에서 빨간 직선까지의 거리가 바로 오차(예측값과 실제값의 차이)를 나타낸다. 

데이터가 많이 흩어져 있어, 이 선형회귀 모델이 높은 정확도로 개별 데이터를 예측하기 어렵다는 점도 알 수 있음. 

하지만 평균적인 경향 (일반적인 BMI 증가 -> 당뇨병 진행률 증가)은 잘 반영하고 있다. 

 

< 인사이트 >

단순 선형 회귀는 변수가 하나일 대 사용 

실제 실무에서는 대부분 다중 선형 회귀 (변수 여러 개) 사용 

model.score(x_test, y_test)를 통해 결정계수(R2)를 구하면 예측력 평가 가능 

r2 = model.score(x_test, y_test)
print(r2)

 


< 선형회귀를 이용한 광고비 기반 매출 예측 > 

설명 : 회사는 다양한 광고 채널 (TV, 라디오, 신문)에 광고비를 집행하고 있다. 이때 광고비 지출이 매출(판매량)에 어떤 영향을 주는지 분석하고, 광고비만 보고 매출을 예측할 수 있는지 모델을 만들어보자. 

모델 평가지표는 MSE, R2로 평가하고 시각화해보자 

데이터 셋 : https://www.kaggle.com/datasets/ashydv/advertising-dataset

 

Advertising Dataset

 

www.kaggle.com

주요 컬럼 : 

- TV : TV 광고비 

- Radio : 라디오 광고비 

- Newspaper : 신문 광고비 

- Sales : 실제 판매량 

 

< 필수 라이브러리 >

import pandas as pd #csv 읽기 및 데이터프레임 처리
import numpy as np #수학 계산 도구 
import matplotlib.pyplot as plt # 시각화 도구 
from sklearn.linear_model import LinearRegression #선형 회귀 모델 생성 
from sklearn.model_selection import train_test_split # 훈련/테스트 데이터 분할
from sklearn.metrics import mean_squared_error, r2_Score #평가 지표 (오차, 설명력 계산)

- r2_score()는 회귀문제에서 거의 항상 사용됨 : 예측한 값이 실제 값을 얼마나 잘 설명하는지를 나타내는 지표 (0~1)

 

# 2. 데이터 로딩
df = pd.read_csv('assets/advertising.csv')
print(df.head())
df.info()

----

     TV  Radio  Newspaper  Sales
0  230.1   37.8       69.2   22.1
1   44.5   39.3       45.1   10.4
2   17.2   45.9       69.3   12.0
3  151.5   41.3       58.5   16.5
4  180.8   10.8       58.4   17.9
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 200 entries, 0 to 199
Data columns (total 4 columns):
 #   Column     Non-Null Count  Dtype  
---  ------     --------------  -----  
 0   TV         200 non-null    float64
 1   Radio      200 non-null    float64
 2   Newspaper  200 non-null    float64
 3   Sales      200 non-null    float64
dtypes: float64(4)
memory usage: 6.4 KB

 

# 3. 독립변수 / 종속변수 분리
X = df[['TV', 'Radio', 'Newspaper']] # 광고비
y = df['Sales'] # 판매량(매출)

# 4. 훈련/테스트 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(
    X,
    y,
    test_size=0.2,
    random_state=42
)

 

# 5. 모델 생성 및 학습 
model = LinearRegression()
model.fit(X_train, y_train)

-> 선형 모델 객체 생성 후 .fit()으로 학습

 

# 6. 모델 평가 지표 출력 
print ('회귀계수(각 광고비의 영향) : ', model.coef_) # 회귀계수 : [0.05450927 0.10094536 0.00433665]
print('절편(기본 판매량) :', model.intercept_) # 절편(기본 판매량) :  4.714126402214127

---
TV광고비를 1 늘리면 판매량이 평균 0.05 증가 
라디오 광고비를 1 늘리면 판매량이 평균 0.1 증가 
신문 광고비를 1 늘리면 판매량이 평균 0.004 증가 

광고비를 지출하지 않아도 측정되는 기본 판매량 : 4.714...

 

y_pred = model.predict(X_test)
-> 테스트 데이터(X_test)에 대한 예측값 구함 

# 평가 지표 출력 
mse = mean_squared_error(y_test, y_pred) #평균제곱오차 (낮을수록 좋음, 예측 오차의 크기)
print(f'평균제곱오차(MSE) : {mse:.2f}')

r2 = r2_score(y_test, y_pred) #결정 계수 (1에 가까울 수록 좋음, 설명력)
print(f'결정계수(R^2) : {r2:.2f}')

평균제곱오차(MSE) : 2.91 => 오차가 평균 2.91의 제곱만큼 발생 
결정계수(R^2) : 0.91 => 전체 판매량 변동 중에서 91%는 광고비로 설명 가능함 

=> 빨간 점선 근처에 파란색 점이 대부분 모여있음 -> 이는 모델이 실제값에 매우 근접한 예측을 하고 있다는 걸 의미 

 

< 실무 활용 팁 > 

- 회귀 계수 해석 : 변수별로 비즈니스 인사이트 도출 가능 

- 신문 광고 효과 없음 : 제외하고 모델 개선 가능 

- 고차항 변환 : 비선형 관계가 있을 경우 x^2, log(x) 등으로 변형 

- 모델 저장 : joblib이나 pickle로 저장해 웹서버/배치에 적용 가능