data / app_simple.py
Tracy André
updated
3bde590
raw
history blame
18 kB
#!/usr/bin/env python3
"""
Application simplifiée d'analyse des adventices agricoles
Compatible avec Python 3.13 - sans Gradio pour éviter les problèmes de dépendances
"""
import os
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
import warnings
from datasets import load_dataset
from huggingface_hub import HfApi, hf_hub_download
warnings.filterwarnings('ignore')
# Configuration Hugging Face
hf_token = os.environ.get("HF_TOKEN")
dataset_id = "HackathonCRA/2024"
# Configuration des graphiques
plt.style.use('default')
sns.set_palette("husl")
class AgricultureAnalyzer:
def __init__(self):
self.df = None
self.risk_analysis = None
def load_data(self):
"""Charge les données du dataset Hugging Face"""
print("🔄 Chargement des données depuis Hugging Face...")
print(f"📋 Dataset ID: {dataset_id}")
print(f"📋 Token disponible: {'Oui' if hf_token else 'Non'}")
self.df = None
# 1) Tentative de chargement direct via datasets.load_dataset
try:
dataset = load_dataset(
dataset_id,
split="train",
token=hf_token,
trust_remote_code=True,
)
print(f"📊 Dataset chargé: {len(dataset)} exemples")
try:
self.df = dataset.to_pandas()
print("✅ Conversion to_pandas() réussie")
except Exception as pandas_error:
print(f"❌ Erreur to_pandas(): {pandas_error}")
print("🔄 Tentative de conversion manuelle...")
data_list = []
for i, item in enumerate(dataset):
data_list.append(item)
if i < 5:
print(f"📋 Exemple {i}: {list(item.keys())}")
self.df = pd.DataFrame(data_list)
print(f"✅ Conversion manuelle réussie: {len(self.df)} lignes")
except Exception as e:
print(f"❌ Erreur lors du chargement depuis Hugging Face: {str(e)}")
print(f"❌ Type d'erreur: {type(e).__name__}")
# 2) Fallback: récupérer directement les fichiers du repo (csv/parquet/tsv/json)
fallback_msg = self._fallback_load_from_repo_files()
if self.df is None:
print(f"❌ Erreur lors du chargement du dataset : {str(e)} | Fallback: {fallback_msg}")
return
# Si on n'a toujours pas de dataframe, arrêter
if self.df is None:
print("❌ Impossible de charger les données")
return
print(f"📊 Données chargées: {len(self.df)} lignes")
print(f"📊 Colonnes disponibles: {list(self.df.columns)}")
# Nettoyage et validation
required_columns = ["numparcell", "surfparc", "millesime"]
missing_cols = [col for col in required_columns if col not in self.df.columns]
if missing_cols:
print(f"❌ Colonnes manquantes: {missing_cols}")
self.df = None
return
# Nettoyage
initial_len = len(self.df)
self.df = self.df.dropna(subset=required_columns)
print(f"📊 Avant nettoyage: {initial_len} lignes")
print(f"📊 Après nettoyage: {len(self.df)} lignes")
def _fallback_load_from_repo_files(self):
"""Fallback pour charger les données en téléchargeant directement les fichiers du repo HF."""
try:
print("🔄 Tentative de chargement alternatif via fichiers du dépôt Hugging Face...")
api = HfApi()
files = api.list_repo_files(repo_id=dataset_id, repo_type="dataset", token=hf_token)
if not files:
print("❌ Aucun fichier dans le dépôt")
return "Aucun fichier trouvé dans le dépôt."
data_files = [
f for f in files if f.lower().endswith((".parquet", ".csv", ".tsv", ".json"))
]
if not data_files:
print("❌ Aucun fichier de données exploitable (csv/tsv/parquet/json)")
return "Aucun fichier exploitable (csv/tsv/parquet/json)."
# Priorité: parquet > csv > tsv > json
for ext in [".parquet", ".csv", ".tsv", ".json"]:
selected = [f for f in data_files if f.lower().endswith(ext)]
if selected:
chosen_ext = ext
selected_files = selected
break
print(f"📂 Fichiers détectés ({chosen_ext}): {selected_files[:5]}{' ...' if len(selected_files) > 5 else ''}")
local_paths = []
for f in selected_files:
local_path = hf_hub_download(
repo_id=dataset_id,
repo_type="dataset",
filename=f,
token=hf_token,
)
local_paths.append(local_path)
frames = []
if chosen_ext == ".parquet":
for p in local_paths:
frames.append(pd.read_parquet(p))
elif chosen_ext == ".csv":
for p in local_paths:
frames.append(pd.read_csv(p))
elif chosen_ext == ".tsv":
for p in local_paths:
frames.append(pd.read_csv(p, sep="\t"))
elif chosen_ext == ".json":
for p in local_paths:
try:
frames.append(pd.read_json(p, lines=True))
except Exception:
frames.append(pd.read_json(p))
self.df = pd.concat(frames, ignore_index=True) if len(frames) > 1 else frames[0]
print(f"✅ Fallback réussi: {len(self.df)} lignes chargées depuis les fichiers du dépôt")
return None
except Exception as e:
print(f"❌ Fallback échoué: {e}")
# Dernier recours: fichier local d'exemple
sample_path = os.path.join(os.path.dirname(__file__), "sample_data.csv")
if os.path.exists(sample_path):
try:
self.df = pd.read_csv(sample_path)
print(f"✅ Chargement du fichier local 'sample_data.csv' ({len(self.df)} lignes)")
return "Chargement via fichier local de secours."
except Exception as e2:
print(f"❌ Échec du chargement du fichier local: {e2}")
return f"Fallback échoué: {e}"
def analyze_data(self):
"""Analyse des données et calcul des risques"""
if self.df is None or len(self.df) == 0:
print("❌ Pas de données à analyser")
return None, None
try:
print(f"🔄 Début de l'analyse sur {len(self.df)} enregistrements...")
# Analyse générale
general_stats = {
'total_parcelles': self.df['numparcell'].nunique(),
'total_interventions': len(self.df),
'surface_totale': self.df['surfparc'].sum(),
'surface_moyenne': self.df['surfparc'].mean(),
'periode': f"{self.df['millesime'].min()} - {self.df['millesime'].max()}"
}
# Analyse des herbicides
if 'familleprod' in self.df.columns:
herbicides_df = self.df[self.df['familleprod'] == 'Herbicides'].copy()
herbicide_stats = {
'nb_interventions_herbicides': len(herbicides_df),
'pourcentage_herbicides': (len(herbicides_df) / len(self.df)) * 100,
'parcelles_traitees': herbicides_df['numparcell'].nunique()
}
else:
herbicide_stats = {
'nb_interventions_herbicides': 0,
'pourcentage_herbicides': 0,
'parcelles_traitees': 0
}
# Calcul de l'analyse des risques
self.calculate_risk_analysis()
print("✅ Analyse terminée avec succès")
return general_stats, herbicide_stats
except Exception as e:
print(f"❌ Erreur lors de l'analyse: {str(e)}")
return None, None
def calculate_risk_analysis(self):
"""Calcule l'analyse des risques par parcelle"""
try:
print("🔄 Calcul de l'analyse des risques...")
# Vérifier les colonnes nécessaires
required_group_cols = ['numparcell', 'surfparc']
optional_group_cols = ['nomparc', 'libelleusag']
# Construire la liste des colonnes de groupement disponibles
group_cols = [col for col in required_group_cols if col in self.df.columns]
group_cols.extend([col for col in optional_group_cols if col in self.df.columns])
if len(group_cols) < 2:
print(f"❌ Colonnes insuffisantes pour le groupement: {group_cols}")
self.risk_analysis = pd.DataFrame()
return
# Construire l'agrégation selon les colonnes disponibles
agg_dict = {}
if 'familleprod' in self.df.columns:
agg_dict['familleprod'] = lambda x: (x == 'Herbicides').sum()
if 'libevenem' in self.df.columns:
agg_dict['libevenem'] = lambda x: len(x.unique())
if 'produit' in self.df.columns:
agg_dict['produit'] = lambda x: len(x.unique())
if 'quantitetot' in self.df.columns:
agg_dict['quantitetot'] = 'sum'
if not agg_dict:
print("❌ Aucune colonne disponible pour l'agrégation")
self.risk_analysis = pd.DataFrame()
return
# Groupement des données par parcelle
risk_analysis = self.df.groupby(group_cols).agg(agg_dict).round(2)
# Quantités d'herbicides spécifiques (seulement si les colonnes existent)
if 'familleprod' in self.df.columns and 'quantitetot' in self.df.columns:
herbicides_df = self.df[self.df['familleprod'] == 'Herbicides']
if len(herbicides_df) > 0:
herbicide_quantities = herbicides_df.groupby(group_cols)['quantitetot'].sum().fillna(0)
risk_analysis['Quantite_herbicides'] = herbicide_quantities.reindex(risk_analysis.index, fill_value=0)
else:
risk_analysis['Quantite_herbicides'] = 0
else:
risk_analysis['Quantite_herbicides'] = 0
# Renommer les colonnes de façon sécurisée
new_column_names = {}
if 'familleprod' in agg_dict:
new_column_names['familleprod'] = 'Nb_herbicides'
if 'libevenem' in agg_dict:
new_column_names['libevenem'] = 'Diversite_evenements'
if 'produit' in agg_dict:
new_column_names['produit'] = 'Diversite_produits'
if 'quantitetot' in agg_dict:
new_column_names['quantitetot'] = 'Quantite_totale'
risk_analysis = risk_analysis.rename(columns=new_column_names)
# Calcul de l'IFT approximatif
if 'surfparc' in group_cols:
risk_analysis['IFT_herbicide_approx'] = (risk_analysis['Quantite_herbicides'] /
risk_analysis.index.get_level_values('surfparc')).round(2)
else:
risk_analysis['IFT_herbicide_approx'] = 0
# Classification du risque
def classify_risk(row):
ift = row.get('IFT_herbicide_approx', 0)
nb_herb = row.get('Nb_herbicides', 0)
if ift == 0 and nb_herb == 0:
return 'TRÈS FAIBLE'
elif ift < 1 and nb_herb <= 1:
return 'FAIBLE'
elif ift < 3 and nb_herb <= 3:
return 'MODÉRÉ'
elif ift < 5 and nb_herb <= 5:
return 'ÉLEVÉ'
else:
return 'TRÈS ÉLEVÉ'
risk_analysis['Risque_adventice'] = risk_analysis.apply(classify_risk, axis=1)
# Tri par risque
risk_order = ['TRÈS FAIBLE', 'FAIBLE', 'MODÉRÉ', 'ÉLEVÉ', 'TRÈS ÉLEVÉ']
risk_analysis['Risk_Score'] = risk_analysis['Risque_adventice'].map({r: i for i, r in enumerate(risk_order)})
self.risk_analysis = risk_analysis.sort_values(['Risk_Score', 'IFT_herbicide_approx'])
print(f"✅ Analyse des risques terminée: {len(self.risk_analysis)} parcelles analysées")
except Exception as e:
print(f"❌ Erreur lors du calcul des risques: {str(e)}")
self.risk_analysis = pd.DataFrame()
def print_summary_stats(self):
"""Affiche les statistiques de résumé"""
if self.df is None:
print("❌ Aucune donnée disponible")
return
print("\n" + "="*60)
print("📊 STATISTIQUES GÉNÉRALES")
print("="*60)
print(f"• Nombre total de parcelles: {self.df['numparcell'].nunique()}")
print(f"• Nombre d'interventions: {len(self.df):,}")
print(f"• Surface totale: {self.df['surfparc'].sum():.2f} hectares")
print(f"• Surface moyenne par parcelle: {self.df['surfparc'].mean():.2f} hectares")
print(f"• Période: {self.df['millesime'].min()} - {self.df['millesime'].max()}")
if 'familleprod' in self.df.columns:
herbicides_df = self.df[self.df['familleprod'] == 'Herbicides']
if len(herbicides_df) > 0:
print("\n🧪 ANALYSE HERBICIDES")
print(f"• Interventions herbicides: {len(herbicides_df)} ({(len(herbicides_df)/len(self.df)*100):.1f}%)")
print(f"• Parcelles traitées: {herbicides_df['numparcell'].nunique()}")
if 'produit' in herbicides_df.columns:
print(f"• Produits herbicides différents: {herbicides_df['produit'].nunique()}")
if self.risk_analysis is not None and len(self.risk_analysis) > 0:
risk_distribution = self.risk_analysis['Risque_adventice'].value_counts()
print("\n🎯 RÉPARTITION DES RISQUES ADVENTICES")
for risk_level in ['TRÈS FAIBLE', 'FAIBLE', 'MODÉRÉ', 'ÉLEVÉ', 'TRÈS ÉLEVÉ']:
if risk_level in risk_distribution:
count = risk_distribution[risk_level]
pct = (count / len(self.risk_analysis)) * 100
print(f"• {risk_level}: {count} parcelles ({pct:.1f}%)")
def save_results(self, output_dir="results"):
"""Sauvegarde les résultats dans des fichiers"""
if not os.path.exists(output_dir):
os.makedirs(output_dir)
# Sauvegarder les données d'analyse
if self.risk_analysis is not None and len(self.risk_analysis) > 0:
risk_df = self.risk_analysis.reset_index()
risk_df.to_csv(f"{output_dir}/risk_analysis.csv", index=False)
print(f"✅ Analyse des risques sauvegardée dans {output_dir}/risk_analysis.csv")
# Graphique de risque
if len(risk_df) > 0:
fig = px.scatter(risk_df,
x='surfparc',
y='IFT_herbicide_approx',
color='Risque_adventice',
size='Nb_herbicides' if 'Nb_herbicides' in risk_df.columns else None,
color_discrete_map={
'TRÈS FAIBLE': 'green',
'FAIBLE': 'lightgreen',
'MODÉRÉ': 'orange',
'ÉLEVÉ': 'red',
'TRÈS ÉLEVÉ': 'darkred'
},
title="🎯 Analyse du Risque Adventice par Parcelle",
labels={
'surfparc': 'Surface de la parcelle (ha)',
'IFT_herbicide_approx': 'IFT Herbicide (approximatif)',
'Risque_adventice': 'Niveau de risque'
})
fig.write_html(f"{output_dir}/risk_visualization.html")
print(f"✅ Graphique de risque sauvegardé dans {output_dir}/risk_visualization.html")
def main():
"""Fonction principale"""
print("🌾 Analyse des Adventices Agricoles - CRA Bretagne")
print("=" * 60)
# Initialisation de l'analyseur
analyzer = AgricultureAnalyzer()
# Chargement des données
analyzer.load_data()
if analyzer.df is None:
print("❌ Impossible de continuer sans données")
return
# Analyse des données
general_stats, herbicide_stats = analyzer.analyze_data()
if general_stats is None:
print("❌ Échec de l'analyse des données")
return
# Affichage des résultats
analyzer.print_summary_stats()
# Sauvegarde
analyzer.save_results()
print("\n" + "="*60)
print("✅ Analyse terminée avec succès !")
print("📁 Résultats sauvegardés dans le dossier 'results/'")
print("🌐 Ouvrez 'results/risk_visualization.html' dans votre navigateur")
if __name__ == "__main__":
main()