Loi de Zipf

La loi de Zipf, du nom de George Kingsley Zipf, correspond à l'observation empirique de la distribution de la fréquence des mots dans un texte.

L'observation de G. K. Zipf est que les textes sont constitués d'un grand nombre de mots qui apparaissent peut de fois et au contraire d'un petit nombre de mots très fréquents dans le texte. En d'autres termes, la fréquence (f) d'un mot dans un texte est corrélée à son rang (f(n)) par une loi du type : f(rang) x rang = K avec K une constante.

Nous allons tenter de vérifier l'application de cette loi sur le corpus Gutenber, un des corpus distribués avec NLTK.

Script Python utilisant NLTK

La version de NLTK utilisée au sein lors de la rédaction de l'article contenu dans le ACM Crossroad semble un petit peu ancien. Voici donc une version actualisée qui devrait fonctionner avec les dernières versions du Toolkit :

>>> from nltk.corpus import gutenberg
>>> from nltk.probability import FreqDist
>>> from nltk.draw.plot import Plot
>>> fd = FreqDist()
>>> for word in gutenberg.words():
... fd.inc(word)
...
>>> ss = fd.sorted()
>>> points = []
>>> for index,word in enumerate(ss):
... points.append( (index+1, fd[word]) )
...
>>> Plot(points, scale='log').mainloop()
Visualisation de la courbe f=

La première partie du script permet simplement de permettre au script d'accéder à quelques éléments du toolkit tel que :

  • le corpus Gutenberg ;
  • la classe FreqDist permettant de comptabiliser des entités et extraire des informations de distribution de ces entités ;
  • l'objet Plot permettant de tracer un graphique à partir des données que nous aurons extraites ;
>>> from nltk.corpus import gutenberg
>>> from nltk.probability import FreqDist
>>> from nltk.draw.plot import Plot

La seconde partie du code permet dans un premier temps d'instancier la classe FreqDist.

>>> fd = FreqDist()

Nous utilisons l'objet obtenu (fd) pour comptabiliser chacun des mots du corpus. Ce corpus est composé de plusieurs textes. Nous pouvons accéder aux identifiants de ces textes par le biais de la méthode items :

>>> gutenberg.items
('austen-emma', 'austen-persuasion', 'austen-sense', 'bible-kjv', 'blake-poems', 'blake-songs', 'chesterton-ball', 'chesterton-brown',
'chesterton-thursday', 'milton-paradise', 'shakespeare-caesar', 'shakespeare-hamlet', 'shakespeare-macbeth', 'whitman-leaves')

L'article original parcourait les textes un par un en utilisant la méthode raw. Ceci est incorrect car raw donne accès au contenu du texte comme une chaîne de caractères. Nous choisissons d'utiliser la méthode words qui retourne la liste des mots du corpus dans leur ordre chronologique d'apparition.

>>> for word in gutenberg.words():
... fd.inc(word)

La classe FreqDist est extrêmement pratique pour calculer une distribution des éléments d'un texte. Les méthodes de la classe permettent d'obtenir rapidement des informations sur cette distribution :

>>> fd.max()
','
>>> fd[',']
146772
>>> fd.Nr(146772)
1
>>> fd['.']
57046
>>> fd.freq('.')
0.027755950431840863

Ainsi, pour connaître les dix mots les plus présents dans le corpus et leur fréquence respective :

>>> for w in fd.sorted()[:10]:
... print "%3s : %0.2f%%" % (w, fd.freq(w)*100)
...
, : 7.14%
the : 4.87%
and : 3.17%
of : 2.85%
. : 2.78%
: : 2.28%
to : 1.69%
in : 1.21%
I : 1.14%
a : 1.13%

La dernière partie du script compile les informations de la distribution des mots sous la forme de coordonnées (x,y) avec :

  • x le rang du mot
  • y la fréquence du mot dans le corpus

Pour calculer le rang du mot, on extrait une version classée par fréquence décroissante de l'objet fd que l'on transforme en enumerable grâce à la fonction enumerate de Python. Un énumérable de la séquence seq est une liste de tuples du type : (0, seq[0]) (1, seq[1]) .... Il suffit donc d'ajouter 1 au premier éléments des tuples pour obtenir le rang :

>>> ss = fd.sorted()
>>> points = []
>>> for index,word in enumerate(ss):
... points.append( (index+1, fd.freq(word)) )
...

La dernière étape consiste à tracer un graphe à partir de cette liste de coordonnées grâce à la classe Plot :

>>> Plot(points, scale='log').mainloop()