"""
Module: Arbre de Decision
Categorie: Supervised Classification
Difficulte: Debutant

Genere depuis la plateforme ML Formation
"""

# Imports
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, mean_squared_error, r2_score

# Charger le dataset
df = pd.read_csv('iris_simple.csv')

# Explorer le dataset Iris
# Type: Code executable
# =============================================================================
# ETAPE 1 : EXPLORATION DU DATASET IRIS
# =============================================================================
# Le dataset Iris est LE dataset classique du machine learning.
# Il contient 150 fleurs de 3 especes differentes, avec 4 mesures par fleur.

print("=" * 70)
print("EXPLORATION DU DATASET IRIS")
print("=" * 70)
print()
print("Le dataset Iris est un classique du ML depuis 1936 !")
print("Objectif : Classifier des fleurs en 3 especes basees sur leurs mesures.")
print()

# --- 1.1 Apercu des donnees ---
print("1. APERCU DES DONNEES")
print("-" * 40)
print("Chaque ligne = une fleur avec 4 mesures et son espece.")
print()
display(df.head(10), title="Dataset Iris - 10 premieres fleurs")

# --- 1.2 Les 4 features ---
print()
print("2. LES 4 CARACTERISTIQUES (FEATURES)")
print("-" * 40)
print()
print("   SEPAL (sepale = partie externe de la fleur) :")
print("   → sepal_length : Longueur du sepale (cm)")
print("   → sepal_width  : Largeur du sepale (cm)")
print()
print("   PETAL (petale = partie interne coloree) :")
print("   → petal_length : Longueur du petale (cm)")
print("   → petal_width  : Largeur du petale (cm)")
print()

# --- 1.3 Distribution des especes ---
print("3. DISTRIBUTION DES ESPECES")
print("-" * 40)
print()
species_counts = df['species'].value_counts()
for species, count in species_counts.items():
    pct = count / len(df) * 100
    bar = "█" * int(pct / 2)
    print(f"   {species:12} : {count:3d} fleurs ({pct:.0f}%) {bar}")

print()
print("   ✓ Dataset parfaitement equilibre : 50 fleurs par espece")
print()

# --- 1.4 Statistiques par espece ---
print("4. STATISTIQUES PAR ESPECE")
print("-" * 40)
print()
print("   Comparons les mesures moyennes de chaque espece :")
print()
display(df.groupby('species').mean().round(2), title="Moyennes par espece")

# --- 1.5 Observations cles ---
print()
print("5. OBSERVATIONS CLES")
print("-" * 40)
print()

# Analyser les differences
stats = df.groupby('species').mean()

print("   SETOSA :")
print(f"   → Petales tres courts ({stats.loc['setosa', 'petal_length']:.1f} cm)")
print("   → Facile a distinguer des autres especes")
print()

print("   VERSICOLOR vs VIRGINICA :")
print(f"   → Versicolor : petal_width = {stats.loc['versicolor', 'petal_width']:.1f} cm")
print(f"   → Virginica  : petal_width = {stats.loc['virginica', 'petal_width']:.1f} cm")
print("   → Plus difficile a distinguer (valeurs proches)")
print()

print("   HYPOTHESE :")
print("   → Les PETALES semblent plus discriminants que les sepales.")
print("   → L'arbre de decision devrait utiliser petal_length et petal_width")
print("     comme features principales.")

print()
print("=" * 70)
print("Verifions notre hypothese avec une visualisation !")
print("=" * 70)


# Visualiser les features
# Type: Code executable
# =============================================================================
# ETAPE 2 : VISUALISATION DES FEATURES
# =============================================================================
# La visualisation nous permet de voir si les especes sont separables
# et quelles features sont les plus discriminantes.

print("=" * 70)
print("VISUALISATION : SEPALES vs PETALES")
print("=" * 70)
print()
print("Comparons la capacite des sepales et des petales a separer les especes.")
print()

fig, axes = plt.subplots(1, 2, figsize=(14, 6))

colors = {'setosa': '#F7E64D', 'versicolor': '#9B7AC4', 'virginica': '#4CAF50'}

# Scatter plot: Sepal
for species in df['species'].unique():
    mask = df['species'] == species
    axes[0].scatter(df[mask]['sepal_length'], df[mask]['sepal_width'],
                   label=species, alpha=0.7, s=60, c=colors[species],
                   edgecolors='white', linewidth=0.5)
axes[0].set_xlabel('Sepal Length (cm)', fontsize=11)
axes[0].set_ylabel('Sepal Width (cm)', fontsize=11)
axes[0].set_title('SEPALES : Length vs Width', fontsize=12, fontweight='bold')
axes[0].legend()
axes[0].grid(True, alpha=0.3)

# Scatter plot: Petal
for species in df['species'].unique():
    mask = df['species'] == species
    axes[1].scatter(df[mask]['petal_length'], df[mask]['petal_width'],
                   label=species, alpha=0.7, s=60, c=colors[species],
                   edgecolors='white', linewidth=0.5)
axes[1].set_xlabel('Petal Length (cm)', fontsize=11)
axes[1].set_ylabel('Petal Width (cm)', fontsize=11)
axes[1].set_title('PETALES : Length vs Width', fontsize=12, fontweight='bold')
axes[1].legend()
axes[1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# --- Analyse visuelle ---
print()
print("ANALYSE VISUELLE")
print("-" * 40)
print()
print("   GRAPHIQUE 1 - SEPALES :")
print("   → Les 3 especes se CHEVAUCHENT beaucoup")
print("   → Difficile de tracer des frontieres claires")
print("   → Les sepales seuls ne suffisent pas a classifier")
print()
print("   GRAPHIQUE 2 - PETALES :")
print("   → SETOSA est completement SEPAREE (en bas a gauche)")
print("   → VERSICOLOR et VIRGINICA sont proches mais distinguables")
print("   → Les petales sont BEAUCOUP plus discriminants !")
print()

print("   CONCLUSION POUR L'ARBRE DE DECISION :")
print("   → L'algorithme devrait d'abord utiliser petal_length")
print("     pour separer Setosa des autres (split facile)")
print("   → Puis utiliser petal_width pour distinguer")
print("     Versicolor de Virginica (split plus fin)")
print()
print("   L'arbre de decision va decouvrir ces regles automatiquement !")


# Entrainer l'arbre de decision
# Type: Code executable
# =============================================================================
# ETAPE 3 : ENTRAINEMENT DE L'ARBRE DE DECISION
# =============================================================================
# L'arbre de decision apprend des regles de classification
# en posant des questions successives sur les features.

print("=" * 70)
print("ENTRAINEMENT DE L'ARBRE DE DECISION")
print("=" * 70)
print()

# --- 3.1 Preparation des donnees ---
print("1. PREPARATION DES DONNEES")
print("-" * 40)

X = df[['sepal_length', 'sepal_width', 'petal_length', 'petal_width']].values
y = df['species'].values
feature_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']

print(f"   Features (X) : {X.shape[0]} fleurs x {X.shape[1]} mesures")
print(f"   Labels (y)   : {len(y)} etiquettes (3 especes)")
print()

# --- 3.2 Division train/test ---
print("2. DIVISION TRAIN/TEST")
print("-" * 40)

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)

print(f"   Ensemble TRAIN : {len(X_train)} fleurs (80%)")
print(f"   Ensemble TEST  : {len(X_test)} fleurs (20%)")
print()

# Verification de la stratification
from collections import Counter
print("   Verification stratification (distribution dans train) :")
for species, count in Counter(y_train).items():
    print(f"   → {species}: {count} fleurs")
print()

# --- 3.3 Configuration de l'arbre ---
print("3. CONFIGURATION DE L'ARBRE")
print("-" * 40)
print()
print("   Parametres choisis :")
print("   → max_depth = 3 (profondeur maximale)")
print("   → criterion = 'gini' (mesure d'impurete)")
print()
print("   POURQUOI LIMITER LA PROFONDEUR ?")
print("   → Un arbre trop profond memorise les donnees (overfitting)")
print("   → Un arbre peu profond reste interpretable")
print("   → max_depth=3 est un bon compromis pour ce dataset")
print()

# --- 3.4 Entrainement ---
print("4. ENTRAINEMENT")
print("-" * 40)

tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X_train, y_train)

print()
print("   ✓ Arbre entraine avec succes !")
print()
print(f"   Structure de l'arbre :")
print(f"   → Profondeur reelle  : {tree.get_depth()}")
print(f"   → Nombre de feuilles : {tree.get_n_leaves()}")
print(f"   → Nombre de noeuds   : {tree.tree_.node_count}")
print()

# --- 3.5 Test rapide ---
print("5. TEST RAPIDE")
print("-" * 40)
print()
print("   Classifions une nouvelle fleur :")

# Prendre un exemple du test set
sample = X_test[0]
pred = tree.predict([sample])[0]
true = y_test[0]

print(f"   → Mesures : sepal({sample[0]:.1f}, {sample[1]:.1f}), petal({sample[2]:.1f}, {sample[3]:.1f})")
print(f"   → Prediction : {pred}")
print(f"   → Vraie espece : {true}")
print(f"   → {'✓ Correct !' if pred == true else '✗ Erreur'}")

print()
print("=" * 70)
print("ARBRE ENTRAINE ! Evaluons ses performances.")
print("=" * 70)


# Evaluer et interpreter
# Type: Code executable
# =============================================================================
# ETAPE 4 : EVALUATION ET INTERPRETATION
# =============================================================================
# Mesurons la performance de l'arbre et comprenons quelles features
# il utilise pour classifier.

print("=" * 70)
print("EVALUATION ET INTERPRETATION DE L'ARBRE")
print("=" * 70)
print()

# Preparation et entrainement
X = df[['sepal_length', 'sepal_width', 'petal_length', 'petal_width']].values
y = df['species'].values
feature_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X_train, y_train)

# --- 4.1 Predictions et accuracy ---
print("1. PERFORMANCE GLOBALE")
print("-" * 40)

y_pred = tree.predict(X_test)
acc = accuracy_score(y_test, y_pred)

print()
print(f"   Accuracy sur les donnees de test : {acc:.1%}")
print()

if acc > 0.95:
    print("   ✓ EXCELLENT ! L'arbre classifie presque parfaitement.")
elif acc > 0.85:
    print("   ✓ BON. L'arbre est performant.")
else:
    print("   ~ ACCEPTABLE. Il y a de la marge d'amelioration.")
print()

# --- 4.2 Rapport de classification ---
print("2. RAPPORT DE CLASSIFICATION")
print("-" * 40)
print()
print("   Ce rapport montre la performance PAR ESPECE :")
print()
print(classification_report(y_test, y_pred))

# --- 4.3 Importance des features ---
print("3. IMPORTANCE DES FEATURES")
print("-" * 40)
print()
print("   L'importance mesure combien chaque feature contribue aux decisions.")
print("   Plus l'importance est elevee, plus la feature est discriminante.")
print()

importances = tree.feature_importances_
sorted_idx = np.argsort(importances)[::-1]

print(f"   {'Feature':<15} {'Importance':>12} {'Barre'}")
print("   " + "-" * 45)

for idx in sorted_idx:
    name = feature_names[idx]
    imp = importances[idx]
    bar = "█" * int(imp * 30)
    print(f"   {name:<15} {imp:>12.3f} {bar}")

print()

# --- 4.4 Interpretation ---
print("4. INTERPRETATION")
print("-" * 40)
print()

top_feature = feature_names[sorted_idx[0]]
top_importance = importances[sorted_idx[0]]

print(f"   FEATURE LA PLUS IMPORTANTE : {top_feature}")
print(f"   → Represente {top_importance:.1%} de l'importance totale")
print()

if 'petal' in top_feature:
    print("   CONFIRMATION DE NOTRE HYPOTHESE !")
    print("   → Les PETALES sont plus discriminants que les sepales.")
    print("   → C'est coherent avec notre analyse visuelle.")
print()

# Verifier si sepal est utilise
sepal_importance = sum(importances[i] for i, name in enumerate(feature_names) if 'sepal' in name)
if sepal_importance < 0.1:
    print("   NOTE : Les sepales sont peu utilises par l'arbre.")
    print("   → L'arbre a appris que les petales suffisent !")

print()
print("=" * 70)
print("Les arbres de decision revelent quelles features comptent vraiment !")
print("=" * 70)


# Visualiser l'importance des features
# Type: Code executable
# =============================================================================
# ETAPE 5 : VISUALISATION DE L'IMPORTANCE DES FEATURES
# =============================================================================
# Une visualisation claire de l'importance aide a comprendre
# le comportement de l'arbre.

print("=" * 70)
print("VISUALISATION DE L'IMPORTANCE DES FEATURES")
print("=" * 70)
print()

# Preparation et entrainement
X = df[['sepal_length', 'sepal_width', 'petal_length', 'petal_width']].values
y = df['species'].values
feature_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y)
tree = DecisionTreeClassifier(max_depth=3, random_state=42)
tree.fit(X_train, y_train)

importances = tree.feature_importances_

print("Ce graphique montre l'importance de chaque mesure pour classifier les fleurs.")
print()

# Trier par importance
sorted_idx = np.argsort(importances)
sorted_names = [feature_names[i] for i in sorted_idx]
sorted_importances = importances[sorted_idx]

# Couleurs selon le type (sepal vs petal)
colors = ['#E5D7F5' if 'sepal' in name else '#F7E64D' for name in sorted_names]

plt.figure(figsize=(10, 6))
bars = plt.barh(sorted_names, sorted_importances, color=colors, edgecolor='white', linewidth=1)

# Ajouter les valeurs sur les barres
for bar, imp in zip(bars, sorted_importances):
    if imp > 0.01:
        plt.text(imp + 0.01, bar.get_y() + bar.get_height()/2,
                 f'{imp:.1%}', va='center', fontsize=11)

plt.xlabel('Importance', fontsize=12)
plt.title('Importance des Features dans l\'Arbre de Decision', fontsize=14, fontweight='bold')
plt.xlim(0, 1.1)

# Legende personnalisee
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor='#F7E64D', label='Petales'),
                  Patch(facecolor='#E5D7F5', label='Sepales')]
plt.legend(handles=legend_elements, loc='lower right')

plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.show()

# --- Analyse ---
print()
print("ANALYSE DU GRAPHIQUE")
print("-" * 40)
print()
print("   OBSERVATIONS :")
print()

petal_importance = sum(importances[i] for i, name in enumerate(feature_names) if 'petal' in name)
sepal_importance = sum(importances[i] for i, name in enumerate(feature_names) if 'sepal' in name)

print(f"   → Importance totale des PETALES : {petal_importance:.1%}")
print(f"   → Importance totale des SEPALES : {sepal_importance:.1%}")
print()

if petal_importance > 0.9:
    print("   CONCLUSION FORTE :")
    print("   → Les petales dominent completement la classification !")
    print("   → On pourrait presque ignorer les sepales.")
print()

print("   IMPLICATION PRATIQUE :")
print("   → Pour identifier une espece d'iris, mesurez les PETALES.")
print("   → Les mesures de sepales sont quasi inutiles pour ce probleme.")
print()
print("   C'est la puissance des arbres de decision :")
print("   → Ils revelent automatiquement les features importantes.")


# Exercice: Comparer profondeurs
# Type: Exercice
# =============================================================================
# EXERCICE : IMPACT DE LA PROFONDEUR
# =============================================================================
# Objectif : Comprendre comment la profondeur affecte les performances
# et le risque d'overfitting.

print("=" * 70)
print("EXERCICE : COMPARER DIFFERENTES PROFONDEURS")
print("=" * 70)
print()
print("Question : Quelle profondeur donne les meilleurs resultats ?")
print("           Une profondeur plus grande est-elle toujours meilleure ?")
print()

depths = [1, 2, 3, 5, 10, None]  # None = sans limite
results = []

# TODO: Pour chaque profondeur :
# 1. Creez un arbre avec cette profondeur
# 2. Entrainez-le sur les donnees train
# 3. Evaluez sur les donnees test
# 4. Stockez l'accuracy

# for depth in depths:
#     tree = DecisionTreeClassifier(max_depth=depth, random_state=42)
#     ...

print("-" * 40)
print("INDICE : Comparez aussi l'accuracy TRAIN vs TEST.")
print("         Un grand ecart indique de l'overfitting !")


# SHAP pour les arbres de decision
# Type: Code executable
# =============================================================================
# EXPLICABILITE AVEC SHAP
# =============================================================================
# SHAP quantifie la contribution de chaque feature a chaque prediction.
# C'est complementaire a l'importance globale des features.

print("=" * 70)
print("EXPLICABILITE AVEC SHAP")
print("=" * 70)
print()

# Preparation et entrainement
X = df[['sepal_length', 'sepal_width', 'petal_length', 'petal_width']].values
y_encoded = pd.factorize(df['species'])[0]
species_names = pd.factorize(df['species'])[1].tolist()
feature_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']

X_train, X_test, y_train, y_test = train_test_split(X, y_encoded, test_size=0.2, random_state=42)

tree = DecisionTreeClassifier(max_depth=4, random_state=42)
tree.fit(X_train, y_train)

print("1. CALCUL DES VALEURS SHAP")
print("-" * 40)

# SHAP TreeExplainer
explainer = shap.TreeExplainer(tree)
shap_values = explainer.shap_values(X_test)

print("   ✓ Valeurs SHAP calculees")
print(f"   → {len(X_test)} echantillons analyses")
print(f"   → {len(feature_names)} features par echantillon")
print(f"   → {len(species_names)} classes possibles")
print()

# --- Importance globale SHAP ---
print("2. IMPORTANCE GLOBALE SELON SHAP")
print("-" * 40)
print()

# Calculer l'importance moyenne absolue
# shap_values est (n_samples, n_features, n_classes)
mean_shap = np.abs(shap_values).mean(axis=(0, 2))

# Trier par importance
sorted_idx = np.argsort(mean_shap)
sorted_names = [feature_names[i] for i in sorted_idx]
sorted_shap = mean_shap[sorted_idx]

colors = ['#E5D7F5' if 'sepal' in name else '#F7E64D' for name in sorted_names]

plt.figure(figsize=(10, 5))
plt.barh(sorted_names, sorted_shap, color=colors, edgecolor='white')
plt.xlabel('Importance SHAP moyenne (|SHAP|)', fontsize=11)
plt.title('Importance des features selon SHAP', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='x')
plt.tight_layout()
plt.show()

print()
print("   INTERPRETATION :")
print(f"   → Feature la plus importante : {feature_names[np.argmax(mean_shap)]}")
print("   → Les PETALES dominent (confirme l'analyse precedente)")
print()

# --- Comparaison avec importance classique ---
print("3. SHAP vs IMPORTANCE CLASSIQUE")
print("-" * 40)
print()
print(f"   {'Feature':<15} {'Gini Importance':>16} {'SHAP':>12}")
print("   " + "-" * 45)

for i, name in enumerate(feature_names):
    gini = tree.feature_importances_[i]
    shap_imp = mean_shap[i]
    print(f"   {name:<15} {gini:>16.3f} {shap_imp:>12.3f}")

print()
print("   Les deux methodes donnent des resultats similaires.")
print("   → SHAP est plus rigoureux mathematiquement.")
print("   → Gini est plus rapide a calculer.")

print()
print("=" * 70)
print("SHAP : Une explicabilite rigoureuse pour vos arbres !")
print("=" * 70)


# Expliquer une prediction individuelle
# Type: Code executable
# =============================================================================
# EXPLICABILITE INDIVIDUELLE : POURQUOI CETTE FLEUR ?
# =============================================================================
# Pour chaque prediction, nous pouvons expliquer exactement
# pourquoi l'arbre a choisi cette espece.

print("=" * 70)
print("EXPLICATION D'UNE PREDICTION INDIVIDUELLE")
print("=" * 70)
print()

# Preparation et entrainement
X = df[['sepal_length', 'sepal_width', 'petal_length', 'petal_width']].values
feature_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
y_encoded, species_labels = pd.factorize(df['species'])

X_train, X_test, y_train, y_test = train_test_split(X, y_encoded, test_size=0.2, random_state=42)

tree = DecisionTreeClassifier(max_depth=4, random_state=42)
tree.fit(X_train, y_train)

explainer = shap.TreeExplainer(tree)
shap_values = explainer.shap_values(X_test)

# Prendre une fleur specifique
idx = 0
sample = X_test[idx]
prediction = int(tree.predict([sample])[0])
predicted_species = species_labels[prediction]
true_species = species_labels[y_test[idx]]

# --- Presentation de la fleur ---
print("1. FLEUR ANALYSEE")
print("-" * 40)
print()
print("   Mesures de la fleur :")
for i, name in enumerate(feature_names):
    print(f"   → {name}: {sample[i]:.2f} cm")
print()
print(f"   Espece predite : {predicted_species}")
print(f"   Vraie espece   : {true_species}")
print(f"   Prediction {'CORRECTE ✓' if prediction == y_test[idx] else 'INCORRECTE ✗'}")
print()

# --- Contributions SHAP ---
print("2. CONTRIBUTIONS DE CHAQUE MESURE")
print("-" * 40)
print()
print(f"   Pourquoi '{predicted_species}' ?")
print()

# Extraire les SHAP pour cette prediction
sample_shap = shap_values[idx, :, prediction]

# Trier par contribution absolue
sorted_idx = np.argsort(np.abs(sample_shap))[::-1]

print(f"   {'Mesure':<15} {'Valeur':>10} {'Contribution':>14} {'Impact'}")
print("   " + "-" * 55)

for i in sorted_idx:
    name = feature_names[i]
    value = sample[i]
    contrib = sample_shap[i]

    if contrib > 0.1:
        impact = "↑↑ Tres favorable"
    elif contrib > 0:
        impact = "↑ Favorable"
    elif contrib < -0.1:
        impact = "↓↓ Tres defavorable"
    elif contrib < 0:
        impact = "↓ Defavorable"
    else:
        impact = "— Neutre"

    print(f"   {name:<15} {value:>10.2f} {contrib:>+14.3f} {impact}")

print()

# --- Visualisation ---
print("3. VISUALISATION DES CONTRIBUTIONS")
print("-" * 40)
print()

colors = ['#27ae60' if v > 0 else '#e74c3c' for v in sample_shap]

plt.figure(figsize=(10, 5))
plt.barh(feature_names, sample_shap, color=colors, edgecolor='white')
plt.axvline(x=0, color='black', linestyle='-', linewidth=0.5)
plt.xlabel('Contribution SHAP', fontsize=11)
plt.title(f'Pourquoi "{predicted_species}" ? (Contributions par feature)', fontsize=14, fontweight='bold')
plt.grid(True, alpha=0.3, axis='x')

# Legende
from matplotlib.patches import Patch
legend_elements = [Patch(facecolor='#27ae60', label='Favorable'),
                  Patch(facecolor='#e74c3c', label='Defavorable')]
plt.legend(handles=legend_elements, loc='lower right')

plt.tight_layout()
plt.show()

# --- Explication en langage naturel ---
print()
print("4. EXPLICATION EN LANGAGE NATUREL")
print("-" * 40)
print()
print(f"   'Cette fleur est classee {predicted_species} car :'")
print()

for i in sorted_idx:
    if abs(sample_shap[i]) > 0.01:
        name = feature_names[i]
        value = sample[i]
        contrib = sample_shap[i]

        if 'petal_length' in name:
            desc = "longueur du petale"
        elif 'petal_width' in name:
            desc = "largeur du petale"
        elif 'sepal_length' in name:
            desc = "longueur du sepale"
        else:
            desc = "largeur du sepale"

        if contrib > 0:
            print(f"   → Sa {desc} ({value:.1f} cm) FAVORISE cette espece")
        else:
            print(f"   → Sa {desc} ({value:.1f} cm) est atypique mais compensee")

print()
print("=" * 70)
print("Les arbres de decision : interpretables et explicables !")
print("=" * 70)

