Le but de cette séance est de vous familiariser avec quelques outils modernes que les économistes doivent maîtriser : pour celles et ceux qui auraient besoin d’un rafraichissement sur les structures de données de base (vecteurs, matrices, dataframes) voir le cours de Laurent Rouvière
On utilisera le logiciel R, et on va :
Apprendre un workflow utile pour tous vos projets
Importer et nettoyer des données
Faire des statistiques descriptives (plus ou moins compliquées)
Un peu de cartographie
De façon générale, l’intelligence artificielle (ChatGPT, Claude etc) sont hyper performants : n’hésitez pas vous en aider, à apprendre avec
R
Langage de programmation libre et open source interprété, datant des années 1990, développé par R. Ihaka et R. Gentleman
Destiné à l’analyse statistique et à la visualisation de données
Langage populaire parmi les statisticiens, chercheurs et data-scientists
Utilise des packages c’est à dire des collections de fonctions qui permettent de faire une grande variété de choses
Pour s’en servir: on installe R, puis un Integrated Development Environment (IDE) comme RStudio par exemple
Premièrement, on crée un projet. Ils permettent de :
Regrouper tout le code au sein d’une architecture reproductible
Créer des environnements afin d’avoir un code reproductible, gérant les dépendances entre projets et dans le temps
Partager rapidement et facilement le code entre différents utilisateurs
Suivre dans le temps via le contrôle de version, notamment en utilisant Git (ce sera pour une autre fois)
Il faut donc créer un nouveau projet:
Créez un dossier facilement identifiable sur votre bureau, nommé Projets
Ouvrez R Studio, et cliquez sur File > New Project
Puis New Directory > New Project puis nommez le analyse_economie_ecologique ou ce que vous préférez comme titre de projet
Vous avez créé un projet!
Reproducibilité
On parle de reproducibilité à plusieurs niveaux dans l’analyse scientifique :
Reproducibilité computationelle : pouvoir répliquer exactement les résultats
Reproducibilité empirique : refaire les tests d’une autre manière
Reproducibilité conceptuelle : retrouver les mêmes résultats avec de nouvelles données
Celle ci est en crise : beaucoup de résultats en science ne sont pas réplicables
Pas simplement la recherche académique, toute production visant à être étayée et partagée doit viser la reproducibilité
Comment rendre la recherche reproductible?
Une architecture claire et organisée par projets:
Des fichiers séparés pour les données brutes, traitées et les scripts
Contrôle de version : on ne verra pas ça ensemble
Management des dépendances :
Créer des environnements pour vérouiller les bibliothèques
Documenter toutes les dépendances
Partager les fichiers .lock avec les collaborateurs
Pipeline de données :
Ne jamais modifier les fichiers de données brutes
Créer des scripts séparés pour le nettoyage et l’analyse
Construire des fonctions modulaires et réutilisables - c’est un art
Documenter la transformation des données
Documentation :
Maintenez des README clairs et exhaustifs
Commentez le code
Générez des rapports automatisés avec les fichiers markdown
Automatisation :
Utilisez des chemins relatifs : le package here()
Créez des rapports reproductibles
Architecture
Le but est d’avoir une architecture claire, pour que vos résultats puissent être reproduits, avec :
Les scripts : c’est le texte, le code qui sera exécuté
Les données : brutes ou nettoyées, ou additionelles de sources extérieures
Les résultats: figures, tableaux etc
La documentation : les rapports, ou encore un fichier qui s’appelle le Readme.md, qui détaille le projet, les instructions d’utilisation etc.
Pour commencer, vous allez aller sur la page Github du cours, pour télécharger les scripts qui vont nous permettre de créer l’architecture, et mener les analyses.
Prenez simplement les fichiers en .md et enregistrez les dans scripts/
Téléchargez également le fichier baie_somme_2025.xlsx
Dans la console de R, vous allez d’abord créer le dossier scripts, afin d’y enregistrer ces scripts que vous aurez téléchargé :
On va utiliser le package here qui facilite le traitement des adresses des fichiers au sein de votre ordinateur
Puis on va créer les dossiers
if(!("here"%in%installed.packages())){ # Vérifie si le package est déja installéinstall.packages("here") # L'installe si besoin}library(here) # Charge la bibliothèqueif(!dir.exists(here("scripts"))){ # Vérifie si le chemin existe déjadir.create(here("scripts"))}print(paste(here("scripts"), " est créé"))
[1] "C:/Users/jean/OneDrive/Desktop/Teaching/AgroParisTech/Master EEET/M1/Economie écologique/intro_data_analysis/scripts est créé"
Utilisez le script 01_Setup.md
On utilise un format spécifique, le markdown, qui mélange le texte, le code Latex si besoin, ainsi que l’analyse de données dans des blocs de code
Vous pouvez l’exporter en cliquant sur Render et avoir un document sous format hmtl, ou pdf que vous pouvez partager
On veut créer l’architecture suivante
Projet/
├── R/ # Contient tous les scripts R du projet
│ ├── 01_utilities.rmd # Script d'importation des données
│ ├── 02_cleaning.rmd # Script de nettoyage des données
│ ├── 03_analysis.rmd # Script d'analyse des données
│ └── 04_visualisation.rmd # Script pour les graphiques et visualisations
├── data/ # Contient les fichiers de données brutes et traitées
│ ├── raw/ # Données brutes
│ │ └── data1.csv # Exemple de fichier de données brutes
│ │ └── README.txt # Texte explicatif des données
│ ├── processed/ # Données nettoyées et traitées
│ │ └── data_clean.csv
│ └── external/ # Données externes ou partagées (ex. bases de données publiques)
│ └── source.csv
├── results/ # Résultats des analyses (sorties, graphiques, tableaux)
│ ├── figures/ # Graphiques générés (png, pdf, etc.)
│ │ └── plot1.png
│ └── tables/ # Tables générées (ex. CSV ou LaTeX)
│ └── table1.csv
├── docs/ # Documentation du projet (rapports, instructions, etc.)
│ ├── report.pdf # Rapport principal du projet
│ └── README.md # Explication du projet, instructions d'utilisation
├── .gitignore # Fichier pour ignorer certains fichiers dans un dépôt Git (si applicable)
├── renv.lock # Fichier verrouillant les versions des packages (géré par renv)
├── renv/ # Dossier contenant l'environnement isolé géré par renv
└── README.md # Fichier d'accueil du projet pour décrire brièvement de quoi il s'agit
On utilise le code suivant :
if(!dir.exists(here("data"))){ # Vérifie si le chemin existe déjadir.create(here("data"))dir.create(here("data", "raw")) # Crée des dossiers à l'intérieur de \datadir.create(here("data", "processed"))dir.create(here("data", "external"))dir.create(here("results"))dir.create(here("results", "figure"))dir.create(here("results", "tables"))dir.create(here("docs"))}print("Architecture à jour")
[1] "Architecture à jour"
Packages et environnements
Ensuite, on va télécharger et installer des packages utiles :
Le package rmarkdown et knitr pour effectivement avoir des documents en markdown qui soient pris en compte par R
La collection de package tidyverse qui contient entre autres :
tidyr et dplyr : sont essentiels pour la manipulation de données de base ainsi que le recodage, la création de nouvelles variables, le filtrage etc
gpplot2 et ggsci: le package de référence pour la data visualisation et des themes() (styles) de référence dans la littérature scientifique
readxl : pour lire des données en format .xlsx
magrittr: un package qui permet d’utiliser l’opérateur pipe%>%, qui permet de connecter les opérations entre elles, simplifiant le processus de \(g(h(x))\) à x %>% h() %>% g()
stringr et stringdist: pour traiter les données textuelles
renv : on va créer un environnement, permettant de travailler avec les bons outils
Des packages dont je vous expliquerai ensuite la nécessité pour les analyses statistiques et cartographiques que l’on va mener comme factoextra, factoMineR, sf et sp
Environnement
Clé pour rendre le travail reproductible :
Les versions des packages sont stockées
Quand l’environnement est partagé, tout le monde travail avec les mêmes packages, permettant la reproducibilité entre personnes et dans le temps
Chaque projet peut avoir des versions de package différentes sans encombre
# I. Installer le package renv s'il ne l'est pas déja dans l'environnement globalif(!("renv"%in%installed.packages())){install.packages("renv")}library(renv)# II. Activation ou création de l'environnementif(!dir.exists(here("renv"))){ # si l'environnement n'existe pas déja (ce qui est mon cas) renv::init() # sinon, on l'initialise}else{#renv::activate()}
NULL
# III. Installer chaque packagepackages_to_install =c("rmarkdown", "knitr", "tidyverse", "readxl", "openxlsx","magrittr", "factoextra", "FactoMineR", "sf", "sp", "here", "ggsci")install_and_load <-function(packages){for (pkg in packages){ if(!(pkg %in%installed.packages())){# Pour chaque package de la liste renv::install(pkg) # installe le cas échéant }library(pkg, character.only =TRUE) # Puis le charge }}install_and_load(packages_to_install) renv::snapshot()
- The lockfile is already up to date.
Data cleaning
Chargement des données
On va maintenant pouvoir charger les données
Enregistrez le fichier baie_somme_2025.xlsx dans data\raw
# A tibble: 6 × 16
Horodateur `Lieu de l'entretien` `Jour de l'entretien`
<dttm> <chr> <dttm>
1 2025-01-13 14:38:18 Hourdel 2025-01-13 00:00:00
2 2025-01-13 14:48:03 Hourdel 2025-01-13 00:00:00
3 2025-01-13 15:06:42 <NA> NA
4 2025-01-13 15:13:54 <NA> NA
5 2025-01-13 15:26:11 <NA> NA
6 2025-01-13 15:34:16 Pointe 2025-01-13 00:00:00
# ℹ 13 more variables: `Heure de l'entretien` <dttm>,
# `Caractéristique de la personne interviewée` <chr>,
# `Age approximatif` <chr>,
# `Lieu de résidence de la personne interviewée` <chr>,
# `Activité de loisir de la personne interrogée :` <chr>,
# `Activité professionnelle de la personne interrogée :` <chr>,
# `Q1 : Connaissez-vous le nom des espèces de phoques qui sont présentes dans la baie de Somme ? (Si oui préciser les espèces, Si ne sait pas, donner les deux noms des espèces (phoque gris et phoque veau-marin))` <chr>, …
Diagnostic
On va regarder déja la structure des données et le taux de complétude des données
On va ensuite regarder comment standardiser les noms de variables, les réponses aux questions (souvent imparfaites), assigner le bon format etc.
chr [1:218] "Il faut laisser la nature évoluer librement, La surfréquentation peut générer des dérangements pour la nature" ...
[1] "Horodateur"
[2] "Lieu de l'entretien"
[3] "Jour de l'entretien"
[4] "Heure de l'entretien"
[5] "Caractéristique de la personne interviewée"
[6] "Age approximatif"
[7] "Lieu de résidence de la personne interviewée"
[8] "Activité de loisir de la personne interrogée :"
[9] "Activité professionnelle de la personne interrogée :"
[10] "Q1 : Connaissez-vous le nom des espèces de phoques qui sont présentes dans la baie de Somme ? (Si oui préciser les espèces, Si ne sait pas, donner les deux noms des espèces (phoque gris et phoque veau-marin))"
[11] "Q2 : Savez-vous combien il y a de phoque gris et de phoque veau-marin dans la baie de Somme ?"
[12] "Q3 : Avez-vous déjà vu des phoques dans la baie"
[13] "Q4 : Est-ce qu’ils vous dérangent dans vos activités de loisir ou professionnelle ? Pourquoi ?"
[14] "Q5 : A quoi assimilez vous le phoque (2 choix max) ?"
[15] "Q6 : Pouvez-vous me dire si vous êtes d’accord avec les affirmations suivantes (cocher lorsque accord) ?"
[16] "Avez-vous des suggestions concernant cette question des phoques dans la Baie de Somme ?"
On a plusieurs informations sur les formats :
POSIT: données temporelles formatées en date
chr : données “character” i.e. données textuelles, sans notion d’ordre etc
Pour certaines variables on va vouloir passer de données textuelles à des données catégorielles, c’est à dire des données textuelles avec différents niveaux
Pour certaines questions à choix multiple, les réponses sont collées entre elles :
Il va falloir réfléchir à comment les extraire
Et à comment les exploiter
Les noms de colonnes ne sont pas pratiques à utiliser : on va les renommer
[1] "Hourdel" NA "Pointe"
[4] "Saint Valéry sur Somme" "Le Crotoy" "Amiens"
[7] "Le Tréport" "Trépot" "Le Treport"
[10] "Tréport" "Treport" "Le treport"
[13] "Le tréport" "treport" "Abbeville"
[16] "Abeville" "L'Oise" "Cayeux-sur-mer"
[19] "Cayeux" "POINTE DU HOURDEL" "Berck"
[22] "Berck sur mer" "Berck-sur-mer"
Pas mal de choses qui se ressemblent mais ne sont pas pareilles : Saint Valéry, St Valery, Saint Valery sur Somme etc
On va essayer de se débarasser de la ponctuation et des lettres majuscules
Corriger les fautes d’orthographe
Se débarasser des imprécisions ou choses non nécessaires (depuis 2 ans, (Somme)) etc
Tout en conservant toutes les données : on ne remplace jamais la donnée initiale!!
On le fait à la main ici, mais des approches plus structurées existent:
Distance entre les groupes nominaux : combien faut il changer de caractères (stringdist)
Matching avec des bases géographiques : on le fera plus tard pour les coordonnées géographiques
Q1 : Complétez pour St Valery
remplacement_lieux =function(x_){#' Standardisation des noms de lieux#'#' Cette fonction normalise les noms de lieux en les mettant en minuscules, #' en supprimant les accents et les articles, puis en les remplaçant par des #' noms standardisés selon une liste prédéfinie.#'#' @param x_ Une chaîne de caractères représentant un nom de lieu (potentiellement mal orthographié ou mal formaté).#' @return Une chaîne de caractères avec le nom de lieu standardisé. Si aucun correspondance n'est trouvée, la fonction retourne l'entrée originale. x =str_to_lower(x_) # On met tout en minuscule x = x %>%gsub("\\ble\\b", "", .)%>%# On enlève "le" à l'aide d'une expression régulière (regex)gsub("é", "e", .) # On enlève également les accents aigus et graves# Après inspection, on fait ce qu'on peut pour remplacer ensemble les zones géographiquesif(x %in%c(' treport', 'trepot', 'treport', "treport depuis 2 ans")){return('Le Tréport') }elseif(x %in%c('abbeville', 'abeville', "abbevilel", "abbville")){return('Abbeville') }elseif(x %in%c(' crotoy', 'le crotoy', 'crotoy')){return('Le Crotoy') }elseif(x %in%c("berck-sur-mer", "berck sur mer", "berck")){return('Berck-sur-mer') }elseif(x %in%c('cayeux-sur-mer', 'cayeux', "cayeux sur mer")){return('Cayeux-sur-mer') }elseif(x %in%c('saint valery sur somme',"saimt valery","saint-valery-sur-somme","saint valéry", "saint valery", "st valery", "vers saint valery" ,"saint-valerie-sur-somme")){return('Saint-Valery-sur-Somme') }elseif(x %in%c('hourdel', 'pointe', 'pointe du hourdel')){return("Pointe du Hourdel") }elseif(x %in%c("feuquieres en vimeu", "feuquieres - en - vimeu" )){return('Feuquières-en-Vimeu') }elseif(x %in%c('noyelles sur mer', 'noyelle sur mer')){return('Noyelles-sur-mer') }elseif(x %in%c("amiens", "amiens / berck" )){return("Amiens") }elseif(x %in%c('lancheres', 'lanchere', 'lancher', "lanchéres")){return('Lanchéres') }elseif(x %in%c('arlay a 5km du crotoy')){return('Arlay') }elseif(x %in%c("fauquembergues (résidence secondaire)")){return('Fauquembergues') }elseif(x %in%c("villers cotterets(02600)" )){return('Villers Cotterets') }elseif( x %in%c("franciere (somme)" )){return('Francières') }elseif( x %in%c("hauts de seine(92)")){return('Hauts de Seine') }elseif( x %in%c("Saint-Riquier", "Saint-riquier", "saint-riquier")){return('Saint-Riquier') }elseif( x %in%c("pendé", "pende")){return('Pendé') }elseif( x %in%c("arras")){return('Arras') }elseif( x %in%c("dunkersue")){return('Dunkerque') }else{return(x_) }}
data_ = data_ %>%mutate(clean_lieu =sapply(lieu, remplacement_lieux)) # On applique la fonction remplacement à chaque lieu# et sapply renvoie un vecteur qui est assigné à # une nouvelle variableunique(data_$clean_lieux)
NULL
Q2 : enregistrez la nouvelle data avec dplyr, sapply
On a classé la population par groupes d’âge. L’usage de variables factor permet d’avoir des variables catégorielles ordonnées, que l’on peut représenter sur un axe ordonné en précisant les niveaux (levels)
Q3: transformez l’âge en variable facteur
Si vous ne savez pas comment vous y prendre, utilisez ?nom_de_la_fonction pour trouver de l’aide
Variables continues tronquées : population des phoques
Variable continue : on va surement devoir discretiser
Qui est limitée à 0
Avec beaucoup de non réponse
Challenges techniques :
Transformer la variable chr en variable int
On va utiliser la population totale
Q4 : Tabulez la population des phoques!
100 - 200 100.0
1 5
1000.0 120.0
4 1
1300.0 1500-3000
1 1
20.0 200-300
1 1
200 gris et 400 veau marin 200.0
1 2
2000.0 20000.0
2 1
300.0 3000.0
4 1
400.0 450.0
3 2
50 000- 100 000 500.0
1 5
50000.0 600.0
1 2
600/700 800 environ
1 1
800.0 Bcp
1 2
Beaucoup Beaucoup trop
1 1
Entre 500 et 800 Nln
1 1
Nn No
2 1
Nô non
1 1
Non Non - 50
112 1
Non (100) Non (20)
2 1
Non (200) Non (2000)
2 1
Non (30) Non (300)
1 1
Non (450) Non (500)
1 2
Non (650) Non (800)
1 1
Non (8000) Non 100
1 1
Non 1000 Non beaucoup
1 1
Oui Oui - 100
16 1
Oui - 100000 Oui - 200
1 1
Oui - 300-400 Oui - 50
1 1
Oui (3000) Oui (800)
1 1
Oui 1000 Oui(1000)
1 1
OUI, 500 et 200 Pas du tout (beaucoup)
1 1
Pas mal Plus de 1500
1 1
Quelques centaines Quelques milliers
1 1
Quelques uns, 25 Une centaine
1 1
On a beaucoup de réponses mixtes, il faudrait extraire les données chiffrées, les ordres de grandeur, et les réponses comme Non et Oui
remplacement_phoques_1 =function(x_){#' Cette fonction donne une indication sur la connaissance (oui ou non) de la population de phoque#' @param x_ une chaine de caractèresreturn(grepl("non", str_to_lower(x_)))}remplacement_phoques_population =function(x_, option ="max"){#' Extraction et classification des nombres liés aux populations de phoques#'#' Cette fonction analyse une chaîne de caractères pour extraire des nombres #' (représentant des populations de phoques) et les classifier en différentes catégories #' (`Beaucoup`, `Centaines`, `Milliers`, `Quelques uns`, etc.).#'#' @param x_ Une chaîne de caractères contenant une estimation de population (peut inclure des chiffres, des mots ou des expressions variées).#' @param option Une option définissant si l'on souhaite récupérer la valeur minimale ou maximale #' dans le cas de plages numériques (`"min"` ou `"max"`). Par défaut `"max"`.#' @return Une valeur numérique si un chiffre est détecté, ou une catégorie (`"Beaucoup"`, `"Centaines"`, `"Milliers"`, `"Quelques uns"`, `"Non"`, etc.).# Approche par regex : potential_number = x_ %>%gsub(" ", "", .)%>%str_extract_all(.,"\\d+\\.?\\d*")%>%unlist()%>%as.numeric()# On repère les chiffres : # \\d+ repère un chiffre ou plusieurs# \\.? repère un point décimal# \\d* prent les 0# Ensuite on vérifie s'il y a des "-" ou des "/" tiret_barre =grepl("[-/]", x_)# Message d'erreur si l'option n'est pas bien spécifiée if(!(str_to_lower(option) %in%c("min", "max"))){stop("Error : option is ill picked") }# Enfin, on prend les "beaucoup" en compte : if(x_ %in%c(startsWith(str_to_lower(x_), "beauco"), 'Bcp', 'Non beaucoup', 'Pas du tout (beaucoup)')){return("Beaucoup") }elseif(is_empty(potential_number)){if(startsWith(str_to_lower(x_), 'oui')){return('Oui') }elseif(grepl("centai", str_to_lower(x_))){return('Centaines') }elseif(grepl('milli', str_to_lower(x_))){return('Milliers') }elseif(grepl('un', str_to_lower(x_))){return('Quelques uns') }else{return('Non') } }else{if(tiret_barre){if(length(potential_number)>1){if(option=='min'){return(min(potential_number)) }elseif(option=="max"){return(max(potential_number)) } }else{return(as.numeric(potential_number)) } }else{return(sum(as.numeric(potential_number))) } }}
< table of extent 0 >
data_ = data_ %>%mutate(clean_pop_yes_no =sapply(q2_pop_phoques, remplacement_phoques_1),clean_pop_max =sapply(q2_pop_phoques, remplacement_phoques_population))# On récupère également la distinction entre chiffrée et ordre de grandeurchiffre_ou_non = data_%>%select(clean_pop_max)%>%pull()%>%as.numeric()data_ = data_ %>%mutate(# Créer la variable numérique en convertissant les valeurs en numériquechiffre_num =as.numeric(clean_pop_max),# Remplacer les valeurs non numériques par NAclean_pop_phoque_chiffre =if_else(is.na(chiffre_num), NA_real_, chiffre_num),# Créer la variable non numérique pour les réponses textuelles clean_pop_phoque_non_chiffrée =if_else(is.na(clean_pop_phoque_chiffre), clean_pop_max, NA_character_) )
Variables catégorielles ordinales : professions
t(table(data_$profession))# Pour voir les fréquences
Agence immobilier Agent public Agriculteur (à préciser) Aide à domicile
[1,] 1 20 3 1
Aide soignante Archéologue Assistante à domicile Cadre Cadre supérieur
[1,] 1 1 1 1 3
Chômage Collégien Commerçant (préciser) Cuisine
[1,] 1 1 13 1
Dessinateur en bureau d'étude Élève Élèves Employé Employee medical
[1,] 1 1 2 26 1
Enseignante Entrepreneur indépendant Entreprise/tourisme (à préciser)
[1,] 1 1 1
Etudiant Étudiant Étudiant college Etudiante Étudiante Étudiantes
[1,] 1 5 1 2 2 1
Famille d'accueil Femme au foyer Foyer d’enfants Gérant de restaurant
[1,] 1 1 1 1
Infirmiere Infirmière
[1,] 1 1
Intermittente du spectacle + boutique d'ésortérisme Mairie
[1,] 1 1
Maîtresse d'école Mécanicien Medical Non Ouvrier Ouvrier agricole
[1,] 1 1 1 1 6 1
Pas demandé Pêcheur (préciser) Photographe Prof Professeur
[1,] 1 2 1 4 1
Réceptionniste en hôtellerie Recruteur de donateurs pour association
[1,] 1 1
Restauration/hôtellerie Retraite Retraité Retraité militaire
[1,] 12 37 32 1
Retraité pêcheurs Retraité/ avant aide soignante Retraitée Retraites
[1,] 1 1 1 1
Rien Sans Sans emploi Tabac Traducteur
[1,] 1 1 1 1 1
Travail avec des enfants handicapés Usine
[1,] 1 2
On a encore des problèmes d’encodage : je vous laisse y penser
Trouvez les dénominations en CSP les plus pertinentes
Regroupez les personnes dans la bonne CSP en :
Modifiant la fonction remplacement()
Et en attribuant les CSP à une nouvelle variable
On peut utiliser un prétraitement des données et la fonction startsWith(x, prefix) pour rassembler tout ce qui commencerait par la même racine
Q5 : utilisez startsWith() pour compléter la fonction suivante et rédigez la documentation
csp_questionnaire =c("Entreprise/tourisme", "Restauration/hôtellerie" , "Agriculteur (à préciser)", "Pêcheur (préciser)","Commerçant (préciser)", "Agent public","Cadre supérieur", "Employé", "Ouvrier")remplacement_professions =function(x_){#' Classification des professions à partir d'un questionnaire#' x = x_%>%str_to_lower()%>%gsub("é", "e", .)%>%gsub("è", "e", .) if(x %in%c('agriculteur (à preciser)')){return("Agriculteur exploitant") }elseif(startsWith(x, "employ") | x %in%c('aide à domicile', 'aide soignante', 'assistante à domicile', "famille d'accueil", "tabac", "medical","recruteur de donateurs pour association")){return("Employé.e") }elseif(x %in%c('sans', 'rien', 'sans emploi', 'chômeur','chômage', 'non', "femme au foyer")){ # COMPLETER L'EXPRESSIONreturn('Sans activité professionelle (hors retraite)') }elseif(startsWith(x, 'retrait')){return('Retraité.e') }elseif(startsWith(x, 'ouvrier') | x %in%c('mecanicien', 'usine')){return("Ouvrier") }elseif(startsWith(x, 'foyer') | x %in%c("infirmiere", "maîtresse d'ecole", 'enseignante', 'professeur', 'prof', "foyer d'enfants", "travail avec des enfants handicapes")){return('Professions intermédiaires') }elseif(startsWith(x, 'cadr') |startsWith(x, 'intermittente') | x %in%c('traducteur', "archeologue", "dessinateur en bureau d'etude", 'photographe', "agence immobilier") ){return("Cadres et professions intellectuelles supérieures")# Catégories du questionnaire }elseif(x %in%c('gerant de restaurant', 'receptionniste en hôtellerie', 'restauration/hôtellerie', "cuisine")){return("Restauration/hôtellerie") }elseif(startsWith(x, "entrepr") |startsWith(x, "commerçant")){return("Artisans, commerçants, chefs d'entreprise") }elseif(x %in%c('agent public', 'mairie')){return('Agent public') }else{return(x_) }}
csp_questionnaire =c("Entreprise/tourisme", "Restauration/hôtellerie" , "Agriculteur (à préciser)", "Pêcheur (préciser)","Commerçant (préciser)", "Agent public","Cadre supérieur", "Employé", "Ouvrier")remplacement_professions =function(x_){#' Classification des professions à partir d'un questionnaire#'#' Cette fonction standardise les réponses d'un questionnaire concernant la profession des répondants #' en les classant dans des catégories socio-professionnelles.#'#' @param x_ Une chaîne de caractères représentant une profession, telle qu'indiquée par un répondant.#' @return Une chaîne de caractères correspondant à une catégorie professionnelle standardisée. x = x_%>%str_to_lower()%>%gsub("é", "e", .)%>%gsub("è", "e", .) if(x %in%c('agriculteur (à preciser)')){return("Agriculteur exploitant") }elseif(startsWith(x, "employ") | x %in%c('aide à domicile', 'aide soignante', 'assistante à domicile', "famille d'accueil", "tabac", "medical","recruteur de donateurs pour association")){return("Employé.e") }elseif((startsWith(x, "etudian") |startsWith(x, "eleve") |startsWith(x, "colleg") | x %in%c('sans', 'rien', 'sans emploi', 'chômeur','chômage', 'non', "femme au foyer"))){return('Sans activité professionelle (hors retraite)') }elseif(startsWith(x, 'retrait')){return('Retraité.e') }elseif(startsWith(x, 'ouvrier') | x %in%c('mecanicien', 'usine')){return("Ouvrier") }elseif(startsWith(x, 'foyer') | x %in%c("infirmiere", "maîtresse d'ecole", 'enseignante', 'professeur', 'prof', "foyer d'enfants", "travail avec des enfants handicapes")){return('Professions intermédiaires') }elseif(startsWith(x, 'cadr') |startsWith(x, 'intermittente') | x %in%c('traducteur', "archeologue", "dessinateur en bureau d'etude", 'photographe', "agence immobilier") ){return("Cadres et professions intellectuelles supérieures")# Catégories du questionnaire }elseif(x %in%c('gerant de restaurant', 'receptionniste en hôtellerie', 'restauration/hôtellerie', "cuisine")){return("Restauration/hôtellerie") }elseif(startsWith(x, "entrepr") |startsWith(x, "commerçant")){return("Artisans, commerçants, chefs d'entreprise") }elseif(x %in%c('agent public', 'mairie')){return('Agent public') }else{return(x_) }}
Variables catégorielles nominales : nom des phoques
Ici, on va adopter la même approche par fonction de remplacement()
table(data_$q1_nom_phoques)
Gris Gris et veau marin
1 1
Gris et veaux marins Les deux
1 1
Nln Nn
1 2
Nob non
1 1
Non Oui
136 47
Oui (manque les phoques gris) Oui les deux
1 2
Oui phoque gris OUI phoque gris et phoque veau-marin
1 1
Oui plus ou moins Oui veau-marin
1 1
Pas de tout Phoque gris
1 1
Phoque gris et blanc Phoque veau-marin oui
1 1
Phoques gris Touriste
1 1
Vaumarin Veau-marin
1 2
Veau-marin et phoque commun Veau marin
1 5
Veau marin , phoque gris Veau marin et phoque
1 1
Veaux-marins et phoques gris
1
On va prendre comme réponses possible : oui, non, Partiel: veau marin et Partiel: phoque gris
Q6 - Faites une fonction de remplacement pour les phoques
remplacement_phoques_noms =function(x){#' Standardisation des réponses sur les espèces de phoques observées#'#' Cette fonction catégorise les réponses concernant la présence des espèces de phoques #' (veau-marin et phoque gris) en les regroupant dans des classes standardisées.#'#' @param x Une chaîne de caractères correspondant à une réponse du questionnaire sur les phoques observés.#' @return Une chaîne de caractères standardisée : `"Oui"`, `"Non"`, `"Partiel : Veau-marin"`, `"Partiel : Phoque gris"`, ou la réponse initiale si elle ne correspond à aucune catégorie.if(x %in%c('Veaux-marins et phoques gris', 'Veau marin , phoque gris', 'OUI phoque gris et phoque veau-marin','Gris et veaux marins', 'Veau marin et phoque', 'Les deux', 'Oui', 'Gris et veau marin', 'Gris et veaux marins', "Les deux ", 'Oui les deux')){return('Oui') }elseif(x %in%c('Non', 'nn', 'Nob', 'Nln', 'Non', 'Nn', 'non', 'Pas de tout', 'Touriste', 'Oui plus ou moins')){return('Non') }elseif(x %in%c('Oui (manque les phoques gris)', 'Veau-marin', 'Phoque veau-marin oui', 'Vaumarin', 'Veau marin','Veau-marin et phoque commun', 'Veau-marin', 'Oui veau-marin')){return("partiel : Veau-marin") }elseif(x %in%c('Gris', 'Oui phoque gris', 'Phoque gris et blanc', 'Phoques gris', 'Phoque gris')){return('Partiel : phoque gris') }else{return(x) }}data_ = data_ %>%mutate(clean_noms_phoques =sapply(q1_nom_phoques, remplacement_phoques_noms))
Variables catégorielles nominales multiples: avis sur les phoques
La différence ici, c’est que plusieurs choix sont possibles
# Trouver toutes les phrases possiblesphrases_ =c() # Initier un stock de phrases videfor(row_ in1:nrow(data_)){ # Pour chaque individu candidate_ =unlist(# On enlève la structure de liste strsplit(as.character(data_[row_, "q6_avis"]), # qui résulte de la séparation de chaque réponse", ") # autour de la virgule ) for(element_ in candidate_){ # On regarde pour chaque phrase cnadidateif(!(element_ %in% phrases_)){ # Si elle a déja été dite phrases_ =c(phrases_, element_) # Sinon, on l'ajoute } }}# Imprimer les 15 premières phrasesphrases_[1:15]
[1] "Il faut laisser la nature évoluer librement"
[2] "La surfréquentation peut générer des dérangements pour la nature"
[3] "Les conflits entre les humains et les phoques augmentent"
[4] "Les phoques font partie du patrimoine du territoire"
[5] "Il y a trop de phoques veau-marin"
[6] "Il y a trop de phoques gris"
[7] "Il ne faut pas que le nombre de phoque augmente plus"
[8] "Les phoques mangent trop de poissons"
[9] "Les phoques représentent une opportunité économique pour le territoire"
[10] "Les phoques représentent une menace économique pour le territoire"
[11] "Il faudrait un groupe de travail local sur les interactions avec les phoques"
[12] "Il faudrait plus de panneaux d’informations sur les phoques"
[13] "Il faudrait des barrières pour empêcher de déranger les phoques"
[14] "Il faudrait un centre d’information sur les phoques"
[15] "Visites guide nature m"
On a des problèmes de remplissage du questionnaire!
On va récupérer les choix du questionnaire
Ensuite, on fait ce qu’on appelle du one hot encoding, c’est à dire que l’on:
Transforme chaque affirmation en une variable
Qui prend la valeur 0 si absente, 1 si présente
Permet de numériser les variables catégorielles sans imposer d’ordre
data_ = data_ %>%mutate(ID =seq(1:nrow(data_))) # On donne un identifiant à chaque individu statistique# On crée une table d'occurence videtable_occurrences <-matrix(0,nrow =nrow(data_), ncol =length(phrases_possibles),dimnames =list(seq(1:nrow(data_)), phrases_possibles)) # Nomme les dimensions# Remplir le tableau avec les occurrencesfor (i in1:nrow(data_)) { phrases_ =unlist( # On enlève la structure de liste strsplit(as.character(data_[i, "q6_avis"]), # qui résulte de la séparation de chaque réponse", ") # autour de la virgule ) for (phrase in phrases_) { # Pour chaque phrase exprimée, if (phrase %in% phrases_possibles) { # si elle est dite, table_occurrences[i, phrase] <- table_occurrences[i, phrase] +1# on encode } }}
Q7 : Convertir la matrice en dataframe pour une meilleure lisibilité
# Convertir la matrice en dataframe pour une meilleure lisibilitétable_occurrences_df <-as.data.frame(table_occurrences)colnames(table_occurrences_df) =paste0("q_clean_", c('nature_libre','surfréquentation', 'conflits', 'patrimoine','trop_veau', 'trop_gris','pas_plus_phoques','trop_poisson','opportunité_économique', 'menace_économique','groupe_travail', 'panneaux','barrières', 'centre_information', 'menace_tradition'))table_occurrences_df = table_occurrences_df%>%mutate('ID'=seq(1, nrow(table_occurrences_df)))
Variables catégorielles nominales multiples : questions sur les phoques
On fait la même chose pour la question sur les phoques
phrases_ =c() # Initier un stock de phrases videfor(row_ in1:nrow(data_)){ # Pour chaque individu candidate_ =unlist(# On enlève la structure de liste strsplit(as.character(data_[row_, "q5_classe_phoque"]), # qui résulte de la séparation de chaque réponse", ") # autour de la virgule ) for(element_ in candidate_){ # On regarde pour chaque phrase cnadidateif(!(element_ %in% phrases_)){ # Si elle a déja été dite phrases_ =c(phrases_, element_) # Sinon, on l'ajoute } }}phrases_possibles =c("Un animal sympathique et paisible", "Une espèce invasive et nuisible","Un concurrent pour les activités humaines", "Une espèce menacée","Un prédateur supérieur" )data_ = data_ %>%mutate(identifier =seq(1:nrow(data_))) # On donne un identifiant à chaque individu statistique# On crée une table d'occurence videtable_occurrences <-matrix(0,nrow =nrow(data_), ncol =length(phrases_possibles),dimnames =list(seq(1:nrow(data_)), phrases_possibles)) # Pour stocker un identifiant# Remplir le tableau avec les occurrencesfor (i in1:nrow(data_)) { phrases_ =unlist(# On enlève la structure de liste strsplit(as.character(data_[i, "q5_classe_phoque"]), # qui résulte de la séparation de chaque réponse", ") # autour de la virgule ) for (phrase in phrases_) {if (phrase %in% phrases_possibles) { # si elle est dite, table_occurrences[i, phrase] <- table_occurrences[i, phrase] +1 } }}
Il faut revenir aux résultats du questionnaire, et traiter notamment les réponses collées (variables catégorielles multiples)
On refait du one hot encoding
# I. On doit cleaner les données pour bien pouvoir les isoler : on supprime ce qu'il se passe entre parenthèse pour pouvoir séparer les entités par l'usage de la virguledata_loc = data_ %>%select(loisir)data_loc= data_loc %>%mutate(loisir_easy =gsub("\\s*\\(.*?\\)", '', loisir))%>%mutate(loisir_easy =gsub(" ", "", loisir_easy))%>%mutate(loisir_easy =str_to_lower(loisir_easy))phrases_possibles =c("Pêche):", "Chasse", "Voile", "vélo", "natation","Planceàvoile", "Paddle", "Kayak/canoë", "Kitesurf", "Equitation", "Wingfoil","Promenade", "Marche", "Naturaliste")phrases_possibles =str_to_lower(phrases_possibles)# II. On crée une table d'occurence videtable_occurrences <-matrix(0,nrow =nrow(data_), ncol =length(phrases_possibles) +1, # On ajoute une case pour prendre en compte les cas "Autres"dimnames =list(seq(1:nrow(data_)),c(phrases_possibles, "Autre"))) # Pour stocker un identifiant#III. Remplir le tableau avec les occurrencesfor (i in1:nrow(data_)) { phrases_ =unlist(# On enlève la structure de liste strsplit(as.character(data_loc[i, "loisir_easy"]), # qui résulte de la séparation de chaque réponse",") # autour de la virgule ) for (phrase in phrases_) {if (phrase %in% phrases_possibles) { # si elle est dite, table_occurrences[i, phrase] <- table_occurrences[i, phrase] +1 }else{ table_occurrences[i, "Autre"] = table_occurrences[i, "Autre"] +1 } }}
Synthèse et nettoyage final
On merge tout et on sélectionne les données propres :
Q8 : utilisez le pipe operator (%>%) pour sélectionner les colonnes de la full_data et récupérer les colonnes qui ont le nom clean avec grepl
On sauvegarde les données en .xlsx mais les formats de référence sont plutot:
.csv
pour des données plus larges .json
.parquet
ou encore les data.tables pour les format de matrices avec beaucoup de 0 suivant le one hot encoding de larges données (accélère également leur écriture et leur ouverture)
data_clean = full_data %>%select(c('date', 'genre', 'classe_age', # Les données déja proprescolnames(full_data)[full_data %>%# Les colonnes que l'on a nommé cleancolnames() %>%# car nettoyées!grepl("clean", .)] ) )colnames(data_clean) =gsub("clean_", "", colnames(data_clean))write.xlsx(data_clean, here("data", "processed", "data_cleaned.xlsx"))
Documentation
Créez un fichier .txt ou .md qui détaille les données brutes et enregistrez le dans /docs/README_rawdata.txt
Créez un fichier .txt ou .md qui détaille les transformations appliquées aux données et les fonctions utilisées, et enregistrez le dans /docs/README_datacleaning.txt
Faites un dictionnaire de variables: dansl’idéal, il faut le faire, mais je l’ai fait pour vous (enfin, ClaudeAI l’a fait à ma place)
Nom des variables; Descriptions; Type de variables ;Unité de mesure ;Valeurs possibles ;Valeurs manquantes ;Source des données; Commentaires et remarques
Idéalement, il faudrait refaire tout cela pour réplication de sorte que :
Updatez le script fonctions.R avec les fonctions que l’on a utilisées : elles ne sont pas encore très modulaires ou beaucoup réutilisables, mais c’est un premier pas.
Appelez le script fonctions.R avec source(here("scripts", "fonctions.R")) en début de script et utilisez les fonctions
Analyse de données
Quelles analyses mener?
Etude des valeurs manquantes
Etude des distributions des variables clés
Analyse de corrélation entre variables
Analyse multivariées
- The lockfile is already up to date.
Analyses simples
Pour cela, on va utiliser la bibliothèque ggplot2 (Grammar of Graphics) qui prend les éléments suivants :
Les données (data)
Les “aesthetics” (aes()) : comment représenter les variables, qui est en abscisse (x=), en ordonnée (y=), en couleur (color=)
La géométrie (geom()): le type de graphique
Une variété d’éléments de style :
scale_color_, scale_fill pour les couleurs
theme() pour les axes, polices, tailles, couleur de fond
On peut également représenter sur différents panels avec les fonctions facet_
Marche au mieux lorsque les données sont sous le format tidy: une variable par colonne, une observation par ligne
On utilise parfois pivot_longer() pour transformer des données “larges” en “longues”, pour tout représenter d’un coup
correlation_matrix =cor(data_ %>%select(to_factor_vect)%>%select(-c('wingfoil'))%>%mutate(across(everything(), as.numeric)), method ='pearson')# On efface la diagonale (corrélation de 1) et on garde seulement un triangle pour éviter la redondancediag(correlation_matrix) =NA# Convert matrix to long format for ggplotcor_long = reshape2::melt(correlation_matrix)p =ggplot(cor_long, aes(Var1, Var2, fill = value)) +geom_tile() +scale_fill_gradient2(low ="blue", mid ="white", high ="red", midpoint =0) +labs(title ="Correlation Heatmap of One-Hot Encoded Variables",x =" ",y =" ",fill ="Correlation") +theme_minimal() +theme(axis.text.x =element_text(angle =45, hjust =1))
On va essayer de restreindre un peu l’analyse : Q4 : prenez un sous ensemble de variables, comme les caractéristiques des phoques et les réponses aux questions et faites une heatmap
correlation_matrix =cor(data_ %>%select(colnames(data_)[startsWith(colnames(data_), "q_")])%>%mutate(across(everything(), as.numeric)), method ='pearson')diag(correlation_matrix) =NA# Convert matrix to long format for ggplotcor_long = reshape2::melt(correlation_matrix)
Analyse par correspondance multiple
Technique d’analyse multivariées pour identifier la structure d’un espace de grande dimension
Origine et développement
Jean-Paul Benzécri (années 1960) :
Pionnier de l’analyse des correspondances, il développe l’Analyse des Correspondances Simples (ACS) pour représenter graphiquement la structure des tableaux de contingence.
Nécessité de visualiser les relations entre catégories dans des enquêtes sociales, marketing, etc.
Extension vers l’Analyse des Correspondances Multiples (ACM)
Adaptation de l’ACS pour traiter simultanément plusieurs variables qualitatives, en combinant plusieurs tableaux de contingence dans une matrice globale.
Harold Hotelling (années 1930-1940) :
Pionnier de l’Analyse en Composantes Principales (ACP), il formalise la réduction de dimensionnalité pour des variables quantitatives.
Introduit l’idée de décomposition en valeurs propres, qui vise à extraire des axes expliquant la plus grande part de la variance.
Théorie de l’information et géométrie
L’inertie (ou dispersion) dans les données est mesurée de manière similaire à la variance en ACP, ce qui permet de comparer et de représenter les structures des données.
Intuition mathématique – De la géométrie à la décomposition
Principe de base : représentation géométrique
Chaque individu est représenté par un point dans un espace de dimension élevée, chaque axe correspondant à une modalité (catégorie).
Objectif : trouver un espace de dimension réduite qui capture l’essentieldes relations entre modalités et individus
Mesure de la dispersion :l’inertie
Inertie totale : analogue à la variance en ACP, elle mesure la dispersion totale des pointspar rapport au centre.
Chaque modalité contribue à cette inertie selon sa fréquence et son écart par rapport à l’attendu en cas d’indépendance.
Décomposition en valeurs singulières (SVD)
Inspirée par l’ACP de Hotelling, la SVD permet de décomposer la matrice normalisée en axes factoriels orthogonaux.
La SVD « fait pivoter » l’espace initial pour révéler les directions (axes) où la dispersion (l’information) est maximale.
Les axes sélectionnés expliquent successivement des parts décroissantes de l’inertie totale.
Fonctionnement pas à pas – De la donnée brute à l’interprétation
Préparation des données : construction d’un tableau binaire :
Chaque individu est décrit par des réponses qualitatives, transformées en indicateurs (1 ou 0) pour chaque modalité.
Normalisation et calcul des distances
Utilisation de la distance du khi-deux, adaptée aux données qualitatives, pour mesurer la similitude entre profils.
Pondération des modalités selon leur fréquence pour équilibrer leur influence.
Décomposition via SVD
La matrice normalisée est décomposée en composantes grâce à la SVD, qui découle directement des idées de réduction de dimensionnalité portées par l’ACP de Hotelling.
Chaque axe factoriel est associé à une valeur propre qui quantifie la part d’inertie expliquée.
Projection et interprétation
Projection simultanée des individus et des modalités sur les axes factoriels.
Les points proches indiquent des profils similaires.
Les axes traduisent des tendances ou oppositions sous-jacentes (ex. profils traditionnels vs. profils modernes).
Idée générale
On minimise la distance du khi-deux intra-classe : les modalités proches doivent rester proches
On maximise la dispersion entre groupes distincts: les modalités et individus différents doivent être bien séparés sur les axes factoriels.
Mise en pratique
Il faut préparer les données: on ne va pas tout garder
Par ailleurs, la structure de nos questions n’est pas parfaite pour utiliser l’ACM :
Fait pour quand plusieurs niveaux d’une même question sont mutuellement exclusifs
Nous, on a autorisé 2 ou 5 réponses par question(s)
On n’a pas exactement la même structure, donc la distance du khi-deux risque d’être un peu modifiée
On pourrait modifier les réponses et les pondérer par le nombre de réponses
Malgré ces problèmes, on va illustrer la méthode : on réfléchira ensuite à la meilleure manière
Pour cela on utilise les bibliothèques FactoMineR et factoextra
library(FactoMineR)library(factoextra)# I. Préparer les données : il faut que des facteursdata_for_analysis = data_%>%select(c(to_factor_vect, 'genre', 'classe_age', 'pop_yes_no', 'professions', 'noms_phoques'))%>%mutate(across(everything(), as.factor))%>%drop_na()
Réaliser l’ACM
La fonction MCA() va :
Construire le tableau disjonctif complet.
Calculer la distance du khi-deux pour chaque individu et modalité.
Réaliser la décomposition en valeurs singulières qui, de manière implicite, minimise la somme des distances (c’est-à-dire l’inertie non expliquée).
ACS : Deux variables qualitatives (tableau de contingence).
ACM : Plusieurs variables qualitatives (tableau disjonctif complet).
Méthodologie sous-jacente
ACP : Basée sur la variance et la covariance (analyse matricielle des données numériques).
ACS & ACM : Basées sur les fréquences observées vs. attendues (utilisation de la distance du khi-deux) et la décomposition en valeurs singulières des tableaux normalisés.
Objectifs analytiques
ACP : Réduction de dimensionnalité et interprétation des relations linéaires entre variables quantitatives.
ACS : Étude de l’association entre deux variables catégorielles.
ACM : Exploration simultanée et synthèse des relations entre plusieurs variables qualitatives.
Comment rendre la recherche reproductible?
.lock
avec les collaborateurs