<jupyter_start><jupyter_text>Table of contents 1. EDA - Multicollinearity and correlations - Check data distribuitions 2. Preprocessing - Train test split - One-hot-encoding - Scaling - Craete lists to add models and reports   3. Applying different models - Baseline: guessing majority class - LR on all features - Compare with StatsModels - LR with the coefficients that have p-values  - Random Forests - XGBoost - Gradient Boosting  4. Correcting class imbalance with rejected dataset - Random Forests - LR  - Gradient Boosting - XGBoost  5. Correcting class imbalance through resampling - Random Forests - Gradient Boosting - XGBoost - LR   6. Models comparison  7. Further exploration of the selected Logistic Regression model (lr4)  - Cross-validated log loss and AUC - Threshold variation - ROC-AUC visualization - Model coefficients   8. Making predictions with lr4 model  9. Checking lr4 model's calibation - Check model's calibration of training and testing set - Cross-validate calibration - Plot cross-validated calibration<jupyter_code>import pandas as pd
import matplotlib.pyplot as plt
from pandas.plotting import scatter_matrix
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.model_selection import cross_val_score, KFold
from sklearn.metrics import confusion_matrix
from sklearn.svm import SVC
from sklearn.tree import DecisionTreeClassifier
from sklearn.pipeline import make_pipeline, FeatureUnion, Pipeline
from sklearn.preprocessing import OneHotEncoder, Imputer, StandardScaler
import random
import seaborn as sns
from sklearn_pandas.pipeline import Pipeline
from sklearn_pandas import DataFrameMapper
from sklearn.preprocessing import FunctionTransformer
from sklearn.linear_model import LogisticRegression
import statsmodels.api as sm
from functions import (ohe, logireg, gb, xgb, rf, predict_probability_and_class, check_calibration,
                       keep_states_drop, preprocess, scale_columns, parse_reports, get_df_coef,
                      cv_calibration, guess_maj_class_cv_auc)
from sklearn.metrics import (roc_curve, auc, precision_recall_curve, make_scorer, 
recall_score, accuracy_score, precision_score, confusion_matrix)

from sklearn.ensemble import GradientBoostingClassifier, RandomForestClassifier
from xgboost import XGBClassifier
from sklearn.utils import resample
import pickle
import scikitplot as skplt
from plr.predict import predict_p

%matplotlib inline
%load_ext autoreload
%autoreload 2
#had to install xgboost
#!pip install xgboost<jupyter_output><empty_output><jupyter_text>Load data<jupyter_code>df_approved = pd.read_csv('data/approved_loan_data.csv', index_col=False)
df_rejected = pd.read_csv('data/rejected_loan_data.csv', index_col=False)<jupyter_output><empty_output><jupyter_text>Table of contents 1. EDA<jupyter_code>len(df_approved)
len(df_rejected)
#How many loans were returned?

df_approved['status'].value_counts()<jupyter_output><empty_output><jupyter_text>75% of application from the approved set were paid pack<jupyter_code>#Are the 2 dataframes's columns similar?

df_rejected.columns == df_approved.columns
#What are the columns in the dataframes?
df_approved.columns
df_approved.shape
df_approved.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 4145 entries, 0 to 4144
Data columns (total 38 columns):
Unnamed: 0                           4145 non-null int64
monthly_income                       4145 non-null float64
gig_economy_weekly_income            4145 non-null float64
atm_check_deposits_weekly_income     4145 non-null float64
direct_deposit_weekly_income         4145 non-null float64
government_aid_weekly_income         4145 non-null float64
frequency_heuristic_weekly_income    4145 non-null float64
large_deposits_weekly_income         4145 non-null float64
income_length                        4145 non-null int64
payrolls_per_month                   4145 non-null float64
income_sources                       4145 non-null int64
days_since_most_recent_payroll       4145 non-null int64
days_until_next_payroll              4145 non-null int64
bank_balance                         4145 non-null float64
overdraft_transactions               4145 non-null int64
nsf_transactions    [...]<jupyter_text>There are no null values and all essential data is numeric, which helps a lot with the analysis process.<jupyter_code>df_rejected.info()<jupyter_output><class 'pandas.core.frame.DataFrame'>
RangeIndex: 2927 entries, 0 to 2926
Data columns (total 38 columns):
Unnamed: 0                           2927 non-null int64
monthly_income                       2927 non-null float64
gig_economy_weekly_income            2927 non-null float64
atm_check_deposits_weekly_income     2927 non-null float64
direct_deposit_weekly_income         2927 non-null float64
government_aid_weekly_income         2927 non-null float64
frequency_heuristic_weekly_income    2927 non-null float64
large_deposits_weekly_income         2927 non-null float64
income_length                        2927 non-null int64
payrolls_per_month                   2927 non-null float64
income_sources                       2927 non-null int64
days_since_most_recent_payroll       2927 non-null int64
days_until_next_payroll              2927 non-null int64
bank_balance                         2927 non-null float64
overdraft_transactions               2927 non-null int64
nsf_transactions    [...]<jupyter_text>There are null values only in 'state' column. Essential data is numeric I decided to drop states that do not apperar in approved datasets and rejected dataset together. The idea is to be able to test the rejected dataset on the model created with approved application. I will also drop rows in rejected application where state is not specified. Table of contents Multicollinearity and correlations<jupyter_code>sns.set(rc={'figure.figsize':(16,16)})
sns.heatmap(df_approved.corr(), center=0);<jupyter_output><empty_output><jupyter_text>It is clear that there is some data that reflects event after granting the loan ('loan_amount', 'loan_borrowed', etc.).There is also hight correlations between length of income and length of transaction history, which asre clearly connected.<jupyter_code>#search where correlation is greater than 0.7
correlations = df_approved.corr().applymap(lambda x: 'yes' if (abs(x) > 0.7) else x)
dfs = []
for name in correlations.columns:
    if len(correlations.loc[correlations[name]=='yes']) > 1:
        dfs.append(correlations.loc[correlations[name]=='yes'])
len(dfs)  #6      
# I checked all dfs to see where are the correlations
# dfs[5].T<jupyter_output><empty_output><jupyter_text>Apart from obvious correlation between features that describe loan after it was granted, the high correlation between `income_length` and `length_of_transaction_history` was confirmed. Table of contents Check data distributions<jupyter_code>#Cannot print out the result for the reasons of confidentiality

#df_approved.hist();<jupyter_output><empty_output><jupyter_text>Distribution is a bit confusing because of the scale of many variables. Some seem to have only 0 values, but it is not the case, as small values are not seen in the histograms. Table of contents 2. Preprocessing1. Drop `Unnamed: 0`, `loan_id` columns2. Drop columns asocciated with granting the loan: `loan_amount`, `loan_borrowed_inc`, `loan_outstanding_inc`3. Drop `length_of_transaction_history` because of correlation with `income_length`2. Leave only states: 'LA', 'NH', 'NJ', 'ND'<jupyter_code>df_approved = preprocess(df_approved)
df_rejected = preprocess(df_rejected)<jupyter_output><empty_output><jupyter_text>Table of contents Train test splitI will work mainly with approved loans dataset<jupyter_code>random.seed(113)
y = df_approved['status']
X = df_approved.drop(['status'], axis=1)

#to maintain time order of the appointment date, shuffle is set to False
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=113)<jupyter_output><empty_output><jupyter_text>Table of contents One-hot-encoding<jupyter_code>X_train = ohe(X_train, 'state')
X_test = ohe(X_test, 'state')
#reset indexes of y-values too (Xs were reset during ohe)
y_train = y_train.reset_index(drop=True)
y_test = y_test.reset_index(drop=True)
#rejected dataset one-hot-encoding
df_rejected = ohe(df_rejected, 'state')<jupyter_output><empty_output><jupyter_text>Table of contents Scaling Without scaling I could not get consistent coefficient among Logistic Regression model from Sklearn and StatsModels.<jupyter_code>#scale X_train
X_train_scaled, ss = scale_columns(X_train)
#scale X_test
X_test_scaled, ss = scale_columns(X_test, ss)<jupyter_output><empty_output><jupyter_text>Table of contents Create lists to add models and reports<jupyter_code>models = []
reports = []<jupyter_output><empty_output><jupyter_text>Table of contents 3. Trying different models Table of contents Baseline: guessing majority class<jupyter_code>guess_maj_class_cv_auc(X_train, X_test, y_train, y_test)<jupyter_output><empty_output><jupyter_text>**Baseline AUC is 0.5** Table of contents Logistic Regression on all the features<jupyter_code>lr1, report_lr1 = logireg(X_train_scaled, X_test_scaled, y_train, y_test)
models.append(lr1)
reports.append(report_lr1)
#extract coefficients
features = get_df_coef(lr1, X_test)
features<jupyter_output><empty_output><jupyter_text>Table of contents Compare with StatsModels model<jupyter_code>X_train_scaled.info()

logit_model=sm.Logit(y_train, X_train_scaled, random_state=113)
result=logit_model.fit(maxiter=100)
print(result.summary())
# TODO(AnnaZubova):  troubleshoot warning<jupyter_output><empty_output><jupyter_text>Table of contents Logistic Regression with the coefficients that have p-values < 0.05<jupyter_code>X_train_mod = X_train[['bank_balance', 'return_transactions', 'auto_loan', 'student_loan',
                                     'predatory_installment_loan', 'pay_advances', 'no_of_other_active_loans', 
                                       'traditional_single_payment_mean', 'loan_requested', 'state_\"LA\"',
                                       'state_\"NH\"', 'state_\"NJ\"']]
X_test_mod = X_test[['bank_balance', 'return_transactions', 'auto_loan', 'student_loan',
                                     'predatory_installment_loan', 'pay_advances', 'no_of_other_active_loans', 
                                       'traditional_single_payment_mean', 'loan_requested', 'state_\"LA\"',
                                       'state_\"NH\"', 'state_\"NJ\"']]
#fit and transform train
X_train_mod_scaled, ss = scale_columns(X_train_mod)
#transform test set
X_test_mod_scaled, ss = scale_columns(X_test_mod, ss)
lr2, report_lr2 = logireg(X_train_mod_scaled, X_test_mod_scaled, y_train, y_test)
models.append(lr2)
reports.append(report_lr2)
#extract coefficients
features = get_df_coef(lr2, X_test_mod)
features
logit_model=sm.Logit(y_train, X_train_mod_scaled, random_state=113)
result=logit_model.fit(maxiter=100)
print(result.summary())<jupyter_output>Optimization terminated successfully.
         Current function value: 0.471676
         Iterations 8
                           Logit Regression Results                           
==============================================================================
Dep. Variable:                 status   No. Observations:                 3313
Model:                          Logit   Df Residuals:                     3301
Method:                           MLE   Df Model:                           11
Date:                Tue, 09 Jul 2019   Pseudo R-squ.:                 0.04065
Time:                        12:41:48   Log-Likelihood:                -1562.7
converged:                       True   LL-Null:                       -1628.9
                                        LLR p-value:                 5.629e-23
===================================================================================================
                                      coef    std err          z      P>|z|      [0.025[...]<jupyter_text>Table of contents Random forests<jupyter_code>rfc1, report_rfc1 = rf(X_train, X_test, y_train, y_test)
models.append(rfc1)
reports.append(report_rfc1)<jupyter_output><empty_output><jupyter_text>Table of contents XG Boost<jupyter_code>xgb1, report_xgb1= xgb(X_train, X_test, y_train, y_test)
models.append(xgb1)
reports.append(report_xgb1)<jupyter_output><empty_output><jupyter_text>Table of contents Gradient Boosting<jupyter_code>gb1, report_gb1 = gb(X_train, X_test, y_train, y_test)
models.append(gb1)
reports.append(report_gb1)<jupyter_output><empty_output><jupyter_text>Conclusion after trying different models: it seems that all models fail to perform on 0 class because of the class imbalance. Next step: correct class imbalance. Best model so far: random forests Table of contents 4. Correcting class imbalance with rejected dataset The current training set has 2649 class 1 values and 664 class 0 values. To correct class imbalance I will treat the rejected applications subset as class 0. I will randomly pick 2000 rejected applications and add them to the training set. I will then test the new model (random forest as it seems to have the best performance) on the original testing set that has true labels.<jupyter_code># Get random 2000 values from rejected dataset 

rejected_subset = df_rejected.sample(n=2000)
# Isolate target column

y_subset_0 = rejected_subset['status']
X_subset_0 = rejected_subset.drop('status', axis=1)
# Doublecheck if the columns correspond
X_train.columns == X_subset_0.columns
new_X_train = X_train.append(X_subset_0)
new_X_train = new_X_train.reset_index(drop=True)
new_y_train = y_train.append(y_subset_0)
new_y_train = new_y_train.reset_index(drop=True)<jupyter_output><empty_output><jupyter_text>Table of contents Train random forests on the new training set<jupyter_code>rfc2, report_rfc2 = rf(new_X_train, X_test, new_y_train, y_test)
models.append(rfc2)
reports.append(report_rfc2)<jupyter_output><empty_output><jupyter_text>Table of contents Try logistic regression on my new training set<jupyter_code>#scale the new dataset
new_X_train_scaled, ss2 = scale_columns(new_X_train)
new_X_test_scaled, ss2 = scale_columns(X_test, ss2)
#apply logistic regression

lr3, report_lr3 = logireg(new_X_train_scaled, new_X_test_scaled, new_y_train, y_test)
models.append(lr3)
reports.append(report_lr3)<jupyter_output><empty_output><jupyter_text>Table of contents Try Gradient Boosting on my new training set<jupyter_code>gb2, report_gb2 = gb(new_X_train, X_test, new_y_train, y_test)
models.append(gb2)
reports.append(report_gb2)<jupyter_output><empty_output><jupyter_text>Table of contents Try XGBoost  regression on my new training set<jupyter_code>xgb2, report_xgb2 = xgb(new_X_train, X_test, new_y_train, y_test)
models.append(xgb2)
reports.append(report_xgb2)<jupyter_output><empty_output><jupyter_text>Table of contents 5. Correcting class imbalance with Sklearn resample<jupyter_code>training_set = X_train.copy()
training_set['status'] = y_train
training_set_0 = training_set.loc[training_set.status == 0]
training_set_0_upsampled = resample(training_set_0, replace=True, n_samples=2000, random_state=113)
y_training_set_0_upsampled = training_set_0_upsampled['status']
X_training_set_0_upsampled = training_set_0_upsampled.drop('status', axis=1)
new_X_train_upsampled = X_train.append(X_training_set_0_upsampled)
new_y_train_upsampled = y_train.append(y_training_set_0_upsampled)
new_X_train_upsampled = new_X_train_upsampled.reset_index(drop=True)
new_y_train_upsampled = new_y_train_upsampled.reset_index(drop=True)<jupyter_output><empty_output><jupyter_text>Table of contents Apply random forest<jupyter_code>rf3, report_rf3 = rf(new_X_train_upsampled, X_test, new_y_train_upsampled, y_test)<jupyter_output>RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini',
                       max_depth=None, max_features='auto', max_leaf_nodes=None,
                       min_impurity_decrease=0.0, min_impurity_split=None,
                       min_samples_leaf=1, min_samples_split=2,
                       min_weight_fraction_leaf=0.0, n_estimators=100,
                       n_jobs=None, oob_score=False, random_state=113,
                       verbose=0, warm_start=False)
Report:
              precision    recall  f1-score   support

           0       0.38      0.13      0.19       185
           1       0.79      0.94      0.86       644

    accuracy                           0.76       829
   macro avg       0.59      0.53      0.53       829
weighted avg       0.70      0.76      0.71       829
 CV AUC score: 0.6434589749633355

Each Cross Validated AUC: 
 [0.68772261 0.63031636 0.66939032 0.60423214 0.62563345]

Overall Classifier AUC: 0.64 (+/- 0.06)<jupyter_text>Table of contents Gradient boosting<jupyter_code>gb3, report_gb3 = gb(new_X_train_upsampled, X_test, new_y_train_upsampled, y_test)<jupyter_output>Report:
              precision    recall  f1-score   support

           0       0.32      0.49      0.39       185
           1       0.83      0.71      0.76       644

    accuracy                           0.66       829
   macro avg       0.58      0.60      0.58       829
weighted avg       0.72      0.66      0.68       829
 CV AUC score: 0.6547837445003143

Each Cross Validated AUC: 
 [0.72616803 0.62350723 0.68154201 0.63691599 0.60578547]

Overall Classifier AUC: 0.65 (+/- 0.09)<jupyter_text>Table of contents XGBoost<jupyter_code>xgb3, report_xgb3 = xgb(new_X_train_upsampled, X_test, new_y_train_upsampled, y_test)<jupyter_output>Report:
              precision    recall  f1-score   support

           0       0.33      0.49      0.40       185
           1       0.83      0.72      0.77       644

    accuracy                           0.67       829
   macro avg       0.58      0.60      0.58       829
weighted avg       0.72      0.67      0.69       829
 CV AUC score: 0.653591163838257

Each Cross Validated AUC: 
 [0.71799707 0.63293526 0.67902787 0.64466792 0.5933277 ]

Overall Classifier AUC: 0.65 (+/- 0.08)<jupyter_text>Table of contents Logistic regression<jupyter_code># Scaling
#train set
new_X_train_upsampled_scaled, ss3 = scale_columns(new_X_train_upsampled)
new_X_test_upsampled_scaled, ss3 = scale_columns(X_test, ss3)
lr4, report_lr4 = logireg(new_X_train_upsampled_scaled, new_X_test_upsampled_scaled, new_y_train_upsampled, y_test)
models.append(rf3)
models.append(gb3)
models.append(xgb3)
models.append(lr4)

reports.append(report_rf3)
reports.append(report_gb3)
reports.append(report_xgb3)
reports.append(report_lr4)<jupyter_output><empty_output><jupyter_text>Table of contents 6. Models comparison<jupyter_code>df_reports = parse_reports(reports)
df_reports['model'] = ['Logistic regression',
                      'Logistic Regression limited features',
                      'Random forest',
                      'XGBoost',
                      'Gradient boosting',
                      'Random forest with data from rejected dataset',
                      'Logistic regression with data from rejected dataset',
                      'Gradient Boosting with data from rejected dataset',
                      'XG Boost with data from rejected dataset',
                      'Random forest with resampled data',
                      'Gradient Boosting with resampled data',
                      'XG Boost with resampled data',
                      'Logistic regression with resampled data']
df_reports<jupyter_output><empty_output><jupyter_text>Conclusion:Baseline AUC was 0.5The models have quite similar AUC score, which in this case is the most important metric. Desicion tree based models have slightly better AUC, but they tend to overfit and are not calibrated as is logistic regression. For this reason the selected model has to be a logistic regression one. There are several logistic regression models. The ones with blanced classes seem to have better balanced predictive power for classes 0 and 1. There is no much difference betweer logistic regression upsampled from rejected daraset and the one upsampled using sklearn resample. I selected the last logistic regression upsampled with sklearn resample as a final model.<jupyter_code>#pickle model
filename = 'lr_model.sav'
pickle.dump(lr4, open(filename, 'wb'))

#pickle standard scaler
filename = 'scaler.pkl'
pickle.dump(ss3, open(filename, 'wb'))<jupyter_output><empty_output><jupyter_text>Table of contents 7. Further exploration of the selected Logistic Regression model (lr4) Table of contents Cross-validated log loss and AUC<jupyter_code>cv_log_loss = cross_val_score(lr4, new_X_test_upsampled_scaled, y_test, cv=5, scoring='neg_log_loss')
print('Cross-validated log loss: ', cv_log_loss.mean())
#interpretation: on average, the probability attributed to the right class
print('Average probability of labeling the class correctly: ', np.log(0.56))
cv_roc_auc = cross_val_score(lr4, new_X_test_upsampled_scaled, y_test, cv=5, scoring='roc_auc')
print('Cross-validated AUC score: ', cv_roc_auc.mean())<jupyter_output>Cross-validated AUC score:  0.6279197438717787<jupyter_text>Table of contents Threshold variation<jupyter_code>#this function allows to see predictions metrics with different thresholds for 0 class
res = predict_probability_and_class(lr4, new_X_test_upsampled_scaled, y_test, 0.20)
res.head()<jupyter_output><empty_output><jupyter_text>Table of contents ROC-AUC visualization<jupyter_code>y_true = res['status']
y_probas = lr4.predict_proba(new_X_test_upsampled_scaled)
skplt.metrics.plot_roc(y_true, y_probas, figsize=(10,10))
plt.savefig('graphs/roc_auc.png')
plt.show();<jupyter_output><empty_output><jupyter_text>Table of contents Model's coefficients<jupyter_code>features = get_df_coef(lr4, new_X_test_upsampled_scaled)
features<jupyter_output><empty_output><jupyter_text>Table of contents 8. Making predictions with lr4 model<jupyter_code>test_application = (0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 20, 1000, 5, 0, 1, 0, 1, 500, 100, 300,
                    3, 100, 0, 300, 2000, 4, 1, 1, 500, 0, 0, 0)
with open('lr_model.sav', 'rb') as f:
    model = pickle.load(f)

with open('scaler.pkl', 'rb') as f:
    scaler = pickle.load(f)
prediction = predict_p(test_application, model, scaler)
print(prediction)<jupyter_output>[[0.06276169 0.93723831]]<jupyter_text>Table of contents 9. Checking lr4 model's calibation Table of contents Check model's calibration of training and testing set<jupyter_code>#check calibration of the test set
res = predict_probability_and_class(lr4, new_X_test_upsampled_scaled, y_test, 0.25)
check_calibration(res)
#check calibration on train test:
res = predict_probability_and_class(lr4, new_X_train_upsampled_scaled, new_y_train_upsampled, 0.25)
check_calibration(res)<jupyter_output>precision    recall  f1-score   support

           0       0.53      0.97      0.68      2642
           1       0.83      0.13      0.23      2671

    accuracy                           0.55      5313
   macro avg       0.68      0.55      0.45      5313
weighted avg       0.68      0.55      0.45      5313<jupyter_text>Table of contents Cross-validate calibration of lr4 model<jupyter_code>avgs = cv_calibration(new_X_train_upsampled_scaled, new_X_test_upsampled_scaled, new_y_train_upsampled, y_test)
# Warning explanation: for some ranges of probability there are no applications, 
# so this is why the above warning appeared.
#Averages true classes for predicted P ranges 0-0.1 to 0.9-1
print(avgs)<jupyter_output>[0.7512120669056153, 0.7169380796508456, 0.6605320173041692, 0.6355672692870717, 0.5477953598102852, 0.4986846135434691, 0.4133785336075871, 0.36123041686613533, 0.30265151515151517, nan]<jupyter_text>Table of contents Plot cross-validated calibration<jupyter_code>ranges = ['0 - 0.1', '0.1 - 0.2', '0.2 - 0.3', '0.3 -0.4', '0.4 - 0.5', '0.5 - 0,6',
           '0.6 - 0.7', '0.7 - 0.8', '0.8 - 0.9', '0.9 - 1']
avg_ranges = [0.05, 0.15, 0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.85, 0.95]
means = [(1-avgs[0]), (1-avgs[1]), (1-avgs[2]), (1-avgs[3]), (1-avgs[4]), (1-avgs[5]), 
         (1-avgs[6]), (1-avgs[7]), (1-avgs[8]), (1-avgs[9])]
plt.figure(figsize=(15, 7))
plt.bar(ranges, avg_ranges, align='center', alpha=0.5)
plt.plot(means, 'r', marker='o')
plt.legend(['Actual default rate'])
plt.ylabel('Predicted probability')
plt.xlabel('Ranges of predicted probability')
plt.title('Calibration check')

plt.savefig('graphs/calibration_check.png')
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.08811074918566775, 'fraction_numerical': 0.05134364820846905, 'mean_word_length': 3.943840579710145, 'pattern_counts': {'":': 0, '<': 154, '<?xml version=': 0, '>': 156, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 26, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '40', 'dir': 'jupyter-structured-clean-dedup', 'id': '37273', 'license': 'MIT', 'n_tokens_mistral': 8818, 'n_tokens_neox': 7850, 'n_words': 2044, 'path': 'model_selection.ipynb', 'repo_name': 'AnnaLara/predicting_loan_repayment', 'size': '24560'}