HealthCare-API / api /services /dataset_service.py
ryanxely's picture
Debugging...
807c110
Raw
History Blame Contribute Delete
52 kB
# 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