<jupyter_start><jupyter_text>Individual Data Generation ExampleFor the individual data generation, the probability to have a true positive label is dependent on the previous predictions for the individual. The number of positive and negative predictions defines, whether individuals move towards the positive or negative cluster. The negative label is 0 here, therefore only positive decisions have an impact on $X$.<jupyter_code>import sys
import numpy as np
sys.path.append("../")

from sklearn.linear_model import LogisticRegression

from ltf.ltf_plot import LongTermFairnessPlot
from ltf.ltf_data.individual_data_generator import IndividualDataGenerator
from ltf.ltf_metric.aif360_metric import AifLongTermMetric
import warnings
warnings.filterwarnings("ignore")
# The helper class is necessary to use sklearn with the ltf framework.
class CLF:
    def __init__(self, clf=LogisticRegression()):
        self.c = clf
    def predict(self, X, X_s):
        return self.c.predict(X)
    def fit(self, X, X_s, y):
        self.c.fit(X, y)<jupyter_output><empty_output><jupyter_text>1. Unconstrained Example<jupyter_code># The data generator.
generator = IndividualDataGenerator(mean_pos=[0, 1],
                                    mean_neg=[12, 11],
                                    num_positive_label=50,
                                    num_negative_label=50,
                                    discrimination_factor=.9)
# The long term metric with default options (disparate impact and base rate).
metric = AifLongTermMetric()
# The clf.
clf = CLF()
# The long term plot generator.
l = LongTermFairnessPlot(generator, clf, metric.metric, x_lim=[-3, 14], y_lim=[-1, 15])
# Initialize the data and plot the first generation.
l.init_data()
l.plot_step()<jupyter_output><empty_output><jupyter_text>On initialization, both left and right figure are the same. The classifier is the same in both pipelines and only trained on the true data in the first iteration.<jupyter_code># Run 6 generations and plot every second step.
for i in range(6):
    l.run_step()
    l.run_baseline_step()
    
    if i % 2 == 0:
        l.plot_step()<jupyter_output><empty_output><jupyter_text>After 6 iterations, the proportions in the true data (left column) did not change. In the baseline data (right column), all points have a true positive label now.<jupyter_code># Plot the metrics for the six generations.
l.plot_ltf(metric._metrics)<jupyter_output><empty_output><jupyter_text>In the true data, accuracy and base rate are constant through all generations. In the baseline pipeline, the base rate improves constantly, and the accuracy drops for a short period of three generations. The accuracy decrease is for instance visible in the plot of generation 3. Some individuals reach the data generating boundary (the border between red and white area) but are labeled negative because the learned boundary (green line) does not perfectly match the true boundary. The consequence of this data generation is that although the decision function perfectly separates the points, it  fails to increase the overall number of true positive individuals and therefore does not unleash the full potential in the data. The next example shows the same setup with a constrained decision boundary.  2. Constrained Example The unconstrained example showed, that a perfect decision boundary might not unleash the full potential of each individual. This section shows that a constrained decision function might be better, but still does not unleash the full potential assuming the underlining setting of data generation where only individuals benefit from decisions.The data generation for this example is the same as before, except that the local variance is smaller now (the local variance is the deviation of the features $X_i ^{(t)}$ from the previous features $X_i ^{(t-1)}$ of individual $i$). Hence, the probability for individuals to randomly cross the decision boundary is much slower. This avoids,  that individuals move to far away from their initial position and randomly cross the decision boundary.<jupyter_code># Fair artificial decision rule. The artificial decision rule is necessary, to produce the desired result...
class FairCLF:
    def predict(self, X, X_s):
        pred = [5, -12] @ X.T + 50
        
        pred[pred>0] = 1
        pred[pred<0] = 0
        
        return np.sign(pred)
    def fit(self, X, X_s, y):
        pass
# New generator with smaller local variance.
generator = IndividualDataGenerator(mean_pos=[0, 1],
                                    mean_neg=[12, 11],
                                    cov_neg= [[10, 0],[0, 7]], 
                                    num_positive_label=50,
                                    num_negative_label=50,
                                    discrimination_factor=.9,
                                    local_variance=[[0.001, 0], [0, 0.001]])
# The clf.
clf = FairCLF()
# LTF plot.
l = LongTermFairnessPlot(generator, clf, metric.metric, x_lim=[-3, 20], y_lim=[-1, 20])
# Plot the initial data.
l.init_data()
l.plot_step()
# Run six generations and plot every second step.
for i in range(6):
    l.run_step()
    l.run_baseline_step()
    
    if i % 2 == 0:
        l.plot_step()<jupyter_output><empty_output><jupyter_text>Only the individuals who were positive labeled in the first iteration have moved towards the positive cluster in the true data. In the baseline data all points moved towards the positive cluster again.<jupyter_code># Run 20 generations.
l.run(20)
l.plot_ltf(metric._metrics)
# Plot the data points after 100 generations with lower variance.
l.plot_step()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.0705965407695023, 'fraction_numerical': 0.014295799505824214, 'mean_word_length': 3.42734375, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 26, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 10, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '370795', 'license': 'MIT', 'n_tokens_mistral': 1567, 'n_tokens_neox': 1472, 'n_words': 646, 'path': 'examples/2_individual_data_generation.ipynb', 'repo_name': 'danilobr94/dynamic-aif-framework', 'size': '5666'}