Motivation
- **Zero-Inflated Negative Binomial Model(ZINB)**은 데이터셋에서 0 값이 과도하게 나타나는 경우에 이를 효과적으로 다루기 위해 개발된 모델입니다. 이 모델은 두 가지 과정을 결합하여 0 값이 생성되는 메커니즘과 실제 데이터 분포(음이항 분포)를 동시에 설명합니다
- 일반적인 음수 이항 분포(Negative Binomial)는 과도한 분산(overdispersion)을 다루는 데 적합하지만, 0 값이 비정상적으로 많이 관측되는 데이터에서는 성능이 저하됩니다. 예를 들어:
- 의료 데이터에서 병원 방문 횟수: 많은 사람들이 병원에 전혀 가지 않음.
- 소셜 미디어 활동 데이터: 대다수의 사용자가 특정 활동(예: 게시물 작성)을 하지 않음.
모델구조
- ZINB 모델은 두 가지 과정으로 구성됩니다:
- Zero Process (이항 분포)
- 데이터가 0이 되는지를 결정하는 과정.
- 로지스틱 회귀를 통해 특정 요인이 0 값을 발생시키는 확률을 추정합니다.
- Count Process (음이항 분포)
- 데이터가 0이 아닌 값(즉, 실제 카운트 값)을 생성하는 과정.
- 음 이항 분포를 사용하여 과도한 분산을 처리합니다.
- 음이항 분포는 **이항 분포(Binomial Distribution)**를 일반화한 것으로, 성공/실패와 같은 이진 사건이 반복될 때, r번의 성공이 발생하기까지의 실패 횟수를 확률적으로 모델링하는 분포
- 이 두 과정은 다음과 같은 확률로 결합됩니다.$$P(Y = y) = \begin{cases} \pi + (1 - \pi) \cdot NB(y=0) & \text{if } y = 0, \ (1 - \pi) \cdot NB(y) & \text{if } y > 0, \end{cases}$$
- $\pi$: Zero Process에서 데이터가 0이 될 확률.
- $NB(y$): 음수 이항 분포의 확률 밀도 함수.
장점
- 과도한 0 처리: ZINB 모델은 데이터의 과도한 0을 효과적으로 다룹니다.
- 과도한 분산 지원: 음수 이항 분포를 사용하므로, 분산이 크거나 불균형한 데이터에 적합합니다.
- 해석 가능성: Zero Process와 Count Process를 분리하여, 0 값의 생성 메커니즘과 실제 데이터의 패턴을 각각 이해할 수 있습니다.
단점
- 모델 복잡성: 두 개의 프로세스를 결합하므로, 매개변수 추정이 복잡하고 계산 비용이 높아질 수 있습니다.
- 과적합 위험: 복잡한 구조 때문에, 특히 데이터가 적을 경우 과적합 위험이 존재합니다.
- 해석의 어려움: 두 개의 과정이 결합되어 있으므로, 각 프로세스의 영향을 분리하여 해석하는 것이 까다로울 수 있습니다.
대안
- Zero-Inflated Poisson(ZIP): ZINB와 유사하지만, Count Process에 음수 이항 분포 대신 포아송 분포를 사용합니다. 데이터에 과도한 분산이 없다면 ZIP 모델이 더 간단하고 적합합니다.
- Hurdle Model: 0 값을 포함한 데이터를 처리하지만, Zero Process와 Count Process를 명확히 분리하여 분석합니다.
예시
import numpy as np
import pandas as pd
import statsmodels.api as sm
from statsmodels.discrete.count_model import ZeroInflatedNegativeBinomialP
import matplotlib.pyplot as plt
# 데이터 생성
np.random.seed(42)
n_reviews = 1000
# Zero Process: 리뷰가 유용하지 않은 경우
zero_prob = 0.9 # 유용하지 않은 리뷰의 비율
is_not_useful = np.random.binomial(1, zero_prob, size=n_reviews)
# Count Process: 유용한 리뷰 점수 (0보다 큰 값)
count_mean = 2 # 평균 점수
useful_scores = np.random.negative_binomial(2, 0.5, size=n_reviews)
# 리뷰 유용성 데이터 생성
review_usefulness = is_not_useful * 0 + (1 - is_not_useful) * useful_scores
# 데이터 프레임 생성
data = pd.DataFrame({
'review_id': range(1, n_reviews + 1),
'usefulness': review_usefulness,
'review_length': np.random.randint(20, 300, size=n_reviews), # 리뷰 길이
'positive_words': np.random.randint(0, 20, size=n_reviews) # 긍정 단어 개수
})
# 데이터 분포 확인
plt.hist(review_usefulness, bins=20, alpha=0.7, color='blue')
plt.title("Review Usefulness Distribution")
plt.xlabel("Usefulness Score")
plt.ylabel("Frequency")
plt.show()
# ZINB 모델 적합
exog = sm.add_constant(data[['review_length', 'positive_words']]) # 외생 변수 (리뷰 길이, 긍정 단어 수)
zinb_model = ZeroInflatedNegativeBinomialP(
data['usefulness'],
exog,
exog_infl=exog,
inflation='logit'
)
result = zinb_model.fit()
# 결과 출력
print(result.summary())
# 예측값 생성
data['predicted_usefulness'] = result.predict(exog)
# 예측값과 실제값 비교
plt.scatter(data['usefulness'], data['predicted_usefulness'], alpha=0.5, color='green')
plt.xlabel('Actual Usefulness')
plt.ylabel('Predicted Usefulness')
plt.title('Actual vs Predicted Usefulness')
plt.grid()
plt.show()