Vous êtes sur la page 1sur 14

Informatique EEIA 2021 - Dataframe

July 26, 2021

1 Le module Pandas
Le module Pandas permet de lire et traiter des blocs de données de manière pratique et facile à
interpreter. Il utilise un type de données qu’on appelle DataFrame. Un DataFrame se représente
comme un tableau. Mais en pratique, il s’agit d’un dictionnaire dont les clés sont les noms des
colonnes formant le tableau. Chaque colonne représente un type de données différent. Il est
également possible de nommer les ligne du tableau.

[1]: import pandas as pd # dans la suite, nous pouvons appeler pandas avec pd
# import matplotlib
# import numpy as np
import datetime as dt

1.1 Premier exemple : lecture d’un fichier csv


Pour lire des données depuis un fichier csv, on utilise la fonction read_csv de Pandas. Par defaut,
pandas considère que les données sont séparées par des virgules (,). De plus, on peut spécifier une
colonne qui sera l’index de la dataframe. Sinon, pandas allouera un index automatique numerique.

[3]: adresses = pd.read_csv('../media/adresses.csv',sep=';')


# adresses est une dataframe. Une dataframe vous permet de visualiser vos␣
,→données comme vous les voyez sur votre classeur Excel.

[6]: # connaitre les colonnes


adresses.columns

[6]: Index(['COL_A', 'COL_B', 'COL_ADRESSE'], dtype='object')

[5]: # decouvrir les 5 premieres lignes


adresses.head(5)

[5]: COL_A COL_B COL_ADRESSE


0 JJJJ 21200 ZI des LAUVES\n83340 LE LUC\nFRANCE
1 2095 21253 Carrefour de Fogata\n20256 CORBARA\nFRANCE
2 530 21238 1 Route DE TAVEL\nC.C. GRAND ANGLE\n30133 LES …
3 1813 7374 Av de la Lib�ration\n ARLES\nFRANCE
4 120 21273 Lotissement Jardins d'Oletta\n20232 OLETTA\nFR…

1
[7]: # decouvrir les 3 dernieres lignes
adresses.tail(3)

[7]: COL_A COL_B COL_ADRESSE


942 LCOM 7597 26 Quai Marcel Boyer\n94200 IVRY SUR SEINE\nFR…
943 0 45922 NaN
944 21 19550 Autoroute A35\nAire du Haut Koenigsbourg\n6760…

[8]: # afficher les 4 premieres colonnes


adresses[:4]

[8]: COL_A COL_B COL_ADRESSE


0 JJJJ 21200 ZI des LAUVES\n83340 LE LUC\nFRANCE
1 2095 21253 Carrefour de Fogata\n20256 CORBARA\nFRANCE
2 530 21238 1 Route DE TAVEL\nC.C. GRAND ANGLE\n30133 LES …
3 1813 7374 Av de la Lib�ration\n ARLES\nFRANCE

[9]: # afficher une colonne dont on connait le nom. ATTENTION: les majuscules et␣
,→miniscules ont une importance

adresses['COL_A'][:6]

[9]: 0 JJJJ
1 2095
2 530
3 1813
4 120
5 906
Name: COL_A, dtype: object

[17]: # afficher une liste de colonnes dont on connait le nom. ATTENTION: les␣
,→majuscules et miniscules ont une importance

adresses[['COL_A','COL_B']][:4]

[17]: COL_A COL_B


0 JJJJ 21200
1 2095 21253
2 530 21238
3 1813 7374

[18]: # connaitre le nombre de lignes et de colonnes


adresses.shape

[18]: (945, 3)

2
1.2 Deuxième exemple : “Bien” lire les dates
[6]: # Premier exemple: La date telle qu'on la souhaite est déjà une colonne en soi
data_dates = pd.read_csv('../media/dates_1.csv')
data_dates.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 3 non-null object
1 product 3 non-null object
2 price 3 non-null int64
dtypes: int64(1), object(2)
memory usage: 200.0+ bytes

[7]: data_dates

[7]: date product price


0 1/1/2019 A 10
1 1/2/2020 B 20
2 1/3/1998 C 30

Commentaires: la date est lue comme un object par pandas mais nous on a le type date. Cor-
rigeons cela en passant les noms des colonnes qui sont des dates dans le paramètre parse_dates
:

[8]: data_dates = pd.read_csv('../media/dates_1.csv', parse_dates=['date'])


data_dates.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 3 entries, 0 to 2
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 date 3 non-null datetime64[ns]
1 product 3 non-null object
2 price 3 non-null int64
dtypes: datetime64[ns](1), int64(1), object(1)
memory usage: 200.0+ bytes

[9]: data_dates

[9]: date product price


0 2019-01-01 A 10
1 2020-01-02 B 20
2 1998-01-03 C 30

3
[10]: # mettre la date en index
data_dates = pd.read_csv('../media/dates_1.csv', index_col=0,␣
,→parse_dates=['date'], dayfirst=True)

data_dates

[10]: product price


date
2019-01-01 A 10
2020-02-01 B 20
1998-03-01 C 30

[11]: data_dates.info()

<class 'pandas.core.frame.DataFrame'>
DatetimeIndex: 3 entries, 2019-01-01 to 1998-03-01
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 product 3 non-null object
1 price 3 non-null int64
dtypes: int64(1), object(1)
memory usage: 72.0+ bytes

1.3 Exemple avancé : un date_parser personnalisé


La date est divisée en 2 colonnes et il faudra les reunir pour former une seule colonne dans notre
dataframe.

[14]: def mydateparser(date_x, hour_x):


if len(hour_x) == 5:
hour_x = '0'+ hour_x
return dt.datetime(int(date_x[:4]),
int(date_x[4:6]),
int(date_x[6:8]),
int(hour_x[0:2]),
int(hour_x[2:4]),
int(hour_x[4:6]))

# #fonction pandas plus rapide


# def mydateparser(date_x, hour_x):
# return pd.to_datetime
data_dates2 = pd.read_csv('../media/dates_2.csv', index_col=0,␣
,→parse_dates={'Time':['DateAsInt', 'HeureAsInt']}, date_parser=mydateparser)

data_dates2

[14]: product price


Time
2019-01-29 08:01:00 A 10

4
2019-01-30 10:21:23 B 20
2019-02-01 09:35:01 C 30
2019-02-02 18:20:03 A 10
2019-02-05 17:30:01 B 20
2019-02-06 12:31:03 C 30

1.4 Slicing : accès aux éléments de la data frame


[2]: # si problème d'encoding, essayer de virer le <encoding="latin1">
data_unicef = pd.read_csv('../media/unicef_enfants.csv', sep=';',␣
,→encoding="latin1", dtype={"Annee": "Int64"})

data_unicef.head()

[2]: Pays Avant_15 Avant_18 Annee


0 Afghanistan 4 28 2017
1 Albania 1 12 2018
2 Algeria 0 4 2019
3 Andorra � � <NA>
4 Angola 8 30 2016

[3]: # Virer les pays sans données


data_unicef = data_unicef.dropna()
data_unicef.head()

[3]: Pays Avant_15 Avant_18 Annee


0 Afghanistan 4 28 2017
1 Albania 1 12 2018
2 Algeria 0 4 2019
4 Angola 8 30 2016
8 Armenia 0 5 2016

[4]: # Récurrence de valeurs dans une colonne


annee_counts = data_unicef['Annee'].value_counts()
print(annee_counts)

2018 29
2019 19
2016 13
2012 12
2015 11
2014 10
2017 10
2013 6
2010 5
2020 5
2011 4
2006 3
2007 3

5
2005 1
Name: Annee, dtype: Int64

[5]: #premier tracé...


annee_counts.plot(kind='bar')

[5]: <matplotlib.axes._subplots.AxesSubplot at 0x7f9644745fd0>

[6]: # Nous aimerons connaitre quelles sont les données récoltées en 2018
data_2018 = data_unicef[data_unicef['Annee'] == 2018]
data_2018[:5] # Une autre manière d'afficher les 5 premieres lignes

[6]: Pays Avant_15 Avant_18 Annee


1 Albania 1 12 2018
19 Benin 9 31 2018
32 Cameroon 11 30 2018
42 Costa Rica 2 21 2018
48 Democratic People's Republic of Korea 0 0 2018

Le code suivant ne marche pas. Pourquoi ? Pouvez-vous expliquer l’erreur ?

[8]: # Ajout d'une colonne : difference entre le pourcentage des mariées avant 15␣
,→ans et les mariées avant 18ans

data_unicef['diff'] = data_unicef['Avant_18'] - data_unicef['Avant_15']


# data_unicef.head(6)

6
1.5 Un peu de nettoyage
[11]: sub_cols = ['Avant_15', 'Avant_18']
# Affecter le type float aux colonnes
for sc in sub_cols:
data_unicef[sc] = pd.to_numeric(data_unicef[sc], errors='coerce')

data_unicef.head()

[11]: Pays Avant_15 Avant_18 Annee


0 Afghanistan 4.0 28 2017
1 Albania 1.0 12 2018
2 Algeria 0.0 4 2019
4 Angola 8.0 30 2016
8 Armenia 0.0 5 2016

1.6 A FAIRE
1. Afficher les données récoltées en 2018 et où plus de 5% du nombre total des filles de l’année
ont été mariées avant 18 ans
2. Ajouter une colonne (booléen) qui dit si la ligne contient des données dans la colonne ‘Mar-
ried_15’

1.7 Aggrégation
Dans cette partie, il s’agira d’aggréger les données en groupe et y effectuer des actions.
pandas.groupby : regroupe une dataframe en se basant sur l’unicité des éléments d’une colonne
ou d’une série de colonnes.
Paramètres : + by : la colonne ou le groupe de colonnes utilisé pour le regroupement + axis : 0 si
regroupement sur les valeurs en ligne ou 1 pour les colonnes. Par défaut axis=0 + sort : booleen.
Trie les valeurs des colonnes de regroupement. Par défaut sort=True. + dropna : booleen. Si
“True”, et si les valeurs clés des groupes contient des valeurs NA, les clés NA seront supprimées.
Par défaut dropna=True.
Un intérêt des groupBy est de pouvoir appliquer des fonctions types ou implementées. Des fonctions
types connues sont mean, sum, std, min, max, count, etc.
Avant d’appliquer un groupby + fonction, s’assurer que toutes les fonctions peuvent être appliquées
aux colonnes de la dataframe. Il s’agira de travailler avec une dataframe plus petite au besoin.
Après l’opération groupby, les colonnes de regroupement deviennents les index.

[29]: # si problème d'encoding, essayer de virer le <encoding="latin1">


data_dep = pd.read_csv('../media/departements.csv', sep=';', encoding="latin1")
# Et si on voulait connaitre les moyennes de taux par departement. Et␣
,→L'ecart-type?

# Etape 1: Puis-je appliquer les fonctions classiques moyenne et ecart-type à␣


,→toutes les colonnes autre que "departements"?

7
data_dep_small =␣
,→data_dep[["Departement","2002_2013_Percentage","1992_2002_Percentage",␣

,→"1979_1992_Percentage"]]

data_dep_agg1 = data_dep_small.groupby(["Departement"]).mean()
data_dep_agg1

[29]: 2002_2013_Percentage 1992_2002_Percentage 1979_1992_Percentage


Departement
ALIBORI 4.611667 3.945000 3.665000
ATACORA 3.017778 3.246667 2.337778
ATLANTIQUE 3.827500 2.948750 2.463750
BORGOU 4.478750 4.432500 3.753750
COLLINES 2.560000 4.703333 3.356667
COUFFO 3.101667 2.755000 2.701667
DONGA 4.075000 3.652500 1.580000
LITTORAL 0.180000 2.170000 3.760000
MONO 2.911667 2.448333 2.226667
OUEME 3.900000 2.407778 2.266667
PLATEAU 3.862000 2.796000 2.812000
ZOU 3.150000 2.064444 2.090000

[30]: data_dep_agg2 = data_dep_small.groupby(["Departement"]).agg(["mean", "std"])


data_dep_agg2

[30]: 2002_2013_Percentage 1992_2002_Percentage \


mean std mean std
Departement
ALIBORI 4.611667 0.706468 3.945000 0.918776
ATACORA 3.017778 0.503855 3.246667 1.417119
ATLANTIQUE 3.827500 2.022507 2.948750 2.611734
BORGOU 4.478750 1.349269 4.432500 0.402306
COLLINES 2.560000 0.634949 4.703333 1.117795
COUFFO 3.101667 0.397513 2.755000 0.885229
DONGA 4.075000 1.086539 3.652500 0.554820
LITTORAL 0.180000 NaN 2.170000 NaN
MONO 2.911667 0.217570 2.448333 0.869308
OUEME 3.900000 1.340438 2.407778 1.493199
PLATEAU 3.862000 0.474521 2.796000 1.891238
ZOU 3.150000 0.834206 2.064444 1.071262

1979_1992_Percentage
mean std
Departement
ALIBORI 3.665000 0.717656
ATACORA 2.337778 0.689489
ATLANTIQUE 2.463750 1.336530

8
BORGOU 3.753750 0.827266
COLLINES 3.356667 0.513524
COUFFO 2.701667 0.563717
DONGA 1.580000 1.471847
LITTORAL 3.760000 NaN
MONO 2.226667 0.704008
OUEME 2.266667 0.775919
PLATEAU 2.812000 0.858877
ZOU 2.090000 0.611453

Remarque :
data_dep_agg2 est un multilevel columns dataframe. Donc attention lors de l’utilisation
des colonnes. Par exemple, s’il faut appeler la premiere colonne il faudra utiliser
data_dep_agg2["2002_2013_Percentage"]["mean"]. Il est aussi possible de renommer les
colonnes et de revenir à un seul niveau de colonnes.

[31]: print(data_dep_agg2["2002_2013_Percentage"]["mean"])

Departement
ALIBORI 4.611667
ATACORA 3.017778
ATLANTIQUE 3.827500
BORGOU 4.478750
COLLINES 2.560000
COUFFO 3.101667
DONGA 4.075000
LITTORAL 0.180000
MONO 2.911667
OUEME 3.900000
PLATEAU 3.862000
ZOU 3.150000
Name: mean, dtype: float64

[32]: print(data_dep_agg2.columns)
cols = ['_'.join(v for v in t ) for t in data_dep_agg2.columns]
print(cols)

MultiIndex([('2002_2013_Percentage', 'mean'),
('2002_2013_Percentage', 'std'),
('1992_2002_Percentage', 'mean'),
('1992_2002_Percentage', 'std'),
('1979_1992_Percentage', 'mean'),
('1979_1992_Percentage', 'std')],
)
['2002_2013_Percentage_mean', '2002_2013_Percentage_std',
'1992_2002_Percentage_mean', '1992_2002_Percentage_std',
'1979_1992_Percentage_mean', '1979_1992_Percentage_std']

9
[33]: # enlver le niveau 0 des colonnes
data_dep_agg2.columns = data_dep_agg2.columns.droplevel(level=0)
# affichage
print("Premier affichage après reduction du niveau de colonnes...")
data_dep_agg2

Premier affichage après reduction du niveau de colonnes…

[33]: mean std mean std mean std


Departement
ALIBORI 4.611667 0.706468 3.945000 0.918776 3.665000 0.717656
ATACORA 3.017778 0.503855 3.246667 1.417119 2.337778 0.689489
ATLANTIQUE 3.827500 2.022507 2.948750 2.611734 2.463750 1.336530
BORGOU 4.478750 1.349269 4.432500 0.402306 3.753750 0.827266
COLLINES 2.560000 0.634949 4.703333 1.117795 3.356667 0.513524
COUFFO 3.101667 0.397513 2.755000 0.885229 2.701667 0.563717
DONGA 4.075000 1.086539 3.652500 0.554820 1.580000 1.471847
LITTORAL 0.180000 NaN 2.170000 NaN 3.760000 NaN
MONO 2.911667 0.217570 2.448333 0.869308 2.226667 0.704008
OUEME 3.900000 1.340438 2.407778 1.493199 2.266667 0.775919
PLATEAU 3.862000 0.474521 2.796000 1.891238 2.812000 0.858877
ZOU 3.150000 0.834206 2.064444 1.071262 2.090000 0.611453

[34]: # changer le nom


data_dep_agg2.columns = cols
#drop column level 1
# affichage
print("Second affichage après changement du nom des colonnes...")
data_dep_agg2

Second affichage après changement du nom des colonnes…

[34]: 2002_2013_Percentage_mean 2002_2013_Percentage_std \


Departement
ALIBORI 4.611667 0.706468
ATACORA 3.017778 0.503855
ATLANTIQUE 3.827500 2.022507
BORGOU 4.478750 1.349269
COLLINES 2.560000 0.634949
COUFFO 3.101667 0.397513
DONGA 4.075000 1.086539
LITTORAL 0.180000 NaN
MONO 2.911667 0.217570
OUEME 3.900000 1.340438
PLATEAU 3.862000 0.474521
ZOU 3.150000 0.834206

1992_2002_Percentage_mean 1992_2002_Percentage_std \

10
Departement
ALIBORI 3.945000 0.918776
ATACORA 3.246667 1.417119
ATLANTIQUE 2.948750 2.611734
BORGOU 4.432500 0.402306
COLLINES 4.703333 1.117795
COUFFO 2.755000 0.885229
DONGA 3.652500 0.554820
LITTORAL 2.170000 NaN
MONO 2.448333 0.869308
OUEME 2.407778 1.493199
PLATEAU 2.796000 1.891238
ZOU 2.064444 1.071262

1979_1992_Percentage_mean 1979_1992_Percentage_std
Departement
ALIBORI 3.665000 0.717656
ATACORA 2.337778 0.689489
ATLANTIQUE 2.463750 1.336530
BORGOU 3.753750 0.827266
COLLINES 3.356667 0.513524
COUFFO 2.701667 0.563717
DONGA 1.580000 1.471847
LITTORAL 3.760000 NaN
MONO 2.226667 0.704008
OUEME 2.266667 0.775919
PLATEAU 2.812000 0.858877
ZOU 2.090000 0.611453

Lambda
Si l’operation qu’on souhaite faire sur une colonne de la dataframe n’est pas une simple operation
(somme ou multiplication), on peut utiliser le mot clé “lambda” qui permet de specifier sa propre
fonction

[35]: df_l = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3', 'K4', 'K5'],


'A': [4, 0, 3, 5, 0, 2],
'B': [8, 6, 7, 6, 3, 5]})
df_l

[35]: key A B
0 K0 4 8
1 K1 0 6
2 K2 3 7
3 K3 5 6
4 K4 0 3
5 K5 2 5

Supposons qu’on veuille créer une colonne ‘C’ qui est égal à:

11
B/A si A different de 0
B/2 sinon

[36]: # Une manière de faire


df_l['C'] = df_l[['A', 'B']].apply(lambda x: x[1] / 2 if x[0] == 0 else x[1] /␣
,→x[0], axis=1)

# Une deuxième manière de faire. Pour changer, nous appelerons 'C' par 'C1'
# Nous allons commencer par créer une fonction qui va prendre en entrée 2␣
,→paramètres

def myLambdaFunc(x):
if x[0]==0:
return x[1]/2
else:
return x[1]/x[0]
df_l['C1'] = df_l[['A', 'B']].apply(myLambdaFunc, axis=1)
df_l

[36]: key A B C C1
0 K0 4 8 2.000000 2.000000
1 K1 0 6 3.000000 3.000000
2 K2 3 7 2.333333 2.333333
3 K3 5 6 1.200000 1.200000
4 K4 0 3 1.500000 1.500000
5 K5 2 5 2.500000 2.500000

Quand on utilise lambda avec plus d’une colonne, il faut absolument avoir le mot clé “axis=1” pour
spécifier qu’on fait les calculs sur les colonnes.
lambda peut être aussi utilisé si la methode pour de calcul lors d’une agregation n’est pas usuelle
(mean, stp, max, min)

1.8 Jointure
Join
DataFrame.join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False) :
permet de joindre 2 dataFrame soit en utilisant l’index ou soit en précisant une colonne.
Paramètres :
• other : Correspond au second dataFrame
• on : Permet de préciser sur quelles colonnes il faut faire la jointure. Par défaut, on=None et
alors la jointure se fera sur les index.
• how : Permet de préciser comment gérer la jointure.
– Si ‘left’ (valeur par défaut), la jointure se fera en utilisant l’index ou la colonne spécifiée
de la dataframe appelant
– Si ‘right’, la jointure se fera en utilisant l’index ou la colonne spécifiée de la dataframe
other

12
– Si ‘outer’, le résulat final sera une union des valeurs de la colonne (ou index) spécifiée
pour la jointure.
– Si ‘inner’, le résulat final sera une intersection des valeurs de la colonne (ou index)
spécifiée en préservant l’ordre des valeurs de la dataframe appelante.
• lsuffix : Permet de rajouter le suffixe spécifié si les 2 dataframes possèdent des colonnes
de mêmes nom autre que les colonnes sur lesquelles se font la jointure. Le suffixe précisé par
lsuffix sera uniquement appliqué sur la dataframe appelante. Par défaut, aucun suffixe ne
sera appliqué sur la dataframe de gauche.
• rsuffix : Permet de rajouter le suffixe spécifié si les 2 dataframes possèdent des colonnes
de mêmes nom autre que les colonnes sur lesquelles se font la jointure. Le suffixe précisé par
rsuffix sera uniquement appliqué sur la dataframe other. Par défaut, aucun suffixe ne sera
appliqué sur la dataframe de droite.
Merge
DataFrame.merge(right, how='inner', on=None, left_on=None, right_on=None,
left_index=False, right_index=False, sort=False, suffixes=('_x', '_y'),
copy=True, indicator=False, validate=None) : Permet de fusionner deux dataframes,
dans le même style de la jointure. On retrouve des paramètres similaires à ceux de la jointure

[37]: # Jointure, reset_index, merge


df1 = pd.DataFrame({'key': ['K0', 'K1', 'K2', 'K3', 'K4', 'K5'],
'A': ['A0', 'A1', 'A2', 'A3', 'A4', 'A5']})
df1

[37]: key A
0 K0 A0
1 K1 A1
2 K2 A2
3 K3 A3
4 K4 A4
5 K5 A5

[38]: df2 = pd.DataFrame({'key': ['K0', 'K1', 'K2'],


'B': ['B0', 'B1', 'B2']})
df2

[38]: key B
0 K0 B0
1 K1 B1
2 K2 B2

[40]: # join partie 1. Puis-je écrire ?


df = df1.join(df2)
# Commentaire: Non; Soit mettre Key en index soit mettre des suffix

[41]: # join partie 2


# df2

13
df3 = df1.join(df2, lsuffix='_appelant', rsuffix='_appele')
df3

[41]: key_appelant A key_appele B


0 K0 A0 K0 B0
1 K1 A1 K1 B1
2 K2 A2 K2 B2
3 K3 A3 NaN NaN
4 K4 A4 NaN NaN
5 K5 A5 NaN NaN

[42]: # merge. Pouvez-vous expliquer ce resultat?


df_merge = df1.merge(df2, on='key')
df_merge

[42]: key A B
0 K0 A0 B0
1 K1 A1 B1
2 K2 A2 B2

[43]: # merge partie 2


df_merge2 = df1.merge(df2, on='key', how='left')
df_merge2

[43]: key A B
0 K0 A0 B0
1 K1 A1 B1
2 K2 A2 B2
3 K3 A3 NaN
4 K4 A4 NaN
5 K5 A5 NaN

Il existe beaucoup de possibilités avec la librairie Pandas. N’hésitez pas à faire des recherches si
vous voulez utiliser une fonction qui n’a pas encore été abordée dans ce chapitre.

1.9 Exercice:
Le fichier bibliotheques contient les statistiques d’entrée dans les bibliothèques du réseau Fondation
Vallet - CAEB - BE.
1. Importer ces données dans une dataframe
2. Combien de bibliothèques dispose ce réseau ? Listez les.
3. Trouver la bibliothèque qui a le plus fort taux de participation.
4. Tracer l’histogramme du nombre de participations pour les étudiants par ville.
5. Quelle bibliothèque a acueillli le plus d’élèves du primaire en May 2019 ?

14

Vous aimerez peut-être aussi