#!/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 import csv def get_city_name(insee_code): """ Récupère le nom de la ville à partir du code INSEE. Args: insee_code: Code INSEE de la commune Returns: Nom de la ville ou None si non trouvé """ try: csv_path = os.path.join(os.path.dirname(__file__), "osm-commerces-villes-export.csv") if os.path.exists(csv_path): with open(csv_path, 'r') as f: reader = csv.DictReader(f) for row in reader: if row.get('zone') == insee_code: return row.get('name') return None except Exception as e: print(f"Erreur lors de la récupération du nom de la ville: {e}") return None 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 = "" city_name = None # 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] city_name = get_city_name(insee_code) if city_name: plt.figtext(0.02, 0.02, f"Commune: {insee_code} - {city_name}", fontsize=10) zone_label = f"{insee_code} - {city_name}" else: 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] city_name = get_city_name(zone) if city_name: plt.figtext(0.02, 0.02, f"Zone: {zone} - {city_name}", fontsize=10) zone_label = f"{zone} - {city_name}" else: 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 zone_label = "" if "code_insee" in df.columns and len(df["code_insee"].unique()) == 1: insee_code = df["code_insee"].iloc[0] city_name = get_city_name(insee_code) if city_name: plt.figtext(0.02, 0.02, f"Commune: {insee_code} - {city_name}", fontsize=10) zone_label = f"{insee_code} - {city_name}" else: 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] city_name = get_city_name(zone) if city_name: plt.figtext(0.02, 0.02, f"Zone: {zone} - {city_name}", fontsize=10) zone_label = f"{zone} - {city_name}" else: plt.figtext(0.02, 0.02, f"Zone: {zone}", fontsize=10) zone_label = zone # Mettre à jour le titre avec le nom de la zone if zone_label: plt.title(f"{zone_label} : Évolution du taux de complétion 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: # 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()