Python으로 이해하는 회귀분석: 단순회귀부터 다중·다항회귀까지

Alchemist·2026년 9월 25일

KT AIVLE

목록 보기
19/130

데이터 분석을 하다 보면 단순히 변수 사이의 관계를 확인하는 것에서 한 걸음 더 나아가, 어떤 변수가 결과값에 얼마나 영향을 주는지 설명하거나 새로운 값을 예측하고 싶은 상황이 생긴다.

예를 들어 웹 서비스라면 다음과 같은 질문을 생각할 수 있다.

  • 동시 접속자 수가 늘어나면 API 응답 시간은 얼마나 증가할까?
  • DB 처리 시간이 길어지면 전체 응답 시간에 어느 정도 영향을 줄까?
  • 사용자 행동 변수를 함께 사용하면 구매 금액을 더 잘 설명할 수 있을까?
  • 단순한 직선이 아니라 특정 구간부터 급격히 증가하는 패턴은 어떻게 모델링할까?

이런 문제를 다루는 대표적인 통계 방법이 회귀분석(Regression Analysis)이다.

독립변수 X
    ↓
회귀모델
    ↓
종속변수 Y를 설명하거나 예측

이번 글에서는 Python의 statsmodels를 사용해 단순선형회귀, 최소제곱법, 결정계수, 회귀계수 검정, 비선형 관계 처리, 다중회귀, 다중공선성까지 한 흐름으로 정리해본다.


회귀분석이란?

회귀분석은 독립변수와 종속변수 사이의 관계를 수식으로 표현하고, 독립변수가 변할 때 종속변수가 어떻게 달라지는지 분석하는 방법이다.

가장 기본적인 형태는 단순선형회귀(Simple Linear Regression)다.

Y = β₀ + β₁X + ε
기호의미
Y종속변수, 설명하거나 예측하려는 값
X독립변수, Y를 설명하기 위해 사용하는 값
β₀절편
β₁X가 1만큼 변할 때 Y가 얼마나 변하는지 나타내는 회귀계수
ε모델이 설명하지 못한 오차

예를 들어 동시 접속자 수를 이용해 API 평균 응답 시간을 설명한다면 다음처럼 생각할 수 있다.

응답시간 = 절편 + 접속자수에 따른 변화량 + 오차

상관분석과 회귀분석의 차이

둘 다 변수 사이의 관계를 다루지만 목적은 다르다.

구분상관분석회귀분석
목적관계의 방향과 강도 확인X로 Y를 설명하거나 예측
변수 역할비교적 대칭적독립변수와 종속변수 역할을 구분
대표 결과상관계수회귀식, 회귀계수, 결정계수
예시 질문둘은 함께 움직이는가?X가 변하면 Y가 얼마나 변하는가?

상관관계가 높다고 해서 반드시 회귀모델이 적절한 것은 아니다. 회귀분석에서는 관계 형태, 잔차, 이상치, 독립변수 간 관계 등을 추가로 살펴봐야 한다.


회귀모델을 만들기 전에 산점도부터 확인하기

회귀분석을 바로 실행하기보다 먼저 산점도로 변수 사이의 관계를 확인하는 것이 좋다.

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

service_df = pd.DataFrame({
    "concurrent_users": [10, 20, 30, 40, 50, 60, 70, 80],
    "response_ms": [110, 118, 125, 137, 149, 168, 195, 230]
})

sns.scatterplot(
    data=service_df,
    x="concurrent_users",
    y="response_ms"
)

plt.show()

이 단계에서는 다음을 확인한다.

  • 관계가 대략 선형인지
  • 곡선 형태의 비선형 관계인지
  • 극단적인 이상치가 있는지
  • 특정 구간에서 분산이 커지는지
  • 직선 하나로 설명하기 어려운 패턴이 있는지

회귀분석에서 모델 선택은 숫자 결과보다 먼저 데이터 구조를 이해하는 것에서 시작한다.


최소제곱법과 잔차

회귀모델은 실제 데이터를 완벽하게 통과하지 않는다. 실제값과 회귀식이 예측한 값 사이에는 차이가 생긴다.

이 차이를 잔차(Residual)라고 한다.

잔차 = 실제값 - 예측값

수식으로 표현하면 다음과 같다.

eᵢ = yᵢ - ŷᵢ

최소제곱법(Ordinary Least Squares, OLS)은 모든 잔차의 제곱합이 가장 작아지도록 회귀계수를 찾는다.

min Σ eᵢ²

잔차를 그냥 더하지 않고 제곱하는 이유는 양수와 음수가 서로 상쇄되는 것을 막고 큰 오차에 더 큰 패널티를 주기 위해서다.


statsmodels로 단순선형회귀 실행하기

Python에서는 통계적 회귀분석에 statsmodels를 많이 사용한다.

import statsmodels.api as sm

sm.OLS() 사용하기

먼저 종속변수와 독립변수를 분리한다.

y = service_df["response_ms"]
X = service_df["concurrent_users"]

sm.OLS()에서는 절편을 자동으로 추가하지 않기 때문에 상수항을 직접 결합한다.

X = sm.add_constant(X)

모델을 생성하고 적합한다.

model = sm.OLS(y, X)
result = model.fit()

print(result.summary())

summary()에서는 회귀계수, 표준오차, t 통계량, p-value, R² 등 여러 정보를 확인할 수 있다.

from_formula() 사용하기

Formula API를 사용하면 수식 형태로 모델을 작성할 수 있다.

model = sm.OLS.from_formula(
    "response_ms ~ concurrent_users",
    data=service_df
)

result = model.fit()

print(result.summary())

수식은 다음처럼 읽으면 된다.

종속변수 ~ 독립변수

독립변수가 여러 개라면 +로 연결한다.

"response_ms ~ concurrent_users + db_query_ms + payload_kb"

회귀계수 해석하기

단순회귀식이 다음과 같다고 하자.

ŷ = β₀ + β₁X

β₁이 양수라면 X가 증가할수록 Y도 증가하는 방향이고, 음수라면 X가 증가할수록 Y가 감소하는 방향이다.

예를 들어 다음 변수라면,

X = 동시 접속자 수
Y = API 응답 시간

회귀계수는 다음과 같이 해석할 수 있다.

동시 접속자 수가 1단위 증가할 때 응답 시간이 회귀계수만큼 변하는 것으로 추정한다.

다중회귀에서는 다른 독립변수가 동일하다고 가정했을 때라는 조건이 붙는다.


회귀계수의 통계적 유의성: t 검정

표본에서 계산된 회귀계수가 우연히 나타난 결과인지 확인하기 위해 개별 회귀계수에 대해 t 검정을 수행한다.

보통 다음과 같은 가설을 설정한다.

H₀: β₁ = 0
H₁: β₁ ≠ 0

즉,

H₀: 해당 독립변수의 선형 효과가 없다.
H₁: 해당 독립변수의 선형 효과가 있다.

summary() 결과의 P>|t| 값을 통해 확인할 수 있다.

유의수준을 0.05로 두었다면 일반적으로 다음처럼 해석한다.

p-value < 0.05
→ H₀ 기각
→ 해당 회귀계수가 0이 아니라고 볼 통계적 근거가 있음

p-value >= 0.05
→ H₀를 기각하지 못함
→ 해당 회귀계수가 유의하다고 보기 어려움

다만 p-value가 작다고 해서 반드시 실무적으로 중요한 변수라는 뜻은 아니다. 회귀계수의 크기, 단위, 신뢰구간과 실제 의미를 함께 봐야 한다.


결정계수 R²

회귀분석 결과에서 자주 확인하는 값이 결정계수(Coefficient of Determination, R²)다.

R²는 모델이 종속변수의 변동을 얼마나 설명하고 있는지를 나타낸다.

전체 변동
= 모델이 설명한 변동
+ 모델이 설명하지 못한 변동

일반적인 선형회귀에서 다음 형태로 이해할 수 있다.

R² = 1 - SSE / SST
  • SST: 전체 제곱합
  • SSE: 잔차 제곱합

statsmodels에서는 다음처럼 확인한다.

print(result.rsquared)

독립변수가 여러 개인 모델에서는 Adjusted R²도 함께 보는 것이 좋다.

print(result.rsquared_adj)

독립변수를 계속 추가하면 일반적인 R²는 낮아지지 않기 때문에, 변수 수를 고려한 지표도 함께 보는 것이다.


잔차를 확인해야 하는 이유

회귀분석은 회귀계수와 R²만 확인하고 끝나는 분석이 아니다.

모델이 설명하지 못한 부분인 잔차를 확인해야 모델이 데이터를 적절하게 표현하는지 판단할 수 있다.

residuals = result.resid

잔차 분포를 확인한다.

sns.histplot(
    residuals,
    kde=True
)

plt.show()

예측값과 잔차의 관계도 확인할 수 있다.

sns.scatterplot(
    x=result.fittedvalues,
    y=result.resid
)

plt.axhline(0)
plt.show()

잔차가 0을 중심으로 특별한 패턴 없이 흩어져 있는지 확인하는 것이 중요하다. 뚜렷한 곡선이나 깔때기 모양이 나타난다면 현재 모델이 데이터 구조를 충분히 반영하지 못했을 수 있다.


선형회귀의 주요 가정

선형회귀를 해석할 때 주로 확인하는 가정은 다음과 같다.

가정의미
선형성독립변수와 종속변수 관계가 설정한 모델 형태에 적절함
독립성오차들이 서로 독립적임
등분산성오차의 분산이 일정함
정규성통계적 추론을 위해 오차가 정규분포에 가까운지 확인
다중공선성 문제 없음다중회귀에서 독립변수끼리 지나치게 중복되지 않음

여기서 중요한 점은 원시 데이터 자체가 반드시 정규분포여야 하는 것이 아니라, 고전적인 회귀 추론에서는 오차항의 분포가 중요하다는 것이다.


선형모델이 잘 맞지 않을 때

산점도에서 곡선 형태가 보인다면 단순한 1차 직선으로는 충분하지 않을 수 있다.

대표적인 대응 방법은 다음과 같다.

변수 변환
또는
다항항 추가

로그 변환

어떤 변수는 값이 커질수록 변화 폭이 완만해지는 형태를 가진다. 이런 경우 로그 변환을 적용하면 관계가 더 선형적으로 보일 수 있다.

import numpy as np

service_df["log_concurrent_users"] = np.log1p(
    service_df["concurrent_users"]
)

관계를 다시 확인한다.

sns.scatterplot(
    data=service_df,
    x="log_concurrent_users",
    y="response_ms"
)

plt.show()

회귀모델은 다음처럼 만들 수 있다.

log_model = sm.OLS.from_formula(
    "response_ms ~ np.log1p(concurrent_users)",
    data=service_df
)

log_result = log_model.fit()

print(log_result.summary())

로그 변환은 단순히 R²를 높이기 위한 작업이 아니라 데이터의 실제 관계를 더 자연스럽게 표현할 때 사용하는 것이 좋다.


2차 회귀와 다항회귀

독립변수와 종속변수 관계가 U자 또는 역U자 형태처럼 보인다면 2차항을 추가할 수 있다.

Y = β₀ + β₁X + β₂X² + ε

Python에서는 Formula API를 이용해 다음처럼 작성할 수 있다.

quadratic_model = sm.OLS.from_formula(
    "response_ms ~ concurrent_users + I(concurrent_users ** 2)",
    data=service_df
)

quadratic_result = quadratic_model.fit()

print(quadratic_result.summary())

I()는 formula 내부에서 Python 식을 그대로 계산할 수 있게 해준다.

I(concurrent_users ** 2)

3차, 4차 이상의 다항식도 만들 수 있지만 차수가 지나치게 높아지면 과적합(Overfitting) 위험이 커질 수 있다.

다항회귀도 선형회귀인가?

다음 모델은 독립변수 X에 대해서는 곡선을 만들지만 추정하는 계수 β에 대해서는 선형이다.

Y = β₀ + β₁X + β₂X²

따라서 이런 모델도 넓은 의미에서는 선형회귀모델에 포함된다.


선형·로그·2차 모델 비교하기

여러 모델을 만들었다면 R² 하나만 보고 선택하지 않는다.

확인 항목의미
R²모델 설명력
Adjusted R²변수 수를 고려한 설명력
회귀계수 p-value개별 변수의 통계적 유의성
잔차 분포모델이 설명하지 못한 패턴 확인
잔차 vs 예측값비선형성·이분산성 확인
해석 가능성실제 문제에서 설명하기 쉬운 모델인지

비교 코드는 다음처럼 작성할 수 있다.

print("linear R²:", linear_result.rsquared)
print("log R²:", log_result.rsquared)
print("quadratic R²:", quadratic_result.rsquared)

R²가 가장 높다는 이유만으로 최종 모델을 선택하면 안 된다. 복잡한 모델일수록 학습 데이터에만 지나치게 맞을 가능성이 있기 때문이다.


다중회귀분석

다중회귀분석(Multiple Regression)은 하나의 종속변수를 두 개 이상의 독립변수로 설명한다.

Y = β₀ + β₁X₁ + β₂X₂ + ... + βₖXₖ + ε

예를 들어 API 응답 시간을 다음 변수들로 설명한다고 생각해보자.

response_ms
    ↑
concurrent_users
DB query time
payload size

새로운 예제 데이터를 만들어보면 다음과 같다.

service_df = pd.DataFrame({
    "response_ms": [120, 135, 150, 170, 190, 220, 250, 290],
    "concurrent_users": [10, 20, 30, 40, 50, 60, 70, 80],
    "db_query_ms": [30, 35, 42, 48, 55, 63, 72, 85],
    "payload_kb": [20, 25, 22, 30, 28, 35, 40, 45]
})

다중회귀모델을 생성한다.

multi_model = sm.OLS.from_formula(
    "response_ms ~ concurrent_users + db_query_ms + payload_kb",
    data=service_df
)

multi_result = multi_model.fit()

print(multi_result.summary())

다중회귀의 중요한 특징은 특정 변수의 효과를 볼 때 다른 독립변수들의 영향을 함께 고려할 수 있다는 점이다.

예를 들어 db_query_ms의 회귀계수는 다른 독립변수가 동일하다고 가정했을 때 DB 처리 시간이 응답 시간과 어떤 관계를 가지는지 보여준다.


다중공선성

다중회귀에서 특히 주의할 문제가 다중공선성(Multicollinearity)이다.

다중공선성은 독립변수끼리 서로 너무 강하게 관련되어 있는 상태를 말한다.

예를 들어 다음 두 변수가 거의 같은 정보를 담고 있다면 문제가 될 수 있다.

전체 요청 수
초당 요청 수

다중공선성이 심하면 다음 문제가 발생할 수 있다.

  • 회귀계수가 불안정해짐
  • 회귀계수 부호가 예상과 달라질 수 있음
  • 표준오차가 커짐
  • 중요한 변수의 p-value가 커질 수 있음

간단한 탐색 단계에서는 독립변수 상관행렬을 확인할 수 있다.

corr = service_df[
    [
        "concurrent_users",
        "db_query_ms",
        "payload_kb"
    ]
].corr()

sns.heatmap(
    corr,
    annot=True
)

plt.show()

보다 정식으로는 VIF(Variance Inflation Factor)를 사용할 수 있다.

from statsmodels.stats.outliers_influence import variance_inflation_factor

X = service_df[
    [
        "concurrent_users",
        "db_query_ms",
        "payload_kb"
    ]
].copy()

X = sm.add_constant(X)

vif = pd.DataFrame({
    "feature": X.columns,
    "VIF": [
        variance_inflation_factor(X.values, i)
        for i in range(X.shape[1])
    ]
})

vif

VIF는 하나의 절대적인 기준만으로 판단하기보다 변수 의미와 모델 구조를 함께 보면서 해석하는 것이 좋다.


변수 선택이 중요한 이유

독립변수를 많이 넣는다고 항상 좋은 모델이 되는 것은 아니다.

불필요한 변수 증가
        ↓
해석 복잡성 증가
        ↓
다중공선성 가능성 증가
        ↓
과적합 가능성 증가

실제 분석에서는 다음을 함께 고려한다.

  • 비즈니스적으로 의미 있는 변수인지
  • 종속변수보다 시간적으로 먼저 관측되는 변수인지
  • 독립변수끼리 지나치게 중복되지 않는지
  • 데이터 누설이 발생하지 않는지
  • 추가했을 때 모델이 실제로 개선되는지

회귀분석에서 자주 헷갈리는 부분

R²가 높으면 인과관계가 증명될까?

아니다.

회귀분석은 변수 사이의 통계적 관계를 모델링한다. 높은 R²는 인과관계를 증명하지 않는다.

인과관계를 주장하려면 실험 설계, 시간적 선후관계, 교란변수 통제 등이 추가로 필요하다.

p-value가 작으면 중요한 변수일까?

통계적으로 유의하다는 것과 실무적으로 중요하다는 것은 다르다.

회귀계수 크기
단위
신뢰구간
p-value
도메인 의미

를 함께 확인해야 한다.

비선형이면 회귀분석을 못 할까?

그렇지 않다.

로그 변환
제곱근 변환
다항항 추가
상호작용항 추가
스플라인
다른 비선형 모델

등을 고려할 수 있다.


개발자 관점에서 회귀분석 활용하기

회귀분석은 서비스 운영 데이터에서도 활용할 수 있다.

예를 들어 서버 지연을 분석한다고 해보자.

종속변수
    API 응답 시간

독립변수
    동시 접속자 수
    DB Query 시간
    요청 Payload 크기
    캐시 Hit 여부

분석 흐름은 다음과 같이 잡을 수 있다.

로그 수집
    ↓
산점도와 분포 확인
    ↓
단순회귀로 개별 관계 확인
    ↓
다중회귀로 여러 변수 동시 분석
    ↓
잔차와 다중공선성 점검
    ↓
필요하면 로그·다항항 적용
    ↓
모델 비교와 해석

이렇게 접근하면 단순히 "트래픽이 많아서 느리다"는 감에 머무르지 않고 어떤 변수가 응답 시간 변화와 더 밀접하게 연결되어 있는지 수치로 설명할 수 있다.

다만 운영 데이터는 실험 데이터가 아니므로 회귀계수를 곧바로 인과 효과로 해석하면 안 된다.


실무에서 확인할 회귀분석 체크리스트

회귀모델을 만든 뒤에는 summary() 숫자만 확인하고 끝내지 않는 것이 좋다.

1. 종속변수와 독립변수 정의가 적절한가?
2. 산점도에서 관계 형태를 확인했는가?
3. 회귀계수의 방향과 크기가 해석 가능한가?
4. p-value와 신뢰구간은 어떤가?
5. R²와 Adjusted R²는 어느 정도인가?
6. 잔차에 뚜렷한 패턴이 없는가?
7. 이상치가 모델을 과도하게 끌고 있지 않은가?
8. 다중회귀라면 다중공선성이 심하지 않은가?
9. 더 복잡한 모델을 사용할 이유가 실제로 있는가?
10. 결과를 인과관계로 과도하게 해석하고 있지 않은가?

핵심 정리

  • 회귀분석은 독립변수로 종속변수를 설명하거나 예측하는 통계 모델이다.
  • 단순선형회귀는 하나의 X와 하나의 Y 관계를 직선으로 모델링한다.
  • 최소제곱법(OLS)은 잔차 제곱합이 최소가 되도록 회귀계수를 추정한다.
  • 회귀계수는 독립변수가 변할 때 종속변수가 얼마나 변하는지 설명한다.
  • 개별 회귀계수의 통계적 유의성은 t 검정과 p-value로 확인할 수 있다.
  • R²는 모델이 종속변수의 변동을 얼마나 설명하는지 보여준다.
  • 회귀계수와 R²만 보지 말고 반드시 잔차를 확인해야 한다.
  • 비선형 관계는 로그 변환이나 다항항으로 표현할 수 있다.
  • 다중회귀는 여러 독립변수를 동시에 사용하지만 다중공선성을 확인해야 한다.
  • 높은 설명력은 인과관계를 의미하지 않는다.

정리하며

회귀분석을 처음 공부하면 공식과 summary() 결과에 집중하기 쉽다. 하지만 실제 데이터 분석에서 더 중요한 것은 어떤 관계를 어떤 모델로 표현할 것인지 판단하는 과정이다.

관계 시각화
    ↓
모델 형태 선택
    ↓
OLS로 계수 추정
    ↓
회귀계수와 R² 확인
    ↓
잔차와 가정 점검
    ↓
필요하면 로그·다항·다중회귀로 확장
    ↓
모델을 데이터와 도메인 관점에서 해석

이 흐름을 익혀두면 단순히 회귀식을 만드는 데서 끝나는 것이 아니라, 왜 이 모델을 사용했고 결과를 어떻게 해석해야 하는지 설명할 수 있는 분석으로 이어갈 수 있다.

profile
html_programming_language

0개의 댓글