Dataframes Pandas

Cet article fait partie d’une série d’articles sur la syntaxe de base Python.

Un dataframe pandas est une structure de données mutable que l’on peut considérer comme un dictionnaire de séries pandas. Les colonnes sont accessibles en utilisant le nom de la colonne en tant que clé du dictionnaire. La valeur extraite avec la clé est une série qui peut avoir un index personnalisé ou non. Les valeurs d’un dataframe peuvent être de type différent toutefois il est possible d’avoir un type par colonne.

Sommaire

Initialisation
A partir d’une liste
A partir d’une liste de listes
A partir d’un dictionnaire de listes
A partir d’une liste de dictionnaires
A partir d’un dictionnaire de séries pandas
A partir d’objets de types différents
Indiquer explicitement des index (argument index)
Indiquer explicitement les colonnes (argument columns)
Indiquer explicitement le type des valeurs (argument dtype)
Initialiser sans effectuer de copies (argument copy)

Lire le contenu d’un dataframe
Accéder à une valeur à partir de l’index
  loc[] et iloc[]
Sous-ensembles
at[] et iat[]

Multi-index
Accès aux éléments en utilisant un multi-index
Utilisation de multi-index pour les lignes
Création de multi-index à partir d’un tableau
Création de multi-index à partir de tuples
Création de multi-index en effectuant un produit cartésien
Création de multi-index à partir d’un dataframe
Création de multi-index avec les arguments levels et codes
Nommer les niveaux du multi-index

Modifier un dataframe
Modifier les colonnes ou lignes
  Renommer des lignes ou colonnes
  Ajouter une colonne
  Changer l’ordre des colonnes
  Supprimer des colonnes
  Supprimer des lignes
Modification de valeurs d’un dataframe
  Modifier les valeurs d’un dataframe par condition
  Modifier un dataframe en appliquant des fonctions

Agrégation
Avec <dataframe>.groupby()
Fonction d’agrégation aggregate() ou agg()

Fonctions mathématiques
Opérations sur les dataframes
Opérations entre un dataframe et un autre type d’objet
  Argument fill_value
Opérations avec des index ou des colonnes non communs

Jointures

Concaténation

Le but de cet article est de présenter les fonctionnalités de base des dataframes pandas.

On peut importer pandas de cette façon pour utiliser les objets dans la bibliothèque:

import pandas as pd

Initialisation

On peut créer un dataframe à partir d’une liste, d’un dictionnaire de listes, d’une liste de dictionnaires ou d’un dictionnaire de séries pandas.

A partir d’une liste

A partir d’une liste, le dataframe ne contient qu’une seule colonne correspondant aux éléments de la liste. Les lignes possèdent un index par défaut non personnalisé, par exemple:

>>> data = [1, 3, 5, 7, 9, 11]
>>> df = pd.DataFrame(data)
>>> print(df)
    0
0   1
1   3
2   5
3   7
4   9
5  11

Avec cet exemple, le dataframe ne contient qu’une seule colonne '0'. Les index des lignes sont les index par défaut. Sans précision, le type est déterminé en fonction du type des valeurs de la liste:

>>> df.dtypes
0    int64
dtype: object

A partir d’une liste de listes

A partir d’une liste de listes, le dataframe contiendra les listes disposées en colonne. Les lignes possèdent un index par défaut non personnalisé.

Par exemple:

>>> data = [[1, 3, 5], ['1', '3', '5'], [1.0, 3.0, 5.0]]
>>> df = pd.DataFrame(data)
>>> print(df)
     0    1    2
0    1    3    5
1    1    3    5
2  1.0  3.0  5.0

Dans cet exemple, les types des objets sont différents pour une même colonne donnée. Ainsi si on regarde le type des objets, ils seront de type object (qui correspond au type le plus large entre les int64 et les float64):

>>> df.dtypes
     0    object
     1    object
     2    object
     dtype: object

A partir d’un dictionnaire de listes

A partir d’un dictionnaire de listes:

  • Les clés du dictionnaire correspondent aux colonnes du dataframe et
  • Les listes seront les colonnes du dataframe.

Par exemple:

>>> data = {'a': [1, 3, 5, 7, 9, 11], 'b': ['1', '3', '5', '7', '9', '11'], 'c': [1.0, 3.0, 5.0, 7.0, 9.0, 11.0]}
>>> df = pd.DataFrame(data)
>>> print(df)
    a   b     c
0   1   1   1.0
1   3   3   3.0
2   5   5   5.0
3   7   7   7.0
4   9   9   9.0
5  11  11  11.0

Le type des colonnes est déduit du type des éléments dans les listes:

>>> df.dtypes
a      int64
b     object
c    float64
dtype: object

A partir d’une liste de dictionnaires

Dans le cas d’une liste de dictionnaires:

  • Les clés des éléments dans les dictionnaires correspondent aux colonnes du dataframe,
  • Les valeurs des éléments dans les dictionnaires correspondent aux lignes du dataframe.

Par exemple:

>>> data = [{'a': 1, 'b': '1', 'c': 1.0}, {'a': 3, 'b': '3', 'c': 3.0}, {'a': 5, 'b': '5', 'c': 5.0},
       {'a': 7, 'b': '7', 'c': 7.0}, {'a': 9, 'b': '9', 'c': 9.0}]
>>> df = pd.DataFrame(data)
>>> print(df)
   a  b    c
0  1  1  1.0
1  3  3  3.0
2  5  5  5.0
3  7  7  7.0
4  9  9  9.0

Le type des colonnes est déduit du type des éléments dans les dictionnaires:

>>> df.dtypes
a      int64
b     object
c    float64
dtype: object

Dans le cas où tous les dictionnaires ne contiennent pas toutes les clés, les valeurs manquantes sont remplacées par NaN.

Par exemple, si on considère la liste de dictionnaires suivantes:

>>> data = [{'a': 1, 'b': '1', 'c': 1.0}, {'a': 3}, {'b': '5'}, {'c': 7.0}]
>>> df = pd.DataFrame(data)
     a    b    c
0  1.0    1  1.0
1  3.0  NaN  NaN
2  NaN    5  NaN
3  NaN  NaN  7.0

A partir d’un dictionnaire de séries pandas

Si on crée un dataframe à partir d’un dictionnaire de séries, chaque série correspond à une colonne du dataframe.

Par exemple:

>>> data1 = pd.Series([1, 3, 5, 7, 9])
>>> data2 = pd.Series(['1', '3', '5', '7', '9'])
>>> data3 = pd.Series([1.0, 3.0, 5.0, 7.0, 9.0])
>>> df = pd.DataFrame({'a': data1, 'b': data2, 'c': data3 })
>>> print(df)
   a  b    c
0  1  1  1.0
1  3  3  3.0
2  5  5  5.0
3  7  7  7.0
4  9  9  9.0

Le type des colonnes est déduit du type des éléments dans les séries:

>>> df.dtypes
a      int64
b     object
c    float64
dtype: object

A partir d’objets de types différents

On peut effectuer l’initialisation du dataframe avec des objets de type différent. Par exemple, si on considère une liste et une série:

>>> data = {'a': [1, 3, 5], 'b': pd.Series(['1', '3', '5'])}
>>> df = pd.DataFrame(data)
   a  b
0  1  1
1  3  3
2  5  5

Indiquer explicitement des index (argument index)

On utilise le terme “index” pour désigner les labels utilisés pour identifier les lignes en opposition aux labels utilisés pour les colonnes. Par défaut, les index des éléments sont des entiers à partir de 0. Avec l’argument index, on peut explicitement préciser des index, par exemple:

>>> data = {'a': [1, 3, 5], 'b': ['1', '3', '5'], 'c': [1.0, 3.0, 5.0]}
>>> i = ['one', 'two', 'three']
>>> df = pd.DataFrame(data, i)
       a  b    c
one    1  1  1.0
two    3  3  3.0
three  5  5  5.0

On peut aussi indiquer l’index de cette façon à l’initialisation:

>>> df = pd.DataFrame(data, index=i)

Si la taille de l’index ne correspond pas au nombre de lignes du dataframe, une erreur survient:

>>> data = {'a': [1, 3, 5], 'b': ['1', '3', '5'], 'c': [1.0, 3.0, 5.0]}
>>> i = ['one', 'two', 'three', 'four']
ValueError: Length mismatch: Expected axis has 3 elements, new values have 4 elements

On peut affecter un index particulier après initialisation avec la propriété <dataframe>.index. Il faut que la taille du tableau de l’index soit la même que le nombre de lignes du dataframe.

Par exemple:

>>> data = {'a': [1, 3, 5], 'b': ['1', '3', '5'], 'c': [1.0, 3.0, 5.0]}
>>> df = pd.DataFrame(data)
>>> print(df)
   a  b    c
0  1  1  1.0
1  3  3  3.0
2  5  5  5.0

>>> df.index = i
>>> print(df)
       a  b    c
one    1  1  1.0
two    3  3  3.0
three  5  5  5.0

On peut nommer l’index d’un dataframe avec:

<dataframe>.index.name = <chaine de caractères>

Par exemple:

>>> print(df)
   a  b    c
0  1  1  1.0
1  3  3  3.0
2  5  5  5.0
>>> df.index.name = 'Index name'
>>> print(df)
            a  b    c
Index name           
0           1  1  1.0
1           3  3  3.0
2           5  5  5.0

Indiquer explicitement les colonnes (argument columns)

On peut indiquer les colonnes du dataframe avec l’argument columns. Les noms de colonnes doivent être indiqués sous la forme d’une liste de même taille que le nombre de colonnes:

>>> data = [[1, 3, 5], ['1', '3', '5'], [1.0, 3.0, 5.0]]
>>> c = ['a', 'b', 'c']
>>> df = pd.DataFrame(data, columns=c)
>>> print(df)
     a    b    c
0    1    3    5
1    1    3    5
2  1.0  3.0  5.0

Si le tableau fourni pour le nom des colonnes n’est pas de la même taille que le nombre de colonnes du dataframe, une erreur se produit:

>>> data = [[1, 3, 5], ['1', '3', '5'], [1.0, 3.0, 5.0]]
>>> c = ['a', 'b', 'c', 'd']
>>> df = pd.DataFrame(data, columns=c)
ValueError: 4 columns passed, passed data had 3 columns

On peut affecter la propriété <dataframe>.columns après initialisation pour préciser les noms de colonnes:

>>> data = [[1, 3, 5], ['1', '3', '5'], [1.0, 3.0, 5.0]]
>>> df = pd.DataFrame(data)
>>> print(df)
     0    1    2
0    1    3    5
1    1    3    5
2  1.0  3.0  5.0
>>> c = ['a', 'b', 'c']
>>> df.columns = c
     a    b    c
0    1    3    5
1    1    3    5
2  1.0  3.0  5.0

On peut nommer les colonnes d’un dataframe avec:

<dataframe>.columns.name = <chaîne de caractères>

Par exemple:

>>> print(df)
   a  b    c
0  1  1  1.0
1  3  3  3.0
2  5  5  5.0
>>> df.columns.name = 'Column name'
>>> print(df)
Columns name  a  b    c
0             1  1  1.0
1             3  3  3.0
2             5  5  5.0

Indiquer explicitement le type des valeurs (argument dtype)

Pandas reconnait les types numpy donc on peut utiliser la même syntaxe que pour numpy pour indiquer explicitement un type à l’initialisation, par exemple:

>>> data = {'a': [1, 3, 5], 'b': ['1', '3', '5'], 'c': [1.0, 3.0, 5.0]}
>>> df = pd.DataFrame(data, dtype='f8')     # f8 pour flottant sur 8 octets/64 bits
>>> print(df)
     a    b    c
0  1.0  1.0  1.0
1  3.0  3.0  3.0
2  5.0  5.0  5.0
>>> df.dtypes
a    float64
b    float64
c    float64
dtype: object

Tous les éléments sont convertis en flottants si c’est possible.

Initialiser sans effectuer de copies (argument copy)

Par défaut, quand un dataframe pandas est initialisé à partir d’une série pandas, une copie des éléments est effectuée. Il est possible d’effectuer une initialisation du dataframe en utilisant des références vers les objets de la structure d’origine avec l’argument copy (par défaut, la valeur est copy est True):

>>> data = {'a': pd.Series([1, 2, 3, 5])}
>>> df = pd.DataFrame(data, copy=False)
>>> print(df)
   a
0  1
1  2
2  3
3  5

>>> print(data)
{'a': 0    1
1    2
2    3
3    5
dtype: int64}

>>> df['a'][1] = 1000
>>> print(data)
{'a': 0       1
1    1000
2       3
3       5
dtype: int64}

L’initialisation du dataframe étant faite avec des références, si on modifie une valeur dans le dataframe alors les éléments dans la structure d’origine sont aussi modifiés.

Lire le contenu d’un dataframe

Les attributs et fonctions suivantes permettent d’obtenir des informations sur le dataframe:

  • <dataframe>.dtypes: renvoie une série contenant les types des éléments. L’index de la série correspond aux noms de colonnes du dataframe.
  • <dataframe>.head(): renvoie un dataframe contenant les 5 premières lignes du dataframe. <dataframe>.head(8) renvoie les 8 premières lignes.
  • <dataframe>.tail(): renvoie un dataframe contenant les 5 dernières lignes du dataframe. <dataframe>.tail(8) renvoie les 8 dernières lignes.
  • <dataframe>.info(): retourne des informations concernant le dataframe (noms, types des colonnes; nombre des valeurs non-nulles; mémoire occupée par l’instance du dataframe), par exemple:
    >>> data = {'a': [1, 3, 5], 'b': ['1', '3', '5'], 'c': [1.0, 3.0, 5.0]}
    >>> df = pd.DataFrame(data)
    >>> df.info()
    <class 'pandas.core.frame.DataFrame'>
    RangeIndex: 3 entries, 0 to 2
    Data columns (total 3 columns):
     #   Column  Non-Null Count  Dtype  
    ---  ------  --------------  -----  
     0   a       3 non-null      int64  
     1   b       3 non-null      object 
     2   c       3 non-null      float64
    dtypes: float64(1), int64(1), object(1)
    memory usage: 200.0+ bytes
    
  • <dataframe>.columns: renvoie un objet itérable permettant d’obtenir le nom des colonnes. <dataframe>.columns.values renvoie les noms de colonnes sous la forme d’un tableau numpy.
  • <dataframe>.index: renvoie un objet itérable permettant d’obtenir les index (nom de lignes). <dataframe>.index.values renvoie les noms des index sous la forme d’un tableau numpy.
  • <dataframe>.values: retourne l’ensemble des éléments du dataframe sous la forme d’un tableau numpy.
  • <dataframe>.describe(): renvoie des informations statistiques sur les éléments du dataframe quand les éléments sont des valeurs numériques.

    Par exemple:

    >>> data = {'a': [1, 3, 5], 'b': ['1', '3', '5'], 'c': [1.0, 3.0, 5.0]}
    >>> df = pd.DataFrame(data)
    >>> df.describe()
             a    c
    count  3.0  3.0
    mean   3.0  3.0
    std    2.0  2.0
    min    1.0  1.0
    25%    2.0  2.0
    50%    3.0  3.0
    75%    4.0  4.0
    max    5.0  5.0
    
  • <dataframe>.shape renvoie la taille du dataframe sous la forme d’un tuple.
  • len(<dataframe>) ou len(<dataframe>.index) renvoie le nombre de lignes du dataframe.
  • len(<dataframe>.columns) renvoie le nombre de colonnes du dataframe.
  • <dataframe>.memory_usage(): retourne la taille occupée pour chaque colonne.

Accéder à une valeur à partir de l’index

Comme indiqué auparavant, un dataframe pandas peut être considéré comme un dictionnaire de séries pandas. On peut accéder à chaque série en utilisant les index des colonnes, par exemple si on considère le dataframe:

>>> data = {'a': ['a0', 'a1', 'a2'], 'b': ['b0', 'b1', 'b2'], 'c': ['c0', 'c1', 'c2']}
>>> i = ['l0', 'l1', 'l2']
>>> df = pd.DataFrame(data, index=i)
>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

Alors df['a'] est une série contenant tous les éléments de la colonne 'a':

>>> df['a']
l0    a0
l1    a1
l2    a2
Name: a, dtype: object

On peut accéder directement à un élément en utilisant l’index de la série:

>>> df['a']['l1']
'a1'

loc[] et iloc[]

La fonction loc[] permet d’accéder à un ou plusieurs éléments en utilisant des index sous la forme <index ligne>, <index colonne>, par exemple:

>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

>>> df.loc['l1', 'b']
'b1'

Dans cet exemple, il existe 2 index:

  • L’index personnalisé paramétré à l’initialisation du dataframe:
    • Lignes: list(df.index)['l0', 'l1', 'l2']
    • Colonnes: list(df.columns)['a', 'b', 'c']
  • L’index implicite du dataframe sous la forme numérique.

On peut utiliser loc[] pour les index personnalisés et iloc[] pour les index numériques. S’il n’existe pas d’index personnalisé alors loc[] fonctionne avec des index implicites numériques.

Par exemple:

>>> data1 = {'a': ['a0', 'a1', 'a2'], 'b': ['b0', 'b1', 'b2'], 'c': ['c0', 'c1', 'c2']}
>>> i = ['l0', 'l1', 'l2']
>>> df_custom_indexes = pd.DataFrame(data1, index=i)
>>> print(df_custom_indexes)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

>>> data2 = [['a0', 'b0', 'c0'], ['a1', 'b1', 'c1'], ['a2', 'b2', 'c2']]
>>> df_implicit_indexes = pd.DataFrame(data2)
>>> print(df_implicit_indexes)
    0   1   2
0  a0  b0  c0
1  a1  b1  c1
2  a2  b2  c2

Dans le cas des index numériques implicites loc[<index ligne>, <index colonne>] fonctionne:

>>> df_implicit_indexes.loc[1, 2]
'c1'

Si des index personnalisés existent, loc[] ne fonctionne pas avec les index numériques:

>>> df_custom_indexes[1, 2]
KeyError: (1, 2)

>>> df_custom_indexes.loc['l1', 'c']
'c1'

iloc[<index ligne>, <index colonne>] fonctionne dans tous les cas seulement pour des index numériques:

>>> df_implicit_indexes.iloc[1, 2]
'c1'

>>> df_custom_indexes.iloc[1, 2]
'c1'

Sous-ensembles

On peut extraire des sous-ensembles en utilisant des index sous la forme slicing:

[<index de début>:<index de fin exclu>:<pas utilisé>]

Ainsi comme pour les listes python classiques:

  • [2:] permet de commencer à l’index 2 (3e élément) jusqu’au dernier.
  • [:3] permet de commencer du début jusqu’à l’index 2 (3e élément). L’index (4e élément) est exclu.
  • [:] désigne tous les éléments de la liste.

Par exemple, si on considère le dataframe définit plus haut:

>>> print(df_implicit_indexes)
    0   1   2
0  a0  b0  c0
1  a1  b1  c1
2  a2  b2  c2

Alors:

  • df_implicit_indexes.iloc[1, 2] correspond à l’élément à l’index ligne 1 (2e ligne) et colonne 2 (3e colonne):
    'c1'
    
  • df_implicit_indexes.iloc[:, 2] correspond à une série contenant toutes les lignes et la colonne 2 (3e colonne):
    0    c0
    1    c1
    2    c2
    Name: 2, dtype: object
    
  • df_implicit_indexes.iloc[:, 0:2] correspond à un dataframe contenant toutes les lignes et les colonnes 0 à 2, la colonne 2 étant exclue:
        0   1
    0  a0  b0
    1  a1  b1
    2  a2  b2
    
  • df_implicit_indexes.iloc[:, 0:2:2] correspond à un dataframe contenant toutes les lignes et les colonnes 0 à 2 avec la colonne 2 exclue. Les colonnes seront énumérées par pas de 2:
        0
    0  a0
    1  a1
    2  a2
    
  • df_implicit_indexes.iloc[0:2, 2] correspond à une série contenant les lignes 0 à 2 avec 2 exclu et la colonne 2 (3e colonne):
    0    c0
    1    c1
    Name: 2, dtype: object
    
  • df_implicit_indexes.iloc[1, [0, 2]] correspond à une série contenant la ligne 1 (2e ligne) et les colonnes 0 (1ère colonne) et 2 (3e colonne):
    0    a1
    2    c1
    Name: 1, dtype: object
    

Avec loc[] et des index personnalisés, on peut aussi utiliser la forme slicing [:] ou [<index 1>, <index 2>, ..., <index i>], par exemple:

  • df_custom_indexes.loc[:,'c'] correspond à une série contenant toutes les lignes et la colonne 'c':
    l0    c0
    l1    c1
    l2    c2
    Name: c, dtype: object
    
  • df_custom_indexes.loc[['l0', 'l1'],:] correspond à un dataframe contenant les lignes 'l0' et 'l1' et toutes les colonnes:
         a   b   c
    l0  a0  b0  c0
    l1  a1  b1  c1
    

at[] et iat[]

at[] et iat[] permettent d’extraire des éléments d’un dataframe comme les colonnes respectivement loc[] et iloc[] c’est-à-dire on peut utiliser:

  • at[] pour les index personnalisés et
  • iat[] pour les index numériques. S’il n’existe pas d’index personnalisé alors at[] fonctionne avec des index implicites numériques.

La différence est qu’avec at[] et iat[], il n’est possible d’utiliser que des index simples sans les formes slicing [:] ou [<index 1>, <index 2>, ..., <index i>]. Si on prend les exemples de dataframes définis plus haut:

>>> print(df_implicit_indexes)
    0   1   2
0  a0  b0  c0
1  a1  b1  c1
2  a2  b2  c2

Alors:

  • df_implicit_indexes.iat[1, 2] renvoie l’élément se trouvant à la ligne 1 (2e ligne) et la colonne 2 (3e colonne):
    'c1'
    
  • df_implicit_indexes.iat[:, 2] renvoie une erreur:
    ValueError: iAt based indexing can only have integer indexers
    
  • df_implicit_indexes.iat[:, [0, 2]] renvoie une erreur:
    ValueError: iAt based indexing can only have integer indexers
    
>>> print(df_custom_indexes)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

Alors:

  • df_custom_indexes.at['l1', 'c'] renvoie l’élément à la ligne l1 et la colonne c:
    'c1'
    
  • df_custom_indexes.at[1, '2] renvoie une erreur car il existe des index personnalisés pour df_custom_indexes.
  • df_implicit_indexes.at[1, 2] ne renvoie pas d’erreur car il n’existe que des index numériques pour df_implicit_indexes:
    'c1'
    

Multi-index

Les multi-index permettent d’avoir des index et des index de colonnes sur plusieurs niveaux de façon à identifier des valeurs suivant un ensemble de clés. Pour accéder aux éléments du dataframe, au lieu d’utiliser un index à une valeur, on peut utiliser un agrégat de valeurs. Les multi-index peuvent être utilisés pour identifier des colonnes ou des lignes du dataframe. Plus précisemment, le terme “index” est utilisé pour désigner des labels pour les lignes toutefois le terme “multi-index” est applicable pour les lignes et les colonnes.

Par exemple, si on considère le dataframe suivant:

>>> data = [['a0', 'a1', 'a2', 'a3'], ['b0', 'b1', 'b2', 'b3'], ['c0', 'c1', 'c2', 'c3'], ['d0', 'd1', 'd2', 'd3']]
>>> df = pd.DataFrame(data)
>>> print(df)
    0   1   2   3
0  a0  a1  a2  a3
1  b0  b1  b2  b3
2  c0  c1  c2  c3
3  d0  d1  d2  d3

On utilise un index implicite pour les colonnes et les lignes:

>>> list(df.index)
[0, 1, 2, 3]

>>> list(df.columns)
[0, 1, 2, 3]

Si on utilise un index explicite pour les colonnes:

>>> df_index = ['a', 'b', 'c', 'd']
>>> df.columns = df_index
>>> print(df)
    a   b   c   d
0  a0  a1  a2  a3
1  b0  b1  b2  b3
2  c0  c1  c2  c3
3  d0  d1  d2  d3

On peut identifier les colonnes en utilisant une valeur parmi les valeurs de l’index explicite:

>>> df.loc[1, 'b']
'b1'

Si on considère le multi-index suivant à 2 niveaux:

>>> df_index_values = [['group1', 'group1', 'group2', 'group2'], ['a', 'b', 'c', 'd']]
>>> df_index = pd.MultiIndex.from_arrays(df_index_values)

On affecte le multi-index aux colonnes du dataframe:

>>> df.columns = df_index
>>> print(df)
  group1     group2    	
       a   b      c   d
0     a0  a1     a2  a3
1     b0  b1     b2  b3
2     c0  c1     c2  c3
3     d0  d1     d2  d3

Pour identifier une colonne, on doit utiliser un agrégat de valeurs:

>>> df.loc[1, ('group1', 'b')]
'b1'

On peut utiliser des multi-index sur n niveaux, par exemple sur 3 niveaux:

>>> df_index_values = [['A', 'B', 'B', 'C'], ['group1', 'group1', 'group2', 'group2'], ['a', 'b', 'c', 'd']]
>>> df_index = pd.MultiIndex.from_arrays(df_index_values)
>>> df.columns = df_index
>>> print(df)
       A      B             C
  group1 group1 group2 group2
       a      b      c      d
0     a0     a1     a2     a3
1     b0     b1     b2     b3
2     c0     c1     c2     c3
3     d0     d1     d2     d3

Accès aux éléments en utilisant un multi-index

On peut accéder aux éléments du dataframe en utilisant 1, 2 ou 3 niveaux:

>>> print(df.loc[:, 'B'])         # 1 niveau
  group1 group2
       b      c
0     a1     a2
1     b1     b2
2     c1     c2
3     d1     d2

>>> print(df.loc[:, ('B', 'group1')])         # 2 niveaux
    b
0  a1
1  b1
2  c1
3  d1

>>> df.loc[:, ('B', 'group1', 'b')]           # 3 niveaux
0    a1
1    b1
2    c1
3    d1
Name: (B, group1, b), dtype: object
On ne peut pas changer l’ordre des éléments dans le multi-index

Par exemple

>>> print(df.loc[:, ('B', 'group1')])         # OK
     b
 0  a1
 1  b1
 2  c1
 3  d1 
>>> df.loc[:, ('group1', 'B')]
KeyError: ('group1', 'B')

Utilisation de multi-index pour les lignes

On peut utiliser des multi-index pour les lignes:

>>> data = [['a0', 'a1', 'a2', 'a3'], ['b0', 'b1', 'b2', 'b3'], ['c0', 'c1', 'c2', 'c3'], ['d0', 'd1', 'd2', 'd3']]
>>> df = pd.DataFrame(data)
>>> df_index_values = [['A', 'A', 'B', 'B'], ['a', 'b', 'c', 'd']]
>>> df_index = pd.MultiIndex.from_arrays(df_index_values)
>>> df.index = df_index
>>> print(df)
      0   1   2   3
A a  a0  a1  a2  a3
  b  b0  b1  b2  b3
B c  c0  c1  c2  c3
  d  d0  d1  d2  d3

Création de multi-index à partir d’un tableau

Comme l’exemple précédent, on peut créer un multi-index à partir d’un tableau en indiquant directement les différents niveaux.

Par exemple pour 2 niveaux:

>>> df_index_values = [['A', 'A', 'B', 'B'], ['a', 'b', 'c', 'd']]
>>> df_index = pd.MultiIndex.from_arrays(df_index_values)

Si on affecte ce multi-index aux colonnes du dataframe d’origine:

>>> df.columns = df_index
>>> print(df)
    A       B    
    a   b   c   d
0  a0  a1  a2  a3
1  b0  b1  b2  b3
2  c0  c1  c2  c3
3  d0  d1  d2  d3

Création de multi-index à partir de tuples

La création de multi-index à partir de tuples est assez directe:

>>> df_index_values = [('A', 'a'), ('A', 'b'), ('B', 'c'), ('B', 'd')]
>>> df_index = pd.MultiIndex.from_tuples(df_index_values)

Si on affecte ce multi-index aux colonnes du dataframe d’origine, on obtient le même résultat que précédemment:

>>> df.columns = df_index
>>> print(df)
    A       B    
    a   b   c   d
0  a0  a1  a2  a3
1  b0  b1  b2  b3
2  c0  c1  c2  c3
3  d0  d1  d2  d3

Création de multi-index en effectuant un produit cartésien

Pour réduire la complexité de création d’un multi-index, il est possible d’effectuer un produit cartésien entre plusieurs valeurs pour obtenir les différents niveaux, par exemple:

>>> first_level = ['a', 'b']
>>> second_level = ['A', 'B']
>>> df_index = pd.MultiIndex.from_product([first_level, second_level])

Avec le dataframe d’origine, on obtient:

>>> df.columns = df_index
>>> print(df)
    a       b    
    A   B   A   B
0  a0  a1  a2  a3
1  b0  b1  b2  b3
2  c0  c1  c2  c3
3  d0  d1  d2  d3

Création de multi-index à partir d’un dataframe

Pour créer un dataframe à partir d’un dataframe

>>> df_multiindex = pd.DataFrame([['1', 'A'], ['1', 'B'], ['2', 'A'], ['2', 'B']])
>>> print(df_multiindex)
   0  1
0  1  A
1  1  B
2  2  A
3  2  B

>>> df_index = pd.MultiIndex.from_frame(df_multiindex)

Avec le dataframe d’origine, on obtient:

>>> df.columns = df_index
>>> print(df)
0   1       2    
1   A   B   A   B
0  a0  a1  a2  a3
1  b0  b1  b2  b3
2  c0  c1  c2  c3
3  d0  d1  d2  d3

Création de multi-index avec les arguments levels et codes

Les arguments levels et codes permettent d’indiquer de quelle façon les niveaux sont définis:

  • L’argument levels permet d’indiquer un tableau pour lequel chaque ligne contient les labels pour un niveau donné.
  • L’argument codes utilise un tableau pour lequel chaque ligne indique comment les labels sont utilisés par rapport aux colonnes du dataframe. Ces indications sont faites en indiquant un index de la ligne correspondante dans le tableau de l’argument levels.

Par exemple on souhaite définir un multi-index à 2 niveaux:

  • 1er niveau avec les labels '1' et '2'
  • 2e niveau avec les labels 'A' et 'B'.
    Le but étant d’avoir le multi-index suivant:

    0   1       2    
    1   A   B   A   B
    

L’argument levels permettant de définir les niveaux contiendra le tableau suivant: [['1', '2'], ['A, 'B']].
L’argument codes va indiquer la façon dont les labels sont utilisés suivant les colonnes du dataframe. Sachant que le dataframe contient 4 colonnes donc chaque ligne du tableau de codes contiendra 4 éléments. Ces éléments indiquent l’index de la ligne dans levels à utiliser pour une colonne du dataframe:

  • Pour le 1er niveau: [0, 0, 1, 1] car 0 est l’index de '1' et 1 est l’index de '2' dans ['1', '2'].
  • Pour le 2e niveau: [0, 1, 0, 1] car 0 est l’index de 'A' et 1 est l’index de 'B' dans ['A', 'B'].

On définit le multi-index de cette façon:

df_index = pd.MultiIndex(levels = [['1', '2'], ['A', 'B']],
  codes = [[0, 0, 1, 1], [0, 1, 0, 1]])

Avec le dataframe d’origine, on obtient:

>>> df.columns = df_index
>>> print(df)
0   1       2    
1   A   B   A   B
0  a0  a1  a2  a3
1  b0  b1  b2  b3
2  c0  c1  c2  c3
3  d0  d1  d2  d3

Nommer les niveaux du multi-index

Pour plus de clarté, il est possible de nommer les différents niveaux du multi-index. Il faut utiliser le paramètre names lors de la création du multi-index.

Par exemple pour un multi-index à 3 niveaux:

>>> df_multiindex = pd.DataFrame([['1', 'A', 'a'], ['1', 'B', 'b'], ['2', 'A', 'c'], ['2', 'B', 'd']])
>>> print(df_multiindex)
   0  1  2
0  1  A  a
1  1  B  b
2  2  A  c
3  2  B  d

>>> df_index = pd.MultiIndex.from_frame(df_multiindex, names=['level1', 'level2', 'level3'])

En affectant ce multi-index au dataframe d’origine, on peut effectuer le nommage des différents niveaux:

>>> df.columns = df_index
>>> print(df)
level1   1       2    
level2   A   B   A   B
level3   a   b   c   d
0       a0  a1  a2  a3
1       b0  b1  b2  b3
2       c0  c1  c2  c3
3       d0  d1  d2  d3

Modifier un dataframe

Un dataframe est un objet mutable. On peut modifier des éléments d’un dataframe en utilisant les mêmes opérateurs ou fonctions utilisés pour accéder à ces éléments. En effet, ces opérateurs et fonctions permettent de renvoyer une référence de l’élément dans le dataframe, on peut donc aussi les utiliser pour modifier la valeur de l’élément.

Ainsi les syntaxes suivantes permettent de modifier un élément dans un dataframe:

  • <dataframe>[<index colonne>][<index ligne>] = <nouvelle valeur>
  • <dataframe>.loc[<index ligne>, <index colonne>] = <nouvelle valeur>
  • <dataframe>.iloc[<index ligne numérique>, <index colonne numérique>] = <nouvelle valeur>
  • <dataframe>.at[<index ligne>, <index colonne>] = <nouvelle valeur>
  • <dataframe>.iat[<index ligne numérique>, <index colonne numérique>] = <nouvelle valeur>

Par exemple, si on considère le dataframe suivant:

>>> data = {'a': ['a0', 'a1', 'a2'], 'b': ['b0', 'b1', 'b2'], 'c': ['c0', 'c1', 'c2']}
>>> i = ['l0', 'l1', 'l2']
>>> df = pd.DataFrame(data, index=i)
>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

Les syntaxes suivantes permettent de modifier les éléments dans le dataframe:

>>> df['a']['l1'] = 'NEW - a1'
>>> df.loc['l1', 'b'] = 'NEW - b1'
>>> df.iloc[1, 2] = 'NEW - c1'
>>> df.at['l2', 'a'] = 'NEW - a2'
>>> df.iloc[2, 1] = 'NEW - b2'
>>> print(df)
           a         b         c
l0        a0        b0        c0
l1  NEW - a1  NEW - b1  NEW - c1
l2  NEW - a2  NEW - b2        c2

On peut modifier une colonne entière en affectant une série. Il faut que la série utilise le même index que le dataframe, par exemple si on considère le dataframe d’origine dans l’exemple précédent:

>>> df['b'] = pd.Series(['NEW/b0', 'NEW/b1', 'NEW/b2'], index=i)
>>> print(df)
     a       b   c
l0  a0  NEW/b0  c0
l1  a1  NEW/b1  c1
l2  a2  NEW/b2  c2

Modifier les colonnes ou lignes

Renommer des lignes ou colonnes

On peut affecter de nouveaux labels pour les lignes ou les colonnes en utilisant les propriétés <dataframe>.index pour les lignes et <dataframe>.columns pour les colonnes.

Par exemple:

>>> df.columns = ['A', 'B', 'C']
>>> df.index = ['1', '2', '3']
>>> print(df)
    A   B   C
1  a0  b0  c0
2  a1  b1  c1
3  a2  b2  c2

Pour renommer un ou plusieurs noms de lignes/colonnes sans tout modifier, on peut utiliser <dataframe>.rename() mais il ne faut pas oublier l’argument inplace=True car rename() renvoie un nouveau dataframe.

Par exemple:

>>> df.rename(columns = {'a': 'B'}, index = {'l1': 'n1'}, inplace=True)
>>> print(df)
     A   b   c
l0  a0  b0  c0
n1  a1  b1  c1
l2  a2  b2  c2

Ajouter une colonne

On peut ajouter une colonne en affectant une série dans un label de colonne qui n’existe pas. Il faut que l’index de la série soit le même que celui du dataframe.

Par exemple:

>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2
>>> df['d'] = pd.Series(['d0', 'd1', 'd2'], index=['l0', 'l1', 'l2'])
>>> print(df)
     a   b   c   d
l0  a0  b0  c0  d0
l1  a1  b1  c1  d1
l2  a2  b2  c2  d2

Si on crée la nouvelle colonne de la façon suivante, toutes les valeurs de la colonne contiendront la même valeur:

>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2
>>> df['d'] = 'NEW'
>>> print(df)
     a   b   c    d
l0  a0  b0  c0  NEW
l1  a1  b1  c1  NEW
l2  a2  b2  c2  NEW

<dataframe>.assign()

On peut utiliser une autre syntaxe avec assign() pour ajouter une nouvelle colonne. assign() renvoie un nouveau dataframe, il n’est pas possible d’utiliser un argument inplace.

Par exemple:

>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2
>>> new_column = pd.Series(['NEW/b0', 'NEW/b1', 'NEW/b2'], index=i)
>>> new_df = df.assign(d = new_column)
>>> print(new_df)
     a   b   c       d
l0  a0  b0  c0  NEW/b0
l1  a1  b1  c1  NEW/b1
l2  a2  b2  c2  NEW/b2

Ajouter une colonne à un index donné

Avec <dataframe>.insert(), on peut ajouter une colonne à un index particulier dans un dataframe existant en utilisant la syntaxe:

<dataframe>.insert(<index>, <nom nouvelle colonne>, <nouveaux éléments>)

Par exemple:

>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2
>>> df.insert(1, 'd', ['NEW/d0', 'NEW/d1', 'NEW/d2'])
>>> print(df)
     a       d   b   c
l0  a0  NEW/d0  b0  c0
l1  a1  NEW/d1  b1  c1
l2  a2  NEW/d2  b2  c2

Changer l’ordre des colonnes

Avec reindex(), on peut réordonner les colonnes d’un dataframe. La fonction génère un nouveau dataframe:

<dataframe>.reindex(columns=['<colonne 1>', '<colonne 2>', ..., '<colonne N>'])

On peut utiliser l’option copy=False pour éviter de générer un nouveau dataframe.

Par exemple:

>>> printf(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

>>> new_df = df.reindex(columns=['c', 'b', 'a'])
>>> print(new_df)
     c   b   a
l0  c0  b0  a0
l1  c1  b1  a1
l2  c2  b2  a2

Une autre méthode est d’utiliser cette syntaxe (le résultat est le même):

>>> new_df = df[['c', 'b', 'a']]

Supprimer des colonnes

Plusieurs syntaxes sont possibles pour supprimer une ou plusieurs colonnes. Pour supprimer une colonne dans un dataframe en modifiant l’instance directement:

del <dataframe>['<colonne i>']

Par exemple:

>>> printf(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

>>> del df['c']
>>> print(df)
     a   b
l0  a0  b0
l1  a1  b1
l2  a2  b2

Les syntaxes suivantes sont équivalentes et permettent de supprimer plusieurs colonnes en générant un nouveau dataframe. Le dataframe courant n’est pas modifié. Pour modifier le dataframe courant, il faut rajouter l’option inplace = True.

  • <dataframe>.drop(['<colonne x>', '<colonne y>', '<colonne z>'], axis = 1)
  • <dataframe>.drop(columns = ['<colonne x>', '<colonne y>', '<colonne z>'])

La fonction <dataframe>.pop('<colonne i>') permet de supprimer une colonne et de renvoyer la série correspondant à la colonne supprimée.

Par exemple:

>>> print(df)
     a   b   c
l0  a0  b0  c0
l1  a1  b1  c1
l2  a2  b2  c2

>>> deleted_column = df.pop('b')
>>> deleted_column
l0    b0
l1    b1
l2    b2
Name: b, dtype: object

Supprimer des lignes

Les syntaxes suivantes permettent de supprimer des lignes d’un dataframe en générant un nouveau dataframe. Le dataframe courant n’est pas modifié. Pour modifier le dataframe courant, il faut rajouter l’option inplace = True.

  • <dataframe>.drop(['<ligne x>', '<ligne y>', '<ligne z>'])
  • <dataframe>.drop(index = ['<ligne x>', '<ligne y>', '<ligne z>'])

Modification de valeurs d’un dataframe

De nombreuses fonctions existent pour modifier directement un dataframe sans avoir à exécuter une boucle pour parcourir toutes les valeurs du dataframe:

  • <dataframe>.astype(<nouveau type>): modifie le type de toutes les valeurs d’un dataframe en générant un nouveau dataframe. Le type est le même que celui indiqué lors de l’initialisation. On peut utiliser l’option copy=False pour modifier le dataframe courant au lieu de générer un nouveau dataframe.
  • <dataframe>.astype({'colonne x': <type 1>, 'colonne y': <type 2>, 'colonne z': <type 3>}): permet de modifier le type de plusieurs colonnes. Le type est le même que celui indiqué lors de l’initialisation. On peut utiliser l’option copy=False pour modifier le dataframe courant au lieu de générer un nouveau dataframe.
  • <dataframe>.replace(<valeur à remplacer>, <valeur remplaçante>): permet de remplacer toute occurence d’une valeur dans un dataframe par une autre. Cette fonction génère un nouveau dataframe, pour modifier le dataframe courant, il faut utiliser l’option: inplace=True.

    On peut utiliser des syntaxes différentes pour effectuer plusieurs remplacements en une fois:

    • <dataframe>.replace([<valeur à remplacer 1>, <valeur remplaçante 1>], [<valeur à remplacer 2>, <valeur remplaçante 2>], [<valeur à remplacer 3>, <valeur remplaçante 3>])
    • <dataframe>.replace({<valeur à remplacer 1>: <valeur remplaçante 1>, <valeur à remplacer 2>: <valeur remplaçante 2>, <valeur à remplacer 3>: <valeur remplaçante 3>)
  • <dataframe>.fillna(<valeur remplaçante>): remplacer tous les NaN par une valeur particulière. Cette fonction génère un nouveau dataframe, pour modifier le dataframe courant, il faut utiliser l’option: inplace=True.
  • <dataframe>.dropna(): supprime toutes les lignes contenant au moins une valeur NaN. Un nouveau dataframe est renvoyé, le dataframe courant n’est pas modifié. On peut utiliser les options suivantes:
    • how = 'all': une ligne est supprimée si toutes les valeurs de cette ligne sont NaN.
    • inplace=True pour modifier le dataframe courant.
    • axis = 1 pour supprimer les colonnes contenant des valeurs NaN plutôt que des lignes.
  • <dataframe>.apply(<lambda>): applique une lambda à toutes les valeurs d’un dataframe. Le dataframe courant n’est pas modifié, le type de la sortie dépend des options.

    Par exemple, on considère le dataframe suivant:

    >>> df = pd.DataFrame({'a': [2, 3, 4], 'b': [5, 6, 7], 'c': [8, 9, 10]}, 
    	index=['l0', 'l1', 'l2'])
    >>> print(df)
        a  b   c
    l0  2  5   8
    l1  3  6   9
    l2  4  7  10
    
    • Par défaut et sans option, la lambda est exécutée pour chaque valeur du dataframe d’origine:
      >>> print(df.apply(lambda x: x ** 2))
           a   b    c
      l0   4  25   64
      l1   9  36   81
      l2  16  49  100
      

      La taille et la structure du dataframe d’origine sont maintenues.

    • Avec l’option axis = 0: la lambda est appliquée sur chaque colonne. Le type de l’objet passé en paramètre de la lambda est une série. La sortie de la lambda n’est pas forcément dans la même dimension que l’entrée:
      >>> print(df.apply(lambda x: x['l0'] + x['l1'] + x['l2'], axis = 0))
      a     9
      b    18
      c    27
      dtype: int64
      

      La traitement se fait en colonne (par série). La sortie de la lambda est une valeur scalaire alors la sortie de df.apply() est une série.

      >>> print(df.apply(lambda x: [x['l0'], x['l1']], axis = 0))
         a  b  c
      0  2  5  8
      1  3  6  9
      

      La sortie de la lambda est un tableau avec 2 valeurs donc la sortie de df.apply() est un dataframe à 2 lignes. La dimension de l’objet en sortie dépend de la sortie de la lambda.

    • Avec l’option axis = 1: la lambda est appliquée sur chaque ligne. Le type de l’objet passé en paramètre de la lambda est une série. La sortie de la lambda n’est pas forcément dans la même dimension que l’entrée:
      >>> print(df.apply(lambda x: x['a'] + x['b'] + x['c'], axis = 1))
      l0    15
      l1    18
      l2    21
      dtype: int64
      

      La traitement se fait en ligne. La sortie de la lambda est une valeur scalaire alors la sortie de df.apply() est une série.

      >>> print(df.apply(lambda x: pd.Series([x['a'], x['b']]), axis = 1))
          0  1
      l0  2  5
      l1  3  6
      l2  4  7
      

      Si la sortie de la lambda est une série, alors la sortie de df.apply() sera un dataframe.

      Pour avoir des labels de colonnes particuliers il faut indiquer un index dans la série en sortie de la lambda:

      >>> print(df.apply(lambda x: pd.Series([x['a'], x['b']], index=['c0', 'c1']), axis = 1))
          c0  c1
      l0   2   5
      l1   3   6
      l2   4   7
      
      >>> print(df.apply(lambda x: [x['a'], x['b']], axis = 1))
      l0    [2, 5]
      l1    [3, 6]
      l2    [4, 7]
      dtype: object
      

      Si la sortie de la lambda est un tableau, alors la sortie de df.apply() est une série de tableaux.

    • result_type = 'expand' permet d’indiquer que la sortie de la lambda sera considérée comme des colonnes. Si on renvoie un tableau alors les éléments du tableau seront des colonnes:
      >>> print(df.apply(lambda x: [x['a'], x['b']], axis = 1, result_type='expand'))
          0  1
      l0  2  5
      l1  3  6
      l2  4  7
      

      Chaque élément du tableau de sortie de la lambda est considéré comme un élément de colonne du dataframe en sortie. Les labels des colonnes d’origine sont toutefois perdus.

    • result_type = 'reduce' permet d’indiquer que la sortie de la lambda sera considérée comme une réduction des colonnes du dataframe d’origine. Si la lambda renvoie un tableau, la sortie de df.apply() sera une série de tableau:
      >>> print(df.apply(lambda x: [x['a'], x['b']], axis = 1, result_type='reduce'))
      l0    [2, 5]
      l1    [3, 6]
      l2    [4, 7]
      dtype: object
      
    • result_type = 'broadcast' permet d’indiquer que la structure du dataframe d’origine sera préservée donc les labels des colonnes seront maintenus en sortie. Il n’est pas nécessaire que la lambda génère une série ou d’indiquer des index en sortie de la lambda:
      >>> print(df.apply(lambda x: [x['a'], x['b'], x['c']], axis = 1, result_type='broadcast'))
          a  b   c
      l0  2  5   8
      l1  3  6   9
      l2  4  7  10
      

      Les labels des colonnes sont maintenus dans le dataframe en sortie.

Modifier les valeurs d’un dataframe par condition

Il est possible de modifier plusieurs valeurs d’un dataframe en une seule ligne en indiquant des conditions.

Par exemple, si on considère le dataframe suivant:

>>> df = pd.DataFrame({'a': [2, 3, 4], 'b': [5, 6, 7], 'c': [8, 9, 10]}, 
	index=['l0', 'l1', 'l2'])
>>> print(df)
    a  b   c
l0  2  5   8
l1  3  6   9
l2  4  7  10

On peut modifier toutes les valeurs d’une colonne de cette façon:

>>> df['b'] = 0
>>> print(df)
    a  b   c
l0  2  0   8
l1  3  0   9
l2  4  0  10

On peut indiquer une condition valable pour toutes les valeurs du dataframe:

>>> print(df)
    a  b   c
l0  2  5   8
l1  3  6   9
l2  4  7  10

>>> df[df > 6] = 0
>>> print(df)
    a  b  c
l0  2  5  0
l1  3  6  0
l2  4  0  0

On affectera 0 à toutes les valeurs strictement supérieures à 6.

On peut aussi modifier une colonne suivant des conditions appliquées sur d’autres colonnes. Par exemple, si un élément de la colonne 'a' >= 3 alors on affecte 0 à l’élément de la même ligne dans la colonne 'b':

>>> print(df)
    a  b   c
l0  2  5   8
l1  3  6   9
l2  4  7  10

>>> df.loc[df['a'] >= 3,'b'] = 0
>>> print(df)
    a  b   c
l0  2  5   8
l1  3  0   9
l2  4  0  10

Modifier un dataframe en appliquant des fonctions

<dataframe>.drop_duplicates()

Cette fonction permet de supprimer les lignes redondantes dans un dataframe et de ne conserver qu’une seule occurence. Par défaut, la fonction renvoie un dataframe avec les duplicats supprimés. Cette fonction peut être utilisée avec les options suivantes:

  • inplace = True: permet de modifier le dataframe courant plutôt que de renvoyer un dataframe avec les modifications.
  • keep = False pour ne pas conserver au moins une ligne parmi les duplicats. keep = 'last' permet de ne conserver que la dernière occurence parmi les duplicats et keep = 'first' est la valeur par défaut, seule la 1ère occurence est conservée.
  • subset = [<nom des colonnes>]: permet d’indiquer quelles sont les colonnes à vérifier pour trouver les duplicats. Par défaut, toutes les colonnes sont vérifiées.

Par exemple, si on considère le dataframe suivant:

>>> df = pd.DataFrame([['1', 'A', 'a'], ['1', 'B', 'b'], ['1', 'B', 'c'], ['2', 'A', 'c'], ['2', 'B', 'd'], ['2', 'B', 'd']])
>>> print(df)
0  1  2
0  1  A  a
1  1  B  b
2  1  B  c
3  2  A  c
4  2  B  d
5  2  B  d

Par défaut, toutes les colonnes sont vérifiées et seule la 1ère occurence est conservée:

>>> print(df.drop_duplicates())
   0  1  2
0  1  A  a
1  1  B  b
2  1  B  c
3  2  A  c
4  2  B  d

Avec subset = [0], seule la colonne 0 est vérifiée pour trouver les duplicats:

>>> print(df.drop_duplicates(subset = [0]))
   0  1  2
0  1  A  a
3  2  A  c

Avec keep = 'last', les duplicats sont cherchés en vérifiant la colonne 0 et on ne garde que la dernière occurence.

>>> print(df.drop_duplicates(subset = [0], keep = 'last'))
   0  1  2
2  1  B  c
5  2  B  d

Tri des index et colonnes avec <dataframe>.sort_index()

<dataframe>.sort_index() permet de trier les éléments d’un dataframe suivant les index ou les colonnes. Par défaut le tri se fait dans le sens ascendant sur les index et en générant un nouveau dataframe. On peut utiliser les options suivantes:

  • inplace = True pour modifier le dataframe courant.
  • ascending = False pour trier par ordre décroissant.
  • axis=1 pour trier suivant les noms de colonnes.
  • level = <int> pour indiquer le niveau à trier dans le cas d’un index multi-niveau.

Par exemple:

>>> df = pd.DataFrame([['1', 'A', 'a'], ['1', 'B', 'b'], ['1', 'C', 'c']], 
                  columns = ['c', 'b', 'a'], 
                  index = ['z', 'y', 'x'])
>>> print(df)
   c  b  a
z  1  A  a
y  1  B  b
x  1  C  c

>>> print(df.sort_index())
   c  b  a
x  1  C  c
y  1  B  b
z  1  A  a

>>> print(df.sort_index(axis=1, ascending=True))
   a  b  c
z  a  A  1
y  b  B  1
x  c  C  1

Tri des valeurs avec <dataframe>.sort_values()

<dataframe>.sort_values() permet de trier les valeurs d’un dataframe. Il faut indiquer la colonne ou les colonnes à trier avec l’argument by. Par défaut le tri se fait dans le sens ascendant en générant un nouveau dataframe. On peut utiliser les options suivantes:

  • inplace = True pour modifier le dataframe courant.
  • ascending = False pour trier par ordre décroissant.
  • axis=1 pour trier suivant les noms de colonnes.
  • na_position indique où les valeurs NaN seront placées:
    • 'first': elles seront placées avant les valeurs triées
    • 'last': elles seront placées après les valeurs triées
  • key: permet d’indiquer une lambda qui sera utilisée pour appliquer un tri particulier sur les colonnes indiquées avec by. Le type de la valeur d’entrée de la lambda sera une série.

Par exemple:

>>> df = pd.DataFrame([['1', 'A', 'a', np.NaN], ['2', 'B', 'b', '0'], ['3', 'C', 'c', '1'], ['4', 'D', 'd', np.NaN]], 
                  columns = ['a', 'b', 'c', 'd'], 
                  index = ['w', 'x', 'y', 'z'])
>>> print(df)
   a  b  c    d
w  1  A  a  NaN
x  2  B  b    0
y  3  C  c    1
z  4  D  d  NaN

>>> print(df.sort_values(by = 'd', na_position='first'))
   a  b  c    d
w  1  A  a  NaN
z  4  D  d  NaN
x  2  B  b    0
y  3  C  c    1

On trie suivant la colonne 'd' et on place les valeurs NaN avant les valeurs triées.

>>> print(df.sort_values(by = ['d', 'c'], na_position='last'))
   a  b  c    d
0  2  B  b    0
1  3  C  c    1
2  1  A  a  NaN
3  4  D  d  NaN

On trie suivant 2 colonnes 'd' et 'c' et on place les valeurs NaN après les valeurs triées.

>>> print(df.sort_values(by = ['a'], key=lambda x: x.apply(lambda y: -int(y))))
   a  b  c    d
z  4  D  d  NaN
y  3  C  c    1
x  2  B  b    0
w  1  A  a  NaN

On effectue le tri suivant la colonne 'a' et on applique la lambda x: x.apply(lambda y: -int(y)) à la colonne 'a' avant d’effectuer le tri. x dans la lambda est une série. x.apply() permet d’appliquer une autre lambda aux valeurs de la série.

La série est entrée de la lambda est:

>>> df['a']
w    1
x    2
y    3
z    4
Name: a, dtype: object

Après application de la lambda:

>>> df['a'].apply(lambda y: -int(y))
w   -1
x   -2
y   -3
z   -4
Name: a, dtype: int64

Agrégation

Avec <dataframe>.groupby()

La fonction <dataframe>.groupby() permet de calculer des agrégats suivant une colonne, plusieurs colonnes ou suivant un mapping particulier. L’argument by permet d’indiquer les groupes des agrégats en indiquant:

  • Une colonne: <dataframe>.groupby([<nom colonne>])
  • Plusieurs colonnes: <dataframe>.groupby([<nom colonne 1>, <nom colonne 2>, ... , <nom colonne i>])
  • Une fonction de mapping: <dataframe>.groupby(<fonction effectuant le mapping>)

Il est possible d’affiner les critères du groupby en précisant certains arguments:

  • axis=1 pour trier suivant les noms de colonnes.
  • level = <int> pour indiquer le niveau à trier dans le cas d’un index multi-niveau.
  • sort=False pour ne pas trier les groupes.
  • group_keys=True: si on utilise apply() en sortie du groupby(), cet argument permet d’indiquer si on veut rajouter un index indiquant les clés de groupage.
  • dropna=False pour ne pas ignorer les valeurs NaN.

Le type de retour de la fonction <dataframe>.groupby() est pandas.core.groupby.generic.DataFrameGroupBy. Ce type autorise d’appliquer des méthodes lors de l’agrégation. Si la colonne ne permet pas l’agrégation, elle sera ignorée:

  • size() pour préciser le nombre de lignes par groupe.
  • sum() pour sommer les valeurs des groupes. Les colonnes où une somme n’est pas possible seront ignorées.
  • min() pour indiquer le minimum des valeurs groupées.
  • max() pour indiquer le maximum des valeurs groupées.
  • describe() pour générer des statistiques descriptives.
  • count() pour compter le nombre de groupes.
  • first() pour renvoyer la 1ère ligne de chaque groupe.
  • last() pour renvoyer la dernière ligne de chaque groupe.
  • nth() pour renvoyer la n-ième ligne de chaque groupe.
  • std() pour renvoyer l’écart-type (i.e. standard déviation) pour chaque groupe.
  • var() pour renvoyer la variance pour chaque groupe.
  • sem() pour renvoyer l’erreur type de la moyenne pour chaque groupe.
  • apply(<lambda>) pour effectuer un traitement sur les groupes d’agrégation.

Par exemple:

>>> df = pd.DataFrame([['1', 'A', 10, np.NaN], ['2', 'B', 15, 3], ['1', 'B', 5, 1], ['2', 'D', 35, np.NaN], ['2', 'A', 25, 3]], 
                  columns = ['a', 'b', 'c', 'd'])
>>> print(df)
   a  b   c    d
0  1  A  10  NaN
1  2  B  15  3.0
2  1  B   5  1.0
3  2  D  35  NaN
4  2  A  25  3.0

>>> df.groupby(['a'])
<pandas.core.groupby.generic.DataFrameGroupBy object at 0xffff58e920d0>

Le résultat d’une agrégation est de type DataFrameGroupBy sur lequel on peut appliquer les fonctions d’agrégation:

>>> print(df.groupby(['a']).sum())
    c    d
a         
1  15  1.0
2  75  6.0

La somme est effectuée sur les colonnes où c’est possible. Les autres colonnes sont ignorées.

>>> print(df.groupby(['a', 'b']).sum())
      c    d
a b         
1 A  10  0.0
  B   5  1.0
2 A  25  3.0
  B  15  3.0
  D  35  0.0

On peut effectuer l’agrégation sur plusieurs colonnes. Le dataframe résultant contient un index multi-niveau.

Si on applique l’agrégation suivant la colonne 'd', inclure ou non les valeurs NaN produit des résultats différents:

>>> print(df.groupby(['d']).sum())
      c
d      
1.0   5
3.0  40

>>> print(df.groupby(['d'], dropna=False).sum())
      c
d      
1.0   5
3.0  40
NaN  45

Si on effectue des agrégations en indiquant une fonction:

>>> def make_group(input_value):
    if input_value < 2:
        return 'group1'
    elif input_value == 2:
        return 'group2'
    else:
        return 'group3'
>>> print(df.groupby(make_group).sum())
         c    d
group1  25  3.0
group2   5  1.0
group3  60  3.0

Si on applique la fonction d’agrégation avec apply():

>>> df.groupby(['a']).apply(lambda x: x['b'])
a   
1  0    A
   2    B
2  1    B
   3    D
   4    A
Name: b, dtype: object

Le résultat est sous la forme d’une série avec un index multi-niveau. On peut éviter d’avoir un index correspondant à la clé d’agrégation avec l’option group_keys=False:

>>> df.groupby(['a'], group_keys=False).apply(lambda x: x['b'])
0    A
2    B
1    B
3    D
4    A
Name: b, dtype: object

Fonction d’agrégation aggregate() ou agg()

La fonction aggregate() ou agg() permet d’appliquer un traitement plus sophistiquée sur des agrégations. Elle s’applique sur un type pandas.core.groupby.generic.DataFrameGroupBy à la suite d’une fonction <dataframe>.groupby().

La fonction aggregate() ou agg() prend comme argument:

  • Une fonction, par exemple np.sum pour effectuer une somme des agrégats ou,
  • Une chaine de caractère indiquant le nom d’une fonction, par exemple 'sum' ou,
  • Une liste de fonctions à appliquer en indiquant directement la fonction ou en indiquant son nom, par exemple [np.mean, 'sum'] et,
  • Un dictionnaire permettant d’indiquer un nom de colonne et la fonction à appliquer et,
  • Des indications sur le nom de la colonne résultante, la colonne du dataframe où la fonction s’applique et la fonction à appliquer.

On peut utiliser les mêmes fonctions d’agrégation que celles indiquées plus haut:

  • np.size ou 'size' pour préciser le nombre de lignes par groupe.
  • np.sum ou 'sum' pour sommer les valeurs des groupes. Les colonnes où une somme n’est pas possible seront ignorées.
  • np.min ou 'min' pour indiquer le minimum des valeurs groupées.
  • np.max ou 'max' pour indiquer le maximum des valeurs groupées.
  • 'describe' pour générer des statistiques descriptives.
  • 'count' pour compter le nombre de groupes.
  • 'first' pour renvoyer la 1ère ligne de chaque groupe.
  • 'last' pour renvoyer la dernière ligne de chaque groupe.
  • np.std ou 'std' pour renvoyer l’écart-type (i.e. standard déviation) pour chaque groupe.
  • np.var ou 'var' pour renvoyer la variance pour chaque groupe.
  • 'sem' pour renvoyer l’erreur type de la moyenne pour chaque groupe.

Par exemple, si on considère le dataframe suivant:

>>> df = pd.DataFrame([['1', 'A', 10, np.NaN], ['2', 'B', 15, 3], ['1', 'B', 5, 1], ['2', 'D', 35, np.NaN], ['2', 'A', 25, 3]], 
                  columns = ['a', 'b', 'c', 'd'])
>>> print(df)
   a  b   c    d
0  1  A  10  NaN
1  2  B  15  3.0
2  1  B   5  1.0
3  2  D  35  NaN
4  2  A  25  3.0

On peut effectuer une agrégation sur une colonne et effectuer la somme du contenu des autres colonnes en indiquant directement la fonction à exécuter:

>>> print(df.groupby(['a']).agg(np.sum))
    c    d
a         
1  15  1.0
2  75  6.0

En indiquant le nom de la fonction:

>>> print(df.groupby(['a']).agg('sum'))
    c    d
a         
1  15  1.0
2  75  6.0

En indiquant plusieurs fonctions à exécuter dans une liste (les fonctions s’appliquent à toutes les colonnes):

>>> print(df.groupby(['a']).agg([np.mean, 'sum']))
      c        d     
   mean sum mean  sum
a                    
1   7.5  15  1.0  1.0
2  25.0  75  3.0  6.0

En utilisant un dictionnaire, on peut indiquer la colonne sur laquelle on souhaite appliquer une fonction:

>>> print(df.groupby(['a']).agg({'c': np.mean, 'd': 'sum'}))
      c    d
a           
1   7.5  1.0
2  25.0  6.0

On peut créer de nouvelles colonnes en indiquant le nom de la colonne résultante, la colonne du dataframe où la fonction s’applique et la fonction à appliquer:

>>> print(df.groupby(['a']).agg(moyenne_c = ('c', np.mean), sum_d = ('d', 'sum')))
   moyenne_c  sum_d
a                  
1        7.5    1.0
2       25.0    6.0

Fonctions mathématiques

Pour les fonctions suivantes, l’opération est appliquée, par défaut, pour chaque colonne. Le résultat est renvoyé sous la forme d’une série.
Pour que l’opération soit effectuée suivant les lignes, il faut utiliser l’argument axis = 1.

Quelques fonctions:

Fonction Explication
<dataframe>.mean() Moyenne des valeurs de chaque colonne.
<dataframe>.median() Moyenne médiane des valeurs de chaque colonne.
<dataframe>.min() Minimum pour chaque colonne.
<dataframe>.max() Maximum pour chaque colonne.
<dataframe>.std() Ecart-type pour chaque colonne.
<dataframe>.var() Variance pour chaque colonne.
<dataframe>.idxmax() Index du maximum de chaque colonne.
<dataframe>.idxmin() Index du minimim de chaque colonne.
<dataframe>.transpose() ou <dataframe>.T Transposée du dataframe.
<dataframe>.round(<nombre de décimales>) Arrondi de toutes les valeurs du dataframe.
<dataframe>.abs() Valeur absolue des valeurs numériques.

Opérations sur les dataframes

Lorsque qu’une opération parmi les opérations suivantes est appliquée sur des dataframes pandas, les opérations ne sont pas appliquées au sens mathématique comme s’il s’agissait de matrices: l’opération est appliquée sur chaque élément du dataframe. Dans le cas où 2 dataframes sont concernés, les calculs sont appliqués entre les éléments des dataframes situés à la même position.

Par exemple si on considère 2 dataframes:

>>> df1 = pd.DataFrame([[1, 2, 3, 4], [10, 20, 30, 40], [100, 200, 300, 400], [1000, 2000, 3000, 4000]])
>>> print(df1)
     0     1     2     3
0    1     2     3     4
1   10    20    30    40
2  100   200   300   400
3 1000  2000  3000  4000
     
>>> df2 = pd.DataFrame([[1, 2, 3, 4], [11, 21, 31, 41], [101, 201, 301, 401], [1001, 2001, 3001, 4001]])
>>> print(df2)
      0     1     2     3
0     1     2     3     4
1    10    20    30    40
2   100   200   300   400
3  1000  2000  3000  4000

Si on applique l’opération df1 * df2, on peut voir qu’il ne s’agit pas d’une multiplication de matrice:

>>> print(df1 * df2)
         0        1        2         3
0        1        4        9        16
1      110      420      930      1640
2    10100    40200    90300    160400
3  1001000  4002000  9003000  16004000

Les opérations suivantes sont applicables:

  • Les opérations arithmétiques classiques:
    • + ou <dataframe1>.add(<dataframe2>),
    • - ou <dataframe1>.sub(<dataframe2>),
    • / ou <dataframe1>.div(<dataframe2>),
    • * ou <dataframe1>.mul(<dataframe2>),
    • // pour la division entière et
    • % ou <dataframe1>.mod(<opérande>) pour le reste de la division entière.
  • <dataframe> ** 2 ou <dataframe>.pow(2) pour le carré
  • Les opérations booléennes en utilisant:
    • & pour “and”,
    • | pour “ou”,
    • ~ ou – pour “not”,
    • ^ pour le “ou exclusif”.
  • Les opérateurs de comparaison:
    • == ou <dataframe1>.eq(<dataframe2>) pour tester l’égalité,
    • != ou <dataframe1>.ne(<dataframe2>) pour tester l’inégalité,
    • < ou <dataframe1>.lt(<dataframe2>),
    • > ou <dataframe1>.gt(<dataframe2>),
    • <= ou <dataframe1>.le(<dataframe2>),
    • >= ou <dataframe1>.ge(<dataframe2>).
ne pas confondre eq() et equals()

eq() compare tous les éléments de 2 dataframes et renvoie un dataframe de même taille avec des booléens résultant de la comparaison de tous les éléments.
equals() compare tous les éléments des 2 dataframes et ne renvoient qu’un seul booléen qui est True si tous les éléments des dataframes sont égaux.

Si on prend les dataframes précédents:

>>> print(df1.eq(df2))
       0      1      2      3
0   True   True   True   True
1  False  False  False  False
2  False  False  False  False
3  False  False  False  False

>>> df1.equals(df2)
False

Opérations entre un dataframe et un autre type d’objet

Les opérations ne sont pas exclusivement réservées aux dataframes entre eux. On peut appliquer des opérations entre:

  • Un dataframe et un scalaire: l’opération avec le scalaire est appliquée sur tous les éléments.

    Par exemple:

    >>> df1 = pd.DataFrame([[1, 2, 3], [10, 20, 30], [100, 200, 300]], index = ['a', 'b', 'c'])
    >>> print(df1 * 3)
         0    1    2
    a    3    6    9
    b   30   60   90
    c  300  600  900
    
  • Un dataframe et une liste: la taille de la liste doit être de la même taille que la dimension du dataframe sur laquelle l’opération est appliquée. L’opération est appliquée sur toutes les lignes ou toutes les colonnes suivant le sens d’application de l’opération.

    Par exemple:

    >>> print(df1 + [5, 15, 25])
         0    1    2
    a    6   17   28
    b   15   35   55
    c  105  215  325
    

    Dans le cas précédent, l’addition des éléments de la liste est appliquée sur toutes les lignes.
    Le résultat est le même en exécutant df1.add([5, 15, 25], axis=1) ou df1.add([5, 15, 25], axis='columns').

    Dans le cas suivant, l’opération avec la liste est appliquée sur toutes les colonnes:

    >>> print(df1.add([5, 15, 25], axis=0)) 
         0    1    2
    a    6    7    8
    b   25   35   45
    c  125  225  325
    

    Une autre syntaxe équivalente est: df1.add([5, 15, 25], axis='index').

  • Un dataframe et une série pandas: l’opération est appliquée sur les labels communs. Si des labels ne sont pas communs, la valeur résultante est NaN.

    Par exemple:

    >>> s = pd.Series([5, 15, 25]
    >>> print(df1 + s)
         0    1    2
    a    6   17   28
    b   15   35   55
    c  105  215  325
    

    L’opération est appliquée dans le sens des lignes. Les labels utilisés sont les labels par défaut.

    >>> print(df1 + pd.Series([5, 15])
           0      1   2
    a    6.0   17.0 NaN
    b   15.0   35.0 NaN
    c  105.0  215.0 NaN
    

    La série ne possède pas le label 2 donc la colonne 2 n’est pas commune aux 2 objets et la valeur affectée est NaN.

    Dans l’exemple suivant, l’opération est appliquée en colonne:

    >>> print(df1.add(pd.Series([5, 15, 25], index=['a', 'b', 'c']), axis=0))
         0    1    2
    a    6    7    8
    b   25   35   45
    c  125  225  325
    

    Il faut indiquer les mêmes labels que le dataframe pour que l’opération soit possible.

  • Un dataframe et un dictionnaire: l’opération est appliquée pour les éléments du dictionnaire pour lesquels la clé correspond à un index du dataframe.

    Par exemple, dans le cas suivant l’opération est appliquée dans le sens des lignes:

    >>> d = {0: 5, 1: 15, 2: 25}
    >>> print(df1 + d)
         0    1    2
    a    6   17   28
    b   15   35   55
    c  105  215  325
    

    Dans le cas suivant, l’opération est appliquée dans le sens des colonnes.

    >>> d = {'a': 5, 'b': 15, 'c': 25}
    >>> print(df1.add(d, axis=0))
         0    1    2
    a    6    7    8
    b   25   35   45
    c  125  225  325
    

Argument fill_value

On peut utiliser l’argument fill_value pour indiquer la valeur à appliquer lorsque les index qui ne sont pas communs.

Par exemple:

>>> df1 = pd.DataFrame([[1, 2, 3], [10, 20, 30], [100, 200, 300]], index = ['a', 'b', 'c'])
>>> df2 = pd.DataFrame([[1, 2, 3], [11, 21, 31], [101, 201, 301]], index = ['a', 'b', 'd'])
>>> print(df1.add(df2))
      0     1     2
a   2.0   4.0   6.0
b  21.0  41.0  61.0
c   NaN   NaN   NaN
d   NaN   NaN   NaN

La valeur NaN est appliquée lorsque les index ne sont pas communs.

>>> print(df1.add(df2, fill_value=0))
       0      1      2
a    2.0    4.0    6.0
b   21.0   41.0   61.0
c  100.0  200.0  300.0
d  101.0  201.0  301.0

Les index 'c' et 'd' ne sont pas communs entre les 2 dataframes. La valeur 0 est utilisée lorsque les index ne sont pas communs.

Opérations avec des index ou des colonnes non communs

Les opérations sont appliquées si les labels des dataframes sont les mêmes. Dans les exemples suivants, les labels utilisés sont les labels par défaut. Si certains labels ne sont pas égaux alors le résultat est NaN pour les éléments ayant des labels différents.

Par exemple si on considère les dataframes suivants:

>>> df1 = pd.DataFrame([[1, 2, 3], [10, 20, 30], [100, 200, 300]], columns = ['a', 'b', 'c'])
>>> df2 = pd.DataFrame([[1, 2, 3], [11, 21, 31], [101, 201, 301]], columns = ['a', 'b', 'd'])
>>> print(df1 + df2)
     a    b   c   d
0    2    4 NaN NaN
1   21   41 NaN NaN
2  201  401 NaN NaN

Les colonnes non communes contiennent NaN.

Jointures

Il est possible d’utiliser la fonction merge() pour effectuer des jointures entre 2 dataframes ayant au moins une colonne en commun et obtenir un dataframe avec le résultat de cette jointure.

Par exemple:

>>> df1 = pd.DataFrame({'id': [2, 1, 5], 'value1': [100, 165, 628]}); 
>>> print(df1)
   id  value1
0   2     100
1   1     165
2   5     628

>>> df2 = pd.DataFrame({'id': [1, 2, 8, 10], 'value2': [92, 27, 87, 43]});
>>> print(df2)
   id  value2
0   1      92
1   2      27
2   8      87
3  10      43

>>> print(df1.merge(df2))
   id  value1  value2
0   2     100      27
1   1     165      92

La jointure est effectuée sur la colonne id puisque c’est la seule colonne commune. Les valeurs 1 et 2 dans cette colonne sont communes.
merge() peut être utilisé avec les options suivantes:

  • on: indique la colonne avec laquelle la jointure sera effectuée. Cette option est utile si plusieurs colonnes sont communes.

    Par exemple:

    >>> df1 = pd.DataFrame({'id': [2, 1, 5], 'id2': ['a', 'b', 'c'], 'value1': [100, 165, 628]}); 
    >>> print(df1)
       id id2  value1
    0   2   a     100
    1   1   b     165
    2   5   c     628
    
    >>> df2 = pd.DataFrame({'id': [1, 2, 8, 10], 'id2': ['c', 'e', 'd', 'a'], 'value2': [92, 27, 87, 43]});
    >>> print(df2)
       id id2  value2
    0   1   c      92
    1   2   e      27
    2   8   d      87
    3  10   a      43
    
    >>> print(df1.merge(df2, on='id2'))
       id_x id2  value1  id_y  value2
    0     2   a     100    10      43
    1     5   c     628     1      92
    
  • left_on et right_on: ces arguments permettent d’indiquer les colonnes sur lesquelles la jointure doit être effectuée respectivement pour le 1er (avant merge()) ou le 2e dataframe (fourni en argument de merge()).

    Par exemple, si on considère ces dataframes n’ayant pas de colonnes communes:

    >>> df1 = pd.DataFrame({'id_1': [2, 1, 5], 'value1': [100, 165, 628]}); 
    >>> print(df1)
       id_1  value1
    0     2     100
    1     1     165
    2     5     628
    
    >>> df2 = pd.DataFrame({'id_2': [1, 2, 8, 10], 'value2': [92, 27, 87, 43]});
    >>> print(df2)
       id_2  value2
    0     1      92
    1     2      27
    2     8      87
    3    10      43
    

    Si on effectue la jointure sur les colonnes id_1 et id_2:

    >>> print(df1.merge(df2, left_on='id_1', right_on='id_2'))
       id_1  value1  id_2  value2
    0     2     100     2      27
    1     1     165     1      92
    
  • left_index et right_index: ces arguments permettent d’effectuer une jointure sur les index plutôt que sur les colonnes. La jointure est effectuée pour le 1er dataframe (avant merge()) avec left_index et pour le 2e dataframe (fourni en argument de merge()) avec right_index.
  • how: cet argument permet d’imiter le comportement d’une jointure ouverte “outer join”, “left outer join” ou “right outer join”. Par défaut, la valeur est 'inner' pour jointure fermée. Les autres valeurs possibles:
    • 'outer' pour une jointure ouverte. Les entrées ayant des colonnes ou des index communs entre les 2 dataframes sont retournées. A ces résultats se rajoutent aussi les entrées qui ne sont pas communes entre les 2 dataframes.
    • 'left' pour une jointure ouverte par la gauche. Les entrées ayant des colonnes ou des index communs entre les 2 dataframes sont retournées. A ces résultats se rajoutent les entrées du 1er dataframe qui ne sont pas communs avec le 2e dataframe.
    • 'right' pour une jointure ouverte par la droite. Les entrées ayant des colonnes ou des index communs entre les 2 dataframes sont retournées. A ces résultats se rajoutent les entrées du 2e dataframe qui ne sont pas communs avec le 1er dataframe.
    • 'cross' pour effectuer un produit cartésien sur les entrées ayant des colonnes ou des index communs entre les 2 dataframes.
  • indicator: permet de rajouter une colonne nommée _merge permettant d’indiquer d’où provient l’entrée.

    Par exemple, si on considère les dataframes et la jointure suivants:

    >>> df1 = pd.DataFrame({'id': [2, 1, 5], 'value1': [100, 165, 628]}); 
    >>> df2 = pd.DataFrame({'id': [1, 2, 8, 10], 'value2': [92, 27, 87, 43]});
    >>> print(df1.merge(df2, how='outer', indicator=True))
       id  value1  value2      _merge
    0   2   100.0    27.0        both
    1   1   165.0    92.0        both
    2   5   628.0     NaN   left_only
    3   8     NaN    87.0  right_only
    4  10     NaN    43.0  right_only
    

Concaténation

On peut effectuer des concaténations de dataframes ou de séries avec la fonction pd.concat([<dataframes ou series>]).

Par exemple, si on considère les dataframes suivants:

>>> df1 = pd.DataFrame([[1, 2, 3], [10, 20, 30]])
>>> df2 = pd.DataFrame([[11, 21, 31], [100, 200, 300]]) 
>>> print(pd.concat([df1, df2]))
     0    1    2
0    1    2    3
1   10   20   30
0   11   21   31
1  100  200  300

On peut remarquer que les colonnes sont les mêmes entre les 2 dataframes (colonnes implicites). Les index sont réutilisés.
Dans le cas où des colonnes ne sont pas communes, les valeurs sont complétées avec des NaN:

>>> df1 = pd.DataFrame([[1, 2, 3], [10, 20, 30]], columns=['a', 'b', 'c'])
>>> df2 = pd.DataFrame([[11, 21, 31], [100, 200, 300]], columns=['a', 'b', 'd'])
>>> print(pd.concat([df1, df2]))
     a    b     c      d
0    1    2   3.0    NaN
1   10   20  30.0    NaN
0   11   21   NaN   31.0
1  100  200   NaN  300.0

On peut concaténer une série à un dataframe:

>>> s = pd.Series([100, 200])
>>> print(pd.concat([df1, s], axis=1))
    0   1   2    0
0   1   2   3  100
1  10  20  30  200

On peut utiliser les arguments suivants pour modifier le comportement. Pour la suite, on définit les dataframes suivants:

>>> df1 = pd.DataFrame([[1, 2, 3], [10, 20, 30]])
>>> print(df1)
    0   1   2
0   1   2   3
1  10  20  30

>>> df2 = pd.DataFrame([[11, 21, 31], [100, 200, 300]])
>>> print(df2)
     0    1    2
0   11   21   31
1  100  200  300
  • ignore_index: permet d’ignorer les index d’origine des dataframes pour utiliser des index implicites.
    Si on considère les dataframes suivants:

    >>> print(pd.concat([df1, df2], ignore_index=True))
         0    1    2
    0    1    2    3
    1   10   20   30
    2   11   21   31
    3  100  200  300
    
  • axis: permet d’indiquer l’axe sur lequel la concaténation est effectuée: 0 pour index (valeur par défaut) et 1 pour colonne.

    Par exemple:

    >>> print(pd.concat([df1, df2], axis=1))
        0   1   2    0    1    2
    0   1   2   3   11   21   31
    1  10  20  30  100  200  300
    

    Les labels de colonnes sont maintenus. On peut utiliser ignore_index pour recréer des labels de colonnes implicites:

    >>> print(pd.concat([df1, df2], axis=1, ignore_index=True))
        0   1   2    3    4    5
    0   1   2   3   11   21   31
    1  10  20  30  100  200  300
    
  • keys: permet de rajouter un niveau dans les index du dataframe de sortie correspondant permettant d’indiquer le dataframe de départ.
    >>> print(pd.concat([df1, df2], keys=['a', 'b']))
           0    1    2
    a 0    1    2    3
      1   10   20   30
    b 0   11   21   31
      1  100  200  300
    
  • join: permet d’indiquer le comportement lorsque les colonnes ou les index ne sont pas communs. Par défaut, la valeur est 'outer' pour indiquer les index ou les colonnes (suivant le sens de la concaténation) sont ajoutés dans le dataframe en sortie même s’ils ne sont pas communs.
    Si la valeur est 'inner' alors seuls les colonnes ou index communs seront pris en compte dans le dataframe en sortie.

    Par exemple, si on considère les dataframes suivants:

    >>> df3 = pd.DataFrame([[1, 2, 3], [10, 20, 30]], columns=['a', 'b', 'c'])
    >>> print(df3)
        a   b   c
    0   1   2   3
    1  10  20  30
    
    >>> df4 = pd.DataFrame([[11, 21, 31], [100, 200, 300]], columns=['a', 'b', 'd'])
    >>> print(df4)
         a    b    d
    0   11   21   31
    1  100  200  300
    

    On peut voir la différence de comportement avec l’option join='inner':

    >>> print(pd.concat([df3, df4]))
         a    b     c      d
    0    1    2   3.0    NaN
    1   10   20  30.0    NaN
    0   11   21   NaN   31.0
    1  100  200   NaN  300.0
    
    
    >>> print(pd.concat([df3, df4], join='inner'))
         a    b
    0    1    2
    1   10   20
    0   11   21
    1  100  200
    

Leave a Reply