Files
training.python.datascience/documentation/01.4-pandas-series.md
Steve Kossouho d06fd5414d Rename documentation files
Removed new- prefix.
Added old- prefix to old files.
2025-07-12 17:03:38 +02:00

19 KiB
Raw Permalink Blame History

title, author
title author
Pandas Steve Kossouho

Manipuler et comprendre les séries


Propriétés d'une série

Une Series dans Pandas est un objet contenant une séquence de valeurs. La série tout entière possède un type unique (voir les types à la fin du chapitre précédent) et tous les éléments de cette structure sont d'un type cohérent avec celui de la série. Une série avec des éléments de types trop divers (ou avec des chaînes de caractères) sera de type object.

Une série peut également posséder un nom.

Exemple de série


Quand obtient-on une série ?

Un objet de type Series s'obtient soit manuellement, soit en extrayant précisément une colonne ou une ligne de données depuis des données tabulaires.


Types de données dans Pandas

Le stockage de données en mémoire avec Pandas proposera de manipuler des entrées, dans des types spécifiques à Pandas :

  • object : données de types divers, dont chaînes de caractères
  • int64/32/16 : nombres entiers (64 bits maximum)
  • Int64/32/16 : nombres entiers (avec prise en charge des nan)
  • float64 : nombres à virgule flottante (64 bits)
  • complex128 : même les nombres complexes (64 et 64 bits)
  • bool : valeurs booléennes
  • boolean : valeurs booléennes (avec prise en charge des nan)
  • datetime64[ns] : représentation d'une date
  • datetime64[ns, UTC] : représentation d'une date, avec décalage horaire (UTC)
  • timedelta64 : représentation d'un intervalle de temps
  • category: choix de valeurs textuelles

Les types timedelta64 et category sont rarement rencontrés.


Type de données d'une Series

Un objet Series possède toujours un type appliqué à tous ses éléments. Selon le type associé, des traitements différents sont possibles sur une série. Il est toujours possible de connaître le type d'une série en accédant à son attribut dtype (data type) :

import pandas as pd

series = pd.Series(data=[1, 2, 3, 4])
print(series.dtype)  # affiche "int64" car les données sont toutes compatibles

Créer une série

Nous avons vu que nous pouvons créer simplement des séries, et nous pouvons le faire en passant des informations variées :

  • Des tableaux Numpy à une dimension (voir sous-chapitre précédent)
  • Des listes ou des tuples de données Python (int{.python}, float{.python}, str{.python}, datetime.datetime{.python}…)
  • Des Series{.python} sont aussi acceptées

import numpy as np
import pandas as pd

# Créer une série avec une somme cumulative générée avec Numpy
series1 = pd.Series(data=np.cumsum([1, 2, 3, 4, 5, 6, 7, 8, 9]))

Exemple de série créée depuis un tableau Numpy (lui-même généré via des données Python).


import pandas as pd

# Créer une série avec des entiers, passés via des données standard Python
series2 = pd.Series(data=[1, 2, 3, 4, 5, 6, 7, 8, 9])  # liste
series3 = pd.Series(data=(1, 2, 3, 4, 5, 6, 7, 8, 9))  # tuple
series4 = pd.Series(data=series2)  # série Pandas (pas très utile)

Exemple de série créée depuis des listes ou tuples Python, ou encore des Series{.python}


Créer une série avec un index

Un index est un ensemble de valeurs associées à chaque élément d'une série pour l'identifier. Une série possède toujours un index, par défaut des nombres successifs commençant par zéro. Par exemple, si l'on considère la série suivante :

Structure d'une série et de l'index


Série

Les valeurs d'index "U1" et "U3", par exemple, sont associées respectivement aux valeurs "Alain" et "Gilles" de la série. Il sera également possible d'extraire la valeur "Alain" avec le code suivant :

import pandas as pd

s1 = pd.Series(data=["Alain", "Lucie", "Gilles", "André", "Zoé", "Paul"], index=["U1", "U2", "U3", "U4", "U5", "U6"])
print(s1["U1"])  # Affiche la valeur "Alain" en extrayant depuis l'index "U1"

Créer une série en forçant le type

Vous pouvez créer une série avec Pandas en précisant le type (dtype) à appliquer à ses valeurs; par exemple, vous pourriez définir votre série en passant uniquement des nombres entiers, et considérer que le type de la série devrait malgré tout être float64. Ou, plus intéressant, vous pouvez par exemple créer une série en passant des chaînes de caractères représentant des dates, et indiquer que ces chaînes doivent être converties vers le type datetime64[ns] (seul le format ISO 8601 des dates est interprété) :

import pandas as pd

s1 = pd.Series(data=["2023-01-02", "2024-03-17"])  # Une série de chaînes de caractères
s2 = pd.Series(data=["2023-01-02", "2024-03-17"], dtype="datetime64[ns]")  # Une série d'objets de type date
s3 = pd.Series(data=["2023-01-02", "2024-03-17"], dtype="float64")  # Une erreur va se produire : la conversion n'a pas de sens

Extraire des informations d'une série

Un objet de type Series dans Pandas possède toujours 3 informations distinctes :

  • Les valeurs stockées dans l'objet (attribut .values : ndarray{.python})
  • Le nom de l'objet (attribut s.name{.python}, peut être None{.python})
  • L'index de l'objet (par défaut une séquence de valeurs numériques, attribut s.index)

Note : On peut récupérer les valeurs de la série sous forme de tableau numpy via la méthode .to_numpy(){.python}.


Opérations sur les séries

Pandas propose d'effectuer des opérations diverses sur des séries, pour obtenir des valeurs scalaires, ou des séries. Par exemple, vous pouvez appliquer des opérations arithmétiques :

import pandas as pd

s1 = pd.Series(data=[1, 2, 3, 4], index=[1, 2, 3, 4], name="counter")
s2 = s1 * 2  # génère une série avec les valeurs 2, 4, 6, 8
s3 = s1 * s1  # génère une série avec les valeurs 1, 4, 9, 16
s4 = s1 + 4  # génère une série avec les valeurs 5, 6, 7, 8

Les séries dans Pandas prennent également en charge l'usage d'opérateurs de comparaison, qui permettent de récupérer des séries de valeurs booléennes. En voici quelques exemples d'illustration :

import pandas as pd

s1 = pd.Series(data=[1, 2, 3, 4], index=[6, 7, 8, 9], name="counter")
s2 = s1 > 2  # génère une série avec False, False, True, True
s3 = (s1 * 5) != 10  # génère une série avec True, False, True, True
print(4 in s1)  # vérifie si la valeur 4 fait partie... de l'index
print(3 in s1.values)  # vérifie si la valeur 3 fait partie... des données
print(s1.isin([1, 4]))  # Dit pour chaque valeur si elle fait partie des valeurs 1 et 4

Nous verrons que les séries contenant des valeurs booléennes pourront être utiles plus tard afin de filtrer des éléments de séries ou de dataframes.


Modifier les valeurs de séries

Les séries Pandas ne proposent généralement pas d'outils sous forme de méthodes pour en modifier le contenu. Pour autant, la méthode la plus simple, disponible en Python sur les objets de type list{.python} ou dict{.python}, fonctionnera très bien sur une série :

import pandas as pd

fruit = pd.Series(data=["apple", "watermelon", "grapefruit", "lemon"])
fruit[0] = "Orange"  # Remplacer la valeur à l'index 0
fruit["total"] = "Cocktail"  # Ajouter une valeur à un nouvel index

Valeurs vides

Si l'on considère un document Excel, par exemple, un document peut contenir des valeurs non renseignées. Les cellules desdites valeurs apparaissent naturellement vides. Dans Pandas, lorsqu'une série (ou un DataFrame) possède des cellules vides, la valeur qui y est contenue est une valeur spéciale nommée NaN (Not a number).

import pandas as pd
# Vous pouvez utiliser la valeur dans vos séries. Elle n'est disponible que dans Numpy.
from numpy import nan  # NaN non disponible depuis numpy 2.0

series_with_holes = pd.Series(data=["apple", "watermelon", nan, "grapefruit", nan]) 

Notez que le type de cette valeur est float64, et que de telles valeurs vont influencer notamment le dtype des séries de nombres entiers, qui prendront ainsi le type float64.


Méthodes pour les valeurs vides

Les séries possèdent plusieurs outils pratiques pour obtenir des informations sur leurs valeurs vides. Une première série de méthodes vous permet d'obtenir, depuis votre série, une série indiquant si une valeur est vide, ou le contraire. Voyons un exemple :

import pandas as pd
from numpy import nan

s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7])
s_na1 = s1.isna()  # donne une série contenant [False, False, True, False, True, False, False]
s_na2 = s1.isnull()  # synonyme : donne exactement le même résultat
s_nn1 = s1.notna()  # donne une série contenant [True, True, False, True, False, True, True]
s_nn2 = s1.notnull()  # synonyme de notna
has_na = s1.hasnans  # renvoie True : des valeurs sont vides

Retirer les valeurs vides

Une méthode vous permet de retirer les valeurs vides dans une série. Notez que les valeurs d'index associées ne sont naturellement pas conservées; l'index associé à la série aura certaines valeurs non contiguës.

import pandas as pd
from numpy import nan

s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7])
sc1 = s1.dropna(inplace=False)  # donne une série contenant [1, 2, 4, 6, 7]

Remplir les valeurs vides

Vous pouvez également remplacer les valeurs vides d'une série, et ce de plusieurs manières, avec la méthode .fillna(){.python} :

import pandas as pd
from numpy import nan

s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7])
# Remplir les valeurs avec une valeur fixe
sc1 = s1.fillna(-1, inplace=False)  # donne une série contenant [1, 2, -1, 4, -1, 6, 7]
# Une série où on fait coincider les index pour remplacer les valeurs vides
sc2 = s1.fillna(pd.Series([9, 8, 7, 6, 5, 4, 3]))  # donne [1, 2, 7, 4, 5, 6, 7]

Connaître le nombre de valeurs non vides

Si l'on peut récupérer le nombre d'éléments d'une série avec la fonction Python len(){.python}, la méthode count() d'une série permettra d'obtenir le nombre de valeurs renseignées. Pour obtenir le nombre de valeurs vides, il faudra ruser un peu :

import pandas as pd
from numpy import nan

s1 = pd.Series(data=[1, 2, nan, 4, nan, 6, 7])
filled_count = s1.count()  # Renvoie 5
empty_count_a = len(s1) - s1.count()  # Renvoie 2
empty_count_b = s1.isna().sum()  # sum() compte `True` comme 1, `False` comme 0. Renvoie 2

Séries booléennes et existence de valeurs True

En Python, les fonctions any(){.python} et all(){.python} prennent un itérable en argument et indiquent respectivement si ledit itérable contient un élément équivalent à True{.python}, et si ledit itérable contient uniquement des éléments équivalents à True{.python}.

Le principe est identique avec les méthodes .any(){.python} et .all(){.python} des séries de booléens :

import pandas as pd

s1 = pd.Series([False, True, True, False])
print(s1.any(), s1.all())  # Affiche True et False

any(){.python} renvoie True si au moins une valeur est équivalente à True{.python}, et all(){.python} renvoie si toutes les valeurs non vides sont équivalentes à True{.python}.


Valeurs uniques

Cela peut être intéressant de savoir dans une série quelles valeurs distinctes apparaissent, ou même supprimer des valeurs afin de ne garder que des valeurs uniques d'une série.

import pandas as pd

numbers = pd.Series(data=[1, 4, 7, 3, 4, 8, 2, 4, 7, 5, 6, 8, 9, 1, 1, 3, 2])
uniques = numbers.unique()  # valeurs distinctes, tableau numpy
unique2 = numbers.drop_duplicates(keep="first")  # ou "last", ou False pour tout supprimer
duplicates = numbers.duplicated(keep="first")  # série de booléens indiquant si une valeur est un doublon
frequencies = numbers.value_counts()  # série indiquant pour chaque valeur son nombre d'apparitions
print(frequencies[4])  # Récupérer le nombre d'apparitions de la valeur 4
print(frequencies.get(4))  # Idem, mais renvoie `None` si la valeur n'apparait pas


Appliquer une fonction aux valeurs d'une série

Occasionnellement, les outils de traitement et de transformations de séries ne suffisent pas. Vous pouvez avoir besoin de votre propre algorithme à appliquer sur des valeurs d'une série, ex. vous avez besoin de simplifier une série de valeurs textuelles, en retirant les accents et en mettant les caractères en minuscules. La méthode apply(){.python} des séries vous permettra ce genre de traitement :

import unidecode  # pip install unidecode
import pandas as pd

def unaccent_lower(text: str):
    """Retire les accents et convertit en minuscules."""
    return unidecode.unidecode(text).lower()

names = pd.Series(data=["Élodie", "Jérémy", "Céleste", "Urünundür"])
# Appliquer la fonction élément par élément pour obtenir une nouvelle série
simple = names.apply(unaccent_lower, by_row="compat")

Documentation de apply


Vous pouvez également appliquer une fonction et lui donner connaissance de tous les éléments de la série, en indiquant la valeur False à l'argument by_row= de la méthode apply. La fonction devra renvoyer une série :

import pandas as pd

def nullify_below_avg(numbers: pd.Series):
    """Met à 0 les nombres inférieurs à la moyenne."""
    average = numbers.mean()
    values = [(n if n >= average else 0) for n in numbers]
    return pd.Series(data=values)

values = pd.Series(data=[19, 33, 12, 5.7, 61, 14])
# Appliquer la fonction élément par élément pour obtenir une nouvelle série
simple = values.apply(nullify_below_avg, by_row=False)

Filtrer les éléments d'une série

Si récupérer un élément unique d'une série fonctionne comme avec un dictionnaire Python, récupérer une partition de série est aussi possible, via certaines techniques. Vous pouvez récupérer :

  • Plusieurs éléments par la valeur d'index associée
  • Plusieurs éléments par un index numérique
  • Slicing, par index ou par index numérique
  • Éléments filtrés par série de booléens

Récupérer plusieurs éléments par index

Récupérer plusieurs éléments consiste à passer en tant que clé un objet de type list{.python} (et uniquement ce type). Chaque valeur de ladite liste représente une valeur d'index à extraire. De préférence, on filtrera avec l'index en utilisant l'attribut .loc{.python} (line of content ou location ?) de la série, et on filtrera par index numérique avec l'attribut .iloc{.python} :

import pandas as pd

names = pd.Series(data=["Élodie", "Hubert", "Céleste", "Jacques", "Albéric"], index=["E", "H", "C", "J", "A"])
names_cha = names.loc[["C", "H", "A"]]  # récupère Céleste, Hubert et Albéric
names_chb = names[["C", "H", "A"]]  # la même opération peut être effectuée directement sur `names`
names_ch2 = names.iloc[[2, 1, 4]]  # même résultat que ci-dessus

Slicing

Vous pouvez récupérer une partition d'une série avec la syntaxe du slicing ([start:openstop:step]{.python}) :

import pandas as pd
from numpy import r_  # trucs d'indexation

names = pd.Series(data=["Élodie", "Hubert", "Céleste", "Jacques", "Albéric"], index=["E", "H", "C", "J", "A"])
elodie_to_celeste = names.loc["E":"C"]  # Attention : l'index de fin est inclus
elodie_to_celeste2 = names.iloc[0:3]  # Attention : l'index de fin n'est pas inclus
every_two = names.iloc[::2]  # L'argument step fonctionne comme prévu
# Appliquer plusieurs slices : outil fourni par Numpy
multiple_slice = names.iloc[r_[0:2, 3:5]]  # Note : ne pas préciser l'index de fin ne fonctionne pas

Filtrage par série de booléens

import pandas as pd

values = pd.Series(data=[19, 33, 12, 5.7, 61, 14])
gt_eighteen = values > 18.0  # booléens : T, T, F, F, T, F 
filtered1 = values.loc[gt_eighteen]  # 19, 33, 61
filtered2 = values.loc[[True, True, False, False, True, False]]  # Équivalent

Filtrer sur plusieurs conditions

Vous pouvez filtrer sur plusieurs conditions, en les assemblant avec des opérateurs ET et OU. Par exemple, si je souhaite filtrer une série en gardant les valeurs entre 0 et 50, et qui sont divisibles par 3, je peux effectuer l'opération logique de la façon suivante:

import pandas as pd
import numpy as np

numbers = pd.Series(data=np.arange(0, 101)).sample(frac=1)
# Récupérer les nombres entre 0 et 30 divisibles par 3
output = numbers.loc[numbers.between(0, 30, inclusive="both") & (numbers % 3 == 0)]

Pour des questions d'ordre d'évaluation des opérateurs, vous devrez quasi systématiquement utiliser des parenthèses pour discriminer vos conditions. Les opérateurs à utiliser pour assembler vos propositions sont les opérateurs logiques binaires:

  • &{.python}: ET binaire
  • |{.python}: OU binaire
  • ~{.python}: NON binaire
  • ^{.python}: OU exclusif binaire

Méthodes par type de séries

On peut très simplement appliquer des opérateurs à des séries, et obtenir des séries. Rappel :

import pandas as pd

values = pd.Series(data=[19, 33, 12, 5.7, 61, 14])
double = values * 2  # 38, 66, 24, 11.4, 122, 28
even = values % 2 == 0  # F, F, T, F, F, T

Séries de type object (chaînes)

Sur une série de type chaîne de caractères, on aura davantage envie d'appliquer simplement des méthodes de base disponibles sur le type str{.python}.

Le moyen d'utiliser lesdites méthodes consiste à passer par un attribut de la série nommé .str{.python} :

import pandas as pd

names = pd.Series(data=["Élodie", "Hubert", "Céleste", "Jacques", "Albéric", "42", "1137"])
lower = names.str.lower()  # récupérer une série avec les noms en minuscule
digits = names.str.isdigit()
containing_plop = names.str.contains("plop")  # Renvoie une série de booléens

Séries de type datetime64[ns] (dates)

Sur une série de type dates, il y a aussi des outils qui permettent d'extraire des informations intéressantes depuis des dates; vous pouvez extraire le jour du mois, le numéro de jour dans l'année, etc.

Le moyen d'utiliser lesdites méthodes consiste à passer par un attribut de la série nommé .dt{.python} (pour datetime) :

import pandas as pd

dates = pd.Series(data=["2001-01-19", "2012-12-21", "2022-04-30", "2016-10-16", "2024-06-01"], dtype="datetime64[ns]")
days = dates.dt.day  # récupérer une série avec le numéro de jour
ordinals = dates.dt.day_of_year  # récupérer une série avec le numéro de jour de l'année
day_names = dates.dt.day_name(locale="")  # récupérer une série avec le nom de jour dans la langue du système