17. Pandas#

En plus de ce qui est inclus dans Anaconda, ce cours nécessitera les bibliothèques suivantes :

!pip install --upgrade wbgapi
!pip install --upgrade yfinance

Hide code cell output

Collecting wbgapi
  Downloading wbgapi-1.0.14-py3-none-any.whl.metadata (13 kB)
Requirement already satisfied: requests in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from wbgapi) (2.34.2)
Requirement already satisfied: PyYAML in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from wbgapi) (6.0.3)
Requirement already satisfied: tabulate in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from wbgapi) (0.10.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->wbgapi) (3.4.4)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->wbgapi) (3.18)
Requirement already satisfied: urllib3<3,>=1.26 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->wbgapi) (2.7.0)
Requirement already satisfied: certifi>=2023.5.7 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests->wbgapi) (2026.5.20)
Downloading wbgapi-1.0.14-py3-none-any.whl (36 kB)
Installing collected packages: wbgapi
Successfully installed wbgapi-1.0.14
Collecting yfinance
  Downloading yfinance-1.5.1-py2.py3-none-any.whl.metadata (6.2 kB)
Requirement already satisfied: pandas>=1.3.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from yfinance) (3.0.3)
Requirement already satisfied: numpy>=1.16.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from yfinance) (2.4.6)
Requirement already satisfied: requests>=2.31 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from yfinance) (2.34.2)
Collecting multitasking>=0.0.7 (from yfinance)
  Downloading multitasking-0.0.13-py3-none-any.whl.metadata (16 kB)
Requirement already satisfied: platformdirs>=2.0.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from yfinance) (4.9.4)
Collecting pytz>=2022.5 (from yfinance)
  Downloading pytz-2026.2-py2.py3-none-any.whl.metadata (22 kB)
Collecting peewee>=3.16.2 (from yfinance)
  Downloading peewee-4.2.6-py3-none-any.whl.metadata (10 kB)
Requirement already satisfied: beautifulsoup4>=4.11.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from yfinance) (4.14.3)
Collecting curl_cffi>=0.15 (from yfinance)
  Downloading curl_cffi-0.15.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (18 kB)
Requirement already satisfied: protobuf>=3.19.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from yfinance) (6.33.5)
Requirement already satisfied: websockets>=13.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from yfinance) (15.0.1)
Requirement already satisfied: soupsieve>=1.6.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5)
Requirement already satisfied: typing-extensions>=4.0.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from beautifulsoup4>=4.11.1->yfinance) (4.15.0)
Requirement already satisfied: cffi>=2.0.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from curl_cffi>=0.15->yfinance) (2.0.0)
Requirement already satisfied: certifi>=2024.2.2 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from curl_cffi>=0.15->yfinance) (2026.5.20)
Requirement already satisfied: rich in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from curl_cffi>=0.15->yfinance) (15.0.0)
Requirement already satisfied: pycparser in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from cffi>=2.0.0->curl_cffi>=0.15->yfinance) (3.0)
Requirement already satisfied: python-dateutil>=2.8.2 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from pandas>=1.3.0->yfinance) (2.9.0.post0)
Requirement already satisfied: six>=1.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from python-dateutil>=2.8.2->pandas>=1.3.0->yfinance) (1.17.0)
Requirement already satisfied: charset_normalizer<4,>=2 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests>=2.31->yfinance) (3.4.4)
Requirement already satisfied: idna<4,>=2.5 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests>=2.31->yfinance) (3.18)
Requirement already satisfied: urllib3<3,>=1.26 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from requests>=2.31->yfinance) (2.7.0)
Requirement already satisfied: markdown-it-py>=2.2.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from rich->curl_cffi>=0.15->yfinance) (3.0.0)
Requirement already satisfied: pygments<3.0.0,>=2.13.0 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from rich->curl_cffi>=0.15->yfinance) (2.20.0)
Requirement already satisfied: mdurl~=0.1 in /home/runner/miniconda3/envs/quantecon/lib/python3.13/site-packages (from markdown-it-py>=2.2.0->rich->curl_cffi>=0.15->yfinance) (0.1.2)
Downloading yfinance-1.5.1-py2.py3-none-any.whl (144 kB)
Downloading curl_cffi-0.15.0-cp310-abi3-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (11.1 MB)
?25l
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 0.0/11.1 MB ? eta -:--:--
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.1/11.1 MB 59.1 MB/s  0:00:00
?25hDownloading multitasking-0.0.13-py3-none-any.whl (16 kB)
Downloading peewee-4.2.6-py3-none-any.whl (173 kB)
Downloading pytz-2026.2-py2.py3-none-any.whl (510 kB)
Installing collected packages: pytz, peewee, multitasking, curl_cffi, yfinance
?25l
   ━━━━━━━━╺━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1/5 [peewee]
   ━━━━━━━━━━━━━━━━━━━━━━━━╺━━━━━━━━━━━━━━━ 3/5 [curl_cffi]
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5/5 [yfinance]

Successfully installed curl_cffi-0.15.0 multitasking-0.0.13 peewee-4.2.6 pytz-2026.2 yfinance-1.5.1

17.1. Vue d’ensemble#

Pandas est un ensemble d’outils d’analyse de données rapides et efficaces pour Python.

Sa popularité a fortement augmenté ces dernières années, en parallèle de l’essor de domaines tels que la science des données et l’apprentissage automatique.

Voici une comparaison de popularité dans le temps face à Matlab et STATA, offerte par Stack Overflow Trends

_images/pandas_vs_rest.png

Tout comme NumPy fournit le type de données tableau de base ainsi que les opérations fondamentales sur les tableaux, pandas

  1. définit des structures fondamentales pour travailler avec les données et

  2. les dote de méthodes qui facilitent des opérations telles que

    • la lecture de données

    • l’ajustement des indices

    • le travail avec les dates et les séries temporelles

    • le tri, le regroupement, la réorganisation et le traitement général des données [1]

    • la gestion des valeurs manquantes, etc., etc.

Des fonctionnalités statistiques plus sophistiquées sont laissées à d’autres paquets, tels que statsmodels et scikit-learn, qui sont construits par-dessus pandas.

Ce cours fournira une introduction de base à pandas.

Tout au long du cours, nous supposerons que les importations suivantes ont été effectuées

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import requests

Deux types de données importants définis par pandas sont Series et DataFrame.

Vous pouvez considérer une Series comme une « colonne » de données, telle qu’une collection d’observations sur une seule variable.

Un DataFrame est un objet à deux dimensions permettant de stocker des colonnes de données liées entre elles.

17.2. Series#

Commençons par les Series.

Nous commençons par créer une série de quatre observations aléatoires

s = pd.Series(np.random.randn(4), name='daily returns')
s
0    1.339102
1    1.228159
2   -0.311071
3   -1.324389
Name: daily returns, dtype: float64

Ici, vous pouvez imaginer les indices 0, 1, 2, 3 comme indexant quatre sociétés cotées, et les valeurs étant les rendements quotidiens de leurs actions.

Les Series de pandas sont construites par-dessus les tableaux NumPy et prennent en charge de nombreuses opérations similaires

s * 100
0    133.910218
1    122.815850
2    -31.107090
3   -132.438928
Name: daily returns, dtype: float64
np.abs(s)
0    1.339102
1    1.228159
2    0.311071
3    1.324389
Name: daily returns, dtype: float64

Mais les Series offrent plus que les tableaux NumPy.

Non seulement elles disposent de méthodes supplémentaires (à orientation statistique)

s.describe()
count    4.000000
mean     0.232950
std      1.282612
min     -1.324389
25%     -0.564400
50%      0.458544
75%      1.255894
max      1.339102
Name: daily returns, dtype: float64

mais leurs indices sont plus flexibles

s.index = ['AMZN', 'AAPL', 'MSFT', 'GOOG']
s
AMZN    1.339102
AAPL    1.228159
MSFT   -0.311071
GOOG   -1.324389
Name: daily returns, dtype: float64

Vues de cette manière, les Series sont comme des dictionnaires Python rapides et efficaces (avec la restriction que tous les éléments du dictionnaire ont le même type — dans ce cas, des flottants).

En fait, vous pouvez utiliser une grande partie de la même syntaxe que les dictionnaires Python

s['AMZN']
np.float64(1.339102177625793)
s['AMZN'] = 0
s
AMZN    0.000000
AAPL    1.228159
MSFT   -0.311071
GOOG   -1.324389
Name: daily returns, dtype: float64
'AAPL' in s
True

17.3. DataFrames#

Alors qu’une Series est une seule colonne de données, un DataFrame comporte plusieurs colonnes, une pour chaque variable.

En substance, un DataFrame dans pandas est analogue à une feuille de calcul Excel (hautement optimisée).

Ainsi, c’est un outil puissant pour représenter et analyser des données naturellement organisées en lignes et en colonnes, souvent avec des indices descriptifs pour les lignes et les colonnes individuelles.

Regardons un exemple qui lit des données à partir du fichier CSV pandas/data/test_pwt.csv, tiré des Penn World Tables.

Le jeu de données contient les indicateurs suivants

| Nom de la variable | Description | | :- : | :- : | | POP | Population (en milliers) | | XRAT | Taux de change par rapport au dollar américain |
| tcgdp | PIB total converti en PPA (en millions de dollars internationaux) | | cc | Part de la consommation dans le PIB par habitant converti en PPA (%) | | cg | Part de la consommation publique dans le PIB par habitant converti en PPA (%) |

Nous allons le lire depuis une URL en utilisant la fonction read_csv de pandas.

df = pd.read_csv('https://raw.githubusercontent.com/QuantEcon/lecture-python-programming/main/lectures/_static/lecture_specific/pandas/data/test_pwt.csv')
type(df)
pandas.DataFrame

Voici le contenu de test_pwt.csv

df
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.653 0.999500 2.950722e+05 75.716805 5.578804
1 Australia AUS 2000 19053.186 1.724830 5.418047e+05 67.759026 6.720098
2 India IND 2000 1006300.297 44.941600 1.728144e+06 64.575551 14.072206
3 Israel ISR 2000 6114.570 4.077330 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000 11801.505 59.543808 5.026222e+03 74.707624 11.658954
5 South Africa ZAF 2000 45064.098 6.939830 2.272424e+05 72.718710 5.726546
6 United States USA 2000 282171.957 1.000000 9.898700e+06 72.347054 6.032454
7 Uruguay URY 2000 3219.793 12.099592 2.525596e+04 78.978740 5.108068

17.3.1. Sélectionner les données par position#

En pratique, une chose que nous faisons tout le temps est de rechercher, sélectionner et travailler avec un sous-ensemble des données qui nous intéressent.

Nous pouvons sélectionner des lignes particulières en utilisant la notation standard de découpage de tableau Python

df[2:5]
country country isocode year POP XRAT tcgdp cc cg
2 India IND 2000 1006300.297 44.941600 1.728144e+06 64.575551 14.072206
3 Israel ISR 2000 6114.570 4.077330 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000 11801.505 59.543808 5.026222e+03 74.707624 11.658954

Pour sélectionner des colonnes, nous pouvons passer une liste contenant les noms des colonnes désirées représentés sous forme de chaînes de caractères

df[['country', 'tcgdp']]
country tcgdp
0 Argentina 2.950722e+05
1 Australia 5.418047e+05
2 India 1.728144e+06
3 Israel 1.292539e+05
4 Malawi 5.026222e+03
5 South Africa 2.272424e+05
6 United States 9.898700e+06
7 Uruguay 2.525596e+04

Pour sélectionner à la fois des lignes et des colonnes en utilisant des entiers, l’attribut iloc doit être utilisé avec le format .iloc[lignes, colonnes].

df.iloc[2:5, 0:4]
country country isocode year POP
2 India IND 2000 1006300.297
3 Israel ISR 2000 6114.570
4 Malawi MWI 2000 11801.505

Pour sélectionner des lignes et des colonnes en utilisant un mélange d’entiers et d’étiquettes, l’attribut loc peut être utilisé de manière similaire

df.loc[df.index[2:5], ['country', 'tcgdp']]
country tcgdp
2 India 1.728144e+06
3 Israel 1.292539e+05
4 Malawi 5.026222e+03

17.3.2. Sélectionner les données par conditions#

Au lieu d’indexer les lignes et les colonnes en utilisant des entiers et des noms, nous pouvons également obtenir un sous-dataframe qui nous intéresse et qui satisfait certaines conditions (potentiellement complexes).

Cette section illustre différentes manières de procéder.

La manière la plus directe est d’utiliser l’opérateur [].

df[df.POP >= 20000]
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.653 0.99950 2.950722e+05 75.716805 5.578804
2 India IND 2000 1006300.297 44.94160 1.728144e+06 64.575551 14.072206
5 South Africa ZAF 2000 45064.098 6.93983 2.272424e+05 72.718710 5.726546
6 United States USA 2000 282171.957 1.00000 9.898700e+06 72.347054 6.032454

Pour comprendre ce qui se passe ici, remarquez que df.POP >= 20000 renvoie une série de valeurs booléennes.

df.POP >= 20000
0     True
1    False
2     True
3    False
4    False
5     True
6     True
7    False
Name: POP, dtype: bool

Dans ce cas, df[___] prend une série de valeurs booléennes et ne renvoie que les lignes ayant les valeurs True.

Prenons un autre exemple,

df[(df.country.isin(['Argentina', 'India', 'South Africa'])) & (df.POP > 40000)]
country country isocode year POP XRAT tcgdp cc cg
2 India IND 2000 1006300.297 44.94160 1.728144e+06 64.575551 14.072206
5 South Africa ZAF 2000 45064.098 6.93983 2.272424e+05 72.718710 5.726546

Cependant, il existe une autre manière de faire la même chose, qui peut être légèrement plus rapide pour les grands dataframes, avec une syntaxe plus naturelle.

# ce qui précède est équivalent à
df.query("POP >= 20000")
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.653 0.99950 2.950722e+05 75.716805 5.578804
2 India IND 2000 1006300.297 44.94160 1.728144e+06 64.575551 14.072206
5 South Africa ZAF 2000 45064.098 6.93983 2.272424e+05 72.718710 5.726546
6 United States USA 2000 282171.957 1.00000 9.898700e+06 72.347054 6.032454
df.query("country in ['Argentina', 'India', 'South Africa'] and POP > 40000")
country country isocode year POP XRAT tcgdp cc cg
2 India IND 2000 1006300.297 44.94160 1.728144e+06 64.575551 14.072206
5 South Africa ZAF 2000 45064.098 6.93983 2.272424e+05 72.718710 5.726546

Nous pouvons également autoriser des opérations arithmétiques entre différentes colonnes.

df[(df.cc + df.cg >= 80) & (df.POP <= 20000)]
country country isocode year POP XRAT tcgdp cc cg
4 Malawi MWI 2000 11801.505 59.543808 5026.221784 74.707624 11.658954
7 Uruguay URY 2000 3219.793 12.099592 25255.961693 78.978740 5.108068
# ce qui précède est équivalent à
df.query("cc + cg >= 80 & POP <= 20000")
country country isocode year POP XRAT tcgdp cc cg
4 Malawi MWI 2000 11801.505 59.543808 5026.221784 74.707624 11.658954
7 Uruguay URY 2000 3219.793 12.099592 25255.961693 78.978740 5.108068

Par exemple, nous pouvons utiliser le conditionnement pour sélectionner le pays ayant la plus grande part de consommation des ménages dans le PIB cc.

df.loc[df.cc == max(df.cc)]
country country isocode year POP XRAT tcgdp cc cg
7 Uruguay URY 2000 3219.793 12.099592 25255.961693 78.97874 5.108068

Lorsque nous ne voulons regarder que certaines colonnes d’un sous-dataframe sélectionné, nous pouvons utiliser les conditions ci-dessus avec la commande .loc[__ , __].

Le premier argument prend la condition, tandis que le second argument prend une liste de colonnes que nous voulons renvoyer.

df.loc[(df.cc + df.cg >= 80) & (df.POP <= 20000), ['country', 'year', 'POP']]
country year POP
4 Malawi 2000 11801.505
7 Uruguay 2000 3219.793

Application : Sous-ensemble d’un Dataframe

Les jeux de données du monde réel peuvent être énormes.

Il est parfois souhaitable de travailler avec un sous-ensemble de données pour améliorer l’efficacité computationnelle et réduire la redondance.

Imaginons que nous ne nous intéressions qu’à la population (POP) et au PIB total (tcgdp).

Une manière de réduire le dataframe df à ces seules variables est d’écraser le dataframe en utilisant la méthode de sélection décrite ci-dessus

df_subset = df[['country', 'POP', 'tcgdp']]
df_subset
country POP tcgdp
0 Argentina 37335.653 2.950722e+05
1 Australia 19053.186 5.418047e+05
2 India 1006300.297 1.728144e+06
3 Israel 6114.570 1.292539e+05
4 Malawi 11801.505 5.026222e+03
5 South Africa 45064.098 2.272424e+05
6 United States 282171.957 9.898700e+06
7 Uruguay 3219.793 2.525596e+04

Nous pouvons ensuite enregistrer le jeu de données plus petit pour une analyse ultérieure.

df_subset.to_csv('pwt_subset.csv', index=False)

17.3.3. Méthode Apply#

Une autre méthode Pandas largement utilisée est df.apply().

Elle applique une fonction à chaque ligne/colonne et renvoie une série.

Cette fonction peut être une fonction intégrée comme la fonction max, une fonction lambda, ou une fonction définie par l’utilisateur.

Voici un exemple utilisant la fonction max

df[['year', 'POP', 'XRAT', 'tcgdp', 'cc', 'cg']].apply(max)
year     2.000000e+03
POP      1.006300e+06
XRAT     5.954381e+01
tcgdp    9.898700e+06
cc       7.897874e+01
cg       1.407221e+01
dtype: float64

Cette ligne de code applique la fonction max à toutes les colonnes sélectionnées.

La fonction lambda est souvent utilisée avec la méthode df.apply()

Un exemple trivial consiste à renvoyer elle-même chaque ligne du dataframe

df.apply(lambda row: row, axis=1)
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.653 0.999500 2.950722e+05 75.716805 5.578804
1 Australia AUS 2000 19053.186 1.724830 5.418047e+05 67.759026 6.720098
2 India IND 2000 1006300.297 44.941600 1.728144e+06 64.575551 14.072206
3 Israel ISR 2000 6114.570 4.077330 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000 11801.505 59.543808 5.026222e+03 74.707624 11.658954
5 South Africa ZAF 2000 45064.098 6.939830 2.272424e+05 72.718710 5.726546
6 United States USA 2000 282171.957 1.000000 9.898700e+06 72.347054 6.032454
7 Uruguay URY 2000 3219.793 12.099592 2.525596e+04 78.978740 5.108068

Note

Pour la méthode .apply()

  • axis = 0 – applique la fonction à chaque colonne (variables)

  • axis = 1 – applique la fonction à chaque ligne (observations)

  • axis = 0 est le paramètre par défaut

Nous pouvons l’utiliser conjointement avec .loc[] pour effectuer des sélections plus avancées.

complexCondition = df.apply(
    lambda row: row.POP > 40000 if row.country in ['Argentina', 'India', 'South Africa'] else row.POP < 20000, 
    axis=1), ['country', 'year', 'POP', 'XRAT', 'tcgdp']

df.apply() renvoie ici une série de valeurs booléennes pour les lignes qui satisfont la condition spécifiée dans l’instruction if-else.

De plus, elle définit également un sous-ensemble de variables d’intérêt.

complexCondition
(0    False
 1     True
 2     True
 3     True
 4     True
 5     True
 6    False
 7     True
 dtype: bool,
 ['country', 'year', 'POP', 'XRAT', 'tcgdp'])

Lorsque nous appliquons cette condition au dataframe, le résultat sera

df.loc[complexCondition]
country year POP XRAT tcgdp
1 Australia 2000 19053.186 1.724830 5.418047e+05
2 India 2000 1006300.297 44.941600 1.728144e+06
3 Israel 2000 6114.570 4.077330 1.292539e+05
4 Malawi 2000 11801.505 59.543808 5.026222e+03
5 South Africa 2000 45064.098 6.939830 2.272424e+05
7 Uruguay 2000 3219.793 12.099592 2.525596e+04

17.3.4. Effectuer des modifications dans les DataFrames#

La capacité d’effectuer des modifications dans les dataframes est importante pour générer un jeu de données propre en vue d’analyses futures.

1. Nous pouvons utiliser df.where() de manière pratique pour « conserver » les lignes que nous avons sélectionnées et remplacer les autres lignes par n’importe quelles autres valeurs

df.where(df.POP >= 20000, False)
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.653 0.9995 295072.21869 75.716805 5.578804
1 False False False False False False False False
2 India IND 2000 1006300.297 44.9416 1728144.3748 64.575551 14.072206
3 False False False False False False False False
4 False False False False False False False False
5 South Africa ZAF 2000 45064.098 6.93983 227242.36949 72.71871 5.726546
6 United States USA 2000 282171.957 1.0 9898700.0 72.347054 6.032454
7 False False False False False False False False

2. Nous pouvons simplement utiliser .loc[] pour spécifier la colonne que nous voulons modifier, et attribuer des valeurs

df.loc[df.cg == max(df.cg), 'cg'] = np.nan
df
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.653 0.999500 2.950722e+05 75.716805 5.578804
1 Australia AUS 2000 19053.186 1.724830 5.418047e+05 67.759026 6.720098
2 India IND 2000 1006300.297 44.941600 1.728144e+06 64.575551 NaN
3 Israel ISR 2000 6114.570 4.077330 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000 11801.505 59.543808 5.026222e+03 74.707624 11.658954
5 South Africa ZAF 2000 45064.098 6.939830 2.272424e+05 72.718710 5.726546
6 United States USA 2000 282171.957 1.000000 9.898700e+06 72.347054 6.032454
7 Uruguay URY 2000 3219.793 12.099592 2.525596e+04 78.978740 5.108068

3. Nous pouvons utiliser la méthode .apply() pour modifier des lignes/colonnes dans leur ensemble

def update_row(row):
    # modifie POP
    row.POP = np.nan if row.POP<= 10000 else row.POP

    # modifie XRAT
    row.XRAT = row.XRAT / 10
    return row

df.apply(update_row, axis=1)
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.653 0.099950 2.950722e+05 75.716805 5.578804
1 Australia AUS 2000 19053.186 0.172483 5.418047e+05 67.759026 6.720098
2 India IND 2000 1006300.297 4.494160 1.728144e+06 64.575551 NaN
3 Israel ISR 2000 NaN 0.407733 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000 11801.505 5.954381 5.026222e+03 74.707624 11.658954
5 South Africa ZAF 2000 45064.098 0.693983 2.272424e+05 72.718710 5.726546
6 United States USA 2000 282171.957 0.100000 9.898700e+06 72.347054 6.032454
7 Uruguay URY 2000 NaN 1.209959 2.525596e+04 78.978740 5.108068

4. Nous pouvons utiliser la méthode .map() pour modifier toutes les entrées individuelles du dataframe d’un seul coup.

# Arrondit tous les nombres décimaux à 2 décimales
df.map(lambda x : round(x,2) if type(x)!=str else x)
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000 37335.65 1.00 295072.22 75.72 5.58
1 Australia AUS 2000 19053.19 1.72 541804.65 67.76 6.72
2 India IND 2000 1006300.30 44.94 1728144.37 64.58 NaN
3 Israel ISR 2000 6114.57 4.08 129253.89 64.44 10.27
4 Malawi MWI 2000 11801.50 59.54 5026.22 74.71 11.66
5 South Africa ZAF 2000 45064.10 6.94 227242.37 72.72 5.73
6 United States USA 2000 282171.96 1.00 9898700.00 72.35 6.03
7 Uruguay URY 2000 3219.79 12.10 25255.96 78.98 5.11

Application : Imputation des valeurs manquantes

Le remplacement des valeurs manquantes est une étape importante du traitement des données.

Insérons aléatoirement quelques valeurs NaN

for idx in list(zip([0, 3, 5, 6], [3, 4, 6, 2])):
    df.iloc[idx] = np.nan

df
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000.0 NaN 0.999500 2.950722e+05 75.716805 5.578804
1 Australia AUS 2000.0 19053.186 1.724830 5.418047e+05 67.759026 6.720098
2 India IND 2000.0 1006300.297 44.941600 1.728144e+06 64.575551 NaN
3 Israel ISR 2000.0 6114.570 NaN 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000.0 11801.505 59.543808 5.026222e+03 74.707624 11.658954
5 South Africa ZAF 2000.0 45064.098 6.939830 2.272424e+05 NaN 5.726546
6 United States USA NaN 282171.957 1.000000 9.898700e+06 72.347054 6.032454
7 Uruguay URY 2000.0 3219.793 12.099592 2.525596e+04 78.978740 5.108068

La fonction zip() crée ici des paires de valeurs à partir des deux listes (c’est-à-dire [0,3], [3,4] …)

Nous pouvons utiliser à nouveau la méthode .map() pour remplacer toutes les valeurs manquantes par 0

# remplace toutes les valeurs NaN par 0
def replace_nan(x):
    if type(x)!=str:
        return  0 if np.isnan(x) else x
    else:
        return x

df.map(replace_nan)
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000.0 0.000 0.999500 2.950722e+05 75.716805 5.578804
1 Australia AUS 2000.0 19053.186 1.724830 5.418047e+05 67.759026 6.720098
2 India IND 2000.0 1006300.297 44.941600 1.728144e+06 64.575551 0.000000
3 Israel ISR 2000.0 6114.570 0.000000 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000.0 11801.505 59.543808 5.026222e+03 74.707624 11.658954
5 South Africa ZAF 2000.0 45064.098 6.939830 2.272424e+05 0.000000 5.726546
6 United States USA 0.0 282171.957 1.000000 9.898700e+06 72.347054 6.032454
7 Uruguay URY 2000.0 3219.793 12.099592 2.525596e+04 78.978740 5.108068

Pandas nous fournit également des méthodes pratiques pour remplacer les valeurs manquantes.

Par exemple, l’imputation simple utilisant les moyennes des variables peut être facilement réalisée dans pandas

df = df.fillna(df.iloc[:,2:8].mean())
df
country country isocode year POP XRAT tcgdp cc cg
0 Argentina ARG 2000.0 1.962465e+05 0.999500 2.950722e+05 75.716805 5.578804
1 Australia AUS 2000.0 1.905319e+04 1.724830 5.418047e+05 67.759026 6.720098
2 India IND 2000.0 1.006300e+06 44.941600 1.728144e+06 64.575551 7.298802
3 Israel ISR 2000.0 6.114570e+03 18.178451 1.292539e+05 64.436451 10.266688
4 Malawi MWI 2000.0 1.180150e+04 59.543808 5.026222e+03 74.707624 11.658954
5 South Africa ZAF 2000.0 4.506410e+04 6.939830 2.272424e+05 71.217322 5.726546
6 United States USA 2000.0 2.821720e+05 1.000000 9.898700e+06 72.347054 6.032454
7 Uruguay URY 2000.0 3.219793e+03 12.099592 2.525596e+04 78.978740 5.108068

L’imputation des valeurs manquantes est un vaste domaine de la science des données impliquant diverses techniques d’apprentissage automatique.

Il existe également des outils plus avancés en Python pour imputer les valeurs manquantes.

17.3.5. Standardisation et visualisation#

Imaginons que nous ne nous intéressions qu’à la population (POP) et au PIB total (tcgdp).

Une manière de réduire le dataframe df à ces seules variables est d’écraser le dataframe en utilisant la méthode de sélection décrite ci-dessus

df = df[['country', 'POP', 'tcgdp']]
df
country POP tcgdp
0 Argentina 1.962465e+05 2.950722e+05
1 Australia 1.905319e+04 5.418047e+05
2 India 1.006300e+06 1.728144e+06
3 Israel 6.114570e+03 1.292539e+05
4 Malawi 1.180150e+04 5.026222e+03
5 South Africa 4.506410e+04 2.272424e+05
6 United States 2.821720e+05 9.898700e+06
7 Uruguay 3.219793e+03 2.525596e+04

Ici, l’indice 0, 1,..., 7 est redondant car nous pouvons utiliser les noms des pays comme indice.

Pour ce faire, nous définissons l’indice comme étant la variable country du dataframe

df = df.set_index('country')
df
POP tcgdp
country
Argentina 1.962465e+05 2.950722e+05
Australia 1.905319e+04 5.418047e+05
India 1.006300e+06 1.728144e+06
Israel 6.114570e+03 1.292539e+05
Malawi 1.180150e+04 5.026222e+03
South Africa 4.506410e+04 2.272424e+05
United States 2.821720e+05 9.898700e+06
Uruguay 3.219793e+03 2.525596e+04

Donnons aux colonnes des noms légèrement meilleurs

df.columns = 'population', 'total GDP'
df
population total GDP
country
Argentina 1.962465e+05 2.950722e+05
Australia 1.905319e+04 5.418047e+05
India 1.006300e+06 1.728144e+06
Israel 6.114570e+03 1.292539e+05
Malawi 1.180150e+04 5.026222e+03
South Africa 4.506410e+04 2.272424e+05
United States 2.821720e+05 9.898700e+06
Uruguay 3.219793e+03 2.525596e+04

La variable population est en milliers, revenons aux unités simples

df['population'] = df['population'] * 1e3
df
population total GDP
country
Argentina 1.962465e+08 2.950722e+05
Australia 1.905319e+07 5.418047e+05
India 1.006300e+09 1.728144e+06
Israel 6.114570e+06 1.292539e+05
Malawi 1.180150e+07 5.026222e+03
South Africa 4.506410e+07 2.272424e+05
United States 2.821720e+08 9.898700e+06
Uruguay 3.219793e+06 2.525596e+04

Ensuite, nous allons ajouter une colonne montrant le PIB réel par habitant, en multipliant par 1 000 000 au passage car le PIB total est en millions

df['GDP percap'] = df['total GDP'] * 1e6 / df['population']
df
population total GDP GDP percap
country
Argentina 1.962465e+08 2.950722e+05 1503.579625
Australia 1.905319e+07 5.418047e+05 28436.433261
India 1.006300e+09 1.728144e+06 1717.324719
Israel 6.114570e+06 1.292539e+05 21138.672749
Malawi 1.180150e+07 5.026222e+03 425.896679
South Africa 4.506410e+07 2.272424e+05 5042.647686
United States 2.821720e+08 9.898700e+06 35080.381854
Uruguay 3.219793e+06 2.525596e+04 7843.970620

L’une des choses agréables avec les objets DataFrame et Series de pandas est qu’ils disposent de méthodes de tracé et de visualisation qui fonctionnent via Matplotlib.

Par exemple, nous pouvons facilement générer un diagramme en barres du PIB par habitant

ax = df['GDP percap'].plot(kind='bar')
ax.set_xlabel('country', fontsize=12)
ax.set_ylabel('GDP per capita', fontsize=12)
plt.show()
_images/3d453166934bf5348c79a58245da4799ebdd9918d8d42b27dbe2e11284bfeda7.png

Pour le moment, le dataframe est ordonné par ordre alphabétique des pays — modifions-le pour l’ordonner selon le PIB par habitant

df = df.sort_values(by='GDP percap', ascending=False)
df
population total GDP GDP percap
country
United States 2.821720e+08 9.898700e+06 35080.381854
Australia 1.905319e+07 5.418047e+05 28436.433261
Israel 6.114570e+06 1.292539e+05 21138.672749
Uruguay 3.219793e+06 2.525596e+04 7843.970620
South Africa 4.506410e+07 2.272424e+05 5042.647686
India 1.006300e+09 1.728144e+06 1717.324719
Argentina 1.962465e+08 2.950722e+05 1503.579625
Malawi 1.180150e+07 5.026222e+03 425.896679

Un tracé comme précédemment donne maintenant

ax = df['GDP percap'].plot(kind='bar')
ax.set_xlabel('country', fontsize=12)
ax.set_ylabel('GDP per capita', fontsize=12)
plt.show()
_images/b029485d5d8fb942d4974da26f537773ab309302eca3835d6bd9dc175d1bb28a.png

17.4. Sources de données en ligne#

Python facilite l’interrogation de bases de données en ligne de manière programmatique.

Une base de données importante pour les économistes est FRED — une vaste collection de données de séries temporelles maintenue par la Fed de St. Louis.

Par exemple, supposons que nous soyons intéressés par le taux de chômage.

(Pour télécharger les données au format csv, cliquez sur Download en haut à droite et sélectionnez l’option CSV (data)).

Alternativement, nous pouvons accéder au fichier CSV depuis un programme Python.

Cela peut être fait avec diverses méthodes.

Nous commençons par une méthode de relativement bas niveau, puis revenons à pandas.

17.4.1. Accéder aux données avec requests#

Une option consiste à utiliser requests, une bibliothèque Python standard pour demander des données via Internet.

Pour commencer, essayez le code suivant sur votre ordinateur

r = requests.get('https://fred.stlouisfed.org/graph/fredgraph.csv?bgcolor=%23e1e9f0&chart_type=line&drp=0&fo=open%20sans&graph_bgcolor=%23ffffff&height=450&mode=fred&recession_bars=on&txtcolor=%23444444&ts=12&tts=12&width=1318&nt=0&thu=0&trc=0&show_legend=yes&show_axis_titles=yes&show_tooltip=yes&id=UNRATE&scale=left&cosd=1948-01-01&coed=2024-06-01&line_color=%234572a7&link_values=false&line_style=solid&mark_type=none&mw=3&lw=2&ost=-99999&oet=99999&mma=0&fml=a&fq=Monthly&fam=avg&fgst=lin&fgsnd=2020-02-01&line_index=1&transformation=lin&vintage_date=2024-07-29&revision_date=2024-07-29&nd=1948-01-01')

S’il n’y a pas de message d’erreur, c’est que l’appel a réussi.

Si vous obtenez une erreur, alors il y a deux causes probables

  1. Vous n’êtes pas connecté à Internet — espérons que ce n’est pas le cas.

  2. Votre machine accède à Internet via un serveur proxy, et Python n’en est pas conscient.

Dans le second cas, vous pouvez soit

  • passer à une autre machine

  • résoudre votre problème de proxy en lisant la documentation

En supposant que tout fonctionne, vous pouvez maintenant utiliser l’objet source renvoyé par l’appel requests.get('https://research.stlouisfed.org/fred2/series/UNRATE/downloaddata/UNRATE.csv')

url = 'https://fred.stlouisfed.org/graph/fredgraph.csv?bgcolor=%23e1e9f0&chart_type=line&drp=0&fo=open%20sans&graph_bgcolor=%23ffffff&height=450&mode=fred&recession_bars=on&txtcolor=%23444444&ts=12&tts=12&width=1318&nt=0&thu=0&trc=0&show_legend=yes&show_axis_titles=yes&show_tooltip=yes&id=UNRATE&scale=left&cosd=1948-01-01&coed=2024-06-01&line_color=%234572a7&link_values=false&line_style=solid&mark_type=none&mw=3&lw=2&ost=-99999&oet=99999&mma=0&fml=a&fq=Monthly&fam=avg&fgst=lin&fgsnd=2020-02-01&line_index=1&transformation=lin&vintage_date=2024-07-29&revision_date=2024-07-29&nd=1948-01-01'
source = requests.get(url).content.decode().split("\n")
source[0]
'observation_date,UNRATE'
source[1]
'1948-01-01,3.4'
source[2]
'1948-02-01,3.8'

Nous pourrions maintenant écrire du code supplémentaire pour analyser ce texte et le stocker sous forme de tableau.

Mais c’est inutile — la fonction read_csv de pandas peut gérer cette tâche pour nous.

Nous utilisons parse_dates=True afin que pandas reconnaisse notre colonne de dates, permettant un filtrage simple par date

data = pd.read_csv(url, index_col=0, parse_dates=True)

Les données ont été lues dans un DataFrame pandas appelé data que nous pouvons maintenant manipuler de la manière habituelle

type(data)
pandas.DataFrame
data.head()  # Une méthode utile pour jeter un coup d'œil rapide à un dataframe
UNRATE
observation_date
1948-01-01 3.4
1948-02-01 3.8
1948-03-01 4.0
1948-04-01 3.9
1948-05-01 3.5
pd.set_option('display.precision', 1)
data.describe()  # Votre sortie peut différer légèrement
UNRATE
count 918.0
mean 5.7
std 1.7
min 2.5
25% 4.4
50% 5.5
75% 6.7
max 14.8

Nous pouvons également tracer le taux de chômage de 2006 à 2012 comme suit

ax = data['2006':'2012'].plot(title='US Unemployment Rate', legend=False)
ax.set_xlabel('year', fontsize=12)
ax.set_ylabel('%', fontsize=12)
plt.show()
_images/ff7a3f2db5ccd04b1215dfb9fec8549fa006fa979eea455f6c81091b8fee8209.png

Notez que pandas offre de nombreuses autres alternatives de types de fichiers.

Pandas dispose d’une grande variété de méthodes de haut niveau que nous pouvons utiliser pour lire des fichiers excel, json, parquet ou nous connecter directement à un serveur de base de données.

17.4.2. Utiliser wbgapi et yfinance pour accéder aux données#

La bibliothèque Python wbgapi peut être utilisée pour récupérer des données à partir des nombreuses bases de données publiées par la Banque mondiale.

Note

Vous pouvez trouver des informations utiles sur le paquet wbgapi dans cet article de blog de la Banque mondiale, en plus de ce tutoriel

Nous utiliserons également yfinance pour récupérer des données de Yahoo finance dans les exercices.

Pour l’instant, parcourons un exemple de téléchargement et de tracé de données — cette fois-ci de la Banque mondiale.

La Banque mondiale collecte et organise des données sur un large éventail d’indicateurs.

Par exemple, voici quelques données sur la dette publique en ratio du PIB.

L’exemple de code suivant récupère les données pour vous et trace les séries temporelles pour les États-Unis et l’Australie

import wbgapi as wb
wb.series.info('GC.DOD.TOTL.GD.ZS')
id value
GC.DOD.TOTL.GD.ZSCentral government debt, total (% of GDP)
1 elements
govt_debt = wb.data.DataFrame('GC.DOD.TOTL.GD.ZS', economy=['USA','AUS'], time=range(2005,2016))
govt_debt = govt_debt.T    # déplace les années des colonnes vers les lignes pour le tracé
govt_debt.plot(xlabel='year', ylabel='Government debt (% of GDP)');
_images/e846408cc9e80bf825aae93b23334261a132772929aba93f2de86253cd066836.png

17.5. Exercices#

Exercice 17.1

Avec ces importations :

import datetime as dt
import yfinance as yf

Écrivez un programme pour calculer la variation en pourcentage du prix au cours de l’année 2021 pour les actions suivantes :

ticker_list = {'INTC': 'Intel',
               'MSFT': 'Microsoft',
               'IBM': 'IBM',
               'BHP': 'BHP',
               'TM': 'Toyota',
               'AAPL': 'Apple',
               'AMZN': 'Amazon',
               'C': 'Citigroup',
               'QCOM': 'Qualcomm',
               'KO': 'Coca-Cola',
               'GOOG': 'Google'}

Voici la première partie du programme

def read_data(ticker_list,
          start=dt.datetime(2021, 1, 1),
          end=dt.datetime(2021, 12, 31)):
    """
    This function reads in closing price data from Yahoo
    for each tick in the ticker_list.
    """
    ticker = pd.DataFrame()

    for tick in ticker_list:
        stock = yf.Ticker(tick)
        prices = stock.history(start=start, end=end)

        # Change the index to date-only
        prices.index = pd.to_datetime(prices.index.date)
        
        closing_prices = prices['Close']
        ticker[tick] = closing_prices

    return ticker

ticker = read_data(ticker_list)

Complétez le programme pour tracer le résultat sous forme de diagramme en barres comme celui-ci :

_images/pandas_share_prices.png

Exercice 17.2

En utilisant la méthode read_data introduite dans Exercice 17.1, écrivez un programme pour obtenir la variation en pourcentage d’une année sur l’autre pour les indices suivants :

indices_list = {'^GSPC': 'S&P 500',
               '^IXIC': 'NASDAQ',
               '^DJI': 'Dow Jones',
               '^N225': 'Nikkei'}

Complétez le programme pour afficher des statistiques descriptives et tracer le résultat sous forme de graphique de série temporelle comme celui-ci :

_images/pandas_indices_pctchange.png