Prise en main de ggplot2

1 Objectifs du TP

L’objectif de ce TP vise à se familiariser avec le package ggplot2 de R. Il s’agit de faire des manipulations graphiques élémentaires et d’interpréter les résultats de ces visualisations.

Dans un premier temps, vous pouvez suivre l’exemple introductif en répliquant le code fourni. Dans un deuxième temps, il convient de réaliser l’exercice point par point.

2 Prérequis

  • Avoir installer R ici.
  • Avoir installer un IDE, par exemple RStudio ici.
  • Créer un nouveau projet (File, puis New Projet) dans un dossier sur votre ordinateur.
  • Télécharger ici les fichiers nécessaires au TD.

Vous pouvez ensuite écrire vos codes soit :

  • En ouvrant un nouveau script .R ;
  • En ouvrant le ouvrant le rapport Rmarkdown 3-td_ggplot2 - enonce. Certains codes sont partiels et sont à compléter (indication ???). N’oubliez pas de modifier l’option eval = TRUE pour que les calculs puissent être réalisés.

3 Exemple introductif

Pour illustrer cette première partie, nous reprenons l’exemple introductif fourni par @wickham2023 sur le jeu de données penguins du package palmerpenguins. Ce jeu de données s’intèresse des mesures réalisées sur des manchots sur 3 îles de l’archipelle Palmer.

3.1 Données

Dans un premier temps, il faut installer le package et le charger.

# install.packages("palmerpenguins")
library(palmerpenguins)

Ce jeu de données contient 344 observations où chaque ligne correspond à un individu.

paged_table(penguins, options = list(rows.print = 15))

On se concentre plus particulièrement sur les variables suivantes :

  • species : l’espèce de manchot ;
  • flipper_length_mm : la longueur de la nageoire en mm ;
  • body_mass_g : la masse en gramme.

Pour plus détails, voir l’aide ?penguins.

3.2 But de la visualisation

On s’intéresse au lien entre le masse et la taille des nageoires des manchots :

  • ceux dont les nageoires sont les plus longues sont-ils plus lourds que les manchots aux nageoires courtes ?
  • si oui quelle est le type de relation (linéaire, croissante, décroissante, …) ?
  • quels facteurs influencent également cette relation (lieu, l’espèce, … ) ?

On cherche à recréer la figure suivante.

3.3 Création de la figure étape par étape

Etape 1 : Scatterplot

On commence par créer un scatterplot pour examiner la relation entre la masse et la taille de la nageoire.

ggplot(
  data = penguins,
  mapping = aes(x = flipper_length_mm, y = body_mass_g)) +
  geom_point()

Cette figure fait clairement apparaître une relation croissante et a priori linéaire entre les deux variables.

Un message d’erreur apparaît pour deux individus avec des données manquantes. Ils sont automatiquement exclus.

Etape 2 : Ajout d’élements esthétiques

On cherche à présent exhiber le rôle de l’espèce à partir d’une couleur. Trois espèces sont présents, ainsi l’ajout de 3 couleurs à la figure ne devrait pas surcharger le graphique.

ggplot(
  data = penguins,
  mapping = aes(x = flipper_length_mm, y = body_mass_g, color = species)) +
  geom_point()

Compte tenu du nombre important de points, nous pouvons renforcer les différences par espèce en ajoutant une variation de forme aux points.

ggplot(
  data = penguins,
  mapping = aes(x = flipper_length_mm, y = body_mass_g)) +
  geom_point(mapping = aes(
    color = species,
    shape = species))

Etape 3 : Ajout d’une géométrie

Voyons à présent comment interpréter la nature de la relation entre masse et longueur de la nageoire. Pour ce faire, nous essayons d’ajout des courbes de tendance. Nous commençons par une tendance linéaire.

ggplot(
  data = penguins,
  mapping = aes(x = flipper_length_mm, y = body_mass_g)) +
  geom_point(mapping = aes(
    color = species,
    shape = species)) +
    geom_smooth(method = "lm")

La même figure peut être générée par espèce en déplaçant l’argument color = species.

ggplot(
  data = penguins,
  mapping = aes(x = flipper_length_mm, y = body_mass_g, color = species)) +
  geom_point(mapping = aes(
    shape = species)) +
    geom_smooth(method = "lm")

Les pentes entre les espèces ne sont pas si éloignées. Nous décidons que conserver une relation commune pour toutes espèces. Pour tester si la nature linéaire de la relation est a priori une bonne hypothèse, nous considérons un lissage non-paramétrique.

ggplot(
  data = penguins,
  mapping = aes(x = flipper_length_mm, y = body_mass_g)) +
  geom_point(mapping = aes(
    color = species,
    shape = species)) +
    geom_smooth(method = "loess")

L’ajout d’un lissage non-paramétrique permet d’affiner l’adéquation aux données, mais sans pour autant clairement remettre en cause la tendance linéaire qui sera donc conservée.

Etape 4 : Ajout des titres et changement de thème

Afin de finaliser la figure, nous ajouter :

  • un titre ;
  • un sous-titre ;
  • des titres aux axes ;
  • un titre à la légende.

Ces informations sont ajoutées avec labs().

De plus, nous modifions le thème avec la commande theme_bw().

ggplot(
  data = penguins,
  mapping = aes(x = flipper_length_mm, y = body_mass_g)
) +
  geom_point(aes(color = species, shape = species)) +
  geom_smooth(method = "lm") +
  labs(
    title = "Masse et taille de la nageoire",
    subtitle = "Manchots d'Adelie, a
    jugulaire et de Gentoo",
    x = "Longueur de la nageoire (mm)", y = "Masse (g)",
    color = "Espece", shape = "Espece"
  ) +
  scale_color_colorblind() +
  theme_bw()


4 Exercice

4.1 Données

Nous travaillons avec les jeux de données FreMTPL2freq et FreMTPL2sev du package Casdatasets. Ces données ont été préalablement pré-formatées et regroupées.

Ce jeux de données regroupent les caractéristiques de 677 991 polices de responsabilité civile automobile, observées principalement sur une année. Dans les données regroupées, on dispose des numéros de sinistre par police, des montants de sinistre correspondants, des caractéristiques du risque et du nombre de sinistres.

On présente ci-dessous un aperçu des données.

# Folds
fold <- getwd()

# Load data
load(paste0(fold, "/data/datafreMPTL.RData"))
paged_table(dat, options = list(rows.print = 15))

Le tableau suivant présente une définition des variables.

kableExtra::kable(
  data.frame(
    Variable = c("IDpol", "Exposure", "VehPower", "VehAge",
                 "DrivAge", "BonusMalus", "VehBrand", "VehGas",
                 "Area", "Density", "Region", 
                 "ClaimTotal", "ClaimNb"),
    Description = c(
      "Identifiant de la police",
      "Exposition au risque",
      "Puissance du véhicule",
      "Age du véhicule en année",
      "Age du conducteur en année",
      "Coefficient de bonus-malus",
      "Marque du véhicule",
      "Carburant du véhicule",
      "Catégorie correspondant à la densité de la zone assurée",
      "Densité de population",
      "Region (selon la classication 1970-2015)",
      "Montant total des sinistres",
      "Nombre de sinistres sur la période"
    ), 
    Type = c(
      rep("Reel", 2),
      rep("Entier", 4),
      rep("Cat", 3),
      "Entier",
      "Cat",
      rep("Reel", 2)
    )
  ),
  booktabs=TRUE)
Variable Description Type
IDpol Identifiant de la police Reel
Exposure Exposition au risque Reel
VehPower Puissance du véhicule Entier
VehAge Age du véhicule en année Entier
DrivAge Age du conducteur en année Entier
BonusMalus Coefficient de bonus-malus Entier
VehBrand Marque du véhicule Cat
VehGas Carburant du véhicule Cat
Area Catégorie correspondant à la densité de la zone assurée Cat
Density Densité de population Entier
Region Region (selon la classication 1970-2015) Cat
ClaimTotal Montant total des sinistres Reel
ClaimNb Nombre de sinistres sur la période Reel
# Short summary
str(dat)
## 'data.frame':    135601 obs. of  13 variables:
##  $ IDpol     : num  5089457 2215917 2202141 1077071 3044896 ...
##  $ Exposure  : num  0.21 0.25 1 0.07 0.09 0.08 1 1 0.49 0.8 ...
##  $ VehPower  : int  7 5 7 7 7 7 13 4 7 4 ...
##  $ VehAge    : int  10 2 6 4 7 4 3 14 8 2 ...
##  $ DrivAge   : int  36 40 79 42 45 53 53 32 31 42 ...
##  $ BonusMalus: int  50 85 54 57 100 53 50 60 71 50 ...
##  $ VehBrand  : Factor w/ 11 levels "B1","B2","B3",..: 1 3 10 2 7 2 1 2 3 9 ...
##  $ VehGas    : Factor w/ 2 levels "Diesel","Regular": 2 2 1 1 1 1 2 2 2 2 ...
##  $ Area      : Factor w/ 6 levels "A","B","C","D",..: 5 5 3 5 6 3 5 4 5 2 ...
##  $ Density   : int  8 8 6 8 10 5 8 7 9 4 ...
##  $ Region    : Factor w/ 21 levels "Alsace","Aquitaine",..: 21 7 7 21 12 20 7 16 21 7 ...
##  $ ClaimTotal: num  0 0 0 0 0 0 0 0 0 0 ...
##  $ ClaimNb   : num  0 0 0 0 0 0 0 0 0 0 ...

Pour plus de détails, consulter l’aide ?CASdatasets::freMTPL2freq.


4.2 But de la visualisation

Nous effectuons une première analyse descriptive de données et cherchons à étudier la relation entre :

  • la fréquence, calculée avec les variables ClaimNb et Exposure (période d’exposition en année).
  • les variables Area et DrivAge.

Le but de la visualisation est de fait ressortir les liens entre la fréquence et ces deux variables.

Etape 1 : Visualisation de la fréquence et de l’exposition

A partir des données dat :

  • afficher les statistiques descriptives du nombre de sinistres ClaimNb et de la variable Exposure ;
  • afficher des histogrammes pour visualiser leur distribution ;
  • afficher les figures côte a côte avec la fonction plot_grid().

Essayer de choisir un thème de couleur et un écartement des barres de l’histogramme facilitant sa lisibilité.

On pourra développer une fonction qui utilise geom_histogram() sous la package ggplot2.

# Descriptive statistics
summary(dat$ClaimNb)
##    Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
## 0.00000 0.00000 0.00000 0.03919 0.00000 3.00000
summary(dat$Exposure)
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## 0.002732 0.170000 0.490000 0.527782 0.990000 1.000000
p1 <- ggplot(dat) +
  geom_histogram(aes(x = ClaimNb), binwidth = 1, fill = "lightblue", color = "black") +
  labs(title = "Distribution du nombre de sinistres", x = "Nombre de sinistres", y = "Effectif") +
  theme_minimal()

p2 <- ggplot(dat) +
  geom_histogram(aes(x = Exposure), binwidth = 0.05, fill = "lightblue", color = "black") +
  labs(title = "Distribution du nombre de sinistres", x = "Nombre de sinistres", y = "Effectif") +
  theme_minimal()

plot_grid(p1, p2, ncol = 2)

Etape 2 : Calculer la fréquence

Construire un tableau présentant l’exposition cumulée et le nombre d’observations avec 0 sinistre, 1 sinistre, …

freq <- dat %>% 
  summarize(total_exposure = sum(Exposure),
            total_claims = sum(ClaimNb),
            frequency = total_claims / total_exposure)

freq
##   total_exposure total_claims  frequency
## 1       71567.74         5314 0.07425133

Ce calcul de fréquence sera ensuite utile pour l’affichage des résultats.

Etape 3 : Calculer l’exposition et la fréquence par variable

Pour la variable DrivAge, présenter :

  1. un histogramme de l’exposition en fonction de cette variable.
  2. un histogramme de la fréquence moyenne de sinistres en fonction de cette variable.

Remplacer ensuite le second histogramme par un scatter plot avec une courbe de tendance. Est-ce plus clair ?

Indice

On pourra développer une fonction qui utilise geom_bar() sous la package ggplot2.

# On regroupe selon les modalites de la DrivAge 
# l'exposition, le nombre de sinistres et la frequence 
df_plot <- dat %>%
    group_by(DrivAge) %>% 
    summarize(exp = Exposure,
              nb_claims = ClaimNb,
              freq = nb_claims / exp)

# Histogramme exposition
ggplot(df_plot) +
  geom_bar(aes(x = DrivAge, y = exp), stat = "identity", fill = "lightblue", color = "blue") +
  labs(title = "Exposition par âge du conducteur", x = "Âge du conducteur", y = "Exposition") +
  theme_minimal()

# Histogramme frequence
ggplot(df_plot) +
  geom_bar(aes(x = DrivAge, y = freq), stat = "identity", fill = "lightblue", color = "blue") +
  labs(title = "Fréquence par âge du conducteur", x = "Âge du conducteur", y = "Fréquence") +
  theme_minimal()
# Scatter plot frequence

# A compléter

Etape 4 : Examiner l’intéraction avec une autre variable

A partir du scatter plot réalisé à l’étape précédente, distinguer les évolutions de fréquence en fonction de DrivAge et de BonusMalus.

Ce graphique vous paraît-il transmettre un message clair ? Proposez des améliorations en modifiant les variables DrivAge et BonusMalus.

# On regroupe selon les modalites de la DrivAge et de Area
# l'exposition, le nombre de sinistres et la frequence 

# A compléter

On propose 4 ajustements :

  • Exclure les âges extrêmes au-delà de 85 ans pour lesquels l’exposition est très faible.
  • Faire des classes d’âges.
  • Limiter le Bonus-Malus à 125.
  • Faire des classes de Bonus-Malus.
# Classes d'âges pour Bonus-Malus
lim_classes <- c(50, 75, 100, 125, Inf)

# Exclusion des donnees "extremes" et faire les regroupement
df_plot <- dat %>%
  filter(DrivAge <= 85, BonusMalus <= 125) %>%
  # regroupement en classes d'ages de 5 ans
  mutate(DrivAge = ceiling(pmin(DrivAge, 85) / 5) * 5) %>%
  mutate(BonusMalus = cut(BonusMalus, 
                          breaks = lim_classes, include.lowest = TRUE))
  
# On regroupe selon les modalites de la DrivAge et de Area
# l'exposition, le nombre de sinistres et la frequence 

# A compléter

Conclure

Comparer à présenter comment l’exposition se répartie entre âge et bonus-malus.

# A compléter

Bonus - Analyse des couples

En traitant toutes les variables comme des variables catégorielles, analyser graphiquement comment évolue la fréquence de sinistres selon les couples de variables.

Compléter pour cela la fonction suivante et appliquer la à différents couples.

# Fonction d'analyse bivariée
# df : nom du data.frame
# var1 : nom de la variable explicative 1
# var2 : nom de la variable explicative 2
plot_pairwise_disc <- function(df, var1, var2)
{
 df <- rename(df, "varx" = all_of(var1), "vary" = all_of(var2))
 
#  replace variable vname by the binning variable
  if(is.numeric(df$varx))
  {
    df <- df %>%
      mutate(varx = ntile(varx, 5))
  }
 
 if(is.numeric(df$vary))
  {
    df <- df %>%
      mutate(vary = ntile(vary, 5),
             vary = factor(vary))
  }
 
 df %>% 
   group_by(??) %>%
   summarize(exp = ??,
             nb_claims = ??,
             freq = ??,
             .groups = "drop") %>%
   ggplot(aes(x = ??, 
              y = ??,
              colour = ??,
              group = vary),
          alpha = 0.3) + 
   geom_point() + geom_line() + theme_bw() +
   labs(x = var1,  y = "Frequence", colour = var2)
}

Informations de session

sessionInfo()
## R version 4.5.1 (2025-06-13)
## Platform: aarch64-apple-darwin20
## Running under: macOS Tahoe 26.0.1
## 
## Matrix products: default
## BLAS:   /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRblas.0.dylib 
## LAPACK: /Library/Frameworks/R.framework/Versions/4.5-arm64/Resources/lib/libRlapack.dylib;  LAPACK version 3.12.1
## 
## locale:
## [1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
## 
## time zone: Europe/Paris
## tzcode source: internal
## 
## attached base packages:
## [1] grid      stats     graphics  grDevices utils     datasets  methods  
## [8] base     
## 
## other attached packages:
##  [1] palmerpenguins_0.1.1 kableExtra_1.4.0     cowplot_1.2.0       
##  [4] ggthemes_5.1.0       rmarkdown_2.30       tidyr_1.3.1         
##  [7] dplyr_1.1.4          plotly_4.11.0        RColorBrewer_1.1-3  
## [10] formattable_0.2.1    scales_1.4.0         locfit_1.5-9.12     
## [13] gridExtra_2.3        ggplot2_4.0.0        lattice_0.22-7      
## 
## loaded via a namespace (and not attached):
##  [1] sass_0.4.10       generics_0.1.4    xml2_1.4.0        stringi_1.8.7    
##  [5] digest_0.6.37     magrittr_2.0.4    evaluate_1.0.5    bookdown_0.45    
##  [9] fastmap_1.2.0     Matrix_1.7-4      jsonlite_2.0.0    mgcv_1.9-3       
## [13] httr_1.4.7        purrr_1.1.0       viridisLite_0.4.2 lazyeval_0.2.2   
## [17] textshaping_1.0.3 jquerylib_0.1.4   cli_3.6.5         rlang_1.1.6      
## [21] splines_4.5.1     withr_3.0.2       cachem_1.1.0      yaml_2.3.10      
## [25] tools_4.5.1       vctrs_0.6.5       R6_2.6.1          lifecycle_1.0.4  
## [29] stringr_1.5.2     htmlwidgets_1.6.4 pkgconfig_2.0.3   pillar_1.11.1    
## [33] bslib_0.9.0       gtable_0.3.6      data.table_1.17.8 glue_1.8.0       
## [37] rmdformats_1.0.4  systemfonts_1.3.1 xfun_0.53         tibble_3.3.0     
## [41] tidyselect_1.2.1  rstudioapi_0.17.1 knitr_1.50        farver_2.1.2     
## [45] nlme_3.1-168      htmltools_0.5.8.1 labeling_0.4.3    svglite_2.2.1    
## [49] compiler_4.5.1    S7_0.2.0

5 Références