statsmodels 기초
학습 내용
smf.ols()와sm.OLS()의 차이를 익히고 회귀식을 만드는 방법 배우기params,fittedvalues,resid,rsquared,pvalues,conf_int()읽는 방법 익히기smf.logit()와sm.Logit()으로 이진 분류 확률을 해석하는 방법 배우기np.exp(params)로 오즈비를 읽고 확률을 0과 1로 나누는 방법 익히기
statsmodels는 예측만 빠르게 하는 라이브러리라기보다, 모델의 계수와 해석을 자세히 보는 데 강한 라이브러리입니다. sklearn이 학습 흐름을 익히기 좋다면, statsmodels는 “이 계수가 무슨 뜻인지”를 읽는 연습에 더 잘 맞습니다.
이 페이지에서는 작은 예시 데이터로 개념을 먼저 이해하고, 바로 외부 CSV에 같은 방식을 적용합니다. 초보자는 처음부터 summary() 전체 표를 외우려 하지 말고, params, pvalues, predict() 같은 핵심 속성부터 읽으면 됩니다.
smf.ols()는 formula API입니다. "y ~ x"처럼 식을 문자열로 적으면 회귀식을 바로 만들 수 있어서, 처음 배울 때 가장 이해하기 쉽습니다.
작은 예시 데이터부터 보면 아래와 같습니다.
실행 결과:
절편 1.0, 기울기 2.0이라는 뜻입니다. 그래서 x=5를 넣으면 1 + 2*5 = 11에 가까운 예측이 나옵니다.
외부 데이터에 적용하면 아래처럼 실제 회귀를 볼 수 있습니다.
실행 결과:
신장 : cm 계수 1.036은 키가 1cm 커질 때 예측 몸무게가 약 1.036kg 커지는 방향으로 학습되었다는 뜻입니다. rsquared는 이 모델이 몸무게 변동을 얼마나 설명하는지 보여 주고, 0.533이면 절반 정도를 설명한다고 읽을 수 있습니다.
formula API에서는 절편이 기본으로 자동 포함됩니다. 그래서 Q("체중 : kg") ~ Q("신장 : cm")처럼 써도 절편과 기울기를 함께 추정합니다.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 1. 간단한 예시 데이터 절편 확인하기 # smf.ols("y ~ x", data=df).fit() 결과에서 절편을 round(..., 1)로 출력해 보세요. # 가이드: result = smf.ols(...).fit()을 먼저 만들어 보세요. import pandas as pd import statsmodels.formula.api as smf df = pd.DataFrame( { "x": [1, 2, 3, 4], "y": [3, 5, 7, 9], } )
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 2. 외부 데이터 신장 : cm 계수 확인하기 # /data/body.csv를 읽어 smf.ols('Q("체중 : kg") ~ Q("신장 : cm")', data=df).fit() 결과에서 신장 : cm 계수를 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.formula.api as smf data_path = "/data/body.csv" df = pd.read_csv(data_path) # 가이드 # 1. formula API로 result를 만드세요. # 2. Q("신장 : cm") 계수를 round(..., 3)으로 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 3. 외부 데이터 예측값 확인하기 # 신장 : cm=170일 때의 예측 체중 : kg를 round(..., 1)로 출력해 보세요. import pandas as pd import statsmodels.formula.api as smf data_path = "/data/body.csv" df = pd.read_csv(data_path) # 가이드 # 1. result를 만든 뒤 예측용 DataFrame을 직접 만드세요. # 2. predict() 결과를 round(..., 1)로 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 4. 외부 데이터 설명력 확인하기 # smf.ols('Q("체중 : kg") ~ Q("신장 : cm")', data=df).fit() 결과의 rsquared를 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.formula.api as smf data_path = "/data/body.csv" df = pd.read_csv(data_path) # 가이드: formula API로 result를 만든 뒤 rsquared를 출력하세요.
sm.OLS()는 class API입니다. formula 문자열 대신 X와 y를 따로 넣어야 합니다. 이때 절편을 자동으로 넣어 주지 않으므로, sm.add_constant()로 상수항 열을 직접 추가해야 합니다.
실행 결과:
첫 번째 줄의 const가 절편용 열입니다. smf.ols()에서는 자동이었지만, sm.OLS()에서는 직접 넣어야 한다는 점이 가장 큰 차이입니다.
계수는 각각 아래처럼 읽으면 됩니다.
신장 : cm: 키가 1 커질 때 예측 몸무게가 얼마나 변하는지측정나이: 나이가 1살 늘 때 예측 몸무게가 얼마나 변하는지악력D : kg: 악력이 1 커질 때 예측 몸무게가 얼마나 변하는지
여기서는 여러 설명변수가 함께 들어간 다중회귀 예시라, 계수 하나를 읽을 때도 “다른 변수들을 함께 고려한 상태에서의 변화량”으로 읽는 것이 맞습니다.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 5. add_constant 열 이름 확인하기 # 간단한 예시 DataFrame에 sm.add_constant()를 적용한 뒤 첫 번째 열 이름을 출력해 보세요. # 가이드: 상수항을 넣으면 첫 열 이름이 무엇이 되는지 보세요. import pandas as pd import statsmodels.api as sm X = pd.DataFrame({"x": [1, 2, 3]})
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 6. 외부 데이터 상수항 열 확인하기 # /data/body.csv에서 X = sm.add_constant(df[["신장 : cm", "측정나이", "악력D : kg"]])를 만든 뒤 첫 번째 열 이름을 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/body.csv" df = pd.read_csv(data_path) # 가이드: 세 열을 고른 뒤 sm.add_constant()를 적용하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 7. 외부 데이터 악력D : kg 계수 확인하기 # sm.OLS() 결과에서 악력D : kg 계수를 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/body.csv" df = pd.read_csv(data_path) X = sm.add_constant(df[["신장 : cm", "측정나이", "악력D : kg"]]) y = df["체중 : kg"] # 가이드: sm.OLS(y, X).fit() 결과에서 악력D : kg 계수를 확인하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 8. 외부 데이터 첫 fitted value 확인하기 # sm.OLS() 결과의 첫 번째 fitted value를 round(..., 1)로 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/body.csv" df = pd.read_csv(data_path) X = sm.add_constant(df[["신장 : cm", "측정나이", "악력D : kg"]]) y = df["체중 : kg"] # 가이드: result를 만든 뒤 fittedvalues의 첫 값을 출력하세요.
statsmodels를 배우는 핵심은 결과 객체를 읽는 것입니다. 초보자가 가장 먼저 익혀야 할 속성은 아래 여섯 가지입니다.
params: 계수fittedvalues: 모델이 맞춘 예측값resid: 실제값 - 예측값인 잔차rsquared: 설명력pvalues: 각 계수가 통계적으로 유의한지 볼 때 참고하는 값conf_int(): 계수의 신뢰구간
실행 결과:
잔차 -5.0은 첫 번째 행에서 실제 몸무게가 모델 예측보다 약 5kg 작았다는 뜻입니다. pvalues < 0.05가 True라는 것은 이 예시에서는 각 계수가 통계적으로 유의하다고 볼 수 있다는 뜻입니다.
conf_int()는 계수의 추정 범위를 보여 줍니다. 예를 들어 신장 : cm의 신뢰구간이 0.697 ~ 0.743이라면, 이 구간 안쪽에서 계수가 형성되었을 가능성이 높다고 읽을 수 있습니다.
summary()도 아주 중요하지만, 표가 길기 때문에 처음에는 위 속성들을 직접 확인하는 방식이 더 이해하기 쉽습니다.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 9. rsquared 확인하기 # sm.OLS() 결과의 rsquared를 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/body.csv" df = pd.read_csv(data_path) X = sm.add_constant(df[["신장 : cm", "측정나이", "악력D : kg"]]) y = df["체중 : kg"] # 가이드: result를 만든 뒤 rsquared를 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 10. 첫 번째 residual 확인하기 # result.resid의 첫 번째 값을 round(..., 1)로 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/body.csv" df = pd.read_csv(data_path) X = sm.add_constant(df[["신장 : cm", "측정나이", "악력D : kg"]]) y = df["체중 : kg"] # 가이드: result를 만든 뒤 resid의 첫 값을 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 11. 측정나이 계수의 유의성 확인하기 # 측정나이의 pvalue가 0.05보다 작은지 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/body.csv" df = pd.read_csv(data_path) X = sm.add_constant(df[["신장 : cm", "측정나이", "악력D : kg"]]) y = df["체중 : kg"] # 가이드: result를 만든 뒤 측정나이 pvalue를 비교하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 12. 신장 : cm 계수 신뢰구간의 아래쪽 값 확인하기 # result.conf_int()에서 신장 : cm 행, 첫 번째 열 값을 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/body.csv" df = pd.read_csv(data_path) X = sm.add_constant(df[["신장 : cm", "측정나이", "악력D : kg"]]) y = df["체중 : kg"] # 가이드: result를 만든 뒤 conf_int()에서 신장 : cm 아래쪽 값을 고르세요.
smf.logit()은 formula API 방식의 로지스틱 회귀입니다. 결과를 보면 각 계수와 함께, 새 데이터가 1일 확률을 바로 확인할 수 있습니다.
작은 예시 데이터부터 보겠습니다.
실행 결과:
study_hours 계수가 양수이므로, 공부 시간이 늘수록 합격 확률이 올라가는 방향으로 모델이 학습되었다고 볼 수 있습니다. study_hours=6일 때 예측 확률이 0.873이라는 것은, 합격할 확률이 약 87.3%라는 뜻입니다.
외부 데이터로 보면 아래와 같습니다.
실행 결과:
여기서 0.992는 새 데이터가 is_male=1, 즉 남성일 확률이 약 99.2%라는 뜻입니다. statsmodels의 로지스틱 회귀는 이런 식으로 클래스 확률을 해석하는 데 매우 좋습니다.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 13. 간단한 예시 데이터 합격 확률 확인하기 # study_hours=6일 때의 합격 확률을 round(..., 3)으로 출력해 보세요. # 가이드: result를 만든 뒤 predict()를 써 보세요. import pandas as pd import statsmodels.formula.api as smf df = pd.DataFrame( { "study_hours": [1, 2, 3, 4, 5, 6, 7, 8], "passed": [0, 0, 0, 1, 0, 1, 1, 1], } )
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 14. 외부 데이터 신장 : cm 계수 확인하기 # /data/body.csv로 smf.logit('is_male ~ Q("신장 : cm") + Q("악력D : kg")', data=df).fit()를 만든 뒤 신장 : cm 계수를 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.formula.api as smf data_path = "/data/body.csv" df = pd.read_csv(data_path) df["is_male"] = (df["측정회원성별"] == "M").astype(int) # 가이드: result를 만든 뒤 Q("신장 : cm") 계수를 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 15. 외부 데이터 예측 확률 확인하기 # 신장 : cm=175, 악력D : kg=40일 때 is_male 확률을 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.formula.api as smf data_path = "/data/body.csv" df = pd.read_csv(data_path) df["is_male"] = (df["측정회원성별"] == "M").astype(int) # 가이드 # 1. result를 만든 뒤 예측용 DataFrame을 만드세요. # 2. predict() 결과를 round(..., 3)으로 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 16. 0.5 기준으로 클래스 판단하기 # 문제 15의 예측 확률이 0.5보다 큰지 출력해 보세요. import pandas as pd import statsmodels.formula.api as smf data_path = "/data/body.csv" df = pd.read_csv(data_path) df["is_male"] = (df["측정회원성별"] == "M").astype(int) # 가이드: 문제 15와 같은 확률을 만든 뒤 0.5와 비교하세요.
로지스틱 회귀의 계수는 바로 확률이 아닙니다. 계수에 np.exp()를 적용하면 오즈비(odds ratio)로 바꿔서 볼 수 있습니다. 오즈비는 “설명변수가 1 늘 때 odds가 몇 배가 되는가”를 보여 줍니다.
먼저 확률과 오즈의 관계를 아주 짧게 보면 아래와 같습니다.
실행 결과:
확률이 0.8이면 오즈는 4.0입니다. 즉 일어날 쪽이 일어나지 않을 쪽보다 4배라는 뜻입니다.
외부 데이터에서는 이렇게 계수를 오즈비로 바꿉니다.
실행 결과:
악력D : kg의 오즈비가 1.463이라는 것은, 악력이 1 증가할 때 is_male=1 쪽의 오즈가 약 1.463배가 되는 방향이라는 뜻입니다. 오즈비가 1보다 크면 양의 방향, 1보다 작으면 음의 방향으로 이해하면 됩니다.
포켓몬 데이터에서도 같은 방식으로 볼 수 있습니다.
실행 결과:
이 값은 Total이 1 증가할 때 전설 포켓몬일 오즈가 약 1.031배가 되는 방향으로 읽을 수 있다는 뜻입니다.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 17. 확률 0.8의 오즈 구하기 # prob = 0.8일 때 odds = prob / (1 - prob)를 계산해 round(..., 1)로 출력해 보세요. # 가이드: 확률을 오즈로 바꾸는 식을 그대로 써 보세요. prob = 0.8
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 18. body 데이터 악력D : kg 오즈비 확인하기 # /data/body.csv의 로지스틱 회귀 결과에서 np.exp(result.params['Q("악력D : kg")'])를 round(..., 3)으로 출력해 보세요. import pandas as pd import numpy as np import statsmodels.formula.api as smf data_path = "/data/body.csv" df = pd.read_csv(data_path) df["is_male"] = (df["측정회원성별"] == "M").astype(int) # 가이드: result를 만든 뒤 악력D : kg 계수에 np.exp()를 적용하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 19. pokemon 데이터 Total 오즈비 확인하기 # /data/pokemon.csv의 로지스틱 회귀 결과에서 np.exp(result.params["Total"])을 round(..., 3)으로 출력해 보세요. import pandas as pd import numpy as np import statsmodels.formula.api as smf data_path = "/data/pokemon.csv" df = pd.read_csv(data_path) df["is_legendary"] = df["Legendary"].astype(int) result = smf.logit("is_legendary ~ Total + Speed", data=df).fit(disp=False)
sm.Logit()도 sm.OLS()처럼 class API입니다. 그래서 입력값 X와 정답 y를 따로 만들고, sm.add_constant()로 상수항을 추가해야 합니다.
실행 결과:
Total 계수가 양수라서 총합 능력치가 클수록 전설일 확률이 커지는 방향으로 학습되었다고 볼 수 있습니다. 반면 Speed의 pvalue가 0.064라서, 이 예시에서는 0.05 기준으로 아주 강하게 유의하다고 보기는 어렵습니다.
이런 식으로 statsmodels는 단순 예측뿐 아니라 “어떤 변수가 더 의미 있어 보이는가”를 읽는 데 강합니다.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 20. pokemon 데이터 상수항 열 확인하기 # X = sm.add_constant(df[["Total", "Speed"]])를 만든 뒤 첫 번째 열 이름을 출력해 보세요. # 가이드: add_constant()를 적용한 뒤 첫 열 이름을 확인하세요. import pandas as pd import statsmodels.api as sm data_path = "/data/pokemon.csv" df = pd.read_csv(data_path) df["is_legendary"] = df["Legendary"].astype(int)
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 21. pokemon 데이터 Total 계수 확인하기 # sm.Logit() 결과에서 Total 계수를 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/pokemon.csv" df = pd.read_csv(data_path) df["is_legendary"] = df["Legendary"].astype(int) # 가이드 # 1. X와 y를 만든 뒤 sm.Logit(y, X).fit(disp=False)를 실행하세요. # 2. Total 계수를 round(..., 3)으로 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 22. pokemon 새 데이터 예측 확률 확인하기 # Total=600, Speed=110일 때 전설일 확률을 round(..., 3)으로 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/pokemon.csv" df = pd.read_csv(data_path) df["is_legendary"] = df["Legendary"].astype(int) # 가이드 # 1. X와 y를 만든 뒤 result를 학습하세요. # 2. 새 데이터를 만들고 add_constant(..., has_constant="add")를 적용하세요. # 3. 예측 확률을 round(..., 3)으로 출력하세요.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 23. Speed의 pvalue 해석하기 # pokemon 데이터 sm.Logit() 결과에서 Speed의 pvalue가 0.05보다 큰지 출력해 보세요. import pandas as pd import statsmodels.api as sm data_path = "/data/pokemon.csv" df = pd.read_csv(data_path) df["is_legendary"] = df["Legendary"].astype(int) # 가이드: result를 만든 뒤 Speed pvalue를 0.05와 비교하세요.
로지스틱 회귀는 기본적으로 확률을 예측합니다. 하지만 분류 문제에서는 마지막에 그 확률을 0과 1, 혹은 클래스 이름으로 나누어야 할 때가 많습니다. 가장 흔한 기준은 0.5입니다.
실행 결과:
0.5 이상이면 1, 작으면 0으로 바꾼 결과입니다. 이 기준은 가장 흔하지만, 항상 정답은 아닙니다. 문제에 따라 0.3이나 0.7 같은 다른 기준을 쓰기도 합니다.
외부 데이터 예측 확률을 같은 방식으로 나누면 아래처럼 읽을 수 있습니다.
실행 결과:
첫 번째 값은 남성으로 분류되고, 두 번째 값은 전설 포켓몬이 아닌 쪽으로 분류됩니다.
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 24. 확률 배열을 0과 1로 바꾸기 # probs가 0.5 이상이면 1, 아니면 0으로 바꾼 리스트를 출력해 보세요. # 가이드: 비교 결과를 정수형으로 바꿔 보세요. import numpy as np probs = np.array([0.2, 0.51, 0.8, 0.49])
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 25. body 예측 확률을 0과 1로 바꾸기 # prob = 0.992를 0.5 기준으로 0과 1 중 하나로 바꿔 출력해 보세요. # 가이드: int(prob >= 0.5) 형태를 떠올려 보세요. prob = 0.992
에디터 로딩 중...
코드 입력 환경을 준비하고 있습니다.
# 문제 26. pokemon 예측 확률을 0과 1로 바꾸기 # prob = 0.388을 0.5 기준으로 0과 1 중 하나로 바꿔 출력해 보세요. # 가이드: 0.5보다 큰지 비교한 뒤 정수로 바꿔 보세요. prob = 0.388