jaimalklyde commited on
Commit
01332cf
·
verified ·
1 Parent(s): 195df9a

Update src/module/Data_Exploration.py

Browse files
Files changed (1) hide show
  1. src/module/Data_Exploration.py +380 -0
src/module/Data_Exploration.py CHANGED
@@ -0,0 +1,380 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MODULE DATA EXPLORATION - VORTEX SYSTEM
3
+ ========================================
4
+ Responsabilité : Analyse exploratoire des données (EDA) avec Google Sheets
5
+ """
6
+ import pandas as pd
7
+ import numpy as np
8
+ import streamlit as st
9
+ import plotly.express as px
10
+ import plotly.graph_objects as go
11
+ from plotly.subplots import make_subplots
12
+ import gspread
13
+ from datetime import datetime
14
+ import json
15
+
16
+ class DataExplorer:
17
+ """Classe pour l'exploration et l'analyse des données hospitalières"""
18
+
19
+ def __init__(self, gspread_client, sheet_name="bdd_mvp_faker"):
20
+ """
21
+ Initialise l'explorateur de données
22
+
23
+ Args:
24
+ gspread_client: Client gspread authentifié
25
+ sheet_name: Nom du fichier Google Sheets
26
+ """
27
+ self.client = gspread_client
28
+ self.sheet_name = sheet_name
29
+ self.dataframes = {}
30
+ self.kpis = {}
31
+
32
+ def load_all_sheets(self):
33
+ """Charge tous les onglets du fichier Google Sheets"""
34
+ try:
35
+ spreadsheet = self.client.open(self.sheet_name)
36
+ sheet_names = [worksheet.title for worksheet in spreadsheet.worksheets()]
37
+
38
+ st.info(f"📊 Onglets détectés : {', '.join(sheet_names)}")
39
+
40
+ for sheet_name in sheet_names:
41
+ worksheet = spreadsheet.worksheet(sheet_name)
42
+ data = worksheet.get_all_records()
43
+
44
+ if data:
45
+ # Standardisation du nom pour le dictionnaire
46
+ standardized_name = sheet_name.lower().replace('é', 'e').replace(' ', '_')
47
+ self.dataframes[standardized_name] = pd.DataFrame(data)
48
+ st.success(f"✅ {sheet_name} chargé ({len(data)} lignes)")
49
+ else:
50
+ st.warning(f"⚠️ {sheet_name} est vide")
51
+
52
+ return True
53
+
54
+ except Exception as e:
55
+ st.error(f"❌ Erreur lors du chargement : {e}")
56
+ return False
57
+
58
+ def get_dataframe(self, sheet_key):
59
+ """Récupère un DataFrame spécifique par son nom standardisé"""
60
+ return self.dataframes.get(sheet_key)
61
+
62
+ def show_data_overview(self):
63
+ """Affiche un aperçu général des données"""
64
+ st.markdown("### 📋 Aperçu Général des Données")
65
+
66
+ if not self.dataframes:
67
+ st.warning("⚠️ Aucune donnée chargée. Veuillez d'abord charger les données.")
68
+ return
69
+
70
+ # Tableau récapitulatif
71
+ overview_data = []
72
+ for name, df in self.dataframes.items():
73
+ overview_data.append({
74
+ "Onglet": name,
75
+ "Lignes": len(df),
76
+ "Colonnes": len(df.columns),
77
+ "Mémoire (KB)": round(df.memory_usage(deep=True).sum() / 1024, 2),
78
+ "Valeurs manquantes": df.isnull().sum().sum()
79
+ })
80
+
81
+ overview_df = pd.DataFrame(overview_data)
82
+ st.dataframe(overview_df, use_container_width=True)
83
+
84
+ def show_detailed_inspection(self, sheet_key):
85
+ """Affiche une inspection détaillée d'un DataFrame spécifique"""
86
+ df = self.get_dataframe(sheet_key)
87
+
88
+ if df is None:
89
+ st.error(f"❌ Onglet '{sheet_key}' non trouvé")
90
+ return
91
+
92
+ st.markdown(f"### 🔍 Inspection Détaillée : `{sheet_key}`")
93
+
94
+ # Onglets d'inspection
95
+ tab1, tab2, tab3, tab4 = st.tabs(["📊 Aperçu", "📈 Statistiques", "🔎 Qualité", "🧹 Colonnes"])
96
+
97
+ with tab1:
98
+ st.markdown("#### Premières lignes")
99
+ st.dataframe(df.head(10), use_container_width=True)
100
+
101
+ st.markdown("#### Dernières lignes")
102
+ st.dataframe(df.tail(5), use_container_width=True)
103
+
104
+ with tab2:
105
+ st.markdown("#### Statistiques Descriptives")
106
+ st.dataframe(df.describe(include='all').T, use_container_width=True)
107
+
108
+ st.markdown("#### Types de Données")
109
+ types_df = pd.DataFrame({
110
+ 'Colonne': df.columns,
111
+ 'Type': df.dtypes.values,
112
+ 'Non-Null': df.count().values,
113
+ 'Null': df.isnull().sum().values
114
+ })
115
+ st.dataframe(types_df, use_container_width=True)
116
+
117
+ with tab3:
118
+ st.markdown("#### Analyse de Qualité")
119
+
120
+ # Valeurs manquantes
121
+ missing_data = df.isnull().sum()
122
+ missing_percent = (missing_data / len(df)) * 100
123
+
124
+ quality_df = pd.DataFrame({
125
+ 'Colonne': missing_data.index,
126
+ 'Valeurs Manquantes': missing_data.values,
127
+ 'Pourcentage (%)': missing_percent.values.round(2)
128
+ })
129
+ quality_df = quality_df[quality_df['Valeurs Manquantes'] > 0].sort_values('Valeurs Manquantes', ascending=False)
130
+
131
+ if not quality_df.empty:
132
+ st.dataframe(quality_df, use_container_width=True)
133
+
134
+ # Graphique
135
+ fig = px.bar(quality_df, x='Colonne', y='Pourcentage (%)',
136
+ title='Pourcentage de Valeurs Manquantes par Colonne',
137
+ color='Pourcentage (%)',
138
+ color_continuous_scale='Reds')
139
+ st.plotly_chart(fig, use_container_width=True)
140
+ else:
141
+ st.success("✅ Aucune valeur manquante détectée !")
142
+
143
+ # Doublons
144
+ duplicates = df.duplicated().sum()
145
+ st.metric("Lignes Dupliquées", duplicates)
146
+
147
+ with tab4:
148
+ st.markdown("#### Liste des Colonnes")
149
+ cols_info = []
150
+ for col in df.columns:
151
+ unique_count = df[col].nunique()
152
+ cols_info.append({
153
+ 'Colonne': col,
154
+ 'Type': str(df[col].dtype),
155
+ 'Valeurs Uniques': unique_count,
156
+ 'Exemple': str(df[col].iloc[0]) if len(df) > 0 else 'N/A'
157
+ })
158
+
159
+ st.dataframe(pd.DataFrame(cols_info), use_container_width=True)
160
+
161
+ def analyze_patients(self):
162
+ """Analyse spécifique des patients"""
163
+ df_patients = self.get_dataframe('patients')
164
+
165
+ if df_patients is None:
166
+ st.warning("⚠️ Onglet 'patients' non disponible")
167
+ return
168
+
169
+ st.markdown("### 👥 Analyse Démographique des Patients")
170
+
171
+ col1, col2, col3 = st.columns(3)
172
+
173
+ with col1:
174
+ total_patients = len(df_patients)
175
+ st.metric("Total Patients", f"{total_patients:,}")
176
+
177
+ with col2:
178
+ if 'age' in df_patients.columns:
179
+ avg_age = df_patients['age'].mean()
180
+ st.metric("Âge Moyen", f"{avg_age:.1f} ans")
181
+
182
+ with col3:
183
+ if 'sexe' in df_patients.columns:
184
+ gender_dist = df_patients['sexe'].value_counts()
185
+ st.metric("Répartition H/F", f"{gender_dist.get('M', 0)}/{gender_dist.get('F', 0)}")
186
+
187
+ # Pyramide des âges
188
+ if 'age' in df_patients.columns:
189
+ st.markdown("#### 📊 Pyramide des Âges")
190
+
191
+ # Détection des outliers
192
+ q1 = df_patients['age'].quantile(0.25)
193
+ q3 = df_patients['age'].quantile(0.75)
194
+ iqr = q3 - q1
195
+ outlier_threshold = q3 + 1.5 * iqr
196
+
197
+ outliers = df_patients[df_patients['age'] > outlier_threshold]
198
+ if not outliers.empty:
199
+ st.warning(f"⚠️ {len(outliers)} outliers détectés (âge > {outlier_threshold:.0f} ans)")
200
+
201
+ fig = px.histogram(df_patients, x='age', nbins=30,
202
+ title='Distribution des Âges',
203
+ labels={'age': 'Âge', 'count': 'Nombre de patients'},
204
+ color_discrete_sequence=['#58a6ff'])
205
+ st.plotly_chart(fig, use_container_width=True)
206
+
207
+ # Répartition par sexe
208
+ if 'sexe' in df_patients.columns:
209
+ st.markdown("#### ⚥ Répartition par Sexe")
210
+
211
+ gender_counts = df_patients['sexe'].value_counts()
212
+ fig = px.pie(values=gender_counts.values, names=gender_counts.index,
213
+ title='Répartition Hommes/Femmes',
214
+ color_discrete_sequence=['#58a6ff', '#ff6b9d'])
215
+ st.plotly_chart(fig, use_container_width=True)
216
+
217
+ def analyze_data_quality(self):
218
+ """Analyse la qualité des données (colonnes dq_)"""
219
+ st.markdown("### 🔬 Audit de Qualité des Données")
220
+
221
+ dq_results = {}
222
+
223
+ for name, df in self.dataframes.items():
224
+ if 'dq_is_anomaly' in df.columns:
225
+ anomalies = df[df['dq_is_anomaly'] == 1]
226
+
227
+ dq_results[name] = {
228
+ "total_rows": len(df),
229
+ "anomaly_count": len(anomalies),
230
+ "anomaly_rate": (len(anomalies) / len(df) * 100) if len(df) > 0 else 0
231
+ }
232
+
233
+ if 'dq_anomaly_type' in df.columns and not anomalies.empty:
234
+ dq_results[name]["types"] = anomalies['dq_anomaly_type'].value_counts().to_dict()
235
+
236
+ if not dq_results:
237
+ st.info("ℹ️ Aucune colonne de qualité (dq_) détectée dans les données")
238
+ return
239
+
240
+ # Affichage des résultats
241
+ for sheet_name, results in dq_results.items():
242
+ with st.expander(f"📋 {sheet_name.upper()}", expanded=True):
243
+ col1, col2, col3 = st.columns(3)
244
+
245
+ with col1:
246
+ st.metric("Total Lignes", f"{results['total_rows']:,}")
247
+ with col2:
248
+ st.metric("Anomalies", results['anomaly_count'])
249
+ with col3:
250
+ st.metric("Taux d'Anomalies", f"{results['anomaly_rate']:.2f}%")
251
+
252
+ if "types" in results and results["types"]:
253
+ st.markdown("**Types d'Anomalies :**")
254
+ types_df = pd.DataFrame(list(results["types"].items()),
255
+ columns=['Type', 'Count'])
256
+ st.dataframe(types_df, use_container_width=True)
257
+
258
+ def calculate_kpis(self):
259
+ """Calcule les KPIs par service"""
260
+ st.markdown("### 📊 Indicateurs de Performance (KPIs)")
261
+
262
+ # Exemple de KPIs - à adapter selon vos données réelles
263
+ kpis = {}
264
+
265
+ # KPI 1: Taux d'occupation
266
+ df_ressources = self.get_dataframe('ressources_jour')
267
+ if df_ressources is not None and 'lits_total' in df_ressources.columns:
268
+ total_lits = df_ressources['lits_total'].sum()
269
+ lits_occupes = df_ressources['lits_occupes'].sum() if 'lits_occupes' in df_ressources.columns else 0
270
+ kpis['taux_occupation'] = (lits_occupes / total_lits * 100) if total_lits > 0 else 0
271
+
272
+ # KPI 2: Durée moyenne de séjour
273
+ df_sejours = self.get_dataframe('sejours')
274
+ if df_sejours is not None and 'duree_sejour' in df_sejours.columns:
275
+ kpis['duree_moyenne_sejour'] = df_sejours['duree_sejour'].mean()
276
+
277
+ # KPI 3: Nombre de patients
278
+ df_patients = self.get_dataframe('patients')
279
+ if df_patients is not None:
280
+ kpis['total_patients'] = len(df_patients)
281
+
282
+ # Affichage
283
+ if kpis:
284
+ cols = st.columns(len(kpis))
285
+ for idx, (key, value) in enumerate(kpis.items()):
286
+ with cols[idx]:
287
+ st.metric(key.replace('_', ' ').title(), f"{value:.2f}")
288
+
289
+ # Export JSON
290
+ if st.button("💾 Exporter les KPIs (JSON)"):
291
+ kpis_json = json.dumps(kpis, indent=2)
292
+ st.download_button(
293
+ label="Télécharger KPIs.json",
294
+ data=kpis_json,
295
+ file_name=f"kpis_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json",
296
+ mime="application/json"
297
+ )
298
+ else:
299
+ st.info("ℹ️ Impossible de calculer les KPIs avec les données actuelles")
300
+
301
+ def show_stock_alerts(self):
302
+ """Affiche les alertes de stock"""
303
+ df_stock = self.get_dataframe('materiel')
304
+
305
+ if df_stock is None:
306
+ st.warning("⚠️ Onglet 'materiel' non disponible")
307
+ return
308
+
309
+ st.markdown("### 🚨 Alertes Stock & Matériel")
310
+
311
+ if 'tension_stock' in df_stock.columns:
312
+ alerts = df_stock[df_stock['tension_stock'] > 0.8]
313
+
314
+ if not alerts.empty:
315
+ st.error(f"⚠️ {len(alerts)} alertes de tension de stock détectées !")
316
+
317
+ # Colonnes à afficher
318
+ display_cols = [col for col in ['date', 'service_code', 'materiel_code', 'tension_stock']
319
+ if col in alerts.columns]
320
+ st.dataframe(alerts[display_cols], use_container_width=True)
321
+ else:
322
+ st.success("✅ Aucune alerte de stock")
323
+ else:
324
+ st.info("ℹ️ Colonne 'tension_stock' non trouvée dans les données")
325
+
326
+
327
+ def show_terminal(gspread_client, sheet_name="bdd_mvp_faker"):
328
+ """
329
+ Fonction principale à appeler depuis Streamlit pour afficher l'interface EDA
330
+
331
+ Args:
332
+ gspread_client: Client gspread authentifié
333
+ sheet_name: Nom du fichier Google Sheets
334
+ """
335
+ st.markdown("## 🔬 EXPLORATEUR DE DONNÉES")
336
+
337
+ # Initialisation
338
+ if 'data_explorer' not in st.session_state:
339
+ st.session_state.data_explorer = DataExplorer(gspread_client, sheet_name)
340
+
341
+ explorer = st.session_state.data_explorer
342
+
343
+ # Bouton de chargement
344
+ if st.button("📥 Charger les Données", type="primary"):
345
+ with st.spinner("Chargement en cours..."):
346
+ success = explorer.load_all_sheets()
347
+ if success:
348
+ st.success("✅ Données chargées avec succès !")
349
+
350
+ # Menu d'analyse
351
+ if explorer.dataframes:
352
+ st.divider()
353
+
354
+ analysis_mode = st.selectbox(
355
+ "Mode d'Analyse",
356
+ ["Aperçu Général", "Inspection Détaillée", "Analyse Patients",
357
+ "Qualité des Données", "KPIs", "Alertes Stock"]
358
+ )
359
+
360
+ if analysis_mode == "Aperçu Général":
361
+ explorer.show_data_overview()
362
+
363
+ elif analysis_mode == "Inspection Détaillée":
364
+ sheet_to_inspect = st.selectbox(
365
+ "Sélectionner un onglet",
366
+ list(explorer.dataframes.keys())
367
+ )
368
+ explorer.show_detailed_inspection(sheet_to_inspect)
369
+
370
+ elif analysis_mode == "Analyse Patients":
371
+ explorer.analyze_patients()
372
+
373
+ elif analysis_mode == "Qualité des Données":
374
+ explorer.analyze_data_quality()
375
+
376
+ elif analysis_mode == "KPIs":
377
+ explorer.calculate_kpis()
378
+
379
+ elif analysis_mode == "Alertes Stock":
380
+ explorer.show_stock_alerts()