19 KiB
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.
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èresint64/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éennesboolean
: valeurs booléennes (avec prise en charge des nan)datetime64[ns]
: représentation d'une datedatetime64[ns, UTC]
: représentation d'une date, avec décalage horaire (UTC)timedelta64
: représentation d'un intervalle de tempscategory
: 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 :
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 êtreNone
{.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 ledtype
des séries de nombres entiers, qui prendront ainsi le typefloat64
.
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")
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