```python
import sys
sys.path.append('..')
import os
import torch
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder, MinMaxScaler, KBinsDiscretizer
from sklearn.impute import SimpleImputer
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold
import time
from sklearn.metrics import accuracy_score
from sympy import simplify_logic
from sklearn.model_selection import train_test_split

from lens.utils.base import validate_network, set_seed, tree_to_formula
from lens.utils.relu_nn import get_reduced_model, prune_features
from lens.utils.psi_nn import prune_equal_fanin
from lens import logic
import lens

set_seed(0)

#%%

data = pd.read_csv('data/mimic-ii/full_cohort_data.csv')
# data.drop('hgb_first')
fs = [
    'aline_flg',
    'gender_num',
    # 'hosp_exp_flg',
    # 'icu_exp_flg',
    # 'day_28_flg',
    # 'censor_flg',
    'sepsis_flg', 'chf_flg', 'afib_flg',
    'renal_flg', 'liver_flg', 'copd_flg', 'cad_flg', 'stroke_flg',
    'mal_flg', 'resp_flg',
]
features = fs
data1 = data[fs].values
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
data1 = imp_mean.fit_transform(data1)

f2 = fs.copy()
f2.append('day_icu_intime')
f2.append('service_unit')
f2.append('day_28_flg')
f2.append('hospital_los_day')
f2.append('icu_exp_flg')
f2.append('hosp_exp_flg')
f2.append('censor_flg')
f2.append('mort_day_censored')
f2 = data.columns.difference(f2)
data2 = data[f2].values
imp_mean = SimpleImputer(missing_values=np.nan, strategy='mean')
data2 = imp_mean.fit_transform(data2)
scaler = MinMaxScaler((0, 1))
data2 = scaler.fit_transform(data2)
features = features + list(f2)
est = KBinsDiscretizer(n_bins=3, encode='onehot-dense', strategy='uniform')
data2d = est.fit_transform(data2)
f2d = []
for feature in f2:
    #f2d.append(feature + '_VLOW')
    f2d.append(feature + '_LOW')
    f2d.append(feature + '_NORMAL')
    f2d.append(feature + '_HIGH')
    #f2d.append(feature + '_VHIGH')
features = fs + f2d

datax = np.hstack((data1, data2d))
datay = data['day_28_flg'].values

x = torch.FloatTensor(datax)
y = torch.LongTensor(datay)
print(x.shape)
print(y.shape)

```

    torch.Size([1776, 90])
    torch.Size([1776])

```python
results_dir = 'results/mimic'
if not os.path.isdir(results_dir):
    os.makedirs(results_dir)

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
# concepts = [f'c{i:03}' for i in range(x.shape[1])]
concepts =  features
n_rep = 10
tot_epochs = 5001
prune_epochs = 2001
seed = 42

n_splits = 10
skf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=seed)
```

```python
def train_nn(x_train, y_train, need_pruning, seed, device, l1=0.001, lr=0.001, relu=False, verbose=False):
    set_seed(seed)
    x_train = x_train.to(device)
    y_train = y_train.to(device)
    layers = [
        torch.nn.Linear(x_train.size(1), 100),
        torch.nn.ReLU() if relu else torch.nn.LeakyReLU(),
        torch.nn.Linear(100, 10),
        torch.nn.ReLU() if relu else torch.nn.LeakyReLU(),
        torch.nn.Linear(10, 2),
        torch.nn.Softmax(dim=1),
    ]
    model = torch.nn.Sequential(*layers).to(device)

    optimizer = torch.optim.AdamW(model.parameters(), lr=lr)
    loss_form = torch.nn.CrossEntropyLoss()
    model.train()
    for epoch in range(tot_epochs):
        # forward pass
        optimizer.zero_grad()
        y_pred = model(x_train)
        # Compute Loss
        loss = loss_form(y_pred, y_train)

        for module in model.children():
            if isinstance(module, torch.nn.Linear):
                loss += l1 * torch.norm(module.weight, 1)
                loss += l1 * torch.norm(module.bias, 1)
                break

        # backward pass
        loss.backward()
        optimizer.step()

        if epoch > prune_epochs and need_pruning and epoch % 1000 == 1:
            prune_features(model, n_classes=1, device=device)
            need_pruning = True

        # compute accuracy
        if epoch % 500 == 0 and verbose:
            y_pred_d = torch.argmax(y_pred, dim=1)
            accuracy = y_pred_d.eq(y_train).sum().item() / y_train.size(0)
            print(f'\t Epoch {epoch}: train accuracy: {accuracy:.4f}')

    return model
```

```python
def train_psi_nn(x_train, y_train, need_pruning, seed, device, verbose=False):
    set_seed(seed)
    x_train = x_train.to(device)
    y_train = y_train.to(device).to(torch.float)
    layers = [
        torch.nn.Linear(x_train.size(1), 10),
        torch.nn.Sigmoid(),
        torch.nn.Linear(10, 4),
        torch.nn.Sigmoid(),
        torch.nn.Linear(4, 1),
        torch.nn.Sigmoid(),
    ]
    model = torch.nn.Sequential(*layers).to(device)

    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    loss_form = torch.nn.BCELoss()
    model.train()
    for epoch in range(tot_epochs):
        # forward pass
        optimizer.zero_grad()
        y_pred = model(x_train).squeeze()
        # Compute Loss
        loss = loss_form(y_pred, y_train)

        for module in model.children():
            if isinstance(module, torch.nn.Linear):
                loss += 0.00001 * torch.norm(module.weight, 1)

        # backward pass
        loss.backward()
        optimizer.step()

        if epoch > prune_epochs and need_pruning:
            model = prune_equal_fanin(model, 2, validate=True, device=device)
            need_pruning = False

        # compute accuracy
        if epoch % 500 == 0 and verbose:
            y_pred_d = y_pred > 0.5
            accuracy = y_pred_d.eq(y_train).sum().item() / y_train.size(0)
            print(f'\t Epoch {epoch}: train accuracy: {accuracy:.4f}')

    return model
```

```python
def c_to_y(method, need_pruning, l1=0.001, lr=0.001, relu=False, verbose=False):
    methods = []
    splits = []
    explanations = []
    explanations_inv = []
    model_accuracies = []
    explanation_accuracies = []
    explanation_accuracies_inv = []
    elapsed_times = []
    elapsed_times_inv = []
    explanation_fidelities = []
    explanation_complexities = []
    for split, (trainval_index, test_index) in enumerate(skf.split(x.cpu().detach().numpy(), y.cpu().detach().numpy())):
        print(f'Split [{split+1}/{n_splits}]')
        x_trainval, x_test = torch.FloatTensor(x[trainval_index]), torch.FloatTensor(x[test_index])
        y_trainval, y_test = torch.LongTensor(y[trainval_index]), torch.LongTensor(y[test_index])
        x_train, x_val, y_train, y_val = train_test_split(x_trainval, y_trainval, test_size=0.3, random_state=42)

        explanation, explanation_inv = '', ''
        explanation_accuracy, explanation_accuracy_inv = 0, 0

        if method == 'tree':
            classifier = DecisionTreeClassifier(random_state=seed)
            classifier.fit(x_trainval.cpu().detach().numpy(), y_trainval.cpu().detach().numpy())
            y_preds = classifier.predict(x_test.cpu().detach().numpy())
            model_accuracy = accuracy_score(y_test.cpu().detach().numpy(), y_preds)

            target_class = 1
            start = time.time()
            explanation = tree_to_formula(classifier, concepts, target_class)
            elapsed_time = time.time() - start
            explanation_accuracy = model_accuracy
            explanation_fidelity = 1.
            explanation_complexity = lens.logic.complexity(explanation)

            target_class_inv = 0
            start = time.time()
            explanation_inv = tree_to_formula(classifier, concepts, target_class_inv)
            elapsed_time_inv = time.time() - start
            explanation_accuracy_inv = model_accuracy

        else:
            if method == 'psi':
                # positive class
                target_class = 1
                model = train_psi_nn(x_trainval, y_trainval.eq(target_class), need_pruning, seed, device, verbose)
                y_preds = model(x_test.to(device)).cpu().detach().numpy()>0.5
                model_accuracy = accuracy_score(y_test.cpu().detach().numpy(), y_preds)

            else:
                model = train_nn(x_trainval, y_trainval, need_pruning, seed, device, l1, lr, relu, verbose)
                y_preds = model(x_test.to(device)).cpu().detach().numpy().argmax(axis=1)
                model_accuracy = accuracy_score(y_test.cpu().detach().numpy(), y_preds)

            # positive class
            target_class = 1
            start = time.time()
            if method == 'psi':
                global_explanation = logic.generate_fol_explanations(model, device)[0]
            else:
                global_explanation, _, _ = logic.relu_nn.combine_local_explanations(model,
                                                                                x_val.to(device),
                                                                                y_val.to(device),
                                                                                topk_explanations=3,
                                                                                target_class=target_class,
                                                                                method=method, device=device)
            elapsed_time = time.time() - start

            if global_explanation:
#                 explanation_accuracy, _ = logic.base.test_explanation(global_explanation, target_class, x_test, y_test)
                explanation = logic.base.replace_names(global_explanation, concepts)
                explanation_accuracy, y_formula = logic.base.test_explanation(global_explanation,
                                                                              target_class,
                                                                              x=x_test, y=y_test,
                                                                              metric=accuracy_score)
                explanation_fidelity = lens.logic.fidelity(y_formula, y_preds)
                explanation_complexity = lens.logic.complexity(global_explanation)

#             # negative class
#             target_class_inv = 0
#             if method == 'psi':
#                 model = train_psi_nn(x_trainval, y_trainval.eq(target_class_inv), need_pruning, seed, device, verbose)

#             start = time.time()
#             if method == 'psi':
#                 global_explanation_inv = logic.generate_fol_explanations(model, device)[0]
#             else:
#                 global_explanation_inv, _, _ = logic.relu_nn.combine_local_explanations(model,
#                                                                                x_val.to(device),
#                                                                                y_val.to(device),
#                                                                                topk_explanations=2,
#                                                                                target_class=target_class_inv,
#                                                                                method=method, device=device)
#             elapsed_time_inv = time.time() - start
#             if global_explanation_inv:
#                 explanation_accuracy_inv, _ = logic.base.test_explanation(global_explanation_inv,
#                                                                           target_class_inv, x_test, y_test)
#                 explanation_inv = logic.base.replace_names(global_explanation_inv, concepts)

        if verbose:
            print(f'\t Model\'s accuracy: {model_accuracy:.4f}')
            print(f'\t Class {target_class} - Global explanation: "{explanation}" - Accuracy: {explanation_accuracy:.4f}')
            print(f'\t Elapsed time {elapsed_time}')
            print(f'\t Fidelity: "{explanation_fidelity:.4f}" - Complexity: "{explanation_complexity}"')
#             print(f'\t Class {target_class_inv} - Global explanation: "{explanation_inv}" - Accuracy: {explanation_accuracy_inv:.4f}')
#             print(f'\t Elapsed time {elapsed_time_inv}')

        methods.append(method)
        splits.append(split)
        explanations.append(explanation)
#         explanations_inv.append(explanation_inv)
        model_accuracies.append(model_accuracy)
        explanation_accuracies.append(explanation_accuracy)
#         explanation_accuracies_inv.append(explanation_accuracy_inv)
        elapsed_times.append(elapsed_time)
#         elapsed_times_inv.append(elapsed_time_inv)
        explanation_fidelities.append(explanation_fidelity)
        explanation_complexities.append(explanation_complexity)

    explanation_consistency = lens.logic.formula_consistency(explanations)
    print(f'Consistency of explanations: {explanation_consistency:.4f}')

    results = pd.DataFrame({
        'method': methods,
        'split': splits,
        'explanation': explanations,
#         'explanation_inv': explanations_inv,
        'model_accuracy': model_accuracies,
        'explanation_accuracy': explanation_accuracies,
        'explanation_fidelity': explanation_fidelities,
        'explanation_complexity': explanation_complexities,
        'explanation_consistency': explanation_consistency,
#         'explanation_accuracy_inv': explanation_accuracies_inv,
        'elapsed_time': elapsed_times,
#         'elapsed_time_inv': elapsed_times_inv,
    })
    results.to_csv(os.path.join(results_dir, f'results_{method}.csv'))

    return results
```

# General pruning

```python
method = 'pruning'
need_pruning = True
relu = False
results_pruning = c_to_y(method, need_pruning, relu=relu)
results_pruning
```

    Split [1/10]
    Split [2/10]
    Split [3/10]
    Split [4/10]
    Split [5/10]
    Split [6/10]
    Split [7/10]
    Split [8/10]
    Split [9/10]
    Split [10/10]
    Consistency of explanations: 0.3222

      method
      split
      explanation
      model_accuracy
      explanation_accuracy
      explanation_fidelity
      explanation_complexity
      explanation_consistency
      elapsed_time

      0
      pruning
      0
      stroke_flg
      0.842697
      0.842697
      1.000000
      1
      0.322222
      0.043916

      1
      pruning
      1
      (stroke_flg & bun_first_LOW & ~sapsi_first_LOW...
      0.837079
      0.837079
      0.988764
      7
      0.322222
      0.077024

      2
      pruning
      2
      stroke_flg
      0.853933
      0.853933
      1.000000
      1
      0.322222
      0.040891

      3
      pruning
      3
      stroke_flg & age_HIGH
      0.865169
      0.865169
      1.000000
      2
      0.322222
      0.072806

      4
      pruning
      4
      (stroke_flg & age_HIGH & ~liver_flg) | (age_HI...
      0.865169
      0.865169
      1.000000
      6
      0.322222
      0.090760

      5
      pruning
      5
      (stroke_flg & age_HIGH & bun_first_LOW) | (age...
      0.859551
      0.865169
      0.994382
      7
      0.322222
      0.053856

      6
      pruning
      6
      (stroke_flg & age_HIGH) | (age_HIGH & ~bun_fir...
      0.847458
      0.847458
      1.000000
      4
      0.322222
      0.058986

      7
      pruning
      7
      (stroke_flg & age_HIGH) | (age_HIGH & ~bun_fir...
      0.864407
      0.864407
      1.000000
      4
      0.322222
      0.049867

      8
      pruning
      8
      (stroke_flg & abg_count_LOW & bun_first_LOW & ...
      0.830508
      0.824859
      0.994350
      9
      0.322222
      0.115722

      9
      pruning
      9
      stroke_flg & ~sapsi_first_LOW
      0.875706
      0.875706
      1.000000
      2
      0.322222
      0.051389

# ReLUs

```python
method = 'weights'
need_pruning = False
relu = True
results_weights = c_to_y(method, need_pruning, 0.008, lr=0.0005, relu=relu, verbose=True)
results_weights
```

    Split [1/10]
    	 Epoch 0: train accuracy: 0.1589
    	 Epoch 500: train accuracy: 0.8411
    	 Epoch 1000: train accuracy: 0.8411
    	 Epoch 1500: train accuracy: 0.8411
    	 Epoch 2000: train accuracy: 0.8411
    	 Epoch 2500: train accuracy: 0.8411
    	 Epoch 3000: train accuracy: 0.8411
    	 Epoch 3500: train accuracy: 0.8411
    	 Epoch 4000: train accuracy: 0.8780
    	 Epoch 4500: train accuracy: 0.8842
    	 Epoch 5000: train accuracy: 0.8936
    	 Model's accuracy: 0.8596
    	 Class 1 - Global explanation: "(tco2_first_NORMAL & ~renal_flg) | (age_HIGH & ~stroke_flg & ~weight_first_NORMAL)" - Accuracy: 0.3483
    	 Elapsed time 0.25098729133605957
    	 Fidelity: "0.2753" - Complexity: "5"
    Split [2/10]
    	 Epoch 0: train accuracy: 0.1589
    	 Epoch 500: train accuracy: 0.8411
    	 Epoch 1000: train accuracy: 0.8411
    	 Epoch 1500: train accuracy: 0.8411
    	 Epoch 2000: train accuracy: 0.8411
    	 Epoch 2500: train accuracy: 0.8411
    	 Epoch 3000: train accuracy: 0.8411
    	 Epoch 3500: train accuracy: 0.8411
    	 Epoch 4000: train accuracy: 0.8411
    	 Epoch 4500: train accuracy: 0.8411
    	 Epoch 5000: train accuracy: 0.8411
    	 Model's accuracy: 0.8371
    	 Class 1 - Global explanation: "" - Accuracy: 0.0000
    	 Elapsed time 0.0019922256469726562
    	 Fidelity: "0.2753" - Complexity: "5"
    Split [3/10]
    	 Epoch 0: train accuracy: 0.1589
    	 Epoch 500: train accuracy: 0.8411
    	 Epoch 1000: train accuracy: 0.8411
    	 Epoch 1500: train accuracy: 0.8411
    	 Epoch 2000: train accuracy: 0.8411
    	 Epoch 2500: train accuracy: 0.8411
    	 Epoch 3000: train accuracy: 0.8411
    	 Epoch 3500: train accuracy: 0.8411
    	 Epoch 4000: train accuracy: 0.8411
    	 Epoch 4500: train accuracy: 0.8855
    	 Epoch 5000: train accuracy: 0.9011
    	 Model's accuracy: 0.8596
    	 Class 1 - Global explanation: "age_HIGH | (bun_first_LOW & ~mal_flg & ~age_LOW & ~sapsi_first_LOW & ~weight_first_NORMAL)" - Accuracy: 0.6573
    	 Elapsed time 0.27993297576904297
    	 Fidelity: "0.6742" - Complexity: "6"
    Split [4/10]
    	 Epoch 0: train accuracy: 0.1596
    	 Epoch 500: train accuracy: 0.8404
    	 Epoch 1000: train accuracy: 0.8404
    	 Epoch 1500: train accuracy: 0.8404
    	 Epoch 2000: train accuracy: 0.8404
    	 Epoch 2500: train accuracy: 0.8404
    	 Epoch 3000: train accuracy: 0.8404
    	 Epoch 3500: train accuracy: 0.8404
    	 Epoch 4000: train accuracy: 0.8404
    	 Epoch 4500: train accuracy: 0.8404
    	 Epoch 5000: train accuracy: 0.8404
    	 Model's accuracy: 0.8427
    	 Class 1 - Global explanation: "" - Accuracy: 0.0000
    	 Elapsed time 0.001994609832763672
    	 Fidelity: "0.6742" - Complexity: "6"
    Split [5/10]
    	 Epoch 0: train accuracy: 0.1596
    	 Epoch 500: train accuracy: 0.8404
    	 Epoch 1000: train accuracy: 0.8404
    	 Epoch 1500: train accuracy: 0.8404
    	 Epoch 2000: train accuracy: 0.8404
    	 Epoch 2500: train accuracy: 0.8404
    	 Epoch 3000: train accuracy: 0.8404
    	 Epoch 3500: train accuracy: 0.8404
    	 Epoch 4000: train accuracy: 0.8404
    	 Epoch 4500: train accuracy: 0.8404
    	 Epoch 5000: train accuracy: 0.8404
    	 Model's accuracy: 0.8427
    	 Class 1 - Global explanation: "" - Accuracy: 0.0000
    	 Elapsed time 0.0015027523040771484
    	 Fidelity: "0.6742" - Complexity: "6"
    Split [6/10]
    	 Epoch 0: train accuracy: 0.1596
    	 Epoch 500: train accuracy: 0.8404
    	 Epoch 1000: train accuracy: 0.8404
    	 Epoch 1500: train accuracy: 0.8404
    	 Epoch 2000: train accuracy: 0.8404
    	 Epoch 2500: train accuracy: 0.8404
    	 Epoch 3000: train accuracy: 0.8404
    	 Epoch 3500: train accuracy: 0.8404
    	 Epoch 4000: train accuracy: 0.8867
    	 Epoch 4500: train accuracy: 0.8949
    	 Epoch 5000: train accuracy: 0.8999
    	 Model's accuracy: 0.8596
    	 Class 1 - Global explanation: "~stroke_flg | ~sofa_first_LOW | (bun_first_LOW & ~sapsi_first_LOW & ~weight_first_NORMAL)" - Accuracy: 0.1685
    	 Elapsed time 0.09474658966064453
    	 Fidelity: "0.1404" - Complexity: "5"
    Split [7/10]
    	 Epoch 0: train accuracy: 0.1595
    	 Epoch 500: train accuracy: 0.8405
    	 Epoch 1000: train accuracy: 0.8405
    	 Epoch 1500: train accuracy: 0.8405
    	 Epoch 2000: train accuracy: 0.8405
    	 Epoch 2500: train accuracy: 0.8405
    	 Epoch 3000: train accuracy: 0.8405
    	 Epoch 3500: train accuracy: 0.8405
    	 Epoch 4000: train accuracy: 0.8405
    	 Epoch 4500: train accuracy: 0.8405
    	 Epoch 5000: train accuracy: 0.8405
    	 Model's accuracy: 0.8418
    	 Class 1 - Global explanation: "" - Accuracy: 0.0000
    	 Elapsed time 0.0019948482513427734
    	 Fidelity: "0.1404" - Complexity: "5"
    Split [8/10]
    	 Epoch 0: train accuracy: 0.1595
    	 Epoch 500: train accuracy: 0.8405
    	 Epoch 1000: train accuracy: 0.8405
    	 Epoch 1500: train accuracy: 0.8405
    	 Epoch 2000: train accuracy: 0.8405
    	 Epoch 2500: train accuracy: 0.8405
    	 Epoch 3000: train accuracy: 0.8405
    	 Epoch 3500: train accuracy: 0.8405
    	 Epoch 4000: train accuracy: 0.8405
    	 Epoch 4500: train accuracy: 0.8487
    	 Epoch 5000: train accuracy: 0.8799
    	 Model's accuracy: 0.8814
    	 Class 1 - Global explanation: "age_HIGH | (stroke_flg & hgb_first_HIGH & sofa_first_NORMAL & ~age_NORMAL & ~hgb_first_NORMAL & ~hr_1st_LOW & ~icu_los_day_NORMAL)" - Accuracy: 0.7740
    	 Elapsed time 0.7427308559417725
    	 Fidelity: "0.7797" - Complexity: "8"
    Split [9/10]
    	 Epoch 0: train accuracy: 0.1595
    	 Epoch 500: train accuracy: 0.8405
    	 Epoch 1000: train accuracy: 0.8405
    	 Epoch 1500: train accuracy: 0.8405
    	 Epoch 2000: train accuracy: 0.8405
    	 Epoch 2500: train accuracy: 0.8405
    	 Epoch 3000: train accuracy: 0.8405
    	 Epoch 3500: train accuracy: 0.8405
    	 Epoch 4000: train accuracy: 0.8893
    	 Epoch 4500: train accuracy: 0.8949
    	 Epoch 5000: train accuracy: 0.8974
    	 Model's accuracy: 0.8418
    	 Class 1 - Global explanation: "(bun_first_LOW & sofa_first_NORMAL) | (hour_icu_intime_LOW & sofa_first_NORMAL) | (age_HIGH & ~stroke_flg)" - Accuracy: 0.4633
    	 Elapsed time 0.14229702949523926
    	 Fidelity: "0.4407" - Complexity: "6"
    Split [10/10]
    	 Epoch 0: train accuracy: 0.1595
    	 Epoch 500: train accuracy: 0.8405
    	 Epoch 1000: train accuracy: 0.8405
    	 Epoch 1500: train accuracy: 0.8405
    	 Epoch 2000: train accuracy: 0.8405
    	 Epoch 2500: train accuracy: 0.8405
    	 Epoch 3000: train accuracy: 0.8405
    	 Epoch 3500: train accuracy: 0.8737
    	 Epoch 4000: train accuracy: 0.8843
    	 Epoch 4500: train accuracy: 0.8887
    	 Epoch 5000: train accuracy: 0.8918
    	 Model's accuracy: 0.8757
    	 Class 1 - Global explanation: "bun_first_LOW | ~stroke_flg" - Accuracy: 0.1582
    	 Elapsed time 0.22486662864685059
    	 Fidelity: "0.1130" - Complexity: "2"
    Consistency of explanations: 0.1944

      method
      split
      explanation
      model_accuracy
      explanation_accuracy
      explanation_fidelity
      explanation_complexity
      explanation_consistency
      elapsed_time

      0
      weights
      0
      (tco2_first_NORMAL & ~renal_flg) | (age_HIGH &...
      0.859551
      0.348315
      0.275281
      5
      0.194444
      0.250987

      1
      weights
      1

      0.837079
      0.000000
      0.275281
      5
      0.194444
      0.001992

      2
      weights
      2
      age_HIGH | (bun_first_LOW & ~mal_flg & ~age_LO...
      0.859551
      0.657303
      0.674157
      6
      0.194444
      0.279933

      3
      weights
      3

      0.842697
      0.000000
      0.674157
      6
      0.194444
      0.001995

      4
      weights
      4

      0.842697
      0.000000
      0.674157
      6
      0.194444
      0.001503

      5
      weights
      5
      ~stroke_flg | ~sofa_first_LOW | (bun_first_LOW...
      0.859551
      0.168539
      0.140449
      5
      0.194444
      0.094747

      6
      weights
      6

      0.841808
      0.000000
      0.140449
      5
      0.194444
      0.001995

      7
      weights
      7
      age_HIGH | (stroke_flg & hgb_first_HIGH & sofa...
      0.881356
      0.774011
      0.779661
      8
      0.194444
      0.742731

      8
      weights
      8
      (bun_first_LOW & sofa_first_NORMAL) | (hour_ic...
      0.841808
      0.463277
      0.440678
      6
      0.194444
      0.142297

      9
      weights
      9
      bun_first_LOW | ~stroke_flg
      0.875706
      0.158192
      0.112994
      2
      0.194444
      0.224867

# Psi network

```python
method = 'psi'
need_pruning = True
relu = False
results_psi = c_to_y(method, need_pruning, relu, verbose=False)
results_psi
```

    Split [1/10]
    Split [2/10]
    Split [3/10]
    Split [4/10]
    Split [5/10]
    Split [6/10]
    Split [7/10]
    Split [8/10]
    Split [9/10]
    Split [10/10]
    Consistency of explanations: 0.3714

      method
      split
      explanation
      model_accuracy
      explanation_accuracy
      explanation_fidelity
      explanation_complexity
      explanation_consistency
      elapsed_time

      0
      psi
      0
      (stroke_flg | bun_first_NORMAL | sapsi_first_H...
      0.837079
      0.831461
      0.848315
      3
      0.371429
      0.030917

      1
      psi
      1
      (stroke_flg | abg_count_NORMAL)
      0.837079
      0.814607
      0.853933
      2
      0.371429
      0.026901

      2
      psi
      2
      (stroke_flg | abg_count_HIGH | sapsi_first_HIGH)
      0.837079
      0.842697
      0.837079
      3
      0.371429
      0.030916

      3
      psi
      3
      (stroke_flg | sapsi_first_HIGH)
      0.842697
      0.842697
      0.876404
      2
      0.371429
      0.023905

      4
      psi
      4
      (stroke_flg | bun_first_NORMAL)
      0.842697
      0.831461
      0.842697
      2
      0.371429
      0.025898

      5
      psi
      5
      (stroke_flg | bun_first_HIGH | chloride_first_...
      0.842697
      0.814607
      0.825843
      3
      0.371429
      0.031914

      6
      psi
      6
      (stroke_flg | bun_first_HIGH | chloride_first_...
      0.836158
      0.790960
      0.819209
      3
      0.371429
      0.029914

      7
      psi
      7
      (stroke_flg | bun_first_NORMAL | bun_first_HIGH)
      0.841808
      0.847458
      0.870056
      3
      0.371429
      0.048869

      8
      psi
      8
      (stroke_flg | bun_first_NORMAL)
      0.841808
      0.807910
      0.819209
      2
      0.371429
      0.027926

      9
      psi
      9
      (stroke_flg | chloride_first_HIGH | sapsi_firs...
      0.841808
      0.858757
      0.870056
      3
      0.371429
      0.040890

# Decision tree

```python
method = 'tree'
need_pruning = False
relu = False
results_tree = c_to_y(method, need_pruning, relu)
results_tree
```

    Split [1/10]
    Split [2/10]
    Split [3/10]
    Split [4/10]
    Split [5/10]
    Split [6/10]
    Split [7/10]
    Split [8/10]
    Split [9/10]
    Split [10/10]
    Consistency of explanations: 0.8150

      method
      split
      explanation
      model_accuracy
      explanation_accuracy
      explanation_fidelity
      explanation_complexity
      explanation_consistency
      elapsed_time

      0
      tree
      0
      (stroke_flg <= 0.50 & age_HIGH <= 0.50 & resp_...
      0.803371
      0.803371
      1.0
      1363
      0.815033
      0.002984

      1
      tree
      1
      (stroke_flg <= 0.50 & age_HIGH <= 0.50 & sapsi...
      0.803371
      0.803371
      1.0
      1424
      0.815033
      0.002992

      2
      tree
      2
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & age_N...
      0.808989
      0.808989
      1.0
      1345
      0.815033
      0.002992

      3
      tree
      3
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & age_N...
      0.780899
      0.780899
      1.0
      1391
      0.815033
      0.002992

      4
      tree
      4
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & sapsi...
      0.786517
      0.786517
      1.0
      1387
      0.815033
      0.003986

      5
      tree
      5
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & resp_...
      0.848315
      0.848315
      1.0
      1319
      0.815033
      0.003986

      6
      tree
      6
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & resp_...
      0.762712
      0.762712
      1.0
      1271
      0.815033
      0.002988

      7
      tree
      7
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & resp_...
      0.813559
      0.813559
      1.0
      1321
      0.815033
      0.004001

      8
      tree
      8
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & resp_...
      0.774011
      0.774011
      1.0
      1353
      0.815033
      0.003003

      9
      tree
      9
      (age_HIGH <= 0.50 & stroke_flg <= 0.50 & sapsi...
      0.774011
      0.774011
      1.0
      1252
      0.815033
      0.004003

# Summary

```python
cols = ['model_accuracy', 'explanation_accuracy', 'explanation_fidelity',
        'explanation_complexity', 'elapsed_time', 'explanation_consistency']
mean_cols = [f'{c}_mean' for c in cols]
sem_cols = [f'{c}_sem' for c in cols]

# pruning
df_mean = results_pruning[cols].mean()
df_sem = results_pruning[cols].sem()
df_mean.columns = mean_cols
df_sem.columns = sem_cols
summary_pruning = pd.concat([df_mean, df_sem])
summary_pruning.name = 'pruning'

# # lime
# df_mean = results_lime[cols].mean()
# df_sem = results_lime[cols].sem()
# df_mean.columns = mean_cols
# df_sem.columns = sem_cols
# summary_lime = pd.concat([df_mean, df_sem])
# summary_lime.name = 'lime'

# weights
df_mean = results_weights[cols].mean()
df_sem = results_weights[cols].sem()
df_mean.columns = mean_cols
df_sem.columns = sem_cols
summary_weights = pd.concat([df_mean, df_sem])
summary_weights.name = 'weights'

# psi
df_mean = results_psi[cols].mean()
df_sem = results_psi[cols].sem()
df_mean.columns = mean_cols
df_sem.columns = sem_cols
summary_psi = pd.concat([df_mean, df_sem])
summary_psi.name = 'psi'

# tree
df_mean = results_tree[cols].mean()
df_sem = results_tree[cols].sem()
df_mean.columns = mean_cols
df_sem.columns = sem_cols
summary_tree = pd.concat([df_mean, df_sem])
summary_tree.name = 'tree'

summary = pd.concat([summary_pruning,
#                      summary_lime,
                     summary_weights,
                     summary_psi,
                     summary_tree], axis=1).T
summary.columns = mean_cols + sem_cols
summary
```

      model_accuracy_mean
      explanation_accuracy_mean
      explanation_fidelity_mean
      explanation_complexity_mean
      elapsed_time_mean
      explanation_consistency_mean
      model_accuracy_sem
      explanation_accuracy_sem
      explanation_fidelity_sem
      explanation_complexity_sem
      elapsed_time_sem
      explanation_consistency_sem

      pruning
      0.854167
      0.854164
      0.997750
      4.3
      0.065522
      0.322222
      0.004556
      0.005011
      0.001243
      0.895048
      0.007483
      1.850372e-17

      weights
      0.854180
      0.256964
      0.418727
      5.4
      0.174305
      0.194444
      0.004888
      0.091907
      0.082731
      0.476095
      0.072187
      0.000000e+00

      psi
      0.840091
      0.828261
      0.846280
      2.6
      0.031805
      0.371429
      0.000894
      0.006595
      0.006734
      0.163299
      0.002393
      0.000000e+00

      tree
      0.795575
      0.795575
      1.000000
      1342.6
      0.003393
      0.815033
      0.007968
      0.007968
      0.000000
      16.940550
      0.000164
      3.700743e-17

```python
summary.to_csv(os.path.join(results_dir, 'summary.csv'))
```


```python
import numpy as np
from matplotlib import colors
from matplotlib.ticker import PercentFormatter
import matplotlib.pyplot as plt
import math
import os
import csv
import pandas as pd
from sympy import *
# os.chdir("C:/Users/User/Google Drive/FUBerlin/Masterarbeit/IntermediateFiles/DecisionTask")
print("Current Working directory " , os.getcwd())
```

    Current Working directory  C:\Users\User\GitHub\WebET_Analysis\Finding_K

# Export

```python
def View(df):
    css = """<style>
    table { border-collapse: collapse; border: 3px solid #eee; }
    table tr th:first-child { background-color: #eeeeee; color: #333; font-weight: bold }
    table thead th { background-color: #eee; color: #000; }
    tr, th, td { border: 1px solid #ccc; border-width: 1px 0 0 1px; border-collapse: collapse;
    padding: 3px; font-family: monospace; font-size: 10px }</style>
    """
    s  = ''
    return(HTML(s+css))
```

# 1 Analyzing Amasino's (Dianna's) data

## 1.1 Data prepare_datasets

### 1.1.1 Import

```python
data_amasino = pd.read_csv("amasinoEtAl_behavior.csv", header=None) \
    .rename(columns={0:"ID", 1: "aSS", 2: "aLL", 3: "tSS", 4: "tLL", 5: "choseLL", 6: "rt", 7: "LLonLeft", 8: "condition"})
print(data_amasino.head(10))
```

       ID  aSS  aLL  tSS  tLL  choseLL        rt  LLonLeft  condition
    0   1  4.0   10    0  365      1.0  4.804574         0          2
    1   1  3.0   10    0  365      1.0  1.341040         0          2
    2   1  7.5   10    0   30      1.0  1.831145         1          2
    3   1  3.5   10    0    7      1.0  1.576825         1          2
    4   1  7.0   10    0   15      1.0  1.698946         1          2
    5   1  0.5   10    0  180      1.0  1.861560         1          2
    6   1  7.0   10    0    1      1.0  2.772035         0          2
    7   1  9.0   10    0  365      0.0  2.590511         0          2
    8   1  1.0   10    0   15      1.0  2.705389         1          2
    9   1  3.0   10    0   15      1.0  2.111287         0          2

### 1.1.2 Adding variables (choseSS and reward for each trial)

```python
data_amasino["choseSS"] = 1-data_amasino["choseLL"]
data_amasino["reward"]=data_amasino["aLL"]*data_amasino["choseLL"]+data_amasino["aSS"]*(1-data_amasino["choseLL"])
data_amasino["aSS_aLL_ratio"] = data_amasino["aSS"] / data_amasino["aLL"]
data_amasino["aLL_tLL_ratio"] = data_amasino["aLL"] / data_amasino["tLL"]
print(data_amasino.head())
```

       ID  aSS  aLL  tSS  tLL  choseLL        rt  LLonLeft  condition  choseSS  \
    0   1  4.0   10    0  365      1.0  4.804574         0          2      0.0
    1   1  3.0   10    0  365      1.0  1.341040         0          2      0.0
    2   1  7.5   10    0   30      1.0  1.831145         1          2      0.0
    3   1  3.5   10    0    7      1.0  1.576825         1          2      0.0
    4   1  7.0   10    0   15      1.0  1.698946         1          2      0.0

       reward  aSS_aLL_ratio  aLL_tLL_ratio
    0    10.0           0.40       0.027397
    1    10.0           0.30       0.027397
    2    10.0           0.75       0.333333
    3    10.0           0.35       1.428571
    4    10.0           0.70       0.666667

## 1.2 Defining add_k

```python
def add_k(aSS, aLL, tLL):
    add_k = (aLL / aSS - 1) / tLL
    return(add_k)
print("add_k = ")
sympify('(aLL/aSS - 1) / tLL')
```

    add_k =

$\displaystyle \frac{\frac{aLL}{aSS} - 1}{tLL}$

add_k is an individual parameter for the strength of the delay discount. The higher add_k, the higher the incentive for aSS. Every question has a certain add_k, assuming that the subjective value of aSS is is equal to aLL

```python
data_amasino["add_k"] =  (data_amasino["aLL"]/data_amasino["aSS"] - 1) / data_amasino["tLL"]
print(data_amasino.head())
```

       ID  aSS  aLL  tSS  tLL  choseLL        rt  LLonLeft  condition  choseSS  \
    0   1  4.0   10    0  365      1.0  4.804574         0          2      0.0
    1   1  3.0   10    0  365      1.0  1.341040         0          2      0.0
    2   1  7.5   10    0   30      1.0  1.831145         1          2      0.0
    3   1  3.5   10    0    7      1.0  1.576825         1          2      0.0
    4   1  7.0   10    0   15      1.0  1.698946         1          2      0.0

       reward  aSS_aLL_ratio  aLL_tLL_ratio         add_k
    0    10.0           0.40       0.027397  0.004110
    1    10.0           0.30       0.027397  0.006393
    2    10.0           0.75       0.333333  0.011111
    3    10.0           0.35       1.428571  0.265306
    4    10.0           0.70       0.666667  0.028571

## Choice probability for each item

```python
df = data_amasino.groupby(['aSS', 'aLL', 'tLL'])['choseLL'].mean() \
    .reset_index()
df['add_k'] = add_k(data_amasino['aSS'], data_amasino['aLL'], data_amasino['tLL'])
df = df.sort_values(by='choseLL')
View(df)
```

<style>
    table { border-collapse: collapse; border: 3px solid #eee; }
    table tr th:first-child { background-color: #eeeeee; color: #333; font-weight: bold }
    table thead th { background-color: #eee; color: #000; }
    tr, th, td { border: 1px solid #ccc; border-width: 1px 0 0 1px; border-collapse: collapse;
    padding: 3px; font-family: monospace; font-size: 10px }</style>

Since, aSS < aLL, we want to know at which discount parameters add_k aSS is never preferred over aLL

```python
conditions_amasino = data_amasino.groupby(["aSS", "aLL", "aSS_aLL_ratio", "tLL", "add_k"])["choseSS"].sum() \
    .add_suffix('').reset_index()
conditions_amasino.columns = ['aSS', 'aLL', 'aSS_aLL_ratio', 'tLL', 'add_k', 'N_choseSS']
conditions_n = data_amasino.groupby(["aSS", "aLL", "aSS_aLL_ratio", "tLL", "add_k"])["choseSS"].count() \
    .add_suffix('').reset_index()
conditions_n.columns = ['aSS', 'aLL', 'aSS_aLL_ratio', 'tLL', 'add_k', 'N']
conditions_amasino = conditions_amasino.merge(conditions_n, on=["aSS", "aLL", "aSS_aLL_ratio", "tLL", "add_k"])
conditions_amasino["choseSS"] = conditions_amasino["N_choseSS"] / conditions_amasino["N"]
conditions_amasino[conditions_amasino.columns] = conditions_amasino[conditions_amasino.columns].apply(pd.to_numeric)
print(conditions_amasino)
```

          aSS  aLL  aSS_aLL_ratio  tLL          add_k  N_choseSS    N   choseSS
    0     0.5   10           0.05    1  19.000000        0.0  117  0.000000
    1     0.5   10           0.05    7   2.714286        2.0  115  0.017391
    2     0.5   10           0.05   15   1.266667        1.0  116  0.008621
    3     0.5   10           0.05   30   0.633333        4.0  113  0.035398
    4     0.5   10           0.05   90   0.211111       11.0  116  0.094828
    ..    ...  ...            ...  ...        ...        ...  ...       ...
    136   9.5   10           0.95   30   0.001754       75.0  116  0.646552
    137   9.5   10           0.95   90   0.000585       85.0  114  0.745614
    138   9.5   10           0.95  180   0.000292       98.0  114  0.859649
    139   9.5   10           0.95  365   0.000144      100.0  115  0.869565
    140  10.0   10           1.00    1   0.000000      100.0  113  0.884956

    [141 rows x 8 columns]

## 1.3 add_k relates do decision difficulty

Check if add_k and P(chose SS) can be modeled

```python
x = conditions_amasino.add_k
y = conditions_amasino.choseSS

round_num = 8
print('Length: ' + str(len(conditions_amasino)) + "\n" +
     'add_k Min: ' + str(round(conditions_amasino.add_k.min(), round_num)) +
      '     add_k Max: ' +
      str(round(conditions_amasino.add_k.max(), round_num)))
fig, ax = plt.subplots()
ax.scatter(x, y)
ax.set_xlabel('add_k')
ax.set_ylabel('chose SS')
plt.show()
```

Find the add_k's where the decision is difficult

```python
conditions_amasino_select = conditions_amasino[
    (conditions_amasino["choseSS"] < 0.9) &
    (conditions_amasino["choseSS"] > 0.1)]
x = conditions_amasino_select.add_k
y = conditions_amasino_select.choseSS

round_num = 16
print('Number of datapoints: ' + str(len(conditions_amasino_select)) + "\n"
    'add_k Min: ' + str(round(conditions_amasino_select.add_k.min(), round_num)) +
      '     add_k Max: ' + str(round(conditions_amasino_select.add_k.max(), round_num)) +
      '\n')
fig, ax = plt.subplots()
ax.scatter(x, y)
plt.show()
```

```python
conditions_amasino_select = conditions_amasino[
    conditions_amasino["add_k"] < 0.33334]
x = conditions_amasino_select.add_k
y = conditions_amasino_select.choseSS

round_num = 16
print('Number of datapoints: ' + str(len(conditions_amasino_select)) + "\n"
    'choseSS Min: ' + str(round(conditions_amasino_select['choseSS'].min(), round_num)) +
      'choseSS Max: ' + str(round(conditions_amasino_select['choseSS'].max(), round_num)) +
      '\n')
fig, ax = plt.subplots()
ax.scatter(x, y)
plt.show()
```

```python
(1/0.5-1)/180
```

    0.005555555555555556

## 2 WebET conditions (my study)

### 2.1 What add_k-range to choose?

```python
sympify('(aLL/aSS - 1) / tLL')
```

$\displaystyle \frac{\frac{aLL}{aSS} - 1}{tLL}$

**add_k Min**

add_k is small if one of the two conditions are met:
- aSS is large
- tLL is large

Question 1: With the minimum difference between aSS and aLL (largest aSS), how small should the delay be at minimum for a difficult decision?

**add_k Max**

add_k is large if one of the two conditions are met:
- aSS is small
- tLL is small

Question 2: With the maximum difference between aSS and aLL (largest aSS), how large should the delay be at minimum for a difficult decision?

```python
print('Amasino Conditions: \n' +
      'add_k Min: ' + str(round(conditions_amasino_select.add_k.min(), round_num)) +
      '     add_k Max: ' + str(round(conditions_amasino_select.add_k.max(), round_num)) +
      '\n')

data_kRange = pd.DataFrame([["Large aSS/aLL-ratio, min  delay", 1, 1.5, 1],
                            ["Large aSS/aLL-ratio, min delay", 4.5, 5, 1],
                            ["Small aSS, max delay", 1, 5, 180],
                            ["Large aSS, max delay", 4.5, 5, 180],
                            ["Small aSS, max allowed  delay", 1, 5, 180],
                            ["Large aSS, min required delay", 4.5, 5, 7]]
                           , columns = ["Description", "aSS", "aLL", "Delay"])
data_kRange["add_k"] = add_k(data_kRange["aSS"], data_kRange["aLL"], data_kRange["Delay"])
print(data_kRange.sort_values(by=["add_k"]))
```

    Amasino Conditions:
    add_k Min: 0.0     add_k Max: 0.3333333333333334

                           Description  aSS  aLL  Delay         add_k
    3             Large aSS, max delay  4.5  5.0    180  0.000617
    5    Large aSS, min required delay  4.5  5.0      7  0.015873
    2             Small aSS, max delay  1.0  5.0    180  0.022222
    4    Small aSS, max allowed  delay  1.0  5.0    180  0.022222
    1   Large aSS/aLL-ratio, min delay  4.5  5.0      1  0.111111
    0  Large aSS/aLL-ratio, min  delay  1.0  1.5      1  0.500000

### 2.2 Generate Conditions

I will set a maximum add_k-value of 0.33

```python
def randomDataset():
    aSS = np.arange(0.5, 5.5, 0.5)
    aLL = np.arange(4, 5.5, 0.5)
    tLL = [1, 7, 15, 30, 90, 180]
    mydf = [['aSS', 'aLL', 'tLL']]
    for i_aSS in range(0, len(aSS)):
        for i_aLL in range(0, len(aLL)):
            for i_tLL in range(0, len(tLL)):
                if (aSS[i_aSS]<aLL[i_aLL]):
                    mydf.append([aSS[i_aSS], aLL[i_aLL], tLL[i_tLL]])
    mydf = pd.DataFrame(mydf)
    mydf.columns = mydf.iloc[0]
    mydf = mydf.drop([0])
    return mydf

conditions_schneegans = randomDataset()
conditions_schneegans["aSS_aLL_ratio"] = conditions_schneegans["aSS"] / conditions_schneegans["aLL"]

conditions_schneegans["add_k"] = add_k(conditions_schneegans["aSS"],
                         conditions_schneegans["aLL"],
                         conditions_schneegans["tLL"])
conditions_schneegans[conditions_schneegans.columns] = conditions_schneegans[conditions_schneegans.columns].apply(pd.to_numeric)
conditions_schneegans_select = conditions_schneegans.loc[
    (conditions_schneegans["add_k"] <= 0.33)
].reset_index()
print("Compare my dataset to Dianna Amasino's \n")
print(round(pd.DataFrame(
                         [
                             [len(conditions_schneegans), len(conditions_schneegans_select), len(conditions_amasino_select)],
                             [conditions_schneegans.add_k.min(), conditions_schneegans_select.add_k.min(), conditions_amasino_select.add_k.min()],
                             [conditions_schneegans.add_k.max(), conditions_schneegans_select.add_k.max(), conditions_amasino_select.add_k.max()],
                         ],
                         columns = ["Schneegans, all Combinations", "Schneegans, selection", "Amasino, selection"],
                         index = [
                             "Length",
                             "add_k Min",
                             "add_k Max"
                         ]), 8
           )
     )
print("\n")

print("Sample dataset of my conditions")
# View(conditions_schneegans_select)
conditions_schneegans_select.to_csv("conditions.csv")
```

    Compare my dataset to Dianna Amasino's

            Schneegans, all Combinations  Schneegans, selection  \
    Length                    144.000000             115.000000
    add_k Min                       0.000617               0.000617
    add_k Max                       9.000000               0.300000

            Amasino, selection
    Length          114.000000
    add_k Min             0.000000
    add_k Max             0.333333

    Sample dataset of my conditions

# 3 Compare Trial Characteristics

## 3.1 add_k-parameter (aLL/aSS - 1) / tLL)

The add_k-parameters of the trial show a similar frequency distribution with more trials with a lower add_k (either small delay tLL or similar rewards).

```python
x1 = conditions_schneegans_select.add_k
x2 = conditions_amasino_select.add_k
n_bins = 15

fig, axs = plt.subplots(1, 2, sharey=True, tight_layout=True)
# We can set the number of bins with the `bins` kwarg
axs[0].hist(x1, bins=n_bins)
axs[0].set_title('Schneegans')
axs[0].set_xlabel('add_k')
axs[0].set_ylabel('number of combinations')
axs[1].hist(x2, bins=n_bins)
axs[1].set_title('Amasino')
axs[1].set_xlabel('add_k')
fig.suptitle('Histogram of parameter add_k = (aLL/aSS - 1) / tLL \n\n' +
            'aLL = larger later amount, aSS = shorter sooner amount, ' +
            'tLL = delay of larger later amount', fontsize=14, y=0)
plt.show()
```

## 3.2 Delays (tLL)

### 3.2.1 Frequency of shorter-sooner-options (i.e. number of conditions)

My task has a few more trials (in relative numbers) with 7 days. Apart from that, the distributions are similar.

```python
frequency_tLL_schneegans = pd.crosstab(index=conditions_schneegans_select["tLL"],
                              columns="count")
frequency_tLL_amasino = pd.crosstab(index=conditions_amasino_select["tLL"],
                              columns="count")
frequency_tLL_comare = frequency_tLL_schneegans.merge(frequency_tLL_amasino, on="tLL").reset_index()
frequency_tLL_comare.columns = ["tLL", "freq_schneegans", "freq_amasino"]
frequency_tLL_comare["rel_schneegans"] = frequency_tLL_comare["freq_schneegans"] / np.nansum(frequency_tLL_comare["freq_schneegans"])
frequency_tLL_comare["rel_amasino"] = frequency_tLL_comare["freq_amasino"] / np.nansum(frequency_tLL_comare["freq_amasino"])
```

```python
labels = frequency_tLL_comare.tLL.unique().astype(str)
x1 = frequency_tLL_comare.rel_schneegans
x2 = frequency_tLL_comare.rel_amasino
label_loc = np.arange(len(labels))  # the label locationswidth = 0.35  # the width of the bars
width = 0.35

fig, ax = plt.subplots()
rects1 = ax.bar(label_loc - width/2, x1, width, label='Schneegans')
rects2 = ax.bar(label_loc + width/2, x2, width, label='Tim')

# Add some text for labels, title and custom x-axis tick labels, etc.
ax.set_ylabel('Relative frequency')
ax.set_xticks(label_loc)
ax.set_xticklabels(labels)
ax.legend()

def autolabel(rects):
    """Attach a text label above each bar in *rects*, displaying its height."""
    for rect in rects:
        height = rect.get_height()
        ax.annotate('{}'.format(round(height, 2)),
                    xy=(rect.get_x() + rect.get_width() / 2, height),
                    xytext=(0, 3),  # 3 points vertical offset
                    textcoords="offset points",
                    ha='center', va='bottom')

autolabel(rects1)
autolabel(rects2)
fig.tight_layout()
plt.title("relative frequency of the combinations across delays")
print(frequency_tLL_comare)
plt.show()
```

### 3.2.2 Shorter-sooner-amount distribution for each delay option

Both study designs show similar patterns. Within the shorter delays, all amounts are present. At the short delays (1 & 7 days), the rewards move towards larger shorter-sooner-amounts.

```python
tLL_values = conditions_schneegans_select["tLL"].unique()
output = []
for i in range(0, len(tLL_values)):
    x1 = str(conditions_schneegans_select[(conditions_schneegans_select["tLL"]==tLL_values[i])]["aSS"].unique())
    x2 = str(conditions_amasino_select[(conditions_amasino_select["tLL"]==tLL_values[i])]["aSS"].unique())
    output.append([tLL_values[i], x1, x2])
output = pd.DataFrame(output)
output.columns = ["tLL", "Schneegans", "Amasino"]
# View(output)
```

```python
fig, axs = plt.subplots(6, 2, sharex="col", tight_layout=True)
tLL_values = conditions_schneegans_select["tLL"].unique()
for i in range(0, len(tLL_values)):
    x1 = conditions_schneegans_select.loc[(conditions_schneegans_select["tLL"]==tLL_values[i]), ["aSS"]]
    x2 = conditions_amasino_select.loc[(conditions_amasino_select["tLL"]==tLL_values[i]), ["aSS"]]
    axs[i, 0].hist(x1)
    axs[i, 1].hist(x2)
    axs[i, 0].set_ylabel(tLL_values[i])

axs[0, 0].set_title('Schneegans')
axs[0, 1].set_title('Amasino')
axs[5, 0].set_xlabel('aSS')
axs[5, 1].set_xlabel('aSS')
fig.suptitle('Distribution of aSS across different levels of tLL', fontsize=14, y=0)
plt.show()
```

### 3.2.3  Shorter-sooner-amount vs. add_k for each delay option

Amasino has a wider range of rewards, thats why there is a smoother add_k-function. Within a delay of 30-180 days, we allow Amasino to have a smaller amounts (< 2.5), which increases add_k. That's because my aSS/aLLL ratio is naturally limited to 0.50 vs. 5 (Amasino has 0.50 vs. 10).

```python
fig, axs = plt.subplots(6, 2, squeeze=False, sharey='row', sharex="col")
tLL_values = conditions_schneegans_select["tLL"].unique()
for i in range(0, len(tLL_values)):
    x0 = conditions_schneegans_select.loc[(conditions_schneegans_select["tLL"]==tLL_values[i]), ["aSS"]]
    y0 = conditions_schneegans_select.loc[(conditions_schneegans_select["tLL"]==tLL_values[i]), ["add_k"]]
    x1 = conditions_amasino_select.loc[(conditions_amasino_select["tLL"]==tLL_values[i]), ["aSS"]]
    y1 = conditions_amasino_select.loc[(conditions_amasino_select["tLL"]==tLL_values[i]), ["add_k"]]
    axs[i, 0].scatter(x0, y0)
    axs[i, 1].scatter(x1, y1)
    axs[i, 0].set_ylabel(tLL_values[i])

axs[0, 0].set_title(str("Schneegans"))
axs[0, 1].set_title(str("Amasino"))

axs[5, 0].set_xlabel('aSS')
axs[5, 1].set_xlabel('aSS')
fig.suptitle('Distribution of aSS across different levels of tLL', fontsize=14, y=0)
plt.show()
```

```python
fig, axs = plt.subplots(6, 2, squeeze=False, sharey='row', sharex=True)
tLL_values = conditions_schneegans_select["tLL"].unique()
for i in range(0, len(tLL_values)):
    x0 = conditions_schneegans_select.loc[(conditions_schneegans_select["tLL"]==tLL_values[i]), ["aSS_aLL_ratio"]]
    y0 = conditions_schneegans_select.loc[(conditions_schneegans_select["tLL"]==tLL_values[i]), ["add_k"]]
    x1 = conditions_amasino_select.loc[(conditions_amasino_select["tLL"]==tLL_values[i]), ["aSS_aLL_ratio"]]
    y1 = conditions_amasino_select.loc[(conditions_amasino_select["tLL"]==tLL_values[i]), ["add_k"]]
    axs[i, 0].scatter(x0, y0)
    axs[i, 1].scatter(x1, y1)
    axs[i, 0].set_ylabel(tLL_values[i])

axs[0, 0].set_title(str("Schneegans"))
axs[0, 1].set_title(str("Amasino"))

axs[5, 0].set_xlabel('aSS_aLL_ratio')
axs[5, 1].set_xlabel('aSS_aLL_ratio')
fig.suptitle('Distribution of aSS/aLL-ratio across different levels of tLL', fontsize=14, y=0)
plt.show()
```

## 3.3 Smaller sooner amounts (aSS)

At both studies, all kinds of smaller-sooner-amounts are present in a fair number, expect 5.5 for Amasino.

### 3.3.1 Frequency of conditions

```python
x1 = conditions_schneegans_select.aSS
x2 = conditions_amasino_select.aSS

fig, axs = plt.subplots(1, 2, sharey=True, tight_layout=True)
# We can set the number of bins with the `bins` kwarg
axs[0].hist(x1, bins=len(x1.unique()), density=True, stacked=True)
axs[0].set_title('Schneegans')
axs[0].set_ylabel('number of combinations')
axs[1].hist(x2, bins=len(x2.unique()), density=True, stacked=True)
axs[1].set_title('Amasino')
fig.suptitle('Histogram of aSS (shorter sooner amount)',
             fontsize=14, y=0)
plt.show()
```

```python
x1 = conditions_schneegans_select.aSS

plt.hist(x1, bins=len(x1.unique()))
plt.ylabel('number of combinations')
plt.title('Histogram of aSS (shorter sooner amount)')
plt.show()
```

```python
x1 = conditions_amasino_select.aSS

plt.hist(x1, bins=len(x1.unique()))
plt.ylabel('number of combinations')
plt.title('Histogram of aSS (shorter sooner amount)')
plt.show()
```

### 3.3.2  add_k-values

Similar distribution between the studies. larger varying add_k for smaller shorter-sooner-amounts (aSS).

```python
x1 = conditions_schneegans_select.aSS
y1 = conditions_schneegans_select.add_k
x2 = conditions_amasino_select[(conditions_amasino_select["aSS"]<5)].aSS
y2 = conditions_amasino_select[(conditions_amasino_select["aSS"]<5)].add_k
n_bins = 8

fig, axs = plt.subplots(1, 2, sharey=True, tight_layout=True)
# We can set the number of bins with the `bins` kwarg
axs[0].scatter(x1, y1)
axs[0].set_title('Schneegans')
axs[0].set_xlabel('aSS')
axs[0].set_ylabel('add_k')
axs[1].scatter(x2, y2)
axs[1].set_title('Amasino')
axs[1].set_xlabel('aSS')
fig.suptitle('Scatter plot: aSS (shorter-sooner-amount) vs. add_k',
             fontsize=14, y=0)
plt.show()
```

## 3.4 Larger-later-reward-delay-ratio (aLL/tLL)

The ratio between the larger-later-amount (aLL) and its delay (tLL) is similar distributed between the studies. There are more combinations for small ratios (long delays, e.g. 30, 90, 180) and a few large ratios (1 day delay).

```python
conditions_schneegans_select["aLL_tLL-ratio"] = conditions_schneegans_select.aLL / conditions_schneegans_select.tLL
conditions_amasino_select["aLL_tLL-ratio"] = conditions_amasino_select.aLL / conditions_amasino_select.tLL

x1 = conditions_schneegans_select["aLL_tLL-ratio"]
x2 = conditions_amasino_select["aLL_tLL-ratio"]
n_bins = len(conditions_schneegans_select["aLL_tLL-ratio"].unique())

fig, axs = plt.subplots(1, 2, sharey=True, tight_layout=True)
# We can set the number of bins with the `bins` kwarg
axs[0].hist(x1, bins=n_bins, density=True, stacked=True)
axs[0].set_title('Schneegans')
axs[0].set_xlabel('aLL / tLL')
axs[0].set_ylabel('number of combinations')
axs[1].hist(x2, bins=n_bins, density=True, stacked=True)
axs[1].set_title('Amasino')
axs[1].set_xlabel('aLL / tLL')
fig.suptitle('Histogram of aLL/aLL-ratio \n\n' +
             'aLL = larger later amount, aSS = shorter sooner amount',
             fontsize=14, y=0)
plt.show()
```

## 3.5 Reward-ratio (aSS/aLL)

The ratio between the rewards is similar between the two studies

```python
x1 = conditions_schneegans_select.aSS / conditions_schneegans_select.aLL
x2 = conditions_amasino_select.aSS / 10

fig, axs = plt.subplots(1, 2, sharey=True, tight_layout=True)
# We can set the number of bins with the `bins` kwarg
axs[0].hist(x1, bins=10)
axs[0].set_title('Schneegans')
axs[0].set_xlabel('aSS / aLL')
axs[0].set_ylabel('number of combinations')
axs[1].hist(x2, bins=20)
axs[1].set_title('Amasino')
axs[1].set_xlabel('aSS / aLL')
fig.suptitle('Histogram of aSS/aLL-ratio \n\n' +
             'aLL = larger later amount, aSS = shorter sooner amount',
             fontsize=14, y=0)
plt.show()
```