Classification Performance

This notebook covers metrics and evaluation techniques for classification models.

Notebook Contents

This notebook covers classification performance evaluation:

  • Confusion Matrix: Understanding true/false positives and negatives
  • Accuracy Metrics: Precision, recall, F1-score
  • ROC Curves: Receiver Operating Characteristic analysis
  • Cross-Validation: Model validation techniques
  • Performance Visualization: Plotting and interpreting results

Use the buttons above to download the notebook or open it in your preferred environment.

šŸ““ Notebook Preview

InĀ [1]:
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis, QuadraticDiscriminantAnalysis
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import classification_report, accuracy_score, f1_score, precision_score, recall_score, confusion_matrix
InĀ [2]:
df = pd.read_csv('Default.csv', index_col=0)
InĀ [3]:
df
Out[3]:
default student balance income
1 No No 729.526495 44361.625074
2 No Yes 817.180407 12106.134700
3 No No 1073.549164 31767.138947
4 No No 529.250605 35704.493935
5 No No 785.655883 38463.495879
... ... ... ... ...
9996 No No 711.555020 52992.378914
9997 No No 757.962918 19660.721768
9998 No No 845.411989 58636.156984
9999 No No 1569.009053 36669.112365
10000 No Yes 200.922183 16862.952321

10000 rows Ɨ 4 columns

InĀ [4]:
df.iloc[:,1:]
Out[4]:
student balance income
1 No 729.526495 44361.625074
2 Yes 817.180407 12106.134700
3 No 1073.549164 31767.138947
4 No 529.250605 35704.493935
5 No 785.655883 38463.495879
... ... ... ...
9996 No 711.555020 52992.378914
9997 No 757.962918 19660.721768
9998 No 845.411989 58636.156984
9999 No 1569.009053 36669.112365
10000 Yes 200.922183 16862.952321

10000 rows Ɨ 3 columns

InĀ [5]:
df.student = df.student.map(dict(Yes=1, No=0))
InĀ [6]:
#df[df.default.isna()]
df
Out[6]:
default student balance income
1 No 0 729.526495 44361.625074
2 No 1 817.180407 12106.134700
3 No 0 1073.549164 31767.138947
4 No 0 529.250605 35704.493935
5 No 0 785.655883 38463.495879
... ... ... ... ...
9996 No 0 711.555020 52992.378914
9997 No 0 757.962918 19660.721768
9998 No 0 845.411989 58636.156984
9999 No 0 1569.009053 36669.112365
10000 No 1 200.922183 16862.952321

10000 rows Ɨ 4 columns

InĀ [7]:
X_train, X_test, y_train, y_test = train_test_split(df.iloc[:,1:], df.default, test_size=.2, random_state = 1)
InĀ [8]:
len(X_train)
Out[8]:
8000
InĀ [9]:
len(X_test)
Out[9]:
2000

Explore your data¶

InĀ [10]:
f, ax = plt.subplots(1, 2, figsize = (8,4), sharey= True)
sns.histplot(df.balance,ax = ax[0])
sns.histplot(df.income, ax= ax[1])
Out[10]:
<Axes: xlabel='income', ylabel='Count'>
No description has been provided for this image
InĀ [Ā ]:
 
InĀ [21]:
sns.boxplot(x=df.student, y=df.balance)
Out[21]:
<Axes: xlabel='student', ylabel='balance'>
No description has been provided for this image
InĀ [19]:
sns.boxplot(x=df.default, y=df.income, showmeans=True)
Out[19]:
<Axes: xlabel='default', ylabel='income'>
No description has been provided for this image
InĀ [13]:
sns.scatterplot(x = df.balance, y = df.default )
Out[13]:
<Axes: xlabel='balance', ylabel='default'>
No description has been provided for this image
InĀ [14]:
sns.scatterplot(x = df.balance, y = df.income, hue = df.default,alpha = .7 )
Out[14]:
<Axes: xlabel='balance', ylabel='income'>
No description has been provided for this image
InĀ [15]:
X_train.iloc[:,1:]
Out[15]:
balance income
2695 1804.036475 31318.296026
5141 1174.194909 35533.484519
2569 978.652180 25742.119731
3672 548.136289 19501.341068
7428 270.072593 36833.645138
... ... ...
2896 1270.092810 16809.006452
7814 1598.020831 39163.361056
906 1234.476479 31313.374575
5193 0.000000 29322.631394
236 964.820253 34390.746035

8000 rows Ɨ 2 columns

InĀ [16]:
lda = QuadraticDiscriminantAnalysis()
lda.fit(X_train.iloc[:,1:],y_train)
y_pred_lda = lda.predict(X_test.iloc[:,1:])
con_mat_lda = confusion_matrix(y_test, y_pred_lda)
print(con_mat_lda)
[[1932    9]
 [  43   16]]
InĀ [24]:
lda = LinearDiscriminantAnalysis()
lda.fit(X_train.iloc[:,1:],y_train)
Out[24]:
LinearDiscriminantAnalysis()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
InĀ [25]:
qda = QuadraticDiscriminantAnalysis()
qda.fit(X_train.iloc[:,1:],y_train)
Out[25]:
QuadraticDiscriminantAnalysis()
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
InĀ [28]:
lda.decision_function
Out[28]:
<bound method LinearDiscriminantAnalysis.decision_function of LinearDiscriminantAnalysis()>
InĀ [31]:
y_pred = qda.predict(X_test.iloc[:,1:])
print('Accuracy of logistic regression classifier on test set: {:.2f}'.format(qda.score(X_test.iloc[:,1:], y_test)))
Accuracy of logistic regression classifier on test set: 0.97
InĀ [32]:
print(classification_report(y_test, y_pred))
              precision    recall  f1-score   support
          No       0.98      1.00      0.99      1941
         Yes       0.64      0.27      0.38        59
    accuracy                           0.97      2000
   macro avg       0.81      0.63      0.68      2000
weighted avg       0.97      0.97      0.97      2000
InĀ [38]:
y_pred = lda.predict(X_test.iloc[:,1:])
print('Accuracy of logistic regression classifier on test set: {:.2f}'.format(lda.score(X_test.iloc[:,1:], y_test)))
Accuracy of logistic regression classifier on test set: 0.98
InĀ [44]:
lda.predict_proba(
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[44], line 1
----> 1 lda.predict_proba()
TypeError: LinearDiscriminantAnalysis.predict_proba() missing 1 required positional argument: 'X'
InĀ [46]:
y_pred_new_threshold = pd.Series(lda.predict_proba(X_test.iloc[:,1:])[:,1]>=0.6) #(lda.predict_proba(X_test.iloc[:,1:])[:,1]>=0.3).astype(str))
InĀ [30]:
print(classification_report(y_test, y_pred))
              precision    recall  f1-score   support
          No       0.98      1.00      0.99      1941
         Yes       0.75      0.25      0.38        59
    accuracy                           0.98      2000
   macro avg       0.86      0.63      0.68      2000
weighted avg       0.97      0.98      0.97      2000
InĀ [47]:
print(classification_report(y_test, y_pred_new_threshold))
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
File ~/.local/lib/python3.13/site-packages/sklearn/metrics/_classification.py:126, in _check_targets(y_true, y_pred)
    125 try:
--> 126     unique_values = _union1d(y_true, y_pred, xp)
    127 except TypeError as e:
    128     # We expect y_true and y_pred to be of the same data type.
    129     # If `y_true` was provided to the classifier as strings,
    130     # `y_pred` given by the classifier will also be encoded with
    131     # strings. So we raise a meaningful error
File ~/.local/lib/python3.13/site-packages/sklearn/utils/_array_api.py:220, in _union1d(a, b, xp)
    219     a_unique, b_unique = cached_unique(a, b, xp=xp)
--> 220     return xp.asarray(numpy.union1d(a_unique, b_unique))
    221 assert a.ndim == b.ndim == 1
File ~/.local/lib/python3.13/site-packages/numpy/lib/_arraysetops_impl.py:1176, in union1d(ar1, ar2)
   1148 """
   1149 Find the union of two arrays.
   1150 
   (...)
   1174 array([1, 2, 3, 4, 6])
   1175 """
-> 1176 return unique(np.concatenate((ar1, ar2), axis=None))
File ~/.local/lib/python3.13/site-packages/numpy/lib/_arraysetops_impl.py:291, in unique(ar, return_index, return_inverse, return_counts, axis, equal_nan)
    290 if axis is None:
--> 291     ret = _unique1d(ar, return_index, return_inverse, return_counts, 
    292                     equal_nan=equal_nan, inverse_shape=ar.shape, axis=None)
    293     return _unpack_tuple(ret)
File ~/.local/lib/python3.13/site-packages/numpy/lib/_arraysetops_impl.py:358, in _unique1d(ar, return_index, return_inverse, return_counts, equal_nan, inverse_shape, axis)
    357 else:
--> 358     ar.sort()
    359     aux = ar
TypeError: '<' not supported between instances of 'bool' and 'str'
The above exception was the direct cause of the following exception:
TypeError                                 Traceback (most recent call last)
Cell In[47], line 1
----> 1 print(classification_report(y_test, y_pred_new_threshold))
File ~/.local/lib/python3.13/site-packages/sklearn/utils/_param_validation.py:216, in validate_params.<locals>.decorator.<locals>.wrapper(*args, **kwargs)
    210 try:
    211     with config_context(
    212         skip_parameter_validation=(
    213             prefer_skip_nested_validation or global_skip_validation
    214         )
    215     ):
--> 216         return func(*args, **kwargs)
    217 except InvalidParameterError as e:
    218     # When the function is just a wrapper around an estimator, we allow
    219     # the function to delegate validation to the estimator, but we replace
    220     # the name of the estimator by the name of the function in the error
    221     # message to avoid confusion.
    222     msg = re.sub(
    223         r"parameter of \w+ must be",
    224         f"parameter of {func.__qualname__} must be",
    225         str(e),
    226     )
File ~/.local/lib/python3.13/site-packages/sklearn/metrics/_classification.py:2671, in classification_report(y_true, y_pred, labels, target_names, sample_weight, digits, output_dict, zero_division)
   2563 """Build a text report showing the main classification metrics.
   2564 
   2565 Read more in the :ref:`User Guide <classification_report>`.
   (...)
   2667 <BLANKLINE>
   2668 """
   2670 y_true, y_pred = attach_unique(y_true, y_pred)
-> 2671 y_type, y_true, y_pred = _check_targets(y_true, y_pred)
   2673 if labels is None:
   2674     labels = unique_labels(y_true, y_pred)
File ~/.local/lib/python3.13/site-packages/sklearn/metrics/_classification.py:132, in _check_targets(y_true, y_pred)
    126     unique_values = _union1d(y_true, y_pred, xp)
    127 except TypeError as e:
    128     # We expect y_true and y_pred to be of the same data type.
    129     # If `y_true` was provided to the classifier as strings,
    130     # `y_pred` given by the classifier will also be encoded with
    131     # strings. So we raise a meaningful error
--> 132     raise TypeError(
    133         "Labels in y_true and y_pred should be of the same type. "
    134         f"Got y_true={xp.unique(y_true)} and "
    135         f"y_pred={xp.unique(y_pred)}. Make sure that the "
    136         "predictions provided by the classifier coincides with "
    137         "the true labels."
    138     ) from e
    139 if unique_values.shape[0] > 2:
    140     y_type = "multiclass"
TypeError: Labels in y_true and y_pred should be of the same type. Got y_true=['No' 'Yes'] and y_pred=[False  True]. Make sure that the predictions provided by the classifier coincides with the true labels.
InĀ [Ā ]:
con_mat = confusion_matrix(y_test, y_pred)
print(con_mat)
int(3)
InĀ [Ā ]:
print(classification_report(y_test, y_pred))
InĀ [Ā ]:
logistic.coef_
InĀ [Ā ]:
logistic.intercept_
InĀ [Ā ]:
sns.scatterplot(x = df.balance, y = df.income, hue = df.default,alpha = .7 )
InĀ [Ā ]:
logistic.predict_proba(X_test)
InĀ [Ā ]:
y_test.unique()
InĀ [45]:
lda.predict_proba(X_test.iloc[:,1:])[:,1]>=0.6
Out[45]:
array([False, False, False, ..., False, False, False])
InĀ [Ā ]:
 
InĀ [Ā ]:
y_pred_new_threshold.unique()
InĀ [Ā ]:
y_pred_new_threshold = y_pred_new_threshold.map({'False': 'No', 'True': 'Yes'})
InĀ [Ā ]:
y_pred_new_threshold.unique()
InĀ [Ā ]:
con_mat = confusion_matrix(y_test, y_pred_new_threshold)
print(con_mat)