owned this note changed a year ago
Published Linked with GitHub




Préparation et manipulation de données dans R

L'un des enjeux actuels autour du traitement et de l'analyse de données se trouve au niveau des étapes de préparation (nettoyage, filtre, restructuration) et d'exploration (statistique, visualisation, cartographie) des jeux de données.

L'open data c'est super, mais bien souvent les jeux de données mis à disposition ne sont pas "prets" et "optimisés" à être directement intégrés au sein des logiciels d'analyse comme les SIG.

La principale problématique est bien celle du volume des données. Dans un fichier .csv ou .xls classique, la plupart des opérations de préparation et d'exploration de données peuvent aisement être réalisées avec excel ou librecalc.

Mais lorsqu'on commence à vouloir manipuler et explorer des jeux de données plus volumineux (plus d'un million de lignes), ces logiciels familiers ne sont plus en capacité de répondre aux besoins. Et le passage à des environnement plus "adapatés" aux données plus volumineuses comme R s'impose.


Présentation du Tidyverse

L'une des solutions les plus efficaces pour cette étape primordiale de "Dataprep" réside dans l'utilisation de l'environnement R qui permet au sein d'un outil unique de disposer d'une multitude de fonctionnalités pour répondre aux besoins des explorateurs de données.

Nous explorons ici la collection de packages proposée au sein du Tidyverse qui permettent de manipuler des données, les restructurer, les modifier, les filtrer, les agréger, les visualiser, de manière assez "simple".

https://www.tidyverse.org/

Développé par Hadley Wickham il propose une "grammaire de la structuration et de la maniplation de données dans R". Tidy data > "Données ordonnées"

Cette collection propose les packages suivants :

  • ggplot2 (visualisation)
  • dplyr (manipulation des données)
  • tidyr (remise en forme des données)
  • purrr (programmation)
  • readr (importation de données)
  • tibble (tableaux de données)
  • forcats (variables qualitatives)
  • stringr (chaînes de caractères)

Concrétement il s'agit d'une suite d’outils graphiques et de manipulation de données cohérents reposant sur une grammaire qui rend la programmation dans R cohérente, lisible et intuitive.


Cheatsheets dplyr et tidyr en français >
https://thinkr.fr/aide-memoire-manipulation-de-donnees-avec-dplyr-tidyr/


Ressources :

En théorie tout parait simple, mais l'environnement R repose sur un langage propre qui n'est pas forcemment accessible et compréhensible à tous de prime abordet souvent malgrès des potentialités très intéressantes, les non spécialistes se découragent et de fait se limitent dans leurs manières d'appréhender la préparation de données.

L'objectif de ce document est de proposer une syntaxe "simple" et "accessible" pour la manipulation de données avec R


Les données

Dans cet exemple nous nous basons sur le jeu de données des signalements de l'application Dans Ma Rue de la ville de Paris.

Ce jeu de données mis à jour quotidiennement est disponible sur le portail open data de la ville de Paris

Ce jeu de données est particuliérement intéressant à plusieurs titres :

  • il contient 1.2 millions de lignes (anomalies)
  • il est bien structuré et complet
  • les anomalies sont caractérisées par de nombreux champs
  • comme des champs temporels
  • comme des champs qualitatifs
  • comme des champs géographiques
  • et même des coordonnées géographiques

Importer le jeu de données dans R

2 options:

  1. Utiliser la boite de dialogue dédiée

  1. Importer par une commande
DMR <- read.csv("D:/Articles/DansMaRue/dans-ma-rue_2020.csv", encoding="UTF-8", sep=";")

Voilà le jeu de données est dans R !


Lancer le Tidyverse

  1. Installer le package (une seule fois)
install.packages("tidyverse")
  1. Lancer le package (à chaque lancement de R Studio)
library(tidyverse)

Le Pipe

Quand on manipule un tableau de données, il est très fréquent d’enchaîner plusieurs opérations. On va par exemple filtrer pour extraire une sous-population, sélectionner des colonnes puis trier selon une variable.

Pour simplifier et améliorer encore la lisibilité du code, on va utiliser un opérateur indispensable, baptisé pipe

Le pipe a été introduit à l’origine par l’extension magrittr, et repris par dplyr

Le pipe se note %>% il peux se traduire par "ensuite"


Une syntaxe assez proche du SQL


La fonction Select

la fonction Select permet de selectionner et de renommer les colonnes (champs) dans un nouveau dataframe

Par exemple je veux constituer un nouveau jeu de données avec uniquement :

  • Le type de signalement [TYPE]
  • L'arrondissement [ARRONDISSEMENT]
  • La date [DATEDECL]
  • L'intervenant [INTERVENANT]
  • les coordonnées géographique [geo_point_2d]

La syntaxe de la commande est la suivante :

DMRsimplifie <- DMR %>% select(Type = TYPE, Arrondissement = ARRONDISSEMENT, Date = DATEDECL, Intervenant = INTERVENANT, Coordonnées = geo_point_2d)

Ne pas sélectionner une ou certaine colonne (ici toutes les colonnes sauf celle des coordonnées géographiques

DMRnotgeo <-DMR %>% select(everything(), -c("geo_point_2d"))


La fonction Rename

La fonction Rename permet de renommer un champ.

On spécifie le nouveau nom du champ = l'ancien nom du champ

Je veux renommer le champ "Arondissement" en "Ardt"

DMRsimplifie <- DMRsimplifie %>% rename(Ardt = Arrondissement)

La fonction Filter

La fonction Filter permet de filtrer des lignes en fonctions des modalités des colonnes.

On peut combiner plusieurs conditions logiques :

  • ET logique, par une virgule ou un &
  • OU logique, par l'opérateur |
  • NON logique par l'opérateur !

Par exemple je veux garder uniquement les signalements de type Graffitis, tags, affiches et autocollants en utilisant l'opérateur ==

Graffitis <- DMR %>% filter(TYPE == 'Graffitis, tags, affiches et autocollants')

Il est aussi possible d'utiliser l'opérateur %in%

Graffitis <- DMR %>% filter(TYPE %in% 'Graffitis, tags, affiches et autocollants') 

Garder les signalements de type Graffitis, tags, affiches et autocollants OU Propreté.
L'opérateur OR est ici mobilisé via le symbole |

Graffitisetproprete <- DMR %>% filter(TYPE == 'Graffitis, tags, affiches et autocollants' | TYPE == 'Propreté')

Garder les signalements de type Proprété ET dans le 10eme arrondissement
L'opérateur AND est ici mobilisé via le symbole , ou &

Proprete10eme <- DMR %>% filter(TYPE == 'Propreté', ARRONDISSEMENT== '10')

Garder les signalements relatifs à la propreté ET qui ont eu lieu en 2018 ET dans le 18eme arrondissement

Sign2018et18emeetProprete <- DMR %>% filter(ANNEE.DECLARATION==2018 & ARRONDISSEMENT==18 & TYPE=='Propreté')

Garder tous les signalements relatifs à la propreté dans le 1er, le 2eme et le 3eme arrondissement ET qui ont eu lieu depuis 2016

Signalementdepuis2016 <- DMR %>% filter(TYPE == 'Propreté', ARRONDISSEMENT == '1' |ARRONDISSEMENT == '2'| ARRONDISSEMENT == '3', ANNEE.DECLARATION == '2016' | ANNEE.DECLARATION == '2017' |ANNEE.DECLARATION == '2018' |ANNEE.DECLARATION == '2019')

On peux égallement venir compléter les critères de selection en utilisant des fonctions supplémentaires :

  • %in% > équivalent du in en SQL (groupe de modalités)
  • grepl > équivalent du like en SQL (intérroger une chaîne de charactères)

Garder tous les signalements relatifs aux 1er, 2eme, 3eme 4eme et 5eme arrondissement grâce à la fonction %in%

Signalementscentre <- DMR %>% filter(ARRONDISSEMENT %in% c(1,2,3,4,5))

Garder tous les signalements sauf ceux relatifs aux 1er, 2eme, 3eme 4eme et 5eme arrondissement grâce à la fonction %in% couplée à la clause négative !

Signalementsnotcentre <- DMR %>% filter( ! ARRONDISSEMENT %in% c(1,2,3,4,5))

Garder les signalements de type Graffitis, dans les arrondissements du centre (1,2,3,4,5), qui ont eu lieu l'été (juin, juillet, août) et depuis 2017 grâce à la fonction %in%

Signalementetecentre <- DMR %>% filter(TYPE =='Graffitis, tags, affiches et autocollants', ARRONDISSEMENT %in% c(1,2,3,4,5), ANNEE.DECLARATION %in% c(2017,2018,2019), MOIS.DECLARATION %in% c(6,7,8))

La fonction grepl peux être utilisée de différentes manières dans l'interrogation de chaines de charactères :

  • grepl("valeur", variable) renvoie toute les lignes contenant la chaine de charatères
  • grepl("^valeur", variable) renvoie toute les lignes contenant le début de chaine de charatères
  • grepl("valeur$", variable) renvoie toute les lignes contenant la fin de chaine de charatères

Garder les signalements relatifs aux "vélos" grâce à la fonction grepl (équivalent du like en SQL)

Velos <- DMR %>% filter(grepl("vélo", SOUSTYPE) | grepl("cycl", SOUSTYPE))

Garder les signalements relatifs aux "stationnement" grâce à la fonction grepl (équivalent du like en SQL)

Stationnement <- DMR %>% filter(grepl("tationne", SOUSTYPE))

Garder les signalement des arrondissement supérieurs au 10eme

ARDT10etplus <- DMR %>% filter(grepl("^7501", CODE_POSTAL))

Filtres basés sur des sous sélections


La fonction Arrange

La fonction arrange permet de trier un tableau selon l'ordre d'une variable (tri alphabétique, croissant, décroissant)

Trier tous les signalements par arrondissement

Signalementtriardt <- DMR %>% arrange(ARRONDISSEMENT)

Trier tous les signalements par date (croissante)

SignalementTriDate <- DMR %>% arrange(DATEDECL)

Trier tous les signalements par date (décroissante)

SignalementTriDate <- DMR %>% arrange (desc(DATEDECL))

Trier tous les signalements par arrondisement et par date

SignalementTriArdtDate <- DMR %>% arrange(ARRONDISSEMENT,DATEDECL)

La fonction Mutate

La fonction mutate permet de créer des nouvelles colonnes pour par exemple effectuer un calcul, une reclassification, une concaténation, une séparation ou encore reformater une variable.

Je veux concatener (fusionner au sein de la même colonne) le TYPE et le SOUSTYPE des signalements avec comme séparteur des deux modalités un tiret (-)

DMRconcat <- DMR %>% mutate(Typecomplet=paste(TYPE, SOUSTYPE, sep='-'))

On peux aussi utiliser la fonctionunite pour concaténer plusieurs champs

Par exemple je veux concaténer le mois et l'année dans un champ unique.

Attention avec cette méthode, les colonnes concaténer disparaissent du dataframe

DMRUnite <- DMR %>% unite("Datebis", MOIS.DECLARATION, ANNEE.DECLARATION, sep= "/")

La fonction Separate

La fonction separate permet de diviser les valeurs d'un champ au sein de plusieurs colonnes

Je veux diviser le champ des coordonées géographique geopoint2D en deux colonnes distinctes (latitude et longitude) car ces informations sont concatenées au sein de la même colonne

DMRGEO <- DMR %>% separate(col= geo_point_2d, into = c("Latitude", "Longitude"), sep= ",")


Modifier le formatage d'une variable

Dans beaucoup de cas les colonnes ne sont pas formatées dans des formats adéquats (texte, numeric, date,) et il faut procéder à un reformatage des valeurs des colonnes.

Dans l'environnement R cette opération peux s'effectuer en combinant la fonction mutate et la reformatage désiré

as.character              pour passer en chaine de charactères
as.numeric / as.integer   pour passer en format numeric
as.date                   pour passer en format date  
...

Je veux transformer la variables DATEDECL (à la base en factor) en format date

DMR <- DMR %>% mutate(DATEDECL = as.Date(DATEDECL))
DMR$DATEDECL <- as.Date(DMR$DATEDECL)

Avec cette modification les sélections basées sur des dates sont plus simples ;)

Garder les signalements entre le 1er mars 2018 et le 30 juin 2019

SignalementsMars2018Juin2019 <- DMR %>% filter(DATEDECL >= as.Date("2018-03-01") & DATEDECL <= as.Date("2019-06-30"))

La fonction Distinct

Cette fonction permet de renvoyer les valeur uniques d'une variable ou de compter les valeur uniques des ID par exemple.

Je veux sortir la liste des "Intervenants"

Intervant <- DMR %>% distinct(INTERVENANT)

Je veux compter le nombre de dates différentes, donc le nombre de jour présents dans la base

length(unique(DMR$DATEDECL))


Agréger les données avec les fonctions group_by et summarise

Les fonctions group_by pour le regroupement et summarise pour les résumés statistiques (compte, somme, moyenne, médiane,) sont très utiles face à de gros de de données caractérisés par des variables qualitatives et quantitatives.

Compte   > n()
Moyenne  > mean()
Somme    > sum()
Médiane  > median()
...

Ces fonctions permettent notamment de créer rapidement des tableaux croisés dynamiques.


Je veux par exemple compter le nombre de signalements par arrondissement

NbsignalementARDT <- DMR %>% group_by(ARRONDISSEMENT) %>% summarise(NBSignalements = n())

Je veux compter le nombre de signalements par arrondissement et par année

NbsignalementARDTAnnee <- DMR %>% group_by(Ardt = ARRONDISSEMENT, Annee = ANNEE.DECLARATION) %>% summarise(NBSignalements = n())

Je veux compter le nombre de signalements par arrondissement, par année et par type

NbsignalARDTAnneeType <- DMR %>% group_by(ARRONDISSEMENT, ANNEE.DECLARATION, TYPE) %>% summarise(NBSignalements = n())

Je veux calculer le nombre moyen de signalements par mois de chacun des arrondissement

MeansignalArdtMois <- DMR %>% group_by(ARRONDISSEMENT, MOIS.DECLARATION) %>% summarise(NBMoyen = mean(n()))

Je veux calculer le nombre moyen de signalements quotidiens en 2018 par arrondissement

MeansignalJourArdt <- DMR %>% filter(ANNEE.DECLARATION==2018) %>% group_by(ARRONDISSEMENT) %>% summarise(NbMoyen=(n()/365))`

Je veux compter le nombre total et calculer le nombre moyen de signalements quotidiens en 2015, 2016, 2017 et 2018

NbsignalAnne <- DMR %>% filter(ANNEE.DECLARATION %in% c(2015,2016,2017,2018)) %>% group_by(ANNEE.DECLARATION) %>% summarise(Total = n(), Moyennequotidienne = n()/365)

Je veux calculer la proportion de chacun des types de signalements

 PropType <- DMR %>% group_by(TYPE) %>% summarise(nb = n(), prop = (n()/nrow(DMR))*100) %>% mutate(prop = round(prop,1)) %>% arrange(desc(prop))

Je veux calculer la proportion de chacun des types de signalement pour le 1er arrondissement

PropType1erArdt <- DMR %>% filter(ARRONDISSEMENT=='1') %>% group_by(ARRONDISSEMENT, TYPE) %>% summarise(nb = n()) %>% arrange(ARRONDISSEMENT, desc(nb)) %>% ungroup(.self) %>% mutate( total = sum(nb), Prop = (nb/total)*100, Prop = round(Prop,1)) %>% arrange(desc(Prop))

Je veux calculer la proportion de chacun des types de signalement pour chaque arrondissement

PropTypeArdt <- DMR %>% group_by(ARRONDISSEMENT, TYPE) %>% summarise(nb = n()) %>% arrange(ARRONDISSEMENT, desc(nb)) %>% ungroup(TYPE) %>% group_by(ARRONDISSEMENT) %>% mutate( total = sum(nb), Prop = (nb/total)*100, Prop = round(Prop,1)) %>% arrange(ARRONDISSEMENT, desc(Prop))

Restructuration des données (lignes<>colonnes)

Il est facilement possible de passer d'un mode long (long) à un mode large (wide) et vice-versa grâce aux fonctions :

  • spread passage lignes vers colonnes
  • gather passage colonnes vers lignes

Ces deux fonctions sont particuliérement utiles pour restructurer des jeux de données pour par exemple faire des datavisualisation ou des représentations cartographiques.

La fonction spread

Je veux produire un tableau avec en lignes les arrondissements et en colonne le nombre de signalements par années

Etape 1. produire un tableau récapitulatif en utilisant le group by

SignalementArdtAnnee <- DMR %>% group_by(Code = CODE_POSTAL, Annee = ANNEE.DECLARATION) %>% summarise(nb= n())

Etape 2. Passage de long à large

Signalementcolonnes <- SignalementArdtAnnee %>% spread(key=Annee, value = nb)

Autre exemple, je veux produire un tableau avec en lignes l'année et en colonne les arrondissements

Signalementcolonnesbis <- SignalementArdtAnnee %>% spread(key=Code, value = nb)

Dernier exemple, je veux produire un tableau récapitulatif avec en ligne l'année et en colonne le nombre de signalement par type

Etape 1. Produire le premier tableau récapitulatif

DMRtype<- DMR %>% group_by(ANNEE = ANNEE.DECLARATION, TYPE) %>% summarise(nb= n())

Etape 2. Passage de long à large

Signalementcolonnestype <- DMRtype %>% spread(key=TYPE, value = nb)

La fonction gather

En partant d'un tableau structuré en colonnes (le dernier produit), je veux produire un tableau où je vais retrouver les nombre de signalements par années et par types structurés en lignes (pour une visualisation spatio-temporelle par exemple)

DMRenlignes <- Signalementcolonnestype %>%  gather(key="Type", value = "Nb", `Activités commerciales et professionnelles`:`Voirie et espace public`)


Jointure attributaire

La jointure attributaire est une fonction très pratique pour venir enrichir des tableau avec des données issus d'autres tableaux.

Nous volons ici venir enrichir la tableau avec les données INSEE du nombre de signalements par arrondissement en 2019

La première fonction merge permet de faire des jointures "simples" et "brutes"

  • 1er solution : merge(DatasetA, DatasetB, by = "id") dans le cas où le nom des champs de jointure est le même
  • 2eme solution : merge(DatasetA, DatasetB, by.x = "id", by.y = "ID") dans le cas où les noms des champs de jointure sont différents

Jointure avec deux champs avec le même nom

Etape 1. Produire un tableau récapitulatif avec le nombre de signalements en 2019 pour chaque arrondissements

DMRArdt2019 <- DMR %>% filter(ANNEE.DECLARATION == 2019) %>% group_by(Ardt = CODE_POSTAL) %>% summarise(nb=n())

Importer le jeu de données avec les données de l'INSEE

Les noms des 2 champs de jointure sont différentes >il va falloir créer dans l'un des jeux de données un champ de jointure avec un nom similaire à celui de l'autre

Créer dans le tableau récapitulatid une nouvelle colonne CODGEO

DMRArdt2019<- DMRArdt2019 %>% mutate(CODGEO=Ardt)

Etape2. Faire la jointure

DMRINSEE <- merge(ParisINSEE, DMRArdt2019, by = "CODGEO")

Jointure avec deux champs avec deux noms différents

Méthode bien plus simple !

DMRINSEEbis <- merge(ParisINSEE, DMRArdt2019, by.x = "CODGEO", by.y = "Ardt")


Joindre avec deux champs différents et ne garde que les champs désirés

Le Graal !

DMRINSEEparfait <- merge(ParisINSEE, DMRArdt2019, by.x = "CodeArdt", by.y = "Ardt") [,c("CodeArdt","Pop2016", "nb")]

Travailler avec des jointures plus complexes


Les fonctions temporelles avec lubridate

Le package lubridate permet de travailler avec des dates et des heures pour reformter des timestamps extraires des noms de journée ou des numéros de semaine par exemple.

https://evoldyn.gitlab.io/evomics-2018/ref-sheets/R_lubridate.pdf

install.packages("lubridate")

library(lubridate)

Première étape pour manipuler des dates, reformater la colonne DATEDECL en format Date

DMR$DATEDECL <- as.Date(DMR$DATEDECL)

Nous voulons extraire le jour de la semaine de chaque signalement en utilisant la fonction wday

DMR <- DMR %>% mutate(journee= wday(DATEDECL,label= TRUE, abbr= FALSE))

Exemple autour de traces GPS

traces$timestamp <- as_datetime(traces$timestamp)
traces <- traces %>% mutate(UNIX= as.numeric(as.POSIXct(timestamp)))

Ecrire un csv

Nous volons enfin exporter un ou plusieurs dataframe pour une utilisation dans un logiciels de visualisation de données ou de SIG.

Il faut utiliser la fonction write.csv en spécifiant :

  • Le nom de dataframe a exporté
  • Le nom du csv
  • l'emplacament ou sera exporté le csv

J'exporte en csv le dataframe Signalement2019 dans le dossier "documents"

write.csv(Signalement2019, 'Signalement2019.csv')

J'exporte en csv le dataframe Signalement2019 dans un dossier dédié sur mon bureau

write.csv(Signalement2019, 'C:/Users/Xo/Desktop/DMR/Signalement2019.csv')

Pour aller plus loin

Séance sur la visualisation de données avec ggplot2


Séance sur la manipulation de données de billétique avec Tidyverse ggplot2 lubridate et sf


Séance sur la manipulation des données DVF avec Tidyverse et sf


Séance sur la manipulation des données spatiales, géotraitements avec Tidyverse et sf

Select a repo