<jupyter_start><jupyter_text>Loan predictions Problem StatementWe want to automate the loan eligibility process based on customer details that are provided as online application forms are being filled. You can find the dataset [here](https://drive.google.com/file/d/1h_jl9xqqqHflI5PsuiQd_soNYxzFfjKw/view?usp=sharing). These details concern the customer's Gender, Marital Status, Education, Number of Dependents, Income, Loan Amount, Credit History and other things as well. |Variable| Description||: ------------- |:-------------||Loan_ID| Unique Loan ID||Gender| Male/ Female||Married| Applicant married (Y/N)||Dependents| Number of dependents||Education| Applicant Education (Graduate/ Under Graduate)||Self_Employed| Self employed (Y/N)||ApplicantIncome| Applicant income||CoapplicantIncome| Coapplicant income||LoanAmount| Loan amount in thousands||Loan_Amount_Term| Term of loan in months||Credit_History| credit history meets guidelines||Property_Area| Urban/ Semi Urban/ Rural||Loan_Status| Loan approved (Y/N) Explore the problem in following stages:1. Hypothesis Generation – understanding the problem better by brainstorming possible factors that can impact the outcome2. Data Exploration – looking at categorical and continuous feature summaries and making inferences about the data.3. Data Cleaning – imputing missing values in the data and checking for outliers4. Feature Engineering – modifying existing variables and creating new ones for analysis5. Model Building – making predictive models on the data  1. Hypothesis GenerationGenerating a hypothesis is a major step in the process of analyzing data. This involves understanding the problem and formulating a meaningful hypothesis about what could potentially have a good impact on the outcome. This is done BEFORE looking at the data, and we end up creating a laundry list of the different analyses which we can potentially perform if data is available. Possible hypothesesWhich applicants are more likely to get a loan1. Applicants having a credit history 2. Applicants with higher applicant and co-applicant incomes3. Applicants with higher education level4. Properties in urban areas with high growth perspectivesDo more brainstorming and create some hypotheses of your own. Remember that the data might not be sufficient to test all of these, but forming these enables a better understanding of the problem.  2. Data ExplorationLet's do some basic data exploration here and come up with some inferences about the data. Go ahead and try to figure out some irregularities and address them in the next section.<jupyter_code>df = pd.read_csv("data.csv") 
df.head()<jupyter_output><empty_output><jupyter_text>One of the key challenges in any data set are missing values. Lets start by checking which columns contain missing values.<jupyter_code>df.shape
df.isnull().sum()<jupyter_output><empty_output><jupyter_text>Look at some basic statistics for numerical variables.<jupyter_code>df.describe()<jupyter_output><empty_output><jupyter_text>1. How many applicants have a `Credit_History`? (`Credit_History` has value 1 for those who have a credit history and 0 otherwise)<jupyter_code>df.Credit_History.value_counts() # also 50 are null credit history<jupyter_output><empty_output><jupyter_text>2. Is the `ApplicantIncome` distribution in line with your expectation? Similarly, what about `CoapplicantIncome`? No, the range on Applicant Income is much higher than anticipated, both wealthy and poor people looking for loans. Concerning the Coapplicant, seems like they have even less money and wouldn't contribute greatly? Or perhaps they assist the poor applicants? 3. Tip: Can you see a possible skewness in the data by comparing the mean to the median, i.e. the 50% figure of a feature. **Application Income** -- yes, few people making more money and skewing the mean higher Loan Amount affecting Loan Status?<jupyter_code>sns.boxplot(x='Loan_Status', y='LoanAmount', data=df)<jupyter_output><empty_output><jupyter_text>Loan Term affecting Loan Status?<jupyter_code>sns.boxplot(x='Loan_Status', y='Loan_Amount_Term', data=df)<jupyter_output><empty_output><jupyter_text>Property Area affecting Loan Status?<jupyter_code>fig, ax = plt.subplots();
splot = sns.countplot(x='Property_Area', hue='Loan_Status', data=df, ax=ax);
ax.set_xlabel('Property Area');
ax.set_ylabel('Count');
for p in splot.patches:
    splot.annotate(format(p.get_height(), 'd'), 
                   (p.get_x() + p.get_width() / 2., p.get_height()), 
                   ha = 'center', va = 'center', 
                   xytext = (0, -12), 
                   textcoords = 'offset points');
plt.savefig('PropertyArea_LoanStatus_bar.png');
df.Property_Area.value_counts()<jupyter_output><empty_output><jupyter_text>Income affecting Loan Status?<jupyter_code>fig, ax=plt.subplots(figsize=(10,8));
sns.scatterplot(x='ApplicantIncome', y='CoapplicantIncome', hue='Loan_Status', data=df, s=70);
ax.set_xlabel('Applicant Income');
ax.set_ylabel('Co-Applicant Income');
ax.legend(title='Loan Status', fontsize = 'medium');<jupyter_output><empty_output><jupyter_text>Let's discuss nominal (categorical) variable. Look at the number of unique values in each of them.<jupyter_code>import copy
df2 = df.copy(deep=True)
df2['TotalIncome'] = df2['ApplicantIncome'] + df2['CoapplicantIncome']
fig,ax=plt.subplots(figsize=(10,8));
sns.boxplot(x='Loan_Status', y='TotalIncome', data=df2);
ax.set_xlabel('Loan Status');
ax.set_ylabel('Total Income');
style.use('seaborn');
df.dtypes
df.Gender.unique()
df.Gender.value_counts()
df.Married.unique()
df.Married.value_counts()
print(df.Dependents.unique())
print(df.Dependents.value_counts())
plt.pie([345,102,101,51,15],labels=["0","1","2","3+",'Unk'], autopct='%1.1f%%',startangle=90);
plt.title("Dependents");
sns.set(font_scale=1.0); #edited as suggested
df.Education.unique()
df.Education.value_counts()
plt.pie([480,134],labels=['Graduate','Not Graduate'], autopct='%1.1f%%',startangle=90);
#plt.savefig('Education_pie.png')
loan_yes = df[df['Loan_Status']=='Y']
loan_no = df[df['Loan_Status']=='N']
edu_yes = df[df['Education']=='Graduate']
edu_no = df[df['Education']=='Not Graduate']

grad_loan = df[(df['Loan_Status']=='Y')&(df['Education']=='Graduate')]['Loan_ID'].count()
grad_noloan = df[(df['Loan_Status']=='N')&(df['Education']=='Graduate')]['Loan_ID'].count()
notgrad_loan = df[(df['Loan_Status']=='Y')&(df['Education']=='Not Graduate')]['Loan_ID'].count()
notgrad_noloan = df[(df['Loan_Status']=='N')&(df['Education']=='Not Graduate')]['Loan_ID'].count()

print('Graduate + Loan', grad_loan, '; ', round(grad_loan/(grad_loan+grad_noloan)*100,2),'% of Graduates got Loan')
print('Graduate + No Loan', grad_noloan)
print('Not Graduate + Loan', notgrad_loan,'; ', round(notgrad_loan/(notgrad_loan+notgrad_noloan)*100,2),'% of Not Graduates got Loan')

fig = axis.get_figure()
plt.pie([340,140,82,52],labels=["Graduate + Loan","Graduate + No Loan","Not Graduate + Loan","Not Graduate + No Loan"], autopct='%1.1f%%',startangle=90);
plt.title("Education + Loan Status");
sns.set(font_scale=1.0);
sns.boxplot(x='Education', y='TotalIncome', data=df2)
df.Self_Employed.unique()
df.Self_Employed.value_counts()
df.Property_Area.unique()
df.Property_Area.value_counts()
df.Loan_Status.unique()
df.Loan_Status.value_counts()<jupyter_output><empty_output><jupyter_text>Explore further using the frequency of different categories in each nominal variable. Exclude the ID obvious reasons.<jupyter_code>print(f'Percentage of People with Credit History: {round(df[df.Credit_History==1].shape[0]/df.shape[0]*100,2)}%')
df.Credit_History = df.Credit_History.fillna(0)
df.isnull().sum()
filter2 = (df.Credit_History==1) & (df.Loan_Status=='Y')
print(f'Percentage of People with Loan AND Credit History: {round(df[filter2].shape[0]/df.shape[0]*100,2)}%')
filter2 = (df.Credit_History==0) & (df.Loan_Status=='Y')
print(f'Percentage of People with Loan WITHOUT Credit History: {round(df[filter2].shape[0]/df.shape[0]*100,2)}%')
filter2 = (df.Credit_History==0) & (df.Loan_Status=='N')
print(f'Percentage of People WITHOUT Loan WITHOUT Credit History: {round(df[filter2].shape[0]/df.shape[0]*100,2)}%')
filter2 = (df.Credit_History==1) & (df.Loan_Status=='N')
print(f'Percentage of People WITHOUT Loan with Credit History: {round(df[filter2].shape[0]/df.shape[0]*100,2)}%')<jupyter_output>Percentage of People WITHOUT Loan with Credit History: 15.8%<jupyter_text>Distribution analysisStudy distribution of various variables. Plot the histogram of ApplicantIncome, try different number of bins.<jupyter_code>plt.hist(x="ApplicantIncome", data=df, bins=50, color='b');<jupyter_output><empty_output><jupyter_text>Look at box plots to understand the distributions.<jupyter_code>df[['ApplicantIncome','CoapplicantIncome']].boxplot();<jupyter_output><empty_output><jupyter_text>Look at the distribution of income segregated  by `Education`<jupyter_code>import copy
df2 = df.copy(deep=True)
df2['TotalIncome'] = df2['ApplicantIncome'] + df2['CoapplicantIncome']
f, ax = plt.subplots(figsize=(8, 6));
#ax.hist(df2['TotalIncome'], 20, histtype='bar', color='b', label=df2['Education'][0]);
#ax.hist(df2['TotalIncome'], 20, histtype='bar', color='k', label=df2['Education'][1]);
#sns.color_palette("Paired");
style.use('bmh')
sns.histplot(data=df2, x='TotalIncome', hue='Education', stat='count', bins=50, legend=True, ax=ax);
ax.set_xlabel('Total Income');
f, ax = plt.subplots(figsize=(8, 6));
df2.hist(column='TotalIncome', by='Education', bins=20, ax=ax);
sns.set(font_scale=2);<jupyter_output><empty_output><jupyter_text>Look at the histogram and boxplot of LoanAmount<jupyter_code>df.boxplot(column=['LoanAmount']);
df.LoanAmount.hist();<jupyter_output><empty_output><jupyter_text>There might be some extreme values. Both `ApplicantIncome` and `LoanAmount` require some amount of data munging. `LoanAmount` has missing and well as extreme values values, while `ApplicantIncome` has a few extreme values, which demand deeper understanding.<jupyter_code>sns.set_theme(style="whitegrid");
f, ax = plt.subplots(figsize=(6.5, 6.5));
sns.scatterplot(x='ApplicantIncome', y='LoanAmount', hue='Loan_Status', data=df, ax=ax);
ax.set_xlim(0,90000);
ax.set_ylim(0,800);
ax.set_xlabel('Applicant Income');
ax.set_ylabel('Loan Amount');
sns.set_theme(style="whitegrid");
f, ax = plt.subplots(figsize=(6.5, 6.5));
sns.scatterplot(x='ApplicantIncome', y='LoanAmount', hue='Loan_Status', data=df, ax=ax);
ax.set_xlim(0,90000);
ax.set_ylim(0,800);
ax.set_xlabel('Applicant Income');
ax.set_ylabel('Loan Amount');<jupyter_output><empty_output><jupyter_text>Categorical variable analysisTry to understand categorical variables in more details using `pandas.DataFrame.pivot_table` and some visualizations.  3. Data CleaningThis step typically involves imputing missing values and treating outliers.   Imputing Missing ValuesMissing values may not always be NaNs. For instance, the `Loan_Amount_Term` might be 0, which does not make sense. Impute missing values for all columns. Use the values which you find most meaningful (mean, mode, median, zero.... maybe different mean values for different groups)<jupyter_code>#df['Loan_Amount_Term'].dropna(inplace=True)
#df.Married = df.Married.fillna('No')
#df.Gender
#df.dropna(inplace=True) # don't do this for future runs, but for now let's get to the modeling!
#df.Self_Employed.fillna('No',inplace=True)
#df[df.Loan_Status=='N'].LoanAmount.dropna(inplace=True)
#df.Credit_History.fillna(0, inplace=True)
df.shape<jupyter_output><empty_output><jupyter_text>Extreme valuesTry a log transformation to get rid of the extreme values in `LoanAmount`. Plot the histogram before and after the transformation<jupyter_code>df.LoanAmount.hist();
df.LoanAmount = df.LoanAmount.apply(np.log)
df.LoanAmount.hist();<jupyter_output><empty_output><jupyter_text>Combine both incomes as total income and take a log transformation of the same.<jupyter_code>#df['TotalIncome'] = df.ApplicantIncome + df.CoapplicantIncome
#df.TotalIncome = df.TotalIncome.apply(np.log)
#df.head()
df.shape
df.info()<jupyter_output><empty_output><jupyter_text>4. Building a Predictive Model<jupyter_code>from IPython.core.interactiveshell import InteractiveShell
InteractiveShell.ast_node_interactivity = "all"
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from matplotlib import style
style.use('fivethirtyeight')

# reload dataframe to start from here when restarting kernel
df = pd.read_csv("data.csv") 
df.head()<jupyter_output><empty_output><jupyter_text>Split features based on categorical or numerical basis for different processing in pipeline<jupyter_code>cat_feats = df.dtypes[df.dtypes == 'object'].index.tolist()
num_feats = df.dtypes[~df.dtypes.index.isin(cat_feats)].index.tolist()<jupyter_output><empty_output><jupyter_text>Transform object columns into category columns<jupyter_code>for col in cat_feats:
    df[col] = df[col].astype('category')
    
cat_feats.remove('Loan_Status') # since this will not be a feature
cat_feats.remove("Loan_ID") # since this is not needed for the model<jupyter_output><empty_output><jupyter_text>Determine X and y, split into Test and Train sets<jupyter_code>X = df.drop(columns=['Loan_Status','Loan_ID'])
y = df['Loan_Status']

from sklearn.model_selection import train_test_split
test_size = 0.2
seed = 7
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=test_size, random_state=seed)<jupyter_output><empty_output><jupyter_text>Build Pipelines<jupyter_code>from sklearn.pipeline import Pipeline, FeatureUnion
# numerical preprocessing
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import SelectKBest
# categorical preprocessing
from sklearn.preprocessing import OneHotEncoder
from sklearn.decomposition import PCA
# models?
from sklearn.linear_model import LogisticRegression

import warnings 
warnings.filterwarnings('ignore')
# create new column given df
def totalIncome(dataset):
    dataset['TotalIncome'] = dataset['ApplicantIncome'] + dataset['CoapplicantIncome']
    return dataset

# allow function transformation on df column
class DataframeFunctionTransformer():
    def __init__(self, func):
        self.func = func

    def transform(self, input_df, **transform_params):
        return self.func(input_df)

    def fit(self, X, y=None, **fit_params):
        return self

# log transform given columns
def LogTransform(dataset):
    dataset = dataset.assign(log_TotalIncome = np.log1p(dataset['TotalIncome']))
    dataset = dataset.assign(log_LoanAmount = np.log1p(dataset['LoanAmount']))
    dataset = dataset.drop(columns=['TotalIncome','LoanAmount'])
#    dataset['TotalIncome'].assign() = dataset['TotalIncome'].apply(np.log)
#    dataset['LoanAmount'] = dataset['LoanAmount'].apply(np.log)
    return dataset

# don't forget ToDenseTransformer after one hot encoder
class ToDenseTransformer():
    # here you define the operation it should perform
    def transform(self, X, y=None, **fit_params):
        return X.todense()
    # just return self
    def fit(self, X, y=None, **fit_params):
        return self

# select specific columns to perform pipeline onto
class SelectColumnsTransformer():
    def __init__(self, columns=None):
        self.columns = columns

    def transform(self, X, **transform_params):
        cpy_df = X[self.columns].copy()
        return cpy_df

    def fit(self, X, y=None, **fit_params):
        return self<jupyter_output><empty_output><jupyter_text>Pre-processing Pipelines<jupyter_code># NUMERICAL --> select k best columns to be tuned later
## different columns need different NaN treatments
nullnumTransform = ColumnTransformer([
    ('impute_zero', SimpleImputer(strategy='constant', fill_value=0), ['Credit_History','log_LoanAmount']), # if empty, assume not-applicable = 'No'
    ('impute_mean', SimpleImputer(strategy='mean'), ['Loan_Amount_Term'])], remainder='passthrough') # assume average loan information if not given
## scale other columns; do not need treatment for 0/1 columns like Credit_History
scalerTransform = ColumnTransformer([('std_scaler', StandardScaler(), [0,1,2])], remainder='passthrough')
## put numerical pre-processing together
numerical = Pipeline([
    ('totalincome', DataframeFunctionTransformer(totalIncome)),
    ('logtransform', DataframeFunctionTransformer(LogTransform)),
    ('selector', SelectColumnsTransformer(['ApplicantIncome', 'CoapplicantIncome', 'Loan_Amount_Term', 'Credit_History','log_TotalIncome','log_LoanAmount'])),
    ('null_fill', nullnumTransform),
    ('standard_scaler', scalerTransform),
    ('kbest', SelectKBest(k=3))])

# CATEGORICAL --> PCA 3 components to be tuned later
## different columns need different NaN treatments
nullcatTransform = ColumnTransformer([
    ('impute_no', SimpleImputer(strategy='constant', fill_value='No'), ['Married','Self_Employed']), # if empty, assume not-applicable = 'No'
    ('impute_zero', SimpleImputer(strategy='constant', fill_value='0'), ['Dependents']), # needs to be string of numbers: '0', '1', '2' or '3+'
    ('impute_mode', SimpleImputer(strategy='most_frequent'), ['Gender'])], remainder='passthrough') # no way to otherwise tell, give most common gender back
## put categorical pre-processing together
categorical = Pipeline([
    ('selector', SelectColumnsTransformer(cat_feats)),
    ('null_fill', nullcatTransform),
    ('encode', OneHotEncoder()),
    ('to_dense',ToDenseTransformer()),
    ('pca', PCA(n_components=3))])

# define which transformer applies to which columns
preprocess = FeatureUnion([
    ('categorical', categorical),
    ('numerical', numerical)])
pipeline = Pipeline([
    ('preprocess', preprocess),
    ('model', LogisticRegression())])
from sklearn import set_config
set_config(display='diagram')
pipeline.fit(X_train, y_train)
print("model score: %.2f%%" % (pipeline.score(X_test, y_test)*100))
from sklearn.utils import estimator_html_repr

with open('model_pipeline.html', 'w') as f:  
    f.write(estimator_html_repr(pipeline))<jupyter_output><empty_output><jupyter_text>Try paramater grid search to improve the results<jupyter_code>from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import CategoricalNB
from xgboost import XGBClassifier
from sklearn.model_selection import GridSearchCV
import warnings 
warnings.filterwarnings('ignore')
import time<jupyter_output><empty_output><jupyter_text>Other Models than LR?<jupyter_code>%%time
# Find the best hyperparameters using GridSearchCV on the train set
param_grid = {'preprocess__numerical__kbest__k': [1, 3, 5], 
              'preprocess__categorical__pca__n_components': [1, 3, 5],
              'model': [LogisticRegression(), RandomForestClassifier(), GradientBoostingClassifier(), KNeighborsClassifier(), CategoricalNB(), XGBClassifier()]}
grid = GridSearchCV(pipeline, param_grid=param_grid, cv=5)
grid.fit(X_train, y_train)

best_model = grid.best_estimator_
best_hyperparams = grid.best_params_
best_acc = grid.score(X_test, y_test)
print(f'Best test set accuracy: {best_acc}\nAchieved with hyperparameters: {best_hyperparams}')<jupyter_output>[15:38:41] WARNING: ../src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[15:38:41] WARNING: ../src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[15:38:41] WARNING: ../src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old behavior.
[15:38:41] WARNING: ../src/learner.cc:1061: Starting in XGBoost 1.3.0, the default evaluation metric used with the objective 'binary:logistic' was changed from 'error' to 'logloss'. Explicitly set eval_metric if you'd like to restore the old be[...]<jupyter_text>Log Reg is best, can we fine tune within LR?<jupyter_code>%%time
# Find the best hyperparameters using GridSearchCV on the train set
param_grid = {'preprocess__numerical__kbest__k': [1, 2, 3], 
              'preprocess__categorical__pca__n_components': [1, 2, 3, 4],
              'model__solver': ['newton-cg', 'lbfgs', 'liblinear', 'sag', 'saga'],
              'model__penalty': ['none', 'l1', 'l2', 'elasticnet'],
              'model__C': [100, 10, 1.0, 0.1, 0.01]}
grid = GridSearchCV(pipeline, param_grid=param_grid, cv=5)
grid.fit(X_train, y_train)

best_model = grid.best_estimator_
best_hyperparams = grid.best_params_
best_acc = grid.score(X_test, y_test)
print(f'Best test set accuracy: {best_acc}\nAchieved with hyperparameters: {best_hyperparams}')<jupyter_output>Best test set accuracy: 0.8048780487804879
Achieved with hyperparameters: {'model__C': 0.1, 'model__penalty': 'l2', 'model__solver': 'liblinear', 'preprocess__categorical__pca__n_components': 3, 'preprocess__numerical__kbest__k': 1}
CPU times: user 21min 56s, sys: 22 s, total: 22min 18s
Wall time: 5min 36s<jupyter_text>Alright let's take hold of this best model parameters!<jupyter_code>import pickle
filename = 'xtrain_logregmodel.sav'
#pickle.dump(best_model, open(filename, 'wb'))
loaded_model = pickle.load(open(filename, 'rb'))
# saving the columns
#model_columns = list(X.columns)
#with open('model_columns.pkl','wb') as file:
#    pickle.dump(model_columns, file)
y_pred = loaded_model.predict(X_test)
y_pred_proba = loaded_model.predict_proba(X_test)[:, 1]
result = loaded_model.score(X_test, y_test)
print(result)
from sklearn.metrics import accuracy_score, roc_auc_score, plot_confusion_matrix
print('Accuracy: ', round(accuracy_score(y_test, y_pred)*100,2),'%')
print('AUC Score: ', round(roc_auc_score(y_test, y_pred_proba)*100,2),'%')
fig, ax = plt.subplots(figsize=(15, 15));
print('Confusion Matrix:');
cm = plot_confusion_matrix(loaded_model, X_test, y_test, cmap=plt.cm.Blues, ax=ax);
sns.set(font_scale=3.0); #edited as suggested
#plt.savefig('confusion_matrix.png');<jupyter_output>Accuracy:  80.49 %
AUC Score:  78.5 %
Confusion Matrix:<jupyter_text>5. Using PipelineIf you didn't use pipelines before, transform your data prep, feat. engineering and modeling steps into Pipeline. It will be helpful for deployment.The goal here is to create the pipeline that will take one row of our dataset and predict the probability of being granted a loan.`pipeline.predict(x)`<jupyter_code>df.head(1)<jupyter_output><empty_output><jupyter_text>6. Deploy your model to cloud and test it with PostMan, BASH or Python<jupyter_code>json_data = {
 'Gender': 'Female',
 'Married': 'No',
 'Dependents': '1',
 'Education': 'Graduate',
 'Self_Employed': 'Yes',
 'ApplicantIncome': 25849,
 'CoapplicantIncome': 0.0,
 'LoanAmount': 300.0,
 'Loan_Amount_Term': 24.0,
 'Credit_History': 1.0,
 'Property_Area': 'Urban'
            }
query = [{
        "Loan_ID": "LP001001",
        "Gender": "Male",
        "Married": "No",
        "Dependents": "3+",
        "Education": "Not Graduate",
        "Self_Employed": "No",
        "ApplicantIncome": 5849,
        "CoapplicantIncome": 0.0,
        "LoanAmount": 300.0,
        "Loan_Amount_Term": 24.0,
        "Credit_History": 1.0,
        "Property_Area": "Rural"}]
import requests
URL = "http://ec2-18-189-186-127.us-east-2.compute.amazonaws.com:5555/predict"
# sending get request and saving the response as response object 
r = requests.post(url = URL, json = json_data) 
print(r.json())<jupyter_output>{'loan status': "['Y']"}
{'fraction_non_alphanumeric': 0.1300795978215333, 'fraction_numerical': 0.01776288227901131, 'mean_word_length': 6.483072100313479, 'pattern_counts': {'":': 12, '<': 116, '<?xml version=': 0, '>': 118, 'https://': 1, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '30', 'dir': 'jupyter-structured-clean-dedup', 'id': '339893', 'license': 'MIT', 'n_tokens_mistral': 8194, 'n_tokens_neox': 7690, 'n_words': 2011, 'path': 'mini-project-IV.ipynb', 'repo_name': 'robynmundle/loan_status_predictions', 'size': '23870'}