Archives Wild Code School
Python

Validation de données avec Pydantic

Quête 63 sur 66

Validation de données avec Pydantic

Attention

As-tu bien terminé ces quêtes ?

Quêtes liées

Introduction

Pydantic est une bibliothèque Python puissante pour la validation des données, particulièrement utile pour les data analysts. Elle permet de définir la structure des données attendues et effectue automatiquement la validation et la conversion des types.

Objectifs

✅ Comprendre l'importance de la validation des données en analyse de données ✅ Maîtriser les bases de Pydantic pour la création de modèles de données ✅ Utiliser les types de données Pydantic pour une validation précise ✅ Implémenter des validateurs personnalisés pour des règles métier spécifiques ✅ Gérer les données manquantes et les valeurs par défaut ✅ Utiliser Pydantic avec des données imbriquées et des listes ✅ Intégrer Pydantic avec Pandas pour une validation efficace des DataFrames ✅ Appliquer Pydantic dans des scénarios réels d'analyse de données

Sommaire

1. Introduction à Pydantic et à la validation des données

Exemple simple

python
from pydantic import BaseModel
from datetime import date

class Vente(BaseModel):
    id: int
    date: date
    montant: float
    produit: str

# Utilisation
vente = Vente(id=1, date="2023-08-21", montant="100.50", produit="Laptop")
print(vente)
# Sortie: id=1 date=datetime.date(2023, 8, 21) montant=100.5 produit='Laptop'

Explications détaillées :

  1. Importation : Nous importons BaseModel de Pydantic, qui est la classe de base pour tous nos modèles de données.

  2. Définition du modèle : La classe Vente hérite de BaseModel et définit la structure de nos données de vente.

    • id: int spécifie que l'ID doit être un entier.
    • date: date indique que nous attendons une date.
    • montant: float précise que le montant doit être un nombre à virgule flottante.
    • produit: str définit le produit comme une chaîne de caractères.
  3. Création d'une instance : Nous créons une instance de Vente en passant des données.

    • Notez que nous passons "2023-08-21" comme une chaîne pour la date et "100.50" comme une chaîne pour le montant.
  4. Conversion automatique : Pydantic effectue automatiquement les conversions nécessaires :

    • La chaîne "2023-08-21" est convertie en objet date.
    • La chaîne "100.50" est convertie en float.
  5. Validation implicite : Si nous avions fourni des données invalides (par exemple, une date incorrecte ou un montant non numérique), Pydantic aurait levé une exception.

Cette approche offre plusieurs avantages pour un data analyst :

  • Fiabilité des données : Vous êtes assuré que les données correspondent au format attendu.
  • Détection précoce des erreurs : Les problèmes de format ou de type sont immédiatement identifiés.
  • Facilité d'utilisation : Les conversions de types sont gérées automatiquement, vous permettant de vous concentrer sur l'analyse plutôt que sur le nettoyage des données.

2. Les bases de Pydantic : création de modèles simples

Créons des modèles Pydantic plus détaillés pour des données courantes en analyse.

Modèle de client

python
from pydantic import BaseModel, EmailStr, Field
from typing import Optional
from datetime import date

class Client(BaseModel):
    id: int
    nom: str = Field(..., min_length=2, max_length=100)
    email: EmailStr
    date_inscription: date
    age: Optional[int] = Field(None, ge=18, le=120)
    ville: str
    pays: str = Field(..., max_length=50)

# Exemple d'utilisation
client_data = {
    "id": 1,
    "nom": "Alice Dupont",
    "email": "alice.dupont@email.com",
    "date_inscription": "2023-01-15",
    "age": 30,
    "ville": "Paris",
    "pays": "France"
}

client = Client(**client_data)
print(client)

Explications détaillées :

  1. Importations avancées :

    • EmailStr de Pydantic pour valider les adresses email.
    • Field pour ajouter des contraintes supplémentaires aux champs.
    • Optional de typing pour indiquer qu'un champ peut être None.
  2. Définition du modèle Client :

    • id: int : Un identifiant unique pour chaque client.
    • nom: str = Field(..., min_length=2, max_length=100) :
      • ... signifie que le champ est obligatoire.
      • min_length=2, max_length=100 impose des contraintes sur la longueur du nom.
    • email: EmailStr : Utilise le type spécial EmailStr pour valider automatiquement le format de l'email.
    • date_inscription: date : Pydantic convertira automatiquement les chaînes de date en objets date.
    • age: Optional[int] = Field(None, ge=18, le=120) :
      • Optional[int] indique que l'âge peut être un entier ou None.
      • Field(None, ge=18, le=120) spécifie que si un âge est fourni, il doit être entre 18 et 120 ans.
    • ville: str : La ville du client, sans contraintes particulières.
    • pays: str = Field(..., max_length=50) : Le pays du client, limité à 50 caractères.
  3. Utilisation du modèle :

    • Nous créons un dictionnaire client_data avec des données de test.
    • Client(**client_data) crée une instance de Client en déballant le dictionnaire.
    • Pydantic valide automatiquement toutes les données et effectue les conversions nécessaires.

Avantages pour un data analyst :

  • Validation structurelle : Assure que toutes les données nécessaires sont présentes et du bon type.
  • Contraintes de domaine : Par exemple, la limitation de l'âge aide à détecter les erreurs de saisie évidentes.
  • Normalisation automatique : Par exemple, la validation de l'email peut inclure une normalisation (comme la mise en minuscules).

Modèle de produit

python
from pydantic import BaseModel, Field
from typing import List

class Produit(BaseModel):
    id: int
    nom: str = Field(..., min_length=3, max_length=100)
    prix: float = Field(..., gt=0)
    description: str = Field("", max_length=1000)
    categories: List[str] = Field(..., min_items=1, max_items=5)

# Exemple d'utilisation
produit_data = {
    "id": 1,
    "nom": "Smartphone XYZ",
    "prix": 599.99,
    "description": "Un smartphone haut de gamme avec de nombreuses fonctionnalités.",
    "categories": ["Électronique", "Téléphonie"]
}

produit = Produit(**produit_data)
print(produit)

Explications détaillées :

  1. Définition du modèle Produit :

    • id: int : Un identifiant unique pour chaque produit.
    • nom: str = Field(..., min_length=3, max_length=100) :
      • Impose une longueur minimale de 3 et maximale de 100 caractères pour le nom du produit.
    • prix: float = Field(..., gt=0) :
      • gt=0 signifie "greater than 0", assurant que le prix est toujours positif.
    • description: str = Field("", max_length=1000) :
      • La description est optionnelle (chaîne vide par défaut) et limitée à 1000 caractères.
    • categories: List[str] = Field(..., min_items=1, max_items=5) :
      • Utilise List[str] pour définir une liste de chaînes.
      • Impose au moins une catégorie et au maximum 5.
  2. Utilisation du modèle :

    • Nous créons un dictionnaire produit_data avec des données de test.
    • Produit(**produit_data) crée une instance de Produit en validant les données.

Avantages pour un data analyst :

  • Cohérence des données : Assure que les produits ont toujours un prix positif et au moins une catégorie.
  • Prévention des erreurs : La limitation de la longueur des champs peut prévenir les problèmes de stockage ou d'affichage.
  • Flexibilité contrôlée : Permet des champs optionnels (comme la description) tout en imposant des contraintes lorsqu'ils sont fournis.

Ces modèles illustrent comment Pydantic peut être utilisé pour définir des structures de données avec des règles de validation intégrées, ce qui est particulièrement utile pour garantir la qualité et la cohérence des données avant l'analyse.

3. Types de données avancés et validation personnalisée

Pydantic offre des fonctionnalités avancées pour une validation plus complexe. Voyons comment implémenter des validations personnalisées et des contraintes plus spécifiques.

Utilisation de validateurs personnalisés

python
from pydantic import BaseModel, field_validator
import re

class ProduitAvance(BaseModel):
    id: str
    nom: str
    prix: float
    code_barre: str

    @field_validator('id')
    @classmethod
    def id_valide(cls, v: str) -> str:
        if not re.match(r'^PROD\d{6}$', v):
            raise ValueError('L\'ID doit être au format PROD suivi de 6 chiffres')
        return v

    @field_validator('prix')
    @classmethod
    def prix_valide(cls, v: float) -> float:
        if v <= 0:
            raise ValueError('Le prix doit être positif')
        return round(v, 2)  # Arrondir à deux décimales

    @field_validator('code_barre')
    @classmethod
    def code_barre_valide(cls, v: str) -> str:
        if not re.match(r'^\d{13}$', v):
            raise ValueError('Le code-barres doit contenir exactement 13 chiffres')
        return v

# Exemple d'utilisation
produit_data = {
    "id": "PROD000123",
    "nom": "Téléviseur LED 55 pouces",
    "prix": 699.995,
    "code_barre": "1234567890123"
}

produit = ProduitAvance.model_validate(produit_data)
print(produit)

Explications détaillées :

  1. Importation de field_validator :

    • field_validator est un décorateur de Pydantic v2 utilisé pour créer des fonctions de validation personnalisées. On l'utilise toujours avec le décorateur classmethod.
  2. Définition du modèle ProduitAvance :

    • Les champs sont définis de manière similaire aux exemples précédents.
  3. Validateurs personnalisés :

    • @field_validator('id') :
      • Utilise une expression régulière pour s'assurer que l'ID commence par 'PROD' suivi de 6 chiffres.
      • Lève une ValueError si le format est incorrect.
    • @field_validator('prix') :
      • Vérifie que le prix est positif.
      • Arrondit le prix à deux décimales.
    • @field_validator('code_barre') :
      • Vérifie que le code-barres contient exactement 13 chiffres.
  4. Fonctionnement des validateurs :

    • Chaque validateur prend la valeur du champ (v) comme argument.
    • cls est la classe du modèle (rarement utilisé mais requis par Pydantic).
    • Les validateurs peuvent modifier la valeur avant de la retourner.
    • Pydantic v2 utilise des annotations de type pour une meilleure clarté et performance.
  5. Utilisation du modèle :

    • Nous utilisons ProduitAvance.model_validate(produit_data) pour créer une instance validée du modèle.
    • Si une validation échoue, une ValidationError est levée avec le message d'erreur spécifié.

Avantages pour un data analyst :

  • Validation spécifique au domaine : Permet d'implémenter des règles de validation complexes spécifiques à votre domaine d'analyse.
  • Nettoyage automatique des données : Par exemple, l'arrondi automatique du prix.
  • Détection précoce des erreurs : Les erreurs de format ou de logique métier sont détectées immédiatement lors de la création de l'objet.

Validation dépendante des champs

python
from pydantic import BaseModel, field_validator, model_validator
from typing import Optional

class Commande(BaseModel):
    id: str
    produit: str
    quantite: int
    prix_unitaire: float
    reduction: Optional[float] = None
    prix_total: float

    @model_validator(mode='after')
    def calculer_prix_total(self) -> 'Commande':
        prix_calcule = self.quantite * self.prix_unitaire * (1 - self.reduction/100 if self.reduction else 1)
        prix_calcule = round(prix_calcule, 2)
        if abs(self.prix_total - prix_calcule) > 0.01:  # Tolérance pour les erreurs d'arrondi
            raise ValueError(f'Le prix total {self.prix_total} ne correspond pas au calcul {prix_calcule}')
        self.prix_total = prix_calcule
        return self

# Exemple d'utilisation
commande_data = {
    "id": "CMD001",
    "produit": "Smartphone XYZ",
    "quantite": 2,
    "prix_unitaire": 599.99,
    "reduction": 10,
    "prix_total": 1079.98
}

commande = Commande.model_validate(commande_data)
print(commande)

Explications détaillées :

  1. Structure du modèle Commande

    • id, produit, quantite, prix_unitaire : Champs de base d'une commande.
    • reduction : Champ optionnel (peut être None) pour une éventuelle réduction en pourcentage.
    • prix_total : Le prix total de la commande, qui sera validé.
  2. Validateur calculer_prix_total

    • Ce validateur utilise @model_validator(mode='after'), une nouveauté de Pydantic v2.
    • Il s'exécute après que tous les champs ont été validés individuellement.
    • Le mode 'after' permet d'accéder directement aux attributs de l'instance via self.
  3. Logique du validateur

    • Calcule le prix total en utilisant directement les attributs de l'instance.
    • Arrondit le résultat à deux décimales pour éviter les problèmes de précision des nombres flottants.
    • Compare le prix calculé avec la valeur fournie, en utilisant une petite tolérance (0.01) pour gérer les erreurs d'arrondi.
  4. Gestion des erreurs

    • Si le prix total fourni ne correspond pas au calcul, une ValueError est levée avec un message explicatif.
  5. Utilisation

    • Nous utilisons Commande.model_validate(commande_data) pour créer et valider l'instance.
    • Le validateur de modèle s'exécute après la validation individuelle des champs.

Avantages pour un data analyst :

  • Cohérence des données : Assure que le prix total correspond toujours aux détails de la commande.
  • Détection d'anomalies : Permet de repérer facilement les incohérences dans les données de commande.
  • Automatisation des calculs : Le prix total est automatiquement recalculé et vérifié, réduisant les risques d'erreurs manuelles.
  • Compatibilité avec Pydantic v2 : Profite des améliorations de performance et des nouvelles fonctionnalités de Pydantic v2, comme le validateur de modèle avec mode 'after'.

4. Gestion des données manquantes et des valeurs par défaut

Dans l'analyse de données, il est courant de rencontrer des données manquantes ou incomplètes. Pydantic offre plusieurs façons de gérer ces cas.

python
from pydantic import BaseModel, Field
from typing import Optional

class DonneesSondage(BaseModel):
    id: int
    question: str
    reponse: Optional[str] = None
    score: float = Field(default=0.0, ge=0.0, le=10.0)
    commentaire: str = ""

# Exemples d'utilisation
sondage_complet = DonneesSondage(id=1, question="Êtes-vous satisfait ?", reponse="Oui", score=8.5)
sondage_partiel = DonneesSondage(id=2, question="Recommanderiez-vous notre service ?")

print(sondage_complet)
print(sondage_partiel)

Explications détaillées :

  1. Structure du modèle DonneesSondage :

    • id et question sont des champs obligatoires.
    • reponse est un champ optionnel qui peut être None.
    • score a une valeur par défaut de 0.0 et doit être entre 0 et 10.
    • commentaire a une chaîne vide comme valeur par défaut.
  2. Utilisation de Optional :

    • Optional[str] pour reponse indique que ce champ peut être une chaîne ou None.
    • Cela permet de représenter des questions sans réponse.
  3. Utilisation de Field :

    • Field(default=0.0, ge=0.0, le=10.0) pour score :
      • default=0.0 définit la valeur par défaut.
      • ge=0.0 signifie "greater than or equal to 0.0".
      • le=10.0 signifie "less than or equal to 10.0".
    • Cela garantit que le score est toujours dans la plage 0-10, même s'il n'est pas fourni.
  4. Valeur par défaut simple :

    • commentaire: str = "" utilise une simple valeur par défaut sans contraintes supplémentaires.
  5. Exemples d'utilisation :

    • sondage_complet montre un cas où toutes les données sont fournies.
    • sondage_partiel montre comment Pydantic gère les champs manquants en utilisant les valeurs par défaut.

Avantages pour un data analyst :

  • Flexibilité : Permet de travailler avec des données incomplètes sans erreurs.
  • Valeurs par défaut sensées : Aide à maintenir la cohérence des données même lorsque certaines informations sont manquantes.
  • Contraintes sur les valeurs par défaut : Assure que même les valeurs par défaut respectent les règles métier (comme la plage de score).

Cette approche est particulièrement utile lors de l'analyse de données de sondages ou d'enquêtes, où les réponses peuvent souvent être incomplètes. Elle permet de créer des modèles qui reflètent fidèlement la nature des données tout en maintenant une structure cohérente pour l'analyse.

5. Modèles imbriqués et listes

Dans de nombreux cas d'analyse de données, nous devons travailler avec des structures de données complexes, impliquant des modèles imbriqués et des listes. Pydantic gère élégamment ces cas.

python
from pydantic import BaseModel
from typing import List, Dict

class Adresse(BaseModel):
    rue: str
    ville: str
    code_postal: str

class HistoriqueAchat(BaseModel):
    date: str
    montant: float
    produits: List[str]

class ClientComplet(BaseModel):
    id: int
    nom: str
    adresses: List[Adresse]
    historique_achats: List[HistoriqueAchat]
    preferences: Dict[str, float]

# Exemple d'utilisation
client_data = {
    "id": 3,
    "nom": "Charlie Brown",
    "adresses": [
        {"rue": "123 Rue Principale", "ville": "Paris", "code_postal": "75001"},
        {"rue": "456 Avenue Secondaire", "ville": "Lyon", "code_postal": "69001"}
    ],
    "historique_achats": [
        {"date": "2023-03-15", "montant": 150.75, "produits": ["Livre", "DVD"]},
        {"date": "2023-04-01", "montant": 89.99, "produits": ["T-shirt", "Chaussettes"]}
    ],
    "preferences": {"Électronique": 0.8, "Livres": 0.6, "Vêtements": 0.4}
}

client_complet = ClientComplet(**client_data)
print(client_complet)

Explications détaillées :

  1. Modèles imbriqués :

    • Adresse est un modèle simple pour représenter une adresse.
    • HistoriqueAchat représente un achat individuel avec une liste de produits.
  2. Structure du modèle ClientComplet :

    • id et nom sont des champs simples.
    • adresses est une liste d'objets Adresse.
    • historique_achats est une liste d'objets HistoriqueAchat.
    • preferences est un dictionnaire avec des clés string et des valeurs float.
  3. Utilisation de List et Dict :

    • List[Adresse] indique une liste d'objets Adresse.
    • List[HistoriqueAchat] pour une liste d'objets HistoriqueAchat.
    • Dict[str, float] pour un dictionnaire avec des clés string et des valeurs float.
  4. Validation imbriquée :

    • Pydantic valide récursivement toutes les structures imbriquées.
    • Chaque Adresse et HistoriqueAchat dans les listes est validé individuellement.
  5. Exemple d'utilisation :

    • Le dictionnaire client_data contient des données complexes et imbriquées.
    • Lors de la création de ClientComplet, Pydantic :
      • Valide la structure globale.
      • Crée des instances Adresse pour chaque adresse.
      • Crée des instances HistoriqueAchat pour chaque achat.
      • Vérifie que les préférences sont bien un dictionnaire de string vers float.

Avantages pour un data analyst :

  • Gestion de données complexes : Permet de travailler avec des structures de données riches et imbriquées, courantes dans l'analyse de données clients.
  • Validation en profondeur : Assure que toutes les sous-structures (adresses, achats) sont également valides.
  • Typage fort : Garantit que les listes et dictionnaires contiennent les types de données attendus.
  • Facilité d'accès : Une fois validé, vous pouvez accéder facilement à des données imbriquées (par exemple, client_complet.adresses[0].ville).

Cette approche est particulièrement utile pour l'analyse de données clients, où vous pourriez avoir besoin de travailler avec des profils clients complexes incluant diverses informations comme l'historique d'achat, les préférences, et les multiples adresses. Elle permet de s'assurer que toutes ces données sont cohérentes et correctement structurées avant de procéder à l'analyse.

6. Intégration de Pydantic avec Pandas

L'intégration de Pydantic avec Pandas est particulièrement puissante pour la validation et le nettoyage des données avant l'analyse. Voici comment vous pouvez utiliser Pydantic v2 pour valider les données avant de les charger dans un DataFrame Pandas.

python
import pandas as pd
from pydantic import BaseModel, Field, ValidationError, field_validator
from typing import List
from datetime import date

class Vente(BaseModel):
    date: date
    produit_id: int
    quantite: int = Field(gt=0)
    prix_unitaire: float = Field(gt=0)
    
    @field_validator('prix_unitaire')
    @classmethod
    def arrondir_prix(cls, v: float) -> float:
        return round(v, 2)

def valider_donnees_ventes(donnees_brutes: List[dict]) -> List[Vente]:
    ventes_validees = []
    erreurs = []
    
    for idx, donnee in enumerate(donnees_brutes):
        try:
            vente = Vente.model_validate(donnee)
            ventes_validees.append(vente)
        except ValidationError as e:
            erreurs.append(f"Erreur à la ligne {idx+1}: {e}")
    
    if erreurs:
        print("Erreurs de validation détectées:")
        for erreur in erreurs:
            print(erreur)
    
    return ventes_validees

# Exemple de données brutes
donnees_brutes = [
    {"date": "2023-08-01", "produit_id": 1, "quantite": 5, "prix_unitaire": 10.99},
    {"date": "2023-08-02", "produit_id": 2, "quantite": -1, "prix_unitaire": 15.50},  # Erreur : quantité négative
    {"date": "2023-08-03", "produit_id": 3, "quantite": 3, "prix_unitaire": 0},  # Erreur : prix nul
    {"date": "2023-08-04", "produit_id": 4, "quantite": 2, "prix_unitaire": 20.005},
]

# Validation des données
ventes_validees = valider_donnees_ventes(donnees_brutes)

# Création du DataFrame avec les données validées
df_ventes = pd.DataFrame([v.model_dump() for v in ventes_validees])
print(df_ventes)

Explications détaillées :

  1. Modèle Vente :

    • Définit la structure attendue pour chaque vente.
    • Utilise Field pour définir des contraintes sur quantite et prix_unitaire.
    • Utilise un field_validator pour arrondir le prix à deux décimales.
  2. Fonction valider_donnees_ventes :

    • Prend une liste de dictionnaires (données brutes) en entrée.
    • Utilise Vente.model_validate() pour créer et valider chaque instance Vente.
    • Collecte les erreurs de validation avec les numéros de ligne correspondants.
    • Retourne une liste d'objets Vente validés.
  3. Gestion des erreurs :

    • Les erreurs sont collectées et affichées, permettant une correction ciblée des données source.
    • Le processus continue même en cas d'erreurs, permettant de valider l'ensemble des données.
  4. Intégration avec Pandas :

    • Après validation, les données sont converties en DataFrame Pandas.
    • Utilisation de [v.model_dump() for v in ventes_validees] pour convertir les objets Pydantic en dictionnaires.
  5. Avantages de cette approche :

    • Validation avant analyse : Assure que seules les données valides entrent dans le DataFrame.
    • Nettoyage automatique : Par exemple, l'arrondi des prix.
    • Détection précise des erreurs : Identifie exactement quelles lignes et quels champs posent problème.
    • Typage fort : Garantit que les types de données dans le DataFrame sont corrects (par exemple, date est bien une date).
  6. Utilisation dans un workflow d'analyse :

    • Cette approche peut être utilisée comme une étape de prétraitement dans un pipeline d'analyse de données.
    • Elle permet de détecter et de corriger les problèmes de données avant qu'ils n'impactent l'analyse.
    • Facilite la documentation et la reproductibilité de vos analyses en définissant clairement la structure attendue des données.
  7. Extensibilité :

    • Le modèle Vente peut être facilement étendu pour inclure des validations plus complexes ou des champs supplémentaires.
    • Par exemple, vous pourriez ajouter un validateur pour s'assurer que la date de vente n'est pas dans le futur.

Exemple d'extension du modèle :

python
from datetime import date, datetime

class VenteEtendue(Vente):
    categorie_produit: str
    
    @field_validator('date')
    @classmethod
    def date_non_future(cls, v: date) -> date:
        if v > date.today():
            raise ValueError("La date de vente ne peut pas être dans le futur")
        return v
    
    @field_validator('categorie_produit')
    @classmethod
    def categorie_valide(cls, v: str) -> str:
        categories_valides = ['Électronique', 'Vêtements', 'Alimentation', 'Maison']
        if v not in categories_valides:
            raise ValueError(f"Catégorie invalide. Doit être l'une de : {categories_valides}")
        return v

Cette extension permet une validation encore plus robuste des données de vente, assurant que les analyses subséquentes sont basées sur des données cohérentes et valides.

7. Cas pratique : Analyse de données de ventes

Mettons en pratique tout ce que nous avons appris dans un cas concret d'analyse de données de ventes. Nous allons créer un pipeline complet, de la validation des données à l'analyse, en utilisant Pydantic et Pandas.

python
from pydantic import BaseModel, field_validator, Field, model_validator
from typing import List
from datetime import date
import pandas as pd
import matplotlib.pyplot as plt

class Produit(BaseModel):
    id: str = Field(..., pattern=r'^P\d{4}$')
    nom: str
    categorie: str
    prix: float = Field(..., gt=0)

class Client(BaseModel):
    id: str = Field(..., pattern=r'^C\d{6}$')
    nom: str
    email: str
    date_inscription: date

class Vente(BaseModel):
    id: str = Field(..., pattern=r'^V\d{8}$')
    date: date
    produit: Produit
    client: Client
    quantite: int = Field(..., gt=0)
    montant_total: float

    @model_validator(mode='after')
    def verifier_montant_total(self) -> 'Vente':
        montant_calcule = round(self.produit.prix * self.quantite, 2)
        if abs(self.montant_total - montant_calcule) > 0.01:
            raise ValueError(f"Le montant total {self.montant_total} ne correspond pas au calcul {montant_calcule}")
        return self

    @field_validator('date')
    @classmethod
    def date_non_future(cls, v: date) -> date:
        if v > date.today():
            raise ValueError("La date de vente ne peut pas être dans le futur")
        return v

def valider_donnees_ventes(donnees_brutes: List[dict]) -> List[Vente]:
    ventes_validees = []
    erreurs = []
    
    for idx, donnee in enumerate(donnees_brutes):
        try:
            vente = Vente.model_validate(donnee)
            ventes_validees.append(vente)
        except ValueError as e:
            erreurs.append(f"Erreur à la ligne {idx+1}: {str(e)}")
    
    if erreurs:
        print("Erreurs de validation détectées:")
        for erreur in erreurs:
            print(erreur)
    
    return ventes_validees

# Données de test
donnees_brutes = [
    {
        "id": "V00000001",
        "date": "2023-08-01",
        "produit": {"id": "P0001", "nom": "Smartphone", "categorie": "Électronique", "prix": 599.99},
        "client": {"id": "C000001", "nom": "Alice Dupont", "email": "alice@example.com", "date_inscription": "2023-01-15"},
        "quantite": 1,
        "montant_total": 599.99
    },
    {
        "id": "V00000002",
        "date": "2023-08-02",
        "produit": {"id": "P0002", "nom": "Livre", "categorie": "Librairie", "prix": 19.99},
        "client": {"id": "C000002", "nom": "Bob Martin", "email": "bob@example.com", "date_inscription": "2023-02-20"},
        "quantite": 3,
        "montant_total": 59.97
    },
    {
        "id": "V00000003",
        "date": "2023-08-03",
        "produit": {"id": "P0003", "nom": "Cafetière", "categorie": "Électroménager", "prix": 89.99},
        "client": {"id": "C000003", "nom": "Claire Dubois", "email": "claire@example.com", "date_inscription": "2023-03-10"},
        "quantite": 1,
        "montant_total": 89.99
    },
    {
        "id": "V00000004",
        "date": "2023-08-04",
        "produit": {"id": "P0004", "nom": "T-shirt", "categorie": "Vêtements", "prix": 29.99},
        "client": {"id": "C000004", "nom": "David Lefebvre", "email": "david@example.com", "date_inscription": "2023-04-05"},
        "quantite": 2,
        "montant_total": 59.98
    },
    {
        "id": "V00000005",
        "date": "2023-08-05",
        "produit": {"id": "P0001", "nom": "Smartphone", "categorie": "Électronique", "prix": 599.99},
        "client": {"id": "C000002", "nom": "Bob Martin", "email": "bob@example.com", "date_inscription": "2023-02-20"},
        "quantite": 1,
        "montant_total": 599.99
    },
    {
        "id": "V00000006",
        "date": "2023-08-06",
        "produit": {"id": "P0005", "nom": "Tablette", "categorie": "Électronique", "prix": 349.99},
        "client": {"id": "C000005", "nom": "Emma Tremblay", "email": "emma@example.com", "date_inscription": "2023-05-12"},
        "quantite": 1,
        "montant_total": 349.99
    },
    {
        "id": "V00000007",
        "date": "2023-08-07",
        "produit": {"id": "P0006", "nom": "Casque audio", "categorie": "Électronique", "prix": 129.99},
        "client": {"id": "C000001", "nom": "Alice Dupont", "email": "alice@example.com", "date_inscription": "2023-01-15"},
        "quantite": 1,
        "montant_total": 129.99
    }
]

# Validation des données
ventes_validees = valider_donnees_ventes(donnees_brutes)

# Création du DataFrame
df_ventes = pd.DataFrame([
    {
        "id_vente": v.id,
        "date": v.date,
        "produit_id": v.produit.id,
        "produit_nom": v.produit.nom,
        "categorie": v.produit.categorie,
        "client_id": v.client.id,
        "client_nom": v.client.nom,
        "quantite": v.quantite,
        "montant_total": v.montant_total
    } for v in ventes_validees
])

# Analyse des données
print(df_ventes.head())

# Ventes totales par catégorie
ventes_par_categorie = df_ventes.groupby('categorie')['montant_total'].sum().sort_values(ascending=False)
print("\nVentes totales par catégorie:")
print(ventes_par_categorie)

# Visualisation des ventes par catégorie
plt.figure(figsize=(10, 6))
ventes_par_categorie.plot(kind='bar')
plt.title('Ventes totales par catégorie')
plt.xlabel('Catégorie')
plt.ylabel('Montant total des ventes')
plt.xticks(rotation=45)
plt.tight_layout()
plt.show()

# Analyse des clients les plus actifs
clients_actifs = df_ventes.groupby('client_id').agg({
    'client_nom': 'first',
    'montant_total': 'sum',
    'id_vente': 'count'
}).sort_values('montant_total', ascending=False)
clients_actifs.columns = ['Nom', 'Montant total des achats', 'Nombre d\'achats']
print("\nTop 5 des clients les plus actifs:")
print(clients_actifs.head())

# Vous pouvez ajouter d'autres analyses ici...

Explications détaillées :

  1. Modèles Pydantic :

    • Produit, Client, et Vente sont définis avec des validations spécifiques.
    • Utilisation de Field avec le paramètre pattern pour des validations regex sur les IDs.
    • Utilisation de model_validator et field_validator pour les validations personnalisées.
  2. Validateurs personnalisés :

    • verifier_montant_total est maintenant un model_validator avec mode='after', ce qui permet d'accéder à tous les champs de l'instance.
    • date_non_future reste un field_validator, mais avec la syntaxe mise à jour de Pydantic v2.
  3. Fonction de validation :

    • valider_donnees_ventes utilise maintenant Vente.model_validate() au lieu de Vente(**).
    • La gestion des erreurs reste inchangée.
  4. Création du DataFrame et Analyses :

    • Cette partie reste largement inchangée, car elle dépend principalement de Pandas et Matplotlib.
  5. Avantages de cette approche avec Pydantic:

    • Performance améliorée : Pydantic offre de meilleures performances, cruciales pour de grands ensembles de données.
    • Validations plus robustes : L'utilisation de Field avec pattern et les nouveaux validateurs offrent une validation plus précise et flexible.
    • Meilleure lisibilité du code : Les annotations de type et la structure des validateurs sont plus claires dans Pydantic.
    • Intégrité des données : Seules les données valides sont incluses dans l'analyse.
    • Flexibilité : Le modèle Pydantic peut être facilement étendu pour inclure de nouvelles validations ou champs.
    • Traçabilité : Les erreurs de validation sont clairement identifiées et peuvent être corrigées à la source.

Ce cas pratique montre comment Pydantic peut être intégré dans un workflow complet d'analyse de données, de la validation initiale à l'analyse et la visualisation. Pour un data analyst, cette approche offre un excellent équilibre entre la rigueur de la validation des données, les performances de traitement, et la flexibilité nécessaire pour l'analyse exploratoire.

Challenge

Clique sur le lien ci-dessous et réalise les exercices. N'oublie pas de faire une copie du google colab sur ton propre Drive avant de commencer les exercices Validation de données avec Pydantic

Critères d'acceptation

N'oublie pas de soumettre ton lien à la quête une fois que tu as terminé !

Une solution ici