TD H3 : loi de benford¶
En étudiant des séries statistiques issues du monde réel, un certain Franck Benford remarqua (bien que son nom soit resté dans les annales, ce n'était en fait pas le premier à le faire...) que la répartition des premiers chiffres significatifs des données de ces séries n'était pas uniforme comme le voudrait une première intuition, mais suivait une répartition logarithmique. Plus précisément, Benford estime que chaque chiffre $i$ doit apparaitre avec une fréquence de: $$ f(i)=\log_{10}\left(1+\dfrac{1}{i}\right) $$
Voir la vidéo de vulgarisation diffusée sur Arte.
Nous allons dans ce TD essayer constater expérimentalement ce phénomène.
Les notions utilisées :
- Utilisation de bibliothèques (
csv,matplotlib,math) - Lecture de données dans un fichier (ici au format .csv)
- Manipulation de dictionnaires et de listes
- Conversion de type (str <-> int)
- Représentation graphique en barres
Table des matières :
I. Préparation du terrain
- Rappels sur les dictionnaires
- Extraction du premier chiffre significatif d'un entier
- Récupération des données depuis un fichier .csv
II. Étude des fleuves du monde
I. Préparation du terrain¶
Dans ce TD, on souhaite étudier la répartition des premiers chiffres significatifs de diverses séries statistiques (populations des départements français, population des pays du monde et longueur des principaux fleuves).
Un format de stockage adpaté à ce type de données est l'utilisation de dictionnaires dont les clés seront les entiers de 1 à 9 et les valeurs seront leur fréquence d'apparition (comme on l'a déjà rencontré dans le TD sur la répartition des fréquences des lettres d'un texte...).
1. Rappels sur les dictionnaires ¶
Exécuter les cellules suivantes pour se remémorer comment fonctionne un dictionnaire~:
# Dictionnaire vide :
vide = {}
print(vide)
# On ajoute un élément :
vide['un']=1
print(vide)
{}
{'un': 1}
# Création d'un dictionnaire par compréhension:
dico = { i : i**2 for i in range(5)}
print(dico)
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
# Accès à une valeur dont on connait la clé :
print("La valeur associée à cla clé '3' est :",dico[3])
La valeur associée à cla clé '3' est : 9
# Parcours des clés d'un dictionnaire:
for k in dico.keys() :
print(k)
0 1 2 3 4
# Parcours des valeurs d'un dictionnaire:
for v in dico.values():
print(v)
0 1 4 9 16
# Parcours des clés/valeurs d'un dictionnaire :
for k,v in dico.items():
print(f"clé : {k}, valeur : {v}")
clé : 0, valeur : 0 clé : 1, valeur : 1 clé : 2, valeur : 4 clé : 3, valeur : 9 clé : 4, valeur : 16
Q1. Écrire un code python qui transforme les deux listes suivantes en un dictionnaire pizzas, la première servant de clé et la deuxième de valeurs:
keys = ['margartia', 'royale', 'arménienne', 'chorizo', 'savoyarde']
values = [10.5 , 12.5, 13, 11, 10]
pizzas = {}
for i in range(len(keys)):
pizzas[keys[i]]=values[i]
print(pizzas)
{'margartia': 10.5, 'royale': 12.5, 'arménienne': 13, 'chorizo': 11, 'savoyarde': 10}
Pour de plus amples informations, on pourra consulter comme déjà évoqué le site w3school.
2. Extraction du premier chiffre significatif d'un nombre entier ¶
On donne ci-dessous une fonction first_digit(n : int) -> int: qui reçoit un entier et qui renvoie le premier chiffre significatif de cet entier. Elle utilise la possibilité de convertir des entiers en chaine de caractères et vice-versa.
def first_digit(number :int) -> int :
""" reçoit un entier et renvoie le premier chiffre significatif"""
n = int(str(number)[0])
return n
print(first_digit(3123))
3
3. Récupération des données depuis un fichier .csv ¶
Dans les TD précédents, on a travaillé sur des fichiers textes sans formatage particulier (.txt). Pour rencontrer d'autres possibilités, on a va utiliser ici un format structuré courant, le .csv (comma separated values). C'est celui que l'on obtient à partir de feuilles de calculs d'un tableur.
La fonction suivante récupère les données stockées dans un fichier au format .csv et renvoie les données sous forme de liste de liste. Examiner l'exemple pour comprendre :
Par exemple, Le fichier fleuves.csv est de la forme :
txt
Continent,Nom du fleuve,Longueur
Afrique,La Medjerda,460
Afrique,Le Bandama,1050
Afrique,La Betsiboka,525
...
on récupère les données sous la forme :
[
['Continent','Nom du fleuve','Longueur'],
['Afrique', 'La Medjerda', 460],
['Afrique','Le Bandama',1050],
['Afrique','La Betsiboka',525],
...
]
import csv
def read_file(filename):
datas = []
with open(filename,"r", encoding='utf-8') as csvfile :
text = csv.reader(csvfile, delimiter=',')
for row in text :
datas.append(row)
datas.pop(0) # on supprime la première ligne car elle ne contient pas de données
return datas
datas = read_file('fleuves.csv')
print(datas[:4]) # on affiche les 5 premières lignes
print(len(datas))
[['Afrique', 'La Medjerda', '460'], ['Afrique', 'Le Bandama', '1050'], ['Afrique', 'La Betsiboka', '525'], ['Afrique', 'Le Chelif', '733']] 228
Q3. Écrire un script python permettant de trouver quel est le plus grand fleuve de la série ? Quelle est sa longueur ?
maxi = 0
nom = ''
rivers = read_file('fleuves.csv')
for river in rivers:
if int(river[2])> maxi:
maxi = int(river[2])
nom = river[1]
print(f"Le plus grand fleuve est {nom}, il mesure {maxi} km")
Le plus grand fleuve est Le Nil, il mesure 6700 km
II. Étude des fleuves du monde ¶
Q4. Écrire une fonction python datas2list(datas) qui reçoit la liste des fleuves et qui renvoie la liste numérique des longueurs des ces fleuves.
def datas2list(datas):
""" reçoit la liste des fleuves et renvoie la liste des longueurs de ces
fleuves (colonne d'indice 2)"""
lst = []
for river in datas:
lst.append(int(river[2]))
return lst
rivers = read_file('fleuves.csv')
# On affiche la liste des longueurs des fleuves :
lengths = datas2list(rivers)
print(lengths)
[460, 1050, 525, 733, 425, 240, 300, 515, 1200, 813, 4700, 1100, 1150, 1658, 1600, 714, 467, 600, 4184, 6700, 690, 893, 1600, 2160, 500, 550, 800, 250, 918, 614, 1750, 1014, 1346, 160, 2750, 6437, 750, 2060, 2330, 724, 1954, 1014, 1370, 507, 422, 1738, 1558, 612, 3780, 72, 102, 660, 2140, 403, 4099, 1344, 665, 160, 810, 1114, 3060, 1135, 673, 1140, 380, 477, 3160, 563, 262, 480, 2600, 1600, 3185, 1590, 2580, 4354, 939, 1146, 2896, 505, 950, 372, 200, 2780, 5646, 1149, 2510, 1500, 70, 1490, 4093, 1439, 1726, 3180, 2170, 360, 758, 1143, 1636, 2129, 1514, 1290, 4400, 1345, 851, 548, 4909, 545, 1289, 3650, 2292, 571, 817, 597, 1024, 327, 2815, 670, 421, 2212, 840, 724, 2030, 1401, 1900, 521, 240, 1772, 800, 6380, 89, 410, 308, 241, 224, 144, 103, 149, 806, 100, 381, 6, 3020, 1020, 2290, 1362, 1950, 483, 940, 744, 928, 1091, 56, 371, 355, 647, 608, 93, 157, 657, 744, 148, 55, 136, 498, 191, 552, 870, 119, 1006, 353, 461, 480, 950, 857, 318, 75, 74, 937, 854, 63, 416, 135, 170, 2428, 1809, 231, 652, 102, 212, 1233, 812, 325, 777, 354, 158, 386, 245, 415, 1078, 346, 623, 116, 405, 521, 297, 114, 388, 95, 218, 1047, 3700, 452, 37, 26, 78, 14, 710, 128, 158, 2530, 170, 80, 120, 1126, 425, 110, 290]
Q5. Écrire une fonction python extract_first_digit(lst : list)-> qui reçoit une liste d'entier et qui renvoie une nouvelle liste, formée par les premiers chiffres (non nuls).
def extract_first_digit(lst : list) -> list :
""" reçoit une liste de nombres entiers et renvoie une nouvelle liste
formée par les premiers chiffres ( non nuls ).
Par exemple :
-> entrée : [ 32 , 12 , 27 , ...]
-> sortie : [ 3 , 1 , 2 , ...] """
digits = []
for n in lst:
digits.append(first_digit(n))
return digits
# on teste
print(extract_first_digit([32, 12,27]))
[3, 1, 2]
Q6. Écrire une fonction python lst2dict(lst : list)-> dict qui reçoit une liste de chiffres et qui renvoie
un dictionnaire dont les clés sont les différents chiffres, et les valeurs le nombre de fois où ces chiffres
apparaissent dans la liste:
def lst2dict(lst : list) -> dict :
""" reçoit une liste de chiffres non nuls et qui renvoie un dictionnaire
dont les clés sont les différents chiffres,
et les valeurs le nombre de fois où ces chiffres apparaissent dans la liste
"""
# on crée un dictionnaire dont les clés sont les entiers 1 à 9, et les valeurs nulles
dic_digits = {i : 0 for i in range(1,10)}
for digit in lst:
dic_digits[digit]+=1
return dic_digits
print(lst2dict([3,2,4,2,1]))
{1: 1, 2: 2, 3: 1, 4: 1, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0}
Q7. Écrire un script python qui affiche le dictionnaire qui associe à chaque chiffre le nombre d'occurences correspondantes et constater visuellement la non uniformité de la répartition.
dico =lst2dict(extract_first_digit(datas2list(rivers)))
print(dico)
{1: 71, 2: 33, 3: 25, 4: 25, 5: 17, 6: 18, 7: 15, 8: 15, 9: 9}
Q8. Utilser la bibliothèque matplotlib pour représenter le diagramme en barres correspondant (les chiffres en abscisses et les effectifs en ordonnées). Il faudra pour cela extraire à partir du dictionnaire, la liste des clés (abscisses) et la liste des valeurs (ordonnées) :
import matplotlib.pyplot as plt
dico = lst2dict(extract_first_digit(lengths))
abscisses = []
ordonnees = []
for key,value in dico.items():
abscisses.append(key)
ordonnees.append(value)
plt.figure(1)
plt.clf()
plt.bar(abscisses, ordonnees)
plt.show()
Conclusion : Si l'on veut comparer cette répartition avec celle prédite par Benford, on va commencer par calculer les fréquences (plutot que les effectifs) puis dessiner sur un même graphique les deux séries :
import math
dico = lst2dict(extract_first_digit(lengths))
ordonnees = [ v for v in dico.values()]
# calcul des fréquences de chaque chiffre
n_total = sum(ordonnees)
frequences = [v/n_total for v in ordonnees]
# répartition de Benford :
benford_list = [math.log10(1+1/i) for i in range(1,10)]
# liste des entiers
digits = [i for i in range(1,10)]
plt.figure(2)
plt.clf()
plt.bar(digits,frequences,label='fleuves')
plt.plot(digits,benford_list,'o-r',label='Benford')
plt.xticks(digits)
plt.legend()
plt.show()
III. Étude de la population des départements français ¶
Q10. Reprendre la même étude que la partie II, avec le fichier departements.csv. À noter que les données de population sont dans la colonne d'indice 1 (et pas 2 comme pour les fleuves):
- Récupérer la liste des départements dans une variable nommée
departements - Extraire la liste des populations dans une variable nommée
lengths - Construire la liste nommée
first_digitsobtenue à partir de la listelengthsen ne gardant que les premiers chiffres de chaque nombre. - Construire le dictionnaire
dicocontenant la répartition de chaque premier chiffre de la liste des départements. - Représenter graphiquement cette répartition et constater la même répartition logarithmique...
#1
departements = # à compléter
print(departements)
# Noter les colonnes dans lesquelles se situent les données
# 2
def datas2list(datas):
""" reçoit la liste des departements et renvoie la liste des populations
(colonne d'indice 1)"""
lst = []
for # à compléter
# à compléter
return lst
lengths = datas2list(depts)
print(lengths)
# 3
first_digits = # à compléter
print(first_digits)
# 4
dico = { i : 0 for i in range(1,10)}
for # à compléter
# à compléter
print(dico)
# 5
import matplotlib.pyplot as plt
x = # à compléter
y = # à compléter
plt.figure(3)
plt.clf()
# à compléter
plt.show()
IV. Étude de la population des pays du monde ¶
Q11. Reprendre la même étude que la partie III mais avec les données des pays du monde situés dans le fichier pays.csv. Les données se situent dans le colonne d'indice 1.
# Code pour Q11