Skip to content

Conversation

@AIgoGracia
Copy link
Contributor

No description provided.

@AIgoGracia AIgoGracia linked an issue Sep 21, 2025 that may be closed by this pull request
Copy link
Contributor

@Soyoung-JUN Soyoung-JUN left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 성은님. 코드 리뷰 남깁니다. 올려주신 내용 잘 확인했습니다!
시뮬레이션 데이터 생성하고 최적 정책을 시각화하는 부분을 깔끔하게 구현해주셨네요. 한눈에 이해하기 쉬운 그래프로 표현된 것 같습니다.
현재 코드에서 특별히 변경할 부분은 없습니다. 말씀하신대로 이 PR은 AIPW 점수 계산을 위한 좋은 준비 단계라고 생각합니다. 이어서 AIPW 점수를 계산하는 로직을 이 PR에 추가로 커밋해서 올려주시면 좋을 것 같습니다. 감사합니다.

@Soyoung-JUN
Copy link
Contributor

Soyoung-JUN commented Sep 28, 2025

안녕하세요, 성은님.
코드 리뷰 남깁니다! 전체적으로 정말 깔끔하고, 분석의 흐름이 더 명확해졌네요.
특히 정책 평가와 이질성 분석까지 깊이 있게 다루신 듯합니다.
이미 훌륭한 코드지만, 완성도를 더 높일 수 있는 몇 가지 의견이 있어 전체 코멘트 남깁니다.

  1. 시각화 코드의 벡터화
    for문으로 데이터 포인트를 하나씩 그리는 대신, NumPy의 boolean indexing을 활용하면 코드를 더 간결하고 효율적으로 만들 수 있습니다. 데이터가 많아질 때 속도 향상에도 도움이 됩니다.
    예시: 처리/비처리 그룹을 한 번에 그리기
    treated_idx = W == 1
    ax1.scatter(X[treated_idx, 0], X[treated_idx, 1], ...)

  2. '매직 넘버' 대신 변수 사용
    코드 곳곳에 사용된 정책 임계값 0.5를 policy_threshold = 0.5와 같이 변수로 선언해두면, 나중에 값을 변경해야 할 때 한 곳만 수정하면 되므로 유지보수성이 훨씬 좋아집니다.

필수적인 수정 사항은 아니니, 시간 되실 때 가볍게 검토해보시면 좋을 것 같습니다.
좋은 코드 공유해주셔서 감사합니다!

@AIgoGracia AIgoGracia closed this Oct 4, 2025
@AIgoGracia AIgoGracia reopened this Oct 4, 2025
@AIgoGracia
Copy link
Contributor Author

일단 aipw score 계산까지 구현했는데, 주신 코멘트는 다음번에 좀 더 반영해보겠습니다!

@Soyoung-JUN
Copy link
Contributor

안녕하세요 성은님. 코드 리뷰 남깁니다. 올려주신 내용 잘 확인했습니다!

미팅 이후 참고자료의 결과와 동일하게 나왔는지 검토하였습니다.
코드 기준으로는 동일하게 잘 구현된 것을 확인하였습니다. 다만, 결과는 단순평균의 경우 동일하였으나 AIPW 결과는 다른 것을 확인했습니다.
AIPW 정책 가치 (Value Estimate)

  • R 코드 (V2): 0.3459015997 (약 34.6%) -> R의 추정치가 단순 평균에 더 가깝게 나왔습니다.
  • Python 코드 (V2): 0.3376925438 (약 33.8%)

AIPW 정책 비교 (Difference Estimate)

  • R 코드 (V2): 0.0806035592 (약 8.1%p)
  • Python 코드 (V2): 0.0721973740 (약 7.2%p)

이렇게 결과 차이가 나는 이유로, R 코드는 grf라는 인과추론 전용 패키지의 causal_forest 함수를 사용했는데요, 이 함수는 CATE 추정에 추가 알고리즘(Honesty, Debiasing)이 사용된 걸로 알고 있습니다. 다만, 작성해주신 Python 코드는 scikit-learn의 범용 RandomForestRegressor를 조합하여 CausalForest 클래스를 사용해서 그런 것 같습니다.
(제가 따로 from econml.grf import CausalForest로 평가해봐도 동일한 결과는 나오지 않았습니다. 그리고 skgrf라는 새로운 패키지를 쓰려고 하였으나 배포 휠이 3.7–3.9까지만 있고 2021년 이후 업뎃이 되지 않아서 사용하기는 어려워보였습니다.)

따라서, CausalForest 클래스에 대해서도 추가 설명하고 결과가 원본링크와 왜 차이 나는지에 대해서 노트로 명시하면 될 듯합니다. 다음주에 노트 적어주신 거 확인 후에 close 하도록 하겠습니다.
감사합니다. 추가 문의 있으시면 언제든 연락 주시면 됩니다~

@AIgoGracia
Copy link
Contributor Author

data_framing.csv

Copy link
Contributor

@Funbucket Funbucket left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

코드 작성하시느라 정말 고생하셨습니다!
덕분에 policy learning의 구현 흐름을 코드 차원에서 이해할 수 있었습니다.

추가로 몇 가지 코멘트를 드립니다.

  • Markdown cell을 활용해 목차를 구분하면 코드의 가독성과 논리 흐름이 더욱 명확해질 것 같습니다.

  • 이번 분석은 이전의 가상 데이터셋이 아니라, 토픽 발표 때 사용하셨던 실제 데이터를 활용하신 게 맞을까요? 그렇다면 실제 데이터 기반 분석으로 별도 페이지로 분리하여 정리하는 것도 좋을 것 같습니다!

Comment on lines +975 to +999
"\"\"\"\n",
"================================================================================\n",
"R vs Python 구현 차이\n",
"================================================================================\n",
"\n",
"1. AIPW 정책 가치 (Value Estimate)\n",
"- R 코드 (V2): 0.3459015997 (약 34.6%) -> R의 추정치가 단순 평균에 더 가깝게 나옴\n",
"- Python 코드 (V2): 0.3376925438 (약 33.8%)\n",
"\n",
"2. AIPW 정책 비교 (Difference Estimate)\n",
"- R 코드 (V2): 0.0806035592 (약 8.1%p)\n",
"- Python 코드 (V2): 0.0721973740 (약 7.2%p)\n",
"\n",
"차이가 발생하는 이유:\n",
"----------------------------\n",
"1. 알고리즘 차이\n",
" - R(grf): Honest splitting, debiasing, CATE 전용 트리 알고리즘 사용\n",
" - Python(scikit-learn): 일반 RandomForest 기반, T-learner 사용, debiasing 없음\n",
"\n",
"2. 패키지 한계\n",
" - grf (R): 인과추론 전용 패키지\n",
" - scikit-learn / econml (Python): 일반 ML 기반, 구현 방식 상이\n",
"\n",
"================================================================================\n",
"\"\"\""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

초반에 연구 주제와 데이터셋에 대한 설명이 함께 들어가면 전체 흐름을 이해하는 데 더 도움이 될 것 같습니다!

Comment on lines +1092 to +1249
"# ==============================================================================\n",
"# STEP 3: CAUSAL FOREST WITH AIPW\n",
"# ==============================================================================\n",
"\n",
"print(\"=\" * 70)\n",
"print(\"CAUSAL FOREST WITH AIPW\")\n",
"print(\"=\" * 70)\n",
"\n",
"# Create model matrix (design matrix with intercept)\n",
"X_design = pd.get_dummies(data[covariates], drop_first=False)\n",
"# Add intercept\n",
"X_design.insert(0, 'intercept', 1)\n",
"X_design = X_design.values\n",
"\n",
"Y = data[outcome].values\n",
"W = data[treatment].values\n",
"\n",
"# Try to use sklearn if available\n",
"try:\n",
" from sklearn.ensemble import RandomForestRegressor, RandomForestClassifier\n",
" use_sklearn = True\n",
" print(\"Using scikit-learn for Random Forest\")\n",
"except ImportError:\n",
" use_sklearn = False\n",
" print(\"scikit-learn not found. Using simplified implementation.\")\n",
" print(\"For exact replication of R results, install scikit-learn: pip install scikit-learn\")\n",
"\n",
"# Causal Forest Implementation\n",
"if use_sklearn:\n",
" class CausalForest:\n",
" \"\"\"Causal Forest implementation matching grf package behavior\"\"\"\n",
" \n",
" def __init__(self, n_estimators=2000, max_features=None, min_samples_leaf=5, \n",
" W_hat=None, honest=True):\n",
" self.n_estimators = n_estimators\n",
" self.max_features = max_features if max_features else 'sqrt'\n",
" self.min_samples_leaf = min_samples_leaf\n",
" self.W_hat_fixed = W_hat\n",
" self.honest = honest\n",
" \n",
" def fit(self, X, Y, W):\n",
" n = len(Y)\n",
" \n",
" # If W.hat is provided (randomized setting), use it\n",
" if self.W_hat_fixed is not None:\n",
" self.W_hat = np.full(n, self.W_hat_fixed)\n",
" else:\n",
" # Estimate propensity score\n",
" ps_model = RandomForestClassifier(\n",
" n_estimators=500,\n",
" max_features=self.max_features,\n",
" min_samples_leaf=self.min_samples_leaf,\n",
" random_state=42,\n",
" n_jobs=-1\n",
" )\n",
" ps_model.fit(X, W)\n",
" self.W_hat = ps_model.predict_proba(X)[:, 1]\n",
" self.W_hat = np.clip(self.W_hat, 0.01, 0.99)\n",
" \n",
" # Estimate outcome model\n",
" outcome_model = RandomForestRegressor(\n",
" n_estimators=500,\n",
" max_features=self.max_features,\n",
" min_samples_leaf=self.min_samples_leaf,\n",
" random_state=42,\n",
" n_jobs=-1\n",
" )\n",
" outcome_model.fit(X, Y)\n",
" self.Y_hat = outcome_model.predict(X)\n",
" \n",
" # T-learner for treatment effects\n",
" model_1 = RandomForestRegressor(\n",
" n_estimators=1000,\n",
" max_features=self.max_features,\n",
" min_samples_leaf=self.min_samples_leaf,\n",
" random_state=42,\n",
" n_jobs=-1\n",
" )\n",
" model_0 = RandomForestRegressor(\n",
" n_estimators=1000,\n",
" max_features=self.max_features,\n",
" min_samples_leaf=self.min_samples_leaf,\n",
" random_state=42,\n",
" n_jobs=-1\n",
" )\n",
" \n",
" # Fit separate models for treated and control\n",
" if np.sum(W == 1) > 0:\n",
" model_1.fit(X[W == 1], Y[W == 1])\n",
" self.mu_1 = model_1.predict(X)\n",
" else:\n",
" self.mu_1 = np.zeros(n)\n",
" \n",
" if np.sum(W == 0) > 0:\n",
" model_0.fit(X[W == 0], Y[W == 0])\n",
" self.mu_0 = model_0.predict(X)\n",
" else:\n",
" self.mu_0 = np.zeros(n)\n",
" \n",
" # Treatment effect\n",
" self.tau_hat = self.mu_1 - self.mu_0\n",
" \n",
" return self\n",
" \n",
" def predict(self):\n",
" return {'predictions': self.tau_hat}\n",
"else:\n",
" # Simplified implementation without sklearn\n",
" class CausalForest:\n",
" def __init__(self, n_estimators=100, W_hat=None, **kwargs):\n",
" self.n_estimators = min(n_estimators, 100)\n",
" self.W_hat_fixed = W_hat\n",
" \n",
" def fit(self, X, Y, W):\n",
" n = len(Y)\n",
" \n",
" if self.W_hat_fixed is not None:\n",
" self.W_hat = np.full(n, self.W_hat_fixed)\n",
" else:\n",
" self.W_hat = np.full(n, np.mean(W))\n",
" \n",
" self.Y_hat = np.full(n, np.mean(Y))\n",
" \n",
" if np.sum(W == 1) > 0:\n",
" self.mu_1 = np.full(n, np.mean(Y[W == 1]))\n",
" else:\n",
" self.mu_1 = np.full(n, np.mean(Y))\n",
" \n",
" if np.sum(W == 0) > 0:\n",
" self.mu_0 = np.full(n, np.mean(Y[W == 0]))\n",
" else:\n",
" self.mu_0 = np.full(n, np.mean(Y))\n",
" \n",
" self.tau_hat = self.mu_1 - self.mu_0\n",
" \n",
" return self\n",
" \n",
" def predict(self):\n",
" return {'predictions': self.tau_hat}\n",
"\n",
"# Estimate a causal forest\n",
"print(\"\\nFitting causal forest (randomized setting with W.hat=0.5)...\")\n",
"forest = CausalForest(n_estimators=2000 if use_sklearn else 100, W_hat=0.5)\n",
"forest.fit(X_design, Y, W)\n",
"\n",
"# Get predictions\n",
"tau_hat = forest.predict()['predictions']\n",
"\n",
"# Estimate outcome models for treated and control\n",
"mu_hat_1 = forest.Y_hat + (1 - forest.W_hat) * tau_hat # E[Y|X,W=1]\n",
"mu_hat_0 = forest.Y_hat - forest.W_hat * tau_hat # E[Y|X,W=0]\n",
"\n",
"# Compute AIPW scores\n",
"gamma_hat_1 = mu_hat_1 + W / forest.W_hat * (Y - mu_hat_1)\n",
"gamma_hat_0 = mu_hat_0 + (1 - W) / (1 - forest.W_hat) * (Y - mu_hat_0)\n",
"\n",
"print(\"Causal forest fitted successfully.\")\n",
"print()"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이번에 CausalForest를 직접 구현해주셔서 구조를 이해하는 데 큰 도움이 됐습니다! 다만 실무적으로는 econml의 CausalForestDML 같은 기존 라이브러리를 활용하면 코드가 훨씬 간결하고 유지보수에도 용이할 것 같아요.

@Funbucket
Copy link
Contributor

연구 및 코드 리뷰 요약

  • 연구 주제: Loss vs Gain 프레이밍이 개인의 지불의사(WTA)에 미치는 인과효과를 추정하고,
    연령 및 가족구성에 따른 정책적 처치 규칙(Policy π(X))의 가치를 평가한 연구.

  • 코드 구성:

    • 데이터 로드 및 변수 정의
    • 단순평균 기반 정책가치 (RCT 가정)
    • CausalForest 직접 구현 + AIPW 계산
    • 정책평가 및 비교 (π vs Random 50%)
    • 추가 통계 요약 (집단별 평균, 효과 차이)
  • 결과 해석:

    • 단순평균 기반 정책가치: 431.86 (SE 24.30)
    • CausalForest + AIPW 추정치: 429.33 (SE 22.47)
    • 정책 비교(π vs 무작위 50%): −22.31 (SE 16.64)
    • 정책 규칙(π): age ≥ 40 & family ≥ 3 → Loss 프레이밍 적용
      결과적으로 해당 집단에서는 Loss 프레이밍이 WTA를 평균 약 83적은 효과가 나타낮으며, 전체적으로 Gain 프레이밍이 더 긍정적 효과를 보임.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Policy Learning

4 participants