# IMPORTS import pandas as pd import numpy as np import matplotlib import matplotlib.pyplot as plt import matplotlib.ticker as mticker import seaborn as sns import plotly.express as px import plotly.graph_objects as go from plotly.subplots import make_subplots import json import warnings import os from time import perf_counter, sleep from datetime import datetime from pathlib import Path warnings.filterwarnings("ignore") from pathlib import Path import json DATASET_PATH = "hf://datasets/ryanxely/medical_dataset_3M/medical_dataset.csv" # Config Section DATA_PATH = Path("/data") KPIS_PATH = DATA_PATH / "kpis_export.json" MOCK_KPIS_PATH = DATA_PATH / "kpis_export_mock.json" LOG_PATH = DATA_PATH / "trace.log" WORKING_DIR = DATA_PATH # In[3]: # Fonctions et variables utiles start_time = 0 def start(): global start_time start_time = perf_counter() def time_lapse(display=True): time_elapsed = perf_counter() - start_time hours, rem = divmod(time_elapsed, 3600) minutes, seconds = divmod(rem, 60) if display: return f"{int(hours)}h {int(minutes)}m {seconds:.2f}s" return int(hours), int(minutes), seconds def log(message): with open(LOG_PATH, "a", encoding="utf-8") as logger: logger.writelines([f"[{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}] : {message}"]) def extract_kpis(): start() log("KPIs Extraction started...") # Configuration globale des graphes matplotlib log("Configuration globale des graphes matplotlib...") matplotlib.rcParams.update({ "figure.dpi": 120, "figure.facecolor": "white", "axes.facecolor": "white", "axes.grid": True, "grid.alpha": 0.3, "font.size": 10, "axes.titlesize": 12, "axes.labelsize": 10, }) # Palette de couleurs cohérente pour tous les graphes PALETTE_PRINCIPALE = "steelblue" PALETTE_CATEGORIELLE = "tab10" PALETTE_DIVERGENTE = "coolwarm" PALETTE_SEQUENTIELLE = "YlOrRd" print("Imports effectues avec succes.") log("Imports effectues avec succes.") print(f"Pandas version : {pd.__version__}") print(f"NumPy version : {np.__version__}") # In[4]: # Chargement et Aperçu de la Dataset print(f"Chemin de la dataset : {DATASET_PATH}") print("Chargement du fichier...") log("Chargement de la dataset...") with open(KPIS_PATH, "r", encoding="utf-8") as f: data = json.load(f) df = pd.read_csv(DATASET_PATH, low_memory=False) print(f"\nDataset charge avec succes.") log("Dataset chargée avec succes.") print(f"Dimensions : {df.shape[0]:,} lignes x {df.shape[1]} colonnes") # Apercu des premieres lignes print("\nApercu des 5 premieres lignes :") print(df.head()) # Types de colonnes print("\nTypes de donnees par colonne (extrait) :") print(df.dtypes.value_counts()) # Identification des colonnes symptomes # Les colonnes symptomes sont toutes celles hors des 4 colonnes descriptives COLONNES_META = ["disease", "age", "sex", "medical_history"] symptom_cols = [c for c in df.columns if c not in COLONNES_META] print(f"\nNombre de colonnes symptomes identifiees : {len(symptom_cols)}") print(f"Exemples : {symptom_cols[:5]}") # Résumé log("Affichage du résumé...") symptom_cols = df.columns[4:].tolist() meta = { "shape": df.shape, "diseases": df["disease"].nunique(), "disease_list": df["disease"].unique().tolist(), "age_range": [int(df["age"].min()), int(df["age"].max())], "sex_values": df["sex"].unique().tolist(), "medical_history_values": df["medical_history"].unique().tolist(), "symptom_count": len(symptom_cols), "symptom_columns": symptom_cols, "missing_values": df.isnull().sum()[df.isnull().sum() > 0].to_dict(), "missing_total_pct": round(df.isnull().sum().sum() / df.size * 100, 2), "disease_distribution_top20": df["disease"].value_counts().head(20).to_dict() } print("\nRésumé de la dataset", json.dumps(meta, indent=2, ensure_ascii=False)) with open(f"{WORKING_DIR}/dataset_summary.json", "w", encoding="utf-8") as f: json.dump(meta, f, indent=2, ensure_ascii=False) log("Résumé exporté avec succès.") # In[ ]: # KPI A1 — Inventaire general du dataset print("\n=== KPI A1 : Inventaire general ===\n") log("Chargement du KPI A1 : Inventaire general") nb_lignes = df.shape[0] nb_colonnes = df.shape[1] nb_symptomes = len(symptom_cols) memoire_mb = df.memory_usage(deep=True).sum() / 1024**2 # conversion en Mo inventaire = pd.DataFrame({ "Indicateur": [ "Nombre de lignes", "Nombre de colonnes total", "Colonnes descriptives patient", "Colonnes symptomes binaires", "Memoire consommee (Mo)" ], "Valeur": [ f"{nb_lignes:,}", nb_colonnes, len(COLONNES_META), nb_symptomes, f"{memoire_mb:.1f}" ] }) print(inventaire.to_string(index=False)) # In[ ]: # KPI A2 — Valeurs manquantes print("\n=== KPI A2 : Valeurs manquantes ===\n") log("Chargement du KPI A2 : Valeurs manquantes") # Calcul du taux de valeurs manquantes par colonne (uniquement colonnes avec manquants) manquants = df.isnull().sum() manquants = manquants[manquants > 0].sort_values(ascending=False) taux_manquants = (manquants / nb_lignes * 100).round(4) df_manquants = pd.DataFrame({ "Colonne": manquants.index, "Nb valeurs manquantes": manquants.values, "Taux (%)": taux_manquants.values }) print(df_manquants.to_string(index=False)) # Taux global de valeurs manquantes sur tout le dataset total_cellules = nb_lignes * nb_colonnes total_manquants = df.isnull().sum().sum() taux_global = total_manquants / total_cellules * 100 print(f"\nTaux global de valeurs manquantes : {taux_global:.4f}%") # Graphe — Barres horizontales des colonnes avec valeurs manquantes if len(df_manquants) > 0: fig, ax = plt.subplots(figsize=(8, max(2, len(df_manquants) * 0.5))) sns.barplot( data=df_manquants, y="Colonne", x="Taux (%)", color=PALETTE_PRINCIPALE, ax=ax ) ax.set_title("Taux de valeurs manquantes par colonne (%)") ax.set_xlabel("Taux (%)") ax.set_ylabel("") ax.set_xlim(0, 100) for bar, val in zip(ax.patches, df_manquants["Taux (%)"]): ax.text(bar.get_width() + 0.1, bar.get_y() + bar.get_height() / 2, f"{val:.2f}%", va="center", fontsize=9) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_a2_valeurs_manquantes.png") plt.show() print("Graphe sauvegarde : kpi_a2_valeurs_manquantes.png") # In[ ]: # KPI A3 — Integrite des colonnes symptomes print("\n=== KPI A3 : Integrite des colonnes symptomes ===\n") log("Chargement du KPI A3 : Integrite des colonnes symptomes") # Verification que chaque colonne symptome ne contient que 0 et 1 colonnes_corrompues = [] for col in symptom_cols: valeurs_uniques = set(df[col].dropna().unique()) if not valeurs_uniques.issubset({0, 1, 0.0, 1.0}): colonnes_corrompues.append((col, valeurs_uniques)) if len(colonnes_corrompues) == 0: print("Integrite binaire validee : toutes les colonnes symptomes contiennent uniquement 0 ou 1.") else: print(f"ATTENTION : {len(colonnes_corrompues)} colonne(s) avec des valeurs non binaires :") for col, vals in colonnes_corrompues[:10]: print(f" - {col} : {vals}") # Resume des types de colonnes types_resume = pd.DataFrame({ "Type": ["Variable cible (disease)", "Numerique (age)", "Categorielle binaire (sex)", "Categorielle (medical_history)", "Binaire symptomes (0/1)"], "Nb colonnes": [1, 1, 1, 1, nb_symptomes] }) print("\nRepartition des types de colonnes :") print(types_resume.to_string(index=False)) # In[ ]: # KPI B1 : Distribution des maladies print("\n=== KPI B1 : Distribution des maladies ===\n") log("Chargement du KPI B1 - Distribution des maladies...") counts_maladies = df["disease"].value_counts() nb_maladies_distinctes = counts_maladies.shape[0] print(f"Nombre de maladies distinctes : {nb_maladies_distinctes}") print(f"Statistiques des effectifs par maladie :") print(counts_maladies.describe().round(2)) # Top 20 maladies les plus frequentes top20 = counts_maladies.head(20).reset_index() top20.columns = ["Maladie", "Nb_cas"] fig, ax = plt.subplots(figsize=(12, 6)) sns.barplot(data=top20, x="Nb_cas", y="Maladie", color=PALETTE_PRINCIPALE, ax=ax) ax.set_title("Top 20 maladies les plus frequentes") ax.set_xlabel("Nombre de cas") ax.set_ylabel("") ax.set_xlim(top20["Nb_cas"].min() / 1.001, top20["Nb_cas"].max() * 1.001) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_b1_top20_maladies.png") plt.show() print("Graphe sauvegarde : kpi_b1_top20_maladies.png") # Bottom 20 maladies les moins frequentes bottom20 = counts_maladies.tail(20).reset_index() bottom20.columns = ["Maladie", "Nb_cas"] fig, ax = plt.subplots(figsize=(12, 6)) sns.barplot(data=bottom20, x="Nb_cas", y="Maladie", color="salmon", ax=ax) ax.set_title("Bottom 20 maladies les moins frequentes") ax.set_xlabel("Nombre de cas") ax.set_ylabel("") ax.set_xlim(bottom20["Nb_cas"].min() / 1.001, bottom20["Nb_cas"].max() * 1.001) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_b1_bottom20_maladies.png") plt.show() print("Graphe sauvegarde : kpi_b1_bottom20_maladies.png") # In[ ]: # KPI B2 : Coefficient de desequilibre print("\n=== KPI B2 : Coefficient de desequilibre ===\n") log("Chargement du KPI B2 - Coefficient de desequilibre...") ratio_desequilibre = counts_maladies.max() / counts_maladies.min() # Indice de Gini de desequilibre (1 = parfaitement desequilibre, 0 = parfaitement equilibre) N = nb_lignes proportions = counts_maladies / N gini = 1 - (proportions ** 2).sum() print(f"Effectif maximum (maladie la plus frequente) : {counts_maladies.max():,}") print(f"Effectif minimum (maladie la moins frequente) : {counts_maladies.min():,}") print(f"Ratio de desequilibre (max/min) : {ratio_desequilibre:.2f}") print(f"Indice de Gini d'equilibre : {gini:.4f}") if ratio_desequilibre < 1.5: message = "Notre dataset est bien équilibré et favorable pour l'entraînement ML." elif ratio_desequilibre < 3: message = "Notre dataset est en déséquilibre modéré. Il faut surveiller les classes minoritaires." else: message = "Notre dataset est en déséquilibre significatif. Nous devons envisager oversampling ou class_weight." print(f"\nSelon le ratio de déséquilibre, {message}") if gini == 1 : print("Selon l'indice de Gini, notre dataset est parfaitement equilibré.") elif gini == 0 : print("Selon l'indice de Gini, notre dataset est parfaitement déséquilibré.") # Histogramme de la distribution des effectifs par maladie fig, ax = plt.subplots(figsize=(10, 5)) ax.hist(counts_maladies.values, bins=50, color=PALETTE_PRINCIPALE, edgecolor="white") ax.set_title("Distribution des effectifs par maladie") ax.set_xlabel("Nombre de cas") ax.set_ylabel("Nombre de maladies") ax.axvline(counts_maladies.mean(), color="red", linestyle="--", label=f"Moyenne : {counts_maladies.mean():.0f}") ax.legend() plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_b2_distribution_effectifs.png") plt.show() print("Graphe sauvegarde : kpi_b2_distribution_effectifs.png") # In[ ]: # KPI C1 : Distribution de l'age print("\n=== KPI C1 : Distribution de l'age ===\n") log("Chargement du KPI C1 - Distribution de l'age...") # Verification des valeurs aberrantes nb_ages_invalides = ((df["age"] < 0) | (df["age"] > 100)).sum() print(f"Valeurs d'age hors plage [0, 100] : {nb_ages_invalides}") print("\nStatistiques descriptives de l'age :") print(df["age"].describe().round(2)) # Creation des tranches d'age — colonne utilisee dans les KPIs suivants tranches_bins = [0, 12, 18, 35, 50, 65, 100] tranches_labels = ["Enfant (0-12)", "Adolescent (13-18)", "Jeune adulte (19-35)", "Adulte (36-50)", "Senior (51-65)", "Tres age (66+)"] df["age_group"] = pd.cut(df["age"], bins=tranches_bins, labels=tranches_labels, right=True) # Distribution par tranche d'age dist_age = df["age_group"].value_counts().sort_index().reset_index() dist_age.columns = ["Tranche", "Nb_patients"] dist_age["Pct"] = (dist_age["Nb_patients"] / nb_lignes * 100).round(2) print("\nDistribution par tranche d'age :") print(dist_age.to_string(index=False)) # Histogramme interactif (plotly) fig_plotly = px.histogram( df, x="age", nbins=20, title="Distribution de l'age des patients", labels={"age": "Age", "count": "Nombre de patients"}, color_discrete_sequence=["steelblue"] ) fig_plotly.update_layout(bargap=0.05) fig_plotly.update_yaxes( range=[ int(df["age"].value_counts().min()), int(df["age"].value_counts().max()) * 1.00001 ] ) fig_plotly.write_html(f"{WORKING_DIR}/kpi_c1_age.html") fig_plotly.show() print("Fichier sauvegarde : kpi_c1_age.html\n") # Graphe barres par tranche fig, ax = plt.subplots(figsize=(10, 5)) sns.barplot(data=dist_age, x="Tranche", y="Nb_patients", color=PALETTE_PRINCIPALE, ax=ax) ax.set_title("Nombre de patients par tranche d'age") ax.set_xlabel("Tranche d'age") ax.set_ylabel("Nombre de patients") ax.tick_params(axis="x", rotation=20) for bar, row in zip(ax.patches, dist_age.itertuples()): ax.text(bar.get_x() + bar.get_width() / 2, bar.get_height() + 1000, f"{row.Pct}%", ha="center", fontsize=8) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_c1_tranches_age.png") plt.show() print("Graphe sauvegarde : kpi_c1_tranches_age.png") # In[ ]: # KPI C2 : Repartition par sexe print("\n=== KPI C2 : Repartition par sexe ===\n") log("Chargement du KPI C2 - Repartition par sexe...") # Repartition globale dist_sexe = df["sex"].value_counts() dist_sexe_pct = (dist_sexe / nb_lignes * 100).round(2) print("Repartition globale par sexe :") for sexe, nb in dist_sexe.items(): print(f" {sexe} : {nb:,} ({dist_sexe_pct[sexe]}%)") # Pie chart global fig, ax = plt.subplots(figsize=(6, 6)) ax.pie(dist_sexe.values, labels=dist_sexe.index, autopct="%1.1f%%", colors=["#5b9bd5", "#ed7d31"], startangle=90) ax.set_title("Repartition globale par sexe") plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_c2_sexe_global.png") plt.show() print("Graphe sauvegarde : kpi_c2_sexe_global.png") # Ratio homme/femme par maladie sexe_par_maladie = df.groupby("disease")["sex"].value_counts(normalize=True).unstack(fill_value=0) # S'assurer que les deux colonnes existent for col in ["Female", "Male"]: if col not in sexe_par_maladie.columns: sexe_par_maladie[col] = 0.0 sexe_par_maladie["ratio_F_M"] = sexe_par_maladie.get("Female", 0) / \ (sexe_par_maladie.get("Male", 0) + 1e-9) # Top 10 maladies a dominante feminine (ratio F/M le plus eleve) top10_fem = sexe_par_maladie["ratio_F_M"].nlargest(10).reset_index() top10_fem.columns = ["Maladie", "Ratio_F_M"] print("\nTop 10 maladies a dominante feminine (ratio F/M) :") print(top10_fem.to_string(index=False)) # Top 10 maladies a dominante masculine (ratio F/M le plus faible) top10_masc = sexe_par_maladie["ratio_F_M"].nsmallest(10).reset_index() top10_masc.columns = ["Maladie", "Ratio_F_M"] print("\nTop 10 maladies a dominante masculine (ratio F/M) :") print(top10_masc.to_string(index=False)) # Analyse # Si ratio_F_M > 2 : la maladie touche majoritairement les femmes. # Si ratio_F_M < 0.5 : la maladie touche majoritairement les hommes. # Si ratio_F_M entre 0.8 et 1.2 : distribution equilibree entre les sexes. # Barres empilees pour les 20 maladies les plus frequentes top20_noms = list(top20["Maladie"]) sexe_top20 = sexe_par_maladie.loc[ sexe_par_maladie.index.isin(top20_noms), ["Female", "Male"] ].reset_index() fig, ax = plt.subplots(figsize=(12, 6)) sexe_top20.set_index("disease")[["Female", "Male"]].plot( kind="barh", stacked=True, ax=ax, color=["#5b9bd5", "#ed7d31"] ) ax.set_title("Repartition homme/femme pour les 20 maladies les plus frequentes (proportion)") ax.set_xlabel("Proportion") ax.set_ylabel("") ax.legend(["Femme", "Homme"], loc="lower right") plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_c2_sexe_top20_maladies.png") plt.show() print("Graphe sauvegarde : kpi_c2_sexe_top20_maladies.png") # In[ ]: sexe_par_maladie["ratio_F_M"] # In[ ]: # KPI C3 : Antecedents medicaux print("\n=== KPI C3 : Antecedents medicaux ===\n") log("Chargement du KPI C3 - Antecedents medicaux...") # Traiter les NaN comme une categorie explicite df["medical_history_filled"] = df["medical_history"].fillna("Aucun antecedent connu") dist_antecedents = df["medical_history_filled"].value_counts().reset_index() dist_antecedents.columns = ["Antecedent", "Nb_patients"] dist_antecedents["Pct"] = (dist_antecedents["Nb_patients"] / nb_lignes * 100).round(2) print("Distribution des antecedents medicaux :") print(dist_antecedents.to_string(index=False)) # Taux de NaN reel taux_nan_antecedents = df["medical_history"].isna().sum() / nb_lignes * 100 print(f"\nTaux de valeurs manquantes dans medical_history : {taux_nan_antecedents:.2f}%") # Graphe barres horizontales fig, ax = plt.subplots(figsize=(12, 6)) sns.barplot( data=dist_antecedents, y="Antecedent", x="Nb_patients", color=PALETTE_PRINCIPALE, ax=ax ) ax.set_title("Distribution des antecedents medicaux (y compris valeurs manquantes)") ax.set_xlabel("Nombre de patients") ax.set_ylabel("") ax.set_xlim(dist_antecedents["Nb_patients"].min() / 1.001, dist_antecedents["Nb_patients"].max() * 1.001) for bar, row in zip(ax.patches, dist_antecedents.itertuples()): ax.text(bar.get_width() + 1000, bar.get_y() + bar.get_height() / 2, f"{row.Pct}%", va="center", fontsize=9) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_c3_antecedents.png") plt.show() print("Graphe sauvegarde : kpi_c3_antecedents.png") # In[ ]: # KPI C4 : Age moyen par maladie print("\n=== KPI C4 : Age moyen par maladie ===\n") log("Chargement du KPI C4 - Age moyen par maladie...") age_par_maladie = df.groupby("disease")["age"].agg(["mean", "median", "std"]).round(2) age_par_maladie.columns = ["Age_moyen", "Age_median", "Ecart_type"] # Top 10 maladies touchant les patients les plus jeunes top10_jeunes = age_par_maladie.nsmallest(10, "Age_moyen").reset_index() print("Top 10 maladies touchant les plus jeunes (age moyen) :") print(top10_jeunes.to_string(index=False)) # Top 10 maladies touchant les patients les plus ages top10_ages = age_par_maladie.nlargest(10, "Age_moyen").reset_index() print("\nTop 10 maladies touchant les plus ages (age moyen) :") print(top10_ages.to_string(index=False)) # Analyse # Commentons sur 10 maladies au hasard # print("\nCommentaires sur 10 maladies au hasard") # sample = age_par_maladie.sample(n=10, random_state=402) # for i in range(len(sample)): # record = sample.iloc[i] # if record["Ecart_type"] > 20: # print(f"\tLa maladie {record.name} touche toutes les tranches d'âge de manière relativement indiscriminée.") # elif record["Ecart_type"] < 10: # print(f"\tLa maladie {record.name} est fortement concentrée sur une tranche d'âge spécifique.") # else: # print(f"\tLa maladie {record.name} présente une dispersion modérée selon les tranches d'âge.") # Boxplot des 20 maladies les plus frequentes par age df_top20 = df[df["disease"].isin(top20_noms)] df_top20_only = ( df[df["disease"].isin(top20_noms)] .drop_duplicates(subset=["disease"]) ) fig, ax = plt.subplots(figsize=(14, 8)) sns.boxplot( data=df_top20, y="disease", x="age", order=top20_noms, palette="tab20", ax=ax, showfliers=True, whis=np.inf, ) ax.set_title("Distribution de l'age pour les 20 maladies les plus frequentes") ax.set_xlabel("Age") ax.set_ylabel("") ax.set_xlim(df_top20_only["age"].min()/1.001, df_top20_only["age"].max()*1.001) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_c4_age_par_maladie.png") plt.show() print("Graphe sauvegarde : kpi_c4_age_par_maladie.png") # # Scatterplot des repartitions # disease_codes = { # disease: i # for i, disease in enumerate(df["disease"].unique()) # } # df["disease_index"] = df["disease"].map(disease_codes) # fig, ax = plt.subplots(figsize=(14, 8)) # sns.scatterplot( # data=df, # x="disease_index", # y="age", # alpha=0.5, # ax=ax # ) # ax.set_title("Répartition des âges selon les maladies") # ax.set_xlabel("Index des maladies") # ax.set_ylabel("Âge") # plt.tight_layout() # plt.show() # In[ ]: # KPI D1 : Prevalence globale des symptomes print("\n=== KPI D1 : Prevalence globale des symptomesKPI ===\n") log("Chargement du KPI D1 - Prevalence globale des symptomes...") # Taux de presence de chaque symptome (en %) sur l'ensemble du dataset prevalence_symptomes = (df[symptom_cols].mean() * 100).sort_values(ascending=False) print(f"Nombre total de symptomes : {len(prevalence_symptomes)}") print(f"Symptome le plus frequent : {prevalence_symptomes.index[0]} ({prevalence_symptomes.iloc[0]:.2f}%)") print(f"Symptome le plus rare : {prevalence_symptomes.index[-1]} ({prevalence_symptomes.iloc[-1]:.2f}%)") # Symptomes quasi-absents (< 1%) et quasi-universels (> 90%) quasi_absents = prevalence_symptomes[prevalence_symptomes < 1] quasi_universels = prevalence_symptomes[prevalence_symptomes > 90] print(f"\nSymptomes avec prevalence < 1% : {len(quasi_absents)}") print(f"Symptomes avec prevalence > 90% : {len(quasi_universels)}") # Top 20 symptomes les plus frequents top20_symp = prevalence_symptomes.head(20).reset_index() top20_symp.columns = ["Symptome", "Prevalence_pct"] fig, ax = plt.subplots(figsize=(12, 7)) sns.barplot(data=top20_symp, x="Prevalence_pct", y="Symptome", color=PALETTE_PRINCIPALE, ax=ax) ax.set_title("Top 20 symptomes les plus frequents (% de patients concernes)") ax.set_xlabel("Prevalence (%)") ax.set_ylabel("") ax.set_xlim(top20_symp["Prevalence_pct"].min() / 1.01, top20_symp["Prevalence_pct"].max() * 1.01) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_d1_top20_symptomes.png") plt.show() print("Graphe sauvegarde : kpi_d1_top20_symptomes.png") # Bottom 20 symptomes les plus rares bottom20_symp = prevalence_symptomes.tail(20).reset_index() bottom20_symp.columns = ["Symptome", "Prevalence_pct"] fig, ax = plt.subplots(figsize=(12, 7)) sns.barplot(data=bottom20_symp, x="Prevalence_pct", y="Symptome", color="salmon", ax=ax) ax.set_title("Bottom 20 symptomes les plus rares (% de patients concernes)") ax.set_xlabel("Prevalence (%)") ax.set_ylabel("") ax.set_xlim(bottom20_symp["Prevalence_pct"].min() / 1.001, bottom20_symp["Prevalence_pct"].max() * 1.001) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_d1_bottom20_symptomes.png") plt.show() print("Graphe sauvegarde : kpi_d1_bottom20_symptomes.png") # In[ ]: # KPI D2 : Nombre de symptomes par patient print("\n=== KPI D2 : Nombre de symptomes par patient ===\n") log("Chargement du KPI D2 - Nombre de symptomes par patient...") # Calcul du nombre de symptomes presents par ligne patient df["nb_symptomes"] = df[symptom_cols].sum(axis=1) # Verification de coherence : lignes sans aucun symptome nb_sans_symptome = (df["nb_symptomes"] == 0).sum() print(f"Patients sans aucun symptome (valeur 0 partout) : {nb_sans_symptome}") if nb_sans_symptome > 0: print(" ATTENTION : ces lignes sont medicalement incoherentes et devront etre examinees.") print("\nStatistiques globales du nombre de symptomes par patient :") print(df["nb_symptomes"].describe().round(2)) # Histogramme de la distribution fig, ax = plt.subplots(figsize=(10, 5)) ax.hist(df["nb_symptomes"], bins=50, color=PALETTE_PRINCIPALE, edgecolor="white") ax.set_title("Distribution du nombre de symptomes par patient") ax.set_xlabel("Nombre de symptomes") ax.set_ylabel("Nombre de patients") ax.axvline(df["nb_symptomes"].mean(), color="red", linestyle="--", label=f"Moyenne : {df['nb_symptomes'].mean():.1f}") ax.legend() plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_d2_nb_symptomes_distribution.png") plt.show() print("Graphe sauvegarde : kpi_d2_nb_symptomes_distribution.png") # Complexite symptomatique par maladie complexite_par_maladie = df.groupby("disease")["nb_symptomes"].mean().sort_values() top10_simple = complexite_par_maladie.head(10).reset_index() top10_simple.columns = ["Maladie", "Nb_symptomes_moyen"] top10_complexe = complexite_par_maladie.tail(10).reset_index() top10_complexe.columns = ["Maladie", "Nb_symptomes_moyen"] print("\nTop 10 maladies avec le moins de symptomes (les plus simples) :") print(top10_simple.to_string(index=False)) print("\nTop 10 maladies avec le plus de symptomes (les plus complexes) :") print(top10_complexe.to_string(index=False)) # Graphe comparatif fig, axes = plt.subplots(1, 2, figsize=(14, 6)) sns.barplot(data=top10_simple, x="Nb_symptomes_moyen", y="Maladie", color="steelblue", ax=axes[0]) axes[0].set_title("10 maladies les plus simples (symptomes)") axes[0].set_xlabel("Nb moyen de symptomes") sns.barplot(data=top10_complexe, x="Nb_symptomes_moyen", y="Maladie", color="coral", ax=axes[1]) axes[1].set_title("10 maladies les plus complexes (symptomes)") axes[1].set_xlabel("Nb moyen de symptomes") plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_d2_complexite_maladies.png") plt.show() print("Graphe sauvegarde : kpi_d2_complexite_maladies.png") # In[ ]: # KPI D3 : Symptomes par maladie (taux d'occurrence) print("\n=== KPI D3 : Symptomes par maladie (taux d'occurrence) ===\n") print("Calcul en cours — operation longue sur 773 maladies x 328 symptomes...") log("Chargement du KPI D3 - Symptomes par maladie (taux d'occurrence)...") # DataFrame (773 x 328) : chaque cellule = % de patients de cette maladie avec ce symptome taux_symptomes_par_maladie = df.groupby("disease")[symptom_cols].mean() * 100 taux_symptomes_par_maladie = taux_symptomes_par_maladie.round(2) print(f"Tableau calcule : {taux_symptomes_par_maladie.shape}") print("Apercu (5 maladies x 5 symptomes) :") print(taux_symptomes_par_maladie.iloc[:5, :5]) # Heatmap limitee aux 20 maladies les plus frequentes x 30 symptomes les plus discriminants # (les symptomes discriminants sont determines au KPI D4 — on les pre-calcule ici) variance_symptomes = taux_symptomes_par_maladie.var(axis=0).sort_values(ascending=False) top30_discriminants = variance_symptomes.head(30).index.tolist() # Sous-tableau pour la heatmap heatmap_data = taux_symptomes_par_maladie.loc[ taux_symptomes_par_maladie.index.isin(top20_noms), top30_discriminants ] fig, ax = plt.subplots(figsize=(18, 10)) sns.heatmap( heatmap_data, annot=False, cmap=PALETTE_SEQUENTIELLE, linewidths=0.3, ax=ax, cbar_kws={"label": "Taux d'occurrence (%)"} ) ax.set_title("Taux d'occurrence des 30 symptomes les plus discriminants\npour les 20 maladies les plus frequentes (%)") ax.set_xlabel("Symptomes") ax.set_ylabel("Maladies") ax.tick_params(axis="x", rotation=45, labelsize=7) ax.tick_params(axis="y", rotation=0, labelsize=8) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_d3_heatmap_symptomes_maladies.png") plt.show() print("Graphe sauvegarde : kpi_d3_heatmap_symptomes_maladies.png") # In[ ]: # KPI D4 : Symptomes discriminants (variance inter-maladies) print("\n=== KPI D4 : Symptomes discriminants (variance inter-maladies) ===\n") log("Chargement du KPI D4 - Symptomes discriminants (variance inter-maladies)...") # La variance inter-maladies est deja calculee dans la section D3 # (variable : variance_symptomes) top30_disc_df = pd.DataFrame({ "Symptome": top30_discriminants, "Variance_inter_maladies": variance_symptomes[top30_discriminants].values }).round(4) print("Top 30 symptomes les plus discriminants :") print(top30_disc_df.to_string(index=False)) # Analyse # Un symptome avec variance elevee est present dans certaines maladies et absent dans d'autres. # C'est un signal fort pour la classification ML — ces features auront probablement # une importance elevee dans un modele Random Forest ou XGBoost. # Un symptome avec variance proche de 0 est soit present partout soit absent partout # — il apporte peu d'information discriminante. fig, ax = plt.subplots(figsize=(12, 9)) sns.barplot( data=top30_disc_df, x="Variance_inter_maladies", y="Symptome", color=PALETTE_PRINCIPALE, ax=ax ) ax.set_title("Top 30 symptomes les plus discriminants (variance du taux d'occurrence entre maladies)") ax.set_xlabel("Variance inter-maladies") ax.set_ylabel("") ax.set_xlim(top30_disc_df["Variance_inter_maladies"].min() / 1.01, top30_disc_df["Variance_inter_maladies"].max() * 1.01) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_d4_symptomes_discriminants.png") plt.show() print("Graphe sauvegarde : kpi_d4_symptomes_discriminants.png") # In[ ]: # KPI D5 : Correlation entre symptomes print("\n=== KPI D5 : Correlation entre symptomes ===\n") print("Calcul de la matrice de correlation sur les 30 symptomes discriminants...") log("Chargement du KPI D5 - Correlation entre symptomes...") # Matrice de correlation limitee aux 30 symptomes discriminants (328x328 = trop large) corr_matrix_30 = df[top30_discriminants].corr().round(3) # Paires avec correlation forte (r > 0.7 en valeur absolue, hors diagonale) mask_upper = np.triu(np.ones(corr_matrix_30.shape), k=1).astype(bool) corr_upper = corr_matrix_30.where(mask_upper) paires_fortes = corr_upper[corr_upper.abs() > 0.7].stack().reset_index() paires_fortes.columns = ["Symptome_A", "Symptome_B", "Correlation"] paires_fortes = paires_fortes.sort_values("Correlation", key=abs, ascending=False) print(f"Paires de symptomes avec |r| > 0.7 : {len(paires_fortes)}") if len(paires_fortes) > 0: print(paires_fortes.head(20).to_string(index=False)) # Heatmap de correlation fig, ax = plt.subplots(figsize=(16, 14)) mask_diag = np.eye(corr_matrix_30.shape[0], dtype=bool) sns.heatmap( corr_matrix_30, mask=mask_diag, cmap=PALETTE_DIVERGENTE, center=0, vmin=-1, vmax=1, annot=False, linewidths=0.2, ax=ax, cbar_kws={"label": "Coefficient de correlation"} ) ax.set_title("Matrice de correlation — 30 symptomes les plus discriminants") ax.tick_params(axis="x", rotation=45, labelsize=7) ax.tick_params(axis="y", rotation=0, labelsize=7) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_d5_correlation_symptomes.png") plt.show() print("Graphe sauvegarde : kpi_d5_correlation_symptomes.png") # In[ ]: # KPI E1 : Maladies dominantes par tranche d'age print("\n=== KPI E1 : Maladies dominantes par tranche d'age ===\n") log("Chargement du KPI E1 - Maladies dominantes par tranche d'age...") # Top 5 maladies par tranche d'age top5_par_tranche = ( df.groupby(["age_group", "disease"], observed=True) .size() .reset_index(name="nb_cas") .sort_values(["age_group", "nb_cas"], ascending=[True, False]) .groupby("age_group", observed=True) .head(5) .reset_index(drop=True) ) print("Top 5 maladies par tranche d'age :") print(top5_par_tranche.to_string(index=False)) # Graphe interactif plotly (barres groupees par tranche) fig_plotly = px.bar( top5_par_tranche, x="disease", y="nb_cas", color="age_group", barmode="group", title="Top 5 maladies par tranche d'age", labels={"disease": "Maladie", "nb_cas": "Nombre de cas", "age_group": "Tranche d'age"} ) fig_plotly.update_layout(xaxis_tickangle=-40, bargap=0, bargroupgap=0) fig_plotly.write_html(f"{WORKING_DIR}/kpi_e1_maladies_par_age.html") fig_plotly.show() # In[ ]: # KPI E2 : Antecedents medicaux x maladies print("\n=== KPI E2 : Antecedents medicaux x maladies ===\n") log("Chargement du KPI E2 - Antecedents medicaux x maladies...") # Top 5 maladies associees a chaque antecedent top5_par_antecedent = ( df.groupby(["medical_history_filled", "disease"]) .size() .reset_index(name="nb_cas") .sort_values(["medical_history_filled", "nb_cas"], ascending=[True, False]) .groupby("medical_history_filled") .head(5) .reset_index(drop=True) ) print("Top 5 maladies par antecedent medical :") print(top5_par_antecedent.to_string(index=False)) # Graphe : une figure globale avec subplots par antecedent antecedents_liste = df["medical_history_filled"].unique().tolist() nb_antecedents = len(antecedents_liste) ncols = 2 nrows = (nb_antecedents + 1) // ncols fig, axes = plt.subplots(nrows=nrows, ncols=ncols, figsize=(16, nrows * 4)) axes = axes.flatten() for i, antecedent in enumerate(sorted(antecedents_liste)): sous_df = top5_par_antecedent[top5_par_antecedent["medical_history_filled"] == antecedent] sns.barplot( data=sous_df, x="nb_cas", y="disease", color=PALETTE_PRINCIPALE, ax=axes[i] ) axes[i].set_title(f"Antecedent : {antecedent}") axes[i].set_xlabel("Nb de cas") axes[i].set_ylabel("") # Masquer les axes inutilises si le nombre d'antecedents est impair for j in range(i + 1, len(axes)): axes[j].set_visible(False) plt.suptitle("Top 5 maladies par antecedent medical", fontsize=14, y=1.01) plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_e2_antecedents_maladies.png") plt.show() print("Graphe sauvegarde : kpi_e2_antecedents_maladies.png") # In[ ]: # KPI E3 : Nombre moyen de symptomes selon l'age et le sexe print("\n=== KPI E3 : Nombre moyen de symptomes selon l'age et le sexe ===\n") log("Chargement du KPI E3 - Nombre moyen de symptomes selon l'age et le sexe...") pivot_e3 = df.groupby(["age_group", "sex"], observed=True)["nb_symptomes"].mean().unstack() pivot_e3 = pivot_e3.round(2) print("Nombre moyen de symptomes par tranche d'age et sexe :") print(pivot_e3) # Analyse # Si les valeurs augmentent avec l'age : les patients plus ages presentent plus de symptomes # simultanement — coherent avec une comorbidite accrue chez les personnes agees. # Si les valeurs sont similaires entre hommes et femmes : pas de biais de genre dans # la complexite symptomatique. Sinon, signaler l'ecart. # Heatmap fig, ax = plt.subplots(figsize=(8, 6)) sns.heatmap( pivot_e3, annot=True, fmt=".2f", cmap=PALETTE_SEQUENTIELLE, ax=ax, cbar_kws={"label": "Nb moyen de symptomes"} ) ax.set_title("Nombre moyen de symptomes par tranche d'age et sexe") ax.set_xlabel("Sexe") ax.set_ylabel("Tranche d'age") plt.tight_layout() plt.savefig(f"{WORKING_DIR}/kpi_e3_symptomes_age_sexe.png") plt.show() print("Graphe sauvegarde : kpi_e3_symptomes_age_sexe.png") # In[ ]: # KPI F1 : Dashboard de synthese print("\n=== KPI F1 : Dashboard de synthese ===\n") log("Chargement du KPI F1 - Dashboard de synthese...") fig = plt.figure(figsize=(20, 24)) fig.suptitle("Dashboard KPI — Dataset Medical", fontsize=16, fontweight="bold", y=0.98) # Panneau 1 : Top 20 maladies ax1 = fig.add_subplot(3, 2, 1) sns.barplot(data=top20, x="Nb_cas", y="Maladie", color="steelblue", ax=ax1) ax1.set_title("Top 20 maladies (effectif)") ax1.set_xlabel("Nb de cas") ax1.set_ylabel("") # Panneau 2 : Repartition par sexe ax2 = fig.add_subplot(3, 2, 2) ax2.pie(dist_sexe.values, labels=dist_sexe.index, autopct="%1.1f%%", colors=["#5b9bd5", "#ed7d31"], startangle=90) ax2.set_title("Repartition par sexe") # Panneau 3 : Distribution de l'age ax3 = fig.add_subplot(3, 2, 3) ax3.hist(df["age"], bins=50, color="steelblue", edgecolor="white") ax3.set_title("Distribution de l'age") ax3.set_xlabel("Age") ax3.set_ylabel("Nb de patients") # Panneau 4 : Top 20 symptomes ax4 = fig.add_subplot(3, 2, 4) top20_symp_dash = prevalence_symptomes.head(20).reset_index() top20_symp_dash.columns = ["Symptome", "Prevalence"] sns.barplot(data=top20_symp_dash, x="Prevalence", y="Symptome", color="teal", ax=ax4) ax4.set_title("Top 20 symptomes (prevalence %)") ax4.set_xlabel("Prevalence (%)") ax4.set_ylabel("") # Panneau 5 : Antecedents medicaux ax5 = fig.add_subplot(3, 2, 5) sns.barplot(data=dist_antecedents, y="Antecedent", x="Nb_patients", color="mediumpurple", ax=ax5) ax5.set_title("Antecedents medicaux") ax5.set_xlabel("Nb de patients") ax5.set_ylabel("") # Panneau 6 : Distribution effectifs par maladie ax6 = fig.add_subplot(3, 2, 6) ax6.hist(counts_maladies.values, bins=50, color="coral", edgecolor="white") ax6.set_title("Distribution des effectifs par maladie") ax6.set_xlabel("Nb de cas") ax6.set_ylabel("Nb de maladies") plt.tight_layout(rect=[0, 0, 1, 0.97]) plt.savefig(f"{WORKING_DIR}/kpi_f1_dashboard_synthese.png", dpi=150, bbox_inches="tight") plt.show() print("Dashboard sauvegarde : kpi_f1_dashboard_synthese.png") # In[ ]: # KPI F2 : Export JSON print("\n=== KPI F2 : Export JSON ===\n") log("Exportation...") # ------------------------------------------------------------------------- # SECTION 1 : Données brutes (KPIs calculés — inchangé) # ------------------------------------------------------------------------- kpis_export = { "meta": { "nb_lignes": int(nb_lignes), "nb_colonnes": int(nb_colonnes), "nb_symptomes": int(nb_symptomes), "nb_maladies_distinctes": int(nb_maladies_distinctes), "taux_manquants_global_pct": round(float(taux_global), 4), "taux_manquants_medical_history_pct": round(float(taux_nan_antecedents), 2) }, "maladies": { "distribution_stats": { "min_cas": int(counts_maladies.min()), "max_cas": int(counts_maladies.max()), "moyenne_cas": round(float(counts_maladies.mean()), 2), "mediane_cas": round(float(counts_maladies.median()), 2), "ecart_type_cas": round(float(counts_maladies.std()), 2), "ratio_desequilibre": round(float(ratio_desequilibre), 4), "gini": round(float(gini), 4) }, "top20": top20.to_dict(orient="records"), "bottom20": bottom20.to_dict(orient="records") }, "demographie": { "age": { "min": float(df["age"].min()), "max": float(df["age"].max()), "moyenne": round(float(df["age"].mean()), 2), "mediane": round(float(df["age"].median()), 2), "ecart_type": round(float(df["age"].std()), 2), "par_tranche": dist_age.to_dict(orient="records") }, "sexe": { "global_pct": dist_sexe_pct.to_dict(), "top10_dominante_feminine": top10_fem.to_dict(orient="records"), "top10_dominante_masculine": top10_masc.to_dict(orient="records") }, "antecedents": { "distribution": dist_antecedents.to_dict(orient="records"), "taux_manquants_pct": round(float(taux_nan_antecedents), 2) }, "age_par_maladie": { "top10_plus_jeunes": top10_jeunes.to_dict(orient="records"), "top10_plus_ages": top10_ages.to_dict(orient="records") } }, "symptomes": { "nb_symptomes_par_patient": { "min": int(df["nb_symptomes"].min()), "max": int(df["nb_symptomes"].max()), "moyenne": round(float(df["nb_symptomes"].mean()), 2), "mediane": float(df["nb_symptomes"].median()), "patients_sans_symptome": int(nb_sans_symptome) }, "prevalence_globale": { "top20": top20_symp.to_dict(orient="records"), "bottom20": bottom20_symp.to_dict(orient="records"), "nb_quasi_absents_inf1pct": int(len(quasi_absents)), "nb_quasi_universels_sup90pct": int(len(quasi_universels)) }, "top30_discriminants": top30_disc_df.to_dict(orient="records"), "complexite_par_maladie": { "top10_simples": top10_simple.to_dict(orient="records"), "top10_complexes": top10_complexe.to_dict(orient="records") } }, "croisements": { "top5_maladies_par_tranche_age": top5_par_tranche.to_dict(orient="records"), "top5_maladies_par_antecedent": top5_par_antecedent.to_dict(orient="records"), "nb_symptomes_moyen_age_sexe": pivot_e3.reset_index().to_dict(orient="records") } } # ------------------------------------------------------------------------- # SECTION 2 : Données pré-formatées Chart.js (nouvelles clés) # # Ces clés sont consommées directement par les composants DataChart du # frontend Vue sans aucune transformation côté client. Chaque objet suit # exactement la structure { labels: [...], datasets: [{ label, data }] } # attendue par Chart.js v3+. # ------------------------------------------------------------------------- kpis_export["chartjs"] = { # --- DataChart B1 : Top 20 maladies — graphe en barres verticales --- # Colonne "Maladie" = labels de l'axe X # Colonne "Nb_cas" = valeurs de la série # [ACTION MANUELLE] : si le nom de colonne dans top20 diffère # ("disease" au lieu de "Maladie"), ajuster les clés ci-dessous. "top20_maladies": { "labels": top20["Maladie"].tolist(), "datasets": [{ "label": "Nombre de cas", "data": top20["Nb_cas"].tolist() }] }, # --- DataChart C2 : Répartition par sexe — graphe en anneau (doughnut) --- # dist_sexe_pct est un dict {"Female": 54.8, "Male": 45.2} # L'ordre des clés est préservé par Python 3.7+ (insertion order). "sexe_global": { "labels": list(dist_sexe_pct.keys()), "datasets": [{ "data": [round(float(v), 1) for v in dist_sexe_pct.values()] }] }, # --- DataChart D4 : Top 5 symptômes discriminants — barres horizontales --- # On limite volontairement à 5 pour la lisibilité du graphe frontend. # Le reste des 30 est disponible dans symptomes.top30_discriminants # pour un éventuel tableau détaillé ou une vue expandable. # [DISCUSSION] : Vérifier que la colonne "Score" correspond bien au # coefficient de discrimination calculé (chi2, mutual info, ou autre). # Si la colonne s'appelle autrement, adapter la clé ci-dessous. "top5_discriminants": { "labels": top30_disc_df["Symptome"].head(5).tolist(), "datasets": [{ "label": "Score discriminant", "data": top30_disc_df["Score"].head(5).round(4).tolist() }] }, # --- DataChart supplémentaire : Distribution par tranche d'âge --- # Graphe en barres — utile pour la section démographie de la page. # dist_age est un DataFrame avec colonnes "Tranche" et "Nb_patients". # [ACTION MANUELLE] : vérifier les noms de colonnes exacts dans dist_age. "age_par_tranche": { "labels": dist_age["Tranche"].tolist(), "datasets": [{ "label": "Nombre de patients", "data": dist_age["Nb_patients"].tolist() }] }, # --- DataChart supplémentaire : Antécédents médicaux — barres horizontales --- # dist_antecedents est un DataFrame avec colonnes "Antecedent" et "Nb_patients". "antecedents": { "labels": dist_antecedents["Antecedent"].tolist(), "datasets": [{ "label": "Nombre de patients", "data": dist_antecedents["Nb_patients"].tolist() }] }, # --- DataChart D5 : Top 20 symptômes les plus prévalents — barres horizontales --- # prevalence_symptomes est une Series indexée par nom de symptôme, valeurs en %. # On prend les 20 premiers triés par prévalence décroissante. "top20_symptomes": { "labels": prevalence_symptomes.head(20).index.tolist(), "datasets": [{ "label": "Prévalence (%)", "data": prevalence_symptomes.head(20).round(2).tolist() }] }, # --- DataChart E1 : Maladie dominante par tranche d'âge --- # top5_par_tranche est un DataFrame avec colonnes "Tranche_age", "Maladie", "Nb_cas". # On extrait la maladie N°1 par tranche (rang 1 après groupby + rank). # Le graphe est un bar chart simple : une barre par tranche, hauteur = Nb_cas, # label = nom de la maladie dominante affiché en tooltip. # [ACTION MANUELLE] : vérifier les noms de colonnes exacts dans top5_par_tranche. "maladies_par_tranche_age": (lambda df: { "labels": df["Tranche_age"].tolist(), "datasets": [{ "label": "Leading disease (case count)", "data": df["Nb_cas"].tolist(), "disease_names": df["Maladie"].tolist() }] })(top5_par_tranche[top5_par_tranche["Rang"] == 1].sort_values("Tranche_age")), # --- DataChart E2 : Maladie dominante par antécédent médical --- # top5_par_antecedent est un DataFrame avec colonnes "Antecedent", "Maladie", "Nb_cas". # On extrait la maladie N°1 par antécédent. # [ACTION MANUELLE] : vérifier les noms de colonnes exacts dans top5_par_antecedent. "maladies_par_antecedent": (lambda df: { "labels": df["Antecedent"].tolist(), "datasets": [{ "label": "Leading disease (case count)", "data": df["Nb_cas"].tolist(), "disease_names": df["Maladie"].tolist() }] })(top5_par_antecedent[top5_par_antecedent["Rang"] == 1]), # --- DataChart E3 : Nombre moyen de symptômes par tranche d'âge et sexe --- # pivot_e3 est un pivot table : index = tranche d'âge, colonnes = Female / Male. # On génère deux datasets (une série par sexe) pour un grouped bar chart. # [ACTION MANUELLE] : vérifier que pivot_e3.columns contient bien "Female" et "Male". "symptomes_age_sexe": { "labels": pivot_e3.index.tolist(), "datasets": [ { "label": "Female", "data": pivot_e3["Female"].round(2).tolist() }, { "label": "Male", "data": pivot_e3["Male"].round(2).tolist() } ] } } # ------------------------------------------------------------------------- # SECTION 3 : Export du fichier JSON # ------------------------------------------------------------------------- kpis_export["last_update"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S") kpis_export["last_update_duration"] = time_lapse() with open(KPIS_PATH, "w", encoding="utf-8") as f: json.dump(kpis_export, f, ensure_ascii=False, indent=2) taille_json_kb = os.path.getsize(KPIS_PATH) / 1024 print(f"Export JSON termine : {KPIS_PATH}") print(f"Taille du fichier : {taille_json_kb:.1f} Ko") print(f"\nCles exportees :") for cle in kpis_export.keys(): if cle == "chartjs": for sous_cle in kpis_export["chartjs"].keys(): print(f" chartjs.{sous_cle}") else: print(f" {cle}") # ------------------------------------------------------------------------- # SECTION 4 : Verification rapide de coherence # Controle que les clés Chart.js ont bien la structure attendue avant # de copier le JSON dans le frontend. # ------------------------------------------------------------------------- print("\nVerification de coherence Chart.js :") for nom, obj in kpis_export["chartjs"].items(): nb_labels = len(obj["labels"]) nb_data = len(obj["datasets"][0]["data"]) statut = "OK" if nb_labels == nb_data else "ERREUR — longueurs differentes" print(f" {nom:30s} labels={nb_labels:3d} data={nb_data:3d} {statut}") print("\n=== Fin du notebook KPI Medical ===") print("\nFichiers generes :") fichiers_generes = [ "kpi_a2_valeurs_manquantes.png", "kpi_b1_top20_maladies.png", "kpi_b1_bottom20_maladies.png", "kpi_b2_distribution_effectifs.png", "kpi_c1_tranches_age.png", "kpi_c2_sexe_global.png", "kpi_c2_sexe_top20_maladies.png", "kpi_c3_antecedents.png", "kpi_c4_age_par_maladie.png", "kpi_d1_top20_symptomes.png", "kpi_d1_bottom20_symptomes.png", "kpi_d2_nb_symptomes_distribution.png", "kpi_d2_complexite_maladies.png", "kpi_d3_heatmap_symptomes_maladies.png", "kpi_d4_symptomes_discriminants.png", "kpi_d5_correlation_symptomes.png", "kpi_e1_maladies_par_age.html (plotly)", "kpi_e2_antecedents_maladies.png", "kpi_e3_symptomes_age_sexe.png", "kpi_f1_dashboard_synthese.png", "kpis_export.json" ] for f in fichiers_generes: print(f" {f}") print("Extraction des KPIs Terminée") log("Extraction des KPIs Terminée") return 0