Lorsque l’on enseigne les mathématiques au lycée, on est souvent amené à construire des arbres de probabilités.

Et là, c’est la galère pour certain(e)s… Mais les profs de maths utilisent de plus en plus le langage $\LaTeX$ et il y a beaucoup de façons de satisfaire les demandent.

Nous allons dans cet article parler de tout ça.

Arbres de probabilités en LaTeX au lycée: avec TiKZ et “child”

La base de “child”

La construction d’un arbre en TikZ repose sur le mot-clé child, qui permet d’attacher des nœuds fils à un nœud parent de manière hiérarchique et intuitive. La syntaxe de base est la suivante :

\begin{tikzpicture}
  \node {Départ}
    child { node {A} }
    child { node {B} };
\end{tikzpicture}

Chaque instruction child { node {...} } crée une branche depuis le nœud parent. On peut bien sûr imbriquer les child pour obtenir plusieurs niveaux :

\begin{tikzpicture}
  \node {Départ}
    child { node {A}
      child { node {A1} }
      child { node {A2} }
    }
    child { node {B}
      child { node {B1} }
      child { node {B2} }
    };
\end{tikzpicture}

Comme on peut le constater, par défaut, les distances ne sont pas calculées correctement et il se peut qu’il y ait des chevauchements.

“child” et les options

Deux options sont particulièrement utiles pour ajuster la mise en page : level distance, qui contrôle la distance verticale entre deux niveaux, et sibling distance, qui règle l’écartement horizontal entre les nœuds frères. On les précise généralement dans les options de \begin{tikzpicture} ou via [level 1/.style={...}, level 2/.style={...}] pour affiner niveau par niveau.

\begin{tikzpicture}[%
	level distance = 1cm,
	level 1/.style = {sibling distance = 15mm},
	level 2/.style = {sibling distance = 7mm}
	]
  \node {Départ}
    child { node {A}
      child { node {A1} }
      child { node {A2} }
    }
    child { node {B}
      child { node {B1} }
      child { node {B2} }
    };
\end{tikzpicture}

ou encore, pour mettre de la couleur:

\begin{tikzpicture}[%
	level distance = 1cm,
	level 1/.style = {
		sibling distance = 15mm,
		color = red
		},
	level 2/.style = {
		sibling distance = 7mm,
		color = blue
		}
	]
  \node {Départ}
    child { node {A}
      child { node {A1} }
      child { node {A2} }
    }
    child { node {B}
      child { node {B1} }
      child { node {B2} }
    };
\end{tikzpicture}

Mais à ce stade, nous n’avons qu’un arbre des possibilités, sans probabilités…

“child” et les probabilités

Pour un arbre de probabilités, il faut pouvoir étiqueter chaque branche avec la probabilité correspondante. On utilise pour cela l’option edge from parent node qui permet de placer un nœud sur la branche reliant le parent à l’enfant :

\begin{tikzpicture}[
  level 1/.style={sibling distance = 4cm, color = red},
  level 2/.style={sibling distance = 2cm, color = blue}
]
  \node {Départ}
    child { node {A}
      child { node {$A_1$}
        edge from parent node[above left] {$0{,}4$}
      }
      child { node {$A_2$}
        edge from parent node[above right] {$0{,}6$}
      }
      edge from parent node[left] {$0{,}3$}
    }
    child { node {B}
      child { node {$B_1$}
        edge from parent node[above left] {$0{,}5$}
      }
      child { node {$B_2$}
        edge from parent node[above right] {$0{,}5$}
      }
      edge from parent node[right] {$0{,}7$}
    };
\end{tikzpicture}

Notez que edge from parent node se place après le nœud enfant mais avant la fermeture de l’accolade du child. C’est un point sur lequel on bute facilement au début : si on le place ailleurs, l’étiquette n’apparaît tout simplement pas, ou se retrouve mal positionnée.

“child” et un peu de style en plus…

Par défaut, TikZ affiche les nœuds sans bordure ni fond particulier, ce qui donne un rendu assez brut. Pour un arbre de probabilités présentable, on peut améliorer l’apparence en jouant sur la forme des nœuds, les couleurs et le style des branches.

La forme des nœuds se définit via l’option shape ou plus directement avec les styles prédéfinis comme circle, rectangle ou ellipse. Pour les nœuds feuilles (les issues finales), on préfère souvent un simple rectangle arrondi, obtenu avec rounded corners :

\tikzset{
  noeud/.style = {
  	circle, 
  	draw, 
  	fill=blue!15, 
  	minimum size=0.8cm
  },
  feuille/.style = {
  rectangle, 
  rounded corners, 
  draw, 
  fill=green!15, 
  minimum width=1.2cm
  }
}

\begin{tikzpicture}[
  level 1/.style={sibling distance = 4cm, color = red},
  level 2/.style={sibling distance = 2cm, color = blue}
]
  \node[noeud] {D}
    child { node[noeud] {A}
      child { node[feuille] {$A_1$}
        edge from parent node[above left] {$0{,}4$}
      }
      child { node[feuille] {$A_2$}
        edge from parent node[above right] {$0{,}6$}
      }
      edge from parent node[left] {$0{,}3$}
    }
    child { node[noeud] {B}
      child { node[feuille] {$B_1$}
        edge from parent node[above left] {$0{,}5$}
      }
      child { node[feuille] {$B_2$}
        edge from parent node[above right] {$0{,}5$}
      }
      edge from parent node[right] {$0{,}7$}
    };
\end{tikzpicture}

Le style des branches peut également être personnalisé. On définit le style des arêtes via edge from parent/.style, par exemple pour obtenir des branches plus épaisses ou colorées :

\tikzset{
  noeud/.style = {
  	circle, 
  	draw, 
  	fill=blue!15, 
  	minimum size=0.8cm
  },
  feuille/.style = {
  rectangle, 
  rounded corners, 
  draw, 
  fill=green!15, 
  minimum width=1.2cm
  }
}

\begin{tikzpicture}[
  edge from parent/.style={draw, thick},
  level distance = 2cm,
  level 1/.style={sibling distance = 4cm, color = red},
  level 2/.style={sibling distance = 2cm, color = blue}
]
  \node[noeud] {$\Omega$}
    child { node[noeud] {A}
      child { node[feuille] {$A_1$}
        edge from parent node[fill=white] {$0{,}4$}
      }
      child { node[feuille] {$A_2$}
        edge from parent node[fill=white] {$0{,}6$}
      }
      edge from parent node[fill=white] {$0{,}3$}
    }
    child { node[noeud] {B}
      child { node[feuille] {$B_1$}
        edge from parent node[fill=white] {$0{,}5$}
      }
      child { node[feuille] {$B_2$}
        edge from parent node[fill=white] {$0{,}5$}
      }
      edge from parent node[fill=white] {$0{,}7$}
    };
\end{tikzpicture}

Probabilités composées en bout de branches

Une fois l’arbre construit et stylisé, il est naturel d’afficher à droite de chaque feuille la probabilité de l’issue correspondante, c’est-à-dire le produit des probabilités rencontrées le long du chemin. Pour cela, on ne peut plus s’appuyer sur edge from parent node — on est arrivé au bout de l’arbre — et on place simplement un nœud annoté à droite de chaque feuille, en utilisant l’option node[right] directement après le nœud feuille :

\begin{tikzpicture}[
  > = latex,
  grow = right,
  edge from parent/.style={draw, thick},
  level distance = 2cm,
  level 1/.style={sibling distance = 4cm, color = red},
  level 2/.style={sibling distance = 2cm, color = blue}
]
  \tikzset{
  noeud/.style  = {circle, draw, fill=blue!15, minimum size=0.8cm, outer sep = 2mm},
  feuille/.style = {rectangle, rounded corners, draw, fill=green!15,
                    minimum width=1cm, outer xsep=2mm},
  prob/.style   = {font=\small, fill=white, inner sep=1pt}
}
  \node[noeud] {$\Omega$}
    child { node[noeud] {A}
      child { node[feuille] (a1) {$A_1$}
        edge from parent node[prob] {$0{,}4$}
      }
      child { node[feuille] (a2) {$A_2$}
        edge from parent node[prob] {$0{,}6$}
      }
      edge from parent node[prob] {$0{,}3$}
    }
    child { node[noeud] {B}
      child { node[feuille] (b1) {$B_1$}
        edge from parent node[prob] {$0{,}5$}
      }
      child { node[feuille] (b2) {$B_2$}
        edge from parent node[prob] {$0{,}5$}
      }
      edge from parent node[prob] {$0{,}7$}
    };
    
    % Flèches et probabilités composées
  \draw[->, thick, orange] (a1.east) -- ++(1,0)
    node[right] {\small $0{,}3 \times 0{,}4 = 0{,}12$};
  \draw[->, thick, orange] (a2.east) -- ++(1,0)
    node[right] {\small $0{,}3 \times 0{,}6 = 0{,}18$};
  \draw[->, thick, orange] (b1.east) -- ++(1,0)
    node[right] {\small $0{,}7 \times 0{,}5 = 0{,}35$};
  \draw[->, thick, orange] (b2.east) -- ++(1,0)
    node[right] {\small $0{,}7 \times 0{,}5 = 0{,}35$};
\end{tikzpicture}

Notez ici l’ajout de l’option option grow=right qui permet de construire l’arbre non plus à la verticale, mais à l’horizontal.

Arbres de probabilités en LaTeX au lycée: génération en Python

Nous allons ici utiliser pythontex pour construire n’importe quel arbre à deux niveaux.

L’idée est de confier entièrement à Python la génération du code TikZ, PythonTeX se chargeant d’injecter le résultat directement dans le document LaTeX. L’arbre est décrit en Python sous forme d’une structure arborescente imbriquée — un dictionnaire récursif — que l’utilisateur remplit avec ses nœuds et ses probabilités, et une fonction se charge de le traduire en code TikZ.

Mais pour que cette automatisation ait un réel sens, il faut que la fonction Python apporte quelque chose en plus. Cela va se passer dans la gestion et l’écriture des probabilités.

Voici le programme python auquel va faire référence le fichier $\LaTeX$:

from fractions import Fraction

def formater_prob(prob_str,mode):
    """Formate une probabilité saisie sous forme LaTeX."""
    #if "/" in prob_str:
    if mode=="fraction":
        if "/" in prob_str:
            num, den = prob_str.split("/")
        else:
            num, den = Fraction(prob_str).numerator, Fraction(prob_str).denominator
        return f"$\\dfrac{{{num.strip()}}}{{{den.strip()}}}$"
    else:
        return str(float(Fraction(prob_str))).replace(".", ",")  # on laisse tel quel, ex: "0,3"

def generer_nodes(noeud, compteur, prob_composee=Fraction(1), mode="decimal", decimales=3):
    code = ""
    style = "noeud"

    if not noeud["enfants"]:  # nœud feuille
        style = "feuille"
        nom = f"f{compteur[0]}"
        if mode == "fraction":
            p = prob_composee
            prob_composee_str = f"\\dfrac{{{p.numerator}}}{{{p.denominator}}}"
        else:
            prob_float = float(prob_composee)
            prob_composee_str = f"{prob_float:.{decimales}f}".replace(".", ",")
        code += f'[{style}] ({nom}) {{{noeud["label"]}}}\n'
        compteur[1].append((nom, prob_composee_str))
        compteur[0] += 1
    else:
        code += f'[{style}] {{{noeud["label"]}}}\n'
        for enfant in noeud["enfants"]:
            try:
                prob = Fraction(enfant["prob"])  # gère "4/10", "1/2", etc.
            except ValueError:
                prob = Fraction(enfant["prob"].replace(",", "."))  # gère "0.4", "0,4"
            code += "child {\n"
            code += "node " + generer_nodes(
                enfant,
                compteur,
                prob_composee=prob_composee * prob,
                mode=mode,
                decimales=decimales
            )
            prob_label = formater_prob(enfant["prob"],mode)
            code += f'edge from parent node[prob] {{{prob_label}}}\n'
            code += "}\n"

    return code


def generer_fleches(feuilles, longueur=1.5):
    code = ""
    for nom, prob in feuilles:
        code += f'\\draw[->, thick] ({nom}.east) -- ++({longueur},0)\n'
        code += f'  node[right] {{\\small ${prob}$}};\n'
    return code


def arbre_tikz(arbre):
    compteur = [0, []]

    # Récupération des paramètres avec valeurs par défaut
    mode      = arbre.get("mode", "decimal")
    decimales = arbre.get("decimales", 3)
    ld1       = arbre.get("level distance 1", 3)
    ld2       = arbre.get("level distance 2", 3)
    sd1       = arbre.get("sibling distance 1", 4)
    sd2       = arbre.get("sibling distance 2", 2)
    fleche    = arbre.get("fleche", 1.5)

    preambule = rf"""
\begin{{tikzpicture}}[
  grow=right,
  edge from parent/.style={{draw, thick}},
  level 1/.style={{sibling distance={sd1}cm, level distance={ld1}cm}},
  level 2/.style={{sibling distance={sd2}cm, level distance={ld2}cm}}
]
\tikzset{{
  noeud/.style   = {{circle, draw, fill=blue!15, minimum size=0.8cm}},
  feuille/.style = {{rectangle, rounded corners, draw, fill=green!15,
                    minimum width=1.2cm}},
  prob/.style    = {{font=\small, fill=white, inner sep=1pt}}
}}
"""

    corps  = "  \\node " + generer_nodes(
        arbre,
        compteur,
        prob_composee=Fraction(1),
        mode=mode,
        decimales=decimales
    ) + ";"
    fleches = generer_fleches(compteur[1], fleche)
    fin     = "\n\\end{tikzpicture}"

    return preambule + corps + "\n" + fleches + fin

Et voici un exemple à compiler avec pythontex:

\documentclass{article}
\usepackage{tikz}
\usepackage{pythontex}
\usepackage{amsmath}

\begin{document}

\begin{pycode}
from arbre_proba import arbre_tikz

arbre = {
	"label": "D",
	"mode": "fraction",
	"level distance 1": 3,
	"level distance 2": 3,
	"sibling distance 1": 4,
	"sibling distance 2": 2,
	"fleche": 1.5,
	"enfants": [
		{
			"prob": "3/10",
			"label": "A",
			"enfants": [
				{"prob": "4/10", "label": "$A_1$", "enfants": []},
				{"prob": "6/10", "label": "$A_2$", "enfants": [
					{"prob": "1/5", "label": "$C_1$", "enfants": []},
					{"prob": "4/5", "label": "$C_2$", "enfants": []}
					]}
			]
		},
		{
			"prob": "7/10",
			"label": "B",
			"enfants": [
				{"prob": "1/2", "label": "$B_1$", "enfants": []},
				{"prob": "1/2", "label": "$B_2$", "enfants": []}
			]
		}
	]
}

print(arbre_tikz(arbre))
\end{pycode}

\end{document}

Avec l’option de mode = "fraction", on obtient l’arbre de gauche. Sans cette option (avec l’option par défaut: mode="decimal"), on obtient celui de droite. Les calculs et les formatages sont automatiques.

Arbre de Bernoulli

J’avais écrit il y a quelques temps un article sur la construction automatisée des arbres de Bernoulli:


0 0 votes
Évaluation de l'article
S’abonner
Notification pour
guest
0 Commentaires
Le plus ancien
Le plus récent Le plus populaire
Commentaires en ligne
Afficher tous les commentaires
0
Nous aimerions avoir votre avis, veuillez laisser un commentaire.x