Aller au contenu

Open In Colab

Module 2: Apprentissage supervisé

Objectif

Notre objectif est de prédire si un patient souffre ou non du diabète. Il s'agit d'un problème d'apprentissage supervisé, problème de classification.

Importer les libairies nécessaires

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

sns.set()
/usr/local/lib/python3.6/dist-packages/statsmodels/tools/_testing.py:19: FutureWarning: pandas.util.testing is deprecated. Use the functions in the public API at pandas.testing instead.
  import pandas.util.testing as tm

Importer les données depuis un fichier

La commande pour importer des données depuis un fichier .csv c'est pandas.read_csv.

data = pd.read_csv('https://raw.githubusercontent.com/joekakone/datasets/master/datasets/datasets_for_learn_data_science/datasets_228_482_diabetes.csv')
data.head()
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
0 6 148 72 35 0 33.6 0.627 50 1
1 1 85 66 29 0 26.6 0.351 31 0
2 8 183 64 0 0 23.3 0.672 32 1
3 1 89 66 23 94 28.1 0.167 21 0
4 0 137 40 35 168 43.1 2.288 33 1

Afficher les dimensions du tableau

data.shape
(768, 9)

Le jeu de données contient 768 données et 9 variables, les huit variables explicatives et une variable expliquée Outcome

# Nombre de variables explicatives
n_features = data.shape[1] - 1
# Liste des variales explicatives
features = data.columns[:-1]
n_features
8
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 768 entries, 0 to 767
Data columns (total 9 columns):
 #   Column                    Non-Null Count  Dtype  
---  ------                    --------------  -----  
 0   Pregnancies               768 non-null    int64  
 1   Glucose                   768 non-null    int64  
 2   BloodPressure             768 non-null    int64  
 3   SkinThickness             768 non-null    int64  
 4   Insulin                   768 non-null    int64  
 5   BMI                       768 non-null    float64
 6   DiabetesPedigreeFunction  768 non-null    float64
 7   Age                       768 non-null    int64  
 8   Outcome                   768 non-null    int64  
dtypes: float64(2), int64(7)
memory usage: 54.1 KB

data.describe()
Pregnancies Glucose BloodPressure SkinThickness Insulin BMI DiabetesPedigreeFunction Age Outcome
count 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000 768.000000
mean 3.845052 120.894531 69.105469 20.536458 79.799479 31.992578 0.471876 33.240885 0.348958
std 3.369578 31.972618 19.355807 15.952218 115.244002 7.884160 0.331329 11.760232 0.476951
min 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.078000 21.000000 0.000000
25% 1.000000 99.000000 62.000000 0.000000 0.000000 27.300000 0.243750 24.000000 0.000000
50% 3.000000 117.000000 72.000000 23.000000 30.500000 32.000000 0.372500 29.000000 0.000000
75% 6.000000 140.250000 80.000000 32.000000 127.250000 36.600000 0.626250 41.000000 1.000000
max 17.000000 199.000000 122.000000 99.000000 846.000000 67.100000 2.420000 81.000000 1.000000
data[data.isna()].count()
Pregnancies                 0
Glucose                     0
BloodPressure               0
SkinThickness               0
Insulin                     0
BMI                         0
DiabetesPedigreeFunction    0
Age                         0
Outcome                     0
dtype: int64

Variable expliquée

La variable expliquée est Outcome. Elle est encodée en 0 et 1, 0 pour négatif et 1 pour positif.

Table des effectifs

La méthode .value_counts

Outcome = data['Outcome'].value_counts(sort=False)

Outcome
0    500
1    268
Name: Outcome, dtype: int64

Deux types de graphes sont adaptés pour représenter: le diagramme en bâtons et le diagramme en secteurs.

Dans la pratique, lorsque le nombre de modalités dépasse trois, le diagramme en bâtons est préféré au digramme en secteurs. La comparaison est plus facile. Les tuyaux d'orgue sont une variante du diagramme en bâtons.

Table des fréquences

Nous utilisons toujours la méthode .value_counts mais cette fois-ci en donnant la valeur True à l'argument normalize .

Outcome = data['Outcome'].value_counts(sort=False, normalize=True)
Outcome
0    0.651042
1    0.348958
Name: Outcome, dtype: float64

Diagramme en bâtons

# diagramme en bâtons
Outcome.plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x7f28ad2f17f0>

Diagramme en secteurs (camenbert)

# diagramme en secteurs
Outcome.plot(kind='pie')
<matplotlib.axes._subplots.AxesSubplot at 0x7f28ad22a710>

Le jeu de données est déséquilibré. Il y'a plus de 65% de données négatives contre un peu plus de 34% de données négatives. Il faudra en tenir compte de ce déséquilibre lors de l'échantillonnage des données d'entraînement.

Variables explicatives

# les boîtes à moustaches
sns.boxplot(data=data.drop(['Outcome'], axis=1))
<matplotlib.axes._subplots.AxesSubplot at 0x7f28acdb5630>

En dessinant les boîtes à moustaches comme ci-dessus, on constate qu'il y'a une différence d'echelle entre les variables. Et on peut commencer par envisager de les centrer et de les réduire pour les ramener à la même échelle.

On remarque par ailleurs la présence de plusieurs valeurs atypiques pour la variable Insulin. Nous allons regarder de plus près en étudiant les variables une à une.

fig = plt.figure(figsize=(12, 20))

for i in range(n_features):
    fig.add_subplot(4, 2, i+1)
    sns.boxplot(x=data[features[i]])
#     plt.xlabel('')
#     plt.title(features[i])

plt.show()
sns.distplot(data['Insulin'])
plt.show()

L'histogramme Nous allons enlever les données pour lesquelles Insulin > 400.

sns.distplot(data['DiabetesPedigreeFunction'])
plt.show()

De même que pour la variable Insulin, nous allons enlever les données pour lesquelles DiabetesPedigreeFunction > 1.5.

Suppression des données atypiques (outliers)

data = data[data['Insulin']<=400]
data = data[data['DiabetesPedigreeFunction']<=1.5]

data.shape
(740, 9)

Nous avons ainsi enlevé 768-740=28 données atypiques qui sont succeptible de biaisé la modélisation.

fig = plt.figure(figsize=(12, 4))

fig.add_subplot(1, 2, 1)
sns.distplot(data['Insulin'])

fig.add_subplot(1, 2, 2)
sns.distplot(data['DiabetesPedigreeFunction'])

plt.show()
Pregnancies = data['Pregnancies'].value_counts(sort=False)
Pregnancies
0     103
1     130
2      97
3      73
4      67
5      56
6      50
7      44
8      35
9      27
10     24
11     11
12      9
13     10
14      2
15      1
17      1
Name: Pregnancies, dtype: int64
Pregnancies.plot(kind='bar')
<matplotlib.axes._subplots.AxesSubplot at 0x7f28ac780f60>
sns.pairplot(data=data, hue='Outcome')
<seaborn.axisgrid.PairGrid at 0x7f28ac70c2e8>
corr = data.drop(['Outcome'], axis=1).corr()
sns.heatmap(corr, xticklabels=corr.columns.values, yticklabels=corr.columns.values)
plt.show()

ACP - Analyse en Composantes Principales

L'ACP est une technique de réduction de dimaensions.

Nous allons ulitiser l'ACP pour réduire le nombre de dimensions et ainsi visualiser les données en deux dimensions. Nous pourrons ainsi juger les facteurs discriminants de nos variables explicatives.

Centrage et Réduction

from sklearn.preprocessing import StandardScaler

X = data.iloc[:, :-1]
y = data.iloc[:, -1]

scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
from sklearn.decomposition import PCA

pca = PCA(n_components=4)

X_projected = pca.fit_transform(X_scaled)
fig = plt.figure(figsize=(12, 4))

fig.add_subplot(1, 2, 1)
plt.scatter(X_projected[:, 0], X_projected[:, 1], c=y)
plt.xlabel('F1')
plt.ylabel('F2')
plt.title('Premier plan factoriel')

fig.add_subplot(1, 2, 2)
plt.scatter(X_projected[:, 2], X_projected[:, 3], c=y)
plt.xlabel('F3')
plt.ylabel('F4')
plt.title('Deuxième plan factoriel')

plt.show()
for i in range(n_features):
    ft = data[features[i]]
#     print(features[i], ft.min(), ft.mean(), ft.max())
    print((ft.max() - ft.min())/10)
1.7
19.9
12.2
9.9
39.2
6.709999999999999
0.13979999999999998
6.0

Modélisation

from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

from sklearn.ensemble import BaggingClassifier
from sklearn.metrics import accuracy_score, f1_score, confusion_matrix
X = data.iloc[:, :-1] # variables
y = data.iloc[:, -1] # étiquettes
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, test_size=.2) # 80% pour le jeu d'entraînement

Bagging

Le Bagging est méthode ensembliste qui consiste à entraîner plusieurs modèles de manière parallèle.

Le Bootstraping est une technique d'échantillonnage utilisée en Statistique. Elle consiste à créer des sous-echantillons de même taille que que l'echantillon de travail. On fait des tirages avec remise.

# bg = BaggingClassifier(n_estimators=20, bootstrap_features=True)
bg = BaggingClassifier(n_estimators=20) # 20 modèles
bg.fit(X_train, y_train)

bg.score(X_train, y_train)
1.0

Évaluation

y_pred = bg.predict(X_test)

acc = accuracy_score(y_test, y_pred) 
f1 = f1_score(y_test, y_pred)
confmat = confusion_matrix(y_test, y_pred)

print('Accuracy: %0.2f' % acc)
print('F1 score: %0.2f' % f1)

plt.matshow(confmat, cmap=plt.cm.Greens, alpha=.3)
for i in range(confmat.shape[0]):
    for j in range(confmat.shape[1]):
        plt.text(x=j, y=i, s=confmat[i, j], va='center', ha='center')
plt.xlabel('Valeur prédite')
plt.ylabel('Vraie valeur')
plt.show()
Accuracy: 0.72
F1 score: 0.60