Fusion de deux CSV d'inscriptions
Le service vie scolaire fusionne deux fichiers CSV. adherents.csv contient 1 200 lignes avec les colonnes id, nom, email. paiements.csv contient 980 lignes avec id, montant. Certains adhérents n'ont jamais payé ; certains paiements référencent un id absent de adherents.csv, par exemple des inscriptions annulées. Un stagiaire écrit :
paiements = {}
for ligne in lire_csv('paiements.csv'):
paiements[ligne['id']] = int(ligne['montant'])
total = 0
for a in lire_csv('adherents.csv'):
total += paiements[a['id']]
Le programme lève une erreur KeyError sur 240 adhérents. Après avoir remplacé par paiements.get(a['id'], 0), il calcule une moyenne de 18,50 € par adhérent. Le tableur de la secrétaire affiche 22,10 €. Le stagiaire affirme que le tableur a tort.
1. Expliquer les deux causes possibles de l'écart entre 18,50 € et 22,10 € : le choix du dénominateur et le traitement des doublons. Calculer un exemple chiffré cohérent avec ces valeurs.
Read the worked answer
Deux causes indépendantes peuvent produire l'écart. D'abord le dénominateur : 18,50 peut être total / 1200 (tous les inscrits) alors que 22,10 peut être total / 980 (lignes de paiement), ou l'inverse. Ensuite les doublons : si un id apparaît plusieurs fois dans paiements.csv, le dictionnaire écrase les montants précédents et ne garde que le dernier, alors qu'un tableur additionne les lignes. Exemple cohérent : le dictionnaire ne conserve que 22 200 €, d'où 22 200 / 1 200 = 18,50 €. En cumulant tous les versements, on obtient 26 520 €, d'où 26 520 / 1 200 = 22,10 €. Les deux programmes ne répondent donc pas à la même question. Avant d'accuser le tableur, il faut vérifier le nombre de lignes, le nombre d'ids distincts et le total. Il faut aussi décider si les paiements orphelins comptent dans le total encaissé : oui pour la comptabilité, non pour la moyenne par adhérent inscrit.
2. Écrire une fonction Python `bilan(adherents, paiements)` qui renvoie le total encaissé, le nombre d'adhérents ayant payé au moins une fois, et la liste des ids orphelins. Gérer les doublons en cumulant les montants.
Read the worked answer
def bilan(adherents, paiements):
total = 0
payeurs = set()
ids_adherents = set()
for a in adherents:
ids_adherents.add(a['id'])
orphelins = []
for p in paiements:
total += int(p['montant'])
if p['id'] in ids_adherents:
payeurs.add(p['id'])
else:
orphelins.append(p['id'])
return total, len(payeurs), orphelins
total cumule toutes les lignes, y compris les doublons et les orphelins. payeurs est un ensemble : un adhérent qui a payé trois fois ne compte qu'une fois. orphelins liste les ids de paiements sans adhérent. Si l'on veut le total uniquement pour les adhérents inscrits, on n'ajoute au total que si l'id est dans ids_adherents ; il faut alors le préciser. Le choix doit être explicité dans la réponse, car la moyenne change.
3. La secrétaire veut la moyenne par adhérent inscrit et la moyenne par adhérent payant. Laquelle des deux est 22,10 € ? Justifier et indiquer comment vérifier avec un tableur ou un script.
Read the worked answer
22,10 € est probablement la moyenne par adhérent inscrit si le total est 26 520 € : 26 520 / 1 200 = 22,10. Mais ce n'est qu'une hypothèse. La moyenne par adhérent payant se calcule avec le nombre d'ids distincts ayant payé, pas avec le nombre de lignes de paiements.csv. Avec 980 lignes, il peut y avoir par exemple 900 payeurs distincts. Pour vérifier : compter COUNT(DISTINCT id) dans les paiements, COUNT(*) dans les adhérents, et SUM(montant). Un tableur peut filtrer les lignes sans paiement ou utiliser MOYENNE sur une colonne de montants, ce qui ignore les non-payeurs. Il faut donc reconstruire explicitement numérateur et dénominateur, puis comparer.