Partie 3
Nettoyer, regrouper, joindre
Traiter les valeurs manquantes et les doublons, calculer des totaux, croiser deux tables.
Compter les valeurs manquantes
Les données réelles sont rarement propres. Ce code compte, pour chaque colonne, combien de valeurs sont manquantes (null). Pas besoin de tout comprendre : retenez juste que isNull() teste si une valeur manque.
ventes.select([F.sum(F.col(c).isNull().cast("int")).alias(c) for c in ventes.columns]).show()98 valeurs manquantes dans quantite (et donc dans montant), 48 dans mode_paiement.
Repérer les doublons
dropDuplicates() supprime les lignes identiques. Combien de lignes sont des doublons ? Calculez la différence entre le nombre de lignes avant et après dropDuplicates().
nb_doublons = ventes.count() - ...
print(nb_doublons)Besoin d'un indice ?
ventes.dropDuplicates().count()
Voir la solution
nb_doublons = ventes.count() - ventes.dropDuplicates().count()
print(nb_doublons)25
Créer une table propre
Créez ventes_propres en enchaînant :
dropDuplicates()pour retirer les doublons ;fillna(...)pour remplacer unequantitemanquante par1et unmode_paiementmanquant par"Inconnu";- un nouveau calcul de
montant(il était vide là où la quantité manquait).
ventes_propres = (ventes
.dropDuplicates()
.fillna(...)
.withColumn("montant", ...))
print(ventes_propres.count())Besoin d'un indice ?
fillna({"quantite": 1, "mode_paiement": "Inconnu"}), puis le même calcul de montant qu'à la partie 2.
Voir la solution
ventes_propres = (ventes
.dropDuplicates()
.fillna({"quantite": 1, "mode_paiement": "Inconnu"})
.withColumn("montant", F.round(F.col("quantite") * F.col("prix_unitaire"), 2)))
print(ventes_propres.count())5000
Garder la table en mémoire
Nous allons réutiliser ventes_propres très souvent. cache() demande à Spark de la garder en mémoire après le premier calcul, pour ne pas relire le fichier à chaque fois. Le count() déclenche ce premier calcul.
ventes_propres.cache()
ventes_propres.count()5000
Regrouper : groupBy et agg
groupBy("colonne") rassemble les lignes qui ont la même valeur dans cette colonne. agg(...) calcule ensuite quelque chose pour chaque groupe : F.count, F.sum, F.avg, F.min, F.max… alias("nom") donne un nom lisible au résultat.
Ici : le nombre de ventes et la quantité totale vendue dans chaque catégorie.
(ventes_propres
.groupBy("categorie")
.agg(F.count("*").alias("nb_ventes"),
F.sum("quantite").alias("quantite_totale"))
.orderBy(F.desc("nb_ventes"))
.show())5 lignes, une par catégorie. Livres arrive en tête avec 1060 ventes.
Le chiffre d'affaires par catégorie
Le chiffre d'affaires (CA) est la somme des montants. Calculez le CA de chaque catégorie, arrondi à 2 décimales, du plus grand au plus petit.
ca_categorie = (ventes_propres
.groupBy(...)
.agg(...)
.orderBy(...))
ca_categorie.show()Besoin d'un indice ?
F.round(F.sum("montant"), 2).alias("ca"), puis orderBy(F.desc("ca")).
Voir la solution
ca_categorie = (ventes_propres
.groupBy("categorie")
.agg(F.round(F.sum("montant"), 2).alias("ca"))
.orderBy(F.desc("ca")))
ca_categorie.show()Mobilier en tête avec 223015.0
Plusieurs calculs d'un coup
Pour chaque catégorie, calculez dans le même agg : le nombre de ventes, le CA et le panier moyen (la moyenne des montants), arrondis à 2 décimales.
(ventes_propres
.groupBy("categorie")
.agg(..., ..., ...)
.show())Besoin d'un indice ?
F.count("*"), F.sum("montant") et F.avg("montant"), chacun avec son alias.
Voir la solution
(ventes_propres
.groupBy("categorie")
.agg(F.count("*").alias("nb_ventes"),
F.round(F.sum("montant"), 2).alias("ca"),
F.round(F.avg("montant"), 2).alias("panier_moyen"))
.orderBy(F.desc("ca"))
.show())Mobilier : 945 ventes, CA 223015.0, panier moyen 235.99
Le chiffre d'affaires par mois
Calculez le CA de chaque mois, trié par mois (de 1 à 12). Quel est le meilleur mois de l'année ?
ca_mois = ...
ca_mois.show(12)Besoin d'un indice ?
Même principe que le CA par catégorie, avec groupBy("mois") et orderBy("mois").
Voir la solution
ca_mois = (ventes_propres
.groupBy("mois")
.agg(F.round(F.sum("montant"), 2).alias("ca"))
.orderBy("mois"))
ca_mois.show(12)Décembre (mois 12) est le meilleur mois, avec 68788.6
Charger les clients
Deuxième fichier : les clients. Chaque vente contient un id_client qui renvoie vers une ligne de ce fichier.
clients = spark.read.csv("data/clients.csv", header=True, inferSchema=True)
clients.show(5)Un tableau avec les colonnes id_client, prenom, age, ville, date_inscription.
Joindre deux tables : join
Une jointure colle deux tables côte à côte en se servant d'une colonne commune, ici id_client. Chaque vente récupère ainsi le prénom, l'âge et la ville de son client.
Type (how=) | Garde… |
|---|---|
"inner" | seulement les lignes présentes des deux côtés |
"left" | toutes les lignes de gauche, avec des vides s'il n'y a pas de correspondance |
"left_anti" | les lignes de gauche sans correspondance à droite |
ventes_clients = ventes_propres.join(clients, on="id_client", how="inner")
ventes_clients.select("id_vente", "produit", "montant", "prenom", "ville").show(5)Des ventes avec, en plus, le prénom et la ville du client.
Le chiffre d'affaires par ville
En partant de ventes_clients, calculez le CA de chaque ville, du plus grand au plus petit.
ca_ville = ...
ca_ville.show()Besoin d'un indice ?
groupBy("ville"), puis la même agrégation que pour les catégories.
Voir la solution
ca_ville = (ventes_clients
.groupBy("ville")
.agg(F.round(F.sum("montant"), 2).alias("ca"))
.orderBy(F.desc("ca")))
ca_ville.show()Bordeaux en tête avec 68730.97
Les ventes sans client
Comparez ventes_propres.count() et ventes_clients.count() : des ventes ont disparu ! Ce sont des ventes dont le client n'existe pas dans le fichier clients. Retrouvez-les avec une jointure "left_anti" et comptez-les.
ventes_orphelines = ventes_propres.join(clients, on="id_client", how=...)
print(ventes_orphelines.count())Besoin d'un indice ?
how="left_anti"
Voir la solution
ventes_orphelines = ventes_propres.join(clients, on="id_client", how="left_anti")
print(ventes_orphelines.count())46
Par tranche d'âge
Calculez le CA et le panier moyen par tranche d'âge : 18-29, 30-44, 45-59 et 60+. Quelle tranche dépense le plus au total ?
tranches = ventes_clients.withColumn("tranche_age", ...)
# puis un groupBy("tranche_age") ...Besoin d'un indice ?
Réutilisez F.when(...).when(...).otherwise(...) comme pour la taille du panier.
Voir la solution
tranches = ventes_clients.withColumn(
"tranche_age",
F.when(F.col("age") < 30, "18-29")
.when(F.col("age") < 45, "30-44")
.when(F.col("age") < 60, "45-59")
.otherwise("60+"))
(tranches
.groupBy("tranche_age")
.agg(F.round(F.sum("montant"), 2).alias("ca"),
F.round(F.avg("montant"), 2).alias("panier_moyen"))
.orderBy("tranche_age")
.show())Les 60+ dépensent le plus au total (142412.04).
Astuce : les flèches ← → du clavier permettent aussi de naviguer.