a-langlais commited on
Commit
a98c212
·
1 Parent(s): 55f21af

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +87 -519
app.py CHANGED
@@ -7,32 +7,14 @@ import matplotlib.pyplot as plt
7
  import matplotlib.colors as mcolors
8
  import matplotlib.cm as cm
9
 
10
- # CHARGEMENT DES DONNEES
11
-
12
- def load_data_antenna():
13
- df_antenna = pd.read_csv(r'data/df_clean_v2.csv', engine = "pyarrow", dtype_backend = "pyarrow")
14
- df_antenna['DATE'] = df_antenna['DATE'].astype('datetime64[ns]')
15
- df_antenna['HEURE'] = pd.to_datetime(df_antenna['HEURE'], format = '%H:%M:%S').dt.time
16
- df_antenna['ANNEE'] = df_antenna['ANNEE'].astype('int')
17
- df_antenna['COMMUNE'] = df_antenna['COMMUNE'].astype('str')
18
- df_antenna['LIEU_DIT'] = df_antenna['LIEU_DIT'].astype('str')
19
- df_antenna['PRECISION_MILIEU'] = df_antenna['PRECISION_MILIEU'].astype('str')
20
- df_antenna['DEPARTEMENT'] = df_antenna['DEPARTEMENT'].astype('str')
21
- df_antenna['CODE_ESP'] = df_antenna['CODE_ESP'].astype('str')
22
- df_antenna['MASSE'] = df_antenna['MASSE'].astype('str')
23
- df_antenna['AB'] = df_antenna['AB'].astype('str')
24
- df_antenna['SEXE'] = df_antenna['SEXE'].astype('str')
25
- df_antenna['ACTION'] = df_antenna['ACTION'].astype('str')
26
- df_antenna['ID_PIT'] = df_antenna['ID_PIT'].astype('str')
27
- df_antenna['NUM_PIT'] = df_antenna['NUM_PIT'].astype('str')
28
- df_antenna['LONG_L93'] = df_antenna['LONG_L93'].astype('float')
29
- df_antenna['LAT_L93'] = df_antenna['LAT_L93'].astype('float')
30
- df_antenna['LONG_WGS'] = df_antenna['LONG_WGS'].astype('float')
31
- df_antenna['LAT_WGS'] = df_antenna['LAT_WGS'].astype('float')
32
- return df_antenna
33
 
34
- df_antenna = load_data_antenna()
35
- df_empty = pd.DataFrame()
 
 
 
36
 
37
  # Initialisation des variables d'état
38
  selected_dpt = []
@@ -40,9 +22,9 @@ selected_dpt_gant = []
40
  selected_sp = []
41
  selected_gender = []
42
  selected_sites = []
43
- selected_dates = [df_antenna['DATE'].min(), df_antenna['DATE'].max()]
44
- filtered_df = df_antenna.copy()
45
- dates_gant = [df_antenna['DATE'].min(), df_antenna['DATE'].max()]
46
 
47
  # CONTENU
48
  ## ROOT PAGE
@@ -59,212 +41,24 @@ with open("pages/page1.md", "r", encoding = "utf-8") as file:
59
  page1 = file.read()
60
 
61
  ## VISUALISATION DES DONNEES D'ANTENNES
62
- departements = sorted(df_antenna['DEPARTEMENT'].unique().tolist())
63
- species = sorted(df_antenna['CODE_ESP'].unique().tolist())
64
- genders = sorted(df_antenna['SEXE'].unique().tolist())
65
- sites = sorted(df_antenna['LIEU_DIT'].unique().tolist())
66
- dates = [df_antenna['DATE'].min(), df_antenna['DATE'].max()]
67
-
68
- def create_trajectories_map(df, width = 1200, height = 1000):
69
- if df.empty:
70
- fig = go.Figure(go.Scattermapbox())
71
- fig.update_layout(mapbox_style = "open-street-map", mapbox_zoom = 6, mapbox_center = {"lat": 46.493889, "lon": 2.602778}, height = height, width = width)
72
- return fig
73
-
74
- center_lat, center_lon = df['LAT_WGS'].mean(), df['LONG_WGS'].mean() # Centre de la carte définie sur la moyenne des coordonnées des points
75
-
76
- fig = go.Figure()
77
-
78
- # Extraction des liaisons uniques entre sites
79
- unique_connections = df.sort_values('DATE').drop_duplicates(subset = ['LAT_WGS', 'LONG_WGS'], keep = 'first')
80
-
81
- # Tracer les liaisons
82
- last_point = None
83
- for _, row in unique_connections.iterrows():
84
- if last_point is not None:
85
- fig.add_trace(go.Scattermapbox(
86
- lat = [last_point['LAT_WGS'], row['LAT_WGS']],
87
- lon = [last_point['LONG_WGS'], row['LONG_WGS']],
88
- mode = 'lines',
89
- line = dict(color = 'violet', width = 2),
90
- showlegend = False
91
- ))
92
- last_point = row
93
-
94
- # Ajout des marqueurs pour les sites
95
- sites = df.drop_duplicates(subset = ['LAT_WGS', 'LONG_WGS', 'LIEU_DIT'])
96
- for _, site in sites.iterrows():
97
- fig.add_trace(go.Scattermapbox(
98
- lat = [site['LAT_WGS']],
99
- lon = [site['LONG_WGS']],
100
- mode = 'markers+text',
101
- marker = go.scattermapbox.Marker(size=9, color='blue'),
102
- textfont = dict(color = 'black'), # Couleur du texte
103
- text = site['LIEU_DIT'],
104
- textposition = "bottom right",
105
- name = site['LIEU_DIT'], # Utiliser la bonne étiquette comme nom de site
106
- showlegend = False
107
- ))
108
-
109
- fig.update_layout(
110
- mapbox_style = "open-street-map", # Utilisation d'un fond de carte en noir et blanc
111
- mapbox_zoom = 6,
112
- mapbox_center = {"lat": center_lat, "lon": center_lon},
113
- height = height,
114
- width = width
115
- )
116
- return fig
117
-
118
- def create_transition_matrix(df, remove_self_loops=True, reduce_self_loops=False, reduction_factor=0.5):
119
- # Tri par individu et date pour suivre les transitions
120
- df = df.sort_values(by=['NUM_PIT', 'DATE'])
121
-
122
- # Créer une colonne pour le lieu précédent
123
- df['LIEU_PRECEDENT'] = df.groupby('NUM_PIT')['LIEU_DIT'].shift()
124
-
125
- # Filtrer pour obtenir seulement les transitions valides (non nulles)
126
- df_transitions = df.dropna(subset=['LIEU_PRECEDENT'])
127
-
128
- # Compter les transitions de chaque lieu vers un autre
129
- transition_counts = df_transitions.groupby(['LIEU_PRECEDENT', 'LIEU_DIT']).size().reset_index(name='count')
130
-
131
- # Retirer les transitions où source == target si demandé
132
- if remove_self_loops:
133
- transition_counts = transition_counts[transition_counts['LIEU_PRECEDENT'] != transition_counts['LIEU_DIT']]
134
-
135
- # Réduire le poids des transitions de recontrôle (si demandé)
136
- if reduce_self_loops:
137
- transition_counts.loc[transition_counts['LIEU_PRECEDENT'] == transition_counts['LIEU_DIT'], 'count'] *= reduction_factor
138
-
139
- # Construire une matrice de transition
140
- lieux = sorted(set(df['LIEU_DIT'].unique()) | set(df['LIEU_PRECEDENT'].dropna().unique()))
141
- transition_matrix = transition_counts.pivot(index='LIEU_PRECEDENT', columns='LIEU_DIT', values='count').fillna(0)
142
-
143
- return transition_matrix, lieux
144
-
145
- def process_transition_matrix(transition_matrix, df, threshold=0):
146
- # Transformer la matrice en table de connexions
147
- transition_table = transition_matrix.stack().reset_index()
148
- transition_table.columns = ['source', 'target', 'count']
149
-
150
- # Assurer l'ordre alphabétique des paires (source, target) pour un regroupement correct
151
- transition_table['site_pair'] = transition_table.apply(lambda row: tuple(sorted([row['source'], row['target']])), axis=1)
152
-
153
- # Grouper par paire de sites et sommer les counts
154
- transition_table = transition_table.groupby('site_pair', as_index=False).agg(count=('count', 'sum'))
155
-
156
- # Séparer les paires de sites dans des colonnes distinctes
157
- transition_table[['source', 'target']] = pd.DataFrame(transition_table['site_pair'].tolist(), index=transition_table.index)
158
 
159
- # Filtrer pour ne garder que les connexions avec au moins un certain nombre d'occurrences
160
- transition_table = transition_table[transition_table['count'] > threshold]
161
-
162
- # Obtenir les limites pour normaliser les valeurs de count
163
- mean_count = transition_table['count'].mean()
164
- std_count = transition_table['count'].std()
165
-
166
- # Normaliser les valeurs de count entre 0 et 1
167
- transition_table['normalized_count'] = (transition_table['count'] - mean_count) / std_count
168
-
169
- # Calculate quantiles for count
170
- quantiles = np.quantile(transition_table['count'], [0, 0.25, 0.5, 0.75, 1])
171
-
172
- # Create a custom colormap
173
- cmap = mcolors.LinearSegmentedColormap.from_list(
174
- "CustomBlackRed",
175
- ["#D3D3D3", "#606060", "#FFA500", "#FF7F50", "#FF0000"]
176
- )
177
-
178
- # Calculate colors based on quantiles
179
- def calculate_color(count):
180
- if count <= quantiles[0]:
181
- return mcolors.to_hex(cmap(0))
182
- elif count <= quantiles[1]:
183
- return mcolors.to_hex(cmap(0.25))
184
- elif count <= quantiles[2]:
185
- return mcolors.to_hex(cmap(0.5))
186
- elif count <= quantiles[3]:
187
- return mcolors.to_hex(cmap(0.75))
188
- else:
189
- return mcolors.to_hex(cmap(1))
190
-
191
- transition_table['color'] = transition_table['count'].apply(calculate_color)
192
-
193
- # Fusionner les coordonnées du df dans transition_table pour source et target
194
- coords = df[['LIEU_DIT', 'LAT_WGS', 'LONG_WGS']].drop_duplicates()
195
- coords = coords.set_index('LIEU_DIT')
196
-
197
- transition_table = transition_table.merge(coords, left_on='source', right_index=True)
198
- transition_table = transition_table.merge(coords, left_on='target', right_index=True, suffixes=('_source', '_target'))
199
-
200
- return transition_table
201
-
202
- def create_trajectories_map_from_matrix(df, transition_table, width=1200, height=1000):
203
- fig = go.Figure()
204
-
205
- # Créer une trace distincte pour chaque segment de ligne avec la couleur appropriée
206
- for index, row in transition_table.iterrows():
207
- fig.add_trace(go.Scattermapbox(
208
- lat=[row['LAT_WGS_source'], row['LAT_WGS_target']],
209
- lon=[row['LONG_WGS_source'], row['LONG_WGS_target']],
210
- mode='lines',
211
- line=dict(
212
- color=row['color'],
213
- width=1
214
- ),
215
- showlegend=False
216
- ))
217
-
218
- # Ajouter tous les marqueurs pour les lieux d'un seul coup
219
- coords = df[['LIEU_DIT', 'LAT_WGS', 'LONG_WGS']].drop_duplicates()
220
- fig.add_trace(go.Scattermapbox(
221
- lat=coords['LAT_WGS'],
222
- lon=coords['LONG_WGS'],
223
- mode='markers+text',
224
- marker=go.scattermapbox.Marker(
225
- size=6,
226
- color='#8467D7', # Light violet
227
- ),
228
- text=coords['LIEU_DIT'],
229
- textposition="top right",
230
- textfont=dict(
231
- size=12,
232
- color='black'
233
- ),
234
- hoverinfo='text',
235
- showlegend=False
236
- ))
237
-
238
- # Définir le centre de la carte
239
- center_lat, center_lon = df['LAT_WGS'].mean(), df['LONG_WGS'].mean()
240
-
241
- fig.update_layout(
242
- mapbox_style="open-street-map",
243
- mapbox_zoom=6,
244
- mapbox_center={"lat": center_lat, "lon": center_lon},
245
- font=dict(color='black'),
246
- height=height,
247
- width=width
248
- )
249
-
250
- return fig
251
-
252
- transition_matrix, labels = create_transition_matrix(df_antenna)
253
- transition_table = process_transition_matrix(transition_matrix, df_antenna)
254
- create_trajectories_map_from_matrix(df_antenna, transition_table)
255
-
256
- map_section = create_trajectories_map(df_empty)
257
 
258
  with open("pages/page2.md", "r", encoding = "utf-8") as file:
259
  page2 = file.read()
260
 
261
- def refresh_button(state):
262
- df_filtered = df_antenna.copy()
 
263
 
264
  # Filtrer par département d'origine
265
  if state.selected_dpt:
266
- equipped_pit = df_filtered[(df_filtered['ACTION'] == 'T') &
267
- (df_filtered['DEPARTEMENT'].isin(state.selected_dpt))]['NUM_PIT'].unique()
268
  df_filtered = df_filtered[df_filtered['NUM_PIT'].isin(equipped_pit)]
269
 
270
  # Filtrer par espèce
@@ -277,8 +71,7 @@ def refresh_button(state):
277
 
278
  # Filtrer par site d'origine
279
  if state.selected_sites:
280
- site_pit = df_filtered[(df_filtered['ACTION'] == 'T') &
281
- (df_filtered['LIEU_DIT'].isin(state.selected_sites))]['NUM_PIT'].unique()
282
  df_filtered = df_filtered[df_filtered['NUM_PIT'].isin(site_pit)]
283
 
284
  # Filtrer par intervalle de dates
@@ -288,100 +81,17 @@ def refresh_button(state):
288
  df_filtered = df_filtered[(df_filtered['DATE'] >= start_date) & (df_filtered['DATE'] <= end_date)]
289
 
290
  # Rafraichir la carte
291
- transition_matrix_filtered, labels_filtered = create_transition_matrix(df_filtered)
292
- transition_table_filtered = process_transition_matrix(transition_matrix_filtered, df_filtered)
293
- state.map_section = create_trajectories_map_from_matrix(df_filtered, transition_table_filtered)
294
 
295
  ## PHENOLOGIES DES SITES
296
-
297
- def gant_diagram(df_original):
298
- # Créer une copie du DataFrame original par sécurité
299
- df_filtered = df_original[df_original['ACTION'] == "C"].copy()
300
-
301
- # Convertir la colonne DATE en datetime et localiser en UTC
302
- df_filtered['DATE'] = pd.to_datetime(df_filtered['DATE'])
303
- df_filtered['DATE'] = df_filtered['DATE'].dt.tz_localize('UTC')
304
-
305
- # Trier les valeurs par DATE
306
- df_filtered = df_filtered.sort_values(by = 'DATE', ascending = True)
307
-
308
- # Ajouter des colonnes pour les visites précédentes et les nouvelles visites
309
- df_filtered['Prev_DATE'] = df_filtered.groupby(['LIEU_DIT'])['DATE'].shift(1)
310
- df_filtered['New_Visit'] = (df_filtered['DATE'] - df_filtered['Prev_DATE']).dt.days > 1
311
- df_filtered['New_Visit'].fillna(True, inplace = True)
312
- df_filtered['Visit_ID'] = df_filtered.groupby(['LIEU_DIT'])['New_Visit'].cumsum()
313
-
314
- # Supprimer les LIEU_DIT qui n'ont aucune visite détectée
315
- df_filtered = df_filtered.dropna(subset = ['Visit_ID'])
316
-
317
- # Calculer min/max dates pour chaque visite
318
- result = df_filtered.groupby(['LIEU_DIT', 'Visit_ID']).agg(
319
- Start = pd.NamedAgg(column = 'DATE', aggfunc = 'min'),
320
- Finish = pd.NamedAgg(column = 'DATE', aggfunc = 'max'),
321
- Departement = pd.NamedAgg(column = 'DEPARTEMENT', aggfunc = 'first')
322
- ).reset_index()
323
-
324
- # Supprimer les LIEU_DIT qui n'ont pas de dates valides
325
- result = result.dropna(subset = ['Start', 'Finish'])
326
-
327
- # Filtrer les LIEU_DIT qui ont des résultats
328
- result = result[result['Finish'] > result['Start']]
329
-
330
- # Trier les sites par ordre alphabétique et les départements pour la légende
331
- result = result.sort_values(by = ['Departement', 'LIEU_DIT'])
332
- result['Start'] = pd.to_datetime(result['Start'])
333
- result['Finish'] = pd.to_datetime(result['Finish'])
334
-
335
- # Déterminer la hauteur de la figure en fonction du nombre de sites
336
- num_sites = result['LIEU_DIT'].nunique()
337
- height_per_site = 50 # Hauteur allouée par site en pixels
338
- base_height = 100 # Hauteur de base pour la figure
339
- max_height = 20000 # Hauteur maximale de la figure
340
- fig_height = min(base_height + num_sites * height_per_site, max_height)
341
-
342
- # Créer le diagramme de Gantt
343
- fig = px.timeline(
344
- result,
345
- x_start = 'Start',
346
- x_end = 'Finish',
347
- y = 'LIEU_DIT',
348
- color = 'Departement', # Changer la couleur en fonction du département
349
- color_discrete_sequence = px.colors.qualitative.Plotly,
350
- labels = {'LIEU_DIT': 'Site'},
351
- title = 'Présence sur chaque site'
352
- )
353
-
354
- # Ajout de lignes verticales pour chaque 1er janvier
355
- start_year = result['Start'].dt.year.min()
356
- end_year = result['Finish'].dt.year.max()
357
- for year in range(start_year, end_year + 1):
358
- fig.add_vline(
359
- x = pd.Timestamp(f'{year}-01-01'),
360
- line = dict(color = 'grey', dash = 'dash', width = 1)
361
- )
362
-
363
- fig.update_layout(legend = dict(traceorder = "normal"), margin = dict(l = 300))
364
-
365
- # Mise à jour des étiquettes et de l'orientation de l'axe des ordonnées
366
- fig.update_yaxes(categoryorder = 'total ascending') # Tri les sites par date de début
367
- fig.update_layout(
368
- xaxis_title = 'Date',
369
- yaxis_title = 'Site',
370
- xaxis = dict(tickformat = '%Y-%m-%d'), # Format des dates sur l'axe X pour plus de clarté
371
- height = fig_height,
372
- legend = dict(bgcolor = 'rgba(0, 0, 0, 0)')
373
- )
374
-
375
- return fig
376
-
377
- gant_global = gant_diagram(df_antenna)
378
 
379
  with open("pages/page3.md", "r", encoding = "utf-8") as file:
380
  page3 = file.read()
381
 
 
382
  def update_gant(state):
383
- df_filtered_gant = df_antenna.copy()
384
- df_filtered_gant = df_filtered_gant[df_filtered_gant['ACTION'] == "C"]
385
 
386
  # Convertir les dates sélectionnées en objets Timestamp
387
  if state.selected_dpt_gant:
@@ -397,216 +107,72 @@ def update_gant(state):
397
  state.gant_global = gant_diagram(df_filtered_gant)
398
 
399
  ## STATISTIQUES ANALYTIQUES
 
 
 
 
 
 
 
 
400
 
401
- def detection_by_year(df_antenna):
402
- df_antenna['YEAR'] = df_antenna['DATE'].dt.year
403
- grouped_data = df_antenna.groupby(['YEAR', 'CODE_ESP']).size().reset_index(name = 'Detections')
404
-
405
- fig = px.bar(grouped_data, x = 'YEAR', y = 'Detections', color = 'CODE_ESP',
406
- labels = {'Detections': 'Nombre de détections', 'CODE_ESP': 'Espèce'},
407
- )
408
-
409
- fig.update_layout(
410
- height = 400,
411
- width = 400,
412
- yaxis_title = 'Nombre de détections',
413
- xaxis_title = None,
414
- xaxis = {'type': 'category', 'tickmode': 'linear'}, # Afficher toutes les années
415
- barmode = 'stack',
416
- legend = dict(
417
- bgcolor = 'rgba(0, 0, 0, 0)',
418
- orientation = 'h', # Légende horizontale
419
- x = 0.5, # Centrer la légende horizontalement
420
- y = -0.2, # Placer la légende en dessous de la figure
421
- xanchor = 'center', # Ancrer la légende au centre
422
- yanchor = 'top' # Ancrer la légende au-dessus de l'axe x
423
- ),
424
- margin = dict(b = 100)
425
- )
426
-
427
- return fig
428
-
429
- def capture_by_year(df_antenna):
430
- df_filtre = df_antenna[df_antenna['ACTION'] == 'T']
431
- df_filtre['YEAR'] = df_filtre['DATE'].dt.year
432
- df_grouped = df_filtre.groupby(['YEAR', 'CODE_ESP'])['NUM_PIT'].nunique().reset_index()
433
- df_grouped.rename(columns = {'NUM_PIT': 'Nombre d\'individus uniques'}, inplace = True)
434
-
435
- fig = px.bar(df_grouped, x = 'YEAR', y = 'Nombre d\'individus uniques', color = 'CODE_ESP',
436
- labels = {'Nombre d\'individus uniques': 'Nombre d\'individus uniques', 'CODE_ESP': 'Espèce'}
437
- )
438
-
439
- fig.update_layout(
440
- height = 400,
441
- width = 400,
442
- yaxis_title = 'Nombre d\'individus uniques',
443
- xaxis_title = None,
444
- xaxis = {'type': 'category', 'tickmode': 'linear'}, # Afficher toutes les années
445
- legend = dict(
446
- bgcolor ='rgba(0, 0, 0, 0)',
447
- orientation = 'h', # Légende horizontale
448
- x = 0.5, # Centrer la légende horizontalement
449
- y = -0.2, # Placer la légende en dessous de la figure
450
- xanchor = 'center', # Ancrer la légende au centre
451
- yanchor = 'top' # Ancrer la légende au-dessus de l'axe x
452
- ),
453
- margin = dict(b = 100)
454
- )
455
-
456
- return fig
457
-
458
- def control_by_year(df_antenna):
459
- df_filtre = df_antenna[df_antenna['ACTION'] == 'C']
460
- df_filtre['YEAR'] = df_filtre['DATE'].dt.year
461
- df_grouped = df_filtre.groupby(['YEAR', 'CODE_ESP'])['NUM_PIT'].nunique().reset_index()
462
- df_grouped.rename(columns = {'NUM_PIT': 'Nombre d\'individus'}, inplace = True)
463
-
464
- fig = px.bar(df_grouped, x = 'YEAR', y = 'Nombre d\'individus', color = 'CODE_ESP',
465
- labels = {'Nombre d\'individus': 'Nombre d\'individus', 'CODE_ESP': 'Espèce'},
466
- )
467
-
468
- fig.update_layout(
469
- height = 400,
470
- width = 400,
471
- yaxis_title = 'Nombre d\'individus',
472
- xaxis_title = None,
473
- xaxis = {'type': 'category', 'tickmode': 'linear'}, # Afficher toutes les années
474
- legend = dict(
475
- bgcolor = 'rgba(0, 0, 0, 0)',
476
- orientation = 'h', # Légende horizontale
477
- x = 0.5, # Centrer la légende horizontalement
478
- y = -0.2, # Placer la légende en dessous de la figure
479
- xanchor = 'center', # Ancrer la légende au centre
480
- yanchor = 'top' # Ancrer la légende au-dessus de l'axe x
481
- ),
482
- margin = dict(b = 100)
483
- )
484
-
485
- return fig
486
-
487
- def detection_frequencies(df_antenna):
488
- df_antenna['MONTH_DAY'] = df_antenna['DATE'].dt.strftime('%m-%d')
489
- global_freq = df_antenna.groupby('MONTH_DAY').size().reset_index(name = 'Global Detections')
490
-
491
- # Calculer les fréquences par site
492
- site_freq = df_antenna.groupby(['MONTH_DAY', 'LIEU_DIT']).size().reset_index(name = 'Detections')
493
- sites = site_freq['LIEU_DIT'].unique()
494
-
495
- # Préparer l'ordre chronologique
496
- months_days = pd.date_range('2021-01-01', '2021-12-31').strftime('%m-%d')
497
- global_freq['MONTH_DAY'] = pd.Categorical(global_freq['MONTH_DAY'], categories = months_days, ordered = True)
498
- global_freq = global_freq.sort_values('MONTH_DAY')
499
-
500
- # Création du graphique
501
- fig = go.Figure()
502
-
503
- # Ajouter la courbe globale
504
- fig.add_trace(go.Scatter(x = global_freq['MONTH_DAY'], y = global_freq['Global Detections'],
505
- mode = 'lines', name = 'Global'))
506
-
507
- # Ajouter une courbe pour chaque site
508
- for site in sites:
509
- site_data = site_freq[site_freq['LIEU_DIT'] == site]
510
- site_data['MONTH_DAY'] = pd.Categorical(site_data['MONTH_DAY'], categories = months_days, ordered = True)
511
- site_data = site_data.sort_values('MONTH_DAY')
512
- fig.add_trace(go.Scatter(x = site_data['MONTH_DAY'], y = site_data['Detections'],
513
- mode = 'lines', name = site))
514
-
515
- # Mise à jour du layout
516
- fig.update_layout(
517
- xaxis_title = 'Jour de l\'année',
518
- yaxis_title = 'Nombre de détections',
519
- xaxis = dict(type = 'category', categoryorder = 'array', categoryarray = [md for md in months_days]),
520
- legend = dict(bgcolor = 'rgba(0, 0, 0, 0)'),
521
- #yaxis = dict(range = [0, global_freq['Global Detections'].max() + 10])
522
- )
523
- return fig
524
-
525
- def pie_controled(df_antenna):
526
- species_counts = df_antenna[df_antenna['ACTION'] == 'C']['CODE_ESP'].value_counts().reset_index()
527
- species_counts.columns = ['Species', 'Count']
528
-
529
- # Créer un diagramme circulaire
530
- fig = px.pie(species_counts,
531
- values = 'Count', names = 'Species',
532
- color_discrete_sequence = px.colors.qualitative.Pastel,
533
- hole = 0.5,)
534
-
535
- # Personnalisation supplémentaire
536
- fig.update_layout(legend_title_text = 'Espèce',
537
- showlegend = True,
538
- legend = dict(bgcolor = 'rgba(0, 0, 0, 0)')
539
- )
540
- return fig
541
-
542
- def pie_marked(df_antenna):
543
- marked_species = df_antenna[df_antenna['ACTION'] == 'T']
544
- species_counts = marked_species['CODE_ESP'].value_counts().reset_index()
545
- species_counts.columns = ['Species', 'Count']
546
-
547
- # Créer un diagramme circulaire
548
- fig = px.pie(species_counts,
549
- values = 'Count', names = 'Species',
550
- color_discrete_sequence = px.colors.qualitative.Pastel,
551
- hole = 0.5)
552
-
553
- # Personnalisation supplémentaire
554
- fig.update_layout(legend_title_text = 'Espèce',
555
- showlegend = True,
556
- legend = dict(bgcolor = 'rgba(0, 0, 0, 0)')
557
- )
558
- return fig
559
-
560
- def top_detection(df_antenna):
561
- df_antenna['NUM_PIT'] = "n° " + df_antenna['NUM_PIT'].astype(str)
562
-
563
- # Obtenir les dix catégories les plus fréquentes avec leurs occurrences
564
- top_categories = df_antenna['NUM_PIT'].value_counts().head(10)
565
-
566
- # Créer un DataFrame à partir des dix premières catégories
567
- df_top_categories = pd.DataFrame({'NUM_PIT': top_categories.index, 'Occurrences': top_categories.values})
568
- df_top_categories = df_top_categories.sort_values(by = 'Occurrences', ascending = False)
569
-
570
- # Créer le diagramme en barres horizontales
571
- fig = px.bar(df_top_categories, x = 'Occurrences', y = 'NUM_PIT', orientation = 'h',
572
- labels = {'Occurrences': "Nombre d'occurrences", 'NUM_PIT': ''},
573
- color = 'NUM_PIT', color_discrete_sequence = px.colors.qualitative.Set3)
574
 
575
- # Ajuster la hauteur en fonction du nombre de catégories (avec une hauteur minimale de 400 pixels)
576
- bar_height = 50 # Hauteur de chaque barre
577
- min_height = 400 # Hauteur minimale du graphique
578
- calculated_height = max(min_height, len(df_top_categories) * bar_height)
579
 
580
- # Mettre à jour la mise en page avec des marges ajustées
581
- fig.update_layout(
582
- height = calculated_height, # Hauteur dynamique
583
- showlegend = False,
584
- margin = dict(l = 200), # Augmenter la marge gauche pour mieux lire les annotations
585
- legend = dict(bgcolor = 'rgba(0, 0, 0, 0)')
586
- )
587
 
588
- return fig
589
-
590
- # Initialisation de tous les plots
591
- plot_detection_year = detection_by_year(df_antenna) # Barplot du nombre de détections par an et par espèce
592
- plot_capture_year = capture_by_year(df_antenna) # Barplot du nombre de captures par an et par espèces
593
- plot_control_year = control_by_year(df_antenna) # Barplot du nombre de contrôles par an et par espèces
594
- plot_frequencies = detection_frequencies(df_antenna) # Courbes de fréquences de détections par jour de l'année et par site
595
- plot_pie_controled = pie_controled(df_antenna) # Pieplot des individus contrôlés
596
- plot_pie_marked = pie_marked(df_antenna) # Pieplot des individus marqués
597
- plot_top_detection = top_detection(df_antenna) # Barplot horizontal des 10 individus les plus détectés
598
- table_plot_raw = process_transition_matrix(transition_matrix, df_antenna, threshold = 0)
599
- transition_table_plot = table_plot_raw[['source', 'target', 'count']].sort_values(by = 'count', ascending = False) # Table des trajectoires
600
 
601
  # Initialisation des variables à plot
602
- total_captured = len(df_antenna[(df_antenna['ACTION'] == 'T') | (df_antenna['ACTION'] == 'M')]) # Individus capturés
603
- total_recaptured = df_antenna[df_antenna['ACTION'] == 'C']['NUM_PIT'].nunique() # Individus contrôlés
604
- total_marked = df_antenna[df_antenna['ACTION'] == 'T']['NUM_PIT'].nunique() # Individus marqués
605
- sites_capture = df_antenna[df_antenna['ACTION'] == 'T']['LIEU_DIT'].nunique() # Sites capturés au moins une fois
606
- sites_antennes = df_antenna['LIEU_DIT'].nunique() # Sites contrôlés au moins une fois
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
607
 
608
- with open("pages/page4.md", "r", encoding = "utf-8") as file:
609
- page4 = file.read()
610
 
611
  # DEMARRAGE DE L'APPLICATION
612
  pages = {
@@ -614,8 +180,10 @@ pages = {
614
  "presentation": page1,
615
  "antennes": page2,
616
  "phenologie": page3,
617
- "statistiques": page4
 
618
  }
619
 
 
620
  gui = Gui(pages = pages, css_file = "assets/styles.css")
621
  gui.run(host = '0.0.0.0', port = 7860, use_session = True) # use_session = True pour permettre l'usage de plusieurs users en même temps
 
7
  import matplotlib.colors as mcolors
8
  import matplotlib.cm as cm
9
 
10
+ from functions import *
11
+ from dashboard import *
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
 
13
+ # CHARGEMENT DES DONNEES
14
+ df_controls, df_individus, df_sites, df_distances, df_mapping = load_data_antenna()
15
+ liste_sites_antennes = ["Brelouze", "Mairie d'Annepont", "Grotte de Loubeau", "Le Plessis", "Puy-Chenin", "Cézelle", "La Bourtière", "Goizet (W)", "Château de Gagemont", "Faye-L'Abbesse - Bourg", "Guibaud", "Cave Billard", "Grotte de Boisdichon", "Les Roches", "Barrage de l'Aigle", "Gouffre de la Fage",
16
+ "La Brumaudière", "Château de Verteuil", "Les Dames", "Château de Hautefort", "Les Tours de Merle", "Le Petit Pin", "Maison Brousse", "Caves de Laubenheimer", "Château de Villandraut", "Tunnel ferroviaire", "Grotte de la carrière", "Centrale hydroélectrique de Claredent", "Fermette des Nobis",
17
+ "Beauregard", "Grotte de la Deveze", "Petexaenea", "Gouffre de Bexanka", "Mikelauenzilo"]
18
 
19
  # Initialisation des variables d'état
20
  selected_dpt = []
 
22
  selected_sp = []
23
  selected_gender = []
24
  selected_sites = []
25
+ selected_dates = [df_controls['DATE'].min(), df_controls['DATE'].max()]
26
+ dates_gant = [df_controls['DATE'].min(), df_controls['DATE'].max()]
27
+ df_empty = pd.DataFrame()
28
 
29
  # CONTENU
30
  ## ROOT PAGE
 
41
  page1 = file.read()
42
 
43
  ## VISUALISATION DES DONNEES D'ANTENNES
44
+ departements = sorted(df_controls['DEPARTEMENT'].unique().tolist())
45
+ species = sorted(df_controls['CODE_ESP'].unique().tolist())
46
+ genders = sorted(df_controls['SEXE'].dropna().unique().tolist())
47
+ sites = sorted(df_controls['LIEU_DIT'].unique().tolist())
48
+ dates = [df_controls['DATE'].min(), df_controls['DATE'].max()]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
49
 
50
+ m = generate_map(df_empty, df_sites)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
  with open("pages/page2.md", "r", encoding = "utf-8") as file:
53
  page2 = file.read()
54
 
55
+ # Callback de la carte
56
+ def refresh_map_button(state):
57
+ df_filtered = df_distances.copy()
58
 
59
  # Filtrer par département d'origine
60
  if state.selected_dpt:
61
+ equipped_pit = df_filtered[(df_filtered['DPT_DEPART'].isin(state.selected_dpt)) | (df_filtered['DPT_ARRIVEE'].isin(state.selected_dpt))]['NUM_PIT'].unique()
 
62
  df_filtered = df_filtered[df_filtered['NUM_PIT'].isin(equipped_pit)]
63
 
64
  # Filtrer par espèce
 
71
 
72
  # Filtrer par site d'origine
73
  if state.selected_sites:
74
+ site_pit = df_filtered[(df_filtered['SITE_DEPART'].isin(state.selected_sites)) | (df_filtered['SITE_ARRIVEE'].isin(state.selected_sites))]['NUM_PIT'].unique()
 
75
  df_filtered = df_filtered[df_filtered['NUM_PIT'].isin(site_pit)]
76
 
77
  # Filtrer par intervalle de dates
 
81
  df_filtered = df_filtered[(df_filtered['DATE'] >= start_date) & (df_filtered['DATE'] <= end_date)]
82
 
83
  # Rafraichir la carte
84
+ state.m = generate_map(df_filtered, df_sites)
 
 
85
 
86
  ## PHENOLOGIES DES SITES
87
+ gant_global = gant_diagram(df_controls)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  with open("pages/page3.md", "r", encoding = "utf-8") as file:
90
  page3 = file.read()
91
 
92
+ # Callbacks du diagramme de Gant
93
  def update_gant(state):
94
+ df_filtered_gant = df_controls.copy()
 
95
 
96
  # Convertir les dates sélectionnées en objets Timestamp
97
  if state.selected_dpt_gant:
 
107
  state.gant_global = gant_diagram(df_filtered_gant)
108
 
109
  ## STATISTIQUES ANALYTIQUES
110
+ # Initialisation de tous les plots
111
+ plot_detection_year = detection_by_year(df_controls) # Barplot du nombre de détections par an et par espèce
112
+ plot_capture_year = capture_by_year(df_individus) # Barplot du nombre de captures par an et par espèces
113
+ plot_control_year = control_by_year(df_controls) # Barplot du nombre de contrôles par an et par espèces
114
+ plot_frequencies = detection_frequencies(df_controls) # Courbes de fréquences de détections par jour de l'année et par site
115
+ plot_pie_controled = pie_controled(df_controls) # Pieplot des individus contrôlés
116
+ plot_pie_marked = pie_marked(df_individus) # Pieplot des individus marqués
117
+ plot_top_detection = top_detection(df_controls) # Barplot horizontal des 10 individus les plus détectés
118
 
119
+ # Initialisation des variables à plot
120
+ total_recaptured = df_controls['NUM_PIT'].nunique() # Individus contrôlés
121
+ total_marked = df_individus['NUM_PIT'].nunique() # Individus marqués
122
+ sites_capture = df_individus['LIEU_DIT'].nunique() # Sites capturés au moins une fois
123
+ sites_antennes = df_sites['LIEU_DIT'].nunique() # Sites contrôlés au moins une fois
124
+ transition_table_plot = df_distances[['CODE_ESP', 'DATE', 'SITE_DEPART', 'DATE_ARRIVEE', 'SITE_ARRIVEE', 'DISTANCE']].sort_values(by='DISTANCE', ascending = False)
125
+ transition_table_plot['DISTANCE'] = transition_table_plot['DISTANCE'].round(2)
126
+ transition_table_plot = transition_table_plot.rename(columns = {'DATE':'DATE_DEPART'})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
+ with open("pages/page4.md", "r", encoding = "utf-8") as file:
129
+ page4 = file.read()
 
 
130
 
131
+ ## FICHE SITE
132
+ # Initialisation du sélecteur et des plots
133
+ selection_fiche = ['Brelouze']
134
+ df_controls_fiche = df_controls[df_controls['LIEU_DIT'].isin(selection_fiche)]
135
+ df_individus_fiche = df_individus[df_individus['LIEU_DIT'].isin(selection_fiche)]
136
+ df_distances_fiche = df_distances[(df_distances['SITE_DEPART'].isin(selection_fiche)) | (df_distances['SITE_ARRIVEE'].isin(selection_fiche))]
 
137
 
138
+ plot_detection_year_fiche = detection_by_year(df_controls_fiche) # Barplot du nombre de détections par an et par espèce
139
+ plot_capture_year_fiche = capture_by_year(df_individus_fiche) # Barplot du nombre de captures par an et par espèces
140
+ plot_control_year_fiche = control_by_year(df_controls_fiche) # Barplot du nombre de contrôles par an et par espèces
141
+ plot_frequencies_fiche = detection_frequencies(df_controls_fiche) # Courbes de fréquences de détections par jour de l'année et par site
142
+ plot_pie_controled_fiche = pie_controled(df_controls_fiche) # Pieplot des individus contrôlés
143
+ plot_pie_marked_fiche = pie_marked(df_individus_fiche) # Pieplot des individus marqués
 
 
 
 
 
 
144
 
145
  # Initialisation des variables à plot
146
+ total_recaptured_fiche = df_controls_fiche['NUM_PIT'].nunique() # Individus contrôlés
147
+ total_marked_fiche = df_individus_fiche['NUM_PIT'].nunique() # Individus marqués
148
+ sites_antennes_fiche = df_sites['LIEU_DIT'].nunique() # Sites contrôlés au moins une fois
149
+ map_fiche = generate_map(df_distances_fiche, df_sites) # Map du site et des connexions
150
+ gant_diagram_fiche = gant_diagram_concat(df_controls_fiche)
151
+
152
+ def update_fiche(state):
153
+ # Filtrage en fonction de la sélection
154
+ selected_sites = [state.selection_fiche]
155
+ df_controls_fiche = df_controls[df_controls['LIEU_DIT'].isin(selected_sites)]
156
+ df_individus_fiche = df_individus[df_individus['LIEU_DIT'].isin(selected_sites)]
157
+ df_distances_fiche = df_distances[(df_distances['SITE_DEPART'].isin(selected_sites)) | (df_distances['SITE_ARRIVEE'].isin(selected_sites))]
158
+
159
+ # Mise à jour des visualisations
160
+ state.plot_detection_year_fiche = detection_by_year(df_controls_fiche)
161
+ state.plot_capture_year_fiche = capture_by_year(df_individus_fiche)
162
+ state.plot_control_year_fiche = control_by_year(df_controls_fiche)
163
+ state.plot_frequencies_fiche = detection_frequencies(df_controls_fiche)
164
+ state.plot_pie_controled_fiche = pie_controled(df_controls_fiche)
165
+ state.plot_pie_marked_fiche = pie_marked(df_individus_fiche)
166
+ state.map_fiche = generate_map(df_distances_fiche, df_sites)
167
+ state.gant_diagram_fiche = gant_diagram_concat(df_controls_fiche)
168
+
169
+ # Mise à jour des variables globales
170
+ state.total_recaptured_fiche = df_controls_fiche['NUM_PIT'].nunique()
171
+ state.total_marked_fiche = df_individus_fiche['NUM_PIT'].nunique()
172
+ state.sites_antennes_fiche = df_sites['LIEU_DIT'].nunique()
173
 
174
+ with open("pages/page5.md", "r", encoding = "utf-8") as file:
175
+ page5 = file.read()
176
 
177
  # DEMARRAGE DE L'APPLICATION
178
  pages = {
 
180
  "presentation": page1,
181
  "antennes": page2,
182
  "phenologie": page3,
183
+ "statistiques": page4,
184
+ "fiche_site": page5
185
  }
186
 
187
+ Gui.register_content_provider(Map, expose_folium)
188
  gui = Gui(pages = pages, css_file = "assets/styles.css")
189
  gui.run(host = '0.0.0.0', port = 7860, use_session = True) # use_session = True pour permettre l'usage de plusieurs users en même temps