#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Script pour générer un graphique interactif avec plotly montrant l'évolution du nombre d'objets OSM à partir d'un fichier CSV thématique d'une ville (code INSEE 91111 par défaut). Ce script utilise la bibliothèque plotly pour créer des graphiques interactifs à partir des données contenues dans des fichiers CSV thématiques. Par défaut, il utilise le code INSEE 91111. Le titre du graphique inclut le tag principal (thème) et le nom de la ville. Utilisation: python plotly_city.py chemin/vers/fichier.csv [options] Options: --output, -o : Chemin de sortie pour le graphique HTML (optionnel) --insee, -i : Code INSEE de la commune à analyser (par défaut: 91111) --city_name, -c : Nom de la ville (si non spécifié, sera généré à partir du code INSEE) Exemple: python plotly_city.py test_results/commune_91111_borne-de-recharge.csv Dépendances requises: - pandas - plotly Installation des dépendances: pip install pandas plotly """ import sys import os import pandas as pd import plotly.graph_objects as go from plotly.subplots import make_subplots import argparse from datetime import datetime def parse_args(): """Parse command line arguments.""" parser = argparse.ArgumentParser( description="Génère un graphique interactif avec plotly à 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 (HTML)", default=None ) parser.add_argument( "--insee", "-i", help="Code INSEE de la commune à analyser", default="91111" ) parser.add_argument( "--city_name", "-c", help="Nom de la ville (si non spécifié, sera extrait du CSV)", default=None, ) return parser.parse_args() def get_city_name(insee_code): """ Récupère le nom de la ville à partir du code INSEE. Cette fonction pourrait être améliorée pour utiliser une API ou une base de données. Args: insee_code: Code INSEE de la commune Returns: Nom de la ville ou le code INSEE si le nom n'est pas trouvé """ # Pour l'instant, on retourne simplement le code INSEE # Dans une version future, on pourrait implémenter une recherche dans une base de données return f"Commune {insee_code}" def load_data(csv_file, insee_code="91111"): """ Charge les données depuis le fichier CSV. Args: csv_file: Chemin vers le fichier CSV insee_code: Code INSEE de la commune à filtrer 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, on_bad_lines="skip") except TypeError: # Pour les versions plus anciennes de pandas df = pd.read_csv(csv_file, error_bad_lines=False, warn_bad_lines=True) # Vérifier si le CSV a la structure attendue if "date" in df.columns: # Format de CSV avec colonne 'date' directement df["date"] = pd.to_datetime(df["date"]) 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 la colonne 'zone' contient des codes INSEE if "zone" 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_plotly_graph( df, city_name=None, output_path=None, insee_code="91111", csv_file=None ): """ Génère un graphique interactif avec plotly montrant l'évolution du nombre d'objets dans le temps. Args: df: DataFrame pandas contenant les données city_name: Nom de la ville (optionnel) output_path: Chemin de sortie pour le graphique (optionnel) insee_code: Code INSEE de la commune csv_file: Chemin vers le fichier CSV source (pour générer un nom de fichier de sortie par défaut) """ # Si le nom de la ville n'est pas fourni, essayer de le récupérer if not city_name: city_name = get_city_name(insee_code) # Déterminer la colonne pour les types d'objets (theme) theme_column = "theme" # Créer une figure avec deux sous-graphiques (nombre total et taux de complétion) fig = make_subplots( rows=2, cols=1, subplot_titles=("Nombre d'objets OSM", "Taux de complétion des attributs (%)"), vertical_spacing=0.15, ) # Obtenir la liste des thèmes uniques if theme_column in df.columns: themes = df[theme_column].unique() # Créer un graphique pour chaque thème for theme in themes: # Filtrer les données pour ce thème theme_data = df[df[theme_column] == theme] # Tracer la ligne pour le nombre total d'objets fig.add_trace( go.Scatter( x=theme_data["date"], y=theme_data["nombre_total"], mode="lines+markers", name=f"{theme} - Total", hovertemplate="%{x}
Nombre: %{y}", ), row=1, col=1, ) # Tracer la ligne pour le taux de complétion si disponible if "pourcentage_completion" in theme_data.columns: fig.add_trace( go.Scatter( x=theme_data["date"], y=theme_data["pourcentage_completion"], mode="lines+markers", name=f"{theme} - Complétion (%)", hovertemplate="%{x}
Complétion: %{y}%", ), row=2, col=1, ) else: # Si aucune colonne de thème n'est trouvée, tracer simplement le nombre total fig.add_trace( go.Scatter( x=df["date"], y=df["nombre_total"], mode="lines+markers", name="Total", hovertemplate="%{x}
Nombre: %{y}", ), row=1, col=1, ) # Tracer la ligne pour le taux de complétion si disponible if "pourcentage_completion" in df.columns: fig.add_trace( go.Scatter( x=df["date"], y=df["pourcentage_completion"], mode="lines+markers", name="Complétion (%)", hovertemplate="%{x}
Complétion: %{y}%", ), row=2, col=1, ) # Configurer les axes et les légendes fig.update_xaxes(title_text="Date", row=1, col=1) fig.update_xaxes(title_text="Date", row=2, col=1) fig.update_yaxes(title_text="Nombre d'objets", row=1, col=1) fig.update_yaxes(title_text="Taux de complétion (%)", range=[0, 100], row=2, col=1) # Obtenir le thème principal (premier thème trouvé) main_tag = ( themes[0] if theme_column in df.columns and len(themes) > 0 else "Objets OSM" ) # Mettre à jour le titre du graphique avec le tag principal et le nom de la ville fig.update_layout( title=f"{main_tag} - {city_name}", hovermode="x unified", legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), height=800, width=1000, margin=dict(t=100, b=50, l=50, r=50), ) # Ajouter des annotations pour les informations supplémentaires fig.add_annotation( text=f"Code INSEE: {insee_code}", xref="paper", yref="paper", x=0.01, y=-0.15, showarrow=False, font=dict(size=10), ) # Ajouter la date de génération now = datetime.now().strftime("%Y-%m-%d %H:%M") fig.add_annotation( text=f"Généré le: {now}", xref="paper", yref="paper", x=0.99, y=-0.15, showarrow=False, font=dict(size=8), align="right", ) # Sauvegarder ou afficher le graphique if output_path: fig.write_html(output_path) print(f"Graphique interactif sauvegardé: {output_path}") elif csv_file: # Déterminer un chemin de sortie par défaut basé sur le fichier CSV base_name = os.path.splitext(csv_file)[0] default_output = f"{base_name}_plotly.html" fig.write_html(default_output) print(f"Graphique interactif sauvegardé: {default_output}") else: # Si aucun chemin de sortie n'est spécifié et aucun fichier CSV n'est fourni, # utiliser un nom par défaut basé sur le code INSEE et le thème default_output = f"commune_{insee_code}_{main_tag}_plotly.html" fig.write_html(default_output) print(f"Graphique interactif sauvegardé: {default_output}") 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) # Vérifier qu'il y a des données if df.empty: print(f"Aucune donnée trouvée pour le code INSEE {args.insee}.") 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 .html base_name = os.path.splitext(args.csv_file)[0] output_path = f"{base_name}_plotly.html" else: output_path = args.output # Générer le graphique generate_plotly_graph(df, args.city_name, output_path, args.insee, args.csv_file) print("Graphique interactif généré avec succès!") if __name__ == "__main__": main()