mirror of
https://github.com/ArthurDanjou/ArtStudies.git
synced 2026-01-14 13:54:06 +01:00
Compare commits
28 Commits
9b0b24bc8b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| b8b0024852 | |||
| 3e1ac18acd | |||
| 77feb27b97 | |||
| bcb8c66a9d | |||
| 03bc530c3a | |||
| 27fd147d0f | |||
| 56fdd5da45 | |||
| 3e6b2e313a | |||
| 346695212d | |||
| 8e7bbc1fe9 | |||
| c8c1bf4807 | |||
| 2e2500b509 | |||
| 5f5bd609d7 | |||
| e56fd6f2af | |||
| 0e65815e38 | |||
| 6eecdd6ab3 | |||
| 06bc1f28a9 | |||
| c5f60472fb | |||
| 0cb4dd4c57 | |||
| 98807a1b63 | |||
| 156411965d | |||
| fd775d1251 | |||
| 2824a9aed1 | |||
| acf1aa82c4 | |||
| f326ca42e0 | |||
| 5d01240748 | |||
| 7e62eaeb04 | |||
| 9e28765022 |
13
.gitignore
vendored
13
.gitignore
vendored
@@ -7,7 +7,6 @@
|
||||
|
||||
.RData
|
||||
.RHistory
|
||||
*.pdf
|
||||
|
||||
.ipynb_checkpoints
|
||||
|
||||
@@ -23,4 +22,14 @@ dashboard_files
|
||||
Beaudelaire.txt
|
||||
Baudelaire_len_32.p
|
||||
|
||||
dataviz-project
|
||||
NoticeTechnique_files
|
||||
.posit
|
||||
renv
|
||||
|
||||
results/
|
||||
results_stage_1/
|
||||
results_stage_2/
|
||||
*.safetensors
|
||||
*.pt
|
||||
*.pth
|
||||
*.bin
|
||||
BIN
M1/General Linear Models/Projet/GLM Final report.pdf
Normal file
BIN
M1/General Linear Models/Projet/GLM Final report.pdf
Normal file
Binary file not shown.
BIN
M1/General Linear Models/Projet/Projet_R_MLG.pdf
Normal file
BIN
M1/General Linear Models/Projet/Projet_R_MLG.pdf
Normal file
Binary file not shown.
Binary file not shown.
208
M2/Advanced Machine Learning/TP1.ipynb
Normal file
208
M2/Advanced Machine Learning/TP1.ipynb
Normal file
@@ -0,0 +1,208 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "8226e658",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import pandas as pd"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "7e95cb09",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"application/vnd.microsoft.datawrangler.viewer.v0+json": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "index",
|
||||
"rawType": "int64",
|
||||
"type": "integer"
|
||||
},
|
||||
{
|
||||
"name": "X1",
|
||||
"rawType": "float64",
|
||||
"type": "float"
|
||||
},
|
||||
{
|
||||
"name": "X2",
|
||||
"rawType": "float64",
|
||||
"type": "float"
|
||||
},
|
||||
{
|
||||
"name": "Y",
|
||||
"rawType": "float64",
|
||||
"type": "float"
|
||||
}
|
||||
],
|
||||
"ref": "018727a2-2342-424f-8395-021f40817c5a",
|
||||
"rows": [
|
||||
[
|
||||
"0",
|
||||
"-0.8363543",
|
||||
"4.520502",
|
||||
"-19.868094121443526"
|
||||
],
|
||||
[
|
||||
"1",
|
||||
"0.4020083",
|
||||
"3.252834",
|
||||
"-10.46598545005849"
|
||||
],
|
||||
[
|
||||
"2",
|
||||
"-0.2492138",
|
||||
"3.610425",
|
||||
"-12.91499193423918"
|
||||
],
|
||||
[
|
||||
"3",
|
||||
"-0.6257167",
|
||||
"4.58877",
|
||||
"-20.67839639765537"
|
||||
],
|
||||
[
|
||||
"4",
|
||||
"-0.9899948",
|
||||
"4.893924",
|
||||
"-22.99404413854238"
|
||||
]
|
||||
],
|
||||
"shape": {
|
||||
"columns": 3,
|
||||
"rows": 5
|
||||
}
|
||||
},
|
||||
"text/html": [
|
||||
"<div>\n",
|
||||
"<style scoped>\n",
|
||||
" .dataframe tbody tr th:only-of-type {\n",
|
||||
" vertical-align: middle;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe tbody tr th {\n",
|
||||
" vertical-align: top;\n",
|
||||
" }\n",
|
||||
"\n",
|
||||
" .dataframe thead th {\n",
|
||||
" text-align: right;\n",
|
||||
" }\n",
|
||||
"</style>\n",
|
||||
"<table border=\"1\" class=\"dataframe\">\n",
|
||||
" <thead>\n",
|
||||
" <tr style=\"text-align: right;\">\n",
|
||||
" <th></th>\n",
|
||||
" <th>X1</th>\n",
|
||||
" <th>X2</th>\n",
|
||||
" <th>Y</th>\n",
|
||||
" </tr>\n",
|
||||
" </thead>\n",
|
||||
" <tbody>\n",
|
||||
" <tr>\n",
|
||||
" <th>0</th>\n",
|
||||
" <td>-0.836354</td>\n",
|
||||
" <td>4.520502</td>\n",
|
||||
" <td>-19.868094</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>1</th>\n",
|
||||
" <td>0.402008</td>\n",
|
||||
" <td>3.252834</td>\n",
|
||||
" <td>-10.465985</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>2</th>\n",
|
||||
" <td>-0.249214</td>\n",
|
||||
" <td>3.610425</td>\n",
|
||||
" <td>-12.914992</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>3</th>\n",
|
||||
" <td>-0.625717</td>\n",
|
||||
" <td>4.588770</td>\n",
|
||||
" <td>-20.678396</td>\n",
|
||||
" </tr>\n",
|
||||
" <tr>\n",
|
||||
" <th>4</th>\n",
|
||||
" <td>-0.989995</td>\n",
|
||||
" <td>4.893924</td>\n",
|
||||
" <td>-22.994044</td>\n",
|
||||
" </tr>\n",
|
||||
" </tbody>\n",
|
||||
"</table>\n",
|
||||
"</div>"
|
||||
],
|
||||
"text/plain": [
|
||||
" X1 X2 Y\n",
|
||||
"0 -0.836354 4.520502 -19.868094\n",
|
||||
"1 0.402008 3.252834 -10.465985\n",
|
||||
"2 -0.249214 3.610425 -12.914992\n",
|
||||
"3 -0.625717 4.588770 -20.678396\n",
|
||||
"4 -0.989995 4.893924 -22.994044"
|
||||
]
|
||||
},
|
||||
"execution_count": 12,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"data = pd.read_excel(\"./data/data_pdp.xlsx\")\n",
|
||||
"data.head()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "4e9a9a97",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def partial_dependant_function(data: pd.DataFrame, model: object, feature: str, grid_points: list) -> list:\n",
|
||||
" \"\"\"Compute the Partial Dependence Plot (PDP) for a given feature.\"\"\"\n",
|
||||
" pdp = []\n",
|
||||
" for val in grid_points:\n",
|
||||
" data_temp = data.copy()\n",
|
||||
" data_temp[feature] = val\n",
|
||||
" preds = model.predict(data_temp)\n",
|
||||
" pdp.append(preds.mean())\n",
|
||||
" return pdp"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "9553a1d8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "studies",
|
||||
"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.13.9"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
BIN
M2/Advanced Machine Learning/data/data_pdp.xlsx
Normal file
BIN
M2/Advanced Machine Learning/data/data_pdp.xlsx
Normal file
Binary file not shown.
53
M2/Clustering In Practice/Encoding.Rmd
Normal file
53
M2/Clustering In Practice/Encoding.Rmd
Normal file
@@ -0,0 +1,53 @@
|
||||
```{r}
|
||||
library(caret)
|
||||
library(dplyr)
|
||||
```
|
||||
|
||||
# One Hot Encoding
|
||||
|
||||
```{r}
|
||||
df <- data.frame(
|
||||
team = c('A', 'A', 'B', 'B', 'B', 'B', 'C', 'C'),
|
||||
points = c(25, 12, 15, 14, 19, 23, 25, 29)
|
||||
)
|
||||
|
||||
dummies <- dummyVars(~team + points, data = df)
|
||||
one_hot_data <- predict(dummies, newdata = df)
|
||||
|
||||
one_hot_data
|
||||
```
|
||||
|
||||
# Target Encoding
|
||||
|
||||
```{r}
|
||||
train <- data.frame(
|
||||
target = c(10, 20, 15),
|
||||
cat_col1 = c('city1', 'city2', 'city1'),
|
||||
cat_col2 = c('james', 'adam', 'charles')
|
||||
)
|
||||
|
||||
global_mean <- mean(train$target)
|
||||
alpha <- 10
|
||||
|
||||
target_encoding <- train %>%
|
||||
group_by(cat_col1) %>%
|
||||
summarise(
|
||||
n = n(),
|
||||
sum_target = sum(target),
|
||||
cat_col1_te = (sum_target + (alpha * global_mean)) / (n + alpha),
|
||||
.groups = "drop"
|
||||
) %>%
|
||||
select(cat_col1, cat_col1_te)
|
||||
|
||||
train <- train %>% left_join(target_encoding, by = "cat_col1")
|
||||
```
|
||||
|
||||
# Frequential Encoding
|
||||
|
||||
|
||||
```{r}
|
||||
df <- data.frame(
|
||||
color = c('blue', 'red', 'blue', 'green'),
|
||||
value = c(10, 20, 10, 30)
|
||||
)
|
||||
```
|
||||
2008
M2/Clustering In Practice/data/chiffres.csv
Normal file
2008
M2/Clustering In Practice/data/chiffres.csv
Normal file
File diff suppressed because it is too large
Load Diff
BIN
M2/Data Visualisation/Exemple Projet/Notice projet.pdf
Normal file
BIN
M2/Data Visualisation/Exemple Projet/Notice projet.pdf
Normal file
Binary file not shown.
1
M2/Data Visualisation/Project/.Rprofile
Normal file
1
M2/Data Visualisation/Project/.Rprofile
Normal file
@@ -0,0 +1 @@
|
||||
source("renv/activate.R")
|
||||
799
M2/Data Visualisation/Project/NoticeTechnique.Rmd
Normal file
799
M2/Data Visualisation/Project/NoticeTechnique.Rmd
Normal file
@@ -0,0 +1,799 @@
|
||||
---
|
||||
output:
|
||||
pdf_document:
|
||||
number_sections: true
|
||||
toc: false
|
||||
toc_depth: 2
|
||||
fig_caption: true
|
||||
highlight: tango
|
||||
latex_engine: xelatex
|
||||
geometry: "left=2cm,right=2cm,top=2cm,bottom=2cm"
|
||||
header-includes:
|
||||
- \usepackage{titling}
|
||||
- \usepackage{graphicx}
|
||||
- \usepackage{fancyhdr}
|
||||
- \pagestyle{fancy}
|
||||
- \fancyhead[L]{Notice Technique - Tuberculose}
|
||||
- \fancyhead[R]{Arthur DANJOU}
|
||||
- \fancyfoot[C]{\thepage}
|
||||
---
|
||||
|
||||
\begin{titlepage}
|
||||
\begin{center}
|
||||
\vspace*{1cm}
|
||||
|
||||
% --- En-tête Université ---
|
||||
{\Large \textsc{Université Paris-Dauphine -- PSL}} \\
|
||||
\vspace{0.2cm}
|
||||
{\large Master 280 -- Ingénierie Statistique et Financière}
|
||||
|
||||
\vspace{1.5cm}
|
||||
|
||||
% --- Bloc Titre ---
|
||||
\hrulefill
|
||||
\vspace{0.4cm}
|
||||
|
||||
{\bfseries \Huge \uppercase{Monitorage et Segmentation \\[0.3cm] de la Tuberculose (OMS)}}
|
||||
|
||||
\vspace{0.4cm}
|
||||
\hrulefill
|
||||
|
||||
\vspace{1.5cm}
|
||||
|
||||
% --- AJOUT : Problématique ---
|
||||
% Utilisation d'une minipage pour contrôler la largeur du texte (80% de la page)
|
||||
\begin{minipage}{0.85\textwidth}
|
||||
\centering
|
||||
\Large \textit{«~Au-delà des agrégats nationaux : comment l'analyse multivariée permet-elle de révéler une typologie opérationnelle des risques sanitaires mondiaux face à la tuberculose ?~»}
|
||||
\end{minipage}
|
||||
|
||||
\vspace{2cm}
|
||||
|
||||
% --- Auteur et Enseignant ---
|
||||
{\Large \textsc{Arthur DANJOU}} \\
|
||||
\vspace{1.5cm}
|
||||
|
||||
{\large Enseignant :} \\ [0.2cm]
|
||||
{\large Quentin GUIBERT}
|
||||
|
||||
\vfill % Pousse le logo vers le bas de page automatiquement
|
||||
|
||||
% --- Logo ---
|
||||
\includegraphics[height=25mm]{logo_dauphine.jpg}
|
||||
\vspace{0.5cm}
|
||||
|
||||
\hrulefill
|
||||
\vspace{0.2cm}
|
||||
|
||||
% --- Pied de page ---
|
||||
{\textsc{Data Visualisation \\ Année Universitaire 2025-2026}}
|
||||
|
||||
\end{center}
|
||||
\end{titlepage}
|
||||
|
||||
\newpage
|
||||
\tableofcontents
|
||||
\newpage
|
||||
|
||||
```{r setup, include=FALSE}
|
||||
knitr::opts_chunk$set(
|
||||
echo = FALSE,
|
||||
warning = FALSE,
|
||||
message = FALSE,
|
||||
fig.align = "center",
|
||||
out.width = "75%"
|
||||
)
|
||||
|
||||
library(tidyverse)
|
||||
library(sf)
|
||||
library(rnaturalearth)
|
||||
library(rnaturalearthdata)
|
||||
library(knitr)
|
||||
library(kableExtra)
|
||||
library(gridExtra)
|
||||
library(moments)
|
||||
library(factoextra)
|
||||
```
|
||||
|
||||
> * **Application déployée :** [https://go.arthurdanjou.fr/datavis-app](https://go.arthurdanjou.fr/datavis-app)
|
||||
> * **Code Source de(GitHub) :** [https://go.arthurdanjou.fr/datavis-code](https://go.arthurdanjou.fr/datavis-code)
|
||||
|
||||
# Introduction
|
||||
|
||||
## Contexte et enjeux sanitaires
|
||||
|
||||
Avec 1,6 million de décès annuels et plus de 10 millions de nouveaux cas estimés en 2022, la tuberculose (TB) demeure la deuxième maladie infectieuse la plus meurtrière au monde après le COVID-19 (OMS, 2025). Pourtant, derrière ces chiffres globaux se cache une épidémie profondément inégalitaire. Alors que certains pays rapportent une incidence maîtrisée inférieure à 10 cas pour 100 000 habitants, d'autres font face à des taux critiques dépassant les 500 cas, révélant des fractures sanitaires majeures entre les nations.
|
||||
|
||||
Pour piloter la réponse mondiale, l'Organisation Mondiale de la Santé produit le *Global Tuberculosis Report*, une base de données exhaustive comptant plus de 200 pays et une quarantaine d'indicateurs. Cependant, la richesse même de ces données pose un défi d'analyse : face à la multitude de variables (incidence, notification, mortalité, co-infection), les tableaux statistiques traditionnels échouent à offrir une vision synthétique et opérationnelle. Ils ne permettent ni d'identifier rapidement les profils à risque, ni de visualiser les dynamiques temporelles complexes.
|
||||
|
||||
## Problématique : Au-delà des agrégats nationaux : comment l'analyse multivariée permet-elle de révéler une typologie opérationnelle des risques sanitaires mondiaux face à la tuberculose ?
|
||||
|
||||
Ce projet déploie une chaîne de traitement Data Science complète reposant sur trois piliers. Premièrement, une rationalisation de la donnée par sélection de variables et analyse exploratoire (EDA) pour isoler les signaux pertinents. Deuxièmement, une segmentation intelligente (Clustering K-Means) pour identifier des profils de risque homogènes au-delà des simples zones géographiques. Enfin, une opérationnalisation interactive via une application R Shiny, offrant aux décideurs une interface dynamique pour visualiser les tendances 2000-2024.
|
||||
|
||||
## Périmètre et Structure
|
||||
|
||||
L'étude se concentre sur les indicateurs épidémiologiques "durs" pour garantir la robustesse du modèle, les facteurs exogènes (PIB, dépenses) étant considérés comme contextuels.
|
||||
|
||||
La suite de cette notice détaille la méthodologie : la préparation des données (Section 2) et la modélisation mathématique (Section 3) précèdent l'analyse des profils identifiés (Section 4). L'architecture de l'application R Shiny est décrite en Section 5, suivie de l'exploitation des résultats et du benchmarking (Section 6). Le document se clôt sur les perspectives d'évolution (Section 7) et le cadre d'intégrité académique (Section 8).
|
||||
|
||||
# Analyse Exploratoire des Données
|
||||
|
||||
## Source et structure des données
|
||||
|
||||
### Origine et portée des données
|
||||
|
||||
Le socle empirique repose sur les données du *Global Tuberculosis Report* 2024 de l'OMS, référence internationale couvrant 25 ans (2000-2024) pour 215 territoires. Le fichier brut de 50 variables s'articule autour de trois dimensions complémentaires : **épidémiologique** (morbidité, mortalité, prise en charge), **démographique** (structure de population nécessaire à la standardisation des taux) et **géopolitique** (métadonnées spatiales et codes ISO-3) dédiées à l'analyse spatiale.
|
||||
|
||||
### Convention de nommage et sémantique
|
||||
|
||||
L'analyse requiert la maîtrise d'une nomenclature rigoureuse distinguant les **Cas notifiés** (préfixe `c_`, données brutes administratives) des **Estimations modélisées** (préfixe `e_`), par lesquelles l'OMS corrige les biais de sous-déclaration et intègre les incertitudes. Pour cette étude, nous privilégierons exclusivement ces variables estimées (`e_`) : ce choix méthodologique permet de neutraliser l'hétérogénéité des performances administratives locales afin de garantir une comparabilité internationale stricte des dynamiques épidémiques.
|
||||
|
||||
### Qualité des données et limites
|
||||
|
||||
Bien qu'offrant une profondeur spatio-temporelle unique appuyée par une méthodologie standardisée, ce jeu de données présente des hétérogénéités inhérentes à la surveillance mondiale. Les biais de mesure restent prégnants pour les pays à faibles revenus ou en conflit, où les estimations reposent sur l'extrapolation statistique plutôt que sur un comptage exhaustif, sans compter le caractère provisoire des données récentes (2023-2024). Ces limites intrinsèques justifient l'adoption d'une approche méthodologique prudente, privilégiant l'exclusion des variables incertaines et le rejet de l'imputation pour les observations incomplètes.
|
||||
|
||||
### Importation et aperçu initial
|
||||
|
||||
```{r}
|
||||
data_raw <- read.csv("data/TB_burden_countries_2025-12-09.csv")
|
||||
```
|
||||
|
||||
Le jeu de données importé contient $5347$ observations et $50$ variables. Le tableau ci-dessous présente les dix premières lignes du jeu de données, illustrant la structure longitudinale pour le premier pays par ordre alphabétique (Afghanistan) au début de la période d'étude (de 2000 à 2009).
|
||||
|
||||
```{r}
|
||||
data_raw |>
|
||||
select(year, country, g_whoregion, e_inc_100k, e_mort_exc_tbhiv_100k) |>
|
||||
head(10) |>
|
||||
kable(
|
||||
col.names = c(
|
||||
"Année",
|
||||
"Pays",
|
||||
"Région",
|
||||
"Incidence (/100k)",
|
||||
"Mortalité (/100k)"
|
||||
),
|
||||
caption = "Aperçu des premières lignes du jeu de données brut",
|
||||
booktabs = TRUE
|
||||
) |>
|
||||
kable_styling(latex_options = c("striped", "hold_position"))
|
||||
```
|
||||
|
||||
## Sélection de variables
|
||||
|
||||
La qualité d'une segmentation non-supervisée étant tributaire de la pertinence des entrées, l'injection brute des 50 variables initiales a été écartée pour prévenir deux écueils méthodologiques. D'une part, le **fléau de la dimension** (*Curse of Dimensionality*) qui tend à uniformiser les distances euclidiennes et flouter les clusters et d'autre part, le **biais de redondance**, où la colinéarité des variables risque de surpondérer artificiellement un même phénomène. Nous avons donc déployé une stratégie de réduction de dimension en deux temps : un filtrage structurel (approche par entonnoir) consolidé par un arbitrage statistique des corrélations.
|
||||
|
||||
### Approche par entonnoir : élimination des métadonnées, des bornes d'incertitude et des valeurs absolues
|
||||
|
||||
Une stratégie de réduction de dimension en quatre étapes successives a été appliquée pour isoler les variables pertinentes. Dans un premier temps, le **nettoyage structurel** et la simplification ont permis d'écarter les métadonnées techniques (ex: `iso_numeric`) ainsi que les bornes d'incertitude (`_lo`, `_hi`), jugées non pertinentes pour le calcul de distances euclidiennes ou redondantes avec l'estimation centrale. Ensuite, l'étape de **standardisation** a exclu les valeurs absolues (`_num`) afin de neutraliser tout biais démographique et permettre la comparaison directe entre pays de tailles hétérogènes. Enfin, un **filtrage de la colinéarité** a supprimé les indicateurs redondants (corrélation > 0,8), tels que les notifications brutes, pour éviter de biaiser la pondération des dimensions dans l'algorithme de clustering.
|
||||
|
||||
### Arbitrage méthodologique : traitement de la colinéarité (Incidence vs Notifications) et de la redondance (Mortalité vs Mortalité VIH)
|
||||
|
||||
À l'issue du filtrage structurel, il subsiste plusieurs candidats potentiels pour mesurer la charge épidémique. Pour éviter la redondance (colinéarité), nous analysons la matrice de corrélation de Pearson entre ces candidats.
|
||||
|
||||
L'objectif est de conserver les variables les plus représentatives tout en maximisant l'orthogonalité (l'indépendance) des informations fournies au modèle. La figure ci-dessous visualise la matrice de corrélation de Pearson entre les quatre variables candidates : l'incidence (estimée et notifiée) et la mortalité (avec et hors VIH).
|
||||
|
||||
```{r}
|
||||
vars_candidates <- data_raw |>
|
||||
select(
|
||||
"Incidence (Estimée)" = e_inc_100k,
|
||||
"Incidence (Notifiée)" = c_newinc_100k,
|
||||
"Mortalité (Hors VIH)" = e_mort_exc_tbhiv_100k,
|
||||
"Mortalité (Avec VIH)" = e_mort_tbhiv_100k
|
||||
)
|
||||
|
||||
cor_mat <- cor(vars_candidates, use = "pairwise.complete.obs")
|
||||
```
|
||||
|
||||
```{r}
|
||||
cor_df <- as.data.frame(cor_mat) %>%
|
||||
tibble::rownames_to_column(var = "Var1") %>%
|
||||
pivot_longer(-Var1, names_to = "Var2", values_to = "r") %>%
|
||||
mutate(
|
||||
Var1 = factor(Var1, levels = unique(Var1)),
|
||||
Var2 = factor(Var2, levels = rev(unique(Var1)))
|
||||
)
|
||||
|
||||
ggplot(cor_df, aes(x = Var1, y = Var2, fill = r)) +
|
||||
geom_tile(color = "white") +
|
||||
geom_text(aes(label = round(r, 2)), size = 3) +
|
||||
scale_fill_gradient2(
|
||||
low = "#313695",
|
||||
mid = "white",
|
||||
high = "#a50026",
|
||||
midpoint = 0,
|
||||
limits = c(-1, 1),
|
||||
name = "r"
|
||||
) +
|
||||
coord_fixed() +
|
||||
theme_minimal() +
|
||||
theme(
|
||||
axis.text.x = element_text(angle = 45, hjust = 1),
|
||||
axis.title = element_blank(),
|
||||
panel.grid = element_blank()
|
||||
)
|
||||
```
|
||||
|
||||
#### Analyse et décisions de modélisation :
|
||||
|
||||
L'analyse de la matrice de corrélation a imposé deux arbitrages majeurs. Premièrement, l'**Incidence Estimée** (`e_inc_100k`) a été préférée aux cas notifiés. En effet, ces derniers souffrent d'un biais administratif : un faible taux de notification peut refléter un manque de médecins plutôt qu'une absence de malades, alors que l'estimation de l'OMS corrige ces sous-diagnostics pour refléter la charge réelle.
|
||||
|
||||
Deuxièmement, nous avons retenu la **Mortalité hors VIH** (`e_mort_exc_tbhiv_100k`) malgré sa redondance avec la mortalité globale. Inclure la mortalité liée au VIH aurait risqué de biaiser la segmentation en isolant un "cluster SIDA" (spécifique à l'Afrique Australe), ce qui aurait masqué notre objectif principal : évaluer la performance des programmes antituberculeux indépendamment de l'accès aux antirétroviraux.
|
||||
|
||||
#### Synthèse des variables retenues :
|
||||
|
||||
Le modèle de clustering reposera donc sur un couple de variables actives parcimonieux et complémentaire :
|
||||
|
||||
- Variable Active 1 : Incidence (Diffusion de la maladie) - `e_inc_100k`.
|
||||
- Variable Active 2 : Mortalité (Sévérité / Échec du traitement) - `e_mort_exc_tbhiv_100k`
|
||||
|
||||
Ces deux dimensions, bien que corrélées ($r \approx 0.73$), ne sont pas redondantes : la variance non expliquée par la corrélation correspond justement à la différence d'efficacité des systèmes de soins (capacité à guérir les malades identifiés), ce qui est le cœur de notre segmentation.
|
||||
|
||||
### Variables illustratives et contextuelles
|
||||
|
||||
En complément des variables actives, cinq variables illustratives sont conservées pour éclairer l'interprétation a posteriori sans biaiser le calcul des distances euclidiennes. Le contexte démographique est porté par la Population (`e_pop_num`), indispensable aux pondérations, tandis que le volet géopolitique repose sur la **Région OMS** (`g_whoregion`), structurant l'analyse spatiale en six zones administratives (AFR, AMR, EMR, EUR, SEA, WPR). Enfin, les identifiants techniques — **Pays, Code ISO et Année** — assurent les fonctions supports : étiquetage, jointure cartographique et filtrage dynamique des trajectoires temporelles.
|
||||
|
||||
#### Création du sous-ensemble de travail :
|
||||
|
||||
Nous appliquons cette sélection au jeu de données brut pour ne conserver que les 7 colonnes d'intérêt.
|
||||
|
||||
```{r, echo=TRUE}
|
||||
tb_clean <- data_raw |>
|
||||
select(
|
||||
iso3,
|
||||
country,
|
||||
year,
|
||||
g_whoregion,
|
||||
e_inc_100k,
|
||||
e_mort_exc_tbhiv_100k,
|
||||
e_pop_num
|
||||
)
|
||||
```
|
||||
|
||||
## Traitement des valeurs manquantes
|
||||
|
||||
La gestion des valeurs manquantes (NA) est une étape critique en analyse de données, particulièrement pour les méthodes de partitionnement comme les K-Means qui reposent sur des calculs de distance euclidienne et ne tolèrent aucune incomplétude vectorielle.
|
||||
|
||||
Cette étape ne relève pas du simple "nettoyage" technique mais constitue un choix méthodologique qui influence la représentativité de l'échantillon final.
|
||||
|
||||
### Diagnostic de la structure des manquants
|
||||
|
||||
Nous analysons la distribution spatio-temporelle des valeurs manquantes sur la variable de mortalité (`e_mort_exc_tbhiv_100k`), l'incidence étant complète par construction (filtrage préalable).
|
||||
|
||||
```{r}
|
||||
ggplot(tb_clean, aes(x = year, y = e_inc_100k)) +
|
||||
geom_point(
|
||||
aes(color = is.na(e_mort_exc_tbhiv_100k)),
|
||||
alpha = 0.6,
|
||||
size = 1.5
|
||||
) +
|
||||
scale_color_manual(
|
||||
values = c("TRUE" = "red", "FALSE" = "blue"),
|
||||
labels = c("FALSE" = "Donnée Complète", "TRUE" = "Donnée Manquante")
|
||||
) +
|
||||
labs(
|
||||
subtitle = "Les points rouges indiquent les observations exclues de l'analyse",
|
||||
x = "Année",
|
||||
y = "Incidence (log scale)",
|
||||
color = "Statut"
|
||||
) +
|
||||
scale_y_log10() +
|
||||
theme_minimal()
|
||||
```
|
||||
|
||||
### Analyse d'impact de l'exclusion
|
||||
|
||||
Le tableau ci-dessous identifie les territoires les plus affectés :
|
||||
|
||||
```{r}
|
||||
missing_profiles <- tb_clean |>
|
||||
filter(is.na(e_mort_exc_tbhiv_100k)) |>
|
||||
group_by(country, g_whoregion) |>
|
||||
summarise(
|
||||
n_missing = n(),
|
||||
avg_incidence = mean(e_inc_100k, na.rm = TRUE),
|
||||
total_pop_affected = mean(e_pop_num, na.rm = TRUE),
|
||||
.groups = "drop"
|
||||
) |>
|
||||
arrange(desc(n_missing)) |>
|
||||
head(10)
|
||||
|
||||
kable(
|
||||
missing_profiles,
|
||||
col.names = c(
|
||||
"Territoire",
|
||||
"Région",
|
||||
"Années manquantes",
|
||||
"Incidence Moyenne",
|
||||
"Population Moy."
|
||||
),
|
||||
caption = "Top 10 des territoires exclus pour données manquantes",
|
||||
digits = 0,
|
||||
format.args = list(big.mark = " "),
|
||||
booktabs = TRUE
|
||||
) |>
|
||||
kable_styling(latex_options = c("striped", "hold_position"))
|
||||
```
|
||||
|
||||
Ce tableau confirme que les données manquantes concernent quasi-exclusivement des micro-états et territoires insulaires à très faible démographie (souvent inférieure à 100 000 habitants), validant ainsi leur exclusion sans impact significatif sur la représentativité mondiale de l'étude.
|
||||
|
||||
### Justification méthodologique
|
||||
|
||||
L'exclusion des données manquantes se fonde sur trois justifications méthodologiques. D'un point de vue **géographique**, ces lacunes concernent quasi-exclusivement des micro-états ou territoires insulaires (ex: Monaco, Anguilla) dont la faible démographie induit une volatilité statistique excessive rendant les estimations peu fiables. Sur le plan **épidémiologique**, cette suppression est sans impact stratégique : ces territoires, bien que représentant 15% des observations, ne cumulent que 0,1% de la population mondiale et affichent une incidence marginale (17 cas/100k contre 125 pour l'échantillon conservé). Enfin, l'i**ntégrité statistique** a prévalu sur l'exhaustivité artificielle : le recours à l'imputation a été écarté car la génération de valeurs synthétiques pour ces profils atypiques risquerait de bruiter le calcul des distances euclidiennes et d'introduire des artefacts mathématiques préjudiciables au clustering.
|
||||
|
||||
### Finalisation de l'échantillon
|
||||
|
||||
Nous appliquons donc le filtre définitif pour générer le jeu de données d'analyse.
|
||||
|
||||
```{r, results='asis', echo=TRUE}
|
||||
tb_clean <- tb_clean |> drop_na(e_inc_100k, e_mort_exc_tbhiv_100k)
|
||||
```
|
||||
|
||||
L'exclusion des observations incomplètes réduit la taille de l'échantillon de 15% (de 5 322 à 4 532 observations valides), couvrant 183 pays sur la période 2000-2024
|
||||
|
||||
## Analyse et Transformation
|
||||
|
||||
Cette étape vise à caractériser la structure distributionnelle des variables actives (`tb_clean`). L'objectif est double : comprendre la dynamique épidémique sous-jacente et préparer les données pour satisfaire les hypothèses de l'algorithme K-Means (sensibilité aux valeurs extrêmes et aux variances inégales).
|
||||
|
||||
### Statistiques descriptives et asymétrie
|
||||
|
||||
Le tableau ci-dessous résume les moments statistiques des deux variables actives sur l'ensemble de la période ($n = 4 532$ observations).
|
||||
|
||||
```{r}
|
||||
desc_stats <- tb_clean |>
|
||||
summarise(
|
||||
across(
|
||||
c(e_inc_100k, e_mort_exc_tbhiv_100k),
|
||||
list(
|
||||
Min = ~ min(.x),
|
||||
Q1 = ~ quantile(.x, 0.25),
|
||||
Med = ~ median(.x),
|
||||
Mean = ~ mean(.x),
|
||||
Q3 = ~ quantile(.x, 0.75),
|
||||
Max = ~ max(.x),
|
||||
Skew = ~ moments::skewness(.x)
|
||||
),
|
||||
.names = "{.col}__{.fn}"
|
||||
)
|
||||
)
|
||||
|
||||
desc_long <- desc_stats |>
|
||||
pivot_longer(
|
||||
everything(),
|
||||
names_to = c("Var", "Stat"),
|
||||
names_sep = "__"
|
||||
) |>
|
||||
pivot_wider(names_from = Stat, values_from = value) |>
|
||||
mutate(
|
||||
Var = case_when(
|
||||
Var == "e_inc_100k" ~ "Incidence",
|
||||
Var == "e_mort_exc_tbhiv_100k" ~ "Mortalité",
|
||||
TRUE ~ Var
|
||||
)
|
||||
)
|
||||
|
||||
kable(
|
||||
desc_long,
|
||||
digits = 2,
|
||||
caption = "Statistiques descriptives des variables actives (2000-2024)",
|
||||
booktabs = TRUE
|
||||
) |>
|
||||
kable_styling(latex_options = c("striped", "hold_position"))
|
||||
```
|
||||
|
||||
L'écart considérable entre la médiane et la moyenne, couplé à des coefficients d'asymétrie (Skewness) largement supérieurs à 1, indique des distributions fortement asymétriques à droite (Lognormales ou de Pareto). Concrètement, la majorité des pays présentent une charge épidémique faible, tandis qu'une minorité d'observations "extrêmes" tire la moyenne vers le haut. Cette structure est typique des phénomènes épidémiques mais problématique pour le K-Means, qui risque de créer des clusters uniquement pour isoler ces valeurs extrêmes.
|
||||
|
||||
### Dynamiques temporelles et spatiales
|
||||
|
||||
L'analyse visuelle permet de contextualiser ces statistiques globales.
|
||||
|
||||
```{r}
|
||||
p_time <- ggplot(tb_clean, aes(x = year, y = e_inc_100k)) +
|
||||
geom_line(aes(group = country), alpha = 0.05, color = "#2c3e50") +
|
||||
geom_smooth(method = "loess", color = "#d73027", se = FALSE) +
|
||||
scale_y_log10() +
|
||||
labs(
|
||||
title = "Trajectoires (2000-2024)",
|
||||
subtitle = "Échelle Log",
|
||||
y = "Incidence",
|
||||
x = "Année"
|
||||
) +
|
||||
theme_minimal()
|
||||
|
||||
p_region <- ggplot(
|
||||
tb_clean,
|
||||
aes(x = g_whoregion, y = e_inc_100k, fill = g_whoregion)
|
||||
) +
|
||||
geom_boxplot(outlier.size = 0.5, alpha = 0.8) +
|
||||
scale_y_log10() +
|
||||
scale_fill_brewer(palette = "Set3") +
|
||||
labs(
|
||||
title = "Disparités Régionales",
|
||||
y = "Incidence",
|
||||
x = ""
|
||||
) +
|
||||
theme_minimal() +
|
||||
theme(
|
||||
legend.position = "none",
|
||||
axis.text.x = element_text(angle = 45, hjust = 1)
|
||||
)
|
||||
|
||||
grid.arrange(p_time, p_region, ncol = 2)
|
||||
```
|
||||
|
||||
L'analyse visuelle révèle une double dynamique. D'une part, la **tendance globale** montre une lente érosion de l'incidence moyenne mondiale (courbe rouge), malgré la forte inertie des trajectoires individuelles. D'autre part, les boxplots confirment une **fracture Nord-Sud** structurelle : les médianes logarithmiques de l'Afrique (AFR) et de l'Asie du Sud-Est (SEA) sont nettement supérieures à celles de l'Europe ou des Amériques. Cette hétérogénéité spatiale valide la pertinence d'inclure la région comme variable illustrative pour l'interprétation post-clustering.
|
||||
|
||||
### Relation Bivariée et Transformation
|
||||
|
||||
La relation entre l'Incidence et la Mortalité est le cœur de notre modélisation.
|
||||
|
||||
```{r}
|
||||
p_raw <- ggplot(tb_clean, aes(x = e_inc_100k, y = e_mort_exc_tbhiv_100k)) +
|
||||
geom_point(alpha = 0.3, color = "#2c3e50") +
|
||||
labs(
|
||||
title = "Espace Naturel (Asymétrique)",
|
||||
x = "Incidence",
|
||||
y = "Mortalité"
|
||||
) +
|
||||
theme_minimal()
|
||||
|
||||
tb_ready <- tb_clean |>
|
||||
mutate(
|
||||
log_inc = log1p(e_inc_100k),
|
||||
log_mort = log1p(e_mort_exc_tbhiv_100k)
|
||||
)
|
||||
|
||||
p_log <- ggplot(tb_ready, aes(x = log_inc, y = log_mort)) +
|
||||
geom_point(alpha = 0.3, color = "#4575b4") +
|
||||
geom_smooth(method = "lm", color = "#d73027", se = FALSE) +
|
||||
labs(
|
||||
title = "Espace Log-Transformé (Symétrique)",
|
||||
subtitle = "Transformation log(1+x)",
|
||||
x = "Log-Incidence",
|
||||
y = "Log-Mortalité"
|
||||
) +
|
||||
theme_minimal()
|
||||
|
||||
grid.arrange(p_raw, p_log, ncol = 2)
|
||||
```
|
||||
|
||||
Le graphique supérieur met en évidence une forte concentration à l'origine et une hétéroscédasticité marquée, risquant de biaiser les distances euclidiennes par les seules valeurs extrêmes. L'application de la transformation $f(x)=ln(1+x)$ corrige ces biais structurels : elle **symétrise les distributions** pour optimiser l'occupation de l'espace vectoriel et **linéarise la relation** entre les variables, facilitant la détection de groupes naturels. De plus, contrairement au logarithme népérien standard, cette fonction assure une gestion **robuste des zéros** (évitant le cas $ln(0)=−\infty$ pour les pays sans décès), garantissant ainsi la stabilité numérique du modèle.
|
||||
|
||||
## Synthèse de l'exploration, du nettoyage et des transformations
|
||||
|
||||
À l'issue de cette phase de préparation, nous disposons d'un jeu de données optimisé pour la modélisation.
|
||||
|
||||
Le tableau ci-dessous synthétise les caractéristiques du dataset final `tb_ready` qui sera injecté dans l'algorithme :
|
||||
|
||||
```{r}
|
||||
summary_final <- data.frame(
|
||||
Metrique = c(
|
||||
"Observations totales",
|
||||
"Pays couverts",
|
||||
"Plage Temporelle",
|
||||
"Variables Actives (Transformées)",
|
||||
"Variables Illustratives"
|
||||
),
|
||||
Valeur = c(
|
||||
nrow(tb_ready),
|
||||
length(unique(tb_ready$iso3)),
|
||||
"2000 - 2024",
|
||||
"log_inc, log_mort",
|
||||
"Population, Région, Année"
|
||||
)
|
||||
)
|
||||
|
||||
kable(
|
||||
summary_final,
|
||||
caption = "Fiche d'identité du jeu de données final",
|
||||
booktabs = TRUE
|
||||
) |>
|
||||
kable_styling(latex_options = "hold_position")
|
||||
```
|
||||
|
||||
La validation de ce socle de données clôture la phase exploratoire. L'absence de valeurs manquantes, la réduction de la dimensionnalité et la normalisation des distributions nous permettent désormais de procéder au partitionnement (Clustering) avec une robustesse statistique garantie.
|
||||
|
||||
# Stratégie de Modélisation (Clustering)
|
||||
|
||||
La préparation des données ayant abouti à un espace vectoriel cohérent et symétrisé (`tb_ready`), nous procédons désormais à la segmentation proprement dite. Nous avons retenu l'algorithme des K-Means (Nuées dynamiques), une méthode de partitionnement non-supervisé privilégiée pour sa robustesse sur des jeux de données de dimension modérée et pour la lisibilité géométrique de ses résultats.
|
||||
|
||||
## Prétraitement : Centrage et Réduction
|
||||
|
||||
Bien que nous ayons appliqué une transformation logarithmique pour corriger l'asymétrie, les variables d'Incidence et de Mortalité possèdent des plages de variation distinctes. L'algorithme K-Means reposant sur la distance euclidienne isotrope, il est impératif que chaque dimension contribue de manière équitable au calcul de similarité.
|
||||
|
||||
Nous appliquons donc une standardisation (Z-score) : $z = \frac{x - \mu}{\sigma}$
|
||||
|
||||
```{r}
|
||||
data_scaled <- tb_ready |> select(log_inc, log_mort) |> scale()
|
||||
|
||||
check_table <- data.frame(
|
||||
Variable = c("Incidence (Log)", "Mortalité (Log)"),
|
||||
Moyenne = apply(data_scaled, 2, mean),
|
||||
Ecart_Type = apply(data_scaled, 2, sd)
|
||||
)
|
||||
|
||||
kable(
|
||||
check_table,
|
||||
digits = 2,
|
||||
col.names = c("Variable", "Moyenne (Z)", "Écart-Type (Z)"),
|
||||
caption = "Validation du Centrage-Réduction",
|
||||
booktabs = TRUE
|
||||
) |>
|
||||
kable_styling(
|
||||
latex_options = c("striped", "hold_position"),
|
||||
font_size = 10
|
||||
)
|
||||
```
|
||||
|
||||
## Détermination du nombre de clusters ($k$)
|
||||
|
||||
L'algorithme K-Means nécessite de fixer a priori le nombre de classes k. Ce choix résulte d'un arbitrage entre performance statistique (minimisation de l'inertie intra-classe) et pertinence opérationnelle (interprétabilité métier).
|
||||
|
||||
### Approche statistique (Méthode du Coude)
|
||||
|
||||
Nous calculons l'inertie intra-classe totale pour des valeurs de k allant de 1 à 10. Le point d'inflexion ("coude") indique le seuil au-delà duquel l'ajout d'un cluster n'apporte plus de gain significatif en compacité. Sur la figure ci-dessous, le coude se situe entre $k=2$ et $k=3$.
|
||||
|
||||
```{r}
|
||||
fviz_nbclust(data_scaled, kmeans, method = "wss") +
|
||||
geom_vline(xintercept = 3, linetype = 2, color = "#d73027") +
|
||||
labs(
|
||||
title = "Optimisation du nombre de clusters",
|
||||
x = "Nombre de clusters k",
|
||||
y = "Inertie Intra-classe totale"
|
||||
) +
|
||||
theme_minimal()
|
||||
```
|
||||
|
||||
### Arbitrage
|
||||
|
||||
L'analyse graphique révèle une rupture de pente franche à $k=3$, seuil au-delà duquel les gains d'inertie deviennent marginaux (rendements décroissants). Ce choix statistique est corroboré par une pertinence opérationnelle majeure : une segmentation ternaire permet d'adopter une logique de signalisation intuitive type Traffic Light (Vert/Contrôle, Orange/Surveillance, Rouge/Critique). Nous retenons donc $k=3$ afin de garantir des clusters à la fois statistiquement denses et immédiatement actionnables par les décideurs.
|
||||
|
||||
## Paramétrage et Exécution de l'algorithme
|
||||
|
||||
L'algorithme K-Means étant sensible à l'initialisation des centroïdes (risque d'optimum local), nous avons configuré une exécution robuste : le modèle opère 25 initialisations aléatoires différentes (`nstart = 25`) pour ne conserver que la partition minimisant l'inertie globale sur les 3 classes définies (`centers = 3`). Enfin, la fixation de la graine aléatoire (`set.seed(123)`) garantit la stricte reproductibilité des résultats présentés.
|
||||
|
||||
```{r, echo=TRUE}
|
||||
set.seed(123)
|
||||
|
||||
km_res <- kmeans(data_scaled, centers = 3, nstart = 25)
|
||||
var_totale <- round(km_res$betweenss / km_res$totss * 100, 1)
|
||||
```
|
||||
|
||||
Avec **83,9 % de variance expliquée**, le modèle valide la robustesse statistique de la segmentation ternaire. Ce score élevé traduit une séparation nette des profils épidémiologiques, corroborant ainsi la forte structuration spatiale pressentie lors de l'analyse exploratoire.
|
||||
|
||||
## Intégration des résultats
|
||||
|
||||
Nous réintégrons les labels de clusters dans le jeu de données principal pour l'analyse.
|
||||
|
||||
```{r}
|
||||
tb_clustered <- tb_ready |>
|
||||
mutate(cluster = as.factor(km_res$cluster))
|
||||
|
||||
table(tb_clustered$cluster) |>
|
||||
kable(
|
||||
col.names = c("Cluster ID", "Nombre d'observations"),
|
||||
caption = "Répartition des observations par cluster (k=3)",
|
||||
booktabs = TRUE
|
||||
) |>
|
||||
kable_styling(latex_options = "hold_position")
|
||||
```
|
||||
|
||||
Le partitionnement étant validé avec 3 classes, nous abordons désormais l'étape de labellisation visant à traduire ces clusters statistiques en profils épidémiologiques intelligibles.
|
||||
|
||||
# Analyse des Profils Épidémiques
|
||||
|
||||
L'analyse mathématique ayant validé la qualité de la partition, nous procédons ici à la caractérisation "métier" des clusters pour les transformer en outils d'aide à la décision.
|
||||
|
||||
## Caractérisation et Labellisation
|
||||
|
||||
Nous calculons les moyennes d'incidence et de mortalité par groupe, ordonnons les clusters du moins au plus sévère et leur attribuons des étiquettes sémantiques explicites.
|
||||
|
||||
```{r}
|
||||
cluster_stats <- tb_clustered |>
|
||||
group_by(cluster) |>
|
||||
summarise(
|
||||
n_obs = n(),
|
||||
mean_inc = mean(e_inc_100k),
|
||||
mean_mort = mean(e_mort_exc_tbhiv_100k)
|
||||
) |>
|
||||
arrange(mean_inc)
|
||||
|
||||
labels_map <- c("1. Impact Faible", "2. Impact Modéré", "3. Impact Critique")
|
||||
|
||||
tb_clustered <- tb_clustered |>
|
||||
mutate(
|
||||
rank_severity = match(cluster, cluster_stats$cluster),
|
||||
label = factor(labels_map[rank_severity], levels = labels_map)
|
||||
)
|
||||
|
||||
tb_clustered |>
|
||||
select(country, year, e_inc_100k, label) |>
|
||||
head(10) |>
|
||||
kable(
|
||||
col.names = c("Pays", "Année", "Incidence (pour 100k)", "Classification"),
|
||||
digits = 1,
|
||||
align = c("l", "c", "r", "l"),
|
||||
caption = "Aperçu de la segmentation sanitaire (Échantillon)"
|
||||
) |>
|
||||
kable_styling(latex_options = c("striped", "hold_position"))
|
||||
```
|
||||
|
||||
## Analyse des Profils Épidémiques
|
||||
|
||||
Le tableau ci-dessous synthétise les caractéristiques moyennes de chaque profil type identifié par le modèle.
|
||||
|
||||
```{r}
|
||||
final_summary <- tb_clustered |>
|
||||
group_by(label) |>
|
||||
summarise(
|
||||
`Nombre d'observations` = n(),
|
||||
`Incidence Moyenne` = round(mean(e_inc_100k), 0),
|
||||
`Mortalité Moyenne` = round(mean(e_mort_exc_tbhiv_100k), 1),
|
||||
`Ratio Mort/Inc (%)` = round(
|
||||
mean(e_mort_exc_tbhiv_100k) / mean(e_inc_100k) * 100,
|
||||
1
|
||||
)
|
||||
)
|
||||
|
||||
kable(
|
||||
final_summary,
|
||||
caption = "Typologie des clusters de Tuberculose (k=3)",
|
||||
booktabs = TRUE
|
||||
) |>
|
||||
kable_styling(latex_options = c("striped", "hold_position"))
|
||||
```
|
||||
|
||||
### Interprétation de la typologie
|
||||
|
||||
L'analyse des centroïdes révèle une hiérarchisation sanitaire nette. Le cluster **Impact Faible** (`n=1 416`), représentatif des standards occidentaux (Europe, Amérique du Nord), affiche une incidence marginale (14 cas/100k) et une mortalité résiduelle (<1 décès/100k). Le faible ratio de létalité (~6 %) témoigne d'une prise en charge thérapeutique efficace où la maladie est rarement fatale.
|
||||
|
||||
Le cluster **Impact Modéré** (`n=1 570`) regroupe des pays en transition (Maghreb, Amérique Latine) confrontés à une circulation active du bacille (79 cas/100k). Toutefois, la mortalité contenue (7 décès/100k) indique que si le contrôle de la transmission reste un défi, les systèmes de santé parviennent à traiter la majorité des patients diagnostiqués.
|
||||
|
||||
Enfin, le cluster **Impact Critique** (`n=1 546`), centré sur l'Afrique subsaharienne, concentre la charge mondiale avec une incidence massive (374 cas/100k) et une mortalité très élevée (57 décès/100k). Le taux de létalité y atteint un niveau alarmant de 15,3 %, révélant des défaillances systémiques graves (retards de diagnostic, résistances) : dans cette zone, la tuberculose ne se contente pas de circuler, elle tue massivement.
|
||||
|
||||
## Visualisation de la Segmentation
|
||||
|
||||
La projection des clusters sur le plan bivarié illustre la logique de séparation opérée par l'algorithme.
|
||||
|
||||
```{r}
|
||||
ggplot(
|
||||
tb_clustered,
|
||||
aes(x = e_inc_100k, y = e_mort_exc_tbhiv_100k, color = label)
|
||||
) +
|
||||
geom_point(alpha = 0.5, size = 1.5) +
|
||||
scale_x_log10() +
|
||||
scale_y_log10() +
|
||||
scale_color_manual(values = c("#66bd63", "#fdae61", "#d73027")) +
|
||||
labs(
|
||||
title = "Projection des Clusters de Risque",
|
||||
subtitle = "k=3 : Une segmentation claire du risque sanitaire",
|
||||
x = "Incidence (Log scale)",
|
||||
y = "Mortalité (Log scale)",
|
||||
color = "Niveau de Risque"
|
||||
) +
|
||||
theme_minimal() +
|
||||
theme(legend.position = "bottom")
|
||||
```
|
||||
|
||||
Le graphique confirme que le score de 83,9 % d'inertie expliquée se traduit visuellement par des frontières nettes entre les groupes, avec très peu de chevauchement. La segmentation en "feux tricolores" est donc statistiquement robuste et opérationnellement pertinente.
|
||||
|
||||
## Préparation pour l'Application
|
||||
|
||||
Nous sauvegardons le jeu de données final enrichi des labels, qui servira de socle à l'application R Shiny.
|
||||
|
||||
```{r, echo=TRUE}
|
||||
save(tb_clustered, file = "data/TB_analysis_ready.RData")
|
||||
```
|
||||
|
||||
# Application R Shiny
|
||||
|
||||
L'étape finale de ce projet consiste à transformer les résultats de la segmentation (K-Means) en un outil de pilotage interactif. Nous avons développé une application web via le framework R Shiny, permettant aux décideurs de santé publique d'explorer les données, de visualiser les disparités géographiques et de monitorer l'évolution des profils de risque en temps réel.
|
||||
|
||||
## Architecture technique : Structure UI/Server et flux de données réactif
|
||||
|
||||
Fondée sur une architecture client-serveur réactive, l'application mobilise un écosystème de librairies R spécialisées pour garantir fluidité et interactivité. L'interface utilisateur, structurée de manière modulaire via `shinydashboard`, articule la cartographie vectorielle de `leaflet` avec les graphiques dynamiques du couple `ggplot2` / `plotly` (survol, zoom). En amont, la manipulation des données et le filtrage en temps réel reposent sur la performance des packages `dplyr` et `tidyr`, assurant une réactivité immédiate aux interactions de l'utilisateur.
|
||||
|
||||
### Flux de Données Réactif
|
||||
|
||||
Le coeur de l'application réside dans son graphe de dépendance réactif qui, contrairement à un script statique, optimise les ressources en ne recalculant les éléments qu'à la demande. Le flux suit une logique séquentielle : toute interaction sur un **Input** (sélection d'une année ou d'un pays) déclenche une **Expression Réactive** chargée de filtrer le jeu de données `tb_clustered`. Ce nouveau sous-ensemble propage alors instantanément la mise à jour vers les **Outputs** (cartes, tableaux et courbes) sans nécessiter de rechargement de la page.
|
||||
|
||||
## Fonctionnalités décisionnelles :
|
||||
|
||||
L'interface a été conçue pour répondre à trois besoins analytiques majeurs : la vision globale, le suivi temporel et l'analyse comparative.
|
||||
|
||||
### Cartographie Interactive des Risques (Vision Globale)
|
||||
|
||||
La page d'accueil déploie une carte mondiale interactive (`leaflet`) où chaque pays est coloré selon son cluster d'appartenance : **Vert** (Impact Faible), **Orange** (Modéré) ou **Rouge** (Critique). Cette visualisation offre une lecture immédiate de la géographie sanitaire, permettant d'identifier les foyers épidémiques structurels (telle la ceinture rouge subsaharienne) tout en repérant rapidement les anomalies locales (pays critiques isolés au sein d'une zone préservée).
|
||||
|
||||
### Monitorage Temporel (Analyse Dynamique)
|
||||
|
||||
Un curseur temporel (Slider Input) permet de naviguer sur la période 2000-2024. L'animation de ce curseur permet de visualiser les transitions de clusters (trajectoires). On peut ainsi observer les succès de certains pays passant du statut "Critique" à "Modéré" suite à l'amélioration de leur système de soins, ou inversement, les dégradations liées à des conflits ou crises sanitaires.
|
||||
|
||||
### Analyse Comparative
|
||||
|
||||
Un module dédié permet de sélectionner un pays spécifique (ex: Nigeria) pour générer son Bulletin de Santé complet. Celui-ci articule l'affichage des **KPIs clés** (valeurs brutes d'incidence, mortalité, cluster) avec une analyse de **positionnement relatif**. En confrontant la trajectoire du pays sélectionné aux moyennes régionales et mondiales, ce graphique permet d'objectiver sa performance réelle et de déterminer s'il sous-performe par rapport à son voisinage direct, indépendamment de la tendance globale.
|
||||
|
||||
## Implémentation et logique applicative
|
||||
|
||||
L'application a été développée selon une architecture modulaire, séparant distinctement l'interface utilisateur (Frontend) de la logique de calcul (Backend), conformément au paradigme du framework Shiny.
|
||||
|
||||
### Stack Technologique et Dépendances
|
||||
|
||||
Le développement repose sur une stack technique optimisée pour l'interactivité. L'orchestration de l'interface est assurée par le couple `shiny` et `shinydashboard`, garantissant une structure modulaire et responsive. La couche géospatiale combine la précision vectorielle de `sf` à la fluidité de rendu de `leaflet`, tandis que la visualisation des résultats exploite les capacités dynamiques de `plotly` (pour les graphiques interactifs) et la puissance de tri de `DT` (pour les tableaux). Enfin, `dplyr` agit comme moteur de calcul en temps réel, assurant le filtrage réactif et l'agrégation instantanée des données en arrière-plan.
|
||||
|
||||
### Architecture de l'Interface Utilisateur (UI)
|
||||
|
||||
L'interface guide l'utilisateur du général au particulier via une structure en trois volets. Le **Dashboard**, véritable cœur décisionnel, orchestre via une grille fluide l'affichage de KPIs dynamiques, d'une double visualisation interactive (Carte/Nuage de points) et d'un module de comparaison des trajectoires. Il est complété par un **Explorateur de Données** pour l'accès aux chiffres bruts et une section **Méthodologie** garantissant l'auto-portance de l'outil. Transversalement, la navigation latérale assure le pilotage global des graphiques via un filtrage régional et un contrôle temporel animé (2000-2024).
|
||||
|
||||
### Logique Serveur et Réactivité
|
||||
|
||||
Le script serveur orchestre l'intelligence applicative via deux leviers. D'une part, le **filtrage réactif** optimise la performance : contrairement à une approche statique, les données ne sont chargées qu'une fois puis segmentées dynamiquement par une expression (`filtered_data`) qui joint instantanément le sous-ensemble aux polygones géographiques (`world_map`) à chaque modification des entrées.
|
||||
|
||||
D'autre part, la gestion d'état centralisée permet un **Cross-Filtering** avancé. Une variable réactive (`reactiveVal`), stockant l'identifiant du pays actif, est mise à jour indifféremment par trois interactions distinctes : un clic sur la carte, le nuage de points ou le graphique de densité. Cette interconnexion totale assure une exploration fluide, où l'investigation d'un point aberrant sur un graphique projette immédiatement l'information sur l'ensemble des autres vues.
|
||||
|
||||
### Rendu Conditionnel et Comparaison
|
||||
|
||||
Le graphique de tendance (`trend_plot`) transforme la simple série temporelle en un outil d'analyse comparative en construisant dynamiquement trois courbes à la volée : **la trajectoire du pays sélectionné** (mise en évidence), confrontée à la **moyenne de sa région** (calculée en temps réel) et à la **référence mondiale** fixe. Cette logique de calcul à la demande permet ainsi de situer instantanément la performance de n'importe quel territoire vis-à-vis de son contexte géographique immédiat.
|
||||
|
||||
# Exploitation et Analyse des Résultats
|
||||
|
||||
Au-delà de l'implémentation technique, l'application R Shiny permet d'objectiver les dynamiques épidémiologiques mondiales. L'exploration interactive des données (2000-2024) met en lumière trois niveaux de lecture.
|
||||
|
||||
## Analyse Macroscopique : La fracture Nord-Sud
|
||||
|
||||
La cartographie interactive confirme que la segmentation ternaire obéit à une logique géopolitique structurante. Le **Cluster 1 (Faible Impact - Vert)** se superpose quasi-intégralement aux pays de l'OCDE, caractérisant une maladie devenue résiduelle. Il se distingue du **Cluster 2 (Intermédiaire - Orange)**, véritable zone tampon hétérogène (Amérique Latine, Europe de l'Est) où les infrastructures de santé font face à des défis de résistance. Enfin, le **Cluster 3 (Critique - Rouge)** dessine une ceinture épidémique continue en Afrique Subsaharienne et sur certains foyers asiatiques, dont la superposition avec les zones de forte prévalence du VIH et d'instabilité politique apparaît frappante.
|
||||
|
||||
## Dynamiques Régionales et Temporelles
|
||||
|
||||
L'outil de monitorage (2000-2024) objective une baisse mondiale de l'incidence à géométrie variable. Tandis que l'**Europe** et les **Amériques** affichent une stagnation ou une décroissance marginale caractéristique d'une épidémie maîtrisée, l'**Afrique** se distingue par la chute la plus rapide en valeur absolue depuis 2010, témoignant du succès des campagnes contre la co-infection TB-VIH. À l'opposé, l'**Asie du Sud-Est** manifeste une inertie inquiétante et demeure, par la densité démographique de l'Inde et de l'Indonésie, le principal réservoir volumique mondial de nouveaux cas.
|
||||
|
||||
## Cas d'usage : la France
|
||||
|
||||
Pour illustrer la puissance analytique de l'outil, nous prenons le cas de la France. L'analyse du cas français illustre la puissance de l'outil pour situer un territoire. Solidement ancrée dans le **Cluster 1 (Faible Impact)** avec une incidence de 8 cas/100k en 2024, la France affiche une performance remarquable sur trois échelles : elle se situe un facteur 15 sous la moyenne mondiale et surperforme nettement la moyenne européenne (~24 cas/100k), cette dernière étant grevée par les pays de l'Est du Cluster 2. La confrontation avec un représentant du Cluster 3 comme l'Afrique du Sud (> 389 cas/100k) objective une fracture sanitaire vertigineuse : maladie du passé pour l'Hexagone, la tuberculose demeure une urgence vitale ailleurs. Ce diagnostic valide l'efficacité de la stratégie nationale tout en rappelant l'impératif de vigilance face aux risques de réintroduction depuis les zones critiques (Orange et Rouge).
|
||||
|
||||
# Conclusion et Perspectives
|
||||
|
||||
Ce projet s'est attaché à transformer une base de données brute et complexe, issue du rapport mondial de l'OMS, en un outil d'aide à la décision sanitaire opérationnel. En combinant une approche statistique rigoureuse (analyse exploratoire, réduction de dimension) et une modélisation non-supervisée (Clustering K-Means), nous avons pu objectiver les disparités mondiales face à l'épidémie de tuberculose.
|
||||
|
||||
## Synthèse des résultats
|
||||
|
||||
L'analyse de la période 2000-2024 valide trois enseignements majeurs. D'abord, la **pertinence d'une segmentation ternaire** ($k=3$) qui, forte d'une robustesse statistique de 83,9 %, dépasse le simple clivage Nord-Sud pour cartographier le risque selon une gradation opérationnelle (Faible, Modéré, Critique). Ensuite, la **polarisation de l'épidémie** : le cluster Critique concentre une létalité disproportionnée (> 15 %), dictant un ciblage prioritaire des efforts sur l'Afrique subsaharienne. Enfin, la valeur ajoutée du **monitorage dynamique** : l'application R Shiny a permis d'objectiver la mobilité des trajectoires, identifiant les pays en transition pour fournir des signaux d'alerte précoce ou valider l'efficacité des politiques publiques.
|
||||
|
||||
## Limites méthodologiques
|
||||
|
||||
Dans une démarche critique, trois limites méthodologiques doivent être soulignées. Premièrement, le **biais déclaratif** persiste malgré l'usage des estimations OMS (`e_`) : les données restent tributaires de la qualité de la surveillance nationale, induisant un paradoxe où l'amélioration du diagnostic peut être confondue avec une dégradation épidémique (hausse mécanique de l'incidence détectée). Deuxièmement, la **parcimonie du modèle**, restreinte à deux variables pour garantir la robustesse, confine l'étude à un rôle descriptif qui occulte les déterminants causaux (pauvreté, VIH). Enfin, la **suppression des données manquantes** (15 % des observations), impérative pour la stabilité du K-Means, rend de facto le modèle inopérant pour les micro-états insulaires exclus.
|
||||
|
||||
## Perspectives d'évolution
|
||||
|
||||
Pour enrichir cet outil de pilotage, trois axes de développement majeurs se dessinent. D'abord, le passage vers une **modélisation explicative** : l'intégration de variables socio-économiques (PIB, Gini) via une ACP permettrait d'identifier les déterminants structurels du cluster Critique. Ensuite, le déploiement d'une **approche prédictive** (via ARIMA ou Prophet) transformerait ce tableau de bord analytique en outil prospectif, capable d'évaluer l'atteinte des objectifs onusiens à l'horizon 2030. Enfin, l'adoption d'une **granularité infra-nationale** s'avérerait pertinente pour les grands états fédéraux (Brésil, Inde) où la moyenne nationale masque de fortes disparités. En somme, ce projet offre une boussole efficace et constitue la première pierre d'une épidémiologie de précision guidée par la donnée.
|
||||
|
||||
# Déclaration d'Intégrité et Usage de l'IA
|
||||
|
||||
Conformément aux consignes académiques relatives au plagiat et à l'utilisation des assistants numériques, cette section explicite le cadre de réalisation de ce projet.
|
||||
|
||||
## Originalité de la démarche
|
||||
|
||||
Le jeu de données utilisé (*Global Tuberculosis Report*) est public et largement étudié. Cependant, l'approche développée dans ce projet est originale et personnelle.
|
||||
|
||||
Disposant d'un **profil d'ingénieur logiciel**, j'ai fait le choix stratégique de concentrer mon effort technique sur l'architecture et l'interactivité de l'application **R Shiny** (Section 5), afin de produire un outil de qualité professionnelle. Cette notice technique assure la couverture rigoureuse de la partie Data Science, justifiant les choix mathématiques implémentés dans l'application.
|
||||
|
||||
## Usage des outils d'IA Générative
|
||||
|
||||
L'utilisation d'outils d'intelligence artificielle générative s'est inscrite dans une démarche d'assistance ponctuelle et rigoureusement contrôlée. Sur le volet **rédactionnel**, l'IA a contribué à l'optimisation syntaxique et à la fluidité des transitions, le raisonnement et les interprétations demeurant strictement personnels. Sur le plan **technique**, elle a servi d'outil de diagnostic pour le débogage de l'application R Shiny (gestion de la réactivité, conflits). L'intégralité du code a été vérifiée et maîtrisée : aucune partie de l'analyse n'a été déléguée sans supervision humaine.
|
||||
|
||||
\newpage
|
||||
|
||||
# Bibliographie
|
||||
|
||||
## Rapports et Encyclopédies
|
||||
|
||||
- [1] Organisation Mondiale de la Santé (OMS). (2024). Global Tuberculosis Report 2024. Disponible sur : https://www.who.int/teams/global-programme-on-tuberculosis-and-lung-health/tb-reports/global-tuberculosis-report-2024
|
||||
|
||||
- [2] Wikipédia. (s.d.). Tuberculose. Disponible sur : https://fr.wikipedia.org/wiki/Tuberculose
|
||||
|
||||
## Supports de Cours - Master 2 ISF (2025-2026)
|
||||
|
||||
- [3] Ochoa, J. (2025-2026). *Les algorithmes non supervisés.* Support de cours : Machine Learning. Université Paris-Dauphine - PSL.
|
||||
|
||||
- [4] Bertrand, P. (2025-2026). *K-Means.* Support de cours : Apprentissage non supervisé et clustering. Université Paris-Dauphine - PSL.
|
||||
|
||||
- [5] Guibert, Q. (2025-2026). *Data Visualisation.* Support de cours : Visualisation des données avec R. Université Paris-Dauphine - PSL.
|
||||
BIN
M2/Data Visualisation/Project/NoticeTechnique.pdf
Normal file
BIN
M2/Data Visualisation/Project/NoticeTechnique.pdf
Normal file
Binary file not shown.
871
M2/Data Visualisation/Project/app.R
Normal file
871
M2/Data Visualisation/Project/app.R
Normal file
@@ -0,0 +1,871 @@
|
||||
# Chargement des bibliothèques
|
||||
library(shiny)
|
||||
library(shinydashboard)
|
||||
library(leaflet)
|
||||
library(plotly)
|
||||
library(dplyr)
|
||||
library(sf)
|
||||
library(RColorBrewer)
|
||||
library(DT)
|
||||
library(rnaturalearth)
|
||||
library(rnaturalearthdata)
|
||||
|
||||
# Chargement des données
|
||||
load("data/TB_analysis_ready.RData")
|
||||
|
||||
# Définition des labels pour les clusters
|
||||
labels <- c("1. Faible Impact", "2. Impact Modéré", "3. Impact Critique")
|
||||
|
||||
# Application des labels aux données
|
||||
tb_clustered$label <- factor(tb_clustered$label)
|
||||
levels(tb_clustered$label) <- labels
|
||||
|
||||
# Création de la carte du monde
|
||||
world_map <- ne_countries(scale = "medium", returnclass = "sf")
|
||||
|
||||
# Définition des couleurs pour les clusters
|
||||
green <- "#66bd63"
|
||||
orange <- "#f48a43"
|
||||
red <- "#d73027"
|
||||
|
||||
# Interface utilisateur
|
||||
ui <- shinydashboard::dashboardPage(
|
||||
skin = "black",
|
||||
|
||||
# Header
|
||||
dashboardHeader(title = "Tuberculose"),
|
||||
|
||||
# Sidebar
|
||||
dashboardSidebar(
|
||||
sidebarMenu(
|
||||
menuItem(
|
||||
"Méthodologie & Définitions",
|
||||
tabName = "methodo",
|
||||
icon = icon("info-circle")
|
||||
),
|
||||
|
||||
menuItem(
|
||||
"Vue d'Ensemble",
|
||||
tabName = "dashboard",
|
||||
icon = icon("dashboard")
|
||||
),
|
||||
|
||||
menuItem("Données Brutes", tabName = "data", icon = icon("table")),
|
||||
|
||||
# Footer - Informations et crédits
|
||||
div(
|
||||
style = "position: absolute; bottom: 10px; width: 100%; text-align: center; font-size: 12px; color: #b8c7ce;",
|
||||
p("© 2026 Arthur Danjou"),
|
||||
p("M2 ISF - Dauphine PSL"),
|
||||
p(
|
||||
a(
|
||||
"Code Source",
|
||||
href = "https://go.arthurdanjou.fr/datavis",
|
||||
target = "_blank",
|
||||
style = "color: #3c8dbc;"
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
hr(),
|
||||
|
||||
# Filtre par Région
|
||||
selectInput(
|
||||
"region_select",
|
||||
"Filtrer par Région :",
|
||||
choices = c("Toutes", unique(tb_clustered$g_whoregion)),
|
||||
selected = "Toutes"
|
||||
),
|
||||
|
||||
# Sélecteur d'année
|
||||
sliderInput(
|
||||
"year_select",
|
||||
"Année :",
|
||||
min = min(tb_clustered$year),
|
||||
max = max(tb_clustered$year),
|
||||
value = max(tb_clustered$year),
|
||||
step = 1,
|
||||
sep = "",
|
||||
animate = animationOptions(interval = 5000, loop = FALSE)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
# Body
|
||||
dashboardBody(
|
||||
tabItems(
|
||||
# Page 1 - Vue d'Ensemble
|
||||
tabItem(
|
||||
tabName = "dashboard",
|
||||
|
||||
# KPI - Total des cas
|
||||
fluidRow(
|
||||
valueBoxOutput("kpi_total_cases", width = 4),
|
||||
valueBoxOutput("kpi_worst_country", width = 4),
|
||||
valueBoxOutput("kpi_critical_count", width = 4)
|
||||
),
|
||||
|
||||
# Carte Épidémiologique
|
||||
fluidRow(
|
||||
box(
|
||||
width = 7,
|
||||
title = "Carte Épidémiologique",
|
||||
status = "primary",
|
||||
solidHeader = TRUE,
|
||||
radioButtons(
|
||||
"metric_select",
|
||||
"Indicateur :",
|
||||
choices = c(
|
||||
"Incidence" = "e_inc_100k",
|
||||
"Mortalité" = "e_mort_exc_tbhiv_100k",
|
||||
"Clusters K-Means" = "label"
|
||||
),
|
||||
inline = TRUE
|
||||
),
|
||||
p(
|
||||
class = "text-muted",
|
||||
"Cliquer sur un point pour filtrer par pays."
|
||||
),
|
||||
leafletOutput("map_plot", height = "500px")
|
||||
),
|
||||
|
||||
# Scatter Plot des Clusters
|
||||
box(
|
||||
width = 5,
|
||||
title = "Analyse des Clusters (Incidence vs Mortalité)",
|
||||
status = "success",
|
||||
solidHeader = TRUE,
|
||||
p(
|
||||
class = "text-muted",
|
||||
style = "font-size:0.9em",
|
||||
"Chaque point est un pays. Les couleurs correspondent aux groupes de risque identifiés par l'algorithme K-Means."
|
||||
),
|
||||
p(
|
||||
class = "text-muted",
|
||||
"Cliquer sur un point pour filtrer par pays."
|
||||
),
|
||||
plotlyOutput("cluster_scatter", height = "530px")
|
||||
)
|
||||
),
|
||||
|
||||
fluidRow(
|
||||
# Plot des tendances
|
||||
box(
|
||||
width = 7,
|
||||
title = "Comparaison : Pays vs Moyenne Régionale vs Moyenne Mondiale",
|
||||
status = "warning",
|
||||
solidHeader = TRUE,
|
||||
plotlyOutput("trend_plot", height = "400px")
|
||||
),
|
||||
|
||||
# Distribution des Clusters
|
||||
box(
|
||||
width = 5,
|
||||
title = "Distribution des Clusters",
|
||||
status = "info",
|
||||
solidHeader = TRUE,
|
||||
p(
|
||||
class = "text-muted",
|
||||
"Cliquer sur un point du rug pour filtrer par pays."
|
||||
),
|
||||
plotlyOutput("density_plot", height = "400px")
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
# Page 2 - Données Brutes
|
||||
tabItem(
|
||||
tabName = "data",
|
||||
fluidRow(
|
||||
box(
|
||||
width = 12,
|
||||
title = "Explorateur de Données",
|
||||
status = "primary",
|
||||
p("Tableau filtrable et exportable des données utilisées."),
|
||||
DTOutput("raw_table")
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
# Page 3 - Méthodologie
|
||||
tabItem(
|
||||
tabName = "methodo",
|
||||
|
||||
fluidRow(
|
||||
# Indicateurs OMS
|
||||
box(
|
||||
width = 12,
|
||||
title = "Définitions des Indicateurs OMS",
|
||||
status = "info",
|
||||
solidHeader = TRUE,
|
||||
column(
|
||||
width = 6,
|
||||
h4(icon("lungs"), "Incidence de la Tuberculose"),
|
||||
p(
|
||||
"Correspond à la variable ",
|
||||
code("e_inc_100k"),
|
||||
" dans le jeu de données de l'OMS."
|
||||
),
|
||||
p(
|
||||
"Il s'agit du nombre estimé de ",
|
||||
strong("nouveaux cas"),
|
||||
" de tuberculose (toutes formes confondues) survenus au cours d'une année donnée, rapporté pour 100 000 habitants."
|
||||
),
|
||||
p(
|
||||
"Cet indicateur mesure la ",
|
||||
em("propagation"),
|
||||
" de la maladie dans la population."
|
||||
),
|
||||
),
|
||||
column(
|
||||
width = 6,
|
||||
h4(icon("skull"), "Mortalité (hors VIH)"),
|
||||
p(
|
||||
"Correspond à la variable ",
|
||||
code("e_mort_exc_tbhiv_100k"),
|
||||
"."
|
||||
),
|
||||
p(
|
||||
"Il s'agit du nombre estimé de décès dus à la tuberculose chez les personnes non infectées par le VIH, rapporté pour 100 000 habitants."
|
||||
),
|
||||
p(
|
||||
"Cet indicateur mesure la ",
|
||||
em("sévérité"),
|
||||
" et l'efficacité de l'accès aux soins (un taux élevé signale souvent un système de santé défaillant)."
|
||||
)
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
# Choix des Variables
|
||||
fluidRow(
|
||||
column(
|
||||
width = 6,
|
||||
box(
|
||||
width = 12,
|
||||
title = "Pourquoi seulement 2 variables actives ?",
|
||||
status = "warning",
|
||||
solidHeader = TRUE,
|
||||
p(
|
||||
"Le modèle de clustering repose uniquement sur l'Incidence et la Mortalité. Ce choix de parcimonie est justifié par 4 contraintes techniques :"
|
||||
),
|
||||
br(),
|
||||
column(
|
||||
width = 6,
|
||||
h4(
|
||||
icon("ruler-combined"),
|
||||
"1. Robustesse Mathématique",
|
||||
class = "text-warning"
|
||||
),
|
||||
p(
|
||||
"Évite le 'fléau de la dimension'. Avec trop de variables, les distances euclidiennes perdent leur sens et les groupes deviennent flous."
|
||||
),
|
||||
br(),
|
||||
h4(
|
||||
icon("project-diagram"),
|
||||
"2. Non-Colinéarité",
|
||||
class = "text-warning"
|
||||
),
|
||||
p(
|
||||
"Évite de compter deux fois la même information (ex: Incidence vs Nombre de cas) qui fausserait le poids des indicateurs."
|
||||
),
|
||||
),
|
||||
column(
|
||||
width = 6,
|
||||
h4(
|
||||
icon("filter"),
|
||||
"3. Qualité des Données",
|
||||
class = "text-warning"
|
||||
),
|
||||
p(
|
||||
"Le K-Means ne tolère pas les données manquantes. Ajouter des variables socio-économiques aurait réduit la taille de l'échantillon de 30% à 50%."
|
||||
),
|
||||
br(),
|
||||
h4(icon("eye"), "4. Lisibilité", class = "text-warning"),
|
||||
p(
|
||||
"Permet une visualisation directe en 2D (Scatterplot) sans déformation, rendant l'outil accessible aux non-statisticiens."
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
# Source des Données
|
||||
box(
|
||||
width = 12,
|
||||
title = "Source des Données",
|
||||
status = "danger",
|
||||
solidHeader = TRUE,
|
||||
p(
|
||||
icon("database"),
|
||||
"Les données sont issues du ",
|
||||
a(
|
||||
"Global Tuberculosis Report",
|
||||
href = "https://www.who.int/teams/global-programme-on-tuberculosis-and-lung-health/data",
|
||||
target = "_blank"
|
||||
),
|
||||
" de l'Organisation Mondiale de la Santé (OMS)."
|
||||
),
|
||||
p("Dernière mise à jour du dataset : Octobre 2024.")
|
||||
),
|
||||
),
|
||||
|
||||
column(
|
||||
width = 6,
|
||||
box(
|
||||
width = 12,
|
||||
title = "Algorithme de Classification (Clustering)",
|
||||
status = "success",
|
||||
solidHeader = TRUE,
|
||||
h4("Pourquoi un Clustering K-Means ?"),
|
||||
p(
|
||||
"Afin de synthétiser l'information et de faciliter la prise de décision, j'ai appliqué un algorithme d'apprentissage non supervisé (K-Means) pour regrouper les pays ayant des profils épidémiques similaires sous 4 clusters."
|
||||
),
|
||||
|
||||
h4("Méthodologie"),
|
||||
tags$ul(
|
||||
tags$li(
|
||||
strong("Variables :"),
|
||||
" Incidence et Mortalité (centrées et réduites pour assurer un poids équivalent)."
|
||||
),
|
||||
tags$li(
|
||||
strong("Nombre de Clusters (k) :"),
|
||||
" Fixé à 3 pour obtenir une segmentation tricolore lisible (Faible, Modéré, Critique)."
|
||||
),
|
||||
tags$li(
|
||||
strong("Stabilité :"),
|
||||
"Utilisation de `set.seed(123)` pour garantir la reproductibilité des résultats."
|
||||
)
|
||||
),
|
||||
|
||||
h4("Interprétation des 3 Groupes"),
|
||||
|
||||
# Tableau des Groupes
|
||||
tags$table(
|
||||
class = "table table-striped",
|
||||
tags$thead(
|
||||
tags$tr(
|
||||
tags$th("Cluster"),
|
||||
tags$th("Description"),
|
||||
tags$th("Profil Type")
|
||||
)
|
||||
),
|
||||
tags$tbody(
|
||||
tags$tr(
|
||||
tags$td(span(
|
||||
style = paste0(
|
||||
"background-color:",
|
||||
green,
|
||||
"; color: black; font-weight: bold; padding: 5px; border-radius: 5px;"
|
||||
),
|
||||
labels[1]
|
||||
)),
|
||||
tags$td("Incidence et mortalité très basses."),
|
||||
tags$td("Europe de l'Ouest, Amérique du Nord")
|
||||
),
|
||||
tags$tr(
|
||||
tags$td(span(
|
||||
style = paste0(
|
||||
"background-color:",
|
||||
orange,
|
||||
"; color: black; font-weight: bold; padding: 5px; border-radius: 5px;"
|
||||
),
|
||||
labels[2]
|
||||
)),
|
||||
tags$td("Incidence significative mais mortalité contenue."),
|
||||
tags$td("Amérique Latine, Maghreb, Europe de l'Est")
|
||||
),
|
||||
tags$tr(
|
||||
tags$td(span(
|
||||
style = paste0(
|
||||
"background-color:",
|
||||
red,
|
||||
"; color: black; font-weight: bold; padding: 5px; border-radius: 5px;"
|
||||
),
|
||||
labels[3]
|
||||
)),
|
||||
tags$td("Incidence massive et forte létalité."),
|
||||
tags$td("Afrique Subsaharienne, Zones de conflit")
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
|
||||
# Code & Documentation
|
||||
box(
|
||||
title = "Code & Documentation",
|
||||
status = "primary",
|
||||
solidHeader = TRUE,
|
||||
width = 12,
|
||||
|
||||
tags$p(
|
||||
"Ce projet suit une approche Open Science.",
|
||||
style = "font-style: italic;"
|
||||
),
|
||||
tags$p(
|
||||
"L'intégralité du code source (Rmd, App) ainsi que la notice technique (PDF) sont disponibles en libre accès sur le dépôt GitHub."
|
||||
),
|
||||
|
||||
tags$a(
|
||||
href = "https://go.arthurdanjou.fr/datavis-code",
|
||||
target = "_blank",
|
||||
class = "btn btn-block btn-social btn-github",
|
||||
style = "color: white; background-color: #333; border-color: #333;",
|
||||
icon("github"),
|
||||
" Accéder au Code"
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
# Serveur
|
||||
server <- function(input, output, session) {
|
||||
# Filtrage des données
|
||||
filtered_data <- shiny::reactive({
|
||||
req(input$year_select)
|
||||
data <- tb_clustered |> filter(year == input$year_select)
|
||||
|
||||
if (input$region_select != "Toutes") {
|
||||
data <- data |> filter(g_whoregion == input$region_select)
|
||||
}
|
||||
return(data)
|
||||
})
|
||||
|
||||
# Données pour la carte
|
||||
map_data_reactive <- shiny::reactive({
|
||||
req(filtered_data())
|
||||
world_map |> inner_join(filtered_data(), by = c("adm0_a3" = "iso3"))
|
||||
})
|
||||
|
||||
# Définition des couleurs pour les clusters
|
||||
cluster_colors <- setNames(
|
||||
c(green, orange, red),
|
||||
labels
|
||||
)
|
||||
|
||||
# Fonction pour dessiner les polygones sur la carte
|
||||
dessiner_polygones <- function(map_object, data, metric) {
|
||||
if (metric == "label") {
|
||||
values_vec <- data$label
|
||||
pal_fun <- leaflet::colorFactor(
|
||||
as.character(cluster_colors),
|
||||
domain = names(cluster_colors)
|
||||
)
|
||||
fill_vals <- pal_fun(values_vec)
|
||||
legend_title <- "Cluster"
|
||||
label_txt <- paste0(data$name, " - ", data$label)
|
||||
legend_vals <- names(cluster_colors)
|
||||
} else {
|
||||
values_vec <- data[[metric]]
|
||||
pal_fun <- leaflet::colorNumeric(
|
||||
palette = c(green, orange, red),
|
||||
domain = values_vec
|
||||
)
|
||||
fill_vals <- pal_fun(values_vec)
|
||||
legend_title <- "Taux / 100k"
|
||||
label_txt <- paste0(data$name, ": ", round(values_vec, 1))
|
||||
legend_vals <- values_vec
|
||||
}
|
||||
|
||||
map_object |>
|
||||
leaflet::addPolygons(
|
||||
data = data,
|
||||
fillColor = ~fill_vals,
|
||||
weight = 1,
|
||||
color = ifelse(metric == "label", "gray", "black"),
|
||||
fillOpacity = 0.7,
|
||||
layerId = ~adm0_a3,
|
||||
label = ~label_txt,
|
||||
highlightOptions = highlightOptions(
|
||||
weight = 3,
|
||||
color = "#666",
|
||||
bringToFront = TRUE
|
||||
)
|
||||
) |>
|
||||
leaflet::addLegend(
|
||||
pal = pal_fun,
|
||||
values = legend_vals,
|
||||
title = legend_title,
|
||||
position = "bottomright"
|
||||
)
|
||||
}
|
||||
|
||||
# Carte
|
||||
output$map_plot <- leaflet::renderLeaflet({
|
||||
isolate({
|
||||
data <- map_data_reactive()
|
||||
metric <- input$metric_select
|
||||
|
||||
leaflet(options = leafletOptions(minZoom = 2, maxZoom = 6)) |>
|
||||
addProviderTiles(
|
||||
providers$CartoDB.Positron,
|
||||
options = providerTileOptions(noWrap = TRUE)
|
||||
) |>
|
||||
setMaxBounds(lng1 = -180, lat1 = -90, lng2 = 180, lat2 = 90) |>
|
||||
setView(lat = 20, lng = 0, zoom = 2) |>
|
||||
dessiner_polygones(data, metric)
|
||||
})
|
||||
})
|
||||
|
||||
# KPI - Total des cas
|
||||
output$kpi_total_cases <- shinydashboard::renderValueBox({
|
||||
data <- filtered_data()
|
||||
val <- round(mean(data$e_inc_100k, na.rm = TRUE))
|
||||
valueBox(
|
||||
val,
|
||||
"Incidence Moyenne (cas/100k)",
|
||||
icon = icon("chart-area"),
|
||||
color = "green"
|
||||
)
|
||||
})
|
||||
|
||||
# KPI - Pire pays
|
||||
output$kpi_worst_country <- shinydashboard::renderValueBox({
|
||||
data <- filtered_data()
|
||||
worst <- data |> arrange(desc(e_inc_100k)) |> slice(1)
|
||||
|
||||
if (nrow(worst) > 0) {
|
||||
valueBox(
|
||||
worst$country,
|
||||
paste("Max Incidence :", round(worst$e_inc_100k)),
|
||||
icon = icon("exclamation-triangle"),
|
||||
color = "red"
|
||||
)
|
||||
} else {
|
||||
valueBox("N/A", "Pas de données", icon = icon("ban"), color = "red")
|
||||
}
|
||||
})
|
||||
|
||||
# KPI - Pays en phase 'Critique'
|
||||
output$kpi_critical_count <- shinydashboard::renderValueBox({
|
||||
data <- filtered_data()
|
||||
count <- sum(data$label == labels[3], na.rm = TRUE)
|
||||
valueBox(
|
||||
count,
|
||||
"Pays en phase 'Critique'",
|
||||
icon = icon("hospital"),
|
||||
color = "orange"
|
||||
)
|
||||
})
|
||||
|
||||
# Plot des tendances
|
||||
output$trend_plot <- plotly::renderPlotly({
|
||||
req(selected_country())
|
||||
country_hist <- tb_clustered |> filter(iso3 == selected_country())
|
||||
if (nrow(country_hist) == 0) {
|
||||
return(NULL)
|
||||
}
|
||||
|
||||
nom_pays <- unique(country_hist$country)[1]
|
||||
region_du_pays <- unique(country_hist$g_whoregion)[1]
|
||||
|
||||
region_benchmark <- tb_clustered |>
|
||||
filter(g_whoregion == region_du_pays) |>
|
||||
group_by(year) |>
|
||||
summarise(mean_inc = mean(e_inc_100k, na.rm = TRUE))
|
||||
|
||||
global_benchmark <- tb_clustered |>
|
||||
group_by(year) |>
|
||||
summarise(mean_inc = mean(e_inc_100k, na.rm = TRUE))
|
||||
|
||||
p <- ggplot() +
|
||||
geom_area(
|
||||
data = country_hist,
|
||||
aes(
|
||||
x = year,
|
||||
y = e_inc_100k,
|
||||
group = 1,
|
||||
text = paste0("<b>Pays : ", nom_pays, "</b>")
|
||||
),
|
||||
fill = red,
|
||||
alpha = 0.1
|
||||
) +
|
||||
geom_line(
|
||||
data = region_benchmark,
|
||||
aes(
|
||||
x = year,
|
||||
y = mean_inc,
|
||||
group = 1,
|
||||
color = "Moyenne Régionale",
|
||||
text = paste0(
|
||||
"<b>Moyenne ",
|
||||
region_du_pays,
|
||||
"</b><br>Année : ",
|
||||
year,
|
||||
"<br>Incidence : ",
|
||||
round(mean_inc, 1)
|
||||
)
|
||||
),
|
||||
size = 0.5,
|
||||
linetype = "dashed"
|
||||
) +
|
||||
geom_line(
|
||||
data = global_benchmark,
|
||||
aes(
|
||||
x = year,
|
||||
y = mean_inc,
|
||||
group = 1,
|
||||
color = "Moyenne Mondiale",
|
||||
text = paste0(
|
||||
"<b>Moyenne Mondiale</b><br>Année : ",
|
||||
year,
|
||||
"<br>Incidence : ",
|
||||
round(mean_inc, 1)
|
||||
)
|
||||
),
|
||||
size = 0.75,
|
||||
linetype = "dashed"
|
||||
) +
|
||||
geom_line(
|
||||
data = country_hist,
|
||||
aes(
|
||||
x = year,
|
||||
y = e_inc_100k,
|
||||
group = 1,
|
||||
color = "Pays Sélectionné",
|
||||
text = paste0(
|
||||
"<b>Pays : ",
|
||||
nom_pays,
|
||||
"</b><br>Incidence : ",
|
||||
round(e_inc_100k, 1),
|
||||
"<br>Mortalité : ",
|
||||
round(e_mort_exc_tbhiv_100k, 1)
|
||||
)
|
||||
),
|
||||
size = 1
|
||||
) +
|
||||
geom_vline(
|
||||
xintercept = as.numeric(input$year_select),
|
||||
linetype = "dotted",
|
||||
color = "black",
|
||||
alpha = 0.6
|
||||
) +
|
||||
scale_color_manual(
|
||||
name = "",
|
||||
values = c(
|
||||
"Moyenne Régionale" = "grey30",
|
||||
"Pays Sélectionné" = red,
|
||||
"Moyenne Mondiale" = "orange"
|
||||
)
|
||||
) +
|
||||
labs(
|
||||
title = paste(
|
||||
"Trajectoire :",
|
||||
nom_pays,
|
||||
"vs",
|
||||
region_du_pays,
|
||||
"vs Monde"
|
||||
),
|
||||
x = "Année",
|
||||
y = "Incidence (pour 100k)"
|
||||
) +
|
||||
theme_minimal() +
|
||||
theme(legend.position = "bottom")
|
||||
|
||||
ggplotly(p, tooltip = "text") |>
|
||||
layout(
|
||||
legend = list(orientation = "h", x = 0.1, y = -0.2),
|
||||
hovermode = "x unified"
|
||||
)
|
||||
})
|
||||
|
||||
# Densité des cas
|
||||
output$density_plot <- plotly::renderPlotly({
|
||||
data <- filtered_data()
|
||||
sel_iso <- selected_country()
|
||||
highlight_data <- data |> filter(iso3 == sel_iso)
|
||||
|
||||
p <- ggplot(data, aes(x = e_inc_100k, fill = label)) +
|
||||
geom_density(
|
||||
aes(text = paste0("<b>Cluster : </b>", label)),
|
||||
alpha = 0.6,
|
||||
color = NA
|
||||
) +
|
||||
geom_rug(
|
||||
aes(
|
||||
color = label,
|
||||
customdata = iso3,
|
||||
text = paste0(
|
||||
"<b>Pays : </b>",
|
||||
country,
|
||||
"<br><b>Incidence : </b>",
|
||||
round(e_inc_100k),
|
||||
" (cas/100k)<br><b>Cluster : </b>",
|
||||
label
|
||||
)
|
||||
),
|
||||
sides = "b",
|
||||
length = unit(0.2, "npc"),
|
||||
size = 1.2,
|
||||
alpha = 0.9
|
||||
) +
|
||||
geom_point(
|
||||
data = highlight_data,
|
||||
aes(
|
||||
x = e_inc_100k,
|
||||
y = 0,
|
||||
text = paste0(
|
||||
"<b>PAYS SÉLECTIONNÉ</b><br><b>",
|
||||
country,
|
||||
"</b><br>Incidence : ",
|
||||
round(e_inc_100k)
|
||||
)
|
||||
),
|
||||
color = "black",
|
||||
fill = "white",
|
||||
shape = 21,
|
||||
size = 4
|
||||
) +
|
||||
scale_fill_manual(values = cluster_colors) +
|
||||
scale_color_manual(values = cluster_colors) +
|
||||
scale_x_log10() +
|
||||
labs(
|
||||
title = "Distribution des Risques",
|
||||
x = "Incidence (Échelle Log)",
|
||||
y = NULL
|
||||
) +
|
||||
theme_minimal() +
|
||||
theme(
|
||||
legend.position = "none",
|
||||
axis.text.y = element_blank(),
|
||||
axis.ticks.y = element_blank(),
|
||||
panel.grid.major.y = element_blank(),
|
||||
panel.grid.minor.y = element_blank()
|
||||
)
|
||||
|
||||
ggplotly(p, tooltip = "text", source = "density_click") |>
|
||||
layout(hovermode = "closest")
|
||||
})
|
||||
|
||||
# Nuage de points des clusters
|
||||
output$cluster_scatter <- plotly::renderPlotly({
|
||||
data <- filtered_data()
|
||||
sel_iso <- selected_country()
|
||||
highlight_data <- data %>% filter(iso3 == sel_iso)
|
||||
|
||||
p <- ggplot(data, aes(x = e_inc_100k, y = e_mort_exc_tbhiv_100k)) +
|
||||
geom_point(
|
||||
aes(
|
||||
color = label,
|
||||
customdata = iso3,
|
||||
text = paste(
|
||||
"Pays:",
|
||||
country,
|
||||
"<br>Cluster:",
|
||||
label,
|
||||
"<br>Pop:",
|
||||
round(e_pop_num / 1e6, 1),
|
||||
"M",
|
||||
"<br>Incidence:",
|
||||
round(e_inc_100k),
|
||||
"<br>Mortalité:",
|
||||
round(e_mort_exc_tbhiv_100k)
|
||||
)
|
||||
),
|
||||
size = 3,
|
||||
alpha = 0.6
|
||||
) +
|
||||
geom_point(
|
||||
data = highlight_data,
|
||||
aes(
|
||||
fill = label,
|
||||
text = paste(
|
||||
"<b>PAYS SÉLECTIONNÉ</b>",
|
||||
"<br>Pays:",
|
||||
country,
|
||||
"<br>Cluster:",
|
||||
label,
|
||||
"<br>Incidence:",
|
||||
round(e_inc_100k),
|
||||
"<br>Mortalité:",
|
||||
round(e_mort_exc_tbhiv_100k)
|
||||
)
|
||||
),
|
||||
shape = 21,
|
||||
color = "black",
|
||||
stroke = 1,
|
||||
size = 5,
|
||||
alpha = 1,
|
||||
show.legend = FALSE
|
||||
) +
|
||||
scale_x_log10() +
|
||||
scale_y_log10() +
|
||||
scale_color_manual(values = cluster_colors) +
|
||||
scale_fill_manual(values = cluster_colors) +
|
||||
labs(title = "Incidence vs Mortalité", x = "Incidence", y = "Mortalité") +
|
||||
theme_minimal() +
|
||||
theme(legend.position = "bottom")
|
||||
|
||||
ggplotly(p, tooltip = "text", source = "scatter_click")
|
||||
})
|
||||
|
||||
# Tableau des données brutes
|
||||
output$raw_table <- DT::renderDT({
|
||||
data <- filtered_data() |>
|
||||
select(
|
||||
country,
|
||||
year,
|
||||
g_whoregion,
|
||||
e_inc_100k,
|
||||
e_mort_exc_tbhiv_100k,
|
||||
label
|
||||
)
|
||||
|
||||
datatable(
|
||||
data,
|
||||
rownames = FALSE,
|
||||
options = list(pageLength = 15, scrollX = TRUE),
|
||||
colnames = c(
|
||||
"Pays",
|
||||
"Année",
|
||||
"Région",
|
||||
"Incidence",
|
||||
"Mortalité",
|
||||
"Cluster"
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
# Mise à jour de la carte
|
||||
shiny::observe({
|
||||
data <- map_data_reactive()
|
||||
metric <- input$metric_select
|
||||
|
||||
leafletProxy("map_plot", data = data) |>
|
||||
clearShapes() |>
|
||||
clearControls() |>
|
||||
dessiner_polygones(data, metric)
|
||||
})
|
||||
|
||||
# Sélection du pays
|
||||
selected_country <- shiny::reactiveVal("FRA")
|
||||
|
||||
# Sélection du pays sur la carte
|
||||
shiny::observeEvent(input$map_plot_shape_click, {
|
||||
click <- input$map_plot_shape_click
|
||||
if (!is.null(click$id)) {
|
||||
selected_country(click$id)
|
||||
}
|
||||
})
|
||||
|
||||
# Sélection du pays dans le nuage de points
|
||||
shiny::observeEvent(event_data("plotly_click", source = "scatter_click"), {
|
||||
click_info <- event_data("plotly_click", source = "scatter_click")
|
||||
if (!is.null(click_info$customdata)) {
|
||||
selected_country(click_info$customdata)
|
||||
}
|
||||
})
|
||||
|
||||
# Sélection du pays dans la densité
|
||||
shiny::observeEvent(event_data("plotly_click", source = "density_click"), {
|
||||
click_info <- event_data("plotly_click", source = "density_click")
|
||||
if (!is.null(click_info$customdata)) {
|
||||
selected_country(click_info$customdata)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
# Lancement de l'application Shiny
|
||||
shiny::shinyApp(ui, server)
|
||||
BIN
M2/Data Visualisation/Project/data/TB_analysis_ready.RData
Normal file
BIN
M2/Data Visualisation/Project/data/TB_analysis_ready.RData
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load Diff
BIN
M2/Data Visualisation/Project/logo_dauphine.jpg
Normal file
BIN
M2/Data Visualisation/Project/logo_dauphine.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
3914
M2/Data Visualisation/Project/renv.lock
Normal file
3914
M2/Data Visualisation/Project/renv.lock
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
851
M2/Generative AI/TP1/TP1 Benchmark.ipynb
Normal file
851
M2/Generative AI/TP1/TP1 Benchmark.ipynb
Normal file
@@ -0,0 +1,851 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "172a7a9f",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"# TP2 - Benchmark automatique\n",
|
||||
"\n",
|
||||
"Dans ce TP nous allons définir une fonction pour mesurer les performances d'un modèle de langage via l'exécution de plusieurs benchmarks. Nous avons vu en cours trois manières de mesurer la performance d'un modèle de langage qu'on peut résumer à:\n",
|
||||
"1. **Évaluation automatique**: via un ensemble de questions dont on connait la réponse\n",
|
||||
"2. **Évaluation humaine**: qualification humaine de la réponse d'un modèle à une question\n",
|
||||
"3. **Évaluation par modèle de langage**: notation ou comparaison de réponse d'un ou plusieurs modèles par un autre modèle\n",
|
||||
"\n",
|
||||
"Nous nous intéressons ici au premier point, en particulier avec les benchmarks [GSM8K](https://huggingface.co/datasets/openai/gsm8k) et [HellaSwag](https://huggingface.co/datasets/Rowan/hellaswag).\n",
|
||||
"Dans l'ensemble du notebook nous utiliserons la librairie LangChain.\n",
|
||||
"\n",
|
||||
"Il est à garder en tête que ce notebook n'a qu'une portée pédagogique et n'est pas forcément à jour puisque le domaine évolue rapidement, ni que les pratiques sont celles validées par l'industrie.\n",
|
||||
"\n",
|
||||
"## Uniformisation des benchmarks\n",
|
||||
"\n",
|
||||
"Pour chaque benchmark que l'on considère, nous avons besoin de plusieurs informations :\n",
|
||||
"* **Dataset** : une fonction pour charger les questions du benchmark\n",
|
||||
"* **Référence** : une fonction capable d'identifier la réponse attentue\n",
|
||||
"* **Prompt** : un prompt qui permet de demander correctement au modèle de répondre à la question\n",
|
||||
"* **Chaîne** : une fonction qui renvoie la chaîne de traitement de LangChain\n",
|
||||
"* **Score** : une fonction qui score la performance d'un modèle sur une question\n",
|
||||
"\n",
|
||||
"Nous allons commencer par créer une classe qui regroupe ces desiderata :\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "cd75374d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import PromptTemplate\n",
|
||||
"from langchain_core.runnables import Runnable\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Benchmark:\n",
|
||||
" \"\"\"Base class for benchmarks.\"\"\"\n",
|
||||
"\n",
|
||||
" name: str\n",
|
||||
"\n",
|
||||
" def __init__(self, prompt: PromptTemplate) -> None:\n",
|
||||
" \"\"\"Initialize the benchmark with a prompt template.\"\"\"\n",
|
||||
" self.prompt = prompt\n",
|
||||
"\n",
|
||||
" def load_data(self) -> list:\n",
|
||||
" \"\"\"Load and return the benchmark data samples.\"\"\"\n",
|
||||
" raise NotImplementedError\n",
|
||||
"\n",
|
||||
" def build_chain(self, model) -> Runnable:\n",
|
||||
" \"\"\"Build and return the evaluation chain using the provided model.\"\"\"\n",
|
||||
" raise NotImplementedError\n",
|
||||
"\n",
|
||||
" def get_reference(self, sample) -> str:\n",
|
||||
" \"\"\"Extract and return the reference answer from a data sample.\"\"\"\n",
|
||||
" raise NotImplementedError\n",
|
||||
"\n",
|
||||
" def score(self, prediction, reference) -> float:\n",
|
||||
" \"\"\"Score the prediction against the reference answer.\"\"\"\n",
|
||||
" raise NotImplementedError"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "e2ab41df",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Pour rendre cette classe plus concrète, commençons par travailler avec le benchmark [GSM8K](https://huggingface.co/datasets/openai/gsm8k).\n",
|
||||
"\n",
|
||||
"### Benchmark GSM8K\n",
|
||||
"\n",
|
||||
"On commence par charger le dataset et observer une question.\n",
|
||||
"\n",
|
||||
"**Consigne** : Résoudre la question *à la main* et vérifier votre réponse. On recommande d'explorer plusieurs questions."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "93979ba0",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Number of questions: 1319\n",
|
||||
"Example of question:\n",
|
||||
" Janet’s ducks lay 16 eggs per day. She eats three for breakfast every morning and bakes muffins for her friends every day with four. She sells the remainder at the farmers' market daily for $2 per fresh duck egg. How much in dollars does she make every day at the farmers' market?\n",
|
||||
"And its answer:\n",
|
||||
" Janet sells 16 - 3 - 4 = <<16-3-4=9>>9 duck eggs a day.\n",
|
||||
"She makes 9 * 2 = $<<9*2=18>>18 every day at the farmer’s market.\n",
|
||||
"#### 18\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"from datasets import load_dataset\n",
|
||||
"\n",
|
||||
"np.random.seed(42)\n",
|
||||
"\n",
|
||||
"dataset = load_dataset(\"gsm8k\", \"main\")\n",
|
||||
"dataset = dataset[\"test\"]\n",
|
||||
"\n",
|
||||
"print(f\"Number of questions: {len(dataset)}\")\n",
|
||||
"index = 0\n",
|
||||
"print(\"Example of question:\\n\", dataset[index][\"question\"])\n",
|
||||
"print(\"And its answer:\\n\", dataset[index][\"answer\"])"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "82d797f0",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Après avoir inspecté plusieurs éléments du dataset, on remarque que la réponse finale est placée après la chaîne de caractères \"####\".\n",
|
||||
"\n",
|
||||
"**Consigne**: Construire une fonction `get_reference` qui prend en argument un élément de GMS8K (dictionnaire avec question et réponse) et renvoie la réponse attendue (string). On pourra utiliser la fonction [`search`](https://docs.python.org/3/library/re.html#re.search) de la librairie [`re`](https://docs.python.org/3/library/re.html#).\n",
|
||||
"Puis tester cette fonction sur l'exemple précédent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "b336056a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Reference: 18\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from re import search\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def get_reference(sample: dict) -> str:\n",
|
||||
" \"\"\"Extract the reference answer from a data sample.\"\"\"\n",
|
||||
" match = search(r\"#### (\\d+)\", sample[\"answer\"])\n",
|
||||
" return match.group(1) if match else None\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"index = 0\n",
|
||||
"reference = get_reference(sample=dataset[index])\n",
|
||||
"print(f\"Reference: {reference}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "4c137e6a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Il nous reste maintenant à définir un prompt tel que l'on puisse appeler un modèle et tester notre mécanique."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "0b899872",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.prompts import PromptTemplate\n",
|
||||
"\n",
|
||||
"prompt = PromptTemplate(\n",
|
||||
" input_variables=[\"question\"],\n",
|
||||
" template=(\n",
|
||||
" \"\"\"You are a careful mathematician. Solve the problem step by step, then display your answer in the end.\n",
|
||||
" Question: {question}\n",
|
||||
" Answer:\"\"\"\n",
|
||||
" ),\n",
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "36433b53",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"En intégrant l'appel à un modèle via Ollama sur notre ordinateur, on peut définir avec LangChain :"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2f0676b6",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Model answer : Here's how we can solve this problem step by step:\n",
|
||||
"\n",
|
||||
"1. **Calculate the total number of eggs laid:** Janet's ducks lay 16 eggs per day.\n",
|
||||
"\n",
|
||||
"2. **Calculate the number of eggs eaten:** She eats 3 eggs per day.\n",
|
||||
"\n",
|
||||
"3. **Calculate the number of eggs remaining after breakfast:** 16 eggs (laid) - 3 eggs (eaten) = 13 eggs\n",
|
||||
"\n",
|
||||
"4. **Calculate the number of eggs used for baking:** She uses 4 eggs for baking.\n",
|
||||
"\n",
|
||||
"5. **Calculate the number of eggs remaining after baking:** 13 eggs - 4 eggs (baking) = 9 eggs\n",
|
||||
"\n",
|
||||
"6. **Calculate the earnings from selling the remaining eggs:** She sells 9 eggs at $2 per egg. So she makes 9 * $2 = $18.\n",
|
||||
"\n",
|
||||
"**Answer:** $18\n",
|
||||
"The answer was : 18\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain_core.output_parsers import StrOutputParser\n",
|
||||
"from langchain_core.runnables import RunnablePassthrough\n",
|
||||
"from langchain_ollama import OllamaLLM\n",
|
||||
"\n",
|
||||
"model = OllamaLLM(model=\"gemma3:4b\")\n",
|
||||
"\n",
|
||||
"chain = {\"question\": RunnablePassthrough()} | prompt | model | StrOutputParser()\n",
|
||||
"\n",
|
||||
"index = 0\n",
|
||||
"\n",
|
||||
"question = dataset[index][\"question\"]\n",
|
||||
"answer = get_reference(dataset[index])\n",
|
||||
"response = chain.invoke(question)\n",
|
||||
"print(f\"Model answer : {response}\")\n",
|
||||
"print(f\"The answer was : {answer}\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "97dd7db7",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Il nous faut extraire la dernière valeur numérique pour obtenir automatiquement la réponse du modèle.\n",
|
||||
"\n",
|
||||
"**Consigne** : Définir une fonction `score` qui prend en paramètre la réponse du modèle et la réponse attendue puis renvoie si les deux réponses sont identiques (1 / 0). On pourra utiliser la fonction [`findall`](https://docs.python.org/3/library/re.html#re.findall) de la librairie `re`.\n",
|
||||
"Puis l'appliquer sur l'exemple précédent."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "ad43cf84",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The model scored 1.0\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from re import findall\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def score(prediction, reference):\n",
|
||||
" if reference is None:\n",
|
||||
" return 0.0\n",
|
||||
"\n",
|
||||
" numbers = findall(r\"\\d+\", prediction)\n",
|
||||
" return 1.0 if numbers and numbers[-1] == reference else 0.0\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"value = score(response, answer)\n",
|
||||
"print(f\"The model scored {value}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "a2ec5088",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Nous avons l'ensemble des éléments nécessaire pour définir la classe `GSM8KBenchmark` depuis la classe `Benchmark` que nous avons défini précédemment.\n",
|
||||
"\n",
|
||||
"**Consigne** : Définir cette classe comme sous-classe de `Benchmark`."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 41,
|
||||
"id": "d83f4394",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class GSM8KBenchmark(Benchmark):\n",
|
||||
" name = \"GSM8K\"\n",
|
||||
"\n",
|
||||
" def load_data(self):\n",
|
||||
" return load_dataset(\"gsm8k\", \"main\", split=\"test\")\n",
|
||||
"\n",
|
||||
" def build_chain(self, model):\n",
|
||||
" return (\n",
|
||||
" {\"question\": RunnablePassthrough()}\n",
|
||||
" | self.prompt\n",
|
||||
" | model\n",
|
||||
" | StrOutputParser()\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" def get_reference(self, sample):\n",
|
||||
" match = search(r\"#### (\\d+)\", sample[\"answer\"])\n",
|
||||
" return match.group(1) if match else None\n",
|
||||
"\n",
|
||||
" def score(self, prediction, reference):\n",
|
||||
" if reference is None:\n",
|
||||
" return 0.0\n",
|
||||
" numbers = findall(r\"\\d+\", prediction)\n",
|
||||
" return 1.0 if numbers and numbers[-1] == reference else 0.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "dfc3cb78",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Il est maintenant temps de définir une fonction qui *fait* le benchmark.\n",
|
||||
"\n",
|
||||
"**Consigne** : Définir une fonction `run_benchmark` qui prend en paramètre :\n",
|
||||
"* `model_name` : le nom du modèle Ollama que l'on veut tester\n",
|
||||
"* `benchmark` : la classe benchmark que l'on souhaite tester\n",
|
||||
"* `max_samples` : le nombre maximum de questions que l'on souhaite utiliser\n",
|
||||
"\n",
|
||||
"Puisque l'object avec lequel nous travaillons est un dataset HuggingFace, pour sélectionner $n$ lignes, on utilisera \n",
|
||||
"```python\n",
|
||||
"dataset = dataset.select(range(max_samples))\n",
|
||||
"```\n",
|
||||
"De cette manière on préserve la structure."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2d7125af",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from tqdm import tqdm\n",
|
||||
"import numpy as np\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def run_benchmark(\n",
|
||||
" model_name: str, benchmark: Benchmark, max_samples: int | None = None\n",
|
||||
") -> dict:\n",
|
||||
" model = OllamaLLM(model=model_name)\n",
|
||||
"\n",
|
||||
" data = benchmark.load_data()\n",
|
||||
" if max_samples:\n",
|
||||
" data = data.select(range(max_samples))\n",
|
||||
" chain = benchmark.build_chain(model)\n",
|
||||
"\n",
|
||||
" scores = []\n",
|
||||
"\n",
|
||||
" for sample in tqdm(data, desc=f\"Running {benchmark.name}\"):\n",
|
||||
" prediction = chain.invoke(sample)\n",
|
||||
" reference = benchmark.get_reference(sample)\n",
|
||||
" scores.append(benchmark.score(prediction, reference))\n",
|
||||
"\n",
|
||||
" results = {\n",
|
||||
" \"benchmark\": benchmark.name,\n",
|
||||
" \"model\": model_name,\n",
|
||||
" \"num_samples\": len(scores),\n",
|
||||
" \"accuracy\": np.mean(scores),\n",
|
||||
" }\n",
|
||||
" return results\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "81de8940",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Consigne** : Utiliser la fonction `run_benchmark` en définissant un prompt pour GSM8K."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f6bbeb53",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Running GSM8K: 100%|██████████| 5/5 [00:50<00:00, 10.18s/it]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'benchmark': 'GSM8K', 'model': 'gemma3:4b', 'num_samples': 5, 'accuracy': 0.8}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"prompt_GMS8K = PromptTemplate(\n",
|
||||
" input_variables=[\"question\"],\n",
|
||||
" template=(\n",
|
||||
" \"\"\"You are a careful mathematician. Solve the problem step by step, then display your answer in the end.\n",
|
||||
" Question: {question}\n",
|
||||
" Answer:\"\"\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"benchmark_GSM8K = GSM8KBenchmark(prompt=prompt_GMS8K)\n",
|
||||
"results = run_benchmark(\n",
|
||||
" model_name=\"gemma3:4b\", benchmark=benchmark_GSM8K, max_samples=5\n",
|
||||
")\n",
|
||||
"print(results)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "0c943124",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"### HellaSwag\n",
|
||||
"\n",
|
||||
"Maintenant que nous avons réussi à le faire pour le dataset GMS8K, attaquons-nous à [HellaSwag](https://huggingface.co/datasets/Rowan/hellaswag).\n",
|
||||
"\n",
|
||||
"**Consigne** : En suivant la même approche que précédemment, implémenter une sous classe `HellaSwagBenchmark` à partir de la classe `Benchmark`. Puis utiliser la fonction `run_benchmark` pour valider votre travail."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 44,
|
||||
"id": "32886901",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class HellaSwagBenchmark(Benchmark):\n",
|
||||
" name = \"HellaSwag\"\n",
|
||||
"\n",
|
||||
" def load_data(self):\n",
|
||||
" return load_dataset(\"hellaswag\", split=\"validation\")\n",
|
||||
"\n",
|
||||
" def build_chain(self, model):\n",
|
||||
" return (\n",
|
||||
" {\n",
|
||||
" \"context\": lambda x: x[\"ctx\"],\n",
|
||||
" \"choices\": lambda x: \"\\n\".join(\n",
|
||||
" f\"{index}: {choice}\" for index, choice in enumerate(x[\"endings\"])\n",
|
||||
" ),\n",
|
||||
" }\n",
|
||||
" | self.prompt\n",
|
||||
" | model\n",
|
||||
" | StrOutputParser()\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" def get_reference(self, sample):\n",
|
||||
" return str(sample[\"label\"])\n",
|
||||
"\n",
|
||||
" def score(self, prediction, reference):\n",
|
||||
" match = search(r\"\\d\", prediction)\n",
|
||||
" return 1.0 if match and match.group(0) == reference else 0.0\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "96a3031a",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Running HellaSwag: 100%|██████████| 5/5 [00:02<00:00, 2.08it/s]"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"{'benchmark': 'HellaSwag', 'model': 'gemma3:4b', 'num_samples': 5, 'accuracy': 1.0}\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"prompt_HellaSwag = PromptTemplate(\n",
|
||||
" input_variables=[\"context\", \"choices\"],\n",
|
||||
" template=(\n",
|
||||
" \"\"\"You will be given a context and then different choices. You need to find the most likely continuation to the context. Answer with the number of the most likely choice only.\n",
|
||||
" Context: {context}\n",
|
||||
" Choices: {choices}\n",
|
||||
" Answer:\"\"\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"benchmark_HellaSwag = HellaSwagBenchmark(prompt=prompt_HellaSwag)\n",
|
||||
"\n",
|
||||
"results = run_benchmark(\n",
|
||||
" model_name=\"gemma3:4b\", benchmark=benchmark_HellaSwag, max_samples=5\n",
|
||||
")\n",
|
||||
"print(results)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "c542783c",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Réponses structurées\n",
|
||||
"\n",
|
||||
"Sur quelques exemples tout semble fonctionner ! Mais il y a au moins une fragilité dans notre travail : la récupération de la réponse est peu fiable et largement dépendante des prompts.\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"Par exemple pour GMS8K, on aimerait avoir une réponse sous la forme d'un JSON :\n",
|
||||
"```json\n",
|
||||
"{\n",
|
||||
" \"reasoning\": \"étapes de raisonnement\",\n",
|
||||
" \"final_answer\": 18\n",
|
||||
"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"De cette manière ce serait particulièrement simple d'extraire la réponse, sans pour autant ne pas avoir de *réflexion* du modèle. En revanche pour HellaSwag, un JSON extrêment simple suffit :\n",
|
||||
"```json\n",
|
||||
"{\n",
|
||||
" \"choice\": 2\n",
|
||||
"}\n",
|
||||
"```\n",
|
||||
"\n",
|
||||
"Pour forcer le modèle à suivre ces formats, nous allons utiliser l'option [Pydantic](https://docs.langchain.com/oss/python/langchain/structured-output). Elle s'utilise comme suit, pour GSM8K :"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "988dbca3",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from pydantic import BaseModel, Field\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class GSM8KOutput(BaseModel):\n",
|
||||
" reasoning: str = Field(description=\"Step-by-step reasoning\")\n",
|
||||
" final_answer: float = Field(description=\"Final numeric answer\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d855adfe",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"Concernant l'intégration dans le prompt :"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "f25afddc",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"The output should be formatted as a JSON instance that conforms to the JSON schema below.\n",
|
||||
"\n",
|
||||
"As an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\n",
|
||||
"the object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n",
|
||||
"\n",
|
||||
"Here is the output schema:\n",
|
||||
"```\n",
|
||||
"{\"properties\": {\"reasoning\": {\"description\": \"Step-by-step reasoning\", \"title\": \"Reasoning\", \"type\": \"string\"}, \"final_answer\": {\"description\": \"Final numeric answer\", \"title\": \"Final Answer\", \"type\": \"number\"}}, \"required\": [\"reasoning\", \"final_answer\"]}\n",
|
||||
"```\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from langchain.output_parsers import PydanticOutputParser\n",
|
||||
"\n",
|
||||
"parser_gsm8k = PydanticOutputParser(pydantic_object=GSM8KOutput)\n",
|
||||
"\n",
|
||||
"prompt_gsm8k = PromptTemplate(\n",
|
||||
" input_variables=[\"question\"],\n",
|
||||
" partial_variables={\"format_instructions\": parser_gsm8k.get_format_instructions()},\n",
|
||||
" template=(\n",
|
||||
" \"\"\"You are a careful mathematician. Solve the problem step by step.\n",
|
||||
" Question: {question}\n",
|
||||
" {format_instructions}\"\"\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"print(parser_gsm8k.get_format_instructions())"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "d1dcc480",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Consigne** : Modifier la classe `Benchmark` et la sous-classe `GMS8KBenchmark` pour intégrer ces évolutions."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 67,
|
||||
"id": "542a31d6",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"from langchain_core.runnables import Runnable\n",
|
||||
"from langchain_core.prompts import PromptTemplate\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class Benchmark:\n",
|
||||
" name: str\n",
|
||||
"\n",
|
||||
" def __init__(self, prompt: PromptTemplate, parser: PydanticOutputParser):\n",
|
||||
" self.prompt = prompt\n",
|
||||
" self.parser = parser\n",
|
||||
"\n",
|
||||
" def load_data(self):\n",
|
||||
" raise NotImplementedError\n",
|
||||
"\n",
|
||||
" def build_chain(self, model) -> Runnable:\n",
|
||||
" raise NotImplementedError\n",
|
||||
"\n",
|
||||
" def get_reference(self, sample):\n",
|
||||
" raise NotImplementedError\n",
|
||||
"\n",
|
||||
" def score(self, prediction, reference):\n",
|
||||
" raise NotImplementedError"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "c94f1dd1",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class GSM8KBenchmark(Benchmark):\n",
|
||||
" name = \"GSM8K\"\n",
|
||||
"\n",
|
||||
" def load_data(self):\n",
|
||||
" return load_dataset(\"gsm8k\", \"main\", split=\"test\")\n",
|
||||
"\n",
|
||||
" def build_chain(self, model):\n",
|
||||
" return {\"question\": RunnablePassthrough()} | self.prompt | model | self.parser\n",
|
||||
"\n",
|
||||
" def get_reference(self, sample):\n",
|
||||
" match = search(r\"#### (\\d+)\", sample[\"answer\"])\n",
|
||||
" return float(match.group(1)) if match else None\n",
|
||||
"\n",
|
||||
" def score(self, prediction: GSM8KOutput, reference: float | None):\n",
|
||||
" if reference is None:\n",
|
||||
" return 0.0\n",
|
||||
" return 1.0 if prediction.final_answer == reference else 0.0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b2076f24",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Consigne** : Utiliser la fonction `run_benchmark` et vérifier que tout fonctionne."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 69,
|
||||
"id": "31e433b0",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Running GSM8K: 100%|██████████| 5/5 [01:01<00:00, 12.25s/it]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'benchmark': 'GSM8K', 'model': 'gemma3:4b', 'num_samples': 5, 'accuracy': 0.8}"
|
||||
]
|
||||
},
|
||||
"execution_count": 69,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"gsm8k = GSM8KBenchmark(\n",
|
||||
" prompt=prompt_gsm8k,\n",
|
||||
" parser=parser_gsm8k,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"run_benchmark(\"gemma3:4b\", gsm8k, max_samples=5)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "b7ed90cd",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"**Consigne** : Réaliser la même modification pour HellaSwag, et vérifier que cela fonctionne."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "e678bed2",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"class HellaSwagOutput(BaseModel):\n",
|
||||
" choice: int = Field(description=\"Index of the chosen continuation\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"class HellaSwagBenchmark(Benchmark):\n",
|
||||
" name = \"HellaSwag\"\n",
|
||||
"\n",
|
||||
" def load_data(self):\n",
|
||||
" return load_dataset(\"hellaswag\", split=\"validation\")\n",
|
||||
"\n",
|
||||
" def build_chain(self, model):\n",
|
||||
" return (\n",
|
||||
" {\n",
|
||||
" \"context\": lambda x: x[\"ctx\"],\n",
|
||||
" \"choices\": lambda x: \"\\n\".join(\n",
|
||||
" f\"{index}: {choice}\" for index, choice in enumerate(x[\"endings\"])\n",
|
||||
" ),\n",
|
||||
" }\n",
|
||||
" | self.prompt\n",
|
||||
" | model\n",
|
||||
" | self.parser\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" def get_reference(self, sample):\n",
|
||||
" return str(sample[\"label\"])\n",
|
||||
"\n",
|
||||
" def score(self, prediction: HellaSwagOutput, reference: str) -> float:\n",
|
||||
" return 1.0 if str(prediction.choice) == reference else 0.0\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "2455f816",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stderr",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Running HellaSwag: 100%|██████████| 5/5 [00:15<00:00, 3.12s/it]\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"{'benchmark': 'HellaSwag',\n",
|
||||
" 'model': 'gemma3:4b',\n",
|
||||
" 'num_samples': 5,\n",
|
||||
" 'accuracy': 1.0}"
|
||||
]
|
||||
},
|
||||
"execution_count": 65,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"parser_hellaswag = PydanticOutputParser(pydantic_object=HellaSwagOutput)\n",
|
||||
"\n",
|
||||
"prompt_HellaSwag = PromptTemplate(\n",
|
||||
" input_variables=[\"context\", \"choices\"],\n",
|
||||
" partial_variables={\n",
|
||||
" \"format_instructions\": parser_hellaswag.get_format_instructions()\n",
|
||||
" },\n",
|
||||
" template=(\n",
|
||||
" \"\"\"You will be given a context and then different choices. You need to find the most likely continuation to the context.\n",
|
||||
" Context: {context}\n",
|
||||
" Choices: {choices}\n",
|
||||
" {format_instructions}\"\"\"\n",
|
||||
" ),\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"hella_swag = HellaSwagBenchmark(\n",
|
||||
" prompt=prompt_HellaSwag,\n",
|
||||
" parser=parser_hellaswag,\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"run_benchmark(\"gemma3:4b\", hella_swag, max_samples=5)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "ba9acd54",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Pour aller plus loin\n",
|
||||
"\n",
|
||||
"On pourrait implémenter d'autres benchmark, comparer vraiment des modèles entre eux, comparer des prompts entre eux..."
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"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.11.13"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
1395
M2/Generative AI/TP1/TP1 RAG.ipynb
Normal file
1395
M2/Generative AI/TP1/TP1 RAG.ipynb
Normal file
File diff suppressed because one or more lines are too long
BIN
M2/Generative AI/TP1/data/ML.pdf
Normal file
BIN
M2/Generative AI/TP1/data/ML.pdf
Normal file
Binary file not shown.
1512
M2/Generative AI/TP2/TP2 Fitting.ipynb
Normal file
1512
M2/Generative AI/TP2/TP2 Fitting.ipynb
Normal file
File diff suppressed because one or more lines are too long
7696
M2/Generative AI/TP2/data/Complete.csv
Normal file
7696
M2/Generative AI/TP2/data/Complete.csv
Normal file
File diff suppressed because one or more lines are too long
@@ -31,7 +31,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 73,
|
||||
"execution_count": 1,
|
||||
"id": "100d1e0d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -42,7 +42,9 @@
|
||||
"np.set_printoptions(\n",
|
||||
" precision=3,\n",
|
||||
" suppress=True,\n",
|
||||
") # (not mandatory) This line is for limiting floats to 3 decimal places, avoiding scientific notation (like 1.23e-04) for small numbers.\n",
|
||||
")\n",
|
||||
"# (not mandatory) This line is for limiting floats to 3 decimal places,\n",
|
||||
"# avoiding scientific notation (like 1.23e-04) for small numbers.\n",
|
||||
"\n",
|
||||
"# For reproducibility\n",
|
||||
"rng = np.random.default_rng(seed=42) # This line creates a random number generator."
|
||||
@@ -98,7 +100,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 74,
|
||||
"execution_count": 2,
|
||||
"id": "f91cda05",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -126,7 +128,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 75,
|
||||
"execution_count": 3,
|
||||
"id": "564cb757-eefe-4be6-9b6f-bb77ace42a97",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -148,7 +150,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 76,
|
||||
"execution_count": 4,
|
||||
"id": "26c821d3-2362-4b60-8c77-3d09296d130d",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -198,7 +200,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 77,
|
||||
"execution_count": 5,
|
||||
"id": "7116044b-c134-43de-9f30-01ab62325300",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -229,7 +231,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 78,
|
||||
"execution_count": 6,
|
||||
"id": "a1258de4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -249,18 +251,17 @@
|
||||
"pos_to_state = {} # (i,j) -> s\n",
|
||||
"\n",
|
||||
"start_state = None # will store the state index of start state\n",
|
||||
"goal_states = [] # will store the state index of goal state # We use a list in case there are multiple goals\n",
|
||||
"trap_states = [] # will store the state index of trap state # We use a list in case there are multiple traps\n",
|
||||
"goal_states = [] # will store the state index of goal state\n",
|
||||
"trap_states = [] # will store the state index of trap state\n",
|
||||
"\n",
|
||||
"s = 0\n",
|
||||
"for i in range(n_rows): # i = row index\n",
|
||||
" for j in range(n_cols): # j = column index\n",
|
||||
" cell = maze_str[i][j] # cell = the character at that position (S, ., #, etc.)\n",
|
||||
"\n",
|
||||
" if (\n",
|
||||
" cell in FREE\n",
|
||||
" ): # FREE contains: free cells \".\", start cell \"S\", goal cell \"G\" and trap cell \"X\"\n",
|
||||
" # Walls # are ignored, they are not MDP states.\n",
|
||||
" if cell in FREE:\n",
|
||||
" # FREE contains: free cells \".\", start cell \"S\", goal cell \"G\" and trap cell \"X\"\n",
|
||||
" # Walls # are ignored, they are not MDP states.\n",
|
||||
" state_to_pos[s] = (i, j)\n",
|
||||
" pos_to_state[(i, j)] = s\n",
|
||||
"\n",
|
||||
@@ -291,7 +292,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 79,
|
||||
"execution_count": 7,
|
||||
"id": "68744dd6-7278-4c20-8b82-34212685352f",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -365,7 +366,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 80,
|
||||
"execution_count": 8,
|
||||
"id": "fc61ceef-217c-47f4-8eba-0353369210db",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -398,7 +399,7 @@
|
||||
" for (\n",
|
||||
" s,\n",
|
||||
" (i, j),\n",
|
||||
" ) in state_to_pos.items(): # Calling .items() returns a list-like sequence of (key, value) pairs in the dictionary.\n",
|
||||
" ) in state_to_pos.items():\n",
|
||||
" cell = maze_str[i][j]\n",
|
||||
"\n",
|
||||
" if cell == \"S\":\n",
|
||||
@@ -475,7 +476,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 81,
|
||||
"execution_count": 9,
|
||||
"id": "f7f0b8e4-1f48-4d03-9e5f-a47e59c3e827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -487,7 +488,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 82,
|
||||
"execution_count": 10,
|
||||
"id": "3773781c-a0cd-48db-967b-d4b432d17046",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -517,7 +518,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 83,
|
||||
"execution_count": 11,
|
||||
"id": "4b06da5e-bc63-48e5-a336-37bce952443d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -596,7 +597,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 84,
|
||||
"execution_count": 12,
|
||||
"id": "610253e7-f3f7-4a30-be3e-2ec5a1e2ed04",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -627,7 +628,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 85,
|
||||
"execution_count": 13,
|
||||
"id": "7a51f242-fe4e-4e74-8a1f-a8df32b194b8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -655,7 +656,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 86,
|
||||
"execution_count": 14,
|
||||
"id": "49d54d1f-dc29-45b6-ad31-ad0e848f920d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -700,7 +701,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 87,
|
||||
"execution_count": 15,
|
||||
"id": "b9b7495a-c233-425c-99c0-5bddaf6c3225",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -726,7 +727,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 88,
|
||||
"execution_count": 16,
|
||||
"id": "eca4c571-39c7-468b-af86-0bab9489415e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -757,7 +758,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 89,
|
||||
"execution_count": 17,
|
||||
"id": "2d03276b-e206-4d1f-9024-f6948ca61523",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -820,7 +821,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 90,
|
||||
"execution_count": 18,
|
||||
"id": "341fe630-8f87-4773-84ad-92d3516e53e2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -967,7 +968,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 91,
|
||||
"execution_count": 19,
|
||||
"id": "2fffe0b7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -1036,7 +1037,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 92,
|
||||
"execution_count": 20,
|
||||
"id": "b4a44e38",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -1065,7 +1066,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 93,
|
||||
"execution_count": 21,
|
||||
"id": "c5f559b2-452a-477c-a1fa-258b40805670",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -1109,7 +1110,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 94,
|
||||
"execution_count": 22,
|
||||
"id": "4c428327",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -1125,7 +1126,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"def plot_values(V: np.ndarray, title=\"Value function\") -> None:\n",
|
||||
"def plot_values(V: np.ndarray, title: str = \"Value function\") -> None:\n",
|
||||
" \"\"\"Plot the value function V on the maze as a heatmap.\"\"\"\n",
|
||||
" grid_values = np.full(\n",
|
||||
" (n_rows, n_cols),\n",
|
||||
@@ -1149,7 +1150,7 @@
|
||||
" # For each state:\n",
|
||||
" # Place the text label at (column j, row i).\n",
|
||||
" # Display value to two decimals.\n",
|
||||
" # Use white text so it’s visible on the heatmap.\n",
|
||||
" # Use white text so it's visible on the heatmap.\n",
|
||||
" # Center the text inside each cell.\n",
|
||||
"\n",
|
||||
" for s, (i, j) in state_to_pos.items():\n",
|
||||
@@ -1183,12 +1184,12 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 95,
|
||||
"execution_count": 23,
|
||||
"id": "c1ab67f0-bd5e-4ffe-b655-aec030401b78",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"def plot_policy(policy: np.ndarray, title=\"Policy\") -> None:\n",
|
||||
"def plot_policy(policy: np.ndarray, title: str =\"Policy\") -> None:\n",
|
||||
" \"\"\"Plot the given policy on the maze.\"\"\"\n",
|
||||
" _fig, ax = plt.subplots()\n",
|
||||
" # draw walls as dark cells\n",
|
||||
@@ -1253,7 +1254,7 @@
|
||||
" ax.set_yticks(np.arange(-0.5, n_rows, 1))\n",
|
||||
" ax.set_xticklabels([])\n",
|
||||
" ax.set_yticklabels([])\n",
|
||||
" ax.grid(True)\n",
|
||||
" ax.grid(visible=True)\n",
|
||||
" ax.set_title(title)\n",
|
||||
" plt.show()"
|
||||
]
|
||||
@@ -1268,7 +1269,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 96,
|
||||
"execution_count": 24,
|
||||
"id": "d452681c-c89c-41cc-95dc-df75993b0391",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -1297,7 +1298,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 97,
|
||||
"execution_count": 25,
|
||||
"id": "929707e6-3022-4d86-96cc-12f251f890a9",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -1323,35 +1324,37 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"my_policy = [\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_DOWN, # First row\n",
|
||||
" A_UP,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_LEFT, # Second row\n",
|
||||
" A_UP,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_DOWN, # Third row\n",
|
||||
" A_UP,\n",
|
||||
" A_LEFT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT, # Fourth row\n",
|
||||
" A_UP,\n",
|
||||
" A_LEFT,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_UP, # Fifth row\n",
|
||||
"]\n",
|
||||
"my_policy = np.array(\n",
|
||||
" [\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_DOWN, # First row\n",
|
||||
" A_UP,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_LEFT, # Second row\n",
|
||||
" A_UP,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_DOWN, # Third row\n",
|
||||
" A_UP,\n",
|
||||
" A_LEFT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_RIGHT, # Fourth row\n",
|
||||
" A_UP,\n",
|
||||
" A_LEFT,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_RIGHT,\n",
|
||||
" A_UP, # Fifth row\n",
|
||||
" ],\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"V_my_policy = policy_evaluation(policy=my_policy, P=P, R=R, gamma=gamma)\n",
|
||||
"\n",
|
||||
"plot_values(V=V_my_policy, title=\"Value function: my policy\")\n",
|
||||
"plot_policy(policy=my_policy, title=\"My policy\")"
|
||||
"plot_policy(policy=my_policy, title=\"My policy\")\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 535,
|
||||
"execution_count": 24,
|
||||
"id": "100d1e0d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -49,7 +49,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 536,
|
||||
"execution_count": 25,
|
||||
"id": "f91cda05",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -72,7 +72,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 537,
|
||||
"execution_count": 26,
|
||||
"id": "564cb757-eefe-4be6-9b6f-bb77ace42a97",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -93,7 +93,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 538,
|
||||
"execution_count": 27,
|
||||
"id": "7116044b-c134-43de-9f30-01ab62325300",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -116,7 +116,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 539,
|
||||
"execution_count": 28,
|
||||
"id": "a1258de4",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -170,7 +170,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 540,
|
||||
"execution_count": 29,
|
||||
"id": "fc61ceef-217c-47f4-8eba-0353369210db",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -250,7 +250,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 541,
|
||||
"execution_count": 30,
|
||||
"id": "f7f0b8e4-1f48-4d03-9e5f-a47e59c3e827",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -262,7 +262,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 542,
|
||||
"execution_count": 31,
|
||||
"id": "4b06da5e-bc63-48e5-a336-37bce952443d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -330,7 +330,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 543,
|
||||
"execution_count": 32,
|
||||
"id": "610253e7-f3f7-4a30-be3e-2ec5a1e2ed04",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -341,7 +341,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 544,
|
||||
"execution_count": 33,
|
||||
"id": "7a51f242-fe4e-4e74-8a1f-a8df32b194b8",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -353,7 +353,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 545,
|
||||
"execution_count": 34,
|
||||
"id": "49d54d1f-dc29-45b6-ad31-ad0e848f920d",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -366,7 +366,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 546,
|
||||
"execution_count": 35,
|
||||
"id": "b9b7495a-c233-425c-99c0-5bddaf6c3225",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -382,7 +382,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 547,
|
||||
"execution_count": 36,
|
||||
"id": "eca4c571-39c7-468b-af86-0bab9489415e",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -397,7 +397,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 548,
|
||||
"execution_count": 37,
|
||||
"id": "2d03276b-e206-4d1f-9024-f6948ca61523",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -445,7 +445,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 549,
|
||||
"execution_count": 38,
|
||||
"id": "341fe630-8f87-4773-84ad-92d3516e53e2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -492,7 +492,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 550,
|
||||
"execution_count": 39,
|
||||
"id": "2fffe0b7",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -543,7 +543,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 551,
|
||||
"execution_count": 40,
|
||||
"id": "4c428327",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -595,7 +595,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 552,
|
||||
"execution_count": null,
|
||||
"id": "c1ab67f0-bd5e-4ffe-b655-aec030401b78",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
@@ -665,7 +665,7 @@
|
||||
" ax.set_yticks(np.arange(-0.5, n_rows, 1))\n",
|
||||
" ax.set_xticklabels([])\n",
|
||||
" ax.set_yticklabels([])\n",
|
||||
" ax.grid(True)\n",
|
||||
" ax.grid(visible=True)\n",
|
||||
" ax.set_title(title)\n",
|
||||
" plt.show()"
|
||||
]
|
||||
@@ -680,7 +680,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 553,
|
||||
"execution_count": 42,
|
||||
"id": "ceb5dfe2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -702,7 +702,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 554,
|
||||
"execution_count": 43,
|
||||
"id": "8f3e2ac2",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -729,7 +729,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 558,
|
||||
"execution_count": 44,
|
||||
"id": "cf45291e",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -761,7 +761,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 557,
|
||||
"execution_count": 45,
|
||||
"id": "5a82a3b7",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
@@ -787,7 +787,7 @@
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"my_policy = [\n",
|
||||
"my_policy = np.array([\n",
|
||||
" A_DOWN,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_LEFT,\n",
|
||||
@@ -850,7 +850,7 @@
|
||||
" A_UP,\n",
|
||||
" A_DOWN,\n",
|
||||
" A_LEFT,\n",
|
||||
"]\n",
|
||||
"])\n",
|
||||
"\n",
|
||||
"V_my_policy = policy_evaluation(policy=my_policy, P=P, R=R, gamma=gamma)\n",
|
||||
"\n",
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
126
M2/Statistiques Non Paramétrique/TP1.Rmd
Normal file
126
M2/Statistiques Non Paramétrique/TP1.Rmd
Normal file
@@ -0,0 +1,126 @@
|
||||
|
||||
```{r}
|
||||
n = 100
|
||||
Z = sample.int(3, n, replace=T)
|
||||
```
|
||||
|
||||
```{r}
|
||||
mu1 = -3
|
||||
mu2 = 0
|
||||
mu3 = 1
|
||||
sigma12 = 1
|
||||
sigma22 = 1
|
||||
sigma32 = 0.03
|
||||
X = rnorm(n, mu1, sqrt(sigma12))
|
||||
X[Z == 2] = rnorm(sum(Z == 2), mu2, sqrt(sigma22))
|
||||
X[Z == 3] = rnorm(sum(Z == 3), mu3, sqrt(sigma32))
|
||||
```
|
||||
|
||||
```{r}
|
||||
plot(density(X, bw = 0.1), ylim = c(0, 0.85), main = 'Estimation de la densité')
|
||||
rug(X)
|
||||
x = seq(-6, 2, length.out = 100)
|
||||
points(
|
||||
x,
|
||||
(dnorm(x, mu1, sqrt(sigma12)) +
|
||||
dnorm(x, mu2, sqrt(sigma22)) +
|
||||
dnorm(x, mu3, sqrt(sigma32))) /
|
||||
3,
|
||||
type = 'l',
|
||||
col = 'darkgreen'
|
||||
)
|
||||
legend(
|
||||
'topleft',
|
||||
c('Vraie densité', 'Estimation'),
|
||||
lty = c(1, 1),
|
||||
col = c('darkgreen', 1))
|
||||
```
|
||||
|
||||
La valeur 0. semble donner une meilleure estimation.
|
||||
|
||||
```{r}
|
||||
plot(
|
||||
density(X, bw = 'SJ'),
|
||||
ylim = c(0, 0.85),
|
||||
main = 'Estimation de la densité'
|
||||
)
|
||||
lines(density(X, bw = 'ucv'), col = 2)
|
||||
lines(density(X, bw = 'bcv'), col = 4)
|
||||
|
||||
rug(X)
|
||||
x = seq(-6, 2, length.out = 100)
|
||||
points(
|
||||
x,
|
||||
(dnorm(x, mu1, sqrt(sigma12)) +
|
||||
dnorm(x, mu2, sqrt(sigma22)) +
|
||||
dnorm(x, mu3, sqrt(sigma32))) /
|
||||
3,
|
||||
type = 'l',
|
||||
col = 'darkgreen'
|
||||
)
|
||||
legend(
|
||||
'topleft',
|
||||
c('Vraie densité', 'SJ', 'ucv', 'bcv'),
|
||||
lty = rep(1, 4),
|
||||
col = c('darkgreen', 1, 2, 4))
|
||||
```
|
||||
|
||||
La validation croisée non biaisée (UCV) semble donner les meilleurs résultats..
|
||||
|
||||
```{r}
|
||||
kmax = 50
|
||||
H = (1:kmax)^2 / (n * sqrt(2 * pi))
|
||||
hmin = min(H)
|
||||
p = 5000
|
||||
fhat = matrix(NA, kmax, p)
|
||||
fhatmin = density(X, bw = hmin, n = p)
|
||||
x = fhatmin$x
|
||||
fhat[1, ] = fhatmin$y
|
||||
for (j in 2:kmax) {
|
||||
fhat[j, ] = density(X, bw = H[j], n = p)$y
|
||||
}
|
||||
# Calcul du critere PCO
|
||||
b = rep(NA, kmax)
|
||||
v = rep(NA, kmax)
|
||||
crit = rep(NA, kmax)
|
||||
for (k in 1:kmax) {
|
||||
b[k] = mean((fhat[k, ] - fhat[1, ])^2)
|
||||
v[k] = 2 * mean(dnorm(x, sd = hmin) * dnorm(x, sd = H[k])) / n
|
||||
crit[k] = b[k] + v[k]
|
||||
}
|
||||
khat = which.min(crit)
|
||||
hhat = H[khat]
|
||||
fhhat = fhat[khat, ]
|
||||
plot(
|
||||
x,
|
||||
fhhat,
|
||||
main = paste("Minimum atteint pour h=", signif(hhat, 2)),
|
||||
xlab = 't',
|
||||
ylab = expression(hat(f)[hat(h)]),
|
||||
type = 'l'
|
||||
)
|
||||
points(
|
||||
x,
|
||||
(dnorm(x, mu1, sqrt(sigma12)) +
|
||||
dnorm(x, mu2, sqrt(sigma22)) +
|
||||
dnorm(x, mu3, sqrt(sigma32))) /
|
||||
3,
|
||||
type = 'l',
|
||||
col = 'darkgreen',
|
||||
lwd = 2
|
||||
)
|
||||
```
|
||||
|
||||
```{r}
|
||||
plot(density(precip, bw = 'ucv'))
|
||||
rug(precip)
|
||||
```
|
||||
|
||||
Les données sont bimodales. On décèle deux groupes de villes : un groupe avec des précipitations situées autour de 15 inches avec une grande dispersion et un autre groupe avec des précipitations moyennes situées autours de 40 inches.
|
||||
|
||||
```{r}
|
||||
plot(density(faithful[, 1], bw = 'ucv'))
|
||||
rug(faithful[, 1])
|
||||
```
|
||||
|
||||
On distingue là aussi deux groupes : un groupe de geysers ayant un temps d'éruption autour de 2 mins et un autre 4 mins/4 mins 30. La règle du pouce donne ds estimateurs très différents de la validation croisée et de la méthode de Sheather et Jones.
|
||||
@@ -29,12 +29,15 @@ The projects are organized into two main sections:
|
||||
- `Statistical Learning`
|
||||
|
||||
- `M2`
|
||||
- `Clustering In Practice`
|
||||
- `Data Visualisation`
|
||||
- `Deep Learning`
|
||||
- `Generative AI`
|
||||
- `Linear Models`
|
||||
- `Machine Learning`
|
||||
- `Reinforcement Learning`
|
||||
- `SQL`
|
||||
- `Statistiques Non Paramétrique`
|
||||
- `Unsupervised Learning`
|
||||
- `VBA`
|
||||
|
||||
@@ -57,4 +60,5 @@ The projects are organized into two main sections:
|
||||
- [FactoMineR](https://factominer.free.fr/): An R package focused on multivariate exploratory data analysis (e.g., PCA, MCA, CA).
|
||||
- [ggplot2](https://ggplot2.tidyverse.org): A grammar-based graphics package for creating complex and elegant visualizations in R.
|
||||
- [RShiny](https://shiny.rstudio.com): A web application framework for building interactive web apps directly from R.
|
||||
- [LangChain](https://langchain.com): A framework for developing applications powered by language models.
|
||||
- and my 🧠.
|
||||
|
||||
@@ -3,24 +3,36 @@ name = "studies"
|
||||
version = "0.1.0"
|
||||
description = "A curated collection of mathematics and data science projects developed during my academic journey."
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.12"
|
||||
requires-python = ">= 3.11"
|
||||
dependencies = [
|
||||
"accelerate>=1.12.0",
|
||||
"catboost>=1.2.8",
|
||||
"datasets>=4.4.2",
|
||||
"faiss-cpu>=1.13.2",
|
||||
"imblearn>=0.0",
|
||||
"ipykernel>=6.29.5",
|
||||
"keras>=3.11.3",
|
||||
"langchain>=1.2.0",
|
||||
"langchain-community>=0.4.1",
|
||||
"langchain-huggingface>=1.2.0",
|
||||
"langchain-ollama>=1.0.1",
|
||||
"langchain-text-splitters>=1.1.0",
|
||||
"matplotlib>=3.10.1",
|
||||
"nbformat>=5.10.4",
|
||||
"numpy>=2.2.5",
|
||||
"opencv-python>=4.11.0.86",
|
||||
"pandas>=2.2.3",
|
||||
"openpyxl>=3.1.5",
|
||||
"pandas>=2.2.3",
|
||||
"pandas-stubs>=2.3.2.250926",
|
||||
"plotly>=6.3.0",
|
||||
"polars>=1.37.0",
|
||||
"pypdf>=6.5.0",
|
||||
"scikit-learn>=1.6.1",
|
||||
"scipy>=1.15.2",
|
||||
"seaborn>=0.13.2",
|
||||
"sentence-transformers>=5.2.0",
|
||||
"shap>=0.49.1",
|
||||
"tensorflow>=2.20.0",
|
||||
"tf-keras>=2.20.1",
|
||||
"xgboost>=3.1.2",
|
||||
"yfinance>=0.2.66",
|
||||
]
|
||||
@@ -44,7 +56,7 @@ select = ["ALL"]
|
||||
|
||||
# Désactiver certaines règles
|
||||
ignore = [
|
||||
# "E501", # line too long, géré par le formatter
|
||||
"E501", # line too long, géré par le formatter
|
||||
"E402", # Imports in top of file
|
||||
"T201", # Print
|
||||
"N806",
|
||||
|
||||
Reference in New Issue
Block a user