diff --git a/book/_toc.yml b/book/_toc.yml index e50fadd..4b59776 100644 --- a/book/_toc.yml +++ b/book/_toc.yml @@ -24,3 +24,6 @@ parts: - file: scm/backdoor_criterion.ipynb - file: scm/frontdoor_criterion.ipynb - file: scm/causal_discovery.ipynb + - file: prescriptive_analytics/overview.md + sections: + - file: prescriptive_analytics/heterogeneous_causal_learning_for_effectiveness_optimization.ipynb \ No newline at end of file diff --git a/book/prescriptive_analytics/heterogeneous_causal_learning_for_effectiveness_optimization.ipynb b/book/prescriptive_analytics/heterogeneous_causal_learning_for_effectiveness_optimization.ipynb new file mode 100644 index 0000000..c7f6f34 --- /dev/null +++ b/book/prescriptive_analytics/heterogeneous_causal_learning_for_effectiveness_optimization.ipynb @@ -0,0 +1,1150 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "d6b70f64", + "metadata": {}, + "source": [ + "# Heterogeneous Causal Learning for Effectiveness Optimization" + ] + }, + { + "cell_type": "markdown", + "id": "b841ee2c", + "metadata": {}, + "source": [ + "- 기존 uplift 모델은 이질적 처치 효과를 추정할 수 있지만, 비용 대비 이익을 충분히 반영하지 못합니다.\n", + "\n", + "- 마케팅처럼 예산이 제한된 환경에서는, 비용을 고려하면서 효과를 최대화하는 처치 효과 최적화(treatment effect optimization) 접근이 필요합니다.\n", + "\n", + "- 이를 위해 다음 알고리즘들을 활용합니다.\n", + " - Duality R-learner\n", + " - Direct Ranking Model (DRM)\n", + " - Constrained Ranking Models" + ] + }, + { + "cell_type": "markdown", + "id": "21447276", + "metadata": {}, + "source": [ + "## Setup" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "0f91658c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mWARNING: There was an error checking the latest version of pip.\u001b[0m\u001b[33m\n", + "\u001b[0mNote: you may need to restart the kernel to use updated packages.\n" + ] + } + ], + "source": [ + "%pip -q install fractional-uplift" + ] + }, + { + "cell_type": "code", + "execution_count": 503, + "id": "9114f7da", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "import pandas as pd\n", + "\n", + "from sklearn.linear_model import Ridge\n", + "from sklearn.metrics import r2_score, roc_auc_score\n", + "from sklearn.model_selection import train_test_split\n", + "\n", + "import fractional_uplift as fr \n", + "\n", + "import matplotlib.pyplot as plt\n", + "\n", + "RANDOM_STATE = 42\n", + "pd.set_option(\"display.max_columns\", 50)\n", + "\n", + "import warnings\n", + "warnings.filterwarnings(\"ignore\")\n" + ] + }, + { + "cell_type": "markdown", + "id": "57cbb979", + "metadata": {}, + "source": [ + "### CriteoWithSyntheticCostAndSpend Dataset\n", + "\n", + "\n", + "- CriteoWithSyntheticCostAndSpend 데이터는\n", + " - treatment: 광고 노출 여부 (0/1)\n", + " - spend: 고객이 발생시킨 매출(이익)\n", + " - cost: 해당 고객에게 treatment를 줬을 때 발생한 고객별 비용\n", + "\n", + " 을 모두 포함하므로, 비용까지 고려한 처치 최적화 실험에 적합합니다. \n", + "\n", + "- 주요 컬럼:\n", + " - `treatment`: 광고/프로모션 노출 여부 (0/1)\n", + " - `spend`: 사용자가 발생시킨 매출(이익) → Gain outcome: $(Y^r)$\n", + " - `cost`: 해당 고객에게 treatment를 줄 때 들어간 비용 → Cost outcome: $(Y^c)$\n", + " - `treatment_propensity`: 실험에서 treatment에 할당될 확률\n", + " - `sample_weight`: 샘플 가중치\n", + " - `criteo.features`: feature 컬럼 이름 리스트 (문자열 리스트)\n", + "\n", + "- Train/Val/Test split:\n", + " \n", + " `train_data`를 train / validation / test 로 분리하여 사용합니다.\n", + " " + ] + }, + { + "cell_type": "code", + "execution_count": 638, + "id": "b2b3d7a2", + "metadata": {}, + "outputs": [], + "source": [ + "criteo = fr.example_data.CriteoWithSyntheticCostAndSpend.load()\n", + "\n", + "df_all = criteo.train_data.copy()\n", + "features = criteo.features\n", + "\n", + "# 1) train vs temp(=val+test)\n", + "train_df, temp_df = train_test_split(\n", + " df_all,\n", + " test_size=0.4, # val 0.2 + test 0.2\n", + " random_state=RANDOM_STATE,\n", + " stratify=df_all[\"treatment\"],\n", + ")\n", + "\n", + "# 2) temp를 val vs test\n", + "val_df, test_df = train_test_split(\n", + " temp_df,\n", + " test_size=0.5, # temp의 절반 = 0.2\n", + " random_state=RANDOM_STATE,\n", + " stratify=temp_df[\"treatment\"],\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 652, + "id": "e286adf5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "X_train shape: (43231, 12)\n", + "X_val shape: (14411, 12)\n", + "X_test shape: (14411, 12)\n" + ] + } + ], + "source": [ + "X_train = train_df[features].values.astype(np.float32)\n", + "X_val = val_df[features].values.astype(np.float32)\n", + "X_test = test_df[features].values.astype(np.float32)\n", + "\n", + "T_train = train_df[\"treatment\"].values.astype(int)\n", + "T_val = val_df[\"treatment\"].values.astype(int)\n", + "T_test = test_df[\"treatment\"].values.astype(int)\n", + "\n", + "Yg_train = train_df[\"spend\"].values.astype(float) # gain\n", + "Yg_val = val_df[\"spend\"].values.astype(float)\n", + "Yg_test = test_df[\"spend\"].values.astype(float)\n", + "\n", + "Yc_train = train_df[\"cost\"].values.astype(float) # cost\n", + "Yc_val = val_df[\"cost\"].values.astype(float)\n", + "Yc_test = test_df[\"cost\"].values.astype(float)\n", + "\n", + "W_train = train_df[\"sample_weight\"].values.astype(float)\n", + "W_val = val_df[\"sample_weight\"].values.astype(float)\n", + "W_test = test_df[\"sample_weight\"].values.astype(float)\n", + "\n", + "print(\"X_train shape:\", X_train.shape)\n", + "print(\"X_val shape:\", X_val.shape)\n", + "print(\"X_test shape:\", X_test.shape)" + ] + }, + { + "cell_type": "markdown", + "id": "576b8d00", + "metadata": {}, + "source": [ + "## Duality R-learner\n", + "\n", + "Duality R-learner는 다음 두 단계를 결합한 방식입니다.\n", + "\n", + "1. R-learner로 Gain/Cost CATE 추정\n", + " - $\\tau_r(x)$: gain uplift\n", + " - $\\tau_c(x)$: cost uplift\n", + "\n", + "2. 예산 제약(budget constraint)을 듀얼 형태로 최적화\n", + " - 라그랑지 승수 $\\lambda$ 를 학습하여 최적 정책을 찾습니다.\n", + "\n", + "우리가 풀고 싶은 문제는 다음과 같습니다.\n", + "\n", + "- 예산 $B$ 하에서\n", + "\n", + " $$\n", + " \\max_{z_i \\in \\{0,1\\}} \\sum_i \\tau_r(x^{(i)}) z_i\n", + " \\quad \\text{s.t.} \\quad\n", + " \\sum_i \\tau_c(x^{(i)}) z_i \\le B\n", + " $$\n", + "\n", + "- $z_i = 1$ 이면 고객 $i$ 에게 프로모션/광고를 집행, $z_i = 0$ 이면 미집행\n", + "\n", + "Duality R-learner 핵심 단계:\n", + "\n", + "1. Nuisance models: $m_r(x)$, $e(x)$ 학습 \n", + "2. Gain / Cost R-learner: $\\tau_r(x)$, $\\tau_c(x)$ 추정 \n", + "3. Duality: $\\lambda$ 를 gradient ascent 로 최적화 \n", + "4. 정책 생성: $s(x) = \\tau_r(x) - \\lambda^* \\tau_c(x)$\n", + "5. Cost Curve / AUCC 로 정책 성능 평가 \n" + ] + }, + { + "cell_type": "markdown", + "id": "5d9e213b", + "metadata": {}, + "source": [ + "### 1. Nuisance Models: $m_r(x)$ 와 $e(x)$\n", + "\n", + "R-learner는 아래 식을 기반으로 합니다.\n", + "\n", + "$$\n", + "Y - m^*(X)\n", + "= (T - e^*(X))\\,\\tau^*(X) + \\epsilon\n", + "$$\n", + "\n", + "여기서 \n", + "\n", + "- $m^*(X) = \\mathbb{E}[Y \\mid X]$: outcome 평균 모델 \n", + "- $e^*(X) = \\mathbb{P}(T=1 \\mid X)$: propensity score \n", + " > propensity score는 treatment_propensity 컬럼을 그대로 사용합니다.\n", + "\n", + "- Gain outcome $Y^r = \\texttt{spend}$\n", + " - $m_r(x) = \\mathbb{E}[Y^r\\mid X=x]$: Ridge 회귀\n", + "\n", + "- Cost outcome $Y^c = \\texttt{cost}$\n", + " - $m_c(x) = \\mathbb{E}[Y^c\\mid X=x]$: Ridge 회귀" + ] + }, + { + "cell_type": "code", + "execution_count": 620, + "id": "3e718594", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== m_r(x) 성능 (R^2: spend 회귀) ==\n", + "Train R^2: 0.5937760649833947\n", + "Val R^2: 0.6185973626734869\n", + "\n", + "예측값 분포 (Val):\n", + "count 14411.000000\n", + "mean 7.721704\n", + "std 11.980173\n", + "min -5.330875\n", + "25% -0.231392\n", + "50% 1.539356\n", + "75% 12.941192\n", + "max 80.358582\n", + "dtype: float64\n" + ] + } + ], + "source": [ + "# Gain outcome 평균 모델 m_r(x): Ridge 회귀\n", + "m_r = Ridge(alpha=1.0, random_state=RANDOM_STATE)\n", + "m_r.fit(X_train, Yg_train)\n", + "\n", + "Yg_pred_train = m_r.predict(X_train)\n", + "Yg_pred_val = m_r.predict(X_val)\n", + "\n", + "r2_train_mr = r2_score(Yg_train, Yg_pred_train)\n", + "r2_val_mr = r2_score(Yg_val, Yg_pred_val)\n", + "\n", + "print(\"== m_r(x) 성능 (R^2: spend 회귀) ==\")\n", + "print(\"Train R^2:\", r2_train_mr)\n", + "print(\"Val R^2:\", r2_val_mr)\n", + "\n", + "print(\"\\n예측값 분포 (Val):\")\n", + "print(pd.Series(Yg_pred_val).describe())" + ] + }, + { + "cell_type": "code", + "execution_count": 621, + "id": "083c0aa5", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== m_c(x) 성능 (R^2: cost 회귀) ==\n", + "Train R^2: 0.2921035090257208\n", + "Val R^2: 0.27535410112615455\n", + "\n", + "예측값 분포 (Val):\n", + "count 14411.000000\n", + "mean 2.570728\n", + "std 4.126056\n", + "min -19.284588\n", + "25% -0.116940\n", + "50% 0.918125\n", + "75% 4.393160\n", + "max 24.175064\n", + "dtype: float64\n" + ] + } + ], + "source": [ + "# Cost outcome 평균 모델 m_c(x): Ridge 회귀 (cost 전용 모델)\n", + "m_c = Ridge(alpha=1.0, random_state=RANDOM_STATE)\n", + "m_c.fit(X_train, Yc_train)\n", + "\n", + "Yc_pred_train = m_c.predict(X_train)\n", + "Yc_pred_val = m_c.predict(X_val)\n", + "\n", + "r2_train_mc = r2_score(Yc_train, Yc_pred_train)\n", + "r2_val_mc = r2_score(Yc_val, Yc_pred_val)\n", + "\n", + "print(\"== m_c(x) 성능 (R^2: cost 회귀) ==\")\n", + "print(\"Train R^2:\", r2_train_mc)\n", + "print(\"Val R^2:\", r2_val_mc)\n", + "\n", + "print(\"\\n예측값 분포 (Val):\")\n", + "print(pd.Series(Yc_pred_val).describe())" + ] + }, + { + "cell_type": "code", + "execution_count": 622, + "id": "1aa8d52b", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== e(x) 성능 (AUC: treatment 모델) ==\n", + "Train AUC: 0.5\n", + "Val AUC: 0.5\n", + "\n", + "Propensity e(x) range:\n", + "Train: 0.8500001287591226 → 0.8500001287591226\n", + "Val : 0.8500001287591226 → 0.8500001287591226\n" + ] + } + ], + "source": [ + "e_train = train_df[\"treatment_propensity\"].values.astype(float)\n", + "e_val = val_df[\"treatment_propensity\"].values.astype(float)\n", + "e_test = test_df[\"treatment_propensity\"].values.astype(float)\n", + "\n", + "auc_train_e = roc_auc_score(T_train, e_train)\n", + "auc_val_e = roc_auc_score(T_val, e_val)\n", + "\n", + "print(\"== e(x) 성능 (AUC: treatment 모델) ==\")\n", + "print(\"Train AUC:\", auc_train_e)\n", + "print(\"Val AUC:\", auc_val_e)\n", + "\n", + "print(\"\\nPropensity e(x) range:\")\n", + "print(\"Train:\", e_train.min(), \"→\", e_train.max())\n", + "print(\"Val :\", e_val.min(), \"→\", e_val.max())" + ] + }, + { + "cell_type": "markdown", + "id": "06b5558e", + "metadata": {}, + "source": [ + "### 2. R-learner: Gain / Cost CATE 추정\n", + "\n", + "Gain outcome $Y^r$ 에 대해 R-learner 구조는 다음과 같습니다.\n", + "\n", + "$$\n", + "Y - m(X) = (T - e(X))\\,\\tau(X) + \\epsilon\n", + "$$\n", + "\n", + "선형 모델 $\\tau(x) = w^\\top x$ 를 쓰면:\n", + "\n", + "1. **잔차 계산**\n", + " $$\n", + " r^Y = Y - \\hat m(X), \\quad r^T = T - \\hat e(X)\n", + " $$\n", + "2. **행 단위 스케일링**\n", + " $$\n", + " Z = X \\odot r^T\n", + " $$\n", + "3. **회귀**\n", + " $$\n", + " r^Y \\approx Z w\n", + " $$\n", + "4. **최종 CATE**\n", + " $$\n", + " \\hat\\tau(x) = w^\\top x\n", + " $$\n", + "\n", + "이를 공통 함수로 구현하고,\n", + "\n", + "- Gain R-learner: $Y = Y^r$, $m = m_r$\n", + "- Cost R-learner: $Y = Y^c$, $m = m_c$\n", + "\n", + "로 각각 학습합니다." + ] + }, + { + "cell_type": "code", + "execution_count": 623, + "id": "28eb5e7c", + "metadata": {}, + "outputs": [], + "source": [ + "def fit_r_learner_linear(\n", + " X_tr, X_val,\n", + " T_tr, T_val,\n", + " Y_tr, Y_val,\n", + " m_tr, m_val,\n", + " e_tr, e_val,\n", + " alpha=1.0,\n", + " name=\"R-learner\",\n", + " rt_clip=1e-6,\n", + "):\n", + " \"\"\"\n", + " 선형 τ(x) = w^T x 를 R-learner 방식으로 학습.\n", + " rY = (T - e(X)) * τ(X) + ε 를 이용해 w를 추정한다.\n", + " \"\"\"\n", + " X_tr = np.asarray(X_tr, dtype=float)\n", + " X_val = np.asarray(X_val, dtype=float)\n", + " T_tr = np.asarray(T_tr, dtype=float)\n", + " T_val = np.asarray(T_val, dtype=float)\n", + " Y_tr = np.asarray(Y_tr, dtype=float)\n", + " Y_val = np.asarray(Y_val, dtype=float)\n", + " m_tr = np.asarray(m_tr, dtype=float)\n", + " m_val = np.asarray(m_val, dtype=float)\n", + " e_tr = np.asarray(e_tr, dtype=float)\n", + " e_val = np.asarray(e_val, dtype=float)\n", + "\n", + " # residuals\n", + " rY_tr = Y_tr - m_tr\n", + " rT_tr = T_tr - e_tr\n", + "\n", + " # rT가 너무 작은 경우 클리핑\n", + " rT_tr = np.where(np.abs(rT_tr) < rt_clip, np.sign(rT_tr) * rt_clip, rT_tr)\n", + "\n", + " # Z = X * rT\n", + " Z_tr = X_tr * rT_tr.reshape(-1, 1)\n", + "\n", + " # fit\n", + " tau_model = Ridge(alpha=alpha, fit_intercept=False, random_state=RANDOM_STATE)\n", + " tau_model.fit(Z_tr, rY_tr)\n", + "\n", + " # τ_hat(x) = w^T x\n", + " w = tau_model.coef_.reshape(-1)\n", + " tau_tr = X_tr @ w\n", + " tau_val = X_val @ w\n", + "\n", + " # val에서 rY를 얼마나 설명하는지 확인\n", + " rY_val = Y_val - m_val\n", + " rT_val = T_val - e_val\n", + " pred_rY_val = rT_val * tau_val\n", + " mse_val = np.mean((rY_val - pred_rY_val) ** 2)\n", + "\n", + " print(f\"== {name} 요약 ==\")\n", + " print(\"Train τ_hat summary:\")\n", + " print(pd.Series(tau_tr).describe())\n", + " print(\"\\nVal τ_hat summary:\")\n", + " print(pd.Series(tau_val).describe())\n", + " print(f\"\\nVal check: MSE(rY, rT*tau) = {mse_val:.6f}\")\n", + "\n", + " return tau_model, tau_tr, tau_val\n" + ] + }, + { + "cell_type": "code", + "execution_count": 624, + "id": "03fc2e68", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== Gain R-learner τ_r(x) 요약 ==\n", + "Train τ_hat summary:\n", + "count 43231.000000\n", + "mean 0.744968\n", + "std 4.095001\n", + "min -14.967210\n", + "25% -0.467654\n", + "50% 0.084019\n", + "75% 1.151731\n", + "max 75.742621\n", + "dtype: float64\n", + "\n", + "Val τ_hat summary:\n", + "count 14411.000000\n", + "mean 0.805699\n", + "std 4.339214\n", + "min -13.043869\n", + "25% -0.465580\n", + "50% 0.088950\n", + "75% 1.189868\n", + "max 70.995090\n", + "dtype: float64\n", + "\n", + "Val check: MSE(rY, rT*tau) = 91.237132\n" + ] + } + ], + "source": [ + "# Gain R-learner: τ_r(x)\n", + "m_r_train = m_r.predict(X_train)\n", + "m_r_val = m_r.predict(X_val)\n", + "\n", + "tau_r_model, tau_r_train, tau_r_val = fit_r_learner_linear(\n", + " X_tr=X_train,\n", + " X_val=X_val,\n", + " T_tr=T_train,\n", + " T_val=T_val,\n", + " Y_tr=Yg_train,\n", + " Y_val=Yg_val,\n", + " m_tr=m_r_train,\n", + " m_val=m_r_val,\n", + " e_tr=e_train,\n", + " e_val=e_val,\n", + " alpha=1.0,\n", + " name=\"Gain R-learner τ_r(x)\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 625, + "id": "554cb0c4", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== Cost R-learner τ_c(x) 요약 ==\n", + "Train τ_hat summary:\n", + "count 43231.000000\n", + "mean 2.810444\n", + "std 4.418757\n", + "min -19.332760\n", + "25% -0.070645\n", + "50% 0.982358\n", + "75% 4.749005\n", + "max 26.477165\n", + "dtype: float64\n", + "\n", + "Val τ_hat summary:\n", + "count 14411.000000\n", + "mean 2.817899\n", + "std 4.431292\n", + "min -20.013473\n", + "25% -0.087577\n", + "50% 1.023093\n", + "75% 4.772627\n", + "max 25.653274\n", + "dtype: float64\n", + "\n", + "Val check: MSE(rY, rT*tau) = 40.727902\n" + ] + } + ], + "source": [ + "# Cost R-learner: τ_c(x)\n", + "m_c_train = m_c.predict(X_train)\n", + "m_c_val = m_c.predict(X_val)\n", + "\n", + "tau_c_model, tau_c_train, tau_c_val = fit_r_learner_linear(\n", + " X_tr=X_train,\n", + " X_val=X_val,\n", + " T_tr=T_train,\n", + " T_val=T_val,\n", + " Y_tr=Yc_train,\n", + " Y_val=Yc_val,\n", + " m_tr=m_c_train,\n", + " m_val=m_c_val,\n", + " e_tr=e_train,\n", + " e_val=e_val,\n", + " alpha=1.0,\n", + " name=\"Cost R-learner τ_c(x)\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 626, + "id": "01798a5e", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "== τ_r(x) 요약 ==\n", + "[Train]\n", + "count 43231.000000\n", + "mean 0.744968\n", + "std 4.095001\n", + "min -14.967210\n", + "25% -0.467654\n", + "50% 0.084019\n", + "75% 1.151731\n", + "max 75.742621\n", + "dtype: float64\n", + "\n", + "[Val]\n", + "count 14411.000000\n", + "mean 0.805699\n", + "std 4.339214\n", + "min -13.043869\n", + "25% -0.465580\n", + "50% 0.088950\n", + "75% 1.189868\n", + "max 70.995090\n", + "dtype: float64\n", + "\n", + "[Test]\n", + "count 14411.000000\n", + "mean 0.713794\n", + "std 4.158598\n", + "min -14.772031\n", + "25% -0.479206\n", + "50% 0.054279\n", + "75% 1.114829\n", + "max 61.358405\n", + "dtype: float64\n", + "\n", + "== τ_c(x) 요약 ==\n", + "[Train]\n", + "count 43231.000000\n", + "mean 2.810444\n", + "std 4.418757\n", + "min -19.332760\n", + "25% -0.070645\n", + "50% 0.982358\n", + "75% 4.749005\n", + "max 26.477165\n", + "dtype: float64\n", + "\n", + "[Val]\n", + "count 14411.000000\n", + "mean 2.817899\n", + "std 4.431292\n", + "min -20.013473\n", + "25% -0.087577\n", + "50% 1.023093\n", + "75% 4.772627\n", + "max 25.653274\n", + "dtype: float64\n", + "\n", + "[Test]\n", + "count 14411.000000\n", + "mean 2.739378\n", + "std 4.419076\n", + "min -20.425985\n", + "25% -0.102309\n", + "50% 0.915572\n", + "75% 4.637743\n", + "max 28.992231\n", + "dtype: float64\n" + ] + } + ], + "source": [ + "# Test set CATE 예측\n", + "tau_r_test = X_test @ tau_r_model.coef_.reshape(-1)\n", + "tau_c_test = X_test @ tau_c_model.coef_.reshape(-1)\n", + "\n", + "print(\"== τ_r(x) 요약 ==\")\n", + "print(\"[Train]\")\n", + "print(pd.Series(tau_r_train).describe())\n", + "print(\"\\n[Val]\")\n", + "print(pd.Series(tau_r_val).describe())\n", + "print(\"\\n[Test]\")\n", + "print(pd.Series(tau_r_test).describe())\n", + "\n", + "print(\"\\n== τ_c(x) 요약 ==\")\n", + "print(\"[Train]\")\n", + "print(pd.Series(tau_c_train).describe())\n", + "print(\"\\n[Val]\")\n", + "print(pd.Series(tau_c_val).describe())\n", + "print(\"\\n[Test]\")\n", + "print(pd.Series(tau_c_test).describe())\n" + ] + }, + { + "cell_type": "markdown", + "id": "e6e387a2", + "metadata": {}, + "source": [ + "### 3. Duality: 예산 제약 하에서 $\\lambda$ 최적화\n", + "\n", + "목표는 다음과 같습니다.\n", + "\n", + "$$\n", + "\\begin{aligned}\n", + "\\max_{z_i \\in \\{0,1\\}}\\quad & \\sum_i \\tau_r(x^{(i)}) z_i \\\\\n", + "\\text{s.t.}\\quad & \\sum_i \\tau_c(x^{(i)}) z_i \\le B\n", + "\\end{aligned}\n", + "$$\n", + "\n", + "- $z_i = 1$: 고객 $i$ 타깃 (프로모션 발송)\n", + "- $B$: 사용할 수 있는 총 비용 예산\n", + "\n", + "이를 위해 라그랑지 승수 $\\lambda \\ge 0$ 를 도입합니다.\n", + "\n", + "$$\n", + "L(z, \\lambda)\n", + "= -\\sum_i \\tau_r(x^{(i)}) z_i\n", + " + \\lambda\\left(\\sum_i \\tau_c(x^{(i)}) z_i - B\\right)\n", + "$$\n", + "\n", + "고정된 $\\lambda$ 에 대해:\n", + "\n", + "$$\n", + "s_i(\\lambda) = \\tau_r(x^{(i)}) - \\lambda\\, \\tau_c(x^{(i)})\n", + "$$\n", + "\n", + "- $s_i(\\lambda) \\ge 0$ 이면 $z_i = 1$ (타깃)\n", + "- $s_i(\\lambda) < 0$ 이면 $z_i = 0$ (비타깃)\n", + "\n", + "듀얼 목적함수 기울기는\n", + "\n", + "$$\n", + "\\frac{\\partial g}{\\partial \\lambda}\n", + "\\approx \\underbrace{\\sum_i z_i\\,\\tau_c^+(x^{(i)})}_{\\text{cost\\_used}} - B\n", + "$$\n", + "\n", + "이며, gradient ascent 업데이트는\n", + "\n", + "$$\n", + "\\lambda \\leftarrow [\\lambda + \\eta(\\text{cost\\_used} - B)]_+\n", + "$$\n", + "\n", + "- 예산 초과($\\text{cost\\_used} > B$) → $\\lambda$ 증가 → cost가 큰 고객 penalize\n", + "- 예산 미만($\\text{cost\\_used} < B$) → $\\lambda$ 감소 → 더 많은 고객 선택 허용" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5fe3e686", + "metadata": {}, + "outputs": [], + "source": [ + "def duality_learn_lambda(\n", + " tau_r,\n", + " tau_c,\n", + " budget_fraction=0.3,\n", + " lr=1e-5,\n", + " n_iter=200,\n", + " verbose_every=20,\n", + " scale=1e4\n", + "):\n", + " \"\"\"\n", + " τ_r, τ_c 가 주어졌을 때 Duality gradient ascent로 λ 학습.\n", + " - budget_fraction: 전체 양의 cost effect 합 중 몇 %를 예산으로 둘지\n", + " \"\"\"\n", + " tau_r = np.asarray(tau_r).astype(float)\n", + " tau_c = np.asarray(tau_c).astype(float)\n", + "\n", + " # 양의 cost effect만 예산 계산에 사용\n", + " tau_c_pos = np.clip(tau_c, a_min=0.0, a_max=None)\n", + " total_pos_cost = tau_c_pos.sum()\n", + " B = budget_fraction * total_pos_cost\n", + "\n", + " lam = 0.0\n", + "\n", + " for it in range(n_iter + 1):\n", + " # effectiveness score\n", + " s = tau_r - lam * tau_c_pos\n", + "\n", + " # z_i: 선택 여부 (s_i >= 0 이면 선택)\n", + " z = (s >= 0).astype(float)\n", + "\n", + " cost_used = (tau_c_pos * z).sum()\n", + " gain_used = (np.clip(tau_r, 0.0, None) * z).sum()\n", + "\n", + " # ∂g/∂λ ≈ cost_used - B\n", + " grad = cost_used - B\n", + "\n", + " # gradient ascent (λ >= 0 유지)\n", + " lr_eff = lr * scale / (total_pos_cost + 1e-12)\n", + " lam = max(0.0, lam + lr_eff * grad)\n", + "\n", + " if it % verbose_every == 0:\n", + " sel_ratio = z.mean()\n", + " print(\n", + " f\"[iter {it:03d}] λ={lam:.6f}, \"\n", + " f\"cost_used={cost_used:.4f}, gain_used={gain_used:.4f}, \"\n", + " f\"grad={grad:.4f}, selected={sel_ratio:.3f}\"\n", + " )\n", + "\n", + " print(\"\\n최종 λ*:\", lam)\n", + " print(\"총 양의 cost effect 합:\", total_pos_cost)\n", + " print(f\"예산 B (fraction={budget_fraction}):\", B)\n", + " return lam, B" + ] + }, + { + "cell_type": "code", + "execution_count": 628, + "id": "233a6a45", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[iter 000] λ=0.036519, cost_used=88238.3493, gain_used=56003.9532, grad=48442.7285, selected=0.530\n", + "[iter 020] λ=0.324064, cost_used=46207.1315, gain_used=49466.9730, grad=6411.5107, selected=0.363\n", + "[iter 040] λ=0.366826, cost_used=40887.6177, gain_used=47644.3566, grad=1091.9968, selected=0.333\n", + "[iter 060] λ=0.374218, cost_used=39912.3018, gain_used=47282.8494, grad=116.6809, selected=0.327\n", + "[iter 080] λ=0.374977, cost_used=39803.9172, gain_used=47242.2461, grad=8.2964, selected=0.326\n", + "[iter 100] λ=0.375001, cost_used=39795.3574, gain_used=47239.0362, grad=-0.2634, selected=0.326\n", + "[iter 120] λ=0.375001, cost_used=39795.3574, gain_used=47239.0362, grad=-0.2634, selected=0.326\n", + "[iter 140] λ=0.375002, cost_used=39795.3574, gain_used=47239.0362, grad=-0.2634, selected=0.326\n", + "[iter 160] λ=0.375002, cost_used=39795.3574, gain_used=47239.0362, grad=-0.2634, selected=0.326\n", + "[iter 180] λ=0.375003, cost_used=39795.3574, gain_used=47239.0362, grad=-0.2634, selected=0.326\n", + "[iter 200] λ=0.375003, cost_used=39801.2137, gain_used=47241.2323, grad=5.5929, selected=0.326\n", + "\n", + "최종 λ*: 0.3750031632094995\n", + "총 양의 cost effect 합: 132652.0694261761\n", + "예산 B (fraction=0.3): 39795.62082785283\n" + ] + } + ], + "source": [ + "# Train 데이터에서 λ* 학습\n", + "lambda_star, B_train = duality_learn_lambda(\n", + " tau_r=tau_r_train,\n", + " tau_c=tau_c_train,\n", + " budget_fraction=0.3, # 전체 양의 cost uplift 중 30%를 예산으로\n", + " lr=1e-5,\n", + " n_iter=200,\n", + " verbose_every=20,\n", + " scale=1e4\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 642, + "id": "a54ac8cf", + "metadata": {}, + "outputs": [], + "source": [ + "def selection_summary(tau_r, tau_c, lam, name=\"\"):\n", + " tau_r = np.asarray(tau_r, float)\n", + " tau_c = np.asarray(tau_c, float)\n", + " tau_c_pos = np.clip(tau_c, 0.0, None)\n", + "\n", + " s = tau_r - lam * tau_c_pos\n", + " z = (s >= 0).astype(float)\n", + "\n", + " gain_used = (tau_r * z).sum()\n", + " cost_used = (tau_c_pos * z).sum()\n", + " sel_ratio = z.mean()\n", + " ratio = gain_used / cost_used if cost_used > 0 else np.nan\n", + "\n", + " print(f\"\\n== Selection summary ({name}) ==\")\n", + " print(f\"λ = {lam:.6f}\")\n", + " print(f\"선택 비율: {sel_ratio:.3f} ({z.sum():.0f} / {len(z)})\")\n", + " print(f\"총 gain (∑ τ_r z): {gain_used:.4f}\")\n", + " print(f\"총 cost (∑ τ_c^+ z): {cost_used:.4f}\")\n", + " print(f\"gain / cost 비율: {ratio:.4f}\")\n", + "\n", + " return {\"lambda\": lam, \"selected_ratio\": sel_ratio, \"gain_used\": gain_used, \"cost_used\": cost_used, \"gain_per_cost\": ratio}\n" + ] + }, + { + "cell_type": "code", + "execution_count": 643, + "id": "6947da6a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "== Selection summary (Train) ==\n", + "λ = 0.375003\n", + "선택 비율: 0.326 (14100 / 43231)\n", + "총 gain (∑ τ_r z): 47239.0362\n", + "총 cost (∑ τ_c^+ z): 39795.3574\n", + "gain / cost 비율: 1.1870\n", + "\n", + "== Selection summary (Val) ==\n", + "λ = 0.375003\n", + "선택 비율: 0.328 (4733 / 14411)\n", + "총 gain (∑ τ_r z): 16722.9333\n", + "총 cost (∑ τ_c^+ z): 13560.8063\n", + "gain / cost 비율: 1.2332\n", + "\n", + "== Selection summary (Test) ==\n", + "λ = 0.375003\n", + "선택 비율: 0.323 (4656 / 14411)\n", + "총 gain (∑ τ_r z): 15984.1864\n", + "총 cost (∑ τ_c^+ z): 12859.2200\n", + "gain / cost 비율: 1.2430\n" + ] + } + ], + "source": [ + "_ = selection_summary(tau_r_train, tau_c_train, lambda_star, name=\"Train\")\n", + "_ = selection_summary(tau_r_val, tau_c_val, lambda_star, name=\"Val\")\n", + "_ = selection_summary(tau_r_test, tau_c_test, lambda_star, name=\"Test\")" + ] + }, + { + "cell_type": "markdown", + "id": "6ec8b309", + "metadata": {}, + "source": [ + "### 4. Cost Curve & AUCC (Test set 평가)\n", + "\n", + "Test 셋에서 정책의 성능을 Incremental Cost 대비 Incremental Gain 곡선으로 평가합니다.\n", + "\n", + "1. **Effectiveness score로 정렬** \n", + "\n", + " Duality R-learner의 점수는\n", + " $$\n", + " s(x)=\\tau_r(x)-\\lambda^*\\tau_c(x)\n", + " $$\n", + " 로 정의하며, 이를 기준으로 샘플을 내림차순으로 정렬합니다.\n", + "\n", + "2. **상위 \\(k\\)명(prefix)에서 ATE 추정** \n", + "\n", + " 정렬된 상위 \\(k\\)개 집단에서 관측 결과 \\(Y\\)를 사용해 gain과 cost에 대한 ATE를 계산합니다:\n", + " $$\n", + " \\widehat{ATE}_g(k)=\\mathbb{E}[Y_g\\mid T=1]-\\mathbb{E}[Y_g\\mid T=0],\\quad\n", + " \\widehat{ATE}_c(k)=\\mathbb{E}[Y_c\\mid T=1]-\\mathbb{E}[Y_c\\mid T=0].\n", + " $$\n", + "\n", + "3. **총 증분 gain/cost 계산** \n", + " 상위 \\(k\\) 집단에서 실제 처치된 샘플 수 \\(n_t(k)\\)를 곱해,\n", + " $$\n", + " \\Delta G(k)=n_t(k)\\cdot \\widehat{ATE}_g(k),\\quad\n", + " \\Delta C(k)=n_t(k)\\cdot \\widehat{ATE}_c(k)\n", + " $$\n", + " 를 각 점으로 사용합니다.\n", + "\n", + "4. **정규화 및 Cost Curve 구성** \n", + " \\((0,0)\\)을 포함한 $(\\Delta C(k), \\Delta G(k))$를 정규화하여\n", + " $$\n", + " x(k)=\\frac{\\Delta C(k)}{C_{\\text{norm}}},\\quad\n", + " y(k)=\\frac{\\Delta G(k)}{G_{\\text{norm}}}\n", + " $$\n", + " 로 변환하고, 이를 이은 곡선을 Cost Curve로 정의합니다. \n", + " 정규화 기준은 전체 집단을 기본으로 사용하되, 전원 처리 시 증분 gain이 0 이하인 경우에는\n", + " **양수 구간의 최대값(max-positive)**을 사용하여 비교 가능하게 합니다.\n", + "\n", + "5. **AUCC 계산** \n", + " Cost Curve 아래 면적을 수치 적분으로 계산합니다:\n", + " $$\n", + " \\text{AUCC}=\\int_0^1 y(x)\\,dx.\n", + " $$\n" + ] + }, + { + "cell_type": "code", + "execution_count": 654, + "id": "6039a560", + "metadata": {}, + "outputs": [], + "source": [ + "def cost_curve_aucc(scores, Yg, Yc, T, W=None, n_points=80, clip_negative_gain=False):\n", + " scores = np.asarray(scores, float)\n", + " Yg = np.asarray(Yg, float)\n", + " Yc = np.asarray(Yc, float)\n", + " T = np.asarray(T, int)\n", + " if W is None:\n", + " W = np.ones_like(T, dtype=float)\n", + " else:\n", + " W = np.asarray(W, float)\n", + "\n", + " order = np.argsort(-scores)\n", + " Yg, Yc, T, W = Yg[order], Yc[order], T[order], W[order]\n", + "\n", + " N = len(T)\n", + " ks = np.linspace(1, N, n_points, dtype=int)\n", + "\n", + " def wmean(y, w):\n", + " return (y * w).sum() / (w.sum() + 1e-12)\n", + "\n", + " inc_g, inc_c = [0.0], [0.0]\n", + " for k in ks:\n", + " T_k, Yg_k, Yc_k, W_k = T[:k], Yg[:k], Yc[:k], W[:k]\n", + " mt, mc = (T_k == 1), (T_k == 0)\n", + "\n", + " if mt.sum() == 0 or mc.sum() == 0:\n", + " inc_g.append(0.0); inc_c.append(0.0); continue\n", + "\n", + " ate_g = wmean(Yg_k[mt], W_k[mt]) - wmean(Yg_k[mc], W_k[mc])\n", + " ate_c = wmean(Yc_k[mt], W_k[mt]) - wmean(Yc_k[mc], W_k[mc])\n", + "\n", + " \n", + " w_t = W_k[mt].sum()\n", + " inc_g.append(ate_g * w_t)\n", + " inc_c.append(ate_c * w_t)\n", + "\n", + " inc_g = np.asarray(inc_g, float)\n", + " if clip_negative_gain:\n", + " inc_g = np.maximum(inc_g, 0.0)\n", + "\n", + " # cost는 음수면 0으로 (안전)\n", + " inc_c = np.maximum(np.asarray(inc_c, float), 0.0)\n", + "\n", + " max_g, max_c = inc_g[-1], inc_c[-1]\n", + " if max_g == 0:\n", + " max_g = np.max(np.abs(inc_g)) if np.max(np.abs(inc_g)) > 0 else 1.0\n", + " if max_c == 0:\n", + " max_c = np.max(inc_c) if np.max(inc_c) > 0 else 1.0\n", + "\n", + " x = inc_c / max_c\n", + " y = inc_g / max_g\n", + "\n", + " x = np.maximum.accumulate(x)\n", + " aucc = np.trapz(y, x)\n", + " return x, y, aucc\n" + ] + }, + { + "cell_type": "code", + "execution_count": 655, + "id": "a60166d5", + "metadata": {}, + "outputs": [], + "source": [ + "def plot_cost_curve(x, y, aucc, title=\"Cost Curve\", label=\"Model\"):\n", + " plt.figure(figsize=(7, 6))\n", + " plt.plot(x, y, label=f\"{label} (AUCC={aucc:.3f})\")\n", + " plt.plot([0, 1], [0, 1], alpha=0.35, linewidth=1, label=\"y=x benchmark\")\n", + " plt.xlabel(\"Incremental cost (normalized)\")\n", + " plt.ylabel(\"Incremental gain (normalized)\")\n", + " plt.title(title)\n", + " plt.grid(alpha=0.3)\n", + " plt.legend()\n", + " plt.tight_layout()\n", + " plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": 656, + "id": "2b41ea6a", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Duality AUCC: 0.6109516208594291\n" + ] + }, + { + "data": { + "image/png": "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qHQCSxAbEhLSKT8dDIL0j314fF5eXup/CWblc8M0AyIi6kqc7NWMJSdWRmKJqG0sn5fOyCknIiJqDwayrWAuK1Hb8fNCRERaYSBLRERERLrEQJY6ZNasWTj//PObbk+YMAF33HGHVY5mUVGRyr9MS0uzyuvT8V122WV47rnneIiIiMgmMZC1o8BSTvHKRWp7RkRE4LTTTsPbb7+tyn9Z29KlS/HYY4813U5MTMSiRYs65bWfeOIJTJs2Tb3m0aZOnaomGG3YsOGY+44XXL/77ruqZFRz5eXlePDBB9GrVy94enqqclJTpkxR70sme1kcPHgQV199NWJjY+Hh4YGkpCRcfvnl2LhxY4ff38qVKzFkyBD1et26dVPt+zvSpmeffRY9evRQz4uJiVHHySInJwdXXHGFul/qF7d2HHbt2oWLLrpIHVf5uWmtvx566CH1umVlZR1+f0RERNbCQNaOnHHGGSqAkZHL77//HhMnTsTtt9+Oc845R5UAs6bg4GD4+fl1+utKeae33noL11577TH3ZWRkYPXq1bj11ltVwN5RUqli9OjReP/993H//fdj8+bN+PXXXzF9+nTcd999TUGcBKtDhw7F/v378dprr2H37t34/PPPVfB79913d2jfhw8fxtlnn636auvWrSrgvO666/DDDz+c8HnSr2+++aYKZvfu3YuvvvoKw4cPb7q/rq4OYWFhKhAdOHDgcY9tcnIynn76aRW4t6Zfv35ISUnBhx9+2KH3R0REZFUmB1NWVibDa+r/o9XU1Jh2796t/u8sRqPRVF9fr/63ppkzZ5qmTZt2zPbly5er9/vGG2+o24cPH1a3t2zZ0vSYkpIStW3FihXqdmNjo+maa64xJSYmmjw9PU09evQwLVq06IT7Gz9+vOn2229vui6v1/xSWVlp8vPzM3366actXufzzz83eXt7m8rLy1t9X/L4sLCwVu975JFHTJdddplpz549poCAAFN1dXWL+5u3qbl33nlHPd7ipptuMvn4+Jiys7OPeWxFRYXqv7q6OlPfvn1NQ4cONRkMhmMeJ8ewI+677z71us1Nnz7dNHXq1OM+R35GXV1dTXv37m3TPo53HJpLSEgw/fvf/271vvnz55vGjh173Oda43PTmaS/cnJyWu030gf2ob6x//TP0MW/R08Uqx2NI7J/H+ijur5Rk0vzU9odNWnS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iHTJFQ7GZDKp/8vLy7tkf0ajERUVFfD09ISzM7836BH7UN/Yf/rHPtQ39p/+Gbs4lrHEaJaY7UQcLpCVjhBxcXFaN4WIiIiIThCzBQQE4EScTG0Jd+3sW8WRI0fg5+cHJyenLvlWIUFzZmYm/P39rb4/6nzsQ31j/+kf+1Df2H/6V97FsYyEphLERkdH/+0IsMONyMoBiY2N7fL9SsczkNU39qG+sf/0j32ob+w//fPvwljm70ZiLZi0SURERES6xECWiIiIiHSJgayVeXh4YN68eep/0if2ob6x//SPfahv7D/987DhWMbhJnsRERERkX3giCwRERER6RIDWSIiIiLSJQayRERERKRLDGQ7wUsvvYTExES1dNuIESOwfv36Ez7+008/Ra9evdTj+/fvj++++64zmkFd1IdvvPEGxo0bh6CgIHWZMmXK3/Y52dZn0GLx4sVqYZTzzz+fXaSzPiwtLcUtt9yCqKgoNQGlR48e/F2qo/5btGgRevbsCS8vL1Vo/84770RtbW2XtZf+8uuvv+Lcc89Viw/I78MvvvgCf2flypUYMmSI+ux169YN7777LjQjk72o4xYvXmxyd3c3vf3226Zdu3aZrr/+elNgYKApLy+v1cf/8ccfJhcXF9OCBQtMu3fvNj300EMmNzc3044dO9gNOunDK664wvTSSy+ZtmzZYtqzZ49p1qxZpoCAAFNWVlaXt53a338Whw8fNsXExJjGjRtnmjZtGg+ljvqwrq7ONGzYMNNZZ51l+v3331Vfrly50rR169Yubzu1v/8++ugjk4eHh/pf+u6HH34wRUVFme68804eTg189913pgcffNC0dOlSmfxv+vzzz0/4+EOHDpm8vb1Nd911l4pjXnjhBRXXLFu2zKQFBrInafjw4aZbbrml6bbBYDBFR0ebnnrqqVYff+mll5rOPvvsFttGjBhhuvHGG0+2KdRFfXi0xsZGk5+fn+m9995jH+ik/6TPRo8ebXrzzTdNM2fOZCCrsz585ZVXTMnJyab6+voubCV1Vv/JYydNmtRimwRFY8aM4UHWGNoQyN53332mvn37ttg2ffp009SpU01aYGrBSaivr8emTZvUqeXmS+DK7TVr1rT6HNne/PFi6tSpx3082V4fHq26uhoNDQ0IDg62YkupM/vv0UcfRXh4OK699loeWB324VdffYVRo0ap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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "tau_c_test_pos = np.clip(tau_c_test, 0.0, None)\n", + "scores_duality = tau_r_test - lambda_star * tau_c_test_pos\n", + "x, y, aucc = cost_curve_aucc(scores_duality, Yg_test, Yc_test, T_test, W=W_test, n_points=80)\n", + "\n", + "\n", + "print(\"Duality AUCC:\", aucc)\n", + "plot_cost_curve(x, y, aucc, title=\"Cost Curve on Test set\", label=\"Duality\")" + ] + }, + { + "cell_type": "code", + "execution_count": 651, + "id": "1d1e4af1", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "train cost_used: 39795.357426534225 selected: 0.32615484258980826\n", + "val cost_used: 13560.806268071696 selected: 0.3284296717785025\n", + "test cost_used: 12859.22000170533 selected: 0.32308653112205954\n", + "B_train: 39795.62082785283\n" + ] + } + ], + "source": [ + "def cost_used_under_policy(tau_r, tau_c, lam):\n", + " tc = np.clip(tau_c, 0.0, None)\n", + " s = tau_r - lam * tc\n", + " z = (s >= 0).astype(float)\n", + " return (tc * z).sum(), z.mean()\n", + "\n", + "for name, tr, tc in [(\"train\", tau_r_train, tau_c_train),\n", + " (\"val\", tau_r_val, tau_c_val),\n", + " (\"test\", tau_r_test, tau_c_test)]:\n", + " c, sel = cost_used_under_policy(tr, tc, lambda_star)\n", + " print(name, \"cost_used:\", c, \"selected:\", sel)\n", + "print(\"B_train:\", B_train)\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "220d5e1f", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".venv", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/book/prescriptive_analytics/overview.md b/book/prescriptive_analytics/overview.md new file mode 100644 index 0000000..ad8685a --- /dev/null +++ b/book/prescriptive_analytics/overview.md @@ -0,0 +1,5 @@ +# Prescriptive Analytics + +- Prescriptive Analytics는 데이터를 활용해 최적의 의사결정을 도출하는 분석 방식입니다. +- 접근 방식은 크게 Prediction + Optimization, Causal Inference + Optimization 으로 나눌 수 있습니다. +- 이 섹션에서는 Causal Inference + Optimization 에 집중하여, 개입의 인과효과(CATE)를 기반으로 가장 효율적인 정책·전략을 선택하는 방법을 다룹니다. \ No newline at end of file