309 lines
10 KiB
Python
Executable file
309 lines
10 KiB
Python
Executable file
#!/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}<br>Nombre: %{y}<extra></extra>",
|
|
),
|
|
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}<br>Complétion: %{y}%<extra></extra>",
|
|
),
|
|
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}<br>Nombre: %{y}<extra></extra>",
|
|
),
|
|
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}<br>Complétion: %{y}%<extra></extra>",
|
|
),
|
|
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()
|