Un corrigé pédagogique pour comprendre et justifier vos réponses. Les conseils de rédaction ne constituent pas un barème officiel détaillé.
Application
Salaires, dictionnaires et biais des k plus proches voisins
Le sujet étudie un jeu de données d’employés avec quatre champs : experience et etudes sont des nombres d’années, sexe vaut F ou M, salaire est un entier en euros. Le fichier donnees.py contient six employés ; donnees_completes.py en contient 2000. Le contexte de l’énoncé évoque les écarts de salaire femmes-hommes, puis interroge un calcul automatique de salaire à l’embauche. Une différence statistique ne suffit pas à identifier toutes les causes d’un écart : nous analysons ici exactement les données et la distance du programme fourni.
| Expérience | Études | Sexe | Salaire |
|---|---|---|---|
| 5 | 3 | F | 2400 |
| 3 | 3 | M | 2550 |
| 5 | 5 | F | 2500 |
| 3 | 5 | M | 2800 |
| 2 | 5 | F | 2300 |
| 2 | 3 | M | 2700 |
Le dossier comporte le sujet et les fichiers listés dans les téléchargements. Les programmes de départ et un aperçu des grands jeux de données sont présentés ci-dessous ; les fichiers complets sont téléchargeables. Travaillez sur une copie. Les appels professeur signalés dans les questions servent à présenter votre démarche ; vous pouvez aussi demander de l’aide en cas de difficulté.
Code fourni : analyse.py
import donnees
import donnees_completes
from math import sqrt
def salaire_moyen_condition(employes, champ, valeur):
'''Renvoie le salaire moyen des employes ayant val comme valeur associée
au champ donné en argument.
Si le nombre d'employés considéré est nul, cette fonction renvoie None'''
pass # à implémenter
def test_salaire_moyen_condition():
e = donnees.employes
assert salaire_moyen_condition([], 'sexe', 'F') == None
assert salaire_moyen_condition(e, 'sexe', 'F') == 2400.0
assert salaire_moyen_condition(e, 'etudes', 3) == 2550.0
assert salaire_moyen_condition(e, 'etudes', 12) == None
def effectif_par_sexe(employes):
'''Renvoie un dictionnaire ayant deux clés 'F' et 'M'
associée respectivement au nombre d'employées femmes et au
nombre d'employés hommes dans les données en arguments.'''
pass # à implémenter
def test_effectif_par_sexe():
e = donnees.employes
assert effectif_par_sexe(e) == {'F': 3, 'M': 3}
def calcul_ecart_sexe(employes):
'''Renvoie l'écart de salaire en pourcentage pour les femmes
par rapport aux hommes'''
moy_h = salaire_moyen_condition(employes, 'sexe', 'M')
moy_f = salaire_moyen_condition('employes', 'sexe', 'F')
return moy_h - moy_f
# Attribution d'un premier salaire après embauche par les k plus proches voisins
def sexe_vers_entier(e):
if e['sexe'] == 'F':
return 1
else:
return -1
def distance(e1, e2):
'''Renvoie la mesure de distance entre deux personnes.'''
s = 0
s = s + (sexe_vers_entier(e1) - sexe_vers_entier(e2))**2
s = s + (e1['experience'] - e2['experience'])**2
s = s + (e1['etudes'] - e2['etudes'])**2
return sqrt(s)
def k_plus_proches(k, employes, e):
'''Renvoie les k employes les plus proches de e par la
distance définie au dessus.'''
e_d = [(distance(e, employes[i]), i) for i in range(len(employes))]
e_d.sort() # va trier en premier sur la distance
voisins = []
for i in range(k):
voisins.append(employes[e_d[i][1]])
return voisins
def salaire_moyen(employes):
'''Renvoie le salaire moyen pour une liste d'employes'''
if len(employes) == 0:
return None
s = sum(e['salaire'] for e in employes)
return s/len(employes)
def salaire_par_proximite(employes, e):
'''Prend en entrée une liste d'employés et un dictionnaire comportant
les champs experience, etudes et sexe et renvoie le salaire le plus
proche en moyennant les 3 plus proches voisins'''
voisins = k_plus_proches(3, employes, e)
return salaire_moyen(voisins)
Code fourni : donnees.py
employes = [
{'experience': 5, 'etudes': 3, 'sexe': 'F', 'salaire': 2400},
{'experience': 3, 'etudes': 3, 'sexe': 'M', 'salaire': 2550},
{'experience': 5, 'etudes': 5, 'sexe': 'F', 'salaire': 2500},
{'experience': 3, 'etudes': 5, 'sexe': 'M', 'salaire': 2800},
{'experience': 2, 'etudes': 5, 'sexe': 'F', 'salaire': 2300},
{'experience': 2, 'etudes': 3, 'sexe': 'M', 'salaire': 2700}
]
Données fournies : donnees_completes.py
Le fichier donnees_completes.py contient la liste employes et ses 2 000 enregistrements. Voici les trois premiers, dans l’ordre du fichier.
sexe | salaire | experience | etudes |
|---|---|---|---|
| M | 2423 | 4 | 1 |
| F | 2479 | 8 | 5 |
| F | 2111 | 5 | 1 |
Pour exécuter le programme sur toutes les données et retrouver les résultats du sujet, utilisez le fichier intégral fourni. Il est aussi inclus dans l’archive ZIP de cette page.
Question 1
#Écrire salaire_moyen_condition(employes, champ, valeur), qui renvoie le salaire moyen (float) des employés dont le champ experience, etudes ou sexe vaut la valeur fournie. Renvoyer None si aucun employé ne correspond. Utiliser la fonction de test fournie et déterminer les salaires moyens des femmes et des hommes dans le jeu complet.
PythonUn filtre change aussi le dénominateurÉcrivez votre solution et mettez-la à l’épreuve
Écrivez salaire_moyen_condition(employes, champ, valeur). Les employés sont des dictionnaires contenant salaire et le champ recherché. Renvoyez la moyenne des seuls employés correspondants, ou None si aucun ne correspond. Les jeux ci-dessous sont de petits jeux complémentaires aux données téléchargeables.
def salaire_moyen_condition(employes, champ, valeur):
# À vous de jouer
pass
Les cas de test proposés :
- Le filtre écarte un salaire : Le dénominateur vaut 2, pas 3.
- Aucune correspondance : Une moyenne sans observation est absente, pas égale à zéro.
- Une moyenne réellement nulle : Le salaire nul reste une observation à compter.
- Un autre champ : Le champ est un paramètre : le filtre ne doit pas être figé sur le sexe.
- Aucun employé : Le parcours vide ne doit pas déclencher une division par zéro.
Indice
Le dénominateur est le nombre d’employés retenus.
Comprendre la correction
def salaire_moyen_condition(employes, champ, valeur):
total = 0
effectif = 0
for employe in employes:
if employe[champ] == valeur:
total += employe['salaire']
effectif += 1
if effectif == 0:
return None
return total / effectifOn filtre et on accumule simultanément la somme et le nombre de salaires concernés. Diviser par len(employes) serait faux lorsque le filtre écarte des personnes. Le jeu de test donne 2400.0 pour F et 2683.3333333333335 pour M. Les 2000 employés donnent 2229.1961382113823 pour F et 2438.0413385826773 pour M, soit 2229,20 € et 2438,04 € après arrondi final. Garder la précision pendant les calculs.
test_salaire_moyen_condition()
print(salaire_moyen_condition(donnees_completes.employes, 'sexe', 'F'))
print(salaire_moyen_condition(donnees_completes.employes, 'sexe', 'M'))Pour le filtre sexe == 'F' du petit jeu, les états utiles de l’accumulateur sont (2400, 1), puis (4900, 2), puis (7200, 3) : le premier nombre est la somme, le second l’effectif. Chaque employé non retenu laisse les deux valeurs inchangées. On divise seulement après le parcours, car une division à chaque étape ne calculerait pas la même moyenne. Une liste sans correspondance doit rester (0, 0) et conduire à None, pas à une moyenne de zéro.
Question 2
#Écrire effectif_par_sexe, prenant un tableau non vide d’employés et renvoyant un dictionnaire avec les clés F et M associées à leurs effectifs. Le petit jeu doit donner {'F':3, 'M':3}.
Comprendre la correction
def effectif_par_sexe(employes):
effectifs = {'F': 0, 'M': 0}
for employe in employes:
effectifs[employe['sexe']] += 1
return effectifsLes deux clés existent dès le départ, même si un sexe n’apparaît pas. Chaque employé incrémente un seul compteur. Sur les données complètes :984 femmes et 1016 hommes, dont la somme vaut 2000. La fonction fonctionne aussi pour une liste vide, bien que le sujet n’impose pas ce cas.
assert effectif_par_sexe([]) == {'F': 0, 'M': 0}
assert effectif_par_sexe([{'sexe': 'F'}]) == {'F': 1, 'M': 0}
test_effectif_par_sexe()Question 3
#L’écart est défini par (salaire moyen hommes - salaire moyen femmes) / salaire moyen hommes ×100. Expliquer les erreurs de calcul_ecart_sexe, proposer des assertions vérifiant None si un seul sexe est présent et un écart entre 0 et 100, puis corriger la fonction et calculer l’écart sur les données complètes.
PythonQuel salaire sert de référence au pourcentage ?Écrivez votre solution et mettez-la à l’épreuve
Corrigez calcul_ecart_sexe(employes) selon la formule du sujet : (moyenne hommes - moyenne femmes) / moyenne hommes × 100. La fonction salaire_moyen_condition est fournie. Renvoyez None si une moyenne manque ou si la référence masculine est nulle. Un écart négatif reste négatif : la formule ne garantit pas un résultat entre 0 et 100.
def calcul_ecart_sexe(employes):
# À vous de jouer
pass
Les cas de test proposés :
- Une baisse de 25 % : La différence 50 est divisée par la référence 200.
- Écart négatif : Prendre une valeur absolue changerait la définition.
- Femmes seulement : La moyenne de référence est absente.
- Hommes seulement : La comparaison demande les deux moyennes.
- Référence nulle : Une différence relative à zéro n’est pas définie.
- Égalité des moyennes : Ici zéro est bien un pourcentage défini.
Indice 1
Distinguer une chaîne de caractères de la variable homonyme.
Indice 2
Pourcentage et différence ont-ils la même unité ?
Comprendre la correction
Trois erreurs apparaissent : la chaîne "employes" est passée à la place de la liste, les moyennes absentes ne sont pas traitées, et une soustraction en euros n’est pas un pourcentage. La correction suit la formule et évite une division par zéro.
def calcul_ecart_sexe(employes):
moy_h = salaire_moyen_condition(employes, 'sexe', 'M')
moy_f = salaire_moyen_condition(employes, 'sexe', 'F')
if moy_h is None or moy_f is None or moy_h == 0:
return None
return (moy_h - moy_f) / moy_h * 100assert calcul_ecart_sexe([{'sexe': 'F', 'salaire': 100}]) is None
assert calcul_ecart_sexe([{'sexe': 'M', 'salaire': 100}]) is None
cas = [{'sexe': 'M', 'salaire': 200}, {'sexe': 'F', 'salaire': 150}]
assert calcul_ecart_sexe(cas) == 25.0
assert 0 <= calcul_ecart_sexe(cas) <= 100Attention à une imprécision de l’énoncé : avec la formule donnée, l’écart n’est pas toujours positif. Si la moyenne des femmes est supérieure à celle des hommes, il est négatif. La propriété 0≤écart≤100 ne s’applique que lorsque 0≤moy_f≤moy_h avec moy_h>0. Il ne faut ni écrêter le résultat ni prendre sa valeur absolue, car cela changerait la définition. Sur le fichier complet, l’écart est 8.56610579428093%, soit environ 8,57%.
Une assertion est-elle toujours vraie ?Un atelier pour expérimenter
Choisissez une réponse puis confrontez votre intuition à l’explication. Vous pouvez modifier vos choix.
Lire les réponses et leurs explications
- Femmes 300€, hommes 200€ : quel écart selon la formule ?
-50%. (200-300)/200×100=-50.
- Que faire lorsqu’aucune femme ne figure dans la liste ?
Retourner
None.. La moyenne manquante empêche la comparaison.
Appuyez votre explication sur les valeurs et les conditions exactes du programme.
Question 4
#Tester et comparer les salaires proposés aux deux futurs employés {experience:3, etudes:3, sexe: F} et {experience:3, etudes:3, sexe: M}. Identifier la source des écarts dans le programme et la corriger.
Comprendre la correction
Dans la distance fournie, F est codé 1 et M est codé-1. Changer seulement le sexe ajoute donc 4 au carré de la distance aux personnes de l’autre sexe. Les voisins sélectionnés diffèrent. Avec le petit jeu, les propositions sont 2416,67 € et 2683,33 € ; avec le jeu complet,2229,67 € et 2406,00 €. Retirons le terme lié au sexe :
def distance(e1, e2):
return sqrt((e1['experience'] - e2['experience'])**2
+ (e1['etudes'] - e2['etudes'])**2)Sur le petit jeu, les deux propositions deviennent 2550 €. Sur le jeu complet, elles deviennent 2229,6666666666665 €. Le tri fourni départage les distances égales par l’indice d’origine : en conservant cet ordre, ces résultats sont reproductibles. Les profils de mêmes expérience et études ont maintenant les mêmes voisins. Cela supprime la dépendance explicite au sexe de la personne candidate ; cela ne prouve pas que les salaires historiques ni toutes les autres dimensions du modèle sont exempts de biais.
for sexe in ('F', 'M'):
candidat = {'experience': 3, 'etudes': 3, 'sexe': sexe}
print(salaire_par_proximite(donnees_completes.employes, candidat))Un protocole de comparaison convaincant fixe l’expérience et le niveau d’études, puis ne modifie qu’une caractéristique à la fois. Affichez aussi les voisins sélectionnés et leurs distances : deux résultats identiques peuvent provenir de voisins différents, et deux résultats différents ne disent pas seuls quelle ligne du programme en est responsable. Ici, retirer le terme lié au sexe rend toutes les distances identiques pour les deux candidatures comparées. Le tri et la moyenne étant ensuite les mêmes, l’égalité des propositions en découle directement.
Changez la distance, observez les voisinsUn atelier pour expérimenter
Les six employés sont ceux de l’énoncé. Changez le sexe du candidat ou retirez ce critère de la distance, puis examinez les trois voisins réellement choisis.
Lire le résultat de l’expérience initiale
Salaire proposé : 2416.67€
Le tableau montre les carrés des distances : leur ordre est identique à celui des distances. Les égalités sont départagées par l’indice initial.
| Employé (indice) | Expérience | Études | Sexe | Salaire | Distance² |
|---|---|---|---|---|---|
| 0 | 5 | 3 | F | 2400 | 4 |
| 1 | 3 | 3 | M | 2550 | 4 |
| 4 | 2 | 5 | F | 2300 | 5 |
Un choix de variable dans une distance peut modifier une décision. Testez des profils identiques sauf sur le critère étudié.
Revoir les notions de cet exercice
Du sujet à la méthode
Votre prochaine séance de révision
- Un test doit porter sur la bonne unité : euros ou pourcentage.
- Lire les variables qui définissent la proximité avant de faire confiance au résultat.
Retrouver ces notions dans d’autres sujets
Toutes les annales de NSI · Le guide pour préparer le bac NSI 2027
Énoncé : sujet 26_BCG_NSI_2 (PDF). Corrigé et explications pédagogiques proposés par Sofien.
