#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Script pour générer un graphique montrant l'évolution du nombre d'objets OSM à partir d'un fichier CSV """ import sys import os import pandas as pd import matplotlib.pyplot as plt import matplotlib.dates as mdates from datetime import datetime import argparse def parse_args(): """Parse command line arguments.""" parser = argparse.ArgumentParser( description="Génère un graphique à partir des données CSV d'objets OSM." ) parser.add_argument( "csv_file", help="Chemin vers le fichier CSV contenant les données" ) parser.add_argument( "--output", "-o", help="Chemin de sortie pour le graphique (PNG)", default=None ) parser.add_argument( "--insee", "-i", help="Code INSEE de la commune à analyser", default=None ) parser.add_argument( "--period", "-p", help="Période à analyser (annual, monthly, daily)", default="monthly", ) return parser.parse_args() def load_data(csv_file, insee_code=None, period="monthly"): """ Charge les données depuis le fichier CSV. Args: csv_file: Chemin vers le fichier CSV insee_code: Code INSEE de la commune à filtrer (optionnel) period: Période à analyser (annual, monthly, daily) Returns: DataFrame pandas contenant les données filtrées """ # Charger le CSV avec gestion des erreurs pour les lignes mal formatées try: df = pd.read_csv(csv_file, error_bad_lines=False, warn_bad_lines=True) except TypeError: # Pour les versions plus récentes de pandas df = pd.read_csv(csv_file, on_bad_lines="skip") # Vérifier si le CSV a la structure attendue if "date" in df.columns: # Format de CSV avec colonne 'date' directement try: df["date"] = pd.to_datetime(df["date"]) except: # Si la conversion échoue, essayer différents formats try: if df["date"].iloc[0].count("-") == 2: # Format YYYY-MM-DD df["date"] = pd.to_datetime(df["date"], format="%Y-%m-%d") elif df["date"].iloc[0].count("-") == 1: # Format YYYY-MM df["date"] = pd.to_datetime(df["date"], format="%Y-%m") else: df["date"] = pd.to_datetime(df["date"]) except: print("Erreur: Impossible de convertir la colonne 'date'.") sys.exit(1) elif "periode" in df.columns: # Ancien format avec colonne 'periode' # Filtrer par période df = df[df["periode"] == period] # Filtrer par code INSEE si spécifié if insee_code and "code_insee" in df.columns: df = df[df["code_insee"] == insee_code] # Convertir les dates en objets datetime if period == "annual" and "annee" in df.columns: df["date"] = pd.to_datetime(df["annee"].astype(str), format="%Y") elif period == "monthly": # Vérifier si la première colonne contient déjà un format de date mensuel (YYYY-MM) first_col = df.columns[0] first_val = str(df.iloc[0, 0]) if not df.empty else "" if first_col == "annee_mois" or (len(first_val) >= 7 and "-" in first_val): # Si la première colonne est 'annee_mois' ou contient une date au format YYYY-MM df["date"] = pd.to_datetime(df.iloc[:, 0].astype(str), format="%Y-%m") elif "annee" in df.columns: # Sinon, utiliser la colonne 'annee' et ajouter un mois fictif (janvier) df["date"] = pd.to_datetime( df["annee"].astype(str) + "-01", format="%Y-%m" ) elif period == "daily": # Vérifier si la première colonne contient déjà un format de date quotidien (YYYY-MM-DD) first_col = df.columns[0] first_val = str(df.iloc[0, 0]) if not df.empty else "" if first_col == "jour" or ( len(first_val) >= 10 and first_val.count("-") == 2 ): # Si la première colonne est 'jour' ou contient une date au format YYYY-MM-DD df["date"] = pd.to_datetime( df.iloc[:, 0].astype(str), format="%Y-%m-%d" ) elif "annee" in df.columns: # Sinon, utiliser la colonne 'annee' et ajouter un jour fictif (1er janvier) df["date"] = pd.to_datetime( df["annee"].astype(str) + "-01-01", format="%Y-%m-%d" ) else: # Si aucune colonne de date n'est trouvée, essayer d'utiliser la première colonne try: df["date"] = pd.to_datetime(df.iloc[:, 0]) except: print("Erreur: Impossible de trouver ou convertir une colonne de date.") sys.exit(1) # Filtrer par code INSEE si spécifié et si la colonne 'zone' contient des codes INSEE if insee_code and "zone" in df.columns and not "code_insee" in df.columns: # Vérifier si la zone contient le code INSEE if any( zone.endswith(insee_code) for zone in df["zone"] if isinstance(zone, str) ): df = df[df["zone"].str.endswith(insee_code)] # Trier par date df = df.sort_values("date") return df def generate_graph(df, output_path=None): """ Génère un graphique montrant l'évolution du nombre d'objets dans le temps. Args: df: DataFrame pandas contenant les données output_path: Chemin de sortie pour le graphique (optionnel) """ # Créer une figure avec une taille adaptée plt.figure(figsize=(12, 8)) # Déterminer la colonne pour les types d'objets (type_objet ou theme) type_column = "type_objet" if "type_objet" in df.columns else "theme" label_objet = "objet" # Obtenir la liste des types d'objets uniques if type_column in df.columns: object_types = df[type_column].unique() # Créer un graphique pour chaque type d'objet for obj_type in object_types: # Filtrer les données pour ce type d'objet obj_data = df[df[type_column] == obj_type] # Filtrer les valeurs nulles obj_data_filtered = obj_data[obj_data["nombre_total"].notna()] if not obj_data_filtered.empty: # Tracer la ligne pour le nombre total d'objets (sans marqueurs, avec courbe lissée) line, = plt.plot( obj_data_filtered["date"], obj_data_filtered["nombre_total"], linestyle="-", label=f"{obj_type}", ) label_objet = obj_type # Ajouter un remplissage pastel sous la courbe plt.fill_between( obj_data_filtered["date"], obj_data_filtered["nombre_total"], alpha=0.3, color=line.get_color() ) else: # Si aucune colonne de type n'est trouvée, tracer simplement le nombre total # Filtrer les valeurs nulles df_filtered = df[df["nombre_total"].notna()] if not df_filtered.empty: # Tracer la ligne pour le nombre total d'objets (sans marqueurs, avec courbe lissée) line, = plt.plot( df_filtered["date"], df_filtered["nombre_total"], linestyle="-", label="", ) # Ajouter un remplissage pastel sous la courbe plt.fill_between( df_filtered["date"], df_filtered["nombre_total"], alpha=0.3, color=line.get_color() ) # Configurer les axes et les légendes plt.xlabel("Date") plt.ylabel("Nombre d'objets") plt.grid(True, linestyle="--", alpha=0.7) # Formater l'axe des x pour afficher les dates correctement plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m")) plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=6)) plt.gcf().autofmt_xdate() # Ajouter une légende plt.legend() zone_label = "" # Ajouter des informations sur la commune if "code_insee" in df.columns and len(df["code_insee"].unique()) == 1: insee_code = df["code_insee"].iloc[0] plt.figtext(0.02, 0.02, f"Commune: {insee_code}", fontsize=10) zone_label = insee_code elif "zone" in df.columns and len(df["zone"].unique()) == 1: zone = df["zone"].iloc[0] plt.figtext(0.02, 0.02, f"Zone: {zone}", fontsize=10) zone_label = zone plt.title(f"{zone_label} : {label_objet} dans le temps") # Ajouter la date de génération now = datetime.now().strftime("%Y-%m-%d %H:%M") plt.figtext(0.98, 0.02, f"Généré le: {now}", fontsize=8, ha="right") # Ajuster la mise en page plt.tight_layout() # Sauvegarder ou afficher le graphique if output_path: plt.savefig(output_path, dpi=300, bbox_inches="tight") print(f"Graphique sauvegardé: {output_path}") else: plt.show() def generate_completion_graph(df, output_path=None): """ Génère un graphique montrant l'évolution du taux de complétion des attributs dans le temps. Args: df: DataFrame pandas contenant les données output_path: Chemin de sortie pour le graphique (optionnel) """ # Vérifier si la colonne de pourcentage de complétion existe if "pourcentage_completion" not in df.columns: print( "Avertissement: La colonne 'pourcentage_completion' n'existe pas dans le CSV. Le graphique de complétion ne sera pas généré." ) return # Créer une figure avec une taille adaptée plt.figure(figsize=(12, 8)) # Déterminer la colonne pour les types d'objets (type_objet ou theme) type_column = "type_objet" if "type_objet" in df.columns else "theme" # Obtenir la liste des types d'objets uniques if type_column in df.columns: object_types = df[type_column].unique() # Créer un graphique pour chaque type d'objet for obj_type in object_types: # Filtrer les données pour ce type d'objet obj_data = df[df[type_column] == obj_type] # Filtrer les valeurs nulles obj_data_filtered = obj_data[obj_data["pourcentage_completion"].notna()] if not obj_data_filtered.empty: # Tracer la ligne pour le taux de complétion (sans marqueurs, avec courbe lissée) line, = plt.plot( obj_data_filtered["date"], obj_data_filtered["pourcentage_completion"], linestyle="-", label=f"{obj_type} - Complétion (%)", ) # Ajouter un remplissage pastel sous la courbe plt.fill_between( obj_data_filtered["date"], obj_data_filtered["pourcentage_completion"], alpha=0.3, color=line.get_color() ) else: # Si aucune colonne de type n'est trouvée, tracer simplement le taux de complétion global # Filtrer les valeurs nulles df_filtered = df[df["pourcentage_completion"].notna()] if not df_filtered.empty: # Tracer la ligne pour le taux de complétion (sans marqueurs, avec courbe lissée) line, = plt.plot( df_filtered["date"], df_filtered["pourcentage_completion"], linestyle="-", label="Complétion (%)", ) # Ajouter un remplissage pastel sous la courbe plt.fill_between( df_filtered["date"], df_filtered["pourcentage_completion"], alpha=0.3, color=line.get_color() ) # Configurer les axes et les légendes plt.xlabel("Date") plt.ylabel("Complétion (%)") plt.title("Évolution du taux de complétion dans le temps") plt.grid(True, linestyle="--", alpha=0.7) # Définir les limites de l'axe y entre 0 et 100% plt.ylim(0, 100) # Formater l'axe des x pour afficher les dates correctement plt.gca().xaxis.set_major_formatter(mdates.DateFormatter("%Y-%m")) plt.gca().xaxis.set_major_locator(mdates.MonthLocator(interval=6)) plt.gcf().autofmt_xdate() # Ajouter une légende plt.legend() # Ajouter des informations sur la commune if "code_insee" in df.columns and len(df["code_insee"].unique()) == 1: insee_code = df["code_insee"].iloc[0] plt.figtext(0.02, 0.02, f"Commune: {insee_code}", fontsize=10) elif "zone" in df.columns and len(df["zone"].unique()) == 1: zone = df["zone"].iloc[0] plt.figtext(0.02, 0.02, f"Zone: {zone}", fontsize=10) # Ajouter la date de génération now = datetime.now().strftime("%Y-%m-%d %H:%M") plt.figtext(0.98, 0.02, f"Généré le: {now}", fontsize=8, ha="right") # Ajuster la mise en page plt.tight_layout() # Sauvegarder ou afficher le graphique if output_path: # Modifier le nom du fichier pour indiquer qu'il s'agit du taux de complétion base, ext = os.path.splitext(output_path) completion_path = f"{base}_completion{ext}" plt.savefig(completion_path, dpi=300, bbox_inches="tight") print(f"Graphique de complétion sauvegardé: {completion_path}") else: plt.show() def main(): """Fonction principale.""" # Analyser les arguments de la ligne de commande args = parse_args() # Vérifier que le fichier CSV existe if not os.path.isfile(args.csv_file): print(f"Erreur: Le fichier {args.csv_file} n'existe pas.") sys.exit(1) # Charger les données df = load_data(args.csv_file, args.insee, args.period) # Vérifier qu'il y a des données if df.empty: print(f"Aucune donnée trouvée pour la période {args.period}.") sys.exit(1) # Déterminer le chemin de sortie si non spécifié if not args.output: # Utiliser le même nom que le fichier CSV mais avec l'extension .png base_name = os.path.splitext(args.csv_file)[0] output_path = f"{base_name}_{args.period}_graph.png" else: output_path = args.output # Générer les graphiques generate_graph(df, output_path) generate_completion_graph(df, output_path) print("Graphiques générés avec succès!") if __name__ == "__main__": main()