Dictionnaires et Pandas#

Vidéo: Dictionnaires#

Les dictionnaires#

Un dictionnaire ressemble à une liste mais il est plus général. Alors que les indices d’une liste doivent être des entiers, dans un dictionnaire, les indices peuvent être de types différents.

Un dictionnaire contient une collection d’indices, appelés clés, et une collection de valeurs. À chaque clé correspond une valeur, ce que l’on appelle une paire clé-valeur, ou item.

La syntaxe pour définir un dictionnaire est :

my_dict = {
   "key1": "value1",
   "key2": "value2",
}

Définissons 2 listes nous donnant les pays européens et leur capitale :

pays = ['allemagne', 'autriche', 'belgique', 'bulgarie', 'chypre', 'croatie', 'danemark', 'espagne', 'estonie', 
        'finlande', 'france', 'grèce', 'hongrie', 'irlande', 'italie', 'lettonie', 'lituanie', 'luxembourg', 
        'malte', 'pays-bas', 'pologne', 'portugal', 'république tchèque', 'roumanie', 'slovaquie', 'slovénie', 'suède']
capitales = ['berlin', 'vienne', 'bruxelles', 'sofia', 'nicosie', 'zagreb', 'copenhague', 'madrid', 'tallinn', 
             'helsinki', 'paris', 'athènes', 'budapest', 'dublin', 'rome', 'riga', 'vilnius', 'luxembourg', 'la valette',
             'amsterdam', 'varsovie', 'lisbonne', 'prague', 'bucarest', 'bratislava', 'ljubljana', 'stockholm']

Nous voulons trouver la capitale de la Hongrie. Nous pouvons utiliser la méthode de liste index() :

# Trouver l'indice de 'hongrie'
ind_hongrie = pays.index('hongrie')

# Afficher la capitale de la Hongrie
print(capitales[ind_hongrie].capitalize())
Budapest

Maintenant, définissons un dictionnaire contenant les même informations :

europe = {
    'allemagne' : 'berlin',
    'autriche' : 'vienne',
    'belgique' : 'bruxelles',
    'bulgarie' : 'sofia',
    'chypre' : 'nicosie',
    'croatie' : 'zagreb',
    'danemark' : 'copenhague',
    'espagne' : 'madrid',
    'estonie' : 'tallinn',
    'finlande' : 'helsinki',
    'france' : 'paris',
    'grèce' : 'athènes',
    'hongrie' : 'budapest',
    'irlande' : 'dublin',
    'italie' : 'rome',
    'lettonie' : 'riga',
    'lituanie' : 'vilnius',
    'luxembourg' : 'luxembourg',
    'malte' : 'la valette',
    'pays-bas' : 'amsterdam',
    'pologne' : 'varsovie',
    'portugal' : 'lisbonne',
    'république tchèque' : 'prague',
    'roumanie' : 'bucarest',
    'slovaquie' : 'bratislava',
    'slovénie' : 'ljubljana',
    'suède' : 'stockholm'
}

Pour accéder à la valeur associée à une clé donnée, il suffit alors d’utiliser la clé en indice du dictionnaire. Ainsi, pour afficher la capitale de la Hongrie :

europe['hongrie']
'budapest'

Il est facile avec un dictionnaire d’ajouter ou enlever une paire clé-valeur, ou bien de changer une valeur :

# Ajouter la Turqie au dictionnaire
europe['turquie'] = 'istanbul'

# Vérifier que le pays est bien contenu dans le dictionnaire
'turquie' in europe
True
# Changer le nom de la capitale de la Turquie (et oui ce n'est pas Istanbul...)
europe['turquie'] = 'ankara'

# Afficher la capitale de la Turquie
print(europe['turquie'].capitalize())
Ankara
# Enlever la paire clé-valeur du dictionnaire
del(europe['turquie'])

# Vérifier que le pays n'est pas contenu dans le dictionnaire
'turquie' in europe
False

Vidéo: L’objet DataFrame#

Pandas DataFrames#

Pandas est un module de Python pour l’analyse et la manipulation de données. L’objet de base de Pandas est le DataFrame, qui est un tableau similaire aux feuilles Excel ou LibreOffice que vous devez connaître. Le module Pandas fonctionne en harmonie avec les modules Numpy et Matplotlib vus dans les cours précédents.

Voyons comment créer un DataFrame à partir d’un dictionnaire. Nous allons reprendre les listes créées plus haut sur les pays et capitales européennes, les transformer en tableaux Numpy, et y ajouter d’autres informations :

# Importation du module Numpy
import numpy as np

# Importation du module Pandas
import pandas as pd

# Création des tableaux Numpy à partir des listes des pays et des capitales : type string
np_pays = np.array(pays)
np_capitales = np.array(capitales)

# Liste avec les populations des pays européens : type integer
np_population = np.array([82162000, 8700471, 11289853, 7153784, 848319, 4190669, 5659715, 46438422, 1315944, 
                       5401267, 66661621, 10793526, 9830485, 4658530, 60665551, 1968957, 2888558, 576249, 
                       434403, 16979120, 37967209, 10341330, 10553853, 19759968, 5426252, 2064188, 9851017],
                       dtype = 'int64')

# Liste avec les dates d'adhésion des pays à l'UE : type datetime
np_date = np.array(['1957', '1995', '1957', '2007', '2004', '2013', '1973', '1986', '2004', '1995',
                    '1957', '1981', '2004', '1973', '1957', '2004', '2004', '1957', '2004', '1957',
                    '2004', '1986', '2004', '2007', '2004', '2004', '1995'],
                    dtype = 'datetime64[Y]')

Maintenant, créons un dictionnaire en utilisant les tableaux Numpy dans les paires clé-valeur :

dic_europe = {
    "pays" : np_pays,
    "capitale" : np_capitales,
    "population" : np_population,
    "date d'adhésion" : np_date
}

Pour créer le DataFrame à partir du dictionnaire il suffit d’utiliser la fonction DataFrame() de Pandas :

# Création du DataFrame
df_europe = pd.DataFrame(data = dic_europe)

# Affichage du DataFrame
df_europe
pays capitale population date d'adhésion
0 allemagne berlin 82162000 1957-01-01
1 autriche vienne 8700471 1995-01-01
2 belgique bruxelles 11289853 1957-01-01
3 bulgarie sofia 7153784 2007-01-01
4 chypre nicosie 848319 2004-01-01
5 croatie zagreb 4190669 2013-01-01
6 danemark copenhague 5659715 1973-01-01
7 espagne madrid 46438422 1986-01-01
8 estonie tallinn 1315944 2004-01-01
9 finlande helsinki 5401267 1995-01-01
10 france paris 66661621 1957-01-01
11 grèce athènes 10793526 1981-01-01
12 hongrie budapest 9830485 2004-01-01
13 irlande dublin 4658530 1973-01-01
14 italie rome 60665551 1957-01-01
15 lettonie riga 1968957 2004-01-01
16 lituanie vilnius 2888558 2004-01-01
17 luxembourg luxembourg 576249 1957-01-01
18 malte la valette 434403 2004-01-01
19 pays-bas amsterdam 16979120 1957-01-01
20 pologne varsovie 37967209 2004-01-01
21 portugal lisbonne 10341330 1986-01-01
22 république tchèque prague 10553853 2004-01-01
23 roumanie bucarest 19759968 2007-01-01
24 slovaquie bratislava 5426252 2004-01-01
25 slovénie ljubljana 2064188 2004-01-01
26 suède stockholm 9851017 1995-01-01

On voit que :

  • les clés du dictionnaire ont été utilisées pour nommer les colonnes du DataFrame,

  • les listes ont été utilisées pour remplir chaque colonne

  • les lignes sont étiquetées, par défaut avec des entiers à partir de l’indice 0 (mais il est possible d’utiliser d’autres types pour les étiquettes, comme pour les clés d’un dictionnaire, ce que nous verrons au paragraphe sur l’objet index)

Nous pouvons vérifier le type du DataFrame avec la fonction type() :

# Affichage du type
print(type(df_europe))
<class 'pandas.core.frame.DataFrame'>

Les attributs de DataFrame sont :

Attributs

Description

ndim

nombre de dimensions

shape

forme, nombre d’éléments dans chaque dimension

size

nombre d’éléments

print('ndim :', df_europe.ndim)
print('shape :', df_europe.shape)
print('size :', df_europe.size)
ndim : 2
shape : (27, 4)
size : 108

Les DataFrame contiennent parfois beaucoup de lignes. On peut afficher seulement les premières ou dernières lignes avec les méthodes head() et tail() :

# Afficher les 2 premières lignes
df_europe.head(n = 2)
pays capitale population date d'adhésion
0 allemagne berlin 82162000 1957-01-01
1 autriche vienne 8700471 1995-01-01
# Afficher les 2 dernières lignes
df_europe.tail(n = 2)
pays capitale population date d'adhésion
25 slovénie ljubljana 2064188 2004-01-01
26 suède stockholm 9851017 1995-01-01

Vidéo: Les objets Series et Index#

L’objet Series#

Il est possible d’extraire une colonne en mettant en indice le nom de la colonne :

# Extraire la colonne pays du DataFrame
col_pays = df_europe['pays']

# Ou de manière similaire
col_pays = df_europe.pays

# Affichage
print(col_pays)
0              allemagne
1               autriche
2               belgique
3               bulgarie
4                 chypre
5                croatie
6               danemark
7                espagne
8                estonie
9               finlande
10                france
11                 grèce
12               hongrie
13               irlande
14                italie
15              lettonie
16              lituanie
17            luxembourg
18                 malte
19              pays-bas
20               pologne
21              portugal
22    république tchèque
23              roumanie
24             slovaquie
25              slovénie
26                 suède
Name: pays, dtype: object

On voit que l’on a extrait la colonne mais aussi les étiquettes des lignes et d’autres informations telles que l’étiquette de la colonne. Une colonne extraite de cette manière est un objet Series de Pandas :

print(type(col_pays))
<class 'pandas.core.series.Series'>

On accède aux valeurs d’un objet Series de la même manière qu’un dictionnaire, c’est-à-dire en écrivant l’étiquette en indice de l’objet :

# Afficher la valeur de la "Series" col_pays correspondant a l'étiquette 23
print(col_pays[23])
roumanie

Pour accéder à une ligne, il faut utiliser un accesseur .loc[] :

# Extraire la ligne 23 du DataFrame
ligne_23 = df_europe.loc[23]

# Affichage
print(ligne_23)
pays                          roumanie
capitale                      bucarest
population                    19759968
date d'adhésion    2007-01-01 00:00:00
Name: 23, dtype: object

Une ligne extraite de cette façon est aussi un objet Series :

type(ligne_23)
pandas.core.series.Series

Les noms des colonnes ont été utilisés pour étiqueter les lignes de ce nouvel objet, et l’étiquette de la ligne a été utilisée pour nommer l’objet.

Exercice#

  1. Créer un objet Series contenant la colonne date d’adhésion

  2. Créer un objet Series contenant la ligne 12

Hide code cell source
# 1
col_adhesion = df_europe["date d'adhésion"]

# 2
ligne_12 = df_europe.loc[12]

L’objet Index#

On peut extraire les étiquettes des lignes et des colonnes avec .index et .columns :

# Étiquettes des lignes
print(df_europe.index)
RangeIndex(start=0, stop=27, step=1)
# Étiquette des lignes
print(df_europe.columns)
Index(['pays', 'capitale', 'population', 'date d'adhésion'], dtype='object')

Ces étiquettes sont des objet Pandas appelés Index. Ce sont des tableaux Numpy immuables, c’est-à-dire qu’on ne peut pas modifier leurs éléments.

On peut par contre changer un index entier :

# Modifier les étiquettes des lignes du DataFrame en les faisant commencer à 1
df_europe.index = np.arange(1, 28)

# Afficher la premiere ligne
df_europe.head(n = 1)
pays capitale population date d'adhésion
1 allemagne berlin 82162000 1957-01-01

Vidéo: Manipulation des données#

Accéder et modifier les données#

On peut accéder aux données du DataFrame pour les extraire ou les modifier grâce à des accesseurs :

Accesseur

Description

.loc[]

accepte les étiquettes des lignes ou colonnes et retourne un objet Series ou DataFrame

.iloc[]

accepte les indices des lignes ou colonnes et retourne un objet Series ou DataFrame

.at[]

accepte les étiquettes des lignes ou colonnes et retourne une valeur unique

.iat[]

accepte les indices des lignes ou colonnes et retourne une valeur unique

Comme pour les tableaux Numpy, les accesseurs supportent l’indexation et le tranchage. Voyons quelques exemples :

# Extraire les capitales des 3 premiers pays
print(df_europe.loc[1:4, 'capitale'])
1       berlin
2       vienne
3    bruxelles
4        sofia
Name: capitale, dtype: object

Attention, avec .loc[] on a utilisé les étiquettes des lignes, décalées de 1 par rapport à leur indice. Pour extraire le même objet Series avec iloc[], on écrira :

# Extraire les capitales des 3 premiers pays
print(df_europe.iloc[0:4, 1])
1       berlin
2       vienne
3    bruxelles
4        sofia
Name: capitale, dtype: object

Note :

  • lorsqu’on accède par étiquettes, la notation m:n signifie \([m,n]\),

  • lorsqu’on accède par indices, la notation m:n signifie \([m,n[\).

Attention aux erreurs !

Comme pour les tableaux Numpy, on peut trancher en fournissant une liste :

# Extraire les noms et les populations des pays 3 à 7
df_europe.loc[3:7, ['pays', 'population']]
pays population
3 belgique 11289853
4 bulgarie 7153784
5 chypre 848319
6 croatie 4190669
7 danemark 5659715

Avec l’accesseur iloc[] on écrira :

# Extraire les noms et les populations des pays 3 à 7
df_europe.iloc[2:7, [0, 2]]
pays population
3 belgique 11289853
4 bulgarie 7153784
5 chypre 848319
6 croatie 4190669
7 danemark 5659715

Pour accéder à une valeur en particulier :

# Nom du pays 23
print(df_europe.at[23, 'pays'])

# Ou avec les indices
print(df_europe.iat[22, 0])
république tchèque
république tchèque

Exercice#

  1. Extraire les date d’adhésion des pays avec les étiquettes 12 et 25

  2. Extraire la population du pays avec l’étiquette 6

Hide code cell source
# 1
print(df_europe.loc[[12, 25], "date d'adhésion"])
12   1981-01-01
25   2004-01-01
Name: date d'adhésion, dtype: datetime64[s]
# 2
print(df_europe.at[6, "population"])
4190669

Insérer et supprimer des données#

Pour insérer une nouvelle ligne, on peut créer un objet Series et l’ajouter au DataFrame avec la méthode .append(). Supposons que la Turquie joigne l’Union Européenne en 2026 :

# Création de l'objet Series
ligne_turquie = pd.Series(data = ['turquie', 'ankara', 83154997, np.datetime64('2026', 'Y')], 
                          index = df_europe.columns, name = 28)

# Affichage
print(ligne_turquie)
pays                turquie
capitale             ankara
population         83154997
date d'adhésion        2026
Name: 28, dtype: object
# Ajout de la ligne à l'objet DataFrame
df_europe = df_europe.append(ligne_turquie)

# Affichage des 2 dernières lignes
df_europe.tail(n = 2)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/tmp/ipykernel_2146/893767574.py in ?()
----> 2 # Ajout de la ligne à l'objet DataFrame
      3 df_europe = df_europe.append(ligne_turquie)
      4 
      5 # Affichage des 2 dernières lignes

/opt/hostedtoolcache/Python/3.8.18/x64/lib/python3.8/site-packages/pandas/core/generic.py in ?(self, name)
   5985             and name not in self._accessors
   5986             and self._info_axis._can_hold_identifiers_and_holds_name(name)
   5987         ):
   5988             return self[name]
-> 5989         return object.__getattribute__(self, name)

AttributeError: 'DataFrame' object has no attribute 'append'

Plusieurs remarques :

  • Pour créer l’objet Series on a utilisé l’option index pour nommer les colonnes, et l’option name pour lui donner un nom

  • L’étiquette de la nouvelle ligne de l’objet DataFrame est le nom de l’objet Series

  • On aurait pu ignorer ce nom en utilisant l’option de la méthode append() : ignore_index = True

La méthode .drop() permet de supprimer des lignes :

# Supprimer la ligne avec l'étiquette 28
df_europe = df_europe.drop([28])

# Affichage des 2 dernières lignes
df_europe.tail(n = 2)

Il est beaucoup plus simple d’ajouter une colonne à un DataFrame. On peut le faire de la même manière que l’on ajoute une paire clé-valeur à un dictionnaire. Par exemple, ajoutons la colonne qui donne le nombre de sièges au parlement européen pour chaque pays de l’UE :

# Nombre de sièges au parlement
np_sieges = np.array([96, 18, 21, 17, 6, 11, 13, 54, 6, 13, 74, 21, 21, 11, 73, 8, 11, 6, 6, 26, 51, 21, 21, 32, 13, 8, 20])

# Ajout de la colonne
df_europe['sièges'] = np_sieges

# Affichage des 2 premières lignes
df_europe.head(n = 2)

Vidéo: Opérations, tri et sauvegarde#

Opérateurs arithmétiques et fonctions#

Il est possible d’utiliser les opérateurs arithmétiques usuels et les fonctions Numpy sur les colonnes d’un objet DataFrame. Par exemple, calculons le pourcentage de la population dans chaque pays par rapport à la population totale :

# Population totale
pop_tot = df_europe['population'].sum()

# Pourcentage de la population
pop_pourcentage = df_europe['population'] / pop_tot * 100

# Affichage
print(pop_pourcentage)

Exercice#

Nous voulons connaître le poids de chaque pays au parlement par rapport à son nombre d’habitants.

  1. Calculer le nombre de sièges au parlement par habitant dans chaque pays, c’est-à-dire :

\[N_i = \frac{\text{nombre de sièges}}{\text{nombre d'habitants}}\]
  1. Normaliser le résultat et l’exprimer en pourcentage, c’est-à-dire :

\[\bar{N}_i = \frac{N_i}{\sum_i N_i}\times 100\]
  1. Ajouter une nouvelle colonne au DataFrame df_europe avec le nom 'poids' qui contient \(\bar{N}_i\)

  2. Afficher les colonnes 'pays' et 'poids' du DataFrame df_europe pour les 20 premières lignes

Hide code cell source
# Nombre de siège par habitant
siege_pourcentage = df_europe['sièges'] / df_europe['population']

# Normalisation
siege_pourcentage = siege_pourcentage / siege_pourcentage.sum() * 100

# Ajout d'une colonne poids
df_europe['poids'] = siege_pourcentage

# Affichage
df_europe.loc[1:20, ['pays', 'poids']]

Trier un DataFrame#

Il est très facile de trier un objet DataFrame avec la méthode .sort_values(). Par exemple, on voit qu’en classant les pays d’abord par nombre de sièges croissant, puis, pour un même nombre de sièges, par poids décroissant, on déduit que plus le nombre de sièges et la population augmentent, plus le poids d’un pays au parlement diminue :

df_europe.sort_values(by = ['sièges', 'poids'], ascending = [True, False])

Sauvegarder un DataFrame#

Il est possible de sauver un objet DataFrame sous la forme d’un fichier csv. C’est un format pratique qui peut être lu par la plupart des tableurs comme LibreOffice Calc ou Microsoft Excel. Il faut utiliser la métode .to_csv() :

df_europe.to_csv("europe.csv")

Cette méthode crée un fichier nommé data.csv dans le répertoire de travail (allez vérifier !), avec le contenu du DataFrame :

,pays,capitale,population,date d'adhésion,sièges,poids
1,allemagne,berlin,82162000,1957-01-01,96,1.3703356988657123
2,autriche,vienne,8700471,1995-01-01,18,2.4263669538020842
3,belgique,bruxelles,11289853,1957-01-01,21,2.1815127149779783
4,bulgarie,sofia,7153784,2007-01-01,17,2.7870184226008328
5,chypre,nicosie,848319,2004-01-01,6,8.295045974023676
6,croatie,zagreb,4190669,2013-01-01,11,3.078478088741746
7,danemark,copenhague,5659715,1973-01-01,13,2.6938631589897866
8,espagne,madrid,46438422,1986-01-01,54,1.363776011827881
9,estonie,tallinn,1315944,2004-01-01,6,5.347374284648732
10,finlande,helsinki,5401267,1995-01-01,13,2.822763201464005
11,france,paris,66661621,1957-01-01,74,1.301914879970682
12,grèce,athènes,10793526,1981-01-01,21,2.28182689046492
13,hongrie,budapest,9830485,2004-01-01,21,2.5053654900782893
14,irlande,dublin,4658530,1973-01-01,11,2.7693033411117423
15,italie,rome,60665551,1957-01-01,73,1.4112613727889787
16,lettonie,riga,1968957,2004-01-01,8,4.765193013788716
17,lituanie,vilnius,2888558,2004-01-01,11,4.466201715066578
18,luxembourg,luxembourg,576249,1957-01-01,6,12.211466060050068
19,malte,la valette,434403,2004-01-01,6,16.198886991198936
20,pays-bas,amsterdam,16979120,1957-01-01,26,1.7959114169499812
21,pologne,varsovie,37967209,2004-01-01,51,1.5753905797479404
22,portugal,lisbonne,10341330,1986-01-01,21,2.381604481215885
23,république tchèque,prague,10553853,2004-01-01,21,2.3336460977552242
24,roumanie,bucarest,19759968,2007-01-01,32,1.8992865050895604
25,slovaquie,bratislava,5426252,2004-01-01,13,2.8097658805528902
26,slovénie,ljubljana,2064188,2004-01-01,8,4.545351557537583
27,suède,stockholm,9851017,1995-01-01,20,2.381089216689604

Il est alors possible, plus tard, de charger le contenu du fichier csv avec la méthode .read_csv() :

df_europe_from_csv = pd.read_csv("europe.csv", index_col = 0, encoding = 'utf-8')

L’option encoding peut être nécessaire sur certains ordinateurs, pas sur d’autres.

Un problème de sauvegarder en csv est que si une des colonnes contient des objets python, il peuvent être convertis en un autre type au moment de l’écriture et de la lecture. Dans l’exemple ci-dessus, la colonne date d'adhésion n’est plus un objet datetime64 au moment de la lecture : il a été converti en objet str.

A = df_europe_from_csv["date d'adhésion"][1]
print(A)
print(type(A))

Une façon de garder toutes les propriétés des objets python est de les sauver dans un format que l’on appelle pickle :

# Sauvegarde d'un DataFrame dans un fichier pickle
df_europe.to_pickle('europe.pkl')

# Lecture du fichier pickle
df_europe_from_pickle = pd.read_pickle('./europe.pkl')

On voit alors que les dates sont restées des objets datetime64 :

df_europe_from_pickle["date d'adhésion"].head(n = 2)