class ET:
    codes = [
        '''
!pip download kagglehub
!pip download pandas
!pip download numpy
!pip download matplotlib
!pip download seaborn
!pip download scipy

import kagglehub
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
import os

path = kagglehub.dataset_download("julianbloise/winners-formula-1-1950-to-2025")
print("Path to dataset files:", path)

csv_files = [f for f in os.listdir(path) if f.endswith('.csv')]
if not csv_files:
    raise FileNotFoundError("No CSV file found in dataset path.")
dataset_file = os.path.join(path, csv_files[0])
print("Using dataset file:", dataset_file)

df = pd.read_csv(dataset_file)

print("Dataset Shape:", df.shape)
print("\nDataset Info:")
print(df.info())

numeric_cols = df.select_dtypes(include=[np.number]).columns
print("\nNumeric Columns:", numeric_cols.tolist())

desc = pd.DataFrame(index=numeric_cols)
desc['Mean'] = df[numeric_cols].mean()
desc['Median'] = df[numeric_cols].median()
desc['Mode'] = df[numeric_cols].mode().iloc[0]

desc['Std Dev'] = df[numeric_cols].std()
desc['Skewness'] = df[numeric_cols].skew()
desc['Kurtosis'] = df[numeric_cols].kurt()

print("\n--- Statistical Summary ---")
print(desc)

print("\n--- Normality Tests (Shapiro-Wilk) ---")
for col in numeric_cols:
    try:
        stat, p = stats.shapiro(df[col].dropna())
        print(f"{col}: W={stat:.3f}, p={p:.3f} --> {'Normal' if p>0.05 else 'Not Normal'}")
    except Exception as e:
        print(f"{col}: Could not run Shapiro-Wilk ({e})")

for col in numeric_cols:
    plt.figure(figsize=(10,5))
    plt.subplot(1,2,1)
    sns.histplot(df[col].dropna(), kde=True, bins=30)
    plt.title(f"Histogram of {col}")

    plt.subplot(1,2,2)
    sns.boxplot(x=df[col].dropna())
    plt.title(f"Boxplot of {col}")

    plt.tight_layout()
    plt.show()

categorical_cols = df.select_dtypes(include=['object', 'category']).columns
for col in categorical_cols:
    plt.figure(figsize=(6,4))
    df[col].value_counts().head(20).plot(kind='bar')  # Top 20 to avoid clutter
    plt.title(f"Class Distribution: {col}")
    plt.ylabel("Count")
    plt.show()

if len(numeric_cols) > 1:
    plt.figure(figsize=(10,6))
    corr_matrix = df[numeric_cols].corr()
    sns.heatmap(corr_matrix, annot=True, cmap="coolwarm", fmt=".2f", linewidths=0.5)
    plt.title("Correlation Heatmap (Numeric Features)", fontsize=14)
    plt.show()
else:
    print("Not enough numeric columns for correlation heatmap.")
        ''',
        '''
import kagglehub
import pandas as pd
import numpy as np
from scipy import stats
import os

path = kagglehub.dataset_download("julianbloise/winners-formula-1-1950-to-2025")
print("Path to dataset files:", path)

csv_files = [f for f in os.listdir(path) if f.endswith('.csv')]
if not csv_files:
    raise FileNotFoundError("No CSV file found in dataset path.")
dataset_file = os.path.join(path, csv_files[0])
print("Using dataset file:", dataset_file)

df = pd.read_csv(dataset_file)

numeric_cols = df.select_dtypes(include=[np.number]).columns
categorical_cols = df.select_dtypes(include=['object', 'category']).columns

print("\nNumeric Columns:", numeric_cols.tolist())
print("Categorical Columns:", categorical_cols.tolist())

desc = pd.DataFrame(index=numeric_cols)
desc['Mean'] = df[numeric_cols].mean()
desc['Median'] = df[numeric_cols].median()
desc['Mode'] = df[numeric_cols].mode().iloc[0]

print("\n--- Mean, Median, Mode (Numeric Features) ---")
print(desc)

print("\n--- Balance Check (Categorical Features) ---")
for col in categorical_cols:
    counts = df[col].value_counts(normalize=True) * 100
    print(f"\n{col} Distribution (%):")
    print(counts.head(10))  # Top 10 values to avoid clutter

    # Simple balance check: if the top category >50%, dataset is imbalanced

    if counts.iloc[0] > 50:
        print(f"{col} is IMBALANCED (Top category = {counts.index[0]}: {counts.iloc[0]:.2f}%)")
    else:
        print(f"{col} is relatively BALANCED")
        ''',
        '''
!pip download kagglehub
!pip download pandas
!pip download numpy
!pip download matplotlib
!pip download seaborn
!pip download scikit-learn

import kagglehub
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

path = kagglehub.dataset_download("julianbloise/winners-formula-1-1950-to-2025")
print("Path to dataset files:", path)

csv_files = [f for f in os.listdir(path) if f.endswith('.csv')]
if not csv_files:
    raise FileNotFoundError("No CSV file found in dataset path.")
dataset_file = os.path.join(path, csv_files[0])
print("Using dataset file:", dataset_file)

df = pd.read_csv(dataset_file)

df['favorable'] = (df['team'] == 'Ferrari').astype(int)

sensitive = 'continent'
outcome = 'favorable' 

def fairness_metrics(data, s=sensitive, y=outcome):

    g1, g0 = data[data[s] == data[s].unique()[0]][y], data[data[s] != data[s].unique()[0]][y]
    return {
        "Demographic Parity Diff": abs(g1.mean() - g0.mean()), 
        "Disparate Impact Ratio": g0.mean() / g1.mean() if g1.mean() > 0 else 0, 
        "Equal Opportunity Diff": abs(g1.mean() - g0.mean()) 
    }

print("📊 Before Mitigation:", fairness_metrics(df))
sns.barplot(x=df[sensitive], y=df[outcome])
plt.title(f"Ferrari Win Rate by {sensitive} (Before)")
plt.show()

majority_grp = df[sensitive].value_counts().idxmax()
df_majority = df[df[sensitive] == majority_grp]
df_minority = df[df[sensitive] != majority_grp]
df_minority_upsampled = df_minority.sample(n=len(df_majority), replace=True, random_state=42)
df_bal = pd.concat([df_majority, df_minority_upsampled])

X_train, X_test, y_train, y_test = train_test_split(df_bal.drop(columns=[outcome]), df_bal[outcome], test_size=0.3, random_state=42)
model = LogisticRegression(max_iter=1000)
model.fit(X_train.select_dtypes(include=[np.number]), y_train)
y_pred = model.predict(X_test.select_dtypes(include=[np.number]))
acc = accuracy_score(y_test, y_pred)
print("\n Model Accuracy After Mitigation:", acc)

print("\n After Mitigation:", fairness_metrics(df_bal))
sns.barplot(x=df_bal[sensitive], y=df_bal[outcome])
plt.title(f"Ferrari Win Rate by {sensitive} (After)")
plt.show()

# Comparison of fairness metrics before vs after mitigation

comparison = pd.DataFrame([fairness_metrics(df), fairness_metrics(df_bal)], index=["Before", "After"])
print("\n Comparison:\n", comparison)
        ''',
        '''
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix
from fairlearn.metrics import (
    MetricFrame,
    selection_rate,
    true_positive_rate
)

# --- Step 1: Load dataset ---
df = pd.read_csv("StudentsPerformance.csv")

# --- Step 2: Basic preprocessing ---
# Encode categorical features
label_enc = LabelEncoder()
for col in df.select_dtypes(include="object").columns:
    df[col] = label_enc.fit_transform(df[col])

# Create target variable: 1 if passed math (>50), else 0
df['passed_math'] = (df['math score'] >= 50).astype(int)

# Sensitive attribute (you can change to another column like 'race/ethnicity')
sensitive_feature = 'gender'

# Define features and target
X = df.drop(columns=['math score', 'passed_math'])
y = df['passed_math']

# --- Step 3: Train-test split ---
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# --- Step 4: Standardize features ---
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# --- Step 5: Train model ---
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

# --- Step 6: Model performance ---
acc = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {acc:.3f}")

# --- Step 7: Fairlearn MetricFrame ---
mf = MetricFrame(
    metrics={
        'accuracy': accuracy_score,
        'selection_rate': selection_rate,
        'true_positive_rate': true_positive_rate,
    },
    y_true=y_test,
    y_pred=y_pred,
    sensitive_features=df.loc[y_test.index, sensitive_feature]
)

print("\n=== Fairness Metrics by Sensitive Group ===")
print(mf.by_group)

# --- Step 8: Equal Opportunity Difference ---
eo_diff = mf.difference(method='between_groups')['true_positive_rate']
print(f"\nEqual Opportunity Difference: {eo_diff:.3f}")

# --- Step 9: Disparate Impact ---
# Ratio of minimum to maximum selection rate across groups
sr = mf.by_group['selection_rate']
disparate_impact = sr.min() / sr.max()
print(f"Disparate Impact (min/max selection rate): {disparate_impact:.3f}")

# --- Step 10: Predictive Parity ---
# Precision parity = ratio or difference of precision between groups
precision = {}
for g in df[sensitive_feature].unique():
    y_true_g = y_test[df.loc[y_test.index, sensitive_feature] == g]
    y_pred_g = y_pred[df.loc[y_test.index, sensitive_feature] == g]
    tn, fp, fn, tp = confusion_matrix(y_true_g, y_pred_g).ravel()
    precision[g] = tp / (tp + fp + 1e-9)
print(f"\nPredictive Parity by Group: {precision}")

# --- Step 11: Threshold Limit Analysis ---
print("\n=== Threshold Fairness Check ===")
y_prob = model.predict_proba(X_test)[:, 1]
thresholds = np.arange(0.1, 1.0, 0.1)
for t in thresholds:
    y_pred_t = (y_prob >= t).astype(int)
    tpr = true_positive_rate(y_test, y_pred_t)
    print(f"Threshold {t:.1f}: True Positive Rate = {tpr:.3f}")

-------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, confusion_matrix
from fairlearn.metrics import (
    MetricFrame,
    selection_rate,
    true_positive_rate
)

# --- Step 1: Load dataset ---
df = pd.read_csv("Affairs.csv")

# --- Step 2: Inspect and preprocess ---
# Encode categorical columns
label_enc = LabelEncoder()
for col in df.select_dtypes(include="object").columns:
    df[col] = label_enc.fit_transform(df[col])

# Create binary target: 1 if affairs > 0 (had an affair), else 0
df['had_affair'] = (df['affairs'] > 0).astype(int)

# Sensitive attribute
sensitive_feature = 'gender'

# Features and target
X = df.drop(columns=['affairs', 'had_affair'])
y = df['had_affair']

# --- Step 3: Train-test split ---
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)

# --- Step 4: Standardize numeric columns ---
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)

# --- Step 5: Train logistic regression model ---
model = LogisticRegression()
model.fit(X_train, y_train)
y_pred = model.predict(X_test)

# --- Step 6: Model performance ---
acc = accuracy_score(y_test, y_pred)
print(f"Model Accuracy: {acc:.3f}")

# --- Step 7: Fairlearn MetricFrame ---
mf = MetricFrame(
    metrics={
        'accuracy': accuracy_score,
        'selection_rate': selection_rate,
        'true_positive_rate': true_positive_rate,
    },
    y_true=y_test,
    y_pred=y_pred,
    sensitive_features=df.loc[y_test.index, sensitive_feature]
)

print("\n=== Fairness Metrics by Sensitive Group ===")
print(mf.by_group)

# --- Step 8: Equal Opportunity Difference ---
eo_diff = mf.difference(method='between_groups')['true_positive_rate']
print(f"\nEqual Opportunity Difference: {eo_diff:.3f}")

# --- Step 9: Disparate Impact ---
sr = mf.by_group['selection_rate']
disparate_impact = sr.min() / sr.max()
print(f"Disparate Impact (min/max selection rate): {disparate_impact:.3f}")

# --- Step 10: Predictive Parity ---
precision = {}
for g in df[sensitive_feature].unique():
    y_true_g = y_test[df.loc[y_test.index, sensitive_feature] == g]
    y_pred_g = y_pred[df.loc[y_test.index, sensitive_feature] == g]
    tn, fp, fn, tp = confusion_matrix(y_true_g, y_pred_g).ravel()
    precision[g] = tp / (tp + fp + 1e-9)
print(f"\nPredictive Parity by Group: {precision}")

# --- Step 11: Threshold Fairness ---
print("\n=== Threshold Fairness Check ===")
y_prob = model.predict_proba(X_test)[:, 1]
thresholds = np.arange(0.1, 1.0, 0.1)
for t in thresholds:
    y_pred_t = (y_prob >= t).astype(int)
    tpr = true_positive_rate(y_test, y_pred_t)
    print(f"Threshold {t:.1f}: True Positive Rate = {tpr:.3f}")
        ''',
        '''

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression
from lime.lime_tabular import LimeTabularExplainer

# --- Step 1: Load and preprocess ---
df = pd.read_csv("StudentsPerformance.csv")

# Encode categorical features
label_enc = LabelEncoder()
for col in df.select_dtypes(include="object").columns:
    df[col] = label_enc.fit_transform(df[col])

# Create target: passed_math = 1 if math score >= 50
df['passed_math'] = (df['math score'] >= 50).astype(int)

X = df.drop(columns=['math score', 'passed_math'])
y = df['passed_math']

# --- Step 2: Split and scale ---
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# --- Step 3: Train model ---
model = LogisticRegression()
model.fit(X_train_scaled, y_train)

# --- Step 4: Create LIME Explainer ---
explainer = LimeTabularExplainer(
    training_data=X_train_scaled,
    training_labels=y_train,
    feature_names=X.columns,
    class_names=['Fail', 'Pass'],
    mode='classification'
)

# --- Step 5: Pick a sample to explain ---
i = 5  # You can change this index
exp = explainer.explain_instance(
    data_row=X_test_scaled[i],
    predict_fn=model.predict_proba
)

# --- Step 6: Display explanation ---
print("\nLIME Explanation for Student Sample:")
print(f"Actual Label: {'Pass' if y_test.iloc[i]==1 else 'Fail'}")
print(f"Predicted Probabilities: {model.predict_proba([X_test_scaled[i]])}")

exp.show_in_notebook(show_table=True)
# Or if not in Jupyter:
exp.save_to_file('lime_student.html')
print("LIME explanation saved as lime_student.html")

--------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder, StandardScaler
from sklearn.linear_model import LogisticRegression
from lime.lime_tabular import LimeTabularExplainer

# --- Step 1: Load dataset ---
df = pd.read_csv("Affairs.csv")

# Encode categorical variables if any
label_enc = LabelEncoder()
for col in df.select_dtypes(include="object").columns:
    df[col] = label_enc.fit_transform(df[col])

# Target variable (assuming 'affair' column = 1 if affair, else 0)
target = 'affairs' if 'affairs' in df.columns else df.columns[-1]
X = df.drop(columns=[target])
y = df[target]

# --- Step 2: Split and scale ---
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42
)
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)

# --- Step 3: Train model ---
model = LogisticRegression()
model.fit(X_train_scaled, y_train)

# --- Step 4: LIME Explainer ---
explainer = LimeTabularExplainer(
    training_data=X_train_scaled,
    training_labels=y_train,
    feature_names=X.columns,
    class_names=['No Affair', 'Affair'],
    mode='classification'
)

# --- Step 5: Pick a sample to explain ---
i = 3  # any sample index
exp = explainer.explain_instance(
    data_row=X_test_scaled[i],
    predict_fn=model.predict_proba
)

# --- Step 6: Display results ---
print("\nLIME Explanation for Individual Record:")
print(f"Actual: {'Affair' if y_test.iloc[i]==1 else 'No Affair'}")
print(f"Predicted Probabilities: {model.predict_proba([X_test_scaled[i]])}")

exp.show_in_notebook(show_table=True)
# Or save explanation
exp.save_to_file('lime_affair.html')
print("LIME explanation saved as lime_affair.html")

        ''',
        '''
import shap, numpy as np, pandas as pd
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split

iris = load_iris()
X = pd.DataFrame(iris.data, columns=iris.feature_names)
y = iris.target

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)

model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)
print("Test acc:", model.score(X_test, y_test))

explainer = shap.TreeExplainer(model)
shap_values = explainer.shap_values(X_test)  

print("shap_values type:", type(shap_values))
print("shap_values shape:", np.shape(shap_values))

if isinstance(shap_values, list):
    stacked = np.stack(shap_values, axis=2)   
    shap_2d = np.mean(np.abs(stacked), axis=2) 
elif isinstance(shap_values, np.ndarray):
    if shap_values.ndim == 3:
        shap_2d = np.mean(np.abs(shap_values), axis=2)
    elif shap_values.ndim == 2:
        shap_2d = np.abs(shap_values)
    else:
        raise ValueError("Unexpected shap_values ndim: " + str(shap_values.ndim))
else:
    raise ValueError("Unexpected type for shap_values")

print("combined shap_2d shape:", shap_2d.shape)
assert shap_2d.shape[1] == X_test.shape[1]

mean_abs = shap_2d.mean(axis=0)
imp_df = pd.DataFrame({"feature": X_test.columns, "mean_abs_shap": mean_abs}).sort_values("mean_abs_shap", ascending=False)
plt.figure(figsize=(6,3))
plt.barh(imp_df['feature'], imp_df['mean_abs_shap'])
plt.gca().invert_yaxis()
plt.title("Global Feature Importance (mean |SHAP|)")
plt.xlabel("mean |SHAP|")
plt.tight_layout()
plt.show()

shap.summary_plot(shap_2d, X_test.values, feature_names=X_test.columns, show=True)
        ''',
        '''
import openml, shap, numpy as np, pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
import seaborn as sns
import warnings
warnings.filterwarnings("ignore")
np.random.seed(42)

SENSITIVE = ['sex', 'age', 'embarked']  
EPS = 0.05              
TOPK = 5                 
SHAP_SAMPLE = 300       

ds = openml.datasets.get_dataset(40945)  
X_raw, y, _, _ = ds.get_data(target=ds.default_target_attribute, dataset_format='dataframe')
df = X_raw.copy()
df['survived'] = y
cols = ['pclass','sex','age','sibsp','parch','fare','embarked','survived']
df = df[cols].dropna().reset_index(drop=True)

y = df['survived'].astype(int)
X = df.drop(columns=['survived'])
cat_cols = ['sex','embarked','pclass']
X_enc = pd.get_dummies(X, columns=cat_cols, drop_first=False)
feature_names = X_enc.columns.tolist()

X_train, X_test, y_train, y_test, X_train_orig, X_test_orig = train_test_split(
    X_enc, y, X, test_size=0.25, random_state=42, stratify=y
)
model = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
model.fit(X_train, y_train)
print("Accuracy:", accuracy_score(y_test, model.predict(X_test)))

sample = X_test.sample(n=min(SHAP_SAMPLE, len(X_test)), random_state=42)
explainer = shap.TreeExplainer(model)
shap_out = explainer.shap_values(sample)

if isinstance(shap_out, list):
    shap_vals = shap_out[1] if len(shap_out) > 1 else shap_out[0]
elif isinstance(shap_out, np.ndarray):
    if shap_out.ndim == 3:           
        idx = 1 if shap_out.shape[2] > 1 else 0
        shap_vals = shap_out[:, :, idx]
    elif shap_out.ndim == 2:
        shap_vals = shap_out
    else:
        raise ValueError("Unhandled SHAP shape: " + str(shap_out.shape))
else:
    raise ValueError("Unhandled SHAP return type")

shap_abs = pd.DataFrame(np.abs(shap_vals), columns=sample.columns, index=sample.index)

agg = pd.DataFrame({
    'feature': shap_abs.columns,
    'mean_abs': shap_abs.mean(axis=0),
    'std_abs': shap_abs.std(axis=0)
}).sort_values('mean_abs', ascending=False).reset_index(drop=True)
agg['rank'] = np.arange(1, len(agg)+1)
agg['relative_share'] = agg['mean_abs'] / agg['mean_abs'].sum()

print("\nTop features by mean |SHAP|:")
print(agg.head(10))

flags = []
for s in SENSITIVE:
    related = [f for f in agg['feature'] if f.startswith(s + '_') or f == s]
    if not related and s in agg['feature'].values:
        related = [s]
    if not related:
        print(f" - Sensitive feature '{s}' not found (skipping).")
        continue
    mean_sum = agg.set_index('feature').loc[related, 'mean_abs'].sum()
    rel = mean_sum / agg['mean_abs'].sum()
    ranks = agg.set_index('feature').loc[related, 'rank']
    min_rank = int(ranks.min())
    flagged = (rel > EPS) or (min_rank <= TOPK)
    flags.append({'sensitive': s, 'related_cols': related, 'relative_share': rel, 'min_rank': min_rank, 'flagged': flagged})

print("\nSensitive feature checks:")
print(pd.DataFrame(flags))

def perturb_rows(orig_df, feature):
    dfp = orig_df.copy()
    if feature not in dfp.columns:
        return dfp
    # Use pandas dtype check (safe for categorical dtypes)
    if pd.api.types.is_numeric_dtype(dfp[feature].dtype):
        dfp[feature] = dfp[feature].median()
    else:
        vals = dfp[feature].dropna().unique().tolist()
        if len(vals) > 1:
            mode = dfp[feature].mode().iloc[0]
            alt = next((v for v in vals if v != mode), vals[0])
            dfp[feature] = alt
    return dfp

perturb_orig = X_test_orig.sample(n=min(200, len(X_test_orig)), random_state=42)
perturb_enc = X_test.loc[perturb_orig.index]
if hasattr(model, "predict_proba"):
    orig_probs = model.predict_proba(perturb_enc)[:, 1]
else:
    orig_probs = model.predict(perturb_enc)

perturb_stats = []
for s in SENSITIVE:
    if s not in X.columns:
        print(f"Skipping perturbation for '{s}' (not in original X columns).")
        continue
    pert = perturb_rows(perturb_orig, s)
    pert_enc = pd.get_dummies(pert, columns=cat_cols, drop_first=False).reindex(columns=feature_names, fill_value=0)
    if hasattr(model, "predict_proba"):
        pert_probs = model.predict_proba(pert_enc)[:, 1]
    else:
        pert_probs = model.predict(pert_enc)
    delta = np.abs(orig_probs - pert_probs)
    perturb_stats.append({'sensitive': s, 'mean_abs_delta': float(delta.mean())})

print("\nPerturbation results (mean absolute change in predicted prob):")
print(pd.DataFrame(perturb_stats))

plt.figure(figsize=(7,6))
topn = min(15, len(agg))
sns.barplot(x='mean_abs', y='feature', data=agg.head(topn))
plt.title('Top features by mean |SHAP|')
plt.xlabel('Mean |SHAP|')
plt.tight_layout()
plt.show()
        ''',
        '''
import numpy as np, pandas as pd, matplotlib.pyplot as plt, seaborn as sns
np.random.seed(0)

def lap(scale): return np.random.laplace(0, scale)
def dp_count(df, pred, eps):
    true = int(pred(df).sum()); scale = 1.0/eps
    noisy = max(0.0, true + lap(scale)); return noisy, true
def dp_mean(df, col, a, b, eps):
    x = df[col].clip(a, b).astype(float).values; n=len(x)
    true = x.mean(); delta = (b-a)/n; scale = delta/eps
    noisy = float(np.clip(true + lap(scale), a, b)); return noisy, true

t = sns.load_dataset('titanic').dropna(subset=['age','fare','sex','pclass','embarked'])

pred = lambda df: df['age'] > 30          
col = 'fare'; a,b = 0.0, min(500, t['fare'].max()) 

eps_list = [0.05, 0.1, 0.2, 0.5, 1.0]
T = 300
results = {'eps':[], 'count_rmse':[], 'mean_rmse':[]}

for eps in eps_list:

    noisy_counts = [dp_count(t, pred, eps)[0] for _ in range(T)]
    true_count = dp_count(t, pred, eps)[1]
    rmse_count = np.sqrt(np.mean((np.array(noisy_counts)-true_count)**2))

    noisy_means = [dp_mean(t, col, a, b, eps)[0] for _ in range(T)]
    true_mean = dp_mean(t, col, a, b, eps)[1]
    rmse_mean = np.sqrt(np.mean((np.array(noisy_means)-true_mean)**2))
    results['eps'].append(eps); results['count_rmse'].append(rmse_count); results['mean_rmse'].append(rmse_mean)

res = pd.DataFrame(results)
print(res)

plt.figure(figsize=(8,3))
plt.subplot(1,2,1)
plt.plot(res.eps, res.count_rmse, marker='o'); plt.xscale('log'); plt.title('Count RMSE'); plt.xlabel('epsilon')
plt.subplot(1,2,2)
plt.plot(res.eps, res.mean_rmse, marker='o'); plt.xscale('log'); plt.title('Mean RMSE'); plt.xlabel('epsilon')
plt.tight_layout(); plt.show()
        ''',
        '''
!pip install -q diffprivlib matplotlib scikit-learn

import numpy as np, pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression as SkLogReg
from diffprivlib.models import LogisticRegression as DPLogReg
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt

np.random.seed(0)

X, y = load_breast_cancer(return_X_y=True)
X = StandardScaler().fit_transform(X)  
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0, stratify=y)

base = SkLogReg(max_iter=200, solver="lbfgs")
base.fit(X_train, y_train)
base_acc = accuracy_score(y_test, base.predict(X_test))
print(f"Non-private baseline accuracy: {base_acc:.4f}")

eps_list = [0.05, 0.1, 0.2, 0.5, 1.0, 2.0]

results = []

data_norm = np.max(np.linalg.norm(X_train, axis=1))

for eps in eps_list:
    dp = DPLogReg(epsilon=eps, data_norm=float(data_norm), max_iter=200)
    dp.fit(X_train, y_train)
    acc = accuracy_score(y_test, dp.predict(X_test))
    print(f"eps={eps:.3f}  => DP accuracy: {acc:.4f}")
    results.append((eps, acc))

eps_vals, accs = zip(*results)
plt.figure(figsize=(6,3))
plt.plot(eps_vals, accs, marker='o', label='DP LogisticReg')
plt.axhline(base_acc, color='gray', linestyle='--', label='Non-private')
plt.xscale('log')
plt.xlabel('epsilon (log scale)')
plt.ylabel('test accuracy')
plt.title('Privacy-Accuracy trade-off (logistic regression)')
plt.legend()
plt.grid(alpha=0.3)
plt.tight_layout()
plt.show()
        ''',
        '''
import numpy as np, pandas as pd
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import (accuracy_score, precision_recall_fscore_support,
                             roc_auc_score, roc_curve)

np.random.seed(0)

X, y = load_breast_cancer(return_X_y=True)
scaler = StandardScaler()
X = scaler.fit_transform(X)

X_target_train, X_target_test, y_target_train, y_target_test = train_test_split(
    X, y, test_size=0.5, random_state=0, stratify=y
)

target = LogisticRegression(max_iter=2000, solver='lbfgs')
target.fit(X_target_train, y_target_train)
print("Target model test accuracy:", f"{target.score(X_target_test, y_target_test):.4f}")

def true_label_conf(model, X, y):
    probs = model.predict_proba(X)
    return probs[np.arange(len(y)), y]

s_train = true_label_conf(target, X_target_train, y_target_train)   # members
s_test  = true_label_conf(target, X_target_test,  y_target_test)    # non-members

s_attack = np.hstack([s_train, s_test])
y_attack_true = np.hstack([np.ones(len(s_train)), np.zeros(len(s_test))])  # 1=member

thr_mid = 0.5 * (s_train.mean() + s_test.mean())

fpr, tpr, ths = roc_curve(y_attack_true, s_attack)
youden_idx = np.argmax(tpr - fpr)
thr_youden = ths[youden_idx]

def eval_attack(threshold):
    y_pred = (s_attack > threshold).astype(int)
    acc = accuracy_score(y_attack_true, y_pred)
    prec, recall, f1, _ = precision_recall_fscore_support(y_attack_true, y_pred, average='binary', zero_division=0)
    return acc, prec, recall, f1

acc_mid, prec_mid, recall_mid, f1_mid = eval_attack(thr_mid)
acc_y, prec_y, recall_y, f1_y = eval_attack(thr_youden)
auc = roc_auc_score(y_attack_true, s_attack)

print("\nAttack AUC (confidence as score):", f"{auc:.4f}")
print("\nMidpoint threshold results:")
print(f"  threshold={thr_mid:.4f}  -> acc={acc_mid:.4f}, prec={prec_mid:.4f}, recall={recall_mid:.4f}, F1={f1_mid:.4f}")

print("\nYouden (ROC-optimal) threshold results:")
print(f"  threshold={thr_youden:.4f}  -> acc={acc_y:.4f}, prec={prec_y:.4f}, recall={recall_y:.4f}, F1={f1_y:.4f}")
        ''',
    ]

    @staticmethod
    def text(index):
        """Fetch a specific code based on the index."""
        try:
            return ET.codes[index - 1]
        except IndexError:
            return f"Invalid code index. Please choose a number between 1 and {len(ET.codes)}."
