data / app.py
Tracy André
updated
ebeb919
raw
history blame
19 kB
import os
os.environ["GRADIO_ANALYTICS_ENABLED"] = "False"
import gradio as gr
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
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, file_path=None):
"""Charge les données agricoles depuis Hugging Face ou fichiers locaux"""
# D'abord, essayer de charger depuis Hugging Face
try:
print(f"🤗 Tentative de chargement depuis Hugging Face: {dataset_id}")
dataset = load_dataset(dataset_id, use_auth_token=hf_token)
# Le dataset peut avoir plusieurs splits, essayer 'train' en premier
if 'train' in dataset:
# Convertir en DataFrame pandas
self.df = dataset['train'].to_pandas()
print(f"✅ Données chargées depuis Hugging Face: {dataset_id}")
# Si le dataset contient plusieurs fichiers CSV, prendre le premier qui contient les données d'intervention
if 'file' in self.df.columns:
# Filtrer pour ne garder que les fichiers d'intervention
intervention_files = self.df[self.df['file'].str.contains('Interventions', na=False)]
if not intervention_files.empty:
self.df = intervention_files
return self.analyze_data()
else:
# Si pas de split 'train', prendre le premier disponible
available_splits = list(dataset.keys())
if available_splits:
self.df = dataset[available_splits[0]].to_pandas()
print(f"✅ Données chargées depuis Hugging Face (split: {available_splits[0]})")
return self.analyze_data()
except Exception as e:
print(f"⚠️ Erreur lors du chargement depuis Hugging Face: {e}")
print("🔄 Basculement vers les fichiers locaux...")
# Si le chargement HF échoue, utiliser l'ancienne méthode
# Liste des chemins possibles pour les données
possible_paths = [
"data/Interventions-(sortie-excel)-Station_Expérimentale_de_Kerguéhennec-2025.csv",
"data/sample_data.csv",
"sample_data.csv"
]
if file_path:
possible_paths.insert(0, file_path)
# Essayer de charger depuis les différents chemins
for path in possible_paths:
try:
self.df = pd.read_csv(path, skiprows=1)
print(f"✅ Données chargées depuis: {path}")
break
except FileNotFoundError:
continue
else:
# Si aucun fichier n'est trouvé, créer des données d'exemple
print("⚠️ Aucun fichier de données trouvé, génération de données d'exemple")
self.df = self.create_sample_data()
return self.analyze_data()
def create_sample_data(self):
"""Crée des données d'exemple pour la démo"""
np.random.seed(42)
n_parcels = 45
n_interventions = 653
parcels = {
'numparcell': np.random.randint(1, n_parcels+1, n_interventions),
'nomparc': [f"Parcelle_{i}" for i in np.random.randint(1, n_parcels+1, n_interventions)],
'surfparc': np.random.uniform(0.1, 7.0, n_interventions),
'libelleusag': np.random.choice(['blé tendre hiver', 'maïs grain', 'colza hiver', 'haricot vert industrie',
'CIPAN autre', 'orge hiver', 'soja', 'avoine printemps'], n_interventions),
'familleprod': np.random.choice(['Herbicides', 'Fongicides', 'Insecticides', 'Fertilisants'],
n_interventions, p=[0.16, 0.25, 0.15, 0.44]),
'produit': [f"Produit_{i}" for i in np.random.randint(1, 50, n_interventions)],
'quantitetot': np.random.uniform(0.1, 25.0, n_interventions),
'libevenem': ['Traitement et protection des cultures'] * n_interventions,
'millesime': [2025] * n_interventions,
'raisonsoci': ['Station Expérimentale de Kerguéhennec'] * n_interventions
}
return pd.DataFrame(parcels)
def analyze_data(self):
"""Analyse des données et calcul des risques"""
if self.df is None:
return "Erreur: Aucune donnée chargée"
# 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
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()
}
# Calcul de l'analyse des risques
self.calculate_risk_analysis()
return general_stats, herbicide_stats
def calculate_risk_analysis(self):
"""Calcule l'analyse des risques par parcelle"""
# Groupement des données par parcelle
risk_analysis = self.df.groupby(['numparcell', 'nomparc', 'libelleusag', 'surfparc']).agg({
'familleprod': lambda x: (x == 'Herbicides').sum(), # Nb traitements herbicides
'libevenem': lambda x: len(x.unique()), # Diversité des événements
'produit': lambda x: len(x.unique()), # Diversité des produits
'quantitetot': 'sum' # Quantité totale
}).round(2)
# Quantités d'herbicides spécifiques
herbicide_quantities = self.df[self.df['familleprod'] == 'Herbicides'].groupby(
['numparcell', 'nomparc', 'libelleusag', 'surfparc'])['quantitetot'].sum().fillna(0)
risk_analysis['Quantite_herbicides'] = herbicide_quantities.reindex(risk_analysis.index, fill_value=0)
risk_analysis.columns = ['Nb_herbicides', 'Diversite_evenements', 'Diversite_produits',
'Quantite_totale', 'Quantite_herbicides']
# Calcul de l'IFT approximatif
risk_analysis['IFT_herbicide_approx'] = (risk_analysis['Quantite_herbicides'] /
risk_analysis.index.get_level_values('surfparc')).round(2)
# Classification du risque
def classify_risk(row):
ift = row['IFT_herbicide_approx']
nb_herb = row['Nb_herbicides']
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'])
def get_summary_stats(self):
"""Retourne les statistiques de résumé"""
if self.df is None:
return "Aucune donnée disponible"
stats_text = f"""
## 📊 Statistiques Générales
- **Nombre total de parcelles**: {self.df['numparcell'].nunique()}
- **Nombre d'interventions**: {len(self.df):,}
- **Surface totale**: {self.df['surfparc'].sum():.2f} hectares
- **Surface moyenne par parcelle**: {self.df['surfparc'].mean():.2f} hectares
- **Période**: {self.df['millesime'].min()} - {self.df['millesime'].max()}
## 🧪 Analyse Herbicides
"""
herbicides_df = self.df[self.df['familleprod'] == 'Herbicides']
if len(herbicides_df) > 0:
stats_text += f"""
- **Interventions herbicides**: {len(herbicides_df)} ({(len(herbicides_df)/len(self.df)*100):.1f}%)
- **Parcelles traitées**: {herbicides_df['numparcell'].nunique()}
- **Produits herbicides différents**: {herbicides_df['produit'].nunique()}
"""
if self.risk_analysis is not None:
risk_distribution = self.risk_analysis['Risque_adventice'].value_counts()
stats_text += f"""
## 🎯 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
stats_text += f"- **{risk_level}**: {count} parcelles ({pct:.1f}%)\n"
return stats_text
def get_low_risk_recommendations(self):
"""Retourne les recommandations pour les parcelles à faible risque"""
if self.risk_analysis is None:
return "Analyse des risques non disponible"
low_risk = self.risk_analysis[
self.risk_analysis['Risque_adventice'].isin(['TRÈS FAIBLE', 'FAIBLE'])
].head(10)
recommendations = "## 🌾 TOP 10 - Parcelles Recommandées pour Cultures Sensibles (Pois, Haricot)\n\n"
for idx, row in low_risk.iterrows():
parcelle, nom, culture, surface = idx
recommendations += f"""
**Parcelle {parcelle}** ({nom})
- Culture actuelle: {culture}
- Surface: {surface:.2f} ha
- Niveau de risque: {row['Risque_adventice']}
- IFT herbicide: {row['IFT_herbicide_approx']:.2f}
- Nombre d'herbicides: {row['Nb_herbicides']}
---
"""
return recommendations
def create_risk_visualization(self):
"""Crée la visualisation des risques"""
if self.risk_analysis is None:
return None
risk_df = self.risk_analysis.reset_index()
fig = px.scatter(risk_df,
x='surfparc',
y='IFT_herbicide_approx',
color='Risque_adventice',
size='Nb_herbicides',
hover_data=['nomparc', 'libelleusag'],
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.update_layout(width=800, height=600, title_font_size=16)
return fig
def create_culture_analysis(self):
"""Analyse par type de culture"""
if self.df is None:
return None
culture_counts = self.df['libelleusag'].value_counts()
fig = px.pie(values=culture_counts.values,
names=culture_counts.index,
title="🌱 Répartition des Cultures")
fig.update_layout(width=700, height=500)
return fig
def create_risk_distribution(self):
"""Distribution des niveaux de risque"""
if self.risk_analysis is None:
return None
risk_counts = self.risk_analysis['Risque_adventice'].value_counts()
fig = px.bar(x=risk_counts.index,
y=risk_counts.values,
color=risk_counts.index,
color_discrete_map={
'TRÈS FAIBLE': 'green',
'FAIBLE': 'lightgreen',
'MODÉRÉ': 'orange',
'ÉLEVÉ': 'red',
'TRÈS ÉLEVÉ': 'darkred'
},
title="📊 Distribution des Niveaux de Risque Adventice",
labels={'x': 'Niveau de risque', 'y': 'Nombre de parcelles'})
fig.update_layout(width=700, height=500, showlegend=False)
return fig
# Initialisation de l'analyseur
analyzer = AgricultureAnalyzer()
analyzer.load_data()
# Interface Gradio
def create_interface():
with gr.Blocks(title="🌾 Analyse Adventices Agricoles CRA", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# 🌾 Analyse des Adventices Agricoles - CRA Bretagne
**Objectif**: Anticiper et réduire la pression des adventices dans les parcelles agricoles bretonnes
Cette application analyse les données historiques pour identifier les parcelles les plus adaptées
à la culture de plantes sensibles comme le pois ou le haricot.
""")
with gr.Tabs():
with gr.TabItem("📊 Vue d'ensemble"):
gr.Markdown("## Statistiques générales des données agricoles")
stats_output = gr.Markdown(analyzer.get_summary_stats())
with gr.Row():
culture_plot = gr.Plot(analyzer.create_culture_analysis())
risk_dist_plot = gr.Plot(analyzer.create_risk_distribution())
with gr.TabItem("🎯 Analyse des Risques"):
gr.Markdown("## Cartographie des risques adventices par parcelle")
risk_plot = gr.Plot(analyzer.create_risk_visualization())
gr.Markdown("""
**Interprétation du graphique**:
- **Axe X**: Surface de la parcelle (hectares)
- **Axe Y**: IFT Herbicide approximatif
- **Couleur**: Niveau de risque adventice
- **Taille**: Nombre d'herbicides utilisés
Les parcelles vertes (risque faible) sont idéales pour les cultures sensibles.
""")
with gr.TabItem("🌾 Recommandations"):
gr.Markdown(analyzer.get_low_risk_recommendations())
gr.Markdown("""
## 💡 Conseils pour la gestion des adventices
### Parcelles à Très Faible Risque (Vertes)
- ✅ **Idéales pour pois et haricot**
- ✅ Historique d'usage herbicide minimal
- ✅ Pression adventice faible attendue
### Parcelles à Faible Risque (Vert clair)
- ⚠️ Surveillance légère recommandée
- ✅ Conviennent aux cultures sensibles avec précautions
### Parcelles à Risque Modéré/Élevé (Orange/Rouge)
- ❌ Éviter pour cultures sensibles
- 🔍 Rotation nécessaire avant implantation
- 📈 Surveillance renforcée des adventices
### Stratégies alternatives
- **Rotation longue**: 3-4 ans avant cultures sensibles
- **Cultures intermédiaires**: CIPAN pour réduire la pression
- **Techniques mécaniques**: Hersage, binage
- **Biostimulants**: Renforcement naturel des cultures
""")
with gr.TabItem("ℹ️ À propos"):
gr.Markdown("""
## 🎯 Méthodologie
Cette analyse se base sur :
### Calcul de l'IFT (Indice de Fréquence de Traitement)
- **IFT ≈ Quantité appliquée / Surface de parcelle**
- Indicateur de l'intensité des traitements herbicides
### Classification des risques
- **TRÈS FAIBLE**: IFT = 0, aucun herbicide
- **FAIBLE**: IFT < 1, usage minimal
- **MODÉRÉ**: IFT < 3, usage modéré
- **ÉLEVÉ**: IFT < 5, usage important
- **TRÈS ÉLEVÉ**: IFT ≥ 5, usage intensif
### Données analysées
- **Source**: Station Expérimentale de Kerguéhennec
- **Période**: Campagne 2025
- **Variables**: Interventions, produits, quantités, surfaces
---
**Développé pour le Hackathon CRA Bretagne** 🏆
*Application d'aide à la décision pour une agriculture durable*
""")
# Bouton de rafraîchissement
refresh_btn = gr.Button("🔄 Actualiser les données", variant="secondary")
def refresh_data():
analyzer.load_data()
return (
analyzer.get_summary_stats(),
analyzer.create_culture_analysis(),
analyzer.create_risk_distribution(),
analyzer.create_risk_visualization(),
analyzer.get_low_risk_recommendations()
)
refresh_btn.click(
refresh_data,
outputs=[stats_output, culture_plot, risk_dist_plot, risk_plot]
)
return demo
# Lancement de l'application
if __name__ == "__main__":
demo = create_interface()
# Configuration pour Hugging Face Spaces
demo.launch(
server_name="0.0.0.0",
server_port=7860,
share=False # Pas besoin de share sur HF Spaces
)