mirror of
https://forge.chapril.org/tykayn/osm-commerces
synced 2025-10-04 17:04:53 +02:00
448 lines
16 KiB
Python
Executable file
448 lines
16 KiB
Python
Executable file
#!/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()
|