Cet article fait partie d’une série d’articles sur la syntaxe de base Python.
Une série pandas est une liste mutable d’objets dont les index peuvent être personnalisés. Le type des objets n’est pas forcément le même.
Initialisation
Indiquer explicitement le type des valeurs (argument dtype)
Initialiser sans effectuer de copies (argument copy)
Indiquer explicitement des index (argument index)
Accéder à une valeur à partir de l’index
Tester l’existence d’un index et d’une valeur
Tester l’existence d’un index
Tester l’existence d’une valeur
Itération sur les éléments de la structure
Opérations sur les séries pandas
Si les tailles des séries ne sont pas les mêmes
Si les index ne sont pas les mêmes
Fonctions particulières
Quelques fonctions utiles
<série>.str
Les séries pandas permettent de stocker tout type d’objets. L’intérêt de cette structure est l’utilisation d’index personnalisables permettant un accès performant aux objets. Les séries pandas ne permettent de stocker des objets que suivant une dimension. Pour stocker suivant 2 dimensions, il faut utiliser des dataframes.
Pandas peut être importé de cette façon pour utiliser les objets dans la bibliothèque:
import pandas as pd
Les séries pandas sont mutables c’est-à-dire qu’on peut modifier la valeur des éléments après instanciation.
Initialisation
On peut initialiser une série pandas à partir d’un tableau Python ou d’un tableau numpy.
Par exemple, à partir d’un tableau Python:
>>> a = pd.Series([1, 4, 5, 8]) >>> a 0 1 1 4 2 5 3 8 dtype: int64
On peut voir les valeurs ainsi que les index correspondant. Comme les index n’ont pas été précisés à l’initialisation, ce sont des index par défaut qui sont utilisés.
Pour initialiser une série pandas à partir d’un tableau numpy:
>>> a = np.array([1, 4, 5, 8]) >>> b = pd.Series(a)
Sans précision sur le type des éléments, pandas déduit le type des objets dans le cas où les objets ont le même type et sont de type float
, int
et bool
sinon c’est le type object
qui sera affecté:
>>> a = np.array(['1', '4', '5', '8']) >>> a 0 1 1 4 2 5 3 8 dtype: object
De même si les types des éléments sont différents alors le type affecté sera object
.
Indiquer explicitement le type des valeurs (argument dtype)
Pandas reconnait les types numpy donc la même syntaxe que numpy peut être utilisée, par exemple:
>>> a = pd.Series([1, 4, 5, 8], dtype='i8') >>> a 0 1 1 4 2 5 3 8 dtype: int64
La syntaxe plus haut est équivalente à:
>>> a = pd.Series([1, 4, 5, 8], dtype=np.int8)
A condition d’avoir importé la bibliothèque numpy avec:
import numpy as np
Initialiser sans effectuer de copies (argument copy)
Par défaut, quand une série pandas est initialisée à partir d’un tableau Python ou numpy, une copie des éléments est effectuée. Il est possible d’effectuer une initialisation de la série en utilisant des références vers les objets de la structure d’origine avec l’argument copy
:
>>> a = np.array([1, 4, 5, 8]) >>> b = pd.Series(a, copy=False) >>> a array([1, 4, 5, 8]) >>> b[2]=1000 >>> a array([ 1, 4, 1000, 8])
L’initialisation de la série pandas étant faite avec des références, si on modifie une valeur dans la série alors les éléments dans la structure d’origine sont aussi modifiés.
Indiquer explicitement des index (argument index)
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. Le type indiqué de l’objet doit être un tableau de même taille que la liste des valeurs:
>>> i = range(4, 8) >>> list(i) [4, 5, 6, 7] >>> a = pd.Series([1, 4, 5, 8], index=i) >>> a 4 1 5 4 6 5 7 8 dtype: int64
Pour créer plus directement une série:
>>> a = pd.Series([1, 4, 5, 8], range(4, 8))
Si la série contient la même valeur:
>>> a = pd.Series(5, range(4)) >>> a 0 5 1 5 2 5 3 5 dtype: int64
On peut affecter un index particulier après initialisation avec la propriété <série>.index
. Il faut que la taille du tableau de l’index soit la même que celle de la série.
Par exemple:
>>> a = pd.Series([1, 4, 5, 8]) >>> a.index = ['a', 'b', 'c', 'd'] >>> a a 1 b 4 c 5 d 8 dtype: int64
Accéder à une valeur à partir de l’index
Pour atteindre une valeur particulière, il suffit d’utiliser l’index:
>>> a = pd.Series([1, 4, 5, 8]) >>> a[2] 5
Si l’index n’existe pas, une exception est levée:
>>> a[5] KeyError: 5
Si on considère la série suivante:
>>> a = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> a a 1 b 4 c 5 d 8 dtype: int64
Il existe d’autres méthodes pour obtenir une valeur dans une série:
<série>.at[<index>]
: par exemple>>> a.at['c'] 5
Une exception est levée si l’index n’existe pas.
<série>.loc[<index>]
:>>> a.loc['c'] 5
Une exception est levée si l’index n’existe pas.
<série>.get(<index>)
:>>> a.get('c') 5
Si l’index n’existe pas,
None
est renvoyé.
Ces 3 syntaxes sont équivalentes.
Même si un index personnalisé est utilisé (différent d’un entier à partir de 0
), on peut accéder aux valeurs en utilisant l’index par défaut avec les syntaxes:
<série>[<index numérique>]
:>>> a[1] 4
Une exception est levée si l’index n’existe pas.
<série>.iat[<index numérique>]
:>>> a.iat[1] 4
Une exception est levée si l’index n’existe pas.
<série>.iloc[<index numérique>]
:>>> a.iat[1] 4
Une exception est levée si l’index n’existe pas.
<série>.get(<index numérique>)
:>>> a.get(1) 4
Si l’index n’existe pas,
None
est renvoyé.
Les syntaxes <série>[<index numérique>]
et <série>.get(<index numérique>)
sont sources d’ambiguïtés car:
- Si l’index existe alors elles renvoient la valeur correspondant à l’index sinon
- Si l’index n’existe pas, elles renvoient la valeur correspondant à l’index numérique.
Si on utilise ces syntaxes, il faut donc s’assurer du type d’index qu’on manipule.
Les autres syntaxes ne sont pas concernées par ces problèmes d’ambiguïté.
Par exemple, si on considère les séries suivantes:
>>> i1 = list(range(3, -1, -1)) >>> a = pd.Series([1, 4, 5, 8], index=i1) >>> a 3 1 2 4 1 5 0 8 dtype: int64 >>> b = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> b a 1 b 4 c 5 d 8 dtype: int64 >>> a[0] 8
0
existe en tant qu’index, la valeur renvoyée est la dernière valeur de la série.
>>> b[0]
1
0
n’existe pas en tant qu’index donc la valeur renvoyée correspond à l’index numérique 0
.
Sous-série et slicing
On peut extraire des sous-séries à partir d’une série existante en indiquant explicitement les index numériques à extraire ou en utilisant la syntaxe de slicing.
Par exemple, si on considère la série suivante:
>>> a = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> a a 1 b 4 c 5 d 8 dtype: int64
Pour extraire une sous-série en indiquant explicitement les index numériques à extraire:
>>> b = a[['b', 'd', 'c']] >>> b b 4 d 8 c 5 dtype: int64
On peut utiliser les règles de slicing habituelles en utilisant les index numériques, par exemple:
>>> c = a[1:3] >>> c b 4 c 5 dtype: int64
Enfin, iloc[]
peut être utilisé pour extraire la sous-série en utilisant les index numériques. iloc[]
évite les ambiguïtés décrites plus haut puisqu’il ne traite que les index numériques:
>>> a.iloc[1:3] b 4 c 5 dtype: int64 >>> a.iloc[[1,3]] b 4 d 8 dtype: int64
Suivant la façon dont la sous-série est extraite, il peut s’agir d’une copie ou d’une référence vers la série d’origine. Dans le cas de références, les modifications dans la sous-série entraînent des modifications dans la série d’origine:
- Si on indique explicitement les index de la série, la sous-série est une copie.
- Si on utilise la syntaxe de slicing sur les index numériques, la sous-série contient des références.
Par exemple, si on considère la série suivante:
>>> a = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> a a 1 b 4 c 5 d 8 dtype: int64
Si on extrait une sous-série en indiquant explicitement les index de la série d’origine:
>>> b = a[['a', 'b']] >>> b[0] = 1000 >>> b a 1000 b 4 dtype: int64 >>> a a 1 b 4 c 5 d 8 dtype: int64
La série d’origine n’est pas modifiée.
Si on effectue un slicing avec les index numériques:
>>> c = a[1:3] >>> c[0] = 1000 >>> c b 1000 c 5 dtype: int64 >>> a a 1 b 1000 c 5 d 8 dtype: int64
La série d’origine est modifiée.
Pour éviter de modifier la structure d’origine, on peut effectuer une copie avec copy()
:
c = a[1:3].copy()
Changement de type
Il est possible de changer le type des éléments d’une série en appliquant la fonction <série>.astype(<nouveau type>)
. La série résultante contient les mêmes index que la série d’origine. Pour indiquer le type, on peut utiliser la même syntaxe qu’à l’initialisation.
Par exemple:
>>> a = pd.Series(['5', '4', '3', '2', '1']) >>> a.astype(float) 0 5.0 1 4.0 2 3.0 3 2.0 4 1.0 dtype: float64
Par défaut, si le changement de type n’est pas possible, une erreur est renvoyée:
>>> a = pd.Series(['5', '4', '3', np.NaN, 'Oups', '1']) >>> a.astype(float) ValueError: could not convert string to float: 'Oups'
Cette erreur peut être ignorée en faisant:
>>> a.astype(float, errors='ignore')
0 5
1 4
2 3
3 NaN
4 Oups
5 1
dtype: object
En cas d’erreur, l’objet original est renvoyé.
Tester l’existence d’un index et d’une valeur
Tester l’existence d’un index
L’opérateur in
peut être utilisé pour tester l’existence d’un index dans une série pandas, par exemple:
>>> a = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> 'b' in a True >>> 'e' in a False
Si on utilise in
directement sur une série, on teste l’existence d’un index dans la série et non l’appartenance de la valeur aux valeurs de la série.
Tester l’existence d’une valeur
Pour tester l’appartenance d’une valeur aux valeurs de la série, il faut utiliser in
avec <série>.values
:
>>> 4 in a.values True >>> 7 in a.values False
Itération sur les éléments de la structure
On peut itérer directement parmi les valeurs d’une série avec une boucle “for“:
>>> a = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> for item in a: print(item) 1 4 5 8
La fonction <série>.iteritems()
peut être utilisée pour obtenir un itérable contenant pour chaque élément son index et sa valeur, par exemple:
>>> a = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> for item in a.iteritems(): print('Index: %s - valeur: %d' % item) Index: a - valeur: 1 Index: b - valeur: 4 Index: c - valeur: 5 Index: d - valeur: 8
Opérations sur les séries pandas
Des opérations mathématiques peuvent être appliquées directement sur des séries pandas.
Par exemple, si on considère la série:
>>> a = pd.Series([1, 4, 5, 8]) >>> a 0 1 1 4 2 5 3 8 dtype: int64 >>> 2*a 0 2 1 8 2 10 3 16 dtype: int64
On peut aussi appliquer des opérations entre 2 séries pandas mais contrairement aux tableaux numpy, il n’est pas obligatoire que les 2 séries soient de même dimension. Toutefois, il faut que les types des éléments des séries permettent l’application de l’opération.
Par exemple si on considère les séries suivantes:
>>> a = pd.Series([1, 4, 5, 8]) >>> b = pd.Series([5, 4, 3, 2]) >>> a+b 0 6 1 8 2 8 3 10 dtype: int64
L’opération est appliquée sur tous les éléments des séries en préservant le type de ces derniers.
Si le type n’est pas le même, il peut être modifié pour rendre l’opération possible, par exemple:
>>> a = pd.Series([1, 4, 5, 8]) >>> b = pd.Series([5.0, 4.0, 3.0, 2.0]) >>> a+b 0 5.0 1 4.0 2 3.0 3 2.0 dtype: float64
a
est une série contenant des entiers et b
contient des flottants, en appliquant l’opération les éléments de a
sont transformés en flottants pour rendre l’opération possible. Le résultat est une série de flottants.
La modification du type n’est pas tout le temps possible, par exemple si on considère une série de chaînes de caractères:
>>> c = pd.Series(['5', '4', '3', '2']) >>> a+c TypeError: unsupported operand type(s) for +: 'int' and 'str'
En revanche, si les éléments de 2 séries sont des chaînes de caractères alors l’opération est possible, le résultat est la concaténation des chaînes:
>>> d = pd.Series(['1', '4', '5', '8']) >>> c+d 0 51 1 44 2 35 3 28 dtype: object
Quand on applique l’opération *
, tous les éléments des séries sont multipliés:
>>> a = pd.Series([1, 4, 5, 8]) >>> b = pd.Series([5, 4, 3, 2]) >>> a*b 0 5 1 16 2 15 3 16 dtype: int64
Si les tailles des séries ne sont pas les mêmes
Si les tailles des séries ne sont pas identiques, l’opération est quand même appliquée toutefois quand il n’existe pas d’éléments dans une série permettant l’opération, la valeur résultante est NaN
:
>>> a = pd.Series([1, 4, 5, 8]) >>> b = pd.Series([5, 4, 3, 2, 1]) >>> a+b 0 6.0 1 8.0 2 8.0 3 10.0 4 NaN dtype: float64
Dans ce cas, la 5e valeur est NaN
et les valeurs sont transformées en flottants à cause de la valeur manquante dans a
.
Si les index ne sont pas les mêmes
Dans le cas où les index des séries ne sont pas les mêmes:
- Pour les index communs: l’opération est appliquée.
- Pour les index qui ne sont pas communs: le résultat de l’opération est
NaN
.
Par exemple:
>>> a = pd.Series([1, 4, 5, 8], ['a', 'b', 'c', 'd']) >>> b = pd.Series([5, 4, 3, 2], ['a', 'e', 'c', 'f']) >>> a+b a 6.0 b NaN c 8.0 d NaN e NaN f NaN dtype: float64
La fonction dropna()
peut être utilisée pour supprimer les valeurs NaN
:
>>> c = a+b >>> c.dropna() a 6.0 c 8.0 dtype: float64
Pour résumer, on peut appliquer les opérations comme:
+
,-
,/
ou*
. Ces opérations sont appliquées sur les éléments des tableaux avec le même index. Pour les éléments dont les index ne sont pas les mêmes, le résultat estNaN
. On peut s’aider dedropna()
pour supprimer les éléments dont la valeur estNaN
.- Les opérations booléennes entre séries pandas peuvent être effectuées en utilisant:
&
pour “and“,|
pour “ou“,~
pour “not“,^
pour le “ou exclusif“.
- Appliquer des opérateurs de comparaison comme
==
,<
,>
,<=
,>=
et!=
.
Fonctions particulières
Quelques fonctions utiles
On peut appliquer les fonctions mathématiques numpy à une série pandas. Le résultat est une série.
Par exemple si on importe numpy avec import numpy as np
:
>>> a = pd.Series([1, 4, 5, 8]) >>> np.log(a) 0 0.000000 1 1.386294 2 1.609438 3 2.079442 dtype: float64
De la même façon, les fonctions suivants peuvent être appliquées:
np.add(a, b)
;np.subtract(a, b)
;np.divide(a, b)
ounp.multiply(a, b)
pour respectivement ajouter, soustraire, diviser ou multiplier les éléments de séries pandas.np.sum(a)
ounp.prod(a)
pour respectivement ajouter ou multiplier tous les éléments d’une série.np.floor(a)
,np.ceil(a)
ounp.trunc(a)
pour effectuer des arrondis ou troncatures sur les éléments d’une série.np.amin(a)
,np.amax(a)
pour obtenir le minimum ou maximum parmi les éléments de la série.np.argmin(a)
,np.argmax(a)
pour obtenir l’index du minimum ou du maximum des éléments de la série.np.mean()
pour obtenir la moyenne des éléments de la série.
Une liste plus exhaustive des opérations numpy possibles peut être retrouvée sur: numpy.org/doc/stable/reference/routines.math.html.
D’autres fonctions permettent d’éviter d’itérer sur les éléments de la série:
<série>.index
permet d’obtenir un itérable (de typeRangeIndex
ouIndex
) contenant les index de la série.<série>.values
pour obtenir un tableau numpy contenant les valeurs de la série.<série>.unique
pour obtenir un tableau numpy contenant les valeurs uniques de la série.<série>.value_counts()
permet d’obtenir une série avec les mêmes index que la série d’origine et le nombre d’occurence pour chaque valeur.<série>.isna()
ou<série>.isnull()
renvoie une série avec les mêmes index que la série d’origine et des booléens pour indiquer si les valeurs correspondantes sontNaN
.<série>.inotna()
ou<série>.notnull()
renvoie une série avec les mêmes index que la série d’origine et des booléens pour indiquer si les valeurs correspondantes ne sont pas égales àNaN
.pd.isnull(<série>)
renvoie une série dont les index sont les mêmes que la série d’origine et dont les valeurs sontTrue
si les valeurs correspondantes sont égales àNaN
ouNone
.pd.notnull(<série>)
renvoie une série dont les index sont les mêmes que la série d’origine et dont les valeurs sontTrue
si les valeurs correspondantes ne sont pas égales àNaN
ouNone
.<série>.min()
,<série>.max()
,<série>.mean()
,<série>.median()
pour respectivement renvoyer le minimum, maximum, la moyenne et la moyenne médiane des valeurs de la série.<série>.all()
indique si toutes les valeurs de la série sont égales àTrue
au sens Truthy/Falsy (voir Truthy vs Falsy).<série>.any()
indique si au moins une valeur de la série est égale àTrue
au sens Truthy/Falsy.<série>.sort_index()
renvoie une série avec les index ordonnés.<série>.sort_values()
renvoie une série avec les valeurs ordonnées.<série>.apply(<fonction>)
renvoie une série où la fonction est exécutée pour toutes les valeurs.Par exemple avec une lambda:
>>> a = pd.Series([1, 4, 5, 8]) >>> a.apply(lambda x: 3 * x) a 3 b 12 c 15 d 24 dtype: int64
<série>.to_frame()
renvoie un dataframe avec une seule colonne contenant les valeurs de la série en ligne.
<série>.str
L’objet <série>.str
permet d’appliquer des traitements sur les éléments d’une série lorsque ce sont des chaînes de caractères.
Par exemple:
<série>.str.startswith(<chaine de caractères>)
: renvoie une série dont les index sont les mêmes que la série d’origine et dont les valeurs contiennentTrue
si la chaine de caractères correspondante commence par la chaîne donnée.<série>.str.len()
renvoie une série dont les index sont les mêmes que la série d’origine et dont les valeurs sont les longueurs des chaînes de caractères correspondantes.<série>.str.match(<regex>)
renvoie une série dont les valeurs sont True si la valeur correspondante dans la série d’origine satisfait la regex donnée.<série>.str.contains(<regex>)
renvoie une série dont les valeurs sontTrue
si la valeur correspondante dans la série d’origine contient une sous-chaine satisfaisant la regex donnée.<série>.str.contains(<chaîne de caractères>, regex=False)
renvoie une série dont les valeurs sontTrue
si la valeur correspondante dans la série d’origine contient une sous-chaine donnée.<série>.str.find(<chaine>)
renvoie une série dont les valeurs sont les index dans la chaîne de caractère de la 1ère occurence de la chaine donnée. Si la chaîne ne contient pas la chaine donnée, la valeur retournée est-1
.<série>.str.get(<index>)
renvoie une série dont les valeurs contiennent le caractère correspondant à l’index donnée dans la chaîne de caractères correspondante.<série>.str.slice(<index début>, <nombre de caractères>)
renvoie une série dont les valeurs sont des sous-chaînes de la chaine correspondante dans la série d’origine.<série>.str[<argument slicing>]
renvoie une série dont les valeurs proviennent d’un slicing appliquée sur la chaîne correspondante dans la série d’origine.<série>.str.count(<regex>)
renvoie une série dont les valeurs contiennent le nombre d’ocurrences de la regex dans la chaîne correspondante dans la série d’origine.<série>.str.replace(<regex>, <chaine de remplacement>)
renvoie une série dont les valeurs contiennent un remplacement des chaines d’origine suivant la regex.