From 24b14045c43761ee6ebac783842736b609cfafb2 Mon Sep 17 00:00:00 2001
From: Alexandre Roulois <alexandre.roulois@u-paris.fr>
Date: Tue, 6 Sep 2022 10:05:10 +0200
Subject: [PATCH] Test binder notebook

---
 5.data-frames.ipynb | 1084 +++++++++++++++++++++++++++++++++++++++++++
 environment.yml     |   20 +
 2 files changed, 1104 insertions(+)
 create mode 100644 5.data-frames.ipynb
 create mode 100644 environment.yml

diff --git a/5.data-frames.ipynb b/5.data-frames.ipynb
new file mode 100644
index 0000000..9ee3d60
--- /dev/null
+++ b/5.data-frames.ipynb
@@ -0,0 +1,1084 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "# Découverte des *data frames*"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Un *data frame* est une structure de données qui peut se concevoir comme une matrice où les colonnes peuvent être de types différents, comme dans ce tableau à deux dimensions :\n",
+    "\n",
+    "|gender|height|\n",
+    "|:-:|:-:|\n",
+    "|F|173|\n",
+    "|F|159|\n",
+    "|M|181|\n",
+    "\n",
+    "Chaque ligne est une *observation* quand les colonnes, autrement appelées *séries*, constituent les variables qui la décrivent."
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Aperçu avec la librairie *Pandas*"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "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, à l’exception que l’on a pour habitude de lui associer un alias *pd* :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "import pandas as pd"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "genders = [\"F\", \"F\", \"M\"]\n",
+    "heights = [173, 159, 181]\n",
+    "\n",
+    "series = {\n",
+    "    \"gender\": genders,\n",
+    "    \"height\": heights\n",
+    "}\n",
+    "\n",
+    "df = pd.DataFrame(series)\n",
+    "\n",
+    "print(df)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Chaque série peut être interrogée individuellement :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(df[\"gender\"])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Tout comme il est possible d’accéder à des observations particulières grâce au *slicing* :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(df[2:])"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Importer un fichier CSV"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Méthodes pour importer un fichier"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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 :\n",
+    "- `.read_csv()` pour le format CSV ;\n",
+    "- `.read_excel()` pour le format XLS de Microsoft ;\n",
+    "- `.read_json()` pour le format JSON ;\n",
+    "- et `.read_sql()` pour le format SQLite.\n",
+    "\n",
+    "Importons le fichier *arrests.csv* (Friendly), issu d’une enquête plus large autour des articles du journal *Toronto Star* :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_table(\"./data/arrests.csv\")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "La méthode `.head()` permet de jeter un œil aux cinq premières observations du fichier :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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 et qu’il peut se paramétrer avec le paramètre `sep` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_table(\"./data/arrests.csv\", sep=\",\")\n",
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Pour les fichiers au format CSV (*comma-separated values*), il est préférable d’opter pour la méthode spécifique :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\")\n",
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Description du jeu de données"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "jp-MarkdownHeadingCollapsed": true,
+    "tags": []
+   },
+   "source": [
+    "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 :\n",
+    "\n",
+    "|Variable|Description|Type|\n",
+    "|:-:|:-|:-:|\n",
+    "|*released*|Facteur à deux niveaux pour distinguer les personnes relâchées avec une convocation (*Yes*) ou arrêtées sur place (*No*).|qualitative binaire|\n",
+    "|*year*|Vecteur numérique pour l’année de l’arrestation. De 1997 à 2002.|qualitative ordonnée|\n",
+    "|*age*|Vecteur numérique pour l’âge, en nombre d’années.|quantitative continue|\n",
+    "|*sex*|Facteurs à deux niveaux pour le sexe de l’individu : *Male* ou *Female*.|qualitative binaire|\n",
+    "|*employed*|Facteur à deux niveaux : l’individu a-t-il une activité professionnelle (*Yes*) ou non (*N*).|qualitative binaire|\n",
+    "|*citizen*|Facteur à deux niveaux pour qualifier les résidents de Toronto (*Yes*) et les autres (*No*).|qualitative binaire|\n",
+    "|*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",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "#### Définitions"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "jp-MarkdownHeadingCollapsed": true,
+    "tags": []
+   },
+   "source": [
+    "**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.\n",
+    "\n",
+    "**Variable aléatoire quantitative :** Donnée mesurée dont on peut faire la somme.\n",
+    "\n",
+    "**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.\n",
+    "\n",
+    "**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).\n",
+    "\n",
+    "**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",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Gestion de l’en-tête"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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.\n",
+    "\n",
+    "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` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", header=None)\n",
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Dans le cas précis, la ligne d’en-tête est devenue une observation comme les autres, avec des valeurs aberrantes. La première variable du *data frame*, qui devrait être un vecteur 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 aberration.\n",
+    "\n",
+    "Pour passer outre, utilisons le paramètre `skiprows` pour lui demander de ne pas tenir compte de la première ligne du fichier :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", header=None, skiprows=1)\n",
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Il reste à rétablir l’en-tête en transmettant des étiquettes personnalisées au paramètre `names` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "names = [\"Relâché\", \"Année\", \"Âge\", \"Genre\", \"En activité\", \"Torontois\", \"Citations\"]\n",
+    "df = pd.read_csv(\"./data/arrests.csv\", header=None, skiprows=1, names=names)\n",
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", index_col=[0])\n",
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "À noter que le nom des variables importées reste toujours disponible dans un paramètre `columns` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.columns"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "## Préparer un jeu de données"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Reconnaître le type d’une série"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", index_col=[0])\n",
+    "df.dtypes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "dtypes= {\n",
+    "    \"released\": \"category\",\n",
+    "    \"sex\": \"category\",\n",
+    "    \"employed\": \"category\",\n",
+    "    \"citizen\": \"category\"\n",
+    "}\n",
+    "df = pd.read_csv(\"./data/arrests.csv\", index_col=[0], dtype=dtypes)\n",
+    "df.dtypes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Conversion de type"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "La solution recommandée pour convertir une colonne 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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df[\"year\"] = df[\"year\"].astype(\"category\")\n",
+    "df.dtypes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Disposer des données manquantes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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 ?\n",
+    "\n",
+    "Chargeons un autre jeu de données, extrait d’une enquête sur les troubles de l’alimentation (Davis, 1997). La méthode `.info()` permet de visualiser rapidement s’il existe ou non des variables qui contiennent des données manquantes :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/davis.csv\", index_col=[0])\n",
+    "\n",
+    "df.info()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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.\n",
+    "\n",
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df[\"repht\"][190:].isnull()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "À l’inverse, il existe une méthode `.notnull()` pour révéler au contraire les données qui ne sont pas manquantes :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.notnull()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.isnull().any()"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.isnull().sum()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Pour véritablement les pister, il peut être utile de connaître plutôt l’indice des observations concernées :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.index[df.isnull().any(axis=1)]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "#### Suppression des données manquantes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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 cette opération, il existe la méthode `.dropna()` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = df.dropna()\n",
+    "df.info()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = df[df[\"repwt\"].notna()]\n",
+    "df.info()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "#### Affecter une valeur prédéfinie"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "La méthode `.fillna()` offre la possibilité de remplir toutes les données manquantes par une même valeur :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/davis.csv\", index_col=[0])\n",
+    "\n",
+    "df = df.fillna(0)\n",
+    "df"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/davis.csv\", index_col=[0])\n",
+    "\n",
+    "df[\"repht\"].fillna(method=\"pad\", inplace=True)\n",
+    "df[\"repwt\"].fillna(method=\"bfill\", inplace=True)\n",
+    "df"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/davis.csv\", index_col=[0])\n",
+    "\n",
+    "repht_mean = int(df[\"repht\"].mean())\n",
+    "df[\"repht\"].fillna(repht_mean, inplace=True)\n",
+    "\n",
+    "df"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Recoder des variables"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Vers des vecteurs numériques"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", index_col=[0])\n",
+    "\n",
+    "translations = {\n",
+    "    \"Yes\": 1,\n",
+    "    \"No\": 0,\n",
+    "    \"Male\": 0,\n",
+    "    \"Female\": 1\n",
+    "}\n",
+    "\n",
+    "df.replace(translations, inplace=True)\n",
+    "df.head()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.dtypes"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", index_col=[0])\n",
+    "\n",
+    "print(\n",
+    "    f\"released ==> { df['released'].unique() }\",\n",
+    "    f\"sex      ==> { df['sex'].unique() }\",\n",
+    "    f\"employed ==> { df['employed'].unique() }\",\n",
+    "    f\"citizen  ==> { df['citizen'].unique() }\",\n",
+    "    sep=\"\\n\"\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Vers des vecteurs catégoriels"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", index_col=[0])\n",
+    "\n",
+    "# delimiters are considered right included 'right=True':\n",
+    "# (0-17] (17-24] (24-35] (35-100]\n",
+    "bins = [0, 17, 24, 35, 100]\n",
+    "labels = [\"-18 ans\", \"18-24 ans\", \"25-35 ans\", \"+35 ans\"]\n",
+    "\n",
+    "# segmentation\n",
+    "df[\"cat_age\"] = pd.cut(df[\"age\"], bins=bins, labels=labels)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Pour effectuer une segmentation qui conserve une certaine proportionnalité, on aurait pu se fonder sur les quartiles :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df[\"age\"].describe()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Sélectionner des données"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Sélectionner une série entière"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "L’opération la plus simple consiste à nommer la série à sélectionner :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df = pd.read_csv(\"./data/arrests.csv\", index_col=[0])\n",
+    "\n",
+    "df[\"checks\"]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Des contraintes peuvent être appliquées à la sélection des données grâce à un prédicat `[]` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# nb checks of persons who live outside Toronto only\n",
+    "df[\"checks\"][df[\"citizen\"] == \"No\"]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Pour sélectionner plus d’une série, il suffit de transmettre la liste de leurs noms :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df[[\"checks\", \"sex\"]]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {
+    "tags": []
+   },
+   "source": [
+    "### Sélectionner des observations"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Le *slicing* permet de sélectionner des observations à l'intérieur du *data frame* :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df[:3]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Tout comme il est possible de limiter à une série particulière :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df[\"sex\"][:3]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.loc[:3, [\"released\", \"employed\", \"citizen\"]]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Appliquer des filtres sur les sélections\n",
+    "\n",
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.loc[:, [\"age\", \"checks\"]][(df[\"sex\"] == \"Male\") & (df[\"citizen\"] == \"Yes\") & (df[\"year\"] >= 2000)]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Le même résultat peut s’obtenir grâce à l’appel à une méthode `.query()` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.query(\"sex == 'Male' & citizen == 'Yes' & year >= 2000 \" ).loc[:, [\"age\", \"checks\"]]"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Décrire les données\n",
+    "\n",
+    "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",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.info()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "La méthode `.describe()` fournit quant à elle un aperçu des vecteurs numériques grâce à quelques statistiques :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "df.describe()"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Grâce à un sélecteur, il est possible de restreindre la description à une série particulière :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(\n",
+    "    df[\"employed\"].describe(),\n",
+    "    df[\"checks\"].describe(),\n",
+    "    sep=\"\\n\\n\"\n",
+    ")"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "De nombreuses opérations statistiques peuvent être ensuite résolues avec les méthodes embarquées par la librairie :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "# max\n",
+    "print(df[\"age\"].max())\n",
+    "\n",
+    "# min\n",
+    "print(df[\"age\"].min())\n",
+    "\n",
+    "# standard deviation\n",
+    "print(df[\"age\"].std())\n",
+    "\n",
+    "# average\n",
+    "print(df[\"age\"].mean())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "Citons une dernière méthode très utile pour obtenir des comptages sur les variables, `.value_counts()` :"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "print(df[\"year\"].value_counts())"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## À propos des données"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "> (Friendly) Personal communication from Michael Friendly, York University. \n",
+    "\n",
+    "> (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.]"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3 (ipykernel)",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.10.6"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 4
+}
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 0000000..ccbddac
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,20 @@
+name: tal-ml
+channels:
+  - conda-forge
+  - defaults
+dependencies:
+  - beautifulsoup4=4.11
+  - jupyterlab=3.4
+  - jupyterlab-latex=3.1
+  - matplotlib=3.5
+  - nltk=3.6
+  - numpy=1.23
+  - pandas=1.4
+  - pip
+  - python=3.10
+  - scikit-learn=1.1
+  - scipy=1.9
+  - scrapy=2.6
+  - seaborn=0.11
+  - pip:
+    - git+https://github.com/ClaudeCoulombe/FrenchLefffLemmatizer.git
-- 
GitLab