<jupyter_start><jupyter_text>Maximizing the Opportunity to be Hired  Business UnderstandingAs a marketplace connecting freelance labor with local demand, the company is not only building a digital supply chain network, but also creating a reliable ecosystem in your local community. Thus, to increase the supply chain sustainability, we need to increase the success in the matching process - allowing consumers to find immediate help and allowing taskers to be selected more frequently.Currently, whenever a Client searches for Taskers, the customer will be given a recommendation ofup to 15 Taskers from which the Client can choose one to book. In this project, I want to understand what makes a Client choose a tasker, and thus how can a Tasker maximize the opportunity to be hired. Data PreparationThe sample dataset contains information about "recommendations". For each recommendation, the position of the Tasker in the recommendation set, the hourly rate for the Tasker when they were shown, the number of Tasks the Tasker had completed in that category, whether the Tasker was hired or not, and the category of work the Client needs help with are collected.Since every recommendation has 1 “hired” and 14 “not hired”, the dataset is highly imbalanced. This will cause bias in the classification model. To solve this problem, I oversampled the minority class by duplicating “hired” entries 14 times.  Import Packages<jupyter_code>import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
%matplotlib inline

from sklearn.ensemble import RandomForestClassifier
from sklearn import model_selection
from sklearn.model_selection import train_test_split, GridSearchCV, RandomizedSearchCV
from yellowbrick.classifier import ConfusionMatrix
from sklearn.metrics import classification_report
from yellowbrick.classifier import ConfusionMatrix, ROCAUC

from tpot import TPOTClassifier

import keras
from keras.layers import Dense
from keras.models import Sequential
from keras.utils import to_categorical
from keras.callbacks import EarlyStopping

import warnings
warnings.filterwarnings("ignore")<jupyter_output><empty_output><jupyter_text>Data Preparation<jupyter_code>data = pd.read_csv("sample.csv")
data.head()
hired = data[data['hired']==1]
hired.head()
data = data.append([hired]*14,ignore_index=True)
data.describe()<jupyter_output><empty_output><jupyter_text>Data Exploration: Overall After preparing the data, I first looked at the overall correlation between variables using a correlation heat map. From the heat map, we can see that whether the Tasker was hired was negatively correlated with position and hourly rate, and positively correlated with the number of tasks completed. These relationships are more clearly shown in the three bar plots below.<jupyter_code>corr = data[['position','hourly_rate','num_completed_tasks','hired']].corr()
sns.heatmap(corr, 
            xticklabels=corr.columns.values,
            yticklabels=corr.columns.values, 
            annot=True, linewidths=0.05, fmt= '.2f', cmap='YlGnBu')
sns.barplot(x="hired", y="hourly_rate", data=data)
sns.barplot(x="hired", y="num_completed_tasks", data=data)
sns.barplot(x="hired", y="position", data=data)<jupyter_output><empty_output><jupyter_text>Explore Three Categories There are three categories of work in the dataset. For each category, the ranges of hourly rate and number of Tasks completed varied. Thus, to increase the modeling accuracy, I partitioned the dataset on category and performed analysis within each category.<jupyter_code>data.category.unique()
assembly = data[data["category"]=="Furniture Assembly"]
moving = data[data["category"]=="Moving Help"]
mounting = data[data["category"]=="Mounting"]
plt.subplots(figsize =(10, 5)) 
sns.distplot(assembly['hourly_rate'] , color='red', label='assembly')
sns.distplot(moving['hourly_rate'] , color='green', label='moving')
sns.distplot(mounting['hourly_rate'] , color='yellow', label='mounting')
plt.suptitle('Hourly Rate vs Hired')
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>Data Exploration: Furniture Assembly Category as an Example I used “Furniture Assembly” category as an example in this project. A correlation heat map for this category is created which shows similar patterns as the overall data set.I took out the three features to look at their relationships with “hired or not”. From the three graphs below, we can see that taskers positioned at the top and with lower hourly rate and more tasks completed are more likely to be hired.<jupyter_code>corr = assembly[['position','hourly_rate','num_completed_tasks','hired']].corr()
sns.heatmap(corr, 
            xticklabels=corr.columns.values,
            yticklabels=corr.columns.values, 
            annot=True, linewidths=0.05, fmt= '.2f', cmap='YlGnBu')
hired=data.loc[:,'hired']==1
not_hired=data.loc[:,'hired']==0
plt.subplots(figsize =(10, 5)) 
sns.distplot(assembly[hired]['position'] , color='lightgreen', label='hired')
sns.distplot(assembly[not_hired]['position'] , color='red', label='not hired')
plt.suptitle('Position vs Hired')
plt.legend()
plt.show()
plt.subplots(figsize =(10, 5)) 
sns.distplot(assembly[hired]['num_completed_tasks'] , color='lightgreen', label='hired')
sns.distplot(assembly[not_hired]['num_completed_tasks'] , color='red', label='not hired')
plt.suptitle('Number of Tasks Completed vs Hired')
plt.legend()
plt.show()
plt.subplots(figsize =(10, 5)) 
sns.distplot(assembly[hired]['hourly_rate'] , color='lightgreen', label='hired')
sns.distplot(assembly[not_hired]['hourly_rate'] , color='red', label='not hired')
plt.suptitle('Hourly Rate vs Hired')
plt.legend()
plt.show()<jupyter_output><empty_output><jupyter_text>Modeling & Evaluation Three classification models are used in my analysis to capture the relationships between tasker features and hiring decision,<jupyter_code># Seperate features and target
X=assembly[['position','hourly_rate','num_completed_tasks']]
y=assembly['hired']

# Train/Test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=100, stratify=y)<jupyter_output><empty_output><jupyter_text>Model 1: Random Forest with GridSearch I first used Random Forest due to its robustness in handling bias, outliers and unbalanced data. Additionally, I applied GridSearch to find the best combination of parameters for the model, which achieves 0.896 accuracy, 0.89 precision and 0.9 recall on the testing set in predicting “hired”. The classification report, confusion matrix, and ROC curves are shown below.  Set up hyperparameter grid<jupyter_code>param_grid = {'n_estimators': [200,300,400,500],
    'max_features': ['auto', 'sqrt', 'log2'],
    'max_depth' : np.arange(3,10),
    'min_samples_leaf': np.arange(2,8),
    'criterion' :['gini', 'entropy']}<jupyter_output><empty_output><jupyter_text>GridSearch for best parameters<jupyter_code>rfc=RandomForestClassifier(random_state=1)
rfc_cv = RandomizedSearchCV(rfc, param_grid, cv=10)
rfc_cv.fit(X_train, y_train)
rfc_cv.best_params_<jupyter_output><empty_output><jupyter_text>Use the best parameters to predict<jupyter_code>rfc_cv.score(X_test,y_test)
cm = ConfusionMatrix(rfc_cv, classes=[0,1])
cm.fit(X_train, y_train)
cm.score(X_test, y_test)
cm.poof()
y_pred = rfc_cv.predict(X_test)
print(classification_report(y_test, y_pred))
# Instantiate the visualizer with the classification model
visualizer = ROCAUC(rfc_cv)

visualizer.fit(X_train, y_train)  # Fit the training data to the visualizer
visualizer.score(X_test, y_test)  # Evaluate the model on the test data
g = visualizer.poof()             # Draw/show/poof the data
best_rfc = RandomForestClassifier(criterion='entropy', max_depth=9, max_features='auto', min_samples_leaf=2, n_estimators=400, random_state=1)
best_rfc = best_rfc.fit(X_train,y_train)
list(zip(X,best_rfc.feature_importances_))<jupyter_output><empty_output><jupyter_text>Model 2: Auto ML with TPOT I then used Auto ML due to its power in feature engineering, model selection, and parameter optimization. This model achieves 0.969 accuracy, 0.94 precision and 1 recall on the testing set in predicting “hired”.<jupyter_code>tpot = TPOTClassifier(generations=10)
tpot.fit(X_train, y_train)
tpot.score(X_test,y_test)
y_pred = tpot.predict(X_test)
print(classification_report(y_test, y_pred))<jupyter_output>precision    recall  f1-score   support

           0       1.00      0.94      0.97      2829
           1       0.94      1.00      0.97      2574

    accuracy                           0.97      5403
   macro avg       0.97      0.97      0.97      5403
weighted avg       0.97      0.97      0.97      5403<jupyter_text>Model 3: Neural Network Both Random Forest and Auto ML model have high accuracy in predicting hiring decision in this sample data (30,000 data points). However, as the data size and complexity increases, these two models will not have enough computational power to work on big data. In this case, a neural network model will be a great choice.Using this data set, I built a neural network model. Since the sample data set is relatively small and clean, setting more layers or nodes might cause overfitting. Thus, I specified only two layers and 50 nodes in each layer. As more and more tasker features collected, we need to tune the parameters accordingly to increase the prediction power.Another reason to use neural network is that it not only can predict whether the tasker will be hired, but also can estimate their probability of being hired. As shown in the table below, we can see that for tasker hired, the predicted probability is very high (0.98). For those not hired, we can also see their probability to be hired.<jupyter_code># Specify input
n_cols = X_train.shape[1]
input_shape = (n_cols,)
target = to_categorical(y_train)

# Specify the model
model = Sequential()
model.add(Dense(50, activation='relu', input_shape = input_shape))
model.add(Dense(50, activation='relu'))
model.add(Dense(2, activation='softmax'))

# Compile the model
model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=['accuracy'])

# Define early_stopping_monitor
early_stopping_monitor = EarlyStopping(patience=2)

# Fit the model
model.fit(X_train, target, epochs=10, validation_split=0.3, callbacks=[early_stopping_monitor])
predictions = model.predict(X_test)
predictions[:,1]
proba_hired = pd.concat([X_test, y_test], axis=1)
proba_hired["probability_hired"] = predictions[:,1]
proba_hired.head()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.09335744194899125, 'fraction_numerical': 0.01655881233346022, 'mean_word_length': 5.806347150259067, 'pattern_counts': {'":': 0, '<': 44, '<?xml version=': 0, '>': 44, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '213979', 'license': 'MIT', 'n_tokens_mistral': 3310, 'n_tokens_neox': 3110, 'n_words': 1069, 'path': 'Hiring_Opportunity_Prediction.ipynb', 'repo_name': 'BeverlyHuang/hiring_opportunity_prediction', 'size': '10508'}