호텔 예약 취소에 숨겨진 인과적 이야기
다른 호텔 방에 배정되는 것이, 고객의 호텔 예약 취소에 어떤 영향을 줄까?
Last updated
다른 호텔 방에 배정되는 것이, 고객의 호텔 예약 취소에 어떤 영향을 줄까?
Last updated
작성자:
원문:
호텔 예약이 취소되기 까지는 다양한 이유들이 존재합니다.
A customer may have requested something that was not available (e.g., car parking), a customer may have found later that the hotel did not meet their requirements, or a customer may have simply cancelled their entire trip. Some of these like car parking are actionable by the hotel whereas others like trip cancellation are outside the hotel's control. In any case, we would like to better understand which of these factors cause booking cancellations.
이러한 상황에서, 호텔 예약을 취소하는 원인을 찾기 위해서는 각 고객들이 랜덤하게 두 가지의 카테고리에 속하는 방식의 Randomized Controlled Trials 와 같은 실험이 가장 Golden Standard인데요. 이러한 실험은 특정 상황에서는 너무 비용이 크거나, 윤리적이지 않습니다. 예를 들어 호텔이 고객들에게 서비스의 차등을 준다는 점을 깨닫게 된다면, 호텔의 명성에 손해가 갈 수 있습니다.
관측 데이터를 통해서, 이러한 질문에 대답할 수는 없을까요?
0
Resort Hotel
0
342
2015
July
27
1
0
0
2
...
No Deposit
NaN
NaN
0
Transient
0.0
0
0
Check-Out
2015-07-01
1
Resort Hotel
0
737
2015
July
27
1
0
0
2
...
No Deposit
NaN
NaN
0
Transient
0.0
0
0
Check-Out
2015-07-01
2
Resort Hotel
0
7
2015
July
27
1
0
1
1
...
No Deposit
NaN
NaN
0
Transient
75.0
0
0
Check-Out
2015-07-02
3
Resort Hotel
0
13
2015
July
27
1
0
1
1
...
No Deposit
304.0
NaN
0
Transient
75.0
0
0
Check-Out
2015-07-02
4
Resort Hotel
0
14
2015
July
27
1
0
2
2
...
No Deposit
240.0
NaN
0
Transient
98.0
0
1
Check-Out
2015-07-03
5 rows × 32 columns
의미 있는 변수들을 만들고, 데이터 셋의 디멘션을 줄이기 위해서 Feature Engineering 을 진행합니다.
Total Stay = stays_in_weekend_nights + stays_in_week_nights
Guests = adults + children + babies
Different_room_assigned = 1 if reserved_room_type & assigned_room_type are different, 0 otherwise.
NULL 값이 있거나, 유저 단위에 1:1 대응이 될 정도로 유니크한 값이 많은 컬럼은 삭제합니다. 또한 country
의 결측치는 최빈값으로 메웁니다. distribution_channel
이라는 컬럼은 market_segment
와 겹치는 부분이 많기 때문에 삭제됩니다.
불필요한 컬럼을 판단해 삭제하고, different_room_assigned
와 is_canceled
라는 컬럼을 여부에 따라 1,0으로 대체합니다.
.dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
0
BB
PRT
Direct
0
0
0
3
No Deposit
0
Transient
0.00
0
0
0
2.0
1
BB
PRT
Direct
0
0
0
4
No Deposit
0
Transient
0.00
0
0
0
2.0
2
BB
GBR
Direct
0
0
0
0
No Deposit
0
Transient
75.00
0
0
1
1.0
3
BB
GBR
Corporate
0
0
0
0
No Deposit
0
Transient
75.00
0
0
1
1.0
4
BB
GBR
Online TA
0
0
0
0
No Deposit
0
Transient
98.00
0
1
2
2.0
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
...
95
BB
PRT
Online TA
0
0
0
0
No Deposit
0
Transient
73.80
0
1
2
2.0
96
BB
PRT
Online TA
0
0
0
0
No Deposit
0
Transient
117.00
0
1
7
2.0
97
HB
ESP
Offline TA/TO
0
0
0
0
No Deposit
0
Transient
196.54
0
1
7
3.0
98
BB
PRT
Online TA
0
0
0
0
No Deposit
0
Transient
99.30
1
2
7
3.0
99
BB
DEU
Direct
0
0
0
0
No Deposit
0
Transient
90.95
0
0
7
2.0
100 rows × 15 columns
데이터셋에서, No Deposit 으로 예약했으면서 취소한 비율에 대해서 미리 확인해봅니다. 총 104,637건 중에서 29,690건이 취소되었습니다.
deposit_type
is_canceled
No Deposit
False
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
74947
True
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
29690
판다스에는 데이터프레임의 복사본을 만들어주는 pandas.DataFrame.copy가 있고 a = b와는 다른 방식의 복사입니다.
a = b는 원본 데이터가 변하면 똑같이 변하는 얕은 복사인 반면, pandas.DataFrame.copy는 복사 당시의 데이터프레임 상태만 복사되는 깊은 복사입니다.
얕은 복사를 하면 복사본은 원본과 데이터/index를 공유하지만, 깊은 복사는 복사본이 자신만의 데이터/index를 갖게합니다.
예약 취소의 개수와, 다른 방에 배정되는 케이스의 개수가 심각하게 불균형적이기 때문에, 랜덤으로 최초 1000개의 관측치를 선택하며 is_cancelled
& different_room_assigned
가 같은 값을 달성하는지를 확인합니다. 이 전체적인 프로세스는 10000회 반복되고, 그 중에서 예상되는 달성 횟수는 50%에 가깝습니다. 그 이유는 두개의 변수가 같은 값을 랜덤하게 가질 수 있을 확률은 50% 이기 때문입니다.
따라서 통계적으로 이야기하면, 이 단계에서 유한한 결론이 있는 것은 아닙니다. 따라서, 고객이 예약한 방과 다른 방을 배정하는 상황은 그 고객이 예약을 취소하게 할수도, 아닐수도 있습니다.
첫번째 시나리오는, 아무 조건 가정 없이 샘플링을 진행합니다.
588.7015
두번째 시나리오는, 예약에 변경이 없었을 것이라는 조건을 가정한 후에 샘플링을 진행합니다.
572.608
세번째 시나리오는 예약에 변경이 있었을 것이라는 조건(>0)을 가정한 후에 샘플링을 진행합니다.
666.015
확실히 예약에 변경이 있었을 것이라는 조건을 통해서, 무언가 숫자에 변화를 볼 수 있습니다. booking_changes
변수가 곧 교란변수가 될 수 있다는 힌트가 됩니다.
하지만 booking_changes
변수가 유일한 교란변수일까요? 만일 관측되지 않은 교란변수가 있고, 그것이 데이터셋에 변수 형태로 포함되지 않은 정보라면, 우리는 이 booking_changes
변수가 곧 교란변수가 될 수 있다는 주장을 계속할 수 있을까요?
예측 모델링 문제에 대한 사전지식을, 인과 그래프 형태로 가정을 사용해서 표현합니다. 전체 그래프를 이 단계에서 표현하지 않아도, 일부 그래프만 표현하여도 충분합니다. 나머지는 DoWhy 를 통해서 찾을 수 있습니다.
아래 내용이 인과 모형으로 해석된 몇 개의 가정들입니다.
Market Segment
변수 : TA(Travel Agents), TO(Tour Operators) 라는 2가지 값으로 구성되며, Lead Time
변수에 영향을 줍니다.
Country
변수 : 한 사람이 일찍 호텔을 예약할지, 아닐지에 대해 좌우하는 역할을 할 수 있으며, 궁극적으로 일찍 호텔을 예약하는 행동은 Lead Time
변수에 영향을 줍니다. 그 외로도, Meal
변수에도 영향을 줍니다.
Lead Time
변수 : Days in Waitlist
변수에 확실하게 영향을 줍니다. 늦게 예약할수록, 예약을 할 수 있는 기회가 적어지기 때문입니다. 추가적으로 높은 Lead Time
은 Cancellations
변수를 높아지게 만들 수 있습니다.
Previous Booking Retentions
변수 : 고객의 Repeated Guest
여부를 좌우하고, 이 두개의 변수는 모두 Cancelled
에도 영향을 줄 수 있습니다. 리텐션이 높았던 고객일수록 취소할 확률이 낮고, 리텐션이 낮았던 즉 늘 취소했던 고객이라면 취소할 확률이 높습닌다.
Booking Changes
변수 : 예약을 변경하는 것은 Different room assigned
변수를 좌우하고, Cancellation
에도 영향을 줄 수 있습니다.
정리하면, Booking Changes
변수가 Treatment, Outcome 에 영향을 주는 가장 유일한 교란변수일 확률은 낮습니다. 현재 변수는 물론, 데이터셋에 고려되지 않은 정보들까지 포함하여 다른 관측되지 않은 교란변수가 많을 것입니다.
pygraphviz 설치에서 에러가 발생할 경우, 터미널에서 아래와 같이 해결할 수 있습니다.
brew install graphviz
pip install graphviz
pip install pygraphviz
여기서, Treatment 는 고객이 예약한 방을 그대로 배정해주는 것입니다. Outcome은 예약이 취소되었는지에 대한 여부입니다.
Common Causes : 우리의 인과 그래프에 따라 Treatment, Outcome 에 영향을 줄 수 있는 변수들을 의미합니다.
앞서 세운 인과적 가정에 따르면 이 Common Causes 가 될 수 있는 조건을 만족하는 변수는 Booking Changes
그리고 Unobserved Confounders
변수 2가지 입니다.
따라서, 만일 그래프를 명시적으로 작성하지 않을 경우에는 아래 함수에 따라 파라미터들을 제공할 수 있습니다.
So if we are not specifying the graph explicitly (Not Recommended!), one can also provide these as parameters in the function mentioned below.
우리는 모든 다른 것들을 유지한 채로 Treatment 를 변화시킬 때, Outcome 에 변화가 있다면 Treatment가 Outcome의 원인이 된다라고 이야기합니다. 따라서 이 단계에서는, 인과 그래프의 특성들을 활용해서 추정하고자 하는 인과 효과를 식별합니다.
identify_effect()
Identified estimand 를 리턴하는 주요한 메소드. 만일 Estimand 의 타입이 non-parametric ATE라면, 주어진 인과 모형에서 Identified estimand가 있는지를 확인하기 위해서 Backdoor / Instrumental Variable / Frontdoor Identification 메소드들을 사용합니다.
estimate_effect()
Step-3 에서 이어집니다.
do()
refute_estimate()
view_model()
interpret()
summary()
Estimand : (예시) Gaussian의 Parameter 가 되는 평균, 분산
Estimate : Random variable 로 월별 수익을 추정한 값
평균의 Estimate : 100만원 +- standard error
표준 편차의 Estimate : 10만원
Estimator : Random variable 로부터 Estimate를 얻어내는 함수
평균의 Estimator : mu = sigma(월 수입) / 총 월 수
표준 편차의 Estimate : s = sqrt(sigma(월 수입 - 평균 월 수입) / (총 월 수 -1))
인과효과 연구에서 관심 모집단의 특성과 연관지어 분석 결과를 해석하고 추론하는 것은 중요하며, 이에 연구자는 통계적 추론(inference)를 위한 관심 모수(estimand; parameter of interest)를 정의하고 이에 대한 추정치(estimates)를 산출하게 된다.
identify_effect()
estimate_effect()
method_name 파라미터 값으로 아래 종류들이 있습니다.
Propensity Score Matching: “backdoor.propensity_score_matching”
Propensity Score Stratification: “backdoor.propensity_score_stratification”
Propensity Score-based Inverse Weighting: “backdoor.propensity_score_weighting”
Linear Regression: “backdoor.linear_regression”
Generalized Linear Models (e.g., logistic regression): “backdoor.generalized_linear_model”
Instrumental Variables: “iv.instrumental_variable”
Regression Discontinuity: “iv.regression_discontinuity”
do()
refute_estimate()
view_model()
interpret()
summary()
결과는 꽤나 놀라웠습니다. 다른 방에 배정받는게 취소할 확률을 낮춰준다는 결과를 의미하기 때문입니다. (-0.251만큼) 여기서, 이게 정말 맞는 인과 효과일까요?
다른 메커니즘이 작용했을 수 있습니다. 다른 방에 배정받는 시간적인 상황이 체크인 때만 발생한다면? 이미 호텔에 와있기 때문에 취소할 수 있는 확률이 당연히 낮습니다. 이러한 케이스라면, 이 예약 취소가 언제 발생했는지에 대한 시각 정보가 주요한 변수가 됩니다. 예를 들어, different_room_assigned
변수가 예약을 한 당일에 주로 발생한다면 또 어떨까요? 이런 변수를 알게 됨으로써 그래프와 분석을 향상시킬 수 있습니다.
앞서 진행했던 연관성 분석이, is_canceled
와 different_room_assigned
사이의 양의 상관관계를 보였는데요. DoWhy를 통해서 인과 효과를 예측하는 것은 그 반대로, 아예 다른 그림을 보였습니다. 이는 곧, 다른 방에 배정하는 현상의 수를 줄이는 정책은 곧 호텔에게 비생산적인 방향의 정책일 수 있다는 점을 암시합니다.
주의해야 할 점은 인과효과는 데이터셋으로부터 발견되는 것이 아니라, Identification 으로 이끄는 연구자의 가정(Assumptions)을 통해서 발견 됩니다. 데이터는 단순히 통계적인 추정(Estimation)에만 사용됩니다. 즉, 가정이 맞았는지 아닌지에 대해서 증명하는 것이 정말 중요합니다.
다른 Common cause 가 존재한다면?
Treatment 자체가 Placebo 효과라면?
Add Random Common Cause:
랜덤한 독립 변수를 Common cause로 데이터셋에 추가했을 때, 현재의 추정 메소드가 다른 추정치를 돌려줄까요?
데이터에서 랜덤하게 공변량 변수들을 Draw하고, 동일한 분석을 재 진행하여 인과 추정치가 변화하는지를 봅니다. (CNN 돌릴 때 랜덤 값 조정하여 강건성 확인하듯이)
정규 분포 내에 랜덤하게 빼고 더하는 형태로 변수 추가
가정이 본래 옳았다면, 인과 추정치는 크게 변화해서는 안됩니다.
Placebo Treatment Refuter:
실제 Treatment 변수 different_room_assigned
를 랜덤한 독립 변수로 대체한다면, 추정된 인과 효과는 어떻게 변화할까요?
데이터에서 랜덤하게 공변량 변수를 하나 Draw하고 Treatment로 대체하여 동일한 분석을 재 진행하여 인과 추정치가 변화하는지를 봅니다.
가정이 본래 옳았다면, 새로운 추정치는 0에 가까워야 합니다.
p-value 는 New effect 가 통계적으로 유의하게 0과 다른지를 검정합니다.
p-value < 0.05 라면 New effect가 0과 다르기 때문에 인과 추정치가 문제가 있다는 것을 의미합니다.
Data Subset Refuter:
주어진 전체 데이터 셋을, 랜덤하게 선택한 데이터셋 일부로 대체한다면 인과 추정치가 변화할까요?
Cross-validation과 유사한 방식으로 데이터셋의 subset 을 생성합니다. 인과 추정치가 subset들에 걸쳐서 차이가 나는지를 확인합니다.
Making use of Bootstrap as we have more than 100 examples.The greater the number of examples, the more accurate are the confidence estimates
정해진 subset을 추출하는 trial 수가 있고, 각 subset 마다의 결과는 보여지지 않고 결과량은 하나.이 값은 어떻게 추출되었는지?
가정이 본래 옳았다면, 분산이 크지 않습니다.
p-value 는 New effect 가 통계적으로 유의하게 Estimated effect 와 다른지를 검정합니다.
우리의 추정치가 세 가지의 Refutation test를 통과하는 것을 확인했습니다. 이는 추정치의 정확함을 증명하는 것은 아니지만, 추정치의 신뢰도를 높여줍니다.
호텔 예약의 취소를 야기하는 요소들이 무엇인지에 대해서 고려합니다. 이 분석은 의 호텔 예약 데이터셋을 활용했습니다. GitHub 에서는 이 링크에서 확인하실 수 있습니다 : .
변수와 그에 대한 설명을 확인하기 위해서는 를 참조해주세요.
Diagraph 란, Edge 와 Node 로 구성되고 추가적인 데이터나 특성을 포함합니다. Self loops 는 가능하지만, Multiple edge 는 허용되지 않습니다. 노드는 주로 임의의 해싱가능한 Python 객체입니다. Edge는 노드간의 링크입니다. ()
dowhy.CausalModel() 클래스를 활용해서 모델을 입력하게 되면, 아래 함수들을 사용할 수 있습니다. ()
예를 들어, 7년간의 월별 수익이 Gaussian 분포를 따른다고 가정합니다. Gaussian 분포는 평균, 분산을 Parameter 로 가지고 있어, 이 경우에 개별 월 수입은 Random variable 이라고 할 수 있습니다. ()
dowhy.CausalModel() 클래스를 활용해서 모델을 입력하게 되면, 아래 함수들을 사용할 수 있습니다.
p-value < 0.05 라면 두 Effect가 다르기 때문에 인과 추정치가 문제가 있다는 것을 의미합니다. ()