<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 H2O Uplift Distributed Random Forest
Author
Step1:  Load data
To demonstrate how Uplift DRF works, Criteo dataset is used. 
Source
Step2:  Prepare data
Inspiration from
Step3:  Import data to H2O
Step4:  Train H2O UpliftDRF model
Step5:  Predict and plot Uplift Score
Step6:  Evaluate the model
Step7:  Area Under Uplift Curve (AUUC) calculation
To calculate AUUC for big data, the predictions are binned to histograms. Due to this feature the results should be different compared to exact computation.
To define AUUC, binned predictions are sorted from largest to smallest value. For every group the cumulative sum of observations statistic is calculated. The uplift is defined based on these statistics.
Types of AUUC
| AUUC type | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; Formula &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|
|
Step8:  Cumulative Uplift curve plot
To plot the uplift curve, the plot_upliftmethod can be used. There is specific parameter metric which can be "qini", "gain", or "lift". The most popular is the Qini uplift curve which is similar to the ROC curve. The Gain and Lift curves are also known from traditional binomial models. 
Depending on these curves, you can decide how many observations (for example customers) from the test dataset you send an offer to get optimal gain.
Step9:  Qini value and Average Excess Cumulative Uplift (AECU)
Qini value is calculated as the difference between the Qini AUUC and area under the random uplift curve (random AUUC). The random AUUC is computed as diagonal from zero to overall gain uplift. 
The Qini value can be generalized for all AUUC metric types. So AECU for Qini metric is the same as Qini value, but the AECU can be also calculated for Gain and Lift metric type. These values are stored in aecu_table.
Step10:  Normalized AUUC
To get normalized AUUC, you have to call auuc_normalized method. The normalized AUUC is calculated from uplift values which are normalized by uplift value from maximal treated number of observations. So if you have for example uplift values [10, 20, 30] the normalized uplift is [1/3, 2/3, 1]. If the maximal value is negative, the normalization factor is the absolute value from this number. The normalized AUUC can be again negative and positive and can be outside of (0, 1) interval. The normalized AUUC for auuc_metric="lift" is not defined, so the normalized AUUC = AUUC for this case. Also the plot_uplift with metric="lift" is the same for normalize=False and normalize=True.
Step11:  Scoring histrory and importance of number of trees
To speed up the calculation of AUUC, the predictions are binned into quantile histograms. To calculate precision AUUC the more bins the better. The more trees usually produce more various predictions and then the algorithm creates histograms with more bins. So the algorithm needs more iterations to get meaningful AUUC results. 
You can see in the scoring history table the number of bins as well as the result AUUC. There is also Qini value parameter, which reflects the number of bins and then is a better pointer of the model improvement. In the scoring history table below you can see the algorithm stabilized after building 6 trees. But it depends on data and model settings on how many trees are necessary.
Step12:  Comparasion Tree-based approach and Generalized Weighed Uplift (LGWUM)
LGWUM (Kane et al., 2014) is one of several methods available for Uplift Modeling, and uses an approach to Uplift Modelling better known as Class Variable Transformation. LGWUM assumes that positive uplift lies in treating treatment-group responders (TR) and control-group non-responders (CN), whilst avoiding treatment-group non-responders (TN) and control-group responders (CR). This is visually shown as
<ASSISTANT_TASK:>
Python Code:
import h2o
from h2o.estimators.uplift_random_forest import H2OUpliftRandomForestEstimator
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.style as style
import pandas as pd
h2o.init(strict_version_check=False) # max_mem_size=10
Explanation: H2O Uplift Distributed Random Forest
Author: Veronika Maurerova veronika.maurerova@h2o.ai
Modeling Uplift
Distributed Uplift Random Forest (Uplift DRF) is a classification tool for modeling uplift - the incremental impact of a treatment. This tool is very useful for example in marketing or in medicine. This machine learning approach is inspired by the A/B testing method. 
To model uplift, the analyst needs to collect data specifically - before the experiment, the objects are divided usually into two groups: 
treatment group: receive some kind of treatment (for example customer get some type of discount) 
control group: is separated from the treatment (customers in this group get no discount). 
Then the data are prepared and an analyst can gather information about the response - for example, whether customers bought a product, patients recovered from the disease, or similar. 
Uplift approaches
There are several approaches to model uplift: 
Meta-learner algorithms
Instrumental variables algorithms
Neural-networks-based algorithms
Tree-based algorithms 
Tree Based Uplift Algorithm
Tree-based algorithm means in every tree, it takes information about treatment/control group assignment and information about response directly into a decision about splitting a node. The uplift score is the criterion to make a decision similar to the Gini coefficient in the standard decision tree. 
Uplift metric to decide best split
The goal is to maximize the differences between the class distributions in the treatment and control sets, so the splitting criteria are based on distribution divergences. The distribution divergence is calculated based on the uplift_metric parameter. In H2O-3, three uplift_metric types are supported:
Kullback-Leibler divergence (uplift_metric="KL") - uses logarithms to calculate divergence, asymmetric, widely used, tends to infinity values (if treatment or control group distributions contain zero values). 
$ KL(P, Q) = \sum_{i=0}^{N} p_i \log{\frac{p_i}{q_i}}$
Squared Euclidean distance (uplift_metric="euclidean") - symmetric and stable distribution, does not tend to infinity values. 
$ E(P, Q) = \sum_{i=0}^{N} \sqrt{p_i-q_i}$
Chi-squared divergence (uplift_metric="chi_squared") - Euclidean divergence normalized by control group distribution. Asymmetric and also tends to infinity values (if control group distribution contains zero values). 
$\sqrt{X}(P, Q) = \sum_{i=0}^{N} \frac{\sqrt{p_i-q_i}}{q_i}$
where:
$P$ is treatment group distribution
$Q$ is control group distribution
In a tree node the result value for a split is sum: $metric(P, Q) + metric(1-P, 1-Q)$. 
For the split gain value, the result within the node is normalized using a Gini coefficient (Eclidean or ChiSquared) or entropy (KL) for each distribution before and after the split.
Uplift score in each leaf is calculated as:
$TP = (TY1 + 1) / (T + 2)$
$CP = (CY1 + 1) / (C + 2)$
$uplift_score = TP - CP $
where:
- $T$ how many observations in a leaf are from the treatment group (how many data rows in a leaf have treatment_column label == 1) 
- $C$ how many observations in a leaf are from the control group (how many data rows in the leaf have treatment_column label == 0)
- $TY1$ how many observations in a leaf are from the treatment group and respond to the offer (how many data rows in the leaf have treatment_column label == 1 and response_column label == 1)
- $CY1$ how many observations in a leaf are from the control group and respond to the offer (how many data rows in the leaf have treatment_column label == 0 and response_column label == 1)
Note: A higher uplift score means more observations from treatment group respond to the offer than from control group. Which means offered treatment has positive effect. The uplift score can be negative, if more observations from control group respond to the offer without treatment.
<br>
<br>
H2O Implementation (Major release 3.36)
The H2O-3 implementation of Uplift DRF is based on DRF because the principle of training is similar to DRF. It is tree based uplift algorithm. Uplift DRF generates a forest of classification uplift trees, rather than a single classification tree. Each of these trees is a weak learner built on a subset of rows and columns. More trees will reduce the variance. Classification takes the average prediction over all of their trees to make a final prediction. 
Currently, in H2O-3 only binomial trees are supported, as well as the uplift curve metric and Area Under Uplift curve (AUUC) metric, normalized AUUC, and the Qini value. We are working on adding also regression trees and more metrics, for example, Qini coefficient, and more. 
Start H2O-3
End of explanation
control_name = "control"
treatment_column = "treatment"
response_column = "visit"
feature_cols = ["f"+str(x) for x in range(0,12)]
df = pd.read_csv("http://mr-0xd4:50070/webhdfs/v1/datasets/criteo/v2.1/criteo-research-uplift-v2.1.csv?op=OPEN")
df.head()
Explanation: Load data
To demonstrate how Uplift DRF works, Criteo dataset is used. 
Source:
Diemert Eustache, Betlei Artem} and Renaudin, Christophe and Massih-Reza, Amini, "A Large Scale Benchmark for Uplift Modeling", ACM, Proceedings of the AdKDD and TargetAd Workshop, KDD, London,United Kingdom, August, 20, 2018, https://ailab.criteo.com/criteo-uplift-prediction-dataset/.
Description:
The dataset was created by The Criteo AI Lab
Consists of 13M rows, each one representing a user with 12 features, a treatment indicator and 2 binary labels (visits and conversions).
Positive labels mean the user visited/converted on the advertiser website during the test period (2 weeks).
The global treatment ratio is 84.6%.
Detailed description of the columns:
f0, f1, f2, f3, f4, f5, f6, f7, f8, f9, f10, f11: feature values (dense, float)
treatment: treatment group (1 = treated, 0 = control)
conversion: whether a conversion occured for this user (binary, label)
visit: whether a visit occured for this user (binary, label)
exposure: treatment effect, whether the user has been effectively exposed (binary)
End of explanation
print('Total number of samples: {}'.format(len(df)))
print('The dataset is largely imbalanced: ')
print(df['treatment'].value_counts(normalize = True))
print('Percentage of users that visit: {}%'.format(100*round(df['visit'].mean(),4)))
print('Percentage of users that convert: {}%'.format(100*round(df['conversion'].mean(),4)))
print('Percentage of visitors that convert: {}%'.format(100*round(df[df["visit"]==1]["conversion"].mean(),4)))
# Print proportion of a binary column
# https://www.kaggle.com/code/hughhuyton/criteo-uplift-modelling/notebook
def print_proportion(df, column):
    fig = plt.figure(figsize = (10,6))
    target_count = df[column].value_counts()
    print('Class 0:', target_count[0])
    print('Class 1:', target_count[1])
    print('Proportion:', int(round(target_count[1] / target_count[0])), ': 1')
    target_count.plot(kind='bar', title='Treatment Class Distribution', color=['#2077B4', '#FF7F0E'], fontsize = 15)
    plt.xticks(rotation=0) 
    
print_proportion(df, treatment_column)
from sklearn.model_selection import train_test_split
train_df, test_df  = train_test_split(df, test_size=0.2, random_state=42, stratify=df['treatment'])
print(train_df.shape)
print(test_df.shape)
del(df)
print_proportion(train_df, treatment_column)
# Random Undersampling (finding the majority class and undersampling it)
# https://www.kaggle.com/code/hughhuyton/criteo-uplift-modelling/notebook
def random_under(df, feature):
    
    target = df[feature].value_counts()
    
    if target.values[0]<target.values[1]:
        under = target.index.values[1]
    
    else: 
        under = target.index.values[0]
        
    df_0 = df[df[feature] != under]
    df_1 = df[df[feature] == under]
    
    df_treatment_under = df_1.sample(len(df_0))
    df_1 = pd.concat([df_treatment_under, df_0], axis=0)
    
    return df_1
train_df = random_under(train_df, treatment_column)
print_proportion(train_df, treatment_column)
# method to transfor data for LGWUM method, will be explained later
def target_class_lgwum(df, treatment, target, column_name):
    
    #CN:
    df[column_name] = 0 
    #CR:
    df.loc[(df[treatment] == 0) & (df[target] != 0), column_name] = 1 
    #TN:
    df.loc[(df[treatment] != 0) & (df[target] == 0), column_name] = 2 
    #TR:
    df.loc[(df[treatment] != 0) & (df[target] != 0), column_name] = 3 
    return df
response_column_lgwum = "lqwum_response"
train_df = target_class_lgwum(train_df, treatment_column, response_column, response_column_lgwum)
test_df = target_class_lgwum(test_df, treatment_column, response_column, response_column_lgwum)
Explanation: Prepare data
Inspiration from: https://www.kaggle.com/code/hughhuyton/criteo-uplift-modelling/notebook
To modeling uplift the treatment and control group data have to have similar distribution. In real world usually the control group is smaller than the treatment group. It is also a case of Crieteo dataset and we have to rebalanced the data to have a similar size.
End of explanation
h2o_train_df = h2o.H2OFrame(train_df)
del(train_df)
h2o_train_df[treatment_column] = h2o_train_df[treatment_column].asfactor()
h2o_train_df[response_column] = h2o_train_df[response_column].asfactor()
h2o_train_df[response_column_lgwum] = h2o_train_df[response_column_lgwum].asfactor()
h2o_test_df = h2o.H2OFrame(test_df)
h2o_test_df[treatment_column] = h2o_test_df[treatment_column].asfactor()
h2o_test_df[response_column] = h2o_test_df[response_column].asfactor()
h2o_test_df[response_column_lgwum] = h2o_test_df[response_column_lgwum].asfactor()
del(test_df)
Explanation: Import data to H2O
End of explanation
ntree = 20
max_depth = 15
metric="Euclidean"
h2o_uplift_model = H2OUpliftRandomForestEstimator(
                    ntrees=ntree,
                    max_depth=max_depth,
                    min_rows=30,
                    nbins=1000,
                    sample_rate=0.80,
                    score_each_iteration=True,
                    treatment_column=treatment_column,
                    uplift_metric=metric,
                    auuc_nbins=1000,
                    auuc_type="gain",
                    seed=42)
h2o_uplift_model.train(y=response_column, x=feature_cols, training_frame=h2o_train_df)
h2o_uplift_model
Explanation: Train H2O UpliftDRF model
End of explanation
# Plot uplift score
# source https://www.kaggle.com/code/hughhuyton/criteo-uplift-modelling/notebook
def plot_uplift_score(uplift_score):
    plt.figure(figsize = (10,6))
    plt.xlim(-.05, .1)
    plt.hist(uplift_score, bins=1000, color=['#2077B4'])
    plt.xlabel('Uplift score')
    plt.ylabel('Number of observations in validation set')
h2o_uplift_pred = h2o_uplift_model.predict(h2o_test_df)
h2o_uplift_pred
plot_uplift_score(h2o_uplift_pred['uplift_predict'].as_data_frame().uplift_predict)
Explanation: Predict and plot Uplift Score
End of explanation
perf_h2o = h2o_uplift_model.model_performance(h2o_test_df)
Explanation: Evaluate the model
End of explanation
perf_h2o.auuc_table()
Explanation: Area Under Uplift Curve (AUUC) calculation
To calculate AUUC for big data, the predictions are binned to histograms. Due to this feature the results should be different compared to exact computation.
To define AUUC, binned predictions are sorted from largest to smallest value. For every group the cumulative sum of observations statistic is calculated. The uplift is defined based on these statistics.
Types of AUUC
| AUUC type | &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; Formula &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;|
|:----------:|:-------------------------------------------:|
| Qini   | $TY1 - CY1 * \frac{T}{C}$                   |
| Lift   | $\frac{TY1}{T} - \frac{CY1}{C}$             |
| Gain   | $(\frac{TY1}{T} - \frac{CY1}{C}) * (T + C)$ |
Where:
T how many observations are in the treatment group (how many data rows in the bin have treatment_column label == 1)
C how many observations are in the control group (how many data rows in the bin have treatment_column label == 0)
TY1 how many observations are in the treatment group and respond to the offer (how many data rows in the bin have treatment_column label == 1 and response_column label == 1)
CY1 how many observations are in the control group and respond to the offer (how many data rows in the bin have treatment_column label == 0 and response_column label == 1)
The resulting AUUC value is:
Not normalized.
The result could be a positive or negative number.
Higher number means better model.
More information about normalization is in Normalized AUUC section.
For some observation groups the results should be NaN. In this case, the results from NaN groups are linearly interpolated to calculate AUUC and plot uplift curve.
End of explanation
perf_h2o.plot_uplift(metric="qini")
perf_h2o.plot_uplift(metric="gain")
perf_h2o.plot_uplift(metric="lift")
Explanation: Cumulative Uplift curve plot
To plot the uplift curve, the plot_upliftmethod can be used. There is specific parameter metric which can be "qini", "gain", or "lift". The most popular is the Qini uplift curve which is similar to the ROC curve. The Gain and Lift curves are also known from traditional binomial models. 
Depending on these curves, you can decide how many observations (for example customers) from the test dataset you send an offer to get optimal gain.
End of explanation
perf_h2o.aecu_table()
Explanation: Qini value and Average Excess Cumulative Uplift (AECU)
Qini value is calculated as the difference between the Qini AUUC and area under the random uplift curve (random AUUC). The random AUUC is computed as diagonal from zero to overall gain uplift. 
The Qini value can be generalized for all AUUC metric types. So AECU for Qini metric is the same as Qini value, but the AECU can be also calculated for Gain and Lift metric type. These values are stored in aecu_table.
End of explanation
perf_h2o.plot_uplift(metric="gain", normalize=True)
perf_h2o.auuc_normalized()
Explanation: Normalized AUUC
To get normalized AUUC, you have to call auuc_normalized method. The normalized AUUC is calculated from uplift values which are normalized by uplift value from maximal treated number of observations. So if you have for example uplift values [10, 20, 30] the normalized uplift is [1/3, 2/3, 1]. If the maximal value is negative, the normalization factor is the absolute value from this number. The normalized AUUC can be again negative and positive and can be outside of (0, 1) interval. The normalized AUUC for auuc_metric="lift" is not defined, so the normalized AUUC = AUUC for this case. Also the plot_uplift with metric="lift" is the same for normalize=False and normalize=True.
End of explanation
h2o_uplift_model.scoring_history()
Explanation: Scoring histrory and importance of number of trees
To speed up the calculation of AUUC, the predictions are binned into quantile histograms. To calculate precision AUUC the more bins the better. The more trees usually produce more various predictions and then the algorithm creates histograms with more bins. So the algorithm needs more iterations to get meaningful AUUC results. 
You can see in the scoring history table the number of bins as well as the result AUUC. There is also Qini value parameter, which reflects the number of bins and then is a better pointer of the model improvement. In the scoring history table below you can see the algorithm stabilized after building 6 trees. But it depends on data and model settings on how many trees are necessary.
End of explanation
from h2o.estimators.gbm import H2OGradientBoostingEstimator
h2o_gbm_lgwum = H2OGradientBoostingEstimator(ntrees=ntree,
                                       max_depth=max_depth,
                                       min_rows=30,
                                       nbins=1000,
                                       score_each_iteration=False,
                                       seed=42)
h2o_gbm_lgwum.train(y=response_column_lgwum, x=feature_cols, training_frame=h2o_train_df)
h2o_gbm_lgwum
uplift_predict_lgwum = h2o_gbm_lgwum.predict(h2o_test_df)
result = uplift_predict_lgwum.as_data_frame()
result.columns = ['predict', 'p_cn', 'p_cr', 'p_tn', 'p_tr']
result['uplift_score'] = result.eval('\
                                p_cn/(p_cn + p_cr) \
                                + p_tr/(p_tn + p_tr) \
                                - p_tn/(p_tn + p_tr) \
                                - p_cr/(p_cn + p_cr)')
result
plot_uplift_score(result.uplift_score)
lgwum_predict = h2o.H2OFrame(result['uplift_score'].tolist())
perf_lgwum = h2o.make_metrics(lgwum_predict, h2o_test_df[response_column], treatment=h2o_test_df[treatment_column], auuc_type="gain", auuc_nbins=81)
perf_lgwum
perf_h2o.plot_uplift(metric="qini")
perf_lgwum.plot_uplift(metric="qini")
Explanation: Comparasion Tree-based approach and Generalized Weighed Uplift (LGWUM)
LGWUM (Kane et al., 2014) is one of several methods available for Uplift Modeling, and uses an approach to Uplift Modelling better known as Class Variable Transformation. LGWUM assumes that positive uplift lies in treating treatment-group responders (TR) and control-group non-responders (CN), whilst avoiding treatment-group non-responders (TN) and control-group responders (CR). This is visually shown as:
𝑈𝑝𝑙𝑖𝑓𝑡 𝐿𝐺𝑊𝑈𝑀 = P(TR)/P(T) + P(CN)/P(C) - P(TN)/P(T) - P(CR)/P(C)
source: https://www.kaggle.com/code/hughhuyton/criteo-uplift-modelling/notebook
End of explanation
<END_TASK>