데이터 분석을 하다 보면 단순히 변수 사이의 관계를 확인하는 것에서 한 걸음 더 나아가, 어떤 변수가 결과값에 얼마나 영향을 주는지 설명하거나 새로운 값을 예측하고 싶은 상황이 생긴다.
예를 들어 웹 서비스라면 다음과 같은 질문을 생각할 수 있다.
이런 문제를 다루는 대표적인 통계 방법이 회귀분석(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ᵢ²
잔차를 그냥 더하지 않고 제곱하는 이유는 양수와 음수가 서로 상쇄되는 것을 막고 큰 오차에 더 큰 패널티를 주기 위해서다.
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 검정을 수행한다.
보통 다음과 같은 가설을 설정한다.
H₀: β₁ = 0
H₁: β₁ ≠ 0
즉,
H₀: 해당 독립변수의 선형 효과가 없다.
H₁: 해당 독립변수의 선형 효과가 있다.
summary() 결과의 P>|t| 값을 통해 확인할 수 있다.
유의수준을 0.05로 두었다면 일반적으로 다음처럼 해석한다.
p-value < 0.05
→ H₀ 기각
→ 해당 회귀계수가 0이 아니라고 볼 통계적 근거가 있음
p-value >= 0.05
→ H₀를 기각하지 못함
→ 해당 회귀계수가 유의하다고 보기 어려움
다만 p-value가 작다고 해서 반드시 실무적으로 중요한 변수라는 뜻은 아니다. 회귀계수의 크기, 단위, 신뢰구간과 실제 의미를 함께 봐야 한다.
회귀분석 결과에서 자주 확인하는 값이 결정계수(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²를 높이기 위한 작업이 아니라 데이터의 실제 관계를 더 자연스럽게 표현할 때 사용하는 것이 좋다.
독립변수와 종속변수 관계가 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²
따라서 이런 모델도 넓은 의미에서는 선형회귀모델에 포함된다.
여러 모델을 만들었다면 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)이다.
다중공선성은 독립변수끼리 서로 너무 강하게 관련되어 있는 상태를 말한다.
예를 들어 다음 두 변수가 거의 같은 정보를 담고 있다면 문제가 될 수 있다.
전체 요청 수
초당 요청 수
다중공선성이 심하면 다음 문제가 발생할 수 있다.
간단한 탐색 단계에서는 독립변수 상관행렬을 확인할 수 있다.
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²는 인과관계를 증명하지 않는다.
인과관계를 주장하려면 실험 설계, 시간적 선후관계, 교란변수 통제 등이 추가로 필요하다.
통계적으로 유의하다는 것과 실무적으로 중요하다는 것은 다르다.
회귀계수 크기
단위
신뢰구간
p-value
도메인 의미
를 함께 확인해야 한다.
그렇지 않다.
로그 변환
제곱근 변환
다항항 추가
상호작용항 추가
스플라인
다른 비선형 모델
등을 고려할 수 있다.
회귀분석은 서비스 운영 데이터에서도 활용할 수 있다.
예를 들어 서버 지연을 분석한다고 해보자.
종속변수
API 응답 시간
독립변수
동시 접속자 수
DB Query 시간
요청 Payload 크기
캐시 Hit 여부
분석 흐름은 다음과 같이 잡을 수 있다.
로그 수집
↓
산점도와 분포 확인
↓
단순회귀로 개별 관계 확인
↓
다중회귀로 여러 변수 동시 분석
↓
잔차와 다중공선성 점검
↓
필요하면 로그·다항항 적용
↓
모델 비교와 해석
이렇게 접근하면 단순히 "트래픽이 많아서 느리다"는 감에 머무르지 않고 어떤 변수가 응답 시간 변화와 더 밀접하게 연결되어 있는지 수치로 설명할 수 있다.
다만 운영 데이터는 실험 데이터가 아니므로 회귀계수를 곧바로 인과 효과로 해석하면 안 된다.
회귀모델을 만든 뒤에는 summary() 숫자만 확인하고 끝내지 않는 것이 좋다.
1. 종속변수와 독립변수 정의가 적절한가?
2. 산점도에서 관계 형태를 확인했는가?
3. 회귀계수의 방향과 크기가 해석 가능한가?
4. p-value와 신뢰구간은 어떤가?
5. R²와 Adjusted R²는 어느 정도인가?
6. 잔차에 뚜렷한 패턴이 없는가?
7. 이상치가 모델을 과도하게 끌고 있지 않은가?
8. 다중회귀라면 다중공선성이 심하지 않은가?
9. 더 복잡한 모델을 사용할 이유가 실제로 있는가?
10. 결과를 인과관계로 과도하게 해석하고 있지 않은가?
회귀분석을 처음 공부하면 공식과 summary() 결과에 집중하기 쉽다. 하지만 실제 데이터 분석에서 더 중요한 것은 어떤 관계를 어떤 모델로 표현할 것인지 판단하는 과정이다.
관계 시각화
↓
모델 형태 선택
↓
OLS로 계수 추정
↓
회귀계수와 R² 확인
↓
잔차와 가정 점검
↓
필요하면 로그·다항·다중회귀로 확장
↓
모델을 데이터와 도메인 관점에서 해석
이 흐름을 익혀두면 단순히 회귀식을 만드는 데서 끝나는 것이 아니라, 왜 이 모델을 사용했고 결과를 어떻게 해석해야 하는지 설명할 수 있는 분석으로 이어갈 수 있다.