Data_10기

Logistic Regression 및 SQL 코드카타

senu 2025. 12. 9. 23:45

오늘도 라이브 세션이 몰아치는 하루였다. 근데 내일은 라이브 세션이 더 많다. 복습을 하기 위해 베이직반 라이브세션까지 듣는데 애매하게 알고 있는 내용이라 그런지 집중을 오랫동안 유지하기가 힘들다. 과연 의미가 있을지, 그냥 익숙해지기만 반복하는 것은 아닌지 하는 의문이 들지만.. 익숙해 지는 것만으로도 도움이 되지 않을까 하며 내일도 세션을 들을 것 같다.


Ⅰ. Logistic Regression

회귀분석을 하기 전에는 Y(종속변수)의 분포를 살펴 보아야 한다. 오늘은 종속변수가 이분형 범주형 변수일 때 사용할 수 있는 로지스틱 회귀분석에 대해 배워보았다. 회귀분석에서 가장 기본이 되는 분석 방법은 일반 선형회귀 분석이다. 하지만 종속변수가 이분형 변수일 경우에는 데이터를 직선으로 설명하기 어렵다. 따라서 종속변수가 이분형 변수인 경우, 우리는 로지스틱이라는 회귀분석 방법을 적용해야 한다.

 

1. 오즈(odds) vs 오즈비(odds ratio)

오즈와 오즈비는 간단하게 아래와 같이 설명할 수 있을 것 같다.

  • 오즈(odds) : 변수 안에서의 확률 비교
  • 오즈비(odds ratio) : 그룹(집단) 간 오즈 비교

확률 대신 오즈로 비교하는 이유는 확률은 값이 0~1 사이에 값을 가지기 때문에 오즈에 비해 비교적 변화 폭이 작게 보이고, '얼마나 늘었는지'에 대한 덧셈적인 차이만 보여줄 수 있기 때문에 오즈를 사용한다. 오즈를 사용하면 '몇 배 가능성이 증가했는지'에 대한 곱셈적 비교를 보여줄 수 있어 비교에 유리하다.

확률 
  • 남성 : 구매 확률 = 0.8, 비구매 확률 = 0.2
  • 여성 : 구매 확률 = 0.4, 비구매 확률 = 0.6
[덧셈적 차이]
남성이 여성보다 0.4 (0.8 - 0.4) 만큼 높음
오즈
  • 남성 : 오즈 = 0.8 / 0.2 = 4
  • 여성 : 오즈 = 0.4 / 0.6 = 0.667
[곱셈적 차이]
남성이 여성보다 약 6배 (4 / 0.667) 구매할 가능성 높음

 

2. 로그 오즈 (log odds)

오즈에 로그를 씌운 형태를 로그 오즈 라고 하며, 로짓 함수(logit function)이라고도 한다. 오즈에 로그를 취하면 덧셈적, 선형적 구조로 바뀌면서 (1) 선형 회귀식 형태를 적용할 수 있게 된다. 또한 이전에 금액 관련 변수에 log를 씌워주면 금액이 커져도 변화폭이 완만해지는 것처럼 오즈에 log를 씌워주면 오즈 값이 커져도 변화폭이 완만해진다. 그러면 (2) 변수의 영향이 더 예측 가능해지고 해석도 쉬워지기 때문에 로그 오즈를 사용한다고 한다.

 

⇒  그럼 데이터 자체에 log를 씌우는 것과 odds에 log를 씌우는 데에는 어떤 차이가 있는 것일까?

 

3. 로지스틱 회귀 (logistic regession)

import pandas as pd

df_logit = pd.DataFrame({
    "x1": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
    "x2": [5, 4, 6, 7, 5, 6, 8, 9, 10, 12],
    "x3": [10, 9, 8, 7, 6, 5, 4, 3, 2, 1],
    "target": [0, 0, 0, 1, 0, 1, 1, 1, 1, 1]   # 이진 종속변수
})

 

위 예시 데이터셋을 가지고 로지스틱 회귀분석을 진행해보는 실습을 해보았다.

# statsmodel - 로지스틱회귀
X = df_logit[['x1', 'x2', 'x3']]
y = df_logit['target']

X = sm.add_constant(X)
model = sm.Logit(y, X).fit() # OLS 대신 Logit을 쓴다는 점이 다름
print(model.summary())

 

이렇게 X(독립변수)에 x1, x2, x3를 넣었을 때는 모델이 돌아가지 않는다. 그 이유는 x1과 x3의 다중공선성 때문이다. 상관분석을 통해 확인을 해보면:

df_logit.corr()

상관분석 결과

 

완벽하게 반비례하는 그래프이기 때문에 상관계수는 -1. 즉, 이 두 변수는 같은 모델에 넣게되면 오류가 발생할수 있기 때문에 x3를 제외하고 분석을 진행해보기로 했다.

X = df_logit[['x1', 'x2']]  # 공선성 강한 x3 제거
y = df_logit['target']

X = sm.add_constant(X)
model = sm.Logit(y, X).fit()
print(model.summary())

 

로지스틱 회귀분석 결과는 다음과 같다:

Warning: Maximum number of iterations has been exceeded.
         Current function value: 0.000000
         Iterations: 35
                           Logit Regression Results                           
==============================================================================
Dep. Variable:                 target   No. Observations:                   10
Model:                          Logit   Df Residuals:                        7
Method:                           MLE   Df Model:                            2
Date:                Tue, 09 Dec 2025   Pseudo R-squ.:                   1.000
Time:                        23:32:54   Log-Likelihood:            -2.3461e-09
converged:                      False   LL-Null:                       -6.7301
Covariance Type:            nonrobust   LLR p-value:                  0.001194
==============================================================================
                 coef    std err          z      P>|z|      [0.025      0.975]
------------------------------------------------------------------------------
const       -233.7338   2.27e+05     -0.001      0.999   -4.45e+05    4.45e+05
x1            14.2369   2.04e+04      0.001      0.999   -4.01e+04    4.01e+04
x2            28.2929   2.99e+04      0.001      0.999   -5.85e+04    5.86e+04
==============================================================================

 

예시 데이터가 불완전했기 때문에 모델 수렴이 실패했다는 경고문이 뜨긴 했지만, Logit 함수를 사용해서 분석을 진행할 수 있는 것 같다. 다음 프로젝트에서는 어떻게 사용할 수 있을지 모르겠지만 활용해볼 수 있기를 기대한다.


Ⅱ. SQL 리마스터

오늘은 SQL의 JOIN에 대해 복습을 했다. 그동안 서로 다른 테이블 두 개를 JOIN해야 하는 문제만 만나서 JOIN을 한 번씩만 사용해도 코드카타 문제를 풀 때 무리가 없었다. 그런데 오늘 세션을 들으며 서로 다른 세 개의 테이블을 JOIN해야 하는 예제가 있어 JOIN을 두 번 사용해보았다. 그리고 쿼리에서 볼 수 있듯이 JOIN의 조건인 ON 절에 컬럼명 뿐만 아니라 범위도 조건으로 줄 수 있었다.

select e.empno as '사원번호', e.ename_ko as '이름', e.sal+e.comm as '총급여', 
       e.deptno as '부서번호', d.dname as '부서명', s.grade as '등급'
from emp as e
left join dept as d
on e.deptno = d.deptno
left join salgrade as s
on e.sal+e.comm >= s.losal and e.sal+e.comm <= s.hisal
order by `부서번호`
;

 

그리고 SQL에서 != NULL, = NULL 같은 일반 비교 연산자는 NULL과 비교할 수 없다는 것을 알게 되었다. NULL은 '값이 없음'을 의미하는 형태라서 숫자나 문자처럼 비교가 되지 않는다. NULL 값이 아닌 것만 가져오도록 쿼리를 짜고 싶어 != NULL 을 사용했는데 아무 데이터가 뜨지 않았고, is not NULL을 사용하니 NULL을 제외한 행들이 가져와졌다.

-- 잘못된 쿼리
select d.location as '지역', round(avg(sal),0) as '평균급여'
from emp as e
left join dept as d
on e.deptno = d.deptno
group by d.location
having d.location != null   # NULL에는 연산자 사용할 수 없음
;

-- 정답 쿼리
select d.location as '지역', round(avg(sal),0) as '평균급여'
from emp as e
left join dept as d
on e.deptno = d.deptno
group by d.location
having d.location is not null 
;

마무리

연구를 하면서 로지스틱에 대해 가볍게 알고 있었는데 이번 세션을 통해 복습을 하며 오즈와 오즈비 개념을 재정립할 수 있어 좋았던 것 같다. 다만 TIL로 정리하다보니 로그 변환에 대해 분포 자체에 로그를 씌우는 것과 오즈에 로그를 씌우는데에 어떤 차이가 있는지는 의문이 남는다. 내일 튜터님께 여쭤봐야겠다.