L’Organisation nationale de lutte contre le faux-monnayage (ONCFM) veut mettre en place des méthodes d’identification des contrefaçons de billets en euros. Il s'agira de réaliser une modélisation qui serait capable d’identifier automatiquement les vrais des faux billets, et ce simplement à partir de certaines dimensions du billet et des éléments qui le composent. On mettra en concurrence deux méthodes de prédiction :
● une régression logistique classique
● un k-means, duquel seront utilisés les centroïdes pour réaliser la prédiction.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import matplotlib.lines as mlines
import seaborn as sns
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
from sklearn.metrics import confusion_matrix
from sklearn.metrics import ConfusionMatrixDisplay
from sklearn.metrics import classification_report
from sklearn.metrics import r2_score
from sklearn.metrics import mean_squared_error
from sklearn.dummy import DummyClassifier
from sklearn.preprocessing import StandardScaler
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA
import statsmodels.formula.api as smf
from statsmodels.stats.diagnostic import het_breuschpagan
from statsmodels.stats.outliers_influence import variance_inflation_factor
from scipy import stats
import statistics
from adjustText import adjust_text
import pickle
import warnings
# Charger les données et visualiser les premières lignes du dataframe
df = pd.read_csv('billets.csv', sep=';')
df.head()
is_genuine | diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|---|
0 | True | 171.81 | 104.86 | 104.95 | 4.52 | 2.89 | 112.83 |
1 | True | 171.46 | 103.36 | 103.66 | 3.77 | 2.99 | 113.09 |
2 | True | 172.69 | 104.48 | 103.50 | 4.40 | 2.94 | 113.16 |
3 | True | 171.36 | 103.91 | 103.94 | 3.62 | 3.01 | 113.51 |
4 | True | 171.73 | 104.28 | 103.46 | 4.04 | 3.48 | 112.54 |
# Afficher les dimensions du dataframe
# Celui-ci contient 1500 lignes (billets) et 7 variables
df.shape
(1500, 7)
# Afficher un échantillon de billets sélectionnés de façon aléatoire
df.sample(10, random_state=42)
is_genuine | diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|---|
1116 | False | 172.26 | 103.90 | 104.12 | 4.99 | 3.42 | 111.27 |
1368 | False | 171.65 | 104.32 | 104.38 | 5.65 | 3.24 | 112.30 |
422 | True | 171.53 | 103.53 | 103.63 | 4.04 | 2.96 | 112.76 |
413 | True | 172.30 | 103.66 | 103.50 | NaN | 3.16 | 112.95 |
451 | True | 172.17 | 103.79 | 103.54 | 4.07 | 2.78 | 113.03 |
861 | True | 171.86 | 104.21 | 103.74 | 4.43 | 2.90 | 113.65 |
1063 | False | 171.44 | 103.99 | 104.04 | 4.81 | 3.49 | 111.74 |
741 | True | 172.17 | 103.75 | 103.29 | 4.43 | 2.88 | 113.38 |
1272 | False | 171.73 | 103.74 | 104.38 | 5.14 | 3.16 | 111.73 |
259 | True | 172.35 | 103.62 | 103.78 | 4.38 | 2.86 | 113.28 |
# Afficher le nombre de valeurs non nulles et le type de données dans chaque colonne
# Nous avons des valeurs manquantes dans la colonne "margin_low"
df.info()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1500 entries, 0 to 1499 Data columns (total 7 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 is_genuine 1500 non-null bool 1 diagonal 1500 non-null float64 2 height_left 1500 non-null float64 3 height_right 1500 non-null float64 4 margin_low 1463 non-null float64 5 margin_up 1500 non-null float64 6 length 1500 non-null float64 dtypes: bool(1), float64(6) memory usage: 71.9 KB
# Afficher le nombre de valeurs manquantes dans les différentes colonnes
df.isna().sum()
is_genuine 0 diagonal 0 height_left 0 height_right 0 margin_low 37 margin_up 0 length 0 dtype: int64
# Afficher le nombre de doublons
df.duplicated().sum()
0
df.is_genuine.value_counts()
True 1000 False 500 Name: is_genuine, dtype: int64
# Afficher la proportion de chaque classe (vrais billets et faux billets) dans le nombre total de billets
plt.figure(figsize=(10, 6))
ax = df['is_genuine'].value_counts(normalize=True).plot(kind='bar', color='blue')
plt.title('Proportion de vrais et de faux billets', fontweight='bold', fontsize=16, pad=20)
plt.xlabel('is_genuine')
plt.ylabel('Proportion')
for p in ax.patches:
ax.annotate("{:.2%}".format(p.get_height()), (p.get_x() + p.get_width() / 2., p.get_height() * 0.50), ha='center', va='center', color='white', fontweight='bold')
ax.spines[['top', 'right']].set_visible(False)
plt.xticks(rotation=0)
ax.set_axisbelow(True)
plt.show()
On avons un déséquilibre de classe, les vrais billets étant deux fois plus nombreux que les faux.
A présent, nous allons créer un dataset sans les valeurs manquantes.
# Créer une dataframe sans les valeurs manquantes
df_without_nan = df.dropna()
# Vérifier si les valeurs manquantes ont été supprimées
df_without_nan.isna().sum()
is_genuine 0 diagonal 0 height_left 0 height_right 0 margin_low 0 margin_up 0 length 0 dtype: int64
# Afficher les statistiques pour les vrais billets
df_true = df_without_nan.loc[df_without_nan['is_genuine'] == True]
df_true.describe()
diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|
count | 971.000000 | 971.000000 | 971.000000 | 971.000000 | 971.000000 | 971.000000 |
mean | 171.988476 | 103.951679 | 103.809094 | 4.116097 | 3.052544 | 113.203059 |
std | 0.301402 | 0.301518 | 0.288862 | 0.319124 | 0.185425 | 0.356123 |
min | 171.040000 | 103.140000 | 102.910000 | 2.980000 | 2.270000 | 111.760000 |
25% | 171.790000 | 103.745000 | 103.610000 | 3.905000 | 2.925000 | 112.960000 |
50% | 171.990000 | 103.950000 | 103.810000 | 4.110000 | 3.050000 | 113.200000 |
75% | 172.200000 | 104.140000 | 104.000000 | 4.340000 | 3.180000 | 113.460000 |
max | 172.920000 | 104.860000 | 104.950000 | 5.040000 | 3.740000 | 114.320000 |
# Afficher les statistiques pour les faux billets
df_false = df_without_nan.loc[df_without_nan['is_genuine'] == False]
df_false.describe()
diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|
count | 492.000000 | 492.000000 | 492.000000 | 492.000000 | 492.000000 | 492.000000 |
mean | 171.901402 | 104.188537 | 104.143272 | 5.215935 | 3.351504 | 111.632114 |
std | 0.305473 | 0.224418 | 0.271683 | 0.553531 | 0.179343 | 0.615343 |
min | 171.040000 | 103.510000 | 103.430000 | 3.820000 | 2.920000 | 109.490000 |
25% | 171.697500 | 104.040000 | 103.950000 | 4.840000 | 3.227500 | 111.200000 |
50% | 171.910000 | 104.180000 | 104.160000 | 5.190000 | 3.350000 | 111.630000 |
75% | 172.092500 | 104.330000 | 104.320000 | 5.592500 | 3.472500 | 112.030000 |
max | 173.010000 | 104.880000 | 104.950000 | 6.900000 | 3.910000 | 113.850000 |
# Comparer les faux et les vrais billets à l'aide d'un boxplot
plt.figure(figsize=(20, 16))
palette = ['red', 'blue']
plt.subplot(4,3,1)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='diagonal', data=df_without_nan, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('Diagonal', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Diagonale en mm')
plt.subplot(4,3,2)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='height_left', data=df_without_nan, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('Height_left', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Hauteur gauche en mm')
plt.subplot(4,3,3)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='height_right', data=df_without_nan, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('height_right', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Hauteur droite en mm')
plt.subplot(4,3,4)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='margin_low', data=df_without_nan, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('margin_low', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Marge basse en mm')
plt.subplot(4,3,5)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='margin_up', data=df_without_nan, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('margin_up', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Marge haute en mm')
plt.subplot(4,3,6)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='length', data=df_without_nan, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('length', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Longueur en mm')
sns.despine()
plt.legend(handles=[mean_marker,outliers_marker], bbox_to_anchor=(0.90, 1), loc='upper left')
plt.subplots_adjust(hspace=1)
plt.tight_layout()
plt.show()
Premier constat:
Un faux billet a généralement une hauteur et des marges (marge haute et marge basse) plus grandes que celles d'un vrai billet, tandis que sa longueur est plus petite.
Avant d'aller plus loin, il nous faut régler le problème des valeurs manquantes. On va combler ces dernières à l'aide d'une régression linéaire.
# Afficher la distribution de chaque varibale
plt.figure(figsize=(20,16))
plt.subplot(4,3,1)
ax = df_without_nan['diagonal'].hist(color='blue')
plt.title('Distribution de diagonal', fontsize=16, pad=20)
plt.xlabel('Longueur en mm')
plt.ylabel('Nombre de billets')
plt.grid(False)
ax.spines[['top', 'right']].set_visible(False)
plt.subplot(4,3,2)
ax = df_without_nan['height_left'].hist(color='blue')
plt.title('Distribution de height_left', fontsize=16, pad=20)
plt.xlabel('Hauteur en mm')
plt.ylabel('Nombre de billets')
plt.grid(False)
ax.spines[['top', 'right']].set_visible(False)
plt.subplot(4,3,3)
ax = df_without_nan['height_right'].hist(color='blue')
plt.title('Distribution de height_right', fontsize=16, pad=20)
plt.xlabel('Hauteur en mm')
plt.ylabel('Nombre de billets')
plt.grid(False)
ax.spines[['top', 'right']].set_visible(False)
plt.subplot(4,3,4)
ax = df_without_nan['margin_up'].hist(color='blue')
plt.title('Distribution de margin_up', fontsize=16, pad=20)
plt.xlabel('Hauteur en mm')
plt.ylabel('Nombre de billets')
plt.grid(False)
ax.spines[['top', 'right']].set_visible(False)
plt.subplot(4,3,5)
ax = df_without_nan['margin_low'].hist(color='blue')
plt.title('Distribution de margin_low', fontsize=16, pad=20)
plt.xlabel('Hauteur en mm')
plt.ylabel('Nombre de billets')
plt.grid(False)
ax.spines[['top', 'right']].set_visible(False)
plt.subplot(4,3,6)
ax = df_without_nan['length'].hist(color='blue')
plt.title('Distribution de length', fontsize=16, pad=20)
plt.xlabel('Longueur en mm')
plt.ylabel('Nombre de billets')
plt.grid(False)
ax.spines[['top', 'right']].set_visible(False)
plt.subplots_adjust(hspace=1)
plt.tight_layout()
plt.show()
Les variables 'diagonal', 'height_left', 'height_right' et 'margin_up' semblent suivre une distribution normale.
La variable 'margin_low' a une distribution étirée à droite tandis que celle de 'length' est étirée à gauche.
# Fonction qui utilise le test de Shapiro-Wilk de scipy.stats pour tester la normalité des distributions
def Shap_Wilk(df, alpha=0.05):
for index, column in enumerate(df.columns):
sw_statistic, sw_p_value = stats.shapiro(df[column])
if sw_p_value > alpha:
print(f'Test de Shapiro-Wilk: la variable {column} semble suivre une gaussienne (on ne rejette pas H0)')
else:
print(f'Test de Shapiro-Wilk: la variable {column} ne semble pas suivre une gaussienne (on rejette H0)')
print('-----------------------------------------')
Shap_Wilk(df_without_nan, alpha=0.05)
Test de Shapiro-Wilk: la variable is_genuine ne semble pas suivre une gaussienne (on rejette H0) ----------------------------------------- Test de Shapiro-Wilk: la variable diagonal semble suivre une gaussienne (on ne rejette pas H0) ----------------------------------------- Test de Shapiro-Wilk: la variable height_left semble suivre une gaussienne (on ne rejette pas H0) ----------------------------------------- Test de Shapiro-Wilk: la variable height_right semble suivre une gaussienne (on ne rejette pas H0) ----------------------------------------- Test de Shapiro-Wilk: la variable margin_low ne semble pas suivre une gaussienne (on rejette H0) ----------------------------------------- Test de Shapiro-Wilk: la variable margin_up ne semble pas suivre une gaussienne (on rejette H0) ----------------------------------------- Test de Shapiro-Wilk: la variable length ne semble pas suivre une gaussienne (on rejette H0) -----------------------------------------
# Afficher un nuage de point pour chaque paire de variables de façon à visualiser les variables linéairement corrélées à margin_low
plt.figure(figsize=(10,8))
palette = ['red', 'blue']
sns.pairplot(df_without_nan, vars=['diagonal', 'height_left', 'height_right',
'margin_up', 'margin_low', 'length'], hue='is_genuine', palette=palette)
plt.show()
<Figure size 720x576 with 0 Axes>
# Afficher la matrice de corrélation à l'aide d'une heatmap
plt.figure(figsize=(9,6))
sns.heatmap(df_without_nan.corr(), cmap='Blues_r', annot=True)
plt.title("Matrice de corrélation", fontweight=16, pad=20)
plt.xticks(rotation=45, ha='right')
plt.yticks(rotation=0)
plt.show()
Si l'on fait abstraction de la variable is_genuine (qui est catégorielle), il semblerait que "length" et (dans une moindre mesure) "margin_up", "height_right" et "height_left" soient les seules variables à avoir un semblant de relation linéaire avec "margin_low".
Testons la significativité de ces 4 relations.
# Créer une fonction qui renvoie le coefficient de corrélation de Pearson et la p-valeur associée (qui mesure la significativité du coefficient)
def pearson_correlation_test(df, df_x, df_y):
n = len(df[df_x])
x_mean = df[df_x].mean()
y_mean = df[df_y].mean()
x_standard_deviation = df[df_x].std()
y_standard_deviation = df[df_y].std()
covariance_x_y = ((df[df_x] - x_mean) * (df[df_y] - y_mean)).sum() / len(df)
pearson_coef = covariance_x_y / (x_standard_deviation * y_standard_deviation)
# calcul de la statistique t utilisée pour calculer la p-valeur
t_statistic = pearson_coef * np.sqrt((n-2)/(1-pearson_coef**2))
# Calcul de la p-valeur associée au test, à comparer au seuil de signification (0.05)
# Le test t de Student est bilatéral car le coefficient de Pearson, compris entre -1 et 1, peut être positif ou négatif
p_value_two_sided = 2 * (1 - stats.t.cdf(abs(t_statistic), n - 2))
return f'Coefficient de corrélation de Pearson: {round(pearson_coef,3)}, p-valeur: {p_value_two_sided}'
# tester le coefficient de corrélation de Pearson entre height_right et margin_low
pearson_correlation_test(df_without_nan, 'height_right', 'margin_low')
'Coefficient de corrélation de Pearson: 0.391, p-valeur: 0.0'
# tester le coefficient de corrélation de Pearson entre margin_up et margin_low
pearson_correlation_test(df_without_nan, 'margin_up', 'margin_low')
'Coefficient de corrélation de Pearson: 0.431, p-valeur: 0.0'
# tester le coefficient de corrélation de Pearson entre length et margin_low
pearson_correlation_test(df_without_nan, 'length', 'margin_low')
'Coefficient de corrélation de Pearson: -0.666, p-valeur: 0.0'
# tester le coefficient de corrélation de Pearson entre height_left et margin_low
pearson_correlation_test(df_without_nan, 'height_left', 'margin_low')
'Coefficient de corrélation de Pearson: 0.302, p-valeur: 0.0'
# Séparer la variable cible des variables explicatives
y = df_without_nan.iloc[:,0]
X = df_without_nan.iloc[:,1:]
# Afficher la variable cible
y
0 True 1 True 2 True 3 True 4 True ... 1495 False 1496 False 1497 False 1498 False 1499 False Name: is_genuine, Length: 1463, dtype: bool
# Afficher les variables explicatives
X.head()
diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|
0 | 171.81 | 104.86 | 104.95 | 4.52 | 2.89 | 112.83 |
1 | 171.46 | 103.36 | 103.66 | 3.77 | 2.99 | 113.09 |
2 | 172.69 | 104.48 | 103.50 | 4.40 | 2.94 | 113.16 |
3 | 171.36 | 103.91 | 103.94 | 3.62 | 3.01 | 113.51 |
4 | 171.73 | 104.28 | 103.46 | 4.04 | 3.48 | 112.54 |
# Train Test split
# Nous avons de vrais billets dans les 1000 premières lignes et des faux dans les 500 dernières...
# mais la fonction train_test_split se charge de mélanger les données (shuffle = True par défaut)
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.80, random_state=42)
# Afficher les dimensions des sous-ensembles
print(f'Dimensions de X_train : {X_train.shape}')
print(f'Dimensions de X_test : {X_test.shape}')
print(f'Dimensions de y_train : {y_train.shape}')
print(f'Dimensions de y_test : {y_test.shape}')
Dimensions de X_train : (1170, 6) Dimensions de X_test : (293, 6) Dimensions de y_train : (1170,) Dimensions de y_test : (293,)
# Fusionner X_train et y_train
df_train = X_train.merge(y_train.to_frame(), left_index=True, right_index=True)
# Nous avons à présent des vrais et des faux billets dans les premières lignes de df_train
# les données ont donc été mélangées comme prévu
df_train.head()
diagonal | height_left | height_right | margin_low | margin_up | length | is_genuine | |
---|---|---|---|---|---|---|---|
893 | 172.20 | 103.52 | 103.78 | 3.66 | 3.40 | 113.35 | True |
1071 | 171.50 | 103.90 | 103.98 | 4.82 | 3.40 | 112.07 | False |
260 | 172.27 | 103.71 | 103.64 | 4.64 | 2.67 | 113.63 | True |
1097 | 172.51 | 104.43 | 104.17 | 5.11 | 3.08 | 111.70 | False |
1224 | 171.74 | 104.52 | 104.23 | 5.59 | 3.61 | 112.14 | False |
# Fusionner X_test et y_test
df_test= X_test.merge(y_test.to_frame(), left_index=True, right_index=True)
# S'assurer que les données ont été mélangées dans l'ensemble de test
# Nous avons bien des vrais billets et des faux dans les 10 premières lignes du dataframe df_test
df_test.head(10)
diagonal | height_left | height_right | margin_low | margin_up | length | is_genuine | |
---|---|---|---|---|---|---|---|
1208 | 171.96 | 104.38 | 103.82 | 5.19 | 3.32 | 111.68 | False |
178 | 171.79 | 104.57 | 104.04 | 4.26 | 3.15 | 113.46 | True |
281 | 171.99 | 103.90 | 104.21 | 4.18 | 3.07 | 113.01 | True |
561 | 172.25 | 103.71 | 103.97 | 3.93 | 3.00 | 113.38 | True |
891 | 172.16 | 103.63 | 103.59 | 4.25 | 3.38 | 113.58 | True |
942 | 172.10 | 104.17 | 103.78 | 4.61 | 2.84 | 113.35 | True |
1018 | 171.79 | 104.18 | 103.87 | 5.55 | 3.25 | 111.88 | False |
899 | 172.39 | 103.98 | 104.05 | 4.26 | 2.98 | 113.41 | True |
756 | 171.72 | 103.95 | 104.11 | 3.75 | 3.13 | 113.51 | True |
920 | 171.85 | 103.27 | 103.96 | 4.00 | 2.65 | 113.62 | True |
Pour chaque régression linéaire qui va suivre, on admet les hypothèses supposées ou vérifiées suivantes:
- Il existe une relation linéaire entre la variable explicative et la variable expliquée
- Les observations du jeu de données sont indépendantes. Pour vérifier cette hypothèse, On utilisera le nuage de points qui montre la relation entre les résidus et les prédictions issus de la régression effectuée sur l'ensemble de test.
- Les résidus issus de la régression suivent une loi normale
Ensuite, il nous faudra tester la significativé du coefficient de la variable explicative, savoir s'il est significativement différent de 0, ce qui nous amène à deux nouvelles hypothèses:
H0: Le coefficient est = 0, et donc la variable associée n'a aucun pouvoir explicatif.
H1 : Le coefficient est significativement différent de 0, et donc la variable associée a un pouvoir explicatif plus ou moins important.
# Créer une fonction régression linéaire
def simple_linear_regression(df_train, df_test, dependent_variable, independent_variable):
'''
Paramètres:
dependent_variable: variable cible, string type,
independent_variable: variable explicative, string type
'''
# Calculer la moyenne des deux variables sur l'ensemble de test
dependent_variable_mean = df_test[dependent_variable].mean()
independent_variable_mean = df_test[independent_variable].mean()
# Effectuer la régression linéaire sur l'ensemble d'entraînement
linear_reg = smf.ols(f"{dependent_variable} ~ {independent_variable}", data=df_train).fit()
# Effectuer les prédictions sur l'ensemble de test
predictions = linear_reg.predict(df_test)
# si la variable indépendante est de type int ou float alors:
if isinstance(df_test[independent_variable].iloc[0], (int, float)):
print()
print('------ Visualiser le nuage de points et la droite de régression ------')
plt.figure(figsize=(12, 9))
plt.scatter(df_test[independent_variable], df_test[dependent_variable], color='blue', label="Données observées dans l'ensemble de test")
# Prédictions pour l'ensemble de test
plt.plot(df_test[independent_variable], predictions, color='red', label='Droite de régression')
plt.scatter(independent_variable_mean, dependent_variable_mean, color='orange', label='Centre de gravité du nuage', s=100)
plt.xlabel(independent_variable)
plt.ylabel(dependent_variable)
plt.title(f'La variable cible {dependent_variable} régressée par {independent_variable}', fontsize=16, fontweight='bold', pad=20)
sns.despine()
plt.legend()
plt.show()
# Afficher le résumé de la régression
print(linear_reg.summary())
# Récupérer les résidus
residuals = linear_reg.resid
print('------ Visualiser l\'allure de la distribution des résidus ------')
fig, ax = plt.subplots(figsize=(12, 9))
sns.histplot(residuals, color='blue', kde=True)
plt.title('Distribution des résidus', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Résidus')
plt.ylabel('Fréquence')
sns.despine()
plt.show()
# Tester l'homoscédasticité des résidus
print("------ Visualiser les résidus vs la variable indépendante ------")
plt.figure(figsize=(12, 9))
plt.scatter(df_train[independent_variable], residuals, color='red')
plt.xlabel(independent_variable)
plt.ylabel('Résidus en mm')
plt.title(f'Répartition des résidus en fonction de {independent_variable}', fontsize=16, fontweight='bold', pad=20)
sns.despine()
plt.legend()
plt.show()
# Vérifier si les index des observations de df_test et de predictions correspondent
print()
print("------ Comparer les index de df_test et de predictions ------")
print()
print("index de df_test")
print(df_test[dependent_variable].index)
print("index de predictions")
print(predictions.index)
print()
# Récupérer les résidus issus de la régression sur l'ensemble de test
test_residuals = df_test[dependent_variable] - predictions
# Tester l'homoscédasticité des résidus
print("------ Visualiser les résidus vs les prédictions ------")
plt.figure(figsize=(12, 9))
plt.scatter(predictions, test_residuals, color='red')
plt.xlabel(f"Prédictions de {dependent_variable} en mm")
plt.ylabel('Résidus en mm')
plt.title(f'Répartition des résidus en fonction des prédictions', fontsize=16, fontweight='bold', pad=20)
sns.despine()
plt.legend()
plt.show()
# Test de Breusch-Pagan
_, LM_p_value, _, _ = het_breuschpagan(residuals, linear_reg.model.exog)
print('------ Test d\'homoscédasticité des résidus ------')
print(f'P-valeur du test de Breusch-Pagan: {LM_p_value}')
print()
else:
# Afficher le résumé de la régression
print(linear_reg.summary())
# Récupérer les résidus
residuals = linear_reg.resid
print()
print('------ Visualiser l\'allure de la distribution des résidus ------')
fig, ax = plt.subplots(figsize=(9, 6))
sns.histplot(residuals, color='blue', kde=True)
plt.title('Distribution des résidus', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Résidus')
plt.ylabel('Fréquence')
sns.despine()
plt.show()
# Tester l'homoscédasticité des résidus
_, LM_p_value, _, _ = het_breuschpagan(residuals, linear_reg.model.exog)
print('------ Test d\'homoscédasticité des résidus ------')
print(f'P-valeur du test de Breusch-Pagan: {LM_p_value}')
print()
# Effectuer une régression linéaire simple en utilisant la variable explicative length
simple_linear_regression(df_train, df_test, 'margin_low', 'length')
------ Visualiser le nuage de points et la droite de régression ------
OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.428 Model: OLS Adj. R-squared: 0.428 Method: Least Squares F-statistic: 874.1 Date: Mon, 26 Aug 2024 Prob (F-statistic): 7.17e-144 Time: 10:54:13 Log-Likelihood: -859.74 No. Observations: 1170 AIC: 1723. Df Residuals: 1168 BIC: 1734. Df Model: 1 Covariance Type: nonrobust ============================================================================== coef std err t P>|t| [0.025 0.975] ------------------------------------------------------------------------------ Intercept 60.7050 1.902 31.922 0.000 56.974 64.436 length -0.4990 0.017 -29.565 0.000 -0.532 -0.466 ============================================================================== Omnibus: 86.499 Durbin-Watson: 1.970 Prob(Omnibus): 0.000 Jarque-Bera (JB): 118.103 Skew: 0.613 Prob(JB): 2.26e-26 Kurtosis: 3.958 Cond. No. 1.45e+04 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. [2] The condition number is large, 1.45e+04. This might indicate that there are strong multicollinearity or other numerical problems. ------ Visualiser l'allure de la distribution des résidus ------
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
------ Visualiser les résidus vs la variable indépendante ------
No artists with labels found to put in legend. Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
------ Comparer les index de df_test et de predictions ------ index de df_test Int64Index([1208, 178, 281, 561, 891, 942, 1018, 899, 756, 920, ... 1248, 1488, 1019, 721, 616, 1491, 493, 494, 1080, 1326], dtype='int64', length=293) index de predictions Int64Index([1208, 178, 281, 561, 891, 942, 1018, 899, 756, 920, ... 1248, 1488, 1019, 721, 616, 1491, 493, 494, 1080, 1326], dtype='int64', length=293) ------ Visualiser les résidus vs les prédictions ------
------ Test d'homoscédasticité des résidus ------ P-valeur du test de Breusch-Pagan: 1.5755002307903877e-12
Le coefficient de détermination R² nous indique que la variable "length" explique 45 % de la variance de la variable cible 'margin_low'. La variable "length" est statistiquement significative puisque la P-valeur associée au test est inférieure au seuil de signification (0.05 soit 5 %). Plus précisément, c'est le coefficient de la variable length qui est significativement différent de 0, ce qui confère à cette variable son pouvoir explicatif.
L'estimation du coefficient de length est de -0.499 ; la margin_low d'un billet diminue de 0.499 mm lorsque sa longueur s'accroît d'un mm. Nous pouvons affirmer, avec un niveau de confiance de 95%, que ce coefficient se trouve dans l'intervalle de confiance compris entre -0.532 et -0.466.
On constate également que la distribution des résidus est à peu près symétrique / normale.
Concernant l'homoscédascticité des résidus, si en observant les nuages de points rouges ci-dessus celle-ci semble respectée, les résultats du test de Breusch-Pagan nous indiquent une violation de cette condition. Je vais me fier aux graphiques.
Essayons la régression avec les 2 autres variables.
# Effectuer une régression linéaire simple en utilisant la variable explicative is_genuine
simple_linear_regression(df_train, df_test, 'margin_low', 'is_genuine')
OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.600 Model: OLS Adj. R-squared: 0.600 Method: Least Squares F-statistic: 1751. Date: Mon, 26 Aug 2024 Prob (F-statistic): 1.40e-234 Time: 10:54:14 Log-Likelihood: -650.69 No. Observations: 1170 AIC: 1305. Df Residuals: 1168 BIC: 1316. Df Model: 1 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 5.2072 0.021 245.349 0.000 5.166 5.249 is_genuine[T.True] -1.0920 0.026 -41.848 0.000 -1.143 -1.041 ============================================================================== Omnibus: 18.369 Durbin-Watson: 2.028 Prob(Omnibus): 0.000 Jarque-Bera (JB): 29.729 Skew: 0.104 Prob(JB): 3.50e-07 Kurtosis: 3.753 Cond. No. 3.20 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. ------ Visualiser l'allure de la distribution des résidus ------
------ Test d'homoscédasticité des résidus ------ P-valeur du test de Breusch-Pagan: 1.7951403343096245e-34
Le coefficient de détermination R² est plus élevé avec la variable "is_genuine" et le pouvoir explicatif de cette dernière est plus grand que celui de "length" et statistiquement significatif puisque la p-valeur est inférieure à 0.05. Les résidus sont normalement distribués mais, à nouveau, l'hypothèse d'homoscédasticité des résidus est violée.
A présent, effectuons une régression linéaire multiple.
Les hypothèses de la régression linéaire simple (énumérées plus haut) s'appliquent à la régression multiple
# Visualiser les relations entre chaque variable explicative et la variable cible
plt.figure(figsize=(20, 16))
palette = ['red', 'blue']
plt.subplot(4,3,1)
sns.scatterplot(x='is_genuine', y='margin_low', data=df_without_nan, hue='is_genuine', palette=palette)
plt.title('margin_low expliquée par is_genuine', fontsize=16, pad=20)
plt.xlabel('is_genuine')
plt.xticks(np.arange(0, 2))
plt.ylabel('margin_low (mm)')
plt.legend()
plt.subplot(4,3,2)
sns.scatterplot(x='diagonal', y='margin_low', data=df_without_nan, hue='is_genuine', palette=palette)
plt.title('margin_low expliquée par diagonal', fontsize=16, pad=20)
plt.xlabel('diagonal (mm)')
plt.ylabel('margin_low (mm)')
plt.legend()
plt.subplot(4,3,3)
sns.scatterplot(x='height_left', y='margin_low', data=df_without_nan, hue='is_genuine', palette=palette)
plt.title('margin_low expliquée par height_left', fontsize=16, pad=20)
plt.xlabel('heigth_left (mm)')
plt.ylabel('margin_low (mm)')
plt.legend()
plt.subplot(4,3,4)
sns.scatterplot(x='height_right', y='margin_low', data=df_without_nan, hue='is_genuine', palette=palette)
plt.title('margin_low expliquée par height_right', fontsize=16, pad=20)
plt.xlabel('height_right (mm)')
plt.ylabel('margin_low (mm)')
plt.legend()
plt.subplot(4,3,5)
sns.scatterplot(x='margin_up', y='margin_low', data=df_without_nan, hue='is_genuine', palette=palette)
plt.title('margin_low expliquée par margin_up', fontsize=16, pad=20)
plt.xlabel('margin_up (mm)')
plt.ylabel('margin_low (mm)')
plt.legend()
plt.subplot(4,3,6)
sns.scatterplot(x='length', y='margin_low', data=df_without_nan, hue='is_genuine', palette=palette)
plt.title('margin_low expliquée par length', fontsize=16, pad=20)
plt.xlabel('length (mm)')
plt.ylabel('margin_low (mm)')
plt.legend()
plt.subplots_adjust(hspace=1)
plt.tight_layout()
sns.despine()
plt.show()
# Afficher la matrice de corrélation
df_without_nan.corr()
is_genuine | diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|---|
is_genuine | 1.000000 | 0.134720 | -0.373624 | -0.487177 | -0.783032 | -0.610412 | 0.850223 |
diagonal | 0.134720 | 1.000000 | 0.018265 | -0.019428 | -0.111534 | -0.059147 | 0.100758 |
height_left | -0.373624 | 0.018265 | 1.000000 | 0.235132 | 0.302643 | 0.243812 | -0.314344 |
height_right | -0.487177 | -0.019428 | 0.235132 | 1.000000 | 0.391085 | 0.306867 | -0.404272 |
margin_low | -0.783032 | -0.111534 | 0.302643 | 0.391085 | 1.000000 | 0.431606 | -0.666753 |
margin_up | -0.610412 | -0.059147 | 0.243812 | 0.306867 | 0.431606 | 1.000000 | -0.521139 |
length | 0.850223 | 0.100758 | -0.314344 | -0.404272 | -0.666753 | -0.521139 | 1.000000 |
Comme évoqué plus haut, "is_genuine" et "length", et dans une moindre mesure "margin_up" et "height_right", sont les variables les plus corrélées à la variable cible "margin_low".
Quant à "height_left" et "diagonal", on peut d'ores et déjà les exclure (relation linéaire inexistante ou insuffisante avec margin_low, coefficients de corrélation de -0.11 et 0.30 respectivement).
On remarque également que is_genuine et length sont fortement corrélées, ce qui pourrait causer des problèmes de multicolinéarité, autrement dit de la redondance dans l'explication de la variance de margin_low. Il nous faudra peut-être exclure l'une des deux, en particulier si leur VIF est supérieur à 5.
# Fonction qui va effectuer plusieurs fois une régression linéaire multiple
# ...et progressivement éliminer les variables restantes qui ne sont pas statistiquement significatives
def multiple_regression_backward_selected(df_train, dependent_variable):
"""Linear model designed by backward selection.
Parameters:
-----------
df : pandas dataFrame with all possible predictors and dependent_variable
dependent_variable: string, name of dependent_variable column in df
Returns:
--------
model: an "optimal" fitted statsmodels linear model
with an intercept
selected by backward selection
evaluated by parameters p-value
"""
remaining = set(df_train._get_numeric_data().columns)
if dependent_variable in remaining:
remaining.remove(dependent_variable)
cond = True
while remaining and cond:
formula = "{} ~ {} + 1".format(dependent_variable,' + '.join(remaining))
print('_______________________________')
print(formula)
multiple_reg_model = smf.ols(formula, df_train).fit()
print(multiple_reg_model.summary())
# # Jauger les éventuels problèmes de colinéarité
# variables = multiple_reg_model.model.exog
# VIF = [variance_inflation_factor(variables, i) for i in np.arange(1,variables.shape[1])]
# print(VIF)
# Récupérer la p-valeur de chaque variable...
score = multiple_reg_model.pvalues[1:]
# ...puis supprimer la variable qui a la p-valeur la plus élevée
# si cette dernière est supérieure à 0.05 (seuil de signification de 5%)
toRemove = score[score == score.max()]
if toRemove.values > 0.05:
print('remove', toRemove.index[0], '(p-value :', round(toRemove.values[0],3), ')')
remaining.remove(toRemove.index[0])
else:
cond = False
print('is the final model !')
print('')
# Afficher les résultats de la régression multiple
print(multiple_reg_model.summary())
# Récupérer les résidus
residuals = multiple_reg_model.resid
print()
print('***** Visualiser l\'allure de la distribution des résidus *****')
fig, ax = plt.subplots(figsize=(9, 6))
sns.histplot(residuals, color='blue', kde=True)
plt.title('Distribution des résidus')
plt.xlabel('Résidus')
plt.ylabel('Fréquence')
plt.show()
# Tester l'homoscédasticité des résidus
_, LM_p_value, _, _ = het_breuschpagan(residuals, multiple_reg_model.model.exog)
print('------ Test d\'homoscédasticité des résidus ------')
print(f'P-valeur du test de Breusch-Pagan: {LM_p_value}')
# Jauger les éventuels problèmes de colinéarité
variables = multiple_reg_model.model.exog
# Si le VIF est inférieur à 5, on considère que l'éventuelle multicolinéarité ne pose pas de problème
# Si le VIF est compris entre 5 et 10, l'éventuelle multicolinéarité pourrait poser problème
# Si le VIF est supérieur à 10, l'estimation des coefficients est fortement biaisée par la multicolinéarité...
# ... rendant ces coefficients inutilisables
print()
print('------ VIF des variables explicatives restantes ------')
VIF = [variance_inflation_factor(variables, i) for i in np.arange(1,variables.shape[1])]
print(VIF)
print()
# Analyser l'atypicité et l'influence des observations
print("------ Atypicité et influence des observations ------")
n = df_train[dependent_variable].count()
p = df_train.shape[1]
influences = multiple_reg_model.get_influence()
summary_fr = influences.summary_frame()
print(summary_fr)
hii = summary_fr['hat_diag']
atypical_indices = []
for i, value in enumerate(hii):
if value > 2 * p / n:
atypical_indices.append(summary_fr.index[i])
print("Observations atypiques:")
print(atypical_indices)
cooks_distance = summary_fr['cooks_d']
influent_indices = []
for i, value in enumerate(cooks_distance):
if value > 4 / (n-p):
influent_indices.append(summary_fr.index[i])
print(" Observations influentes:")
print(influent_indices)
outliers_indices = []
for i in df_train.index:
if i in atypical_indices and i in influent_indices:
outliers_indices.append(i)
return multiple_reg_model, outliers_indices
# Utiliser la fonction ci-dessus pour effectuer la régression multiple
# Exclure au préalables les variables height_left et diagonal
multiple_reg_variables = ['height_right', 'margin_low', 'margin_up', 'length', 'is_genuine']
multiple_reg, outlier_index_list = multiple_regression_backward_selected(df_train[multiple_reg_variables], 'margin_low')
multiple_reg
_______________________________ margin_low ~ height_right + is_genuine + length + margin_up + 1 OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.602 Method: Least Squares F-statistic: 442.4 Date: Mon, 26 Aug 2024 Prob (F-statistic): 6.80e-232 Time: 10:54:17 Log-Likelihood: -646.11 No. Observations: 1170 AIC: 1302. Df Residuals: 1165 BIC: 1328. Df Model: 4 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 4.5273 5.305 0.853 0.394 -5.880 14.935 is_genuine[T.True] -1.1443 0.055 -20.846 0.000 -1.252 -1.037 height_right 0.0162 0.043 0.376 0.707 -0.068 0.101 length -0.0030 0.026 -0.113 0.910 -0.054 0.049 margin_up -0.2015 0.067 -2.998 0.003 -0.333 -0.070 ============================================================================== Omnibus: 18.732 Durbin-Watson: 2.023 Prob(Omnibus): 0.000 Jarque-Bera (JB): 30.943 Skew: 0.097 Prob(JB): 1.91e-07 Kurtosis: 3.773 Cond. No. 6.60e+04 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. [2] The condition number is large, 6.6e+04. This might indicate that there are strong multicollinearity or other numerical problems. remove length (p-value : 0.91 ) _______________________________ margin_low ~ height_right + is_genuine + margin_up + 1 OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.602 Method: Least Squares F-statistic: 590.4 Date: Mon, 26 Aug 2024 Prob (F-statistic): 2.59e-233 Time: 10:54:17 Log-Likelihood: -646.12 No. Observations: 1170 AIC: 1300. Df Residuals: 1166 BIC: 1320. Df Model: 3 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 4.2080 4.492 0.937 0.349 -4.606 13.022 is_genuine[T.True] -1.1490 0.036 -31.527 0.000 -1.220 -1.077 height_right 0.0161 0.043 0.374 0.709 -0.068 0.101 margin_up -0.2014 0.067 -2.998 0.003 -0.333 -0.070 ============================================================================== Omnibus: 18.800 Durbin-Watson: 2.023 Prob(Omnibus): 0.000 Jarque-Bera (JB): 31.124 Skew: 0.097 Prob(JB): 1.74e-07 Kurtosis: 3.775 Cond. No. 3.79e+04 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. [2] The condition number is large, 3.79e+04. This might indicate that there are strong multicollinearity or other numerical problems. remove height_right (p-value : 0.709 ) _______________________________ margin_low ~ is_genuine + margin_up + 1 OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.602 Method: Least Squares F-statistic: 886.1 Date: Mon, 26 Aug 2024 Prob (F-statistic): 8.27e-235 Time: 10:54:17 Log-Likelihood: -646.19 No. Observations: 1170 AIC: 1298. Df Residuals: 1167 BIC: 1314. Df Model: 2 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 5.8844 0.227 25.975 0.000 5.440 6.329 is_genuine[T.True] -1.1545 0.033 -34.656 0.000 -1.220 -1.089 margin_up -0.2016 0.067 -3.003 0.003 -0.333 -0.070 ============================================================================== Omnibus: 18.930 Durbin-Watson: 2.023 Prob(Omnibus): 0.000 Jarque-Bera (JB): 31.406 Skew: 0.097 Prob(JB): 1.51e-07 Kurtosis: 3.779 Cond. No. 65.2 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. is the final model ! OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.602 Method: Least Squares F-statistic: 886.1 Date: Mon, 26 Aug 2024 Prob (F-statistic): 8.27e-235 Time: 10:54:17 Log-Likelihood: -646.19 No. Observations: 1170 AIC: 1298. Df Residuals: 1167 BIC: 1314. Df Model: 2 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 5.8844 0.227 25.975 0.000 5.440 6.329 is_genuine[T.True] -1.1545 0.033 -34.656 0.000 -1.220 -1.089 margin_up -0.2016 0.067 -3.003 0.003 -0.333 -0.070 ============================================================================== Omnibus: 18.930 Durbin-Watson: 2.023 Prob(Omnibus): 0.000 Jarque-Bera (JB): 31.406 Skew: 0.097 Prob(JB): 1.51e-07 Kurtosis: 3.779 Cond. No. 65.2 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. ***** Visualiser l'allure de la distribution des résidus *****
------ Test d'homoscédasticité des résidus ------ P-valeur du test de Breusch-Pagan: 1.1912050767104977e-32 ------ VIF des variables explicatives restantes ------ [1.6409829916476797, 1.6409829916476788] ------ Atypicité et influence des observations ------ dfb_Intercept dfb_is_genuine[T.True] dfb_margin_up cooks_d \ 893 0.051124 -0.047068 -0.051348 0.001242 1071 0.001612 0.025128 -0.005872 0.000697 260 0.064469 -0.022992 -0.064752 0.001891 1097 -0.017954 0.021874 0.016308 0.000202 1224 -0.036332 -0.007138 0.041364 0.001470 ... ... ... ... ... 1161 0.049668 -0.066179 -0.044191 0.001880 1328 0.001620 -0.003003 -0.001304 0.000005 884 0.000528 0.005607 -0.000530 0.000057 1496 0.000461 -0.004772 0.000266 0.000020 1157 -0.016208 -0.014203 0.020246 0.000733 standard_resid hat_diag dffits_internal student_resid dffits 893 -0.915439 0.004425 -0.061029 -0.915376 -0.061025 1071 -0.901486 0.002568 -0.045738 -0.901414 -0.045735 260 1.067906 0.004950 0.075319 1.067971 0.075324 1097 -0.365433 0.004509 -0.024595 -0.365297 -0.024585 1224 1.031797 0.004126 0.066411 1.031826 0.066413 ... ... ... ... ... ... 1161 1.205977 0.003862 0.075093 1.206212 0.075108 1328 0.068507 0.002887 0.003686 0.068478 0.003685 884 0.363657 0.001294 0.013091 0.363522 0.013086 1496 0.154631 0.002528 0.007785 0.154567 0.007782 1157 0.840522 0.003104 0.046900 0.840416 0.046894 [1170 rows x 9 columns] Observations atypiques: [48, 664, 1029, 52] Observations influentes: [1229, 1422, 1284, 1199, 791, 1084, 1143, 1060, 1482, 354, 664, 1254, 1412, 1163, 1348, 1198, 1134, 1329, 1244, 1053, 1290, 1413, 341, 1376, 1429, 1133, 1420, 1075, 1029, 1463, 1450, 1478, 1223, 1426, 1122, 52, 1341, 1361, 239, 1103, 1027, 1023, 1472, 1383, 924, 1015, 1124, 1110, 1416, 1415, 1115, 1022, 897, 1407, 1495, 1333, 1353, 1403, 1245, 1459, 1025, 1111, 1031, 1177, 1345, 1092, 1464, 1252, 1048, 1074, 1473, 1174, 1310, 1010, 1355, 1041, 1215, 1089, 1226, 1378, 1094, 1024, 1160, 1186, 1154, 477, 1073]
<statsmodels.regression.linear_model.RegressionResultsWrapper at 0x1f9679306d0>
# Visualiser les outliers (observations qui sont à la fois atypiques et influentes)
outlier_index_list
[664, 1029, 52]
# Retirer les outliers
df_train = df_train.loc[~df_train.index.isin(outlier_index_list)]
# Vérifier
df_train.index.isin(outlier_index_list).sum()
0
# Lancer une nouvelle régression multiple sans les outliers
multiple_reg, outlier_index_list = multiple_regression_backward_selected(df_train[multiple_reg_variables], 'margin_low')
multiple_reg
_______________________________ margin_low ~ height_right + is_genuine + length + margin_up + 1 OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.602 Method: Least Squares F-statistic: 442.1 Date: Mon, 26 Aug 2024 Prob (F-statistic): 1.41e-231 Time: 10:54:18 Log-Likelihood: -641.22 No. Observations: 1167 AIC: 1292. Df Residuals: 1162 BIC: 1318. Df Model: 4 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 3.9781 5.295 0.751 0.453 -6.410 14.366 is_genuine[T.True] -1.1435 0.055 -20.830 0.000 -1.251 -1.036 height_right 0.0176 0.043 0.409 0.683 -0.067 0.102 length 0.0003 0.026 0.012 0.991 -0.051 0.052 margin_up -0.1900 0.068 -2.785 0.005 -0.324 -0.056 ============================================================================== Omnibus: 19.466 Durbin-Watson: 2.017 Prob(Omnibus): 0.000 Jarque-Bera (JB): 32.764 Skew: 0.096 Prob(JB): 7.68e-08 Kurtosis: 3.798 Cond. No. 6.60e+04 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. [2] The condition number is large, 6.6e+04. This might indicate that there are strong multicollinearity or other numerical problems. remove length (p-value : 0.991 ) _______________________________ margin_low ~ height_right + is_genuine + margin_up + 1 OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.602 Method: Least Squares F-statistic: 590.0 Date: Mon, 26 Aug 2024 Prob (F-statistic): 5.34e-233 Time: 10:54:18 Log-Likelihood: -641.22 No. Observations: 1167 AIC: 1290. Df Residuals: 1163 BIC: 1311. Df Model: 3 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 4.0111 4.480 0.895 0.371 -4.779 12.801 is_genuine[T.True] -1.1430 0.037 -31.295 0.000 -1.215 -1.071 height_right 0.0176 0.043 0.410 0.682 -0.067 0.102 margin_up -0.1900 0.068 -2.786 0.005 -0.324 -0.056 ============================================================================== Omnibus: 19.459 Durbin-Watson: 2.017 Prob(Omnibus): 0.000 Jarque-Bera (JB): 32.745 Skew: 0.097 Prob(JB): 7.75e-08 Kurtosis: 3.798 Cond. No. 3.79e+04 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. [2] The condition number is large, 3.79e+04. This might indicate that there are strong multicollinearity or other numerical problems. remove height_right (p-value : 0.682 ) _______________________________ margin_low ~ is_genuine + margin_up + 1 OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.603 Method: Least Squares F-statistic: 885.5 Date: Mon, 26 Aug 2024 Prob (F-statistic): 1.73e-234 Time: 10:54:18 Log-Likelihood: -641.30 No. Observations: 1167 AIC: 1289. Df Residuals: 1164 BIC: 1304. Df Model: 2 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 5.8441 0.230 25.422 0.000 5.393 6.295 is_genuine[T.True] -1.1491 0.033 -34.384 0.000 -1.215 -1.083 margin_up -0.1903 0.068 -2.791 0.005 -0.324 -0.057 ============================================================================== Omnibus: 19.607 Durbin-Watson: 2.017 Prob(Omnibus): 0.000 Jarque-Bera (JB): 33.072 Skew: 0.097 Prob(JB): 6.58e-08 Kurtosis: 3.802 Cond. No. 66.2 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. is the final model ! OLS Regression Results ============================================================================== Dep. Variable: margin_low R-squared: 0.603 Model: OLS Adj. R-squared: 0.603 Method: Least Squares F-statistic: 885.5 Date: Mon, 26 Aug 2024 Prob (F-statistic): 1.73e-234 Time: 10:54:18 Log-Likelihood: -641.30 No. Observations: 1167 AIC: 1289. Df Residuals: 1164 BIC: 1304. Df Model: 2 Covariance Type: nonrobust ====================================================================================== coef std err t P>|t| [0.025 0.975] -------------------------------------------------------------------------------------- Intercept 5.8441 0.230 25.422 0.000 5.393 6.295 is_genuine[T.True] -1.1491 0.033 -34.384 0.000 -1.215 -1.083 margin_up -0.1903 0.068 -2.791 0.005 -0.324 -0.057 ============================================================================== Omnibus: 19.607 Durbin-Watson: 2.017 Prob(Omnibus): 0.000 Jarque-Bera (JB): 33.072 Skew: 0.097 Prob(JB): 6.58e-08 Kurtosis: 3.802 Cond. No. 66.2 ============================================================================== Notes: [1] Standard Errors assume that the covariance matrix of the errors is correctly specified. ***** Visualiser l'allure de la distribution des résidus *****
------ Test d'homoscédasticité des résidus ------ P-valeur du test de Breusch-Pagan: 1.3758856997860188e-32 ------ VIF des variables explicatives restantes ------ [1.6563355365205068, 1.656335536520507] ------ Atypicité et influence des observations ------ dfb_Intercept dfb_is_genuine[T.True] dfb_margin_up cooks_d \ 893 0.052682 -0.048413 -0.052906 0.001306 1071 0.001981 0.024756 -0.006170 0.000697 260 0.066544 -0.024415 -0.066827 0.001997 1097 -0.017547 0.021317 0.015978 0.000191 1224 -0.037450 -0.006230 0.042417 0.001505 ... ... ... ... ... 1161 0.050743 -0.067412 -0.045278 0.001946 1328 0.001835 -0.003397 -0.001483 0.000006 884 0.000547 0.005607 -0.000550 0.000058 1496 0.000426 -0.004900 0.000317 0.000022 1157 -0.016901 -0.013730 0.020897 0.000749 standard_resid hat_diag dffits_internal student_resid dffits 893 -0.926638 0.004541 -0.062583 -0.926582 -0.062580 1071 -0.899606 0.002579 -0.045741 -0.899532 -0.045737 260 1.082065 0.005089 0.077392 1.082145 0.077398 1097 -0.353418 0.004568 -0.023941 -0.353285 -0.023932 1224 1.033401 0.004209 0.067187 1.033431 0.067189 ... ... ... ... ... ... 1161 1.221017 0.003901 0.076410 1.221275 0.076426 1328 0.077400 0.002898 0.004173 0.077367 0.004171 884 0.365783 0.001298 0.013185 0.365647 0.013180 1496 0.160250 0.002536 0.008080 0.160183 0.008076 1157 0.844272 0.003143 0.047403 0.844168 0.047397 [1167 rows x 9 columns] Observations atypiques: [48] Observations influentes: [1229, 1422, 1284, 1199, 791, 1084, 1143, 1060, 1482, 354, 1254, 1412, 1163, 1348, 1198, 1134, 1329, 1244, 1053, 1290, 1413, 341, 1376, 1429, 1133, 1420, 1075, 1463, 1450, 1478, 1223, 1426, 1122, 1341, 1361, 239, 1103, 1027, 1023, 1472, 1383, 924, 804, 1015, 1124, 1110, 1416, 1415, 1115, 1022, 897, 1407, 1495, 1333, 1353, 1403, 1245, 1459, 1025, 1111, 1031, 1177, 1345, 1092, 1464, 1252, 1048, 1074, 1473, 1174, 1310, 1010, 1355, 1041, 1215, 1089, 1226, 1378, 1094, 1024, 1160, 1186, 1154, 477, 1073]
<statsmodels.regression.linear_model.RegressionResultsWrapper at 0x1f96ace9970>
D'après les résultats ci-dessus, seules les variables is_genuine et margin_up sont satistiquement significatives car leurs p-valeurs associées sont inférieures au seuil de signification.
# Prédire la margin_low à partir de is_genuine et margin_up sur l'ensemble de test
# et afficher les intervalles de prédiction pour les billets qui s'y trouvent
variables = ['is_genuine', 'margin_up']
predictions = multiple_reg.predict(df_test[variables])
summary_frame = multiple_reg.get_prediction(df_test[variables]).summary_frame()
fig, ax = plt.subplots(figsize=(12, 8))
sns.lineplot(data=summary_frame, x=predictions.index, y=summary_frame['obs_ci_upper'], color='brown', label="Limite haute de l'intervalle de prédiction")
sns.lineplot(data=summary_frame, x=predictions.index, y=summary_frame['obs_ci_lower'], color='orange', label="Limite basse de l'intervalle de prédiction")
sns.scatterplot(data=predictions, x=predictions.index, y=predictions, color='blue', label="margin_low prédite par le modèle")
sns.scatterplot(data=predictions, x=df_test["margin_low"].index, y=df_test["margin_low"], color='gray', label="margin_low observée dans l'ensemble de test")
plt.ylabel("margin_low (mm)")
plt.xlabel("Billets dans l'ensemble de test")
plt.xticks([])
ax.spines[['top', 'right', 'left', 'bottom']].set_visible(False)
plt.show()
# Afficher le R² du modèle sur l'ensemble de test
predictions = multiple_reg.predict(df_test[variables])
# Afficher le R² sur l'ensemble de test
r2 = r2_score(df_test['margin_low'], predictions)
mse = mean_squared_error(df_test['margin_low'], predictions)
rmse = np.sqrt(mse)
print("Coefficient de détermination (R²) sur l'ensemble de test :", r2)
print("Erreur quadratique moyenne sur l'ensemble de test :", mse)
print("Erreur type sur l'ensemble de test:", rmse)
Coefficient de détermination (R²) sur l'ensemble de test : 0.6733672157443473 Erreur quadratique moyenne sur l'ensemble de test : 0.13767746151333726 Erreur type sur l'ensemble de test: 0.3710491362519757
# Lister les index des valeurs manquantes
nan_indices = df.loc[df['margin_low'].isna()].index
nan_indices
Int64Index([ 72, 99, 151, 197, 241, 251, 284, 334, 410, 413, 445, 481, 505, 611, 654, 675, 710, 739, 742, 780, 798, 844, 845, 871, 895, 919, 945, 946, 981, 1076, 1121, 1176, 1303, 1315, 1347, 1435, 1438], dtype='int64')
# Effectuer les prédictions pour les lignes contenant des valeurs manquantes dans la colonne margin_low
df_pred = df.loc[df.index.isin(nan_indices)]
margin_low_preds = multiple_reg.predict(df_pred[variables])
summary_frame = multiple_reg.get_prediction(df_pred[variables]).summary_frame()
fig, ax = plt.subplots(figsize=(12, 8))
sns.lineplot(data=summary_frame, x=margin_low_preds.index, y=summary_frame['obs_ci_upper'], color='brown', label="Limite haute de l'intervalle de prédiction")
sns.lineplot(data=summary_frame, x=margin_low_preds.index, y=summary_frame['obs_ci_lower'], color='orange', label="Limite basse de l'intervalle de prédiction")
sns.scatterplot(data=margin_low_preds, x=margin_low_preds.index, y=margin_low_preds, color='blue', label="margin_low prédite par le modèle")
plt.ylabel("margin_low (mm)")
plt.xlabel("Billets pour lesquels la valeur de margin_low était manquante")
plt.xticks([])
ax.spines[['top', 'left', 'right', 'bottom']].set_visible(False)
plt.show()
# Afficher les 37 valeurs manquantes
df_nan = df.loc[df['margin_low'].index.isin(nan_indices)]
df_nan
is_genuine | diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|---|
72 | True | 171.94 | 103.89 | 103.45 | NaN | 3.25 | 112.79 |
99 | True | 171.93 | 104.07 | 104.18 | NaN | 3.14 | 113.08 |
151 | True | 172.07 | 103.80 | 104.38 | NaN | 3.02 | 112.93 |
197 | True | 171.45 | 103.66 | 103.80 | NaN | 3.62 | 113.27 |
241 | True | 171.83 | 104.14 | 104.06 | NaN | 3.02 | 112.36 |
251 | True | 171.80 | 103.26 | 102.82 | NaN | 2.95 | 113.22 |
284 | True | 171.92 | 103.83 | 103.76 | NaN | 3.23 | 113.29 |
334 | True | 171.85 | 103.70 | 103.96 | NaN | 3.00 | 113.36 |
410 | True | 172.56 | 103.72 | 103.51 | NaN | 3.12 | 112.95 |
413 | True | 172.30 | 103.66 | 103.50 | NaN | 3.16 | 112.95 |
445 | True | 172.34 | 104.42 | 103.22 | NaN | 3.01 | 112.97 |
481 | True | 171.81 | 103.53 | 103.96 | NaN | 2.71 | 113.99 |
505 | True | 172.01 | 103.97 | 104.05 | NaN | 2.98 | 113.65 |
611 | True | 171.80 | 103.68 | 103.49 | NaN | 3.30 | 112.84 |
654 | True | 171.97 | 103.69 | 103.54 | NaN | 2.70 | 112.79 |
675 | True | 171.60 | 103.85 | 103.91 | NaN | 2.56 | 113.27 |
710 | True | 172.03 | 103.97 | 103.86 | NaN | 3.07 | 112.65 |
739 | True | 172.07 | 103.74 | 103.76 | NaN | 3.09 | 112.41 |
742 | True | 172.14 | 104.06 | 103.96 | NaN | 3.24 | 113.07 |
780 | True | 172.41 | 103.95 | 103.79 | NaN | 3.13 | 113.41 |
798 | True | 171.96 | 103.84 | 103.62 | NaN | 3.01 | 114.44 |
844 | True | 171.62 | 104.14 | 104.49 | NaN | 2.99 | 113.35 |
845 | True | 172.02 | 104.21 | 104.05 | NaN | 2.90 | 113.62 |
871 | True | 171.37 | 104.07 | 103.75 | NaN | 3.07 | 113.27 |
895 | True | 171.81 | 103.68 | 103.80 | NaN | 2.98 | 113.82 |
919 | True | 171.92 | 103.68 | 103.45 | NaN | 2.58 | 113.68 |
945 | True | 172.09 | 103.74 | 103.52 | NaN | 3.02 | 112.78 |
946 | True | 171.63 | 103.87 | 104.66 | NaN | 3.27 | 112.68 |
981 | True | 172.02 | 104.23 | 103.72 | NaN | 2.99 | 113.37 |
1076 | False | 171.57 | 104.27 | 104.44 | NaN | 3.21 | 111.87 |
1121 | False | 171.40 | 104.38 | 104.19 | NaN | 3.17 | 112.39 |
1176 | False | 171.59 | 104.05 | 103.94 | NaN | 3.02 | 111.29 |
1303 | False | 172.17 | 104.49 | 103.76 | NaN | 2.93 | 111.21 |
1315 | False | 172.08 | 104.15 | 104.17 | NaN | 3.40 | 112.29 |
1347 | False | 171.72 | 104.46 | 104.12 | NaN | 3.61 | 110.31 |
1435 | False | 172.66 | 104.33 | 104.41 | NaN | 3.56 | 111.47 |
1438 | False | 171.90 | 104.28 | 104.29 | NaN | 3.24 | 111.49 |
# Remplacer les valeurs manquantes par les prédictions
df['margin_low'].fillna(margin_low_preds, inplace=True)
# Vérifier si le remplacement a été effectué
df.loc[df['margin_low'].index.isin(nan_indices)]
is_genuine | diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|---|
72 | True | 171.94 | 103.89 | 103.45 | 4.076606 | 3.25 | 112.79 |
99 | True | 171.93 | 104.07 | 104.18 | 4.097537 | 3.14 | 113.08 |
151 | True | 172.07 | 103.80 | 104.38 | 4.120371 | 3.02 | 112.93 |
197 | True | 171.45 | 103.66 | 103.80 | 4.006201 | 3.62 | 113.27 |
241 | True | 171.83 | 104.14 | 104.06 | 4.120371 | 3.02 | 112.36 |
251 | True | 171.80 | 103.26 | 102.82 | 4.133691 | 2.95 | 113.22 |
284 | True | 171.92 | 103.83 | 103.76 | 4.080412 | 3.23 | 113.29 |
334 | True | 171.85 | 103.70 | 103.96 | 4.124177 | 3.00 | 113.36 |
410 | True | 172.56 | 103.72 | 103.51 | 4.101343 | 3.12 | 112.95 |
413 | True | 172.30 | 103.66 | 103.50 | 4.093731 | 3.16 | 112.95 |
445 | True | 172.34 | 104.42 | 103.22 | 4.122274 | 3.01 | 112.97 |
481 | True | 171.81 | 103.53 | 103.96 | 4.179359 | 2.71 | 113.99 |
505 | True | 172.01 | 103.97 | 104.05 | 4.127983 | 2.98 | 113.65 |
611 | True | 171.80 | 103.68 | 103.49 | 4.067092 | 3.30 | 112.84 |
654 | True | 171.97 | 103.69 | 103.54 | 4.181262 | 2.70 | 112.79 |
675 | True | 171.60 | 103.85 | 103.91 | 4.207902 | 2.56 | 113.27 |
710 | True | 172.03 | 103.97 | 103.86 | 4.110857 | 3.07 | 112.65 |
739 | True | 172.07 | 103.74 | 103.76 | 4.107051 | 3.09 | 112.41 |
742 | True | 172.14 | 104.06 | 103.96 | 4.078509 | 3.24 | 113.07 |
780 | True | 172.41 | 103.95 | 103.79 | 4.099440 | 3.13 | 113.41 |
798 | True | 171.96 | 103.84 | 103.62 | 4.122274 | 3.01 | 114.44 |
844 | True | 171.62 | 104.14 | 104.49 | 4.126080 | 2.99 | 113.35 |
845 | True | 172.02 | 104.21 | 104.05 | 4.143205 | 2.90 | 113.62 |
871 | True | 171.37 | 104.07 | 103.75 | 4.110857 | 3.07 | 113.27 |
895 | True | 171.81 | 103.68 | 103.80 | 4.127983 | 2.98 | 113.82 |
919 | True | 171.92 | 103.68 | 103.45 | 4.204096 | 2.58 | 113.68 |
945 | True | 172.09 | 103.74 | 103.52 | 4.120371 | 3.02 | 112.78 |
946 | True | 171.63 | 103.87 | 104.66 | 4.072800 | 3.27 | 112.68 |
981 | True | 172.02 | 104.23 | 103.72 | 4.126080 | 2.99 | 113.37 |
1076 | False | 171.57 | 104.27 | 104.44 | 5.233268 | 3.21 | 111.87 |
1121 | False | 171.40 | 104.38 | 104.19 | 5.240879 | 3.17 | 112.39 |
1176 | False | 171.59 | 104.05 | 103.94 | 5.269422 | 3.02 | 111.29 |
1303 | False | 172.17 | 104.49 | 103.76 | 5.286547 | 2.93 | 111.21 |
1315 | False | 172.08 | 104.15 | 104.17 | 5.197114 | 3.40 | 112.29 |
1347 | False | 171.72 | 104.46 | 104.12 | 5.157154 | 3.61 | 110.31 |
1435 | False | 172.66 | 104.33 | 104.41 | 5.166668 | 3.56 | 111.47 |
1438 | False | 171.90 | 104.28 | 104.29 | 5.227559 | 3.24 | 111.49 |
# Nombre de nan dans la colonne margin_low
df['margin_low'].isna().sum()
0
D'après les statistiques (voir les toutes premières cellules de l'analyse exploratoire), la margin_low médiane est de 4.11 mm pour les vrais billets et de 5.19 mm pour les faux billets. On peut donc affirmer que les prédictions qui remplacent les NaN sont très proches de ce qui est attendu.
# Comparer les faux et les vrais billets à l'aide d'un boxplot
plt.figure(figsize=(20, 16))
palette = ['red', 'blue']
plt.subplot(4,3,1)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='diagonal', data=df, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('Diagonal', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Diagonale en mm')
plt.subplot(4,3,2)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='height_left', data=df, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('Height_left', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Hauteur gauche en mm')
plt.subplot(4,3,3)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='height_right', data=df, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('height_right', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Hauteur droite en mm')
plt.subplot(4,3,4)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='margin_low', data=df, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('margin_low', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Marge basse en mm')
plt.subplot(4,3,5)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='margin_up', data=df, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('margin_up', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Marge haute en mm')
plt.subplot(4,3,6)
meanprops = dict(marker='o', markersize=8, markeredgecolor='black', markerfacecolor='red')
mean_marker = mlines.Line2D([], [], color='red', marker='o', linestyle='None', markersize=8, label='Moyenne')
flierprops = dict(marker='o', markersize=5, markerfacecolor='darkorange', linestyle='none', alpha=0.7)
outliers_marker = mlines.Line2D([], [], color='darkorange', marker='o', linestyle='None', markersize=5, label='Outliers')
sns.boxplot(x='is_genuine', y='length', data=df, palette=palette, meanprops=meanprops, showmeans=True, flierprops=flierprops)
plt.title('length', fontsize=16, pad=20)
plt.xlabel('Catégorie des billets')
plt.ylabel('Longueur en mm')
sns.despine()
plt.legend(handles=[mean_marker,outliers_marker], bbox_to_anchor=(0.90, 1), loc='upper left')
plt.subplots_adjust(hspace=1)
plt.tight_layout()
plt.show()
Hypothèse fondamentale de la régression logistique:
- Logit(p) = Logit(p/1-p) = b0 + b1*x1
Autres hypothèses de la régression logistique (supposées ou vérifiées):
- La variable cible est binaire; elle suit une loi de bernoulli de paramètre p. C'est le cas puisque is_genuine ne peut prendre que deux valeurs; True ou False (1 ou 0).
- La taille du jeu de données est suffisante. C'est le cas, nous avons 1500 individus.
- Les individus sont indépendants. C'est le cas; le fait qu'un billet donné soit contrefait ou non n'influe pas sur l'authenticité d'un autre billet.
- Pas ou peu de multicolinéarité entre les variables explicatives utilisées. Vérifiée lors de la régression linéaire multiple; aucun VIF ne dépasse 5
# Créer une fonction pour centrer et réduire les données
# c'est à dire soustraire de chaque valeur la moyenne puis diviser chaque valeur centrée par l'écart type
def standardization(df):
# Instancier un scaler
scaler = StandardScaler()
# Ajustement et transformation des données
df_scaled = scaler.fit_transform(df)
# Conversion du tableau numpy en dataframe
df_scaled = pd.DataFrame(df_scaled, index=df.index, columns=df.columns)
return df_scaled
# Séparer la variable cible des variables explicatives
y = df.iloc[:,0]
X = df.iloc[:,1:]
# Centrer et réduire les variables explicatives
X = standardization(X)
# Afficher les variables explicatives
X.head()
diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|
0 | -0.486540 | 2.774123 | 3.163240 | 0.056462 | -1.128325 | 0.173651 |
1 | -1.633729 | -2.236535 | -0.799668 | -1.080631 | -0.696799 | 0.471666 |
2 | 2.397823 | 1.504756 | -1.291191 | -0.125473 | -0.912562 | 0.551901 |
3 | -1.961498 | -0.399294 | 0.060498 | -1.308050 | -0.610494 | 0.953075 |
4 | -0.748754 | 0.836669 | -1.414072 | -0.671278 | 1.417677 | -0.158750 |
A présent, séparons le jeu de données en deux sous-ensembles:
-Un sous-ensemble d'entraînement qui représente 80% des données
-Un sous-ensemble de test qui représente les 20% restant et sur lequel nous allons tester notre modèle de régression logistique, c'est à dire comparer les métriques obtenus sur l'ensemble de test à ceux obtenus sur l'ensemble d'entraînement.
Si les scores obtenus à l'entraînement sont significativement meilleurs qu'au test, il faut s'attendre à ce que le modèle souffre de surapprentissage.
# Train Test split
X_train, X_test, y_train, y_test = train_test_split(X, y, train_size=0.80, random_state=42)
# Afficher les dimensions des sous-ensembles
print(f'Dimensions de X_train : {X_train.shape}')
print(f'Dimensions de X_test : {X_test.shape}')
print(f'Dimensions de y_train : {y_train.shape}')
print(f'Dimensions de y_test : {y_test.shape}')
Dimensions de X_train : (1200, 6) Dimensions de X_test : (300, 6) Dimensions de y_train : (1200,) Dimensions de y_test : (300,)
# Afficher le nombre de vrais et de faux billets dans chaque sous-ensemble y
print('y_train:')
print(y_train.value_counts())
print('y_test:')
print(y_test.value_counts())
y_train: True 810 False 390 Name: is_genuine, dtype: int64 y_test: True 190 False 110 Name: is_genuine, dtype: int64
# Afficher X_train
X_train.head()
diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|
382 | 1.053972 | 1.972418 | -0.369585 | -0.610633 | -0.308426 | 0.666522 |
538 | -0.191548 | 1.571565 | -1.321911 | -0.610633 | -1.430393 | 0.941613 |
1493 | -1.076523 | 1.003691 | 2.118753 | 0.602267 | 0.856694 | -0.594311 |
1112 | 0.332882 | 0.836669 | 1.197146 | 1.709037 | 0.511473 | -1.224728 |
324 | 0.463989 | 1.304330 | -0.983989 | -0.459020 | -1.128325 | 0.735295 |
y_train.head()
382 True 538 True 1493 False 1112 False 324 True Name: is_genuine, dtype: bool
# L'accuracy score du dummy sera notre score repère / de base
dummy_clf = DummyClassifier(strategy='most_frequent', random_state=42) # on prédit la classe la plus fréquente à chaque fois
dummy_clf.fit(X_train, y_train)
y_pred = dummy_clf.predict(X_test)
accuracy_score = dummy_clf.score(X_test, y_test)
print(accuracy_score )
0.6333333333333333
Nous allons utiliser GridSearchCV de scikit learn pour entraîner plusieurs modèles de régression logistique, choisir le modèle le plus performant et implémenter la validation croisée par la même occasion.
# créer une fonction qui renvoie les scores du modèle à l'entrainement et au test
def scores(estimator):
train_score = estimator.score(X_train, y_train).round(4)
test_score = estimator.score(X_test, y_test).round(4)
return f'Train score : {train_score} | Test score : {test_score}'
# Créer une fonction qui entraîne plusieurs modèles de régression logistique
# tout en implémentant la validation croisée
def GridSearchCV_Logistic_Regression(X_train, X_test, y_train, y_test, random_state, x_variable):
'''
random_state: nombre quelconque passé en paramètre pour la reproductibilité des résultats
x_variable: variable explicative à utiliser pour la visualisation nuage de point + courbe sigmoïde
x_variable_coef_index: dictionnaire avec en clé le nom de la variable, en valeur l'index de la colonne
'''
warnings.filterwarnings('ignore')
# Instancier un classifieur
classifier = LogisticRegression(random_state=random_state)
parameters = [{'penalty': ['l1', 'l2'], 'solver': ['liblinear'], 'C': [1, 10, 100, 1000]}]
grid_search = GridSearchCV(classifier, parameters, cv=5)
grid_search.fit(X_train, y_train)
# On récupère le modèle qui s'est montré le plus performant pendant la phase d'entraînement
best_estimator = grid_search.best_estimator_
coefs = best_estimator.coef_
intercept = best_estimator.intercept_
# print(coefs, intercept)
# Effectuer des prédictions sur l'ensemble de test
y_pred = best_estimator.predict(X_test)
# Calculer les probabilités qu'un individu appartienne à la classe 0, à la classe 1
y_pred_proba = best_estimator.predict_proba(X_test)[:,1]
# Générer des valeurs z pour la courbe sigmoïde
z_values = np.linspace(-3, 3, 300)
# Index des coefficients
x_variable_coef_index = {'diagonal':0, 'height_left':1, 'height_right':2, 'margin_low':3, 'margin_up':4, 'length':5} # !! codé en dur...
# Calculer les probabilités à partir du coefficient de la variable explicative et de l'intercept
# coefs[0][x_variable_coef_index[x_variable]] correspond au coefficient de la variable length
sigmoid_probs = 1 / (1 + np.exp(-coefs[0][x_variable_coef_index[x_variable]]*z_values-intercept[0]))
print(f"------ Visualiser le nuage de points et la courbe sigmoïde ------")
fig, ax = plt.subplots(figsize=(12, 8))
sns.scatterplot(data=X_test, x=X_test[x_variable], y=y_test, color='blue')
# Ajouter la courbe sigmoïde
plt.plot(z_values, sigmoid_probs, color='red', label='Courbe sigmoïde')
plt.title("Données observées dans l'ensemble de test + courbe sigmoïde", fontsize=16, fontweight='bold', pad=20)
plt.xlabel(f"{x_variable} (centrée réduite)")
plt.ylabel("Probabilité de is_genuine")
plt.yticks(np.arange(0,1.01,0.1))
y_intersection = 0.5
x_intersection = -0.45
ax.axhline(y=y_intersection, color='gray', linestyle='--', label='Seuil de décision')
ax.axvline(x=x_intersection, color='lightgray', linestyle='--', label='')
plt.legend(loc='best')
sns.despine()
plt.show()
# Afficher l'accuracy score du meilleur modèle
score = round(best_estimator.score(X_test, y_test),2)
print()
print('Le modèle le plus performant a classifié correctement {} % des billets'.format(str(score*100)))
print()
# Afficher le rapport de classification
report = pd.DataFrame(classification_report(y_test, y_pred, output_dict=True)).T
print(report)
print()
# Comparer scores d'entraînement et de test
train_test_scores = scores(best_estimator)
print(train_test_scores)
print()
# Afficher le meilleur score lors de l'entraînement
best_score = grid_search.best_score_
print(f"Meilleur score atteint pendant l'entraînement: {best_score*100:.2f}")
print()
best_parameters = grid_search.best_params_
print(f'Meilleurs paramètres: {best_parameters}')
print()
# Afficher la matrice de confusion
conf_matrix = confusion_matrix(y_test, y_pred)
display_matrix = ConfusionMatrixDisplay(conf_matrix, display_labels=['Faux billets', 'Vrais billets'])
display_matrix.plot(cmap='Blues', colorbar=False)
plt.title('Matrice de confusion', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Classe prédite')
plt.ylabel('Classe réelle')
plt.show()
# Récupérer le taux de vrais positifs et le taux de faux positifs
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
# Visualiser le taux de faux positifs (axe des abscisses) versus le taux de vrais positifs (axe des ordonnées)
plt.figure(figsize=(12,8))
plt.plot([0,1], [0,1], '--')
plt.plot(fpr,tpr)
plt.title('Courbe ROC de la régression logistique', fontsize=16, fontweight='bold', pad=20)
plt.ylabel('Taux de vrais positifs')
plt.xlabel('Taux de faux positifs')
xticks = np.arange(0.00, 1.01, 0.10)
xlabels = [f'{x*100:.0f}%' for x in xticks]
plt.xticks(xticks, labels=xlabels)
yticks = np.arange(0.00, 1.01, 0.10)
ylabels = [f'{y*100:.0f}%' for y in yticks]
plt.yticks(yticks, labels=ylabels)
sns.despine()
plt.show()
# Calcul de l'aire sous la courbe ROC, plus celle-ci est proche de 1 et plus le modèle est bon
au_roc_curve = roc_auc_score(y_test, y_pred_proba)
print('Aire sous la courbe ROC: {}'.format(au_roc_curve))
# On enregistre le meilleur modèle avec pickle.
pickle.dump(grid_search.best_estimator_, open('./log_reg_model.pkl', 'wb'))
# Utiliser la fonction ci-dessus pour effectuer la régression logistique
GridSearchCV_Logistic_Regression(X_train, X_test, y_train, y_test, 42, "length")
------ Visualiser le nuage de points et la courbe sigmoïde ------
Le modèle le plus performant a classifié correctement 99.0 % des billets precision recall f1-score support False 1.000000 0.981818 0.990826 110.000000 True 0.989583 1.000000 0.994764 190.000000 accuracy 0.993333 0.993333 0.993333 0.993333 macro avg 0.994792 0.990909 0.992795 300.000000 weighted avg 0.993403 0.993333 0.993320 300.000000 Train score : 0.9925 | Test score : 0.9933 Meilleur score atteint pendant l'entraînement: 99.25 Meilleurs paramètres: {'C': 1, 'penalty': 'l1', 'solver': 'liblinear'}
Aire sous la courbe ROC: 0.9999043062200957
A présent, essayons de partitionner les données en deux clusters. Dans l'idéal, l'un de ces clusters contiendra uniquement des faux billets et le deuxième uniquement des vrais billets. Ces deux clusters doivent donc être les plus homogènes possible. On comparera ensuite les résulats du K-means à ceux de la régression logistique.
# Fonction qui affiche le nombre de vrais et de faux billets présents dans chaque cluster
def kmeans_confusion_matrix(clusters):
count = {}
keys = list(clusters.keys())
for key, value in clusters.items():
most_frequent = statistics.mode(value)
mode_count = value.count(most_frequent)
homogeneity_rate = round(mode_count / len(value) * 100, 2)
if most_frequent == 0:
nb_false = mode_count
nb_true = len(value) - nb_false
else:
nb_true = mode_count
nb_false = len(value) - nb_true
count[f"Cluster {keys[key]}"] = {"Nombre de vrais billets": nb_true, "Nombre de faux billets": nb_false, "Taux d'homogénéité": homogeneity_rate}
count_df = pd.DataFrame(count)
return count_df
# Fonction qui utilise l'algorithme k-means pour partitionner les données
def K_means_3D_clustering(nb_clusters, X_train, X_test, y_test, random_state, first_var_index, second_var_index, third_var_index):
'''
first_var_index: index de la première variable (colonne) utilisée dans le nuage de points en 3D
second_var_index: index de la seconde variable...
third_var_index: index de la troisième variable
'''
warnings.filterwarnings('ignore')
labels = y_test.values
colors = ['blue', 'red']
# Exécuter K-means
clf = KMeans(n_clusters=nb_clusters, init='k-means++', n_init=3, max_iter=300, random_state=random_state).fit(X_train)
predictions = clf.predict(X_test)
clusters = {i: [] for i in range(nb_clusters)}
for i, prediction in enumerate(predictions):
clusters[prediction].append(labels[i])
# Afficher la matrice de confusion
print("----- Visualiser le nombre de vrais et de faux billets dans chaque cluster ------")
print(kmeans_confusion_matrix(clusters))
print()
# On affiche un nuage de points en 3D car nous avons sélectionné 3 composantes
fig = plt.figure(figsize=(16,12))
ax = fig.add_subplot(111, projection='3d')
for cluster, bills in clusters.items():
print(f"Billets appartenant au cluster {cluster}: {bills}")
points = np.array([X_test.iloc[i] for i in range(len(predictions)) if predictions[i] == cluster])
ax.scatter(points[:, first_var_index], points[:, second_var_index], points[:, third_var_index], color=colors[cluster], edgecolor='black', linewidths=0.8, s=100)
ax.scatter([], [], [], color=colors[cluster], label=f"Cluster {cluster}", edgecolor='black', linewidths=0.8, s=100)
plt.legend(bbox_to_anchor=(1, 1), loc='upper left')
plt.title(f"K-means clustering sur l'ensemble de test", fontsize=16, fontweight='bold')
plt.show()
# On enregistre le meilleur modèle avec pickle.
pickle.dump(clf, open('./kmeans_model.pkl', 'wb'))
# return clusters
# Utiliser la fonction ci-dessus pour effectuer un clustering des billets
K_means_3D_clustering(2, X_train, X_test, y_test, 42, 3, 4, 5)
----- Visualiser le nombre de vrais et de faux billets dans chaque cluster ------ Cluster 0 Cluster 1 Nombre de vrais billets 188.00 2.0 Nombre de faux billets 1.00 109.0 Taux d'homogénéité 99.47 98.2 Billets appartenant au cluster 0: [True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, False, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True, True] Billets appartenant au cluster 1: [False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, True, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False, False]
On constate que le K-means est tout aussi efficace pour faire distinguer les vrais des faux billets. Le taux d'homogénéité est très élevé.
Nous allons à présent réduire les données et vérifier s'il est possible d'obtenir d'aussi bons résultats en utilisant les composantes principales de notre jeu de données.
# Retirer la variable cible du dataset d'origine
df_without_target = df.iloc[:,1:]
df_without_target.head()
diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|
0 | 171.81 | 104.86 | 104.95 | 4.52 | 2.89 | 112.83 |
1 | 171.46 | 103.36 | 103.66 | 3.77 | 2.99 | 113.09 |
2 | 172.69 | 104.48 | 103.50 | 4.40 | 2.94 | 113.16 |
3 | 171.36 | 103.91 | 103.94 | 3.62 | 3.01 | 113.51 |
4 | 171.73 | 104.28 | 103.46 | 4.04 | 3.48 | 112.54 |
# Centrer et réduire le dataframe à l'aide de la fonction ci-dessus
df_scaled = standardization(df_without_target)
df_scaled
diagonal | height_left | height_right | margin_low | margin_up | length | |
---|---|---|---|---|---|---|
0 | -0.486540 | 2.774123 | 3.163240 | 0.056462 | -1.128325 | 0.173651 |
1 | -1.633729 | -2.236535 | -0.799668 | -1.080631 | -0.696799 | 0.471666 |
2 | 2.397823 | 1.504756 | -1.291191 | -0.125473 | -0.912562 | 0.551901 |
3 | -1.961498 | -0.399294 | 0.060498 | -1.308050 | -0.610494 | 0.953075 |
4 | -0.748754 | 0.836669 | -1.414072 | -0.671278 | 1.417677 | -0.158750 |
... | ... | ... | ... | ... | ... | ... |
1495 | -0.683201 | 1.170713 | 0.767063 | -0.095150 | -0.265273 | -1.602978 |
1496 | 0.758981 | 2.005822 | 1.596509 | 1.193555 | 0.942999 | -1.958303 |
1497 | -0.519316 | -0.065250 | 0.613462 | 1.557425 | 0.899846 | -0.835016 |
1498 | 0.332882 | 0.836669 | 0.429141 | 1.041943 | 1.331372 | -0.491152 |
1499 | -1.600953 | 0.402412 | -0.308144 | 0.223236 | 0.942999 | -0.697470 |
1500 rows × 6 columns
# Fonction qui génère et affiche le cercle des corrélations
def correlation_graph(pca, x_y, features):
# Extrait x et y
x,y = x_y
# Taille de l'image (en inches)
fig, ax = plt.subplots(figsize=(12, 8))
texts = []
# Générer une liste de couleurs
colors = cm.viridis(np.linspace(0, 1, len(features)))
# Pour chaque composante :
for i in range(0, pca.components_.shape[1]):
# Les flèches
ax.arrow(0,0,
pca.components_[x, i],
pca.components_[y, i],
head_width=0.04,
head_length=0.04,
width=0.01, color=colors[i])
# Les labels
texts.append(plt.text(pca.components_[x, i] + 0.02,
pca.components_[y, i] + 0.02,
features[i], fontsize=10, color=colors[i]))
# Affichage des lignes horizontales et verticales
plt.plot([-1, 1], [0, 0], color='grey', ls='--')
plt.plot([0, 0], [-1, 1], color='grey', ls='--')
# Nom des axes, avec le pourcentage d'inertie expliqué
plt.xlabel('PC{} ({}%)'.format(x+1, round(100*pca.explained_variance_ratio_[x],1)))
plt.ylabel('PC{} ({}%)'.format(y+1, round(100*pca.explained_variance_ratio_[y],1)))
plt.title("Cercle des corrélations (PC{} et PC{})".format(x+1, y+1), fontsize=16, fontweight='bold')
# Le cercle
an = np.linspace(0, 2 * np.pi, 100)
plt.plot(np.cos(an), np.sin(an))
# Axes et display
plt.axis('equal')
# Ajustement automatique du texte
adjust_text(texts)
sns.despine()
plt.show(block=False)
# Créer une fonction ACP
def Analyse_en_Composantes_Principales(scaled_df, labels_variable, number_components):
'''
Paramètres;
scaled_df : dataframe centré réduit
labels_variable : nom de la variable des labels, type string
number_components : nombre de composantes, type int
'''
# Lister les variables à utiliser pour l'analyse en composantes principales
pca_variables = scaled_df.columns
print('Variables utilisées:')
print(f'{pca_variables}')
print()
# On récupère le label de chaque ligne du dataframe d'origine
labels = df[labels_variable].values
# Déterminer un nombre de composantes permettant de capter près de 90 % de l'inertie,
# puis instancier et entraîner l'ACP
pca = PCA(n_components=number_components) # number_components ne peut pas dépasser le nombre de variables du jeu de données
pca_data = pca.fit_transform(scaled_df)
# Afficher la variance captée par chaque composante
scree = (pca.explained_variance_ratio_*100).round(2)
print(f'Pourcentage de variance captée par chacune des {number_components} composantes:')
print(scree)
print()
# Afficher la variance captée cumulée (en %)
cum_sum = scree.cumsum().round(2)
print(f'Variance captée cumulée en %:')
print(cum_sum)
print()
# Afficher l'éboulis des valeurs propres
print('Visualiser l\'éboulis de valeurs propres:')
components_list = range(1, number_components+1)
fig, ax = plt.subplots(1, 1, figsize=(12, 8))
plt.bar(components_list, scree, color='blue')
plt.plot(components_list, cum_sum, c="red", marker='o')
plt.xlabel("Rang de l'axe d'inertie")
plt.ylabel("Pourcentage d'inertie captée par un axe")
plt.yticks(np.arange(0, 100, 5))
plt.xticks(np.arange(1, number_components+1, 1))
plt.title("Eboulis des valeurs propres", fontsize=16, fontweight='bold')
ax.spines['right'].set_visible(False)
ax.spines['top'].set_visible(False)
plt.show(block=False)
# Transformer les composantes principales en dataframe
principal_components = pd.DataFrame(pca.components_)
principal_components.columns = pca_variables
# Nommer les index du dataframe principal_components PC1, PC2, etc
components_list = range(1, number_components+1)
principal_components.index = [f"PC{i}" for i in components_list]
# Transposer le dataframe des composantes de façon à avoir celles-ci en colonnes et les variables en index
# puis l'afficher
print('Dataframe des composantes principales:')
principal_components = principal_components.T
return pca, pca_data, principal_components
# Effectuer une première ACP en utilisant la fonction ci-dessus
Analyse_en_Composantes_Principales(df_scaled, 'is_genuine', 6)
Variables utilisées: Index(['diagonal', 'height_left', 'height_right', 'margin_low', 'margin_up', 'length'], dtype='object') Pourcentage de variance captée par chacune des 6 composantes: [43.21 16.96 13.02 11.83 9.66 5.33] Variance captée cumulée en %: [ 43.21 60.17 73.19 85.02 94.68 100.01] Visualiser l'éboulis de valeurs propres:
Dataframe des composantes principales:
(PCA(n_components=6), array([[ 1.64716429, 0.7472495 , 2.39636562, 3.1847226 , 0.29732302, -0.00844471], [-2.01970214, -2.20675832, -1.00231371, 0.07233277, 0.03810651, -0.47549589], [-0.96891668, 2.61937219, 1.20008898, -0.92470532, -0.83681788, 0.19663021], ..., [ 1.88884875, -0.60085161, -0.54591505, -0.10035698, -0.303995 , 0.50353773], [ 1.78983604, 0.51094681, 0.02777919, -0.47366235, 0.35078122, 0.53071532], [ 1.04315284, -1.47132486, 0.48277852, -0.68304951, 0.48997702, -0.22980172]]), PC1 PC2 PC3 PC4 PC5 PC6 diagonal -0.084826 0.941309 -0.287081 -0.102802 -0.117041 0.007697 height_left 0.331314 0.307256 0.884802 -0.047763 0.103105 0.006236 height_right 0.393749 0.108588 -0.165502 0.866943 0.232737 0.004728 margin_low 0.506602 -0.073121 -0.107145 -0.091559 -0.570821 0.626345 margin_up 0.439347 -0.004474 -0.270935 -0.442480 0.710688 0.180783 length -0.527567 0.048710 0.149780 0.177167 0.300981 0.758214)
# Effectuer une deuxième ACP avec seulement 4 composantes
ACP = Analyse_en_Composantes_Principales(df_scaled, 'is_genuine', 4)
ACP
Variables utilisées: Index(['diagonal', 'height_left', 'height_right', 'margin_low', 'margin_up', 'length'], dtype='object') Pourcentage de variance captée par chacune des 4 composantes: [43.21 16.96 13.02 11.83] Variance captée cumulée en %: [43.21 60.17 73.19 85.02] Visualiser l'éboulis de valeurs propres:
Dataframe des composantes principales:
(PCA(n_components=4), array([[ 1.64716429, 0.7472495 , 2.39636562, 3.1847226 ], [-2.01970214, -2.20675832, -1.00231371, 0.07233277], [-0.96891668, 2.61937219, 1.20008898, -0.92470532], ..., [ 1.88884875, -0.60085161, -0.54591505, -0.10035698], [ 1.78983604, 0.51094681, 0.02777919, -0.47366235], [ 1.04315284, -1.47132486, 0.48277852, -0.68304951]]), PC1 PC2 PC3 PC4 diagonal -0.084826 0.941309 -0.287081 -0.102802 height_left 0.331314 0.307256 0.884802 -0.047763 height_right 0.393749 0.108588 -0.165502 0.866943 margin_low 0.506602 -0.073121 -0.107145 -0.091559 margin_up 0.439347 -0.004474 -0.270935 -0.442480 length -0.527567 0.048710 0.149780 0.177167)
# Sauvegarder le modèle pca
saved_pca = ACP[0]
# On enregistre le meilleur modèle avec pickle.
pickle.dump(saved_pca, open('./pca_model.pkl', 'wb'))
# Lister les variables à utiliser
variables = ['diagonal', 'height_left', 'height_right', 'margin_low', 'margin_up', 'length']
# Afficher le cercle des corrélations pour PC1 et PC2
x_y = (0,1)
correlation_graph(ACP[0], x_y, variables)
On remarque que les variables les plus corrélées à l'axe horizontal PC1 sont les marges (haute et basse) et la longueur. On constate également que les marges et la longueur d'un billet sont négativement corrélées; plus les marges sont hautes moins la longueur est grande et vice versa.
On peut donc définir PC1 comme étant la composante "Marges et longueur d'un billet".
Ensuite, la variable la plus corrélée à l'axe vertical PC2 est 'diagonal'.
On peut définir PC2 comme étant la composante diagonale.
# Visualiser les corrélations sur les axes PC3 et PC4
x_y = (2,3)
correlation_graph(ACP[0], x_y, variables)
'height left' est la variable la plus corrélée à l'axe horizontal PC3 tandis que 'height_right' est fortement corrélée à l'axe vertical PC4. Nous avons de la redondance car la hauteur gauche diffère très peu de la hauteur droite.
On peut se contenter d'utiliser uniquement PC3 et la définir comme la composante hauteur d'un billet.
# récupérer le numpy array pca_data retourné précédemment par la fonction Analyse_en_Composantes_Principales
# c'est à dire ACP[1], puis le transformer en dataframe:
pca_data = pd.DataFrame(ACP[1])
pca_data.rename(columns={0:"PC1",1:"PC2",2:"PC3", 3:"PC4"}, inplace=True)
pca_data
PC1 | PC2 | PC3 | PC4 | |
---|---|---|---|---|
0 | 1.647164 | 0.747249 | 2.396366 | 3.184723 |
1 | -2.019702 | -2.206758 | -1.002314 | 0.072333 |
2 | -0.968917 | 2.619372 | 1.200089 | -0.924705 |
3 | -1.375774 | -1.817690 | 0.648106 | 0.831914 |
4 | 0.150457 | -0.566280 | 0.853318 | -1.782867 |
... | ... | ... | ... | ... |
1495 | 1.428784 | -0.270038 | 0.947005 | 0.521412 |
1496 | 3.280895 | 1.317216 | 0.615952 | 0.336768 |
1497 | 1.888849 | -0.600852 | -0.545915 | -0.100357 |
1498 | 1.789836 | 0.510947 | 0.027779 | -0.473662 |
1499 | 1.043153 | -1.471325 | 0.482779 | -0.683050 |
1500 rows × 4 columns
# Train test split avec les composantes principales de l'ACP effectuée précédemment
X_train, X_test, y_train, y_test = train_test_split(pca_data, y, train_size=0.80, random_state=42)
# Afficher X_train
X_train
PC1 | PC2 | PC3 | PC4 | |
---|---|---|---|---|
382 | -0.377927 | 1.636516 | 1.752611 | -0.212502 |
538 | -1.418121 | 0.255940 | 2.258296 | -0.345742 |
1493 | 2.253144 | -0.551701 | 0.460805 | 1.360063 |
1112 | 2.456979 | 0.513500 | -0.058538 | 0.363900 |
324 | -1.110843 | 0.805100 | 1.648740 | -0.291502 |
... | ... | ... | ... | ... |
1130 | 2.048310 | -0.949491 | 0.371676 | -0.153367 |
1294 | 1.365726 | 1.597352 | -0.082021 | 0.227983 |
860 | -0.552885 | -1.916119 | 0.100454 | 0.641147 |
1459 | 2.667098 | -0.573207 | 0.391204 | -0.989310 |
1126 | 1.759038 | -0.410525 | -0.349260 | -0.060996 |
1200 rows × 4 columns
# Fonction régression logistique qui utilise les composantes principales
def LogisticRegression_PCA(X_train, X_test, y_train, y_test, random_state):
# Instancier un classifieur
classifier = LogisticRegression(random_state=random_state)
classifier.fit(X_train, y_train)
# Effectuer des prédictions sur l'ensemble de test
y_pred = classifier.predict(X_test)
# Calculer les probabilités qu'un individu appartienne à la classe 0, à la classe 1
y_pred_proba = classifier.predict_proba(X_test)[:,1]
# Afficher l'accuracy score du meilleur modèle
score = round(classifier.score(X_test, y_test),2)
print()
print('Le modèle a classifié correctement {} % des billets'.format(str(score*100)))
print()
# Afficher le rapport de classification
report = pd.DataFrame(classification_report(y_test, y_pred, output_dict=True)).T
print(report)
print()
# Afficher la matrice de confusion
conf_matrix = confusion_matrix(y_test, y_pred)
display_matrix = ConfusionMatrixDisplay(conf_matrix, display_labels=['Faux billets', 'Vrais billets'])
display_matrix.plot(cmap='Blues', colorbar=False)
plt.title('Matrice de confusion', fontsize=16, fontweight='bold', pad=20)
plt.xlabel('Classe prédite')
plt.ylabel('Classe réelle')
plt.show()
# Récupérer le taux de vrais positifs et le taux de faux positifs
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
# Visualiser le taux de faux positifs (axe des abscisses) versus le taux de vrais positifs (axe des ordonnées)
plt.figure(figsize=(12,8))
plt.plot([0,1], [0,1], '--')
plt.plot(fpr,tpr)
plt.title('Courbe ROC', fontsize=16, fontweight='bold', pad=10)
plt.ylabel('Taux de vrais positifs')
plt.xlabel('Taux de faux positifs')
xticks = np.arange(0.00, 1.01, 0.10)
xlabels = [f'{x*100:.0f}%' for x in xticks]
plt.xticks(xticks, labels=xlabels)
yticks = np.arange(0.00, 1.01, 0.10)
ylabels = [f'{y*100:.0f}%' for y in yticks]
plt.yticks(yticks, labels=ylabels)
sns.despine()
plt.show()
# Calcul de l'aire sous la courbe ROC, plus celle-ci est proche de 1 et plus le modèle est bon
au_roc_curve = roc_auc_score(y_test, y_pred_proba)
print('Aire sous la courbe ROC: {}'.format(au_roc_curve))
# On enregistre le modèle avec pickle.
pickle.dump(classifier, open('./log_reg_pca_model.pkl', 'wb'))
# Effectuer la régression logistique sur les composantes principales
LogisticRegression_PCA(X_train, X_test, y_train, y_test, 42)
Le modèle a classifié correctement 99.0 % des billets precision recall f1-score support False 0.990741 0.972727 0.981651 110.000000 True 0.984375 0.994737 0.989529 190.000000 accuracy 0.986667 0.986667 0.986667 0.986667 macro avg 0.987558 0.983732 0.985590 300.000000 weighted avg 0.986709 0.986667 0.986640 300.000000
Aire sous la courbe ROC: 0.9995693779904307
Essayons également le K-means sur les composantes principales.
# Créer une fonction qui Utilisera les 3 premières composantes principales de l'ACP avec le K-means
def K_means_pca_clustering(nb_clusters, X_train, X_test, y_test, random_state, PC1_index, PC2_index, PC3_index):
warnings.filterwarnings('ignore')
# # Récupérer le modèle pca entraîné précédemment
# with open(pca_model_path, 'rb') as f:
# pca = pickle.load(f)
# pca_data = pca.fit_transform(df_scaled)
labels = y_test.values
colors = ['blue', 'red']
# Exécuter K-means
clf = KMeans(n_clusters=nb_clusters, init='k-means++', n_init=3, max_iter=300, random_state=random_state).fit(X_train)
predictions = clf.predict(X_test)
clusters = {i: [] for i in range(nb_clusters)}
for i, prediction in enumerate(predictions):
clusters[prediction].append(labels[i])
# Utiliser la fonction créée plus haut pour visualiser l'homogénéité de chaque cluster
print('------ Visualiser le nombre de vrais et de faux billets se trouvant dans chaque cluster ------')
print(kmeans_confusion_matrix(clusters))
# On affiche un nuage de points en 3D car nous avons sélectionné 3 composantes
fig = plt.figure(figsize=(16,12))
ax = fig.add_subplot(111, projection='3d')
for cluster, bills in clusters.items():
points = np.array([X_test.iloc[i] for i in range(len(predictions)) if predictions[i] == cluster])
ax.scatter(points[:, PC1_index], points[:, PC2_index], points[:, PC3_index], color=colors[cluster], edgecolor='black', linewidths=0.8, s=100)
ax.scatter([], [], [], color=colors[cluster], label=f"Cluster {cluster}", edgecolor='black', linewidths=0.8, s=100)
ax.set_xlabel('PC1')
ax.set_ylabel('PC2')
ax.set_zlabel('PC3')
plt.legend(bbox_to_anchor=(1, 1), loc='upper left')
plt.title(f'K-means clustering en utilisant les composantes principales', fontsize=16, fontweight='bold')
plt.show()
# On enregistre le meilleur modèle avec pickle.
pickle.dump(clf, open('./kmeans_pca_model.pkl', 'wb'))
# return clusters
# Utiliser la fonction ci-dessus pour effectuer un clustering des billets
K_means_pca_clustering(2, X_train, X_test, y_test, 42, 0, 1, 2)
------ Visualiser le nombre de vrais et de faux billets se trouvant dans chaque cluster ------ Cluster 0 Cluster 1 Nombre de vrais billets 187.00 3.00 Nombre de faux billets 1.00 109.00 Taux d'homogénéité 99.47 97.32
Les modèles entraînés sur les composantes principales donnent des résultats légèrement moins bons, ce qui n'a rien d'étonnant puisque nous avons perdu de l'information en réduisant le nombre de variables. Mais ces résultats restent plus que satisfaisants.
Notre algorithme final sera une classe python qui fera appel à l'un des 4 modèles entraînés et sauvegardés précédemment pour classifier (ou partitionner) de nouvelles données.
# Créer une classe pour la détection de faux billets
class Counterfeit_Money_Detection:
'''
pca_path : chemin d'accès au modèle pca si l'algorithme choisi est le K-means
'''
def __init__(self, csv_path, classifier_path, pca_model_path=None, first_var_index=None, second_var_index=None, third_var_index=None):
self.csv_path = csv_path
self.classifier_path = classifier_path
self.pca_model_path = pca_model_path
self.first_var_index = first_var_index
self.second_var_index = second_var_index
self.third_var_index = third_var_index
def predict(self):
if self.classifier_path == 'log_reg_model.pkl':
# Charger le fichier contenant les données à prédire
df = pd.read_csv(self.csv_path)
print("------ Dataframe d'origine ------")
print()
print(df.head(10))
# Ne conserver que les 6 variables explicatives
columns = ['diagonal', 'height_left', 'height_right', 'margin_low', 'margin_up', 'length']
df_independent = df[columns]
# Instancier un scaler
scaler = StandardScaler()
# Ajustement et transformation des données
df_scaled = scaler.fit_transform(df_independent)
# Conversion du tableau numpy en dataframe
self.df_scaled = pd.DataFrame(df_scaled, index=df.index, columns=df_independent.columns)
# Charger le modèle entraîné précédemment
with open(self.classifier_path, 'rb') as f:
clf = pickle.load(f)
# puis effectuer des prédictions sur les nouvelles données...
self.predict = clf.predict(df_scaled)
# ...et récupérer, pour chaque billet, les probabilités que ledit billet appartienne
# à la classe des faux billets et à la classe des vrais billets
# soit un array avec 2 probabilités pour chaque billet
self.proba = clf.predict_proba(df_scaled)
# Transformer le array des prédictions en Series
series_predict = pd.Series(self.predict).T.astype(bool)
# Transformer le array des probabilités en dataframe
df_proba = pd.DataFrame(self.proba)
df_proba.rename(columns={0:'probability_of_being_counterfeit', 1:'probability_of_being_genuine'}, inplace=True)
df_proba['probability_of_being_genuine'] = round(df_proba['probability_of_being_genuine'],3)
df_proba['probability_of_being_counterfeit'] = round(df_proba['probability_of_being_counterfeit'],3)
# Fusionner df, df_proba et series_predict
df_proba_predict = df.merge(df_proba, left_index=True, right_index=True).merge(series_predict.rename('is_genuine'), left_index=True, right_index=True)
print()
print("------ Visualiser les prédictions ------")
plt.figure(figsize=(9,6))
sns.scatterplot(data=df_proba_predict, x='id', y='is_genuine', hue='is_genuine', palette=['red', 'blue'], s=100)
plt.title('Prédictions de classe', fontsize=16, pad=20)
plt.xlabel('id')
plt.ylabel('is_genuine')
plt.yticks(np.arange(0, 2))
sns.despine()
plt.show()
print()
print("------ Dataframe d'origine + probabilités + prédiction ------")
return df_proba_predict
elif self.classifier_path == 'kmeans_model.pkl':
# Charger le fichier contenant les données à prédire / partitionner
df = pd.read_csv(self.csv_path)
print("------ Dataframe d'origine ------")
print()
print(df.head(10))
print()
# Ne conserver que les 6 variables explicatives
columns = ['diagonal', 'height_left', 'height_right', 'margin_low', 'margin_up', 'length']
df_independent = df[columns]
labels = df.id.values
# Instancier un scaler
scaler = StandardScaler()
# Ajustement et transformation des données
df_scaled = scaler.fit_transform(df_independent)
# Conversion du tableau numpy en dataframe
self.df_scaled = pd.DataFrame(df_scaled, index=df.index, columns=df_independent.columns)
# Charger le modèle entraîné précédemment
with open(self.classifier_path, 'rb') as f:
clf = pickle.load(f)
# puis effectuer des prédictions sur les nouvelles données...
self.predict = clf.predict(df_scaled)
# 2 clusters car on souhaite partitionner les données en deux; vrais billets d'un côté et faux billets de l'autre
clusters = {i: [] for i in range(2)}
for i, prediction in enumerate(self.predict):
clusters[prediction].append(labels[i])
# Nuage de points en 3D
fig = plt.figure(figsize=(16,12))
ax = fig.add_subplot(111, projection='3d')
colors = ['blue', 'red']
for cluster, bills in clusters.items():
# print(f"Billets appartenant au cluster {cluster}: {bills}")
points = np.array([df_scaled[i] for i in range(len(self.predict)) if self.predict[i] == cluster])
ax.scatter(points[:, self.first_var_index], points[:, self.second_var_index], points[:, self.third_var_index], color=colors[cluster], edgecolor='black', linewidths=0.8, s=180)
ax.scatter([], [], [], color=colors[cluster], label=f"Cluster {cluster}", edgecolor='black', linewidths=0.8, s=100)
for index, bill in enumerate(bills):
ax.text(points[index, self.first_var_index], points[index, self.second_var_index], points[index, self.third_var_index], bill, fontsize=16, ha='right', va='bottom',
bbox=dict(facecolor='white', alpha=0.3, edgecolor='none'))
plt.legend(bbox_to_anchor=(1, 1), loc='upper left')
plt.title(f"Clustering des billets en 3D", fontsize=16, fontweight='bold')
plt.show()
elif self.classifier_path == 'log_reg_pca_model.pkl':
# Charger le fichier contenant les données à prédire
df = pd.read_csv(self.csv_path)
print("------ Dataframe d'origine ------")
print()
print(df.head(10))
# Ne conserver que les 6 variables explicatives
columns = ['diagonal', 'height_left', 'height_right', 'margin_low', 'margin_up', 'length']
df_independent = df[columns]
# Instancier un scaler
scaler = StandardScaler()
# Ajustement et transformation des données
df_scaled = scaler.fit_transform(df_independent)
# Conversion du tableau numpy en dataframe
self.df_scaled = pd.DataFrame(df_scaled, index=df.index, columns=df_independent.columns)
# Récupérer le modèle pca entraîné précédemment
with open(self.pca_model_path, 'rb') as f:
pca = pickle.load(f)
pca_data = pca.fit_transform(self.df_scaled)
# Récupérer le modèle entraîné précédemment
# puis effectuer des prédictions sur les nouvelles données
with open(self.classifier_path, 'rb') as f:
clf = pickle.load(f)
self.predict = clf.predict(pca_data)
# ...et récupérer, pour chaque billet, les probabilités que ledit billet appartienne
# à la classe des faux billets et à la classe des vrais billets
# soit un array avec 2 probabilités pour chaque billet
self.proba = clf.predict_proba(pca_data)
# Transformer le array des prédictions en Series
series_predict = pd.Series(self.predict).T.astype(bool)
# Transformer le array des probabilités en dataframe
df_proba = pd.DataFrame(self.proba)
df_proba.rename(columns={0:'probability_of_being_counterfeit', 1:'probability_of_being_genuine'}, inplace=True)
df_proba['probability_of_being_genuine'] = round(df_proba['probability_of_being_genuine'],3)
df_proba['probability_of_being_counterfeit'] = round(df_proba['probability_of_being_counterfeit'],3)
# Fusionner df, df_proba et series_predict
df_proba_predict = df.merge(df_proba, left_index=True, right_index=True).merge(series_predict.rename('is_genuine'), left_index=True, right_index=True)
print()
print("------ Visualiser les prédictions ------")
plt.figure(figsize=(9,6))
sns.scatterplot(data=df_proba_predict, x='id', y='is_genuine', hue='is_genuine', palette=['red', 'blue'], s=100)
plt.title('Prédictions de classe', fontsize=16, pad=20)
plt.xlabel('id')
plt.ylabel('is_genuine')
plt.yticks(np.arange(0, 2))
sns.despine()
plt.show()
print()
print("------ Dataframe avec les prédictions ------")
return df_proba_predict
elif self.classifier_path == 'kmeans_pca_model.pkl':
# Charger le fichier contenant les données à prédire
df = pd.read_csv(self.csv_path)
print("------ Dataframe d'origine ------")
print()
print(df.head(10))
# Ne conserver que les 6 variables explicatives
columns = ['diagonal', 'height_left', 'height_right', 'margin_low', 'margin_up', 'length']
df_independent = df[columns]
labels = df.id.values
# Instancier un scaler
scaler = StandardScaler()
# Ajustement et transformation des données
df_scaled = scaler.fit_transform(df_independent)
# Conversion du tableau numpy en dataframe
self.df_scaled = pd.DataFrame(df_scaled, index=df.index, columns=df_independent.columns)
# Récupérer le modèle pca entraîné précédemment
with open(self.pca_model_path, 'rb') as f:
pca = pickle.load(f)
pca_data = pca.fit_transform(self.df_scaled)
# Récupérer le modèle entraîné précédemment
# puis effectuer des prédictions sur les nouvelles données
with open(self.classifier_path, 'rb') as f:
clf = pickle.load(f)
self.predict = clf.predict(pca_data)
# 2 clusters car on souhaite partitionner les données en deux; vrais billets d'un côté et faux billets de l'autre
clusters = {i: [] for i in range(2)}
for i, prediction in enumerate(self.predict):
clusters[prediction].append(labels[i])
# Nuage de points en 3D
fig = plt.figure(figsize=(16,12))
ax = fig.add_subplot(111, projection='3d')
colors = ['blue', 'red']
for cluster, bills in clusters.items():
print(f"Billets appartenant au cluster {cluster}: {bills}")
points = np.array([pca_data.iloc[i] for i in range(len(self.predict)) if self.predict[i] == cluster])
ax.scatter(points[:, self.first_var_index], points[:, self.second_var_index], points[:, self.third_var_index], color=colors[cluster], edgecolor='black', linewidths=0.8, s=180)
ax.scatter([], [], [], color=colors[cluster], label=f"Cluster {cluster}", edgecolor='black', linewidths=0.8, s=100)
for index, bill in enumerate(bills):
ax.text(points[index, self.first_var_index], points[index, self.second_var_index], points[index, self.third_var_index], bill, fontsize=16, ha='right', va='bottom',
bbox=dict(facecolor='white', alpha=0.3, edgecolor='none'))
plt.legend(bbox_to_anchor=(1, 1), loc='upper left')
plt.title(f"Clustering des billets en 3D", fontsize=16, fontweight='bold')
plt.show()
else:
raise ValueError("Ce chemin d'accès n'est pas valide")
Rappel:
Un faux billet a généralement une hauteur et des marges (marge haute et marge basse) plus grandes que celles d'un vrai billet, tandis que sa longueur et sa diagonale sont plus petites.
# Tester l'algorithme avec le fichier billet_production.csv
# Sans ACP
clf = Counterfeit_Money_Detection('billets_test.csv', 'log_reg_model.pkl', 'pca_model.pkl', 3, 4, 5)
# clf = Counterfeit_Money_Detection('billets_test.csv', 'kmeans_model.pkl', 'pca_model.pkl', 3, 4, 5)
# # Avec ACP
# clf = Counterfeit_Money_Detection('billets_production.csv', 'log_reg_pca_model.pkl', 'pca_model.pkl', 0, 1, 2)
# clf = Counterfeit_Money_Detection('billets_production.csv', 'kmeans_pca_model.pkl', 'pca_model.pkl', 0, 1, 2)
clf.predict()
------ Dataframe d'origine ------ diagonal height_left height_right margin_low margin_up length id 0 172.09 103.95 103.73 4.39 3.09 113.19 B_1 1 171.52 104.17 104.03 5.27 3.16 111.82 B_2 2 171.78 103.80 103.75 3.81 3.24 113.39 B_3 3 172.02 104.08 103.99 5.57 3.30 111.10 B_4 4 171.79 104.34 104.37 5.00 3.07 111.87 B_5 ------ Visualiser les prédictions ------
------ Dataframe d'origine + probabilités + prédiction ------
diagonal | height_left | height_right | margin_low | margin_up | length | id | probability_of_being_counterfeit | probability_of_being_genuine | is_genuine | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 172.09 | 103.95 | 103.73 | 4.39 | 3.09 | 113.19 | B_1 | 0.000 | 1.000 | True |
1 | 171.52 | 104.17 | 104.03 | 5.27 | 3.16 | 111.82 | B_2 | 0.938 | 0.062 | False |
2 | 171.78 | 103.80 | 103.75 | 3.81 | 3.24 | 113.39 | B_3 | 0.000 | 1.000 | True |
3 | 172.02 | 104.08 | 103.99 | 5.57 | 3.30 | 111.10 | B_4 | 1.000 | 0.000 | False |
4 | 171.79 | 104.34 | 104.37 | 5.00 | 3.07 | 111.87 | B_5 | 0.704 | 0.296 | False |