Plus proches voisins - projection

Links: notebook, html, PDF, python, slides, GitHub

On projette le jeu de données initiale selon les premiers axes d’une analyse en composantes principales (ACP).

%matplotlib inline
from papierstat.datasets import load_wines_dataset
df = load_wines_dataset()
X = df.drop(['quality', 'color'], axis=1)
y = df['quality']

On utilise la classe PCA.

from sklearn.decomposition import PCA
from sklearn.preprocessing import normalize
pca = PCA(n_components=5)
pca.fit(X)
PCA(copy=True, iterated_power='auto', n_components=5, random_state=None,
  svd_solver='auto', tol=0.0, whiten=False)

On regarde la variance expliquée par chaque axe : la valeur propre.

import pandas
eig = pandas.DataFrame(dict(valeur=pca.explained_variance_ratio_))
ax = eig.plot(kind='bar', figsize=(3,3))
ax.set_title("Valeurs propres ACP");
../_images/wines_knn_acp_6_0.png

Le premier explique une grande part de la variance, trop grande. C’est louche. Regardons les coordonnées du premier axe.

v1 = pandas.DataFrame(dict(valeur=pca.components_[0,:]))
v1.index = df.columns[:-2]
ax = v1.plot(kind='bar')
ax.set_title("Coordonnées du premier axe de l'ACP");
../_images/wines_knn_acp_8_0.png

Il est quasiment égal à une seule coordonnées. Cela suppose que les variables ont des échelles différentes.

df.iloc[:, 3:8].describe()
residual_sugar chlorides free_sulfur_dioxide total_sulfur_dioxide density
count 6497.000000 6497.000000 6497.000000 6497.000000 6497.000000
mean 5.443235 0.056034 30.525319 115.744574 0.994697
std 4.757804 0.035034 17.749400 56.521855 0.002999
min 0.600000 0.009000 1.000000 6.000000 0.987110
25% 1.800000 0.038000 17.000000 77.000000 0.992340
50% 3.000000 0.047000 29.000000 118.000000 0.994890
75% 8.100000 0.065000 41.000000 156.000000 0.996990
max 65.800000 0.611000 289.000000 440.000000 1.038980
mean_val = df.describe().T['mean']
mean_val.index = df.columns[:-1]
ax = mean_val.plot(kind='bar', figsize=(6,3))
ax.set_title("Moyenne de chaque variable");
../_images/wines_knn_acp_11_0.png

La valeur moyenne de chaque variable ressemble étrangement aux coordonnées du premier axe de l’ACP. Il faut normaliser les données avec normalize.

pca = PCA(n_components=5)
Xn = normalize(X)
pca.fit(Xn)
PCA(copy=True, iterated_power='auto', n_components=5, random_state=None,
  svd_solver='auto', tol=0.0, whiten=False)

C’est beaucoup mieux.

import pandas
eig = pandas.DataFrame(dict(valeur=pca.explained_variance_ratio_))
ax = eig.plot(kind='bar', figsize=(3,3))
ax.set_title("Valeur propres de l'ACP\naprès normalisation");
../_images/wines_knn_acp_15_0.png
v2 = pandas.DataFrame(pca.components_[0:2,:]).T
v2.index = df.columns[:-2]
v2.columns = ['v1', 'v2']
ax = v2.plot(y=['v1', 'v2'], kind='bar', figsize=(6,3))
ax.legend(loc='upper left')
ax.set_title("Comparaison des coordonnées\ndes deux premiers axes de l'ACP");
c:Python364_x64libsite-packagespandasplotting_core.py:1716: UserWarning: Pandas doesn't allow columns to be created via a new attribute name - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute-access
  series.name = label
../_images/wines_knn_acp_16_1.png

L’alcool, l’acidité, le dioxyde, le pH semble jouer un rôle plus grand que les autres variables. On projette maintenant les observations.

proj = pca.transform(Xn)
pl = pandas.DataFrame(proj[:, :3])
pl.columns = ['v1', 'v2', 'v3']
pl['quality'] = df['quality']
pl['color'] = df['color']

Premier graphe selon les couleurs.

import seaborn
ax = seaborn.lmplot(x="v1", y="v2", hue="color", truncate=True, data=pl,
                    scatter_kws={"s": 1}, fit_reg=False, size=3)
ax.ax.set_title("Projection des vins sur\nles deux premiers axes de l'ACP");
../_images/wines_knn_acp_21_0.png

Autre façon de tracer le même graphe.

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(8, 4))

red = pl[pl.color == 'red']
white = pl[pl.color == 'white']

# second graphd avec pandas
red.plot(x='v1', y='v2', label='red', kind='scatter', ax=ax[0], color="red", s=1)
white.plot(x='v1', y='v2', label='white', kind='scatter', ax=ax[0], color="blue", s=1)

# troisième graphe pour la densité
seaborn.kdeplot(red.v1, red.v2, cmap="Reds", shade=True, shade_lowest=False, ax=ax[1])
seaborn.kdeplot(white.v1, white.v2, cmap="Blues", shade=True, shade_lowest=False, ax=ax[1])
ax[0].set_title("Projection des vins sur\nles deux premiers axes de l'ACP")
ax[1].set_title("Estimation de la densité");
../_images/wines_knn_acp_23_0.png

Avec le troisième axe.

import matplotlib.pyplot as plt
fig, ax = plt.subplots(1, 2, figsize=(8, 4))

red = pl[pl.color == 'red']
white = pl[pl.color == 'white']

red.plot(x='v1', y='v2', label='red', kind='scatter', ax=ax[0], color="red", s=1)
white.plot(x='v1', y='v2', label='white', kind='scatter', ax=ax[0], color="blue", s=1)
ax[0].set_title("Axes 1, 2")
red.plot(x='v1', y='v3', label='red', kind='scatter', ax=ax[1], color="red", s=1)
white.plot(x='v1', y='v3', label='white', kind='scatter', ax=ax[1], color="blue", s=1)
ax[1].set_title("Axes 1, 3");
../_images/wines_knn_acp_25_0.png

On représente maintenant les notes des vins.

import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cmx
fig, axs = plt.subplots(1, 3, figsize=(12, 4))

# Choisir un dégragé ici
cmap = plt.get_cmap('plasma')
cnorm = colors.Normalize(vmin=pl['quality'].min(), vmax=pl['quality'].max())
scalar = cmx.ScalarMappable(norm=cnorm, cmap=cmap)

for i, data, title in [(0, pl, 'tous'), (1, red, 'red'), (2, white, 'white')]:
    ax = axs[i]
    # On trace les points pour que le texte n'apparaissent pas en dehors des zones
    pl.plot(x='v1', y='v2', kind='scatter', color="white", ax=ax)

    for note in sorted(set(data['quality'])):
        sub = data[data.quality == note]
        if sub.shape[0] > 100:
            sub = sub.sample(n=30)

        color = scalar.to_rgba(note)
        for i, row in enumerate(sub.itertuples()):
            ax.text(row[1], row[2], str(row[4]), color=color)
    ax.set_title(title);
../_images/wines_knn_acp_27_0.png

Les vins rouges et blancs apparaissent comme très différents, cela vaudra sans doute le coup de faire deux modèles si la performance n’est pas assez bonne. Les bonnes notes ne se détache pas particulièremnt sur ces graphes. Le problème est peut-être simple mais ce ne sont pas ces graphes qui vont nous le dire.