Corrigé Sujet N°11
- Prédiction d'Habitat du Renard (k-NN) -
Vous pouvez télécharger les éléments suivants en cliquant sur les boutons associés :
Question 1
Écrire le code de la fonction
distance qui prend en paramètres deux habitats sous forme de dictionnaires et renvoie la distance euclidienne entre ces deux habitats selon la formule donnée dans l'énoncé.
Correction simple : On calcule la somme des carrés des différences pour chacune des 5 caractéristiques, puis on applique la racine carrée.
from math import sqrt
def distance(habitat_1, habitat_2):
"""Calcule la distance euclidienne entre deux habitats."""
d = (
(habitat_1['vegetation'] - habitat_2['vegetation']) ** 2 +
(habitat_1['proximite_eau'] - habitat_2['proximite_eau']) ** 2 +
(habitat_1['densite_urbaine'] - habitat_2['densite_urbaine']) ** 2 +
(habitat_1['disponibilite_proies'] - habitat_2['disponibilite_proies']) ** 2 +
(habitat_1.get('presence_renard', 0) - habitat_2.get('presence_renard', 0)) ** 2
)
return sqrt(d)
# Test rapide
h1 = {'vegetation': 9, 'proximite_eau': 6, 'densite_urbaine': 0,
'disponibilite_proies': 4, 'presence_renard': 1}
h2 = {'vegetation': 5, 'proximite_eau': 2, 'densite_urbaine': 4,
'disponibilite_proies': 6, 'presence_renard': 0}
print(distance(h1, h2)) # Doit s'approcher de 7.211...
Correction avancée : On peut écrire une version générique qui calcule la distance sur toutes les clés communes aux deux dictionnaires, pour ne pas dépendre d'une liste de clés codée en dur.
from math import sqrt
def distance(habitat_1, habitat_2):
"""Version avancée : distance sur toutes les clés numériques communes."""
cles = set(habitat_1.keys()) & set(habitat_2.keys())
return sqrt(sum((habitat_1[c] - habitat_2[c]) ** 2 for c in cles))
# On s'assure que les deux dictionnaires ont les mêmes clés
h_test = {'vegetation': 9, 'proximite_eau': 6, 'densite_urbaine': 0,
'disponibilite_proies': 4, 'presence_renard': 1}
nouveau = {'vegetation': 5, 'proximite_eau': 2, 'densite_urbaine': 4,
'disponibilite_proies': 6, 'presence_renard': 0}
print(round(distance(h_test, nouveau), 12)) # >> 7.211102550927978
Question 2
Écrire le code de la fonction
distance_d_un_habitat qui prend en paramètres un habitat et une liste d'habitats, et renvoie une liste de tuples (distance, habitat).
Correction simple : On parcourt chaque habitat de la liste, on calcule sa distance avec l'habitat fourni, et on construit la liste de tuples.
def distance_d_un_habitat(habitat, habitats):
"""Retourne la liste de tuples (distance, habitat) pour chaque habitat de la liste."""
result = []
for h in habitats:
d = distance(habitat, h)
result.append((d, h))
return result
Correction avancée : Une compréhension de liste rend l'écriture plus concise. On peut aussi ajouter un tri optionnel par distance pour faciliter l'exploitation du résultat.
def distance_d_un_habitat(habitat, habitats):
"""Version avancée avec compréhension de liste."""
return [(distance(habitat, h), h) for h in habitats]
Question 3
Tester la fonction
distance_d_un_habitat avec l'habitat nouveau et zones_connues, en affichant les 3 premiers tuples de la liste. Les résultats attendus sont indiqués dans l'énoncé.
Correction simple : On appelle la fonction et on affiche les 3 premiers éléments de la liste retournée.
nouveau = {
'vegetation': 5,
'proximite_eau': 2,
'densite_urbaine': 4,
'disponibilite_proies': 6
}
distances = distance_d_un_habitat(nouveau, zones_connues)
for t in distances[:3]:
print(t)
# >> (7.211102550927978, {'vegetation': 9, 'proximite_eau': 6, ...})
# >> (8.660254037844387, {'vegetation': 10, 'proximite_eau': 5, ...})
# >> (5.196152422706632, {'vegetation': 8, 'proximite_eau': 5, ...})
Remarque : L'habitat
nouveau ne possède pas la clé presence_renard. C'est normal : on cherche à prédire justement cette valeur. La distance est calculée sur les 4 caractéristiques communes. On utilise .get('presence_renard', 0) ou on s'assure que la fonction distance ignore les clés absentes (version avancée avec l'intersection des clés).
Question 4
La fonction
presence_renard contient une erreur de traitement des tuples. Corriger la fonction.
Identification du problème : Dans la boucle, la variable
distance reçoit habitat[0] (le premier élément du tuple, qui est un float), et caracteristiques reçoit habitat[1] (le dictionnaire). Mais le test suivant utilise distance['presence_renard'] comme si distance était un dictionnaire, alors que c'est un flottant. Il faut utiliser caracteristiques['presence_renard'].
Correction simple : On remplace
distance['presence_renard'] par caracteristiques['presence_renard'].
def presence_renard(k, habitat, habitats):
"""Vérifie si l'habitat donné a plus de k/2 voisins avec des renards."""
voisins = k_plus_proches(k, habitat, habitats)
n_renards = 0
for voisin in voisins:
distance_val = voisin[0] # float : la distance euclidienne
caracteristiques = voisin[1] # dict : l'habitat voisin
if caracteristiques['presence_renard'] == 1: # CORRECTION : caracteristiques, pas distance
n_renards += 1
return n_renards > k / 2
Correction avancée : On peut utiliser le déballage de tuple directement dans la boucle
for pour rendre le code plus lisible, et ajouter une vérification que k est positif.
def presence_renard(k, habitat, habitats):
"""Version avancée avec déballage de tuple et vérification de k."""
if k <= 0:
raise ValueError("k doit être un entier strictement positif.")
voisins = k_plus_proches(k, habitat, habitats)
n_renards = sum(
1 for _, caract in voisins
if caract['presence_renard'] == 1
)
return n_renards > k / 2
Question 5
L'habitat
nouveau est-il susceptible ou non de contenir une population de renards ? Expliquer en utilisant la fonction presence_renard avec plusieurs valeurs pour k.
Correction simple : On teste avec différentes valeurs de k et on observe la majorité des résultats.
nouveau = {
'vegetation': 5,
'proximite_eau': 2,
'densite_urbaine': 4,
'disponibilite_proies': 6
}
for k in [1, 3, 5, 7, 9]:
resultat = presence_renard(k, nouveau, zones_connues)
print(f"k={k} : présence du renard prédite = {resultat}")
Interprétation avancée : Si la majorité des valeurs de k donne
L'habitat
False, l'habitat nouveau est probablement non favorable à la présence du renard. Le choix de k influence le résultat : un k trop petit peut être sensible aux anomalies locales (bruit), un k trop grand lisse les décisions. En pratique, on choisit k impair pour éviter les ex-aequo et on teste plusieurs valeurs pour évaluer la robustesse de la prédiction.
L'habitat
nouveau présente une végétation modérée (5/10), une faible proximité de l'eau (2/10) et une densité urbaine moyenne (4/10). Ces caractéristiques sont défavorables aux renards (qui préfèrent les zones à forte végétation et proche de l'eau). La prédiction k-NN devrait donc pencher vers l'absence de renard.
# Analyse détaillée pour k=5
voisins = k_plus_proches(5, nouveau, zones_connues)
print("5 voisins les plus proches :")
for dist, caract in voisins:
present = "Renard présent" if caract['presence_renard'] == 1 else "Renard absent"
print(f" distance={dist:.2f} | {present}")
print("\nPrédiction finale (k=5) :", presence_renard(5, nouveau, zones_connues))
© 2026 - Clément Legoubé