Fil d'Ariane
Mis à jour le mercredi 4 octobre 2017
  • 4 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

Ce cours est en vidéo.

Vous pouvez obtenir un certificat de réussite à l'issue de ce cours.

J'ai tout compris !

Créez votre premier graphique

Connectez-vous ou inscrivez-vous pour bénéficier de toutes les fonctionnalités de ce cours !

 

Dans ce chapitre, nous allons aborder trois librairies très utilisées pour manipuler des données en python : NumPy, Pandas et Matplotlib.

Matplotlib, c'est pour les graphiques. Numpy, c'est pour manipuler des données, et plus précisément des séries de nombres. Enfin, la librairie Pandas introduit un objet très pratique appelé le Dataframe. Ce dernier s'apparente beaucoup à ce qu'il est possible de faire dans des logiciels de type tableur : des tableaux avec plein de nombres dedans !

Qu'allons-nous faire dans ce chapitre ?

Voici notre objectif : afficher un graphique de type "camembert". Bon... soyons un peu plus "pros" : le terme officiel est le Diagramme circulaire. Nous irons ici droit au but, sans étudier ces librairies en profondeur.

Allez, c'est parti !

Installer les trois librairies

Commençons par installer les trois librairies Numpy, Pandas et Matplotlib.

Comme elles sont assez "lourdes", cela peut prendre un certain temps.

Dans votre console, saisissez successivement ces commandes :

pip install numpy
pip install matplotlib
pip install pandas

 

La librairie NumPy

La force de la librairie Numpy, c'est son objet ndarray, qui permet de représenter des listes multidimensionnelles. Le terme est un peu pompeux, mais nous allons voir ensemble de quoi il s'agit.

Imaginons que nous souhaitions représenter des animaux. Disons... des pandas par exemple !

Nous pouvons caractériser un panda par sa taille ; ou plutôt, par ses tailles : disons par exemple, la taille de ses pattes, la longueur moyenne des poils de sa fourrure, la taille de sa queue, et le diamètre de son ventre.

En python, on peut "créer" un panda de cette manière, avec les 4 mesures que nous venons de choisir :

un_panda = [100, 5, 20, 80]

Ici, notre panda a des pattes de 100cm, des poils de 5cm en moyenne, une queue de 20cm et un ventre de 80cm de diamètre.

Mais NumPy ça sert à quoi ici?

J'y arrive. Ici un panda peut très bien se représenter avec une simple liste Python basique. Mais cette liste, nous pouvons la transformer en un objet ndarray, comme ceci :

>>> import numpy as np
>>> un_panda_numpy = np.array(un_panda)
>>> un_panda_numpy
array([100,   3,  20,  80])

Ce qui est pratique avec les objets ndarray, c'est qu'en mathématiques, nous avons souvent besoin de manipuler des listes de données, et donc de faire subir à ces listes toutes sortes d'opérations.

Par exemple, disons que notre panda mette au monde un bébé panda. Supposons (même si c'est faux) que ce bébé panda ait des dimensions exactement proportionnelles à sa celles de sa mère, c'est à dire que la taille de ses pattes soit égale à la taille des pattes de sa mère divisée par 2. Idem pour la longueur de ses poils : 2 fois moins que la longueur des poils de sa mère, etc.

Comment ferions nous sans NumPy ?

import numpy as np

un_panda = [100, 5, 20, 80]
k = 2

# on initialise le bebe panda avec 0 partout
un_bebe_panda = [0,0,0,0]

# une boucle qui parcourt chacune des 4 mesures
for index in range(4):
    # on divise chaque mesure par le coefficient k
    un_bebe_panda[index] = un_panda[index] / k
>>> un_bebe_panda
[50.0, 2.5, 10.0, 40.0]

 C'est plutôt long et fastidieux non? Et bien c'est ici que NumPy vient à notre rescousse ! Regardez-moi un peu cela :

import numpy as np

un_panda_numpy = np.array([100, 5, 20, 80])
k = 2

un_bebe_panda_numpy = un_panda_numpy / k
>>> un_bebe_panda_numpy
array([ 50. ,   2.5,  10. ,  40. ])

Fabuleux n'est-ce pas ? En une ligne de code, nous avons divisé par 2 tous les éléments de la liste !

On appelle cela une opération vectorielle. Ici, notre panda est représenté par un vecteur : ce vecteur, c'est simplement la liste de nombres qui le caractérise.

Et si je veux représenter plusieurs pandas, je crée une liste de ndarray?

C'est une bonne idée (bravo!). Il est effectivement possible de faire cela :

famille_panda = [
    np.array([100, 5  , 20, 80]), # maman panda
    np.array([50 , 2.5, 10, 40]), # bébé panda
    np.array([110, 6  , 22, 80]), # papa panda
]

Notez l'apparition du papa panda (Bonjour monsieur!).

Mais nous pouvons faire mieux :

famille_panda = [
    [100, 5  , 20, 80], # maman panda
    [50 , 2.5, 10, 40], # bébé panda
    [110, 6  , 22, 80], # papa panda
]

famille_panda_numpy = np.array(famille_panda)
>>> famille_panda_numpy
    array([[ 100. ,    5. ,   20. ,   80. ],
           [  50. ,    2.5,   10. ,   40. ],
           [ 110. ,    6. ,   22. ,   80. ]])

Ici, nous avons créé une liste de pandas, c'est à dire une liste de listes ; des listes imbriquées en fait ! En langage Numpy, on dit que le rang de famille_panda_numpy est de 2, car nous avons 2 niveaux d'imbrication.

Quel avantage?

Par exemple, supposons que je veuille obtenir la taille des pattes (situé en position 0 dans nos vecteurs) du panda situé en position 2 dans ma liste (c'est-à-dire papa panda). Numpy offre la possibilité de le faire élégamment :

>>> famille_panda[2] # papa panda
[110, 6, 22, 80]
>>> famille_panda[2][0] # taille des pattes de papa panda
110
>>> famille_panda_numpy[2, 0] # taille des pattes de papa panda, à la manière numpy
110.0

Et si je veux connaître les tailles des pattes de toute ma famille panda ?

Facile ! Il suffit de supprimer le 2 (qui correspondait au papa panda), et de le remplacer par le caractère:, signifiant que je veux TOUS les pandas !

>>> pattes = famille_panda_numpy[:, 0]
>>> pattes
array([ 100.,   50.,  110.])

Voilà ! La taille des pattes de la maman est de 100cm, celle du bébé 50cm, et celle du papa 110cm.

Un petit dernier pour la route ?

Imaginons que je veuille calculer la somme des tailles de ces pattes (ce qui n'a aucun sens, je vous l'accorde) :

>>> pattes.sum()
260.0

 

La librairie Pandas

Tout est une histoire de tableau !

Au fait, la manière dont j'ai écrit la variablefamille_panda:

famille_panda = [
    [100, 5  , 20, 80],
    [50 , 2.5, 10, 40],
    [110, 6  , 22, 80],
]

... ça ressemble un peu à un tableau avec des lignes et des colonnes non ? Vous ne trouvez pas que c'est très similaire à cela?

 

pattes

poil

queue

ventre

maman_panda

100

5

20

80

bébé_panda

50

2.5

10

40

papa_panda

110

6

22

80

(La bonne réponse est : "Oui, c'est très similaire")

Dans ce cas, pourquoi ne pas utiliser une librairie Python qui manipulerait des tableaux comme celui-ci ? Cette librairie existe, et elle s'appelle Pandas.

C'est parti, représentons nos pandas avec Pandas !

>>> import pandas as pd
>>> famille_panda_df = pd.DataFrame(famille_panda)
>>> famille_panda_df

         0    1   2   3
    0  100  5.0  20  80
    1   50  2.5  10  40
    2  110  6.0  22  80

L'objet qui permet de représenter des "tableaux" est l'objet DataFrame. Pour instancier un tel objet (et pour lui donner de la donnée), on lui transmet une liste de rang 2, c'est à dire une liste de listes.

Nous pouvons même faire mieux, en indiquant les noms de colonnes et les noms des lignes. Et encore mieux : la librairie Pandas se base en grande partie sur la librairie Numpy dans son fonctionnement interne. Comme pandas connaît intimement Numpy, on peut très bien transmettre à l'objet DataFrame de la donnée au format ndarray :

>>> import pandas as pd
>>> famille_panda_df = pd.DataFrame(famille_panda_numpy,
...                                 index = ['maman', 'bebe', 'papa'],
...                                 columns = ['pattes', 'poil', 'queue', 'ventre'])
>>> famille_panda_df

           pattes  poil  queue  ventre
    maman   100.0   5.0   20.0    80.0
    bebe     50.0   2.5   10.0    40.0
    papa    110.0   6.0   22.0    80.0

Vous allez encore me demander quel est l'intérêt de cette librairie n'est-ce pas ?

En fait, l'objet DataFrame est très similaire à certains concepts que l'on trouve en dehors du cadre du langage Python. Il est similaire :

  • aux tables des bases de données relationnelles (type MySQL, PostgreSQL, etc.) que l'on manipule grâce au langage SQL

  • à l'objet dataframe sur lequel se base tout le langage R, langage destiné aux statisticiens et aux Data Analysts

Du coup, si vous connaissez déjà le langage SQL ou le langage R, vous aurez beaucoup de facilité à utiliser le DataFrame de Pandas !

 

Quelques fonctionalités des DataFrames

Voici quelques petites fonctionnalités des Dataframes.

Tout d'abord, accédons à la colonne ventre de notre table. Il y a deux syntaxes possibles, qui renvoient exactement le même résultat :

>>> famille_panda_df.ventre
    maman    80.0
    bebe     40.0
    papa     80.0
    Name: ventre, dtype: float64

>>> famille_panda_df["ventre"]
    maman    80.0
    bebe     40.0
    papa     80.0
    Name: ventre, dtype: float64

 Parcourrons maintenant tous les pandas un à un, grâce à la méthode iterrows qui renvoie (à chaque itération de la boucle for) un tuple dont le premier élément est l'index de la ligne, et le second le contenu de la ligne en question :

for ligne in famille_panda_df.iterrows():
    index_ligne = ligne[0]
    contenu_ligne = ligne[1]
    print("Voici le panda %s :" % index_ligne)
    print(contenu_ligne)
    print("--------------------")
    Voici le panda maman :
    pattes    100.0
    poil        5.0
    queue      20.0
    ventre     80.0
    Name: maman, dtype: float64
    --------------------
    Voici le panda bebe :
    pattes    50.0
    poil       2.5
    queue     10.0
    ventre    40.0
    Name: bebe, dtype: float64
    --------------------
    Voici le panda papa :
    pattes    110.0
    poil        6.0
    queue      22.0
    ventre     80.0
    Name: papa, dtype: float64
    --------------------

 

Accédons maintenant au papa panda : d'abord par sa position (position 2), puis par son nom "papa" :

>>> famille_panda_df.iloc[2] # Avec iloc(), indexation positionnelle
    pattes    110.0
    poil        6.0
    queue      22.0
    ventre     80.0
    Name: papa, dtype: float64

>>> famille_panda_df.loc["papa"] # Avec loc(), indexation par label
    pattes    110.0
    poil        6.0
    queue      22.0
    ventre     80.0
    Name: papa, dtype: float64

 

Déterminons les pandas dont le diamètre du ventre est de 80cm :

>>> famille_panda_df["ventre"] == 80
    maman     True
    bebe     False
    papa      True
    Name: ventre, dtype: bool

Le résultat de cette opération est très pratique pour filtrer des lignes ! Par exemple, pour sélectionner uniquement les pandas dont le ventre est de 80cm, il suffit d'intégrer ce précédent résultat en tant que masque, comme ceci :

>>> masque = famille_panda_df["ventre"] == 80
>>> pandas_80 = famille_panda_df[masque]
>>>
>>> # On écrit plus souvent cela de cette manière :
>>> # pandas_80 = famille_panda_df[famille_panda_df["ventre"] == 80]
>>>
>>> pandas_80

           pattes  poil  queue  ventre
    maman   100.0   5.0   20.0    80.0
    papa    110.0   6.0   22.0    80.0

Qu'est-ce qu'un masque ?

Lorsque vous mettez un masque sur votre visage, celui-ci cache certaines parties de votre tête, mais en laisse visible d'autres : vos yeux, votre nez et votre bouche. Ici, c'est un peu la même chose : comme nous souhaitons ne garder que certaines lignes du dataframe, et en cacher d'autres, nous appliquons un masque. Un masque est ici une liste de variables booléennes (TrueouFalse) dans laquelle chaque élément est associé à une ligne du dataframe. Si cet élément estTrue, cela signfie que nous souhaitons garder la ligne en question. Si nous souhaitons ne pas garder la ligne, cet élément estFalse.

Pour inverser le masque, il suffit d'utiliser l'opérateur~, et nous sélectionnons les pandas qui n'ont pas un ventre de 80cm :

>>> famille_panda_df[~masque]

           pattes  poil  queue  ventre
    bebe     50.0   2.5   10.0    40.0

Maintenant, ajoutons des lignes à notre dataframe. Il y a plusieurs méthodes pour cela, mais voyons ici la plus simple : assemblons ensemble deux dataframes.

>>> quelques_pandas = pd.DataFrame([[105,4,19,80],[100,5,20,80]],      # deux nouveaux pandas
...                                columns = famille_panda_df.columns) 
...                                # même colonnes que famille_panda_df
>>> tous_les_pandas = famille_panda_df.append(quelques_pandas)
>>> tous_les_pandas

           pattes  poil  queue  ventre
    maman   100.0   5.0   20.0    80.0
    bebe     50.0   2.5   10.0    40.0
    papa    110.0   6.0   22.0    80.0
    0       105.0   4.0   19.0    80.0
    1       100.0   5.0   20.0    80.0

Dans le dataframe tous_les_pandas, il y a des doublons. En effet, le premier panda (maman) et le dernier panda (dont l'index est 1) ont exactement les mêmes mesures. Si nous souhaitions dédoublonner, nous ferions ceci :

>>> tous_les_pandas.drop_duplicates()

           pattes  poil  queue  ventre
    maman   100.0   5.0   20.0    80.0
    bebe     50.0   2.5   10.0    40.0
    papa    110.0   6.0   22.0    80.0
    0       105.0   4.0   19.0    80.0

Allez, encore un petit effort : voici quelques autres fonctionnalités en vrac, que nous utiliserons dans le projet :

# accéder aux noms des colonnes
famille_panda_df.columns

# créer une nouvelle colonne, composée de chaînes de caractères
famille_panda_df["sexe"] = ["f", "f", "m"] 
# la maman et le bébé sont des femelles, le papa est un mâle

# obtenir le nombre de lignes
len(famille_panda_df)
>>> # obtenir les valeurs distinctes d'une colonne :
>>> # pour la colonne ventre, il y a deux valeurs distinctes : 40 et 80
>>> famille_panda_df.ventre.unique()
array([ 80.,  40.])

 

Lire un fichier CSV avec Pandas

Revenons à nos moutons : notre analyse des députés de l'assemblée nationale, et laissons de côté notre famille panda.

Un fichier CSV, c'est un fichier permettant de représenter des données sous forme de tableau. D'ailleurs, si vous utilisez un logiciel de type tableur, il y a fort à parier qu'il puisse lire et exporter au format CSV !

Vous me voyez venir ? La librairie Pandas est justement spécialisée dans la manipulation de tableaux ! Lire un fichier CSV avec pandas est donc un vrai jeu d'enfant : il ne suffit que d'une ligne pour créer un dataframe à partir d'un CSV :

mps = pd.read_csv("data/current_mps.csv", sep=";")

C'est fait ! La variable mps (signifiant "Members of Parliament" en anglais) contient maintenant un dataframe dont chaque ligne correspond à un député. Petite particularité ici : dans notre fichier CSV, les valeurs sont séparées par le caractère;. Par défaut, pd.read_csv attend des valeurs séparées par une virgule. Nous sommes donc obligés de spécifier sep=";".

 

Revenons à notre projet

Maintenant que nous avons lu notre fichier CSV, nous allons créer une classe python qui permet de manipuler et d'analyser des groupes de députés. Nous appellerons cette classe ensemble de députés, ou en anglais SetOfParliamentMembers. Par exemple, un parti politique représenté à l'assemblée nationale pourra être considéré comme un ensemble de députés.

Chacun de ces ensembles aura un nom, qui sera stocké dans la variable name de chaque objet SetOfParliamentMembers. La liste de députés y sera également stockée sous forme de dataframe, dans la variable nommée dataframe. Nous aurons la possibilité d'ajouter des députés soit à partir d'un fichier CSV via la méthode data_from_csv, ou soit via un dataframe déjà existant via la méthode data_from_dataframe.

Nous créerons aussi deux méthodes, l'une appelée display_chart, qui affichera un diagramme circulaire (camembert pour les intimes) représentant la proportion de femmes et d'hommes dans le groupe de députés, et une autre méthode split_by_political_party, qui séparera le groupe de députés selon leur parti politique en renvoyant un objet de type dictionnaire.

Nous définirons également la fonction launch_analysis, destinée à lancer nos analyses futures selon nos envies. Pour le moment, elle affichera le diagramme de la proportion femme/homme pour toute l'assemblée nationale, et affichera (si nous lui spécifions l'argument by_party = True) ces diagrammes pour chacun des partis politiques.

Voici le code correspondant :

import os
import pandas as pd

class SetOfParliamentMembers:
    def __init__(self, name):
        self.name = name

    def data_from_csv(self, csv_file):
        self.dataframe = pd.read_csv(csv_file, sep=";")

    def data_from_dataframe(self, dataframe):
        self.dataframe = dataframe

    def display_chart(self):
        # à venir, patience !
        pass

    def split_by_political_party(self):
        result = {}
        data = self.dataframe

        all_parties = data["parti_ratt_financier"].dropna().unique()

        for party in all_parties:
            data_subset = data[data.parti_ratt_financier == party]
            subset = SetOfParliamentMembers('MPs from party "{}"'.format(party))
            subset.data_from_dataframe(data_subset)
            result[party] = subset

        return result


def launch_analysis(data_file, by_party = False):
    sopm = SetOfParliamentMembers("All MPs")
    sopm.data_from_csv(os.path.join("data",data_file))
    sopm.display_chart()

    if by_party:
        for party, s in sopm.split_by_political_party().items():
            s.display_chart()

Une petite explication à propos de la méthode split_by_political_party s'impose n'est-ce-pas ?

Nous souhaitons ici renvoyer un dictionnaire (que l'on appelle ici result) dans lequel les clés sont les noms des partis politiques, et les valeurs sont des objets de type SetOfParliamentMembers contenant les députés du parti en question.

Il nous faut tout d'abord établir la liste des partis politiques. Celle-ci est présente dans la colonne parti_ratt_financier du dataframe. Cependant, certains députés ne sont rattachés à aucun parti. Il arrive donc d'avoir des valeurs nulles dans cette colonne. Nous ne nous attarderons pas ici sur les valeurs nulles dans les dataframes, mais retenez juste que pour supprimer celles-ci, nous employons la méthode dropna(). Ensuite, comme nous ne souhaitons que les valeurs distinctes de la colonne parti_ratt_financier, nous appelons la méthode unique().

Ensuite, dans la boucle for, nous créons à chaque itération un nouveau dataframe nommé subset, obtenu par filtrage du dataframe data. La suite de la construction du dictionnaire result ne devrait pas vous poser de problème.

 

La librairie Matplotlib

Je sens qu'à ce stade, vous mourrez d'envie d'afficher un graphique !

Parmi les types de graphiques que propose la librairie matplolib, celui qui nécessite le moins de ligne de code tout en étant très simple à comprendre est probablement l'histogramme. Voici sans plus attendre un exemple dans lequel on cherche à représenter graphiquement les âges d'un groupe de personnes :

import matplotlib.pyplot as plt

ages = [25, 65, 26, 26, 46, 37, 36, 36, 28, 28, 57, 37, 48, 48, 37, 28, 60,
       25, 65, 46, 26, 46, 37, 36, 37, 29, 58, 47, 47, 48, 48, 47, 48, 60]

fig, ax = plt.subplots()
ax.hist(ages)
plt.show()
L'histogramme des âges
L'histogramme des âges

Avec Matplotlib, nous créons des graphiques grâce à l'objet matplotlib.pyplot, que l'on nomme souvent plt comme ici.

Lorsque vous exécuterez ce code, une nouvelle fenêtre s'ouvrira pour afficher l'histogramme. Avec Matplotlib, le terme figure correspond à une fenêtre.

A l'intérieur d'une fenêtre, il est possible d'afficher plusieurs graphiques. Ici, nous ne souhaitons qu'un seul graphique. Créer un unique graphique par fenêtre, c'est justement le comportement par défaut de la méthode plt.subplot(). Cette dernière renvoie un tuple contenant deux objets, que l'on nomme communément fig et ax. Nous ne verrons pas la signification de fig dans ce chapitre. Quant à ax, il représente notre unique graphique.

À cet unique graphique, nous spécifions que nous souhaitons afficher un histogramme, grâce à la méthode ax.hist() à laquelle nous transmettons la liste des âges à afficher.

A ce stade, le graphique n'est pas encore visible, car il était toujours en cours de création. Pour afficher la fenêtre, nous appelons plt.show().

Vous aimez ? Alors continuons sans plus tarder avec le diagramme circulaire tant attendu ! Représentons un groupe de personnes composé de 24 femmes et 18 hommes :

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
ax.pie([24, 18],
        labels = ["Femmes", "Hommes"])
plt.show()

Nous pouvons améliorer quelques petites choses. Tout d'abord, ajoutons un titre à notre graphique, grâce à le méthode plt.title().

Peut-être avez-vous remarqué, en fonction de la dimension de votre fenêtre, le camembert est peut-être aplati. C'est parce que les axes du graphique (l'axe vertical et l'axe horizontal) s'adaptent à la dimension de la fenêtre. Ici, pour que le camembert soit un cercle et non pas une ellipse, nous devons spécifier que l'axe vertical doit être de même taille que l'axe horizontal, pour éviter un aplatissement. Cette opération est rendue possible en appelant ax.axis("equal").

Petite amélioration d'ordre cosmétique. Il existe une ligne un peu magique, qui modifie les propriétés graphiques par défaut de matplotlib, afin de rendre les graphiques un peu plus beaux. Cette ligne consiste juste en l'importation de la librairie seaborn.

Enfin, il est possible d'afficher pour chaque part du camembert la proportion représentée. Ici, on a  57.1% de femmes. Nous le faisons en transmettant le paramètre autopct="%1.1f pourcents" à la méthode ax.pie().

import matplotlib.pyplot as plt
import seaborn as sns

fig, ax = plt.subplots()
ax.axis("equal")
ax.pie([24, 18],
        labels = ["Femmes", "Hommes"],
        autopct="%1.1f pourcents")
plt.title("Un superbe graphique")
plt.show()
Un beau diagramme circulaire
Un beau diagramme circulaire avec matplotlib

Félicitation ! Vous avez maintenant toutes les connaissances requises pour écrire la méthode display_chart de notre objet SetOfParliamentMembers.

L'objectif ici est de calculer la proportion de femmes et d'hommes à partir de la colonne sexe du dataframe contenant les députés.

Commençons donc par créer deux dataframes, l'un contenant les députés femmes : female_mps et l'autre contenant les députés hommes male_mps. Ensuite, stockons le nombre de députés de chaque sexe dans la variable counts. Il ne nous reste plus qu'à diviser ces deux nombres par le nombre total de députés nb_mps, grâce à une opération vectorielle, et nous obtenons la variable proportions.

Voici le code :

class SetOfParliamentMembers:

    #[...]

    def display_chart(self):
        data = self.dataframe
        female_mps = data[data.sexe == "F"]
        male_mps = data[data.sexe == "H"]

        counts = [len(female_mps), len(male_mps)]
        counts = np.array(counts)
        nb_mps = counts.sum()
        proportions = counts / nb_mps

        labels = ["Female ({})".format(counts[0]), "Male ({})".format(counts[1])]

        fig, ax = plt.subplots()
        ax.axis("equal")
        ax.pie(
                proportions,
                labels=labels,
                autopct="%1.1f pourcents"
                )
        plt.title("{} ({} MPs)".format(self.name, nb_mps))
        plt.show()

Voilà, vous savez maintenant utiliser les librairies les plus populaires de manipulation de données mathématiques en python, et surtout, vous savez afficher un graphique !

Code du chapitre

Retrouvez le code de ce chapitre en cliquant ici. 

Exemple de certificat de réussite
Exemple de certificat de réussite