Commit 8cb2dc8a authored by ROULOIS Alexandre's avatar ROULOIS Alexandre 🚴🏻
Browse files

Merge branch 'develop' into 'main'

Typo

See merge request !10
parents ea1a3009 8ed49624
%% Cell type:markdown id: tags:
# Manipulation de structures de données avec *Pandas*
%% Cell type:markdown id: tags:
Écrite pour le langage Python, la librairie *Pandas* permet de manipuler avec souplesse des jeux de données en vue de leur analyse. Ses facultés de communication avec les deux autres grandes librairies *Numpy* et *Matplotlib* en font un outil très populaire en *data science* et en *machine learning*. Elle met notamment à disposition une nouvelle structure de données, les *data frames*.
Un *data frame* se conçoit comme un tableau à deux dimensions où chaque colonne peut être de type différent :
|gender|height|
|:-:|:-:|
|F|173|
|F|159|
|M|181|
Chaque ligne est une *observation* quand les colonnes, autrement appelées *séries*, constituent les variables qui la décrivent.
%% Cell type:markdown id: tags:
## Aperçu avec la librairie *Pandas*
%% Cell type:markdown id: tags:
En python, la librairie *Pandas* est dévolue à gérer ces structures essentielles pour l’analyse de données. Elle s’importe comme n’importe quel module grâce à son nom, à l’exception près qu’il est d’usage de lui associer un alias *pd* :
%% Cell type:code id: tags:
``` python
import pandas as pd
```
%% Cell type:markdown id: tags:
L’exemple de l’introduction pourrait se matérialiser en passant un objet de type `dict` au constructeur de la classe `DataFrame` :
%% Cell type:code id: tags:
``` python
# two lists of values
genders = ["F", "F", "M"]
heights = [173, 159, 181]
# a dict
series = {
"gender": genders,
"height o": heights
"height": heights
}
# series are now into a data frame
df = pd.DataFrame(series)
# view
df
```
%% Cell type:markdown id: tags:
Chaque série peut être interrogée individuellement en l’appelant avec son intitulé grâce à la notation `[]` :
%% Cell type:code id: tags:
``` python
df["gender"]
```
%% Cell type:markdown id: tags:
La notation pointée, héritée de la programmation objet, parvient au même résultat, imposant le respect de conventions syntaxiques au moment du nommage des séries :
%% Cell type:code id: tags:
``` python
df.gender
```
%% Cell type:markdown id: tags:
La technique du *slicing* permet d’accéder à des observations particulières :
%% Cell type:code id: tags:
``` python
df[1:]
```
%% Cell type:markdown id: tags:
## Importer un fichier CSV
%% Cell type:markdown id: tags:
Dans la pratique, il est rare de devoir créer un *data frame* manuellement. Comme ces structures servent à manipuler en ensemble large de données, elles les puisent soit de flux (signaux d’entrées d’un périphérique, calculs à la volée…) soit de fichiers.
%% Cell type:markdown id: tags:
### Méthodes pour importer un fichier
%% Cell type:markdown id: tags:
La méthode principale pour importer des données depuis un fichier est `.read_table()` mais, dans la vie réelle, on lui préfère des méthodes spécifiques à certains formats usuels :
- `.read_csv()` pour le format CSV ;
- `.read_excel()` pour le format XLS de Microsoft ;
- `.read_json()` pour le format JSON ;
- et `.read_sql()` pour le format SQLite.
Importons le fichier *arrests.csv* ([Friendly](#À-propos-des-données)), issu d’une enquête plus large autour des articles du journal *Toronto Star* :
%% Cell type:code id: tags:
``` python
df = pd.read_table("./files/arrests.csv")
```
%% Cell type:markdown id: tags:
La méthode `.head()` permet de jeter un œil aux cinq premières observations du fichier :
%% Cell type:code id: tags:
``` python
df.head()
```
%% Cell type:markdown id: tags:
Le résultat de l’importation n’est pas probant. Il faut savoir que, par défaut, le caractère de séparation de la méthode `.read_table()` est la tabulation (`\t`) et qu’il peut se paramétrer avec le paramètre `sep`. Dans le fichier, le séparateur est la virgule :
%% Cell type:code id: tags:
``` python
df = pd.read_table("./files/arrests.csv", sep=",")
df.head()
```
%% Cell type:markdown id: tags:
Pour les fichiers au format CSV (*comma-separated values*), il est préférable d’opter pour la méthode spécifique `.read_csv()` dont le séparateur par défaut est la virgule :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv")
df.head()
```
%% Cell type:markdown id: tags:
### Description du jeu de données
%% Cell type:markdown id: tags:
Le fichier *arrests.csv* est issu du package R carData (*Companion to Applied Regression Data Sets*). Il recense les personnes arrêtées à Toronto en possession d’une petite quantité de marijuana. L’enquête est constituée de sept variables aléatoires :
|Variable|Description|Type|
|:-:|:-|:-:|
|*released*|Facteur à deux niveaux pour distinguer les personnes relâchées avec une convocation (*Yes*) ou arrêtées sur place (*No*).|qualitative binaire|
|*year*|Vecteur numérique pour l’année de l’arrestation. De 1997 à 2002.|qualitative ordonnée|
|*age*|Vecteur numérique pour l’âge, en nombre d’années.|quantitative continue|
|*sex*|Facteurs à deux niveaux pour le sexe de l’individu : *Male* ou *Female*.|qualitative binaire|
|*employed*|Facteur à deux niveaux : l’individu a-t-il une activité professionnelle (*Yes*) ou non (*N*).|qualitative binaire|
|*citizen*|Facteur à deux niveaux pour qualifier les résidents de Toronto (*Yes*) et les autres (*No*).|qualitative binaire|
|*checks*|Vecteur numérique (0 à 6) qui recense le nombre d’apparitions de l’individu sur les bases de données de la police (arrestations, condamnations antérieures, libération conditionnelle…).|quantitative continue|
%% Cell type:markdown id: tags:
#### Définitions
%% Cell type:markdown id: tags:
**Variable aléatoire :** Donnée mesurée dont le résultat est, en partie, dû au hasard. Du point de vue de l’enquêteur, les réponses des personnes interrogées sont effectivement imprévisibles.
**Variable aléatoire quantitative :** Donnée mesurée dont on peut faire la somme.
**Variable aléatoire quantitative discrète :** Variable dont la mesure peut prendre une valeur isolée, comme la taille, le poids ou encore la tension.
**Variable aléatoire quantitative continue :** Variable dont la mesure pourrait prendre toutes les valeurs d’un intervalle entre deux nombres (âge, quotient intellectuel, numération globulaire).
**Variable aléatoire qualitative :** Donnée mesurée dont on ne peut pas faire la somme, comme la profession, un taux de satisfaction ou encore le sexe d’un individu. Elle peut être de trois types : ordonnée, binaire ou non ordonnée.
%% Cell type:markdown id: tags:
### Gestion de l’en-tête
%% Cell type:markdown id: tags:
Le jeu de données dispose de son en-tête propre, imposé par le responsable ayant modélisé l’enquête. Dans certains cas, il est intéressant de pouvoir modifier les étiquettes associées aux variables, soit pour des questions de lisibilité, soit pour des questions pratiques.
Par défaut, la méthode `.read_csv()` considère la première ligne comme la ligne d’en-tête, mais il est possible de la neutraliser avec la paramètre `header` fixé à `None` :
Par défaut, la méthode `.read_csv()` considère la première ligne comme la ligne d’en-tête, mais il est possible de la neutraliser avec le paramètre `header` fixé à `None` :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv", header=None)
df.head()
```
%% Cell type:markdown id: tags:
Dans ce cas précis, la ligne d’en-tête est devenue une observation comme les autres. La première variable du *data frame*, qui devrait être une série numérique, affiche pour elle `NaN` (*Not a Number*). La raison est simple : dans le fichier de départ, la première variable n’est pas nommée afin d’indiquer qu’il s’agit de la colonne d’index des observations, or, comme *Pandas* s’attend à trouver une donnée numérique, il la considère comme une donnée aberrante.
Pour passer outre, utilisons le paramètre `skiprows` fixé à 1 :
%% Cell type:code id: tags:
``` python
# first row is ignored
df = pd.read_csv("./files/arrests.csv", header=None, skiprows=1)
df.head()
```
%% Cell type:markdown id: tags:
Il reste à rétablir l’en-tête en transmettant des étiquettes personnalisées au paramètre `names` :
%% Cell type:code id: tags:
``` python
names = ["Relâché", "Année", "Âge", "Genre", "En activité", "Torontois", "Citations"]
df = pd.read_csv("./files/arrests.csv", header=None, skiprows=1, names=names)
df.head()
```
%% Cell type:markdown id: tags:
La première colonne est de nouveau la colonne d’index. Si l’on avait voulu parvenir au même résultat tout en conservant l’en-tête original, il aurait simplement fallu lui renseigner la colonne servant d’index avec le paramère `index_col` :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv", index_col=[0])
df.head()
```
%% Cell type:markdown id: tags:
À noter que le nom des variables importées reste toujours disponible dans un paramètre `columns` :
%% Cell type:code id: tags:
``` python
df.columns
```
%% Cell type:markdown id: tags:
## Sélectionner des données
%% Cell type:markdown id: tags:
### Sélectionner une série entière
%% Cell type:markdown id: tags:
L’opération la plus simple consiste à nommer la série à sélectionner :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv", index_col=[0])
df.checks
```
%% Cell type:markdown id: tags:
Des contraintes peuvent être appliquées à la sélection grâce à un prédicat `[]` :
%% Cell type:code id: tags:
``` python
# nb checks of persons who live outside Toronto
df.checks[ df.citizen == "No" ]
```
%% Cell type:markdown id: tags:
Pour sélectionner plus d’une série, il suffit de transmettre la liste de leurs noms :
%% Cell type:code id: tags:
``` python
df[["checks", "sex"]]
```
%% Cell type:markdown id: tags:
### Sélectionner des observations
%% Cell type:markdown id: tags:
Le *slicing* permet de sélectionner des observations à l'intérieur du *data frame* :
%% Cell type:code id: tags:
``` python
df[:3]
```
%% Cell type:markdown id: tags:
Tout comme il est possible de limiter à une série particulière :
%% Cell type:code id: tags:
``` python
df.sex[:3]
```
%% Cell type:markdown id: tags:
Pour appliquer ces restrictions à plusieurs séries, il existe une propriété `loc` qui prend deux paramètres : une *slice* et une liste de séries :
%% Cell type:code id: tags:
``` python
df.loc[:3, ["released", "employed", "citizen"]]
```
%% Cell type:markdown id: tags:
### Appliquer des filtres sur les sélections
%% Cell type:markdown id: tags:
De multiples conditions peuvent s'appliquer sur les séries pour filtrer les données. Si par exemple on voulait ne retenir que l’âge et le nombre de citations des hommes de Toronto interpellés depuis 2000, on traduirait l’énoncé comme ci-dessous. Les opérateurs de comparaison classiques (`==` `>` `<=`…) ainsi que les opérateurs *bitwise* `&` `|` `~` peuvent être utilisés.
%% Cell type:code id: tags:
``` python
df.loc[:, ["age", "checks"]][(df.sex == "Male") & (df.citizen == "Yes") & (df.year >= 2000)]
```
%% Cell type:markdown id: tags:
Le même résultat peut s’obtenir grâce à l’appel à une méthode `.query()` :
%% Cell type:code id: tags:
``` python
df.query("sex == 'Male' & citizen == 'Yes' & year >= 2000 " ).loc[:, ["age", "checks"]]
```
%% Cell type:markdown id: tags:
### Créer un tableau de contingence
%% Cell type:markdown id: tags:
Un tableau de contingence représente des données sous forme de comptage selon plusieurs modalités. Il s’obtient avec la méthode `.crosstab()`, directement attachée à l’objet `pd`, et qui attend deux paramètres obligatoires `index` et `columns`. Un autre paramètre `margins` fait apparaître les sous-totaux :
%% Cell type:code id: tags:
``` python
pd.crosstab(index=df.year, columns=df.employed, margins=True)
```
%% Cell type:markdown id: tags:
## Préparer un jeu de données
%% Cell type:markdown id: tags:
L’objectif derrière l’importation d’un fichier dans un *data frame* est d’exploiter les facilités de *Pandas* afin de préparer un jeu de données qui soit à la fois complet et cohérent. Parmi les opérations les plus fréquentes, nous pouvons citer :
- la conversion d’une série numérique en catégorielle (un taux de satisfaction noté de 1 à 5 exprime effectivement davantage une qualité qu’une quantité) ;
- le remplacement d’une donnée manquante ou aberrante ;
- le regroupement de données dans des catégories plus larges.
%% Cell type:markdown id: tags:
### Reconnaître le type d’une série
%% Cell type:markdown id: tags:
Toute série de données exprimée par une variable statistique est réputée contenir un même type de données au sein d’un vecteur. Pour connaître le type des différents vecteurs, on peut interroger la propriété `dtypes` du *data frame* :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv", index_col=[0])
df.dtypes
```
%% Cell type:markdown id: tags:
Lorsque le jeu de données contient des données ambiguës au sein d’une même série, il peut se révéler utile de préciser dès l’importation le type des différents vecteurs avec l’option `dtype` :
%% Cell type:code id: tags:
``` python
dtypes= {
"released": "category",
"sex": "category",
"employed": "category",
"citizen": "category"
}
df = pd.read_csv("./files/arrests.csv", index_col=[0], dtype=dtypes)
df.dtypes
```
%% Cell type:markdown id: tags:
### Conversion de type
%% Cell type:markdown id: tags:
La solution recommandée pour convertir une série en un autre type de données est de passer par la méthode `.astype()`. Certaines conversions étant impossibles, comme par exemple convertir la chaîne de caractères `"chat"` en entier, il convient de s’assurer au préalable de la légitimité de l’opération :
%% Cell type:code id: tags:
``` python
df.year = df.year.astype("category")
df.dtypes
```
%% Cell type:markdown id: tags:
### Disposer des données manquantes
%% Cell type:markdown id: tags:
La gestion des données manquantes est une étape cruciale de la phase de préparation d’un *dataset*. Pour une seule variable manquante, faut-il écarter l’observation complète, lui attribuer une valeur par défaut ou encore opter pour une solution plus élaborée ?
Chargeons un autre jeu de données, extrait d’une enquête sur les troubles de l’alimentation ([Davis, 1997](#À-propos-des-données)). La méthode `.info()` permet de visualiser rapidement s’il existe ou non des variables qui contiennent des données manquantes :
%% Cell type:code id: tags:
``` python
davis = pd.read_csv("./files/davis.csv", index_col=[0])
davis.info()
```
%% Cell type:markdown id: tags:
Sur un total de 200 observations, deux des cinq variables comportent des valeurs manquantes. Il s’agit de *repwt* et *repht*, qui comptent chacune 17 données manquantes. L’égalité ne doit pas induire en erreur : rien n’assure que les données soient localisées sur les 17 mêmes observations.
Pour s’en assurer, il faut souvent leur faire la chasse. La méthode `.isnull()` permet de jeter un coup d’oeil global sur le *data frame*, sur une série particulière ou encore sur une extraction :
%% Cell type:code id: tags:
``` python
davis.repht[190:].isnull()
```
%% Cell type:markdown id: tags:
À l’inverse, il existe une méthode `.notnull()` pour révéler les données qui ne sont pas manquantes :
%% Cell type:code id: tags:
``` python
davis.notnull()
```
%% Cell type:markdown id: tags:
Couplée aux méthodes `.any()` et `.sum()`, il est possible de reproduire exactement l’information obtenue plus haut avec la méthode `.info()` :
%% Cell type:code id: tags:
``` python
davis.isnull().any()
```
%% Cell type:code id: tags:
``` python
davis.isnull().sum()
```
%% Cell type:markdown id: tags:
Pour véritablement les pister, il peut être utile de connaître plutôt l’indice des observations concernées :
%% Cell type:code id: tags:
``` python
davis.index[davis.isnull().any(axis=1)]
```
%% Cell type:markdown id: tags:
#### Suppression des données manquantes
%% Cell type:markdown id: tags:
S’il s’agit de supprimer moins de 10 % de l’effectif total, la question n’est pas anodine, surtout si le jeu de données est volumineux. Pour réaliser malgré tout cette opération, il existe la méthode `.dropna()` :
%% Cell type:code id: tags:
``` python
davis.dropna(inplace=True)
davis.info()
```
%% Cell type:markdown id: tags:
Une autre stratégie consisterait à ne sélectionner dans un *data frame* que les observations non nulles pour une variable donnée :
%% Cell type:code id: tags:
``` python
davis = pd.read_csv("./files/davis.csv", index_col=[0])
davis = davis[davis.repwt.notna()]
davis.info()
```
%% Cell type:markdown id: tags:
#### Affecter une valeur prédéfinie
%% Cell type:markdown id: tags:
La méthode `.fillna()` offre la possibilité de remplir toutes les données manquantes par une même valeur :
%% Cell type:code id: tags:
``` python
davis = pd.read_csv("./files/davis.csv", index_col=[0])
davis = davis.fillna(0)
davis
```
%% Cell type:markdown id: tags:
Un attribut `method` autorise une stratégie plus subtile, en remplaçant les données manquantes soit par celles qui précèdent (`pad`) soit par celles qui suivent (`bfill`). Il convient alors de s’assurer que les première et dernière observations ne comportent pas de données manquantes :
%% Cell type:code id: tags:
``` python
davis = pd.read_csv("./files/davis.csv", index_col=[0])
davis.repht.fillna(method="pad", inplace=True)
davis.repwt.fillna(method="bfill", inplace=True)
davis
```
%% Cell type:markdown id: tags:
Plus finement, nous pouvons bénéficier des facilités de *Pandas* pour attribuer une valeur moins nocive aux données manquantes d’une série, comme la moyenne arithmétique de l’ensemble de ses valeurs :
%% Cell type:code id: tags:
``` python
davis = pd.read_csv("./files/davis.csv", index_col=[0])
repht_mean = int(davis.repht.mean())
davis.repht.fillna(repht_mean, inplace=True)
davis
```
%% Cell type:markdown id: tags:
### Recoder des variables
%% Cell type:markdown id: tags:
#### Vers des vecteurs numériques
%% Cell type:markdown id: tags:
Comme il est plus facile de manipuler des nombres dans un *data frame*, une opération préléminaire à toute analyse de données consiste souvent à transformer au maximum les séries en vecteurs numériques. C’est par exemple possible en transmettant un dictionnaire d’équivalences à la méthode `.replace()` :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv", index_col=[0])
translations = {
"Yes": 1,
"No": 0,
"Male": 0,
"Female": 1
}
df.replace(translations, inplace=True)
df.head()
```
%% Cell type:markdown id: tags:
Par cette simple opération, notre tableau de données n’utilise désormais que des vecteurs numériques. Il est possible de s’en assurer rapidement :
%% Cell type:code id: tags:
``` python
df.dtypes
```
%% Cell type:markdown id: tags:
Avant de définir des conversions, il est toutefois prudent de bien s’assurer des différentes valeurs contenues dans une série avec la méthode `.unique()` :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv", index_col=[0])
print(
f"released ==> { df['released'].unique() }",
f"sex ==> { df['sex'].unique() }",
f"employed ==> { df['employed'].unique() }",
f"citizen ==> { df['citizen'].unique() }",
sep="\n"
)
```
%% Cell type:markdown id: tags:
#### Vers des vecteurs catégoriels
%% Cell type:markdown id: tags:
L’opération inverse consiste à recoder une variable en plusieurs modalités. Pour cela, il existe la méthode `.cut()` qui permet de segmenter une variable en plusieurs tranches en fonction des modalités convenues. C’est souvent le cas de l’âge des individus que l’on souhaite regrouper en différentes modalités :
%% Cell type:code id: tags:
``` python
df = pd.read_csv("./files/arrests.csv", index_col=[0])
# delimiters are considered right included 'right=True':
# (0-17] (17-24] (24-35] (35-100]
bins = [0, 17, 24, 35, 100]
labels = ["-18 ans", "18-24 ans", "25-35 ans", "+35 ans"]
# segmentation
df["cat_age"] = pd.cut(df.age, bins=bins, labels=labels)
```
%% Cell type:markdown id: tags:
Pour effectuer une segmentation qui conserve une certaine proportionnalité, on aurait pu se fonder sur les quartiles :
%% Cell type:code id: tags:
``` python
df.age.describe()
```
%% Cell type:markdown id: tags:
## Décrire les données
La librairie *Pandas* fournit un ensemble de méthodes pour décrire les données. La première d’entre elles, `.info()` affiche un résumé du *data frame* (nom des variables, présence de valeurs nulles, nombre d’observations) :
%% Cell type:code id: tags:
``` python
df.info()
```
%% Cell type:markdown id: tags:
La méthode `.describe()` fournit quant à elle un aperçu des vecteurs numériques grâce à quelques statistiques :
%% Cell type:code id: tags:
``` python
df.describe()
```
%% Cell type:markdown id: tags:
Grâce à un sélecteur, il est possible de restreindre la description à une série particulière :
%% Cell type:code id: tags:
``` python
print(
df["employed"].describe(),
df["checks"].describe(),
sep="\n\n"
)
```
%% Cell type:markdown id: tags:
De nombreuses opérations statistiques peuvent être ensuite résolues avec les méthodes embarquées par la librairie :
%% Cell type:code id: tags:
``` python
print(
f"Maximum : {df.age.max()}",
f"Minimum : {df.age.min()}",
f"Écart-type : {df.age.std()}",
f"Moyenne : {df.age.mean()}",
f"Mode : {df.age.mode()}",
sep="\n"
)
```
%% Cell type:markdown id: tags:
Citons une dernière méthode très utile pour obtenir des comptages sur les variables, `.value_counts()` :
%% Cell type:code id: tags:
``` python
df.year.value_counts()
```
%% Cell type:markdown id: tags:
## Visualisation graphique
%% Cell type:markdown id: tags:
Nous le disions dans l’introduction, *Pandas* est compatible avec la librairie de référence en matière de visualisation graphique, *Matplolib*. Elle expose un ensemble de méthodes qui peuvent être appliquées soit au *data frame* complet, soit à une série particulière. Nous n’aborderons que les cas les plus simples. Pour des configurations avancées, se référer à [la documentation officielle](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.Series.plot.html).
Toutes les représentations sont disponibles via un espace de nommage `.plot`, quand seule la méthode `.hist()` dispose d’un alias :
%% Cell type:code id: tags:
``` python
_ = df.hist()
```
%% Cell type:markdown id: tags:
### Les diagrammes en barres
%% Cell type:markdown id: tags:
Ils sont utilisés pour représenter la répartition des effectifs d’une variable catégorielle :
%% Cell type:code id: tags:
``` python
sex = df.sex.value_counts()
_ = sex.plot.bar()
```
%% Cell type:markdown id: tags:
### Les histogrammes
%% Cell type:markdown id: tags:
L’histogramme se distingue du diagramme en barres en ce que les barres sont contiguës. Il est donc à réserver aux variables aléatoires quantitatives continues :
%% Cell type:code id: tags:
``` python
_ = df.age.plot.hist()
```
%% Cell type:markdown id: tags:
### Les diagrammes linéaires
%% Cell type:markdown id: tags:
Ce type de graphique permet de représenter des points positionnés sur un plan à deux axes. Ils sont employés avec toute variable quantitative. La méthode `.line()` s’utilise pour souligner une évolution :
%% Cell type:code id: tags:
``` python
data = pd.crosstab(index=df.age, columns=df.sex)
_ = data.plot.line()
```
%% Cell type:markdown id: tags:
### Les diagrammes à secteur
%% Cell type:markdown id: tags:
Les fameux camemberts, qui s’obtiennent avec la méthode `.pie()`. À réserver aux cas où les proportions sont nettes :
%% Cell type:code id: tags:
``` python
checks = df.checks.value_counts()
_ = checks.plot.pie()
```
%% Cell type:markdown id: tags:
### Les boîtes à moustache
%% Cell type:markdown id: tags:
La boîte à moustaches, ou boîte de Tukey du nom de son inventeur John Tukey, est une manière synthétique de représenter la distribution des observations d’une variable quantitative. En effet, elle présente pour une variable donnée la répartition de ses observations entre les trois quartiles. Le corps de la boîte sera alors constitué de 50 % des données, séparées équitablement par la médiane, tandis que les moustaches inférieure et supérieure représenteront chacune à peu près 25 %.
Ces deux dernières mesures sont approximatives, car l’une des forces de la boîte à moustaches est de représenter, au-delà des bornes inférieure et supérieure, les *outliers*, ou données aberrantes.
Et les boîtes à moustaches montrent encore plus leur utilité quand il s’agit de comparer la distribution d’une variable dans des sous-groupes, comme ci-dessous pour la distribution de la longueur des ailes entre les manchots mâles et femelles, pour chaque espèce répertoriée.
%% Cell type:code id: tags:
``` python
_ = df.plot.box(column="checks", by="employed")
```
%% Cell type:markdown id: tags:
## À propos des données
%% Cell type:markdown id: tags:
> (Friendly) Personal communication from Michael Friendly, York University.
> (Davis, 1997) Davis, C., G. Claridge, and D. Cerullo (1997) Personality factors predisposing to weight preoccupation: A continuum approach to the association between eating disorders and personality disorders. *Journal of Psychiatric Research* 31, 467–480. [personal communication from the authors.]
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment