py-why / py-why/EconML

Understanding on Discrete Treatment (p>2) Inference with CausalForestDML

Open
#496 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Jupyter Notebook
Stars
4.8k
Forks
827
PR merge metrics
No merged PRs in 30d

Description

Hello!

I didn't see any examples where there existed a discrete treatment with multiple values (>2) and a binary outcome. I am hopeful someone can confirm my understanding.

This data set is from a marketing campaign where customers received one of three treatments (https://blog.minethatdata.com/2008/03/minethatdata-e-mail-analytics-and-data.html):

  • No Email
  • Email about Womans category products
  • Email about Male category products

The outcome I chose here is if the customer visited after the campaign, or not.

Lets say the research question was if the treatment effect depended on the customers prior purchase categories (of which Mens and Womens are binary values in the data)

Here I am setting the treatment to a numeric (1,2,3) for the three categories and using a regression wrapper function to overcome the fact that econml doesnt natively support non-numeric outcomes.

import econml
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np

from econml.dml import CausalForestDML
from sklearn.model_selection import train_test_split
import xgboost

import warnings
warnings.filterwarnings("ignore")

from sklearn.base import BaseEstimator, clone

class RegressionWrapper(BaseEstimator):

    def __init__(self, clf):
        self.clf = clf

    def fit(self, X, y, **kwargs):
        self.clf_ = clone(self.clf)
        self.clf_.fit(X, y, **kwargs)
        return self

    def predict(self, X):
        return self.clf_.predict_proba(X)[:, 1]
    
# read data and create indicator variables    
dat = pd.read_csv('http://www.minethatdata.com/Kevin_Hillstrom_MineThatData_E-MailAnalytics_DataMiningChallenge_2008.03.20.csv')

dat['phone'] = np.where(dat.channel == 'Phone',1,0)
dat['web'] = np.where(dat.channel == 'Web',1,0)
dat['multi'] = np.where(dat.channel == 'Multichannel',1,0)

dat['suburban'] = np.where(dat.zip_code == 'Suburban',1,0)
dat['rural'] = np.where(dat.zip_code == 'Rural',1,0)
dat['urban'] = np.where(dat.zip_code == 'Urban',1,0)

# treatment 
dat['test_numeric'] = 3  # womens
dat['test_numeric'] = np.where(dat.segment == 'No E-Mail',1,dat['test_numeric'].values) # control
dat['test_numeric'] = np.where(dat.segment == 'Mens E-Mail',2,dat['test_numeric'].values) # mens

# train / test split
X_train, X_test, y_train, y_test = train_test_split(dat.drop('visit',axis=1), dat[['visit']], test_size=0.50, random_state=42)

# treatment, confounders / nusiance and two variables of interest
T = X_train['test_numeric']
W = X_train[['phone','web','multi','history','recency']]
X = X_train[['mens','womens']]

# outcome
Y = y_train

#model for the treatments
xgb_model_mc = xgboost.XGBClassifier(objective="multi:softmax", num_class =3, random_state=42)
# model for the outcome
xgb_model = xgboost.XGBClassifier(objective="binary:logistic", random_state=42)


causal_forest = CausalForestDML(criterion='het', 
                                n_estimators=5000,       
                                min_samples_leaf=10, 
                                max_depth=5, 
                                max_samples=0.5,
                                discrete_treatment=True,  # discrete treatments
                                honest=True,
                                inference=True,
                                cv=10,
                                model_t=xgb_model_mc, # model to use for treatments
                                model_y=RegressionWrapper(xgb_model), # model for y
                                )
                      
# fit train data to causal forest model 
causal_forest.fit(Y = Y.values , T = T.values, X = X.values, W = W.values)

The inference for the treatment effect of Womans email versus no email is here (Mens would be simiiar)

#treatment effect (womens email - no email) when the customers purchased......
# 1) only from womens and not mens 
# 2) both womens and mens 
# 3) only mens
# 4) neither 


X = np.array([[0,1],[1,1],[1,0],[0,0]])
infer_result = causal_forest.effect_inference(X =X,T0 =1 , T1 =3 )
result_pd = infer_result.summary_frame()
result_pd.index=['Only Womens', 'Both Mens and Womens', 'Only Mens', 'Neither']
result_pd

and the result:

image

image

Is this the proper way to conduct this analysis using Casual Forest?

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the CausalForestDML entry point and the treatment and outcome configuration shown in the issue. Review the existing documentation and examples for discrete multi-valued treatments and binary outcomes, then define what guidance or example would answer whether this analysis is supported and how the effects should be interpreted.

Written by the indexing model from the issue text.

Assessment

Tech stack
jupyter-notebook, machine-learning, python
Domain
machine-learning
Issue type
Documentation
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.