├── .gitbook └── assets │ ├── img1.png │ ├── ceviche.png │ ├── image.png │ ├── image (1).png │ ├── image (2).png │ ├── image (3).png │ ├── image (4).png │ ├── image (5).png │ ├── image (6).png │ ├── uber_opt1.png │ ├── uber_opt2.png │ ├── uber_opt3.png │ ├── uber_opt4.png │ ├── uber_opt5.png │ ├── uber_opt6.png │ ├── uber_opt7.png │ ├── uber_opt8.png │ ├── uber_opt9.png │ ├── debias_dml.png │ ├── dynamicdml1.png │ ├── dynamicdml2.png │ ├── dynamicdml3.png │ ├── instrum_var.png │ ├── output_33_1.png │ ├── uber_opt10.png │ ├── uber_opt11.png │ ├── uber_opt12.png │ ├── uber_opt13.png │ ├── uber_opt14.png │ ├── uber_opt15.png │ ├── uber_opt16.png │ ├── uber_opt17.png │ ├── uber_opt18.png │ ├── dowhy-diagram.png │ ├── hotel-estimand.png │ ├── interpret-img1.png │ ├── interpret-img2.png │ ├── interpret-img3.png │ ├── interpret-img4.png │ ├── interpret-img5.png │ ├── ceviche_framework.png │ ├── unified_pipeline.png │ ├── hotel-causal-graph.png │ ├── ceviche_treatment_lift.png │ ├── longtermroi_problem1.png │ ├── longtermroi_problem2.png │ ├── longtermroi_problem3.png │ ├── ceviche_framework_setup.png │ ├── ceviche_treatment_setup.png │ ├── longtermroi_problem1_dml.png │ ├── longtermroi_problem1_sol.png │ ├── longtermroi_problem2_sol.png │ ├── longtermroi_problem3_sol.png │ ├── match_by_group_stratify.png │ ├── ceviche_framework_inference.png │ ├── hotel-causal-graph-in-model.png │ ├── attribute_incremental_revenue.png │ └── 스크린샷 2022-01-15 오후 9.35.19.png ├── README.md ├── SUMMARY.md ├── dowhy-key-concepts ├── propensity-score │ ├── README.md │ ├── propensity-score-matching.md │ ├── propensity-score-weighting.md │ └── propensity-score-stratification.md ├── instrumental-variable.md ├── sensitivity-analysis.md └── interpret-results.md ├── advanced-econml ├── a-b-tripadvisor.md └── proxy-metric-roi-microsoft.md ├── advanced-causalml ├── causal-impact-analysis-uber.md └── uplift-modeling-uber.md └── example-notebooks ├── custom-refutation.md ├── ihdp-health.md ├── membership-effect.md ├── iv-effect.md └── hotel-booking-cancel.md /.gitbook/assets/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/img1.png -------------------------------------------------------------------------------- /.gitbook/assets/ceviche.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/ceviche.png -------------------------------------------------------------------------------- /.gitbook/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/image.png -------------------------------------------------------------------------------- /.gitbook/assets/image (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/image (1).png -------------------------------------------------------------------------------- /.gitbook/assets/image (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/image (2).png -------------------------------------------------------------------------------- /.gitbook/assets/image (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/image (3).png -------------------------------------------------------------------------------- /.gitbook/assets/image (4).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/image (4).png -------------------------------------------------------------------------------- /.gitbook/assets/image (5).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/image (5).png -------------------------------------------------------------------------------- /.gitbook/assets/image (6).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/image (6).png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt1.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt2.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt3.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt4.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt5.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt6.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt7.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt8.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt9.png -------------------------------------------------------------------------------- /.gitbook/assets/debias_dml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/debias_dml.png -------------------------------------------------------------------------------- /.gitbook/assets/dynamicdml1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/dynamicdml1.png -------------------------------------------------------------------------------- /.gitbook/assets/dynamicdml2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/dynamicdml2.png -------------------------------------------------------------------------------- /.gitbook/assets/dynamicdml3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/dynamicdml3.png -------------------------------------------------------------------------------- /.gitbook/assets/instrum_var.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/instrum_var.png -------------------------------------------------------------------------------- /.gitbook/assets/output_33_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/output_33_1.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt10.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt11.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt12.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt13.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt14.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt15.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt16.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt17.png -------------------------------------------------------------------------------- /.gitbook/assets/uber_opt18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/uber_opt18.png -------------------------------------------------------------------------------- /.gitbook/assets/dowhy-diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/dowhy-diagram.png -------------------------------------------------------------------------------- /.gitbook/assets/hotel-estimand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/hotel-estimand.png -------------------------------------------------------------------------------- /.gitbook/assets/interpret-img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/interpret-img1.png -------------------------------------------------------------------------------- /.gitbook/assets/interpret-img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/interpret-img2.png -------------------------------------------------------------------------------- /.gitbook/assets/interpret-img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/interpret-img3.png -------------------------------------------------------------------------------- /.gitbook/assets/interpret-img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/interpret-img4.png -------------------------------------------------------------------------------- /.gitbook/assets/interpret-img5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/interpret-img5.png -------------------------------------------------------------------------------- /.gitbook/assets/ceviche_framework.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/ceviche_framework.png -------------------------------------------------------------------------------- /.gitbook/assets/unified_pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/unified_pipeline.png -------------------------------------------------------------------------------- /.gitbook/assets/hotel-causal-graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/hotel-causal-graph.png -------------------------------------------------------------------------------- /.gitbook/assets/ceviche_treatment_lift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/ceviche_treatment_lift.png -------------------------------------------------------------------------------- /.gitbook/assets/longtermroi_problem1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/longtermroi_problem1.png -------------------------------------------------------------------------------- /.gitbook/assets/longtermroi_problem2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/longtermroi_problem2.png -------------------------------------------------------------------------------- /.gitbook/assets/longtermroi_problem3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/longtermroi_problem3.png -------------------------------------------------------------------------------- /.gitbook/assets/ceviche_framework_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/ceviche_framework_setup.png -------------------------------------------------------------------------------- /.gitbook/assets/ceviche_treatment_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/ceviche_treatment_setup.png -------------------------------------------------------------------------------- /.gitbook/assets/longtermroi_problem1_dml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/longtermroi_problem1_dml.png -------------------------------------------------------------------------------- /.gitbook/assets/longtermroi_problem1_sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/longtermroi_problem1_sol.png -------------------------------------------------------------------------------- /.gitbook/assets/longtermroi_problem2_sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/longtermroi_problem2_sol.png -------------------------------------------------------------------------------- /.gitbook/assets/longtermroi_problem3_sol.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/longtermroi_problem3_sol.png -------------------------------------------------------------------------------- /.gitbook/assets/match_by_group_stratify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/match_by_group_stratify.png -------------------------------------------------------------------------------- /.gitbook/assets/ceviche_framework_inference.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/ceviche_framework_inference.png -------------------------------------------------------------------------------- /.gitbook/assets/hotel-causal-graph-in-model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/hotel-causal-graph-in-model.png -------------------------------------------------------------------------------- /.gitbook/assets/attribute_incremental_revenue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/attribute_incremental_revenue.png -------------------------------------------------------------------------------- /.gitbook/assets/스크린샷 2022-01-15 오후 9.35.19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/playinpap/gitbook_dowhy/HEAD/.gitbook/assets/스크린샷 2022-01-15 오후 9.35.19.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Microsoft의 인과추론을 위한 라이브러리 DoWhy의 가이드를 스터디하며 한국어 자료로 생성한 깃북입니다. 3 | --- 4 | 5 | # DoWhy | 인과추론을 위한 라이브러리 6 | 7 | * 원문 : [https://www.pywhy.org/dowhy/](https://www.pywhy.org/dowhy/) 8 | * 라이브러리 : [https://github.com/py-why/dowhy](https://github.com/py-why/dowhy) 9 | * 제작자 : 10 | 11 | Amit Sharma, Emre Kiciman. DoWhy: An End-to-End Library for Causal Inference. 2020. [https://arxiv.org/abs/2011.04216](https://arxiv.org/abs/2011.04216) 12 | 13 | \ 14 | 15 | 16 | ![](.gitbook/assets/dowhy-diagram.png) 17 | 18 | DoWhy는 인과추론을 위한 메커니즘을 4단계로 구성했습니다. 1단계 (Model)에서는 데이터를 인과 그래프로 인코딩하고, 2단계 (Identify)에서는 모델의 인과 관계를 식별하고 원인을 추정합니다. 3단계 (Estimate)에서는 식별된 인과관계에 대해 추정치를 구하고 4단계 (Refute)에서는 얻어진 추정치에 대해 반박을 시도합니다. 19 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [DoWhy | 인과추론을 위한 라이브러리](README.md) 4 | 5 | ## Example Notebooks 6 | 7 | * [호텔 예약 취소에 숨겨진 인과적 이야기](example-notebooks/hotel-booking-cancel.md) 8 | * [IHDP 데이터셋에 DoWhy 적용하기](example-notebooks/ihdp-health.md) 9 | * [사용자 정의 결과 함수를 사용해 추정치 반박하기](example-notebooks/custom-refutation.md) 10 | * [멤버십 리워드 프로그램의 효과 추정하기](example-notebooks/membership-effect.md) 11 | * [도구변수를 활용하여 인과효과를 추정하기](example-notebooks/iv-effect.md) 12 | 13 | ## DoWhy Key Concepts 14 | 15 | * [모델 결과 해석하기](dowhy-key-concepts/interpret-results.md) 16 | * [추정치를 검증하는 방법](dowhy-key-concepts/sensitivity-analysis.md) 17 | * [성향점수 (Propensity Score)](dowhy-key-concepts/propensity-score/README.md) 18 | * [Propensity Score Matching](dowhy-key-concepts/propensity-score/propensity-score-matching.md) 19 | * [Propensity Score Stratification](dowhy-key-concepts/propensity-score/propensity-score-stratification.md) 20 | * [Propensity Score Weighting](dowhy-key-concepts/propensity-score/propensity-score-weighting.md) 21 | * [도구변수 (Instrumental Variable)](dowhy-key-concepts/instrumental-variable.md) 22 | 23 | ## Advanced : EconML 24 | 25 | * [멤버십 상품이 유저의 Engagement를 증가시킬까? Recommendation A/B 테스트 - TripAdvisor](advanced-econml/a-b-tripadvisor.md) 26 | * [단기 Proxy Metric를 통한 장기 ROI 추정 - Microsoft](advanced-econml/proxy-metric-roi-microsoft.md) 27 | 28 | ## Advanced : CausalML 29 | 30 | * [Causal Impact Analysis 활용한 고객 가치 분석 - Uber](advanced-causalml/causal-impact-analysis-uber.md) 31 | * [Uplift Modeling을 활용한 광고 입찰 최적화 - Uber](advanced-causalml/uplift-modeling-uber.md) 32 | -------------------------------------------------------------------------------- /dowhy-key-concepts/propensity-score/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Propensity Score Matching, Stratification, Weighting 3 | --- 4 | 5 | # 성향점수 (Propensity Score) 6 | 7 | * 작성자: 김가연 8 | 9 | *** 10 | 11 | 인과관계의 성립을 위해서는 다음과 같은 3가지 조건이 필요합니다. 12 | 13 | 1. 원인변수의 시간적 선행 14 | 2. 원인 및 결과변수 간 상관(Correlation) 15 | 3. 혼동요인(Confounder)의 통제 16 | 17 | 하지만 혼동요인에 대한 통제는 엄격한 수준에서 불가능합니다. 무작위 통제 실험(Randomized Controlled Trial, RCT)이 아닌 이미 수집된 자료를 바탕으로 인과관계를 추론할 경우, 사전 동등성이 확보되어있지 않아 선택 편의(Selection Bias)의 문제가 발생합니다. 18 | 19 | 이러한 문제를 최소화하기 위한 통계적 해결법 중 하나가 '성향점수(Propensity Score)' 방법입니다. 20 | 21 | > **성향점수(Propensity Score)란,** 개체들이 처치집단(Treatment Group)이나 비교집단(Control Group)에 속할 가능성에 영향을 주는 모든 변수들, 즉 공변량(Covariates)의 값이 주어졌을 때 해당 공변량 값을 가진 개체가 **처치집단에 배치될 조건부 확률**입니다. 22 | 23 | 성향점수를 식으로 표현하면 아래와 같습니다. 24 | 25 | $$ 26 | e(X) = prob(T=1 | X) 27 | $$ 28 | 29 | X 라는 공변량이 주어졌을 때 특정 개체가 통제집단이 아닌 처치집단에 배치될 확률로, 주어진 공변량 X 를 이용해 원인 배치 과정 T 를 모형화합니다. 30 | 31 | 즉, RCT 와 매우 비슷하게 만드는 통계적 접근 방법이라고 할 수 있겠습니다. 32 | 33 | 성향점수는 로지스틱 회귀모형, 프로빗 모형 등을 통해 추정될 수 있으며 이를 대응(Matching), 층화(Stratification), 가중(Weighting) 등의 방법을 통해 인과효과 추정에 활용합니다. 다음 장부터는 각 방법들에 대해 알아보겠습니다. 34 | 35 | *** 36 | 37 | 성향점수(Propensity Score) 챕터를 작성하며 참고한 자료는 아래와 같습니다. 38 | 39 | \[1] [R 기반 성향점수분석 : 루빈 인과모형 기반 인과추론](https://tidyverse-korea.github.io/seoul-R/data/RMeetup\_PSA\_slide\_210414.pdf) 40 | 41 | \[2] [경향점수를 활용한 인과효과 추정 방법 비교 : 대응, 가중, 층화, 이중경향점수 보정 The Journal of Curriculum and Evaluation 2019, Vol. 22, No. 2, pp. 269~291](https://www.dbpia.co.kr/Journal/articleDetail?nodeId=NODE09249298) 42 | 43 | \[3] [KDD Tutorial 2018](https://causalinference.gitlab.io/kdd-tutorial/methods.html) 44 | 45 | \[4] [Microsoft DoWhy Package](https://microsoft.github.io/dowhy/\_modules/index.html) 46 | -------------------------------------------------------------------------------- /advanced-econml/a-b-tripadvisor.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Customer Segmentation at TripAdvisor with Recommendation A/B Tests 3 | --- 4 | 5 | # 멤버십 상품이 유저의 Engagement를 증가시킬까? Recommendation A/B 테스트 - TripAdvisor 6 | 7 | * 작성자: 김가연, [최보경](https://www.facebook.com/pagebokyung/) 8 | * 원문: [KDD 2021 Slide](https://drive.google.com/file/d/1yyIu\_3epIVXbwzJj658Iv4vxHGjtPh8n/view) / [ALICE Case Study](https://www.microsoft.com/en-us/research/uploads/prod/2020/04/MSR\_ALICE\_casestudy\_2020.pdf) 9 | 10 | TripAdvisor와 Microsoft Research는 협업을 통해 TripAdvisor의 멤버십 제품을 더 잘 이해하고 개선하고자 했습니다. TripAdvisor 서비스는 TripAdvisorPlus라는 멤버십 상품을 출시했습니다. 연간 $99의 가격으로 캐시백, 차량 대여 및 항공권에 있어서의 추가 할인 혜택, 환불에 있어서의 유동성 등의 혜택들이 주어지는데요. 11 | 12 | ![](<../.gitbook/assets/image (1).png>) 13 | 14 | TripAdvisor 의사결정권자들은 2가지 질문이 있었습니다. 15 | 16 | 1. **이 프로그램이 효과적인가?** 17 | 1. 멤버십에 가입하는 것이 유저들이 웹사이트에서 더 많은 체류시간을 보내게 하는가? 18 | 2. 멤버십을 플랫폼에서 홍보하는 것이 유저의 Engagement 와 예약을 증가시키는가? 19 | 2. **어떤 종류의 유저에게 가장 효과적인가?** 20 | 21 | ![](../.gitbook/assets/image.png) 22 | 23 | 이 질문에 대답하기 위해서 연구자들은 3가지 방법을 고안했습니다. 24 | 25 | {% tabs %} 26 | {% tab title="Proposal #1" %} 27 | **멤버와 멤버가 아닌 집단을 비교하는 방식** 28 | 29 | 이 경우, 멤버가 되는 집단이 이미 다른 유저에 비해 더 서비스에 대한 Engagement가 높을 수 있으므로 교란 변수가 존재합니다. (Confounders : User affinity) 30 | 31 | ![](<../.gitbook/assets/image (3).png>) 32 | {% endtab %} 33 | 34 | {% tab title="Proposal #2" %} 35 | **A/B 테스트** 36 | 37 | 랜덤하게 일부의 유저들에게 멤버십을 가입하도록 강제할 수 없기에 직접적인 A/B 테스트는 불가능합니다. 38 | 39 | ![](<../.gitbook/assets/image (6).png>) 40 | {% endtab %} 41 | 42 | {% tab title="Proposal #3 (선택)" %} 43 | **Recommendation A/B 테스트를 진행하고 도구변수로 활용하는 방식** 44 | 45 | 다행히도, TripAdvisor는 유저의 리텐션을 향상시키기 위해 일부 랜덤한 유저에게 멤버십에 더 쉽게 가입하는 프로세스를 추가해 실험을 진행한 적이 있었습니다. 46 | 47 | ![](<../.gitbook/assets/image (5).png>) 48 | {% endtab %} 49 | {% endtabs %} 50 | 51 | ## Methodology 52 | 53 | Proposal #3 의 더 쉬운 가입 프로세스(Instrument)는 도구변수의 역할로, 멤버십 가입(Treatment)에는 영향을 주지만 User engagement(Outcome)에는 영향을 주지 않습니다. 54 | 55 | ![Instrumental Variables (IVs)](https://user-images.githubusercontent.com/76609403/153713882-4bf9d6b8-255d-405f-9df4-1bf9e9159c34.png) 56 | 57 | 일반적으로 도구변수는 활용함에 있어 여러 한계점이 존재합니다. 일반적인 ML 알고리즘처럼 확장하는 것은 어렵고, 약한 도구변수는 효과 추정을 위해 매우 큰 데이터셋이 필요합니다. 또한 complex effect 혹은 compliance heterogeneity 모두에 대해 설명할 수 없어 편향된 결과를 가져올 수 있습니다. 58 | 59 | ![Challenges and Limitations of Typical IV Methods](https://user-images.githubusercontent.com/76609403/153714542-f73ae01d-170b-4979-abbf-49f2f243a5a0.png) 60 | 61 | TripAdvisor 의 Recommendation A/B 테스트를 자세히 알아보겠습니다. 400만 유저 중 랜덤으로 절반이 '더 쉬운 가입 프로세스'를 받게 하여 멤버십 가입을 유도합니다. 62 | 63 | 각 유저마다 아래와 같은 변수를 관측합니다. 64 | 65 | - T (treatment) : 유저 멤버십 가입 여부 66 | - Y (outcome) : 실험 이후 14일 동안의 방문 횟수 67 | - X : heterogeneity 를 잡아낼 유저 관련 변수들 68 | - Z (instrumental variables) : Recommendation A/B 테스트 할당 69 | 70 | ![Trip Advisor Experiment](https://user-images.githubusercontent.com/76609403/153714994-b8fc674d-f2ae-46f3-a68e-65c81a42195b.png) 71 | -------------------------------------------------------------------------------- /dowhy-key-concepts/propensity-score/propensity-score-matching.md: -------------------------------------------------------------------------------- 1 | # Propensity Score Matching 2 | 3 | - 작성자: 김가연 4 | 5 | --- 6 | 7 | **성향점수 대응(Propensity Score Matching, PSM)** 은 성향점수가 동일하거나 유사한 처치집단의 개체와 비교집단의 개체를 한 쌍으로 매칭하는 방법입니다. 8 | 9 | 아래 그림은 운동 여부에 따른 콜레스테롤 수치를 비교하는 사례로, 성향점수 대응 결과 연령대가 비슷한 처치집단의 개체와 비교집단의 개체가 매칭되었습니다. 각 처치집단의 개체와 비교집단의 개체가 서로 반사실(counterfactual)임을 알 수 있습니다. 10 | 11 | ![운동을 하지 않는 비교집단과 운동을 하는 처치집단의 평균 콜레스테롤 수치 (출처: [kdd-tutorial 2018](https://causalinference.gitlab.io/kdd-tutorial/methods.html))](https://user-images.githubusercontent.com/76609403/151827491-4d3f84da-b76d-42df-a910-1c3b40404751.png) 12 | 13 | ![유사한 성향점수를 갖는 비교집단의 개체와 처치집단의 개체를 매칭 (출처: [kdd-tutorial 2018](https://causalinference.gitlab.io/kdd-tutorial/methods.html))](https://user-images.githubusercontent.com/76609403/151827715-9fc5f95a-e028-4c4a-a2db-3eb01098515b.png) 14 | 15 | 이러한 성향점수를 비교하는 방법에는 매칭 기준에 따라 최근린, 라디우스, 최적화 매칭 등으로 구분할 수 있습니다. 16 | 17 | DoWhy 라이브러리의 [propensity_score_matching_estimator](https://microsoft.github.io/dowhy/_modules/dowhy/causal_estimators/propensity_score_matching_estimator.html#PropensityScoreMatchingEstimator.construct_symbolic_estimator) 모듈에서는 Nearest Neighbor Matching 을 사용합니다. 18 | 19 | ```python 20 | # dowhy.causal_estimators.propensity_score_matching_estimator _estimate_effect 함수 코드 일부 21 | 22 | from sklearn.neighbors import NearestNeighbors 23 | 24 | # ATT (average treatment effect for the treated) 계산 25 | control_neighbors = ( 26 | NearestNeighbors(n_neighbors=1, algorithm='ball_tree') 27 | .fit(control['propensity_score'].values.reshape(-1, 1)) 28 | ) 29 | distances, indices = control_neighbors.kneighbors(treated['propensity_score'].values.reshape(-1, 1)) 30 | self.logger.debug("distances:") 31 | self.logger.debug(distances) 32 | 33 | att = 0 34 | numtreatedunits = treated.shape[0] 35 | for i in range(numtreatedunits): 36 | treated_outcome = treated.iloc[i][self._outcome_name].item() 37 | control_outcome = control.iloc[indices[i]][self._outcome_name].item() 38 | att += treated_outcome - control_outcome 39 | 40 | att /= numtreatedunits 41 | 42 | # ATC (average treatment effect for the control) 계산 43 | treated_neighbors = ( 44 | NearestNeighbors(n_neighbors=1, algorithm='ball_tree') 45 | .fit(treated['propensity_score'].values.reshape(-1, 1)) 46 | ) 47 | distances, indices = treated_neighbors.kneighbors(control['propensity_score'].values.reshape(-1, 1)) 48 | atc = 0 49 | numcontrolunits = control.shape[0] 50 | for i in range(numcontrolunits): 51 | control_outcome = control.iloc[i][self._outcome_name].item() 52 | treated_outcome = treated.iloc[indices[i]][self._outcome_name].item() 53 | atc += treated_outcome - control_outcome 54 | 55 | atc /= numcontrolunits 56 | ``` 57 | 58 | 다만 Nearest Neighbor Matching 은 가장 근접한 성향점수를 가져도 실제로 큰 차이를 가질 수 있다는 한계가 있습니다. 이 때에는 처치집단과 비교집단의 성향점수 차이를 일정 범위로 유지하는 threshold 를 설정한 매칭 방법을 사용합니다. 해당 범위 안에서 대응 짝을 찾을 수 있도록 강제함으로써, 좀 더 엄격하게 실험설계와 유사한 상황을 만들어 낼 수 있습니다. 59 | 60 | DoWhy 라이브러리 또한 주어진 radius 값보다 멀리 있는 neighbors 는 제외할 수 있도록 [관련 코드](https://microsoft.github.io/dowhy/_modules/dowhy/causal_estimators/propensity_score_matching_estimator.html#:~:text=%23%20TODO%20remove%20neighbors%20that%20are%20more%20than%20a%20given%20radius%20apart)를 추가할 계획인 것으로 보입니다. (2022.01.31 기준) 61 | -------------------------------------------------------------------------------- /advanced-causalml/causal-impact-analysis-uber.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Causal Impact Analysis - CeViChE 3 | --- 4 | 5 | # Causal Impact Analysis 활용한 고객 가치 분석 - Uber 6 | 7 | * 작성자: 허현 8 | * 원문: [KDD 2021 Slide](https://docs.google.com/presentation/d/1FvRtis2fm4c2R7XmRKWMTtZaZjUObW1fGxpNmapmjKI/edit?usp=sharing) 9 | 10 | ## CeViChE란? 11 | ![](<../.gitbook/assets/ceviche.png>) 12 | 13 | **Customer Value Changing Events의 줄임말** 14 | 15 | 세비체라고 하는 날생선 샐러드에 끼워 맞추려고 노력한 모습 (~~트렌드코리아 김난도 교수님한테 작명교육 좀 받아야 할 것 같음~~) 16 | 17 | 관측 데이터로 특정 이벤트를 통해 어떻게 고객의 가치가 변했는지/얼마나 효과 있었는지 추정하기 위한 방법론입니다. 18 | 19 | ### 사용하는 이유 20 | 실험이 아닌 관측 데이터로 인과성을 측정할 수 있습니다. 21 | - 실험과 달리 시간이 이대로 흘렀다면 이러했을 것이다에 기반하는 방식 22 | - 비싸지 않다 (실험과 달리 비용 없음) 23 | - (데이터와 실험디자인에 따라서) 외적 타당성을 가짐 24 | - 행동적 걱정이 적다 (실험은 어떤 효과를 어떤 실험 설계를 통해 검증할 것인지 미리 생각할 게 많은 반면, 관측 데이터를 가공하면 다양한 피쳐를 원하는대로 만들 수 있기 때문) 25 | 26 | ### A/B 테스트가 어려울 때, 실패할 때 사용되는 접근법을 일반화 한 것 27 | A/B 테스트가 어려운 상황들 28 | - 크로스셀링, 업셀링 29 | - 몰입 감소 30 | - 로열티 프로그램 31 | - 브랜드 효과 32 | - 마케팅 캠페인 33 | 34 | > 경고: SOTA 인과추론 방법론을 썼지만 실험데이터가 아닌 관측데이터이기 때문에 인과성을 보장하는 것은 아님 35 | 36 | 37 | ## CeViChE 프레임워크 38 | ![](<../.gitbook/assets/ceviche_framework.png>) 39 | 준비 - 성향점수 - 매칭 - 추론 - 검증 과정 40 | 41 | 세부 과정에서 다양한 방법론을 사용할 수 있으나 층화 PSM, Meta Learner, 민감도 분석을 사용하는 것으로 정형화 됨 42 | 43 | ### 준비 44 | ![](<../.gitbook/assets/ceviche_framework_setup.png>) 45 | - cohort preference, 피쳐 모음, 피쳐 엔지니어링을 진행 46 | - cohort preference : 어떤 시점에 대해 분석을 할지, 어떤 사람들을 대상(통제집단, 실험집단)으로 할지 등에 대한 선택 과정 47 | - 숨겨진 confounder가 있을 수 있기 때문에 편향을 없애기 위해 최대한 피쳐 리스트들을 포괄하려 함 48 | 49 | ### 성향점수 50 | - 숨겨진 confounder가 있을 수 있어 되도록 많은 피쳐를 사용하는데 피쳐가 많기 때문에 로지스틱 회귀 대신 elasticnetpropensitymodel을 사용 51 | - 로지스틱 회귀에 elasticnet 쓴 것으로 추정됨 (github엔 pass로 코드 생략되었음) 52 | - 머신러닝 예측모델 만들 때처럼 train 데이터셋이 따로 있음 53 | - treatment와 control 그룹의 피쳐 분포를 비교해 최적의 매치를 찾게 함 54 | - 예측한 성향점수 값을 pihat으로 부르는데(예측하는 값이니까) pihat과 caliper의 범위를 조절하면서 최적의 매치를 찾음 55 | - 층화 성향점수를 match_by_group 기능을 써서 그룹 내에서 성향점수 구하게 할 수도 있음 56 | ![](<../.gitbook/assets/match_by_group_stratify.png>) 57 | - 일반적으로는 층화를 상위 20%, 40%, 60%, 80%, 100%와 같이 백분위로 쪼갬 58 | - 매칭할 때 SMD(Standard Mean Difference)를 많이 봄 59 | - 그룹 내 수치 차이의 평균 / 참가자의 표준 편차 60 | 61 | ```python 62 | def smd(feature, treatment): 63 | """Calculate the standard mean difference (SMD) of a feature between the 64 | treatment and control groups. 65 | The definition is available at 66 | https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3144483/#s11title 67 | Args: 68 | feature (pandas.Series): a column of a feature to calculate SMD for 69 | treatment (pandas.Series): a column that indicate whether a row is in 70 | the treatment group or not 71 | Returns: 72 | (float): The SMD of the feature 73 | """ 74 | t = feature[treatment == 1] 75 | c = feature[treatment == 0] 76 | return (t.mean() - c.mean()) / np.sqrt(.5 * (t.var() + c.var())) 77 | ``` 78 | 79 | ### 추론 80 | XGB모델과 LinearRegression 모델로 나누어 S/X/T/R Learner 메타러너로 추론 81 | ![](<../.gitbook/assets/ceviche_framework_inference.png>) 82 | 83 | ### 검증 84 | 아래 방법론 활용 85 | - Placebo treatment 86 | - Replace/Add Irrelevant Confounder 87 | - Subset Validation 88 | - [Selection Bias](https://www.mattblackwell.org/files/papers/causalsens.pdf) 89 | 90 | ## 케이스 스터디 91 | ### 이번 Case Study에서 알고 싶은 것 92 | - 전사적 사업 관점에서 우버 라이더를 우버 이터로 활동하도록 크로스셀링하는 것이 좋은지, 나쁜지 93 | - 크로스셀링의 증분적 효과가 어느 정도인지 94 | 95 | ### 준비 96 | ![](<../.gitbook/assets/ceviche_treatment_setup.png>) 97 | 98 | Pre-Treatment 기간에는 둘 다 Uber 라이더인 사람만 대상으로 99 | 100 | Treatment 기간에는 그대로 Uber 라이더인 경우 Control Group, 이터로 활동한 사람을 Treatment Group으로 101 | 102 | Post-Treatment 기간에는 Control Group만 Uber 라이더 유지 조건 103 | 104 | GB(Gross Booking, 총 요청 수)로 성과 확인 105 | 106 | ### 결론 107 | Uber Rider → Eater로 전환할 때 인센티브를 줬더니 GB기준 1.5배 lift가 있었고, Uber Ride로 버는 돈도 늘었다 108 | ![](<../.gitbook/assets/ceviche_treatment_lift.png>) 109 | -------------------------------------------------------------------------------- /dowhy-key-concepts/propensity-score/propensity-score-weighting.md: -------------------------------------------------------------------------------- 1 | # Propensity Score Weighting 2 | 3 | - 작성자: 김가연 4 | 5 | --- 6 | 7 | **성향점수 가중(Propensity Score Weighting)** 은 처치집단의 성향점수와 통제집단의 성향점수가 같아지도록 가중치를 부여하는 방법입니다. 8 | 9 | 아래 그림은 앞서 다뤘던 운동 여부에 따른 콜레스테롤 수치 비교 사례로, 일반적으로 처치집단에는 성향점수가 높은 개체가, 비교집단에는 성향점수가 낮은 개체가 과대표집 되었을 가능성이 큽니다. 10 | 11 | ![성향점수가 높은 개체가 처치집단에, 낮은 개체가 비교집단에 과대표집 (출처: kdd-tutorial 2018)](https://user-images.githubusercontent.com/76609403/152136280-466ae2de-1a42-4d6a-a053-c31dee143001.png) 12 | 13 | 따라서 과대표집에 대한 균형을 맞추고자 각 집단에 다른 가중치를 부여해 처치효과를 추정합니다. 14 | 15 | DoWhy 라이브러리의 [propensity_score_weighting_estimator](https://microsoft.github.io/dowhy/_modules/dowhy/causal_estimators/propensity_score_weighting_estimator.html) 모듈에서는 가중치를 부여하는 방법으로 inverse propensity score (’ips_weight’), stabilized IPS score (’ips_stabilized_weight’), normalized IPS score (’ips_normalized_weight’) 총 3가지를 제공합니다. 16 | 17 | ```python 18 | # dowhy.causal_estimators.propensity_score_weighting_estimator _estimate_effect 함수 코드 일부 19 | 20 | # ips_weight 21 | self._data['ips_weight'] = (1/num_units) * ( 22 | self._data[self._treatment_name[0]] / self._data['ps'] + 23 | (1 - self._data[self._treatment_name[0]]) / (1 - self._data['ps']) 24 | ) 25 | self._data['tips_weight'] = (1/num_treatment_units) * ( 26 | self._data[self._treatment_name[0]] + 27 | (1 - self._data[self._treatment_name[0]]) * self._data['ps']/ (1 - self._data['ps']) 28 | ) 29 | self._data['cips_weight'] = (1/num_control_units) * ( 30 | self._data[self._treatment_name[0]] * (1 - self._data['ps'])/ self._data['ps'] + 31 | (1 - self._data[self._treatment_name[0]]) 32 | ) 33 | 34 | # ips_normalized_weight 35 | self._data['ips_normalized_weight'] = ( 36 | self._data[self._treatment_name[0]] / self._data['ps'] / ipst_sum + 37 | (1 - self._data[self._treatment_name[0]]) / (1 - self._data['ps']) / ipsc_sum 38 | ) 39 | ipst_for_att_sum = sum(self._data[self._treatment_name[0]]) 40 | ipsc_for_att_sum = sum((1-self._data[self._treatment_name[0]])/(1 - self._data['ps'])*self._data['ps'] ) 41 | self._data['tips_normalized_weight'] = ( 42 | self._data[self._treatment_name[0]]/ ipst_for_att_sum + 43 | (1 - self._data[self._treatment_name[0]]) * self._data['ps'] / (1 - self._data['ps']) / ipsc_for_att_sum 44 | ) 45 | ipst_for_atc_sum = sum(self._data[self._treatment_name[0]] / self._data['ps'] * (1-self._data['ps'])) 46 | ipsc_for_atc_sum = sum((1 - self._data[self._treatment_name[0]])) 47 | self._data['cips_normalized_weight'] = ( 48 | self._data[self._treatment_name[0]] * (1 - self._data['ps']) / self._data['ps'] / ipst_for_atc_sum + 49 | (1 - self._data[self._treatment_name[0]])/ipsc_for_atc_sum 50 | ) 51 | 52 | # ips_stabilized_weight 53 | p_treatment = sum(self._data[self._treatment_name[0]])/num_units 54 | self._data['ips_stabilized_weight'] = (1/num_units) * ( 55 | self._data[self._treatment_name[0]] / self._data['ps'] * p_treatment + 56 | (1 - self._data[self._treatment_name[0]]) / (1 - self._data['ps']) * (1- p_treatment) 57 | ) 58 | self._data['tips_stabilized_weight'] = (1/num_treatment_units) * ( 59 | self._data[self._treatment_name[0]] * p_treatment + 60 | (1 - self._data[self._treatment_name[0]]) * self._data['ps'] / (1 - self._data['ps']) * (1- p_treatment) 61 | ) 62 | self._data['cips_stabilized_weight'] = (1/num_control_units) * ( 63 | self._data[self._treatment_name[0]] * (1 - self._data['ps']) / self._data['ps'] * p_treatment + 64 | (1 - self._data[self._treatment_name[0]])* (1-p_treatment) 65 | ) 66 | 67 | # ATE, ATT, ATC 에 따라 다른 가중치 부여 방법 68 | if self._target_units == "ate": 69 | weighting_scheme_name = self.weighting_scheme 70 | elif self._target_units == "att": 71 | weighting_scheme_name = "t" + self.weighting_scheme 72 | elif self._target_units == "atc": 73 | weighting_scheme_name = "c" + self.weighting_scheme 74 | else: 75 | raise ValueError("Target units string value not supported") 76 | 77 | # 처치효과 계산 78 | self._data['d_y'] = ( # 처치집단 가중치 * outcome 79 | self._data[weighting_scheme_name] * 80 | self._data[self._treatment_name[0]] * 81 | self._data[self._outcome_name] 82 | ) 83 | self._data['dbar_y'] = ( # 비교집단 가중치 * outcome 84 | self._data[weighting_scheme_name] * 85 | (1 - self._data[self._treatment_name[0]]) * 86 | self._data[self._outcome_name] 87 | ) 88 | est = self._data['d_y'].sum() - self._data['dbar_y'].sum() 89 | ``` 90 | 91 | 성향점수 가중 방법은 성향점수의 역수를 가중치로 적용하여 처치집단과 비교집단에 배치될 확률을 동일하게 만듭니다. 예를 들어 ATE 를 추정하는 경우, 아래 식과 같이 성향점수(PS(X))가 높을수록 처치집단(T)에 속한 개체는 작은 가중치(W)를, 비교집단(1-T)에 속한 개체는 큰 가중치(W)를 갖게 합니다. 92 | 93 | $$ 94 | W = T*(1/PS(X)) + (1-T)*(1/(1-PS(X))) 95 | $$ 96 | 97 | 이렇듯 성향점수의 역수를 활용하여 통계적 의미에서 실험적 설계 상황을 설정하게 함으로써 데이터가 관심 모집단 특성을 갖추도록 하는데 용이합니다. 다만 극단적 가중치의 영향을 받을 수 있어, 10보다 큰 가중치는 제외하고 분석하거나 성향점수의 상·하위 각 2.5%에 해당하는 개체를 제외하고 분석하는 방법이 있습니다. 98 | -------------------------------------------------------------------------------- /dowhy-key-concepts/propensity-score/propensity-score-stratification.md: -------------------------------------------------------------------------------- 1 | # Propensity Score Stratification 2 | 3 | - 작성자: 김가연 4 | 5 | --- 6 | 7 | **성향점수 층화(Propensity Score Stratification)** 는 성향점수가 유사한 처치집단과 통제집단 개체들을 K개 집단으로 층화하는 방법입니다. 8 | 9 | 아래 그림은 앞서 다뤘던 운동 여부에 따른 콜레스테롤 수치 비교 사례로, 성향점수의 범위에 따라 집단을 3개로 층화한 결과 연령대가 비슷한 처치집단의 개체들과 비교집단의 개체들이 같은 층(strata)에 포함되었습니다. 10 | 11 | ![운동을 하지 않는 비교집단과 운동을 하는 처치집단 (출처: kdd-tutorial 2018)](https://user-images.githubusercontent.com/76609403/152100158-c0b6078b-57ef-4021-b01a-086b951742ce.png) 12 | 13 | ![성향점수의 범위에 따라 집단을 3개로 층화 (출처: kdd-tutorial 2018)](https://user-images.githubusercontent.com/76609403/152100220-295ee8d3-6df6-4b91-8d76-cd1e825c631e.png) 14 | 15 | 처치효과를 추정하기 위해 각 층에는 충분한 데이터가 있어야 하며 층의 개수 또한 중요합니다. 보편적으로 100개 이하의 data points 를 갖는 경우 5개의 층, 10,000 ~ 1,000,000 혹은 그 이상의 data points 를 갖는 경우 100 ~ 1000개의 층이 적절합니다. 16 | 17 | DoWhy 라이브러리의 [propensity_score_stratification_estimator](https://microsoft.github.io/dowhy/_modules/dowhy/causal_estimators/propensity_score_matching_estimator.html#PropensityScoreMatchingEstimator.construct_symbolic_estimator) 모듈에서는 기본 strata 개수(num_strata)가 50 이고 각 strata 에 속한 처치집단 혹은 비교집단의 data points 개수(clipping_threshold)가 10 입니다. 이때 clipping_threshold 에 미달인 strata 는 처치효과 계산 시 제외되는데, 기술적으로는 LATE (Local Average Treatment Effect) 를 계산하는 것과 같습니다. 18 | 19 | ```python 20 | # dowhy.causal_estimators.propensity_score_stratification_estimator _estimate_effect 함수 코드 일부 21 | 22 | # 각 개체를 성향점수 오름차순으로 정렬 후 strata 지정 23 | num_rows = self._data[self._outcome_name].shape[0] 24 | self._data['strata'] = ( 25 | (self._data['propensity_score'].rank(ascending=True) / num_rows) * self.num_strata 26 | ).round(0) 27 | 28 | self._data['dbar'] = 1 - self._data[self._treatment_name[0]] # 비교집단일 경우 1(True) 29 | self._data['d_y'] = self._data[self._treatment_name[0]] * self._data[self._outcome_name] # 처치집단의 outcome 30 | self._data['dbar_y'] = self._data['dbar'] * self._data[self._outcome_name] # 비교집단의 outcome 31 | 32 | # clipping_threshold 보다 적은 처치집단 혹은 비교집단이 있는 strata 는 제외 33 | stratified = self._data.groupby('strata') 34 | clipped = stratified.filter( 35 | lambda strata: min(strata.loc[strata[self._treatment_name[0]] == 1].shape[0], 36 | strata.loc[strata[self._treatment_name[0]] == 0].shape[0]) > self.clipping_threshold 37 | ) 38 | self.logger.debug("After using clipping_threshold={0}, here are the number of data points in each strata:\n {1}".format(self.clipping_threshold, clipped.groupby(['strata',self._treatment_name[0]])[self._outcome_name].count())) 39 | if clipped.empty: 40 | raise ValueError("Method requires strata with number of data points per treatment > clipping_threshold (={0}). No such strata exists. Consider decreasing 'num_strata' or 'clipping_threshold' parameters.".format(self.clipping_threshold)) 41 | 42 | # 각 strata 별로 처치집단 혹은 비교집단의 outcome 가중합 (비교집단 개체 수에 대해) 43 | weighted_outcomes = clipped.groupby('strata').agg({ 44 | self._treatment_name[0]: ['sum'], 45 | 'dbar': ['sum'], 46 | 'd_y': ['sum'], 47 | 'dbar_y': ['sum'] 48 | }) 49 | weighted_outcomes.columns = ["_".join(x) for x in weighted_outcomes.columns.ravel()] 50 | treatment_sum_name = self._treatment_name[0] + "_sum" 51 | control_sum_name = "dbar_sum" 52 | 53 | weighted_outcomes['d_y_mean'] = weighted_outcomes['d_y_sum'] / weighted_outcomes[treatment_sum_name] # 처치집단의 평균 outcome 54 | weighted_outcomes['dbar_y_mean'] = weighted_outcomes['dbar_y_sum'] / weighted_outcomes['dbar_sum'] # 비교집단의 평균 outcome 55 | weighted_outcomes['effect'] = weighted_outcomes['d_y_mean'] - weighted_outcomes['dbar_y_mean'] # 처치집단의 평균 outcome - 비교집단의 평균 outcome 56 | 57 | total_treatment_population = weighted_outcomes[treatment_sum_name].sum() # 처치집단 개체 수 58 | total_control_population = weighted_outcomes[control_sum_name].sum() # 비교집단 개체 수 59 | total_population = total_treatment_population + total_control_population # 전체 개체 수 60 | self.logger.debug("Total number of data points is {0}, including {1} from treatment and {2} from control.". format(total_population, total_treatment_population, total_control_population)) 61 | 62 | if self._target_units=="att": # ATT 계산 63 | est = (weighted_outcomes['effect'] * weighted_outcomes[treatment_sum_name]).sum() / total_treatment_population 64 | elif self._target_units=="atc": # ATC 계산 65 | est = (weighted_outcomes['effect'] * weighted_outcomes[control_sum_name]).sum() / total_control_population 66 | elif self._target_units == "ate": # ATE 계산 67 | est = (weighted_outcomes['effect'] * (weighted_outcomes[control_sum_name]+weighted_outcomes[treatment_sum_name])).sum() / total_population 68 | else: 69 | raise ValueError("Target units string value not supported") 70 | ``` 71 | 72 | 성향점수 층화 방법은 비슷한 공변량 분포를 갖는 처치집단과 비교집단의 outcome 을 비교해 처치효과를 계산하므로 관찰되지 않은 공변량, 즉 혼동요인에 의한 효과가 의미 있게 감소하는 것으로 알려져 있습니다. 73 | 74 | 또한 층에 따른 차별적인 처치효과를 알 수 있으므로 보다 많은 정보를 확인할 수 있는 장점이 있으나 이를 위해서는 각 층에 충분히 많은 개체 수가 필요합니다. 75 | -------------------------------------------------------------------------------- /dowhy-key-concepts/instrumental-variable.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Instrumental Variable 3 | --- 4 | 5 | # 도구변수 (Instrumental Variable) 6 | 7 | * 작성자 : 경윤영 8 | 9 | ## 도구변수란? 10 | 11 | 설명변수를 통해서만 y에 영향을 미치는 변수 (설명변수가 통제될 때 도구변수의 변화는 y에 영향을 미치지 않는다.) 12 | 13 | ① 도구변수는 설명변수와 관련됨 14 | 15 | ② 도구변수는 외생적 16 | 17 | ## 도구변수를 사용할 때는? 18 | 19 | 설명변수가 내생성을 가질때 20 | $$E(U|X)\neq 0$$ 일 때 사용할 수 있다. 21 | 22 | 즉, 내생성이란 설명변수와 오차가 서로 correlated 일때를 말하고, 23 | 24 | 오차항과 상관된 설명변수들은 2가지 문제가 있다. 25 | 26 | 1) OLS 추정량은 비일관적 27 | 28 | 2) 표본의 크기가 아무리 커도 OLS 추정값은 참값과 다를 수 있음 29 | 30 | ## 설명변수를 내생적으로 만드는 3가지 31 | 32 | 33 | 34 | 1. **변수누락** 35 | 36 | log(임금) = $$\beta_0+\beta_1$$학력+$$\beta_2$$경력+$$u$$ 37 | 38 | if omitted variable = 능력 39 | 40 | 학력, 능력은 서로 관련되어 있고 능력은 오차항의 일부를 구성하게됨 41 | 42 | → 설명변수와 오차항이 서로 관련되는 문제 발생!! 43 | 44 | 즉, 동일한 경력에서 임금차이가 발생하게 되고 이 것이 학력차이인지 능력차이인지 알 수 없게됨 45 | 46 | 1. **동시성(Simultaneity)** 47 | 48 | 설명변수와 종속변수가 동시에 결정될 때 49 | 50 | 1. **설명변수의 측정오차** 51 | 52 | 설명변수 측정시 오차가 존재할 때 53 | 54 | 소비=$$\beta_0$$+$$\beta_1$$항상소득 55 | 56 | 소비를 추정하려고 할 때 항상소득(현재부터 미래까지 자신에게 올 소득의 평균)은 관측이 힘들기 때문에 실제소득을 사용함 57 | 58 | **실제소득 = 항상소득 + 일시소득** 59 | 60 | (여기에서 일시소득은 항상소득과 무관하게 발생함) 61 | 62 | 소비=$$\beta_0+\beta_1$$실제소득 + $$(-\beta_1$$일시소득) 63 | 64 | 일시소득은 관측이 불가능하여 $$(-\beta_1$$일시소득)이 오차항이 됨, 이때 일시소득은 실제소득의 구성항목으로 오차항인 일시소득과 설명변수인 실제소득이 관련되어 내생성을 가지게 됨 65 | 66 | ## 도구변수 수식으로 정리 67 | 68 | ### 1. just identified 일 때 69 | 70 | $$y = \beta_0+\beta_1x_1+\beta_2x_2 +u$$ 71 | 72 | $$x_1$$와 $$x_2$$ 가 외생적이면 $$\beta_0$$, $$\beta_1$$, $$\beta_2$$는 $$E(u)=0$$, $$E(x_1u)=0$$, $$E(x_2u) =0$$ 에 대응하는 관계에 의해 정의가 가능하다. 73 | 74 | $$E(u)=0\leftrightarrow E(y-\beta_0 -\beta_1x_1 - \beta_2x_2 ) = 0$$ 75 | 76 | $$E(x_1u)=0\leftrightarrow E[x_1(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] = 0$$ 77 | 78 | $$E(x_2u)=0\leftrightarrow E[x_2(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] = 0$$ 79 | 80 | 위의 직교방정식은 3개이고 결정해야할 모수 $$\beta_0$$, $$\beta_1$$, $$\beta_2$$가 3개이므로 특이한 상황이 아닌 이상 세 모수들은 관측변수들의 분포(평균, 분산, 공분산 등)에 의해 식별된다(identified). 81 | 82 | 아래와 같이 우리는 적률법(method of moments)를 사용하여 OLS 추정량을 구한다. 83 | 84 | *적률법: 모집단의 평균이 표본평균과 일치하는 모수를 찾는 방법 85 | 86 | $$E(y)=\beta_0+\beta_1E(x_1)+\beta_2E(x_2)$$ 87 | 88 | $$E(x_1y)=\beta_0E(x_1)+\beta_1E(x_1^2)+\beta_2E(x_1x_2)$$ 89 | 90 | $$E(x_2y)=\beta_0E(x_2)+\beta_1E(x_1x_2)+\beta_2E(x_2^2)$$ 91 | 92 | 93 | 즉, $$E(y)$$를 $$n^{-1}\sum_{i=1}^{n}y_i$$ 로 추정하고 $$E(x_1y)$$를 $$ n^{-1}\sum_{i=1}^{n}x_{i1}y_i$$ 로 추정, 94 | 95 | $$E(x_2y)$$를 $$ n^{-1}\sum_{i=1}^{n}x_{i2}y_i$$ 추정하는 등의 방식을 사용하여 모집단 상수를 구한 후 위의 등식에 대입한다면 $$\beta_0$$, $$\beta_1$$, $$\beta_2$$가 결정된다. (단, 비특이성을 만족시킨다: 3원 1차 연립방정식의 해가 유일할 조건이 있다.) 96 | 97 | 하지만, $$x_2$$가 내생적이라면 $$E(x_2, u) \neq 0$$ 이고 98 | 99 | $$E(x_2u)=0\leftrightarrow E[x_2(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] \neq 0$$ 이게 된다. 100 | 101 | 102 | 추가 정보가 없다면 위의 식을 만족시키는 $$\beta_0$$, $$\beta_1$$, $$\beta_2$$ 는 무수히 많게 된다. 103 | 104 | 그렇기에 세 모수들을 식별하려면 별도의 식이 요구되고 이를 위해 추가적인 외생변수인 도구변수 $$(z_2)$$ 를 사용한다. 105 | 106 | $$E(u)=0\leftrightarrow E(y-\beta_0 -\beta_1x_1 - \beta_2x_2 ) = 0$$ 107 | 108 | $$E(x_1u)=0\leftrightarrow E[x_1(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] = 0$$ 109 | 110 | 즉, 기존의 2개의 식과 아래의 식이 추가되어 $$\beta_0$$, $$\beta_1$$, $$\beta_2$$ 를 식별할 수 있게 된다(just identified). 111 | 112 | $$E(z_2u)=0\leftrightarrow E[z_2(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] = 0$$ 113 | 114 | ### 2. over-identified 일 때 115 | 116 | 모수들의 식별에 필요한 만큼보다 더 많은 제약식이 생길 때 over-identified 라고 한다. 117 | 118 | 예를 들어 내생적 설명변수 1개인데 도구변수가 2개이상 일때 over-identified라고 한다! 119 | 120 | 도구변수 $$z_{2a}$$, $$z_{2b}$$가 있으면 아래의 두 개의 식이 추가된다. 121 | 122 | $$E[z_{2a}(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] = 0$$ 123 | $$E[z_{2b}(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] = 0$$ 124 | 125 | 그렇다면 세 모수 $$\beta_0$$, $$\beta_1$$, $$\beta_2$$ 는 기존의 식들과 새롭게 생긴 위의 식들을 만족시켜야한다. 126 | 127 | $$E(u)=0\leftrightarrow E(y-\beta_0 -\beta_1x_1 - \beta_2x_2 ) = 0$$ 128 | 129 | $$E(x_1u)=0\leftrightarrow E[x_1(y-\beta_0 -\beta_1x_1 - \beta_2x_2 )] = 0$$ 130 | 131 | ### 3. under identified 일 때 132 | 133 | 반대로 제약조건의 개수가 모수의 개수보다 작으면 모수들은 under-identified 된다. 134 | 135 | 단순한 모형 $$y = \beta_2x_2+u$$ 일때 설명변수 $$x_2$$가 내생적이며 $$z_2$$가 도구변수라고 가정하자. 136 | 137 | 이 때 $$E(z_2u)=0$$ 이면 $$\beta_2$$와 관계없이 항상 0이 성립하게 되어 $$\beta_2$$를 식별할 수 없게 된다. 138 | 139 | $$E(z_2u)=0\leftrightarrow E[z_{2}(y- \beta_2x_2 )] = 0$$ 140 | 141 | $$E(z_2y) = \beta_2E(z_2x_2) = 0$$ 142 | 143 | ### 4. 2단계 최소제곱법 이용하기 144 | 145 | 2단계 최소제곱법은 회귀분석을 두 번 하는 것이다. 146 | 147 | $$y = \beta_0 + \beta_1x_1+\beta_2x_2 +u$$ 148 | 149 | 여기에서 $$x_1$$은 외생적이고 $$x_2$$는 내생적일 때, 추가 도구 변수는 $$z_{2a}$$ 이다. 150 | 151 | 1단계) $$\hat{x_2} = \hat{x_0} + \hat{x_1}z_{2a}$$ 152 | 153 | 154 | 1단계에서는 내생적 설명변수인 $$x_2$$를 $$x_1$$과 $$z_2$$에 대해 회귀하여 맞춘값을 구한다. 155 | 156 | 그리고 $$z_2$$의 유의성을 점검하는 것이 좋다. 157 | 158 | 2단계) $$y = \beta_0 + \beta_1x_1 + \beta_2\hat{x_2} + u$$ 159 | 2단계에서는 y를 $$x_1$$과 $$\hat{x_2}$$에 대해 OLS 회귀를 시킨다. 160 | -------------------------------------------------------------------------------- /advanced-causalml/uplift-modeling-uber.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Targeting Optimization Bidder at Uber 3 | --- 4 | 5 | # Uplift Modeling을 활용한 광고 입찰 최적화 - Uber 6 | 7 | * 작성자: [최보경](https://www.facebook.com/pagebokyung/) 8 | * 원문: [KDD2021 자료](https://drive.google.com/file/d/1QJJUCo4LH5kGQP3kaJlG1RdhjhaJWp-5/view)와 [Colab 노트북](https://colab.research.google.com/drive/1fnZEHIAcNxrvSxFrlO1hRTHO7sazXbo0?usp=sharing) 9 | 10 | Online Real Time Bidding에서 Uplift Modeling을 활용하여, HTE를 추정한 후 최적의 유저를 선택하는 방법론을 소개합니다.실시간 입찰에서 Uplift Modeling의 효과를 조사하기 위해 실제 캠페인 데이터에 대한 4개의 Meta-Learner 비교 분석을 실시했습니다. Offline Evaluation 및 Online Evaluation를 실시하고, 평가를 위한 근거 자료로 TML(Target Maximum likely Estimation) 기반 평균 처치 효과(ATE)를 사용하는 방법을 소개합니다. 11 | 12 | ## Background 13 | 14 | 우버가 **광고주(Demand-side)로서 어떤 광고를 어떤 퍼블리셔(Supply-side)에 내보내야** 주어진 예산 하에서 최적의 Performance를 낼 수 있을까?의 문제입니다. 15 | 16 | 퍼블리셔에 소속된 개별 유저가 Supply-side Platform에 광고 요청을 보내면, Uber Bidder 시스템이 4가지 단계를 통해서 관심이 있을 만한 유저에게 광고를 보여줍니다. 이를 통해 유저는 우버에서 더 많은 라이드를 타게 됩니다. 17 | 18 | 이 과정에서 핵심적인 역할을 하는 플랫폼을 ‘Uber Bidding Platform’이라고 부르며, 플랫폼의 중요한 컴포넌트는 3가지입니다. 19 | 20 | 1. Dynamic Bidding Strategies : 얼마나 실시간으로 빠르게 입찰을 진행하는가? 21 | 2. ML models 22 | 3. Incrementality Measurement : 얼마나 HTE를 추정이 잘 되었는가? 23 | 24 | ![](../.gitbook/assets/uber\_opt1.png)\ 25 | ![POI : point-of-interest](../.gitbook/assets/uber\_opt2.png) 26 | 27 | 여기서의 Incrementality Measurement 에서는 Uplift Modeling 이 핵심입니다. 기본 컨셉에 대해 설명하면, **이론적으로 광고주에게는 ‘Persuadable’ 세그먼트에 해당하는 유저를 인과추론과 머신러닝을 통해서 찾는 것**에 관심이 있습니다. 투자한 금액의 효용을 극대화할 수 있기 때문입니다. 28 | 29 | ![](../.gitbook/assets/uber\_opt3.png)\ 30 | ![설득 가능한 세그먼트 (Persuadables)는 타겟이 되고, 청개구리 (Sleeping dogs)는 타겟에서 반드시 제외합니다.무관심 (Lost causes), 잡은 물고기 (Sure things)는 비용 대비 임팩트를 내기 어려운 세그먼트입니다.](../.gitbook/assets/uber\_opt4.png) 31 | 32 | 위 다이어그램은 Uplift Modeling의 이론적인 동기가 됩니다. 33 | 34 | #### Uplift Modeling (Meta-learners for estimating HTE) 35 | 36 | Uplift Modeling은 그룹 단위의 유저에 대한 특정 개입(ex. 마케팅 캠페인, 프로모션 등)의 `인과 효과(증분)`를 모델링하는 기법입니다. 주로 프로모션, Up-selling, Cross-selling, 금융 서비스, 유저 이탈 및 유지(CRM) 분야에서 사용 되어 왔습니다. 37 | 38 | Uplift modeling은 `인과 효과(증분`)을 모델링하기 때문에, 수요를 만들고 관리하는 모든 비즈니스 영역에서 예상되는 임팩트(더 나아가 ROI)를 설명해줄 수 있다는 점이 장점입니다. 39 | 40 | A/B 테스트를 통한 실험 데이터를 Input으로 받아서 사용하기 때문에, A/B 테스트로 캠페인의 효과를 비교하는 것에 그치지 않고 **실제 Business value를 극대화해줄 수 있는 기법**입니다. 41 | 42 | 알아두어야 할 용어인, Heterogenous Treatment Effect (HTE)란 CATE간의 차이를 의미합니다. 데이터를 계층화(stratify)하고, 각 계층(strata) 내에서 ATE를 추정하여 계층 간의 차이를 비교(subgroup analysis)합니다. 43 | 44 | 이 CATE를 추정하는 모델은 다양한 종류가 있고, 그 중 하나가 Uplift modeling이며 머신 러닝을 통해 인과 효과를 추론하는 방법입니다. 좀 더 자세히는, Meta-learner를 활용해 CATE를 예측하는 방법입니다. 45 | 46 | **Meta-learner**란, 일반적인 `Supervised learning model(i.e. Base-learner)을 인과 효과를 추정하기 위해 다양하게 활용`하는 알고리즘을 의미합니다. 47 | 48 | 인과 효과 모델링에서는 근본적으로 ground truth가 없기 때문에, 일반적인 Supervised learning에서의 가정을 만족하지 못합니다. 실제 인과 효과는 알 수 없는 신의 영역이기 때문에, **보통 가능한 모든 Meta-learner를 사용**해보고 가장 정확한 인과 효과 추정치를 내는 모델을 선택한다고 합니다. 49 | 50 | Meta-learner 각 알고리즘의 장/단점을 인지하고, Uplift modeling을 사용하는 상황과 데이터셋의 크기에 맞게 선택해야 합니다. Meta-learner 알고리즘의 종류에는 S, T, X 가 있습니다. [인과 관계 분석 시리즈 (4): 머신러닝을 이용한 인과관계 추론 (feat. Metalearners)](https://assaeunji.github.io/machine%20learning/2020-07-05-causalml/) 에 상세한 설명이 있어서 참고하시면 좋을 것 같습니다. 51 | 52 | 다양한 레퍼런스를 찾아보며 용어가 헷갈렸는데요. 실제로 다양한 분야에서 연구되면서, HTE, CATE, Subgroup analysis, Uplift modeling 등 용어가 통일되지 않은 문제가 있다고 합니다. ([Rolling, 2014](https://core.ac.uk/download/pdf/76348572.pdf)) 53 | 54 | ``` 55 | The lack of a common framework and language for 56 | this problem may contribute to the disconnect; phrases used for conditional treatment 57 | effect estimation include heterogeneous treatment effect estimation, subgroup analysis, 58 | incremental response modeling, uplift modeling, and true lift modeling. 59 | ``` 60 | 61 | ![](../.gitbook/assets/uber\_opt5.png) 62 | 63 | [논문 Akshay Kumar 2018](http://cs229.stanford.edu/proj2018/report/296.pdf.) 에서, **유저에게 특정 offer를 보내는 것이 수익성이 있는지 결정하는 비즈니스 문제**에 있어서는 2가지 다른 관점에서 접근이 가능하다고 합니다. 64 | 65 | 1. Predictive response modeling (보통 알고 있는 classification 분류 문제로, 모델이 데이터가 각 클래스에 할당될 확률을 assign해주는 로직) 66 | 2. Uplift modeling (구매 확률의 \*\*증분 `i.e. probability gain that the customer will buy`\*\*을 모델이 예측해주는 로직) 67 | 68 | _예측의 대상이 일반적인 분류 문제와 달라, Uplift modeling은 인과 추론의 성격을 가집니다._ 69 | 70 | ## Overview 71 | 72 | 실험 셋업 → 데이터 수집 → 모델링 → 평가 과정을 거치며, 평가에서는 Offline / Online 두 타입으로 나뉩니다. 73 | 74 | ![](../.gitbook/assets/uber\_opt6.png) 75 | 76 | 실험 셋업의 절차에서는 실제로 실험을 실행하기 보다는 실험군과 대조군 간, Pre-treatment period에서의 행동 특성을 일치시켜준다는 특이점이 있습니다. 77 | 78 | ![](../.gitbook/assets/uber\_opt7.png) 79 | 80 | 데이터 수집은 다음과 같이 진행됩니다. 피쳐에는 우버 라이드 서비스와 우버 이츠 서비스가 모두 포함되었고, 앱 내에서 보게 된 광고, 도시의 특성이 포함되었습니다. Propensity Score 또한 pihat 형태로 포함이 됩니다만 0.5로 통일 되어 있는 상태입니다. 81 | 82 | ![](../.gitbook/assets/uber\_opt8.png) 83 | 84 | 코드에서의 데이터 셋 형태와 통계는 아래와 같습니다. 85 | 86 | ![](../.gitbook/assets/uber\_opt9.png) 87 | 88 | #### Meta Learners : Modeling 89 | 90 | 모델링을 이해하기 위해서 Meta Learner 들에 대한 이해가 필요한데요. CausalML의 근간은 Meta Learner 가 큰 역할을 차지하기 때문입니다. 우선 CausalML은 각 Meta Learner에서 CATE를 계산하기 쉬운 인터페이스(함수 사용)를 제공합니다. 91 | 92 | Uplift modeling에서는 2가지 common approach가 있습니다. 93 | 94 | ![](../.gitbook/assets/uber\_opt10.png) 95 | 96 | 1. **Two Model (이중 학습기)** 97 | * 기계학습 모델이 2가지로 생성됩니다. 한 모델은 실험군 관측치로 학습하고, 두번째 모델은 대조군 관측치로 학습합니다. 98 | 2. **One Model (단일 학습기)** 99 | * 1가지 기계학습 모델만 생성됩니다. 실험군, 대조군 관측치 함께 모델에 input으로 들어가 학습합니다. 100 | 101 | Meta-learner를 어떤 구조, 모델로 만들 것인가? 에 따라 여러 종류로 나뉩니다. 아래 장표는 슬라이드와 코랩에서 제공하는 각 구조에 대한 설명인데요. 가장 쉽게 표현되어 있습니다. 102 | 103 | ![](../.gitbook/assets/uber\_opt11.png) 104 | 105 | 구조는 S, T, X 를 모두 사용하고 아래 함수로 표현이 됩니다. 106 | 107 | ![](../.gitbook/assets/uber\_opt12.png) 108 | 109 | Meta-Learner 들을 통해서 Uplift Score 를 예측하는 것이 핵심인데요. 이 값을 이해하기 위해서, 스코어를 도출하는 예시 간단한 것을 가져왔습니다. ([출처](https://towardsdatascience.com/uplift-modeling-e38f96b1ef60)) 110 | 111 | > uplift score: the treatment effect between treatment and control 112 | 113 | ![](../.gitbook/assets/uber\_opt13.png)\ 114 | ![](../.gitbook/assets/uber\_opt14.png) 115 | 116 | ## Offline Evaluation 117 | 118 | 다양한 방식들을 거칩니다. 상세 내용은 [KDD2021 자료](https://drive.google.com/file/d/1QJJUCo4LH5kGQP3kaJlG1RdhjhaJWp-5/view)를 확인해주세요. 119 | 120 | 1. Base Learner 121 | * ![](../.gitbook/assets/uber\_opt18.png) 122 | 2. Lift Curve 123 | 3. Gain Chart 124 | 4. Qini Curve 125 | 5. Area Under Uplift Curve 126 | 6. TMLE (Targeted Maximum Likelihood Estimator) 127 | 7. TMLE Evaluation 128 | * 이를 통해서 Targeting Strategy를 결정합니다. S\_XGB 를 선택했을 때, 60% 상위의 Uplift Score 유저를 타겟팅하는 것이 가장 높은 ATE를 도출할 것입니다. 이는 예산을 40% 줄이면서, Advertising 효율을 67% 증가시킬 수 있습니다. 129 | * [TMLE 관련 레퍼런스](https://towardsdatascience.com/targeted-maximum-likelihood-tmle-for-causal-inference-1be88542a749) 130 | * ![](../.gitbook/assets/uber\_opt15.png) 131 | * ![](../.gitbook/assets/uber\_opt16.png) 132 | 133 | ## Online Evaluation 134 | 135 | ![](../.gitbook/assets/uber\_opt17.png) 136 | 137 | Offline Evaluation을 통해서 결정한 Policy를 집행하는 온라인 실험을 진행했습니다. 결론적으로 Spend 는 46% 줄었고, GB기준 ROAS는 93% 증가했습니다. 138 | -------------------------------------------------------------------------------- /example-notebooks/custom-refutation.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | A Simple Example on Creating a Custom Refutation Using User-Defined Outcome 4 | Functions 5 | --- 6 | 7 | # 사용자 정의 결과 함수를 사용해 추정치 반박하기 8 | 9 | * 작성자: 경윤영 10 | 11 | * 원문: [Dowhy -Simple Example on Creating a Custom Refutation Using User-Defined Outcome Functions](https://microsoft.github.io/dowhy/example_notebooks/dowhy_demo_dummy_outcome_refuter.html) 12 | 13 | 본 글은 Refute 방법론을 사용하는 코드 레퍼런스를 남기는 목적으로 작성되었습니다. 그렇기에 Refute에 대한 학습이 필요하신 경우 DOWHY KEY CONCEPTS의 [추정치를 검증하는 방법](https://playinpap.gitbook.io/dowhy/dowhy-key-concepts/sensitivity-analysis) 을 참고하시길 바랍니다. 14 | 15 | 본 실험에서는 선형데이터(linear dataset)를 구축하였고, 선형 회귀를 estimator로 사용합니다. 16 | 17 | Refute의 방법으로는 outcome을 대체하는 dummy outcome refuter 방법을 사용하여 진행합니다. 18 | 19 | ## 1. Insert Dependencies 20 | 21 | ```python 22 | from dowhy import CausalModel 23 | import dowhy.datasets 24 | import pandas as pd 25 | import numpy as np 26 | # Config dict to set the logging level 27 | import logging.config 28 | DEFAULT_LOGGING = { 29 | 'version': 1, 30 | 'disable_existing_loggers':False, 31 | 'loggers': { 32 | '': { 33 | 'level': 'WARN', 34 | }, 35 | } 36 | } 37 | 38 | logging.config.dictConfig(DEFAULT_LOGGING) 39 | ``` 40 | 41 | ## 2. Create the dataset 42 | 43 | Hyper parameter의 값을 바꾸며 effect의 변화를 확인할 수 있습니다. 44 | 45 | | Variable Name | Data Type | Interpretation | 46 | | --- | --- | --- | 47 | | Zi | float | 도구변수(Instrument Variable) | 48 | | Wi | float | 교란변수(Confounder) | 49 | | V0 | float | 처치변수(Treatment) | 50 | | Y | float | 결과변수(Outcome) | 51 | 52 | ```python 53 | # Value of the coefficient [BETA] 54 | BETA = 10 55 | # Number of Common Causes 56 | NUM_COMMON_CAUSES = 2 57 | # Number of Instruments 58 | NUM_INSTRUMENTS = 1 59 | # Number of Samples 60 | NUM_SAMPLES = 100000 61 | # Treatment is Binary 62 | TREATMENT_IS_BINARY =False 63 | data = dowhy.datasets.linear_dataset(beta=BETA, 64 | num_common_causes=NUM_COMMON_CAUSES, 65 | num_instruments=NUM_INSTRUMENTS, 66 | num_samples=NUM_SAMPLES, 67 | treatment_is_binary=TREATMENT_IS_BINARY) 68 | data['df'].head() 69 | ``` 70 | 71 | | | Z0 | W0 | W1 | V0 | y | 72 | | --- | --- | --- | --- | --- | --- | 73 | | 0 | 1.0 | 0.112689 | -0.501474 | 8.076574 | 80.106461 | 74 | | 1 | 0.0 | 0.645347 | -0.072829 | -0.219279 | -0.092377 | 75 | | 2 | 0.0 | 0.323480 | 0.989825 | 0.365947 | 6.900517 | 76 | | 3 | 0.0 | 0.030437 | 1.334423 | 1.740524 | 20.319910 | 77 | | 4 | 1.0 | 1.377841 | 0.628397 | 11.938058 | 125.523936 | 78 | 79 | ## 3. Creating the Causal Model 80 | 81 | ```python 82 | model = CausalModel( 83 | data = data['df'], 84 | treatment = data['treatment_name'], 85 | outcome = data['outcome_name'], 86 | graph = data['gml_graph'], 87 | instruments = data['instrument_names'] 88 | ) 89 | 90 | model.view_model() 91 | ``` 92 | 93 | 아래의 그림은 처치변수(treatment), 결과변수(outcome), 교란변수(confounders), 도구변수(instrument variable)의 관계를 살펴볼 수 있습니다. 94 | 95 | W0, W1 : 교란변수(confounders) 96 | 97 | Z0 : 도구변수(instrument variable) 98 | 99 | V0: 처치변수(treatment) 100 | 101 | Y: 결과변수(outcome) 102 | 103 | ![변수들의 관계](https://user-images.githubusercontent.com/39981604/153433647-39b2fd58-d7f1-485c-9f5e-39e1aa8e8899.png) 104 | 105 | ## 4. Identify the Estimand 106 | 107 | ```python 108 | identified_estimand = model.identify_effect(proceed_when_unidentifiable=True) 109 | print(identified_estimand) 110 | ``` 111 | 112 | ``` 113 | Estimand type: nonparametric-ate 114 | 115 | ### Estimand : 1 116 | Estimand name: backdoor 117 | Estimand expression: 118 | d 119 | ─────(Expectation(y|W1,W0)) 120 | d[v₀] 121 | Estimand assumption 1, Unconfoundedness: If U→{v0} and U→y then P(y|v0,W1,W0,U) = P(y|v0,W1,W0) 122 | 123 | ### Estimand : 2 124 | Estimand name: iv 125 | Estimand expression: 126 | Expectation(Derivative(y, [Z0])*Derivative([v0], [Z0])**(-1)) 127 | Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0}) 128 | Estimand assumption 2, Exclusion: If we remove {Z0}→{v0}, then ¬({Z0}→y) 129 | 130 | ### Estimand : 3 131 | Estimand name: frontdoor 132 | No such variable found! 133 | ``` 134 | 135 | ## 5. Estimating the Effect 136 | 137 | ```python 138 | causal_estimate = model.estimate_effect( identified_estimand, 139 | method_name="iv.instrumental_variable", 140 | method_params={'iv_instrument_name':'Z0'} 141 | ) 142 | print(causal_estimate) 143 | ``` 144 | 145 | ``` 146 | *** Causal Estimate *** 147 | 148 | ## Identified estimand 149 | Estimand type: nonparametric-ate 150 | 151 | ### Estimand : 1 152 | Estimand name: iv 153 | Estimand expression: 154 | Expectation(Derivative(y, [Z0])*Derivative([v0], [Z0])**(-1)) 155 | Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0}) 156 | Estimand assumption 2, Exclusion: If we remove {Z0}→{v0}, then ¬({Z0}→y) 157 | 158 | ## Realized estimand 159 | Realized estimand: Wald Estimator 160 | Realized estimand type: nonparametric-ate 161 | Estimand expression: 162 | -1 163 | Expectation(Derivative(y, Z0))⋅Expectation(Derivative(v0, Z0)) 164 | Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0}) 165 | Estimand assumption 2, Exclusion: If we remove {Z0}→{v0}, then ¬({Z0}→y) 166 | Estimand assumption 3, treatment_effect_homogeneity: Each unit's treatment ['v0'] is affected in the same way by common causes of ['v0'] and y 167 | Estimand assumption 4, outcome_effect_homogeneity: Each unit's outcome y is affected in the same way by common causes of ['v0'] and y 168 | 169 | Target units: ate 170 | 171 | ## Estimate 172 | Mean value: 9.99706705820163 173 | ``` 174 | 175 | ## 6. Refuting the Estimate 176 | 177 | ### 1) Using a Randomly Generated Outcome 178 | 179 | ```python 180 | ref = model.refute_estimate(identified_estimand, 181 | causal_estimate, 182 | method_name="dummy_outcome_refuter" 183 | ) 184 | print(ref[0]) 185 | ``` 186 | 187 | ``` 188 | Refute: Use a Dummy Outcome 189 | Estimated effect:0 190 | New effect:4.657654751543888e-05 191 | p value:0.49 192 | 193 | ``` 194 | 195 | ### 2) Using a Function that Generates the Outcome from the Confounders 196 | 197 | 아래와 같이 새로운 Y를 생성하기 위해 교란변수를 사용한 선형식을 정의합니다. 198 | 199 | $$y_{new} = \beta_0W_0 + \beta_1W_1 + \gamma_0$$ 200 | 201 | where, $$\beta_0 = 1, \beta_1 = 2, \gamma_0 = 3$$ 202 | 203 | ```python 204 | coefficients = np.array([1,2]) 205 | bias = 3 206 | def linear_gen(df): 207 | y_new = np.dot(df[['W0','W1']].values,coefficients) + 3 208 | return y_new 209 | ``` 210 | 211 | ```python 212 | ref = model.refute_estimate(identified_estimand, 213 | causal_estimate, 214 | method_name="dummy_outcome_refuter", 215 | outcome_function=linear_gen 216 | ) 217 | 218 | print(ref[0]) 219 | ``` 220 | 221 | ``` 222 | Refute: Use a Dummy Outcome 223 | Estimated effect:0 224 | New effect:-1.1692081553648758e-05 225 | p value:0.47 226 | 227 | ``` 228 | -------------------------------------------------------------------------------- /advanced-econml/proxy-metric-roi-microsoft.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Long-Term Return-on-Investment at Microsoft via Short-Term Proxies 3 | --- 4 | 5 | # 단기 Proxy Metric를 통한 장기 ROI 추정 - Microsoft 6 | 7 | * 작성자: 허현 8 | * 원문: [KDD 2021 Slide](https://drive.google.com/file/d/1FEKXFHHATntHjsEymXnEw6GAiUGMm8sG/view) 9 | 10 | 11 | ## Double Machine Learning (DML)이란? 12 | 두번의 머신러닝을 통해 Treatment-Outcome 효과 추정하는 방법론 13 | 14 | Double Machine Learning (DML) 통해 하고 싶은 것은 아래와 같이 추정 bias를 줄여서 정확도를 높이려는 것 (논문 제목도 [Double/Debiased Machine Learning for Treatment and Causal Parameters](https://arxiv.org/abs/1608.00060)) 15 | ![](<../.gitbook/assets/debias_dml.png>) 16 | 17 | 18 | ### 대략적인 DML 진행 방식 19 | 1. T(Treatment), Y(outcome), X(covariates)가 있을 때 [T, X]와 [T, Y] 데이터셋 생성 20 | 2. T\~X, Y\~T ML 모델 훈련 21 | 3. ML 모델로 예측한 T_hat과 Y_hat을 각각 T와 Y에서 뺌 → T_tilda, Y_tilda 22 | 4. Y_tilda ~ T_tilda로 X의 영향력을 제거한 회귀를 통해 효과 추정 23 | 24 | 잔차끼리 회귀하여 X의 영향력을 제거한 효과를 구하는 것은 [프리슈-워-로벨 정리](https://datascienceschool.net/03%20machine%20learning/04.05%20%EB%B6%80%EB%B6%84%ED%9A%8C%EA%B7%80.html)에 근거 25 | 26 | 27 | ## 케이스 스터디 28 | 목표: 단기간의 데이터로 장기간의 ROI를 측정하게 하는 목적으로 진행 29 | ![](<../.gitbook/assets/attribute_incremental_revenue.png>) 30 | 위와 같이 어떤 액션의 Incremental Revenue를 구하기를 원함 31 | 32 | ### 문제상황1 33 | 인과 그래프는 아래와 같이 그릴 수 있는데 현재시점투자(treatment), 장기수익(outcome)에 과거 투자, 인구통계정보, 과거 수익, 과거 대리변수 등의 confounder가 영향을 주기 때문에 바로 treatment→outcome 구조로 모델링하면 공통원인으로 인한 편향된 수치가 나옴 34 | ![](<../.gitbook/assets/longtermroi_problem1.png>) 35 | 36 | DML을 통해 confounder의 영향에 의한 효과를 제거(통제)한 효과를 추정하고자 함 37 | ![](<../.gitbook/assets/longtermroi_problem1_sol.png>) 38 | 39 | **DML 절차** 40 | ![](<../.gitbook/assets/longtermroi_problem1_dml.png>) 41 | 42 | 1. Y를 W로 예측하도록 모델을 만들어서 Y hat을 만듦 43 | 2. T를 W로 예측하도록 모델을 만들어서 T hat을 만듦 44 | 3. 각각을 잔차화시켜서 (Y-Yhat)을 (T-That)으로 회귀 45 | 4. 이렇게 causal effect 추정 46 | 47 | ### 문제상황2 48 | outcome이 장기수익이기 때문에 아직 관측되지 않았음 49 | ![](<../.gitbook/assets/longtermroi_problem2.png>) 50 | 51 | 과거 데이터셋을 통해 단기 매출로 장기 매출을 예측하는 ML 모델 만들고, 이 모델로 현재 가진 데이터셋으로 장기 매출을 예측하게 함 52 | ![](<../.gitbook/assets/longtermroi_problem2_sol.png>) 53 | 54 | ### 문제상황3 55 | 현재의 투자가 미래의 투자에 영향을 주고 미래의 투자가 장기수익에 영향을 주어 결과적으로 현재 투자의 영향이 두 번 카운팅 됨 56 | ![](<../.gitbook/assets/longtermroi_problem3.png>) 57 | 58 | 순차적으로 DML 적용하는 방식(Dynamic DML)으로 해결 59 | ![](<../.gitbook/assets/longtermroi_problem3_sol.png>) 60 | 61 | - T_t는 Y_t, Y_t+1, T_t+1 세 가지에 연결되는데 T_t와 Y_t는 (시점이 두 개만 있다 했을 때) 교란 요소가 없기 때문에 빼면 T_t, Y_t+1, T_t+1 세 변수가 남게 됨 62 | - 이 때 DML을 하면 T_t의 영향력을 통제한 T_t+1 → Y_t+1이 나오게 되고(theta_t+1) 63 | - Y_t+1에서 theta_t+1 * T_t+1을 빼면 T_t의 t+1 시점 장기적 영향으로 인한 Y 값이 나오고, 이를 Y_t와 더하면 두 시점에 걸친 T_t를 통해 발생한 Y_adj를 구할 수 있음 64 | 65 | 시점을 좀 더 확대하여 2년 데이터를 6개월씩 4번 나눴을 때, 4번째 기간 수익부터 여러 시점의 투자 영향을 구하고 그만큼 빼줌 66 | 67 | ![](<../.gitbook/assets/dynamicdml1.png>) 68 | ![](<../.gitbook/assets/dynamicdml2.png>) 69 | ![](<../.gitbook/assets/dynamicdml3.png>) 70 | 71 | ```python 72 | # on historical data construct adjusted outcomes 73 | from econml.dynamic.dml import DynamicDML 74 | 75 | panelYadj = panelY.copy() 76 | 77 | est = DynamicDML( 78 | model_y=LassoCV(max_iter=2000), model_t=MultiTaskLassoCV(max_iter=2000), cv=2 79 | ) 80 | for t in range(1, n_periods): # for each target period 1...m 81 | # learn period effect for each period treatment on target period t 82 | est.fit( 83 | long(panelY[:, 1 : t + 1]), 84 | long(panelT[:, 1 : t + 1, :]), # reshape data to long format 85 | X=None, 86 | W=long(panelX[:, 1 : t + 1, :]), 87 | groups=long(panelGroups[:, 1 : t + 1]), 88 | ) 89 | # remove effect of observed treatments 90 | T1 = wide(panelT[:, 1 : t + 1, :]) 91 | panelYadj[:, t] = panelY[:, t] - est.effect( 92 | T0=np.zeros_like(T1), T1=T1 93 | ) # reshape data to wide format 94 | ``` 95 | (코드상으로는 앞쪽 시점부터 하는 것 같아서 뭐가 맞는지 헷갈림) 96 | 97 | ### 최종형태 98 | 일련의 과정들을 파이프라인화 하면 ROI 측정을 할 수 있음 99 | ![](<../.gitbook/assets/unified_pipeline.png>) 100 | 101 | 102 | 103 | # (추가) Double Machine Learning이란? 104 | 105 | * 작성자: 경윤영 106 | 107 | 본글은 DML이 무엇인지 아주 가볍게 이론을 소개합니다. 108 | 자세한 정보를 알고 싶으시다면 [Double Machine Learning for causal inference](https://towardsdatascience.com/double-machine-learning-for-causal-inference-78e0c6111f9d)를 찾아가시면 됩니다! 109 | 110 | ## 1. 소개 111 | 112 | ### Double Machine Learning(DML)이란 113 | 114 | 1. 머신러닝을 사용하여 인과효과를 추정하는 프레임워크이다. 115 | 2. 신뢰구간 추정을 사용한다. 116 | 3. “root n-consistency” 추정량에 따라 convergence와 data-efficiency를 갖고 있다. 117 | 118 | ### 그렇다면 DML의 개념은 어디서 나왔을까? 119 | 120 | 1. 머신러닝을 통계적인 관점에서는 비모수적(nonparametric) 혹은 반모수적(semiparametric) 모형의 모음이라고 볼 수 있다. 121 | 2. 또한 비모수적 및 반모수적 추정방법(한계, 효율성 등)에 대한 이론이 많이 들어가 있다. 122 | 123 | ### DML을 왜 사용할까? 124 | 125 | 1. 머신러닝은 modeling functions과 expectations에 대해 힘이 있다. 126 | 2. 머신러닝은 전통적인 통계 기법(예시: OLS)보다 예측에 사용하기 좋다. 127 | 특히 데이터가 고차원일 때! 128 | 3. 전통적인 통계 방법과 비교할 때 머신러닝은 $$m_0(Z), g_0(Z)$$에 대해서 강한 가정이 없어도 된다. 129 | 4. 직교화(Orthogonality)를 통해서 정규화 편향을, cross-fitting을 통해서 과대적합 편향을 수정하는 것을 목표로 한다. 130 | 131 | ## 2. 세팅 132 | 133 | ### 1. DAG 134 | 135 | ![](https://user-images.githubusercontent.com/39981604/153712545-26522887-a67a-4877-834b-0b5479535dd1.png) 136 | 137 | $$Y = D\theta_0 + g_0(Z) + U, E[U|Z, D] = 0$$ 식(1) 138 | 139 | $$D=m_0(Z)+V, E[V|Z]=0$$ 식(2) 140 | 141 | Y: 결과변수 142 | 143 | D: 처리변수(이항변수) 144 | 145 | $$\theta_0$$: 추정하고자 하는 파라미터 146 | 147 | Z: 공변량 벡터 148 | 149 | U, V: 오차항 150 | 151 | $$\eta_0=(g_0, m_0)$$ : 장애모수(nuisance parameter) 152 | 153 | 식(1)은 $$\theta_0$$ 를 추정하기 위해 만든식이고, 식(2)는 공변량에 대한 treatment의 종속성을 추적하기 위함이다. 154 | 155 | ## 3. Naive estimator 156 | 157 | 그렇다면 머신런닝만을 사용하여 직접적으로 $\theta_0$을 추정하지 않는걸까? 158 | 추정에서 편향성이 나타나기 때문이다. 159 | 160 | ![](https://user-images.githubusercontent.com/39981604/153712577-f12b3983-bb38-46c8-adfe-de2575ba6ad6.png) 161 | 162 | ### 4. Neyman Orthogonality 163 | 164 | 위의 편향을 없애기 위해 우리는 neyman orthogonality를 사용한다. 직교성은 Frisch-Waugh-Lovell 정리가 기반이 된다. (Frisch-Waugh-Lovell에 대한 자세한 설명은 [여기](https://datascienceschool.net/03%20machine%20learning/04.05%20%EB%B6%80%EB%B6%84%ED%9A%8C%EA%B7%80.html)로) 165 | 166 | 예를 들어 $$Y = \beta_0 + \beta_1D + \beta_2Z + U$$ 에서 $$\beta_1$$를 추정하기 위해 두 가지 방법을 쓴다. 167 | 168 | 1. OLS를 사용하여 D 및 Z에 대한 Y의 선형회귀 169 | 2. ① Z에서 D를 회귀 ②Z에서 Y를 회귀, ③ $$\beta_1$$를 얻기 위해 ①의 잔차에 대해 ②의 잔차를 회귀한다. 170 | 171 | 편향을 없애기 위해 3단계에 걸쳐서 머신러닝을 진행한다. 172 | 173 | 1. 머신러닝을 사용하여 Z를 기반으로 D를 예측한다. 174 | 2. 머신러닝을 사용하여 Z를 기반으로 Y를 예측한다. 175 | 3. $$\theta_0$$를 얻기 위해 ①의 잔차에 대해 ②의 잔차를 회귀한다. 176 | 177 | 3단계를 걸친 머신러닝은 “직교화”되어 편향되지 않는 “root n-consistency” 를 산출한다. 178 | 179 | ![](https://user-images.githubusercontent.com/39981604/153712587-a521419b-7d5f-4bf7-b0e4-9c0ae4e5318b.png) 180 | 181 | ### 5. Sample-splitting and Cross-fitting 182 | 183 | 과적합의 편향을 제거하기 위해서 sample splitting의 방법 중 하나인 cross-fitting을 진행한다. 184 | 185 | 1. 데이터를 무작위 추출하여 두 개의 하위 집합으로 분할을 진행한다. 186 | 2. 첫 번째 하위 집합에서 D와 Y에 대한 머신러닝 모델을 맞춘다(fit). 187 | 3. 2단계에서 얻은 모델을 사용하여 두번째 부분 집합에서 $$\theta_{0,1}$$을 추정한다. 188 | 4. 두 번째 하위집합에 머신러닝 모델을 맞춘다(fit). 189 | 5. 4단계에서 얻은 모델을 사용하여 두번째 부분 집합에서 $$\theta_{0,2}$$을 추정한다. 190 | 6. 최종적으로 추정량 $$\theta_0$$은 $$\theta_{0,1}$$와 $$\theta_{0,2}$$의 평균한 값으로 한다. 191 | 192 | cf) sample splitting 방법 193 | 194 | 1. 데이터를 무작위 추출하여 두 개의 하위 집합으로 분할을 진행한다. 195 | 2. 첫 번째 하위 집합에서 D와 Y에 대한 머신러닝 모델을 맞춘다(fit). 196 | 3. 2단계에서 얻은 모델을 사용하여 두번째 부분 집합에서 $$\theta_0$$을 추정한다. 197 | -------------------------------------------------------------------------------- /example-notebooks/ihdp-health.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 가정 방문과 특수 아동 발달 센터 방문이 조산아의 건강 및 발달에 얼마나 영향을 줄까? 3 | --- 4 | 5 | # IHDP 데이터셋에 DoWhy 적용하기 6 | 7 | * 작성자: 허현 8 | * 원문: [DoWhy example on ihdp (Infant Health and Development Program) dataset](https://microsoft.github.io/dowhy/example\_notebooks/dowhy\_ihdp\_data\_example.html) 9 | 10 | > 역자주 - 원문의 내용 자체가 IHDP 데이터셋에 대한 설명이나 인과분석 과정에 대한 설명이 생략되어 있습니다.\ 11 | > 해당 문서의 주요 목적은 여러 Propensity Score 방법론과 Refute 방법론을 사용하는 코드 레퍼런스를 남기는 것에 있기 때문에 관련 내용에 대한 학습이 필요한 경우 DOWHY KEY CONCEPTS의 [성향점수(Propensity Score)](https://playinpap.gitbook.io/dowhy/dowhy-key-concepts/propensity-score)와 [추정치를 검증하는 방법](https://playinpap.gitbook.io/dowhy/dowhy-key-concepts/sensitivity-analysis)를 참고하시면 좋습니다. 12 | 13 | ```python 14 | # importing required libraries : 필요 라이브러리 불러오기 15 | import dowhy 16 | from dowhy import CausalModel 17 | import pandas as pd 18 | import numpy as np 19 | ``` 20 | 21 | ## 데이터 로드 22 | 23 | ```python 24 | data= pd.read_csv("https://raw.githubusercontent.com/AMLab-Amsterdam/CEVAE/master/datasets/IHDP/csv/ihdp_npci_1.csv", header = None) 25 | col = ["treatment", "y_factual", "y_cfactual", "mu0", "mu1" ,] 26 | for i in range(1,26): 27 | col.append("x"+str(i)) 28 | data.columns = col 29 | data = data.astype({"treatment":'bool'}, copy=False) 30 | data.head() 31 | ``` 32 | 33 | | | treatment | y\_factual | y\_cfactual | mu0 | mu1 | x1 | x2 | x3 | x4 | x5 | ... | x16 | x17 | x18 | x19 | x20 | x21 | x22 | x23 | x24 | x25 | 34 | | - | --------- | ---------- | ----------- | -------- | -------- | --------- | --------- | --------- | --------- | --------- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | 35 | | 0 | True | 5.599916 | 4.318780 | 3.268256 | 6.854457 | -0.528603 | -0.343455 | 1.128554 | 0.161703 | -0.316603 | ... | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 36 | | 1 | False | 6.875856 | 7.856495 | 6.636059 | 7.562718 | -1.736945 | -1.802002 | 0.383828 | 2.244320 | -0.629189 | ... | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 37 | | 2 | False | 2.996273 | 6.633952 | 1.570536 | 6.121617 | -0.807451 | -0.202946 | -0.360898 | -0.879606 | 0.808706 | ... | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 38 | | 3 | False | 1.366206 | 5.697239 | 1.244738 | 5.889125 | 0.390083 | 0.596582 | -1.850350 | -0.879606 | -0.004017 | ... | 1 | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 39 | | 4 | False | 1.963538 | 6.202582 | 1.685048 | 6.191994 | -1.045229 | -0.602710 | 0.011465 | 0.161703 | 0.683672 | ... | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 40 | 41 | 5 rows × 30 columns 42 | 43 | ## 1. Model 44 | 45 | ```python 46 | # Create a causal model from the data and given common causes. : 데이터와 공통 원인으로 인과 모델 생성 47 | xs = "" 48 | for i in range(1,26): 49 | xs += ("x"+str(i)+"+") 50 | 51 | model=CausalModel( 52 | data = data, 53 | treatment='treatment', 54 | outcome='y_factual', 55 | common_causes=xs.split('+') 56 | ) 57 | ``` 58 | 59 | ## 2. Identify 60 | 61 | ```python 62 | #Identify the causal effect : 인과 효과 규명하기 63 | identified_estimand = model.identify_effect(proceed_when_unidentifiable=True) 64 | print(identified_estimand) 65 | ``` 66 | 67 | ``` 68 | Estimand type: nonparametric-ate 69 | 70 | ### Estimand : 1 71 | Estimand name: backdoor1 (Default) 72 | Estimand expression: 73 | d 74 | ────────────(Expectation(y_factual|x18,x17,x11,x2,x5,x14,x22,x23,x24,x21,x16,x20,x8,x4,x7,x19,x10,x15,x25,x9,x12,x3,x6,x1,x13)) 75 | 76 | Estimand assumption 1, Unconfoundedness: If U→{treatment} and U→y_factual then P(y_factual|treatment,x18,x17,x11,x2,x5,x14,x22,x23,x24,x21,x16,x20,x8,x4,x7,x19,x10,x15,x25,x9,x12,x3,x6,x1,x13,U) = P(y_factual|treatment,x18,x17,x11,x2,x5,x14,x22,x23,x24,x21,x16,x20,x8,x4,x7,x19,x10,x15,x25,x9,x12,x3,x6,x1,x13) 77 | ``` 78 | 79 | ## 3. Estimate (using different methods) 80 | 81 | ### 3.1 Using Linear Regression 82 | 83 | ```python 84 | # Estimate the causal effect and compare it with Average Treatment Effect : 인과 효과를 추정하고 ATE와 비교 85 | estimate = model.estimate_effect(identified_estimand, 86 | method_name="backdoor.linear_regression", test_significance=True 87 | ) 88 | 89 | print(estimate) 90 | 91 | print("Causal Estimate is " + str(estimate.value)) 92 | data_1 = data[data["treatment"]==1] 93 | data_0 = data[data["treatment"]==0] 94 | 95 | print("ATE", np.mean(data_1["y_factual"])- np.mean(data_0["y_factual"])) 96 | ``` 97 | 98 | ``` 99 | *** Causal Estimate *** 100 | 101 | ## Identified estimand 102 | Estimand type: nonparametric-ate 103 | 104 | ## Realized estimand 105 | b: y_factual~treatment+x18+x17+x11+x2+x5+x14+x22+x23+x24+x21+x16+x20+x8+x4+x7+x19+x10+x15+x25+x9+x12+x3+x6+x1+x13 106 | Target units: ate 107 | 108 | ## Estimate 109 | Mean value: 3.928671750872714 110 | p-value: [1.58915682e-156] 111 | 112 | Causal Estimate is 3.928671750872714 113 | ATE 4.021121012430829 114 | ``` 115 | 116 | ### 3.2 Using Propensity Score Matching 117 | 118 | ```python 119 | estimate = model.estimate_effect(identified_estimand, 120 | method_name="backdoor.propensity_score_matching" 121 | ) 122 | 123 | print("Causal Estimate is " + str(estimate.value)) 124 | 125 | print("ATE", np.mean(data_1["y_factual"])- np.mean(data_0["y_factual"])) 126 | ``` 127 | 128 | ``` 129 | Causal Estimate is 3.9791388232170393 130 | ATE 4.021121012430829 131 | ``` 132 | 133 | ### 3.3 Using Propensity Score Stratification 134 | 135 | ```python 136 | estimate = model.estimate_effect(identified_estimand, 137 | method_name="backdoor.propensity_score_stratification", method_params={'num_strata':50, 'clipping_threshold':5} 138 | ) 139 | 140 | print("Causal Estimate is " + str(estimate.value)) 141 | print("ATE", np.mean(data_1["y_factual"])- np.mean(data_0["y_factual"])) 142 | ``` 143 | 144 | ``` 145 | Causal Estimate is 3.4550471588628207 146 | ATE 4.021121012430829 147 | ``` 148 | 149 | ### 3.4 Using Propensity Score Weighting 150 | 151 | ```python 152 | estimate = model.estimate_effect(identified_estimand, 153 | method_name="backdoor.propensity_score_weighting" 154 | ) 155 | 156 | print("Causal Estimate is " + str(estimate.value)) 157 | 158 | print("ATE", np.mean(data_1["y_factual"])- np.mean(data_0["y_factual"])) 159 | ``` 160 | 161 | ``` 162 | Causal Estimate is 3.409737824407883 163 | ATE 4.021121012430829 164 | ``` 165 | 166 | ## 4. Refute 167 | 168 | ### 4.1 random\_common\_cause 169 | 170 | ```python 171 | refute_results=model.refute_estimate(identified_estimand, estimate, 172 | method_name="random_common_cause") 173 | print(refute_results) 174 | ``` 175 | 176 | ``` 177 | Refute: Add a Random Common Cause 178 | Estimated effect:3.409737824407883 179 | New effect:3.4652727093798434 180 | ``` 181 | 182 | ### 4.2 placebo\_treatment\_refuter 183 | 184 | ```python 185 | res_placebo=model.refute_estimate(identified_estimand, estimate, 186 | method_name="placebo_treatment_refuter", placebo_type="permute") 187 | print(res_placebo) 188 | ``` 189 | 190 | ``` 191 | Refute: Use a Placebo Treatment 192 | Estimated effect:3.409737824407883 193 | New effect:-0.023837129277084368 194 | p value:0.44999999999999996 195 | ``` 196 | 197 | ### 4.3 Data Subset Refuter 198 | 199 | ```python 200 | res_subset=model.refute_estimate(identified_estimand, estimate, 201 | method_name="data_subset_refuter", subset_fraction=0.9) 202 | print(res_subset) 203 | ``` 204 | 205 | ``` 206 | Refute: Use a subset of data 207 | Estimated effect:3.409737824407883 208 | New effect:3.3635040963705256 209 | p value:0.29000000000000004 210 | ``` 211 | -------------------------------------------------------------------------------- /example-notebooks/membership-effect.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Estimating the effect of a Member Rewards program 3 | --- 4 | 5 | # 멤버십 리워드 프로그램의 효과 추정하기 6 | 7 | * 작성자: [김가연](https://www.facebook.com/profile.php?id=1721702213) 8 | * 원문: [Estimating the effect of a Member Rewards program](https://microsoft.github.io/dowhy/example_notebooks/dowhy_example_effect_of_memberrewards_program.html) 9 | 10 | DoWhy 를 사용하여 고객에 대한 **구독 또는 리워드 프로그램의 효과**를 추정하는 방법을 알아보겠습니다. 11 | 12 | 고객이 웹사이트에 가입하면 추가 혜택을 받는 멤버십 리워드 프로그램이 있다고 가정해봅시다. 해당 프로그램이 효과적인지 어떻게 알 수 있을까요? 여기서 우리는 **인과적인 질문**을 할 수 있습니다. 13 | 14 | > 멤버십 리워드 프로그램 제공이 총 매출에 미치는 영향은 무엇인가? 15 | > 16 | 17 | 그리고 이와 동등한 반사실적(counterfactual) 질문은 다음과 같습니다. 18 | 19 | > 만약 고객들이 멤버십 리워드 프로그램에 가입하지 않았다면, 그들은 웹사이트에 얼마나 더 적은 돈을 썼을 것인가? 20 | > 21 | 22 | 다시 말하면, 우리는 처치집단에 대한 평균 처치 효과(the Average Treatment Effect On the Treated, ATT)를 구하고자 합니다. 23 | 24 | ## I. Formulating the causal model 25 | 26 | 리워드 프로그램이 2019년 1월에 도입되었다고 가정해봅시다. 결과 변수는 연말 총 지출액입니다. 우리는 모든 유저의 모든 월별 거래 내역과 리워드 프로그램 가입을 선택한 유저들의 가입 시간에 대한 데이터를 가지고 있습니다. 데이터는 다음과 같습니다. 27 | 28 | ```python 29 | # Creating some simulated data for our example example 30 | import pandas as pd 31 | import numpy as np 32 | num_users = 10000 33 | num_months = 12 34 | 35 | signup_months = np.random.choice(np.arange(1, num_months), num_users) * np.random.randint(0,2, size=num_users) 36 | df = pd.DataFrame({ 37 | 'user_id': np.repeat(np.arange(num_users), num_months), 38 | 'signup_month': np.repeat(signup_months, num_months), # signup month == 0 means customer did not sign up 39 | 'month': np.tile(np.arange(1, num_months+1), num_users), # months are from 1 to 12 40 | 'spend': np.random.poisson(500, num_users*num_months) #np.random.beta(a=2, b=5, size=num_users * num_months)*1000 # centered at 500 41 | }) 42 | # Assigning a treatment value based on the signup month 43 | df["treatment"] = (1-(df["signup_month"]==0)).astype(bool) 44 | # Simulating effect of month (monotonically increasing--customers buy the most in December) 45 | df["spend"] = df["spend"] - df["month"]*10 46 | # The treatment effect (simulating a simple treatment effect of 100) 47 | after_signup = (df["signup_month"] < df["month"]) & (df["signup_month"] !=0) 48 | df.loc[after_signup,"spend"] = df[after_signup]["spend"] + 100 49 | df 50 | ``` 51 | 52 | | | user_id | signup_month | month | spend | treatment | 53 | | --- | --- | --- | --- | --- | --- | 54 | | 0 | 0 | 6 | 1 | 526 | True | 55 | | 1 | 0 | 6 | 2 | 464 | True | 56 | | 2 | 0 | 6 | 3 | 473 | True | 57 | | 3 | 0 | 6 | 4 | 502 | True | 58 | | 4 | 0 | 6 | 5 | 436 | True | 59 | | ... | ... | ... | ... | ... | ... | 60 | | 119995 | 9999 | 7 | 8 | 533 | True | 61 | | 119996 | 9999 | 7 | 9 | 518 | True | 62 | | 119997 | 9999 | 7 | 10 | 485 | True | 63 | | 119998 | 9999 | 7 | 11 | 504 | True | 64 | | 119999 | 9999 | 7 | 12 | 459 | True | 65 | 66 | 120000 rows × 5 columns 67 | 68 | ### The importance of time 69 | 70 | 이 문제를 모델링하는 데 있어서 **시간이 중요한 역할**을 합니다. 71 | 72 | 리워드 프로그램 가입은 향후 거래에 영향을 미칠 수 있지만, 이전 거래에는 영향을 미치지 않습니다. 사실 리워드 가입 이전의 거래는 리워드 가입 결정을 유발한다고 가정할 수 있습니다. 73 | 74 | 따라서 각 유저의 변수들을 다음과 같이 나눌 수 있습니다. 75 | 76 | 1. 처치 전 활동 (처치의 원인) 77 | 2. 처치 후 활동 (처치 적용 결과) 78 | 79 | 물론 가입과 총 지출에 영향을 미치는 많은 중요한 변수가 누락되어 있습니다(e.g., 구입한 제품 유형, 유저 계정 사용 기간, 지역 등). 관측되지 않은 교란 변수(`Unobserved Confounders`)를 나타내는 노드가 필요합니다. 80 | 81 | 아래는 `i=3` 개월에 가입한 유저에 대한 인과 그래프입니다. 모든 `i`에 대해서 분석은 유사할 것입니다. 82 | 83 | ```python 84 | import os, sys 85 | sys.path.append(os.path.abspath("../../../")) 86 | import dowhy 87 | 88 | # Setting the signup month (for ease of analysis) 89 | i = 3 90 | ``` 91 | 92 | ```python 93 | causal_graph = """digraph { 94 | treatment[label="Program Signup in month i"]; 95 | pre_spends; 96 | post_spends; 97 | Z->treatment; 98 | U[label="Unobserved Confounders"]; 99 | pre_spends -> treatment; 100 | treatment->post_spends; 101 | signup_month->post_spends; signup_month->pre_spends; 102 | signup_month->treatment; 103 | U->treatment; U->pre_spends; U->post_spends; 104 | }""" 105 | 106 | # Post-process the data based on the graph and the month of the treatment (signup) 107 | df_i_signupmonth = df[df.signup_month.isin([0,i])].groupby(["user_id", "signup_month", "treatment"]).apply( 108 | lambda x: pd.Series({'pre_spends': np.sum(np.where(x.month < i, x.spend,0))/np.sum(np.where(x.month i, x.spend,0))/np.sum(np.where(x.month>i, 1,0)) }) 110 | ).reset_index() 111 | print(df_i_signupmonth) 112 | model = dowhy.CausalModel(data=df_i_signupmonth, 113 | graph=causal_graph.replace("\n", " "), 114 | treatment="treatment", 115 | outcome="post_spends") 116 | model.view_model() 117 | from IPython.display import Image, display 118 | display(Image(filename="causal_model.png")) 119 | ``` 120 | 121 | ``` 122 | user_id signup_month treatment pre_spends post_spends 123 | 0 0 6 True 480.2 511.000000 124 | 1 2 0 False 477.8 408.166667 125 | 2 5 0 False 472.6 423.333333 126 | 3 6 6 True 465.2 505.833333 127 | 4 8 0 False 447.6 396.333333 128 | ... ... ... ... ... ... 129 | 5446 9993 0 False 488.8 415.166667 130 | 5447 9994 0 False 471.4 392.666667 131 | 5448 9995 0 False 489.4 402.666667 132 | 5449 9997 0 False 498.0 414.333333 133 | 5450 9998 0 False 469.6 400.000000 134 | 135 | [5451 rows x 5 columns] 136 | ``` 137 | 138 | ![i개월에 가입한 유저에 대한 인과 그래프](https://user-images.githubusercontent.com/76609403/153709477-d7e76bde-aaf9-47bb-bd7b-42955602ca55.png) 139 | 140 | 더 일반적으로, 고객에 대한 모든 활동 데이터를 위 그래프에 포함시킬 수 있습니다. 모든 사전 및 사후 활동 데이터는 이미 사용된 사전 및 사후 노드들과 동일한 위치 및 엣지를 차지합니다. 141 | 142 | ## **II. Identifying the causal effect** 143 | 144 | 만약 관측되지 않은 교란 변수가 큰 역할을 하지 않는다고 가정해봅시다. 145 | 146 | ```python 147 | identified_estimand = model.identify_effect(proceed_when_unidentifiable=True) 148 | print(identified_estimand) 149 | ``` 150 | 151 | ``` 152 | Estimand type: nonparametric-ate 153 | 154 | ### Estimand : 1 155 | Estimand name: backdoor 156 | Estimand expression: 157 | d 158 | ────────────(Expectation(post_spends|signup_month,pre_spends)) 159 | d[treatment] 160 | Estimand assumption 1, Unconfoundedness: If U→{treatment} and U→post_spends then P(post_spends|treatment,signup_month,pre_spends,U) = P(post_spends|treatment,signup_month,pre_spends) 161 | 162 | ### Estimand : 2 163 | Estimand name: iv 164 | Estimand expression: 165 | Expectation(Derivative(post_spends, [Z])*Derivative([treatment], [Z])**(-1)) 166 | Estimand assumption 1, As-if-random: If U→→post_spends then ¬(U →→{Z}) 167 | Estimand assumption 2, Exclusion: If we remove {Z}→{treatment}, then ¬({Z}→post_spends) 168 | 169 | ### Estimand : 3 170 | Estimand name: frontdoor 171 | No such variable found! 172 | ``` 173 | 174 | DoWhy 는 그래프를 바탕으로, 가입 월과 처지 이전 월(`signup_month`, `pre_spend`)에 소요된 금액을 조건화할 필요가 있다고 판단합니다. 175 | 176 | ## **III. Estimating the effect** 177 | 178 | 이제 backdoor estimand(추정치)를 기반으로 target_units 를 “att”로 설정하여 효과를 추정합니다. 179 | 180 | ```python 181 | estimate = model.estimate_effect(identified_estimand, 182 | method_name="backdoor1.propensity_score_matching", 183 | target_units="att") 184 | print(estimate) 185 | ``` 186 | 187 | ``` 188 | *** Causal Estimate *** 189 | 190 | ## Identified estimand 191 | Estimand type: nonparametric-ate 192 | 193 | ## Realized estimand 194 | b: post_spends~treatment+signup_month+pre_spends 195 | Target units: att 196 | 197 | ## Estimate 198 | Mean value: 115.21872571872572 199 | ``` 200 | 201 | 위와 같이 평균 처치 효과를 알려줍니다. 즉, `i=3` 개월에 리워드 프로그램에 등록한 고객의 총 지출에 대한 평균 효과입니다. i 값을 변경한 후 분석을 다시 실행하여 다른 달에 가입한 고객에 대한 효과를 비슷한 방법으로 계산할 수 있습니다. 202 | 203 | 다만 좌측 및 우측 관측 중단으로 인해 효과 추정에 어려움을 겪는 경우가 존재합니다. 204 | 205 | 1. **좌측 관측 중단 (Left-censoring)** 206 | 207 | : 고객이 첫 달에 가입하는 경우, 우리는 가입하지 않은 고객과 비교할 만큼 충분한 거래 이력이 없습니다. 따라서 backdoor identified estimand 를 적용해야 합니다. 208 | 209 | 2. **우측 관측 중단 (Right-censoring)** 210 | 211 | : 고객이 마지막 달에 가입하는 경우, 가입 후 결과를 추정하기에는 향후(사후) 거래 이력이 부족합니다. 212 | 213 | 214 | 따라서 아무리 가입 효과가 모든 달에 걸쳐 동일했더라도, 데이터가 부족한 상황에서는 추정된 pre-treatment 또는 post-treatment 거래 활동의 변동이 클 수 있기 때문에 가입 월별로 추정 효과가 다를 수 있습니다. 215 | 216 | ## **IV. Refuting the estimate** 217 | 218 | Placebo treatment refuter 를 사용하여 추정치를 반박합니다. 이 refuter 는 treatment 를 독립적인 랜덤 변수로 대체하고 추정치가 0이 되는지 여부를 확인합니다. (0이 되어야 합니다!) 219 | 220 | 221 | ```python 222 | refutation = model.refute_estimate(identified_estimand, estimate, method_name="placebo_treatment_refuter", 223 | placebo_type="permute", num_simulations=2) 224 | print(refutation) 225 | ``` 226 | 227 | ``` 228 | Refute: Use a Placebo Treatment 229 | Estimated effect:115.21872571872572 230 | New effect:1.3245920745920756 231 | p value:0.015071603412401298 232 | ``` 233 | -------------------------------------------------------------------------------- /dowhy-key-concepts/sensitivity-analysis.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Refutation / Sensitivity Analysis 3 | --- 4 | 5 | # 추정치를 검증하는 방법 6 | 7 | * 작성자: 허현 8 | 9 | ![](<../.gitbook/assets/dowhy-diagram.png>) 10 | 11 | dowhy는 위 플로우차트 순서대로 진행하도록 정형화 되어있습니다. 12 | graph 구축 → identify → estimate → refute 단계이며 각 단계를 간단히 설명하자면 13 | - identify 단계에서는 만들어놓은 그래프의 관계를 규명하는 과정입니다 14 | - 이미 우리가 알려줬는데 왜 규명해야 할까? 15 | - DAG 구조인지 확인해야 하고, path에 따라 연결관계를 조정해야 treatment→outcome 효과를 정확히 측정할 수 있기 때문에 16 | - estimate 단계에서는 효과를 추정합니다 17 | - refute 단계에서는 효과가 강건하게 추정된 것인지 검증합니다. 이는 robust check, sensitivity analysis와 같은 절차로 볼 수 있습니다 18 | - 예측 모델링과 달리 인과 모델링에선 **효과의 방향이 +인지 -인지, 효과의 크기가 얼마나 큰지의 중요도가 높아 중요**한 절차입니다 19 | 20 | ## dowhy refute 함수 21 | dowhy 라이브러리에서 자동화해놓은 refute는 [add_unobserved_common_cause](https://github.com/microsoft/dowhy/blob/master/dowhy/causal_refuters/add_unobserved_common_cause.py), [bootstrap_refuter](https://github.com/microsoft/dowhy/blob/master/dowhy/causal_refuters/bootstrap_refuter.py), [data_subset_refuter](https://github.com/microsoft/dowhy/blob/master/dowhy/causal_refuters/data_subset_refuter.py), [dummy_outcome_refuter](https://github.com/microsoft/dowhy/blob/master/dowhy/causal_refuters/dummy_outcome_refuter.py), [placebo_treatment_refuter](https://github.com/microsoft/dowhy/blob/master/dowhy/causal_refuters/placebo_treatment_refuter.py), [random_common_cause](https://github.com/microsoft/dowhy/blob/master/dowhy/causal_refuters/random_common_cause.py) 가 있습니다. 22 | 23 | 이를 유형화 하면 아래 3가지로 나눠 볼 수 있습니다. 24 | 1. treatment, outcome 변수를 대체하기 25 | 2. 관측 되지 않은 confounder 추가하기 26 | 3. 데이터셋으로 작은 집합 만들기 27 | 28 | (+) 유형과 무관하게 dowhy refute 방법론은 선정된 횟수(디폴트 100)만큼 refutation을 반복하며 이 반복의 평균값으로 결과값을 보여주고, 추가적으로 test_significance test 과정을 내부적으로 진행하여 estimate 단계에서 추정한 수치와 통계적으로 유의하게 같은지 검증합니다. 29 | 30 | 31 | ## 1. treatment, outcome 변수를 대체하기 32 | ### placebo_treatment_refuter 33 | - 기존 DAG 구조에서 treatment에 해당되는 곳을 placebo 데이터로 채워서 refute 진행 34 | - bool 형태면 0/1, int 형태면 정규분포와 같이 기존 treatment 형태에 따라 다르게 랜덤한 값으로 대체 35 | - treatment_effect가 당연히 0 수준으로 나와야 되고, 위의 test_significance 통해 원래 구한 값과 다른 것으로 결과가 나와야 통과 36 | 37 | ### dummy_outcome_refuter 38 | - outcome을 가상으로 만들어 refute 진행 39 | - 일반적으로 treatment를 제외한 나머지 변수들로 새로운 y값을 예측하는 모델링을 하고 이 y_hat을 y 대신 사용하여 estimate_effect 진행 40 | 41 | ## 2. 관측 되지 않은 confounder 추가하기 42 | ### random_common_cause 43 | - 정규분포를 따르는 랜덤한 데이터를 common_cause(confounder)로 추가하여 refute 진행 44 | 45 | ### add_unobserved_common_cause 46 | - 관측되지 않은 common cause를 추가하여 estimate_effect 진행 47 | - 위 random_common_cause와의 차이는 가지고 있는 데이터를 통해 만들어진 값으로 common_cuase(confounder)를 추가한다는 점 48 | - treatment와 outcome의 residual을 활용해 U(unobserved common cause)를 생성 49 | - y~t+observed 형태로 OLS 진행하고 이 때 구해진 y hat과 y의 차이로 y residual 도출 50 | - t~observed 형태로 OLS 진행하고 이 때 구해진 t hat과 t의 차이로 t residual 도출 51 | ```python 52 | This function simulates an unobserved confounder based on the data using the following steps: 53 | 1. It calculates the "residuals" from the treatment and outcome model 54 | i.) The outcome model has outcome as the dependent variable and all the observed variables including treatment as independent variables 55 | ii.) The treatment model has treatment as the dependent variable and all the observed variables as independent variables. 56 | 2. U is an intermediate random variable drawn from the normal distribution with the weighted average of residuals as mean and a unit variance 57 | U ~ N(c1*d_y + c2*d_t, 1) 58 | where 59 | *d_y and d_t are residuals from the treatment and outcome model 60 | *c1 and c2 are coefficients to the residuals 61 | 3. The final U, which is the simulated unobserved confounder is obtained by debiasing the intermediate variable U by residualising it with X 62 | ``` 63 | - 4가지 파라미터 존재 - treatment와 outcome에 대한 unobserved common cause의 관계 형태와 강도에 대한 파라미터 64 | - confounders_effect_on_treatment 65 | - "linear", “binary_flip” 66 | - confounders_effect_on_outcome 67 | - "linear", “binary_flip” 68 | - effect_strength_on_treatment 69 | - effect_strength_on_outcome 70 | 71 | ## 3. 데이터셋으로 작은 집합 만들기 72 | ### data_subset_refuter 73 | - 디폴트로 선정된 비율(0.8), 횟수(100)만큼 subset을 랜덤 추출하여 estimate_effect를 진행 74 | 75 | ### bootstrap_refuter 76 | - 디폴트로 선정된 횟수(100)만큼 confounder w를 error를 추가하여 estimate_effect 진행 77 | - confounder에 measurement error(측정 오차)가 있어 잘못된 효과가 추정될 수 있으므로 error term을 추가하는 것 78 | 79 | 80 | ## 추가자료 : Common Misconceptions (흔한 오해) 81 | 원문 : [Nick Huntington-Klein 블로그](https://www.nickchk.com/robustness.html) 82 | 83 | ### **I should "do all the robustness tests."** 84 | 85 | No! Why not? The reason has to do with *multiple hypothesis testing*, especially when discussing robustness tests that take the form of statistical significance tests. Roughly, if you have 20 null hypotheses that are *true*, and you run statistical significance tests on all of them at the 95% level, then you will on average reject one of those true nulls just by chance.[4](https://www.nickchk.com/robustness.html#fn4) We commonly think of this problem in terms of looking for results - if you are disappointed with an insignificant result in your analysis and so keep changing your model until you find a significant effect, then that significant effect is likely just an illusion, and not really significant. You just found a significant coefficient by random chance, even though the true effect is likely zero. The same problem applies in the opposite direction with robustness tests. If you just run a whole bunch of robustness tests for no good reason, some of them will fail just by random chance, even if your analysis is totally fine! 86 | 87 | And that might leave you in a pickle - do you stick with the original analysis because your failed test was probably just random chance, or do you adjust your analysis because of the failed test, possibly ending up with the wrong analysis? 88 | 89 | We can minimize this problem by sticking to testing assumptions you think might actually be dubious in your analysis, or assumptions that, if they fail, would be really bad for the analysis. A good rule of thumb for econometrics in general: don't do anything unless you have a reason for it. 90 | 91 | > 모든 테스트를 해야 한다는 것은 잘못된 생각이고 분석 과정에서 의심되는 로버스트 체크를 하는 것이 좋다. 92 | > 가설 검정에서 랜덤한 데이터로 테스트를 해도 일부는 영가설을 기각해야 된다는 결론이 나오는 것과 같이 검정을 지나치게 많이 하면 우연히 로버스트 체크에서 문제가 될 수 있다. 93 | 94 | ### **If my analysis passes the robustness tests I do, then it's correct.** 95 | 96 | After all, if you are doing a fixed effects analysis, for example, and you did the fixed effects tests you learned about in class, and you passed, then your analysis is good, right? 97 | 98 | No! Why not? Because your analysis depends on *all* the assumptions that go into your analysis, not just the ones you have neat and quick tests for. If you really want to do an analysis super-correctly, you shouldn't be doing one of those fill-in lists above for every *robustness check* you run - you should be trying to do a fill-in list for every *assumption your analysis makes*. Of course, for some of those assumptions you won't find good reasons to be concerned about them and so won't end up doing a robustness test. But you should think carefully about the A, B, C in the fill-in list for each assumption. This conveniently corresponds to a mnemonic: Ask what each (A)ssumption is, how (B)ad it would be if it were wrong, and whether that assumption is likely to be (C)orrect or not for you. 99 | 100 | There's another reason, too - sometimes the test is just weak! Sometimes, even if your assumption is wrong, the test you're using won't be able to pick up the problem and will tell you you're fine, just by chance. Type I error, in other words. There's not much you can do about that. But do keep in mind that passing a test about assumption A is some *evidence* that A is likely to be true, but it doesn't ever really *confirm* that A is true. So you can never really be sure. Just try to be as sure as you reasonably can be, and exercise common sense! 101 | 102 | > 로버스트 체크를 통과해도 무조건 그 분석이 맞지 않을 수 있다. 103 | > 테스트 자체는 통과했어도 모델의 가설에 어긋난 분석을 했을 수 있고, 우연의 결과로 통과했을 수도 있다. 104 | 105 | ### **Robustness tests are always specialized *tests*.** 106 | 107 | No! Why not? Because a robustness test is anything that lets you evaluate the importance of one of your assumptions for your analysis. We've already gone over the robustness test of adding additional controls to your model to see what changes - that's not a specialized robustness test. These kinds of robustness tests can include lots of things, from simply looking at a graph of your data to see if your functional form assumption looks reasonable, to checking if your treatment and control groups appear to have been changing in similar ways in the "before" period of a difference-in-difference (i.e. parallel trends). Don't be fooled by the fancy stuff - getting to know your data and context well is the best way of figuring out what assumptions are likely to be true. 108 | 109 | > 이러한 검증법들은 범용적인 아이디어이면서 모델이 가정을 잘 지켰는지 확인하게 도와주는 역할을 한다. difference-in-difference를 검정하기 위해 평행 추세 확인하는 것을 어떤 기법으로 여기지 말고 데이터와 맥락상 적절한 가정이 무엇일지 파악하는 것이 필요하다. 110 | -------------------------------------------------------------------------------- /example-notebooks/iv-effect.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Simple Example of DoWhy using Instrumental Variables 3 | --- 4 | 5 | # 도구변수를 활용하여 인과효과를 추정하기 6 | 7 | * 작성자: [최보경](https://www.facebook.com/pagebokyung/) 8 | * 원문: [Simple example on using Instrumental Variables method for estimation](https://microsoft.github.io/dowhy/example\_notebooks/dowhy-simple-iv-example.html) 9 | 10 | ## Loading the dataset 11 | 12 | ```python 13 | import numpy as np 14 | import pandas as pd 15 | import patsy as ps 16 | 17 | from statsmodels.sandbox.regression.gmm import IV2SLS 18 | import os, sys 19 | from dowhy import CausalModel 20 | ``` 21 | 22 | 개인의 교육이 미래 소득에 주는 영향을 추정하기 위한, 가상의 데이터 셋을 생성합니다. 23 | 24 | * 교란변수(unobserved confounder) : 개인의 능력(ability) 25 | * 도구변수(instrumental variable) : 교육 보조(education\_voucher) 26 | 27 | ```python 28 | n_points = 10000 # 데이터 개수 29 | education_abilty = 1 # discrete 30 | education_voucher = 2 # discrete 31 | income_abilty = 2 # discrete 32 | income_education = 4 # education 과 곱해지는 계수로, 찾고자 하는 인과효과 estimate 의 정답지 33 | 34 | 35 | # confounder (unobserved) 36 | ability = np.random.normal(0, 3, size=n_points) 37 | 38 | # instrument 39 | voucher = np.random.normal(2, 1, size=n_points) 40 | 41 | # treatment 42 | education = np.random.normal(5, 1, size=n_points) + education_abilty * ability +\ 43 | education_voucher * voucher 44 | 45 | # outcome 46 | income = np.random.normal(10, 3, size=n_points) +\ 47 | income_abilty * ability + income_education * education 48 | ``` 49 | 50 | ```python 51 | # 가상의 dataset (exclude confounder `ability` which we assume to be unobserved) 52 | data = np.stack([education, income, voucher]).T 53 | df = pd.DataFrame(data, columns = ['education', 'income', 'voucher']) 54 | df.head() 55 | ``` 56 | 57 | | | education | income | voucher | 58 | | - | --------- | --------- | -------- | 59 | | 0 | 5.638493 | 29.349797 | 1.974566 | 60 | | 1 | 8.522389 | 46.239001 | 1.949956 | 61 | | 2 | 12.840748 | 64.198096 | 2.253389 | 62 | | 3 | 2.954071 | 15.591053 | 0.939508 | 63 | | 4 | 10.117519 | 51.873490 | 1.779209 | 64 | 65 | ## Using DoWhy to estimate the causal effect of education on future income 66 | 67 | **4가지 절차를 따릅니다.** 68 | 69 | 1. 문제를 인과 그래프로 모델링합니다. (model) 70 | 2. 관측된 변수로부터 인과추론이 될 수 있는지 확인합니다. (identify) 71 | 3. 인과효과를 추정합니다. (estimate) 72 | 4. 인과효과 추정치의 Robustness 를 확인합니다. (refute) 73 | 74 | ## step 1 : model 75 | 76 | ```python 77 | model = CausalModel( 78 | data = df, 79 | treatment = 'education', 80 | outcome = 'income', 81 | common_causes = ['U'], # unobserved confounding 을 임의의 U 컬럼에 넣는 것 82 | instruments = ['voucher'] 83 | ) 84 | model.view_model() 85 | 86 | from IPython.display import Image, display 87 | display(Image(filename="causal_model.png")) 88 | ``` 89 | 90 | ![](../.gitbook/assets/instrum\_var.png) 91 | 92 | ## step 2 : identify 93 | 94 | ```python 95 | identified_estimand = model.identify_effect(proceed_when_unidentifiable = True) 96 | print(identified_estimand) 97 | ``` 98 | 99 | ``` 100 | Estimand type: nonparametric-ate 101 | 102 | ### Estimand : 1 103 | Estimand name: backdoor1 (Default) 104 | Estimand expression: 105 | d 106 | ────────────(Expectation(income)) 107 | d[education] 108 | Estimand assumption 1, Unconfoundedness: If U→{education} and U→income then P(income|education,,U) = P(income|education,) 109 | 110 | ### Estimand : 2 111 | Estimand name: iv 112 | Estimand expression: 113 | Expectation(Derivative(income, [voucher])*Derivative([education], [voucher])** 114 | (-1)) 115 | Estimand assumption 1, As-if-random: If U→→income then ¬(U →→{voucher}) 116 | Estimand assumption 2, Exclusion: If we remove {voucher}→{education}, then ¬({voucher}→income) 117 | 118 | ### Estimand : 3 119 | Estimand name: frontdoor 120 | No such variable found! 121 | ``` 122 | 123 | **identify\_effect()** 124 | 125 | Identified Estimand 를 리턴해주는 메소드로, Estimand 가 3가지 타입이 나옵니다. 126 | 127 | Estimand 의 종류가 non-parametric ATE 일 때 `Backdoor / IV / Frontdoor` 와 같은 Identification methods 를 리턴합니다. 128 | 129 | ```python 130 | # step 3 : estimate 131 | # step 2 에서 Estimand 가 3가지가 있는데, 그중에서 IV 를 선택함. 132 | 133 | estimate = model.estimate_effect(identified_estimand, 134 | method_name = 'iv.instrumental_variable', 135 | test_significance = True) 136 | 137 | print(estimate) 138 | ``` 139 | 140 | ``` 141 | Estimand type: nonparametric-ate 142 | 143 | ### Estimand : 1 144 | Estimand name: backdoor 145 | Estimand expression: 146 | d 147 | ────────────(Expectation(income)) 148 | d[education] 149 | Estimand assumption 1, Unconfoundedness: If U→{education} and U→income then P(income|education,,U) = P(income|education,) 150 | 151 | ### Estimand : 2 152 | Estimand name: iv 153 | Estimand expression: 154 | Expectation(Derivative(income, [voucher])*Derivative([education], [voucher])** 155 | (-1)) 156 | Estimand assumption 1, As-if-random: If U→→income then ¬(U →→{voucher}) 157 | Estimand assumption 2, Exclusion: If we remove {voucher}→{education}, then ¬({voucher}→income) 158 | 159 | ### Estimand : 3 160 | Estimand name: frontdoor 161 | No such variable found! 162 | ``` 163 | 164 | ## Step 3: Estimate 165 | 166 | ``` 167 | #Choose the second estimand: using IV 168 | estimate = model.estimate_effect(identified_estimand, 169 | method_name="iv.instrumental_variable", test_significance=True) 170 | 171 | print(estimate) 172 | ``` 173 | 174 | ``` 175 | *** Causal Estimate *** 176 | 177 | ## Identified estimand 178 | Estimand type: nonparametric-ate 179 | 180 | ### Estimand : 1 181 | Estimand name: iv 182 | Estimand expression: 183 | Expectation(Derivative(income, [voucher])*Derivative([education], [voucher])** 184 | (-1)) 185 | Estimand assumption 1, As-if-random: If U→→income then ¬(U →→{voucher}) 186 | Estimand assumption 2, Exclusion: If we remove {voucher}→{education}, then ¬({voucher}→income) 187 | 188 | ## Realized estimand 189 | Realized estimand: Wald Estimator 190 | Realized estimand type: nonparametric-ate 191 | Estimand expression: 192 | 193 | Expectation(Derivative(income, voucher))⋅Expectation(Derivative(education, vou 194 | 195 | -1 196 | cher)) 197 | Estimand assumption 1, As-if-random: If U→→income then ¬(U →→{voucher}) 198 | Estimand assumption 2, Exclusion: If we remove {voucher}→{education}, then ¬({voucher}→income) 199 | Estimand assumption 3, treatment_effect_homogeneity: Each unit's treatment ['education'] is affected in the same way by common causes of ['education'] and income 200 | Estimand assumption 4, outcome_effect_homogeneity: Each unit's outcome income is affected in the same way by common causes of ['education'] and income 201 | 202 | Target units: ate 203 | 204 | ## Estimate 205 | Mean value: 3.984270555650861 206 | p-value: [0, 0.001] 207 | ``` 208 | 209 | **해석 방법** 교육(education)을 한 단위 높일 때, 소득(income)이 4.01 포인트 가량 높아진다는 의미를 가지는 Estimate (추정치)를 보입니다. 210 | 211 | 추정된 인과효과는, p-value 가 0 \~ 0.001 사이로 통계적으로 유의합니다. 212 | 213 | 그 다음으로는, Placebo refutation test 를 통해서 이 추정치의 강건성을 검사합니다. 이 테스트에서는, 독립확률변수(X)가 도구변수와의 상관관계를 유지하면서, Treatment를 대체하도록 합니다. 따라서 진실된 인과효과는 0이 됩니다. 214 | 215 | 이 Estimator 가 올바르게 0에 가까운 값을 리턴하는지를 확인합니다. 216 | 217 | ## step 4 : refute 218 | 219 | ``` 220 | ref = model.refute_estimate(identified_estimand, 221 | estimate, 222 | method_name = "placebo_treatment_refuter", 223 | placebo_type = "permute") 224 | 225 | print(ref) 226 | ``` 227 | 228 | ``` 229 | Refute: Use a Placebo Treatment 230 | Estimated effect:3.984270555650861 231 | New effect:-0.004644847508369445 232 | p value:0.43999999999999995 233 | ``` 234 | 235 | ## 2SLS와 비교 236 | 237 | 2-stage linear square regression 동일한 로직이기 때문에 기존 패키지의 coefficient, p-value 와 동일한 결과를 보입니다. 238 | 239 | ``` 240 | income_vec, endog = ps.dmatrices("income ~ education", data=df) 241 | exog = ps.dmatrix("voucher", data=df) 242 | ``` 243 | 244 | ``` 245 | m = IV2SLS(income_vec, endog, exog).fit() 246 | m.summary() 247 | ``` 248 | 249 | | Dep. Variable: | income | R-squared: | 0.890 | 250 | | ----------------- | ---------------- | ------------------- | --------- | 251 | | Model: | IV2SLS | Adj. R-squared: | 0.890 | 252 | | Method: | Two Stage | F-statistic: | 1.373e+04 | 253 | | | Least Squares | Prob (F-statistic): | 0.00 | 254 | | Date: | Sun, 19 Dec 2021 | | | 255 | | Time: | 09:48:50 | | | 256 | | No. Observations: | 10000 | | | 257 | | Df Residuals: | 9998 | | | 258 | | Df Model: | 1 | | | 259 | 260 | | | coef | std err | t | P>\|t\| | \[0.025 | 0.975] | 261 | | --------- | ------- | ------- | ------- | ------- | ------- | ------ | 262 | | Intercept | 10.2193 | 0.314 | 32.568 | 0.000 | 9.604 | 10.834 | 263 | | education | 3.9779 | 0.034 | 117.195 | 0.000 | 3.911 | 4.044 | 264 | 265 | | Omnibus: | 3.175 | Durbin-Watson: | 1.953 | 266 | | -------------- | ----- | ----------------- | ----- | 267 | | Prob(Omnibus): | 0.204 | Jarque-Bera (JB): | 3.137 | 268 | | Skew: | 0.040 | Prob(JB): | 0.208 | 269 | | Kurtosis: | 3.032 | Cond. No. | 25.7 | 270 | -------------------------------------------------------------------------------- /dowhy-key-concepts/interpret-results.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: Interpreting DoWhy Results 3 | --- 4 | 5 | # 모델 결과 해석하기 6 | 7 | * 작성자: [최보경](https://www.facebook.com/pagebokyung/) 8 | 9 | ## DoWhy에서 Estimate Effect란? 10 | 11 | ![](../.gitbook/assets/dowhy-diagram.png) 12 | 13 | DoWhy는 인과추론을 위한 메커니즘을 4단계로 구성했습니다. 1단계 (Model)에서는 데이터를 인과 그래프로 인코딩하고, 2단계 (Identify)에서는 모델의 인과 관계를 식별하고 원인을 추정합니다. 3단계 (Estimate)에서는 식별된 인과관계에 대해 추정치를 구하고 4단계 (Refute)에서는 얻어진 추정치에 대해 반박을 시도합니다. 14 | 15 | ```python 16 | model = CausalModel(data, graph, treatment, outcome) 17 | estimand = model.identify_effect() 18 | estimate = model.estimate_effect(estimand, method_name="propensity_score_weighting") 19 | refute = model.refute_estimate(estimand, estimate, method_name="placebo_treatment_refuter") 20 | ``` 21 | 22 | 인과 효과를 추정할 때, 효과의 방향성, 도구 변수 또는 매개자의 존재, 모든 관련 교란 요인이 관찰되는지 여부 등의 중요한 가정을 기반으로 이루어집니다. 이 가정을 위반하게 되면, 인과 효과 추정치에 유의한 오류가 발생합니다. 머신 러닝을 활용한 예측 모델 같은 경우, 교차 검증 방식이 존재하지만 인과추론에 대한 전역 검증 방식은 현재 존재하지 않습니다. 23 | 24 | 따라서, 인과적 가정을 최대한 공식적으로 표현하고 검증하는 것이 중요합니다. 이를 위해 DoWhy는 1단계에서 인과 모형을 명시적으로 선언할 수 있도록 제공합니다. 또한 가정의 부분 집합들을 확인하기 위해 여러 검증 방식을 제공합니다. 평균 인과 효과 및 조건부 인과 효과 등 오류를 더 잘 감지할 수 있는 검증 테스트가 개발되어 4단계에서 다뤄지고 있습니다. ([Sharma, 2021](https://arxiv.org/pdf/2108.13518.pdf)) 25 | 26 | 이 장에서는 DoWhy에서 결과를 확인하게 되는 3단계인 인과 효과 추정 단계에 대해서 전체적인 그림을 이해하고, 함수의 출력 결과를 어떻게 해석해서 실무에 전달할 수 있을지 안내하고자 합니다. 27 | 28 | *** 29 | 30 | ## 인과 효과 추정을 할 수 있는 방식들 31 | 32 | ### 1. **어떤 메소드들이 있을까?** 33 | 34 | ```python 35 | # Estimate the causal effect and compare it with Average Treatment Effect 36 | estimate = model.estimate_effect(identified_estimand, 37 | method_name = "backdoor.linear_regression", 38 | test_significance = True, # Linear regression 에 적합 39 | confidence_intervals = True # Linear regression 에 적합 40 | ) 41 | ``` 42 | 43 | estimate\_effect 함수에 들어가는 `method_name` 입력값으로는 아래와 같은 종류의 방식들이 있습니다. 44 | 45 | * Propensity Score Matching: “backdoor.propensity\_score\_matching” 46 | * Propensity Score Stratification: “backdoor.propensity\_score\_stratification” 47 | * Propensity Score-based Inverse Weighting: “backdoor.propensity\_score\_weighting” 48 | * Linear Regression: “backdoor.linear\_regression” 49 | * Generalized Linear Models (e.g., logistic regression): “backdoor.generalized\_linear\_model” 50 | * Instrumental Variables: “iv.instrumental\_variable” 51 | * Regression Discontinuity: “iv.regression\_discontinuity” 52 | 53 | ### 2. estimate\_effect **함수를 사용하기 위한 조건** 54 | 55 | 1. 하나의 그래프 `graph` 입력이 필요합니다. 56 | 2. `common_causes` 또는 `instruments` 가 입력이 되어야 합니다. 57 | 58 | *** 59 | 60 | ## 함수 출력 이해하기 61 | 62 | ```python 63 | ## Identified estimand 64 | ### Estimand 65 | ## Realized estimand 66 | ## Estimate 67 | ``` 68 | 69 | estimate\_effect 함수의 출력은 4가지로 구성됩니다. 어떤 메소드를 선택하느냐에 따라 세부적인 항목은 바뀌지만 4가지 프레임은 동일하게 유지됩니다. 하나씩 해석해보겠습니다. ([지명진, 2021](https://www.koreascience.or.kr/article/CFKO202125036032267.pdf)) 70 | 71 | ### **1. Identified estimand** 72 | 73 | ```python 74 | ## Identified estimand 75 | Estimand type: nonparametric-ate 76 | ``` 77 | 78 | * **Identifed estimand** : 어떤 Estimand를 추론하는지 그 종류를 정의하고 추정치를 식별한 결과 79 | * **Estimand** : 통계 분석에서 추론의 대상이 되는 추정값 (e.g. 가우시안 분포를 추정하고 싶으면 평균과 분산 2가지 Estimand를 추론) 80 | * **Estimand type** : 어떤 Estimand를 추론하는지 그 종류. nonparametric-ate, nonparametric-nde, nonparametric-nie로 구성됩니다. ([github](https://github.com/microsoft/dowhy/blob/95be0350818db3233051f1bbb849b6c3925e2e0b/dowhy/causal\_identifier.py#L22)) 81 | * **nonparametric-ate** : 인과추론에서의 구조적 가정 (Ignorability, Parametric Assumption) 중 Parametric Assumption을 만족시키지 않고도 인과 효과를 추정할 수 있는 방식. Propensity score 및 매칭 기법을 통한 접근, 반응 표면에서 선형적인 모델링이 아닌 유연한 모델링 접근일 때 가능합니다. 82 | 83 | 현재 DoWhy 패키지에서는 현실 문제에 사용하기 비교적 쉬운 스탠다드 인과추론 방식인 nonparametric만 제공하고 있습니다. 향후에 더 다양한 parametric form 의 identification 이 추가될 예정이라고 합니다. 84 | 85 | * **Ignorability Assumption** : 가능한 모든 교란 변수를 통제했을 때 잠재적 결과가 처치에 독립일 것이다. 즉, 모든 교란 변수가 통제되었을 것이다. ([이은지님 블로그, 2020](https://assaeunji.github.io/bayesian/2020-04-10-causal/#strong-ignorability-assumption)) 86 | 87 | ![](../.gitbook/assets/interpret-img1.png) 88 | * **Parametric Assumption** : 강한 Ignorability Assumption 아래에서 ATE, ATT와 같은 대표적인 인과적 Estimand를 추정하는 것은, E\[Y (1) | X ] 와 E\[Y(0) | X] 를 추정하는 것을 필요로 합니다. 이 때, 두 E\[Y (1) | X ] 와 E\[Y(0) | X] 가 Linear Regression 으로 피팅될 것을 가정으로 합니다. 이는 X 자체가 고차원이거나, Common Support Assumption 을 만족시키지 못할 수 있기 때문에 현실에서 이 가정이 만족되기 어렵습니다. ([J Hill of NYU, 2015](https://cepim.northwestern.edu/calendar-events/2015-09-29)) 89 | 90 | ![](../.gitbook/assets/interpret-img2.png) 91 | 92 | > Causal inference is important but hard because it requires strong assumptions (ignorability, parametric assumptions). 93 | 94 | Standard causal inference tools (e.g. most matching techniques) do not have these properties. ([J Hill of NYU, 2015](https://cepim.northwestern.edu/calendar-events/2015-09-29)) 95 | 96 | > [Slides-2015-09-29 Hill.pdf](DoWhy%20Key%20Concept%20%E1%84%86%E1%85%A9%E1%84%83%E1%85%A6%E1%86%AF%20%E1%84%80%E1%85%A7%E1%86%AF%E1%84%80%E1%85%AA%20%E1%84%92%E1%85%A2%E1%84%89%E1%85%A5%E1%86%A8%E1%84%92%E1%85%A1%E1%84%80%E1%85%B5%20f1bace74b64b49fab834081d8ac2cb4e/Slides-2015-09-29\_Hill.pdf) 97 | 98 | * **Common Support Assumption ( = Positivity / Overlap Assumption)**: 처치가 주어지거나, 주어지지 않을 확률이 0 초과 1 미만일 것. 성향점수매칭이 유효할 수 있는 2가지 조건 중 하나입니다. (나머지 하나는 조건부 독립) 만족시키지 못할 경우, 매칭 기법에서 해결될 수 없는 불균형 / inverse-probability-of-treatment weighting (IPTW) 기법에서 불안정한 가중치를 야기합니다. [(J Hill, 2013](https://projecteuclid.org/journals/annals-of-applied-statistics/volume-7/issue-3/Assessing-lack-of-common-support-in-causal-inference-using-Bayesian/10.1214/13-AOAS630.pdf)) 99 | 100 | ![F Baum, 2013](../.gitbook/assets/interpret-img3.png) 101 | 102 | ([F Baum, 2013](http://fmwww.bc.edu/EC-C/S2013/823/EC823.S2013.nn12.slides.pdf)) 103 | 104 | 3단계의 출력값 중 **Estimand type**을 이해하기 위해서는 2단계의 인과 관계 식별 방법에 대해서 조금 더 알아보는 것이 좋은데요. 1단계 (Model)에서 입력된 인과 그래프를 기반으로 가능한 모든 식별 방법을 2단계 (Identify)에서 찾게 됩니다. 이 때, 2단계에서 찾는 가능한 모든 **식별 방법**은 총 3가지로 크게 분류가 됩니다 : `Back-door / Front-door / Instrumental-Variables` 105 | 106 | * 2단계 (Identify) 출력 예시 107 | 108 | ```python 109 | Estimand type: nonparametric-ate 110 | 111 | ### Estimand : 1 112 | Estimand name: backdoor1 (Default) 113 | Estimand expression: 114 | d────────────(Expectation(y_factual|x6,x18,x9,x21,x2,x23,x25,x17,x3,x20,x16,x14 115 | d[treatment],x12,x10,x13,x22,x24,x7,x1,x11,x4,x15,x8,x5,x19)) 116 | Estimand assumption 1, Unconfoundedness: If U→{treatment} and U→y_factual then P(y_factual|treatment,x6,x18,x9,x21,x2,x23,x25,x17,x3,x20,x16,x14,x12,x10,x13,x22,x24,x7,x1,x11,x4,x15,x8,x5,x19,U) = P(y_factual|treatment,x6,x18,x9,x21,x2,x23,x25,x17,x3,x20,x16,x14,x12,x10,x13,x22,x24,x7,x1,x11,x4,x15,x8,x5,x19) 117 | 118 | ### Estimand : 2 119 | Estimand name: iv 120 | No such variable found! 121 | 122 | ### Estimand : 3 123 | Estimand name: frontdoor 124 | No such variable found! 125 | ``` 126 | 127 | * **Estimand** : 통계 분석에서 추론의 대상이 되는 추정값 128 | * **Estimand 1 (Back-door)** : 원인 변수 X 가 결과 변수 Y에 가지는 인과 관계를 식별하기 위해서, X 와 Y 에 영향을 주는 측정이 가능한 일반적인 원인들이 있을 경우, 그 원인 들을 Conditioning 하는 방식 129 | * **Estimand 2 (Front-door)** : Two-stage Linear Regression 을 통해서 인과 관계를 식별하는 방식. 이 때 헤크만 2단계 추정(Heckman Two-Stage Selection Model)을 활용합니다. 2SLS와의 차이에 대해 이해를 돕기 위해 [이 링크](https://stats.stackexchange.com/questions/172508/two-stage-models-difference-between-heckman-models-to-deal-with-sample-selecti)를 첨부합니다. 130 | * **Estimand 3 (Instrumental-Variables**) : 도구변수를 통해 원인과 결과가 관찰되지 않는 경우에도 결과를 예측하도록 한 방식. 도구 변수는 2가지 ([Exclusion Restriction](https://medium.com/bondata/instrumental-variable-1-6c249de6ea34) / Randomness)에 대해서 가정을 하여 진행됩니다. 131 | 132 | 일반적으로 Estimand 1 (Back-door)이 기본값이며 이를 사용해서 추정하게 됩니다. 133 | 134 | ### 2. **Estimand** 135 | 136 | ```python 137 | ### Estimand : 1 138 | Estimand name: iv 139 | Estimand expression: 140 | Expectation(Derivative(y, [Z0, Z1])*Derivative([v0], [Z0, Z1])**(-1)) 141 | Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1}) 142 | Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y) 143 | ``` 144 | 145 | * **Estimand name** : 2단계에서 어떤 식별 방법을 선택했는지 출력 146 | * **Estimand expression** : Identified Estimand 의 확률적 표현으로, 2단계에서 선택한 식별 방법의 Estimand expression을 출력 147 | * **Estimand assumption** : 추정값을 식별하는 과정에서 사용된 가정들을 명시. Estimand type 에 따라 정해진 가정이 존재합니다. 148 | * Back-door 일 때 ([github](https://github.com/microsoft/dowhy/blob/95be0350818db3233051f1bbb849b6c3925e2e0b/dowhy/causal\_identifier.py#L460)) 149 | 150 | ```python 151 | 'Unconfoundedness': ( 152 | u"If U\N{RIGHTWARDS ARROW}{{{0}}} and U\N{RIGHTWARDS ARROW}{1}" 153 | " then P({1}|{0},{2},U) = P({1}|{0},{2})" 154 | ).format(",".join(treatment_name), outcome_name, ",".join(common_causes)) 155 | ``` 156 | * IV 일 때 ([github](https://github.com/microsoft/dowhy/blob/95be0350818db3233051f1bbb849b6c3925e2e0b/dowhy/causal\_identifier.py#L486)) 157 | 158 | ```python 159 | "As-if-random": ( 160 | "If U\N{RIGHTWARDS ARROW}\N{RIGHTWARDS ARROW}{0} then " 161 | "\N{NOT SIGN}(U \N{RIGHTWARDS ARROW}\N{RIGHTWARDS ARROW}{{{1}}})" 162 | ).format(outcome_name, ",".join(instrument_names)), 163 | "Exclusion": ( 164 | u"If we remove {{{0}}}\N{RIGHTWARDS ARROW}{{{1}}}, then " 165 | u"\N{NOT SIGN}({{{0}}}\N{RIGHTWARDS ARROW}{2})" 166 | ).format(",".join(instrument_names), ",".join(treatment_name), 167 | outcome_name) 168 | ``` 169 | * Front-door 일 때 ([github](https://github.com/microsoft/dowhy/blob/95be0350818db3233051f1bbb849b6c3925e2e0b/dowhy/causal\_identifier.py#L517)) 170 | 171 | ```python 172 | "Full-mediation": ( 173 | "{2} intercepts (blocks) all directed paths from {0} to {1}." 174 | ).format(",".join(treatment_name), ",".join(outcome_name), ",".join(frontdoor_variables_names)), 175 | "First-stage-unconfoundedness": ( 176 | u"If U\N{RIGHTWARDS ARROW}{{{0}}} and U\N{RIGHTWARDS ARROW}{{{1}}}" 177 | " then P({1}|{0},U) = P({1}|{0})" 178 | ).format(",".join(treatment_name), ",".join(frontdoor_variables_names)), 179 | "Second-stage-unconfoundedness": ( 180 | u"If U\N{RIGHTWARDS ARROW}{{{2}}} and U\N{RIGHTWARDS ARROW}{1}" 181 | " then P({1}|{2}, {0}, U) = P({1}|{2}, {0})" 182 | ).format(",".join(treatment_name), outcome_name, ",".join(frontdoor_variables_names)) 183 | ``` 184 | 185 | ### 3. **Realized Estimand** 186 | 187 | ```python 188 | ## Realized estimand 189 | b: y~v0+W3+W4+W2+Z0+X0+W0+Z1+W1 190 | Target units: ate 191 | 192 | ## Realized estimand 193 | b: is_canceled~different_room_assigned+previous_bookings_not_canceled+hotel+total_of_special_requests+market_segment+is_repeated_guest+guests+lead_time+meal+days_in_waiting_list+country+booking_changes+total_stay+required_car_parking_spaces 194 | Target units: ate 195 | 196 | ## Realized estimand 197 | Realized estimand: Wald Estimator 198 | Realized estimand type: nonparametric-ate 199 | Estimand expression: 200 | -1 201 | Expectation(Derivative(y, Z0))⋅Expectation(Derivative(v0, Z0)) 202 | Estimand assumption 1, As-if-random: If U→→y then ¬(U →→{Z0,Z1}) 203 | Estimand assumption 2, Exclusion: If we remove {Z0,Z1}→{v0}, then ¬({Z0,Z1}→y) 204 | Estimand assumption 3, treatment_effect_homogeneity: Each unit's treatment ['v0'] is affected in the same way by common causes of ['v0'] and y 205 | Estimand assumption 4, outcome_effect_homogeneity: Each unit's outcome y is affected in the same way by common causes of ['v0'] and y 206 | 207 | Target units: ate 208 | ``` 209 | 210 | * **Realized Estimand** : 실제로 실현된 추정. 2단계 (Identify)에서 다양한 식별 방법이 가능함을 모델이 알려주지만, 그 중에서 사용자가 직접 선택한 Estimand type 으로 필터링하여 업데이트합니다. ([github](https://github.com/microsoft/dowhy/blob/95be0350818db3233051f1bbb849b6c3925e2e0b/dowhy/causal\_estimator.py#L793)) 211 | 212 | 위 첫번째 예시의 경우 v0, w3, \~ w1 의 변수를 통해서 y를 예측했다는 것으로 보입니다. 세번째 예시는 도구변수를 활용했을 경우의 출력인데, Wald Estimator 란 아래와 같이 정의됩니다. 213 | 214 | ![](../.gitbook/assets/interpret-img4.png) 215 | * **Target Units** : ate, att, atc 중 어떤 단위로 분석된 결과인지 출력. Lambda 함수를 통해서 직접 정의할 수도 있습니다. 216 | * ate : 평균 처치 효과 (`Average Treatment Effect`) 217 | * att : 실험군 대상의 평균 처치 효과 (`Average Treatment Effect on Treated`) 218 | * atc : 대조군 대상의 평균 처치 효과 (`Average Treatment Effect on Control`) 219 | 220 | ### 4. **Estimate** 221 | 222 | ```python 223 | ## Estimate 224 | Mean value: 10.503496778665827 225 | 226 | Causal Estimate is 10.503496778665827 227 | 228 | ## Estimate 229 | Mean value: 3.9286717508727174 230 | p-value: [1.58915682e-156] 231 | 95.0% confidence interval: [[3.70717741 4.15016609]] 232 | ``` 233 | 234 | 마지막 단계는 Realized Estimand 에 대해서 실제로 추정한 값 (Estimate)을 계산하여 출력합니다. Linear Regression 메소드를 사용해서 추정할 경우, 95.0% confidence interval 을 추가로 출력할 수 있습니다. 235 | 236 | * **Mean value** : 우선, 처치가 가질 수 있는 값이 0,1이라는 가정을 기반으로 합니다. 하나의 관측 데이터에 대해서 Y(1), Y(0)는 가질 수 없기 때문에 개별 수준의 처치 효과를 추정하기 어렵습니다. 따라서 개별 효과들의 평균을 구하여 인과 효과를 계산합니다. 237 | 238 | * τATE=E\[Y(1)−Y(0)]=E\[Y(1)]−E\[Y(0)] 239 | * τATT=E\[Y(1)−Y(0)|Z=1] (Z : 처치 받은 여부) 240 | * τATC=E\[Y(1)−Y(0)|Z=0] 241 | 242 | 아래 함수를 보시면 처치 받은 여부에 따라 데이터 프레임을 분리한 후, 데이터 프레임의 결과값 컬럼을 np.mean 으로 평균낸 후 두 평균값의 차이를 통해 인과 효과를 계산합니다. ([github](https://github.com/microsoft/dowhy/blob/95be0350818db3233051f1bbb849b6c3925e2e0b/dowhy/causal\_estimator.py#L186)) 243 | 244 | ```python 245 | def estimate_effect_naive(self): 246 | # TODO Only works for binary treatment 247 | df_withtreatment = self._data.loc[self._data[self._treatment_name] == 1] 248 | df_notreatment = self._data.loc[self._data[self._treatment_name] == 0] 249 | est = np.mean(df_withtreatment[self._outcome_name]) - np.mean(df_notreatment[self._outcome_name]) 250 | return CausalEstimate(est, None, None, control_value=0, treatment_value=1) 251 | ``` 252 | * **p-value** : 인과 효과 추정치가 같다는 가정 하에, 무작위 표본 추출 오류로 인해 1000개의 표본 중 \_\_% 에서 관찰된 수준 이상의 차이가 도출됩니다. 모든 Estimator 에서 `test_significance="bootstrap"` 입력을 통해서 p-value 를 계산할 수 있습니다. 기본적으로 Two-sided test 를 진행하며 부트스트랩을 몇 회 진행할 지는 num\_null\_simulations 입력을 받습니다. 초기값은 1000입니다. 253 | 254 | > 1. Dummy outcome 을 np.random.permutation 을 통해서 생성 255 | 256 | 1. 기존 데이터에 dataframe.assign 를 통해서 Dummy outcome 을 할당 257 | 2. 학습된 Estimator 에 2에서 만들어진 데이터 셋을 넣고 인과 효과를 추정 258 | 3. 1000개의 인과 효과 추정치가 만들어짐 259 | 4. 1000개의 인과 효과 추정치의 중앙값을 계산한 후, 실제 인과 효과 추정치와 비교함 ([github](https://github.com/microsoft/dowhy/blob/95be0350818db3233051f1bbb849b6c3925e2e0b/dowhy/causal\_estimator.py#L471)) 260 | 261 | > 262 | * **95.0% confidence interval** : 인과 효과 추정치의 95% 신뢰 구간. 인과 효과 추정에서 사용된 Estimator 의 특정한 방식이 없다면 부트스트랩을 통해서 계산됩니다. 부트스트랩을 어떻게 진행할지는 (num\_simulations, sample\_size\_fraction) 2가지 입력을 받습니다. 두 값의 초기값은 (100, 1) 입니다. 263 | 264 | ## (예시) 함수 출력 결과 해석해서 전달하기 265 | 266 | * **문제** 명시 : 멤버십 프로그램을 가입한 여부가 구매액 증가에 영향을 줬을까? 267 | * **인과 모형** 에 대한 설명 : 268 | 269 | 멤버십 프로그램을 가입한 여부 → 구매액 증가 사이의 관계에 영향을 주는 외부 요인들이 많으며, 정성적으로 고려할 수 있는 주요한 외부 요인은 가입 월, 멤버십 프로그램 가입 전의 구매액이었습니다. 270 | 271 | 다만 정성적으로 고려할 수 없는 / 측정할 수 없는 외부 요인도 존재하여 Unobserved Confounders 라는 이름으로 반영이 되었습니다. 272 | 273 | ![Causal Graph](../.gitbook/assets/interpret-img5.png) 274 | * **결과** 요약 : 275 | * 멤버십 보상 프로그램이 적용되었던 집단에 한정하여, 멤버십 보상 프로그램에 한 명의 사용자가 더 가입할수록, 평균 $102.40의 구매액 증가가 있었습니다. 276 | * 다만, 이 결과는 몇 가지 가정을 기반으로 합니다 : 277 | * 출력 결과에서 Assumption 에 대해서 이해할 수 있게 풀어서 표기함. 278 | * 이 결과는 \_\_, \_\_, \_\_ 의 검증 테스트를 통과하여 추정치의 강건성을 확인했습니다. 279 | * 참고용 실제 출력 280 | 281 | ```python 282 | *** Causal Estimate *** 283 | 284 | ## Identified estimand 285 | Estimand type: nonparametric-ate 286 | 287 | ### Estimand : 1 288 | Estimand name: backdoor1 (Default) 289 | Estimand expression: 290 | d 291 | ────────────(Expectation(post_spends|signup_month,pre_spends)) 292 | d[treatment] 293 | Estimand assumption 1, Unconfoundedness: If U→{treatment} and U→post_spends then P(post_spends|treatment,signup_month,pre_spends,U) = P(post_spends|treatment,signup_month,pre_spends) 294 | 295 | ## Realized estimand 296 | b: post_spends~treatment+signup_month+pre_spends 297 | Target units: att 298 | 299 | ## Estimate 300 | Mean value: 102.40033381020511 301 | ``` 302 | -------------------------------------------------------------------------------- /example-notebooks/hotel-booking-cancel.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 다른 호텔 방에 배정되는 것이, 고객의 호텔 예약 취소에 어떤 영향을 줄까? 3 | --- 4 | 5 | # 호텔 예약 취소에 숨겨진 인과적 이야기 6 | 7 | * 작성자: [최보경](https://www.facebook.com/pagebokyung/) 8 | * 원문: [DoWhy - The Causal Story Behind Hotel Booking Cancellations](https://microsoft.github.io/dowhy/example\_notebooks/DoWhy-The%20Causal%20Story%20Behind%20Hotel%20Booking%20Cancellations.html) 9 | 10 | ## Background 11 | 12 | ![](../.gitbook/assets/hotel-causal-graph.png) 13 | 14 | 호텔 예약의 취소를 야기하는 요소들이 무엇인지에 대해서 고려합니다. 이 분석은 [Antonio, Almeida and Nunes (2019)](https://www.sciencedirect.com/science/article/pii/S2352340918315191) 의 호텔 예약 데이터셋을 활용했습니다. GitHub 에서는 이 링크에서 확인하실 수 있습니다 : [rfordatascience/tidytuesday](https://github.com/rfordatascience/tidytuesday/blob/master/data/2020/2020-02-11/readme.md). 15 | 16 | 호텔 예약이 취소되기 까지는 다양한 이유들이 존재합니다. 17 | 18 | > 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. 19 | 20 | 이러한 상황에서, 호텔 예약을 취소하는 원인을 찾기 위해서는 각 고객들이 랜덤하게 두 가지의 카테고리에 속하는 방식의 _Randomized Controlled Trials_ 와 같은 실험이 가장 Golden Standard인데요. 이러한 실험은 특정 상황에서는 너무 비용이 크거나, 윤리적이지 않습니다. 예를 들어 호텔이 고객들에게 서비스의 차등을 준다는 점을 깨닫게 된다면, 호텔의 명성에 손해가 갈 수 있습니다. 21 | 22 | 관측 데이터를 통해서, 이러한 질문에 대답할 수는 없을까요? 23 | 24 | ```python 25 | # Config dict to set the logging level : 로깅의 수준을 세팅하기 위한 config 라이브러리 26 | 27 | import logging.config 28 | DEFAULT_LOGGING = { 29 | 'version': 1, 30 | 'disable_existing_loggers': False, 31 | 'loggers': { 32 | '': { 33 | 'level': 'INFO', 34 | }, 35 | } 36 | } 37 | 38 | logging.config.dictConfig(DEFAULT_LOGGING) 39 | 40 | # Disabling warnings output 41 | import warnings 42 | from sklearn.exceptions import DataConversionWarning, ConvergenceWarning 43 | warnings.filterwarnings(action='ignore', category=DataConversionWarning) 44 | warnings.filterwarnings(action='ignore', category=ConvergenceWarning) 45 | warnings.filterwarnings(action='ignore', category=UserWarning) 46 | warnings.filterwarnings('ignore') 47 | 48 | #!pip install dowhy : dowhy 설치 되지 않았을 경우 49 | import dowhy 50 | import pandas as pd 51 | import numpy as np 52 | import matplotlib.pyplot as plt 53 | ``` 54 | 55 | ```python 56 | dataset = pd.read_csv('https://raw.githubusercontent.com/Sid-darthvader/DoWhy-The-Causal-Story-Behind-Hotel-Booking-Cancellations/master/hotel_bookings.csv') 57 | dataset.head() 58 | ``` 59 | 60 | | | hotel | is\_canceled | lead\_time | arrival\_date\_year | arrival\_date\_month | arrival\_date\_week\_number | arrival\_date\_day\_of\_month | stays\_in\_weekend\_nights | stays\_in\_week\_nights | adults | ... | deposit\_type | agent | company | days\_in\_waiting\_list | customer\_type | adr | required\_car\_parking\_spaces | total\_of\_special\_requests | reservation\_status | reservation\_status\_date | 61 | | - | ------------ | ------------ | ---------- | ------------------- | -------------------- | --------------------------- | ----------------------------- | -------------------------- | ----------------------- | ------ | --- | ------------- | ----- | ------- | ----------------------- | -------------- | ---- | ------------------------------ | ---------------------------- | ------------------- | ------------------------- | 62 | | 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 | 63 | | 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 | 64 | | 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 | 65 | | 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 | 66 | | 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 | 67 | 68 | 5 rows × 32 columns 69 | 70 | ```python 71 | dataset.columns 72 | ``` 73 | 74 | ``` 75 | Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_year', 76 | 'arrival_date_month', 'arrival_date_week_number', 77 | 'arrival_date_day_of_month', 'stays_in_weekend_nights', 78 | 'stays_in_week_nights', 'adults', 'children', 'babies', 'meal', 79 | 'country', 'market_segment', 'distribution_channel', 80 | 'is_repeated_guest', 'previous_cancellations', 81 | 'previous_bookings_not_canceled', 'reserved_room_type', 82 | 'assigned_room_type', 'booking_changes', 'deposit_type', 'agent', 83 | 'company', 'days_in_waiting_list', 'customer_type', 'adr', 84 | 'required_car_parking_spaces', 'total_of_special_requests', 85 | 'reservation_status', 'reservation_status_date'], 86 | dtype='object') 87 | ``` 88 | 89 | ## Data Description 90 | 91 | 변수와 그에 대한 설명을 확인하기 위해서는 [이 링크](https://github.com/rfordatascience/tidytuesday/blob/master/data/2020/2020-02-11/readme.md)를 참조해주세요. 92 | 93 | ## Feature Engineering 94 | 95 | 의미 있는 변수들을 만들고, 데이터 셋의 디멘션을 줄이기 위해서 Feature Engineering 을 진행합니다. 96 | 97 | * **Total Stay** = stays\_in\_weekend\_nights + stays\_in\_week\_nights 98 | * **Guests** = adults + children + babies 99 | * **Different\_room\_assigned** = 1 if reserved\_room\_type & assigned\_room\_type are different, 0 otherwise. 100 | 101 | ```python 102 | # Total stay in nights 103 | dataset['total_stay'] = dataset['stays_in_week_nights']+dataset['stays_in_weekend_nights'] 104 | 105 | # Total number of guests 106 | dataset['guests'] = dataset['adults']+dataset['children'] +dataset['babies'] 107 | 108 | # Creating the different_room_assigned feature 109 | dataset['different_room_assigned']=0 110 | slice_indices =dataset['reserved_room_type']!=dataset['assigned_room_type'] 111 | dataset.loc[slice_indices, 'different_room_assigned'] = 1 112 | ``` 113 | 114 | ```python 115 | # Total stay in nights 116 | dataset['total_stay'] = dataset['stays_in_week_nights']+dataset['stays_in_weekend_nights'] 117 | 118 | # Total number of guests 119 | dataset['guests'] = dataset['adults']+dataset['children'] +dataset['babies'] 120 | 121 | # Creating the different_room_assigned feature 122 | dataset['different_room_assigned']=0 123 | slice_indices =dataset['reserved_room_type']!=dataset['assigned_room_type'] 124 | # Reserved room type 과 Assigned room type 이 다른 인덱스에 대해서, 'different_room_assigned' 컬럼을 1로 채워라 125 | dataset.loc[slice_indices,'different_room_assigned']=1 126 | 127 | # Deleting older features : 가공에 사용되어서 불필요한 피쳐는 삭제합니다. 128 | dataset = dataset.drop(['stays_in_week_nights','stays_in_weekend_nights','adults','children','babies' 129 | ,'reserved_room_type','assigned_room_type'],axis=1) 130 | 131 | dataset.columns 132 | ``` 133 | 134 | ``` 135 | Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_year', 136 | 'arrival_date_month', 'arrival_date_week_number', 137 | 'arrival_date_day_of_month', 'meal', 'country', 'market_segment', 138 | 'distribution_channel', 'is_repeated_guest', 'previous_cancellations', 139 | 'previous_bookings_not_canceled', 'booking_changes', 'deposit_type', 140 | 'agent', 'company', 'days_in_waiting_list', 'customer_type', 'adr', 141 | 'required_car_parking_spaces', 'total_of_special_requests', 142 | 'reservation_status', 'reservation_status_date', 'total_stay', 'guests', 143 | 'different_room_assigned'], 144 | dtype='object') 145 | ``` 146 | 147 | NULL 값이 있거나, 유저 단위에 1:1 대응이 될 정도로 유니크한 값이 많은 컬럼은 삭제합니다. 또한 `country`의 결측치는 최빈값으로 메웁니다. `distribution_channel` 이라는 컬럼은 `market_segment` 와 겹치는 부분이 많기 때문에 삭제됩니다. 148 | 149 | ```python 150 | dataset.isnull().sum() # Country,Agent,Company contain 488,16340,112593 missing entries 151 | ``` 152 | 153 | ``` 154 | hotel 0 155 | is_canceled 0 156 | lead_time 0 157 | arrival_date_year 0 158 | arrival_date_month 0 159 | arrival_date_week_number 0 160 | arrival_date_day_of_month 0 161 | meal 0 162 | country 488 163 | market_segment 0 164 | distribution_channel 0 165 | is_repeated_guest 0 166 | previous_cancellations 0 167 | previous_bookings_not_canceled 0 168 | booking_changes 0 169 | deposit_type 0 170 | agent 16340 171 | company 112593 172 | days_in_waiting_list 0 173 | customer_type 0 174 | adr 0 175 | required_car_parking_spaces 0 176 | total_of_special_requests 0 177 | reservation_status 0 178 | reservation_status_date 0 179 | total_stay 0 180 | guests 4 181 | different_room_assigned 0 182 | dtype: int64 183 | ``` 184 | 185 | ```python 186 | # 국가 컬럼의 최빈값 확인 187 | dataset['country'].mode() 188 | ``` 189 | 190 | ``` 191 | 0 PRT 192 | dtype: object 193 | ``` 194 | 195 | ```python 196 | dataset = dataset.drop(['agent','company'],axis=1) 197 | 198 | # Replacing missing countries with most freqently occuring countries 199 | dataset['country']= dataset['country'].fillna(dataset['country'].mode()[0]) 200 | ``` 201 | 202 | 불필요한 컬럼을 판단해 삭제하고, `different_room_assigned` 와 `is_canceled` 라는 컬럼을 여부에 따라 1,0으로 대체합니다. 203 | 204 | ```python 205 | dataset = dataset.drop(['reservation_status','reservation_status_date','arrival_date_day_of_month'],axis=1) 206 | dataset = dataset.drop(['arrival_date_year'],axis=1) 207 | dataset = dataset.drop(['distribution_channel'], axis=1) 208 | ``` 209 | 210 | ```python 211 | # Replacing 1 by True and 0 by False for the experiment and outcome variables 212 | 213 | dataset['different_room_assigned']= dataset['different_room_assigned'].replace(1,True) 214 | dataset['different_room_assigned']= dataset['different_room_assigned'].replace(0,False) 215 | dataset['is_canceled']= dataset['is_canceled'].replace(1,True) 216 | dataset['is_canceled']= dataset['is_canceled'].replace(0,False) 217 | dataset.dropna(inplace=True) 218 | print(dataset.columns) 219 | dataset.iloc[:, 5:20].head(100) 220 | ``` 221 | 222 | ``` 223 | Index(['hotel', 'is_canceled', 'lead_time', 'arrival_date_month', 224 | 'arrival_date_week_number', 'meal', 'country', 'market_segment', 225 | 'is_repeated_guest', 'previous_cancellations', 226 | 'previous_bookings_not_canceled', 'booking_changes', 'deposit_type', 227 | 'days_in_waiting_list', 'customer_type', 'adr', 228 | 'required_car_parking_spaces', 'total_of_special_requests', 229 | 'total_stay', 'guests', 'different_room_assigned'], 230 | dtype='object') 231 | ``` 232 | 233 | .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; } 234 | 235 | | | meal | country | market\_segment | is\_repeated\_guest | previous\_cancellations | previous\_bookings\_not\_canceled | booking\_changes | deposit\_type | days\_in\_waiting\_list | customer\_type | adr | required\_car\_parking\_spaces | total\_of\_special\_requests | total\_stay | guests | 236 | | --- | ---- | ------- | --------------- | ------------------- | ----------------------- | --------------------------------- | ---------------- | ------------- | ----------------------- | -------------- | ------ | ------------------------------ | ---------------------------- | ----------- | ------ | 237 | | 0 | BB | PRT | Direct | 0 | 0 | 0 | 3 | No Deposit | 0 | Transient | 0.00 | 0 | 0 | 0 | 2.0 | 238 | | 1 | BB | PRT | Direct | 0 | 0 | 0 | 4 | No Deposit | 0 | Transient | 0.00 | 0 | 0 | 0 | 2.0 | 239 | | 2 | BB | GBR | Direct | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 75.00 | 0 | 0 | 1 | 1.0 | 240 | | 3 | BB | GBR | Corporate | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 75.00 | 0 | 0 | 1 | 1.0 | 241 | | 4 | BB | GBR | Online TA | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 98.00 | 0 | 1 | 2 | 2.0 | 242 | | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | 243 | | 95 | BB | PRT | Online TA | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 73.80 | 0 | 1 | 2 | 2.0 | 244 | | 96 | BB | PRT | Online TA | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 117.00 | 0 | 1 | 7 | 2.0 | 245 | | 97 | HB | ESP | Offline TA/TO | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 196.54 | 0 | 1 | 7 | 3.0 | 246 | | 98 | BB | PRT | Online TA | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 99.30 | 1 | 2 | 7 | 3.0 | 247 | | 99 | BB | DEU | Direct | 0 | 0 | 0 | 0 | No Deposit | 0 | Transient | 90.95 | 0 | 0 | 7 | 2.0 | 248 | 249 | 100 rows × 15 columns 250 | 251 | 데이터셋에서, No Deposit 으로 예약했으면서 취소한 비율에 대해서 미리 확인해봅니다. 총 104,637건 중에서 29,690건이 취소되었습니다. 252 | 253 | ```python 254 | dataset = dataset[dataset.deposit_type=="No Deposit"] 255 | dataset.groupby(['deposit_type','is_canceled']).count() 256 | ``` 257 | 258 | | | | hotel | lead\_time | arrival\_date\_month | arrival\_date\_week\_number | meal | country | market\_segment | is\_repeated\_guest | previous\_cancellations | previous\_bookings\_not\_canceled | booking\_changes | days\_in\_waiting\_list | customer\_type | adr | required\_car\_parking\_spaces | total\_of\_special\_requests | total\_stay | guests | different\_room\_assigned | 259 | | ------------- | ------------ | ----- | ---------- | -------------------- | --------------------------- | ----- | ------- | --------------- | ------------------- | ----------------------- | --------------------------------- | ---------------- | ----------------------- | -------------- | ----- | ------------------------------ | ---------------------------- | ----------- | ------ | ------------------------- | 260 | | deposit\_type | is\_canceled | | | | | | | | | | | | | | | | | | | | 261 | | No Deposit | False | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 74947 | 262 | | True | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | 29690 | | 263 | 264 | 판다스에는 데이터프레임의 복사본을 만들어주는 pandas.DataFrame.copy가 있고 a = b와는 다른 방식의 복사입니다. 265 | 266 | * a = b는 원본 데이터가 변하면 똑같이 변하는 얕은 복사인 반면, pandas.DataFrame.copy는 복사 당시의 데이터프레임 상태만 복사되는 깊은 복사입니다. 267 | * 얕은 복사를 하면 복사본은 원본과 데이터/index를 공유하지만, 깊은 복사는 복사본이 자신만의 데이터/index를 갖게합니다. 268 | 269 | ```python 270 | dataset_copy = dataset.copy(deep=True) 271 | ``` 272 | 273 | ## Calculating Expected Counts : Confounder 를 유추할 수 있는 간단한 방법 274 | 275 | 예약 취소의 개수와, 다른 방에 배정되는 케이스의 개수가 심각하게 불균형적이기 때문에, 랜덤으로 최초 1000개의 관측치를 선택하며 `is_cancelled` & `different_room_assigned` 가 같은 값을 달성하는지를 확인합니다. 이 전체적인 프로세스는 10000회 반복되고, 그 중에서 예상되는 달성 횟수는 50%에 가깝습니다. 그 이유는 두개의 변수가 같은 값을 랜덤하게 가질 수 있을 확률은 50% 이기 때문입니다. 276 | 277 | 따라서 통계적으로 이야기하면, 이 단계에서 유한한 결론이 있는 것은 아닙니다. 따라서, 고객이 예약한 방과 다른 방을 배정하는 상황은 그 고객이 예약을 취소하게 할수도, 아닐수도 있습니다. 278 | 279 | **첫번째 시나리오**는, 아무 조건 가정 없이 샘플링을 진행합니다. 280 | 281 | ```python 282 | counts_sum=0 283 | for i in range(1,10000): 284 | counts_i = 0 285 | # 데이터셋에서 1000개의 행을 랜덤 샘플링합니다. 286 | rdf = dataset.sample(1000) 287 | # is_canceled FALSE, different_room_assigned FALSE 288 | # is_canceled TRUE, different_room_assigned TRUE 인 행들의 개수를 셉니다. 289 | counts_i = rdf[rdf["is_canceled"]== rdf["different_room_assigned"]].shape[0] 290 | # 이 과정을 10000회 반복하면서, 총 몇개의 행들이 같았는지를 더합니다. 291 | counts_sum+= counts_i 292 | 293 | # 10000회 반복 시행을 종료했을 때, 평균적으로 몇개의 행들이 같았는지를 파악합니다. 294 | counts_sum/10000 295 | ``` 296 | 297 | `588.7015` 298 | 299 | **두번째 시나리오**는, 예약에 변경이 없었을 것이라는 조건을 가정한 후에 샘플링을 진행합니다. 300 | 301 | ```python 302 | # Expected Count when there are no booking changes : booking_changes = 0 이라는 조건을 걸어 계층화할 경우 303 | 304 | counts_sum=0 305 | for i in range(1,10000): 306 | counts_i = 0 307 | # 조건을 건 후 그 중에서 1000개의 행을 랜덤 샘플링합니다. 308 | rdf = dataset[dataset["booking_changes"]==0].sample(1000) 309 | counts_i = rdf[rdf["is_canceled"]== rdf["different_room_assigned"]].shape[0] 310 | counts_sum+= counts_i 311 | 312 | counts_sum/10000 313 | ``` 314 | 315 | `572.608` 316 | 317 | **세번째 시나리오**는 예약에 변경이 있었을 것이라는 조건(>0)을 가정한 후에 샘플링을 진행합니다. 318 | 319 | ```python 320 | # Expected Count when there are booking changes = 66.4% 321 | counts_sum=0 322 | for i in range(1,10000): 323 | counts_i = 0 324 | # 조건을 건 후 그 중에서 1000개의 행을 랜덤 샘플링합니다. 325 | rdf = dataset[dataset["booking_changes"]>0].sample(1000) 326 | counts_i = rdf[rdf["is_canceled"]== rdf["different_room_assigned"]].shape[0] 327 | counts_sum+= counts_i 328 | 329 | counts_sum/10000 330 | ``` 331 | 332 | `666.015` 333 | 334 | 확실히 예약에 변경이 있었을 것이라는 조건을 통해서, 무언가 숫자에 변화를 볼 수 있습니다. `booking_changes` 변수가 곧 교란변수가 될 수 있다는 힌트가 됩니다. 335 | 336 | 하지만 `booking_changes` 변수가 유일한 교란변수일까요? 만일 관측되지 않은 교란변수가 있고, 그것이 데이터셋에 변수 형태로 포함되지 않은 정보라면, 우리는 이 `booking_changes` 변수가 곧 교란변수가 될 수 있다는 주장을 계속할 수 있을까요? 337 | 338 | *** 339 | 340 | ## _DoWhy_ 의 시작 341 | 342 | ## Step-1. Create a Causal Graph : 인과 그래프 생성하기 343 | 344 | 예측 모델링 문제에 대한 사전지식을, 인과 그래프 형태로 가정을 사용해서 표현합니다. 전체 그래프를 이 단계에서 표현하지 않아도, 일부 그래프만 표현하여도 충분합니다. 나머지는 DoWhy 를 통해서 찾을 수 있습니다. 345 | 346 | 아래 내용이 인과 모형으로 해석된 몇 개의 가정들입니다. 347 | 348 | * `Market Segment` 변수 : TA(Travel Agents), TO(Tour Operators) 라는 2가지 값으로 구성되며, `Lead Time` 변수에 영향을 줍니다. 349 | * `Country` 변수 : 한 사람이 일찍 호텔을 예약할지, 아닐지에 대해 좌우하는 역할을 할 수 있으며, 궁극적으로 일찍 호텔을 예약하는 행동은 `Lead Time` 변수에 영향을 줍니다. 그 외로도, `Meal` 변수에도 영향을 줍니다. 350 | * `Lead Time` 변수 : `Days in Waitlist` 변수에 확실하게 영향을 줍니다. 늦게 예약할수록, 예약을 할 수 있는 기회가 적어지기 때문입니다. 추가적으로 높은 `Lead Time` 은 `Cancellations` 변수를 높아지게 만들 수 있습니다. 351 | * `Previous Booking Retentions` 변수 : 고객의 `Repeated Guest` 여부를 좌우하고, 이 두개의 변수는 모두 `Cancelled` 에도 영향을 줄 수 있습니다. 리텐션이 높았던 고객일수록 취소할 확률이 낮고, 리텐션이 낮았던 즉 늘 취소했던 고객이라면 취소할 확률이 높습닌다. 352 | * `Booking Changes` 변수 : 예약을 변경하는 것은 `Different room assigned` 변수를 좌우하고, `Cancellation` 에도 영향을 줄 수 있습니다. 353 | 354 | 정리하면, `Booking Changes` 변수가 Treatment, Outcome 에 영향을 주는 가장 유일한 교란변수일 확률은 낮습니다. 현재 변수는 물론, 데이터셋에 고려되지 않은 정보들까지 포함하여 다른 관측되지 않은 교란변수가 많을 것입니다. 355 | 356 | ### Pygraphviz 활용 357 | 358 | pygraphviz 설치에서 에러가 발생할 경우, 터미널에서 아래와 같이 해결할 수 있습니다. 359 | 360 | `brew install graphviz` `pip install graphviz` `pip install pygraphviz` 361 | 362 | Diagraph 란, Edge 와 Node 로 구성되고 추가적인 데이터나 특성을 포함합니다. Self loops 는 가능하지만, Multiple edge 는 허용되지 않습니다. 노드는 주로 임의의 해싱가능한 Python 객체입니다. Edge는 노드간의 링크입니다. ([설명](https://networkx.org/documentation/stable/reference/classes/digraph.html#)) 363 | 364 | ```python 365 | import pygraphviz 366 | 367 | # 변수명[label = 그림에 표기될 이름] 368 | # 변수명(label 없으면 변수명 그대로 그림에 표기됨) 369 | # U = Unobserved confounder 370 | # 변수명 -> 변수명 : 영향을 주는 관계 371 | # 변수명 -> {변수명, 변수명} : 다수에 영향을 주는 변수 관계 372 | 373 | causal_graph = """digraph { 374 | different_room_assigned[label="Different Room Assigned"]; 375 | is_canceled[label="Booking Cancelled"]; 376 | booking_changes[label="Booking Changes"]; 377 | previous_bookings_not_canceled[label="Previous Booking Retentions"]; 378 | days_in_waiting_list[label="Days in Waitlist"]; 379 | lead_time[label="Lead Time"]; 380 | market_segment[label="Market Segment"]; 381 | country[label="Country"]; 382 | U[label="Unobserved Confounders"]; 383 | 384 | is_repeated_guest; 385 | total_stay; 386 | guests; 387 | meal; 388 | hotel; 389 | 390 | U->different_room_assigned; 391 | U->is_canceled; 392 | U->required_car_parking_spaces; 393 | 394 | market_segment -> lead_time; 395 | lead_time -> is_canceled; 396 | country -> lead_time; 397 | different_room_assigned -> is_canceled; 398 | country -> meal; 399 | lead_time -> days_in_waiting_list; 400 | days_in_waiting_list -> is_canceled; 401 | previous_bookings_not_canceled -> is_canceled; 402 | previous_bookings_not_canceled -> is_repeated_guest; 403 | is_repeated_guest -> is_canceled; 404 | total_stay -> is_canceled; 405 | guests -> is_canceled; 406 | booking_changes -> different_room_assigned; 407 | booking_changes -> is_canceled; 408 | hotel -> is_canceled; 409 | required_car_parking_spaces -> is_canceled; 410 | total_of_special_requests -> is_canceled; 411 | 412 | country -> {hotel, required_car_parking_spaces,total_of_special_requests,is_canceled}; 413 | market_segment -> {hotel, required_car_parking_spaces,total_of_special_requests,is_canceled}; 414 | }""" 415 | ``` 416 | 417 | 여기서, Treatment 는 고객이 예약한 방을 그대로 배정해주는 것입니다. Outcome은 예약이 취소되었는지에 대한 여부입니다. 418 | 419 | * Common Causes : 우리의 인과 그래프에 따라 Treatment, Outcome 에 영향을 줄 수 있는 변수들을 의미합니다. 420 | * 앞서 세운 인과적 가정에 따르면 이 Common Causes 가 될 수 있는 조건을 만족하는 변수는 `Booking Changes` 그리고 `Unobserved Confounders` 변수 2가지 입니다. 421 | 422 | 따라서, 만일 그래프를 명시적으로 작성하지 않을 경우에는 아래 함수에 따라 파라미터들을 제공할 수 있습니다. 423 | 424 | > So if we are not specifying the graph explicitly (Not Recommended!), one can also provide these as parameters in the function mentioned below. 425 | 426 | ```python 427 | model= dowhy.CausalModel( 428 | data = dataset, 429 | graph = causal_graph.replace("\n", " "), # causal_graph 라는 문자열에서 띄어쓰기를 space 로 대체해주는 역할 430 | treatment = 'different_room_assigned', 431 | outcome = 'is_canceled') 432 | model.view_model() 433 | 434 | from IPython.display import Image, display 435 | display(Image(filename = "causal_model.png")) 436 | ``` 437 | 438 | ``` 439 | INFO:dowhy.causal_model:Model to find the causal effect of treatment ['different_room_assigned'] on outcome ['is_canceled'] 440 | ``` 441 | 442 | ![](../.gitbook/assets/hotel-causal-graph-in-model.png) 443 | 444 | ## Step-2. Identify Causal Effect : 인과 효과 식별하기 445 | 446 | 우리는 모든 다른 것들을 유지한 채로 Treatment 를 변화시킬 때, Outcome 에 변화가 있다면 Treatment가 Outcome의 원인이 된다라고 이야기합니다. 따라서 이 단계에서는, 인과 그래프의 특성들을 활용해서 추정하고자 하는 인과 효과를 식별합니다. 447 | 448 | dowhy.CausalModel() 클래스를 활용해서 모델을 입력하게 되면, 아래 함수들을 사용할 수 있습니다. ([모듈 코드](https://microsoft.github.io/dowhy/\_modules/dowhy/causal\_model.html)) 449 | 450 | * identify\_effect() 451 | * Identified estimand 를 리턴하는 주요한 메소드. 만일 Estimand 의 타입이 non-parametric ATE라면, 주어진 인과 모형에서 Identified estimand가 있는지를 확인하기 위해서 Backdoor / Instrumental Variable / Frontdoor Identification 메소드들을 사용합니다. 452 | * estimate\_effect() 453 | * Step-3 에서 이어집니다. 454 | * do() 455 | * refute\_estimate() 456 | * view\_model() 457 | * interpret() 458 | * summary() 459 | 460 | ### Estimand 461 | 462 | 예를 들어, 7년간의 월별 수익이 Gaussian 분포를 따른다고 가정합니다. Gaussian 분포는 평균, 분산을 Parameter 로 가지고 있어, 이 경우에 개별 월 수입은 Random variable 이라고 할 수 있습니다. ([출처](https://fullfu.tistory.com/2)) 463 | 464 | ![](../.gitbook/assets/hotel-estimand.png) 465 | 466 | * Estimand : (예시) Gaussian의 Parameter 가 되는 평균, 분산 467 | * Estimate : Random variable 로 월별 수익을 추정한 값 468 | * 평균의 Estimate : 100만원 +- standard error 469 | * 표준 편차의 Estimate : 10만원 470 | * Estimator : Random variable 로부터 Estimate를 얻어내는 함수 471 | * 평균의 Estimator : mu = sigma(월 수입) / 총 월 수 472 | * 표준 편차의 Estimate : s = sqrt(sigma(월 수입 - 평균 월 수입) / (총 월 수 -1)) 473 | 474 | > 인과효과 연구에서 관심 모집단의 특성과 연관지어 분석 결과를 해석하고 추론하는 것은 중요하며, 이에 연구자는 통계적 추론(inference)를 위한 관심 모수(estimand; parameter of interest)를 정의하고 이에 대한 추정치(estimates)를 산출하게 된다. 475 | 476 | ```python 477 | import statsmodels 478 | 479 | # Identify the causal effect : 아까 위에서 명시한 model 을 활용합니다. 480 | identified_estimand = model.identify_effect(proceed_when_unidentifiable = True) 481 | print(identified_estimand) 482 | ``` 483 | 484 | ``` 485 | Estimand type: nonparametric-ate 486 | 487 | ### Estimand : 1 488 | Estimand name: backdoor 489 | Estimand expression: 490 | d 491 | ──────────────────────────(Expectation(is_canceled|country,booking_changes,mar 492 | d[different_room_assigned] 493 | 494 | 495 | ket_segment,required_car_parking_spaces,total_of_special_requests,guests,days_ 496 | 497 | 498 | 499 | in_waiting_list,lead_time,total_stay,meal,is_repeated_guest,previous_bookings_ 500 | 501 | 502 | 503 | not_canceled,hotel)) 504 | 505 | Estimand assumption 1, Unconfoundedness: If U→{different_room_assigned} and U→is_canceled then P(is_canceled|different_room_assigned,country,booking_changes,market_segment,required_car_parking_spaces,total_of_special_requests,guests,days_in_waiting_list,lead_time,total_stay,meal,is_repeated_guest,previous_bookings_not_canceled,hotel,U) = P(is_canceled|different_room_assigned,country,booking_changes,market_segment,required_car_parking_spaces,total_of_special_requests,guests,days_in_waiting_list,lead_time,total_stay,meal,is_repeated_guest,previous_bookings_not_canceled,hotel) 506 | 507 | ### Estimand : 2 508 | Estimand name: iv 509 | No such variable found! 510 | 511 | ### Estimand : 3 512 | Estimand name: frontdoor 513 | No such variable found! 514 | ``` 515 | 516 | ## Step-3. Estimate Identified Estimand : 517 | 518 | dowhy.CausalModel() 클래스를 활용해서 모델을 입력하게 되면, 아래 함수들을 사용할 수 있습니다. [모듈 코드](https://microsoft.github.io/dowhy/\_modules/dowhy/causal\_model.html) 519 | 520 | * identify\_effect() 521 | * estimate\_effect() 522 | * method\_name 파라미터 값으로 아래 종류들이 있습니다. 523 | * Propensity Score Matching: “backdoor.propensity\_score\_matching” 524 | * Propensity Score Stratification: “backdoor.propensity\_score\_stratification” 525 | * Propensity Score-based Inverse Weighting: “backdoor.propensity\_score\_weighting” 526 | * Linear Regression: “backdoor.linear\_regression” 527 | * Generalized Linear Models (e.g., logistic regression): “backdoor.generalized\_linear\_model” 528 | * Instrumental Variables: “iv.instrumental\_variable” 529 | * Regression Discontinuity: “iv.regression\_discontinuity” 530 | * do() 531 | * refute\_estimate() 532 | * view\_model() 533 | * interpret() 534 | * summary() 535 | 536 | ```python 537 | estimate = model.estimate_effect(identified_estimand, 538 | method_name="backdoor.propensity_score_stratification",target_units="ate") 539 | 540 | # target_units can be these three : 541 | # ATE = Average Treatment Effect 542 | # ATT = Average Treatment Effect on Treated (i.e. those who were assigned a different room) 543 | # ATC = Average Treatment Effect on Control (i.e. those who were not assigned a different room) 544 | 545 | print(estimate) 546 | ``` 547 | 548 | ``` 549 | *** Causal Estimate *** 550 | 551 | ## Identified estimand 552 | Estimand type: nonparametric-ate 553 | 554 | ## Realized estimand 555 | b: is_canceled~different_room_assigned+previous_bookings_not_canceled+hotel+total_of_special_requests+market_segment+is_repeated_guest+guests+lead_time+meal+days_in_waiting_list+country+booking_changes+total_stay+required_car_parking_spaces 556 | Target units: ate 557 | 558 | ## Estimate 559 | Mean value: -0.2508732026233869 560 | ``` 561 | 562 | **결과는 꽤나 놀라웠습니다. 다른 방에 배정받는게 취소할 확률을 낮춰준다는 결과를 의미하기 때문입니다. (-0.251만큼) 여기서, 이게 정말 맞는 인과 효과일까요?** 563 | 564 | 다른 메커니즘이 작용했을 수 있습니다. 다른 방에 배정받는 시간적인 상황이 체크인 때만 발생한다면? 이미 호텔에 와있기 때문에 취소할 수 있는 확률이 당연히 낮습니다. 이러한 케이스라면, 이 예약 취소가 언제 발생했는지에 대한 시각 정보가 주요한 변수가 됩니다. 예를 들어, `different_room_assigned` 변수가 예약을 한 당일에 주로 발생한다면 또 어떨까요? 이런 변수를 알게 됨으로써 그래프와 분석을 향상시킬 수 있습니다. 565 | 566 | 앞서 진행했던 연관성 분석이, `is_canceled` 와 `different_room_assigned` 사이의 양의 상관관계를 보였는데요. DoWhy를 통해서 인과 효과를 예측하는 것은 그 반대로, 아예 다른 그림을 보였습니다. 이는 곧, 다른 방에 배정하는 현상의 수를 줄이는 정책은 곧 호텔에게 비생산적인 방향의 정책일 수 있다는 점을 암시합니다. 567 | 568 | ## Step-4. Refute results 569 | 570 | 주의해야 할 점은 인과효과는 데이터셋으로부터 발견되는 것이 아니라, Identification 으로 이끄는 연구자의 가정(Assumptions)을 통해서 발견 됩니다. 데이터는 단순히 통계적인 추정(Estimation)에만 사용됩니다. 즉, 가정이 맞았는지 아닌지에 대해서 증명하는 것이 정말 중요합니다. 571 | 572 | * 다른 Common cause 가 존재한다면? 573 | * Treatment 자체가 Placebo 효과라면? 574 | 575 | ### Method-1 576 | 577 | **Add Random Common Cause:** 578 | 579 | * 랜덤한 독립 변수를 Common cause로 데이터셋에 추가했을 때, 현재의 추정 메소드가 다른 추정치를 돌려줄까요? 580 | * 데이터에서 랜덤하게 공변량 변수들을 Draw하고, 동일한 분석을 재 진행하여 인과 추정치가 변화하는지를 봅니다. (CNN 돌릴 때 랜덤 값 조정하여 강건성 확인하듯이) 581 | * 정규 분포 내에 랜덤하게 빼고 더하는 형태로 변수 추가 582 | * 가정이 본래 옳았다면, 인과 추정치는 크게 변화해서는 안됩니다. 583 | 584 | ```python 585 | refute1_results = model.refute_estimate(identified_estimand, estimate, 586 | method_name = "random_common_cause") 587 | 588 | print(refute1_results) 589 | ``` 590 | 591 | ``` 592 | Refute: Add a Random Common Cause 593 | Estimated effect:-0.2509269875427757 594 | New effect:-0.2484279296616035 595 | ``` 596 | 597 | ### Method-2 598 | 599 | **Placebo Treatment Refuter:** 600 | 601 | * 실제 Treatment 변수 `different_room_assigned` 를 랜덤한 독립 변수로 대체한다면, 추정된 인과 효과는 어떻게 변화할까요? 602 | * 데이터에서 랜덤하게 공변량 변수를 하나 Draw하고 Treatment로 대체하여 동일한 분석을 재 진행하여 인과 추정치가 변화하는지를 봅니다. 603 | * 가정이 본래 옳았다면, 새로운 추정치는 0에 가까워야 합니다. 604 | * p-value 는 New effect 가 통계적으로 유의하게 0과 다른지를 검정합니다. 605 | * p-value < 0.05 라면 New effect가 0과 다르기 때문에 인과 추정치가 문제가 있다는 것을 의미합니다. 606 | 607 | ```python 608 | refute2_results=model.refute_estimate(identified_estimand, estimate, 609 | method_name="placebo_treatment_refuter") 610 | 611 | print(refute2_results) 612 | ``` 613 | 614 | ``` 615 | Refute: Use a Placebo Treatment 616 | Estimated effect:-0.2509269875427757 617 | New effect:7.121213729569751e-05 618 | p value:0.5 619 | ``` 620 | 621 | ### Method-3 622 | 623 | **Data Subset Refuter:** 624 | 625 | * 주어진 전체 데이터 셋을, 랜덤하게 선택한 데이터셋 일부로 대체한다면 인과 추정치가 변화할까요? 626 | * Cross-validation과 유사한 방식으로 데이터셋의 subset 을 생성합니다. 인과 추정치가 subset들에 걸쳐서 차이가 나는지를 확인합니다. 627 | * Making use of Bootstrap as we have more than 100 examples.The greater the number of examples, the more accurate are the confidence estimates 628 | * 정해진 subset을 추출하는 trial 수가 있고, 각 subset 마다의 결과는 보여지지 않고 결과량은 하나.이 값은 어떻게 추출되었는지? 629 | * 가정이 본래 옳았다면, 분산이 크지 않습니다. 630 | * p-value 는 New effect 가 통계적으로 유의하게 Estimated effect 와 다른지를 검정합니다. 631 | * p-value < 0.05 라면 두 Effect가 다르기 때문에 인과 추정치가 문제가 있다는 것을 의미합니다. ([출처](https://issueexplorer.com/issue/microsoft/dowhy/312)) 632 | 633 | ```python 634 | refute3_results=model.refute_estimate(identified_estimand, estimate, 635 | method_name="data_subset_refuter") 636 | print(refute3_results) 637 | ``` 638 | 639 | ``` 640 | Refute: Use a subset of data 641 | Estimated effect:-0.2509269875427757 642 | New effect:-0.2495950577205681 643 | p value:0.28 644 | ``` 645 | 646 | 우리의 추정치가 세 가지의 Refutation test를 통과하는 것을 확인했습니다. 이는 추정치의 정확함을 증명하는 것은 아니지만, 추정치의 신뢰도를 높여줍니다. 647 | --------------------------------------------------------------------------------