818 lines
28 KiB
Markdown
818 lines
28 KiB
Markdown
---
|
||
title: Manipuler des données avec Pandas
|
||
author: Steve Kossouho
|
||
---
|
||
|
||
# Analyse de données fichier avec Pandas
|
||
|
||
---
|
||
|
||
## Les `Index` dans une `Series`
|
||
|
||
Un objet de type `Series` est un jeu de données à une dimension. Ce qui n'empêche pas d'associer
|
||
un index à chacune des lignes du document. Un des intérêts est de pouvoir associer une étiquette
|
||
à chacune des données, par exemple un moment dans le temps. Cela permet d'obtenir une série temporelle
|
||
en bonne et dûe forme.
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
s0 = pd.Series(data=[2, 5, 2, 6], index=["ligne1", "ligne2", "ligne3", "ligne4"])
|
||
s1 = pd.Series(data=[2, 5, 2, 6], index=pd.date_range("2023-01-01", "2023-01-31", 4))
|
||
s2 = pd.Series(data=[2, 5, 2, 6], index=["2022-01-01", "2022-01-02", "2022-01-03", "2022-01-04"])
|
||
s3 = pd.Series(data=[2, 5, 2, 6]) # Créer une série sans index
|
||
s3 = s3.set_axis(["ligne1", "ligne2", "ligne3", "ligne4"]) # Utilise ces valeurs comme index. Renvoie une nouvelle série.
|
||
print(s1, s2)
|
||
print(s0, s3)
|
||
```
|
||
|
||
---
|
||
|
||
### Petit aparté : utilisation de `date_range()`
|
||
|
||
La fonction de Pandas nommée `date_range()` permet de générer une suite de dates, qu'on pourrait
|
||
utiliser pour générer des colonnes d'un `DataFrame` ou des index à associer aux lignes d'un `DataFrame` ou une `Series`.
|
||
|
||
La fonction permet de générer des dates à intervalles réguliers, mais peut faire beaucoup plus intéressant :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Génère une liste de 8 dates, contenant la date de début et de fin, ainsi que 6 autres dates
|
||
# uniformément dispersés (avec les heures, minutes, secondes et **nanosecondes**)
|
||
print(pd.date_range("2023-01-01", "2023-01-31", periods=8)
|
||
```
|
||
|
||
Un problème potentiel de cette méthode, c'est justement qu'on n'obtient pas toujours des dates exactes à minuit.
|
||
Cela peut s'arranger, en utilisant la fonction avec un autre argument.
|
||
|
||
TODO: Reformuler ce truc
|
||
|
||
---
|
||
|
||
Si on utilise la même fonction, mais en utilisant l'argument `freq` au lieu de l'argument `periods` (on ne peut
|
||
pas utiliser les deux en même temps), on peut demander à Pandas de générer une suite de dates, répondant à certains critères, documentés
|
||
[ici](https://pandas.pydata.org/docs/user_guide/timeseries.html#offset-aliases)
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Génère une liste de dates, contenant la date de début et de fin, une par jour du calendrier
|
||
print(pd.date_range("2023-01-01", "2023-01-31", freq="D")) # D signifie chaque jour de la semaine
|
||
print(pd.date_range("2023-01-01", "2023-01-31", freq="W-SUN")) # W-SUN signifie chaque dimanche
|
||
print(pd.date_range("2023-01-01", "2023-01-31", freq="W-MON")) # W-MON signifie chaque lundi
|
||
print(pd.date_range("2023-01-01", "2023-01-31", freq="W-TUE")) # W-MON signifie chaque mardi
|
||
print(pd.date_range("2023-01-01", "2023-01-02", freq="H")) # H signifie chaque heure
|
||
```
|
||
|
||
|
||
|
||
---
|
||
|
||
## Les données absentes (`Not a Number`)
|
||
|
||
Dans un objet `Series` ou un objet `DataFrame`, toutes les cellules n'ont pas forcément de données.
|
||
|
||
Prenons un exemple au hasard : une table contenant des informations d'identité, et qui contient des colonnes telles que `nom`, `prenom`, et `nom de naissance`, peut présenter des lignes de contenu où la colonne `nom de naissance` ne sera pas renseignée.
|
||
|
||
Lorsque c'est le cas, Pandas représente le contenu de la cellule comme étant `NaN` (not a number), une donnée qui en Python standard serait la valeur `None`{.python}.
|
||
|
||
Dans de nombreux calculs fournis par Pandas, il est souvent facile d'exclure les cellules `NaN` des
|
||
résultats, voire de retirer des informations selon l'état de remplissage de cellules.
|
||
|
||
---
|
||
|
||
## Gestion des données manquantes (NaN)
|
||
|
||
Pandas offre plusieurs méthodes pour gérer les données manquantes, identifiées par la valeur `NaN` (Not a Number) :
|
||
|
||
- `isna()` ou `isnull()` : ces deux méthodes retournent un masque booléen indiquant les entrées manquantes.
|
||
- `notna()` ou `notnull()` : ces méthodes fonctionnent comme les précédentes, mais renvoient `True` pour les entrées non manquantes.
|
||
- `dropna()` : cette méthode permet de supprimer les lignes (ou colonnes, selon l'axe choisi) contenant des entrées manquantes.
|
||
- `fillna()` : cette méthode permet de remplir les entrées manquantes avec une valeur spécifiée.
|
||
|
||
---
|
||
|
||
Exemple d'utilisation :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
import numpy as np
|
||
|
||
# Création d'un DataFrame avec des valeurs manquantes
|
||
df = pd.DataFrame({
|
||
"A": [1, 2, np.nan],
|
||
"B": [5, np.nan, np.nan],
|
||
"C": [1, 2, 3]
|
||
})
|
||
print(df)
|
||
```
|
||
|
||
---
|
||
|
||
```{.python .numberLines}
|
||
# Utilisation de dropna() pour supprimer les lignes avec des valeurs NaN
|
||
df_drop = df.dropna()
|
||
# On peut aussi choisir quelles colonnes considérer pour la vérification des valeurs NaN
|
||
# Supprimer les lignes entières où on a NaN dans la colonne "A" uniquement
|
||
df_drop2 = df.dropna(subset=["A"])
|
||
print(df_drop)
|
||
print(df_drop2)
|
||
```
|
||
|
||
---
|
||
|
||
Pour remplacer des valeurs `NaN` dans un `DataFrame`, on a plusieurs possibilités :
|
||
|
||
```{.python .numberLines}
|
||
# Utilisation de fillna() pour remplir les valeurs manquantes
|
||
# Ici, on remplace TOUTES les valeurs NaN du DataFrame par une valeur choisie
|
||
df_fill = df.fillna(value='FILL VALUE')
|
||
|
||
# Si on souhaite choisir la valeur par défaut par colonne
|
||
# Ici, on indique qu'il faut remplacer les valeurs NaN de la colonne "A"
|
||
# par 0, et les valeurs NaN de la colonne "B" par -1
|
||
df_fill2 = df.fillna(value={"A": 0, "B": -1})
|
||
|
||
print(df_fill)
|
||
print(df_fill2)
|
||
```
|
||
|
||
---
|
||
|
||
Pour avoir une carte des valeurs `NaN` ou équivalentes dans un `DataFrame`, on a les méthodes `isna()` :
|
||
|
||
```{.python .numberLines}
|
||
# Utilisation de isna() pour récupérer un DataFrame de valeurs booléennes.
|
||
# On aura un `True` pour les valeurs `None`, `NaN`, `NaT` etc.
|
||
df_empty_map = df.isna()
|
||
|
||
print(df_empty_map)
|
||
```
|
||
|
||
---
|
||
|
||
## Gestion des index
|
||
|
||
Nous avons déjà vu comment utiliser des index avec un `DataFrame`. Il existe plusieurs méthodes utiles pour gérer ces index :
|
||
|
||
- `reset_index()` : Permet de réinitialiser l'index à un index par défaut (0, 1, 2, ...).
|
||
- `set_index()` : Utiliser une colonne différente pour l'index.
|
||
|
||
Exemple d'utilisation :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Création d'un DataFrame avec un index
|
||
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}, index=["x", "y", "z"])
|
||
print(df)
|
||
```
|
||
|
||
---
|
||
|
||
```{.python .numberLines}
|
||
# Réinitialisation de l'index
|
||
df = ...
|
||
# df.reset_index() renvoie un nouveau DataFrame avec l'index mis à jour.
|
||
df_reset = df.reset_index()
|
||
# df.reset_index(inplace=True) modifie le DataFrame en modifiant son index.
|
||
df.reset_index(inplace=True)
|
||
print(df_reset)
|
||
|
||
# Utilisation d'une autre colonne pour l'index
|
||
# Les valeurs de la colonne "A" seront utilisées comme index pour référencer les lignes
|
||
df_set = df.set_index("A")
|
||
print(df_set)
|
||
```
|
||
|
||
---
|
||
|
||
## Filtrage et modifications d'un dataframe
|
||
|
||
Pandas offre une multitude de méthodes pour filtrer et modifier un `DataFrame`. Par exemple, il est possible de sélectionner des colonnes ou des lignes spécifiques, de filtrer selon certaines conditions, ou encore de modifier des valeurs.
|
||
|
||
---
|
||
|
||
Exemple d'utilisation :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Création d'un DataFrame
|
||
df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]})
|
||
# Sélection d'une colonne
|
||
print(df["A"])
|
||
# Sélection de plusieurs colonnes
|
||
print(df[["A", "B"]])
|
||
# Filtrage selon une condition
|
||
# On peut filtrer avec des conditions sans passer par df.iloc
|
||
# Mais ce sera plus explicite d'utiliser df.iloc
|
||
print(df.loc[df["A"] > 2]) # Récupérer les lignes où A > 2
|
||
print(df.loc[(df["A"] > 2) & (df["B"] == 8)]) # Récupérer les lignes où A > 2 et B = 8
|
||
print(df.loc[~(df["A"] > 2) & (df["B"] == 8)]) # Récupérer les lignes où A n'est pas > 2 et B = 8
|
||
|
||
# Modification de valeurs :
|
||
# Changer les lignes de df où la colonne "A" contient une
|
||
# valeur supérieure à 2, et y mettre la valeur 10 dans les colonnes B et C
|
||
df.loc[df["A"] > 2, ["B", "C"]] = 10
|
||
# Si je veux mettre deux valeurs différentes dans B et C lorsque la condition
|
||
# est vérifiée, j'écrirai plutôt
|
||
df.loc[df["A"] > 2, ["B", "C"]] = (9, 12)
|
||
print(df)
|
||
```
|
||
|
||
---
|
||
|
||
### Tri des lignes d'un `DataFrame`
|
||
|
||
Il est fréquent de vouloir réorganiser les données d'un `DataFrame` via un tri sur une ou plusieurs colonnes.
|
||
Le type propose naturellement une méthode nommée `.sort_values()`
|
||
|
||
---
|
||
|
||
## Slicing et lignes de `DataFrame`
|
||
|
||
Nous avons vu qu'il est possible de filtrer un `DataFrame` via des critères avancés. De façon plus générale, le filtrage de lignes peut se faire avec deux attributs :
|
||
|
||
- `df.iloc` : extraire des lignes individuelles par leur numéro séquentiel dans le dataframe;
|
||
- `df.loc` : extraire des lignes individuelles par la valeur assignée comme index aux lignes du document.
|
||
|
||
Les deux méthodes s'utilisent généralement de la même manière, en faisant attention à utiliser des alias d'index dans le
|
||
cas de `df.loc`.
|
||
|
||
---
|
||
|
||
### Extraire une ligne d'un `DataFrame`
|
||
|
||
Au même titre que dans une liste Python, il est très simple d'extraire une ligne d'un objet `DataFrame` :
|
||
|
||
```python
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]}, index=["l1", "l2", "l3", "l4"])
|
||
row_a = df.loc["l1"] # extraire la première ligne
|
||
row_b = df.iloc[0] # extraire la première ligne
|
||
|
||
print(row_a, row_b)
|
||
```
|
||
|
||
---
|
||
|
||
### Extraire plusieurs lignes distinctes d'un `DataFrame`
|
||
|
||
Impossible avec des types standard en Python, il est très simple avec Pandas d'extraire des lignes distinctes d'un objet `DataFrame` :
|
||
|
||
```python
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]}, index=["l1", "l2", "l3", "l4"])
|
||
rows_a = df.iloc[[0, 2]] # extraire les 1e et 3e lignes
|
||
|
||
print(rows_a)
|
||
```
|
||
|
||
---
|
||
|
||
### Filtrer les lignes d'un `DataFrame` avec des booléens
|
||
|
||
On peut filtrer les lignes d'un objet `DataFrame` en passant une liste de booléens. Pour chaque booléen valant `True`, la ligne
|
||
correspondante sera conservée dans la sortie :
|
||
|
||
```python
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8], "C": ["a", "b", "c", "d"]}, index=["l1", "l2", "l3", "l4"])
|
||
rows_a = df.iloc[[False, True, True, False]] # extraire les 2e et 3e lignes
|
||
|
||
print(rows_a)
|
||
```
|
||
|
||
Cette possibilité de filtrer via une liste de booléens, associée au fonctionnement des opérateurs sur les `Series`,
|
||
permet des choses très intéressantes pour facilement filtrer des `DataFrame`
|
||
|
||
---
|
||
|
||
#### Filtrer avec des conditions
|
||
|
||
Utiliser des opérateurs de comparaison sur une colonne de données renverra toujours une série de même longueur, contenant
|
||
des booléens indiquant si la comparaison était vraie pour chaque valeur.
|
||
|
||
```python
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame({
|
||
"A": [1, -2, 3, -4],
|
||
"B": [5, 6, 7, 8],
|
||
"C": ["ant", "ball", "chord", "double"]
|
||
}, index=["l1", "l2", "l3", "l4"])
|
||
# Masque de valeurs positives
|
||
positive_a = df["A"] >= 0
|
||
# L'opposé d'une condition utilise l'opérateur
|
||
# de complément binaire, le ~
|
||
exclude_cond = ~df["A"] == 0
|
||
# Masque de valeurs dans une plage
|
||
range_a = df["A"].between(0, 100, inclusive="both")
|
||
```
|
||
|
||
---
|
||
|
||
Les séries de booléens résultantes peuvent naturellement assemblées entre elles via des opérations logiques, booléennes :
|
||
|
||
```python
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame({"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]}, index=["l1", "l2", "l3", "l4"])
|
||
multi_condition = (df["A"] >= 0) & (df["B"] > 6) # lignes où A est positif et B est > 6
|
||
|
||
print(multi_condition)
|
||
```
|
||
|
||
---
|
||
|
||
#### Conditions sur des chaînes de caractères
|
||
|
||
Il est facile d'utiliser des opérateurs classiques pour tester des conditions sur des
|
||
colonnes d'un objet `DataFrame`. Par exemple :
|
||
|
||
```python
|
||
filtered_df = df[df["column"] > 1]
|
||
```
|
||
|
||
Si l'on souhaite filtrer sur une colonne textuelle, on peut être intéressé
|
||
de pouvoir considérer une partie d'une chaîne, ou bien ignorer la casse du texte.
|
||
Pour cela nous avons sur les objets `Series` un attribut `.str` :
|
||
|
||
```python
|
||
filtered_df = df[df["column"].str.contains("(?i)texte")]
|
||
```
|
||
|
||
La méthode comprend les expressions régulières pour plus de contrôle sur les valeurs à tester.
|
||
|
||
---
|
||
|
||
#### Conditions avec des fonctions
|
||
|
||
Il peut arriver qu'une condition de test à appliquer à une colonne nécessite autre chose que simplement le contenu
|
||
brut de la colonne; par exemple, le nombre de caractères d'une colonne textuelle pourrait être utilisé pour
|
||
discriminer les résultats. Dans ce cas de figure, on utilisera plutôt la méthode `apply` de la série :
|
||
|
||
```python
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame(
|
||
{"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]},
|
||
index=["l1", "l2", "l3", "l4"]
|
||
)
|
||
# Dire si une ligne de C a plus de 4 caractères
|
||
# La fonction renvoie une série de valeurs booléennes
|
||
long_c = df["C"].apply(lambda val: len(val) > 4)
|
||
|
||
print(long_c)
|
||
print(df.loc[long_c])
|
||
```
|
||
|
||
---
|
||
|
||
### Slicing sur les lignes
|
||
|
||
La syntaxe normale du slicing fonctionne sur les lignes d'un `DataFrame`, ce qui vous permet de récupérer simplement
|
||
une partition des lignes du `DataFrame` original.
|
||
|
||
```python
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame(
|
||
{"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]},
|
||
index=["l1", "l2", "l3", "l4"]
|
||
)
|
||
partition = df.iloc[0:2]
|
||
|
||
print(partition)
|
||
```
|
||
|
||
---
|
||
|
||
### Plus : Multiple slicing sur les lignes
|
||
|
||
La syntaxe normale du slicing fonctionne sur les lignes d'un `DataFrame`, ce qui vous permet de récupérer simplement
|
||
une partition des lignes du `DataFrame` original. Un outil de `numpy` nous permet d'appliquer un slicing multiple sur les
|
||
lignes d'un `DataFrame` :
|
||
|
||
```python
|
||
import pandas as pd
|
||
import numpy as np
|
||
|
||
df = pd.DataFrame(
|
||
{"A": [1, -2, 3, -4], "B": [5, 6, 7, 8], "C": ["ant", "ball", "chord", "double"]},
|
||
index=["l1", "l2", "l3", "l4"]
|
||
)
|
||
partition = df.iloc[np.r_[0:1, 2:4]] # extraire les lignes 0 et 2 à 3 avec du slice
|
||
|
||
print(partition)
|
||
```
|
||
|
||
---
|
||
|
||
## Grouper les contenus d'un DataFrame pour calculs
|
||
|
||
Pandas propose des méthodes pour grouper les données selon certains critères et effectuer des calculs sur ces groupes.
|
||
C'est ce qu'on appelle souvent l'opération de `group by` (groupement).
|
||
Cela est particulièrement utile pour l'analyse de données.
|
||
|
||
Exemple d'utilisation :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
import numpy as np
|
||
|
||
# Création d'un DataFrame
|
||
df = pd.DataFrame({
|
||
"A": ["foo", "bar", "foo", "bar", "foo", "bar", "foo", "foo"],
|
||
"B": ["one", "one", "two", "three", "two", "two", "one", "three"],
|
||
"C": np.random.randn(8),
|
||
"D": np.random.randn(8)
|
||
})
|
||
|
||
# Grouper par colonne "A" et calculer la somme sur chaque groupe
|
||
grouped = df.groupby("A").sum()
|
||
print(grouped)
|
||
```
|
||
|
||
---
|
||
|
||
## Fusion de dataframes
|
||
|
||
Pandas propose plusieurs méthodes pour combiner des `DataFrame`, comme `concat()`, `merge()`, ou `join()`. Ces opérations sont similaires à celles que l'on peut réaliser avec des bases de données SQL.
|
||
|
||
Exemple d'utilisation :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Création de deux DataFrame
|
||
df1 = pd.DataFrame({
|
||
"A": ["A0", "A1", "A2", "A3"],
|
||
"B": ["B0", "B1", "B2", "B3"],
|
||
"C": ["C0", "C1", "C2", "C3"],
|
||
"D": ["D0", "D1", "D2", "D3"]
|
||
})
|
||
|
||
df2 = pd.DataFrame({
|
||
"A": ["A4", "A5", "A6", "A7"],
|
||
"B": ["B4", "B5", "B6", "B7"],
|
||
"C": ["C4", "C5", "C6", "C7"],
|
||
"D": ["D4", "D5", "D6", "D7"]
|
||
})
|
||
|
||
# Concaténation des deux DataFrame
|
||
result = pd.concat([df1, df2])
|
||
print(result)
|
||
```
|
||
|
||
---
|
||
|
||
## Manipulation des formats de dates
|
||
|
||
Pandas offre une multitude de méthodes pour manipuler les dates.
|
||
Vous pouvez convertir des chaînes de caractères en dates, extraire des composants spécifiques d'une date (comme l'année, le mois, le jour, etc.), ou encore effectuer des calculs avec des dates.
|
||
|
||
Exemple d'utilisation :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Création d'un DataFrame avec une colonne date
|
||
df = pd.DataFrame({
|
||
"date": ["2023-01-01", "2023-01-02", "2023-01-03"],
|
||
"value": [1, 2, 3]
|
||
})
|
||
|
||
# Afficher les types des colonnes pour s'assurer que la colonne de date a le bon type
|
||
print(df.dtypes) # Regardez le résultat pour être sûr
|
||
|
||
# Si le type est incorrect, faire la conversion de la colonne vers le type datetime
|
||
df["date"] = pd.to_datetime(df["date"])
|
||
|
||
# Extraction de l'année, du mois, et du jour dans 3 nouvelles colonnes
|
||
df["year"] = df["date"].dt.year
|
||
df["month"] = df["date"].dt.month
|
||
df["day"] = df["date"].dt.day
|
||
print(df)
|
||
```
|
||
|
||
---
|
||
|
||
## Mesures statistiques variées sur les DataFrames
|
||
|
||
Pandas propose plusieurs méthodes pour calculer des statistiques sur un `DataFrame`, comme la moyenne (`mean()`), la somme (`sum()`), l'écart-type (`std()`), le minimum (`min()`), le maximum (`max()`), et bien d'autres.
|
||
|
||
Exemple d'utilisation :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Création d'un DataFrame
|
||
df = pd.DataFrame({
|
||
"A": [1, 2, 3, 4, 5],
|
||
"B": [6, 7, 8, 9, 10]
|
||
})
|
||
|
||
# Calcul de la moyenne, de la somme, et de l'écart-type sur chaque colonne
|
||
mean = df.mean() # Ceci est une `Series` avec une valeur calculée par colonne
|
||
sum = df.sum() # Ceci aussi est une `Series`
|
||
std = df.std() # Pareil
|
||
|
||
print(mean)
|
||
print(sum)
|
||
print(std)
|
||
```
|
||
|
||
---
|
||
|
||
### Comment ajouter une `Series` comme nouvelle ligne d'un `DataFrame`
|
||
|
||
Cela peut être intéressant d'ajouter à un `DataFrame` une ligne qui provient d'un objet `Series` (par exemple, les résultats de moyennes, sommes ou écarts-types etc.) Pour cela, c'est assez simple, il faut se servir de l'attribut `.loc` d'un `DataFrame` :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
# Création d'un DataFrame
|
||
df = pd.DataFrame({"A": [1, 2, 3, 4, 5], "B": [6, 7, 8, 9, 10]})
|
||
# Calcul de la moyenne, de la somme, et de l'écart-type sur chaque colonne
|
||
mean = df.mean() # Ceci est une `Series` avec une valeur calculée par colonne
|
||
|
||
# Ajouter la `Series` dans `mean` comme nouvelle ligne dans `df` dont l'index est "moyenne"
|
||
df.loc["moyenne"] = mean
|
||
df.loc["hasard"] = [99, 25] # On peut même définir des lignes avec des valeurs dans une liste
|
||
print(df) # Une nouvelle ligne "moyenne" devrait être présente dans le DataFrame
|
||
```
|
||
|
||
---
|
||
|
||
### Noms des méthodes les plus fréquemment utilisées
|
||
|
||
Pour faire des calculs statistiques sur les colonnes d'un `DataFrame`, les méthodes disponibles de
|
||
base dans Pandas ont des noms pas toujours faciles à deviner :
|
||
|
||
- `df.mean()` : Calcule la moyenne des colonnes
|
||
- `df.sum()` : Calcule la somme des colonnes
|
||
- `df.std()` : Calcule l'écart-type (racine de la variance)
|
||
- `df.var()` : Calcule la variance
|
||
- `df.median()` : Valeur centrale des éléments d'une série
|
||
- `df.quantile(q)` : Calcule un quantile (q entre 0.0 et 1.0)
|
||
- `df.mode()` : Renvoie un DataFrame des valeurs modales de chaque colonne
|
||
- `df.max()` et `df.min()` : Calculer les extrêmes
|
||
- `df.cum*()` : Fonctions cumulatives qui renvoient des DataFrame
|
||
|
||
---
|
||
|
||
#### Mode d'un `DataFrame`
|
||
|
||
Le mode d'un `DataFrame` renvoie un `DataFrame` avec toutes les colonnes, et autant de lignes qu'il y a de valeurs modales par colonne. (_Une valeur modale est la valeur qui revient le plus souvent dans une série_)
|
||
|
||
Si j'ai un `DataFrame` avec une colonne `A` et une colonne `B`, et que `A` possède une seule valeur modale qui est `10`, et que `B` possède 3 valeurs modales qui sont `"pomme"`, `"citron"`, et `"ananas"`, le résultat du mode du `DataFrame` ressemblera à ça :
|
||
|
||
```
|
||
A B
|
||
10 pomme
|
||
NaN citron
|
||
NaN ananas
|
||
```
|
||
|
||
Les colonnes où il y a moins de valeurs modales sont remplies avec des `NaN`.
|
||
|
||
---
|
||
|
||
## Faire des liaisons entre plusieurs `DataFrame`
|
||
|
||
Des fois, on est obligé de récupérer des informations différentes depuis plusieurs `DataFrame` qui ont des données en commun. Par exemple :
|
||
|
||
- Une table d'assurés sociaux : `numero_secu`, `prenom`, `nom`, `naissance`...
|
||
- Une table de remboursements médicaments : `num_secu`, `montant`, `date`, `categorie`...
|
||
|
||
Si on veut travailler sur le `DataFrame` des remboursements, en y ajoutant les informations de prénom, nom et date de naissance pour chaque remboursement, on peut faire une fusion avec Pandas.
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
# Imaginez que ces deux fichiers factices correspondent à nos deux Dataframes
|
||
df_assures = pd.read_csv("assures.csv")
|
||
df_remboursements = pd.read_csv("remboursements.csv")
|
||
|
||
# On veut partir du DataFrame des remboursements pour y ajouter
|
||
# des colonnes prises depuis la liste des assurés.
|
||
resultat = df_remboursements.merge(df_assures, how="left", on_left="num_secu", on_right="numero_secu")
|
||
print(resultat)
|
||
```
|
||
|
||
---
|
||
|
||
Le résultat qui est affiché contient toutes les colonnes des assurés et des remboursements, et contient autant de lignes que dans la table de "gauche", à savoir `df_remboursements`.
|
||
|
||
Les colonnes sont `numero_secu`, `prenom`, `nom`, `naissance`, `num_secu`, `montant`, `date`, `categorie`.
|
||
Les colonnes `num_secu` et `numero_secu` auront les memes valeurs, car c'est justement sur ces colonnes qu'on a fait la fusion. Attention cependant !
|
||
|
||
---
|
||
|
||
### Stratégie de fusion de `DataFrame`
|
||
|
||
La méthode `.merge()` accepte un argument `how=` qui permet de préciser comment le résultat de la fusion doit etre généré. Les valeurs possibles de cet argument sont : `"left"`, `"right"`, `"inner"`, `"outer"`.
|
||
|
||
Si je possède les `DataFrame` suivants :
|
||
|
||
---
|
||
|
||
**Personnes**
|
||
|
||
```
|
||
id prenom nom naissance
|
||
0 1 Jacques Dupont 1959
|
||
1 2 Paul Biya 1947
|
||
2 3 Marie Curie 1861
|
||
3 5 Jules César 0
|
||
```
|
||
|
||
---
|
||
|
||
**Transactions**
|
||
|
||
```
|
||
id_transaction id_client montant
|
||
0 1 1 100
|
||
1 2 1 500
|
||
2 3 2 -90
|
||
3 4 2 200
|
||
4 5 1 30
|
||
5 6 3 15
|
||
6 7 2 -60
|
||
7 8 1 -10
|
||
8 9 1 250
|
||
9 10 2 170
|
||
10 11 3 90
|
||
15 16 4 77
|
||
```
|
||
|
||
---
|
||
|
||
#### Stratégie `left`
|
||
|
||
Si j'utilise la stratégie `"left"`, alors toutes les lignes du `DataFrame` de gauche (`df_remboursements`) seront conservées, et on y ajoutera les colonnes du `DataFrame` de droite, en y choisissant comme données celles de la ligne
|
||
où la colonne `"numero_secu"` a la meme valeur que dans la colonne `"num_secu"`.
|
||
|
||
Si il n'existe pas de ligne dans le `DataFrame` de droite où la colonne `"numero_secu"` a une valeur qui correspond, les
|
||
colonnes ajoutées contiendront `NaN`. En gros : on conservera tous les enregistrements de **gauche**, en ajoutant des `NaN` à droite s'il n'y a aucune correspondance trouvée.
|
||
|
||
---
|
||
|
||
Résultat :
|
||
|
||
```
|
||
id_transaction id_client montant id prenom nom naissance
|
||
0 1 1 100 1.0 Jacques Dupont 1959.0
|
||
1 2 1 500 1.0 Jacques Dupont 1959.0
|
||
2 3 2 -90 2.0 Paul Biya 1947.0
|
||
3 4 2 200 2.0 Paul Biya 1947.0
|
||
4 5 1 30 1.0 Jacques Dupont 1959.0
|
||
5 6 3 15 3.0 Marie Curie 1861.0
|
||
6 7 2 -60 2.0 Paul Biya 1947.0
|
||
7 8 1 -10 1.0 Jacques Dupont 1959.0
|
||
8 9 1 250 1.0 Jacques Dupont 1959.0
|
||
9 10 2 170 2.0 Paul Biya 1947.0
|
||
10 16 4 77 NaN NaN NaN NaN
|
||
```
|
||
|
||
---
|
||
|
||
#### Stratégie `right`
|
||
|
||
Si j'utilise la stratégie `"right"`, alors toutes les lignes du `DataFrame` de droite (`df_assures`)
|
||
seront conservées, et on y ajoutera les colonnes du `DataFrame` de gauche, en y choisissant comme données celles de la ligne
|
||
où la colonne `"num_secu"` a la meme valeur que dans la colonne `"num_secu"`. Si plusieurs lignes correspondent, on ajoutera autant de lignes que nécessaire.
|
||
|
||
Si il n'existe pas de ligne dans le `DataFrame` de gauche où la colonne `"num_secu"` a une valeur qui correspond, les colonnes ajoutées contiendront `NaN`.
|
||
|
||
---
|
||
|
||
Résultat :
|
||
|
||
```
|
||
id_transaction id_client montant id prenom nom naissance
|
||
0 1.0 1.0 100.0 1 Jacques Dupont 1959
|
||
1 2.0 1.0 500.0 1 Jacques Dupont 1959
|
||
2 5.0 1.0 30.0 1 Jacques Dupont 1959
|
||
3 8.0 1.0 -10.0 1 Jacques Dupont 1959
|
||
4 9.0 1.0 250.0 1 Jacques Dupont 1959
|
||
5 3.0 2.0 -90.0 2 Paul Biya 1947
|
||
6 4.0 2.0 200.0 2 Paul Biya 1947
|
||
7 7.0 2.0 -60.0 2 Paul Biya 1947
|
||
8 10.0 2.0 170.0 2 Paul Biya 1947
|
||
9 6.0 3.0 15.0 3 Marie Curie 1861
|
||
10 NaN NaN NaN 5 Jules César 0
|
||
```
|
||
|
||
---
|
||
|
||
#### Stratégie `inner`
|
||
|
||
Avec la stratégie `inner` on ne garde que les lignes où les colonnes utilisées pour faire la fusion ont une correspondance
|
||
de l'autre coté de de la fusion. En gros, on n'ajoute jamais dans le résultat de ligne ou on aurait des colonnes avec `NaN`.
|
||
|
||
---
|
||
|
||
Résultat :
|
||
|
||
```
|
||
id_transaction id_client montant id prenom nom naissance
|
||
0 1 1 100 1 Jacques Dupont 1959
|
||
1 2 1 500 1 Jacques Dupont 1959
|
||
2 5 1 30 1 Jacques Dupont 1959
|
||
3 8 1 -10 1 Jacques Dupont 1959
|
||
4 9 1 250 1 Jacques Dupont 1959
|
||
5 3 2 -90 2 Paul Biya 1947
|
||
6 4 2 200 2 Paul Biya 1947
|
||
7 7 2 -60 2 Paul Biya 1947
|
||
8 10 2 170 2 Paul Biya 1947
|
||
9 6 3 15 3 Marie Curie 1861
|
||
```
|
||
|
||
---
|
||
|
||
#### Stratégie `outer`
|
||
|
||
Avec la stratégie `outer` on garde toutes lignes et si les colonnes utilisées pour faire la fusion n'ont pas de correspondance
|
||
de l'autre coté de la fusion, on ajoute simplement des colonnes avec `NaN`..
|
||
|
||
---
|
||
|
||
Résultat :
|
||
|
||
```
|
||
id_transaction id_client montant id prenom nom naissance
|
||
0 1.0 1.0 100.0 1.0 Jacques Dupont 1959.0
|
||
1 2.0 1.0 500.0 1.0 Jacques Dupont 1959.0
|
||
2 5.0 1.0 30.0 1.0 Jacques Dupont 1959.0
|
||
3 8.0 1.0 -10.0 1.0 Jacques Dupont 1959.0
|
||
4 9.0 1.0 250.0 1.0 Jacques Dupont 1959.0
|
||
5 3.0 2.0 -90.0 2.0 Paul Biya 1947.0
|
||
6 4.0 2.0 200.0 2.0 Paul Biya 1947.0
|
||
7 7.0 2.0 -60.0 2.0 Paul Biya 1947.0
|
||
8 10.0 2.0 170.0 2.0 Paul Biya 1947.0
|
||
9 6.0 3.0 15.0 3.0 Marie Curie 1861.0
|
||
10 16.0 4.0 77.0 NaN NaN NaN NaN
|
||
11 NaN NaN NaN 5.0 Jules César 0.0
|
||
```
|
||
|
||
---
|
||
|
||
### Appliquer un Pivot
|
||
|
||
La méthode `pivot` des `DataFrame` permet de récupérer une représentation d'un `DataFrame`, réorganisé autour de certaines colonnes/index.
|
||
|
||
Par exemple, si nous avons les données suivantes :
|
||
|
||
```{.python .numberLines}
|
||
import pandas as pd
|
||
|
||
df = pd.DataFrame({
|
||
'country': ['fra', 'fra', 'fra', 'fra', 'bel', 'bel', 'bel', "bel"],
|
||
'district': ['north', 'east', 'west', 'south', 'north', 'east', 'west', 'south'],
|
||
'population': [10_000, 30_000, 50_000, 70_000, 20_000, 40_000, 60_000, 80_000],
|
||
'extra': ['h', 'i', 'j', 'k', 'l', 'm', 'n', 'o']
|
||
})
|
||
```
|
||
|
||
---
|
||
|
||
Si nous appliquons le pivot suivant sur ces données :
|
||
|
||
```{.python .numberLines}
|
||
df2 = df.pivot(index='district', columns='country', values='population')
|
||
```
|
||
|
||
Nous obtiendrons les données suivantes :
|
||
|
||
```
|
||
country bel fra
|
||
district
|
||
east 40000 30000
|
||
north 20000 10000
|
||
south 80000 70000
|
||
west 60000 50000
|
||
```
|
||
|
||
---
|
||
|
||
L'argument `values` est facultatif. S'il est omis, alors le `DataFrame` résultant
|
||
contiendra des colonnes de type `MultiIndex`, un index dont les entrées sont composées de `tuples`.
|
||
|
||
Ici, au lieu d'avoir deux colonnes de pays pour la colonne `population`, nous aurons deux colonnes de pays pour la colonne `population`, deux colonnes de pays pour la colonne `extra`, et ainsi de suite pour toutes les colonnes non utilisées en tant qu'index ou colonnes.
|
||
|
||
Les noms de ces colonnes seront :
|
||
|
||
```
|
||
("population", "bel")
|
||
("population", "fra")
|
||
("extra", "bel")
|
||
("extra", "fra")
|
||
```
|