Partie 2
Les DataFrames
Charger un fichier CSV, l'explorer, filtrer et créer des colonnes.
C'est quoi un DataFrame ?
Un DataFrame est une table distribuée : des lignes et des colonnes, chaque colonne ayant un nom et un type (entier, texte, date…). L'ensemble des noms et types s'appelle le schéma.
C'est l'outil que l'on utilise le plus avec Spark aujourd'hui : plus simple que les RDD, et optimisé automatiquement. Si vous connaissez pandas ou SQL, vous serez en terrain connu.
Dans la suite, on désigne une colonne avec F.col("nom_de_la_colonne").
Charger les ventes
On lit le fichier CSV des ventes.
header=True: la première ligne contient les noms des colonnes ;inferSchema=True: Spark devine le type de chaque colonne (sans cela, tout serait du texte).
printSchema() affiche le nom et le type de chaque colonne.
ventes = spark.read.csv("data/ventes.csv", header=True, inferSchema=True)
ventes.printSchema()root |-- id_vente: integer (nullable = true) |-- date_vente: date (nullable = true) |-- id_client: integer (nullable = true) |-- produit: string (nullable = true) |-- categorie: string (nullable = true) |-- quantite: integer (nullable = true) |-- prix_unitaire: double (nullable = true) |-- mode_paiement: string (nullable = true)
Regarder les premières lignes
show(5) affiche les 5 premières lignes sous forme de tableau, et count() compte les lignes.
ventes.show(5)
print("Nombre de ventes :", ventes.count())Un tableau de 5 lignes, puis : Nombre de ventes : 5025
Quelques statistiques
describe calcule rapidement le nombre de valeurs, la moyenne, l'écart-type, le minimum et le maximum des colonnes demandées.
Regardez la ligne count : la colonne quantite a moins de valeurs que prix_unitaire. Il y a donc des valeurs manquantes. Nous y reviendrons dans la partie 3.
ventes.describe("quantite", "prix_unitaire").show()count : 4927 pour quantite, 5025 pour prix_unitaire
Choisir des colonnes : select
Affichez seulement les colonnes produit, quantite et prix_unitaire, pour les 10 premières lignes.
ventes.select(...).show(10)Besoin d'un indice ?
select prend les noms des colonnes, séparés par des virgules.
Voir la solution
ventes.select("produit", "quantite", "prix_unitaire").show(10)Un tableau de 10 lignes avec 3 colonnes.
Filtrer des lignes : filter
Combien y a-t-il de ventes dans la catégorie Sport ?
nb_sport = ventes.filter(...).count()
print(nb_sport)Besoin d'un indice ?
La condition s'écrit F.col("categorie") == "Sport".
Voir la solution
nb_sport = ventes.filter(F.col("categorie") == "Sport").count()
print(nb_sport)1011
Combiner deux conditions
Trouvez les ventes où la quantité est d'au moins 3 et où le client a payé par Carte. Combien y en a-t-il ?
Pour combiner des conditions : & veut dire « et », | veut dire « ou ». Chaque condition doit être entre parenthèses.
grosses_cartes = ventes.filter((...) & (...))
print(grosses_cartes.count())Besoin d'un indice ?
(F.col("quantite") >= 3) & (F.col("mode_paiement") == "Carte")
Voir la solution
grosses_cartes = ventes.filter((F.col("quantite") >= 3) & (F.col("mode_paiement") == "Carte"))
print(grosses_cartes.count())615
Créer une colonne : withColumn
withColumn("nom", expression) ajoute une colonne (ou la remplace si elle existe déjà). Ici, on s'assure que date_vente est bien une date, puis on en extrait le mois.
Important : un DataFrame ne se modifie jamais. withColumn renvoie un nouveau DataFrame : on le range à nouveau dans la variable ventes.
ventes = (ventes
.withColumn("date_vente", F.to_date("date_vente"))
.withColumn("mois", F.month("date_vente")))
ventes.select("id_vente", "date_vente", "mois").show(5)Un tableau avec une nouvelle colonne mois (de 1 à 12).
Calculer le montant de chaque vente
Ajoutez à ventes une colonne montant égale à quantite × prix_unitaire, arrondie à 2 décimales.
Cette colonne servira jusqu'à la fin du TP : pensez bien à écrire ventes = ....
ventes = ventes.withColumn("montant", ...)
ventes.select("produit", "quantite", "prix_unitaire", "montant").show(5)Besoin d'un indice ?
F.round(F.col("quantite") * F.col("prix_unitaire"), 2)
Voir la solution
ventes = ventes.withColumn("montant", F.round(F.col("quantite") * F.col("prix_unitaire"), 2))
ventes.select("produit", "quantite", "prix_unitaire", "montant").show(5)Par exemple : Livre de cuisine | 2 | 25.0 | 50.0
Trier : orderBy
Affichez les 5 ventes les plus chères (le plus grand montant en premier).
ventes.orderBy(...).show(5)Besoin d'un indice ?
F.desc("montant") trie dans l'ordre décroissant.
Voir la solution
ventes.orderBy(F.desc("montant")).show(5)Le montant le plus élevé est 995.0 (5 bureaux à 199 €).
Les valeurs distinctes
Affichez la liste des catégories différentes, puis celle des modes de paiement différents. Que remarquez-vous dans les modes de paiement ?
ventes.select(...).distinct().show()
ventes.select(...).distinct().show()Besoin d'un indice ?
Mettez "categorie" dans le premier select, "mode_paiement" dans le second.
Voir la solution
ventes.select("categorie").distinct().show()
ventes.select("mode_paiement").distinct().show()5 catégories. Pour les paiements : Carte, PayPal, Virement… et NULL, c'est-à-dire des valeurs manquantes.
Petit, moyen ou gros panier
Créez une colonne taille_panier qui vaut "petit" si le montant est inférieur à 30, "moyen" s'il est inférieur à 100, et "gros" sinon. Comptez ensuite les ventes de chaque taille.
paniers = ventes.withColumn("taille_panier", ...)
paniers.groupBy("taille_panier").count().show()Besoin d'un indice ?
F.when(condition, valeur).when(condition, valeur).otherwise(valeur)
Voir la solution
paniers = ventes.withColumn(
"taille_panier",
F.when(F.col("montant") < 30, "petit")
.when(F.col("montant") < 100, "moyen")
.otherwise("gros"))
paniers.groupBy("taille_panier").count().show()gros : 1243, moyen : 2097, petit : 1685
Astuce : les flèches ← → du clavier permettent aussi de naviguer.