Spaces:
Sleeping
Sleeping
Tracy André
commited on
Commit
·
150fdd6
1
Parent(s):
f084836
updated
Browse files- NOUVELLE_FONCTIONNALITE_PARCELLES.md +156 -0
- app.py +209 -85
NOUVELLE_FONCTIONNALITE_PARCELLES.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# 🏠 Nouvelle Fonctionnalité : Filtrage par Parcelle
|
| 2 |
+
|
| 3 |
+
## 📋 Fonctionnalité Ajoutée
|
| 4 |
+
|
| 5 |
+
Dans l'onglet **"📅 Analyse par Année"**, une nouvelle liste déroulante a été ajoutée pour permettre la sélection par parcelle spécifique, en complément du filtrage par année existant.
|
| 6 |
+
|
| 7 |
+
## 🎯 Objectif
|
| 8 |
+
|
| 9 |
+
Permettre une analyse granulaire des données agricoles en combinant :
|
| 10 |
+
- **Filtrage temporel** : Par année
|
| 11 |
+
- **Filtrage spatial** : Par parcelle spécifique
|
| 12 |
+
|
| 13 |
+
## 🔧 Fonctionnement
|
| 14 |
+
|
| 15 |
+
### Interface Utilisateur
|
| 16 |
+
|
| 17 |
+
L'interface comprend maintenant **deux contrôles de filtrage** côte à côte :
|
| 18 |
+
|
| 19 |
+
1. **🗓️ Sélectionner une année**
|
| 20 |
+
- Liste déroulante des années disponibles
|
| 21 |
+
- Année la plus récente sélectionnée par défaut
|
| 22 |
+
|
| 23 |
+
2. **🏠 Sélectionner une parcelle** *(NOUVEAU)*
|
| 24 |
+
- Liste déroulante de toutes les parcelles disponibles
|
| 25 |
+
- Format d'affichage : `Numéro - Nom de la parcelle`
|
| 26 |
+
- Option "Toutes les parcelles" par défaut
|
| 27 |
+
- 84 parcelles disponibles dans les données actuelles
|
| 28 |
+
|
| 29 |
+
### Synchronisation des Contrôles
|
| 30 |
+
|
| 31 |
+
- **Mise à jour automatique** : Changer l'année OU la parcelle rafraîchit tous les graphiques
|
| 32 |
+
- **Réactivité** : Les modifications sont appliquées immédiatement
|
| 33 |
+
- **Cohérence** : Tous les graphiques et statistiques reflètent la sélection combinée
|
| 34 |
+
|
| 35 |
+
## 📊 Impacts sur les Visualisations
|
| 36 |
+
|
| 37 |
+
### 1. Statistiques Résumé
|
| 38 |
+
- **Titre dynamique** : Indique l'année et la parcelle sélectionnées
|
| 39 |
+
- **Métriques adaptées** :
|
| 40 |
+
- Nombre de parcelles (1 si parcelle spécifique, N si toutes)
|
| 41 |
+
- Surface totale/moyenne de la sélection
|
| 42 |
+
- Interventions herbicides pour la sélection
|
| 43 |
+
|
| 44 |
+
**Exemple :**
|
| 45 |
+
```
|
| 46 |
+
## 📊 Statistiques pour l'année 2014 - Parcelle 2 (Kersuzan Bas)
|
| 47 |
+
- Nombre de parcelles: 1
|
| 48 |
+
- Nombre d'interventions: 27
|
| 49 |
+
- Surface totale: 49.68 hectares
|
| 50 |
+
- Interventions herbicides: 6 (22.2%)
|
| 51 |
+
```
|
| 52 |
+
|
| 53 |
+
### 2. Graphique des Cultures
|
| 54 |
+
- **Scope adapté** : Répartition des cultures pour la parcelle sélectionnée
|
| 55 |
+
- **Titre dynamique** : Inclut l'année et la parcelle
|
| 56 |
+
- **Données filtrées** : Seules les cultures de la parcelle sélectionnée
|
| 57 |
+
|
| 58 |
+
### 3. Timeline des Interventions
|
| 59 |
+
- **Activité focalisée** : Répartition mensuelle pour la parcelle spécifique
|
| 60 |
+
- **Pattern individuel** : Comprendre les habitudes d'une parcelle particulière
|
| 61 |
+
- **Comparaison temporelle** : Évolution des pratiques sur une parcelle
|
| 62 |
+
|
| 63 |
+
### 4. Analyse des Herbicides
|
| 64 |
+
- **Usage spécifique** : Top des herbicides utilisés sur la parcelle
|
| 65 |
+
- **Pratiques individuelles** : Stratégies herbicides parcelle par parcelle
|
| 66 |
+
- **Traçabilité** : Historique précis des traitements
|
| 67 |
+
|
| 68 |
+
## 💡 Cas d'Usage
|
| 69 |
+
|
| 70 |
+
### 🔍 Analyse Parcellaire Détaillée
|
| 71 |
+
```
|
| 72 |
+
Sélection : 2014 + Parcelle 2 (Kersuzan Bas)
|
| 73 |
+
Résultat : Vue complète des pratiques sur cette parcelle en 2014
|
| 74 |
+
Usage : Audit détaillé, préparation de rotation, analyse de conformité
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
### 📈 Comparaison Entre Parcelles
|
| 78 |
+
```
|
| 79 |
+
Étape 1 : Analyser Parcelle A en 2014
|
| 80 |
+
Étape 2 : Analyser Parcelle B en 2014
|
| 81 |
+
Résultat : Comparaison des pratiques entre parcelles similaires
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
### 🎯 Suivi Longitudinal
|
| 85 |
+
```
|
| 86 |
+
Sélection : Parcelle 2 en 2014, puis 2015, puis 2016...
|
| 87 |
+
Résultat : Évolution des pratiques sur une parcelle spécifique
|
| 88 |
+
Usage : Analyse de l'impact des changements de pratiques
|
| 89 |
+
```
|
| 90 |
+
|
| 91 |
+
### 🌾 Préparation de Culture
|
| 92 |
+
```
|
| 93 |
+
Contexte : Vouloir planter du pois sur Parcelle 2
|
| 94 |
+
Action : Analyser l'historique herbicide de cette parcelle
|
| 95 |
+
Décision : Évaluer la compatibilité avec la culture sensible
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
## ⚙️ Aspects Techniques
|
| 99 |
+
|
| 100 |
+
### Gestion des Types de Données
|
| 101 |
+
- **Conversion automatique** : String ↔ Integer pour les numéros de parcelles
|
| 102 |
+
- **Robustesse** : Gestion des erreurs de conversion
|
| 103 |
+
- **Compatibilité** : Fonctionne avec différents formats de données
|
| 104 |
+
|
| 105 |
+
### Performance
|
| 106 |
+
- **Filtrage optimisé** : Pandas operations pour des performances élevées
|
| 107 |
+
- **Mémoire efficace** : Pas de duplication inutile des données
|
| 108 |
+
- **Réactivité** : Mise à jour instantanée des visualisations
|
| 109 |
+
|
| 110 |
+
### Architecture
|
| 111 |
+
```python
|
| 112 |
+
# Nouvelle méthode de filtrage combiné
|
| 113 |
+
def filter_data_by_year_and_parcel(self, year, parcel_id):
|
| 114 |
+
# Filtrage temporel ET spatial
|
| 115 |
+
# Gestion des types de données
|
| 116 |
+
# Validation des entrées
|
| 117 |
+
|
| 118 |
+
# Méthodes de visualisation mises à jour
|
| 119 |
+
def create_year_culture_analysis(self, year, parcel_id=None):
|
| 120 |
+
def create_year_interventions_timeline(self, year, parcel_id=None):
|
| 121 |
+
def create_year_herbicide_analysis(self, year, parcel_id=None):
|
| 122 |
+
def get_year_summary_stats(self, year, parcel_id=None):
|
| 123 |
+
```
|
| 124 |
+
|
| 125 |
+
## 🎉 Avantages
|
| 126 |
+
|
| 127 |
+
### Pour l'Utilisateur
|
| 128 |
+
- **Granularité** : Analyse au niveau parcelle le plus fin
|
| 129 |
+
- **Flexibilité** : Vue globale OU spécifique selon les besoins
|
| 130 |
+
- **Intuitivité** : Interface simple avec deux listes déroulantes
|
| 131 |
+
- **Rapidité** : Exploration interactive des données
|
| 132 |
+
|
| 133 |
+
### Pour l'Analyse
|
| 134 |
+
- **Précision** : Données exactes pour une parcelle donnée
|
| 135 |
+
- **Comparabilité** : Benchmarking facile entre parcelles
|
| 136 |
+
- **Traçabilité** : Suivi détaillé des pratiques
|
| 137 |
+
- **Conformité** : Audit parcelle par parcelle
|
| 138 |
+
|
| 139 |
+
### Pour la Décision
|
| 140 |
+
- **Ciblage** : Recommandations spécifiques à une parcelle
|
| 141 |
+
- **Planning** : Préparation des rotations avec historique précis
|
| 142 |
+
- **Optimisation** : Ajustement des pratiques parcelle par parcelle
|
| 143 |
+
- **Réglementation** : Respect des contraintes par parcelle
|
| 144 |
+
|
| 145 |
+
## 🔮 Évolutions Futures Possibles
|
| 146 |
+
|
| 147 |
+
1. **Filtrage multiple** : Sélection de plusieurs parcelles simultanément
|
| 148 |
+
2. **Comparaison visuelle** : Graphiques côte-à-côte pour plusieurs parcelles
|
| 149 |
+
3. **Alertes personnalisées** : Notifications pour parcelles à surveiller
|
| 150 |
+
4. **Export parcellaire** : Rapports PDF par parcelle
|
| 151 |
+
5. **Cartographie** : Visualisation géospatiale des parcelles
|
| 152 |
+
|
| 153 |
+
---
|
| 154 |
+
|
| 155 |
+
*Développé pour le Hackathon CRA Bretagne 🏆*
|
| 156 |
+
*Amélioration continue de l'aide à la décision agricole*
|
app.py
CHANGED
|
@@ -470,6 +470,35 @@ class AgricultureAnalyzer:
|
|
| 470 |
years = sorted(self.df['millesime'].dropna().unique())
|
| 471 |
return [int(year) for year in years if pd.notna(year)]
|
| 472 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 473 |
def filter_data_by_year(self, year):
|
| 474 |
"""Filtre les données par année"""
|
| 475 |
if self.df is None or year is None:
|
|
@@ -478,95 +507,157 @@ class AgricultureAnalyzer:
|
|
| 478 |
year_data = self.df[self.df['millesime'] == year].copy()
|
| 479 |
return year_data
|
| 480 |
|
| 481 |
-
def
|
| 482 |
-
"""
|
| 483 |
-
|
|
|
|
| 484 |
|
| 485 |
-
|
| 486 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 487 |
|
| 488 |
stats_text = f"""
|
| 489 |
-
|
| 490 |
-
- **Nombre de parcelles**: {
|
| 491 |
-
- **Nombre d'interventions**: {len(
|
| 492 |
-
- **Surface totale**: {
|
| 493 |
-
- **Surface moyenne par parcelle**: {
|
| 494 |
|
| 495 |
## 🧪 Analyse Herbicides
|
| 496 |
"""
|
| 497 |
|
| 498 |
-
if 'familleprod' in
|
| 499 |
-
herbicides_df =
|
| 500 |
if len(herbicides_df) > 0:
|
| 501 |
stats_text += f"""
|
| 502 |
-
- **Interventions herbicides**: {len(herbicides_df)} ({(len(herbicides_df)/len(
|
| 503 |
- **Parcelles traitées**: {herbicides_df['numparcell'].nunique()}
|
| 504 |
- **Produits herbicides différents**: {herbicides_df['produit'].nunique()}
|
| 505 |
"""
|
| 506 |
else:
|
| 507 |
-
stats_text += "\n- **Aucune intervention herbicide cette
|
| 508 |
|
| 509 |
return stats_text
|
| 510 |
|
| 511 |
-
def create_year_culture_analysis(self, year):
|
| 512 |
-
"""Analyse par type de culture pour une année spécifique"""
|
| 513 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 514 |
|
| 515 |
-
if
|
| 516 |
-
fig = px.pie(title=f"❌ Aucune donnée disponible
|
| 517 |
-
fig.add_annotation(text=f"Aucune donnée pour
|
| 518 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 519 |
return fig
|
| 520 |
|
| 521 |
-
if 'libelleusag' not in
|
| 522 |
-
fig = px.pie(title=f"❌ Données de culture manquantes
|
| 523 |
fig.add_annotation(text="Colonne 'libelleusag' manquante",
|
| 524 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 525 |
return fig
|
| 526 |
|
| 527 |
-
culture_counts =
|
| 528 |
|
| 529 |
fig = px.pie(values=culture_counts.values,
|
| 530 |
names=culture_counts.index,
|
| 531 |
-
title=f"🌱 Répartition des Cultures
|
| 532 |
|
| 533 |
fig.update_layout(width=700, height=500)
|
| 534 |
return fig
|
| 535 |
|
| 536 |
-
def create_year_interventions_timeline(self, year):
|
| 537 |
-
"""Crée un graphique temporel des interventions pour une année"""
|
| 538 |
-
|
| 539 |
|
| 540 |
-
|
| 541 |
-
|
| 542 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 543 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 544 |
return fig
|
| 545 |
|
| 546 |
-
if 'datedebut' not in
|
| 547 |
# Fallback: graphique des types d'événements
|
| 548 |
-
if 'libevenem' in
|
| 549 |
-
event_counts =
|
| 550 |
fig = px.bar(x=event_counts.index,
|
| 551 |
y=event_counts.values,
|
| 552 |
-
title=f"📊 Types d'Interventions
|
| 553 |
labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
|
| 554 |
fig.update_layout(width=800, height=500)
|
| 555 |
fig.update_xaxis(tickangle=45)
|
| 556 |
return fig
|
| 557 |
else:
|
| 558 |
-
fig = px.bar(title=f"❌ Données d'intervention manquantes
|
| 559 |
fig.add_annotation(text="Colonnes de dates manquantes",
|
| 560 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 561 |
return fig
|
| 562 |
|
| 563 |
# Convertir les dates et créer le graphique temporel
|
| 564 |
try:
|
| 565 |
-
|
| 566 |
format='%d/%m/%y', errors='coerce')
|
| 567 |
-
|
| 568 |
|
| 569 |
-
monthly_counts =
|
| 570 |
monthly_counts.columns = ['mois', 'nb_interventions']
|
| 571 |
|
| 572 |
# Ajouter les noms des mois
|
|
@@ -577,7 +668,7 @@ class AgricultureAnalyzer:
|
|
| 577 |
fig = px.bar(monthly_counts,
|
| 578 |
x='mois_nom',
|
| 579 |
y='nb_interventions',
|
| 580 |
-
title=f"📅 Répartition Mensuelle des Interventions
|
| 581 |
labels={'mois_nom': 'Mois', 'nb_interventions': 'Nombre d\'interventions'})
|
| 582 |
|
| 583 |
fig.update_layout(width=800, height=500)
|
|
@@ -586,40 +677,46 @@ class AgricultureAnalyzer:
|
|
| 586 |
except Exception as e:
|
| 587 |
print(f"❌ Erreur lors de la création du graphique temporel: {e}")
|
| 588 |
# Fallback vers le graphique des événements
|
| 589 |
-
if 'libevenem' in
|
| 590 |
-
event_counts =
|
| 591 |
fig = px.bar(x=event_counts.index,
|
| 592 |
y=event_counts.values,
|
| 593 |
-
title=f"📊 Types d'Interventions
|
| 594 |
labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
|
| 595 |
fig.update_layout(width=800, height=500)
|
| 596 |
fig.update_xaxis(tickangle=45)
|
| 597 |
return fig
|
| 598 |
else:
|
| 599 |
-
fig = px.bar(title=f"❌ Erreur lors du traitement des données
|
| 600 |
return fig
|
| 601 |
|
| 602 |
-
def create_year_herbicide_analysis(self, year):
|
| 603 |
-
"""Analyse des herbicides pour une année spécifique"""
|
| 604 |
-
|
| 605 |
|
| 606 |
-
|
| 607 |
-
|
| 608 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 609 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 610 |
return fig
|
| 611 |
|
| 612 |
-
if 'familleprod' not in
|
| 613 |
-
fig = px.bar(title=f"❌ Données de produits manquantes
|
| 614 |
fig.add_annotation(text="Colonne 'familleprod' manquante",
|
| 615 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 616 |
return fig
|
| 617 |
|
| 618 |
-
herbicides_df =
|
| 619 |
|
| 620 |
if len(herbicides_df) == 0:
|
| 621 |
-
fig = px.bar(title=f"✅ Aucun herbicide utilisé
|
| 622 |
-
fig.add_annotation(text=f"Aucune intervention herbicide
|
| 623 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 624 |
return fig
|
| 625 |
|
|
@@ -629,13 +726,13 @@ class AgricultureAnalyzer:
|
|
| 629 |
fig = px.bar(x=herbicide_counts.values,
|
| 630 |
y=herbicide_counts.index,
|
| 631 |
orientation='h',
|
| 632 |
-
title=f"🧪 Top 10 Herbicides Utilisés
|
| 633 |
labels={'x': 'Nombre d\'utilisations', 'y': 'Produit'})
|
| 634 |
|
| 635 |
fig.update_layout(width=800, height=500)
|
| 636 |
return fig
|
| 637 |
else:
|
| 638 |
-
fig = px.bar(title=f"❌ Détails des produits manquants
|
| 639 |
fig.add_annotation(text="Colonne 'produit' manquante",
|
| 640 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 641 |
return fig
|
|
@@ -713,25 +810,41 @@ def create_interface():
|
|
| 713 |
""")
|
| 714 |
|
| 715 |
with gr.TabItem("📅 Analyse par Année"):
|
| 716 |
-
gr.Markdown("## Visualisation des données par année")
|
| 717 |
|
| 718 |
-
#
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
|
| 728 |
-
|
| 729 |
-
|
| 730 |
-
|
| 731 |
-
|
| 732 |
-
|
| 733 |
-
|
| 734 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 735 |
|
| 736 |
# Statistiques pour l'année sélectionnée
|
| 737 |
year_stats = gr.Markdown("")
|
|
@@ -744,8 +857,8 @@ def create_interface():
|
|
| 744 |
with gr.Row():
|
| 745 |
year_herbicide_plot = gr.Plot()
|
| 746 |
|
| 747 |
-
# Fonction de mise à jour quand l'année change
|
| 748 |
-
def update_year_analysis(selected_year):
|
| 749 |
if selected_year is None:
|
| 750 |
empty_fig = px.bar(title="❌ Aucune année sélectionnée")
|
| 751 |
return (
|
|
@@ -753,23 +866,30 @@ def create_interface():
|
|
| 753 |
empty_fig, empty_fig, empty_fig
|
| 754 |
)
|
| 755 |
|
| 756 |
-
stats = analyzer.get_year_summary_stats(selected_year)
|
| 757 |
-
culture_fig = analyzer.create_year_culture_analysis(selected_year)
|
| 758 |
-
timeline_fig = analyzer.create_year_interventions_timeline(selected_year)
|
| 759 |
-
herbicide_fig = analyzer.create_year_herbicide_analysis(selected_year)
|
| 760 |
|
| 761 |
return stats, culture_fig, timeline_fig, herbicide_fig
|
| 762 |
|
| 763 |
-
# Connecter
|
| 764 |
year_dropdown.change(
|
| 765 |
update_year_analysis,
|
| 766 |
-
inputs=[year_dropdown],
|
| 767 |
outputs=[year_stats, year_culture_plot, year_timeline_plot, year_herbicide_plot]
|
| 768 |
)
|
| 769 |
|
| 770 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 771 |
if available_years:
|
| 772 |
-
|
|
|
|
| 773 |
year_stats.value = initial_stats
|
| 774 |
year_culture_plot.value = initial_culture
|
| 775 |
year_timeline_plot.value = initial_timeline
|
|
@@ -777,10 +897,14 @@ def create_interface():
|
|
| 777 |
|
| 778 |
gr.Markdown("""
|
| 779 |
**Informations sur cet onglet:**
|
| 780 |
-
-
|
|
|
|
|
|
|
| 781 |
- 🌱 **Cultures**: Répartition des types de cultures
|
| 782 |
- 📅 **Timeline**: Répartition temporelle des interventions
|
| 783 |
- 🧪 **Herbicides**: Top 10 des herbicides utilisés
|
|
|
|
|
|
|
| 784 |
""")
|
| 785 |
|
| 786 |
with gr.TabItem("🔍 Requêtes Herbicides"):
|
|
|
|
| 470 |
years = sorted(self.df['millesime'].dropna().unique())
|
| 471 |
return [int(year) for year in years if pd.notna(year)]
|
| 472 |
|
| 473 |
+
def get_available_parcels(self):
|
| 474 |
+
"""Retourne la liste des parcelles disponibles dans les données"""
|
| 475 |
+
if self.df is None or len(self.df) == 0:
|
| 476 |
+
return []
|
| 477 |
+
|
| 478 |
+
# Créer une liste avec numéro et nom de parcelle si disponible
|
| 479 |
+
parcels_info = []
|
| 480 |
+
|
| 481 |
+
if 'nomparc' in self.df.columns:
|
| 482 |
+
# Grouper par parcelle et prendre le premier nom (en cas de doublons)
|
| 483 |
+
parcels_data = self.df.groupby('numparcell')['nomparc'].first().reset_index()
|
| 484 |
+
for _, row in parcels_data.iterrows():
|
| 485 |
+
parcel_id = str(row['numparcell'])
|
| 486 |
+
parcel_name = str(row['nomparc']) if pd.notna(row['nomparc']) else ""
|
| 487 |
+
if parcel_name and parcel_name != "nan":
|
| 488 |
+
display_name = f"{parcel_id} - {parcel_name}"
|
| 489 |
+
else:
|
| 490 |
+
display_name = parcel_id
|
| 491 |
+
parcels_info.append((display_name, parcel_id))
|
| 492 |
+
else:
|
| 493 |
+
# Seulement les numéros de parcelles
|
| 494 |
+
unique_parcels = sorted(self.df['numparcell'].dropna().unique())
|
| 495 |
+
parcels_info = [(str(p), str(p)) for p in unique_parcels]
|
| 496 |
+
|
| 497 |
+
# Ajouter l'option "Toutes les parcelles" en premier
|
| 498 |
+
parcels_info.insert(0, ("Toutes les parcelles", "ALL"))
|
| 499 |
+
|
| 500 |
+
return parcels_info
|
| 501 |
+
|
| 502 |
def filter_data_by_year(self, year):
|
| 503 |
"""Filtre les données par année"""
|
| 504 |
if self.df is None or year is None:
|
|
|
|
| 507 |
year_data = self.df[self.df['millesime'] == year].copy()
|
| 508 |
return year_data
|
| 509 |
|
| 510 |
+
def filter_data_by_parcel(self, parcel_id):
|
| 511 |
+
"""Filtre les données par parcelle"""
|
| 512 |
+
if self.df is None or parcel_id is None or parcel_id == "ALL":
|
| 513 |
+
return self.df
|
| 514 |
|
| 515 |
+
parcel_data = self.df[self.df['numparcell'] == parcel_id].copy()
|
| 516 |
+
return parcel_data
|
| 517 |
+
|
| 518 |
+
def filter_data_by_year_and_parcel(self, year, parcel_id):
|
| 519 |
+
"""Filtre les données par année et parcelle"""
|
| 520 |
+
if self.df is None:
|
| 521 |
+
return None
|
| 522 |
+
|
| 523 |
+
filtered_data = self.df.copy()
|
| 524 |
+
|
| 525 |
+
# Filtrer par année si spécifiée
|
| 526 |
+
if year is not None:
|
| 527 |
+
filtered_data = filtered_data[filtered_data['millesime'] == year]
|
| 528 |
+
|
| 529 |
+
# Filtrer par parcelle si spécifiée (et différente de "ALL")
|
| 530 |
+
if parcel_id is not None and parcel_id != "ALL":
|
| 531 |
+
# Convertir parcel_id en type approprié (gérer string/int)
|
| 532 |
+
try:
|
| 533 |
+
# Essayer de convertir en entier si c'est une chaîne
|
| 534 |
+
if isinstance(parcel_id, str) and parcel_id.isdigit():
|
| 535 |
+
parcel_id_converted = int(parcel_id)
|
| 536 |
+
else:
|
| 537 |
+
parcel_id_converted = parcel_id
|
| 538 |
+
|
| 539 |
+
filtered_data = filtered_data[filtered_data['numparcell'] == parcel_id_converted]
|
| 540 |
+
except (ValueError, TypeError):
|
| 541 |
+
# En cas d'erreur de conversion, essayer tel quel
|
| 542 |
+
filtered_data = filtered_data[filtered_data['numparcell'] == parcel_id]
|
| 543 |
+
|
| 544 |
+
return filtered_data
|
| 545 |
+
|
| 546 |
+
def get_year_summary_stats(self, year, parcel_id=None):
|
| 547 |
+
"""Retourne les statistiques de résumé pour une année spécifique et optionnellement une parcelle"""
|
| 548 |
+
filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
|
| 549 |
+
|
| 550 |
+
if filtered_data is None or len(filtered_data) == 0:
|
| 551 |
+
if parcel_id and parcel_id != "ALL":
|
| 552 |
+
return f"❌ Aucune donnée disponible pour l'année {year} et la parcelle {parcel_id}"
|
| 553 |
+
else:
|
| 554 |
+
return f"❌ Aucune donnée disponible pour l'année {year}"
|
| 555 |
+
|
| 556 |
+
# Titre dynamique selon le filtrage
|
| 557 |
+
if parcel_id and parcel_id != "ALL":
|
| 558 |
+
title = f"## 📊 Statistiques pour l'année {year} - Parcelle {parcel_id}"
|
| 559 |
+
# Obtenir le nom de la parcelle si disponible
|
| 560 |
+
if 'nomparc' in filtered_data.columns:
|
| 561 |
+
parcel_name = filtered_data['nomparc'].iloc[0] if len(filtered_data) > 0 else ""
|
| 562 |
+
if parcel_name and str(parcel_name) != "nan":
|
| 563 |
+
title = f"## 📊 Statistiques pour l'année {year} - Parcelle {parcel_id} ({parcel_name})"
|
| 564 |
+
else:
|
| 565 |
+
title = f"## 📊 Statistiques pour l'année {year}"
|
| 566 |
|
| 567 |
stats_text = f"""
|
| 568 |
+
{title}
|
| 569 |
+
- **Nombre de parcelles**: {filtered_data['numparcell'].nunique()}
|
| 570 |
+
- **Nombre d'interventions**: {len(filtered_data):,}
|
| 571 |
+
- **Surface totale**: {filtered_data['surfparc'].sum():.2f} hectares
|
| 572 |
+
- **Surface moyenne par parcelle**: {filtered_data['surfparc'].mean():.2f} hectares
|
| 573 |
|
| 574 |
## 🧪 Analyse Herbicides
|
| 575 |
"""
|
| 576 |
|
| 577 |
+
if 'familleprod' in filtered_data.columns:
|
| 578 |
+
herbicides_df = filtered_data[filtered_data['familleprod'] == 'Herbicides']
|
| 579 |
if len(herbicides_df) > 0:
|
| 580 |
stats_text += f"""
|
| 581 |
+
- **Interventions herbicides**: {len(herbicides_df)} ({(len(herbicides_df)/len(filtered_data)*100):.1f}%)
|
| 582 |
- **Parcelles traitées**: {herbicides_df['numparcell'].nunique()}
|
| 583 |
- **Produits herbicides différents**: {herbicides_df['produit'].nunique()}
|
| 584 |
"""
|
| 585 |
else:
|
| 586 |
+
stats_text += "\n- **Aucune intervention herbicide cette période**"
|
| 587 |
|
| 588 |
return stats_text
|
| 589 |
|
| 590 |
+
def create_year_culture_analysis(self, year, parcel_id=None):
|
| 591 |
+
"""Analyse par type de culture pour une année spécifique et optionnellement une parcelle"""
|
| 592 |
+
filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
|
| 593 |
+
|
| 594 |
+
# Titre dynamique
|
| 595 |
+
if parcel_id and parcel_id != "ALL":
|
| 596 |
+
title_suffix = f" - {year} (Parcelle {parcel_id})"
|
| 597 |
+
else:
|
| 598 |
+
title_suffix = f" - {year}"
|
| 599 |
|
| 600 |
+
if filtered_data is None or len(filtered_data) == 0:
|
| 601 |
+
fig = px.pie(title=f"❌ Aucune donnée disponible{title_suffix}")
|
| 602 |
+
fig.add_annotation(text=f"Aucune donnée pour cette sélection",
|
| 603 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 604 |
return fig
|
| 605 |
|
| 606 |
+
if 'libelleusag' not in filtered_data.columns:
|
| 607 |
+
fig = px.pie(title=f"❌ Données de culture manquantes{title_suffix}")
|
| 608 |
fig.add_annotation(text="Colonne 'libelleusag' manquante",
|
| 609 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 610 |
return fig
|
| 611 |
|
| 612 |
+
culture_counts = filtered_data['libelleusag'].value_counts()
|
| 613 |
|
| 614 |
fig = px.pie(values=culture_counts.values,
|
| 615 |
names=culture_counts.index,
|
| 616 |
+
title=f"🌱 Répartition des Cultures{title_suffix}")
|
| 617 |
|
| 618 |
fig.update_layout(width=700, height=500)
|
| 619 |
return fig
|
| 620 |
|
| 621 |
+
def create_year_interventions_timeline(self, year, parcel_id=None):
|
| 622 |
+
"""Crée un graphique temporel des interventions pour une année et optionnellement une parcelle"""
|
| 623 |
+
filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
|
| 624 |
|
| 625 |
+
# Titre dynamique
|
| 626 |
+
if parcel_id and parcel_id != "ALL":
|
| 627 |
+
title_suffix = f" - {year} (Parcelle {parcel_id})"
|
| 628 |
+
else:
|
| 629 |
+
title_suffix = f" - {year}"
|
| 630 |
+
|
| 631 |
+
if filtered_data is None or len(filtered_data) == 0:
|
| 632 |
+
fig = px.bar(title=f"❌ Aucune donnée disponible{title_suffix}")
|
| 633 |
+
fig.add_annotation(text=f"Aucune donnée pour cette sélection",
|
| 634 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 635 |
return fig
|
| 636 |
|
| 637 |
+
if 'datedebut' not in filtered_data.columns:
|
| 638 |
# Fallback: graphique des types d'événements
|
| 639 |
+
if 'libevenem' in filtered_data.columns:
|
| 640 |
+
event_counts = filtered_data['libevenem'].value_counts()
|
| 641 |
fig = px.bar(x=event_counts.index,
|
| 642 |
y=event_counts.values,
|
| 643 |
+
title=f"📊 Types d'Interventions{title_suffix}",
|
| 644 |
labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
|
| 645 |
fig.update_layout(width=800, height=500)
|
| 646 |
fig.update_xaxis(tickangle=45)
|
| 647 |
return fig
|
| 648 |
else:
|
| 649 |
+
fig = px.bar(title=f"❌ Données d'intervention manquantes{title_suffix}")
|
| 650 |
fig.add_annotation(text="Colonnes de dates manquantes",
|
| 651 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 652 |
return fig
|
| 653 |
|
| 654 |
# Convertir les dates et créer le graphique temporel
|
| 655 |
try:
|
| 656 |
+
filtered_data['datedebut_parsed'] = pd.to_datetime(filtered_data['datedebut'],
|
| 657 |
format='%d/%m/%y', errors='coerce')
|
| 658 |
+
filtered_data['mois'] = filtered_data['datedebut_parsed'].dt.month
|
| 659 |
|
| 660 |
+
monthly_counts = filtered_data.groupby('mois').size().reset_index()
|
| 661 |
monthly_counts.columns = ['mois', 'nb_interventions']
|
| 662 |
|
| 663 |
# Ajouter les noms des mois
|
|
|
|
| 668 |
fig = px.bar(monthly_counts,
|
| 669 |
x='mois_nom',
|
| 670 |
y='nb_interventions',
|
| 671 |
+
title=f"📅 Répartition Mensuelle des Interventions{title_suffix}",
|
| 672 |
labels={'mois_nom': 'Mois', 'nb_interventions': 'Nombre d\'interventions'})
|
| 673 |
|
| 674 |
fig.update_layout(width=800, height=500)
|
|
|
|
| 677 |
except Exception as e:
|
| 678 |
print(f"❌ Erreur lors de la création du graphique temporel: {e}")
|
| 679 |
# Fallback vers le graphique des événements
|
| 680 |
+
if 'libevenem' in filtered_data.columns:
|
| 681 |
+
event_counts = filtered_data['libevenem'].value_counts()
|
| 682 |
fig = px.bar(x=event_counts.index,
|
| 683 |
y=event_counts.values,
|
| 684 |
+
title=f"📊 Types d'Interventions{title_suffix}",
|
| 685 |
labels={'x': 'Type d\'intervention', 'y': 'Nombre'})
|
| 686 |
fig.update_layout(width=800, height=500)
|
| 687 |
fig.update_xaxis(tickangle=45)
|
| 688 |
return fig
|
| 689 |
else:
|
| 690 |
+
fig = px.bar(title=f"❌ Erreur lors du traitement des données{title_suffix}")
|
| 691 |
return fig
|
| 692 |
|
| 693 |
+
def create_year_herbicide_analysis(self, year, parcel_id=None):
|
| 694 |
+
"""Analyse des herbicides pour une année spécifique et optionnellement une parcelle"""
|
| 695 |
+
filtered_data = self.filter_data_by_year_and_parcel(year, parcel_id)
|
| 696 |
|
| 697 |
+
# Titre dynamique
|
| 698 |
+
if parcel_id and parcel_id != "ALL":
|
| 699 |
+
title_suffix = f" - {year} (Parcelle {parcel_id})"
|
| 700 |
+
else:
|
| 701 |
+
title_suffix = f" - {year}"
|
| 702 |
+
|
| 703 |
+
if filtered_data is None or len(filtered_data) == 0:
|
| 704 |
+
fig = px.bar(title=f"❌ Aucune donnée disponible{title_suffix}")
|
| 705 |
+
fig.add_annotation(text=f"Aucune donnée pour cette sélection",
|
| 706 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 707 |
return fig
|
| 708 |
|
| 709 |
+
if 'familleprod' not in filtered_data.columns:
|
| 710 |
+
fig = px.bar(title=f"❌ Données de produits manquantes{title_suffix}")
|
| 711 |
fig.add_annotation(text="Colonne 'familleprod' manquante",
|
| 712 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 713 |
return fig
|
| 714 |
|
| 715 |
+
herbicides_df = filtered_data[filtered_data['familleprod'] == 'Herbicides']
|
| 716 |
|
| 717 |
if len(herbicides_df) == 0:
|
| 718 |
+
fig = px.bar(title=f"✅ Aucun herbicide utilisé{title_suffix}")
|
| 719 |
+
fig.add_annotation(text=f"Aucune intervention herbicide pour cette sélection",
|
| 720 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 721 |
return fig
|
| 722 |
|
|
|
|
| 726 |
fig = px.bar(x=herbicide_counts.values,
|
| 727 |
y=herbicide_counts.index,
|
| 728 |
orientation='h',
|
| 729 |
+
title=f"🧪 Top 10 Herbicides Utilisés{title_suffix}",
|
| 730 |
labels={'x': 'Nombre d\'utilisations', 'y': 'Produit'})
|
| 731 |
|
| 732 |
fig.update_layout(width=800, height=500)
|
| 733 |
return fig
|
| 734 |
else:
|
| 735 |
+
fig = px.bar(title=f"❌ Détails des produits manquants{title_suffix}")
|
| 736 |
fig.add_annotation(text="Colonne 'produit' manquante",
|
| 737 |
xref="paper", yref="paper", x=0.5, y=0.5, showarrow=False)
|
| 738 |
return fig
|
|
|
|
| 810 |
""")
|
| 811 |
|
| 812 |
with gr.TabItem("📅 Analyse par Année"):
|
| 813 |
+
gr.Markdown("## Visualisation des données par année et par parcelle")
|
| 814 |
|
| 815 |
+
# Contrôles de filtrage
|
| 816 |
+
with gr.Row():
|
| 817 |
+
with gr.Column():
|
| 818 |
+
# Liste déroulante pour sélectionner l'année
|
| 819 |
+
available_years = analyzer.get_available_years()
|
| 820 |
+
if available_years:
|
| 821 |
+
default_year = available_years[-1] # Dernière année par défaut
|
| 822 |
+
year_dropdown = gr.Dropdown(
|
| 823 |
+
choices=available_years,
|
| 824 |
+
value=default_year,
|
| 825 |
+
label="🗓️ Sélectionner une année",
|
| 826 |
+
info="Choisissez l'année à analyser dans la liste déroulante"
|
| 827 |
+
)
|
| 828 |
+
else:
|
| 829 |
+
year_dropdown = gr.Dropdown(
|
| 830 |
+
choices=[],
|
| 831 |
+
value=None,
|
| 832 |
+
label="🗓️ Sélectionner une année",
|
| 833 |
+
info="Aucune année disponible"
|
| 834 |
+
)
|
| 835 |
+
|
| 836 |
+
with gr.Column():
|
| 837 |
+
# Liste déroulante pour sélectionner la parcelle
|
| 838 |
+
available_parcels = analyzer.get_available_parcels()
|
| 839 |
+
parcel_choices = [item[0] for item in available_parcels] # Noms d'affichage
|
| 840 |
+
parcel_values = [item[1] for item in available_parcels] # Valeurs réelles
|
| 841 |
+
|
| 842 |
+
parcel_dropdown = gr.Dropdown(
|
| 843 |
+
choices=list(zip(parcel_choices, parcel_values)),
|
| 844 |
+
value="ALL" if available_parcels else None,
|
| 845 |
+
label="🏠 Sélectionner une parcelle",
|
| 846 |
+
info="Choisissez une parcelle spécifique ou 'Toutes les parcelles'"
|
| 847 |
+
)
|
| 848 |
|
| 849 |
# Statistiques pour l'année sélectionnée
|
| 850 |
year_stats = gr.Markdown("")
|
|
|
|
| 857 |
with gr.Row():
|
| 858 |
year_herbicide_plot = gr.Plot()
|
| 859 |
|
| 860 |
+
# Fonction de mise à jour quand l'année ou la parcelle change
|
| 861 |
+
def update_year_analysis(selected_year, selected_parcel):
|
| 862 |
if selected_year is None:
|
| 863 |
empty_fig = px.bar(title="❌ Aucune année sélectionnée")
|
| 864 |
return (
|
|
|
|
| 866 |
empty_fig, empty_fig, empty_fig
|
| 867 |
)
|
| 868 |
|
| 869 |
+
stats = analyzer.get_year_summary_stats(selected_year, selected_parcel)
|
| 870 |
+
culture_fig = analyzer.create_year_culture_analysis(selected_year, selected_parcel)
|
| 871 |
+
timeline_fig = analyzer.create_year_interventions_timeline(selected_year, selected_parcel)
|
| 872 |
+
herbicide_fig = analyzer.create_year_herbicide_analysis(selected_year, selected_parcel)
|
| 873 |
|
| 874 |
return stats, culture_fig, timeline_fig, herbicide_fig
|
| 875 |
|
| 876 |
+
# Connecter les listes déroulantes aux mises à jour
|
| 877 |
year_dropdown.change(
|
| 878 |
update_year_analysis,
|
| 879 |
+
inputs=[year_dropdown, parcel_dropdown],
|
| 880 |
outputs=[year_stats, year_culture_plot, year_timeline_plot, year_herbicide_plot]
|
| 881 |
)
|
| 882 |
|
| 883 |
+
parcel_dropdown.change(
|
| 884 |
+
update_year_analysis,
|
| 885 |
+
inputs=[year_dropdown, parcel_dropdown],
|
| 886 |
+
outputs=[year_stats, year_culture_plot, year_timeline_plot, year_herbicide_plot]
|
| 887 |
+
)
|
| 888 |
+
|
| 889 |
+
# Initialiser avec l'année et la parcelle par défaut
|
| 890 |
if available_years:
|
| 891 |
+
default_parcel = "ALL"
|
| 892 |
+
initial_stats, initial_culture, initial_timeline, initial_herbicide = update_year_analysis(default_year, default_parcel)
|
| 893 |
year_stats.value = initial_stats
|
| 894 |
year_culture_plot.value = initial_culture
|
| 895 |
year_timeline_plot.value = initial_timeline
|
|
|
|
| 897 |
|
| 898 |
gr.Markdown("""
|
| 899 |
**Informations sur cet onglet:**
|
| 900 |
+
- 🗓️ **Filtre Année**: Sélectionnez l'année à analyser
|
| 901 |
+
- 🏠 **Filtre Parcelle**: Choisissez une parcelle spécifique ou toutes les parcelles
|
| 902 |
+
- 📊 **Statistiques**: Résumé pour la sélection (année + parcelle)
|
| 903 |
- 🌱 **Cultures**: Répartition des types de cultures
|
| 904 |
- 📅 **Timeline**: Répartition temporelle des interventions
|
| 905 |
- 🧪 **Herbicides**: Top 10 des herbicides utilisés
|
| 906 |
+
|
| 907 |
+
💡 **Utilisation**: Combinez les filtres pour explorer les données en détail
|
| 908 |
""")
|
| 909 |
|
| 910 |
with gr.TabItem("🔍 Requêtes Herbicides"):
|