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

# +
from collections import Counter
import json
import datetime as dt
import numpy as np
import pandas as pd
import dask.bag as db
import matplotlib.pyplot as plt

from sklearn.preprocessing import StandardScaler, label_binarize, normalize
from sklearn.metrics import f1_score, auc, roc_auc_score, roc_curve, precision_score, recall_score, accuracy_score, precision_recall_curve, confusion_matrix
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.linear_model import LogisticRegression
# from sklearn.ensemble import RandomForestClassifier
from sklearn.pipeline import Pipeline, FeatureUnion

from functions.data import (bag_to_df,
                            flatten_review, flatten_business,
                            preprocess_review_df, preprocess_business_df)
from functions.model import compute_multiclass_ROC, TextSelector, NumberSelector

# Logistic regression works the best
# GaussianNB, DecisionTree, and RandomForest are poor
# SVC and XGBoost will crash
# -

save=False

# # Get Data

# +
review_bag = db.read_text("data/reviews.json").map(json.loads) # Loads the json file as a dask bag
review_df = bag_to_df(review_bag, flatten_review)
review_df = preprocess_review_df(review_df)

business_bag = db.read_text("data/business.json").map(json.loads) # Loads the json file as a dask bag
business_df = bag_to_df(business_bag, flatten_business)
business_df = preprocess_business_df(business_df)

# merge
business_review_df = pd.merge(business_df, review_df, on="business_id")
business_review_df.columns
# -

# # Logistic Regresssion on Text Only

# +
# Set aside 20% of data for out of sample validation, using date to filter

features_list = ['text_length','useful','review_count', 'Dogs Allowed', 'funny',
                 'Open to All', 'Accepts Credit Cards', 'cool',
                 'Bike Parking', 'Good for Kids', 'avg_stars', 'text_clean']

features_train_df = business_review_df[business_review_df['date'] <= dt.datetime(2017, 5, 28)][features_list]
features_test_df = business_review_df[business_review_df['date'] > dt.datetime(2017, 5, 28)][features_list]
target_train_ser = business_review_df[business_review_df['date'] <= dt.datetime(2017, 5, 28)]['stars']
target_test_ser = business_review_df[business_review_df['date'] > dt.datetime(2017, 5, 28)]['stars']

# Binarize output
target_train_bin_array = label_binarize(target_train_ser, classes= [1, 2, 3, 4, 5])
target_test_bin_array = label_binarize(target_test_ser, classes = [1, 2, 3, 4, 5])

# +
# Preprocessing steps

# Count Vectorizer
count_vectorizer = CountVectorizer(stop_words='english', ngram_range=(1,3)) #ngram_range=(1,3)
features_train_vectorized_csr = count_vectorizer.fit_transform(features_train_df['text_clean']) # csr
features_test_vectorized_csr = count_vectorizer.transform(features_test_df['text_clean'])
# -

logreg_mlt_clf = LogisticRegression(multi_class = 'multinomial', solver = 'newton-cg') # Initialize
logreg_mlt_clf.fit(features_train_vectorized_csr, target_train_ser) # Train

# # Unweighted

# +
# Unweighted
features_train_vectorized_predict_array = logreg_mlt_clf.predict(features_train_vectorized_csr) # Needed for confusion matrix
features_train_vectorized_proba_array = logreg_mlt_clf.predict_proba(features_train_vectorized_csr)
features_test_vectorized_predict_array = logreg_mlt_clf.predict(features_test_vectorized_csr) # Needed for confusion matrix
features_test_vectorized_proba_array = logreg_mlt_clf.predict_proba(features_test_vectorized_csr)
confusion = confusion_matrix(target_test_ser, features_test_vectorized_predict_array)
confusion_normalized_array = normalize(confusion, axis=1, norm='l1') # Total Test accuracy to within +/- 1 class, 

print("Micro-Average Training Accuracy: ", f1_score(target_train_ser, features_train_vectorized_predict_array, average = "micro"))
print("Micro-Average Test Accuracy: ", f1_score(target_test_ser, features_test_vectorized_predict_array, average = "micro"))
print("Micro-Average Test Accuracy: ", confusion.trace()/confusion.sum()) # Total Test Accuracy Score

# Average Test Accuracy to within +/- 1 class
print("Macro-Average Test Accuracy (+/-1): ", sum([confusion_normalized_array[1:, :-1].trace()/5, confusion_normalized_array.trace()/5, confusion_normalized_array[:-1, 1:].trace()/5]))

# +
# Unweighted


# Plot Multiclass Confusion Matrix
fig = plot_heatmap(confusion, xlabel='Predicted', ylabel='Actual', title='Confusion Matrix',
                   xticklabels=['1', '2', '3', '4', '5'], yticklabels=['1', '2', '3', '4', '5'],
                   color='Blues', annot=True, fmt='d', dpi=80)
if save:
    plt.savefig("figures/classification/confusion-unweighted.png", transparent=False, bbox_inches="tight")

    
# Plot Distribution for Each Class
star_ratings = range(1, 6)
fig, ax = plot_empty(xlabel='Model Prediction', ylabel='Fraction of Predictions', title='Reviews Assigned Per Class',
                     figsize=(12, 8))
for i in range(5):
    ax.plot(star_ratings, confusion_normalized_array[i], label='{0}-Star'.format(i+1), lw=2)
ax.set_xlim(1, 5)
ax.set_ylim(0, 1)
ax.set_xticks(star_ratings)
plt.legend(loc="lower right")
ax.xaxis.grid(True)
if save:
    plt.savefig("figures/classification/class-predictions-unweighted.png", transparent=False, bbox_inches="tight")
# -

# # Weighted

# +
# Weighted

# Thresholds
count_dict = Counter(target_train_ser)
class_weight = np.array([1/count_dict[1], 1/count_dict[2], 1/count_dict[3], 1/count_dict[4], 1/count_dict[5]])

# Generating new predict and proba variables
features_train_vectorized_proba_array_new = normalize(features_train_vectorized_proba_array * class_weight, axis=1, norm='l1')
features_train_vectorized_predict_array_new = np.argmax(features_train_vectorized_proba_array_new, axis=1)+1 # Needed for confusion matrix
features_test_vectorized_proba_array_new = normalize(features_test_vectorized_proba_array * class_weight, axis=1, norm='l1')
features_test_vectorized_predict_array_new = np.argmax(features_test_vectorized_proba_array_new, axis=1)+1 # Needed for confusion matrix
confusion = confusion_matrix(target_test_ser, features_test_vectorized_predict_array_new)
confusion_normalized_array = normalize(confusion, axis=1, norm='l1')

print("Weighted Micro-Average Training Accuracy: ", f1_score(target_train_ser, features_train_vectorized_predict_array_new, average = "micro"))
print("Weighted Micro-Average Test Accuracy: ", f1_score(target_test_ser, features_test_vectorized_predict_array_new, average = "micro"))
print("Weighted Micro-Average Test Accuracy: ", confusion.trace()/confusion.sum()) # Average Test Accuracy

# Average Test Accuracy to within +/- 1 class
print("Weighted Macro-Average Test Accuracy (+/-1): ", sum([confusion_normalized_array[1:, :-1].trace()/5, confusion_normalized_array.trace()/5, confusion_normalized_array[:-1, 1:].trace()/5]))

# +
# Weighted


# Plot Multiclass Confusion Matrix
fig = plot_heatmap(confusion, xlabel='Predicted', ylabel='Actual', title='Confusion Matrix',
                   xticklabels=['1', '2', '3', '4', '5'], yticklabels=['1', '2', '3', '4', '5'],
                   color='Blues', annot=True, fmt='d', dpi=80)
if save:
    plt.savefig("figures/classification/confusion-weighted.png", transparent=False, bbox_inches="tight")

    
# Plot Distribution for Each Class
star_ratings = range(1, 6)
fig, ax = plot_empty(xlabel='Model Prediction', ylabel='Fraction of Predictions', title='Reviews Assigned Per Class',
                     figsize=(12, 8))
for i in range(5):
    ax.plot(star_ratings, confusion_normalized_array[i], label='{0}-Star'.format(i+1), lw=2)
ax.set_xlim(1, 5)
ax.set_ylim(0, 1)
ax.set_xticks(star_ratings)
plt.legend(loc="lower right")
ax.xaxis.grid(True)
if save:
    plt.savefig("figures/classification/class-predictions-weighted.png", transparent=False, bbox_inches="tight")

# +
# Compute Multiclass ROC
fpr, tpr, thresholds, roc_auc = compute_multiclass_ROC(target_test_bin_array, features_test_vectorized_proba_array_new)

# Plot Micro-ROC curve for Weighted Test set
fig, ax = plot_empty(xlabel='False Positive Rate', ylabel='True Positive Rate', title='ROC Curve',
                     figsize=(12, 8))
for i in range(5):
    plt.plot(fpr[i], tpr[i], label='ROC curve of class {0} (area = {1:1.4f})'.format(i+1, roc_auc[i]), lw=2)
plt.plot(fpr["micro"], tpr["micro"], label='micro-average ROC curve (area = {0:0.4f})'.format(roc_auc["micro"]), lw=2)
plt.plot(fpr["macro"], tpr["macro"], label='macro-average ROC curve (area = {0:0.4f})'.format(roc_auc["macro"]), color='deeppink', lw=2)
plt.plot([0, 1], [0, 1], c='orange', lw = 2, ls='--', label= "Random Guess")
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.legend(loc="lower right")
if save:
    plt.savefig("figures/classification/roc-weighted.png", transparent=False, bbox_inches="tight")
# -

# # Logistic Regression on Text and Categorical Features

# +
# Build the pipeline

count = Pipeline([('selector', TextSelector(key='text_clean')),
    ('count_vectorizer', CountVectorizer(stop_words='english', ngram_range=(1,3)))])
tfidf = Pipeline([('selector', TextSelector(key='text_clean')),
    ('tfidf_vectorizer', TfidfVectorizer(stop_words='english', ngram_range=(1,3)))])
text_length = Pipeline([('selector', NumberSelector(key='text_length')),
    ('scaler', StandardScaler())])
useful = Pipeline([('selector', NumberSelector(key='useful')),
    ('scaler', StandardScaler())])
review_count = Pipeline([('selector', NumberSelector(key='review_count')),
    ('scaler', StandardScaler())])
dogs_allowed = Pipeline([('selector', NumberSelector(key='Dogs Allowed')),
    ('scaler', StandardScaler())])
funny = Pipeline([('selector', NumberSelector(key='funny')),
    ('scaler', StandardScaler())])
open_to_all = Pipeline([('selector', NumberSelector(key='Open to All')),
    ('scaler', StandardScaler())])
accepts_credit_cards = Pipeline([('selector', NumberSelector(key='Accepts Credit Cards')),
    ('scaler', StandardScaler())])
cool = Pipeline([('selector', NumberSelector(key='cool')),
    ('scaler', StandardScaler())])
bike_parking = Pipeline([('selector', NumberSelector(key='Bike Parking')),
    ('scaler', StandardScaler())])
good_for_kids = Pipeline([('selector', NumberSelector(key='Good for Kids')),
    ('scaler', StandardScaler())])
avg_stars = Pipeline([('selector', NumberSelector(key='avg_stars')),
    ('scaler', StandardScaler())])

feats = FeatureUnion([('count', count), 
                      ('tfidf', tfidf), 
                      ('text_length', text_length),
                      ('useful', useful),
                      ('review_count', review_count),
                      ('Dogs Allowed', dogs_allowed),
                      ('funny', funny),
                      ('Open to All', open_to_all),
                      ('Accepts Credit Cards', accepts_credit_cards),
                      ('cool', cool),
                      ('Bike Parking', bike_parking),
                      ('Good for Kids', good_for_kids), 
                      ('avg_stars', avg_stars)])

# Create the pipeline
pipeline = Pipeline([
    ('feats', feats),
    ('logreg_mlt_pipe clf', LogisticRegression(multi_class = 'multinomial', solver = 'newton-cg')),
])

# +
# Process the features
feature_processing = Pipeline([('feats', feats)])
feature_processing.fit_transform(features_train_df)

# Fit, takes a while
pipeline.fit(features_train_df, target_train_ser)
# -

# # Unweighted 

# +
# Unweighted
features_train_vectorized_predict_array = pipeline.predict(features_train_df) # Needed for confusion matrix
features_train_vectorized_proba_array = pipeline.predict_proba(features_train_df)
features_test_vectorized_predict_array = pipeline.predict(features_test_df) # Needed for confusion matrix
features_test_vectorized_proba_array = pipeline.predict_proba(features_test_df)
confusion = confusion_matrix(target_test_ser, features_test_vectorized_predict_array)
confusion_normalized_array = normalize(confusion, axis=1, norm='l1')

print("Unweighted Training Accuracy Score: ", f1_score(target_train_ser, features_train_vectorized_predict_array, average = "micro"))
print("Unweighted Test Accuracy Score: ", f1_score(target_test_ser, features_test_vectorized_predict_array, average = "micro"))
print("Micro-Average Test Accuracy: ", confusion.trace()/confusion.sum()) # Un-weighted accuracy

# Un-weighted accuracy to within +/- 1 class
print("Macro-Average Test Accuracy (+/-1): ", sum([confusion_normalized_array[1:, :-1].trace()/5, confusion_normalized_array.trace()/5, confusion_normalized_array[:-1, 1:].trace()/5]))

# +
# Unweighted


# Plot Multiclass Confusion Matrix
fig = plot_heatmap(confusion, xlabel='Predicted', ylabel='Actual', title='Confusion Matrix',
                   xticklabels=['1', '2', '3', '4', '5'], yticklabels=['1', '2', '3', '4', '5'],
                   color='Blues', annot=True, fmt='d', dpi=80)
if save:
    plt.savefig("figures/feature-union/confusion-unweighted.png", transparent=False, bbox_inches="tight")

    
# Plot Distribution for Each Class
star_ratings = range(1, 6)
fig, ax = plot_empty(xlabel='Model Prediction', ylabel='Fraction of Predictions', title='Reviews Assigned Per Class',
                     figsize=(12, 8))
for i in range(5):
    ax.plot(star_ratings, confusion_normalized_array[i], label='{0}-Star'.format(i+1), lw=2)
ax.set_xlim(1, 5)
ax.set_ylim(0, 1)
ax.set_xticks(star_ratings)
plt.legend(loc="lower right")
ax.xaxis.grid(True)
if save:
    plt.savefig("figures/feature-union/class-prediction-unweighted.png", transparent=False, bbox_inches="tight")
# -

# # Weighted

# +
# Weighted

# New Thresholds
count_dict = Counter(target_train_ser)
threshold_array = np.array([1/count_dict[1], 1/count_dict[2], 1/count_dict[3], 1/count_dict[4], 1/count_dict[5]])

# Generating new predict and proba variables
features_train_vectorized_proba_array_new = normalize(features_train_vectorized_proba_array * threshold_array, axis=1, norm='l1')
features_train_vectorized_predict_array_new = np.argmax(features_train_vectorized_proba_array_new, axis=1)+1 # Needed for confusion matrix
features_test_vectorized_proba_array_new = normalize(features_test_vectorized_proba_array * threshold_array, axis=1, norm='l1')
features_test_vectorized_predict_array_new = np.argmax(features_test_vectorized_proba_array_new, axis=1)+1 # Needed for confusion matrix
confusion = confusion_matrix(target_test_ser, features_test_vectorized_predict_array_new)
confusion_normalized_array = normalize(confusion, axis=1, norm='l1')

print("Weighted Micro-Average Training Accuracy: ", f1_score(target_train_ser, features_train_vectorized_predict_array_new, average = "micro"))
print("Weighted Micro-Average Test Accuracy: ", f1_score(target_test_ser, features_test_vectorized_predict_array_new, average = "micro"))
print("Weighted Micro-Average Test Accuracy: ", confusion.trace()/confusion.sum()) # Average Test Accuracy

# Average Test Accuracy to within +/- 1 class
print("Weighted Macro-Average Test Accuracy (+/-1): ", sum([confusion_normalized_array[1:, :-1].trace()/5, confusion_normalized_array.trace()/5, confusion_normalized_array[:-1, 1:].trace()/5]))

# +
# Reweighted


# Plot Multiclass Confusion Matrix
fig = plot_heatmap(confusion, xlabel='Predicted', ylabel='Actual', title='Confusion Matrix',
                   xticklabels=['1', '2', '3', '4', '5'], yticklabels=['1', '2', '3', '4', '5'],
                   color='Blues', annot=True, fmt='d', dpi=80)
if save:
    plt.savefig("figures/feature-union/confusion-weighted.png", transparent=False, bbox_inches="tight")

    
# Plot Distribution for Each Class
star_ratings = range(1, 6)
fig, ax = plot_empty(xlabel='Model Prediction', ylabel='Fraction of Predictions', title='Reviews Assigned Per Class',
                     figsize=(12, 8))
for i in range(5):
    ax.plot(star_ratings, confusion_normalized_array[i], label='{0}-Star'.format(i+1), lw=2)
ax.set_xlim(1, 5)
ax.set_ylim(0, 1)
ax.set_xticks(star_ratings)
plt.legend(loc="lower right")
ax.xaxis.grid(True)
if save:
    plt.savefig("figures/feature-union/class-predictions-weighted.png", transparent=False, bbox_inches="tight")

# +
# Compute Multiclass ROC for training set
fpr, tpr, thresholds, roc_auc = compute_multiclass_ROC(target_test_bin_array, features_test_vectorized_proba_array_new)

# Plot Micro-ROC curve for Weighted Test set
fig, ax = plot_empty(xlabel='False Positive Rate', ylabel='True Positive Rate', title='ROC Curve',
                     figsize=(12, 8))
for i in range(5):
    plt.plot(fpr[i], tpr[i], label='ROC curve of class {0} (area = {1:1.4f})'.format(i+1, roc_auc[i]), lw=2)
plt.plot(fpr["micro"], tpr["micro"], label='micro-average ROC curve (area = {0:0.4f})'.format(roc_auc["micro"]), lw=2)
plt.plot(fpr["macro"], tpr["macro"], label='macro-average ROC curve (area = {0:0.4f})'.format(roc_auc["macro"]), color='deeppink', lw=2)
plt.plot([0, 1], [0, 1], c='orange', lw = 2, ls='--', label= "Random Guess")
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.legend(loc="lower right")
if save:
    plt.savefig("figures/feature-union/roc-weighted.png", transparent=False, bbox_inches="tight")
# -



StarcoderdataJupyterScriptsDedupFiltered