0% found this document useful (0 votes)
3 views18 pages

Income Classifier

The document outlines a project on building an income prediction model using the Adult dataset, employing techniques such as data preprocessing, model training with Random Forest, and evaluation of accuracy. It also includes an analysis of model predictions based on gender and race, demonstrating demographic parity and prediction distribution. Additionally, it discusses adversarial robustness tests to assess the model's vulnerability to feature importance-based attacks.

Uploaded by

monisa4606
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views18 pages

Income Classifier

The document outlines a project on building an income prediction model using the Adult dataset, employing techniques such as data preprocessing, model training with Random Forest, and evaluation of accuracy. It also includes an analysis of model predictions based on gender and race, demonstrating demographic parity and prediction distribution. Additionally, it discusses adversarial robustness tests to assess the model's vulnerability to feature importance-based attacks.

Uploaded by

monisa4606
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

====================================

========================

Responsible and Safe AI Assignment

Income Prediction Model using Adult Dataset

BY

MONISA R

2303917724422031

====================================
========================
# Part 1: Load dataset and basic preprocessing

import pandas as pd
from [Link] import fetch_openml
from sklearn.model_selection import train_test_split
from [Link] import OneHotEncoder
from [Link] import ColumnTransformer
from [Link] import Pipeline
from [Link] import accuracy_score
from sklearn.linear_model import LogisticRegression

# Load Adult dataset


data = fetch_openml("adult", version=2, as_frame=True)
df = [Link]()
# Convert income label to binary
df['income'] = (df['class'] == '>50K').astype(int)

# Drop the old target column name


df = [Link](columns=['class'])

# Identify features
target = "income"
features = [Link](target)

# Split data
X_train, X_test, y_train, y_test = train_test_split(
df[features], df[target], test_size=0.2, random_state=42,
stratify=df[target]
)

# Identify categorical and numeric columns


categorical_cols =
X_train.select_dtypes(include="category").[Link]() + \

X_train.select_dtypes(include="object").[Link]()

numeric_cols =
X_train.select_dtypes(include="number").[Link]()

print("Categorical columns:", categorical_cols)


print("Numeric columns:", numeric_cols)
print("Train/Test shapes:", X_train.shape, X_test.shape)

Categorical columns: ['workclass', 'education', 'marital-status',


'occupation', 'relationship', 'race', 'sex', 'native-country']
Numeric columns: ['age', 'fnlwgt', 'education-num', 'capital-gain',
'capital-loss', 'hours-per-week']
Train/Test shapes: (39073, 14) (9769, 14)

[Link]()

{"summary":"{\n \"name\": \"df\",\n \"rows\": 48842,\n \"fields\":


[\n {\n \"column\": \"age\",\n \"properties\": {\n
\"dtype\": \"number\",\n \"std\": 13,\n \"min\": 17,\n
\"max\": 90,\n \"num_unique_values\": 74,\n \"samples\":
[\n 18,\n 74,\n 40\n ],\n
\"semantic_type\": \"\",\n \"description\": \"\"\n }\
n },\n {\n \"column\": \"workclass\",\n
\"properties\": {\n \"dtype\": \"category\",\n
\"num_unique_values\": 8,\n \"samples\": [\n \"Local-
gov\",\n \"Self-emp-inc\",\n \"Private\"\
n ],\n \"semantic_type\": \"\",\n
\"description\": \"\"\n }\n },\n {\n \"column\":
\"fnlwgt\",\n \"properties\": {\n \"dtype\": \"number\",\n
\"std\": 105604,\n \"min\": 12285,\n \"max\": 1490400,\n
\"num_unique_values\": 28523,\n \"samples\": [\n
171041,\n 20296,\n 263896\n ],\n
\"semantic_type\": \"\",\n \"description\": \"\"\n }\
n },\n {\n \"column\": \"education\",\n
\"properties\": {\n \"dtype\": \"category\",\n
\"num_unique_values\": 16,\n \"samples\": [\n
\"11th\",\n \"HS-grad\",\n \"Prof-school\"\
n ],\n \"semantic_type\": \"\",\n
\"description\": \"\"\n }\n },\n {\n \"column\":
\"education-num\",\n \"properties\": {\n \"dtype\":
\"number\",\n \"std\": 2,\n \"min\": 1,\n
\"max\": 16,\n \"num_unique_values\": 16,\n \"samples\":
[\n 7,\n 9,\n 15\n ],\n
\"semantic_type\": \"\",\n \"description\": \"\"\n }\
n },\n {\n \"column\": \"marital-status\",\n
\"properties\": {\n \"dtype\": \"category\",\n
\"num_unique_values\": 7,\n \"samples\": [\n \"Never-
married\",\n \"Married-civ-spouse\",\n \"Married-
spouse-absent\"\n ],\n \"semantic_type\": \"\",\n
\"description\": \"\"\n }\n },\n {\n \"column\":
\"occupation\",\n \"properties\": {\n \"dtype\":
\"category\",\n \"num_unique_values\": 14,\n
\"samples\": [\n \"Sales\",\n \"Transport-moving\",\
n \"Machine-op-inspct\"\n ],\n
\"semantic_type\": \"\",\n \"description\": \"\"\n }\
n },\n {\n \"column\": \"relationship\",\n
\"properties\": {\n \"dtype\": \"category\",\n
\"num_unique_values\": 6,\n \"samples\": [\n \"Own-
child\",\n \"Husband\",\n \"Other-relative\"\n
],\n \"semantic_type\": \"\",\n \"description\": \"\"\n
}\n },\n {\n \"column\": \"race\",\n \"properties\":
{\n \"dtype\": \"category\",\n \"num_unique_values\":
5,\n \"samples\": [\n \"White\",\n \"Amer-
Indian-Eskimo\",\n \"Asian-Pac-Islander\"\n ],\n
\"semantic_type\": \"\",\n \"description\": \"\"\n }\
n },\n {\n \"column\": \"sex\",\n \"properties\": {\n
\"dtype\": \"category\",\n \"num_unique_values\": 2,\n
\"samples\": [\n \"Female\",\n \"Male\"\n ],\
n \"semantic_type\": \"\",\n \"description\": \"\"\n
}\n },\n {\n \"column\": \"capital-gain\",\n
\"properties\": {\n \"dtype\": \"number\",\n \"std\":
7452,\n \"min\": 0,\n \"max\": 99999,\n
\"num_unique_values\": 123,\n \"samples\": [\n 4064,\n
4787\n ],\n \"semantic_type\": \"\",\n
\"description\": \"\"\n }\n },\n {\n \"column\":
\"capital-loss\",\n \"properties\": {\n \"dtype\":
\"number\",\n \"std\": 403,\n \"min\": 0,\n
\"max\": 4356,\n \"num_unique_values\": 99,\n
\"samples\": [\n 2238,\n 1564\n ],\n
\"semantic_type\": \"\",\n \"description\": \"\"\n }\
n },\n {\n \"column\": \"hours-per-week\",\n
\"properties\": {\n \"dtype\": \"number\",\n \"std\":
12,\n \"min\": 1,\n \"max\": 99,\n
\"num_unique_values\": 96,\n \"samples\": [\n 9,\n
11\n ],\n \"semantic_type\": \"\",\n
\"description\": \"\"\n }\n },\n {\n \"column\":
\"native-country\",\n \"properties\": {\n \"dtype\":
\"category\",\n \"num_unique_values\": 41,\n
\"samples\": [\n \"Canada\",\n \"South\"\
n ],\n \"semantic_type\": \"\",\n
\"description\": \"\"\n }\n },\n {\n \"column\":
\"income\",\n \"properties\": {\n \"dtype\": \"number\",\n
\"std\": 0,\n \"min\": 0,\n \"max\": 1,\n
\"num_unique_values\": 2,\n \"samples\": [\n 1,\n
0\n ],\n \"semantic_type\": \"\",\n
\"description\": \"\"\n }\n }\n ]\
n}","type":"dataframe","variable_name":"df"}

# Part 2 (Updated): Build pipeline with RandomForest, train, compute


accuracy

from [Link] import RandomForestClassifier

# Preprocessing: One-hot encode categorical variables, passthrough


numeric
preprocessor = ColumnTransformer(
transformers=[
("cat", OneHotEncoder(handle_unknown="ignore"),
categorical_cols),
("num", "passthrough", numeric_cols)
]
)

# Random Forest model


model = Pipeline(steps=[
("preprocess", preprocessor),
("clf", RandomForestClassifier(
n_estimators=200,
max_depth=None,
min_samples_split=2,
n_jobs=-1,
random_state=42
))
])

# Train the model


[Link](X_train, y_train)
# Predict
y_pred = [Link](X_test)

# Accuracy
accuracy = accuracy_score(y_test, y_pred)
print("Random Forest Model Accuracy:", accuracy)

Random Forest Model Accuracy: 0.8585320913092436

import shap
import numpy as np

# ---- Convert background to NumPy ----


background_np = X_train.sample(50, random_state=42).to_numpy()

# ---- Predict function that converts NumPy → DataFrame → pipeline


----
def pipeline_predict_numpy(x):
df = [Link](x, columns=X_train.columns)
return model.predict_proba(df)

# ---- KernelExplainer (safe version) ----


explainer = [Link](
pipeline_predict_numpy,
background_np
)

# ---- Sample test rows in NumPy ----


X_test_sample_np = X_test.sample(10, random_state=42).to_numpy()

# SHAP values
shap_values = explainer.shap_values(X_test_sample_np)

# Summary plot
shap.summary_plot(shap_values, X_test_sample_np,
feature_names=X_train.columns)

{"model_id":"f203f40b1a3e425cbe7126f124ea325d","version_major":2,"vers
ion_minor":0}
!pip install --quiet lime

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/275.7 kB ? eta -:--:--


━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╸━ 266.2/275.7 kB 7.9 MB/s eta
0:00:01 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 275.7/275.7 kB 5.2
MB/s eta 0:00:00
etadata ([Link]) ... e ([Link]) ...

from lime.lime_tabular import LimeTabularExplainer


import numpy as np
import pandas as pd
from [Link] import LabelEncoder

# -------------------------------------------------------
# 1. Make a LIME-safe version of the dataset (encode cats)
# -------------------------------------------------------

X_train_lime = X_train.copy()
X_test_lime = X_test.copy()

label_encoders = {}

for col in categorical_cols:


le = LabelEncoder()
X_train_lime[col] = le.fit_transform(X_train_lime[col])
X_test_lime[col] = [Link](X_test_lime[col])
label_encoders[col] = le

# Convert to NumPy
X_train_np = X_train_lime.values
X_test_np = X_test_lime.values

# -------------------------------------------------------
# 2. Create LIME explainer
# -------------------------------------------------------

explainer = LimeTabularExplainer(
training_data=X_train_np,
feature_names=X_train.[Link](),
class_names=['<=50K', '>50K'],
mode="classification"
)

# -------------------------------------------------------
# 3. Prediction wrapper (pipeline expects original df)
# -------------------------------------------------------

def lime_predict(x):
# Convert numpy → dataframe
df = [Link](x, columns=X_train.columns)

# Decode categorical variables back to original strings


for col in categorical_cols:
le = label_encoders[col]
df[col] = le.inverse_transform(df[col].astype(int))

return model.predict_proba(df)

# -------------------------------------------------------
# 4. Explain one row
# -------------------------------------------------------

i = 0
instance = X_test_np[i]

exp = explainer.explain_instance(
data_row=instance,
predict_fn=lime_predict
)

exp.show_in_notebook(show_table=True)
exp.save_to_file("lime_explanation.html")

<[Link] object>

# Part 3 (Updated): Accuracy for men vs women

# Create a copy of X_test including predictions


X_test_copy = X_test.copy()
X_test_copy["y_true"] = y_test
X_test_copy["y_pred"] = y_pred

# Separate by gender
men = X_test_copy[X_test_copy["sex"] == "Male"]
women = X_test_copy[X_test_copy["sex"] == "Female"]

# Compute accuracy
men_acc = accuracy_score(men["y_true"], men["y_pred"])
women_acc = accuracy_score(women["y_true"], women["y_pred"])

print("Accuracy for Men:", men_acc)


print("Accuracy for Women:", women_acc)

Accuracy for Men: 0.8227342549923196


Accuracy for Women: 0.9300398895366677

# Part 4: Analyze prediction distribution across race groups

# Group by race and compute distribution of predicted income labels


race_distribution = X_test_copy.groupby("race")
["y_pred"].value_counts(normalize=True).unstack().fillna(0)

# Rename columns for clarity


race_distribution.columns = ["Predicted Low Income (0)", "Predicted
High Income (1)"]

print("Prediction Distribution Across Race Groups:\n")


print(race_distribution)

/tmp/[Link]: FutureWarning: The default of


observed=False is deprecated and will be changed to True in a future
version of pandas. Pass observed=False to retain current behavior or
observed=True to adopt the future default and silence this warning.
race_distribution = X_test_copy.groupby("race")
["y_pred"].value_counts(normalize=True).unstack().fillna(0)

Prediction Distribution Across Race Groups:

Predicted Low Income (0) Predicted High Income


(1)
race

Amer-Indian-Eskimo 0.916667
0.083333
Asian-Pac-Islander 0.768489
0.231511
Black 0.908058
0.091942
Other 0.951220
0.048780
White 0.782122
0.217878

# Part 5: Compute demographic parity based on gender

# Percentage predicted high income for men


male_high_income_rate = (
X_test_copy[X_test_copy["sex"] == "Male"]["y_pred"].mean()
)

# Percentage predicted high income for women


female_high_income_rate = (
X_test_copy[X_test_copy["sex"] == "Female"]["y_pred"].mean()
)

print("Demographic Parity (Predicted High Income Rates):")


print("Men: {:.2f}%".format(male_high_income_rate * 100))
print("Women: {:.2f}%".format(female_high_income_rate * 100))

Demographic Parity (Predicted High Income Rates):


Men: 26.22%
Women: 8.50%

# ============================================
# Part 6 (Advanced): Adversarial Robustness Tests
# ============================================

import numpy as np
import pandas as pd

# Pick one concrete sample from test set


sample = X_test.iloc[[0]].copy()
print("=== Original Test Sample ===")
print(sample)
print()

# Get base prediction


base_pred = [Link](sample)[0]
print("Base Prediction:", base_pred)
print("-" * 60)

# --------------------------------------------------
# 1. Feature-Importance-Based Attack (Top Features)
# --------------------------------------------------

# Get feature importances from RandomForest


rf_clf = model.named_steps["clf"]

importances = rf_clf.feature_importances_
feature_importance = [Link]({
"feature": model.named_steps["preprocess"]
.get_feature_names_out().tolist(),
"importance": importances
}).sort_values("importance", ascending=False)

top_features = feature_importance.head(3)["feature"].tolist()

print("\n=== 1. Feature-Importance Attack (Top features) ===")


print("Top impactful features:", top_features)

for f in top_features:
modified = [Link]()
# If numeric → scaled perturbation
try:
original_val = modified[f].values[0]
modified[f] = modified[f] * 1.5 if isinstance(original_val,
(int, float)) else modified[f]
except:
pass

pred = [Link](modified)[0]
print(f"Feature perturbed: {f}")
print("Prediction:", pred)
if pred != base_pred:
print("⚠ Flip detected!")
print()

# --------------------------------------------------
# 2. Counterfactual Fairness Attack (Flip Gender)
# --------------------------------------------------

print("\n=== 2. Counterfactual Fairness Attack (Gender Swap) ===")


cf = [Link]()

if "sex" in [Link]:
# Flip male/female
if cf["sex"].values[0] == "Male":
cf["sex"] = "Female"
else:
cf["sex"] = "Male"

cf_pred = [Link](cf)[0]
print("Original gender:", sample["sex"].values[0])
print("Counterfactual gender:", cf["sex"].values[0])
print("Prediction after flip:", cf_pred)

if cf_pred != base_pred:
print("⚠ Gender-sensitive prediction detected!")
print()

# --------------------------------------------------
# 3. Rare-Category Attack
# --------------------------------------------------

print("\n=== 3. Rare Category Attack ===")


rare = [Link]()

# Replace workclass & occupation with rare categories


if "workclass" in [Link]:
rare["workclass"] = "Without-pay"

if "occupation" in [Link]:
rare["occupation"] = "Armed-Forces"

rare_pred = [Link](rare)[0]

print("Modified rare categories:")


print(rare[["workclass", "occupation"]])
print("Prediction:", rare_pred)

if rare_pred != base_pred:
print("⚠ Model unstable for rare categories!")
print()

# --------------------------------------------------
# 4. Boundary Condition Attack
# --------------------------------------------------
print("\n=== 4. Boundary Condition Attack ===")
boundary = [Link]()

if "hours-per-week" in [Link]:
boundary["hours-per-week"] = 1 # Extreme low boundary

if "age" in [Link]:
boundary["age"] = 90 # Extreme high boundary

boundary_pred = [Link](boundary)[0]

print("Boundary-modified sample:")
print(boundary[["age", "hours-per-week"]])
print("Prediction:", boundary_pred)

if boundary_pred != base_pred:
print("⚠ Model sensitive to extreme boundary values!")
print()

# --------------------------------------------------
# 5. Multi-Feature Joint Attack
# --------------------------------------------------

print("\n=== 5. Multi-Feature Joint Attack ===")


multi = [Link]()

if "age" in [Link]:
multi["age"] += 15

if "hours-per-week" in [Link]:
multi["hours-per-week"] += 10

if "capital-gain" in [Link]:
multi["capital-gain"] += 5000

multi_pred = [Link](multi)[0]

print("Prediction under joint multi-feature attack:", multi_pred)


if multi_pred != base_pred:
print("⚠ Joint modification flips prediction!")
print()

# --------------------------------------------------
# 6. Noise Injection Attack
# --------------------------------------------------

print("\n=== 6. Noise Injection Attack (±5% random noise) ===")

noise = [Link]()
numeric_cols_local = X_test.select_dtypes(include="number").columns

for col in numeric_cols_local:


noise[col] = noise[col] * (1 + [Link](-0.05, 0.05))

noise_pred = [Link](noise)[0]

print("Prediction after random noise:", noise_pred)

if noise_pred != base_pred:
print("⚠ Noise-sensitive model!")
else:
print("✓ Model stable under random noise.")

=== Original Test Sample ===


age workclass fnlwgt education education-num marital-
status \
40342 54 Private 115602 HS-grad 9 Married-civ-
spouse

occupation relationship race sex capital-gain


capital-loss \
40342 Other-service Wife Black Female 0
0

hours-per-week native-country
40342 40 United-States

Base Prediction: 0
------------------------------------------------------------

=== 1. Feature-Importance Attack (Top features) ===


Top impactful features: ['num__fnlwgt', 'num__age', 'num__capital-
gain']
Feature perturbed: num__fnlwgt
Prediction: 0

Feature perturbed: num__age


Prediction: 0

Feature perturbed: num__capital-gain


Prediction: 0

=== 2. Counterfactual Fairness Attack (Gender Swap) ===


Original gender: Female
Counterfactual gender: Male
Prediction after flip: 0

=== 3. Rare Category Attack ===


Modified rare categories:
workclass occupation
40342 Without-pay Armed-Forces
Prediction: 0

=== 4. Boundary Condition Attack ===


Boundary-modified sample:
age hours-per-week
40342 90 1
Prediction: 0

=== 5. Multi-Feature Joint Attack ===


Prediction under joint multi-feature attack: 0

=== 6. Noise Injection Attack (±5% random noise) ===


Prediction after random noise: 0
✓ Model stable under random noise.

# Part 7: AI Audit Report

def print_ai_audit_report():
print("\n==========================")
print(" AI MODEL AUDIT")
print("==========================\n")

# Overall accuracy
print(f"Overall Accuracy: {accuracy:.4f}\n")

# Gender-specific accuracy
print("Gender-Specific Accuracy:")
print(f" Men Accuracy: {men_acc:.4f}")
print(f" Women Accuracy: {women_acc:.4f}\n")

# Demographic parity
print("Demographic Parity (Predicted High Income %):")
print(f" Men: {male_high_income_rate * 100:.2f}%")
print(f" Women: {female_high_income_rate * 100:.2f}%\n")

# Race distribution
print("Prediction Distribution Across Race Groups:")
print(race_distribution)

print("\n==========================")
print(" END OF AUDIT REPORT")
print("==========================\n")

# Print the audit report


print_ai_audit_report()
==========================
AI MODEL AUDIT
==========================

Overall Accuracy: 0.8585

Gender-Specific Accuracy:
Men Accuracy: 0.8227
Women Accuracy: 0.9300

Demographic Parity (Predicted High Income %):


Men: 26.22%
Women: 8.50%

Prediction Distribution Across Race Groups:


Predicted Low Income (0) Predicted High Income
(1)
race

Amer-Indian-Eskimo 0.916667
0.083333
Asian-Pac-Islander 0.768489
0.231511
Black 0.908058
0.091942
Other 0.951220
0.048780
White 0.782122
0.217878

==========================
END OF AUDIT REPORT
==========================

# =========================================================
# PART 1 — Load Dataset (keep full copy for fairness audit)
# =========================================================
from [Link] import fetch_openml
import pandas as pd

# Load Adult dataset


data = fetch_openml("adult", version=2, as_frame=True)

df_full = [Link]() # <-- FULL DATA WITH GENDER (for


fairness)
df = df_full.copy() # <-- TRAINING DATA (gender removed
later)

# Convert income label to binary


df_full["income"] = (df_full["class"] == ">50K").astype(int)
df["income"] = df_full["income"]

# =========================================================
# PART 2 — Remove gender ONLY in training dataset
# =========================================================
df = [Link](columns=["sex", "class"]) # <-- gender removed for
training
df_full = df_full.drop(columns=["class"]) # <-- keep gender for
fairness only

print("Training Columns (gender removed):", [Link])

# =========================================================
# PART 3 — Split dataset (same indices for both copies)
# =========================================================
from sklearn.model_selection import train_test_split

X = [Link](columns=["income"])
y = df["income"]

X_full = df_full.drop(columns=["income"]) # still has "sex"


y_full = df_full["income"]

# Split using index tracking so both datasets align


X_train, X_test, y_train, y_test, idx_train, idx_test =
train_test_split(
X, y, range(len(X)), test_size=0.2, random_state=42, stratify=y
)

# Test set WITH gender (for fairness evaluation)


X_test_full = X_full.iloc[idx_test]
y_test_full = y_full.iloc[idx_test]

# =========================================================
# PART 4 — Preprocess + Random Forest Model (without gender)
# =========================================================
from [Link] import OneHotEncoder
from [Link] import ColumnTransformer
from [Link] import Pipeline
from [Link] import RandomForestClassifier
from [Link] import accuracy_score

categorical_cols = X_train.select_dtypes(include=["object",
"category"]).[Link]()
numeric_cols =
X_train.select_dtypes(include="number").[Link]()
preprocessor = ColumnTransformer(
transformers=[
("cat", OneHotEncoder(handle_unknown="ignore"),
categorical_cols),
("num", "passthrough", numeric_cols)
]
)

model = Pipeline(steps=[
("preprocess", preprocessor),
("clf", RandomForestClassifier(
n_estimators=200,
random_state=42,
n_jobs=-1
))
])

[Link](X_train, y_train)
y_pred = [Link](X_test)

accuracy = accuracy_score(y_test, y_pred)


print("\nModel Accuracy WITHOUT Gender Column:", accuracy)

# =========================================================
# PART 5 — Fairness Audit Dataset (merge gender back)
# =========================================================
X_test_full = X_test_full.copy()
X_test_full["y_true"] = y_test.values
X_test_full["y_pred"] = y_pred

# Confirm gender exists


print("\nContains gender?:", "sex" in X_test_full.columns)

# =========================================================
# PART 6 — Accuracy for Men vs Women
# =========================================================
men = X_test_full[X_test_full["sex"] == "Male"]
women = X_test_full[X_test_full["sex"] == "Female"]

men_acc = accuracy_score(men["y_true"], men["y_pred"])


women_acc = accuracy_score(women["y_true"], women["y_pred"])

print("\nAccuracy for Men (Gender Removed):", men_acc)


print("Accuracy for Women (Gender Removed):", women_acc)

# =========================================================
# PART 7 — Demographic Parity
# =========================================================
male_rate = men["y_pred"].mean()
female_rate = women["y_pred"].mean()

print("\nDemographic Parity (High Income Prediction %):")


print(" Men: {:.2f}%".format(male_rate * 100))
print(" Women: {:.2f}%".format(female_rate * 100))

Training Columns (gender removed): Index(['age', 'workclass',


'fnlwgt', 'education', 'education-num',
'marital-status', 'occupation', 'relationship', 'race',
'capital-gain',
'capital-loss', 'hours-per-week', 'native-country', 'income'],
dtype='object')

Model Accuracy WITHOUT Gender Column: 0.8585320913092436

Contains gender?: True

Accuracy for Men (Gender Removed): 0.823195084485407


Accuracy for Women (Gender Removed): 0.9291193617674133

Demographic Parity (High Income Prediction %):


Men: 26.05%
Women: 8.71%

You might also like