18. Pandas pour les données de panel#
En plus de ce qui est inclus dans Anaconda, ce cours nécessitera les bibliothèques suivantes :
!pip install --upgrade seaborn
Nous utilisons les importations suivantes.
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme()
18.1. Vue d’ensemble#
Dans un cours précédent sur pandas, nous avons examiné le travail avec des ensembles de données simples.
Les économètres ont souvent besoin de travailler avec des ensembles de données plus complexes, comme les panels.
Les tâches courantes comprennent
Importer des données, les nettoyer et les remodeler selon plusieurs axes.
Sélectionner une série temporelle ou une coupe transversale à partir d’un panel.
Grouper et résumer des données.
pandas (dérivé de « panel » et « data ») contient des outils puissants et
faciles à utiliser pour résoudre exactement ce genre de problèmes.
Dans ce qui suit, nous utiliserons un ensemble de données de panel des salaires minimums réels de l’OCDE pour créer :
des statistiques descriptives sur plusieurs dimensions de nos données
une série temporelle du salaire minimum moyen des pays de l’ensemble de données
des estimations par noyau de la densité des salaires par continent
Nous commencerons par lire nos données de panel au format long à partir d’un fichier CSV et
par remodeler le DataFrame résultant avec pivot_table afin de construire un MultiIndex.
Des détails supplémentaires seront ajoutés à notre DataFrame en utilisant la fonction
merge de pandas, et les données seront résumées avec la fonction groupby.
18.2. Découpage et remodelage des données#
Nous allons lire un ensemble de données de l’OCDE des salaires minimums réels dans 32
pays et l’assigner à realwage.
L’ensemble de données est accessible via le lien suivant :
url1 = 'https://github.com/QuantEcon/data-lectures/raw/main/lectures/realwage.csv'
import pandas as pd
# Afficher 6 colonnes à des fins de visualisation
pd.set_option('display.max_columns', 6)
# Réduire les décimales à 2
pd.options.display.float_format = '{:,.2f}'.format
realwage = pd.read_csv(url1)
Regardons ce avec quoi nous devons travailler
realwage.head() # Afficher les 5 premières lignes
| Unnamed: 0 | Time | Country | Series | Pay period | value | |
|---|---|---|---|---|---|---|
| 0 | 0 | 2006-01-01 | Ireland | In 2015 constant prices at 2015 USD PPPs | Annual | 17,132.44 |
| 1 | 1 | 2007-01-01 | Ireland | In 2015 constant prices at 2015 USD PPPs | Annual | 18,100.92 |
| 2 | 2 | 2008-01-01 | Ireland | In 2015 constant prices at 2015 USD PPPs | Annual | 17,747.41 |
| 3 | 3 | 2009-01-01 | Ireland | In 2015 constant prices at 2015 USD PPPs | Annual | 18,580.14 |
| 4 | 4 | 2010-01-01 | Ireland | In 2015 constant prices at 2015 USD PPPs | Annual | 18,755.83 |
Les données sont actuellement au format long, ce qui est difficile à analyser lorsque les données comportent plusieurs dimensions.
Nous utiliserons pivot_table pour créer un panel au format large, avec un MultiIndex pour gérer les données de dimension supérieure.
Les arguments de pivot_table doivent spécifier les données (values), l’index et les colonnes que nous voulons dans notre dataframe résultant.
En passant une liste dans columns, nous pouvons créer un MultiIndex dans notre axe des colonnes
realwage = realwage.pivot_table(values='value',
index='Time',
columns=['Country', 'Series', 'Pay period'])
realwage.head()
| Country | Australia | ... | United States | ||||
|---|---|---|---|---|---|---|---|
| Series | In 2015 constant prices at 2015 USD PPPs | In 2015 constant prices at 2015 USD exchange rates | ... | In 2015 constant prices at 2015 USD PPPs | In 2015 constant prices at 2015 USD exchange rates | ||
| Pay period | Annual | Hourly | Annual | ... | Hourly | Annual | Hourly |
| Time | |||||||
| 2006-01-01 | 20,410.65 | 10.33 | 23,826.64 | ... | 6.05 | 12,594.40 | 6.05 |
| 2007-01-01 | 21,087.57 | 10.67 | 24,616.84 | ... | 6.24 | 12,974.40 | 6.24 |
| 2008-01-01 | 20,718.24 | 10.48 | 24,185.70 | ... | 6.78 | 14,097.56 | 6.78 |
| 2009-01-01 | 20,984.77 | 10.62 | 24,496.84 | ... | 7.58 | 15,756.42 | 7.58 |
| 2010-01-01 | 20,879.33 | 10.57 | 24,373.76 | ... | 7.88 | 16,391.31 | 7.88 |
5 rows × 128 columns
Pour filtrer plus facilement nos données de série temporelle par la suite, nous convertirons l’index en DateTimeIndex
realwage.index = pd.to_datetime(realwage.index)
type(realwage.index)
pandas.DatetimeIndex
Les colonnes contiennent plusieurs niveaux d’indexation, appelés
MultiIndex, les niveaux étant ordonnés hiérarchiquement (Country >
Series > Pay period).
Un MultiIndex est le moyen le plus simple et le plus flexible de gérer les données de panel
dans pandas
type(realwage.columns)
pandas.MultiIndex
realwage.columns.names
FrozenList(['Country', 'Series', 'Pay period'])
Comme précédemment, nous pouvons sélectionner le pays (le niveau le plus élevé de notre
MultiIndex)
realwage['United States'].head()
| Series | In 2015 constant prices at 2015 USD PPPs | In 2015 constant prices at 2015 USD exchange rates | ||
|---|---|---|---|---|
| Pay period | Annual | Hourly | Annual | Hourly |
| Time | ||||
| 2006-01-01 | 12,594.40 | 6.05 | 12,594.40 | 6.05 |
| 2007-01-01 | 12,974.40 | 6.24 | 12,974.40 | 6.24 |
| 2008-01-01 | 14,097.56 | 6.78 | 14,097.56 | 6.78 |
| 2009-01-01 | 15,756.42 | 7.58 | 15,756.42 | 7.58 |
| 2010-01-01 | 16,391.31 | 7.88 | 16,391.31 | 7.88 |
L’empilement et le désempilement des niveaux du MultiIndex seront utilisés
tout au long de ce cours pour remodeler notre dataframe dans le format dont nous avons besoin.
.stack() fait pivoter le niveau le plus bas du MultiIndex des colonnes vers
l’index des lignes (.unstack() fonctionne dans la direction opposée — essayez-le)
realwage.stack(future_stack=True).head()
| Country | Australia | Belgium | ... | United Kingdom | United States | |||
|---|---|---|---|---|---|---|---|---|
| Series | In 2015 constant prices at 2015 USD PPPs | In 2015 constant prices at 2015 USD exchange rates | In 2015 constant prices at 2015 USD PPPs | ... | In 2015 constant prices at 2015 USD exchange rates | In 2015 constant prices at 2015 USD PPPs | In 2015 constant prices at 2015 USD exchange rates | |
| Time | Pay period | |||||||
| 2006-01-01 | Annual | 20,410.65 | 23,826.64 | 21,042.28 | ... | 20,376.32 | 12,594.40 | 12,594.40 |
| Hourly | 10.33 | 12.06 | 10.09 | ... | 9.81 | 6.05 | 6.05 | |
| 2007-01-01 | Annual | 21,087.57 | 24,616.84 | 21,310.05 | ... | 20,954.13 | 12,974.40 | 12,974.40 |
| Hourly | 10.67 | 12.46 | 10.22 | ... | 10.07 | 6.24 | 6.24 | |
| 2008-01-01 | Annual | 20,718.24 | 24,185.70 | 21,416.96 | ... | 20,902.87 | 14,097.56 | 14,097.56 |
5 rows × 64 columns
Nous pouvons également passer un argument pour sélectionner le niveau que nous aimerions empiler
realwage.stack(level='Country', future_stack=True).head() # future_stack=True est requis jusqu'à pandas>3.0
| Series | In 2015 constant prices at 2015 USD PPPs | In 2015 constant prices at 2015 USD exchange rates | |||
|---|---|---|---|---|---|
| Pay period | Annual | Hourly | Annual | Hourly | |
| Time | Country | ||||
| 2006-01-01 | Australia | 20,410.65 | 10.33 | 23,826.64 | 12.06 |
| Belgium | 21,042.28 | 10.09 | 20,228.74 | 9.70 | |
| Brazil | 3,310.51 | 1.41 | 2,032.87 | 0.87 | |
| Canada | 13,649.69 | 6.56 | 14,335.12 | 6.89 | |
| Chile | 5,201.65 | 2.22 | 3,333.76 | 1.42 | |
L’utilisation d’un DatetimeIndex facilite la sélection d’une période temporelle
particulière.
Sélectionner une année et empiler les deux niveaux inférieurs du
MultiIndex crée une coupe transversale de nos données de panel
realwage.loc['2015'].stack(level=(1, 2), future_stack=True).transpose().head() # future_stack=True est requis jusqu'à pandas>3.0
| Time | 2015-01-01 | |||
|---|---|---|---|---|
| Series | In 2015 constant prices at 2015 USD PPPs | In 2015 constant prices at 2015 USD exchange rates | ||
| Pay period | Annual | Hourly | Annual | Hourly |
| Country | ||||
| Australia | 21,715.53 | 10.99 | 25,349.90 | 12.83 |
| Belgium | 21,588.12 | 10.35 | 20,753.48 | 9.95 |
| Brazil | 4,628.63 | 2.00 | 2,842.28 | 1.21 |
| Canada | 16,536.83 | 7.95 | 17,367.24 | 8.35 |
| Chile | 6,633.56 | 2.80 | 4,251.49 | 1.81 |
Pour le reste du cours, nous travaillerons avec un dataframe des salaires minimums réels horaires par pays et par période, mesurés en dollars américains de 2015.
Pour créer notre dataframe filtré (realwage_f), nous pouvons utiliser la méthode xs
pour sélectionner des valeurs aux niveaux inférieurs du multiindex, tout en conservant
les niveaux supérieurs (les pays dans ce cas)
realwage_f = realwage.xs(('Hourly', 'In 2015 constant prices at 2015 USD exchange rates'),
level=('Pay period', 'Series'), axis=1)
realwage_f.head()
| Country | Australia | Belgium | Brazil | ... | Turkey | United Kingdom | United States |
|---|---|---|---|---|---|---|---|
| Time | |||||||
| 2006-01-01 | 12.06 | 9.70 | 0.87 | ... | 2.27 | 9.81 | 6.05 |
| 2007-01-01 | 12.46 | 9.82 | 0.92 | ... | 2.26 | 10.07 | 6.24 |
| 2008-01-01 | 12.24 | 9.87 | 0.96 | ... | 2.22 | 10.04 | 6.78 |
| 2009-01-01 | 12.40 | 10.21 | 1.03 | ... | 2.28 | 10.15 | 7.58 |
| 2010-01-01 | 12.34 | 10.05 | 1.08 | ... | 2.30 | 9.96 | 7.88 |
5 rows × 32 columns
18.3. Fusion de dataframes et remplissage des NaN#
Similairement aux bases de données relationnelles comme SQL, pandas dispose de méthodes intégrées pour fusionner des ensembles de données ensemble.
En utilisant les informations sur les pays de
WorldData.info, nous ajouterons
le continent de chaque pays à realwage_f avec la fonction merge.
L’ensemble de données est accessible via le lien suivant :
url2 = 'https://github.com/QuantEcon/data-lectures/raw/main/lectures/countries.csv'
worlddata = pd.read_csv(url2, sep=';')
worlddata.head()
| Country (en) | Country (de) | Country (local) | ... | Deathrate | Life expectancy | Url | |
|---|---|---|---|---|---|---|---|
| 0 | Afghanistan | Afghanistan | Afganistan/Afqanestan | ... | 13.70 | 51.30 | https://www.laenderdaten.info/Asien/Afghanista... |
| 1 | Egypt | Ägypten | Misr | ... | 4.70 | 72.70 | https://www.laenderdaten.info/Afrika/Aegypten/... |
| 2 | Åland Islands | Ålandinseln | Åland | ... | 0.00 | 0.00 | https://www.laenderdaten.info/Europa/Aland/ind... |
| 3 | Albania | Albanien | Shqipëria | ... | 6.70 | 78.30 | https://www.laenderdaten.info/Europa/Albanien/... |
| 4 | Algeria | Algerien | Al-Jaza’ir/Algérie | ... | 4.30 | 76.80 | https://www.laenderdaten.info/Afrika/Algerien/... |
5 rows × 17 columns
Tout d’abord, nous sélectionnerons uniquement les variables country et continent de
worlddata et renommerons la colonne en « Country »
worlddata = worlddata[['Country (en)', 'Continent']]
worlddata = worlddata.rename(columns={'Country (en)': 'Country'})
worlddata.head()
| Country | Continent | |
|---|---|---|
| 0 | Afghanistan | Asia |
| 1 | Egypt | Africa |
| 2 | Åland Islands | Europe |
| 3 | Albania | Europe |
| 4 | Algeria | Africa |
Nous voulons fusionner notre nouveau dataframe, worlddata, avec realwage_f.
La fonction merge de pandas permet de joindre des dataframes ensemble par
lignes.
Nos dataframes seront fusionnés en utilisant les noms de pays, ce qui nous oblige à utiliser
la transposée de realwage_f afin que les lignes correspondent aux noms de pays
dans les deux dataframes
realwage_f.transpose().head()
| Time | 2006-01-01 | 2007-01-01 | 2008-01-01 | ... | 2014-01-01 | 2015-01-01 | 2016-01-01 |
|---|---|---|---|---|---|---|---|
| Country | |||||||
| Australia | 12.06 | 12.46 | 12.24 | ... | 12.67 | 12.83 | 12.98 |
| Belgium | 9.70 | 9.82 | 9.87 | ... | 10.01 | 9.95 | 9.76 |
| Brazil | 0.87 | 0.92 | 0.96 | ... | 1.21 | 1.21 | 1.24 |
| Canada | 6.89 | 6.96 | 7.24 | ... | 8.22 | 8.35 | 8.48 |
| Chile | 1.42 | 1.45 | 1.44 | ... | 1.76 | 1.81 | 1.91 |
5 rows × 11 columns
Nous pouvons utiliser une jointure left, right, inner ou outer pour fusionner nos ensembles de données :
la jointure left n’inclut que les pays de l’ensemble de données de gauche
la jointure right n’inclut que les pays de l’ensemble de données de droite
la jointure outer inclut les pays qui sont soit dans l’ensemble de gauche, soit dans celui de droite
la jointure inner n’inclut que les pays communs aux deux ensembles de données de gauche et de droite
Par défaut, merge utilisera une jointure inner.
Ici, nous passerons how='left' pour conserver tous les pays de
realwage_f, mais écarter les pays de worlddata qui n’ont pas
d’entrée de données correspondante dans realwage_f.
Ceci est illustré par la zone ombrée en rouge dans le diagramme suivant
Nous devrons également spécifier où se trouve le nom du pays dans chaque
dataframe, qui sera la key utilisée pour fusionner les
dataframes (« on »).
Notre dataframe « left » (realwage_f.transpose()) contient les pays dans
l’index, donc nous définissons left_index=True.
Notre dataframe « right » (worlddata) contient les pays dans la
colonne « Country », donc nous définissons right_on='Country'
merged = pd.merge(realwage_f.transpose(), worlddata,
how='left', left_index=True, right_on='Country')
merged.head()
| 2006-01-01 00:00:00 | 2007-01-01 00:00:00 | 2008-01-01 00:00:00 | ... | 2016-01-01 00:00:00 | Country | Continent | |
|---|---|---|---|---|---|---|---|
| 17.00 | 12.06 | 12.46 | 12.24 | ... | 12.98 | Australia | Australia |
| 23.00 | 9.70 | 9.82 | 9.87 | ... | 9.76 | Belgium | Europe |
| 32.00 | 0.87 | 0.92 | 0.96 | ... | 1.24 | Brazil | South America |
| 100.00 | 6.89 | 6.96 | 7.24 | ... | 8.48 | Canada | North America |
| 38.00 | 1.42 | 1.45 | 1.44 | ... | 1.91 | Chile | South America |
5 rows × 13 columns
Les pays qui apparaissaient dans realwage_f mais pas dans worlddata auront
NaN dans la colonne Continent.
Pour vérifier si cela s’est produit, nous pouvons utiliser .isnull() sur la
colonne continent et filtrer le dataframe fusionné
merged[merged['Continent'].isnull()]
| 2006-01-01 00:00:00 | 2007-01-01 00:00:00 | 2008-01-01 00:00:00 | ... | 2016-01-01 00:00:00 | Country | Continent | |
|---|---|---|---|---|---|---|---|
| NaN | 3.42 | 3.74 | 3.87 | ... | 5.28 | Korea | NaN |
| NaN | 0.23 | 0.45 | 0.39 | ... | 0.55 | Russian Federation | NaN |
| NaN | 1.50 | 1.64 | 1.71 | ... | 2.08 | Slovak Republic | NaN |
3 rows × 13 columns
Nous avons trois valeurs manquantes !
Une option pour traiter les valeurs NaN consiste à créer un dictionnaire contenant ces pays et leurs continents respectifs.
.map() fera correspondre les pays de merged['Country'] avec leur
continent à partir du dictionnaire.
Remarquez comment les pays qui ne figurent pas dans notre dictionnaire sont mappés avec NaN
missing_continents = {'Korea': 'Asia',
'Russian Federation': 'Europe',
'Slovak Republic': 'Europe'}
merged['Country'].map(missing_continents)
17.00 NaN
23.00 NaN
32.00 NaN
100.00 NaN
38.00 NaN
108.00 NaN
41.00 NaN
225.00 NaN
53.00 NaN
58.00 NaN
45.00 NaN
68.00 NaN
233.00 NaN
86.00 NaN
88.00 NaN
91.00 NaN
NaN Asia
117.00 NaN
122.00 NaN
123.00 NaN
138.00 NaN
153.00 NaN
151.00 NaN
174.00 NaN
175.00 NaN
NaN Europe
NaN Europe
198.00 NaN
200.00 NaN
227.00 NaN
241.00 NaN
240.00 NaN
Name: Country, dtype: str
Nous ne voulons pas écraser toute la série avec ce mappage.
.fillna() ne remplit que les valeurs NaN dans merged['Continent']
avec le mappage, tout en laissant les autres valeurs de la colonne inchangées
merged['Continent'] = merged['Continent'].fillna(merged['Country'].map(missing_continents))
# Vérifier si les continents ont été correctement mappés
merged[merged['Country'] == 'Korea']
| 2006-01-01 00:00:00 | 2007-01-01 00:00:00 | 2008-01-01 00:00:00 | ... | 2016-01-01 00:00:00 | Country | Continent | |
|---|---|---|---|---|---|---|---|
| NaN | 3.42 | 3.74 | 3.87 | ... | 5.28 | Korea | Asia |
1 rows × 13 columns
Nous combinerons également les Amériques en un seul continent — cela rendra notre visualisation plus agréable par la suite.
Pour ce faire, nous utiliserons .replace() et boucler sur une liste des valeurs de continent que nous voulons remplacer
replace = ['Central America', 'North America', 'South America']
merged['Continent'] = merged['Continent'].replace(to_replace=replace, value='America')
Maintenant que nous avons toutes les données que nous voulons dans un seul DataFrame, nous allons
le remodeler à nouveau sous forme de panel avec un MultiIndex.
Nous devrions également veiller à trier l’index en utilisant .sort_index() afin de
pouvoir filtrer efficacement notre dataframe par la suite.
Par défaut, les niveaux seront triés de haut en bas
merged = merged.set_index(['Continent', 'Country']).sort_index()
merged.head()
| 2006-01-01 00:00:00 | 2007-01-01 00:00:00 | 2008-01-01 00:00:00 | ... | 2014-01-01 00:00:00 | 2015-01-01 00:00:00 | 2016-01-01 00:00:00 | ||
|---|---|---|---|---|---|---|---|---|
| Continent | Country | |||||||
| America | Brazil | 0.87 | 0.92 | 0.96 | ... | 1.21 | 1.21 | 1.24 |
| Canada | 6.89 | 6.96 | 7.24 | ... | 8.22 | 8.35 | 8.48 | |
| Chile | 1.42 | 1.45 | 1.44 | ... | 1.76 | 1.81 | 1.91 | |
| Colombia | 1.01 | 1.02 | 1.01 | ... | 1.13 | 1.13 | 1.12 | |
| Costa Rica | NaN | NaN | NaN | ... | 2.41 | 2.56 | 2.63 |
5 rows × 11 columns
Lors de la fusion, nous avons perdu notre DatetimeIndex, car nous avons fusionné des colonnes qui
n’étaient pas au format datetime
merged.columns
Index([2006-01-01 00:00:00, 2007-01-01 00:00:00, 2008-01-01 00:00:00,
2009-01-01 00:00:00, 2010-01-01 00:00:00, 2011-01-01 00:00:00,
2012-01-01 00:00:00, 2013-01-01 00:00:00, 2014-01-01 00:00:00,
2015-01-01 00:00:00, 2016-01-01 00:00:00],
dtype='object')
Maintenant que nous avons défini les colonnes fusionnées comme index, nous pouvons recréer un
DatetimeIndex en utilisant .to_datetime()
merged.columns = pd.to_datetime(merged.columns)
merged.columns = merged.columns.rename('Time')
merged.columns
DatetimeIndex(['2006-01-01', '2007-01-01', '2008-01-01', '2009-01-01',
'2010-01-01', '2011-01-01', '2012-01-01', '2013-01-01',
'2014-01-01', '2015-01-01', '2016-01-01'],
dtype='datetime64[us]', name='Time', freq=None)
Le DatetimeIndex a tendance à fonctionner plus harmonieusement dans l’axe des lignes, donc nous
allons transposer merged
merged = merged.transpose()
merged.head()
| Continent | America | ... | Europe | ||||
|---|---|---|---|---|---|---|---|
| Country | Brazil | Canada | Chile | ... | Slovenia | Spain | United Kingdom |
| Time | |||||||
| 2006-01-01 | 0.87 | 6.89 | 1.42 | ... | 3.92 | 3.99 | 9.81 |
| 2007-01-01 | 0.92 | 6.96 | 1.45 | ... | 3.88 | 4.10 | 10.07 |
| 2008-01-01 | 0.96 | 7.24 | 1.44 | ... | 3.96 | 4.14 | 10.04 |
| 2009-01-01 | 1.03 | 7.67 | 1.52 | ... | 4.08 | 4.32 | 10.15 |
| 2010-01-01 | 1.08 | 7.94 | 1.56 | ... | 4.81 | 4.30 | 9.96 |
5 rows × 32 columns
18.4. Groupement et synthèse des données#
Le groupement et la synthèse des données peuvent être particulièrement utiles pour comprendre les grands ensembles de données de panel.
Un moyen simple de résumer les données consiste à appeler une méthode
d’agrégation
sur le dataframe, comme .mean() ou .max().
Par exemple, nous pouvons calculer le salaire minimum réel moyen pour chaque pays sur la période de 2006 à 2016 (par défaut, l’agrégation se fait sur les lignes)
merged.mean().head(10)
Continent Country
America Brazil 1.09
Canada 7.82
Chile 1.62
Colombia 1.07
Costa Rica 2.53
Mexico 0.53
United States 7.15
Asia Israel 5.95
Japan 6.18
Korea 4.22
dtype: float64
En utilisant cette série, nous pouvons tracer le salaire minimum réel moyen au cours de la dernière décennie pour chaque pays de notre ensemble de données
merged.mean().sort_values(ascending=False).plot(kind='bar',
title="Salaire minimum réel moyen 2006 - 2016")
# Définir les étiquettes de pays
country_labels = merged.mean().sort_values(ascending=False).index.get_level_values('Country').tolist()
plt.xticks(range(0, len(country_labels)), country_labels)
plt.xlabel('Pays')
plt.show()
Passer axis=1 à .mean() agrégera sur les colonnes (donnant
le salaire minimum moyen pour tous les pays au fil du temps)
merged.mean(axis=1).head()
Time
2006-01-01 4.69
2007-01-01 4.84
2008-01-01 4.90
2009-01-01 5.08
2010-01-01 5.11
dtype: float64
Nous pouvons tracer cette série temporelle sous forme de graphique linéaire
merged.mean(axis=1).plot()
plt.title('Salaire minimum réel moyen 2006 - 2016')
plt.ylabel('USD 2015')
plt.xlabel('Année')
plt.show()
Nous pouvons également spécifier un niveau du MultiIndex (dans l’axe des colonnes)
sur lequel agréger.
Dans le cas de groupby, nous devons utiliser .T pour transposer les colonnes en lignes car pandas a déprécié l’utilisation de axis=1 dans la méthode groupby.
merged.T.groupby(level='Continent').mean().head()
| Time | 2006-01-01 | 2007-01-01 | 2008-01-01 | ... | 2014-01-01 | 2015-01-01 | 2016-01-01 |
|---|---|---|---|---|---|---|---|
| Continent | |||||||
| America | 2.80 | 2.85 | 2.99 | ... | 3.22 | 3.26 | 3.30 |
| Asia | 4.29 | 4.44 | 4.45 | ... | 4.86 | 5.10 | 5.44 |
| Australia | 10.25 | 10.73 | 10.76 | ... | 11.25 | 11.52 | 11.73 |
| Europe | 4.80 | 4.94 | 4.99 | ... | 5.17 | 5.48 | 5.57 |
4 rows × 11 columns
Nous pouvons tracer les salaires minimums moyens de chaque continent sous forme de série temporelle
merged.T.groupby(level='Continent').mean().T.plot()
plt.title('Salaire minimum réel moyen')
plt.ylabel('USD 2015')
plt.xlabel('Année')
plt.show()
Nous exclurons l’Australie en tant que continent à des fins de traçage
merged = merged.drop('Australia', level='Continent', axis=1)
merged.T.groupby(level='Continent').mean().T.plot()
plt.title('Salaire minimum réel moyen')
plt.ylabel('USD 2015')
plt.xlabel('Année')
plt.show()
.describe() est utile pour récupérer rapidement un certain nombre de
statistiques descriptives courantes
merged.stack(future_stack=True).describe()
| Continent | America | Asia | Europe |
|---|---|---|---|
| count | 69.00 | 44.00 | 200.00 |
| mean | 3.19 | 4.70 | 5.15 |
| std | 3.02 | 1.56 | 3.82 |
| min | 0.52 | 2.22 | 0.23 |
| 25% | 1.03 | 3.37 | 2.02 |
| 50% | 1.44 | 5.48 | 3.54 |
| 75% | 6.96 | 5.95 | 9.70 |
| max | 8.48 | 6.65 | 12.39 |
Il s’agit d’une manière simplifiée d’utiliser groupby.
L’utilisation de groupby suit généralement un processus « diviser-appliquer-combiner » :
diviser : les données sont groupées en fonction d’une ou plusieurs clés
appliquer : une fonction est appelée sur chaque groupe indépendamment
combiner : les résultats des appels de fonction sont combinés dans une nouvelle structure de données
La méthode groupby réalise la première étape de ce processus, créant
un nouvel objet DataFrameGroupBy avec les données divisées en groupes.
Divisons à nouveau merged par continent, cette fois en utilisant la
fonction groupby, et nommons l’objet résultant grouped
grouped = merged.T.groupby(level='Continent')
grouped
<pandas.api.typing.DataFrameGroupBy object at 0x7ff515b872f0>
L’appel d’une méthode d’agrégation sur l’objet applique la fonction à chaque groupe, dont les résultats sont combinés dans une nouvelle structure de données.
Par exemple, nous pouvons retourner le nombre de pays de notre ensemble de données pour
chaque continent en utilisant .size().
Dans ce cas, notre nouvelle structure de données est une Series
grouped.size()
Continent
America 7
Asia 4
Europe 19
dtype: int64
En appelant .get_group() pour retourner uniquement les pays d’un seul groupe,
nous pouvons créer une estimation par noyau de la densité de la distribution des salaires
minimums réels en 2016 pour chaque continent.
grouped.groups.keys() retournera les clés de l’objet groupby
continents = grouped.groups.keys()
for continent in continents:
sns.kdeplot(grouped.get_group(continent).T.loc['2015'].unstack(), label=continent, fill=True)
plt.title('Salaires minimums réels en 2015')
plt.xlabel('Dollars américains')
plt.legend()
plt.show()
18.5. Remarques finales#
Ce cours a fourni une introduction à certaines des fonctionnalités plus avancées de pandas, notamment les multiindices, la fusion, le groupement et le traçage.
D’autres outils qui peuvent être utiles dans l’analyse des données de panel comprennent xarray, un package python qui étend pandas aux structures de données à N dimensions.
18.6. Exercices#
Exercice 18.1
Dans ces exercices, vous travaillerez avec un ensemble de données de taux d’emploi en Europe par âge et par sexe provenant d”Eurostat.
L’ensemble de données est accessible via le lien suivant :
url3 = 'https://github.com/QuantEcon/data-lectures/raw/main/lectures/employ.csv'
La lecture du fichier CSV retourne un ensemble de données de panel au format long. Utilisez .pivot_table() pour construire
un dataframe au format large avec un MultiIndex dans les colonnes.
Commencez par explorer le dataframe et les variables disponibles dans les
niveaux du MultiIndex.
Écrivez un programme qui retourne rapidement toutes les valeurs du MultiIndex.
Solution
employ = pd.read_csv(url3)
employ = employ.pivot_table(values='Value',
index=['DATE'],
columns=['UNIT','AGE', 'SEX', 'INDIC_EM', 'GEO'])
employ.index = pd.to_datetime(employ.index) # s'assurer que les dates sont au format datetime
employ.head()
| UNIT | Percentage of total population | ... | Thousand persons | ||||
|---|---|---|---|---|---|---|---|
| AGE | From 15 to 24 years | ... | From 55 to 64 years | ||||
| SEX | Females | ... | Total | ||||
| INDIC_EM | Active population | ... | Total employment (resident population concept - LFS) | ||||
| GEO | Austria | Belgium | Bulgaria | ... | Switzerland | Turkey | United Kingdom |
| DATE | |||||||
| 2007-01-01 | 56.00 | 31.60 | 26.00 | ... | NaN | 1,282.00 | 4,131.00 |
| 2008-01-01 | 56.20 | 30.80 | 26.10 | ... | NaN | 1,354.00 | 4,204.00 |
| 2009-01-01 | 56.20 | 29.90 | 24.80 | ... | NaN | 1,449.00 | 4,193.00 |
| 2010-01-01 | 54.00 | 29.80 | 26.60 | ... | 640.00 | 1,583.00 | 4,186.00 |
| 2011-01-01 | 54.80 | 29.80 | 24.80 | ... | 661.00 | 1,760.00 | 4,164.00 |
5 rows × 1440 columns
Il s’agit d’un grand ensemble de données, il est donc utile d’explorer les niveaux et les variables disponibles
employ.columns.names
FrozenList(['UNIT', 'AGE', 'SEX', 'INDIC_EM', 'GEO'])
Les variables au sein des niveaux peuvent être rapidement récupérées avec une boucle
for name in employ.columns.names:
print(name, employ.columns.get_level_values(name).unique())
UNIT Index(['Percentage of total population', 'Thousand persons'], dtype='str', name='UNIT')
AGE Index(['From 15 to 24 years', 'From 25 to 54 years', 'From 55 to 64 years'], dtype='str', name='AGE')
SEX Index(['Females', 'Males', 'Total'], dtype='str', name='SEX')
INDIC_EM Index(['Active population', 'Total employment (resident population concept - LFS)'], dtype='str', name='INDIC_EM')
GEO Index(['Austria', 'Belgium', 'Bulgaria', 'Croatia', 'Cyprus', 'Czech Republic',
'Denmark', 'Estonia', 'Euro area (17 countries)',
'Euro area (18 countries)', 'Euro area (19 countries)',
'European Union (15 countries)', 'European Union (27 countries)',
'European Union (28 countries)', 'Finland',
'Former Yugoslav Republic of Macedonia, the', 'France',
'France (metropolitan)',
'Germany (until 1990 former territory of the FRG)', 'Greece', 'Hungary',
'Iceland', 'Ireland', 'Italy', 'Latvia', 'Lithuania', 'Luxembourg',
'Malta', 'Netherlands', 'Norway', 'Poland', 'Portugal', 'Romania',
'Slovakia', 'Slovenia', 'Spain', 'Sweden', 'Switzerland', 'Turkey',
'United Kingdom'],
dtype='str', name='GEO')
Exercice 18.2
Filtrez le dataframe ci-dessus pour n’inclure que l’emploi en pourcentage de la « population active ».
Créez un diagramme en boîte groupé en utilisant seaborn des taux d’emploi en 2015
par groupe d’âge et par sexe.
Indication
GEO inclut à la fois des zones et des pays.
Solution
Pour filtrer facilement par pays, déplacez GEO au niveau supérieur et triez le
MultiIndex
employ.columns = employ.columns.swaplevel(0,-1)
employ = employ.sort_index(axis=1)
Nous devons nous débarrasser de quelques éléments dans GEO qui ne sont pas des pays.
Un moyen rapide de se débarrasser des zones de l’UE consiste à utiliser une compréhension de liste pour
trouver les valeurs de niveau dans GEO qui commencent par « Euro »
geo_list = employ.columns.get_level_values('GEO').unique().tolist()
countries = [x for x in geo_list if not x.startswith('Euro')]
employ = employ[countries]
employ.columns.get_level_values('GEO').unique()
Index(['Austria', 'Belgium', 'Bulgaria', 'Croatia', 'Cyprus', 'Czech Republic',
'Denmark', 'Estonia', 'Finland',
'Former Yugoslav Republic of Macedonia, the', 'France',
'France (metropolitan)',
'Germany (until 1990 former territory of the FRG)', 'Greece', 'Hungary',
'Iceland', 'Ireland', 'Italy', 'Latvia', 'Lithuania', 'Luxembourg',
'Malta', 'Netherlands', 'Norway', 'Poland', 'Portugal', 'Romania',
'Slovakia', 'Slovenia', 'Spain', 'Sweden', 'Switzerland', 'Turkey',
'United Kingdom'],
dtype='str', name='GEO')
Sélectionnez uniquement le pourcentage employé dans la population active à partir du dataframe
employ_f = employ.xs(('Percentage of total population', 'Active population'),
level=('UNIT', 'INDIC_EM'),
axis=1)
employ_f.head()
| GEO | Austria | ... | United Kingdom | ||||
|---|---|---|---|---|---|---|---|
| AGE | From 15 to 24 years | ... | From 55 to 64 years | ||||
| SEX | Females | Males | Total | ... | Females | Males | Total |
| DATE | |||||||
| 2007-01-01 | 56.00 | 62.90 | 59.40 | ... | 49.90 | 68.90 | 59.30 |
| 2008-01-01 | 56.20 | 62.90 | 59.50 | ... | 50.20 | 69.80 | 59.80 |
| 2009-01-01 | 56.20 | 62.90 | 59.50 | ... | 50.60 | 70.30 | 60.30 |
| 2010-01-01 | 54.00 | 62.60 | 58.30 | ... | 51.10 | 69.20 | 60.00 |
| 2011-01-01 | 54.80 | 63.60 | 59.20 | ... | 51.30 | 68.40 | 59.70 |
5 rows × 306 columns
Supprimez la valeur « Total » avant de créer le diagramme en boîte groupé
employ_f = employ_f.drop('Total', level='SEX', axis=1)
box = employ_f.loc['2015'].unstack().reset_index()
sns.boxplot(x="AGE", y=0, hue="SEX", data=box, palette=("husl"), showfliers=False)
plt.xlabel('')
plt.xticks(rotation=35)
plt.ylabel('Pourcentage de la population (%)')
plt.title('Emploi en Europe (2015)')
plt.legend(bbox_to_anchor=(1,0.5))
plt.show()