# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     name: python3
# ---

# + [markdown] id="view-in-github" colab_type="text"
# <a href="https://colab.research.google.com/github/andytorrestb/AIG/blob/master/naive_bayes_4.ipynb" target="_parent"><img src="https://colab.research.google.com/assets/colab-badge.svg" alt="Open In Colab"/></a>

# + [markdown] id="eKEJ8sTQx4uV"
# # **Naive Bayes Example**
#
# Predciting whether or not a person makes under $50K / year.  
# 0. Notebook Set Up
# 1. Data Preprocessing
# 2. Exploratory Data Analysis
# 3. Performance Metrics: Basic Model
# 4. Performance Metrics: Range of Split Percentages
# 5. Performance Metrics: Tuned Hyperparameters

# + [markdown] id="3d_WNx1wyOgO"
# ## **0. Notebook Set Up**

# + [markdown] id="Hg02-dg9z4lg"
# Import files from google drive.

# + colab={"base_uri": "https://localhost:8080/"} id="SICYlH6AxUwq" outputId="6a727bad-6768-47cc-e008-f2400b662d02"
from google.colab import drive
drive.mount('/content/drive', force_remount = True)

# + [markdown] id="AZPu34Z50AgN"
# Import required Python modules.

# + colab={"base_uri": "https://localhost:8080/"} id="cROsJcyw0Ks6" outputId="dcbda89f-e593-4571-9c0a-0ebd9d604e59"
# %%time

import numpy as np
import pandas as pd

import matplotlib.pyplot as plt 
import seaborn as sns

from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
from sklearn.metrics import f1_score
from sklearn.metrics import accuracy_score as acc_score
from sklearn.metrics import roc_auc_score as roc_score

from sklearn.preprocessing import OrdinalEncoder


# + [markdown] id="e-OIXFox0Nly"
# Functions for Data Pre-Processing

# + colab={"base_uri": "https://localhost:8080/"} id="-MJdYMb20Rla" outputId="10dc01e4-efc9-485d-e5a5-a447b00da24c"
# %%time

# Replace labels for columns. Ugly but gets the job done.
def replace_labels(df):
  df.columns = ['age', 'workclass', 'final_weight', 'education', 'education_num', 'marital_status', 'occupation', 'relationship',
             'race', 'sex', 'capital_gain', 'capital_loss', 'hours_per_week', 'native_country', '<=50K']
  return df

# Split features according to data type. Necessary?
def split_column_types(df):
  num_labels = []
  cat_labels = []

  for feature in df.columns:
    if df[feature].dtype == 'int64':
      num_labels.append(feature)
    else :
      cat_labels.append(feature)

  return num_labels, cat_labels

# returns a list containing names of columns cotaining 
# categorical data. 
def make_cat_labels(df):
  cat_labels = []
  for feature in df.columns:
    if df[feature].dtype != 'int64':
      cat_labels.append(feature)

  return cat_labels

# Remove leading and trailing spaces from the df
def strip_df(df, cat_labels):
  for feature in cat_labels:
    df[feature] = df[feature].str.strip()
  return df

# Helper function for remove_null_records
def check_for_null(df, cat_labels):
  missing_list = []

  for feature in cat_labels:
    count_list = df[feature].value_counts().index.tolist()
    if count_list.count('?'):
      missing_list.append(feature)
  
  return missing_list

# Helper function for remove_null_records
def insert_null_values(df, cat_labels):
  missing_list = check_for_null(df, cat_labels)
  for feature in missing_list:
    df[feature].replace('?', np.NaN, inplace = True)
  return df

# Helper function for process_cat_data
def remove_null_records(df, cat_labels):
  return insert_null_values(df, cat_labels).dropna()

# Cleans categorical data by stripping spaces
# removing null records. 
def process_cat_data(df):
  cat_labels = make_cat_labels(df)

  df = strip_df(df, cat_labels)
  df = remove_null_records(df, cat_labels)

  return df

# Changes data type of target vector from string to integer
def convert_target_vector(df):
  df['<=50K'].replace('<=50K', 1, inplace = True)
  df['<=50K'].replace('>50K', 0, inplace = True)

  return df  


# + [markdown] id="kvlCAnP70Yyj"
# Functions for producing distributions of a model's performance metrics.

# + colab={"base_uri": "https://localhost:8080/"} id="flUOmDsc08wy" outputId="1815b6d4-213b-4c0b-909d-ca417698f9bf"
# %%time

# Here to remind me of my computer's hard work <3
def progress_bar(trial, no_trials):
    if trial != no_trials - 1:
      if trial % (no_trials / 20) == 0:
        print('#', end = '')
    else :
      print('#')

# Helper function for type_error()
def make_test_dict():
  df = pd.DataFrame()
  gnb = GaussianNB()
  t_pct = 0.5
  no_trials = 100

  return {'df': df, 'gnb': gnb, 't_pct': t_pct, 'no_trials': no_trials}

# Simple sanity check. 
def type_error(model):
  test = make_test_dict()

  if type(model) != type(test):
    return 1

  if len(model) == 0:
    return 1
  
  for feature in test.keys():
    if type(test[feature]) != type(model[feature]):
      return 1

# Returns dictionary that contains resulting metrics distributions 
def model_dist(model):

  if type_error(model):
    print('type error: check definition of "model"')
    return

  # lists to store distributions of different metrics.
  f1_dist = []
  acc_dist = []
  roc_dist = []

  # Do that one thing. TODO: look this up
  model['df'] = model['df'].sample(frac = 1).reset_index(drop = True)

  # Instantiate the tranformer
  ec = OrdinalEncoder()
  
  for x in range(model['no_trials']):
    X = model["df"].drop(columns = '<=50K')
    y = model["df"]["<=50K"]

    X = ec.fit_transform(X)

    # Split into training/testing set
    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size = model['t_pct'], stratify = y)
  
    # fit the model
    model['gnb'].fit(X_train, y_train)

    # Make prediction
    y_pred = model['gnb'].predict(X_test)

    # Update individual metrics
    f1_dist.append(f1_score(y_test, y_pred))
    acc_dist.append(acc_score(y_test, y_pred))
    roc_dist.append(roc_score(y_test, y_pred))

    # Print small progress bar.
    progress_bar(x, model['no_trials'])
  
  return {'f1': f1_dist, 'acc': acc_dist, 'roc': roc_dist, 't_pct': model['t_pct']}

def make_model_dict(train, t_pct, no_trials):
  return {'df': train, 'gnb': GaussianNB(), 't_pct': t_pct, 'no_trials': no_trials}

# Returns a dictionary containing three df, that each have 
# one of the three metrics distributions for evaluating model performance. 
def split_dist(train, t_range, no_trials):
  split_dist = []

  for t_pct in range(t_range['l_bound'], t_range['u_bound'], t_range['incr']):
    model = make_model_dict(train, t_pct / 100, no_trials)
    split_dist.append(model_dist(model))

  return pd.DataFrame(split_dist)


# + [markdown] id="vdJ7ijd71CxI"
# Functions for graphing distributions of model metrics.

# + colab={"base_uri": "https://localhost:8080/"} id="N39yZZL-1CFk" outputId="01e1084c-7524-4ea9-cb67-f65da5853c6c"
# %%time

def metrics_histplot(metrics):
  # Graph perfomance distributions for model metrics. 
  perf_fig, axes = plt.subplots(1, 3, figsize = (30, 10));
  perf_fig.suptitle('Perfomance Metrics ', y = 1.02);
  perf_fig.tight_layout()

  scores = list(metrics.keys())[:3]

  index = 0
  for score in scores:
    sns.histplot(ax = axes[index], x = metrics[score])
    axes[index].set_title(score)
    index += 1

def metrics_boxplot(metrics):
  # Graph perfomance distributions for model metrics. 
  perf_fig, axes = plt.subplots(1, 3, figsize = (30, 10));
  perf_fig.suptitle('Perfomance Metrics ', y = 1.02);
  perf_fig.tight_layout()
  index = 0

  scores = list(metrics.keys())[:3]
  
  for score in scores:
      sns.boxplot(ax = axes[index], y = metrics[score])
      axes[index].set_title(score)
      index += 1

# Graph performance metrics across a range of test/train
# split values. Please enter all values as integers.
# They all represent percentages.   
# TODO: 1. use dicts to simplify the parameters.
#       2. finish
# def graph_metrics_by_split(t_range):

#   for t_pct in range(t_range['l_bound'], t_range['u_bound'], t_range['incr']):


# + [markdown] id="cE1bumz21QH9"
# ## **1. Data Preprocessing** 

# + colab={"base_uri": "https://localhost:8080/"} id="ZA5dN1G81L1k" outputId="068a4276-43f9-4541-902f-73417b7bb73a"
# %%time

# Load and clean training data. 
path = '/content/drive/My Drive/adult.csv'
train = replace_labels(pd.read_csv(path))

train = process_cat_data(train)
train = convert_target_vector(train)

# + [markdown] id="rUEMezqv1nTP"
# ## **2. Exploratory Data Analysis**

# + id="1Ssqmb_e1tfW"


# + [markdown] id="vs2jPD5N1uCQ"
# ## **3. Performance Metrics: Basic Model**
#

# + id="Ps5lsR6112cd" colab={"base_uri": "https://localhost:8080/"} outputId="6f6257eb-e611-470e-d57c-728fcf13737e"
# %%time

# Run model and produce distributions for performance metrics.
model = {'df': train, 'gnb': GaussianNB(), 't_pct': 0.5, 'no_trials': 5}
metrics = model_dist(model)

# + colab={"base_uri": "https://localhost:8080/", "height": 1000} id="zFgN5ggrMRE-" outputId="5ff0e8d9-12e0-4992-ded3-3e3f524cccdc"
# Graph performance metrics for basic model.
metrics_histplot(metrics)
metrics_boxplot(metrics)

# + [markdown] id="tMRJVAD_YLFt"
# ## **4. Performance Metrics: Range of Split Percentages**
#

# + colab={"base_uri": "https://localhost:8080/"} id="49dWyViLYWbd" outputId="8e26e203-d585-4bd4-9ec7-f662d6bc1256"
# %%time

# Range of split percentages to loop over. 
t_range = {'l_bound': 5, 'u_bound': 50, 'incr': 5}

# Produce dictionary containing dictionaries that store the 
# performance metrics for each t_pct value. 
dist_split = split_dist(train, t_range, 25)
#TODO: re-write split_dist to that each record holds the metrics single for a single run,
# not the entire minte carlo simulation ditribution. This should solve issues with graphing. 

# + colab={"base_uri": "https://localhost:8080/", "height": 328} id="FTMQgX3iC3Mb" outputId="2e30815f-135b-45aa-ccfc-726128035d56"
dist_split

# + colab={"base_uri": "https://localhost:8080/", "height": 877} id="sn59fJa2qefi" outputId="3a7b64aa-f448-4824-8bf0-dabec69beda0"
# for t_pct in dist_split.keys():
#   metrics_boxplot(dist_split[t_pct])
#   # TODO: Refactor to be nice and tidy like Dr. H's
#   # Should give clear visual comparisons of each t_pct value.
# print(type(dist_split.iat[0,0]))
# print(dist_split)

split_figs, axes = plt.subplots(1, 10, figsize = (50, 10))

for t_pct in range(int((t_range['u_bound']  - t_range['l_bound']) / t_range['incr'])):
  print(t_pct)
  sns.boxplot(y = 'f1', x = 't_pct', data = dist_split)
  # sns.boxplot(ax = axes[t_pct], y = dist_split.iat[0, t_pct])

# dist_split

# + colab={"base_uri": "https://localhost:8080/", "height": 510} id="ZzR6Qs3B5i3b" outputId="af8ec470-549b-44a3-ccfe-bdd344e7e8bb"
print(type(dist_split))
sns.boxplot(x = 't_pct', y = 'f1', data = dist_split)

StarcoderdataJupyterScriptsDedupFiltered