[Review] 패널데이터와 고정효과
포스트
취소

[Review] 패널데이터와 고정효과

본 글은 Korea Summer Workshop on Causal Inference 2023의 내용을 주관적으로 정리한 글입니다. 추가적인 설명이 필요한 분들을 위해 원래 영상 링크를 같이 첨부합니다.

Week 3. Regression and Matching

3-3. How Fixed Effects Work for Causal Inference

예시. 모바일 푸쉬 알림의 구매 효과

  • 데이터
Customer IDDayPush NotificationPurchase AmountGenderAgeAddressDummy 1Dummy 2Dummy 3
11050 + 0120Area A100
12150 + 20 = 70120Area A100
13150 + 20 = 70120Area A100
21010 + 0021Area A010
22110 + 20 = 30021Area A010
23110 + 40 = 50021Area A010
31030 + 0122Area B001
32030 -10 = 20122Area B001
33030 -20 = 10122Area B001
  • 주어진 데이터는 Customer ID와 Day로 구성된 패널 데이터의 예시.
    • Customer ID는 ‘Panel unit ID’를 나타냄.
      • ID 1 & 2 = Treatment Group
      • ID 3 = Control Group
    • Day는 ‘Time’에 대한 변수.
  • Push Notification는 ‘Treatment’에 대한 변수.
  • Purchase Amount는 ‘Outcome’에 대한 변수.
  • Gender, Age, Address는 ‘Time-invariant covariates’를 의미.
    • 분석 기간 내로 기간을 한정했을 때, 시간에 따라 값이 변하지 않으므로 ‘time-invariant’한 변수로 볼 수 있음.
  • Dummy 1~3은 ‘Unit Fixed effects’를 의미.
    • Unit Fixed effects를 통해 모든 time-invariant covariates를 설명할 수 있음 (= perfectly collinear).
    • 또한 Purchase Amount는 time-varying한 요소지만, 유닛 별 baseline(e.g. 구매력)과 같은 time-invariant한 요소도 Unit Fixed effects에 흡수됨.

Q&A

  • What is the treatment effect without unit fixed effects?
    • A. 31
    • 프로그램 실행 결과
      1
      2
      3
      4
      5
      6
      7
      
        # Call:
        #   lm(formula = Purchase.Amount ~ Push.Notification, data = data)
        # Coefficients:
        #   Estimate Std. Error t value Pr(>|t|)  
        #   (Intercept)         24.000      7.964   3.013   0.0196 *
        #   Push.Notification   31.000     11.946   2.595   0.0357 *
        #   ---
      
  • What is the treatment effect with customer fixed effects?
    • A. 25
    • 프로그램 실행 결과
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        # Call:
        #   lm(formula = Purchase.Amount ~ Push.Notification + Dummy.1 + 
        #        Dummy.2 + Dummy.3, data = data)
        # Coefficients: (1 not defined because of singularities)
        # Estimate Std. Error t value Pr(>|t|)  
        #   (Intercept)         20.000      5.375   3.721   0.0137 *
        #   Push.Notification   25.000      8.062   3.101   0.0268 *
        #   Dummy.1             26.667      9.309   2.864   0.0352 *
        #   Dummy.2             -6.667      9.309  -0.716   0.5060  
        #   Dummy.3                 NA         NA      NA       NA  
        # ---
      
  • What is the treatment effect with customer fixed effects, but without untreated units?
    • A. 25
    • 프로그램 실행 결과
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      
        # Call:
        #   lm(formula = Purchase.Amount ~ Push.Notification + Dummy.1 + 
        #        Dummy.2 + Dummy.3, data = filtered_data)
        # Coefficients: (2 not defined because of singularities)
        # Estimate Std. Error t value Pr(>|t|)  
        #   (Intercept)         13.333      7.201   1.852   0.1612  
        #   Push.Notification   25.000      7.638   3.273   0.0467 *
        #   Dummy.1             33.333      7.201   4.629   0.0190 *
        #   Dummy.2                 NA         NA      NA       NA  
        #   Dummy.3                 NA         NA      NA       NA  
        # ---
      
  • What is the treatment effect with both customer and day fixed effects?
    • A. 40
    • 프로그램 실행 결과
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      
        # Call:
        #   lm(formula = Purchase.Amount ~ Push.Notification + Dummy.1 + 
        #        Dummy.2 + Dummy.3 + Dummy.Day.1 + Dummy.Day.2 + Dummy.Day.3, 
        #      data = data)
        # Coefficients: (2 not defined because of singularities)
        # Estimate Std. Error t value Pr(>|t|)  
        #   (Intercept)         16.667      7.698   2.165   0.1190  
        #   Push.Notification   40.000     14.142   2.828   0.0663 .
        #   Dummy.1             16.667     12.172   1.369   0.2644  
        #   Dummy.2            -16.667     12.172  -1.369   0.2644  
        #   Dummy.3                 NA         NA      NA       NA  
        #   Dummy.Day.1         13.333     12.172   1.095   0.3534  
        #   Dummy.Day.2         -3.333      7.698  -0.433   0.6942  
        #   Dummy.Day.3             NA         NA      NA       NA  
        # ---
      
  • What if Customer 1 receives the treatment at Day 3?
    • Staggered treatment(; unit 마다 treatment 시점이 다른 경우). 나중에 설명 예정.

(참고) R v.s. Python(using statsmodels)

  • 작성한 코드는 아래에 첨부.
  • R과 Python statsmodels 라이브러리 결과 비교 시, 통계학적으로는 R의 결과를 보는 것이 더 적절해 보인다는 개인적인 의견.
    • 예시. 독립변수 = {상수항, Push.Notification, Dummy.1, Dummy.2, Dummy.3}.
      • ‘Dummy.3 = const. - Dummy.1 - Dummy.2’으로 표현될 수 있음(; 선형종속, Linearly Dependent).
      • 독립 변수 행렬 X는 full rank가 아님 = singular matrix (; 통계적으로 not estimable한 parameter 존재).
      • R은 not estimable 파라미터의 추정 결과를 NA로 제공.
      • Python statsmodels OLS는 기본적으로 Moore-Penrose Inverse(;일반화 역행렬의 특정한 형태)를 이용하므로 모든 parameter를 추정함. 하지만 추정량의 표준오차나 신뢰구간 등이 불안정한 결과를 보임.

(참고) R 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# install.packages("dplyr")
library(dplyr)

data <- data.frame(
  Customer.ID = c(1, 1, 1, 2, 2, 2, 3, 3, 3),
  Day = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
  Push.Notification = c(0, 1, 1, 0, 1, 1, 0, 0, 0),
  Purchase.Amount = c(50, 70, 70, 10, 30, 50, 30, 20, 10),
  Gender = c(1, 1, 1, 0, 0, 0, 1, 1, 1),
  Age = c(20, 20, 20, 21, 21, 21, 22, 22, 22)
)

# Case 1. without unit fixed effects
model <- lm(Purchase.Amount ~ Push.Notification, data = data)
summary(model)


# Case 2. with unit fixed effects
data <- data %>%
  mutate(
    Dummy.1 = case_when(Customer.ID == 1 ~ 1, TRUE ~ 0),
    Dummy.2 = case_when(Customer.ID == 2 ~ 1, TRUE ~ 0),
    Dummy.3 = case_when(Customer.ID == 3 ~ 1, TRUE ~ 0)
  )
model <- lm(Purchase.Amount ~ Push.Notification + Dummy.1 + Dummy.2 + Dummy.3, data = data)
summary(model)


# Case 3. with unit fixed effects, but without control gorup
filtered_data = data %>% filter(Dummy.3 != 1)
model <- lm(Purchase.Amount ~ Push.Notification + Dummy.1 + Dummy.2 + Dummy.3, data = filtered_data)
summary(model)


# Case 4. with both customer and day fixed effects
data <- data %>%
  mutate(
    Dummy.Day.1 = case_when(Day == 1 ~ 1, TRUE ~ 0),
    Dummy.Day.2 = case_when(Day == 2 ~ 1, TRUE ~ 0),
    Dummy.Day.3 = case_when(Day == 3 ~ 1, TRUE ~ 0)
  )
model <- lm(Purchase.Amount ~ Push.Notification + Dummy.1 + Dummy.2 + Dummy.3 + Dummy.Day.1 + Dummy.Day.2 + Dummy.Day.3, data = data)
summary(model)

(참고) Python 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import pandas as pd
import statsmodels.api as sm

data = pd.DataFrame({
	'Customer.ID': [1, 1, 1, 2, 2, 2, 3, 3, 3],
	'Day': [1, 2, 3, 1, 2, 3, 1, 2, 3],
	'Push.Notification': [0, 1, 1, 0, 1, 1, 0, 0, 0],
	'Purchase.Amount': [50, 70, 70, 10, 30, 50, 30, 20, 10],
	'Gender': [1, 1, 1, 0, 0, 0, 1, 1, 1],
	'Age': [20, 20, 20, 21, 21, 21, 22, 22, 22],
	'Address': ["Area A", "Area A", "Area A", "Area A", "Area A", "Area A", "Area B", "Area B", "Area B"]
	})
data = pd.get_dummies(data, columns=['Customer.ID'], prefix='Dummy', drop_first=False)

# Case 1. without unit fixed effects
X = data[['Push.Notification']]
y = data['Purchase.Amount']
X = sm.add_constant(X)

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


# Case 2. with unit fixed effects
X = data[['Push.Notification','Dummy_1', 'Dummy_2', 'Dummy_3']]
y = data['Purchase.Amount']
X = sm.add_constant(X)

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


# Case 3. with unit fixed effects, but without control gorup
filtered_row = (X['Dummy_3'] != 1)
X = X[filtered_row]
y = y[filtered_row]

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


# Case 4. with both customer and day fixed effects
data = pd.get_dummies(data, columns=['Day'], prefix='Dummy.Day', drop_first=False)
dummy_cols = data.columns[data.columns.str.startswith('Dummy.Day')]
data[dummy_cols] = data[dummy_cols].astype(int)

X = data[['Push.Notification','Dummy_1', 'Dummy_2', 'Dummy_3', 'Dummy.Day_1', 'Dummy.Day_2', 'Dummy.Day_3']]
y = data['Purchase.Amount']
X = sm.add_constant(X)

model = sm.OLS(y, X).fit()
print(model.summary())
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.