r-Corrigé
r-Corrigé
r-Corrigé
Année 2024-2025
Évaluation intermédiaire
Durée : 1h30
Ce sujet comporte 8 pages.
Le barème sur 20 est donné à titre indicatif. La notation tiendra compte du soin et de la clarté des réponses.
L’usage de tout appareil électronique est interdit à l’exception d’une calculatrice.
Les documents ne sont pas autorisés. Une carte de référence détachable se trouve à la fin du sujet
pour obtenir
n 2
1X 2 n+1 1 n(n + 1)(2n + 1) (n + 1)2 2n + 1 n + 1
var(x) = i − = − = (n + 1) −
n 2 n 6 4 6 4
i=1
(n + 1)(n − 1)
= .
12
Code 2.1 (1 pt.) : Écrire une fonction C qui, étant donné deux vecteurs numpy x et y, ainsi que deux réels a et b
renvoie la valeur du critère C(a, b).
1
# Solution :
def C(x, y, a, b):
return np.sum((y - a - b * x) ** 2)
# ou return (y - a - b * x) @ (y - a - b * x)
Question 2.2 (1 pt.) : Le tableau suivant donne les valeurs du critère C(a, b) pour trois couples de valeurs (a, b) :
a b C(a, b)
1.5 0.10 18.7
2.0 0.10 13.7
2.5 0.05 12.0
Lequel des ces trois couples (a, b) fournit le meilleur ajustement au sens du critère C ?
Solution : La qualité de l’ajustement est d’autant meilleure que la valeur du critère C(a, b) est faible : le
meilleur ajustement est donc fournit par la dernière paire de coefficients (a = 2.5, b = 0.05).
La figure suivante présente la droite de régression obtenue en minimisant le critère C(a, b) ainsi que le
paramètres optimaux (a∗ , b∗ ) et la qualité de cet ajustement, mesurée par le critère de R2 .
a∗ = 1.390
b∗ = 0.126
R2 = 0.94
Question 2.3 (1 pt.) : Au vu de cette figure, vous semble-t-il raisonnable d’utiliser une approximation linéaire
pour approcher la largeur du noyau à partir de la surface de la graine ?
Solution : La figure montre une association relativement linéaire : le recours à une régression linéaire n’est pas
aburde.
On peut voir une légère concavité qui pourrait suggérer une approximation plus complexe.
Question 2.4 (1 pt.) : Donner une approximation de la largeur du noyau pour une graine ayant une surface de
x = 15mm2 et pour une graine ayant une surface de x = 30mm2 .
Solution : Pour x = 15mm2 , on obtient ye = a∗ + b∗ x = 3.28mm. L’approximation linéaire ne s’applique pas
au cas x = 30mm2 qui est nettement en dehors de l’intervalle des valeurs de surface xi observées dans le jeu
de données.
Seconde régression. On cherche maintenant à approcher la largeur du noyau d’une graine i à partir de son
périmètre, noté, à son tour, xi au moyen d’une deuxième régression linéaire. Pour cela, en notant x = [x1 . . . xn ]⊤
le vecteur contenant les périmètres des n graines et y = [y1 . . . yn ]⊤ le vecteur contenant les largeurs de leurs
noyaux, on a calculé
x = 14.6 mm, y = 5.63 mm, var(x) = 1.71 mm2 , cov(x, y) = 0.466 mm2 .
Question 2.5 (1 pt.) : Calculer les coefficients optimaux a∗ et b∗ pour cette nouvelle régression.
Solution : On calcule b∗ = cov(x, y)/ var(x) = 0.273 et a∗ = y − b∗ x = 1.651.
Code 2.6 (1 pt.) : Écrire une fonction cov qui, étant donné deux vecteurs numpy x et y, renvoie la covariance de
x et y.
2
# Solution :
def cov(x, y):
return np.mean((x - np.mean(x)) * (y - np.mean(y)))
# ou return np.mean(x * y) - np.mean(x) * np.mean(y)
Code 2.7 (1 pt.) : Écrire une fonction regression qui, étant donné deux vecteurs numpy x et y, renvoie les
valeurs a∗ et b∗ .
# Solution :
def regression(x, y):
b = cov(x, y) / np.var(x)
return np.mean(y) - b * np.mean(x), b
Code 2.8 (1 pt.) : Écrire les instructions permettant de dessiner le nuage de points (x, y ) ainsi que la droite de
régression calculée par regression sur un même graphique.
# Solution :
plt.scatter(x, y)
a, b = regression(x, y)
plt.plot([np.min(x), np.max(x)], [a + b * np.min(x), a + b * np.max(x)])
Code 2.9 (1 pt.) : Écrire une fonction R2 qui, étant donné deux vecteurs numpy x et y, renvoie la qualité de
l’ajustement mesuré par le R2 .
# Solution :
def R2(x, y):
return cov(x, y) ** 2 / (np.var(x) * np.var(y))
La figure suivante présente la droite de régression obtenue pour le périmètre ainsi que la qualité de son
ajustement.
R2 = 0.89
Question 2.10 (1 pt.) : L’approximation de la largeur du noyau est-elle plus précise à partir de la surface de la
graine ou à partir de son périmètre ?
Solution : Dans les deux, les figures montrent qu’une approximation linéaire n’est pas absurde, mais le R2 de
la régression à partir de la surface est supérieur à celui de la régression à partir du périmètre : on préférera
donc l’approximation fondée sur la surface.
Code 3.1 (1 pt.) : On suppose que les données sur les graines de blé sont données par un tableau numpy appelé
x ayant 210 lignes et deux colonnes correpondant à la surface et à l’asymétrie. Écrire une instruction qui permet
de stocker dans une variable x_cr les données x centrées et réduites.
# Solution :
x_cr = (x - np.mean(x, axis = 0)) / np.std(x, axis = 0)
# ou
x_cr = np.array([(x[:, 0] - np.mean(x[:, 0])) / np.std(x[:, 0]), \
(x[:, 1] - np.mean(x[:, 1])) / np.std(x[:, 1])]).T
# ou avec des boucles
Effet de l’initialisation. La qualité d’une classification est mesurée par la dispersion intra-groupes notée D(C, M)
dans le cours et on sait que la classification retournée par l’algorithme des k-means dépend de son initialisation,
(0) (0)
c’est-à-dire du choix des centres de groupes initiaux m1 , . . . mk , réunis dans la matrice M(0) .
Pour étudier l’effet de ce choix, on a exécuté l’algorithme des k-means avec 1000 initialisations différentes
en tirant au hasard k = 3 observations parmi les n = 210 en guise de centres initiaux. La figure suivante donne
l’histogramme des dispersions intra-groupes D(C, M) obtenues à l’issue de ces 1000 exécutions :
et le tableau suivant donne les valeurs arrondies de D(C, M) obtenues pour les 1000 initialisations :
Code 3.3 (1 pt.) : On suppose qu’on dispose d’une fonction kmeans3 qui prend en argument des données de type
x_cr et qui renvoie le resultat de l’application de l’algorithme k-means pour k = 3 et une initialisation aléatoire,
sous la forme d’un vecteur numpy y où chaque y[i] correspond au groupe auquel appartient x_cr[i] (parmi
{1, 2, 3}).
Expliquer ce que calcule la suite d’instructions suivante :
y = kmeans3(x_cr)
np.sum((x_cr[y == 1] - np.mean(x_cr[y == 1], axis = 0)) ** 2)
# Solution :
# La première instruction calcule un vecteur indiquant le groupes
# auquel appartient chaque donnée de \codepy{x_cr} après application
# de l'algorithme k-means.
# Le seconde instruction calcule la dispersion du groupe 1 en
# récupérant le vecteur des points du groupe 1 avec x_cr[y == 1],
# en retirant sa moyenne à chacune de ses coordonnées,
# en mettant chaque coordonnée au carré puis en les additionnant toutes.
Code 3.4 (1 pt.) : Écrire la fonction d_intra qui étant donné un vecteur de données x_cr et un vecteur de
groupes y, renvoie la valeur de la dispersion intra groupes.
# Solution :
def d_intra(x_cr, y):
s = 0
for i in range(1, 4):
s = s + np.sum((x_cr[y == i] - np.mean(x_cr[y == i], axis = 0)) ** 2)
return s
Code 3.5 (1 pt.) : En utilisant la fonction précédente et la fonction kmeans3, écrire la suite d’instructions per-
mettant de générer l’histogramme des dispersions intra-groupes précédent.
# Solution :
data = [d_intra(x_cr, kmeans3(x_cr)) for _ in range(1000)]
plt.hist(data)
On compare maintenant les classifications en k = 3 groupes obtenues en partant de deux initialisations diffé-
rentes. La figure ci-dessous donne le critère de dispersion intra-groupes D(C, M) et la classifications finalement
obtenus avec chacune d’elle.
Initialisation 1 : D(C, M) = 149.9 Initialisation 2 : D(C, M) = 111.3
5
Les symboles et couleurs correspondent aux k = 3 groupes dans lesquels les données sont classées.
Code 3.7 (1 pt.) : Écrire la fonction d_inter qui étant donné un vecteur de données x_cr et un vecteur de
groupes y, renvoie la valeur de la dispersion inter-groupes.
# Solution :
def d_inter(x_cr, y):
return np.sum((x_cr - np.mean(x_cr, axis = 0)) ** 2) - d_intra(x_cr, y)
Lien avec la variété. Les n = 210 graines sont en fait issues de trois variétés de blé différentes : ’Canadian’,
’Kama’ et ’Rosa’. On cherche à savoir s’il est possible de regrouper les graines par variété en se fondant seulement
sur leur forme. Pour cela, on dresse le tableau des effectifs croisés des variétés auxquelles appartiennent les graines
avec les groupes auxquels elles sont affectées par l’algorithme des k-means.
Initialisation 1 Initialisation 2
Groupe Groupe
Variété 1 2 3 Total 1 2 3 Total
Canadian 1 64 5 70 Canadian 52 18 0 70
Kama 27 17 26 70 Kama 4 58 8 70
Rosa 36 0 34 70 Rosa 0 7 63 70
Total 64 81 65 210 Total 56 83 71 210
Question 3.8 (1 pt.) : Parvient-on finalement à regrouper les graines par variété seulement à partir de leur forme ?
(Justifiez votre réponse.)
Solution : Les groupes résultants de la 1ère initialisation ont peu à voir avec les variétés des graines, mais
cette classification n’est de toute façon pas satisfaisante du point de vue de la dispersion intra-groupes.
Les variétés correspondent mieux aux groupes résultants de la 2ème initialisation (qui donne une plus faible
dispersion intra-groupes) : la variété Canadian avec le groupe 1, la variété Kama avec le groupe 2 et la variété
Rosa avec la groupe 3.
Le tableau croisé n’est cependant pas diagonal : la correspondance exacte ne vaut que pour 52 + 58 + 63 = 173
graines parmi les 210 (soit 82%). La classification résultant des k-means ne permet donc que partiellement de
retrouver les variétés de graines.
6
Opérations mathématiques sur les matrices (array 2D / liste de listes)
def transposition(LL:List[List[float]]) -> List[List[float]]:
LL_t: List[List[float]] = []
Carte de correspondance >>> M.T
for j in range(len(LL[0])):
L: List[float] = []
LU1INMA1 – Sciences des données array([[1.5, 4. ], for i in range(len(LL)):
[2. , 5. ], L.append(LL[i][j])
[3. , 6. ]]) LL_t.append(L)
return LL_t
Avertissement >>> transposition(LLM)
Les bibliothèques numpy, pandas et matplotlib utilisées dans l’UE LU1INMA1 ne sont pas utilisées dans les UE LU1INI001 et [[1.5, 4. ],[2. , 5. ],[3. , 6. ]]
LU1INI011 de programmation python. Le type dataframe (propre à pandas) et le type array (propre à numpy) ne sont pas def mult_scal(LL:List[List[float]],a:float) -> List[List[float]]:
disponibles en LU1INI0*1. LL_s: List[List[float]] = []
Ce document décrit certaines opérations réalisées sur une array numpy de dimension 1 ou 2 en vis-à-vis d’opérations qui for i in range(len(LL)):
correspondraient sur une liste (ou une liste de listes). Pour plus de compacité, les opérations sont faites sous forme de L: List[float] = []
compréhensions quand c’est possible et les types des variables ne sont pas systématiquement déclarés. >>> 3 * x for j in range(len(LL[0])):
Notez que certaines opérations vues n’ont pas d’équivalent simple sans les bibliothèques numpy, pandas et/ou matplotlib. array([[3, 6, 9]]) L.append(LL[i][j]*a)
LL_s.append(L)
return LL_s
>>> mult_scal(LLx,3)
Création et initialisation de vecteurs (array 1D / liste) [[3, 6, 9]]
def prod_mat(LL1:List[List[float]], LL2:List[List[float]]) ->
u = np.array([1,2,3]) Lu: List[int] = [1,2,3]
List[List[float]]:
v = np.zeros(4) Lv: List[int] = [0 for i in range(4)]
LL_pdt: List[List[float]] = []
w = np.ones(3) Lw: List[int] = [1 for i in range(3)] for i in range(len(LL1)):
t = np.random.rand(3) Lt: List[float] = [random.random() for i in range(3)] LL_pdt.append([])
>>> M @ y for j in range(len(LL2[0])):
array([[14.5], t_ij: float = 0
[32. ]]) for k in range(len(LL1[0])):
Opérations mathématiques sur les vecteurs (array 1D / liste) t_ij = t_ij + LL1[i][k]*LL2[k][j]
LL_pdt[i].append(t_ij)
def pdt_scalaire (L1: List[float], L2: List[float]) -> float: return LL_pdt
scal: float = 0 >>> prod_mat (LLM, LLy)
for i in range(len(L1)): [[14.5], [32.]]
>>> u @ w
6 scal = scal + L1[i]*L2[i]
return scal
>>> pdt_scalaire (Lu,Lw)
6 Fonctions d’agrégation
def som_vec (L1:List[float], L2:List[float]) -> List[float]: On suppose que la fonction d’agrégation sera appliquée à une liste (non-vide) contenant l’ensemble des données.
L_som: List[float] = []
>>> u + w for i in range(len(L1)):
L_som.append(L1[i]+L2[i]) def somme(Lt: List[float]) -> float:
array([2, 3, 4]) som: float = 0
return L_som
>>> som_vec (Lu,Lw) np.sum(t) for elt in Lt:
som = som + elt
[2, 3, 4] return som
def minimum(Lt: List[float]) -> float:
mn: float = Lt[0]
Création et initialisation de matrices (array 2D / liste de listes) np.min(t) for elt in Lt[1:]:
if mn < elt:
mn = elt
x = np.array([[1, 2, 3]]) LLx: List[List[int]] = [[1, 2, 3]] return mn
y = np.array([[1], [2], [3]]) LLy: List[List[int]] = [[1], [2], [3]]
def moyenne(Lt: List[float]) -> float:
M = np.array([[1.5, 2, 3], [4, 5, 6]]) LLM: List[List[float]] = [[1.5, 2, 3], [4, 5, 6]] som: float = 0
z = np.zeros((1, 3)) LLz: List[List[int]] = [[0 for j in range(3)]] np.mean(t) for elt in Lt:
P = np.ones((3,2)) LLP = [[1 for j in range(2)] for i in range(3)] som = som + elt
Q = np.random.rand(3, 3) LLQ = [[random.random() for j in range(3)] for i in range(3)] return som/len(Lt)
def variance(Lt: List[float]) -> float:
moy: float = moyenne(Lt)
somcar: float = 0
Forme de la structure (array 1 ou 2D / liste ou liste de listes) np.var(t) for elt in Lt:
somcar = somcar + (elt - moy)**2
>>> np.shape(M) >>> len(LLM),len(LLM[0]) return somcar/len(Lt)
(2, 3) (2, 3) def uniques(Lt: List[float]) -> List[float]:
>>> np.ravel(y) >>> [elt for L in LLy for elt in L] Lu: List[float] = []
np.unique(t) for elt in Lt:
array([1, 2, 3]) [1, 2, 3] if elt not in Lu:
>>> np.reshape(u, (3, 1)) >>> [[Lu[j] for j in range(i,i+1)] for i in range(3)] Lu.append(elt)
array([[1],[2],[3]]) [[1],[2],[3]] return Lu
Forme du tableau Opérations mathématiques sur les vecteurs
>>> len(u) Taille d’un vecteur >>> u @ w Produit scalaire
3 6
Carte de référence >>> np.shape(M)
(2, 3)
Dimensions d’une matrice >>> u + w
array([2, 3, 4])
Somme de vecteurs
LU1INMA1 – Sciences des données
>>> np.ravel(y) >>> 2 * u Multiplication par un
Conversion en vecteur
array([1, 2, 3]) array([2, 4, 6]) scalaire
Bibliothèques >>> np.reshape(u, (1, 3))
Conversion en matrice
array([[1],
Pour importer les bibliothèques utilisées en cours, on utilisera la convention [2], colonne
suivante au début de chaque notebook : [3]])
Opérations mathématiques sur les matrices
import numpy as np
import pandas as pd >>> M.T
import matplotlib.pyplot as plt array([[1.5, 4. ],
Transposition
%matplotlib inline Fonctions d’agrégation [2. , 5. ],
[3. , 6. ]])
np.sum(t) Somme du tableau >>> 3 * x Multiplication par un
Données avec Pandas np.min(t) Minimum du tableau array([[3, 6, 9]]) scalaire
np.argmin(t) Indice du minimum
>>> x + x
df = pd.read_csv(’fichier.csv’) Chargement d’un fichier csv np.max(t) Maximum du tableau Somme de x et x
array([[2, 4, 6]])
df.info() Information sur le tableau np.argmax(t) Indice du maximum
Nombre d’éléments par np.mean(t) Moyenne >>> M @ y Produit matriciel de M par
df.count() array([[14.5],
colonne np.std(t) Écart-type y
Tableau des intitulés de np.var(t) Variance [32. ]])
df.columns
colonnes np.median(t) Médiane
Nombre de lignes du np.unique(t) Tableau des valeurs uniques
len(df)
tableau Optimisation
df.head(10) 10 premières lignes
df.tail(10) 10 dernières lignes
series = df[’prix’] Extraction d’une colonne Opération globales sur les tableaux Résolution de l’équation
np.linalg.solve(Q, y) matricielle Qx = y
cols = df[[’col1’, ’col4’]] Idem plusieurs colonnes
Tableau des valeurs d’inconnue x
df.iloc[5] 5e ligne np.abs(t)
df.iloc[0:4] Lignes de 0 inclus à 4 exclu absolues
vec = np.array(series) Conversion en vecteur np.round(t) Tableau des arrondis
mat = np.array(cols) Conversion en matrice t + 3
Ajout d’une constante à Représentations graphiques
chaque case
Tableau de booléens
brut = np.array([3, 1, 1, 2, 1, 3, 2]) Données brutes
q = (t[’col1’] > 0) calculés en effectuant le
Création et initialisation de vecteurs test sur chaque ligne
X = np.array([1, 2, 3]) Positions
Y = np.array([2.2, 1,5, 7,2]) Données
r = (t[’col2’] > 0) & (t[’col3’] <= 5) Test plus complexe
u = np.array([1, 2, 3]) u= 1 2 3 etiq = [’gauche’, ’milieu’, ’droite’] Étiquettes
Filtage des lignes de t ne
v = np.zeros(4) v = 0 0 0 0 Histogramme à 3 barres des
t[q] conservant que les lignes de plt.hist(brut, bins = 3)
w = np.ones(3) w = 1 1 1 données brutes
q à True
Histogramme des données
t = 0.45 0.11 0.9 q & r ’et’ logique case à case plt.bar(X, Y, tick_label = etiq)
Y
t = np.random.rand(3) (tirages uniformes dans q | r ’ou’ logique case à case
plt.pie(Y, labels = etiq) Camembert des données Y
[0, 1[) négation logique pour
~q Nuage de points rouges de
chaque case plt.scatter(X, Y, s=1, color=’red’)
taille 1
Points reliés par les
plt.plot(X, Y, color=’red’)
Création et initialisation de matrices segments rouges
Manipulations non mathématiques
x = np.array([[1, 2, 3]]) x = 1 2 3
1
y = np.array([[1], [2], [3]])
y = 2
>>> np.vstack( (M, x) ) Python
array([[1.5, 2. , 3. ],
Concaténation verticale
3 [4. , 5. , 6. ],
Boucle pour i de 0 à 4
M = np.array([[1.5, 2, 3], [4, 5, 6]]) 1.5 2 3 [1. , 2. , 3. ]]) for i in range(0, 5):
M= # intérieur de la boucle inclus
4 5 6
>>> np.hstack( (y, P) ) Compréhension de liste
z = np.zeros((1, 3)) z = 0 0 0 >>> [ i ** 2 for i in range(0, 5) ]
array([[1, 1, 1],
1 1 Concaténation horizontale [0, 1, 4, 9, 16] simple
P = np.ones((3, 2)) [2, 1, 1],
P= 1 1 [3, 1, 1]]) for cle, valeur in dico.items(): Boucle parcourant le
1 1 # intérieur de la boucle dictionnaire dico
Q = np.random.rand(3, 3) Q matrice 3 × 3 aléatoire