Trouver les types d'acheteurs en gros
- Étude de cas 7
- 100 min
- Facultative · 1 étude de cas requise pour le certificat
Étude de cas 7 sur 7 · Apprentissage automatique : du problème à un modèle fiableTrouver les types d'acheteurs en gros
1. Comprendre l'apprentissage automatique
2. Cadrer le problème de prédiction
3. Évaluer les modèles supervisés
4. Créer les premiers modèles
5. Essayer d'autres familles de modèles
6. Régler et finaliser le modèle
7. Comprendre et choisir le modèle
8. Préparer la passation du modèle
Ce que vous saurez faire
Découvrez si les clients d’un grossiste alimentaire achètent comme leurs étiquettes le suggèrent. Les dépenses sont si asymétriques que le regroupement (clustering) ayant la meilleure silhouette est inutile. Vous corrigez cela avec un logarithme et une mise à l’échelle. Vous représentez ensuite les clients en deux dimensions avec la PCA, l’analyse en composantes principales. Puis vous comparez les groupes à l’étiquette que l’entreprise utilise déjà. Le résultat intéressant concerne les clients pour lesquels les deux ne concordent pas. Avec seulement 440 clients, vous apprenez aussi à préciser jusqu’où vous pouvez faire confiance au résultat.
Le carnet (notebook) Jupyter contient tous les blocs de code de cette page, dans le même ordre. Il comprend trois tâches et leurs vérifications. Ses cellules de préparation installent les paquets et téléchargent les données (7 Ko), sans configuration locale. Il s’exécute en moins d’une minute. Lisez les explications ici et exécutez le code dans le carnet.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- k-means, la mise à l’échelle, l’inertie, la silhouette et la PCA, dans L’exploration non supervisée.
- Les pipelines, dans Prétraiter avec un pipeline.
- Les sous-groupes et pourquoi un petit sous-groupe est peu fiable, dans L’analyse par sous-groupes.
- Le logarithme avant la mise à l’échelle, la vérification de stabilité, l’indice de Rand ajusté (ARI) et le journal d’expériences de regroupement, dans Segmenter les clients d’une boutique en ligne. Si vous avez sauté cette section, lisez-la d’abord : cette page utilise les mêmes outils.
Ce qu’il vous faut sur votre ordinateur (seulement pour l’exécution locale; le carnet n’en a pas besoin)
- Un environnement Python avec pandas 3.0.6, scikit-learn 1.9.1 et matplotlib, comme le
.venvde votre projet de cours dans Définir une référence de base : préparer votre projet. - Un dossier pour les études de cas, par exemple
~/projects/case-studies. Ouvrez-y le carnet. Sa cellule de préparation crée le dossierfind-wholesale-buyer-typeset y télécharge les données. Pour recommencer, supprimez ce dossier et exécutez le carnet depuis le début.
Testé sur macOS avec Python 3.14.6, pandas 3.0.6, scikit-learn 1.9.1 et matplotlib 3.11.2, dans Jupyter. Windows, Colab et Kaggle n’ont pas été testés.
La situation
Section intitulée « La situation »Un grossiste alimentaire au Portugal livre à deux types d’entreprises. Inês, la responsable des ventes, gère deux plans de livraison :
- La tournée des cuisines : de petites livraisons plusieurs fois par semaine, surtout d’aliments frais et surgelés, pour les hôtels, restaurants et cafés. Le secteur appelle ce canal Horeca (hôtel, restaurant, café).
- La tournée des rayons : une grosse livraison hebdomadaire de produits d’épicerie, de lait, de détergents et de papier aux magasins (le canal Retail, commerce de détail).
Aujourd’hui, le fichier clients détermine la tournée : chaque client a une étiquette de canal, Horeca ou Retail. Inês a un doute. « Certains cafés vendent aussi des produits d’épicerie, et certains magasins ont un comptoir de produits frais. Nos clients achètent-ils vraiment comme leur étiquette le suggère? » Une mauvaise tournée a un coût. Un café sur la tournée des rayons reçoit ses aliments frais trop tard et les achète ailleurs. Un magasin sur la tournée des cuisines paie pour des livraisons inutiles.
Rui, un représentant commercial, propose un raccourci : « Laissons l’ordinateur regrouper les clients et prenons le regroupement avec le meilleur score. » Cette étude de cas montre pourquoi ce raccourci échoue ici.
Inês et Rui sont fictifs. Les données sont réelles : une année de dépenses de 440 clients d’un distributeur grossiste au Portugal.
Ce que vous allez créer
Section intitulée « Ce que vous allez créer »Vous créerez une liste de types d’acheteurs. Chaque client reçoit un type selon ses achats, et non selon son étiquette. La liste signale les clients dont le type et l’étiquette ne concordent pas, pour que l’équipe d’Inês les vérifie. Vous rédigerez aussi une note courte et honnête sur la confiance à accorder au résultat.
L’étude de cas utilise ces compétences :
- k-means, la PCA et les indices pour choisir k (L’exploration non supervisée);
- le logarithme avant la mise à l’échelle, la stabilité et l’ARI (Segmenter les clients d’une boutique en ligne);
- un pipeline pour le prétraitement (Prétraiter avec un pipeline);
- les sous-groupes et leurs effectifs (L’analyse par sous-groupes);
- l’étiquette existante comme référence de base : ce que l’entreprise fait déjà (Définir une référence de base).
Les données
Section intitulée « Les données »Les données proviennent de Wholesale customers, du UCI Machine Learning Repository. Margarida Cardoso les a publiées sous la licence Creative Commons Attribution 4.0 (CC BY 4.0).
Cardoso, M. (2013). Wholesale customers [Jeu de données]. UCI Machine Learning Repository. https://doi.org/10.24432/C5030X. CC BY 4.0.
Le cours conserve une copie fixe dans son dépôt de laboratoires. C’est le fichier source, compressé, sans autre modification; la fiche du jeu de données donne les détails. Chaque ligne représente un client et ses dépenses annuelles pour six types de produits :
| Colonne | Signification |
|---|---|
Channel |
1 = Horeca (hôtel, restaurant, café), 2 = Retail (magasin). L’étiquette que l’entreprise utilise aujourd’hui. |
Region |
1 = Lisbonne, 2 = Porto, 3 = autres régions. |
Fresh, Milk, Grocery, Frozen |
Dépenses annuelles pour chaque type de produit : frais, lait, épicerie et surgelés. |
Detergents_Paper |
Dépenses en produits de nettoyage et articles en papier. |
Delicassen |
Dépenses en produits d’épicerie fine (orthographe de la source). |
La source indique les dépenses en « unités monétaires » (u.m.), sans préciser la devise ni l’année.
customers = pd.read_csv("data/wholesale_customers.csv.gz")SPEND = ["Fresh", "Milk", "Grocery", "Frozen", "Detergents_Paper", "Delicassen"]print(customers.shape, "| missing values:", customers.isna().sum().sum(), "| repeated rows:", customers.duplicated().sum())customers.head()Le résultat :
(440, 8) | missing values: 0 | repeated rows: 0 Channel Region Fresh Milk Grocery Frozen Detergents_Paper Delicassen0 2 3 12669 9656 7561 214 2674 13381 2 3 7057 9810 9568 1762 3293 17762 2 3 6353 8808 7684 2405 3516 78443 1 3 13265 1196 4221 6404 507 17884 2 3 22615 5410 7198 3915 1777 5185Ce que cela nous apprend. Les données sont peu nombreuses et propres : 440 clients, aucune valeur manquante, aucune ligne répétée. Le client 0 est un magasin (canal 2). Il a dépensé 12 669 u.m. en aliments frais et 7 561 en produits d’épicerie durant l’année. Le client 3 est une entreprise Horeca qui dépense beaucoup en aliments frais et surgelés, mais peu en détergents et papier. C’est ce qu’on attend d’une cuisine.
Les codes 1, 2 et 3 sont difficiles à lire. Donnez-leur des noms :
customers["channel"] = customers["Channel"].map({1: "Horeca", 2: "Retail"})customers["region"] = customers["Region"].map({1: "Lisbon", 2: "Oporto", 3: "Other"})pd.crosstab(customers["channel"], customers["region"], margins=True)Le résultat :
region Lisbon Oporto Other AllchannelHoreca 59 28 211 298Retail 18 19 105 142All 77 47 316 440Ce que cela nous apprend. Deux clients sur trois sont Horeca (298). La plupart se trouvent hors de Lisbonne et de Porto (316). Certains sous-groupes sont petits : seulement 18 magasins à Lisbonne et 19 à Porto. Tout résultat pour ces sous-groupes repose sur quelques clients.
Cadrer le problème
Section intitulée « Cadrer le problème »- L’unité est un client, car le plan de livraison est établi par client.
- Il n’y a pas de cible. Personne ne connaît le « vrai » type d’acheteur. Le regroupement cherche des types uniquement dans les dépenses.
- L’étiquette de canal reste hors du regroupement. Si elle y entrait, les groupes la copieraient. Vous ne pourriez pas découvrir où les dépenses la contredisent. Vous la gardez à part et la comparez aux groupes une seule fois, à la fin, comme des données de test.
- La région reste aussi à l’écart. Ce n’est pas un profil d’achat. Vous l’utilisez seulement pour vérifier les résultats dans chaque région.
- Peu de données. 440 clients sur une année, c’est peu. Il faut vérifier la stabilité de chaque résultat. Les conclusions sont provisoires : une liste à vérifier par l’équipe d’Inês, pas une décision.
Explorer ce qu’exige la décision
Section intitulée « Explorer ce qu’exige la décision »Les distances déterminent les groupes de k-means. La première question concerne donc la répartition des dépenses :
summary = customers[SPEND].describe().T[["min", "25%", "50%", "75%", "max"]].round(0)summary["skew"] = customers[SPEND].skew().round(1)summaryLe résultat :
min 25% 50% 75% max skewFresh 3.0 3128.0 8504.0 16934.0 112151.0 2.6Milk 55.0 1533.0 3627.0 7190.0 73498.0 4.1Grocery 3.0 2153.0 4756.0 10656.0 92780.0 3.6Frozen 25.0 742.0 1526.0 3554.0 60869.0 5.9Detergents_Paper 3.0 257.0 816.0 3922.0 40827.0 3.6Delicassen 3.0 408.0 966.0 1820.0 47943.0 11.2Ce que cela nous apprend. Chaque colonne a une longue traîne. Le client médian dépense 4 756 u.m. en produits d’épicerie, mais le plus gros dépense 92 780, presque 20 fois plus. L’épicerie fine est le cas le plus extrême (asymétrie de 11,2) : la médiane est 966 et le maximum, 47 943. À l’autre extrême, certains clients dépensent seulement 3 u.m. par année dans une catégorie. Le graphique montre les dépenses d’épicerie telles qu’enregistrées, puis après un logarithme.
Description textuelle du graphique
Un histogramme avec une très longue traîne à droite. 226 clients dépensent moins de 5 000 u.m., 97 dépensent de 5 000 à 10 000 et 55, de 10 000 à 15 000. Puis les barres diminuent : 24, 18, 8, 5 et 2 clients. Seulement 5 clients dépensent plus de 45 000, le plus gros atteignant 92 780.
Description textuelle du graphique
Un histogramme avec une bosse principale. La plupart des clients se situent entre 3 et 4,5 sur l’échelle logarithmique (1 000 à 30 000 u.m.). La barre la plus haute va de 3,25 à 3,5 et compte 91 clients. Quelques clients sont plus bas, entre 2 et 3. Un client est seul vers 0,5 : une dépense de 3 u.m.
Le logarithme donne à chaque client une place équitable. Il a aussi un coût : les valeurs minuscules deviennent les nouveaux extrêmes. Gardez cela en tête pour la section sur les valeurs aberrantes.
Certaines colonnes varient aussi ensemble :
customers[SPEND].corr().round(2)Le résultat :
Fresh Milk Grocery Frozen Detergents_Paper DelicassenFresh 1.00 0.10 -0.01 0.35 -0.10 0.24Milk 0.10 1.00 0.73 0.12 0.66 0.41Grocery -0.01 0.73 1.00 -0.04 0.92 0.21Frozen 0.35 0.12 -0.04 1.00 -0.13 0.39Detergents_Paper -0.10 0.66 0.92 -0.13 1.00 0.07Delicassen 0.24 0.41 0.21 0.39 0.07 1.00Ce que cela nous apprend. L’épicerie et les détergents et papier varient presque ensemble (0,92). Le lait suit les deux (0,73 et 0,66). Ce sont les produits de la tournée des rayons. Le frais et les surgelés varient un peu ensemble (0,35) : les produits des cuisines. Deux colonnes fortement liées comptent presque deux fois dans une distance. Ce n’est pas faux, mais cela donne plus de poids aux produits des rayons. La variante dans le carnet vérifie cet effet.
La règle actuelle : l’étiquette de canal
Section intitulée « La règle actuelle : l’étiquette de canal »La référence de base est la pratique actuelle de l’entreprise : planifier selon le canal. À quel point les achats des deux canaux diffèrent-ils?
share = customers[SPEND].div(customers[SPEND].sum(axis=1), axis=0)print("Median spend per year, by channel:")print(customers.groupby("channel")[SPEND].median())print("\nMedian share of a customer's spend, by channel:")print(share.groupby(customers["channel"]).median().round(3))Le résultat :
Median spend per year, by channel: Fresh Milk Grocery Frozen Detergents_Paper DelicassenchannelHoreca 9581.5 2157.0 2684.0 2057.5 385.5 821.0Retail 5993.5 7812.0 12390.0 1081.0 5614.5 1350.0
Median share of a customer's spend, by channel: Fresh Milk Grocery Frozen Detergents_Paper DelicassenchannelHoreca 0.492 0.101 0.137 0.099 0.019 0.04Retail 0.138 0.223 0.339 0.028 0.155 0.03Ce que cela nous apprend. L’étiquette fonctionne en moyenne. Le client Horeca médian consacre 49,2 % de son budget au frais et 1,9 % aux détergents et papier. Le magasin médian consacre 33,9 % à l’épicerie et 15,5 % aux détergents et papier. Une part est la portion des dépenses d’un client consacrée à une catégorie. Elle montre ce que le client achète, indépendamment de combien. La question est de savoir si chaque client suit la moyenne de son étiquette.
Premier essai : k-means sur les dépenses brutes
Section intitulée « Premier essai : k-means sur les dépenses brutes »Le raccourci de Rui : exécuter k-means sur les dépenses telles qu’enregistrées et choisir le k ayant la meilleure silhouette.
from sklearn.cluster import KMeansfrom sklearn.metrics import adjusted_rand_score, silhouette_score
def try_k(X, ks=range(2, 7)): """Silhouette and cluster sizes for each k.""" for k in ks: labels = KMeans(n_clusters=k, n_init=20, random_state=0).fit_predict(X) print(f"k {k}: silhouette {silhouette_score(X, labels):.3f} " f"sizes {sorted(np.bincount(labels).tolist(), reverse=True)}")
try_k(customers[SPEND].to_numpy())Le résultat :
k 2: silhouette 0.512 sizes [375, 65]k 3: silhouette 0.478 sizes [330, 60, 50]k 4: silhouette 0.387 sizes [276, 95, 58, 11]k 5: silhouette 0.373 sizes [226, 104, 81, 22, 7]k 6: silhouette 0.375 sizes [227, 113, 71, 22, 5, 2]Ce que cela nous apprend. k = 2 donne une silhouette de 0,512, supérieure à toutes celles de l’étude de cas sur la boutique en ligne. Rui est content. Mais regardez les effectifs : 375 et 65. Pour les k plus grands, certains groupes comptent 7, 5 et 2 clients. Que signifient les deux groupes de k = 2?
raw_labels = KMeans(n_clusters=2, n_init=20, random_state=0).fit_predict(customers[SPEND].to_numpy())raw_profile = customers.groupby(raw_labels)[SPEND].median()raw_profile["customers"] = np.bincount(raw_labels)raw_profile["total"] = customers[SPEND].sum(axis=1).groupby(raw_labels).median()raw_profileLe résultat :
Fresh Milk Grocery Frozen Detergents_Paper Delicassen customers total0 30624.0 4411.0 5428.0 3443.0 632.0 1824.0 65 52700.01 6758.0 3354.0 4563.0 1383.0 836.0 806.0 375 24689.0Erreur courante
Le piège de la silhouette
Symptôme : le regroupement ayant la meilleure silhouette (0,512) sépare 65 clients des 375 autres. Ces 65 clients dépensent beaucoup en aliments frais (médiane de 30 624 u.m.). L’épicerie, le lait et les détergents diffèrent à peine entre les deux groupes. La mise à l’échelle sans logarithme est encore « meilleure » :
from sklearn.preprocessing import FunctionTransformer, StandardScaler
scaled_only = StandardScaler().fit_transform(customers[SPEND])try_k(scaled_only, ks=[2])k 2: silhouette 0.567 sizes [391, 49]Une silhouette de 0,567, et les 49 clients sont les plus gros acheteurs de lait, d’épicerie et de détergents.
Cause : la silhouette récompense les groupes éloignés les uns des autres. Avec une longue traîne, il est facile de créer des groupes éloignés : les quelques géants d’un côté, tous les autres de l’autre. Le score est élevé parce que les géants sont loin de tous les autres, pas parce qu’il existe deux types d’acheteurs. Inês connaît déjà ses plus gros clients. Une séparation par taille ne lui indique pas quelle tournée convient à un client.
Correction : décidez d’abord ce que « semblable » doit signifier pour la décision. Ici, deux clients sont semblables s’ils achètent une combinaison semblable de produits, quelle que soit leur taille. Appliquez le logarithme avant la mise à l’échelle. Ainsi, un écart d’un facteur 10 compte autant dans chaque colonne et à chaque taille. Utilisez ensuite la silhouette seulement pour comparer des options qui ont toutes du sens. Lisez toujours les profils et les effectifs.
Conséquence en pratique : une équipe annonce « deux types de clients, silhouette de 0,57 », et les types sont « gros » et « les autres ». Le chiffre semble scientifique, et rien dans ce chiffre ne vous avertit.
Appliquer le logarithme et la mise à l’échelle
Section intitulée « Appliquer le logarithme et la mise à l’échelle »Reprenez la même recherche avec un logarithme et une mise à l’échelle dans un pipeline, comme dans l’étude de cas sur la boutique en ligne :
from sklearn.pipeline import make_pipeline
prepare = make_pipeline(FunctionTransformer(np.log1p), StandardScaler())X = prepare.fit_transform(customers[SPEND])print("skew after the log:", np.log1p(customers[SPEND]).skew().round(1).to_dict())try_k(X)Le résultat :
skew after the log: {'Fresh': -1.6, 'Milk': -0.2, 'Grocery': -0.7, 'Frozen': -0.4, 'Detergents_Paper': -0.2, 'Delicassen': -1.1}k 2: silhouette 0.290 sizes [252, 188]k 3: silhouette 0.259 sizes [212, 147, 81]k 4: silhouette 0.188 sizes [132, 129, 118, 61]k 5: silhouette 0.192 sizes [100, 100, 96, 90, 54]k 6: silhouette 0.193 sizes [113, 90, 84, 82, 51, 20]Ce que cela nous apprend. Les longues traînes à droite ont disparu. Le frais (−1,6) et l’épicerie fine (−1,1) penchent maintenant à gauche. Quelques valeurs minuscules sont devenues extrêmes sur l’échelle logarithmique. Les silhouettes sont beaucoup plus faibles (0,290 pour k = 2). Mais les effectifs des groupes pourraient maintenant correspondre à de vrais types : 252 et 188. Une silhouette plus faible ne signifie pas ici de moins bons groupes. Cela signifie que les vrais acheteurs se chevauchent et que la méthode ne triche plus avec les géants. k = 2 reste la meilleure option parmi celles-ci.
k = 2 est-il stable? Utilisez la vérification de stabilité de l’étude de cas sur la boutique en ligne : 20 exécutions sur des échantillons aléatoires de 80 % :
def stability(X, k, runs=20): """Mean agreement (ARI) between k-means runs fitted on different random 80% samples.""" rng = np.random.default_rng(0) labels = [] for run in range(runs): rows = rng.choice(len(X), int(0.8 * len(X)), replace=False) labels.append(KMeans(n_clusters=k, n_init=20, random_state=run).fit(X[rows]).predict(X)) return np.mean([adjusted_rand_score(a, b) for i, a in enumerate(labels) for b in labels[i + 1:]])
for k in range(2, 7): print(f"k {k}: stability {stability(X, k):.3f}")Le résultat :
k 2: stability 0.921k 3: stability 0.795k 4: stability 0.673k 5: stability 0.654k 6: stability 0.605Ce que cela nous apprend. Seul k = 2 est stable (0,921). À partir de k = 3, un autre échantillon de 80 % des clients donne des groupes sensiblement différents. 440 clients ne fournissent pas assez de données pour des types plus précis. Vous choisissez donc k = 2. Il offre la meilleure silhouette parmi les options sensées et c’est le seul résultat stable. Inês a aussi deux plans de livraison auxquels affecter les clients.
Les deux types d’acheteurs
Section intitulée « Les deux types d’acheteurs »Ajustez le pipeline avec k = 2 et nommez chaque groupe selon son profil. Le groupe dont les dépenses médianes d’épicerie sont les plus élevées est le type rayons :
segmenter = make_pipeline(FunctionTransformer(np.log1p), StandardScaler(), KMeans(n_clusters=2, n_init=20, random_state=0))customers["cluster"] = segmenter.fit_predict(customers[SPEND])shelf = customers.groupby("cluster")["Grocery"].median().idxmax()customers["buyer_type"] = np.where(customers["cluster"] == shelf, "shelf", "kitchen")profile = customers.groupby("buyer_type")[SPEND].median()profile["customers"] = customers["buyer_type"].value_counts()profileLe résultat :
Fresh Milk Grocery Frozen Detergents_Paper Delicassen customersbuyer_typekitchen 10347.5 1871.0 2406.0 2225.0 327.5 749.5 252shelf 5406.5 7690.5 11527.0 1061.5 4495.0 1388.5 188Ce que cela nous apprend. Inês lit les profils et approuve les noms. Les acheteurs de type cuisine (252) dépensent deux fois plus en aliments frais et surgelés que les autres. Ils dépensent peu en détergents et papier. Les acheteurs de type rayons (188) dépensent environ quatre fois plus en épicerie et en lait. Ils dépensent plus de dix fois plus en détergents et papier. Les deux profils ressemblent beaucoup aux médianes de Horeca et Retail. Le regroupement a trouvé les deux types d’entreprises sans voir l’étiquette.
Une image : la PCA
Section intitulée « Une image : la PCA »On ne peut pas représenter six colonnes sur une page. La PCA, dans L’exploration non supervisée, trouve les deux directions qui conservent le plus de dispersion. Avant de faire confiance à l’image, lisez ce que chaque direction signifie :
from sklearn.decomposition import PCA
pca = PCA(n_components=2).fit(X)print("share of the spread kept:", pca.explained_variance_ratio_.round(3))pd.DataFrame(pca.components_, index=["PC1", "PC2"], columns=SPEND).round(2)Le résultat :
share of the spread kept: [0.441 0.272] Fresh Milk Grocery Frozen Detergents_Paper DelicassenPC1 -0.11 0.54 0.57 -0.14 0.55 0.21PC2 0.59 0.13 -0.01 0.59 -0.07 0.53Ce que cela nous apprend. L’image conserve 44,1 % + 27,2 % = 71,3 % de la dispersion. C’est plus que dans l’exemple des clients de L’exploration non supervisée (57,5 %). Les deux directions ont des significations claires :
- PC1 représente les « produits des rayons » : elle augmente avec le lait (0,54), l’épicerie (0,57) et les détergents et papier (0,55). Un client loin à droite achète beaucoup de produits pour magasin.
- PC2 représente les « produits des cuisines » : elle augmente avec le frais (0,59), les surgelés (0,59) et l’épicerie fine (0,53). Un client situé en haut achète beaucoup d’aliments à cuisiner.
Le graphique montre les 440 clients dans ces deux directions. La couleur combine l’étiquette et le type d’acheteur.
- Horeca, acheteur de type cuisine (244)
- Retail, acheteur de type rayons (134)
- Horeca, achète comme un magasin (54)
- Retail, achète comme une cuisine (8)
Description textuelle du graphique
Un nuage de points représentant 440 clients. L’axe horizontal, PC1, augmente avec les dépenses en lait, en épicerie et en détergents et papier, d’environ −6 à 4. L’axe vertical, PC2, augmente avec les dépenses en frais, en surgelés et en épicerie fine, d’environ −5 à 4. Les acheteurs Horeca de type cuisine (244) occupent la moitié gauche, avec une PC1 moyenne de −1,18. Les acheteurs Retail de type rayons (134) occupent la partie droite, avec une PC1 moyenne de 1,81. Les 54 clients Horeca qui achètent comme des magasins se trouvent au milieu et à droite, mêlés aux magasins. Leur PC1 moyenne est de 0,87. Les 8 clients Retail qui achètent comme des cuisines se trouvent près du milieu, à gauche de la frontière. Quelques clients ayant des dépenses minuscules sont très éloignés, en bas et à gauche. Aucun espace vide ne sépare les deux types : un seul nuage s’étend de gauche à droite.
Ce que cela nous apprend. La principale différence entre les clients va de gauche à droite : la place des produits des rayons dans leurs achats. k-means a coupé le nuage en deux dans cette direction. Aucun espace vide ne sépare les types. Un client près de la frontière pourrait donc appartenir à l’un ou à l’autre. Les couleurs montrent où l’étiquette et le type ne concordent pas. Les 54 clients Horeca du côté des rayons constituent le groupe intéressant.
Comparer avec l’étiquette de canal
Section intitulée « Comparer avec l’étiquette de canal »Dévoilez maintenant l’étiquette que vous aviez gardée cachée. Dans quelle mesure les deux types d’acheteurs correspondent-ils à Horeca et Retail?
print(pd.crosstab(customers["buyer_type"], customers["channel"], margins=True))print("ARI with the channel:", round(adjusted_rand_score(customers["channel"], customers["buyer_type"]), 3))Le résultat :
channel Horeca Retail Allbuyer_typekitchen 244 8 252shelf 54 134 188All 298 142 440ARI with the channel: 0.514Ce que cela nous apprend. La concordance est réelle, mais incomplète. Parmi les 298 clients Horeca, 244 (81,9 %) achètent comme des cuisines. Parmi les 142 commerces, 134 (94,4 %) achètent comme des commerces. L’ARI est de 0,514 : bien au-dessus du hasard (0), mais loin d’une correspondance parfaite (1). À titre de comparaison, le regroupement des dépenses brutes avec la meilleure silhouette avait un ARI de −0,031 avec le canal. Il n’y avait aucune concordance. Ici, la discordance n’est pas un échec. L’étiquette indique la nature de l’entreprise, et le type indique comment elle achète. 62 clients (54 + 8) achètent autrement que leur étiquette le laisse penser. Ils méritent qu’on les examine.
Les clients qui achètent autrement que leur étiquette le laisse penser
Section intitulée « Les clients qui achètent autrement que leur étiquette le laisse penser »Combinez l’étiquette et le type, puis comparez les parts des dépenses :
customers["group"] = customers["channel"] + " / " + customers["buyer_type"]groups = pd.concat([customers["group"].value_counts().rename("customers"), customers[SPEND].sum(axis=1).groupby(customers["group"]).median().rename("total"), share.groupby(customers["group"]).median().round(2)], axis=1)groups.sort_values("customers", ascending=False)Le résultat :
customers total Fresh Milk Grocery Frozen Detergents_Paper DelicassengroupHoreca / kitchen 244 19636.5 0.53 0.08 0.12 0.12 0.02 0.04Retail / shelf 134 37293.0 0.12 0.23 0.36 0.03 0.16 0.03Horeca / shelf 54 27646.5 0.19 0.22 0.29 0.04 0.07 0.05Retail / kitchen 8 31763.5 0.46 0.12 0.18 0.13 0.03 0.03Ce que cela nous apprend.
- 54 clients Horeca achètent comme des commerces. Les aliments frais représentent seulement 19 % de leurs dépenses, contre 53 % pour un client Horeca typique. Les parts des produits d’épicerie (29 %) et du lait (22 %) sont proches de celles des commerces. Ces clients sont aussi plus importants que le client Horeca typique (une médiane de 27 647 u.m., contre 19 637). Inês a une hypothèse : « Des hôtels avec une boutique ou un minibar, et des cafés qui vendent des produits d’épicerie. » Ensemble, ils dépensent 2 070 985 u.m., soit 14,2 % de toutes les dépenses. Aujourd’hui, ils sont tous sur la tournée des cuisines.
- 8 commerces achètent comme des cuisines. Les aliments frais représentent 46 % de leurs dépenses. Ce sont peut-être des marchands de fruits et légumes ou des épiceries fines. Huit clients, c’est trop peu pour en dire plus.
Voilà le constat utile pour l’entreprise. L’étiquette n’est pas erronée : ces entreprises sont des hôtels, des restaurants et des cafés. Mais leurs besoins de livraison dépendent de ce qu’elles achètent.
Un sous-groupe par région
Section intitulée « Un sous-groupe par région »Le constat est-il le même dans chaque région? Comptez les groupes :
pd.crosstab(customers["group"], customers["region"], margins=True)Le résultat :
region Lisbon Oporto Other AllgroupHoreca / kitchen 46 26 172 244Horeca / shelf 13 2 39 54Retail / kitchen 2 2 4 8Retail / shelf 16 17 101 134All 77 47 316 440Ce que cela nous apprend. Les clients Horeca qui achètent comme des commerces sont présents dans chaque région. Ils sont 13 sur 59 à Lisbon (22 %), 39 sur 211 dans les autres régions (18 %) et 2 sur 28 à Oporto (7 %). Le chiffre d’Oporto repose sur 2 clients; il peut donc être dû au hasard. Avec ces effectifs, aucune région n’a besoin de son propre plan.
Dans quelle mesure le résultat dépend-il des choix?
Section intitulée « Dans quelle mesure le résultat dépend-il des choix? »Les groupes dépendent de vos choix : k, les colonnes et le traitement des clients extrêmes. Avec 440 clients, vérifiez chaque choix.
Le nombre de groupes
Section intitulée « Le nombre de groupes »Le canal est resté caché pendant que vous choisissiez k. Maintenant, vous pouvez examiner comment la concordance varie avec k :
for k in range(2, 7): labels = KMeans(n_clusters=k, n_init=20, random_state=0).fit_predict(X) print(f"k {k}: ARI with the channel {adjusted_rand_score(customers['channel'], labels):.3f} " f"Retail share per cluster {pd.Series(customers['Channel'] == 2).groupby(labels).mean().round(2).tolist()}")print(f"raw spend, k 2: ARI with the channel {adjusted_rand_score(customers['channel'], raw_labels):.3f}")Le résultat :
k 2: ARI with the channel 0.514 Retail share per cluster [0.03, 0.71]k 3: ARI with the channel 0.346 Retail share per cluster [0.59, 0.62, 0.01]k 4: ARI with the channel 0.280 Retail share per cluster [0.01, 0.66, 0.8, 0.05]k 5: ARI with the channel 0.212 Retail share per cluster [0.0, 0.06, 0.87, 0.7, 0.11]k 6: ARI with the channel 0.200 Retail share per cluster [0.18, 0.01, 0.87, 0.07, 0.15, 0.84]raw spend, k 2: ARI with the channel -0.031- Silhouette, dépenses brutes
- Silhouette, log + mise à l'échelle
- ARI avec le canal, données brutes
- ARI avec le canal, log + mise à l'échelle
Description textuelle du graphique
Quatre lignes selon k, de 2 à 6. La silhouette des dépenses brutes est la ligne la plus haute : 0,512 pour k = 2, puis 0,375 pour k = 6. La silhouette après le logarithme et la mise à l’échelle est plus basse : 0,290 pour k = 2, 0,259 pour k = 3, puis environ 0,19. L’ARI avec le canal sur les dépenses brutes est de −0,031 pour k = 2, 0,153 pour k = 3, 0,329 pour k = 4, 0,223 pour k = 5 et 0,183 pour k = 6. L’ARI après le logarithme et la mise à l’échelle est de 0,514 pour k = 2, puis diminue régulièrement : 0,346, 0,280, 0,212 et 0,200.
Ce que cela nous apprend. La meilleure silhouette et les groupes les plus pertinents mènent dans des directions opposées. Sur les dépenses brutes, la silhouette la plus élevée (k = 2) s’accompagne d’une absence totale de concordance (−0,031). Aucun regroupement des données brutes ne dépasse 0,329. Avec le logarithme, k = 2 concorde le mieux avec le canal (0,514). Un k plus grand ne révèle pas de nouveaux types d’entreprises. Chaque groupe supplémentaire contient encore soit surtout des commerces, soit presque aucun commerce. Il divise seulement les deux mêmes types en groupes plus petits.
Qu’ajoute le troisième groupe pour k = 3?
three = KMeans(n_clusters=3, n_init=20, random_state=0).fit_predict(X)k3_profile = customers.groupby(three)[SPEND].median()k3_profile["customers"] = np.bincount(three)k3_profile["Retail"] = pd.crosstab(three, customers["channel"])["Retail"]k3_profileLe résultat :
Fresh Milk Grocery Frozen Detergents_Paper Delicassen customers Retail0 1454.0 6264.0 10487.0 388.0 4196.0 436.0 81 481 12126.0 7184.0 9965.0 2005.0 3378.0 2005.0 147 912 9635.0 1598.5 2151.0 2151.0 274.5 686.0 212 3Ce que cela nous apprend. Les acheteurs de type cuisine restent presque inchangés (212 clients, dont seulement 3 commerces). Le type rayons se divise en deux. 81 clients n’achètent presque aucun aliment frais ou surgelé, et 147 grands clients achètent beaucoup de tout. C’est une division selon la taille et le comptoir de produits frais, pas un nouveau plan de livraison. Avec une stabilité de 0,795, elle est aussi moins fiable. Inês garde deux types.
Les clients extrêmes
Section intitulée « Les clients extrêmes »Le logarithme a rendu extrêmes les très petites dépenses. Combien de clients sont très éloignés des autres, et changent-ils les groupes?
extreme = (np.abs(X) > 3).any(axis=1)print(f"{extreme.sum()} customers have a value more than 3 SDs from the mean, after the log")print("their smallest yearly spend in one category:", sorted(customers.loc[extreme, SPEND].min(axis=1).tolist()))refit = make_pipeline(FunctionTransformer(np.log1p), StandardScaler(), KMeans(n_clusters=2, n_init=20, random_state=0))without = refit.fit_predict(customers.loc[~extreme, SPEND])print("agreement (ARI) with the clusters of all 440, on the other", (~extreme).sum(), "customers:", round(adjusted_rand_score(customers.loc[~extreme, "cluster"], without), 3))Le résultat :
18 customers have a value more than 3 SDs from the mean, after the logtheir smallest yearly spend in one category: [3, 3, 3, 3, 3, 3, 3, 3, 7, 7, 9, 11, 18, 23, 25, 37, 56, 239]agreement (ARI) with the clusters of all 440, on the other 422 customers: 0.972Ce que cela nous apprend. Après le logarithme, les clients extrêmes ne sont plus les plus grands. Ce sont ceux qui ne dépensent presque rien dans une catégorie : huit ont dépensé 3 u.m. en un an. Cette valeur peut vraiment signifier « n’en achète jamais », ou signaler un problème de données. Sans ces 18 clients, les 422 autres se retrouvent dans presque les mêmes groupes (ARI de 0,972). Le résultat ne dépend donc pas d’eux. Inês devrait quand même se renseigner sur les clients qui ont dépensé 3 u.m. Ils achètent peut-être ailleurs les produits d’une catégorie qu’ils n’achètent jamais chez vous.
À vous de jouer : une colonne de moins
Section intitulée « À vous de jouer : une colonne de moins »Les produits d’épicerie et les détergents et papiers varient ensemble (0,92). Ils comptent donc presque double. Changez une seule chose : faites le regroupement sur les cinq autres colonnes, sans Detergents_Paper. Avant de l’exécuter, faites une prédiction : la concordance avec le canal augmente-t-elle ou diminue-t-elle? Plus ou moins de clients Horeca deviennent-ils des acheteurs de type rayons? Le carnet Jupyter contient la tâche et une vérification.
Afficher la réponse
five = [c for c in SPEND if c != "Detergents_Paper"]variant = make_pipeline(FunctionTransformer(np.log1p), StandardScaler(), KMeans(n_clusters=2, n_init=20, random_state=0))labels = variant.fit_predict(customers[five])shelf_five = customers.groupby(labels)["Grocery"].median().idxmax()type_five = np.where(labels == shelf_five, "shelf", "kitchen")print("ARI with the channel:", round(adjusted_rand_score(customers["channel"], type_five), 3))horeca_shelf_five = int(((customers["channel"] == "Horeca") & (type_five == "shelf")).sum())print("Horeca customers of the shelf type:", horeca_shelf_five)ARI with the channel: 0.392Horeca customers of the shelf type: 72La concordance passe de 0,514 à 0,392, et 72 clients Horeca deviennent des acheteurs de type rayons, au lieu de 54. Les détergents et papiers sont le signe le plus clair d’un commerce. Les cuisines en achètent très peu (1,9 % des dépenses d’un client Horeca typique). Sans cette colonne, la frontière se déplace. La liste des « clients Horeca qui achètent comme des commerces » dépend donc des colonnes choisies. C’est une raison de plus de considérer les 54 clients comme une liste à vérifier, pas comme un fait établi. Les 54 clients de type rayons avec les six colonnes sont les cas les plus solides.
Le journal des expériences
Section intitulée « Le journal des expériences »Comme dans l’étude de cas de la boutique en ligne, le journal contient les colonnes du regroupement : silhouette, stabilité et résultat.
from datetime import date
log = pd.DataFrame([ ("E01", "Today's rule: the channel label", None, None, "Horeca 298, Retail 142", "baseline", "What the sales team uses now."), ("E02", "k-means on raw spend, k=2", 0.512, None, "sizes 375 / 65", "reject", "Splits off big Fresh buyers; not a buying pattern."), ("E03", "k-means, scaled only, k=2", 0.567, None, "sizes 391 / 49", "reject", "Splits off the largest customers."), ("E04", "k-means, log + scale, k=2", 0.290, 0.921, "kitchen 252 / shelf 188", "keep", "Two buying patterns a manager can use."), ("E05", "k-means, log + scale, k=3", 0.259, None, "adds 147 large all-round buyers", "reject", "A size split; no new delivery plan."), ("E06", "Check: buyer type against channel", None, None, "ARI 0.514; 54 Horeca buy like shops", "keep", "The finding for the sales team."),], columns=["id", "change", "silhouette", "stability", "result", "decision", "reason"])log.insert(1, "date", date.today().isoformat())log.to_csv("experiment_log.csv", index=False)log[["id", "change", "decision"]]Le résultat :
id change decision0 E01 Today's rule: the channel label baseline1 E02 k-means on raw spend, k=2 reject2 E03 k-means, scaled only, k=2 reject3 E04 k-means, log + scale, k=2 keep4 E05 k-means, log + scale, k=3 reject5 E06 Check: buyer type against channel keepCe que cela nous apprend. Les deux lignes rejetées ayant les silhouettes les plus élevées (E02 et E03) sont les plus utiles du journal. Elles expliquent pourquoi un meilleur score n’était pas la meilleure réponse. C’est exactement ce qu’une personne chargée de la révision voudra savoir.
Recommandations
Section intitulée « Recommandations »Voici la recommandation pour Inês, dans ses mots. Tous les chiffres viennent d’une seule année et de 440 clients. Les recommandations forment donc une liste à vérifier, pas une décision.
- Gardez deux plans de livraison, fondés sur les achats, pas seulement sur l’étiquette. Les deux types d’acheteurs correspondent aux deux tournées, et ils sont stables (0,921).
- Demandez aux 54 clients Horeca qui achètent comme des commerces si la tournée des rayons leur convient. Ils consacrent 19 % de leur budget aux aliments frais, contre 53 % pour un client Horeca typique. Ils représentent 14,2 % de toutes les dépenses. Un représentant des ventes devrait d’abord les appeler : certains pourraient avoir besoin des deux tournées.
- Demandez aux 8 commerces qui achètent comme des cuisines s’ils ont besoin de livraisons de produits frais plus fréquentes. Ils consacrent 46 % de leurs dépenses aux aliments frais.
- Ne prévoyez pas encore de types plus précis. Avec trois groupes ou plus, les résultats changent d’un échantillon à l’autre (stabilité de 0,795 ou moins). Les groupes supplémentaires divisent les clients selon leur taille.
- Examinez les clients qui ne dépensent presque rien dans une catégorie. Huit ont dépensé 3 u.m. en un an. Ils achètent peut-être ailleurs les produits d’une catégorie qu’ils n’achètent jamais chez vous.
Le carnet Jupyter écrit la liste pour l’équipe des ventes :
deliverable = customers[["channel", "region", *SPEND, "buyer_type"]].copy()deliverable["review"] = np.where(customers["group"].isin(["Horeca / shelf", "Retail / kitchen"]), "buys unlike its channel", "")deliverable.to_csv("buyer_types.csv", index_label="customer")print(deliverable["review"].value_counts().to_string())Le résultat :
review 378buys unlike its channel 62Ce que cela nous apprend. buyer_types.csv contient les 440 clients avec leur type, et 62 sont marqués comme étant à vérifier. La colonne de vérification est vide pour les 378 clients dont le type correspond à l’étiquette.
À quoi ne pas utiliser les types. Ne changez pas l’étiquette de canal d’un client à cause de son type. L’étiquette indique la nature de l’entreprise; le type indique comment elle achète. N’utilisez pas les types pour les prix ou le crédit. Ne changez pas la tournée d’un client sans lui demander.
Limites et prochaines étapes
Section intitulée « Limites et prochaines étapes »- Peu de données. 440 clients, une année. Les deux types sont stables; les types plus précis ne le sont pas. Une différence dans un sous-groupe de 8 ou 28 clients peut être due au hasard.
- Aucune information temporelle. Il n’y a ni dates ni nombre de commandes. La fréquence de livraison, ce qu’Inês planifie, n’est pas dans les données. Les types montrent ce que les clients achètent, pas à quelle fréquence ils en ont besoin.
- La taille et la composition des achats restent mêlées. Le logarithme réduit l’effet de la taille, mais les grands clients restent souvent des acheteurs de type rayons. Le regroupement selon les parts des dépenses est une autre option. Il concorde moins avec le canal (ARI de 0,295); voyez Pour aller plus loin.
- Les colonnes comptent. Sans les détergents et papiers, 72 clients Horeca deviennent des acheteurs de type rayons, au lieu de 54.
- L’étiquette est un fait d’âge inconnu. La source ne précise pas comment ni quand le canal a été enregistré.
Prochaines étapes : obtenez les dates des commandes pour mesurer à quelle fréquence chaque client commande. Répétez le regroupement sur les données de l’année suivante. Interrogez les 62 clients lors d’un court appel; leurs réponses sont la meilleure vérification des types.
Faites-en un projet pour votre portfolio
Section intitulée « Faites-en un projet pour votre portfolio »Cette étude de cas montre une compétence rare à un employeur. Vous savez reconnaître une métrique trompeuse et expliquer pourquoi vous ne l’avez pas suivie.
Quoi mettre dans le dépôt :
README.md, avec cinq courtes parties :- Problème : « Trouver des types d’acheteurs parmi les clients d’un grossiste alimentaire et les comparer à l’étiquette de canal existante. »
- Données et licence : Wholesale customers, UCI, CC BY 4.0, avec la référence.
- Approche : l’asymétrie, le piège de la silhouette (E02 et E03 du journal), logarithme + mise à l’échelle + k-moyennes, k = 2 choisi selon la silhouette et la stabilité, le graphique d’ACP avec le sens de chaque axe.
- Résultats avec la référence de base : le tableau croisé avec le canal (ARI de 0,514), les 54 clients Horeca qui achètent comme des commerces et leur profil.
- Limites : peu de données, aucune date, l’effet des colonnes.
- Le carnet Jupyter avec ses résultats,
experiment_log.csvetbuyer_types.csv. - Une courte note sur l’utilisation : à quoi servent les types (planification des livraisons, liste à vérifier) et à quoi ils ne servent pas (étiquettes, prix, crédit).
Comment le décrire en entrevue, en deux phrases : « J’ai regroupé 440 clients d’un grossiste selon leurs dépenses et montré que la meilleure silhouette séparait seulement les plus grands clients. Après le logarithme et la mise à l’échelle, deux types d’acheteurs stables correspondaient au canal existant pour 378 clients, et les 54 hôtels, restaurants et cafés qui achètent comme des commerces formaient une liste à vérifier par l’équipe des ventes. »
Pour aller plus loin :
- Faites le regroupement selon les parts des dépenses plutôt que les montants, et comparez la concordance avec le canal.
- Essayez un modèle de mélange gaussien, qui donne à chaque client une probabilité pour chaque type. Dressez la liste des clients proches de la frontière.
- Utilisez le canal comme étiquette pour un classificateur (régression logistique). Comparez les clients mal classés aux 62 de cette étude de cas.
Vérification des connaissances
Section intitulée « Vérification des connaissances »Vérifiez votre compréhension
Rui dit : le regroupement brut a une silhouette de 0,512, contre seulement 0,290 après logarithme et mise à l'échelle. Le regroupement brut est donc meilleur. Que répondez-vous?
Afficher la réponse
Une silhouette plus élevée signifie que les groupes sont plus éloignés, pas qu’ils sont plus utiles. Sur les dépenses brutes, les k-moyennes séparent 65 grands acheteurs d’aliments frais des 375 autres. La mise à l’échelle sans logarithme sépare les 49 plus grands clients. Les deux divisions reposent sur la taille, qu’Inês connaît déjà. La concordance de la division brute avec le canal est de −0,031, pas mieux que le hasard. Après le logarithme et la mise à l’échelle, les deux groupes représentent des habitudes d’achat (cuisine et rayons). Ils sont stables (0,921) et concordent avec le canal (ARI de 0,514). La silhouette compare les options seulement si elles mesurent toutes le type de similitude nécessaire à la décision.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Pourquoi avez-vous exclu l'étiquette de canal du regroupement, et qu'arriverait-il si vous l'y ajoutiez?
Afficher la réponse
La question était de savoir si les clients achètent comme leur étiquette le laisse penser. Si l’étiquette entre comme colonne, les groupes en copient une partie. La comparaison avec l’étiquette n’est alors plus une vérification équitable, car la méthode a vu la réponse. L’étiquette attirerait les 54 clients Horeca qui achètent comme des commerces vers le groupe Horeca. Ils pourraient disparaître de la liste. Garder l’étiquette cachée jusqu’à la fin fonctionne comme des données de test : le regroupement ne peut pas en avoir tiré d’apprentissage.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Inês demande : puis-je déplacer les 54 clients Horeca vers la tournée des rayons la semaine prochaine? Que dites-vous, et pourquoi?
Afficher la réponse
Pas tout de suite. Le résultat vient de 440 clients et d’une seule année, sans dates de commande. La liste dépend aussi des colonnes : sans les détergents et papiers, le nombre passe à 72. Les 54 sont de bons candidats avec un profil clair (19 % de frais, contre 53 % pour un client Horeca typique). La prochaine étape est donc un appel d’un représentant des ventes pour demander quelles livraisons leur sont nécessaires. Certains pourraient avoir besoin des deux tournées. Utilisez le regroupement pour trouver qui interroger, pas pour prendre la décision.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
Dans cette étude de cas, vous avez cherché des types d’acheteurs parmi 440 vrais clients d’un grossiste :
- Le piège de la silhouette : sur les dépenses brutes, la meilleure silhouette (0,512, et 0,567 avec la mise à l’échelle seule) isolait les plus grands clients. La division brute ne concordait pas du tout avec le canal (ARI de −0,031).
- Logarithme et mise à l’échelle : après le logarithme, deux types d’acheteurs sont apparus : cuisine (252) et rayons (188). Ils étaient le seul choix stable (0,921), et une silhouette plus basse (0,290) était le chiffre honnête.
- ACP avec un sens concret : PC1 représente les produits des rayons et PC2 les produits de cuisine. Le graphique conserve 71,3 % de la dispersion.
- L’étiquette cachée comme vérification : les types concordent avec Horeca et Retail (ARI de 0,514). 54 clients Horeca achètent comme des commerces; ils représentent 14,2 % de toutes les dépenses. Aujourd’hui, ils sont tous sur la tournée des cuisines.
- Conclusions provisoires : davantage de groupes étaient instables, et les clients extrêmes ne changeaient pas le résultat (0,972). Une colonne de moins faisait passer la liste de 54 à 72. Le résultat est donc une liste à vérifier, pas une décision.
Vous avez maintenant terminé les études de cas de ce cours. Vient ensuite le travail final : une nouvelle décision pour Larkfield. Vous utilisez tous les modules et remettez un classificateur qu’une autre personne peut exécuter et auquel elle peut se fier.
Leçon suivante : Travail final