통계

비즈니스 의사결정에 사용하는 수학적 원리

장수우 2025. 12. 23. 12:03

1. 선형회귀(Linear) vs 이차회귀(Quadratic)

선형회귀 : y = Ax + b

  • 이 식은 x 가 오를 때마다 y가 일정한 비율(A)으로 계속 증가하거나 감소한다고 가정합니다.
  • 다만 실제 세상에서는, 적당히 따뜻할 땐 좋지만, 너무 더우면 나가기 싫어지는 현상을 설명하지 못합니다 (변곡점)

이차회귀 : y = Ax^2 + Bx + C

  • 그래서 이런 변곡점을 설명하기 위해 X의 제곱항을 추가한 다항 회귀식을 만듭니다.
  • 여기서 A는 그래프의 곡률(Curvature)을 결정하는 핵심 변수 입니다.
  • A가 음수나 양수에 따라 이차함수의 그래프 모양이 달라집니다.
  • A > 0  = 아래로 볼록한(U자) 모양
  • A < 0 = 위로 볼록한(∩자) 모양 : 특정 지점까지 증가하다가 정점을 찍고 내려오는 모양
  • 따라서 이차항의 계수(A)가 음수이면서 통계적으로 유의미(p < 0.05)하다는 결과가 나오면
  • 수학적으로 이 데이터는 위로 볼록한 포물선 형태를 띄며, 수요가 꺽이는 지점(peak)가 존재한다 라고 결론을 내릴 수 있다.

2. 최적(peak)찾기 : 미분활용

  • 그래서 어느 지점일때 가장 수요가 많을까? 라는 질문에 답하기 위해서는 미분 시 기울기가 0이 되는 지점을  찾으면 됩니다.
  • A < 0 이면, 최대값이 존재, A > 0 이면 최소값만 존재
  • 최대값(or 최소값)이 발생하는 x 좌표는 다음 꼭지점 공식으로 계산이 가능합니다.
    • x(peak) = - B / 2A
  • 피크의 y 좌표 계산
    • y(peak) = A(x peak)^2 + B(x peak) + C
  • 예시
    • y = -2x^2 + 4x +3
    • A = -2, B = 4 , C = 3
    • x peak = - (4/ 2 *-2) = 1
    • y peak = -2(1)^2 + 4(1) + 3 = 5
    • 이 모델의 피크는 (1, 5) 지점에서 발생

3. 타겟 변수의 분포 분석 및 정규화

 : 우리가 예측하려는 정답이 어떻게 생겼는가? 를 확인하는 단계

  • 왜 이 작업을 하나요?
    • 선형 회귀 모델(Linear Regression)은 기본적으로 오차(Residual)가 정규분포를 따른다는 가정을 바탕으로 설계되었습니다.
    • 만약 타겟 변수가 한쪽으로 심하게 치우져 있다면, 모델은 데이터가 많은 쪽에만 편향되어 학습하게 되고,
    • 높은 수요가 발생하는 피크 타임을 제대로 예측하지 못하게 됩니다.
  • 무엇을 확인해야 하나요?
    • 왜도(Skewness) : 데이터 분포가 얼마나 비대칭인가를 나타냅니다.
    • 0에 가까우면 정규분포와 비슷합니다.
    • 양수(> 0)면 왼쪽으로 쏠린 형태, 즉 오른쪽으로 긴 꼬리 형태입니다.
    • 첨도(Kurtosis) : 데이터 분포가 얼마나 뾰족한가를 나타냅니다.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from scipy.stats import skew

# 데이터 로드
df = pd.read_csv('train.csv')

# 1. 시각화: 히스토그램과 커널 밀도 추정(KDE)
plt.figure(figsize=(12, 5))
sns.histplot(df['count'], kde=True)
plt.title(f"Original Demand Distribution (Skewness: {skew(df['count']):.2f})")
plt.show()

이런 차트가 나옵니다.

4. 로그 변환(Log Transformation)

 위의 그래프를 보면 0 근처에 데이터가 몰려있고 오른쪽으로 긴 꼬리가 있죠 이를 해결하기 위해 로그 변환을 사용합니다.

  • y' = log(1 + y) : 1을 더하는 이유는 count가 0인 경우 log(0)이 정의되지 않기 때문입니다.
  • 로그 변환을 사용하면 큰 값들 사이의 간격은 좁히고, 작은 값들 사이의 간격을 넓혀서 분포를 종 모양(정규분포)에 가깝게 만듭니다.
    # 2. 로그 변환 적용
    df['count_log'] = np.log1p(df['count'])
    
    # 변환 후 시각화
    plt.figure(figsize=(12, 5))
    sns.histplot(df['count_log'], kde=True, color='green')
    plt.title(f"Log Transformed Distribution (Skewness: {skew(df['count_log']):.2f})")
    plt.show()


  • 로그 변환하는 이유
    • 비대칭의 압축 :
      로그를 취하면 1000은 3, 10000 은 4 로 변환되기 때문에(상용로그 기준), 멀리 도망가 있던 극단치들을 중앙으로 강하게
      끌어당기는 효과가 있습니다.
    • 곱셈을 덧셈으로:
      자연 현상이나 경제 데이터는 전년대비 "몇  % 성장"과 같은 곱셈 단위로 움직이는 경우가 많습니다. 로그는 이러한
      지수적 증가(Exponential)을 선형적 증가(Linear)로 바꿔 줍니다.
      원리 :
      log(A * B) = log(A) + log(B)
      이점 :
      - 복잡한 곱셈 연산을 다순한 덧셈 연산으로 바꿔준다. 변수들이 결합해 수요를 어떻게 변화시키는지 쉽게 계산가능
      - 선형 모델에서는 계수를 X 가 1단위 증가할 때 Y가 몇 단위 증가한다로 해석하지만 로그변환 시
      - 해석의 단위가 비율(%)로 바뀝니다.
      - 5대 라는 절대 수치 보다 5% 증가 라는 성장률 단위가 예산 책정이나 공급 계획 수립에 훨씬 직관적이고 유용합니다.
    • 등분산성 확보:
      수요량이 처질수록 그 변동 폭(오차)도 커지는 경향이 있는데, 로그 변환은 이 변동 폭을 일정하게 만들어 줍니다.
      이는 회귀 분석의 핵심 가정인 '등분산성'을 만족시키는데 도움을 줍니다.


  • 왜도(skewness)가 어느 정도여야 심각하다고 판단하나요?
    • 통용되는 외도의 판단 기준(Thumb rule)
      1. -1 < 왜도 값 < +1 : 데이터 분포가 정규 분포에 가깝고 양호한 것
      2. -2 < 왜도 값 < +2 : 일반적으로 허용 가능한 수준
      3. -2 > 왜도 값 > + 2 : 분포가 심하게 비대칭(비정규)임을 시사합니다

5. 집단 간 차이 검정(Independent T-test)

왜 T-test를 하나요? (Statistical Reason)

- 직관적으로 나오는 추측을 데이터의 평균값만 보고 결론을 내리면 위험합니다.
- 표본의 크기와 데이터의 변동성(분산)을 고려했을 때, 그 차이가 오차 범위를 벗어날 만큼 통계적으로 유의미 한지 확인하기 위해
- T-test를 사용합니다.

 

가설 설정 (Hypothesis)

  • 귀무가설(H0) : 평일의 평균 대여량과 주말의 평균 대여량은 같다. (차이가 없다.)
  • 대립가설(H1) : 평일의 평균 대여량과 주말의 평균 대여량은 다르다. (차이가 있다.)
    import pandas as pd
    import numpy as np
    import seaborn as sns
    import matplotlib.pyplot as plt
    from scipy import stats
    
    # 1. 데이터 준비 (1단계에서 만든 로그 변환 데이터 사용 권장)
    workingday_demand = df[df['workingday'] == 1]['count']
    weekend_demand = df[df['workingday'] == 0]['count']
    
    # 2. 시각화: 두 집단의 평균 차이 확인
    plt.figure(figsize=(8, 6))
    sns.barplot(x='workingday', y='count', data=df)
    plt.title('Average Demand: Working Day (1) vs Weekend/Holiday (0)')
    plt.xticks([0, 1], ['Weekend/Holiday', 'Working Day'])
    plt.show()
    
    # 3. T-test 수행 (등분산 가정은 False로 설정하여 더 보수적으로 검정 - Welch's T-test)
    t_stat, p_val = stats.ttest_ind(workingday_demand, weekend_demand, equal_var=False)
    
    print(f"--- T-test Result ---")
    print(f"T-statistic: {t_stat:.4f}")
    print(f"P-value: {p_val:.4e}")
    
    # 4. 결과 해석
    if p_val < 0.05:
        print("\n결론: P-value가 0.05보다 작으므로 귀무가설을 기각합니다.")
        print("즉, 평일과 주말의 대여량은 통계적으로 유의미한 차이가 있습니다.")
    else:
        print("\n결론: P-value가 0.05보다 크므로 귀무가설을 채택합니다.")
        print("즉, 평일과 주말의 대여량 차이는 통계적으로 유의미하지 않습니다.")

  • T-statistic : 이 값이 클수록 두 집단의 평균 차이가 크다는 뜻입니다.
  • P-value : 이 차이가 우현이 일어날 확률 입니다 

t-test는 전체 평균을 비교하기 때문에 이런 오류가 생기진 않았는지 주말과 평일을 분리하는 작업을 거치도록 하겠습니다.

가설 재설정 : 평일과 주말의 전체 평균 대여량은 통계적으로 차이가 없으니 시간대에 따른 수요의 패턴은 통계적으로 유의미하게 다를 것이다.

평일(1, 주황색)이 쌍봉형 분포이고 주말(0, 파란색)이 완만한 단봉형 분포입니다.

평일의 출퇴근 시간과 주말의 낮 시간 집중 수요가 합쳐져 하루 전체의 평균이 비슷해져 평균의 함정에 빠지게되었습니다.

 

6. 다중 회귀 분석(Multiple Regression Analysis)

단순히 온도가 높으면 수요가 많다는 결과는 위험할 수 있습니다. 온도가 높은 시간이
마침 퇴근 시간이라 수요가 많았던게 아닐까요?

  • 통제 : 시간대와 근무일의 영향을 통계적으로 고정(통제)한 상태에서 순수하게 온도 만의 영향력을 변별할 수 있습니다.
  • 상대적 중요도 파악 : 변수 중 어떤 녀석이 상대적으로 중요한지 파악 가능합니다.
import pandas as pd
import numpy as np
import statsmodels.api as sm
from sklearn.preprocessing import StandardScaler

# 1. 분석에 사용할 변수 선택
# 앞서 만든 'hour' 변수가 포함되어 있어야 합니다.
features = ['temp', 'hour', 'workingday']
X = df[features].copy()
y = df['count']

# 2. 표준화 (Standardization)
# 변수들의 단위를 맞춰서 계수(Beta)의 크기로 영향력을 직접 비교하기 위함입니다.
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_scaled = pd.DataFrame(X_scaled, columns=features)

# 3. 상수항(절편) 추가 및 모델 적합
X_scaled = sm.add_constant(X_scaled)
multi_model = sm.OLS(y, X_scaled).fit()

# 4. 결과 출력
print(multi_model.summary())

  • [A] 모델의 전체적인 건강 상태 (Goodness of Fit)
    • Dep. Variable (Dependent Variable) : 종속 변수
    • R-squard (R^2) : 모델의 설명력
    • Adj.R-squared (수정된 결정계수) :
      변수가 많아지면 R^2 값이 억지로 올라가는데 이걸 보정한 값
      보통 이 값을 더 선호
      이 모델이 전체 수요 변동의 몇 %를 설명하는가 (0.3 이상이면 유의미)
    • F-statistic & Prob (F-statistic) : 모델 전체가 통계적으로 유의미 한지 묻는 시험
      - Prob < 0.05 = 이 모델은 쓸모가 있다.
      - 예약 데이터를 얼마나 정확하게 복제 해냈는지를 보여줌
    • Log-Likelihood (로그 우도)
      - 모델이 현재의 데이터를 얼마나 잘 나타내는지에 대한 확률 값의 로그치
      - 값이 클수록 (0에 가까울 수록) 모델의 적합도가 좋다는 뜻, 변수 추가하면 보통 올라감
    • AIC (Akaike Information Criterion)
      - 적합도와 단순함사이의 균형을 잡는 지표 ( AIC = -2 * log(L) + 2k)
      - 낮을수록 좋습니다. 변수를 너무 많이 넣어 모델이 불필요하게 복잡해지면 패널티를 부여
      - 모델이 얼마나 가성비 있는지를 판단 
    • BIC (Bayesian Information Criterion)
      - AIC 와 비슷하지만 변수 개수에 대해 AIC 보다 더 엄격한 패널티를 줍니다.
      - 낮을 수록 좋고 데이터 양이 많을 수록 BIC를 더 신뢰하기도 합니다.
      - 변수를 10개써서 예측력을 1% 올리느냐, 핵심 변수 3개만 쓰는게 나은가? 를 판단하게 해줌
    • DF Resiudals
      - 잔차의 자유도 : 전체 관측치 수(No. Observation) - 모델이 사용한 변수 개수(Df Model) - 1(절편)
      - 가지고 있는 데이터 중 모델을 만드는 데 소비하고 남은 여유분, 값이 클 수록 통계적 검정(t-test, F-test)결과가
        안정적이고 신뢰할 수 있다.
    • Covariance Type
      - 회귀 계수의 오차 (분산 - 공선성 매트릭스)를 계산하는 방시
      - nonorobust (비강건성) : 가장 기본적인 방식, 오차의 분산이 모든 데이터에서 일정하다는 등분산성 가정을 전제
        만약 데이터에 이질성이 크다면 robust 옵션으로 바꿔서 분석하기도 합니다.
      - nonorobust 사용 시 분석은 오차들이 평등하게 퍼져 있다고 가정하고 진행
  • [B] 변수별 성적표
    • P-value : 0.05 보다 작은 변수들은 유의미한 영향
    • Coefficient (계수, coef) : 기울기(계수), 독립변수가 1단위 변할 때 종속변수가 얼마나 변하는지 보여줍니다.
      표준화된 계수, 절댓값이 가장 큰 변수가 수요에 가장 큰 영향을 미친다.
    • std err(standard Error) : 계수의 오차 범위, 작을수록 계수가 정확하게 추정되었다는 뜻
    • t-value  : coef / std err 값 : 클 수록 해당 변수의 영향력이 확실하다는 증거
  • [C] 잔차 분석 - 모델의 약점 진단
    • Omnibus / Jarque-Bera (JB) :
      잔차가 정규분포를 따르는지 확인. 값이 너무 크거나 0에 가까우면 잡지 못한 패턴이 있다는 증거
    • Skew(왜도) : 잔차 분포의 비 대칭도
    • Kurtosis(첨도) : 분포의 뾰족한 정도 : 3에 가까울 수록 정규분포와 흡사합니다.
    • Durbin-Watson : 잔차들 사이에 상관관계(자기상관)이 있는지 봅니다. 2에 가까울 수록 좋습니다.

OLS 모델은 상반된 패턴을 하나의 직선으로 설명하기 때문에 workingday 가 상쇄되어 영향이 없다고 오류를 범했습니다.

R^2 값을 보니 모델이 수요 변동의 27.6%만 설명하고 있다는 뜻이고 Skew(왜도) : 1.297 이기 때문에 심각하게 치우친 데이터 라는 뜻입니다. count 대신 count에 로그변환을 해 다시 돌려보면

  • R^2 : 0.276 -> 0.414
    - 모델의 설명력이 약 50% 개선되었습니다.
  • Skew : 1.297 -> -0.322
    - 절대값이 0.5 미만이면 거의 대칭임으로 통계적으로 매우 안정한 상태입니다.
  • workingday 의 P 값이 0.968 -> 0 의미있는 값으로 변환됐습니다.
  • workingday의 coef 값이 음수로 바뀌었습니다. 이는 시간과 기온이 동일하다면, 주말(0)의 평균적인 수요 베이스가 평일(1)보다 약간 더 높음을 시사합니다. 

7. 상호작용 항(Interaction Term) 을 추가한 모델 고도화

  • 두 개 이상의 독립변수가 서로 영향을 주고받으며 반응변수에 미치는 효과가 단순 합산 이상으로 달라지는 현상을 모델링하기 위해 추가하는 항
  • 두 변수를 곱한 형태(X1 * X2)로 나타나면 한 변수의 영향력이 다른 변수의 수준에 따라 변하는 조절효과를 검증하는 핵심 요소
  • 지금 데이터를 기준으로 Hour * Workingday 를 기준으로 새로운 회귀식을 만듭니다
  • count_log = b0 + b1 * temp + b2 * hour + b3 * workingday + b4 * (hour * workingday)
  • 이전 모델이 hour의 영향력이 workingday에 상관없이 항상 일정하다고 가정했을 때 평일 8시 수요가 치솟고 주말은 낮에 치솟았습니다.
  • b4의 역할 : 근무일 여부에 따라 시간대의 영향력이 얼마나 변하는가를 측정해, 이 값이 유의미 하게 나오면 두 변수가 서로
    시너지나 상쇄 효과를 내고있다는 통계 근거를 만듭니다.
import pandas as pd
import numpy as np
import statsmodels.api as sm
from sklearn.preprocessing import StandardScaler

# 1. 상호작용 항 생성 (시간 * 근무일)
# workingday가 1인 경우에만 hour 값이 살아남고, 0인 주말에는 0이 됩니다.
df['hour_interaction'] = df['hour'] * df['workingday'].astype(int)

# 2. 분석 변수 설정 (기존 변수 + 상호작용 항)
features = ['temp', 'hour', 'workingday', 'hour_interaction']
X = df[features].copy()
y = np.log1p(df['count']) # 1단계에서 배운 로그 변환 적용

# 3. 표준화 (비교를 위해 필수)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_scaled = pd.DataFrame(X_scaled, columns=features)
X_scaled = sm.add_constant(X_scaled)

# 4. 모델 적합 및 결과 출력
interaction_model = sm.OLS(y, X_scaled).fit()
print(interaction_model.summary())

  • R^2 : 0.414 -> 0.421 : 변수를 단 하나 추가했을 뿐인데 모델의 설명력이 향상되었습니다.
  • AIC/BIC 감소 : 모델이 복잡해졌음에도 불구하고 정보 손실 지표인 AIC, BIC가 모두 낮아졌습니다.
  • 통계적 유의성 : P >|t| 값이 0으로 매우 유의합니다. 평일과 주말의 시간대별 수요 패턴 차이는 우연이 아닌 인과관계가 있습니다.
  • hour (0.5849) : 주말 (=0)에 시간이 1단위 증가할 때 로그 대여량이 늘어나는 속도
  • workingday(-0.2228) : 시간이 0일 때(심야), 평일이 주말보다 기본 수요 베이스가 낮음을 의미
  • hour_interaction(0.2718) : 평일이 되면 시간이 흐름에 따른 수요 증가 속도가 주말보다 약 0.27 만큼 가팔라짐
  • 해석 : 평일은 주말보다 시작은 낮게 출발하지만, 시간이 지날수록 수요가 훨씬 더 급격하게 치솟는 패턴을 보인다.

한정된 자원을 어디에 우선 투입할지 결정하는 기준이 됩니다.

1. 시간대별 탄력적 배치 : 평일의 시간당 수요 증가율 (0.58 + 0.27 = 0.85)이 주말 (0.58)보다 약 46% 높습니다.

2. 기온 (temp) 기반 가중치 적용 : 예상 수요에 기온 가중치를 곱하여 공급량을 상향 조정해야 합니다.

3. workingday의 음수 계수는 역설적으로 주말 심야 수요가 평일보다 높음을 시사합니다.

 

 

8. 날씨 변수와 더미 변수 적용

import pandas as pd
import numpy as np
import statsmodels.api as sm
from sklearn.preprocessing import StandardScaler

# 1. 날씨 변수를 더미 변수로 변환 (One-Hot Encoding)
# drop_first=True를 통해 '맑음(1)'을 기준점으로 설정합니다.
weather_dummies = pd.get_dummies(df['weather'], prefix='weather', drop_first=True)

# 2. 분석 데이터 결합
# 기존 변수들 + 상호작용 항 + 날씨 더미 변수
df_final = pd.concat([df, weather_dummies], axis=1)

features = ['temp', 'hour', 'workingday', 'hour_interaction', 'weather_2', 'weather_3', 'weather_4']
X = df_final[features].copy()
y = np.log1p(df_final['count']) # 로그 변환 유지

# 3. 표준화 및 모델 적합
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
X_scaled = pd.DataFrame(X_scaled, columns=features)
X_scaled = sm.add_constant(X_scaled)

weather_model = sm.OLS(y, X_scaled).fit()
print(weather_model.summary())

  • 날씨 변수를 추가하여 R^2 값이 0.421 -> 0.432로 증가했습니다. AIC 값도 220정도 감소해 모델의 효율성이 좋아졌음을 알 수 있습니다.
  • weather_3 (-0.1521)
    : 맑은 날(기준)대비, 눈이나 비가 오는날(weather_3)은 수요가 통계적으로 매우 유의미하게 (p = 0) 감소합니다.
    - 로그 스케일에서 -0.15라는 수치는 대략 전체의 15%를 증발 시킬 수 있는 강한 하방 압력
  • weather_2, weather_4
    : p-value가 0.661, 0.724로 매우 높습니다. 
    - 흐린 날(weather_2) 은 맑은 날과 수요 차이가 거의 없음을 의미하며, 악천후(weather_4)는 데이터셋 내에 표본 수가 너무 적어 통계적 유의성을 확보하지 못한 것으로 추정
  • 해석 :
    - weather3 = 비 예보가 있을 경우 수요가 약 15% 감소할 것, 이때 세차 및 경정비 주기를 우천 시간대로 집중 배치하여 맑은 날의 가동률을 올리는 정비 스케줄링이 필요합니다.
    - 다만 날씨가 변해도 hour_interaction (0.2581)의 유의성은 유지됩니다. = 비가와도 출근은 해야한다.

9. 모델의 약점 진단 : 잔차분석

모델에서 현재 Durbin-Watson 지수가 0.502로 여전히 낮아 이는 잔차들 사이에 어떤 시간적 흐름이 남아 있다는 신호

import matplotlib.pyplot as plt
import seaborn as sns

# 1. 잔차(Residuals) 계산
df['predicted'] = weather_model.predict(X_scaled)
df['residuals'] = y - df['predicted']

# 2. 잔차 시각화 (시간 흐름에 따른 잔차)
plt.figure(figsize=(15, 6))
plt.subplot(1, 2, 1)
sns.scatterplot(x=df['predicted'], y=df['residuals'], alpha=0.3)
plt.axhline(0, color='red', linestyle='--')
plt.title('Residuals vs Predicted')

plt.subplot(1, 2, 2)
sns.histplot(df['residuals'], kde=True)
plt.title('Residual Distribution')
plt.show()

  • 이 두 그래프는 모델의 신뢰성을 최종적으로 검증하는 척도입니다.
  • 잔차분포(Residual Distribution) : 우측 히스토그램을 보면 0을 중심으로 아주 예쁜 종 모양(Bell-curve) 모양을 띄고 있어 모델이 예측한 오차가 특정방향으로 쏠리지 않고 정규분포를 따른다는 것으로, 로그 변환이 매우 효과적이었다는 것을 증명
  • 예측값 vs 잔차 : 좌측 산점도에서 데이터가 0을 중심으로 위아래로 넓게 퍼져있습니다.
    다만 좌측 하단에 대각선 형태의 패턴이 보이는데 이는 카운트 데이터(정수형)의 특성이 반영된 것
  • 다만 Durbin-Watsion 지수가 낮고, 산점도에서 보이는 미세한 패턴은 아직 시간적 흐름을 다 반영하지 못했음을 의미합니다.

10. 추천하는 비즈니스 중심 구성 순서

  1. 요약 및 핵심 결론(Executive Summary)
    : 결론을 보여주고, 통합 시각화 대시보드 배치
  2. 비즈니스 가설(Business Hypotheses)
    : 왜 이 분석을 하는가? 에 대한 답
  3. Visual Storytelling - EDA
    : 코드는 간결하게, 그래프가 주인공
  4. 통계적 신뢰도
  5. 액션 플랜
반응형

'통계' 카테고리의 다른 글

HHI(HerfindahI-Hirschmana Index)  (1) 2026.01.01
MinMaxScaler  (0) 2025.12.24
가설검정에 사용하는 분포 선택 및 통계적 검정법  (1) 2024.11.06