# Les packages doivent au préalable être installés sur le disque dur
# Pour installer un package :
# install.packages("nom_du_package")
Aide-mémoire SAS
- R
- pandas
Un aide-mémoire pour les statisticiens traduisant des codes standards en SAS
, en R
, suivant 4 environnements (R base
, tidyverse
, data.table
, arrow/duckdb
) et en python pandas
.
L’aide-mémoire a pour but de fournir des codes écrits en SAS
et d’en donner la traduction en différents environnements R
:
R base
tidyverse
data.table
arrow/duckdb
et en python pandas
.
Les codes traduits sont typiques de la production statistique ou la réalisation d’études descriptives.
Ce document vise à faciliter la compréhension ou la traduction de codes ainsi que le passage d’un langage présenté à un autre. Il s’adresse notamment aux utilisateurs d’un de ces langages qui souhaitent comprendre ou traduire des codes écrits dans un autre langage.
Il se veut complémentaire de la documentation en ligne en français Utilit’R, née à l’Insee (https://www.book.utilitr.org/). Le lecteur est invité à s’y référer pour obtenir des informations importantes sur l’utilisation de R
et qui ne sont pas discutées dans ce document, comme l’importation de données en R
(https://www.book.utilitr.org/03_fiches_thematiques/fiche_import_fichiers_plats).
Enfin, si vous souhaitez collaborer à cet aide-mémoire ou nous faire part de votre avis, n’hésitez pas à nous contacter via nos adresses email.
1 Importation des packages
1.1 Installation des packages
Des informations sur l’installation des packages en R
sont disponibles sur le site Utilit’R : https://book.utilitr.org/01_R_Insee/Fiche_installer_packages.html.
/* Sans objet pour SAS */
# Les packages doivent au préalable être installés sur le disque dur
# Pour installer un package :
# install.packages("nom_du_package")
# Les packages doivent au préalable être installés sur le disque dur
# Pour installer un package :
# install.packages("nom_du_package")
# Les packages doivent au préalable être installés sur le disque dur
# Pour installer un package :
# install.packages("nom_du_package")
# Commande à écrire dans le prompt d'Anaconda
# Pour installer un package :
# pip install nom_du_package
1.2 Importation des packages
/* Sans objet pour SAS */
# Sans objet pour R-Base
# Cependant, on importe le package lubridate pour faciliter la gestion des dates
library(lubridate)
# Documentation de R base
"[.data.frame" ?
# Chargement des packages
# Le tidyverse proprement dit
library(tidyverse)
# Les packages importés par le tidyverse sont :
# - dplyr (manipulation de données)
# - tidyr (réorganisation de bases de données)
# - readr (importation de données)
# - purrr (permet de réaliser des boucles)
# - tibble (format de données tibble, complémentaire du data.frame)
# - stringr (manipulation de chaînes de caractères)
# - ggplot2 (création de graphiques)
# - forcats (gestion des formats "factors")
# Pour manipuler les dates
library(lubridate)
# Pour utiliser le pipe %>%
library(magrittr)
# Documentation de tidyverse
vignette("dplyr")
library(data.table)
# Pour manipuler les dates
library(lubridate)
# Documentation de data.table
'[.data.table' ?
#library(duckdb)
#library(arrow)
import pandas as pd
import numpy as np
from datetime import datetime
1.3 Documentation (Utilit’R, cheatsheets, etc.)
Sans objet pour SAS.
Pas de documentation spécifique sur R base
dans Utilit’R.
Aide-mémoire (Cheatsheet) : https://dplyr.tidyverse.org/articles/base.html
Documentation Utilit’R : https://book.utilitr.org/03_Fiches_thematiques/Fiche_tidyverse.html.
Aide-mémoire (Cheatsheets) :
- général : https://www.rstudio.com/wp-content/uploads/2015/02/data-wrangling-cheatsheet.pdf
- dplyr : https://rstudio.github.io/cheatsheets/data-transformation.pdf
- tidyr : https://rstudio.github.io/cheatsheets/tidyr.pdf
- readr : https://rstudio.github.io/cheatsheets/data-import.pdf
- purrr : https://rstudio.github.io/cheatsheets/purrr.pdf
- stringr : https://rstudio.github.io/cheatsheets/strings.pdf
- ggplot2 : https://rstudio.github.io/cheatsheets/data-visualization.pdf
- forcats : https://rstudio.github.io/cheatsheets/factors.pdf
- lubridate : https://rstudio.github.io/cheatsheets/lubridate.pdf
Documentation Utilit’R : https://book.utilitr.org/03_Fiches_thematiques/Fiche_datatable.html.
Aide-mémoire (Cheatsheets) :
Documentation Utilit’R pour arrow
: https://book.utilitr.org/03_Fiches_thematiques/Fiche_arrow.html.
Documentation Utilit’R pour duckdb
: https://book.utilitr.org/03_Fiches_thematiques/Fiche_duckdb.html.
1.4 Documentation pour RStudio
Sans objet pour SAS.
Si vous utilisez l’IDE RStudio
: https://rstudio.github.io/cheatsheets/rstudio-ide.pdf
Plusieurs raccourcis clavier sont notamment très utiles :
Raccourci | Effet |
---|---|
Alt et - | -> |
Ctrl et Shift et m | %>% |
Ctrl et Entrée | Exécuter le code sélectionné ou de la ligne où se trouve le curseur |
Alt et Entrée | Exécuter le code jusqu’à la ligne où se trouve le curseur |
Ctrl et Shift et a | Reformater automatiquement le code sélectionné pour qu’il soit plus lisible |
Alt et flèche de droite ou de gauche | Aller directement à la fin (flèche de droite) ou au début (flèche de gauche) de la ligne |
Alt et flèche du haut ou du bas | Intervertir la ligne avec celle du dessus (flèche du haut) ou du dessous (flèche du bas) |
Ctrl et flèche de droite ou de gauche | Passer d’un mot à l’autre de la ligne |
Alt et déplacement du curseur de la souris en haut ou bas | Permet de modifier simultanément le même emplacement de plusieurs lignes successives |
Ctrl et Shift et U | Met en minuscule les caractères sélectionnés |
Si vous utilisez l’IDE RStudio
: https://rstudio.github.io/cheatsheets/rstudio-ide.pdf
Plusieurs raccourcis clavier sont notamment très utiles :
Raccourci | Effet |
---|---|
Alt et - | -> |
Ctrl et Shift et m | %>% |
Ctrl et Entrée | Exécuter le code sélectionné ou de la ligne où se trouve le curseur |
Alt et Entrée | Exécuter le code jusqu’à la ligne où se trouve le curseur |
Ctrl et Shift et a | Reformater automatiquement le code sélectionné pour qu’il soit plus lisible |
Alt et flèche de droite ou de gauche | Aller directement à la fin (flèche de droite) ou au début (flèche de gauche) de la ligne |
Alt et flèche du haut ou du bas | Intervertir la ligne avec celle du dessus (flèche du haut) ou du dessous (flèche du bas) |
Ctrl et flèche de droite ou de gauche | Passer d’un mot à l’autre de la ligne |
Alt et déplacement du curseur de la souris en haut ou bas | Permet de modifier simultanément le même emplacement de plusieurs lignes successives |
Ctrl et Shift et U | Met en minuscule les caractères sélectionnés |
Si vous utilisez l’IDE RStudio
: https://rstudio.github.io/cheatsheets/rstudio-ide.pdf
Plusieurs raccourcis clavier sont notamment très utiles :
Raccourci | Effet |
---|---|
Alt et - | -> |
Ctrl et Shift et m | %>% |
Ctrl et Entrée | Exécuter le code sélectionné ou de la ligne où se trouve le curseur |
Alt et Entrée | Exécuter le code jusqu’à la ligne où se trouve le curseur |
Ctrl et Shift et a | Reformater automatiquement le code sélectionné pour qu’il soit plus lisible |
Alt et flèche de droite ou de gauche | Aller directement à la fin (flèche de droite) ou au début (flèche de gauche) de la ligne |
Alt et flèche du haut ou du bas | Intervertir la ligne avec celle du dessus (flèche du haut) ou du dessous (flèche du bas) |
Ctrl et flèche de droite ou de gauche | Passer d’un mot à l’autre de la ligne |
Alt et déplacement du curseur de la souris en haut ou bas | Permet de modifier simultanément le même emplacement de plusieurs lignes successives |
Ctrl et Shift et U | Met en minuscule les caractères sélectionnés |
Sans objet pour pandas.
2 Importation des données
2.1 Mode d’emploi de l’aide-mémoire
Les codes informatiques sont appliqués sur une base de données illustrative fictive sur les formations. Cette base est importée à cette étape. Aussi, pour répliquer les codes sur sa machine, le lecteur doit d’abord exécuter le code d’importation de la base de données ci-dessous.
Les codes sont majoritairement exécutables indépendamment les uns des autres. Les codes de la partie “Les jointures de bases” nécessitent cependant l’importation des bases réalisée lors de la première section de la partie.
2.2 Création d’une base de données d’exemple
/* Données fictives sur des formations */
data donnees_sas;
infile cards dsd dlm='|';
format Identifiant $3. Sexe 1. CSP $1. Niveau $30. Date_naissance ddmmyy10. Date_entree ddmmyy10. Duree Note_Contenu Note_Formateur Note_Moyens
4.1 CSPF $25. Sexef $5.;
Note_Accompagnement Note_Materiel poids_sondage input Identifiant $ Sexe CSP $ Niveau $ Date_naissance :ddmmyy10. Date_entree :ddmmyy10. Duree Note_Contenu Note_Formateur Note_Moyens
Note_Accompagnement Note_Materiel poids_sondage CSPF $ Sexef $;cards;
173|2|1|Qualifié|17/06/1998|01/01/2021|308|12|6|17|4|19|117.1|Cadre|Femme
173|2|1|Qualifié|17/06/1998|01/01/2022|365|6||12|7|14|98.3|Cadre|Femme
173|2|1|Qualifié|17/06/1998|06/01/2022|185|8|10|11|1|9|214.6|Cadre|Femme
173|2|1|Non qualifié|17/06/1998|02/01/2023|365|14|15|15|10|8|84.7|Cadre|Femme
174|1|1|Qualifié|08/12/1984|17/08/2021|183|17|18|20|15|12|65.9|Cadre|Homme
175|1|1|Qualifié|16/09/1989|21/12/2022|730|5|5|8|4|9|148.2|Cadre|Homme
198|2|3|Non qualifié|17/03/1987|28/07/2022|30|10|10|10|16|8|89.6|Employé|Femme
198|2|3|Qualifié|17/03/1987|17/11/2022|164|11|7|6|14|13|100.3|Employé|Femme
198|2|3|Qualifié|17/03/1987|21/02/2023|365|9|20|3|4|17|49.3|Employé|Femme
168|1|2|Qualifié|30/07/2002|04/09/2019|365|18|11|20|13|15|148.2|Profession intermédiaire|Homme
211|2|3|Non qualifié||17/12/2021|135|16|16|15|12|9|86.4|Employé|Femme
278|1|5|Qualifié|10/08/1948|07/06/2018|365|14|10|6|8|12|99.2|Retraité|Homme
347|2|5|Qualifié|13/09/1955||180|12|5|7|11|12|105.6|Retraité|Femme
112|1|3|Non qualifié|13/09/2001|02/03/2022|212|3|10|11|9|8|123.1|Employé|Homme
112|1|3|Non qualifié|13/09/2001|01/03/2021|365|7|13|8|19|2|137.4|Employé|Homme
112|1|3|Qualifié|13/09/2001|01/12/2023|365|9|||||187.6|Employé|Homme
087|2|4|Non qualifié|||365||10||||87.3|Ouvrier|Femme
087|2|4|Non qualifié||31/10/2020|365|||11|||87.3|Ouvrier|Femme
099|1|4|Qualifié|06/06/1998|01/03/2021|364|12|11|10|12|13|169.3|Ouvrier|Homme
099|1|4|Qualifié|06/06/1998|01/03/2022|364|12|11|10|12|13|169.3|Ouvrier|Homme
099|1|4|Qualifié|06/06/1998|01/03/2023|364|12|11|10|12|13|169.3|Ouvrier|Homme
187|2|2|Qualifié|05/12/1986|01/01/2022|364|10|10|10|10|10|169.3|Profession intermédiaire|Femme
187|2|2|Qualifié|05/12/1986|01/01/2023|364|10|10|10|10|10|234.1|Profession intermédiaire|Femme
689|1|1||01/12/2000|06/11/2017|123|9|7|8|13|16|189.3|Cadre|Homme
765|1|4|Non qualifié|26/12/1995|17/04/2020|160|13|10|12|18|10|45.9|Ouvrier|Homme
765|1|4|Non qualifié|26/12/1995|17/04/2020|160|13|10|12|18|10|45.9|Ouvrier|Homme
765|1|4|Non qualifié|26/12/1995|17/04/2020|160|13|10|12|18|10|45.9|Ouvrier|Homme
;run;
/* Ajout de variables utiles */
data donnees_sas;
set donnees_sas;
/* Date de sortie du dispositif : ajout de la durée à la date d'entrée */
format date_sortie ddmmyy10.;
intnx('day', date_entree, duree);
date_sortie = /* Âge à l'entrée dans le dispositif */
intck('year', date_naissance, date_entree);
Age = run;
# Données fictives sur des formations
library(lubridate)
<- data.frame(
donnees_rbase Identifiant = c("173", "173", "173", "173", "174", "175", "198", "198", "198", "168", "211", "278", "347", "112", "112", "112", "087", "087", "099", "099", "099", "187", "187", "689", "765", "765", "765"),
Sexe = c("2", "2", "2", "2", "1", "1", "2", "2", "2", "1", "2", "1", "2", "1", "1", "1", "2", "2", "1", "1", "1", "2", "2", "1", "1", "1", "1"),
CSP = c("1", "1", "1", "1", "1", "1", "3", "3", "3", "2", "3", "5", "5", "3", "3", "3", "4", "4", "4", "4", "4", "2", "2", "1", "4", "4", "4"),
Niveau = c("Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié",
"Non qualifié", "Qualifié", "Non qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", NA, "Non qualifié", "Non qualifié", "Non qualifié"),
Date_naissance = c("17/06/1998", "17/06/1998", "17/06/1998", "17/06/1998", "08/12/1984", "16/09/1989", "17/03/1987", "17/03/1987", "17/03/1987", "30/07/2002", NA, "10/08/1948",
"13/09/1955", "13/09/2001", "13/09/2001", "13/09/2001", NA, NA, "06/06/1998", "06/06/1998", "06/06/1998", "05/12/1986", "05/12/1986", "01/12/2000", "26/12/1995", "26/12/1995", "26/12/1995"),
Date_entree = c("01/01/2021", "01/01/2022", "06/01/2022", "02/01/2023", "17/08/2021", "21/12/2022", "28/07/2022", "17/11/2022", "21/02/2023", "04/09/2019", "17/12/2021", "07/06/2018", NA, "02/03/2022", "01/03/2021", "01/12/2023", NA,
"31/10/2020", "01/03/2021", "01/03/2022", "01/03/2023", "01/01/2022", "01/01/2023", "06/11/2017", "17/04/2020", "17/04/2020", "17/04/2020"),
Duree = c("308", "365", "185", "365", "183", "730", "30", "164", "365", "365", "135", "365", "180", "212", "365", "365", "365", "365", "364", "364", "364", "364", "364", "123", "160", "160", "160"),
Note_Contenu = c("12", "6", "8", "14", "17", "5", "10", "11", "9", "18", "16", "14", "12", "3", "7", "9", NA, NA, "12", "12", "12", "10", "10", "9", "13", "13", "13"),
Note_Formateur = c("6", NA, "10", "15", "18", "5", "10", "7", "20", "11", "16", "10", "5", "10", "13", NA, "10", NA, "11", "11", "11", "10", "10", "7", "10", "10", "10"),
Note_Moyens = c("17", "12", "11", "15", "20", "8", "10", "6", "3", "20", "15", "6", "7", "11", "8", NA, NA, "11", "10", "10", "10", "10", "10", "8", "12", "12", "12"),
Note_Accompagnement = c("4", "7", "1", "10", "15", "4", "16", "14", "4", "13", "12", "8", "11", "9", "19", NA, NA, NA, "12", "12", "12", "10", "10", "13", "18", "18", "18"),
Note_Materiel = c("19", "14", "9", "8", "12", "9", "8", "13", "17", "15", "9", "12", "12", "8", "2", NA, NA, NA, "13", "13", "13", "10", "10", "16", "10", "10", "10"),
poids_sondage = c("117.1", "98.3", "214.6", "84.7", "65.9", "148.2", "89.6", "100.3", "49.3", "148.2", "86.4", "99.2", "105.6", "123.1", "137.4", "187.6", "87.3", "87.3",
"169.3", "169.3", "169.3", "169.3", "234.1", "189.3", "45.9", "45.9", "45.9"),
CSPF = c("Cadre", "Cadre", "Cadre", "Cadre", "Cadre","Cadre", "Employé", "Employé", "Employé", "Profession intermédiaire", "Employé", "Retraité", "Retraité", "Employé",
"Employé", "Employé", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Profession intermédiaire", "Profession intermédiaire", "Cadre", "Ouvrier", "Ouvrier",
"Ouvrier"),
Sexef = c("Femme", "Femme", "Femme", "Femme", "Homme", "Homme", "Femme", "Femme", "Femme", "Homme", "Femme", "Homme", "Femme", "Homme", "Homme", "Homme", "Femme", "Femme",
"Homme", "Homme", "Homme", "Femme", "Femme", "Homme", "Homme", "Homme", "Homme")
)
# Mise en forme des données
# R est sensible à la casse, il est pertinent d'harmoniser les noms des variables en minuscule
colnames(donnees_rbase) <- tolower(colnames(donnees_rbase))
# On a importé toutes les variables en format caractère
# On convertit certaines variables en format numérique
<- c("duree", "note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
enNumerique <- lapply(donnees_rbase[, enNumerique], as.integer)
donnees_rbase[, enNumerique] $poids_sondage <- as.numeric(donnees_rbase$poids_sondage)
donnees_rbase
# On récupère les variables dont le nom débute par le mot "date"
<- names(donnees_rbase)[grepl("date", tolower(names(donnees_rbase)))]
enDate # On exprime les dates en format Date
<- lapply(donnees_rbase[, enDate], lubridate::dmy)
donnees_rbase[, enDate]
# Date de sortie du dispositif
$date_sortie <- donnees_rbase$date_entree + lubridate::days(donnees_rbase$duree)
donnees_rbase
# Âge à l'entrée dans le dispositif
$age <- floor(lubridate::time_length(difftime(donnees_rbase$date_entree, donnees_rbase$date_naissance), "years")) donnees_rbase
# Données fictives sur des formations
library(tidyverse)
library(lubridate)
<- tibble(
donnees_tidyverse Identifiant = c("173", "173", "173", "173", "174", "175", "198", "198", "198", "168", "211", "278", "347", "112", "112", "112", "087", "087", "099", "099", "099", "187", "187", "689", "765", "765", "765"),
Sexe = c("2", "2", "2", "2", "1", "1", "2", "2", "2", "1", "2", "1", "2", "1", "1", "1", "2", "2", "1", "1", "1", "2", "2", "1", "1", "1", "1"),
CSP = c("1", "1", "1", "1", "1", "1", "3", "3", "3", "2", "3", "5", "5", "3", "3", "3", "4", "4", "4", "4", "4", "2", "2", "1", "4", "4", "4"),
Niveau = c("Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié",
"Non qualifié", "Qualifié", "Non qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", NA, "Non qualifié", "Non qualifié", "Non qualifié"),
Date_naissance = c("17/06/1998", "17/06/1998", "17/06/1998", "17/06/1998", "08/12/1984", "16/09/1989", "17/03/1987", "17/03/1987", "17/03/1987", "30/07/2002", NA, "10/08/1948",
"13/09/1955", "13/09/2001", "13/09/2001", "13/09/2001", NA, NA, "06/06/1998", "06/06/1998", "06/06/1998", "05/12/1986", "05/12/1986", "01/12/2000", "26/12/1995", "26/12/1995", "26/12/1995"),
Date_entree = c("01/01/2021", "01/01/2022", "06/01/2022", "02/01/2023", "17/08/2021", "21/12/2022", "28/07/2022", "17/11/2022", "21/02/2023", "04/09/2019", "17/12/2021", "07/06/2018", NA, "02/03/2022", "01/03/2021", "01/12/2023", NA,
"31/10/2020", "01/03/2021", "01/03/2022", "01/03/2023", "01/01/2022", "01/01/2023", "06/11/2017", "17/04/2020", "17/04/2020", "17/04/2020"),
Duree = c("308", "365", "185", "365", "183", "730", "30", "164", "365", "365", "135", "365", "180", "212", "365", "365", "365", "365", "364", "364", "364", "364", "364", "123", "160", "160", "160"),
Note_Contenu = c("12", "6", "8", "14", "17", "5", "10", "11", "9", "18", "16", "14", "12", "3", "7", "9", NA, NA, "12", "12", "12", "10", "10", "9", "13", "13", "13"),
Note_Formateur = c("6", NA, "10", "15", "18", "5", "10", "7", "20", "11", "16", "10", "5", "10", "13", NA, "10", NA, "11", "11", "11", "10", "10", "7", "10", "10", "10"),
Note_Moyens = c("17", "12", "11", "15", "20", "8", "10", "6", "3", "20", "15", "6", "7", "11", "8", NA, NA, "11", "10", "10", "10", "10", "10", "8", "12", "12", "12"),
Note_Accompagnement = c("4", "7", "1", "10", "15", "4", "16", "14", "4", "13", "12", "8", "11", "9", "19", NA, NA, NA, "12", "12", "12", "10", "10", "13", "18", "18", "18"),
Note_Materiel = c("19", "14", "9", "8", "12", "9", "8", "13", "17", "15", "9", "12", "12", "8", "2", NA, NA, NA, "13", "13", "13", "10", "10", "16", "10", "10", "10"),
poids_sondage = c("117.1", "98.3", "214.6", "84.7", "65.9", "148.2", "89.6", "100.3", "49.3", "148.2", "86.4", "99.2", "105.6", "123.1", "137.4", "187.6", "87.3", "87.3",
"169.3", "169.3", "169.3", "169.3", "234.1", "189.3", "45.9", "45.9", "45.9"),
CSPF = c("Cadre", "Cadre", "Cadre", "Cadre", "Cadre","Cadre", "Employé", "Employé", "Employé", "Profession intermédiaire", "Employé", "Retraité", "Retraité", "Employé",
"Employé", "Employé", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Profession intermédiaire", "Profession intermédiaire", "Cadre", "Ouvrier", "Ouvrier",
"Ouvrier"),
Sexef = c("Femme", "Femme", "Femme", "Femme", "Homme", "Homme", "Femme", "Femme", "Femme", "Homme", "Femme", "Homme", "Femme", "Homme", "Homme", "Homme", "Femme", "Femme",
"Homme", "Homme", "Homme", "Femme", "Femme", "Homme", "Homme", "Homme", "Homme")
)
# Mise en forme des données
# R est sensible à la casse, il est pertinent d'harmoniser les noms des variables en minuscule
<- donnees_tidyverse %>% rename_with(tolower)
donnees_tidyverse
# On a importé toutes les variables en format caractère
# On convertit certaines variables en format numérique
<- c("duree", "note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
enNumerique # On convertit certaines variables au format date
# On récupère d'abord les variables dont le nom débute par le mot "date"
<- names(donnees_tidyverse)[grepl("^date", tolower(names(donnees_tidyverse)))]
enDate
# Conversion proprement dite
<- donnees_tidyverse %>%
donnees_tidyverse mutate(across(all_of(enNumerique), as.integer)) %>%
mutate(poids_sondage = as.numeric(poids_sondage)) %>%
mutate(across(all_of(enDate), lubridate::dmy))
# Date de sortie du dispositif
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_sortie = date_entree + lubridate::days(duree)) %>%
# Âge à l'entrée dans le dispositif
mutate(age = as.period(interval(start = date_naissance, end = date_entree))$year)
# Données fictives sur des formations
library(data.table)
library(lubridate)
<- data.table(
donnees_datatable Identifiant = c("173", "173", "173", "173", "174", "175", "198", "198", "198", "168", "211", "278", "347", "112", "112", "112", "087", "087", "099", "099", "099", "187", "187", "689", "765", "765", "765"),
Sexe = c("2", "2", "2", "2", "1", "1", "2", "2", "2", "1", "2", "1", "2", "1", "1", "1", "2", "2", "1", "1", "1", "2", "2", "1", "1", "1", "1"),
CSP = c("1", "1", "1", "1", "1", "1", "3", "3", "3", "2", "3", "5", "5", "3", "3", "3", "4", "4", "4", "4", "4", "2", "2", "1", "4", "4", "4"),
Niveau = c("Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié",
"Non qualifié", "Qualifié", "Non qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", NA, "Non qualifié", "Non qualifié", "Non qualifié"),
Date_naissance = c("17/06/1998", "17/06/1998", "17/06/1998", "17/06/1998", "08/12/1984", "16/09/1989", "17/03/1987", "17/03/1987", "17/03/1987", "30/07/2002", NA, "10/08/1948",
"13/09/1955", "13/09/2001", "13/09/2001", "13/09/2001", NA, NA, "06/06/1998", "06/06/1998", "06/06/1998", "05/12/1986", "05/12/1986", "01/12/2000", "26/12/1995", "26/12/1995", "26/12/1995"),
Date_entree = c("01/01/2021", "01/01/2022", "06/01/2022", "02/01/2023", "17/08/2021", "21/12/2022", "28/07/2022", "17/11/2022", "21/02/2023", "04/09/2019", "17/12/2021", "07/06/2018", NA, "02/03/2022", "01/03/2021", "01/12/2023", NA,
"31/10/2020", "01/03/2021", "01/03/2022", "01/03/2023", "01/01/2022", "01/01/2023", "06/11/2017", "17/04/2020", "17/04/2020", "17/04/2020"),
Duree = c("308", "365", "185", "365", "183", "730", "30", "164", "365", "365", "135", "365", "180", "212", "365", "365", "365", "365", "364", "364", "364", "364", "364", "123", "160", "160", "160"),
Note_Contenu = c("12", "6", "8", "14", "17", "5", "10", "11", "9", "18", "16", "14", "12", "3", "7", "9", NA, NA, "12", "12", "12", "10", "10", "9", "13", "13", "13"),
Note_Formateur = c("6", NA, "10", "15", "18", "5", "10", "7", "20", "11", "16", "10", "5", "10", "13", NA, "10", NA, "11", "11", "11", "10", "10", "7", "10", "10", "10"),
Note_Moyens = c("17", "12", "11", "15", "20", "8", "10", "6", "3", "20", "15", "6", "7", "11", "8", NA, NA, "11", "10", "10", "10", "10", "10", "8", "12", "12", "12"),
Note_Accompagnement = c("4", "7", "1", "10", "15", "4", "16", "14", "4", "13", "12", "8", "11", "9", "19", NA, NA, NA, "12", "12", "12", "10", "10", "13", "18", "18", "18"),
Note_Materiel = c("19", "14", "9", "8", "12", "9", "8", "13", "17", "15", "9", "12", "12", "8", "2", NA, NA, NA, "13", "13", "13", "10", "10", "16", "10", "10", "10"),
poids_sondage = c("117.1", "98.3", "214.6", "84.7", "65.9", "148.2", "89.6", "100.3", "49.3", "148.2", "86.4", "99.2", "105.6", "123.1", "137.4", "187.6", "87.3", "87.3",
"169.3", "169.3", "169.3", "169.3", "234.1", "189.3", "45.9", "45.9", "45.9"),
CSPF = c("Cadre", "Cadre", "Cadre", "Cadre", "Cadre","Cadre", "Employé", "Employé", "Employé", "Profession intermédiaire", "Employé", "Retraité", "Retraité", "Employé",
"Employé", "Employé", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Profession intermédiaire", "Profession intermédiaire", "Cadre", "Ouvrier", "Ouvrier",
"Ouvrier"),
Sexef = c("Femme", "Femme", "Femme", "Femme", "Homme", "Homme", "Femme", "Femme", "Femme", "Homme", "Femme", "Homme", "Femme", "Homme", "Homme", "Homme", "Femme", "Femme",
"Homme", "Homme", "Homme", "Femme", "Femme", "Homme", "Homme", "Homme", "Homme")
)
# Mise en forme des données
# R est sensible à la casse, il est pertinent d'harmoniser les noms des variables en minuscule
colnames(donnees_datatable) <- tolower(colnames(donnees_datatable))
# On a importé toutes les variables en format caractère
# On convertit certaines variables en format numérique
<- c("duree", "note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
enNumerique lapply(.SD, as.integer), .SDcols = enNumerique]
donnees_datatable[, # Autre solution
# En data.table, les instructions débutant par set modifient les éléments par référence, c'est-à-dire sans copie.
# Ceci est plus efficace pour manipuler des données volumineuses.
for (j in enNumerique) {
set(donnees_datatable, j = j, value = as.numeric(donnees_datatable[[j]]))
}:= as.numeric(poids_sondage)]
donnees_datatable[, poids_sondage
# On récupère les variables dont le nom débute par le mot "date"
<- names(donnees_datatable)[grepl("date", tolower(names(donnees_datatable)))]
varDates # On exprime les dates en format Date
:= lapply(.SD, lubridate::dmy), .SDcols = varDates]
donnees_datatable[, (varDates)
# Date de sortie du dispositif
:= date_entree + lubridate::days(duree)]
donnees_datatable[, date_sortie
# Âge à l'entrée dans le dispositif
:= floor(lubridate::time_length(difftime(donnees_datatable$date_entree, donnees_datatable$date_naissance), "years"))] donnees_datatable[, age
Duckdb est un serveur SQL séparé de la session R. Les calculs sont effectués en dehors de R
et l’espace mémoire est distinct de celui de R
. Au lieu d’accéder directement aux données, il faut passer par un objet connection qui contient l’adresse du serveur, un peu comme lorsque l’on se connecte à un serveur web. Ici en particulier, il est nécessaire de transférer les données vers duckdb
.
# Ouvrir une connexion au serveur duckdb
<- DBI::dbConnect(duckdb::duckdb());
con
# On "copie" les données dans une table du nom table_duckdb
# Données fictives sur des formations
%>% duckdb::duckdb_register(name = "table_duckdb", df = donnees_tidyverse)
con
%>% tbl("table_duckdb")
con
# Fermer la connexion au serveur duckdb
::dbDisconnect(con, shutdown = TRUE) DBI
Pour la suite, on suppose que la connexion est ouverte sous le nom con
, et que les données sont accessibles par la requête requete_duckdb
. Le code modifiera la requête, mais pas la table dans le serveur SQL.
<- DBI::dbConnect(duckdb::duckdb());
con %>% duckdb::duckdb_register(name = "table_duckdb", df = donnees_tidyverse)
con <- con %>% tbl("table_duckdb") requete_duckdb
N.B. Duckdb est envisagé pour des traitements sans charger des données en mémoire, par exemple en lisant directement un fichier .parquet
sur le disque dur. Dans ce cas, les opérations sont effectuées à la volée, mais n’affectent pas les fichiers source.
= pd.DataFrame({
donnees_python "Identifiant": ["173", "173", "173", "173", "174", "175", "198", "198", "198", "168", "211", "278", "347", "112", "112", "112", "087", "087", "099", "099", "099", "187", "187", "689", "765", "765", "765"],
"Sexe": ["2", "2", "2", "2", "1", "1", "2", "2", "2", "1", "2", "1", "2", "1", "1", "1", "1", "1", "1", "1", "1", "2", "2", "1", "1", "1", "1"],
"CSP": ["1", "1", "1", "1", "1", "1", "4", "4", "4", "2", "3", "5", "5", "3", "3", "3", "3", "3", "3", "3", "3", "2", "2", "1", "4", "4", "4"],
"Niveau": ["Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Non qualifié", "Qualifié", "Qualifié", "Non qualifié",
"Non qualifié", "Qualifié", "Non qualifié", "Non qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", "Qualifié", None, "Non qualifié", "Non qualifié", "Non qualifié"],
"Date_naissance": ["17/06/1994", "17/06/1995", "17/06/1998", "17/06/1998", "08/12/1984", "16/09/1989", "17/03/1987", "17/03/1987", "17/03/1987", "30/07/2002", None, "10/08/1948", "13/09/1955", "13/09/2001", "13/09/2001", "13/09/2001", None, None, "06/06/1998", "06/06/1998", "06/06/1998", "05/12/1986", "05/12/1986", "01/12/2000", "26/12/1995", "26/12/1995", "26/12/1995"],
"Date_entree": ["01/01/2021", "01/01/2021", "06/01/2022", "02/01/2023", "17/08/2021", "21/12/2022", "28/07/2022", "17/11/2022", "21/02/2023", "04/09/2019", "17/12/2021", "07/06/2018", None, "02/03/2022", "01/03/2021", "01/12/2023", None, "31/10/2020", "01/03/2021", "01/03/2022", "01/03/2023", "01/01/2022", "01/01/2023", "06/11/2017", "17/04/2020", "17/04/2020", "17/04/2020"],
"Duree": ["308", "365", "185", "365", "183", "730", "30", "164", "365", "365", "135", "365", "180", "212", "365", "365", "365", "365", "364", "364", "364", "364", "364", "123", "160", "160", "160"],
"Note_Contenu": ["12", "6", "8", "14", "17", "5", "10", "11", "9", "18", "16", "14", "12", "3", "7", None, None, None, "12", "12", "12", "10", "10", "9", "13", "13", "13"],
"Note_Formateur": ["6", None, "10", "15", "18", "5", "10", "7", "20", "11", "16", "10", "5", "10", "13", None, None, None, "11", "11", "11", "10", "10", "7", "10", "10", "10"],
"Note_Moyens": ["17", "12", "11", "15", "20", "8", "10", "6", "3", "20", "15", "6", "7", "11", "8", None, None, None, "10", "10", "10", "10", "10", "8", "12", "12", "12"],
"Note_Accompagnement": ["4", "7", "1", "10", "15", "4", "16", "14", "4", "13", "12", "8", "11", "9", "19", None, None, None, "12", "12", "12", "10", "10", "13", "18", "18", "18"],
"Note_Materiel": ["19", "14", "9", "8", "12", "9", "8", "13", "17", "15", "9", "12", "12", "8", "2", None, None, None, "13", "13", "13", "10", "10", "16", "10", "10", "10"],
"poids_sondage": ["117.1", "98.3", "214.6", "84.7", "65.9", "148.2", "89.6", "100.3", "49.3", "148.2", "86.4", "99.2", "105.6", "123.1", "137.4", "187.6", "87.3", "87.3", "169.3", "169.3", "169.3", "169.3", "234.1", "189.3", "45.9", "45.9", "45.9"],
"CSPF": ["Cadre", "Cadre", "Cadre", "Cadre", "Cadre","Cadre", "Employé", "Employé", "Employé", "Profession intermédiaire", "Employé", "Retraité", "Retraité", "Employé", "Employé", "Employé", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Ouvrier", "Profession intermédiaire", "Profession intermédiaire", "Cadre", "Ouvrier", "Ouvrier", "Ouvrier"],
"Sexef": ["Femme", "Femme", "Femme", "Femme", "Homme", "Homme", "Femme", "Femme", "Femme", "Homme", "Femme", "Homme", "Femme", "Homme", "Homme", "Homme", "Femme", "Femme", "Homme", "Homme", "Homme", "Femme", "Femme", "Homme", "Homme", "Homme", "Homme"]
})
# Mise en forme des données
# Python est sensible à la casse, il est pertinent d'harmoniser les noms des variables en minuscule
= donnees_python.columns.str.lower()
donnees_python.columns
# On convertit certaines variables en format numérique
= ["duree", "note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel"]
enNumerique = donnees_python[enNumerique].astype(float)
donnees_python[enNumerique] 'poids_sondage'] = donnees_python['poids_sondage'].astype(float)
donnees_python[
# Colonnes à convertir en date
= ['date_naissance', 'date_entree']
enDate = donnees_python[enDate].apply(pd.to_datetime, format='%d/%m/%Y', errors='coerce')
donnees_python[enDate]
# Date de sortie du dispositif
'date_sortie'] = donnees_python['date_entree'] + pd.to_timedelta(donnees_python['duree'], unit='D')
donnees_python[
# Âge à l'entrée dans le dispositif
'age'] = np.floor((donnees_python['date_entree'] - donnees_python['date_naissance']).dt.days / 365.25).astype('Int64') donnees_python[
2.3 Manipulation du format de la base de données
Sans objet pour SAS
.
# On vérifie que la base importée est bien un data.frame
is.data.frame(donnees_rbase)
# Format de la base
class(donnees_rbase)
# On vérifie que la base importée est bien un tibble
is_tibble(donnees_tidyverse)
# Transformation en tibble, le format de Tidyverse
<- as_tibble(donnees_tidyverse)
donnees_tidyverse
# Format de la base
class(donnees_tidyverse)
# On vérifie que la base est bien un data.table
is.data.table(donnees_datatable)
# Transformation en data.frame
setDF(donnees_datatable)
is.data.frame(donnees_datatable)
# Transformation en data.table
# En data.table, les instructions débutant par set modifient les éléments par référence, c'est-à-dire sans copie.
# Ceci est plus efficace pour manipuler des données volumineuses.
setDT(donnees_datatable)
is.data.table(donnees_datatable)
# Autre possibilité
<- as.data.table(donnees_datatable)
donnees_datatable
# La data.table est une liste
is.list(donnees_datatable)
# Format de la base
class(donnees_datatable)
type(donnees_python)
2.4 Importation de données extérieures
Importer des données extérieures dans SAS
ou R
est sans doute la première tâche à laquelle est confronté l’utilisateur de ces logiciels. Ce point important est décrit sur le site Utilit’R : https://www.book.utilitr.org/03_fiches_thematiques/fiche_import_fichiers_plats.
Pour importer des fichiers :
- plats (.csv, .tsv, .txt, etc.) : https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_fichiers_plats.html
SAS
: https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_tables_sas.html- issus de tableurs (Excel, Calc) : https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_tableurs.html
- parquet : https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_fichiers_parquet.html
Quelques éléments additionnels non couverts dans Utilit’R sont présentés ici.
2.4.1 La fonction readLines() de R
La fonction readLines() de R
peut s’avérer utile lors de l’importation de fichiers très volumineux. Elle permet de n’importer que les premières lignes du fichier, sans importer l’ensemble de la base, et ainsi de visualiser rapidement le contenu des données et la nature de l’éventuel séparateur de colonnes.
Les options de la fonction utiles sont :
- con : chemin du fichier à importer
- n : nombre maximal de lignes du fichier lues
- encoding : définir l’encodage du fichier (“UTF-8” ou “latin1”)
2.4.2 Spécificité des environnements
/* Importer un fichier xls */
/* proc import out = NomBaseImportee
datafile = "CHEMIN DE LA BASE"
DBMS = XLS REPLACE;
run; */
/* Importer un fichier avec séparateur | */
/* data NomDeLaBase;
infile "CHEMIN DE LA BASE IMPORTEE" dlm = "|" missover dsd firstobs = 2;
informat VARIABLES;
format VARIABLES;
input VARIABLES;
run; */
On utilisera les fonctions read.table, read.csv et read.csv2.
On utilisera les fonctions du package readr : https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_fichiers_plats.html#importer-un-fichier-avec-le-package-readr.
On utilisera la fonction fread : https://book.utilitr.org/03_Fiches_thematiques/Fiche_import_fichiers_plats.html#importer-un-fichier-avec-le-package-data.table.
Une option utile non présentée dans le lien est : keepLeadingZeros. Si cette option est valorisée à TRUE, les valeurs numériques précédées par des 0 seront importées sous forme caractère et le zéro initial sera conservé.
On utilisera les fonctions pd.read_csv() pour lire les fichiers CSV.
#Pour les fichiers SAS :
#from sas7bdat import SAS7BDAT
#import retrying
#file_path = 'chemin/nom_fichier.sas7bdat'
#with SAS7BDAT(file_path) as reader:
# data = reader.to_data_frame()
3 Préambule
3.1 Chemin du bureau de l’utilisateur
/* On vide la log */
dm "log; clear; ";
/* On récupère déjà l'identifiant de l'utilisateur (systèmes Windows) */
%let user = &sysuserid;
/* Chemin proprement dit */
%let bureau = C:\Users\&user.\Desktop;
libname bur "&bureau.";
# On récupère déjà l'identifiant de l'utilisateur
<- Sys.getenv("USERNAME")
user
# Chemin proprement dit
<- file.path("C:/Users", user, "Desktop") chemin
# On récupère déjà l'identifiant de l'utilisateur
<- Sys.getenv("USERNAME")
user
# Chemin proprement dit
<- file.path("C:/Users", user, "Desktop") chemin
# On récupère déjà l'identifiant de l'utilisateur
<- Sys.getenv("USERNAME")
user
# Chemin proprement dit
<- file.path("C:/Users", user, "Desktop") chemin
# On récupère déjà l'identifiant de l'utilisateur
= os.getenv('USERNAME')
user
# Chemin en texte
= "C:/Users/" + str(user) + "/Desktop" chemin
3.2 Affichage de l’année
/* Année courante */
%let annee = %sysfunc(year(%sysfunc(today())));
/* & (esperluette) indique à SAS qu'il doit remplacer an par sa valeur définie par le %let */
%put Année : &annee.;
/* Autre possibilité */
data _null_;call symput('annee', strip(year(today())));run;
%put Année (autre méthode) : &annee.;
/* Année passée */
%put Année passée : %eval(&annee. - 1);
# Année courante
<- lubridate::year(Sys.Date())
annee sprintf("Année : %04d", annee)
print(paste0("Année : ", annee))
# Autre possibilité
print(paste0("Année : ", format(Sys.Date(), "%Y")))
# Année passée
<- annee - 1
annee_1 paste0("Année passée : ", annee_1)
# Année courante
<- lubridate::year(Sys.Date())
annee sprintf("Année : %04d", annee)
print(paste0("Année : ", annee))
# Autre possibilité
print(paste0("Année : ", format(Sys.Date(), "%Y")))
# Année passée
<- annee - 1
annee_1 paste0("Année passée : ", annee_1)
# Année courante
<- lubridate::year(Sys.Date())
annee sprintf("Année : %04d", annee)
print(paste0("Année : ", annee))
# Autre possibilité
print(paste0("Année : ", format(Sys.Date(), "%Y")))
# Année passée
<- annee - 1
annee_1 paste0("Année passée : ", annee_1)
# Année courante
= datetime.now().year
annee # Afficher l'année actuelle
print("Année :", annee)
# Année passée
= annee - 1
annee_1 print("Année passée :", annee_1)
3.3 Construction des instructions if / else
%macro Annee(an);
%if &an. >= 2024 %then %put Nous sommes en 2024 ou après !;
%else %put Nous sommes avant 2024 !;
%mend Annee;
%Annee(&annee.);
# Construction incorrecte ! Le else doit être sur la même ligne que le {
#if (annee >= 2024) {
# print("Nous sommes en 2024 ou après !")
#}
#else {
# print("Nous sommes avant 2024 !")
#}
# Construction correcte ! Le else doit être sur la même ligne que le {
if (annee >= 2024) {
print("Nous sommes en 2024 ou après !")
else {
} print("Nous sommes avant 2024 !")
}
# Construction incorrecte ! Le else doit être sur la même ligne que le {
#if (annee >= 2024) {
# print("Nous sommes en 2024 ou après !")
#}
#else {
# print("Nous sommes avant 2024 !")
#}
# Construction correcte ! Le else doit être sur la même ligne que le {
if (annee >= 2024) {
print("Nous sommes en 2024 ou après !")
else {
} print("Nous sommes avant 2024 !")
}
# Construction incorrecte ! Le else doit être sur la même ligne que le {
#if (annee >= 2024) {
# print("Nous sommes en 2024 ou après !")
#}
#else {
# print("Nous sommes avant 2024 !")
#}
# Construction correcte ! Le else doit être sur la même ligne que le {
if (annee >= 2024) {
print("Nous sommes en 2024 ou après !")
else {
} print("Nous sommes avant 2024 !")
}
if annee >= 2024:
print("Nous sommes en 2024 ou après !")
else:
print("Nous sommes avant 2024 !")
3.4 Répertoire de travail
/* Afficher le répertoire de travail par défaut (la Work) */
%let chemin_work = %sysfunc(pathname(work));
%put &chemin_work.;
/* Autre solution */
proc sql;
select path from dictionary.libnames where libname = "WORK";
quit;
/* Définir le répertoire de travail, si besoin */
/* libname "nom du répertoire"; */
# Afficher le répertoire de travail
getwd()
# Définir le répertoire de travail, si besoin
#setwd(dir = "nom du répertoire")
# Afficher le répertoire de travail
getwd()
# Définir le répertoire de travail, si besoin
#setwd(dir = "nom du répertoire")
# Afficher le répertoire de travail
getwd()
# Définir le répertoire de travail, si besoin
#setwd(dir = "nom du répertoire")
# Afficher le répertoire de travail
os.getcwd()
3.5 Autres points à connaître
Mise en garde : certains codes SAS
pourraient aussi avec profit être écrits en langage SAS IML
(Interactive Matrix Language). Cet aide-mémoire n’ayant pas vocation à être un dictionnaire SAS
, cette méthode d’écriture n’est pas proposée ici.
R base
est réputé plus lent que ses concurrents, ce qui est souvent vrai. Mais certaines fonctions en R base
peuvent être très rapides (rowsum, rowSums, colSums, rowMeans, colMeans, tapply, etc.)
# Le pipe permet d'enchaîner des opérations sur une même base.
# Il n'est pas réservé au tidyverse, et peut être utilisé avec R-Base et data.table.
1:10 |> sum()
tidyverse
promeut l’utilisation du pipe (%>%), qui permet d’enchaîner des opérations sur une même base modifiée successivement. 2 types de pipes existent, le pipe de magrittr (%>%) et le pipe de R-Base (|>, à partir de la version 4.1) Les fonctionnalités simples des deux opérateurs sont identiques, mais il existe des différences. Dans cet aide-mémoire, le pipe de magrittr (%>%) est privilégié.
Le tidyverse peut s’utiliser sans pipe, mais le pipe simplifie la gestion des programmes. Les autres environnements (R base
, data.table
) peuvent aussi se présenter avec le pipe.
# Principe de base de data.table
# dt[i, j, by = ]
# dt : nom de la base en format data.table (instruction FROM de SQL)
# i : sélection de lignes (instructions WHERE et ORDER de SQL)
# j : sélection et manipulation de colonnes (instruction SELECT de SQL)
# by = : groupements (instruction GROUP BY de SQL)
# L'instruction HAVING de SQL peut être obtenue par une seconde instruction de sélection, par exemple :
# dt[i, j, by = ][SOMME > VALEUR]
4 Informations sur la base de données
4.1 Avoir une vue d’ensemble des données
/* Statistiques globales sur les variables numériques */
proc means data = donnees_sas n mean median min p10 p25 median p75 p90 max;var _numeric_;run;
/* Statistiques globales sur les variables caractères */
proc freq data = donnees_sas;tables _character_ / missing;run;
# Informations sur les variables
str(donnees_rbase)
# Statistiques descriptives des variables de la base
summary(donnees_rbase)
library(Hmisc)
::describe(donnees_rbase)
Hmisc
# Visualiser la base de données
View(donnees_rbase)
# Informations sur les variables
%>% str()
donnees_tidyverse %>% glimpse()
donnees_tidyverse
# Statistiques descriptives des variables de la base
%>% summary()
donnees_tidyverse
# Visualiser la base de données
%>% View() donnees_tidyverse
# Informations sur les variables
str(donnees_datatable)
# Statistiques descriptives des variables de la base
summary(donnees_datatable)
# Visualiser la base de données
View(donnees_datatable)
On accède aux données du serveur SQL DuckDB au travers de l’objet requete_duckdb
, qui est une requête (avec l’adresse du serveur) et non pas un dataframe
ou un tibble
. Comme l’accès n’est pas direct, la plupart des fonctions du tidyverse fonctionnent, mais opèrent sur “l’adresse du serveur DuckDB” au lieu d’opérer sur les valeurs (nombres, chaînes de caractères). A part glimpse
, la plupart des fonctions ne renvoient pas un résultat exploitable.
# Informations sur les variables
# requete_duckdb %>% str()
%>% glimpse() # préférer glimpse()
requete_duckdb # requete_duckdb %>% summary()
# requete_duckdb %>% View()
# Informations sur les variables
# donnees_python.info()
# Statistiques descriptives des variables de la base
# donnees_python.describe()
4.2 Afficher le type des variables
proc contents data = donnees_sas;run;
sapply(donnees_rbase, class)
::map(donnees_tidyverse, class)
purrrclass(donnees_tidyverse)
lapply(.SD, class)] donnees_datatable[,
On ne peut pas appliquer directement la fonction class
sur un objet de type connection
. Cependant, DuckDB affiche le type des variables dans un print. On peut également appliquer la fonction class
sur un extrait des données (après collect
).
::map(requete_duckdb %>% select(c(1,2)) %>% head() %>% collect(), class)
purrrclass(requete_duckdb)
### Afficher le type des variables :
donnees_python.dtypes
4.3 Extraire les x premières lignes de la base (10 par défaut)
%let x = 10;
proc print data = donnees_sas (firstobs = 1 obs = &x.);run;
/* Ou alors */
data Lignes&x.;set donnees_sas (firstobs = 1 obs = &x.);proc print;run;
<- 10
x 1:x, ]
donnees_rbase[head(donnees_rbase, x)
<- 10
x %>%
donnees_tidyverse slice(1:x)
<- 10
x first(.SD, x)]
donnees_datatable[, 1:x]]
donnees_datatable[, .SD[first(donnees_datatable, x)
head(donnees_datatable, x)
DuckDB affiche les dix premières lignes par défaut lorsque l’on évalue une requête, comme indiqué dans le code ci-dessous.
requete_duckdb# Ceci est équivalent au code suivant
# requete_duckdb %>% print(n=10)
Attention, comme il n’y a pas d’ordre en SQL, il faut ordonner les lignes si on veut un résultat reproductible. C’est une opération qui peut être couteuse en temps CPU.
%>% arrange(duree) %>% print() requete_duckdb
L’objet requete_duckdb
est bien une requête (i.e. une liste à deux éléments) même si on peut en afficher le résultat avec la fonction print
. Notamment, les informations restent dans la mémoire de DuckDB. Il faut demander explicitement le transfert du résultat vers la session R avec la fonction collect()
. On obtient alors un objet de type data.frame
ou au lieu de tbl_duckdb_connection
.
class(requete_duckdb)
<- requete_duckdb %>% collect()
resultat_tibble class(resultat_tibble)
La fonction collect()
transfère l’ensemble des données. Pour obtenir uniquement 10 lignes, il faut utiliser l’une des fonctions slice_*
(cf documentation). On conseille slice_min
ou slice_max
qui indiquent explicitement l’ordre utilisé.
%>% slice_max(duree, n=4, with_ties=FALSE) # with_ties = TRUE retourne les cas d'égalité, donc plus de 4 lignes requete_duckdb
En DuckDB et/ou sur un serveur SQL, on déconseille les fonctions head
(qui ne respecte pas toujours l’ordre indiqué par arrange
) ou top_n
(superseded). La fonction slice
en fonctionne pas : elle ne peut pas respecter l’ordre.
= 10
x
donnees_python.head(x)# Autre méthode : la spécificité de Python est que l'on commence à compter à partir de 0
# La première ligne se situe en position 0
0:x, :] donnees_python.iloc[
4.4 Extraire les x dernières lignes de la base (10 par défaut)
%let x = 10;
proc sql noprint;select count(*) into :total_lignes from donnees_sas;quit;
%let deb = %eval(&total_lignes. - &x. + 1);
data Lignes_&x.;set donnees_sas (firstobs = &deb. obs = &total_lignes.);run;
<- 10
x tail(donnees_rbase, x)
# Autre possibilité
nrow(donnees_rbase) - x ) : nrow(donnees_rbase), ]
donnees_rbase[ (
# Les parenthèses sont importantes. Comparer les deux expressions ! Bon exemple du recycling
nrow(donnees_rbase) - x ) : nrow(donnees_rbase)
( nrow(donnees_rbase) - x : nrow(donnees_rbase)
<- 10
x %>%
donnees_tidyverse slice( (n() - x) : n())
<- 10
x last(.SD, x)]
donnees_datatable[, tail(.SD, x)]
donnees_datatable[, last(donnees_datatable, x)
tail(donnees_datatable, x)
Mêmes remarques que pour les premières lignes : il n’y a pas d’ordre a priori en SQL. On conseille slice_min
ou slice_max
qui indiquent explicitement l’ordre utilisé, et l’on déconseille slice
et tail
.
%>% slice_min(duree, n=5, with_ties=FALSE) # with_ties = TRUE retourne les cas d'égalité, donc plus de 5 lignes requete_duckdb
= 10
x donnees_python.tail(x)
4.5 Nombre de lignes et de colonnes dans la base
/* Nombre de lignes */
proc sql;select count(*) as Nb_Lignes from donnees_sas;quit;
proc sql;
select count(*) as Nb_Lignes, count(distinct identifiant) as Nb_Identifiants
from donnees_sas;
quit;
/* Nombre de colonnes */
proc sql;select count(*) as Nb_Colonnes from Var;run;
# Les syntaxes dim(donnees_rbase)[1] et dim(donnees_rbase)[2] sont plus rapides que nrow() et ncol()
sprintf("Nombre de lignes : %d | Nombre de colonnes : %d", dim(donnees_rbase)[1], dim(donnees_rbase)[2])
sprintf("Nombre de lignes : %d | Nombre de colonnes : %d", nrow(donnees_rbase), ncol(donnees_rbase))
sprintf("Nombre de lignes : %d | Nombre de colonnes : %d",
%>% nrow(),
donnees_tidyverse %>% ncol())
donnees_tidyverse
# Nombre de lignes
%>% nrow()
donnees_tidyverse # Nombre de colonnes
%>% ncol() donnees_tidyverse
dim(donnees_datatable) ; dim(donnees_datatable)[1] ; dim(donnees_datatable)[2]
dim(donnees_datatable) ; nrow(donnees_datatable) ; ncol(donnees_datatable)
sprintf("Nombre de lignes : %d | Nombre de colonnes : %d", dim(donnees_datatable)[1], dim(donnees_datatable)[2])
# Autre solution rapide pour le nombre de lignes
donnees_datatable[, .N]
Duckdb/SQL ne connaît pas le nombre de lignes sans un calcul. Il faut faire count()
.
#Nombre de lignes
%>% nrow() # retourne NA
requete_duckdb %>% count() # correct
requete_duckdb
#Nombre de colonnes
%>% ncol() requete_duckdb
donnees_python.shapeprint('Nombre de lignes : ' + str(donnees_python.shape[0]))
print('Nombre de colonnes : ' + str(donnees_python.shape[1]))
4.6 Les variables de la base
/* Par ordre d'apparition dans la base */
proc contents data = donnees_sas out = Var noprint;run;
proc sql;select name into :nom_col separated by " " from Var order by varnum;run;
/* On affiche les noms des variables */
%put Liste des variables : &nom_col.;
/* Par ordre alphabétique */
proc contents data = donnees_sas out = Var noprint;run;
proc sql;select name into :nom_col separated by " " from Var;run;
/* On affiche les noms des variables */
%put Liste des variables : &nom_col.;
/* On supprime la base Var temporaire */
proc datasets lib = Work nolist;delete Var;run;
# Les variables par ordre d'apparition dans la base
names(donnees_rbase)
colnames(donnees_rbase)
# Les variables par ordre alphabétique
ls(donnees_rbase)
sort(colnames(donnees_rbase))
# Les variables par ordre d'apparition dans la base
%>% names()
donnees_tidyverse %>% colnames()
donnees_tidyverse
# Les variables par ordre alphabétique
%>% colnames() %>% sort() donnees_tidyverse
# Les variables par ordre d'apparition dans la base
names(donnees_datatable)
colnames(donnees_datatable)
# Les variables par ordre alphabétique
sort(colnames(donnees_datatable))
%>% colnames() requete_duckdb
donnees_python.columns
4.7 Mettre les noms des variables en minuscule
R
est sensible à la casse, il est pertinent d’harmoniser les noms des variables en minuscule.
Sans objet, SAS
n’est pas sensible à la casse.
colnames(donnees_rbase) <- tolower(colnames(donnees_rbase))
# Autre possibilité
setNames(donnees_rbase, tolower(names(donnees_rbase)))
<- donnees_tidyverse %>% rename_with(tolower)
donnees_tidyverse
# Autre solution
<- donnees_tidyverse %>%
donnees_tidyverse ::set_colnames(value = casefold(colnames(.), upper = FALSE)) magrittr
colnames(donnees_datatable) <- tolower(colnames(donnees_datatable))
= donnees_python.columns.str.lower() donnees_python.columns
4.8 Nombre d’identifiants uniques et de lignes dans la base
proc sql;
select count(*) as Nb_Lignes, count(distinct identifiant) as Nb_Identifiants_Uniques
from donnees_sas;
quit;
sprintf("La base de données contient %d lignes et %d identifiants uniques !",
nrow(donnees_rbase),
length(unique(donnees_rbase$identifiant)))
sprintf("La base de données contient %d lignes et %d identifiants uniques !",
%>% nrow(),
donnees_tidyverse %>% select(identifiant) %>%
donnees_tidyverse n_distinct()
)# Autre solution pour le nombre d'identifiants uniques
%>% select(identifiant) %>% n_distinct()
donnees_tidyverse %>% distinct(identifiant) %>% nrow() donnees_tidyverse
sprintf("La base de données contient %d lignes et %d identifiants uniques !",
nrow(donnees_datatable),
uniqueN(identifiant)]) donnees_datatable[,
%>% nrow()
requete_duckdb %>% distinct(identifiant) %>% count() requete_duckdb
Note : on a vu que nrow
ne fonctionne pas en DuckDB.
'identifiant']).nunique() (donnees_python[
4.9 Quelle est la position de la variable date_entree ?
%let var = date_entree;
proc contents data = donnees_sas out = Var noprint;run;
proc sql;
select varnum as Position from Var where lowcase(NAME) = "&var.";
run;
<- "date_entree"
variable <- match(variable, names(donnees_rbase))
pos sprintf("La variable %s se trouve en colonne n°%s !", variable, pos)
<- "date_entree"
variable <- match(variable, donnees_tidyverse %>% colnames())
pos sprintf("La variable %s se trouve en colonne n°%s !", variable, pos)
<- "date_entree"
variable <- match(variable, names(donnees_datatable))
pos sprintf("La variable %s se trouve en colonne n°%s !", variable, pos)
<- "date_entree"
variable <- match(variable, requete_duckdb %>% colnames())
pos sprintf("La variable %s se trouve en colonne n°%s !", variable, pos)
# Attention, Python commence à compter à partir de 0
# Si date_entree est la première colonne, alors on affichera 0
= "date_entree"
variable = donnees_python.columns.get_loc(variable)
pos print(f"La variable {variable} se trouve en colonne n°{pos} !")
4.10 Variables qui débutent par le mot Note
proc contents data = donnees_sas out = Variables;run;
proc sql;select Name from Variables where upcase(substr(Name, 1, 4)) = "NOTE";run;
grep("^note", names(donnees_rbase), ignore.case = TRUE, value = TRUE)
# Autre possibilité
names(donnees_rbase)[grepl("^note", names(donnees_rbase), ignore.case = TRUE)]
names(donnees_tidyverse) %>% str_subset("^note")
grep("^note", names(donnees_datatable), ignore.case = TRUE, value = TRUE)
# Autre possibilité
names(donnees_datatable)[grepl("^note", names(donnees_datatable), ignore.case = TRUE)]
import re
# Obtenir les noms des colonnes qui commencent par "note" en ignorant la casse
= list(filter(lambda col: re.match(r'^note', col, re.IGNORECASE), donnees_python.columns))
columns_with_note columns_with_note
4.11 Variables qui se terminent par le mot Naissance
proc contents data = donnees_sas out = Variables;run;
proc sql;
select Name from Variables
where upcase(substr(NAME, length(Name) - %length(NAISSANCE) + 1, length(name))) = "NAISSANCE";
run;
grep("naissance$", names(donnees_rbase), ignore.case = TRUE, value = TRUE)
# Autre possibilité
names(donnees_rbase)[grepl("naissance$", names(donnees_rbase), ignore.case = TRUE)]
names(donnees_tidyverse) %>% str_subset("naissance$")
grep("naissance$", names(donnees_datatable), ignore.case = TRUE, value = TRUE)
# Autre possibilité
names(donnees_datatable)[grepl("naissance$", names(donnees_datatable), ignore.case = TRUE)]
= list(filter(lambda col: re.search(r'naissance$', col, re.IGNORECASE), donnees_python.columns))
columns_with_naissance columns_with_naissance
5 Sélection de colonnes
5.1 Sélectionner une colonne par sa position
%let pos = 1;
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;
select name into :nom_col separated by " "
from Var
where varnum = &pos.;
run;
data Colonnes;set donnees_sas (keep = &nom_col.);run;
proc datasets lib = Work nolist;delete Var;run;
<- 1
pos # Résultat sous forme de vecteur caractère
<- donnees_rbase[[pos]] ; class(id)
id <- donnees_rbase[, pos] ; class(id)
id
# Résultat sous forme de data.frame
<- donnees_rbase[pos] ; class(id)
id # Attention, utilisation du drop = FALSE étrange
# En fait, l'affectation par [] a pour option par défaut drop = TRUE. Ce qui implique que si l'affectation renvoie un data.frame d'1 seule colonne, l'objet sera transformé en objet plus simple (vecteur en l'occurrence)
<- donnees_rbase[, pos, drop = FALSE] ; class(id) id
# Sous forme de vecteur
<- donnees_tidyverse %>% pull(1)
id class(id)
<- 1
pos <- donnees_tidyverse %>% pull(all_of(pos))
id class(id)
# Sous forme de tibble
<- donnees_tidyverse %>% select(1)
id class(id)
<- 1
pos <- donnees_tidyverse %>% select(all_of(pos))
id class(id)
<- 1
pos # Résultat sous forme de vecteur caractère
<- donnees_datatable[[pos]] ; class(id)
id
# Résultat sous forme de data.table
<- donnees_datatable[pos] ; class(id) id
En DuckDB, il y a une vraie différence entre select
et pull
. Dans le premier cas, les calculs restent du côté DuckDB, et c’est donc le moteur SQL qui continue à exécuter les calculs. Avec pull
, le résultat est un tibble
et les données sont transférées à la session R.
%>% select(3)
requete_duckdb # # Source: SQL [?? x 1]
# # Database: DuckDB v0.10.2 [sebastien.li-thiao-t@Windows 10 x64:R 4.3.2/:memory:]
# csp
# <chr>
# 1 1
# 2 1
# 3 1
# 4 1
# # ℹ more rows
%>% pull(3)
requete_duckdb # [1] "1" "1" "1" "1" "1" "1" "3" "3" "3" "2" "3" "5" "5" "3" "3" "3" "4" "4" "4"
# [20] "4" "4" "2" "2" "1" "4" "4" "4"
= 0 # Contrairement à R, le compte commence à partir de 0 en Python
pos
# Résultat sous forme de vecteur caractère
donnees_python.iloc[:, pos]
# Résultat sous forme de data.frame
donnees_python.iloc[:, [pos]]
5.2 Sélectionner une colonne par son nom
data Colonnes;set donnees_sas (keep = identifiant);run;
data Colonnes;set donnees_sas;keep identifiant;run;
# Résultat sous forme de vecteur caractère
<- donnees_rbase$identifiant ; class(id)
id <- donnees_rbase[["identifiant"]] ; class(id)
id <- donnees_rbase[, "identifiant"] ; class(id)
id
# Résultat sous forme de data.frame
<- donnees_rbase["identifiant"] ; class(id)
id # Attention, utilisation du drop = FALSE étrange
# En fait, l'affectation par [] a pour option par défaut drop = TRUE. Ce qui implique que si l'affectation renvoie
# un data.frame d'1 seule colonne, l'objet sera transformé en objet plus simple (vecteur en l'occurrence)
class(donnees_rbase[, "identifiant", drop = FALSE])
<- donnees_rbase["identifiant"] ; class(id)
id <- donnees_rbase[, "identifiant", drop = FALSE] ; class(id) id
# Sous forme de vecteur
<- donnees_tidyverse %>% pull(identifiant)
id <- donnees_tidyverse %>% pull("identifiant")
id
# Sous forme de tibble
<- donnees_tidyverse %>% select(identifiant)
id <- donnees_tidyverse %>% select("identifiant") id
# Résultat sous forme de vecteur caractère
<- donnees_datatable$identifiant ; class(id)
id <- donnees_datatable[["identifiant"]] ; class(id)
id <- donnees_datatable[, identifiant] ; class(id)
id
# Résultat sous forme de data.table
<- donnees_datatable[, "identifiant"] ; class(id)
id <- donnees_datatable[, .SD, .SDcols = "identifiant"] ; class(id)
id # Ne fonctionnent pas !
#id <- donnees_datatable[, .("identifiant")] ; class(id)
#id <- donnees_datatable[J("identifiant")] ; class(id)
#id <- donnees_datatable[, list("identifiant")] ; class(id)
#id <- donnees_datatable[list("identifiant")] ; class(id)
%>% select(identifiant)
requete_duckdb %>% select("identifiant") # déconseillé
requete_duckdb %>% select(any_of("identifiant")) requete_duckdb
Note : certaines fonction du tidyverse nécessitent de passer par les opérateurs any_of
ou all_of
pour ce genre d’opérations (distinct
par exemple). On conseille de le faire aussi pour select
.
# Résultat sous forme de vecteur caractère
"identifiant"]
donnees_python[
donnees_python.identifiant
# Résultat sous forme de data.frame
"identifiant"]] donnees_python[[
5.3 Selection de colonnes par un vecteur contenant des chaînes de caractères
%let var = identifiant Sexe note_contenu;
data Colonnes;
/* Sélection de colonnes */
set donnees_sas (keep = &var.);
/* Autre solution */
keep &var.;
run;
<- "identifiant"
variable # Résultat sous forme de vecteur caractère
<- donnees_rbase[, variable] ; class(id)
id <- donnees_rbase[[variable]] ; class(id)
id
# Résultat sous forme de data.frame
<- donnees_rbase[variable] ; class(id)
id # Attention, utilisation du drop = FALSE étrange
# En fait, l'affectation par [] a pour option par défaut drop = TRUE. Ce qui implique que si l'affectation renvoie un data.frame d'1 seule colonne, l'objet sera transformé en objet plus simple (vecteur en l'occurrence)
<- donnees_rbase[, variable, drop = FALSE] ; class(id) id
<- "identifiant"
variable # Sous forme de vecteur
<- donnees_tidyverse %>% pull(all_of(variable))
id # Sous forme de tibble
<- donnees_tidyverse %>% select(all_of(variable)) id
# Résultat sous forme de vecteur caractère
<- "identifiant"
variable <- donnees_datatable[[variable]] ; class(id)
id <- donnees_datatable[, get(variable)] ; class(id)
id
# Résultat sous forme de data.table
<- donnees_datatable[, ..variable] ; class(id)
id <- donnees_datatable[, variable, with = FALSE] ; class(id)
id <- donnees_datatable[, .SD, .SDcols = variable] ; class(id)
id <- donnees_datatable[, variable, env = list(variable = as.list(variable))] ; class(id)
id
# Attention, ces syntaxes ne fonctionnent pas ! Il faut nécessairement passer par les syntaxes au-dessus.
#id <- donnees_datatable[, .(variable)] ; class(id)
#id <- donnees_datatable[, list(variable)] ; class(id)
<- c("identifiant","duree")
variable %>% select(any_of(variable)) requete_duckdb
= 'identifiant'
variable
# Résultat sous forme de vecteur caractère
donnees_python[nom_var]
# Résultat sous forme de data.frame
donnees_python[[nom_var]]
5.4 Sauf certaines variables
%let var = identifiant Sexe note_contenu;
data Colonnes;set donnees_sas (drop = &var.);run;
<- c("identifiant", "sexe", "note_contenu")
variable <- donnees_rbase[, setdiff(names(donnees_rbase), variable)]
exclusion_var
# Ne fonctionnent pas !
#exclusion_var <- donnees_rbase[, -c(variable)]
#exclusion_var <- donnees_rbase[, !c(variable)]
<- c("identifiant", "sexe", "note_contenu")
variable <- donnees_tidyverse %>% select(!all_of(variable))
exclusion_var <- donnees_tidyverse %>% select(-all_of(variable)) exclusion_var
<- c("identifiant", "sexe", "note_contenu")
variable <- donnees_datatable[, !..variable] exclusion_var
Les opérateurs -
et !
fonctionnent.
%>% select(!identifiant)
requete_duckdb %>% select(-all_of(variable)) requete_duckdb
= ["identifiant", "sexe_red", "note_contenu"]
variable =variable, axis = 0)
donnees_python.drop(columns# En ajoutant l'argument inplace = True à la fonction .drop(), la base de données est directement modifiée en supprimant les variables du vecteur
5.5 Sélectionner la 3e colonne
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;
select name into :nom_col separated by " "
from Var
where varnum = 3;
run;
data Col3;set donnees_sas (keep = &nom_col.);run;
<- donnees_rbase[, 3]
col3
# Autre possibilité
<- donnees_rbase[3] col3
<- donnees_tidyverse %>% pull(3)
col3
# Autre possibilité
<- donnees_tidyverse %>% select(3) col3
<- donnees_datatable[, 3] col3
%>% select(3) requete_duckdb
# Attention, en Python, la position de la 3e colonne est 2
= 3
pos -1] donnees_python.iloc[:, pos
5.6 Sélectionner plusieurs colonnes
%let var = identifiant note_contenu sexe;
data Colonnes;set donnees_sas (keep = &var.);run;
/* Autre solution */
/* En SQL, les variables sélectionnées dans l'instruction SELECT sont séparées par des virgules. On ajoute des virgules entre les variables. */
proc sql;
create table Colonnes as
select %sysfunc(tranwrd(&var., %str( ), %str(, )))
from donnees_sas;
quit;
<- c("identifiant", "note_contenu", "sexe")
cols <- donnees_rbase[, cols]
colonnes
# Autre possibilité
<- donnees_rbase[cols] colonnes
<- c("identifiant", "note_contenu", "sexe")
cols # Plusieurs possibilités
<- donnees_tidyverse %>% select(all_of(cols))
colonnes <- donnees_tidyverse %>% select(any_of(cols))
colonnes <- donnees_tidyverse %>% select({{ cols }})
colonnes <- donnees_tidyverse %>% select(!!cols) colonnes
<- c("identifiant", "note_contenu", "sexe")
cols # Plusieurs écritures possibles
# Ecriture cohérente avec la logique data.table
<- donnees_datatable[, .SD, .SDcols = cols]
colonnes
# Ecriture avec with = FALSE : désactive la possibilité de se référer à des colonnes sans les guillemets
<- donnees_datatable[, cols, with = FALSE]
colonnes
# Ecriture avec mget
<- donnees_datatable[, mget(cols)]
colonnes
# Ecriture un peu contre-intuitve. Attention ! L'écriture est bien ..cols, et non ..(cols) !!
# Les syntaxes donnees_datatable[, ..(cols)] et donnees_datatable[, .(cols)] ne fonctionnent pas
<- donnees_datatable[, ..cols] colonnes
<- c("identifiant", "note_contenu", "sexe")
cols # Plusieurs possibilités
%>% select(all_of(cols))
requete_duckdb %>% select(any_of(cols))
requete_duckdb %>% select({{ cols }})
requete_duckdb %>% select(!!cols) requete_duckdb
= ["identifiant", "note_contenu", "sexe"]
cols = donnees_python[cols] colonnes
5.7 Sélectionner les colonnes qui débutent par le mot Note
/* 1ère solution */
data Selection_Variables;set donnees_sas (keep = Note:);run;
/* 2e solution */
proc contents data = donnees_sas out = Var noprint;run;
proc sql;
select name into :var_notes separated by " "
from Var where substr(upcase(name), 1, 4) = "NOTE" order by varnum;
run;
proc datasets lib = Work nolist;delete Var;run;
data donnees_sas_Notes;set donnees_sas (keep = &var_notes.);run;
<- donnees_rbase[grepl("^note", names(donnees_rbase), ignore.case = TRUE)]
varNotes
# Autre possibilité
<- donnees_rbase[substr(tolower(names(donnees_rbase)), 1, 4) == "note"] varNotes
<- donnees_tidyverse %>% select(starts_with("note")) varNotes
# 1ère méthode
<- names(donnees_datatable)[substr(names(donnees_datatable), 1, 4) == "note"]
cols # Ou encore
<- names(donnees_datatable)[names(donnees_datatable) %like% "^note"]
cols
<- donnees_datatable[, .SD, .SDcols = cols]
sel
# 2e méthode
<- donnees_datatable[, .SD, .SDcols = patterns("^note")] sel
%>% select(starts_with("note")) requete_duckdb
= donnees_python[list(filter(lambda col: re.match(r'^note', col, re.IGNORECASE), donnees_python.columns))] varNotes
5.8 Sélectionner les colonnes qui ne débutent pas par le mot Note
data Selection_Variables;set donnees_sas (drop = Note:);run;
<- donnees_rbase[! grepl("^note", names(donnees_rbase), ignore.case = TRUE)]
varNotes
# Autre possibilité
<- donnees_rbase[substr(tolower(names(donnees_rbase)), 1, 4) != "note"] varNotes
<- donnees_tidyverse %>% select(-starts_with("note"))
varNotes <- donnees_tidyverse %>% select(!starts_with("note")) varNotes
<- grep("^note", names(donnees_datatable), value = TRUE, ignore.case = TRUE)
cols <- donnees_datatable[, .SD, .SDcols = -cols]
sel <- donnees_datatable[, .SD, .SDcols = -patterns("^note")]
sel
# Autre possibilité
<- donnees_datatable[, grep("^note", names(donnees_datatable)) := NULL] sel
%>% select(-starts_with("note"))
requete_duckdb %>% select(!starts_with("note")) requete_duckdb
= donnees_python.drop(columns=list(filter(lambda col: re.match(r'^note', col, re.IGNORECASE), donnees_python.columns)),
varNotes = 0) axis
5.9 Sélectionner l’ensemble des variables numériques de la base
data Colonnes;set donnees_sas (keep = _numeric_);run;
<- donnees_rbase[, sapply(donnees_rbase, is.numeric), drop = FALSE] varNumeriques
<- donnees_tidyverse %>% select_if(is.numeric)
varNumeriques <- donnees_tidyverse %>% select(where(is.numeric)) varNumeriques
<- donnees_datatable[, .SD, .SDcols = is.numeric] sel
%>% select_if(is.numeric)
requete_duckdb # requete_duckdb %>% select(where(is.numeric))
= donnees_python.select_dtypes(include='number') varNumeriques
5.10 Sélectionner l’ensemble des variables de format “Date”
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;
select name into :nom_col separated by " "
from Var where format not in ("$", "");
run;
data Colonnes;set donnees_sas (keep = &nom_col.);run;
proc datasets lib = Work nolist;delete Var;run;
<- donnees_rbase[, sapply(donnees_rbase, is.Date), drop = FALSE]
varDates <- Filter(is.Date, donnees_rbase) varDates
<- donnees_tidyverse %>% select(where(is.Date))
varDates <- donnees_tidyverse %>% select_if(is.Date) varDates
<- donnees_datatable[, .SD, .SDcols = is.Date] var_dates
%>% select_if(is.Date)
requete_duckdb # requete_duckdb %>% select(where(is.Date))
= donnees_python.select_dtypes(include=['datetime64[ns]']) varDates
6 Sélection de lignes
6.1 Sélectionner des lignes par leur numéro
6.1.1 3e ligne
data Ligne3; set donnees_sas (firstobs = 3 obs = 3); run;
<- donnees_rbase[3, ] ligne3
<- donnees_tidyverse %>% slice(3) ligne3
<- donnees_datatable[3, ]
ligne3 <- donnees_datatable[3] ligne3
DuckDB, moteur SQL, ne respecte pas l’ordre des lignes. Il faut passer par un filtre ou choisir explicitement un ordre.
2] # En Python, la troisieme ligne est en position 2 donnees_python.iloc[
6.1.2 3 premières lignes et 3 premières colonnes
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;
select name into :nom_col separated by " " from Var
where 1 <= varnum <= 3;
run;
data Top3;
set donnees_sas (firstobs = 1 obs = 3 keep = &nom_col.);
run;
proc datasets lib = Work nolist;delete Var;run;
<- donnees_rbase[1:3, 1:3] top3
<- donnees_tidyverse %>% slice(1:3) %>% select(1:3) top3
<- donnees_datatable[1:3, 1:3] top3
DuckDB, moteur SQL, ne respecte pas l’ordre des lignes. Il faut passer par un filtre ou choisir explicitement un ordre.
= donnees_python.iloc[:3, :3] top3
6.2 Sélectionner des lignes par condition
6.2.1 Entrées en 2023
data En2023;
set donnees_sas (where = (year(date_entree) = 2023));
run;
# Bonnes écritures, qui excluent les valeurs manquantes
<- donnees_rbase[lubridate::year(donnees_rbase$date_entree) %in% c(2023), ]
en2023 <- donnees_rbase[which(lubridate::year(donnees_rbase$date_entree) == 2023), ]
en2023 <- subset(donnees_rbase, lubridate::year(donnees_rbase$date_entree) == 2023) en2023
<- donnees_tidyverse %>% filter(lubridate::year(date_entree) == 2023) en2023
# Pas de problème avec les valeurs manquantes comme pour la syntaxe en R-Base
# Une fonction year() est déjà implémentée en data.table, l'usage de lubridate est inutile
<- donnees_datatable[data.table::year(date_entree) == 2023, ]
en2023 <- donnees_datatable[data.table::year(date_entree) == 2023]
en2023 <- subset(donnees_datatable, data.table::year(date_entree) == 2023) en2023
%>% filter(lubridate::year(date_entree) == 2023) requete_duckdb
= donnees_python[donnees_python['date_entree'].dt.year == 2023] en2023
6.2.2 Entrées entre 2021 et 2023
data Entre2021_2023;
set donnees_sas (where = (2021 <= year(date_entree) <= 2023));
run;
<- donnees_rbase[lubridate::year(donnees_rbase$date_entree) %in% 2021:2023, ]
entre2021_2023 <- donnees_rbase[lubridate::year(donnees_rbase$date_entree) >= 2021 & lubridate::year(donnees_rbase$date_entree) <= 2023, ] entre2021_2023
<- donnees_tidyverse %>% filter(between(lubridate::year(date_entree), 2021, 2023))
entre2021_2023 <- donnees_tidyverse %>% filter(lubridate::year(date_entree) %in% 2021:2023)
entre2021_2023 <- donnees_tidyverse %>% filter(lubridate::year(date_entree) >= 2021, lubridate::year(date_entree) <= 2023) entre2021_2023
# Une fonction year() est déjà implémentée en data.table, l'usage de lubridate est inutile
<- donnees_datatable[data.table::year(date_entree) %in% 2021:2023]
entre2021_2023 <- donnees_datatable[between(data.table::year(date_entree), 2021, 2023)] entre2021_2023
%>% filter(between(lubridate::year(date_entree), 2021, 2023)) requete_duckdb
= donnees_python[(donnees_python['date_entree'].dt.year >= 2021) &
en2021_2023 'date_entree'].dt.year <= 2023)] (donnees_python[
6.3 Sélectionner des lignes suivant de multiples conditions
/* Femmes entrées avant 2023 */
/* Ecriture correcte */
data Avant2023_Femme;
set donnees_sas (where = (year(date_entree) < 2023 and not missing(date_entree) and sexe = 2));
run;
/* Ecriture incorrecte. Les valeurs manquantes sont considérées comme des nombres négatifs faibles, et inférieurs à 2023. */
/* Elles sont sélectionnées dans le code suivant : */
data Avant2023_Femme;
set donnees_sas (where = (year(date_entree) < 2023 and sexe = 2));
run;
# Femmes entrées avant 2023
<- subset(donnees_rbase, lubridate::year(date_entree) < 2023 & sexe == "2")
avant2023_femme
# Autre solution
<- with(donnees_rbase, donnees_rbase[which(lubridate::year(date_entree) < 2023 & sexe == "2"), ]) avant2023_femme
# Femmes entrées avant 2023
<- donnees_tidyverse %>%
avant2023_femme filter(lubridate::year(date_entree) < 2023 & sexe == "2")
<- donnees_tidyverse %>%
avant2023_femme filter(lubridate::year(date_entree) < 2023, sexe == "2")
# Femmes entrées avant 2023
# Une fonction year() est déjà implémentée en data.table, l'usage de lubridate est inutile
<- donnees_datatable[data.table::year(date_entree) < 2023 & sexe == "2"]
avant2023_femme <- subset(donnees_datatable, data.table::year(date_entree) < 2023 & sexe == "2") avant2023_femme
%>%
requete_duckdb filter(lubridate::year(date_entree) < 2023 & sexe == "2") # Femmes entrées avant 2023
= donnees_python[(donnees_python['date_entree'].dt.year < 2023) &
avant2023_femme 'sexe'] == "2")] (donnees_python[
6.4 Sélectionner des lignes par référence : lignes de l’identifiant “087”
%let var = identifiant;
%let sel = 087;
data Selection;
set donnees_sas;
if &var. in ("&sel.");
run;
/* Autre solution */
data Selection;
set donnees_sas (where = (&var. in ("&sel.")));
run;
<- "identifiant"
variable <- "087"
sel %in% sel, ]
donnees_rbase[donnees_rbase[, variable]
# Autre solution
subset(donnees_rbase, get(variable) %in% sel)
%>% filter(identifiant %in% c("087")) %>% select(identifiant)
donnees_tidyverse %>% filter(identifiant == "087") %>% select(identifiant)
donnees_tidyverse
# Essayons désormais par variable
<- "identifiant"
variable <- "087"
sel %>% filter(if_any(variable, ~ .x %in% sel)) %>% select(all_of(variable))
donnees_tidyverse %>% filter(get(variable) %in% sel) %>% select(all_of(variable)) donnees_tidyverse
<- "identifiant"
variable <- "087"
sel %chin% sel, ]
donnees_datatable[donnees_datatable[[variable]] get(variable) %chin% sel, ] donnees_datatable[
%>% filter(identifiant %in% c("087")) %>% select(identifiant)
requete_duckdb %>% filter(identifiant == "087") %>% select(identifiant)
requete_duckdb # Essayons désormais par variables
<- "identifiant"
variable <- "087"
sel %>% select(any_of(variable)) requete_duckdb
= "identifiant"
variable = "087"
sel == sel]
donnees_python[donnees_python[variable] donnees_python[donnees_python[variable].isin([sel])]
6.5 Sélectionner des lignes et des colonnes
%let cols = identifiant note_contenu sexe;
data Femmes;
set donnees_sas (where = (Sexe = 2) keep = &cols.);
run;
/* Autre solution */
data Femmes;
set donnees_sas;
if Sexe = 2;
keep &cols.;
run;
/* Par nom ou par variable */
%let var = identifiant Sexe note_contenu;
data Femmes;
/* Sélection de colonnes */
set donnees_sas (keep = &var.);
/* Sélection de lignes respectant une certaine condition */
if Sexe = "2";
/* Création de colonne */
20 * 5;
note2 = note_contenu / /* Suppression de colonnes */
drop Sexe;
/* Selection de colonnes */
keep identifiant Sexe note_contenu;
run;
<- c("identifiant", "note_contenu", "sexe", "date_naissance")
cols <- donnees_rbase[donnees_rbase$sexe %in% c("2"), cols]
femmes <- subset(donnees_rbase, sexe %in% c("2"), cols) femmes
<- c("identifiant", "note_contenu", "sexe", "date_naissance")
cols <- donnees_tidyverse %>% filter(sexe == "2") %>% select(all_of(cols))
femmes <- donnees_tidyverse %>% filter(sexe == "2") %>% select({{cols}}) femmes
<- c("identifiant", "note_contenu", "sexe", "date_naissance")
cols <- donnees_datatable[sexe == "2", ..cols]
femmes <- subset(donnees_datatable, sexe %in% c("2"), cols) femmes
<- c("identifiant", "note_contenu", "sexe", "date_naissance")
cols %>% filter(sexe == "2") %>% select(all_of(cols))
requete_duckdb %>% filter(sexe == "2") %>% select({{cols}}) requete_duckdb
= ["identifiant", "note_contenu", "sexe", "date_naissance"]
cols = donnees_python[donnees_python["sexe"] == "2"][cols] femmes
6.6 Sélectionner des lignes selon une condition externe
On souhaite sélectionner des colonnes selon une condition, mais cette condition est située à l’extérieur des opérateurs de manipulation des données.
%let condition = sexe = 2;
data Femmes;
set donnees_sas (where = (&condition.));
run;
<- substitute(sexe == "2")
condition <- subset(donnees_rbase, eval(condition))
femmes
# Autre solution
<- quote(sexe == "2")
condition <- subset(donnees_rbase, eval(condition)) femmes
<- expr(sexe == "2")
condition <- donnees_tidyverse %>%
femmes filter(!!condition)
<- quote(sexe == "2")
condition <- donnees_datatable[condition, , env = list(condition = condition)]
femmes <- donnees_datatable[eval(condition)] femmes
<- . %>% filter(sexe == "2")
filter_condition %>% filter_condition() requete_duckdb
= lambda df: df['sexe'] == "2"
condition = donnees_python[condition(donnees_python)] femmes
7 Manipulation des lignes et des colonnes
7.1 Renommer des variables
On renomme sexe en sexe2, puis on renomme à son tour sexe2 en sexe.
data donnees_sas;
set donnees_sas (rename = (sexe = sexe2));
rename sexe2 = sexe;
run;
# On renomme la variable sexe en sexe_red
names(donnees_rbase)[names(donnees_rbase) == "sexe"] <- "sexe_red"
# On la renomme en sexe
names(donnees_rbase)[names(donnees_rbase) == "sexe_red"] <- "sexe"
# On renomme la variable sexe en sexe_red
<- donnees_tidyverse %>%
donnees_tidyverse rename(sexe_red = sexe)
# On la renomme en sexe
<- donnees_tidyverse %>%
donnees_tidyverse rename(sexe = sexe_red)
# On renomme la variable sexe en sexe_red
names(donnees_datatable)[names(donnees_datatable) == "sexe"] <- "sexe_red"
# On la renomme en sexe
names(donnees_datatable)[names(donnees_datatable) == "sexe_red"] <- "sexe"
# Autre solution
# En data.table, les instructions débutant par set modifient les éléments par référence, c'est-à-dire sans copie.
# Ceci est plus efficace pour manipuler des données volumineuses.
setnames(donnees_datatable, "sexe", "sexe_red")
setnames(donnees_datatable, "sexe_red", "sexe")
En dplyr/arrow/duckdb, le renommage n’est pas persistant, i.e. la variable requete_duckdb
n’est pas modifiée par la fonction rename
.
# On renomme la variable sexe en sexe_red
%>% rename(sexe_red = sexe) requete_duckdb
# Renommer la colonne sexe en sexe_red
= donnees_python.rename(columns={'sexe': 'sexe_red'})
donnees_python
# On la renomme en sexe
= donnees_python.rename(columns={'sexe_red': 'sexe'}) donnees_python
7.2 Créer des variables avec des conditions
data Civilite;
set donnees_sas;
/* 1ère solution (if) */
format Civilite $20.;
if Sexe = 2 then Civilite = "Mme";
else if Sexe = 1 then Civilite = "M";
else Civilite = "Inconnu";
/* 2e solution (do - end) */
if Sexe = 2 then do;
"Mme";
Civilite2 = end;
else if Sexe = 1 then do;
"M";
Civilite2 = end;
else do;
"Inconnu";
Civilite2 = end;
/* 3e solution (select) */
format Civilite3 $20.;
select;
2) Civilite3 = "Mme";
when (Sexe = 1) Civilite3 = "M";
when (Sexe = "Inconnu";
otherwise Civilite3 = end;
keep Sexe Civilite Civilite2 Civilite3;run;
run;
$civilite <- ifelse(donnees_rbase$sexe == "2", "Mme",
donnees_rbaseifelse(donnees_rbase$sexe == "1", "M",
"Inconnu"))
# Autre solution (rapide)
$civilite <- "Inconnu"
donnees_rbase$civilite[donnees_rbase$sexe == "1"] <- "M"
donnees_rbase$civilite[donnees_rbase$sexe == "2"] <- "Mme" donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse mutate(civilite = case_when(sexe == "2" ~ "Mme",
== "1" ~ "M",
sexe TRUE ~ "Inconnu")
)
<- donnees_tidyverse %>%
donnees_tidyverse mutate(civilite = if_else(sexe == "2", "Mme",
if_else(sexe == "1", "M",
"Inconnu")))
:= fcase(sexe == "2", "Mme",
donnees_datatable[, civilite == "1", "M",
sexe is.na(sexe), "Inconnu")]
Note : l’opération n’est pas persistante, i.e. l’objet requete_duckdb
n’est pas modifié
%>%
requete_duckdb mutate(civilite = case_when(sexe == "2" ~ "Mme",
== "1" ~ "M",
sexe .default = "Inconnu"))
%>%
requete_duckdb mutate(civilite = if_else(sexe == "2", "Mme",
if_else(sexe == "1", "M",
"Inconnu")))
# Avec un mapping :
= {'2': 'Mme', '1': 'M'}
mapping 'civilite'] = donnees_python['sexe'].map(mapping).filna('Inconnu')
donnees_python[
# Avec une fonction apply/lambda et les condition IF/ELSE :
'civilite'] = donnees_python['sexe'].apply(
donnees_python[lambda x: 'Mme' if x == '2' else ('M' if x == '1' else 'Inconnu')
)
7.3 Formater les modalités des valeurs discrètes ou caractères
7.3.1 Création des formats
/* Utilisation des formats */
proc format;
/* Variable discrète */
value sexef1 = "Homme"
2 = "Femme";
/* Variable caractère */
value $ cspf'1' = "Cadre"
'2' = "Profession intermédiaire"
'3' = "Employé"
'4' = "Ouvrier"
'5' = "Retraité";
run;
<- c("1" = "Homme", "2" = "Femme")
sexef <- c("1" = "Cadre", "2" = "Profession intermédiaire", "3" = "Employé", "4" = "Ouvrier", "5" = "Retraité") cspf
<- c("1" = "Homme", "2" = "Femme")
sexef_format <- c("1" = "Cadre", "2" = "Profession intermédiaire", "3" = "Employé", "4" = "Ouvrier", "5" = "Retraité") cspf_format
<- c("1" = "Homme", "2" = "Femme")
sexeform <- c("1" = "Cadre", "2" = "Profession intermédiaire", "3" = "Employé", "4" = "Ouvrier", "5" = "Retraité") cspform
Préférer case_match
quand il s’agit de valeurs déterminées.
%>%
requete_duckdb mutate(sexef = case_when(
=="1" ~ "Homme",
sexef=="2" ~ "Femme",
sexef.default = sexef),
cspf = case_match(csp,
"1" ~ "Cadre",
"2" ~ "Profession intermédiaire",
"3" ~ "Employé",
"4" ~ "Ouvrier",
"5" ~ "Retraité",
.default = csp)) %>%
select(Sexe, sexef, csp, cspf)
# On créée les formats sous type de dictionnaire
= {
sexef_format "1": "Homme",
"2": "Femme"
}= {
cspf_format "1": "Cadre",
"2": "Profession intermédiaire",
"3": "Employé",
"4": "Ouvrier",
"5": "Retraité"
}
7.3.2 Utiliser les formats (valeurs discrètes ou caractères)
Nécessite le lancement des formats à l’étape précédente.
data donnees_sas;
set donnees_sas;
/* Exprimer dans le format sexef (Hommes / Femmes) */
format Sexef $25.;
put(Sexe, sexef.);
Sexef = /* On exprime la CSP en texte dans une variable CSPF avec le format */
format CSPF $25.;
put(CSP, $cspf.);
CSPF = run;
# On exprime CSP et sexe en variable formatée
$cspf <- cspf[donnees_rbase$csp]
donnees_rbase$sexef <- sexef[donnees_rbase$sexe] donnees_rbase
# On exprime CSP et sexe en variable formatée
<- donnees_tidyverse %>%
donnees_tidyverse mutate(sexef = sexef_format[sexe],
cspf = cspf_format[csp])
# Autre solution
# Les éventuelles valeurs manquantes sont conservées en NA
<- donnees_tidyverse %>%
donnees_tidyverse mutate(
sexef = case_when(
== "1" ~ "Homme",
sexe == "2" ~ "Femme",
sexe TRUE ~ sexe),
cspf = case_when(
== "1" ~ "Cadre",
csp == "2" ~ "Profession intermédiaire",
csp == "3" ~ "Employé",
csp == "4" ~ "Ouvrier",
csp == "5" ~ "Retraité",
csp TRUE ~ csp)
)
# Syntaxe pour attribuer une valeur aux NA
<- donnees_tidyverse %>%
valeurAuxNA mutate(sexef = case_when(
== "1" ~ "Homme",
sexe == "2" ~ "Femme",
sexe is.na(x) ~ "Inconnu",
TRUE ~ sexe))
# On exprime CSP et sexe en variable formatée
`:=` (cspf = cspform[csp], sexef = sexeform[sexe])] donnees_datatable[,
'sexef'] = donnees_python['sexe'].map(sexef_format)
donnees_python[# On peut aussi utiliser replace : donnees_python['sexef'] = donnees_python['sexe'].replace(sexef_format)
'cspf'] = donnees_python['csp'].map(cspf_format) donnees_python[
7.4 Formater les modalités des valeurs continues
/* Âge formaté */
/* Fonctionne aussi sur le principe du format */
proc format;
/* Variable continue */
value agef26 = "1. De 15 à 25 ans"
low-<26<-<50 = "2. De 26 à 49 ans"
50-high = "3. 50 ans ou plus";
run;
data donnees_sas;
set donnees_sas;
/* Âge formaté */
put(Age, agef.);
Agef = run;
# Âge formaté
# L'option right = TRUE implique que les bornes sont ]0; 25] / ]25; 49] / ]49; Infini[
<- cut(donnees_rbase$age,
agef breaks = c(0, 25, 49, Inf),
right = TRUE,
labels = c("1. De 15 à 25 ans", "2. De 26 à 49 ans", "3. 50 ans ou plus"),
ordered_result = TRUE)
# Autres solutions
$agef[donnees_rbase$age < 26] <- "1. De 15 à 25 ans"
donnees_rbase# 26 <= donnees_rbase$age < 50 ne fonctionne pas, il faut passer en 2 étapes
$agef[26 <= donnees_rbase$age & donnees_rbase$age < 50] <- "2. De 26 à 49 ans"
donnees_rbase$agef[donnees_rbase$age >= 50] <- "3. 50 ans ou plus"
donnees_rbase
$agef <- ifelse(donnees_rbase$age < 26, "1. De 15 à 25 ans",
donnees_rbaseifelse(26 <= donnees_rbase$age & donnees_rbase$age < 50, "2. De 26 à 49 ans",
ifelse(donnees_rbase$age >= 50, "3. 50 ans ou plus",
NA_integer_)))
# Âge formaté
<- donnees_tidyverse %>%
donnees_tidyverse mutate(agef = case_when(
< 26 ~ "1. De 15 à 25 ans",
age >= 26 & age < 50 ~ "2. De 26 à 49 ans",
age >= 50 ~ "3. 50 ans ou plus")
age )
# Âge formaté
:= fcase(age < 26, "1. De 15 à 25 ans",
donnees_datatable[, agef 26 <= age & age < 50, "2. De 26 à 49 ans",
>= 50, "3. 50 ans ou plus")] age
Préférer case_match
quand il s’agit de valeurs déterminées.
# Âge formaté
%>%
requete_duckdb mutate(agef = case_when(
< 26 ~ "1. De 15 à 25 ans",
age >= 26 | age < 50 ~ "2. De 26 à 49 ans",
age >= 50 ~ "3. 50 ans ou plus")) %>%
age select(age, agef)
# Pour les bins : [0, 26, 51] correspond à [0, 26[, [26, 51[, etc
'agef'] = pd.cut(donnees_python['age'],
donnees_python[=[0, 26, 51, float('inf')],
bins=["1. De 15 à 25 ans", "2. De 26 à 49 ans", "3. 50 ans ou plus"],
labels=False) right
7.5 Changer le type d’une variable
data donnees_sas;
set donnees_sas;
/* Transformer la variable Sexe en caractère */
put(Sexe, $1.);
Sexe_car =
/* Transformer la variable Sexe_car en numérique */
input(Sexe_car, 1.);
Sexe_num =
/* Transformer une date d'un format caractère à un format Date */
format date $10.;
date = "01/01/2000";
format date_sas yymmdd10.;
input(date, ddmmyy10.);
date_sas = run;
# Transformer la variable sexe en numérique
$sexe_numerique <- as.numeric(donnees_rbase$sexe)
donnees_rbase
# Transformer la variable sexe_numerique en caractère
$sexe_caractere <- as.character(donnees_rbase$sexe_numerique)
donnees_rbase
# Transformer une date d'un format caractère à un format Date
$date_r <- lubridate::dmy("01/01/2000") donnees_rbase
# Transformer la variable sexe en numérique
<- donnees_tidyverse %>%
donnees_tidyverse mutate(sexe_numerique = as.numeric(sexe))
# Transformer la variable sexe_numerique en caractère
<- donnees_tidyverse %>%
donnees_tidyverse mutate(sexe_caractere = as.character(sexe_numerique))
# Transformer une date d'un format caractère à un format Date
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_r = lubridate::dmy("01/01/2000"))
# Transformer la variable sexe en numérique
:= as.numeric(sexe)]
donnees_datatable[, sexe_numerique
# Transformer la variable sexe_numerique en caractère
:= as.character(sexe_numerique)]
donnees_datatable[, sexe_caractere
# Transformer une date d'un format caractère à un format Date
:= lubridate::dmy("01/01/2000")] donnees_datatable[, date_r
%>%
requete_duckdb mutate(sexe_numerique = as.numeric(sexe)) %>% # Transformer la variable sexe en numérique
mutate(sexe_caractere = as.character(sexe_numerique)) %>% # Transformer la variable sexe_numerique en caractère
select(starts_with("sexe")) %>% print(n=5)
En DuckDB, plusieurs syntaxes sont possibles pour transformer une chaîne de caractères en date si la chaîne de caractères est au format YYYY-MM-DD
. Dans le cas contraire, passer par la fonction strptime
de DuckDB pour indiquer le format de la date.
# Transformer une date d'un format caractère à un format Date
%>%
requete_duckdb mutate(date_0 = as.Date("2000-01-01")) %>%
mutate(date_1 = as.Date(strptime("01/01/2000","%d/%m/%Y"))) %>%
# mutate(date_r = lubridate::dmy("01/01/2000")) %>% # no known SQL translation
select(starts_with("date"))
Note : duckdb
fait des conversions de type implicitement, mais seulement les conversions incontestables. Il faudra souvent préciser le type des variables.
# Transformer la variable sexe en numérique
'sexe_numerique'] = pd.to_numeric(donnees_python['sexe'])
donnees_python[
# Transformer la variable sexe_numerique en caractère
'sexe_caractere'] = donnees_python['sexe_numerique'].astype(str)
donnees_python[
# Transformer une date d'un format caractère à un format Date
'date_r'] = pd.to_datetime('01/01/2000', format='%d/%m/%Y') donnees_python[
7.6 Changer le type de plusieurs variables À FAIRE
<- c("duree", "note_contenu", "note_formateur")
enNumerique <- c('date_naissance', 'date_entree')
enDate
%>%
requete_duckdb mutate_at(enNumerique, as.integer) %>%
mutate_at(enDate, as.character) %>%
mutate_at(enDate, ~ as.Date(strptime(.,'%Y-%m-%d'))) %>% # strptime est une fonction duckdb
select(enNumerique, enDate) %>% print(n=5)
7.7 Créer et supprimer des variables
7.7.1 1er exemple
/* Manipulation de colonnes par référence */
data Creation;
set donnees_sas;
20 * 5;
note_contenu2 = note_contenu / 20 * 5;
note_formateur2 = note_formateur / /* Suppression des variables créées */
drop note_contenu2 note_formateur2;
run;
$note2 <- donnees_rbase$note_contenu / 20 * 5
donnees_rbase# Le with permet de s'affranchir des expressions "donnees_rbase$"
with(donnees_rbase, note2 <- note_contenu / 20 * 5)
# On ne peut pas utiliser transform pour des variables récemment créées
#donnees_rbase <- transform(donnees_rbase, note3 = note_contenu ** 2, note3 = log(note3))
<- transform(donnees_rbase, note2 = note_contenu / 20 * 5)
donnees_rbase
# Suppression de variables
$note2 <- NULL donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse mutate(note2 = note_contenu / 20 * 5)
# Suppression de variables
<- donnees_tidyverse %>%
donnees_tidyverse select(-note2)
# Création de variables
:= note_contenu / 20 * 5]
donnees_datatable[, note2
# Suppression de variables
:= NULL] donnees_datatable[, note2
Note : l’opération n’est pas persistante, i.e. l’objet requete_duckdb
n’est pas modifié
# Création de la colonne note2
%>%
requete_duckdb mutate(note2 = as.integer(note_contenu) / 20 * 5) %>%
select(note2)
# Suppression de colonnes
%>% select(-contains("date"), -starts_with("note")) requete_duckdb
# Création de la colonne note2
'note2'] = donnees_python['note_contenu'] / 20 * 5
donnees_python[
# Suppression de variables :
'note2'], axis = 1, inplace = True) donnees_python.drop([
7.7.2 2e exemple
/* Création et suppressions de plusieurs variables */
data donnees_sas;
set donnees_sas;
20 * 5;
note_contenu2 = note_contenu / 20 * 5;
note_formateur2 = note_formateur /
/* Suppression des variables créées */
drop note_contenu2 note_formateur2;
run;
# Création et suppressions de plusieurs variables
<- transform(donnees_rbase,
donnees_rbase note_contenu2 = note_contenu / 20 * 5,
note_formateur2 = note_formateur / 20 * 5)
# Suppression des variables créées
<- c("note_contenu2", "note_formateur2")
variable <- NULL donnees_rbase[, variable]
# Création et suppressions de plusieurs variables
<- donnees_tidyverse %>%
donnees_tidyverse mutate(note_contenu2 = note_contenu / 20 * 5,
note_formateur2 = note_formateur / 20 * 5)
# Suppression des variables créées
<- c("note_contenu2", "note_formateur2")
variable <- donnees_tidyverse %>%
donnees_tidyverse select(-all_of(variable))
# Création et suppressions de plusieurs variables
c("note_contenu2", "note_formateur2") := list(note_contenu / 20 * 5, note_formateur / 20 * 5)]
donnees_datatable[, `:=` (note_contenu2 = note_contenu / 20 * 5, note_formateur2 = note_formateur / 20 * 5)]
donnees_datatable[,
# Suppression des variables créées
c("note_contenu2", "note_formateur2") := NULL]
donnees_datatable[,
# Ou par référence extérieure
<- c("note_contenu2", "note_formateur2")
variable `:=` (note_contenu2 = note_contenu / 20 * 5, note_formateur2 = note_formateur / 20 * 5)]
donnees_datatable[, := NULL] donnees_datatable[, (variable)
# À FAIRE : à compléter !
# Création de la colonne note2
%>%
requete_duckdb mutate(note2 = as.integer(Note_Contenu) / 20 * 5) %>%
select(note2)
# Suppression de colonnes
#requete_duckdb %>% select(- CSP, -contains("Date"), -starts_with("Note"))
# Création des colonnes note_contenu2 et note_formateur2
= donnees_python.assign(
donnees_python = donnees_python['note_contenu'] / 20 * 5,
note_contenu2 = donnees_python['note_formateur'] / 20 * 5
note_formateur2
)
# Suppression des variables nouvellement crées
=['note_contenu2', 'note_formateur2'], axis = 1, inplace = True) donnees_python.drop(columns
7.8 On souhaite réexprimer toutes les notes sur 100 et non sur 20
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
/* On supprime d'abord les doubles blancs entre les variables */
%let notes = %sysfunc(compbl(¬es.));
/* On affiche les notes dans la log de SAS */
%put ¬es;
/* 1ère solution : avec les array */
/* Les variables sont modifiées dans cet exemple */
data Sur100_1;
set donnees_sas;
array variables (*) ¬es.;
do increment = 1 to dim(variables);
20 * 100;
variables[increment] = variables[increment] / end;
drop increment;
run;
/* 2e solution : avec une macro */
/* De nouvelles variables sont ajoutées dans cet exemple */
data Sur100_2;
set donnees_sas;
%macro Sur100;
%do i = 1 %to %sysfunc(countw(¬es.));
%let note = %scan(¬es., &i.);
¬e._100 = ¬e. / 20 * 100;
%end;
%mend Sur100;
%Sur100;run;
<- names(donnees_rbase)[grepl("^note", names(donnees_rbase))]
notes
# Les variables sont modifiées dans cet exemple
<- donnees_rbase[, notes] / 20 * 100
sur100
# On souhaite conserver les notes sur 100 dans d'autres variables, suffixées par _100
paste0(notes, "_100")] <- donnees_rbase[, notes] / 20 * 100 donnees_rbase[,
# Les variables sont modifiées dans cet exemple
<- donnees_tidyverse %>%
sur100 mutate(across(starts_with("note"), ~ .x / 20 * 100))
# On souhaite conserver les notes sur 100 dans d'autres variables, suffixées par _100
<- donnees_tidyverse %>%
donnees_tidyverse mutate(across(starts_with("note"), ~ .x / 20 * 100, .names = "{.col}_100"))
<- names(donnees_datatable)[grepl("^note", names(donnees_datatable))]
notes
# Les variables sont modifiées dans cet exemple
<- copy(donnees_datatable)
sur100 <- sur100[, (notes) := lapply(.SD, function(x) x / 20 * 100), .SDcols = notes]
sur100 <- sur100[, (notes) := lapply(.SD, function(x) x / 20 * 100), .SD = notes]
sur100
# Ou encore, plus simple
# Dans cet exemple, les notes dans la base donnees_datatable ne sont pas changées
<- sur100[, lapply(.SD, function(x) x / 20 * 100), .SDcols = patterns("^note")]
sur100
# On souhaite conserver les notes sur 20 dans d'autres variables, suffixées par _20
paste0(notes, "_100")) := lapply(.SD, function(x) x / 20 * 100), .SDcols = notes]
donnees_datatable[, (
# Autre possibilité en utilisant l'instruction set, très rapide
for (j in notes) {
set(x = donnees_datatable, j = paste0(j, "_100"), value = donnees_datatable[[j]] / 20 * 100)
}
%>%
requete_duckdb mutate(across(starts_with("note"), ~ as.numeric(.x)/20*100)) %>%
select(starts_with("note"))
# Sélectionner les colonnes dont les noms commencent par "note"
= [col for col in donnees_python.columns if col.startswith('note')]
notes
# Transformer les colonnes sélectionnées
= donnees_python[notes] / 20 * 100
sur100
# Ajouter les nouvelles colonnes avec un suffixe "_100"
for col in notes:
f"{col}_100"] = sur100[col] donnees_python[
7.9 Mettre un 0 devant un nombre
data Zero_devant;
set donnees_sas (keep = date_entree);
/* Obtenir le mois et la date */
month(date_entree);
Mois = year(date_entree);
Annee = /* Mettre le mois sur 2 positions (avec un 0 devant si le mois <= 9) : format prédéfini z2. */
put(Mois, z2.);
Mois_a = drop Mois;
rename Mois_a = Mois;
run;
$mois <- sprintf("%02d", lubridate::month(donnees_rbase$date_entree)) donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse mutate(mois = sprintf("%02d", lubridate::month(date_entree)))
# Autre solution
<- donnees_tidyverse %>%
donnees_tidyverse mutate(mois = lubridate::month(date_entree),
mois = ifelse(str_length(mois) < 2, paste0("0", mois), mois))
# Une fonction month() est déjà implémentée en data.table, l'usage de lubridate est inutile
:= sprintf("%02d", data.table::month(date_entree))] donnees_datatable[, mois
%>%
requete_duckdb mutate(mois = stringr::str_pad(as.character(month(date_entree)), width = 2L, pad = "0")) %>%
select(mois, date_entree)
# Extraire le mois et l'année
'mois'] = donnees_python['date_entree'].dt.month
donnees_python['annee'] = donnees_python['date_entree'].dt.year
donnees_python[
# Mettre le numéro du mois sur 2 positions (avec un 0 devant si le mois <= 9)
'mois'] = donnees_python['mois'].fillna(0).astype(int).apply(lambda x: f"{x:02d}") donnees_python[
7.10 Arrondir une valeur numérique
data Arrondis;
set donnees_sas (keep = poids_sondage);
/* Arrondi à l'entier le plus proche */
round(poids_sondage);
poids_arrondi_0 = /* Arrondi à 1 chiffre après la virgule */
round(poids_sondage, 0.1);
poids_arrondi_1 = /* Arrondi à 2 chiffre après la virgule */
round(poids_sondage, 0.01);
poids_arrondi_2 = /* Arrondi à l'entier inférieur */
floor(poids_sondage);
poids_inf = /* Arrondi à l'entier supérieur */
ceil(poids_sondage);
poids_sup = run;
# Arrondi à l'entier le plus proche
<- round(donnees_rbase$poids_sondage, 0)
poids_arrondi_0 # Arrondi à 1 chiffre après la virgule
<- round(donnees_rbase$poids_sondage, 1)
poids_arrondi_1 # Arrondi à 2 chiffre après la virgule
<- round(donnees_rbase$poids_sondage, 2)
poids_arrondi_2 # Arrondi à l'entier inférieur
<- floor(donnees_rbase$poids_sondage)
poids_inf # Arrondi à l'entier supérieur
<- ceiling(donnees_rbase$poids_sondage) poids_sup
<- donnees_tidyverse %>%
donnees_tidyverse # Arrondi à l'entier le plus proche
mutate(poids_arrondi_0 = round(poids_sondage, 0)) %>%
# Arrondi à 1 chiffre après la virgule
mutate(poids_arrondi_1 = round(poids_sondage, 1)) %>%
# Arrondi à 2 chiffre après la virgule
mutate(poids_arrondi_2 = round(poids_sondage, 2)) %>%
# Arrondi à l'entier inférieur
mutate(poids_inf = floor(poids_sondage)) %>%
# Arrondi à l'entier supérieur
mutate(poids_sup = ceiling(poids_sondage))
%>% select(starts_with("poids")) donnees_tidyverse
# Arrondi à l'entier le plus proche
:= round(poids_sondage, 0)]
donnees_datatable[, poids_arrondi_0 # Arrondi à 1 chiffre après la virgule
:= round(poids_sondage, 1)]
donnees_datatable[, poids_arrondi_1 # Arrondi à 2 chiffre après la virgule
:= round(poids_sondage, 2)]
donnees_datatable[, poids_arrondi_2 # Arrondi à l'entier inférieur
:= floor(poids_sondage)]
donnees_datatable[, poids_inf # Arrondi à l'entier supérieur
:= ceiling(poids_sondage)] donnees_datatable[, poids_sup
%>%
requete_duckdb mutate( # la fonction round de duckdb ne prend pas l'argument digits, mais la traduction fonctionne
poids_arrondi_0 = round(as.numeric(poids_sondage), 0),
poids_arrondi_1 = round(as.numeric(poids_sondage), 1),
poids_arrondi_2 = round(as.numeric(poids_sondage), -1),
poids_floor = floor(as.numeric(poids_sondage) ),
poids_ceiling = ceiling(as.numeric(poids_sondage) )
%>%
) select(starts_with("poids"))
# Arrondi à l'entier le plus proche
'poids_arrondi_0'] = donnees_python['poids_sondage'].round(0)
donnees_python[
# Arrondi à 1 chiffre après la virgule
'poids_arrondi_1'] = donnees_python['poids_sondage'].round(1)
donnees_python[
# Arrondi à 2 chiffres après la virgule
'poids_arrondi_2'] = donnees_python['poids_sondage'].round(2)
donnees_python[
# Arrondi à l'entier inférieur
'poids_inf'] = np.floor(donnees_python['poids_sondage'])
donnees_python[
# Arrondi à l'entier supérieur
'poids_sup'] = np.ceil(donnees_python['poids_sondage']) donnees_python[
7.11 Corriger une valeur de la base
On souhaite corriger une valeur dans la base. La note_contenu de l’identifiant 168 est en fait 8 et non 18.
data donnees_sas_corr;
set donnees_sas;
if identifiant = "168" then note_contenu = 8;
run;
<- donnees_rbase
donnees_rbase_cor $identifiant == "168", "note_contenu"] <- 8 donnees_rbase_cor[donnees_rbase_cor
https://dplyr.tidyverse.org/reference/rows.html Note : rows_update
ne modifie pas l’objet.
%>%
donnees_tidyverse_cor rows_update(tibble(identifiant = "168", note_contenu = 8), by = "identifiant") # guillemets nécessaires
# Autre solution, qui n'est pas du pur Tidyverse
<- donnees_tidyverse
donnees_tidyverse_cor $identifiant == "168", "note_contenu"] <- 8 donnees_tidyverse_cor[donnees_tidyverse_cor
<- copy(donnees_datatable)
donnees_datatable_cor == "168", note_contenu := 8] donnees_datatable_cor[identifiant
https://dplyr.tidyverse.org/reference/rows.html
C’est compliqué de modifier efficacement une valeur en duckDB.
# Exemple avec rows_update
%>% duckdb::duckdb_register(name = "temp", df = tibble(identifiant = "168", note_contenu = 8), overwrite = TRUE)
con %>%
requete_duckdb rows_update(con %>% tbl("temp"), by = "identifiant", unmatched = "ignore") %>% # guillemets nécessaires
filter(identifiant == "168")
# Il vaut mieux écrire du SQL ou bien faire plusieurs modifications avec case_when
%>%
requete_duckdb mutate(note_contenu = case_when(
== "168" ~ 8,
identifiant .default = note_contenu)) %>%
filter(identifiant == "168")
Note : l’opération n’est pas persistante, i.e. l’objet requete_duckdb
n’est pas modifié
= donnees_python.copy()
donnees_python_cor 'identifiant'] == '168', 'note_contenu'] = 8 donnees_python_cor.loc[donnees_python_cor[
8 Manipulation de dates
Pour en savoir plus sur le fonctionnement des dates en R
: https://book.utilitr.org/03_Fiches_thematiques/Fiche_donnees_temporelles.html.
8.1 Créer une date à partir d’une chaîne de caractères
Créer le 31 décembre de l’année sous forme de date.
/* Pour créer une date avec l'année courante */
%let an = %sysfunc(year(%sysfunc(today())));
data donnees_sas;
set donnees_sas;
/* Deux manières de créer une date */
format Decembre_31_&an._a Decembre_31_&an._b ddmmyy10.;
&an._a = "31dec&an."d;
Decembre_31_/* mdy pour month, day, year (pas d'autre alternative, ymd par exemple n'existe pas) */
&an._b = mdy(12, 31, &an.);
Decembre_31_run;
# Pour créer une date avec l'année courante
<- format(Sys.Date(), "%Y")
annee as.Date(paste0(annee, "-12-31"), origin = "1970-01-01")
::ymd(paste0(annee, "-12-31"))
lubridate::dmy(paste0("31/12/", annee))
lubridate::mdy(paste0("12.31.", annee)) lubridate
# Pour créer une date avec l'année courante
<- format(Sys.Date(), "%Y")
annee as.Date(paste0(annee, "-12-31"))
::ymd(paste0(annee, "-12-31"))
lubridate::dmy(paste0("31/12/", annee))
lubridate::mdy(paste0("12.31.", annee)) lubridate
# Pour créer une date avec l'année courante
<- format(Sys.Date(), "%Y")
annee as.Date(paste0(annee, "-12-31"))
::ymd(paste0(annee, "-12-31"))
lubridate::dmy(paste0("31/12/", annee))
lubridate::mdy(paste0("12.31.", annee)) lubridate
# Pour créer une date avec l'année courante
%>%
requete_duckdb mutate(exemple1 = as.Date("2024/07/14"),
exemple2 = as.Date(strptime("01/01/2000", "%d/%m/%Y"))) %>%
# mutate(date_r = lubridate::dmy("01/01/2000")) %>% # no known SQL translation
select(contains("exemple"))
# Pour créer une date avec l'année courante
= datetime.now().year
annee
# Méthode 1 : Utiliser pandas pour créer une date
f"{annee}-12-31")
pd.to_datetime(f"31/12/{annee}", dayfirst=True, format="%d/%m/%Y")
pd.to_datetime(f"12.31.{annee}", format="%m.%d.%Y")
pd.to_datetime(
# Méthode 2 : Utiliser datetime pour créer une date
f"{annee}-12-31", "%Y-%m-%d") datetime.strptime(
8.2 Calculer sur des dates
Attention, calculer sur des dates est un peu compliqué à cause de cas particuliers. Par exemple, le 29 février, les années bisextiles, le calcul des mois, des semaines, les fuseaux horaires, etc. Calculer en nombre de jours ou secondes ne pose pas de problème en général.
8.2.1 Écart entre deux dates
data donnees_sas;
set donnees_sas;
/* Durée (en année) entre 2 dates */
/* Âge à l'entrée dans le dispositif */
intck('year', date_naissance, date_entree);
Age = /* En mois */
intck('month', date_naissance, date_entree);
Age_mois = /* En jours */
intck('days', date_naissance, date_entree);
Age_jours =
Age_jours_2 = date_entree - date_naissance;run;
# Durée (en année) entre 2 dates
# Âge à l'entrée dans le dispositif
$age <- floor(lubridate::time_length(difftime(donnees_rbase$date_entree, donnees_rbase$date_naissance), "years"))
donnees_rbase
# En mois
$age_mois <- floor(lubridate::time_length(difftime(donnees_rbase$date_entree, donnees_rbase$date_naissance), "months"))
donnees_rbase
# En jours
$age_jours <- floor(lubridate::time_length(difftime(donnees_rbase$date_entree, donnees_rbase$date_naissance), "days"))
donnees_rbase$age_jours_2 <- donnees_rbase$date_entree - donnees_rbase$date_naissance donnees_rbase
# Durée (en année) entre 2 dates
# Âge à l'entrée dans le dispositif
<- donnees_tidyverse %>%
donnees_tidyverse mutate(age = as.period(interval(start = date_naissance, end = date_entree))$year)
# En mois : À FAIRE
# En jours : À FAIRE
<- donnees_tidyverse %>%
donnees_tidyverse mutate(age_jours_2 = date_entree - date_naissance)
# Durée (en année) entre 2 dates
# Âge à l'entrée dans le dispositif
:= floor(lubridate::time_length(difftime(date_entree, date_naissance), "years"))]
donnees_datatable[, age
# En mois
:= floor(lubridate::time_length(difftime(date_entree, date_naissance), "months"))]
donnees_datatable[, age_mois
# En jours
:= floor(lubridate::time_length(difftime(date_entree, date_naissance), "days"))]
donnees_datatable[, age_jours := date_entree - date_naissance] donnees_datatable[, age_jours_2
# Durée entre deux dates
%>%
requete_duckdb mutate(duree_annees = year(age(date_entree,date_naissance)),
duree_mois = month(age(date_entree,date_naissance)),
%>%
) select(contains("duree_"))
'age_jours'] = (donnees_python['date_entree'] - donnees_python['date_naissance']).dt.days
donnees_python[# Remplacer les valeurs NaN par 0
'age_jours'] = np.floor(donnees_python['age_jours'].fillna(0)).astype(int) donnees_python[
8.2.2 Ajouter une durée à une date
/* On utilise ici %sysevalf et non %eval pour des calculs avec des macro-variables non entières */
%let sixmois = %sysevalf(365.25/2);
%put sixmois : &sixmois.;
data donnees_sas;
set donnees_sas;
/* Date de sortie du dispositif : ajout de la durée à la date d'entrée */
format date_sortie ddmmyy10.;
intnx('day', date_entree, duree);
date_sortie =
/* Date 6 mois après la sortie */
format Date_6mois ddmmyy10.;
intnx('month', date_sortie, 6);
Date_6mois =
/* Ajout de jours, cette fois */
format Date_6mois_2 ddmmyy10.;
intnx('days', date_sortie, &sixmois.);
Date_6mois_2 = run;
# Date de sortie du dispositif
$date_sortie <- donnees_rbase$date_entree + lubridate::days(donnees_rbase$duree)
donnees_rbase
# Date 6 mois après la sortie
$date_6mois <- donnees_rbase$date_sortie %m+% months(6)
donnees_rbase$date_6mois <- lubridate::add_with_rollback(donnees_rbase$date_sortie, months(6))
donnees_rbase$date_6mois_2 <- donnees_rbase$date_sortie + lubridate::days(round(365.25/2)) donnees_rbase
# Date de sortie du dispositif
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_sortie = date_entree + lubridate::days(duree))
# Date 6 mois après la sortie
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_6mois = date_sortie %m+% months(6))
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_6mois = lubridate::add_with_rollback(date_sortie, months(6)))
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_6mois_2 = date_sortie + lubridate::days(round(365.25/2)))
# Date de sortie du dispositif
:= date_entree + lubridate::days(duree)]
donnees_datatable[, date_sortie
# Date 6 mois après la sortie
:= date_sortie %m+% months(6)]
donnees_datatable[, date_6mois := lubridate::add_with_rollback(date_sortie, months(6))]
donnees_datatable[, date_6mois := date_sortie + lubridate::days(round(365.25/2))] donnees_datatable[, date_6mois_2
%>%
requete_duckdb mutate(date_sortie = date_entree + duree,
date_6mois = date_sortie + to_months(6L)) %>% # préciser le type de 6
select(date_sortie, date_6mois)
from datetime import timedelta
from dateutil.relativedelta import relativedelta
# Date de sortie du dispositif
'date_sortie'] = donnees_python['date_entree'] + pd.to_timedelta(donnees_python['duree'], unit='D')
donnees_python[
# Ajouter une colonne date_6mois qui est la date six mois après date_sortie
'date_6mois'] = donnees_python['date_sortie'] + pd.DateOffset(months=6) donnees_python[
8.3 Formater les dates
/* On utilise ici %sysevalf et non %eval pour des calculs avec des macro-variables non entières */
%let sixmois = %sysevalf(365.25/2);
%put sixmois : &sixmois.;
data donnees_sas;
set donnees_sas;
/* Âge à l'entrée dans le dispositif */
intck('year', date_naissance, date_entree);
Age =
/* Âge formaté */
put(Age, agef.);
Agef =
/* Date de sortie du dispositif : ajout de la durée à la date d'entrée */
format date_sortie ddmmyy10.;
intnx('day', date_entree, duree);
date_sortie = /* La durée du contrat est-elle inférieure à 6 mois ? */
&sixmois. & Duree ne .);
Duree_Inf_6_mois = (Duree <
/* Deux manières de créer une date */
format Decembre_31_&an._a Decembre_31_&an._b ddmmyy10.;
&an._a = "31dec&an."d;
Decembre_31_
/* mdy pour month, day, year (pas d'autre alternative, ymd par exemple n'existe pas) */
&an._b = mdy(12, 31, &an.);
Decembre_31_
/* Date 6 mois après la sortie */
format Date_6mois ddmmyy10.;
intnx('month', date_sortie, 6);
Date_6mois = run;
/* Ventilation pondérée (cf. infra) */
proc freq data = donnees_sas;tables apres_31_decembre;weight poids_sondage;run;
# Âge à l'entrée dans le dispositif
$age <- floor(lubridate::time_length(difftime(donnees_rbase$date_entree, donnees_rbase$date_naissance), "years"))
donnees_rbase
# Âge formaté
$agef[donnees_rbase$age < 26] <- "1. De 15 à 25 ans"
donnees_rbase# 26 <= donnees_rbase$age < 50 ne fonctionne pas, il faut passer en 2 étapes
$agef[26 <= donnees_rbase$age & donnees_rbase$age < 50] <- "2. De 26 à 49 ans"
donnees_rbase$agef[donnees_rbase$age >= 50] <- "3. 50 ans ou plus"
donnees_rbase
# Autre solution
# L'option right = TRUE implique que les bornes sont ]0; 25] / ]25; 49] / ]49; Infini[
<- cut(donnees_rbase$age,
agef breaks = c(0, 25, 49, Inf),
right = TRUE,
labels = c("1. De 15 à 25 ans", "2. De 26 à 49 ans", "3. 50 ans ou plus"),
ordered_result = TRUE)
# Manipuler les dates
<- 365.25/2
sixmois
# La durée du contrat est-elle inférieure à 6 mois ?
$duree_inf_6_mois <- ifelse(donnees_rbase$duree < sixmois, 1, 0)
donnees_rbase
# Date de sortie du dispositif
$date_sortie <- donnees_rbase$date_entree + lubridate::days(donnees_rbase$duree)
donnees_rbase
# Date 6 mois après la sortie
$date_6mois <- donnees_rbase$date_sortie + lubridate::month(6) donnees_rbase
# Âge à l'entrée dans le dispositif
<- donnees_tidyverse %>%
donnees_tidyverse mutate(age = as.period(interval(start = date_naissance, end = date_entree))$year)
# Âge formaté
<- donnees_tidyverse %>%
donnees_tidyverse mutate(agef = case_when(
< 26 ~ "1. De 15 à 25 ans",
age >= 26 & age < 50 ~ "2. De 26 à 49 ans",
age >= 50 ~ "3. 50 ans ou plus")
age
)
# Manipuler les dates
<- 365.25/2
sixmois # La durée du contrat est-elle inférieure à 6 mois ?
<- donnees_tidyverse %>%
donnees_tidyverse mutate(duree_inf_6_mois = case_when(duree < sixmois ~ 1,
>= sixmois ~ 0))
duree %>% pull(duree_inf_6_mois) %>% table()
donnees_tidyverse
# Date de sortie du dispositif
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_sortie = date_entree + lubridate::days(duree))
# Date 6 mois après la sortie
<- donnees_tidyverse %>%
donnees_tidyverse mutate(date_6mois = date_sortie + lubridate::month(6))
# Âge à l'entrée dans le dispositif
:= floor(lubridate::time_length(difftime(donnees_datatable$date_entree, donnees_datatable$date_naissance), "years"))]
donnees_datatable[, age
# Âge formaté
:= fcase(age < 26, "1. De 15 à 25 ans",
donnees_datatable[, agef 26 <= age & age < 50, "2. De 26 à 49 ans",
>= 50, "3. 50 ans ou plus")]
age
# Manipuler les dates
<- 365.25/2
sixmois # La durée du contrat est-elle inférieure à 6 mois ?
:= ifelse(duree >= sixmois, 1, 0)]
donnees_datatable[, duree_inf_6_mois := fifelse(duree >= sixmois, 1, 0)]
donnees_datatable[, duree_inf_6_mois := fcase(duree >= sixmois, 1,
donnees_datatable[, duree_inf_6_mois < sixmois, 0)]
duree # Date de sortie du dispositif
:= date_entree + lubridate::days(duree)]
donnees_datatable[, date_sortie
# Date 6 mois après la sortie
:= date_sortie + lubridate::month(6)] donnees_datatable[, date_6mois
# Création de la colonne age
%>%
requete_duckdb mutate(age = year(age(date_entree,date_naissance))) %>%
select(age)
# Âge formaté
%>%
requete_duckdb mutate(age = year(age(date_entree,date_naissance))) %>%
mutate(agef = case_when(
< 26 ~ "1. De 15 à 25 ans",
age >= 26 | age < 50 ~ "2. De 26 à 49 ans",
age >= 50 ~ "3. 50 ans ou plus")) %>%
age select(age, agef)
from datetime import timedelta
from dateutil.relativedelta import relativedelta
# Calculer la durée en jours pour six mois
= 365.25 / 2
sixmois
# La durée du contrat est-elle inférieure à 6 mois ?
'duree_inf_6_mois'] = np.where(donnees_python['duree'] < sixmois, 1, 0)
donnees_python[
# Créer une date spécifique (31 décembre de l'année en cours)
'date_specifique'] = pd.to_datetime(donnees_python['date_entree'].dt.year.fillna(0).astype(int).astype(str) + "-12-31", format='%Y-%m-%d', errors='coerce') donnees_python[
9 Manipulation de chaînes de caractères
En R
, la manipulation des chaînes de caractères passe par deux librairies principales, R base et stringr
. Ces librairies sont transversales à dplyr
/ data.table
/ duckdb
, on peut mélanger sans difficulté, et la séparation en onglets est un peu artificielle dans ce chapitre. Il reste préférable de s’accorder sur un style de programmation homogène. En duckdb
, certaines fonctions ne sont pas disponibles, et nous proposons des alternatives.
Les fonctions de R base sont souvent mieux connues (notamment dans les tutoriels et cours de programmation). La librairie stringr
est intéressante car les noms des fonctions sont plus simples et plus homogènes. Cette librairie est efficace, car implémentée au-dessus de stringi
, librairie qui pourra être utile pour certains traitements complexes (l’inversion d’une chaîne, l’encodage des caractères, les accents par exemple).
Pour en savoir plus sur le fonctionnement des chaînes de caractères en R
: https://book.utilitr.org/03_Fiches_thematiques/Fiche_donnees_textuelles.html.
9.1 Majuscule, minuscule
9.1.1 Majuscule
data donnees_sas;
set donnees_sas;
upcase(CSPF);
CSP_majuscule = run;
$csp_maj <- toupper(donnees_rbase$cspf) donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse mutate(csp_maj = toupper(cspf))
:= toupper(cspf)] donnees_datatable[, csp_maj
%>% mutate(csp_maj = toupper(cspf)) %>% select(csp_maj) requete_duckdb
'csp_maj'] = donnees_python['cspf'].str.upper() donnees_python[
9.1.2 Minuscule
data donnees_sas;
set donnees_sas;
lowcase(CSPF);
CSP_minuscule = run;
$csp_min <- tolower(donnees_rbase$cspf) donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse mutate(csp_maj = tolower(cspf))
# En minuscule
:= tolower(cspf)] donnees_datatable[, csp_min
%>% mutate(csp_maj = tolower(cspf)) %>% select(csp_maj) requete_duckdb
'csp_min'] = donnees_python['cspf'].str.lower() donnees_python[
9.1.3 Première lettre en majuscule
data donnees_sas;
set donnees_sas;
Niveau = propcase(Niveau);run;
# 1ère lettre en majuscule, autres lettres en minuscule
$niveau <- paste0(
donnees_rbasetoupper(substr(donnees_rbase$niveau, 1, 1)),
tolower(substr(donnees_rbase$niveau, 2, length(donnees_rbase$niveau)))
)
# 1ère lettre en majuscule, autres lettres en minuscule
<- donnees_tidyverse %>%
donnees_tidyverse mutate(niveau = str_to_title(niveau))
# 1ère lettre en majuscule, autres lettres en minuscule
:= paste0(toupper(substr(niveau, 1, 1)), tolower(substr(niveau, 2, length(niveau))))] donnees_datatable[, niveau
%>%
requete_duckdb # mutate(csp_maj = str_to_title(cspf)) %>% # fonction non traduite
mutate(
l_niveau = as.integer(length(niveau)-1),
niveau = paste0(toupper(substr(niveau, 1, 1)), tolower(right(niveau, l_niveau)))) %>%
# note : on utilise la fonction duckdb right car substr semble ne pas accepter un paramètre variable
select(l_niveau, niveau)
'niveau'] = donnees_python['cspf'].str.capitalize() donnees_python[
9.2 Nombre de caractères dans une chaîne de caractères
data donnees_sas;
set donnees_sas;
length(identifiant);
taille_id = run;
$taille_id <- nchar(donnees_rbase$identifiant) donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse mutate(taille_id = nchar(identifiant))
<- donnees_tidyverse %>%
donnees_tidyverse mutate(taille_id = str_split(identifiant, '') %>%
lengths)
:= nchar(identifiant)] donnees_datatable[, taille_id
%>% mutate(taille_id = nchar(identifiant)) %>% select(taille_id) %>% print() requete_duckdb
'taille_id'] = donnees_python['identifiant'].str.len() donnees_python[
9.3 Remplacer une chaîne de caractères par une autre
On souhaite remplacer le mot qualifie par le mot Qualifié.
data A_Corriger;
infile cards dsd dlm='|';
format A_corriger $8.;
input A_corriger $;
cards;
Qualifie
qualifie
Qualifie
QUALIFIE
;run;
data A_Corriger;
set A_Corriger;
lowcase(A_corriger);
Corrige = tranwrd(Corrige, "qualifie", "Qualifié");
Corrige = run;
# Le mot Qualifié n'a pas d'accent : on le corrige
<- c("Qualifie", "qualifie", "Qualifie", "QUALIFIE")
aCorriger
# [Q-q] permet de représenter Q ou q, et donc de prendre en compte Qualifie et qualifie
gsub("[Q-q]ualifie", "Qualifié", tolower(aCorriger))
# Le mot Qualifié n'a pas d'accent : on le corrige
<- c("Qualifie", "qualifie", "Qualifie", "QUALIFIE")
aCorriger
# [Q-q] permet de représenter Q ou q, et donc de prendre en compte Qualifie et qualifie
%>% tolower() %>% str_replace_all("[Q-q]ualifie", "Qualifié") aCorriger
# Le mot Qualifié n'a pas d'accent : on le corrige
<- c("Qualifie", "qualifie", "Qualifie", "QUALIFIE")
aCorriger
# [Q-q] permet de représenter Q ou q, et donc de prendre en compte Qualifie et qualifie
gsub("[Q-q]ualifie", "Qualifié", tolower(aCorriger))
%>% mutate(niveau = stringr::str_replace_all(niveau, "[Q-q]ualifie", "Qualifié")) %>% select(niveau) requete_duckdb
= ["Qualifie", "qualifie", "Qualifie", "QUALIFIE"]
aCorriger r'[qQ]ualifie', 'Qualifié', mot.lower()) for mot in aCorriger] [re.sub(
9.4 Extraire des éléments d’une chaîne de caractères
Le comportement de la fonction substr est différent entre SAS
et R
:
en
SAS
, dans substr(extrait, 2, 3), le 2 correspond à la position du 1er caractère à récupérer, le 3 au nombre total de caractères extrait à partir du 2e => Le résultat est xtren
R
, dans substr(“extrait”, 2, 3), le 2 correspond à la position du 1er caractère à récupérer, le 3 à la position du dernier caractère => Le résultat est “xt”.
data Exemple_texte;
set Exemple_texte;
/* Extraire les 2e, 3e et 4e caractère du mot extrait */
/* Fonction tranwrd (TRANslate WoRD) */
/* 2 correspond à la position du 1er caractère à récupérer, 3 le nombre total de caractères à partir du 2e */
substr(extrait, 2, 3);
extrait = run;
proc print data = Exemple_texte;run;
# Extraire les 2e, 3e et 4e caractères de Concatener
# 2 correspond à la position du 1er caractère à récupérer, 5 la position du dernier caractère
<- substr("extrait", 2, 5) extrait
# Extraire les 2e, 3e et 4e caractères de Concatener
# 2 correspond à la position du 1er caractère à récupérer, 5 la position du dernier caractère
<- str_sub("extrait", 2, 5) extrait
# Extraire les 2e, 3e et 4e caractères de texte
# 2 correspond à la position du 1er caractère à récupérer, 5 la position du dernier caractère
<- substr("extrait", 2, 5) extrait
%>% mutate(niveau = stringr::str_sub(niveau, 2, 5)) %>% select(niveau) requete_duckdb
# La position 1 en Python correspond au 2eme élément
= "extrait"[1:5] extrait
9.5 Enlever les blancs superflus d’une chaîne de caractères
data Exemple_texte;
" Ce Texte mériterait d être corrigé ";
Texte = run;
data Exemple_texte;
set Exemple_texte;
/* Enlever les blancs au début et à la fin de la chaîne de caractère */
Enlever_Blancs_Initiaux = strip(Texte);
/* Enlever les doubles blancs dans la chaîne de caractères */
compbl(Enlever_Blancs_Initiaux);
Enlever_Blancs_Entre =
/* Enlever doubles blancs */
/* REVOIR !!!!! */
compress(Texte, " ", "t");
Enlever_Doubles_Blancs = run;
proc print data = Exemple_texte;run;
# Enlever les blancs au début et à la fin de la chaîne de caractère
<- " Ce Texte mériterait d être corrigé "
texte
# "\\s+" est une expression régulière indiquant 1 ou plusieurs espaces successifs
# Le gsub remplace 1 ou plusieurs espaces successifs par un seul espace
# trimws enlève les espaces au début et à la fin d'une chaîne de caractère
<- gsub("\\s+", " ", trimws(texte)) texte
# Enlever les blancs au début et à la fin de la chaîne de caractère
<- " Ce Texte mériterait d être corrigé "
texte
# str_squish() supprime les espaces blancs au début et à la fin, et remplace tous les espaces blancs internes par un seul espace
<- str_squish(texte) texte
# Enlever les blancs au début et à la fin de la chaîne de caractère
<- " Ce Texte mériterait d être corrigé "
texte
# "\\s+" est une expression régulière indiquant 1 ou plusieurs espaces successifs
# Le gsub remplace 1 ou plusieurs espaces successifs par un seul espace
# trimws enlève les espaces au début et à la fin d'une chaîne de caractère
<- gsub("\\s+", " ", trimws(texte)) texte
%>% mutate(niveau = stringr::str_squish(niveau)) %>% select(niveau) requete_duckdb
# Enlever les blancs au début et à la fin de la chaîne de caractère
= " Ce Texte mériterait d être corrigé "
texte = re.sub(r'\s+', ' ', texte).strip() texte
9.6 Concaténer des chaînes de caractères
data Exemple_texte;
"Ce texte";
Texte1 = "va être";
Texte2 = "concaténé";
Texte3 = "";
Texte4 = run;
data Exemple_texte;
set Exemple_texte;
/* Trois méthodes pour concaténer des chaînes de caractères */
" "||Texte2;
Concatener = Texte1||" "!!Texte2;
Concatener2 = Texte1!!" ", Texte1, Texte2);
Concatener3 = catx(
/* Effet des valeurs manquantes */
/* Le séparateur est enlevé lors d'une concaténation avec une chaîne de caractère vide */
"-", Texte4, Texte3);
Concatener4 = catx(run;
proc print data = Exemple_texte;run;
# Concaténer des chaînes de caractères
<- "Ce texte"
texte1 <- "va être"
texte2 <- "concaténé"
texte3 <- ""
texte4
<- paste(texte1, texte2, texte3, sep = " ")
concatene paste0(texte1, " ", texte2, " ", texte3)
# Effet des valeurs manquantes : le délimiteur (ici -) apparaît avec la concaténation avec le caractère manquant
paste(texte4, texte3, sep = "-")
# Concaténer des chaînes de caractères
<- "Ce texte"
texte1 <- "va être"
texte2 <- "concaténé"
texte3 <- ""
texte4
<- str_flatten(c(texte1, texte2, texte3), collapse = " ")
concatene
# Effet des valeurs manquantes : le délimiteur (ici -) apparaît avec la concaténation avec le caractère manquant
str_flatten(c(texte4, texte3), collapse = "-")
# Concaténer des chaînes de caractères
<- "Ce texte"
texte1 <- "va être"
texte2 <- "concaténé"
texte3 <- ""
texte4
<- paste(texte1, texte2, texte3, sep = " ")
concatene paste0(texte1, " ", texte2, " ", texte3)
# Effet des valeurs manquantes : le délimiteur (ici -) apparaît avec la concaténation avec le caractère manquant
paste(texte4, texte3, sep = "-")
%>% mutate(niveau = paste0(niveau,niveau)) %>% select(niveau) requete_duckdb
# Concaténer des chaînes de caractères
= "Ce texte"
texte1 = "va être"
texte2 = "concaténé"
texte3 = ""
texte4
= ' '.join([texte1, texte2, texte3])
concatene
# Effet des valeurs manquantes : le délimiteur (ici -) apparaît avec la concaténation avec le caractère manquant
'-'.join([texte4, texte3])
9.7 Transformer plusieurs caractères différents
Supprimer les accents, cédilles, caractères spéciaux.
data Exemple_texte;
set Exemple_texte;
/* Transformer plusieurs caractères différents */
/* On transforme le é en e, le â en a, le î en i, ... */
"éèêëàâçîô";
texte = translate(texte, "eeeeaacio", "éèêëàâçîô");
texte_sans_accent = run;
proc print data = Exemple_texte;run;
# Transformer plusieurs caractères différents
<- "éèêëàâçîô"
texte chartr("éèêëàâçîô", "eeeeaacio", texte)
# Transformer plusieurs caractères différents
<- "éèêëàâçîô"
texte chartr("éèêëàâçîô", "eeeeaacio", texte)
# Transformer plusieurs caractères différents
<- "éèêëàâçîô"
texte chartr("éèêëàâçîô", "eeeeaacio", texte)
%>% mutate(niveau = strip_accents(niveau)) %>% select(niveau) # strip_accents est une fonction duckdb
requete_duckdb # chartr n'est pas traduite en duckdb
= "éèêëàâçîô"
texte "éèêëàâçîô", "eeeeaacio") texte.replace(
9.8 Découper une chaîne de caractères selon un caractère donné
Découper une phrase selon les espaces pour isoler les mots.
data Mots;
" ";
delim = "Mon texte va être coupé !";
Texte =
/* Chaque mot dans une variable */
%macro Decouper;
%do i = 1 %to %sysfunc(countw(Texte, delim));
&i. = scan(Texte, &i., delim);
Mot%end;
%mend Decouper;
%Decouper;
/* Les mots empilés */
nb_mots = countw(Texte, delim); do nb = 1 to nb_mots;
scan(Texte, nb, delim);
mots = output;
end;
run;
proc print data = Mots;run;
<- "Mon texte va être coupé !"
texte unlist(strsplit(texte, split = " "))
<- "Mon texte va être coupé !"
texte str_split(texte, pattern = " ") %>% unlist()
<- "Mon texte va être coupé !"
texte unlist(strsplit(texte, split = " "))
%>% mutate(niveau = string_split(niveau, " ")) %>% select(niveau) # string_split est une fonction duckdb
requete_duckdb # `str_split()` is not available in this SQL variant
# strsplit n'est pas disponible non plus
N.B. On obtient une seule colonne contenant des listes (de chaînes de caractères). DuckDB sait gérer des types complexes dans des cases, tout comme dplyr
, mais c’est plus difficile à manipuler.
= "Mon texte va être coupé !"
texte texte.split()
9.9 Inverser une chaîne de caractères
data Mots;
"Mon texte va être inversé !";
Texte = x = left(reverse(Texte));
run;
proc print data = Mots;run;
<- "Mon texte va être inversé !"
texte <- function(x) {
inverserTexte sapply(
lapply(strsplit(x, NULL), rev),
collapse = "")
paste,
}inverserTexte(texte)
library(stringi)
<- "Mon texte va être inversé !"
texte ::stri_reverse(texte) stringi
<- "Mon texte va être inversé !"
texte <- function(x) {
inverserTexte sapply(
lapply(strsplit(x, NULL), rev),
collapse = "")
paste,
}inverserTexte(texte)
%>% mutate(niveau = reverse(niveau)) %>% select(niveau) # reverse est une fonction duckdb
requete_duckdb # stri_reverse : No known SQL translation
= "Mon texte va être inversé !"
texte -1] texte[::
10 Les valeurs manquantes
10.1 Repérer les valeurs manquantes (variables Age et Niveau)
Lignes où les variables Age ou Niveau sont manquantes.
data Manquant;
set donnees_sas;
/* 1ère solution */
if missing(age) or missing(Niveau) then missing1 = 1;else missing1 = 0;
/* 2e solution */
if age = . or Niveau = '' then missing2 = 1;else missing2 = 0;
keep Age Niveau Missing1 Missing2;
run;
# Mauvaise méthode pour repérer les valeurs manquantes
<- donnees_rbase[donnees_rbase$age == NA | donnees_rbase$niveau == NA, ]
manquant
# Bonne méthode pour repérer les valeurs manquantes
<- donnees_rbase[is.na(donnees_rbase$age) | is.na(donnees_rbase$niveau), ] manquant
# Mauvaise méthode pour repérer les valeurs manquantes
<- donnees_tidyverse %>%
manquant filter(age == NA | niveau == NA)
# Bonne méthode pour repérer les valeurs manquantes
<- donnees_tidyverse %>%
manquant filter(is.na(age) | is.na(niveau))
# Mauvaise méthode pour repérer les valeurs manquantes
<- donnees_datatable[age == NA | niveau == NA]
manquant
# Bonne méthode pour repérer les valeurs manquantes
<- donnees_datatable[is.na(age)]
manquant := fifelse(is.na(age) | is.na(niveau), 1, 0)] donnees_datatable[, manquant
10.2 Nombre et proportion de valeurs manquantes par variable
10.2.1 Pour l’ensemble des variables
/* Une solution possible */
%macro Iteration(base = donnees_sas);
%local nbVar;
proc contents data = donnees_sas out = ListeVar noprint;run;
proc sql noprint;select count(*) into :nbVar from ListeVar;quit;
%do i = 1 %to &nbVar.;
data _null_;
set ListeVar (firstobs = &i. obs = &i.);
call symput('var', name);
run;
proc sql;
select max("&var.") as Variable, sum(missing(&var.)) as Manquants, sum(missing(&var.)) / count(*) * 100 as Prop_Manquants
from &base.;
quit;
%end;
proc datasets lib = work nolist;delete ListeVar;run;
%mend Iteration;
%Iteration;
# Nombre de valeurs manquantes
colSums(is.na(donnees_rbase))
apply(is.na(donnees_rbase), 2, sum)
# Proportion de valeurs manquantes
colMeans(is.na(donnees_rbase)) * 100
apply(is.na(donnees_rbase), 2, mean) * 100
# Nombre et proportion de valeurs manquantes
%>%
donnees_tidyverse reframe(across(everything(), ~c( sum(is.na(.x)), mean(is.na(.x) * 100)) ))
# Proportion de valeurs manquantes
%>%
donnees_tidyverse reframe(across(everything(), ~c( sum(is.na(.x)), mean(is.na(.x) * 100)) ))
# Autres solutions
%>% map(~c( sum(is.na(.x)), mean(is.na(.x) * 100)))
donnees_tidyverse # Obsolète
%>% summarise_each(funs(mean(is.na(.)) * 100)) donnees_tidyverse
# Nombre et proportion de valeurs manquantes
lapply(.SD, function(x) c(sum(is.na(x)), mean(is.na(x)) * 100))] donnees_datatable[,
10.2.2 Pour les variables numériques ou dates
/* Partie "Missing Values" en bas du tableau consacré à la variable */
proc univariate data = donnees_sas;var _numeric_;run;
apply(is.na(
sapply(donnees_rbase, function(x) is.numeric(x) | lubridate::is.Date(x))]
donnees_rbase[
), 2,
function(x) c( sum(x), mean(x) * 100 ) )
# Autres solutions
sapply(
sapply(donnees_rbase, function(x) is.numeric(x) | lubridate::is.Date(x))],
donnees_rbase[function(x) c( sum(is.na(x)), mean(is.na(x)) * 100 ) )
sapply(
sapply(donnees_rbase, function(x) is.numeric(x) | lubridate::is.Date(x))],
donnees_rbase[function(x) c (sum(is.na(x)), sum(is.na(x)) / length(x) * 100) )
%>%
donnees_tidyverse summarise(across(where(~ is.numeric(.x) | lubridate::is.Date(.x)),
c(~sum(is.na(.x)), ~mean(is.na(.x)))))
%>%
donnees_tidyverse summarise(across(where(~ is.numeric(.x) | lubridate::is.Date(.x)),
list(~sum(is.na(.x)), ~sum(is.na(.x)) / length(.x))))
lapply(.SD, function(x) mean(is.na(x)) * 100),
donnees_datatable[, = function(x) c(lubridate::is.Date(x) | is.numeric(x))] .SDcols
10.3 Incidence des valeurs manquantes
/* En SAS, les valeurs manquantes sont des nombres négatifs faibles */
data Valeur_Manquante;
set donnees_sas;
/* Lorsque Age est manquant (missing), Jeune_Correct vaut 0 mais Jeune_Incorrect vaut 1 */
/* En effet, pour SAS, un Age manquant est une valeur inférieure à 0, donc bien inférieure à 25.
Donc la variable Jeune_Incorrect vaut bien 1 pour les âges inconnus */
25);
Jeune_Incorrect = (Age <= 0 <= Age <= 25);
Jeune_Correct = (run;
/* On affiche les résultats */
proc print data = Valeur_Manquante (keep = Age Jeune_Correct Jeune_Incorrect
where = (missing(Age)));
run;
proc freq data = Valeur_Manquante;tables Jeune_Incorrect Jeune_Correct;run;
# Une somme avec NA donne NA en résultat
mean(donnees_rbase$note_formateur)
# Sauf avec l'option na.rm = TRUE
mean(donnees_rbase$note_formateur, na.rm = TRUE)
# Une somme avec NA donne NA en résultat
%>% pull(note_formateur) %>% mean()
donnees_tidyverse # Sauf avec l'option na.rm = TRUE
%>% pull(note_formateur) %>% mean(na.rm = TRUE)
donnees_tidyverse
# Attention, en tidyverse, les syntaxes suivantes ne fonctionnent pas !
# donnees_tidyverse %>% mean(note_formateur)
# donnees_tidyverse %>% mean(note_formateur, na.rm = TRUE)
# Une somme avec NA donne NA en résultat
mean(note_formateur)]
donnees_datatable[, # Sauf avec l'option na.rm = TRUE
mean(note_formateur, na.rm = TRUE)] donnees_datatable[,
10.4 Remplacer les valeurs manquantes d’une seule variable par 0
%let var = note_contenu;
data donnees_sas_sans_missing;
set donnees_sas;
if missing(&var.) then &var. = 0;
/* Ou alors */
if &var. = . then &var. = 0;
/* Ou encore */
if note_contenu = . then note_contenu = 0;
run;
<- "note_contenu"
variable <- donnees_rbase
donnees_rbase_sans_na is.na(donnees_rbase_sans_na[, variable]), variable] <- 0
donnees_rbase_sans_na[
# Autres solutions
is.na(donnees_rbase_sans_na[, variable])] <- 0
donnees_rbase_sans_na[, variable][<- replace(donnees_rbase_sans_na[, variable],
donnees_rbase_sans_na[, variable] is.na(donnees_rbase_sans_na[, variable]), 0)
# Ou alors
<- donnees_rbase
donnees_rbase_sans_na $note_contenu[is.na(donnees_rbase_sans_na$note_contenu)] <- 0 donnees_rbase_sans_na
<- "note_contenu"
variable <- donnees_tidyverse %>%
donnees_tidyverse_sans_na mutate(across(variable, ~tidyr::replace_na(.x, 0)))
# Ou alors
<- donnees_tidyverse %>%
donnees_tidyverse_sans_na mutate(note_contenu = tidyr::replace_na(note_contenu, 0))
<- "note_contenu"
variable replace(.SD, is.na(.SD), 0), .SDcols = variable]
donnees_datatable[, lapply(.SD, function(x) fifelse(is.na(x), 0, x)), .SDcols = variable]
donnees_datatable[, lapply(.SD, \(x) fifelse(is.na(x), 0, x)), .SDcols = variable]
donnees_datatable[,
# Ou alors
replace(.SD, is.na(.SD), 0), .SDcols = "note_contenu"] donnees_datatable[,
10.5 Remplacer toutes les valeurs numériques manquantes par 0
/* On sélectionne toutes les variables numériques */
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;
select name into :nom_col separated by " " from Var where format = "";
run;
data donnees_sas_sans_missing;
set donnees_sas;
%macro Missing;
%local i var;
%do i = 1 %to %sysfunc(countw(&nom_col.));
%let var = %scan(&nom_col., &i);
if missing(&var.) then &var. = 0;
%end;
%mend Missing;
%Missing;
run;
proc datasets lib = Work nolist;delete Var;run;
# Dans le cas des dates, la valeur manquante a été remplacée par 1970-01-01
<- donnees_rbase
donnees_rbase_sans_na is.na(donnees_rbase_sans_na)] <- 0
donnees_rbase_sans_na[
# On remplace seulement les valeurs numériques par 0
<- donnees_rbase
donnees_rbase_sans_na <- sapply(donnees_rbase, is.numeric)
varNumeriques is.na(donnees_rbase_sans_na[, varNumeriques])] <- 0
donnees_rbase_sans_na[, varNumeriques][
# Autre solution, avec replace
<- lapply(donnees_rbase_sans_na[, varNumeriques],
donnees_rbase_sans_na[, varNumeriques] function(x) {replace(x, is.na(x), 0)})
# On remplace seulement les valeurs numériques par 0
<- donnees_tidyverse %>%
donnees_tidyverse_sans_na mutate(across(where(is.numeric), ~tidyr::replace_na(.x, 0)))
# Autres façons d'écrire les fonctions anonymes
# La méthode complète
<- donnees_tidyverse %>%
donnees_tidyverse_sans_na mutate(across(where(is.numeric), function(x) tidyr::replace_na(x, 0)))
# Une autre façon de raccourcir (depuis R 4.1)
# \(x) est un raccourci pour function(x)
<- donnees_tidyverse %>%
donnees_tidyverse_sans_na mutate(across(where(is.numeric), \(x) tidyr::replace_na(x, 0)))
# Autre solution
<- donnees_tidyverse %>%
donnees_tidyverse_sans_na ::modify_if(is.numeric, ~tidyr::replace_na(.x, 0)) purrr
<- copy(donnees_datatable)
donnees_datatable_sans_na setnafill(donnees_datatable[, .SD, .SDcols = is.numeric], fill = 0)
# Autre solution
<- copy(donnees_datatable)
donnees_datatable_sans_na <- colnames(donnees_datatable_sans_na[, .SD, .SDcols = is.numeric])
cols := lapply(.SD, function(x) fifelse(is.na(x), 0, x)), .SDcols = cols]
donnees_datatable_sans_na[, (cols)
# Ensemble des colonnes
<- copy(donnees_datatable)
donnees_datatable_sans_na is.na(donnees_datatable_sans_na)] <- 0 donnees_datatable_sans_na[
10.6 Supprimer les lignes où une certaine variable est manquante
On souhaite supprimer toutes les lignes où la variable age est manquante.
data age_non_manquant;
set donnees_sas (where = (age ne .));
/* Ou alors */
if age ne .;
run;
<- donnees_rbase[complete.cases(donnees_rbase[, "age"]), ]
age_non_manquant <- donnees_rbase[! is.na(donnees_rbase[, "age"]), ] age_non_manquant
<- donnees_tidyverse %>% drop_na(age)
age_non_manquant <- donnees_tidyverse %>% filter(!is.na(age)) age_non_manquant
<- na.omit(donnees_datatable, cols = c("age"))
age_non_manquant <- donnees_datatable[! is.na(age), ] age_non_manquant
10.7 Supprimer les lignes où au moins une variable de la base est manquante
On souhaite supprimer toutes les lignes où au moins une variable de la base est manquante.
data non_manquant;
set donnees_sas;
if cmiss(of _all_) then delete;
run;
<- donnees_rbase[complete.cases(donnees_rbase), ] non_manquant
<- donnees_tidyverse %>% drop_na() non_manquant
<- na.omit(donnees_datatable) non_manquant
11 Les tris
11.1 Trier les colonnes de la base
11.1.1 Mettre identifiant et date_entree au début de la base
%let colTri = identifiant date_entree;
data donnees_sas;
retain &colTri.;
set donnees_sas;
run;
/* Autre solution */
proc sql;
create table donnees_sas as
/* Dans la proc SQL, les variables doivent être séparées par des virgules */
/* On remplace les blancs entre les mots par des virgules pour la proc SQL */
select %sysfunc(tranwrd(&colTri., %str( ), %str(, ))), * from donnees_sas;
quit;
<- c("identifiant", "date_entree")
colTri <- donnees_rbase[, union(colTri, colnames(donnees_rbase))]
donnees_rbase
# Autres possibilités, plus longues !
<- donnees_rbase[, c(colTri, setdiff(colnames(donnees_rbase), colTri))]
donnees_rbase <- donnees_rbase[, c(colTri, colnames(donnees_rbase)[! colnames(donnees_rbase) %in% colTri])] donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse relocate(identifiant, date_entree)
# Autres solutions
<- c("identifiant", "date_entree")
colTri <- donnees_tidyverse %>%
donnees_tidyverse relocate(all_of(colTri))
<- donnees_tidyverse %>%
donnees_tidyverse_tri select(all_of(colTri), everything())
<- c("identifiant", "date_entree")
colTri <- union(colTri, colnames(donnees_datatable))
tri <- donnees_datatable[, ..tri]
donnees_datatable
# Autre solution, à privilégier
# En data.table, les instructions débutant par set modifient les éléments par référence, c'est-à-dire sans copie. Ceci est plus efficace pour manipuler des données volumineuses.
setcolorder(donnees_datatable, colTri)
%>%
requete_duckdb mutate_at(enDate, ~ as.Date(strptime(.,'%d/%m/%Y'))) %>% # strptime est une fonction duckdb
select(identifiant, date_entree, everything())
%>%
requete_duckdb mutate_at(enDate, ~ as.Date(strptime(.,'%d/%m/%Y'))) %>% # strptime est une fonction duckdb
relocate(identifiant, date_entree)
= ["identifiant", "date_entree"]
colTri
= colTri + [col for col in donnees_python.columns if col not in colTri]
cols = donnees_python[cols] donnees_python
11.1.2 Mettre la variable poids_sondage au début de la base
data donnees_sas;
retain poids_sondage;
set donnees_sas;
run;
union("poids_sondage", colnames(donnees_rbase))] donnees_rbase[,
<- donnees_tidyverse %>%
donnees_tidyverse relocate(poids_sondage)
setcolorder(donnees_datatable, "poids_sondage")
= ['poids_sondage'] + [col for col in donnees_python.columns if col != 'poids_sondage']
cols = donnees_python[cols] donnees_python
11.1.3 Mettre la variable poids_sondage après la variable date_naissance
proc contents data = donnees_sas out = var;run;
proc sql noprint;
select name into :var separated by " "
from var
where varnum <= (select varnum from var where lowcase(name) = "date_naissance")
order by varnum;
quit;
data donnees_sas;
retain &var. poids_sondage;
set donnees_sas;
run;
<- c( colnames(donnees_rbase)[1 : which("date_naissance" == colnames(donnees_rbase))], "poids_sondage" )
varAvant <- donnees_rbase[, c(varAvant, setdiff(colnames(donnees_rbase), varAvant))] donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse relocate(poids_sondage, .after = date_naissance)
setcolorder(donnees_datatable, "poids_sondage", after = "date_naissance")
# Trouver l'index de la colonne 'date_naissance'
= donnees_python.columns.get_loc('date_naissance')
date_naissance_index
# Sélectionner toutes les colonnes jusqu'à 'date_naissance' inclus
= list(donnees_python.columns[:date_naissance_index + 1]) + ['poids_sondage']
varAvant
# Réorganiser les colonnes du DataFrame
= donnees_python[varAvant + [col for col in donnees_python.columns if col not in varAvant]] donnees_python
11.1.4 Mettre la variable poids_sondage à la fin de la base
proc contents data = donnees_sas out = var;run;
proc sql noprint;
select name into :var separated by " " from var
where lowcase(name) ne "poids_sondage" order by varnum;
quit;
data donnees_sas;
retain &var. poids_sondage;
set donnees_sas;
run;
<- donnees_rbase[, c(setdiff(colnames(donnees_rbase), "poids_sondage"), "poids_sondage")] donnees_rbase
<- donnees_tidyverse %>%
donnees_tidyverse relocate(poids_sondage, .after = last_col())
setcolorder(donnees_datatable, c(setdiff(colnames(donnees_datatable), "poids_sondage"), "poids_sondage"))
= [col for col in donnees_python.columns if col != 'poids_sondage'] + ['poids_sondage']
cols = donnees_python[cols] donnees_python
11.2 Trier les lignes de la base
11.2.1 Tri par ordre croissant d’identifiant et date_entree
/* 1ère possibilité */
proc sort data = donnees_sas;by Identifiant Date_entree;run;
/* 2e possibilité */
proc sql;
create table donnees_sas as select * from donnees_sas
order by Identifiant, Date_entree;
quit;
# Tri par ordre croissant
# L'option na.last = FALSE (resp. TRUE) indique que les valeurs manquantes doivent figurer à la fin (resp. au début) du tri, que le tri soit croissant ou décroissant
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ] donnees_rbase
# Tri par ordre croissant
<- donnees_tidyverse %>%
donnees_tidyverse arrange(identifiant, date_entree)
# Tri par ordre croissant
# L'option na.last = FALSE (resp. TRUE) indique que les valeurs manquantes doivent figurer à la fin (resp. au début) du tri, que le tri soit croissant ou décroissant
<- donnees_datatable[order(identifiant, date_entree, na.last = FALSE)]
donnees_datatable
# En data.table, les instructions débutant par set modifient les éléments par référence, c'est-à-dire sans copie.
# Ceci est plus efficace pour manipuler des données volumineuses.
setorder(donnees_datatable, "identifiant", "date_entree", na.last = FALSE)
setorder(donnees_datatable, identifiant, date_entree, na.last = FALSE)
setorderv(donnees_datatable, cols = c("identifiant", "date_entree"), order = c(1L, 1L), na.last = FALSE)
# Mettre les na en premier
= donnees_python.sort_values(by=['identifiant', 'date_entree'], na_position='first') donnees_python
11.2.2 Tri par ordre décroissant
/* Idem par ordre croissant d'identifiant et ordre décroissant de date d'entrée */
/* 1ère possibilité */
proc sort data = donnees_sas;by Identifiant descending Date_entree;run;
/* 2e possibilité */
proc sql;
create table donnees_sas as select * from donnees_sas
order by Identifiant, Date_entree desc;
quit;
# Tri par ordre croissant de identifiant et décroissant de date_entree
<- donnees_rbase[
donnees_rbase order(donnees_rbase$identifiant, donnees_rbase$date_entree,
na.last = FALSE,
decreasing = c(FALSE, TRUE),
method = "radix"
)
, ]
# Autre possibilité : - devant la variable (uniquement pour les variables numériques)
<- donnees_rbase[
donnees_rbase order(donnees_rbase$identifiant, -donnees_rbase$duree,
na.last = FALSE)
, ]
# Tri par ordre croissant de identifiant et décroissant de date_entree
<- donnees_tidyverse %>%
donnees_tidyverse arrange(identifiant, desc(date_entree))
# Tri par ordre croissant de identifiant et décroissant de date_entree (- avant le nom de la variable)
<- donnees_datatable[order(identifiant, -date_entree, na.last = FALSE)]
donnees_datatable setorder(donnees_datatable, "identifiant", -"date_entree", na.last = FALSE)
setorder(donnees_datatable, identifiant, -date_entree, na.last = FALSE)
setorderv(donnees_datatable, cols = c("identifiant", "date_entree"), order = c(1L, -1L), na.last = FALSE)
= donnees_python.sort_values(by=['identifiant', 'date_entree'], na_position='first', ascending=[True, False]) donnees_python
11.3 Incidence des valeurs manquantes dans les tris
/* Dans SAS, les valeurs manquantes sont considérées comme des valeurs négatives */
/* Elles sont donc situées en premier dans un tri par ordre croissant ... */
proc sort data = donnees_sas;by identifiant date_entree;run;proc print;run;
/* ... et en dernier dans un tri par ordre décroissant */
proc sort data = donnees_sas;by identifiant descending date_entree;run;
proc print;run;
# Les valeurs manquantes sont situées en dernier dans un tri par ordre croissant ou décroissant (car par défaut l'option na.last = TRUE) ...
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree), ]
donnees_rbase
# SAS considère les valeurs manquantes comme des nombres négatifs faibles.
# Pour mimer le tri par ordre croissant en SAS :
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase
# Pour mimer le tri par ordre décroissant en SAS :
<- donnees_rbase[order(donnees_rbase$identifiant, is.na(donnees_rbase$date_entree), donnees_rbase$date_entree,
donnees_rbase na.last = FALSE,
decreasing = c(FALSE, FALSE, TRUE),
method = "radix"), ]
# Attention, avec arrange, les variables manquantes (NA) sont toujours classées en dernier, même avec desc()
<- donnees_tidyverse %>%
donnees_tidyverse arrange(identifiant, date_entree)
<- donnees_tidyverse %>%
donnees_tidyverse arrange(identifiant, desc(date_entree))
# Or, SAS considère les valeurs manquantes comme des nombres négatifs faibles.
# Elles sont donc classées en premier dans un tri par ordre croissant, et en dernier dans un tri par ordre décroissant
# Pour mimer le tri par ordre croissant en SAS : les valeurs manquantes de date_entree sont classées en premier
<- donnees_tidyverse %>%
donnees_tidyverse arrange(identifiant, !is.na(date_entree), date_entree)
# Pour mimer le tri par ordre décroissant en SAS
<- donnees_tidyverse %>%
donnees_tidyverse arrange(identifiant, desc(date_entree))
# Les valeurs manquantes sont situées en dernier dans un tri par ordre croissant ou décroissant (car par défaut l'option na.last = TRUE) ...
<- donnees_datatable[order(identifiant, date_entree)]
donnees_datatable
# SAS considère les valeurs manquantes comme des nombres négatifs faibles.
# Pour mimer le tri par ordre croissant en SAS :
setorderv(donnees_datatable, cols = c("identifiant", "date_entree"), order = c(1L, 1L), na.last = FALSE)
# Pour mimer le tri par ordre décroissant en SAS :
:= is.na(date_entree)]
donnees_datatable[, date_entree_na setorderv(donnees_datatable, cols = c("identifiant", "date_entree_na", "date_entree"), order = c(1L, 1L, -1L), na.last = FALSE)
:= NULL] donnees_datatable[, date_entree_na
# Les valeurs manquantes sont situées en dernier dans un tri par ordre croissant ou décroissant (car par défaut l'option na.last = TRUE) ...
%>%
requete_duckdb arrange(Identifiant, Note_Contenu) %>%
select(Identifiant, Note_Contenu)
# Pour mimer le tri par ordre croissant en SAS :
# Note : il faut faire select d'abord, sinon il y a une erreur quand "! is.na()" est dans la liste des colonnes
%>%
requete_duckdb select(Identifiant, Note_Contenu) %>%
arrange(Identifiant, ! is.na(Note_Contenu), Note_Contenu)
# Pour mimer le tri par ordre décroissant en SAS :
# Note : il faut faire select d'abord, sinon il y a une erreur quand "! is.na()" est dans la liste des colonnes
%>%
requete_duckdb select(Identifiant, Note_Contenu) %>%
arrange(Identifiant, is.na(Note_Contenu), Note_Contenu)
# Les valeurs manquantes sont situées en dernier dans un tri par ordre croissant ou décroissant
= donnees_python.sort_values(by=['identifiant', 'date_entree'])
donnees_python
# SAS considère les valeurs manquantes comme des nombres négatifs faibles.
# Pour mimer le tri par ordre croissant en SAS : ajouter l'option na_position = 'first'
= donnees_python.sort_values(by=['identifiant', 'date_entree'], na_position='first')
donnees_python
# Pour mimer le tri par ordre décroissant en SAS :
= donnees_python.sort_values(by=['identifiant', 'date_entree'], ascending=[True, False]) donnees_python
11.4 Trier par ordre croissant de toutes les variables de la base
proc sort data = donnees_sas;by _all_;run;
<- donnees_rbase[order(colnames(donnees_rbase), na.last = FALSE)] tri_toutes_variables
<- donnees_tidyverse %>%
tri_toutes_variables arrange(pick(everything()))
<- donnees_tidyverse %>%
tri_toutes_variables arrange(across(everything()))
<- setorderv(donnees_datatable, na.last = FALSE) tri_toutes_variables
= donnees_python.sort_values(by=list(donnees_python.columns), na_position='first') donnees_python
12 Les doublons
12.1 Doublons pour toutes les colonnes
/* On extrait seulement les doublons, pas la première occurrence */
/* On récupère déjà la dernière variable de la base (on en aura besoin plus loin) */
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;
select name into :derniere_var
from Var
where varnum = (select max(varnum) from Var);
quit;
proc sort data = donnees_sas;by &nom_col.;run;
data Doublons;
set donnees_sas;
by &nom_col.;
if not (first.&derniere_var. and last.&derniere_var.);
run;
# On extrait seulement les doublons, pas la première occurrence
<- donnees_rbase[duplicated(donnees_rbase), ] doublons
# On extrait seulement les doublons, pas la première occurrence
%>%
donnees_tidyverse group_by(across(everything())) %>%
filter(n() > 1) %>%
slice(-1) %>%
ungroup()
# Autre solution
<- donnees_tidyverse %>%
doublons group_by_all() %>%
filter(n() > 1) %>%
slice(-1) %>%
ungroup()
# On extrait seulement les doublons, pas la première occurrence
<- donnees_datatable[duplicated(donnees_datatable), ] doublons
# On extrait seulement les doublons, pas la première occurrence
= donnees_python[donnees_python.duplicated()] doublons
12.2 Doublons pour une ou plusieurs colonnes
/* On extrait seulement les doublons, pas la première occurrence */
%let var = identifiant;
proc sort data = donnees_sas;by &var.;run;
data doublons;
set donnees_sas;
by &var.;
if not first.&var.;
run;
/* À FAIRE : nodupkey ??? */
# On extrait seulement les doublons, pas la première occurrence
<- "identifiant"
variable <- donnees_rbase[duplicated(donnees_rbase[, variable]), ] doublons
# On extrait seulement les doublons, pas la première occurrence
<- "identifiant"
variable <- donnees_tidyverse %>%
doublons group_by(across(variable)) %>%
filter(n() > 1) %>%
slice(-1) %>%
ungroup()
# On extrait seulement les doublons, pas la première occurrence
<- "identifiant"
variable <- donnees_datatable[duplicated(donnees_datatable[, ..variable]), ] doublons
# On extrait seulement les doublons, pas la première occurrence
= "identifiant"
variable = donnees_python[donnees_python[variable].duplicated()] doublons
12.3 Récupérer toutes les lignes pour les identifiants en doublon
%let var = identifiant;
/* On groupe par la colonne identifiant, et si on aboutit à strictement plus d'une ligne, c'est un doublon */
proc sql;
create table enDouble as
select * from donnees_sas
group by &var.
having count(*) > 1;
quit;
<- "identifiant"
variable <- donnees_rbase[donnees_rbase[, variable] %in%
enDouble duplicated(donnees_rbase[, variable]), variable]] donnees_rbase[
<- "identifiant"
variable <- donnees_tidyverse %>%
enDouble group_by(across(variable)) %>%
filter(n() > 1) %>%
ungroup()
<- "identifiant"
variable <- donnees_datatable[donnees_datatable[[variable]] %chin%
enDouble duplicated(donnees_datatable[[variable]])], ] donnees_datatable[[variable]][
= 'identifiant'
variable
# Identifier les valeurs dupliquées
= donnees_python[variable][donnees_python[variable].duplicated()]
doublons_values
# Filtrer les lignes qui contiennent ces valeurs dupliquées
= donnees_python[donnees_python[variable].isin(doublons_values)] enDouble
12.4 Récupérer toutes les lignes pour les identifiants sans doublon
%let var = identifiant;
proc sql;
create table sansDouble as
select * from donnees_sas
group by &var.
having count(*) = 1;
quit;
<- "identifiant"
variable <- donnees_rbase[! donnees_rbase[, variable] %in%
sansDouble duplicated(donnees_rbase[, variable]), variable]] donnees_rbase[
<- "identifiant"
variable <- donnees_tidyverse %>%
sansDouble group_by(across(variable)) %>%
filter(n() == 1) %>%
ungroup()
<- "identifiant"
variable <- donnees_datatable[! donnees_datatable[[variable]] %chin%
sansDouble duplicated(donnees_datatable[[variable]])], ]
donnees_datatable[[variable]][<- donnees_datatable[donnees_datatable[[variable]] %notin%
sansDouble duplicated(donnees_datatable[[variable]])], ] donnees_datatable[[variable]][
= 'identifiant'
variable
# Identifier les valeurs dupliquées
= donnees_python[variable][donnees_python[variable].duplicated()]
doublons_values
# Filtrer les lignes qui contiennent ces valeurs dupliquées
= donnees_python[~donnees_python[variable].isin(doublons_values)] sansDouble
12.5 Suppression des doublons pour l’ensemble des variables
/* 1ère méthode */
proc sort data = donnees_sas nodupkey;
by _all_;
run;
/* 2e méthode, avec first. et last. (cf. infra) */
/* On récupère déjà la dernière variable de la base (on en aura besoin plus loin) */
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;
select name into :derniere_var from Var
where varnum = (select max(varnum) from Var);
quit;
proc sql noprint;
select name into :nom_col separated by " " from Var order by varnum;
quit;
%put Dernière variable de la base : &derniere_var.;
proc sort data = donnees_sas;by &nom_col.;run;
data sansDouble;
set donnees_sas;
by &nom_col.;
if first.&derniere_var.;
run;
<- unique(donnees_rbase)
donnees_rbase_sansdoublon <- donnees_rbase[! duplicated(donnees_rbase), ]
donnees_rbase_sansdoublon
# Autre solution (équivalente à la solution first. de SAS)
<- donnees_rbase[order(colnames(donnees_rbase), na.last = FALSE), ]
donnees_rbase_sansdoublon <- donnees_rbase[! duplicated(donnees_rbase[, colnames(donnees_rbase)], fromLast = TRUE), ] donnees_rbase_sansdoublon
<- donnees_tidyverse %>%
donnees_tidyverse arrange(pick(everything())) %>%
distinct()
# Autre solution
<- donnees_tidyverse %>%
donnees_tidyverse_sansdoublon arrange(across(everything())) %>%
distinct()
<- unique(donnees_datatable)
donnees_datatable_sansdoublon <- donnees_datatable[! duplicated(donnees_datatable), ] donnees_datatable_sansdoublon
= donnees_python.drop_duplicates() donnees_python_sansdoublon
12.6 Suppression des doublons pour une seule variable
proc sort data = donnees_sas;by _all_;run;
data sansDouble;
set donnees_sas;
by _all_;
if first.identifiant;
run;
<- donnees_rbase[order(colnames(donnees_rbase), na.last = FALSE), ]
donnees_rbase <- donnees_rbase[! duplicated(donnees_rbase$identifiant), , drop = FALSE] sansDouble
# L'option .keep_all = TRUE est nécessaire
# À FAIRE : REVOIR LE TRI PAR RAPPORT A SAS !!!
<- donnees_tidyverse %>%
sansDouble arrange(pick(everything())) %>%
distinct(identifiant, .keep_all = TRUE)
<- donnees_tidyverse %>%
sansDouble arrange(across(everything())) %>%
distinct(identifiant, .keep_all = TRUE)
setorderv(donnees_datatable, cols = colnames(donnees_datatable), na.last = FALSE)
<- donnees_datatable[! duplicated(donnees_datatable[, c("identifiant")]), ] sansDouble
# Trier le DataFrame par toutes les colonnes avec les valeurs NaN en premier
= donnees_python.sort_values(by=donnees_python.columns.tolist(), na_position='first')
donnees_python_sorted
# Supprimer les doublons en gardant la première occurrence pour chaque identifiant
= donnees_python_sorted.drop_duplicates(subset=['identifiant'], keep='first') sansDouble
12.7 Identifiants uniques
proc sql;
create table id as select distinct identifiant from donnees_sas order by identifiant;
quit;
/* Autre possibilité */
proc sort data = donnees_sas;by identifiant;run;
data id;
set donnees_sas (keep = identifiant);
by identifiant;
if first.identifiant;
run;
# Sous forme de data.frame
unique(donnees_rbase["identifiant"])
# Sous forme de vecteur
unique(donnees_rbase[, "identifiant"])
unique(donnees_rbase[["identifiant"]])
# Sous forme de tibble
%>%
donnees_tidyverse distinct(identifiant)
# Sous forme de vecteur
%>% distinct(identifiant) %>% pull() donnees_tidyverse
# Sous forme de data.table
unique(donnees_datatable[, "identifiant"])
# Sous forme de vecteur
unique(donnees_datatable[["identifiant"]])
# Sous forme de liste (vecteur) :
list(pd.unique(donnees_python['identifiant']))
# Dataframe
# Convertir les valeurs uniques en DataFrame
'identifiant']].drop_duplicates().reset_index(drop=True) donnees_python[[
12.8 Nombre de lignes uniques, sans doublon
proc contents data = donnees_sas out = Var noprint;run;
proc sql noprint;select name into :nom_col separated by ", " from Var order by varnum;quit;
proc sql;
select count(*) as Nb_Lignes_Uniques
from (select &nom_col., count(*) from donnees_sas group by &nom_col.);
quit;
nrow(unique(donnees_rbase))
%>%
donnees_tidyverse distinct() %>%
nrow()
uniqueN(donnees_datatable)
0] donnees_python.drop_duplicates().shape[
13 Transposer une base
13.1 Transposer une base
/* On commence déjà par calculer un tableau croisé comptant les occurrences */
proc freq data = donnees_sas;table Sexef * cspf / out = Nb;run;
proc sort data = Nb;by cspf Sexef;run;
proc print data = Nb;run;
/* On transpose le tableau */
proc transpose data = Nb out = transpose;
by cspf;
var count;
id Sexef;run;
data transpose;set transpose (drop = _name_ _label_);run;
proc print data = transpose;run;
# On commence déjà par calculer un tableau croisé comptant les occurrences
# as.data.frame.matrix est nécessaire, car le résultat de xtabs est un array
<- as.data.frame.matrix(xtabs( ~ cspf + sexef, data = donnees_rbase))
nb
# On transpose le tableau
# t() renvoie un objet matrix, d'où le as.data.frame
<- as.data.frame(t(nb)) nb_transpose
# On commence déjà par calculer un tableau croisé comptant les occurrences
<- donnees_tidyverse %>%
nb count(cspf, sexef) %>%
spread(sexef, n)
# On transpose le tableau (on fait passer sexef en ligne et cspf en colonne)
<- nb %>%
nb_transpose # Créer les combinaisons de cspf et sexef en ligne
pivot_longer(cols = -cspf, names_to = "sexef") %>%
# Mettre sexef en ligne et cspf en colonne
pivot_wider(names_from = cspf, values_from = value, values_fill = 0)
# Autre solution avec les packages janitor et sjmisc
library(janitor)
library(sjmisc)
<- donnees_tidyverse %>%
nb ::tabyl(cspf, sexef) %>%
janitor# colonne cspf comme nom de ligne
column_to_rownames(var="cspf")
<- nb %>%
nb_transpose ::rotate_df() sjmisc
# Etablissement d'un tableau croisé comptant les occurrences
<- donnees_datatable[, .N, by = list(cspf, sexef)]
nb <- dcast(nb, cspf ~ sexef, value.var = "N")
nb
# On transpose le tableau
transpose(nb, keep.names = "sexef", make.names = "cspf")
# Autre solution
dcast(melt(nb, id.vars = "cspf", variable.name = "sexef"), sexef ~ cspf)
# Tableau croisé en python :
= pd.crosstab(donnees_python['cspf'], donnees_python['sexef'])
nb # Transposer le tableau croisé
= nb.T nb_transpose
13.2 Passer d’une base en largeur (wide) à une base en longueur (long)
/* Note moyenne par identifiant */
/* On va créer une base Wide avec les notes en colonne et les identifiants en ligne */
%let notes = note_contenu note_formateur note_moyens note_accompagnement note_materiel;
proc sort data = donnees_sas;by identifiant;run;
proc means data = donnees_sas mean noprint;var ¬es.;output out = Temp;by identifiant;run;
data Wide;
set Temp (where = (_STAT_ = "MEAN") drop = _TYPE_ _FREQ_);
keep identifiant ¬es.;
drop _STAT_;
run;
/* On passe de Wide à Long */
/* On met les notes en ligne */
proc transpose data = Wide out = Long;by Identifiant;var ¬es.;run;
Lien utile : https://stats.oarc.ucla.edu/r/faq/how-can-i-reshape-my-data-in-r/.
# On souhaite mettre les notes en ligne et non en colonne
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes # Note moyenne par identifiant
<- aggregate(donnees_rbase[, varNotes], donnees_rbase[, "identifiant", drop = FALSE], mean, na.rm = TRUE)
wide_rbase
<- reshape(data = wide_rbase,
long_rbase varying = varNotes,
v.names = "notes",
timevar = "type_note",
times = varNotes,
new.row.names = NULL,
direction = "long")
<- long_rbase[order(long_rbase$identifiant), ]
long_rbase row.names(long_rbase) <- NULL
# On souhaite mettre les notes en ligne et non en colonne
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes # Note moyenne par identifiant
<- donnees_tidyverse %>%
wide_tidyverse group_by(identifiant) %>%
summarise(across(all_of(varNotes), ~ mean(.x, na.rm = TRUE)))
# On l'exprime en format long
# Mise en garde : ne pas écrire value_to !
<- wide_tidyverse %>%
long_tidyverse pivot_longer(cols = !identifiant,
names_to = "type_note",
values_to = "note") %>%
arrange(type_note, identifiant)
# On souhaite mettre les notes en ligne et non en colonne
# Note moyenne par identifiant
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- donnees_datatable[, lapply(.SD, mean, na.rm = TRUE), by = identifiant, .SDcols = varNotes]
wide_datatable
<- melt(wide_datatable,
long_datatable id.vars = c("identifiant"),
measure.vars = varNotes,
variable.name = "type_note",
value.name = "note")
# On souhaite mettre les notes en ligne et non en colonne
= ["note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel"]
varNotes
# Calculer la note moyenne par identifiant
= donnees_python.groupby('identifiant')[varNotes].mean().reset_index()
wide_python
# Transformer les données de large à long
= wide_python.melt(id_vars=['identifiant'],
long_python =varNotes,
value_vars='type_note',
var_name='notes')
value_name
# Trier par identifiant
= long_python.sort_values(by='identifiant').reset_index(drop=True) long_python
13.3 Passer d’une base en longueur (long) à une base en largeur (wide)
Le code précédent doit être lancé au préalable.
/* On souhaite mettre les notes en ligne et non en colonne */
/* On commence par calculer les notes moyennes par identifiant */
%let notes = note_contenu note_formateur note_moyens note_accompagnement note_materiel;
proc sort data = donnees_sas;by identifiant;run;
proc means data = donnees_sas mean noprint;var ¬es.;output out = Temp;by identifiant;run;
data Wide;
set Temp (where = (_STAT_ = "MEAN") drop = _TYPE_ _FREQ_);
keep identifiant ¬es.;
drop _STAT_;
run;
/* On passe de Wide à Long */
proc transpose data = Wide out = Long;by Identifiant;var ¬es.;run;
data Long;set Long (rename = (_NAME_ = Type_Note COL1 = Note));run;
/* On passe de Long à Wide */
proc transpose data = Long out = Wide;
by Identifiant;
var Note;
id Type_Note;run;
Lien utile : https://stats.oarc.ucla.edu/r/faq/how-can-i-reshape-my-data-in-r/.
# Passer de long à wide : on souhaite revenir à la situation initiale
<- reshape(long_rbase,
wide_rbase timevar = "type_note",
idvar = c("identifiant", "id"),
direction = "wide")
# Passer de long à wide : on souhaite revenir à la situation initiale
# Mise en garde : ne pas écrire value_from !
<- pivot_wider(long_tidyverse,
wide_tidyverse names_from = type_note,
values_from = note)
<- dcast(long_datatable, identifiant ~ type_note, value.var = "note") wide_datatable
= long_python.pivot_table(index='identifiant',
wide_python ='type_note',
columns='notes').reset_index() values
14 Gestion par groupe
14.1 Numéroter les lignes
14.1.1 Numéroter les lignes de la base
data donnees_sas;
set donnees_sas;
Num_observation = _n_;run;
/* Autre solution */
proc sql noprint;select count(*) into :nbLignes from donnees_sas;quit;
data numLigne;do Num_observation = 1 to &nbLignes.;output;end;run;
/* Autre possibilité */
data _NULL_;
set donnees_sas nobs = n;
call symputx('nbLignes', n);
run;
%put Nombre de lignes : &nbLignes.;
/* Le merge "simple" (sans by) va seulement concaténer les deux bases l'une à côté de l'autre */
data donnees_sas;
merge donnees_sas numLigne;
run;
# Numéro de l'observation : 2 manières différentes
$num_observation <- seq(1, nrow(donnees_rbase))
donnees_rbase$num_observation <- seq_len(nrow(donnees_rbase))
donnees_rbase$num_observation <- row.names(donnees_rbase) donnees_rbase
# Numéro de l'observation
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
mutate(num_observation = row_number())
# Numéro de l'observation : 2 manières différentes
:= .I]
donnees_datatable[, num_observation := seq_len(.N)] donnees_datatable[, num_observation
# Python commence le compte à 0 (penser à ajouter 1 pour coïncider avec la numérotation de R)
'num_observation'] = range(1, len(donnees_python) + 1)
donnees_python[
'num_observation'] = donnees_python.index + 1 donnees_python[
14.1.2 Numéroter les contrats de l’individu
/* Numéro du contrat de chaque individu, contrat trié par date d'entrée */
proc sort data = donnees_sas;by identifiant date_entree;run;
data donnees_sas;
set donnees_sas;
by identifiant date_entree;
retain num_contrat;
if first.identifiant then num_contrat = 1;
else num_contrat = num_contrat + 1;
run;
# Numéro du contrat de chaque individu, contrat trié par date d'entrée
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase $un <- 1
donnees_rbase# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
$numero_contrat <- ave(donnees_rbase$un, donnees_rbase$identifiant, FUN = cumsum)
donnees_rbase$un <- NULL
donnees_rbase
# Autre solution
# Utiliser seq_along ne nécessite pas un tri préalable !
$numero_contrat <- as.numeric(ave(donnees_rbase$identifiant, donnees_rbase$identifiant, FUN = seq_along))
donnees_rbase
# Autre solution : order pour éviter le as.numeric
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase $numero_contrat <- ave(order(donnees_rbase$date_entree), donnees_rbase$identifiant, FUN = seq_along) donnees_rbase
# Numéro du contrat de chaque individu, contrat trié par date d'entrée
# arrange() va permettre de trier les observations par identifiant et date d'entrée
<- donnees_tidyverse %>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
mutate(numero_contrat = row_number()) %>%
ungroup()
# À FAIRE : Dans group_by, à quoi sert le drop ?
# Numéro du contrat de chaque individu, contrat trié par date d'entrée
setorder(donnees_datatable, "identifiant", "date_entree", na.last = FALSE)
:= rowid(identifiant)]
donnees_datatable[, numero_contrat := seq_len(.N), by = identifiant]
donnees_datatable[, numero_contrat
# Les seuls numéros de colonnes
rowidv(donnees_datatable, identifiant)
# 1. Trier les données par 'identifiant' et 'date_entree'
= donnees_python.sort_values(by=['identifiant', 'date_entree'])
donnees_python
# 2. Créer le numéro de contrat
'numero_contrat'] = donnees_python.groupby('identifiant').cumcount() + 1 donnees_python[
14.2 Première et dernière ligne par identifiant
14.2.1 Première ligne par identifiant
proc sort data = donnees_sas;by identifiant date_entree;run;
/* L'instruction options permet de ne pas afficher d'erreur si la variable numero_contrat n'existe pas */
options dkricond=nowarn dkrocond=nowarn;
data donnees_sas;
set donnees_sas (drop = numero_contrat);
by identifiant date_entree;
retain numero_contrat 0;
if first.identifiant then numero_contrat = 1;
else numero_contrat = numero_contrat + 1;
run;
options dkricond=warn dkrocond=warn;
/* Pour trier les colonnes */
data donnees_sas;
retain identifiant date_entree numero_contrat numero_contrat;
set donnees_sas;
run;
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase ! duplicated(donnees_rbase$identifiant), , drop = FALSE] donnees_rbase[
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
filter(row_number() == 1) %>%
ungroup()
# Autres solutions
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
slice(1) %>%
ungroup()
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
slice_head(n = 1) %>%
ungroup()
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
filter(row_number() == nth(row_number(), 1)) %>%
ungroup()
1], by = identifiant]
donnees_datatable[, .SD[
# On peut aussi utiliser keyby si l'on souhaite que les résultats soient triés par la variable de groupement (ici identifiant)
1], keyby = identifiant] donnees_datatable[, .SD[
='identifiant', keep='first') donnees_python.drop_duplicates(subset
14.2.2 Dernière ligne par identifiant
proc sort data = donnees_sas;by identifiant date_entree;run;
/* L'instruction options permet de ne pas afficher d'erreur si la variable numero_contrat n'existe pas */
options dkricond=nowarn dkrocond=nowarn;
data donnees_sas;
set donnees_sas (drop = numero_contrat);
by identifiant date_entree;
retain numero_contrat 0;
if first.identifiant then numero_contrat = 1;
else numero_contrat = numero_contrat + 1;
run;
options dkricond=warn dkrocond=warn;
/* Pour trier les colonnes */
data donnees_sas;
retain identifiant date_entree numero_contrat numero_contrat;
set donnees_sas;
run;
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase ! duplicated(donnees_rbase$identifiant, fromLast = TRUE), , drop = FALSE] donnees_rbase[
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
filter(row_number() == n()) %>%
ungroup()
# Autres solutions
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
slice(n()) %>%
ungroup()
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
filter(row_number() == nth(row_number(), -1)) %>%
ungroup()
= identifiant] donnees_datatable[, .SD[.N], by
='identifiant', keep='last') donnees_python.drop_duplicates(subset
14.3 Le premier contrat, le dernier contrat, ni le premier ni le dernier contrat de chaque individu
proc sort data = donnees_sas;by identifiant date_entree;run;
data donnees_sas;
set donnees_sas;
by identifiant date_entree;
1);
Premier_Contrat = (first.identifiant = 1);
Dernier_Contrat = (last.identifiant = 0 and last.identifiant = 0);
Ni_Prem_Ni_Der = (first.identifiant = run;
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase $premier_contrat <- ifelse(! duplicated(donnees_rbase$identifiant, fromLast = FALSE),
donnees_rbase1, 0)
$dernier_contrat <- ifelse(! duplicated(donnees_rbase$identifiant, fromLast = TRUE),
donnees_rbase1, 0)
$ni_prem_ni_der <- ifelse(! c(! duplicated(donnees_rbase$identifiant, fromLast = FALSE) | ! duplicated(donnees_rbase$identifiant, fromLast = TRUE)),
donnees_rbase1, 0)
# Premier contrat
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
group_by(identifiant) %>%
mutate(premier_contrat = ifelse(row_number() == 1, 1, 0)) %>%
ungroup()
# Dernier contrat
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
group_by(identifiant) %>%
mutate(dernier_contrat = ifelse(row_number() == n(), 1, 0)) %>%
ungroup()
# Ni le premier, ni le dernier contrat
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
group_by(identifiant) %>%
mutate(ni_prem_ni_der = ifelse( ! (row_number() == n() | row_number() == 1), 1, 0)) %>%
ungroup()
<- donnees_datatable[order(identifiant, date_entree, na.last = FALSE)]
donnees_datatable := fifelse(! duplicated(identifiant, fromLast = FALSE),
donnees_datatable[, premier_contrat 1, 0)]
:= fifelse(! duplicated(identifiant, fromLast = TRUE),
donnees_datatable[, dernier_contrat 1, 0)]
:= fifelse(! c(! duplicated(identifiant, fromLast = FALSE) | ! duplicated(identifiant, fromLast = TRUE)),
donnees_datatable[, ni_prem_ni_der 1, 0)]
# 1. Trier les données par 'identifiant' et 'date_entree'
= donnees_python.sort_values(by=['identifiant', 'date_entree'])
donnees_python
# Premier contrat
'premier_contrat'] = 1 - donnees_python.duplicated(subset='identifiant', keep='first').astype(int)
donnees_python[
# Dernier contrat
'dernier_contrat'] = 1 - donnees_python.duplicated(subset='identifiant', keep='last').astype(int)
donnees_python[
# Ni premier ni dernier contrat
'ni_prem_ni_der'] = (~donnees_python['premier_contrat'].astype(bool) & ~donnees_python['dernier_contrat'].astype(bool)).astype(int) donnees_python[
14.4 Sélection de lignes par identifiant
14.4.1 Les 2 premières lignes de chaque identifiant
/* Numéro du contrat */
proc sort data = donnees_sas;by identifiant date_entree;run;
data donnees_sas;
set donnees_sas;
by identifiant date_entree;
retain num_contrat;
if first.identifiant then num_contrat = 1;
else num_contrat = num_contrat + 1;
run;
proc sort data = donnees_sas;by identifiant numero_contrat;run;
proc sql;
select * from donnees_sas group by identifiant
having numero_contrat <= 2;
quit;
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase
# En utilisant la fonction by
<- Reduce(rbind, by(donnees_rbase, donnees_rbase["identifiant"], head, 2))
deux_premieres_lignes
# En utilisant la fonction split pour découper par identifiant, et en ne retenant que les deux premières lignes des groupes créés
<- do.call(rbind,
deux_premieres_lignes lapply(
split(donnees_rbase, donnees_rbase$identifiant), head, 2
))
# On peut aussi utiliser les numéros de contrat
$un <- 1L
donnees_rbase# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
$numero_contrat <- ave(donnees_rbase$un, donnees_rbase$identifiant, FUN = cumsum)
donnees_rbase<- donnees_rbase[which(donnees_rbase$numero_contrat <= 2), ]
deux_premieres_lignes $un <- NULL
donnees_rbase
# Version en R Base
#https://stackoverflow.com/questions/14800161/select-the-top-n-values-by-group
<- donnees_tidyverse %>%
deux_premieres_lignes # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
group_by(identifiant) %>%
slice(1:2) %>%
ungroup()
<- donnees_datatable[, .SD[1:2], by = identifiant] deux_premieres_lignes
= (donnees_python
deux_premieres_lignes =['identifiant', 'date_entree'], ascending=[True, True], na_position='last')
.sort_values(by'identifiant')
.groupby(2)
.head(=True)
.reset_index(drop )
14.4.2 Les 2 dernières lignes de chaque identifiant
/* Numéro du contrat */
proc sort data = donnees_sas;by identifiant date_entree;run;
data donnees_sas;
set donnees_sas;
by identifiant date_entree;
retain num_contrat;
if first.identifiant then num_contrat = 1;
else num_contrat = num_contrat + 1;
run;
proc sort data = donnees_sas;by identifiant numero_contrat;run;
proc sql;
select * from donnees_sas group by identifiant
having numero_contrat >= count(*) - 1;
quit;
<- donnees_rbase[unlist(tapply(seq_len(nrow(donnees_rbase)),
deux_dernieres_lignes $identifiant,
donnees_rbasefunction(x) tail(x, 2))), ]
# Version en R Base
#https://stackoverflow.com/questions/14800161/select-the-top-n-values-by-group
# À FAIRE : ne fait pas la même-chose !
<- donnees_tidyverse %>%
deux_dernieres_lignes # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
group_by(identifiant) %>%
slice(n() - 2) %>%
ungroup()
<- donnees_datatable[, tail(.SD, 2), by = identifiant] deux_dernieres_lignes
= (donnees_python
deux_dernieres_lignes =['identifiant', 'date_entree'], ascending=[True, True], na_position='last')
.sort_values(by'identifiant')
.groupby(2)
.tail(=True)
.reset_index(drop )
14.4.3 2e ligne de l’individu (et rien si l’individu a 1 seule ligne)
/* Numéro du contrat */
proc sort data = donnees_sas;by identifiant date_entree;run;
data donnees_sas;
set donnees_sas;
by identifiant date_entree;
retain numero_contrat 0;
if first.identifiant then numero_contrat = 1;
else numero_contrat = numero_contrat + 1;
run;
/* 2 stratégies possibles */
data Deuxieme_Contrat;
set donnees_sas;
if numero_contrat = 2;
run;
data Deuxieme_Contrat;
set donnees_sas (where = (numero_contrat = 2));
run;
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase unlist(tapply(seq_len(nrow(donnees_rbase)), donnees_rbase$identifiant, function(x) head(x, 2))), ]
donnees_rbase[
# Avec le numéro de contrat
$un <- 1L
donnees_rbase# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
$numero_contrat <- ave(donnees_rbase$un, donnees_rbase$identifiant, FUN = cumsum)
donnees_rbase<- donnees_rbase[donnees_rbase$numero_contrat == 2, ]
deuxieme_ligne $un <- NULL donnees_rbase
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
filter(row_number() == 2) %>%
ungroup()
<- donnees_datatable[, .SD[2], by = identifiant] deuxieme_ligne
= (
deuxieme_ligne_par_groupe
donnees_python=['identifiant', 'date_entree'], ascending=[True, True], na_position='last')
.sort_values(by'identifiant')
.groupby(1) # 1 correspond à la deuxieme ligne
.nth(
.reset_index() )
14.4.4 L’avant-dernière ligne de l’individu (et rien si l’individu a 1 seul contrat)
/* Nécessite d'avoir le numéro du contrat */
proc sql;
select * from donnees_sas group by identifiant
having numero_contrat = count(*) - 1;
quit;
unlist(tapply(seq_len(nrow(donnees_rbase)), donnees_rbase$identifiant, function(x) x[length(x)-1])), ] donnees_rbase[
%>%
donnees_tidyverse group_by(identifiant) %>%
# Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
filter(row_number() == nth(row_number(), -2))
-1], by = identifiant] donnees_datatable[, .SD[.N
= (
deuxieme_ligne_par_groupe
donnees_python=['identifiant', 'date_entree'], ascending=[True, True], na_position='last')
.sort_values(by'identifiant')
.groupby(1) # 1 correspond à la deuxieme ligne
.nth(
.reset_index() )
14.5 Sélection par groupement
14.5.1 Personnes qui ont eu au moins une entrée en 2022
/* Personnes qui ont eu au moins une entrée en 2022 */
proc sql;
select *
from donnees_sas
group by identifiant
having sum(year(date_entree) = 2022) >= 1;
quit;
# Personnes qui ont eu au moins une entrée en 2022
<- subset(donnees_rbase, identifiant %in% unique(identifiant[lubridate::year(date_entree) %in% c(2022)]))
auMoins2022
# Autre solution : ne semble possible que pour une seule variable
# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
<- donnees_rbase[with(donnees_rbase, ave(lubridate::year(date_entree) %in% c(2022), identifiant, FUN = any)), ]
auMoins2022 <- subset(
auMoins2022 transform(donnees_rbase,
cond = ave(lubridate::year(date_entree), identifiant, FUN = function(x) sum(ifelse(x %in% c(2022), 1, 0)))),
>= 1)
cond $cond <- NULL auMoins2022
# Personnes qui ont eu au moins une entrée en 2022
<- donnees_tidyverse %>%
auMoins2022 group_by(identifiant) %>%
filter(any(lubridate::year(date_entree) == 2022)) %>%
ungroup()
# Ou plus simplement
<- donnees_tidyverse %>%
auMoins2022 filter(any(lubridate::year(date_entree) == 2022), .by = identifiant)
# Personnes qui ont eu au moins une entrée en 2022
# Une fonction year() est déjà implémentée en data.table, l'usage de lubridate est inutile
<- donnees_datatable[, if (any(data.table::year(date_entree) %in% 2022)) .SD, by = identifiant]
auMoins2022
# Autre solution
<- donnees_datatable[, if (sum(data.table::year(date_entree) == 2022, na.rm = TRUE) > 0) .SD, by = identifiant] auMoins2022
= (
auMoins2022
donnees_python'identifiant')
.groupby(filter(lambda x: (x['date_entree'].dt.year == 2022).any())
. )
14.5.2 Personnes qui ont suivi à la fois une formation qualifiée et une formation non qualifiée
proc sql;
create table Qualif_Non_Qualif as
select *
from donnees_sas
group by identifiant
having sum(Niveau = "Non qualifie") >= 1 and sum(Niveau = "Non qualifie") >= 1;
quit;
# Personnes qui ont suivi à la fois une formation qualifiée et une formation non qualifiée
# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
<- subset(
qualif_non_qualif transform(donnees_rbase,
qualif = ave(niveau, identifiant, FUN = function(x) sum(ifelse(x == "Qualifié", 1, 0), na.rm = TRUE)),
non_qualif = ave(niveau, identifiant, FUN = function(x) sum(ifelse(x == "Non Qualifié", 1, 0), na.rm = TRUE))),
>= 1 & non_qualif >= 1) qualif
# Personnes qui ont suivi à la fois une formation qualifiée et une formation non qualifiée
<- donnees_tidyverse %>%
qualif_non_qualif group_by(identifiant) %>%
filter(any(niveau == "Qualifié") & any(niveau == "Non qualifié")) %>%
ungroup()
# Ou plus simplement
<- donnees_tidyverse %>%
qualif_non_qualif filter(any(niveau == "Qualifié") & any(niveau == "Non qualifié"), .by = identifiant)
# Personnes qui ont suivi à la fois une formation qualifiée et une formation non qualifiée
# Méthode la plus simple
if (sum(niveau == "Qualifié", na.rm = TRUE) > 0 & sum(niveau == "Non qualifié", na.rm = TRUE) > 0) .SD, by = identifiant]
donnees_datatable[,
# Autre méthode
`:=` (qualif = sum(fifelse(niveau == "Qualifié", 1, 0), na.rm = TRUE),
donnees_datatable[, non_qualif = sum(fifelse(niveau == "Non qualifié", 1, 0), na.rm = TRUE)),
= identifiant][qualif > 0 & non_qualif > 0]
by
# Autre méthode
`:=` (qualif = sum(niveau == "Qualifié", na.rm = TRUE), non_qualif = sum(niveau == "Non qualifié", na.rm = TRUE)), by = identifiant][qualif > 0 & non_qualif > 0]
donnees_datatable[,
# Group by et Having de SQL
# https://github.com/Rdatatable/data.table/issues/788
= (
qualif_non_qualif
donnees_python'identifiant')
.groupby(filter(lambda x: (x['niveau'] == 'Qualifié').any() and (x['niveau'] == 'Non qualifié').any())
. )
14.5.3 Personnes qui ont suivi deux contrats, et seulement deux, dont l’un au moins a débuté en 2022
/* Personnes qui ont suivi deux contrats, et seulement deux, dont l'un au moins a débuté en 2022 */
proc sql;
create table Deux_Contrats as
select *
from donnees_sas
group by identifiant
having count(*) = 2 and sum(year(date_entree) = 2022) >= 1;
quit;
# Personnes qui ont suivi deux contrats, et seulement deux, dont l'un au moins a débuté en 2022
# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
<- subset(
deux_contrats transform(donnees_rbase,
nb = ave(identifiant, identifiant, FUN = length),
an = ave(date_entree, identifiant,
FUN = function(x)
sum(ifelse(lubridate::year(x) == 2022, 1, 0), na.rm = TRUE))),
== 2 & an >= 1) nb
# Personnes qui ont suivi deux contrats, et seulement deux, dont l'un au moins a débuté en 2022
<- donnees_tidyverse %>%
deux_contrats group_by(identifiant) %>%
filter(n() == 2) %>%
filter(any(lubridate::year(date_entree) == 2022)) %>%
ungroup()
# Ou plus simplement
<- donnees_tidyverse %>%
deux_contrats filter(any(lubridate::year(date_entree) == 2022 & n() == 2), .by = identifiant)
# Personnes qui ont suivi deux contrats, et seulement deux, dont l'un au moins a débuté en 2022
# Une fonction year() est déjà implémentée en data.table, l'usage de lubridate est inutile
if (.N == 2 & sum(data.table::year(date_entree) == 2022, na.rm = TRUE) >= 1) .SD, by = identifiant] donnees_datatable[,
= (
deux_contrats
donnees_python'identifiant')
.groupby(filter(lambda x: len(x) == 2 and (x['date_entree'].dt.year == 2022).any())
. )
14.6 Ajouter le nombre d’observations par CSP
proc sql;
create table donnees_sas as
select a.*, b.n
from donnees_sas a left join
select CSPF, count(*) as n from donnees_sas group by CSPF) b on CSPF = CSPF
(order by identifiant;
quit;
# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
<- transform(donnees_rbase,
donnees_rbase n = ave(cspf, cspf, FUN = length))
<- donnees_tidyverse %>% add_count(cspf)
donnees_tidyverse
# Autre solution
<- donnees_tidyverse %>%
donnees_tidyverse group_by(cspf) %>%
mutate(n = n()) %>%
ungroup()
:= .N, by = cspf]
donnees_datatable[, n := length(identifiant), by = cspf] donnees_datatable[, n
'n'] = donnees_python.groupby('cspf')['cspf'].transform('count') donnees_python[
14.7 Ajouter deux colonnes désignant la note moyenne et la somme de Note_Contenu, par individu
/* 1ère solution */
proc sort data = donnees_sas;by identifiant;run;
proc means data = donnees_sas mean noprint;
var Note_Contenu;
by identifiant;
output out = Temp;
run;
data Temp;
set Temp (where = (_STAT_ = "MEAN"));
keep identifiant Note_Contenu;
rename Note_Contenu = Note_Contenu_Moyenne;
run;
data donnees_sas;
merge donnees_sas (in = a) Temp (in = b);
by identifiant;
if a;
run;
/* 2e solution : plus souple */
/* Pour supprimer la variable ajoutée lors de la 1ère solution */
data donnees_sas;
set donnees_sas (drop = Note_Contenu_Moyenne Note_Contenu_Somme);
run;
proc sql;
create table donnees_sas as
select *
from donnees_sas a left join
select identifiant,
(mean(Note_Contenu) as Note_Contenu_Moyenne,
sum(Note_Contenu) as Note_Contenu_Somme
from donnees_sas group by identifiant) b
on a.identifiant = b.identifiant
order by identifiant;
quit;
# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
<- transform(donnees_rbase,
donnees_rbase note_contenu_moyenne = ave(note_contenu, identifiant, FUN = mean, na.rm = TRUE),
note_contenu_somme = ave(note_contenu, identifiant, FUN = sum, na.rm = TRUE))
<- donnees_tidyverse %>%
donnees_tidyverse group_by(identifiant) %>%
mutate(note_contenu_moyenne = mean(note_contenu, na.rm = TRUE),
note_contenu_somme = sum(note_contenu, na.rm = TRUE)) %>%
ungroup()
`:=` (note_contenu_moyenne = mean(note_contenu, na.rm = TRUE),
donnees_datatable[, note_contenu_somme = sum(note_contenu, na.rm = TRUE)), by = identifiant]
# Moyenne de chaque note par individu
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes paste0(notes, "_m") := lapply(.SD, mean, na.rm = TRUE), .SDcols = notes, keyby = identifiant] donnees_datatable[,
'note_contenu_moyenne'] = donnees_python.groupby('identifiant')['note_contenu'].transform('mean')
donnees_python['note_contenu_somme'] = donnees_python.groupby('identifiant')['note_contenu'].transform('sum') donnees_python[
14.8 Ajouter une variable d’entrée initiale par individu
On souhaite ajouter dans la base une variable représentant la première date d’entrée de l’individu.
proc sort data = donnees_sas;by Identifiant date_entree;run;
data donnees_sas;
set donnees_sas;
by Identifiant date_entree;
format premiere_entree ddmmyy10.;
retain premiere_entree;
if first.Identifiant then premiere_entree = date_entree;
else premiere_entree = premiere_entree;
run;
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase # MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
<- transform(donnees_rbase,
donnees_rbase premiere_entree = ave(date_entree, identifiant, FUN = function(x) head(x, 1)))
# Autre solution, sans le tri préalable
<- transform(donnees_rbase,
donnees_rbase premiere_entree = ave(date_entree, identifiant, FUN = function(x) min(x) ))
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
mutate(premiere_entree = head(date_entree, 1), .by = identifiant)
# Autre solution
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
group_by(identifiant) %>%
mutate(premiere_entree = case_when(row_number() == 1 ~ date_entree,
TRUE ~ NA)) %>%
fill(premiere_entree, .direction = c("down")) %>%
ungroup()
setorderv(donnees_datatable, c("identifiant", "date_entree"), na.last = FALSE)
:= head(date_entree, 1), by = identifiant] donnees_datatable[, premiere_entree
'premiere_entree'] = donnees_python.groupby('identifiant')['date_entree'].transform('min') donnees_python[
14.9 Ligne où se trouve une valeur maximale pour un individu (À REVOIR)
On cherche, pour chaque individu, la ligne où se trouve la valeur maximale de note_contenu.
https://stackoverflow.com/questions/24558328/select-the-row-with-the-maximum-value-in-each-group
/* On note un comportement un peu différent de R : la note de l'identifiant 087 est inconnue, et l'identifiant est conservé en SAS, pas en R */
/* Renvoie toutes les lignes où note_contenu est maximale, s'il y a plusieurs ex-aequo */
proc sort data = donnees_sas;by identifiant descending note_contenu;run;
data ligne_max_note_contenu;
set donnees_sas;
by identifiant descending note_contenu;
if first.note_contenu;
run;
/* Renvoie seulement la première ligne en cas d'ex-aequo */
proc sort data = donnees_sas;by identifiant descending note_contenu;run;
data ligne_max_note_contenu;
set donnees_sas;
by identifiant descending note_contenu;
if first.identifiant;
run;
# On note un comportement un peu différent de R : la note de l'identifiant 087 est inconnue, et la ligne est conservée en SAS, pas en R.
# Ceci est dû au fait que la fonction max ignore les NA.
# Renvoie toutes les lignes où note_contenu est maximale, s'il y a plusieurs ex-aequo
<- merge(aggregate(note_contenu ~ identifiant, max, data = donnees_rbase), donnees_rbase)
ligne_max_note_contenu
# Autre solution
# MISE EN GARDE : en utilisant la fonction ave, toujours précéder la fonction de FUN = !
<- donnees_rbase[with(donnees_rbase, which(note_contenu == ave(note_contenu, identifiant, FUN = max))), ]
ligne_max_note_contenu
# Renvoie seulement la première ligne en cas d'ex-aequo
<- donnees_rbase[order(donnees_rbase$identifiant, -donnees_rbase$note_contenu), ]
donnees_rbase <- donnees_rbase[! duplicated(donnees_rbase$identifiant), ]
ligne_max_note_contenu
# Autre solution
<- do.call(rbind, lapply(split(donnees_rbase, as.factor(donnees_rbase$identifiant)), function(x) {return(x[which.max(x$note_contenu), ])})) ligne_max_note_contenu
# On note un comportement un peu différent de R : la note de l'identifiant 087 est inconnue, et l'identifiant est conservé en SAS, pas en R
# Renvoie toutes les lignes où note_contenu est maximale, s'il y a plusieurs ex-aequo
<- donnees_tidyverse %>%
ligne_max_note_contenu group_by(identifiant) %>%
slice_max(note_contenu)
# Renvoie seulement la première ligne en cas d'ex-aequo
<- donnees_tidyverse %>%
ligne_max_note_contenu group_by(identifiant) %>%
slice(which.max(note_contenu))
# On note un comportement un peu différent de R : la note de l'identifiant 087 est inconnue, et l'identifiant est conservé en SAS, pas en R
# Renvoie toutes les lignes où note_contenu est maximale, s'il y a plusieurs ex-aequo
<- donnees_datatable[donnees_datatable[, .I[note_contenu == max(note_contenu)], by = identifiant]$V1]
ligne_max_note_contenu
# Renvoie seulement la première ligne en cas d'ex-aequo
<- donnees_datatable[, .SD[which.max(note_contenu)], by = identifiant] ligne_max_note_contenu
# Renvoie toutes les lignes où note_contenu est maximale, s'il y a plusieurs ex-aequo
= (
ligne_max_note_contenu
donnees_python'identifiant')
.groupby(apply(lambda x: x[x['note_contenu'] == x['note_contenu'].max()], include_groups=False).reset_index()
. )
14.10 Identifier les changements d’état
Numérote les états successifs identiques d’un même identifiant. À chaque changement d’état d’un même individu, la variable d’état est incrémentée d’une unité.
/* On suppose que l'on dispose d'une base sur le type de financement de la formation */
data Financement_sas;
infile cards dsd dlm='|';
format Identifiant $3. Date ddmmyy10. Financement $10.;
input Identifiant $ Date :ddmmyy10. Financement $;
cards;
173|02/01/2022|Public
173|18/07/2022|Public
173|15/09/2022|Privé
173|28/12/2022|Public
173|02/04/2023|Privé
173|06/06/2024|Privé
211|02/07/2024|Privé
;run;
proc sort data = Financement_sas;by Identifiant Date Financement;run;
data Financement_sas;
set Financement_sas;
lag(Financement);
Financement_1 = by Identifiant;
retain Etat;
if first.Identifiant then Etat = 1;
else if Financement = Financement_1 then Etat = Etat;
else if Financement ne Financement_1 then Etat = Etat + 1;
run;
# On suppose que l'on dispose d'une base sur le type de financement de la formation
<- data.frame(
financement_rbase identifiant = c(rep("173", 6), "211"),
date = c("02/01/2022", "18/07/2022", "15/09/2022", "28/12/2022", "02/04/2023", "06/06/2024", "02/07/2024"),
financement = c("Public", "Public", "Privé", "Public", "Privé", "Privé", "Privé")
)$date <- lubridate::dmy(financement_rbase$date)
financement_rbase<- financement_rbase[order(financement_rbase$identifiant, financement_rbase$date, na.last = FALSE), ]
financement_rbase $etat <- rep(seq_along(rle(financement_rbase$financement)$values),
financement_rbasetimes = rle(financement_rbase$financement)$lengths)
# On suppose que l'on dispose d'une base sur le type de financement de la formation
<- data.frame(
financement_tidyverse identifiant = c(rep("173", 6), "211"),
date = c("02/01/2022", "18/07/2022", "15/09/2022", "28/12/2022", "02/04/2023", "06/06/2024", "02/07/2024"),
financement = c("Public", "Public", "Privé", "Public", "Privé", "Privé", "Privé")
)
%>%
financement_tidyverse arrange(identifiant, date) %>%
group_by(identifiant) %>%
mutate(etat = consecutive_id(financement))
# On suppose que l'on dispose d'une base sur le type de financement de la formation
<- data.table(
financement_datatable identifiant = c(rep("173", 6), "211"),
date = c("02/01/2022", "18/07/2022", "15/09/2022", "28/12/2022", "02/04/2023", "06/06/2024", "02/07/2024"),
financement = c("Public", "Public", "Privé", "Public", "Privé", "Privé", "Privé")
)
setorder(financement_datatable, identifiant, date)
:= rleid(financement_datatable), by = identifiant] financement_datatable[, etat
= pd.DataFrame({
financement_python 'identifiant': ['173']*6 + ['211'],
'date': ['02/01/2022', '18/07/2022', '15/09/2022', '28/12/2022', '02/04/2023', '06/06/2024', '02/07/2024'],
'financement': ['Public', 'Public', 'Privé', 'Public', 'Privé', 'Privé', 'Privé']
})
# Création de la fonction pour identifier les groupes consécutifs
def consecutive_id(series):
return (series != series.shift()).cumsum()
# Transformation des dates en format datetime
'date'] = pd.to_datetime(financement_python['date'], format='%d/%m/%Y')
financement_python[
# Tri des données par identifiant et date
= financement_python.sort_values(by=['identifiant', 'date'])
financement_python
# Application de la fonction pour identifier les groupes consécutifs
'etat'] = (
financement_python['identifiant')['financement']
financement_python.groupby(
.transform(consecutive_id) )
15 Gestion par rangées de lignes
15.1 Sélectionner les lignes avec au moins une note inférieure à 10
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data Note_Inferieure_10;
set donnees_sas;
%macro Inf10;
%global temp;
%let temp = ;
%do i = 1 %to %sysfunc(countw(¬es.));
%let j = %scan(¬es., &i.);
&j._inf_10 = (&j. < 10 and not missing(&j.));
%let temp = &temp. &j._inf_10;
%end;
%mend Inf10;
%Inf10;
if sum(of &temp.) >= 1;
drop &temp.;
run;
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes apply(donnees_rbase[, varNotes], 1, function(x) any(x < 10, na.rm = TRUE)), ] donnees_rbase[
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes %>%
donnees_tidyverse filter(if_any(varNotes, ~ .x < 10))
# Autre solution
%>%
donnees_tidyverse filter_at(varNotes, any_vars(. < 10))
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- donnees_datatable[donnees_datatable[, .I[rowSums(.SD < 10, na.rm = TRUE) >= 1], .SDcols = varNotes]]
note_moins_10
# Autre solution
# Le Reduce(`|`, ...) permet d'appliquer la condition | (ou) à tous les élements de la ligne, qui sont une vérification d'une note < 10
<- donnees_datatable[donnees_datatable[, Reduce(`|`, lapply(.SD, `<`, 10)), .SDcols = varNotes]]
note_moins_10
# https://arelbundock.com/posts/datatable_rowwise/
= ["note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel"]
varNotes apply(lambda x: (x < 10).any(), axis=1)] donnees_python[donnees_python[varNotes].
15.2 Sélectionner les lignes avec toutes les notes supérieures à 10
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data Note_Sup_10;
set donnees_sas;
%macro Sup10;
%global temp;
%let temp = ;
%do i = 1 %to %sysfunc(countw(¬es.));
%let j = %scan(¬es., &i.);
&j._sup_10 = (&j. >= 10);
%let temp = &temp. &j._sup_10;
%end;
%mend Sup10;
%Sup10;
if sum(of &temp.) = %sysfunc(countw(¬es.));
drop &temp.;
run;
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes
# Toutes les notes >= 10 et non manquantes
apply(donnees_rbase[, varNotes], 1, function(x) all(x >= 10 & ! is.na(x), na.rm = TRUE)), ] donnees_rbase[
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes
# Toutes les notes >= 10 et non manquantes
%>%
donnees_tidyverse filter(if_all(varNotes, ~ . >= 10))
# Autre solution
%>%
donnees_tidyverse filter_at(varNotes, all_vars(. >= 10))
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes
# Toutes les notes >= 10 et non manquantes
<- donnees_datatable[
note_sup_10 rowSums(.SD >= 10, na.rm = TRUE) == length(varNotes)], .SDcols = varNotes]]
donnees_datatable[, .I[
# Autre solution
<- donnees_datatable[donnees_datatable[, Reduce(`&`, lapply(.SD, `>=`, 10)), .SDcols = varNotes]] note_sup_10
= ["note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel"]
varNotes apply(lambda x: (x >= 10).all() and x.notna().all(), axis=1)] donnees_python[donnees_python[varNotes].
15.3 Moyenne par ligne
Pour chaque observation, 5 notes sont renseignées. On calcule la moyenne de ces 5 notes pour chaque ligne.
15.3.1 Moyenne par ligne
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data donnees_sas;
set donnees_sas;
/* 1ère solution */
mean(of ¬es.);
Note_moyenne =
/* 2e solution : l'équivalent des list-comprehension de Python en SAS */
%macro List_comprehension;
mean(of %do i = 1 %to %sysfunc(countw(¬es.));
Note_moyenne2 = %let j = %scan(¬es., &i.);
&j.
%end;);;
%mend List_comprehension;
%List_comprehension;run;
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
varNotes
$note_moyenne <- rowMeans(donnees_rbase[, varNotes], na.rm = TRUE)
donnees_rbase# apply permet d'appliquer une fonctions aux lignes (1) ou colonnes (2) d'un data.frame
$note_moyenne <- apply(donnees_rbase[, varNotes], 1, mean, na.rm = TRUE) donnees_rbase
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
varNotes # Codes à privilégier
<- donnees_tidyverse %>%
donnees_tidyverse mutate(note_moyenne = rowMeans(pick(all_of(varNotes)), na.rm = TRUE))
<- donnees_tidyverse %>%
donnees_tidyverse mutate(note_moyenne = rowMeans(across(all_of(varNotes)), na.rm = TRUE))
# Alternative lente
# Noter l'utilisation de c_across dans ce cas de figure pour traiter automatiquement plusieurs variables
<- donnees_tidyverse %>%
donnees_tidyverse rowwise() %>%
mutate(note_moyenne = mean(c_across(all_of(varNotes)), na.rm = TRUE)) %>%
ungroup()
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
varNotes # On souhaite moyenner les notes par formation
:= rowMeans(.SD, na.rm = TRUE), .SDcols = varNotes]
donnees_datatable[, note_moyenne
# Manière alternative, qui ne semble pas fonctionner
#donnees_datatable[, note_moyenne := Reduce(function(...) sum(..., na.rm = TRUE), .SD),
# .SDcols = varNotes,
# by = 1:nrow(donnees_datatable)]
#donnees_datatable[, do.call(function(x, y) sum(x, y, na.rm = TRUE), .SD), .SDcols = varNotes, by = 1:nrow(donnees_datatable)]
= ["note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel"]
varNotes 'note_moyenne'] = donnees_python[varNotes].mean(axis=1, skipna=True) donnees_python[
15.3.2 Moyenne des moyennes par ligne
/* Note moyenne (moyenne des moyennes), non pondérée et pondérée */
proc means data = donnees_sas mean;var Note_moyenne;run;
proc means data = donnees_sas mean;var Note_moyenne;weight poids_sondage;run;
# Note moyenne (moyenne des moyennes), non pondérée et pondérée
mean(donnees_rbase$note_moyenne, na.rm = TRUE)
weighted.mean(donnees_rbase$note_moyenne, donnees_rbase$poids_sondage, na.rm = TRUE)
# Note moyenne (moyenne des moyennes) non pondérée
%>% pull(note_moyenne) %>% mean(na.rm = TRUE)
donnees_tidyverse %>% summarise(Moyenne = mean(note_moyenne, na.rm = TRUE))
donnees_tidyverse
# Note moyenne (moyenne des moyennes) pondérée
%>% summarise(Moyenne_ponderee = weighted.mean(note_moyenne, poids_sondage, na.rm = TRUE)) donnees_tidyverse
# Note moyenne (moyenne des moyennes), non pondérée et pondérée
mean(note_moyenne, na.rm = TRUE)]
donnees_datatable[, weighted.mean(note_moyenne, poids_sondage, na.rm = TRUE)] donnees_datatable[,
'note_moyenne'].mean()
donnees_python['note_moyenne'] * donnees_python['poids_sondage']).sum(skipna=True) / donnees_python['poids_sondage'].sum(skipna=True) (donnees_python[
15.3.3 La moyenne par ligne est-elle supérieure à la moyenne ?
/* On crée une macro-variable SAS à partir de la valeur de la moyenne */
proc sql noprint;select mean(Note_moyenne) into :moyenne from donnees_sas;quit;
data donnees_sas;
set donnees_sas;
&moyenne.);
Note_Superieure_Moyenne = (Note_moyenne > run;
proc freq data = donnees_sas;tables Note_Superieure_Moyenne;run;
<- mean(donnees_rbase$note_moyenne, na.rm = TRUE)
moyenne $note_superieure_moyenne <- ifelse(donnees_rbase$note_moyenne > moyenne, 1, 0)
donnees_rbasetable(donnees_rbase$note_superieure_moyenne, useNA = "always")
<- donnees_tidyverse %>% pull(note_moyenne) %>% mean(na.rm = TRUE)
moyenne <- donnees_tidyverse %>% mutate(note_superieure_moyenne = ifelse(note_moyenne > moyenne, 1, 0))
donnees_tidyverse %>% pull(note_superieure_moyenne) %>% table(useNA = "always") donnees_tidyverse
<- donnees_datatable[, mean(note_moyenne, na.rm = TRUE)]
moyenne := fcase(note_moyenne >= moyenne, 1,
donnees_datatable[, note_superieure_moyenne < moyenne, 0)]
note_moyenne table(donnees_datatable$note_superieure_moyenne, useNA = "always")
= donnees_python['note_moyenne'].mean()
moyenne 'note_superieure_moyenne'] = (donnees_python['note_moyenne'] > moyenne).astype(int)
donnees_python[
'note_superieure_moyenne'].value_counts(dropna=False) donnees_python[
15.4 Moyenne pondérée par ligne
Pour chaque observation, 5 notes sont renseignées. On calcule la moyenne de ces 5 notes pour chaque ligne, mais cette fois-ci en pondérant chacune de ces notes.
/* On souhaite affecter les pondérations suivantes aux notes :
Note_Contenu : 30%, Note_Formateur : 20%, Note_Moyens : 25%, Note_Accompagnement : 15%, Note_Materiel : 10% */
/* Voici une solution possible. Une alternative intéressante serait de passer par IML (non traité ici) */
%let ponderation = 0.3 0.2 0.25 0.15 0.1;
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data donnees_sas;
set donnees_sas;
%macro Somme_pond;
%global temp;
%let temp = ;
%do i = 1 %to %sysfunc(countw(¬es.));
%let k = %scan(¬es., &i.);
%let l = %scan(&ponderation., &i., %str( ));
&k._pond = &k. * &l.;
%let temp = &temp. &k._pond;
%end;
%mend Somme_pond;
%Somme_pond;
sum(of &temp.);
Note_moyenne_pond = drop &temp.;
run;
proc means data = donnees_sas mean;var Note_moyenne_pond;run;
# On souhaite affecter les pondérations suivantes aux notes :
# note_contenu : 30%, note_formateur : 20%, note_moyens : 25%, note_accompagnement : 15%, note_materiel : 10%
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
notes <- c(note_contenu = 30, note_formateur = 20, note_moyens = 25, note_accompagnement = 15, note_materiel = 10) / 100
ponderation # On vérifie que la somme des poids vaut 1
sum(ponderation)
# La fonction RowMeans ne fonctionne plus, cette fois !
$note_moyennepond <- apply(donnees_rbase[, notes], 1, function(x) weighted.mean(x, ponderation, na.rm = TRUE))
donnees_rbase
# Autre manière, en exploitant le calcul matriciel
# Ne fonctionne pas dans cet exemple, du fait des NA
as.matrix(donnees_rbase[, notes]) %*% as.matrix(ponderation)
# Produit élément par élément
# On peut procéder par produit tensoriel
# À REVOIR
as.matrix(donnees_rbase[, notes]) * matrix(t(as.matrix(ponderation)), nrow(donnees_rbase), length(notes))
# On souhaite affecter les pondérations suivantes aux notes :
# note_contenu : 30%, note_formateur : 20%, note_moyens : 25%, note_accompagnement : 15%, note_materiel : 10%
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
notes <- c(note_contenu = 30, note_formateur = 20, note_moyens = 25, note_accompagnement = 15, note_materiel = 10) / 100
ponderation # On vérifie que la somme des poids vaut 1
sum(ponderation)
# La fonction RowMeans ne fonctionne plus, cette fois !
# Noter l'utilisation de c_across dans ce cas de figure pour traiter automatiquement plusieurs variables
<- donnees_tidyverse %>%
donnees_tidyverse rowwise() %>%
mutate(note_moyenne = weighted.mean(c_across(varNotes), ponderation, na.rm = TRUE)) %>%
ungroup()
# On souhaite affecter les pondérations suivantes aux notes :
# note_contenu : 30%, note_formateur : 20%, note_moyens : 25%, note_accompagnement : 15%, note_materiel : 10%
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
notes <- c(note_contenu = 30, note_formateur = 20, note_moyens = 25, note_accompagnement = 15, note_materiel = 10) / 100
ponderation # On vérifie que la somme des poids vaut 1
sum(ponderation)
# La fonction RowMeans ne fonctionne plus, cette fois !
:= rowSums(mapply(FUN = `*`, .SD, ponderation), na.rm = TRUE), .SDcols = notes] donnees_datatable[, note_moyenne_pond
# On souhaite affecter les pondérations suivantes aux notes :
# note_contenu : 30%, note_formateur : 20%, note_moyens : 25%, note_accompagnement : 15%, note_materiel : 10%
= ["note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel"]
notes = {"note_contenu" : 0.3, "note_formateur" : 0.2, "note_moyens" : 0.25, "note_accompagnement" : 0.15, "note_materiel" : 0.1}
ponderation_dict # On vérifie que la somme des poids vaut 1
sum(ponderation_dict.values())
# Extraire les pondérations dans le meme ordre que le vecteur de notes :
= np.array([ponderation_dict[note] for note in varNotes])
ponderation
# Moyenne pondérée
'note_moyenne'] = donnees_python[varNotes].apply(
donnees_python[lambda row: np.average(row, weights=ponderation[:len(row.dropna())]) if len(row.dropna()) > 0 else np.nan,
=1
axis )
16 Variable retardée (lag) et avancée (lead)
16.1 Variable retardée (lag)
/* La date de fin du contrat précédent (lag) */
/* Ecriture correcte d'un lag en SAS */
proc sort data = donnees_sas;by identifiant date_entree;run;
data donnees_sasBon;
set donnees_sas;
by identifiant date_entree;
format Date_fin_1 ddmmyy10.;
lag(Date_sortie);
Date_fin_1 = if first.identifiant then Date_fin_1 = .;
run;
/* Ecriture incorrecte d'un lag en SAS */
/* ATTENTION au lag DANS UNE CONDITION IF */
/* Il faut toujours "sortir" le lag de la condition IF */
proc sort data = donnees_sas;by identifiant date_entree;run;
data Lag_Bon;
set donnees_sas (keep = identifiant date_entree date_sortie);
format date_sortie_1 lag_faux lag_bon ddmmyy10.;
/* Erreur */
if date_entree = lag(date_sortie) + 1 then lag_faux = lag(date_sortie) + 1;
/* Bonne écriture */
lag(date_sortie);
date_sortie_1 = if date_entree = date_sortie_1 + 1 then lag_bon = date_sortie_1 + 1;
run;
# La date de fin du contrat précédent
<- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entre, na.last = FALSE), ]
donnees_rbase
# Il n'existe pas de fonction lag dans le R de base (à notre connaissance)
# Il faut soit utiliser un package, soit utiliser cette astuce
$date_sortie_1 <- c(as.Date(NA), donnees_rbase$date_sortie[ seq(1, length(donnees_rbase$date_sortie) - 1)])
donnees_rbase$date_sortie_1 <- c(as.Date(NA), donnees_rbase$date_sortie[ 1:(length(donnees_rbase$date_sortie) - 1)])
donnees_rbase
# Ou, encore plus simple !
$date_sortie_1 <- c(as.Date(NA), head(donnees_rbase$date_sortie, -1)) donnees_rbase
# La date de fin du contrat précédent
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
mutate(date_sortie_1 = lag(date_sortie))
# La date de fin du contrat précédent
setorderv(donnees_datatable, cols = c("identifiant", "date_entree"), order = c(1L, 1L), na.last = FALSE)
:= shift(.SD, n = 1, fill = NA, "lag"), .SDcols = "date_sortie"]
donnees_datatable[, date_sortie_1 donnees_datatable[, .(date_sortie, date_sortie_1)]
= donnees_python.sort_values(by=['identifiant', 'date_entree'], ascending=[True, True])
donnees_python 'date_sortie_1'] = donnees_python['date_sortie'].shift(1) donnees_python[
16.2 Variable avancée (lead)
proc expand data= donnees_sas out = Lead;
1);
convert date_sortie = date_sortie__1 / transformout = (lead run;
# Il n'existe pas de fonction lead dans le R de base (à notre connaissance)
# La date du contrat futur (lead)
$date_sortie__1 <- c(donnees_rbase$date_sortie[ 2:(length(donnees_rbase$date_sortie))], as.Date(NA))
donnees_rbase
# Ou, encore plus simple !
$date_sortie_1 <- c(tail(donnees_rbase$date_sortie, -1), as.Date(NA)) donnees_rbase
# La date du contrat futur (lead)
<- donnees_tidyverse %>%
donnees_tidyverse # Pour trier les données de la même façon que SAS
arrange(identifiant, !is.na(date_entree), date_entree) %>%
mutate(date_sortie__1 = lead(date_sortie))
# La date du contrat futur (lead)
setorderv(donnees_datatable, cols = c("identifiant", "date_entree"), order = c(1L, 1L), na.last = FALSE)
:= shift(.SD, n = 1, fill = NA, "lead"), .SDcols = "date_sortie"]
donnees_datatable[, date_sortie__1 donnees_datatable[, .(date_sortie, date_sortie__1)]
= donnees_python.sort_values(by=['identifiant', 'date_entree'], ascending=[True, True])
donnees_python 'date_sortie_1'] = donnees_python['date_sortie'].shift(-1) donnees_python[
17 Les jointures de bases
Pour fonctionner, les codes de cette partie nécessitent l’importation des bases d’exemple de la section “Importation de bases pour les jointures”.
17.1 Importation de bases pour les jointures
/* On suppose que l'on dispose d'une base supplémentaire avec les diplômes des personnes */
data Diplome;
infile cards dsd dlm='|';
format Identifiant $3. Diplome $20.;
input Identifiant $ Diplome $;
cards;
173|Bac
168|Bep-Cap
112|Bep-Cap
087|Bac+2
689|Bac+2
765|Pas de diplôme
113|Bac
999|Bac
554|Bep-Cap
;run;
/* On suppose que l'on dispose aussi d'une base supplémentaire indiquant la date d'une entrevue avec un conseiller */
data Entrevue;
infile cards dsd dlm='|';
format Identifiant $3. Date_entrevue ddmmyy10.;
input Identifiant $ Date_entrevue ddmmyy10.;
cards;
173|06/08/2021
168|17/10/2019
087|12/06/2021
689|28/03/2018
099|01/09/2022
765|01/10/2020
;run;
/* On récupère un extrait de la base initiale */
data Jointure;
set donnees_sas (keep = Identifiant Sexe date_entree date_sortie);
run;
# On suppose que l'on dispose d'une base supplémentaire avec les diplômes des personnes
<- data.frame(identifiant = c("173", "168", "112", "087", "689", "765", "113", "999", "554"),
diplome_rbase diplome = c("Bac", "Bep-Cap", "Bep-Cap", "Bac+2", "Bac+2", "Pas de diplôme", "Bac", "Bac", "Bep-Cap"))
# On suppose que l'on dispose d'une base supplémentaire indiquant la date d'une entrevue avec un conseiller
<- data.frame(identifiant = c("173", "168", "087", "689", "099", "765"),
entrevue_rbase date_entrevue = c("06/08/2021", "17/10/2019", "12/06/2021", "28/03/2018", "01/09/2022", "01/10/2020"))
$date_entrevue <- lubridate::dmy(entrevue_rbase$date_entrevue)
entrevue_rbase
# On récupère un extrait de la base initiale
<- donnees_rbase[, c("identifiant", "sexe", "date_entree", "date_sortie")] jointure_rbase
# On suppose que l'on dispose d'une base supplémentaire avec les diplômes des personnes
<- tibble(identifiant = c("173", "168", "112", "087", "689", "765", "113", "999", "554"),
diplome_tidyverse diplome = c("Bac", "Bep-Cap", "Bep-Cap", "Bac+2", "Bac+2", "Pas de diplôme", "Bac", "Bac", "Bep-Cap"))
# On suppose que l'on dispose d'une base supplémentaire indiquant la date d'une entrevue avec un conseiller
<- tibble(identifiant = c("173", "168", "087", "689", "099", "765"),
entrevue_tidyverse date_entrevue = c("06/08/2021", "17/10/2019", "12/06/2021", "28/03/2018", "01/09/2022", "01/10/2020"))
<- entrevue_tidyverse %>%
entrevue_tidyverse mutate(date_entrevue = lubridate::dmy(date_entrevue))
# On récupère un extrait de la base initiale
<- c("identifiant", "sexe", "date_entree", "date_sortie")
variable <- donnees_tidyverse %>%
jointure_tidyverse select(all_of(variable))
# On suppose que l'on dispose d'une base supplémentaire avec les diplômes des personnes
<- data.table(identifiant = c("173", "168", "112", "087", "689", "765", "113", "999", "554"),
diplome_datatable diplome = c("Bac", "Bep-Cap", "Bep-Cap", "Bac+2", "Bac+2", "Pas de diplôme", "Bac", "Bac", "Bep-Cap"))
# On suppose que l'on dispose d'une base supplémentaire indiquant la date d'une entrevue avec un conseiller
<- data.table(identifiant = c("173", "168", "087", "689", "099", "765"),
entrevue_datatable date_entrevue = c("06/08/2021", "17/10/2019", "12/06/2021", "28/03/2018", "01/09/2022", "01/10/2020"))
:= lubridate::dmy(date_entrevue)]
entrevue_datatable[, date_entrevue
# On récupère un extrait de la base initiale
<- donnees_datatable[, c("identifiant", "sexe", "date_entree", "date_sortie")] jointure_datatable
# On suppose que l'on dispose d'une base supplémentaire avec les diplômes des personnes
= pd.DataFrame({
diplome_python 'identifiant': ["173", "168", "112", "087", "689", "765", "113", "999", "554"],
'diplome': ["Bac", "Bep-Cap", "Bep-Cap", "Bac+2", "Bac+2", "Pas de diplôme", "Bac", "Bac", "Bep-Cap"]
})
# On suppose que l'on dispose d'une base supplémentaire indiquant la date d'une entrevue avec un conseiller
= pd.DataFrame({
entrevue_python 'identifiant': ["173", "168", "087", "689", "099", "765"],
'date_entrevue': ["06/08/2021", "17/10/2019", "12/06/2021", "28/03/2018", "01/09/2022", "01/10/2020"]
})'date_entrevue'] = pd.to_datetime(entrevue_python['date_entrevue'], format='%d/%m/%Y') # Conversion des dates en datetime
entrevue_python[
# On récupère un extrait de la base initiale
= donnees_python[['identifiant', 'sexe', 'date_entree', 'date_sortie']] jointure_python
17.2 Inner join : les seuls identifiants communs aux deux bases
/* Sont appariés les identifiants communs aux deux bases */
/* Le tri préalable des bases de données à joindre par la variable de jointure est nécessaire avec la stratégie merge */
proc sort data = Diplome;by identifiant;run;
proc sort data = Jointure;by identifiant;run;
data Inner_Join1;
merge Jointure (in = a) Diplome (in = b);
by identifiant;
if a and b;
run;
/* Autre solution */
/* Le tri préalable des bases de données à joindre n'est pas nécessaire avec la jointure SQL */
proc sql;
create table Inner_Join2 as
select * from Jointure a inner join Diplome b on a.identifiant = b.identifiant
order by a.identifiant;
quit;
proc print data = Inner_Join1 (obs = 10);run;
proc sql;select count(*) from Inner_Join1;quit;
proc sql;select count(*) from Inner_Join2;quit;
# Sont appariés les identifiants communs aux deux bases
<- merge(jointure_rbase, diplome_rbase, by.x = "identifiant", by.y = "identifiant")
innerJoin dim(innerJoin)
# Sont appariés les identifiants communs aux deux bases
<- jointure_tidyverse %>%
innerJoin inner_join(diplome_tidyverse, by = "identifiant")
dim(innerJoin)
# Autres solutions
<- jointure_tidyverse %>%
innerJoin inner_join(diplome_tidyverse, by = join_by(identifiant == identifiant))
dim(innerJoin)
<- inner_join(jointure_tidyverse, diplome_tidyverse, by = "identifiant")
innerJoin dim(innerJoin)
# Sont appariés les identifiants communs aux deux bases
<- merge(jointure_datatable, diplome_datatable, by.x = "identifiant", by.y = "identifiant")
innerJoin <- jointure_datatable[diplome_datatable, nomatch = 0, on = list(identifiant == identifiant)]
innerJoin <- jointure_datatable[diplome_datatable, nomatch = 0, on = .(identifiant == identifiant)]
innerJoin dim(innerJoin)
# Sont appariés les identifiants communs aux deux bases
= jointure_python.merge(diplome_python,
inner_join ='identifiant',
left_on= 'identifiant',
right_on ='inner')
how inner_join.shape
17.3 Left join : les identifiants de la base de gauche
/* Sont appariés tous les identifiants de la base de gauche, et les correspondants éventuels de la base de droite */
/* Le tri préalable des bases de données à joindre par la variable de jointure est nécessaire avec la stratégie merge */
proc sort data = Diplome;by identifiant;run;
proc sort data = Jointure;by identifiant;run;
data Left_Join1;
merge Jointure (in = a) Diplome (in = b);
by identifiant;
if a;
run;
/* Autre solution */
/* Le tri préalable des bases de données à joindre n'est pas nécessaire avec la jointure SQL */
proc sql;
create table Left_Join2 as
select * from Jointure a left join Diplome b on a.identifiant = b.identifiant
order by a.identifiant;
quit;
proc print data = Left_Join1 (obs = 10);run;
proc sql;select count(*) from Left_Join1;quit;
proc sql;select count(*) from Left_Join2;quit;
# Sont appariés tous les identifiants de la base de gauche, et les correspondants éventuels de la base de droite
<- merge(jointure_rbase, diplome_rbase, by.x = "identifiant", by.y = "identifiant", all.x = TRUE)
leftJoin dim(leftJoin)
# Sont appariés tous les identifiants de la base de gauche, et les correspondants éventuels de la base de droite
<- jointure_tidyverse %>%
leftJoin left_join(diplome_tidyverse, by = "identifiant")
dim(leftJoin)
# Autres solutions
<- jointure_tidyverse %>%
leftJoin left_join(diplome_tidyverse, by = join_by(identifiant == identifiant))
dim(leftJoin)
<- left_join(jointure_tidyverse, diplome_tidyverse, by = "identifiant")
leftJoin dim(leftJoin)
# Sont appariés tous les identifiants de la base de gauche, et les correspondants éventuels de la base de droite
<- merge(jointure_datatable, diplome_datatable, by.x = "identifiant", by.y = "identifiant", all.x = TRUE)
leftJoin dim(leftJoin)
<- diplome_datatable[jointure_datatable, on = .(identifiant == identifiant)]
leftJoin dim(leftJoin)
# Sont appariés tous les identifiants de la base de gauche, et les correspondants éventuels de la base de droite
= jointure_python.merge(diplome_python,
left_join ='identifiant',
left_on= 'identifiant',
right_on ='left')
how left_join.shape
17.4 Right join : les identifiants de la base de droite
/* Sont appariés tous les identifiants de la base de droite et les correspondants éventuels de la base de gauche */
/* Le tri préalable des bases de données à joindre par la variable de jointure est nécessaire avec la stratégie merge */
proc sort data = Diplome;by identifiant;run;
proc sort data = Jointure;by identifiant;run;
data Right_Join1;
merge Jointure (in = a) Diplome (in = b);
by identifiant;
if b;
run;
/* Autre solution */
/* Le tri préalable des bases de données à joindre n'est pas nécessaire avec la jointure SQL */
proc sql;
create table Right_Join2 as
select * from Jointure a right join Diplome b on a.identifiant = b.identifiant
order by a.identifiant;
quit;
proc print data = Right_Join1 (obs = 10);run;
proc sql;select count(*) from Right_Join1;quit;
proc sql;select count(*) from Right_Join2;quit;
# Sont appariés tous les identifiants de la base de droite et les correspondants éventuels de la base de gauche
<- merge(jointure_rbase, diplome_rbase, by.x = "identifiant", by.y = "identifiant", all.y = TRUE)
rightJoin dim(rightJoin)
# Sont appariés tous les identifiants de la base de droite et les correspondants éventuels de la base de gauche
<- jointure_tidyverse %>%
rightJoin right_join(diplome_tidyverse, by = "identifiant")
dim(rightJoin)
# Autre solution
<- jointure_tidyverse %>%
rightJoin right_join(diplome_tidyverse, by = join_by(identifiant == identifiant))
dim(rightJoin)
<- right_join(jointure_tidyverse, diplome_tidyverse, by = "identifiant")
rightJoin dim(rightJoin)
# Sont appariés tous les identifiants de la base de droite et les correspondants éventuels de la base de gauche
<- merge(jointure_datatable, diplome_datatable, by.x = "identifiant", by.y = "identifiant", all.y = TRUE)
rightJoin dim(rightJoin)
<- jointure_datatable[diplome_datatable, on = .(identifiant == identifiant)]
rightJoin dim(rightJoin)
# Sont appariés tous les identifiants de la base de droite et les correspondants éventuels de la base de gauche
= jointure_python.merge(diplome_python,
right_join ='identifiant',
left_on= 'identifiant',
right_on ='right')
how right_join.shape
17.5 Full join : les identifiants des deux bases
/* Sont appariés les identifiants des deux bases */
/* Le tri préalable des bases de données à joindre par la variable de jointure est nécessaire avec la stratégie merge */
proc sort data = Diplome;by identifiant;run;
proc sort data = Jointure;by identifiant;run;
data Full_Join1;
merge Jointure (in = a) Diplome (in = b);
by identifiant;
if a or b;
run;
/* Autre solution */
/* Le tri préalable des bases de données à joindre n'est pas nécessaire avec la jointure SQL */
proc sql;
create table Full_Join2 as
select coalesce(a.identifiant, b.identifiant) as Identifiant, *
from Jointure a full outer join Diplome b on a.identifiant = b.identifiant
order by calculated identifiant;
quit;
proc print data = Full_Join1 (obs = 10);run;
proc sql;select count(*) from Full_Join1;quit;
proc sql;select count(*) from Full_Join2;quit;
# Sont appariés les identifiants des deux bases
<- merge(jointure_rbase, diplome_rbase, by.x = "identifiant", by.y = "identifiant", all = TRUE)
fullJoin dim(fullJoin)
# Sont appariés les identifiants des deux bases
<- jointure_tidyverse %>%
fullJoin full_join(diplome_tidyverse, by = "identifiant")
dim(fullJoin)
# Autre solution
<- jointure_tidyverse %>%
fullJoin full_join(diplome_tidyverse, by = join_by(identifiant == identifiant))
dim(fullJoin)
<- full_join(jointure_tidyverse, diplome_tidyverse, by = "identifiant")
fullJoin dim(fullJoin)
# Sont appariés les identifiants des deux bases
<- merge(jointure_datatable, diplome_datatable, by.x = "identifiant", by.y = "identifiant", all = TRUE)
fullJoin dim(fullJoin)
# Sont appariés tous les identifiants de la base de droite et les correspondants éventuels de la base de gauche
= jointure_python.merge(diplome_python,
full_join ='identifiant',
left_on= 'identifiant',
right_on ='outer')
how full_join.shape
17.6 Jointure de 3 bases ou plus en une seule opération (exemple avec inner join)
proc sort data = Jointure;by identifiant;run;
proc sort data = Diplome;by identifiant;run;
proc sort data = Entrevue;by identifiant;run;
data Inner_Join3;
merge Jointure (in = a) Diplome (in = b) Entrevue (in = c);
by identifiant;
if a and b and c;
run;
/* Autre solution */
/* Le tri préalable des bases de données à joindre n'est pas nécessaire avec la jointure SQL */
proc sql;
create table Inner_Join4 as
select * from Jointure a inner join Diplome b on a.identifiant = b.identifiant
on a.identifiant = c.identifiant
inner join Entrevue c order by a.identifiant;
quit;
proc print data = Inner_Join4 (obs = 10);run;
proc sql;select count(*) from Inner_Join3;quit;
proc sql;select count(*) from Inner_Join4;quit;
# Utilisation de la fonction Reduce
# Elle applique successivement (et non simultanément, comme do.call) une fonction à tous les éléments d'une liste
<- Reduce(function(x, y) merge(x, y, all = FALSE, by.x = "identifiant", by.y = "identifiant"),
innerJoin2 list(jointure_rbase, diplome_rbase, entrevue_rbase))
dim(innerJoin2)
# Utilisation de la fonction reduce de purrr
# Elle applique successivement (et non simultanément, comme do.call) à tous les éléments d'une liste une fonction
<- list(jointure_tidyverse, diplome_tidyverse, entrevue_tidyverse) %>%
innerJoin2 ::reduce(dplyr::inner_join, by = join_by(identifiant == identifiant))
purrrdim(innerJoin2)
# Utilisation de la fonction Reduce
# Elle applique successivement (et non simultanément, comme do.call) une fonction à tous les éléments d'une liste
<- Reduce(function(x, y) merge(x, y, all = FALSE, by.x = "identifiant", by.y = "identifiant"),
innerJoin2 list(jointure_datatable, diplome_datatable, entrevue_datatable))
dim(innerJoin2)
from functools import reduce
# Liste des DataFrames à joindre
= [jointure_python, diplome_python, entrevue_python]
dataframes
## Methode 1 : avec la fonction reduce
# Fonction de jointure interne
def inner_join(x, y):
return pd.merge(x, y, on='identifiant', how='inner')
# Application successive de la fonction de jointure à tous les éléments de la liste
= reduce(inner_join, dataframes)
inner_join2
## Méthode 2 : Sans la fonction reduce
# Initialisation du DataFrame résultant avec le premier DataFrame de la liste
= dataframes[0]
inner_join2
# Jointure successive des autres DataFrames
for df in dataframes[1:]:
= pd.merge(inner_join2, df, on='identifiant', how='inner')
inner_join2
inner_join2.shape
17.7 Jointure sur inégalités
On cherche à obtenir les entrevues qui se sont déroulées durant le contrat. On cherche alors à apparier les bases Jointure et Entrevue par identifiant si la date d’entrevue de la base Entrevue est comprise entre la date d’entrée et la date de sortie de la base Jointure.
/* On associe l'entrevue au contrat au cours duquel elle a eu lieu */
proc sql;
create table Inner_Join_Inegalite as
select *
from Jointure a inner join Entrevue b
on a.identifiant = b.identifiant and a.date_entree <= b.date_entrevue <= a.date_sortie
order by a.identifiant;
quit;
proc print data = Inner_Join_Inegalite (obs = 10);run;
proc sql;select count(*) from Inner_Join_Inegalite;quit;
# Ne semble pas natif en R-Base.
# Une proposition indicative où l'on applique la sélection après la jointure, ce qui ne doit pas être très efficace ...
<- merge(jointure_rbase, entrevue_rbase, by = "identifiant")
innerJoinInegalite <- with(innerJoinInegalite,
innerJoinInegalite which(date_entree <= date_entrevue & date_entrevue <= date_sortie), ])
innerJoinInegalite[dim(innerJoinInegalite)
<- jointure_tidyverse %>%
innerJoinInegalite inner_join(entrevue_tidyverse, join_by(identifiant == identifiant,
<= date_entrevue,
date_entree >= date_entrevue))
date_sortie dim(innerJoinInegalite)
# Attention, l'ordre des conditions doit correspondre à l'ordre des bases dans la jointure !
# Il semble que l'on soit forcé de spécifier tous les noms des colonnes, et ce qui est un peu problématique ...
# À FAIRE : Peut-on faire plus simplement ??
<- jointure_datatable[entrevue_datatable,
innerJoinInegalite
.(identifiant, sexe, date_entree, date_sortie, date_entrevue),= .(identifiant, date_entree <= date_entrevue, date_sortie >= date_entrevue),
on = 0L
nomatch order(identifiant)]
][dim(innerJoinInegalite)
# En procédant en deux temps :
# Jointure interne sur la colonne 'identifiant'
= pd.merge(jointure_python, entrevue_python, on='identifiant', how='inner')
intermediate_join
# Filtrage selon les conditions d'inégalité
#inner_join_inegalite = intermediate_join.query('date_entree <= date_entrevue <= date_sortie')
inner_join_inegalite.shape
17.8 Cross join : toutes les combinaisons possibles de CSP, sexe et diplôme
proc sql;
create table CrossJoin as
select *
from (select distinct CSPF from donnees_sas) cross join
select distinct Sexef from donnees_sas) cross join
(select distinct Diplome from Diplome)
(order by CSPF, Sexef, Diplome;
quit;
proc sql;select count(*) from CrossJoin;quit;
# Toutes les combinaisons possibles de CSP, sexe et diplome
<- unique(expand.grid(donnees_rbase$cspf, donnees_rbase$sexef, diplome_rbase$diplome))
crossJoin colnames(crossJoin) <- c("cspf", "sexef", "diplome")
dim(crossJoin)
# Autre solution
<- unique(merge(donnees_rbase[, c("cspf", "sexef")], diplome_rbase[, "diplome"], by = NULL))
crossJoin2 dim(crossJoin2)
# Toutes les combinaisons possibles de CSP, sexe et diplome
<- donnees_tidyverse %>%
crossJoin select(cspf, sexef) %>%
cross_join(diplome_tidyverse %>% select(diplome)) %>%
distinct()
dim(crossJoin)
# Autres solutions
<- cross_join(donnees_tidyverse %>% select(cspf, sexef), diplome_tidyverse %>% select(diplome)) %>%
crossJoin distinct()
dim(crossJoin)
<- donnees_tidyverse %>%
crossJoin ::expand(cspf, sexef, diplome_tidyverse$diplome) %>%
tidyrdistinct()
dim(crossJoin)
<- tidyr::crossing(donnees_tidyverse %>% select(cspf, sexef), diplome_tidyverse %>% select(diplome)) %>%
crossJoin distinct()
dim(crossJoin)
<- data.table::CJ(donnees_datatable[, cspf], donnees_datatable[, sexef], diplome_datatable[, diplome], unique = TRUE)
crossJoin colnames(crossJoin) <- c("cspf", "sexef", "diplome")
dim(crossJoin)
# Toutes les combinaisons possibles de CSP, sexe et diplome
= pd.MultiIndex.from_product([pd.unique(donnees_python['cspf']),
crossJoin 'sexef']),
pd.unique(donnees_python['diplome'])],
pd.unique(diplome_python[=['cspf', 'sexef', 'diplome']).to_frame(index=False)
names crossJoin.shape
17.9 Semi join
/* Identifiants de la base de gauche qui ont un correspondant dans la base de droite */
proc sql;
create table Semi_Join as select * from donnees_sas
where Identifiant in (select distinct Identifiant from Diplome);
select count(*) from Semi_Join;
quit;
/* Autre possibilité */
proc sql;
create table Semi_Join as select * from donnees_sas a
where exists (select * from Diplome b where (a.Identifiant = b.Identifiant));
select count(*) from Semi_Join;
quit;
# Identifiants de la base de gauche qui ont un correspondant dans la base de droite
<- donnees_rbase[donnees_rbase$identifiant %in% diplome_rbase$identifiant, ]
semiJoin dim(semiJoin)
# Identifiants de la base de gauche qui ont un correspondant dans la base de droite
<- donnees_tidyverse %>%
semiJoin semi_join(diplome_tidyverse, join_by(identifiant == identifiant))
dim(semiJoin)
# Autre solution
<- semi_join(donnees_tidyverse, diplome_tidyverse, join_by(identifiant == identifiant))
semiJoin dim(semiJoin)
# Identifiants de la base de gauche qui ont un correspondant dans la base de droite
<- donnees_datatable[identifiant %in% diplome_datatable[, identifiant], ]
semiJoin dim(semiJoin)
# Toutes les combinaisons possibles de CSP, sexe et diplome
= donnees_python[donnees_python['identifiant'].isin(diplome_python['identifiant'])]
semiJoin semiJoin.shape
17.10 Anti join
/* Identifiants de la base de gauche qui n'ont pas de correspondant dans la base de droite */
proc sql;
create table Anti_Join as select * from donnees_sas
where Identifiant not in (select distinct Identifiant from Diplome);
select count(*) from Anti_Join;
quit;
proc sql;
create table Anti_Join as select * from donnees_sas a
where not exists (select * from Diplome b where (a.Identifiant = b.Identifiant);
select count(*) from Anti_Join;
quit;
# Identifiants de la base de gauche qui n'ont pas de correspondant dans la base de droite
<- donnees_rbase[! donnees_rbase$identifiant %in% diplome_rbase$identifiant, ]
antiJoin dim(antiJoin)
# Identifiants de la base de gauche qui n'ont pas de correspondant dans la base de droite
<- donnees_tidyverse %>%
antiJoin anti_join(diplome_tidyverse, join_by(identifiant == identifiant))
dim(antiJoin)
# Autre solution
<- anti_join(donnees_tidyverse, diplome_tidyverse, join_by(identifiant == identifiant))
antiJoin dim(antiJoin)
# Identifiants de la base de gauche qui n'ont pas de correspondant dans la base de droite
! diplome_datatable, on = "identifiant", j = ! "diplome"]
donnees_datatable[
# Autre solution
<- donnees_datatable[! identifiant %in% diplome_datatable[, identifiant], ]
antiJoin dim(antiJoin)
# Toutes les combinaisons possibles de CSP, sexe et diplome
= donnees_python[~donnees_python['identifiant'].isin(diplome_python['identifiant'])]
antiJoin antiJoin.shape
17.11 Autres fonctions utiles
17.11.1 Concaténation des identifiants
proc sql;
/* Concaténation des identifiants */
select Identifiant from Jointure union all
select Identifiant from Diplome order
by identifiant;
quit;
# Concaténation des identifiants avec les doublons
sort(c(jointure_rbase$identifiant, diplome_rbase$identifiant))
# dplyr:: permet de s'assurer que ce sont les fonctions du Tidyverse (et non leurs homonymes de R-Base qui sont utilisées)
# Concaténation des identifiants
::union_all(jointure_tidyverse$identifiant, diplome_tidyverse$identifiant) %>%
dplyrsort()
# Les fonctions spécifiques à data.table fonctionnent avec des formats data.table, d'où la syntaxe un peu différente de R base
# Concaténation des identifiants
<- "identifiant"
variable sort(c(jointure_datatable[[variable]], diplome_datatable[[variable]]))
# Toutes les combinaisons possibles de CSP, sexe et diplome
# Concaténer les identifiants avec doublons
'identifiant'], diplome_python['identifiant']]).sort_values() pd.concat([donnees_python[
17.11.2 Identifiants uniques des 2 bases
proc sql;
/* Identifiants uniques des 2 bases */
select distinct Identifiant from
select Identifiant from Jointure union select Identifiant from Diplome)
(order by identifiant;
quit;
# base:: permet de s'assurer que les fonctions proviennent de R base
# Des fonctions du même nom existent en Tidyverse, et tendent à prédominer si le package est lancé
# Identifiants uniques des 2 bases
sort(base::union(jointure_rbase$identifiant, diplome_rbase$identifiant))
sort(base::unique(c(jointure_rbase$identifiant, diplome_rbase$identifiant)))
# dplyr:: permet de s'assurer que ce sont les fonctions du Tidyverse (et non leurs homonymes de R-Base qui sont utilisées)
# Identifiants uniques des 2 bases
unique(dplyr::union_all(jointure_tidyverse$identifiant, diplome_tidyverse$identifiant)) %>%
sort()
::union(jointure_tidyverse$identifiant, diplome_tidyverse$identifiant) %>%
dplyrsort()
# Les fonctions spécifiques à data.table fonctionnent avec des formats data.table, d'où la syntaxe un peu différente de R base
# Identifiants uniques des 2 bases
<- "identifiant"
variable sort(unique(c(jointure_datatable[[variable]], diplome_datatable[[variable]])))
funion(jointure_datatable[, ..variable], diplome_datatable[, ..variable])
# Toutes les combinaisons possibles de CSP, sexe et diplome
# Concaténer les identifiants avec doublons
= pd.concat([donnees_python['identifiant'], diplome_python['identifiant']]).sort_values()
identifiants
pd.Series(identifiants.unique())
17.11.3 Identifiants communs des 2 bases
proc sql;
/* Identifiants communs des 2 bases */
select Identifiant from Jointure intersect select Identifiant from Diplome
order by identifiant;
quit;
# base:: permet de s'assurer que les fonctions proviennent de R base
# Des fonctions du même nom existent en Tidyverse, et tendent à prédominer si le package est lancé
# Identifiants communs des 2 bases
sort(base::intersect(jointure_rbase$identifiant, diplome_rbase$identifiant))
# dplyr:: permet de s'assurer que ce sont les fonctions du Tidyverse (et non leurs homonymes de R-Base qui sont utilisées)
# Identifiants communs des 2 bases
::intersect(jointure_tidyverse$identifiant, diplome_tidyverse$identifiant) %>%
dplyrsort()
# Les fonctions spécifiques à data.table fonctionnent avec des formats data.table, d'où la syntaxe un peu différente de R base
# Identifiants communs des 2 bases
<- "identifiant"
variable fintersect(jointure_datatable[, ..variable], diplome_datatable[, ..variable])[order(identifiant)]
# Identifiants communs des 2 bases
sorted(set(donnees_python['identifiant']).intersection(set(diplome_python['identifiant'])))
17.11.4 Identifiants dans Jointure mais pas Diplome
proc sql;
/* Identifiants dans Jointure mais pas Diplome */
select distinct Identifiant from Jointure where
not in (select distinct Identifiant from Diplome)
Identifiant order by identifiant;
/* Autre possibilité */
select Identifiant from Jointure except select Identifiant from Diplome;
quit;
# base:: permet de s'assurer que les fonctions proviennent de R base
# Des fonctions du même nom existent en Tidyverse, et tendent à prédominer si le package est lancé
# Identifiants dans Jointure mais pas Diplome
sort(base::setdiff(jointure_rbase$identifiant, diplome_rbase$identifiant))
# dplyr:: permet de s'assurer que ce sont les fonctions du Tidyverse (et non leurs homonymes de R-Base qui sont utilisées)
# Identifiants dans Jointure mais pas Diplome
::setdiff(jointure_tidyverse$identifiant, diplome_tidyverse$identifiant) %>%
dplyrsort()
# Les fonctions spécifiques à data.table fonctionnent avec des formats data.table, d'où la syntaxe un peu différente de R base
# Identifiants dans Jointure mais pas Diplome
<- "identifiant"
variable fsetdiff(jointure_datatable[, ..variable], diplome_datatable[, ..variable])[order(identifiant)]
# Identifiants communs des 2 bases
sorted(set(donnees_python['identifiant']) - set(diplome_python['identifiant']))
17.11.5 Identifiants dans Diplome mais pas Jointure
proc sql;
/* Identifiants dans Diplome mais pas Jointure */
select distinct Identifiant from Diplome
where Identifiant not in (select distinct Identifiant from Jointure)
order by identifiant;
select Identifiant from Diplome except
select Identifiant from Jointure order by identifiant;
quit;
# base:: permet de s'assurer que les fonctions proviennent de R base
# Des fonctions du même nom existent en Tidyverse, et tendent à prédominer si le package est lancé
# Identifiants dans Diplome mais pas Jointure
sort(base::setdiff(diplome_rbase$identifiant, jointure_rbase$identifiant))
# dplyr:: permet de s'assurer que ce sont les fonctions du Tidyverse (et non leurs homonymes de R-Base qui sont utilisées)
# Identifiants dans Diplome mais pas Jointure
::setdiff(diplome_tidyverse$identifiant, jointure_tidyverse$identifiant) %>%
dplyrsort()
# Les fonctions spécifiques à data.table fonctionnent avec des formats data.table, d'où la syntaxe un peu différente de R base
# Identifiants dans Diplome mais pas Jointure
<- "identifiant"
variable fsetdiff(diplome_datatable[, ..variable], jointure_datatable[, ..variable])[order(identifiant)]
# Identifiants communs des 2 bases
sorted(set(diplome_python['identifiant']) - set(donnees_python['identifiant']))
18 Concaténer et empiler des bases
18.1 Concaténer deux bases de données
On va mettre côte à côte (juxtaposer) le numéro de la ligne et la base de données.
18.1.1 Les deux bases concaténées ont le même nombre de lignes
/* Numéro de la ligne */
proc sql noprint;select count(*) into :tot from donnees_sas;run;
data Ajout;do Num_ligne = 1 to &tot.;output;end;run;
/* Le merge sans by va juxtaposer côte à côte les bases */
data Concatener;merge Ajout donnees_sas;run;
# Numéro de la ligne
<- data.frame(num_ligne = seq_len(nrow(donnees_rbase)))
ajout # cbind si les deux bases comprennent le même nombre de lignes
<- cbind(ajout, donnees_rbase) concatener
# Numéro de la ligne
<- tibble(num_ligne = seq_len(nrow(donnees_tidyverse)))
ajout # bind_cols si les deux bases comprennent le même nombre de lignes
<- donnees_tidyverse %>% bind_cols(ajout) concatener
# Numéro de la ligne
<- data.table(num_ligne = seq_len(nrow(donnees_datatable)))
ajout # data.frame::cbind si les deux bases comprennent le même nombre de lignes
<- data.frame::cbind(ajout, donnees_datatable) concatener
# Numéro de la ligne
= pd.DataFrame({'num_ligne': range(1, len(donnees_python) + 1)})
ajout # Concatener
= pd.concat([ajout, donnees_python], axis=1) concatener
18.1.2 Les deux bases concaténées n’ont pas le même nombre de lignes
/* Si l'une des bases comprend plus de ligne que l'autre, ajout d'une ligne de valeurs manquantes */
proc sql noprint;select count(*) + 1 into :tot from donnees_sas;run;
data Ajout;do Num_ligne = 1 to &tot.;output;end;run;
data Concatener;merge Ajout donnees_sas;run;
# Erreur si l'une des bases comprend plus de lignes que l'autre
<- data.frame(num_ligne = seq_len(nrow(donnees_rbase) + 1))
ajout # donnees_rbase_ajout <- cbind(ajout, donnees_rbase)
# Proposition de solution
<- function(liste) {
cbind_alt # Nombre maximal de colonnes dans la liste de dataframes
<- max(unlist(lapply(liste, nrow)))
maxCol # Ajout d'une colonne de valeurs manquantes pour toutes les bases ayant moins de ligne que le maximum
<- lapply(liste, function(x) {
res for (i in seq_len(maxCol - nrow(x))) {
nrow(x) + i, ] <- NA
x[
}return(x)
})# On joint les résultats
return(do.call(cbind, res))
}<- cbind_alt(list(ajout, donnees_rbase)) concatener
# Ne fonctionne si l'une des bases comprend plus de lignes que l'autre !
<- tibble(num_ligne = seq_len(nrow(donnees_tidyverse) + 1))
ajout #concatener <- donnees_tidyverse %>% bind_cols(ajout)
# cf. solution proposée dans R-Base
<- function(liste) {
cbind_alt # Nombre maximal de colonnes dans la liste de dataframes
<- max(unlist(lapply(liste, nrow)))
maxCol # Ajout d'une colonne de valeurs manquantes pour toutes les bases ayant moins de ligne que le maximum
<- lapply(liste, function(x) {
res for (i in seq_len(maxCol - nrow(x))) {
nrow(x) + i, ] <- NA
x[
}return(x)
})# On joint les résultats
return(bind_cols(res))
}<- cbind_alt(list(ajout, donnees_tidyverse)) concatener
# data.table::cbind fonctionne aussi avec des bases comportant un nombre différent de lignes
# Mais attention, le résultat n'est pas le même que sur SAS, il y a recycling
<- data.table(num_ligne = seq_len(nrow(donnees_datatable) + 1))
ajout <- data.table::cbind(ajout, donnees_datatable) concatener
# Si l'une des bases comprend plus de ligne que l'autre, ajout d'une ligne de valeurs manquantes, comme avec SAS
= pd.DataFrame({'num_ligne': range(1, len(donnees_python) + 2)})
num_ligne = pd.concat([num_ligne, donnees_python], axis=1) concatener
18.2 Empiler deux bases de données
On va empiler la moyenne des notes en dessous de la base des notes.
/* On sélectionne un nombre réduit de variables pour simplifier l'exemple */
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data Notes;set donnees_sas (keep = identifiant ¬es.);run;
/* Moyenne des notes */
proc means data = Notes noprint mean;var ¬es.;output out = Ajout mean = ¬es.;run;
/* On concatène avec les données. Valeur manquante si les variables ne correspondent pas */
/* L'instruction set permet de concaténer les bases */
data Empiler;set Notes Ajout (drop = _type_ _freq_);run;
/* Autre solution, proc append */
data Empiler;set Notes;run;
proc append base = Empiler data = Ajout force;run;
/* On renomme la ligne des moyennes ajoutée */
data Empiler;
set Empiler nobs = nobs;
if _N_ = nobs then Identifiant = "Moyenne";
run;
# On va empiler la somme des notes en dessous de la base des notes
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes # Moyenne des notes
<- data.frame(t(colMeans(donnees_rbase[, varNotes], na.rm = TRUE)))
moyennes # On crée la base des notes
<- donnees_rbase[, varNotes]
notes # rbind lorsque les bases empilées ont le même nombre de colonne
<- rbind(notes, moyennes)
empiler
# Mais, ne fonctionne plus si l'on concatène des bases de taille différente
<- donnees_rbase[, c("identifiant", varNotes)]
notes # Ne fonctionne pas
#empiler <- rbind(notes, moyennes)
# Une solution alternative, lorsque le nombre de colonnes diffère entre les deux bases
# Lorsque les variables ne correspondent pas, on les crée avec des valeurs manquantes, via setdiff
<- function(x, y) {
rbind_alt rbind(data.frame(c(x, sapply(setdiff(names(y), names(x)), function(z) NA))),
data.frame(c(y, sapply(setdiff(names(x), names(y)), function(z) NA)))
)
}<- rbind_alt(notes, moyennes)
empiler # On renomme la ligne des moyennes ajoutée
nrow(empiler), "identifiant"] <- "Moyenne" empiler[
# On va empiler la somme des notes en dessous de la base des notes
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes # Moyenne des notes
<- donnees_tidyverse %>%
moyennes summarise(across(varNotes, ~mean(., na.rm = TRUE)))
<- donnees_tidyverse %>%
empiler select(all_of(varNotes)) %>%
bind_rows(moyennes)
# Fonctionne toujours si l'on concatène des bases de taille différente
<- donnees_tidyverse %>%
empiler select(identifiant, all_of(varNotes)) %>%
bind_rows(moyennes)
<- empiler %>%
empiler # On renomme la ligne des moyennes ajoutée
mutate(identifiant = ifelse(row_number() == nrow(empiler),
"Moyenne",
identifiant))
# On va empiler la somme des notes en dessous de la base des notes
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes # Moyenne des notes
<- data.table(donnees_datatable[, lapply(.SD, mean, na.rm = TRUE), .SDcols = varNotes])
moyennes
# On crée la base des notes
<- donnees_datatable[, mget(c("identifiant", varNotes))]
notes
# Empilement proprement dit
<- rbindlist(list(notes, moyennes), fill = TRUE)
empiler # On renomme la ligne des moyennes ajoutée
set(empiler, i = nrow(empiler), j = "identifiant", value = "Moyenne")
# On va empiler la somme des notes en dessous de la base des notes
= ["note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel"]
varNotes # Moyenne des notes
= pd.DataFrame(donnees_python[varNotes].mean()).T
moyennes 'identifiant'] = 'Moyenne'
moyennes[# On crée la base des notes
= donnees_python[['identifiant'] + varNotes]
notes
for col in notes.columns:
if col not in moyennes.columns:
= np.nan
moyennes[col]
# Concaténer les DataFrames
= pd.concat([notes, moyennes], ignore_index=True) empiler
18.3 Ajouter une ligne de valeurs manquantes à une base de données
data Ajout;run;
data Ajout_Missing;set Jointure Ajout;run;
<- donnees_rbase
ajout_na nrow(ajout_na) + 1, ] <- NA ajout_na[
<- donnees_tidyverse %>%
ajout_na bind_rows(tibble(NA))
<- rbindlist(list(donnees_datatable, data.table(NA)), fill = TRUE) ajout_na
19 Statistiques descriptives
19.1 Moyenne
19.1.1 Moyenne
proc means data = donnees_sas mean;var note_contenu;run;
proc sql;select mean(note_contenu) from donnees_sas;run;
# Importance du na.rm = TRUE en cas de variables manquantes
mean(donnees_rbase$note_contenu)
mean(donnees_rbase$note_contenu, na.rm = TRUE)
# La fonction mean de R est lente. Ecriture plus rapide
sum(donnees_rbase$note_contenu, na.rm = TRUE) / sum(! is.na(donnees_rbase$note_contenu))
# Importance du na.rm = TRUE en cas de variables manquantes
%>% pull(note_contenu) %>% mean()
donnees_tidyverse %>% pull(note_contenu) %>% mean(na.rm = TRUE)
donnees_tidyverse
# Autres solutions
# Le chiffre est arrondi lorsqu'il est affiché, du fait des propriétés des tibbles
%>% summarise(mean(note_contenu, na.rm = TRUE))
donnees_tidyverse %>%
donnees_tidyverse summarise(across(note_contenu, ~mean(., na.rm = TRUE)))
# Attention, en tidyverse, les syntaxes suivantes ne fonctionnent pas !
# donnees_tidyverse %>% mean(note_contenu)
# donnees_tidyverse %>% mean(note_contenu, na.rm = TRUE)
# Importance du na.rm = TRUE en cas de variables manquantes
mean(note_contenu)]
donnees_datatable[, mean(note_contenu, na.rm = TRUE)]
donnees_datatable[,
# Autre solution
lapply(.SD, mean, na.rm = TRUE), .SDcols = "note_contenu"] donnees_datatable[,
19.1.2 Moyenne par sélection
/* Ici pour les seules femmes */
proc means data = donnees_sas mean;
var note_contenu;
where sexef = "Femme";
run;
# Ici, pour les seules femmes
with(subset(donnees_rbase, sexef == "Femme"), mean(note_contenu, na.rm = TRUE))
mean(donnees_rbase[donnees_rbase$sexef == "Femme", "note_contenu"], na.rm = TRUE)
# Ici, pour les seules femmes
%>%
donnees_tidyverse filter(sexef == "Femme") %>%
pull(note_contenu) %>%
mean(na.rm = TRUE)
# Autres solutions
%>%
donnees_tidyverse filter(sexef == "Femme") %>%
summarise(moyenne = mean(note_contenu, na.rm = TRUE))
%>%
donnees_tidyverse filter(sexef == "Femme") %>%
summarise(across(note_contenu, ~ mean(., na.rm = TRUE)))
# Attention, syntaxe qui ne fonctionne qu'avec %>%, pas avec %>% !
%>%
donnees_tidyverse filter(sexef == "Femme") %>%
mean(.$note_contenu, na.rm = TRUE)} {
# Ici, pour les seules femmes
== "Femme", mean(note_contenu, na.rm = TRUE)]
donnees_datatable[sexef == "Femme", lapply(.SD, function(x) mean(x, na.rm = TRUE)), .SDcols = "note_contenu"] donnees_datatable[sexef
19.2 Moyenne pondérée
19.2.1 Moyenne pondérée
proc means data = donnees_sas mean;
var note_contenu;
weight poids_sondage;run;
weighted.mean(donnees_rbase$note_contenu, donnees_rbase$poids_sondage, na.rm = TRUE)
# Autre méthode, mais attention aux NA !!
with(donnees_rbase, sum(note_contenu * poids_sondage, na.rm = TRUE) / sum((!is.na(note_contenu)) * poids_sondage, na.rm = TRUE))
%>%
donnees_tidyverse summarise(across(note_contenu, ~weighted.mean(., w = poids_sondage, na.rm = TRUE)))
weighted.mean(note_contenu, poids_sondage, na.rm = TRUE)] donnees_datatable[,
19.2.2 Moyenne pondérée par sélection
/* Par sélection (ici pour les seules femmes) */
proc means data = donnees_sas mean;
var note_contenu;
where sexef = "Femme";
weight poids_sondage;run;
# Par sélection (ici pour les seules femmes)
with(subset(donnees_rbase, sexef == "Femme"), weighted.mean(note_contenu, poids_sondage, na.rm = TRUE))
# Par sélection (ici pour les seules femmes)
%>%
donnees_tidyverse filter(sexef == "Femme") %>%
summarise(across(note_contenu, ~weighted.mean(., w = poids_sondage, na.rm = TRUE)))
# Par sélection (ici pour les seules femmes)
== "Femme", weighted.mean(note_contenu, poids_sondage, na.rm = TRUE)]
donnees_datatable[sexef == "Femme", lapply(.SD, function(x) weighted.mean(x, poids_sondage, na.rm = TRUE)), .SDcols = "note_contenu"] donnees_datatable[sexef
19.3 Moyenne de plusieurs variables
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
proc means data = donnees_sas mean;
var ¬es.;
run;
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes
# Plusieurs solutions
# Sous forme de liste
lapply(donnees_rbase[, notes], mean, na.rm = TRUE)
# Sous forme de vecteur
sapply(donnees_rbase[, notes], mean, na.rm = TRUE)
apply(donnees_rbase[, notes], 2, mean, na.rm = TRUE)
# Si l'on souhaite renommer les colonnes
<- sapply(donnees_rbase[, notes], mean, na.rm = TRUE)
moyennes names(moyennes) <- paste("Moyenne", names(moyennes), sep = "_")
moyennes
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes %>%
donnees_tidyverse summarise(across(all_of(notes), ~ mean(.x, na.rm = TRUE)))
# Si l'on souhaite renommer les colonnes
<- donnees_tidyverse %>%
moyennes summarise(across(all_of(notes), ~ mean(.x, na.rm = TRUE), .names = "Moyenne_{.col}"))
# Autres solutions
# Obsolètes
%>%
donnees_tidyverse summarise_at(notes, mean, na.rm = TRUE)
%>%
donnees_tidyverse select(starts_with("Note") & !ends_with("_100")) %>%
summarise_all(.funs = ~ mean(., na.rm = TRUE), .vars = notes)
# Si l'on souhaite renommer les colonnes
<- donnees_tidyverse %>%
moyennes summarise_at(notes, mean, na.rm = TRUE) %>%
rename_with(~ paste("Moyenne", ., sep = "_"))
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes <- donnees_datatable[, lapply(.SD, mean, na.rm = TRUE), .SDcols = notes]
moyennes
# Si l'on souhaite renommer les colonnes
setnames(moyennes, notes, paste("Moyenne", notes, sep = "_"))
moyennes
19.4 Moyenne pondérée de plusieurs variables
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
proc means data = donnees_sas mean;
var ¬es.;
weight poids_sondage;run;
with(donnees_rbase, sapply(donnees_rbase[, notes], function(x) weighted.mean(x, poids_sondage, na.rm = TRUE)))
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes %>%
donnees_tidyverse summarise(across(notes, ~ weighted.mean(.x, poids_sondage, na.rm = TRUE)))
# Autre solution
%>%
donnees_tidyverse summarise_at(notes, ~ weighted.mean(.x, poids_sondage, na.rm = TRUE))
<- donnees_datatable[, lapply(.SD, function(x) weighted.mean(x, poids_sondage, na.rm = TRUE)), .SDcols = notes]
moyennes moyennes
19.5 Nombreuses statistiques (somme, moyenne, médiane, mode, etc.)
/* Petite différence avec SAS sur le nombre de lignes du fait des valeurs manquantes */
/* Somme, moyenne, médiane, minimum, maximum, variance, écart-type, nombre de données non manquantes (n), nombre de données manquantes (nmiss), intervalle (entre max et min), mode */
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
/* Par la proc means, en un seul tableau */
proc means data = donnees_sas sum mean median min max var std n nmiss range mode;
var ¬es.;
run;
/* Par la proc univariate, variable par variable */
proc univariate data = donnees_sas;
var ¬es.;
run;
# Somme, moyenne, médiane, minimum, maximum, variance, écart-type, nombre de données (manquantes et non manquantes), nombre de valeurs manquantes, Intervalle (entre max et min), mode
# Petite différence avec SAS sur le nombre de lignes du fait des valeurs manquantes
# Une solution pour obtenir le mode en R est d'utiliser fmode du package collapse
library(collapse)
sapply(donnees_rbase[, notes], function(x) c("Somme" = sum(x, na.rm = TRUE),
"Moyenne" = mean(x, na.rm = TRUE),
"Médiane" = median(x, na.rm = TRUE),
"Min" = min(x, na.rm = TRUE),
"Max" = max(x, na.rm = TRUE),
# Pour la variance, la somme des carrés est divisée par n - 1, où n est le nombre de données
"Variance" = var(x, na.rm = TRUE),
"Ecart-type" = sd(x, na.rm = TRUE),
"N" = length(x),
"NMiss" = sum(is.na(x)),
"Intervalle" = max(x, na.rm = TRUE) - min(x, na.rm = TRUE),
"Mode" = collapse::fmode(x)
))
# Somme, moyenne, médiane, minimum, maximum, variance, écart-type, nombre de données (manquantes et non manquantes), nombre de valeurs manquantes, Intervalle (entre max et min), mode
# Petite différence avec SAS sur le nombre de lignes du fait des valeurs manquantes
# Une solution pour obtenir le mode en R est d'utiliser fmode du package collapse
library(collapse)
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes <- function(x) {
StatsDesc_tidyverse c(
Somme = sum(x, na.rm = TRUE),
Moyenne = mean(x, na.rm = TRUE),
Mediane = median(x, na.rm = TRUE),
Min = min(x, na.rm = TRUE),
Max = max(x, na.rm = TRUE),
Variance = var(x, na.rm = TRUE),
Ecart_type = sd(x, na.rm = TRUE),
N = length(x),
NMiss = sum(is.na(x)),
Intervalle = max(x, na.rm = TRUE) - min(x, na.rm = TRUE),
Mode = collapse::fmode(x)
)
}
# 1ère solution avec les notes en ligne et les statistiques en colonnes
%>%
donnees_tidyverse select(all_of(notes)) %>%
map(~ StatsDesc_tidyverse(.x)) %>%
bind_rows() %>%
bind_cols(tibble(Note = c(notes))) %>%
relocate(Note)
# 2e solution avec les notes en colonne
%>%
donnees_tidyverse reframe(across(notes, ~ StatsDesc_tidyverse(.x))) %>%
bind_cols(tibble(Indicateur = c("Somme", "Moyenne", "Mediane", "Min", "Max", "Variance",
"Ecart_type", "N", "NMiss", "Intervalle", "Mode"))) %>%
relocate(Indicateur)
# Somme, moyenne, médiane, minimum, maximum, variance, écart-type, nombre de données (manquantes et non manquantes), nombre de valeurs manquantes, Intervalle (entre max et min), mode
# Petite différence avec SAS sur le nombre de lignes du fait des valeurs manquantes
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes # Une solution pour obtenir le mode en R est d'utiliser fmode du package collapse
library(collapse)
<- donnees_datatable[, lapply(.SD, function(x) c(sum(x, na.rm = TRUE),
moyennes mean(x, na.rm = TRUE),
median(x, na.rm = TRUE),
min(x, na.rm = TRUE),
max(x, na.rm = TRUE),
var(x, na.rm = TRUE),
sd(x, na.rm = TRUE),
.N,sum(is.na(x)),
max(x, na.rm = TRUE) - min(x, na.rm = TRUE),
::fmode(x)
collapse
)),= notes]
.SDcols cbind(data.table(Nom = c("Somme", "Moyenne", "Médiane", "Min", "Max", "Variance", "Ecart_type", "N", "NMiss", "Intervalle", "Mode")), moyennes)
# Autre solution
<- function(x) {
StatsDesc list(
Variable = names(x),
Somme = lapply(x, sum, na.rm = TRUE),
Moyenne = lapply(x, mean, na.rm = TRUE),
Mediane = lapply(x, median, na.rm = TRUE),
Min = lapply(x, min, na.rm = TRUE),
Max = lapply(x, max, na.rm = TRUE),
Variance = lapply(x, var, na.rm = TRUE),
Ecart_type = lapply(x, sd, na.rm = TRUE),
N = lapply(x, function(x) length(x)),
NMiss = lapply(x, function(x) sum(is.na(x))),
Intervalle = lapply(x, function(x) max(x, na.rm = TRUE) - min(x, na.rm = TRUE)),
Mode = lapply(x, collapse::fmode)
)
}StatsDesc(.SD), .SDcols = notes] donnees_datatable[,
19.6 Nombreuses statistiques pondérées (somme, moyenne, médiane, mode, etc.)
/* Somme, moyenne, médiane, minimum, maximum, variance, écart-type, nombre de données non manquantes (n), nombre de données manquantes (nmiss), intervalle, mode */
/* Par la proc means, en un seul tableau */
/* L'option vardef = wgt permet de diviser la variable par la somme des poids et non le nombre de données, pour être cohérent
avec R */
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
/* Par la proc means, en un seul tableau */
proc means data = donnees_sas sum mean median min max var std n nmiss range mode vardef = wgt;
var ¬es.;
weight poids_sondage;run;
/* Par la proc univariate, variable par variable */
proc univariate data = donnees_sas;
var ¬es.;
weight poids_sondage;run;
# Une solution pour obtenir les résultats pondérés est d'utiliser les fonctions du package collapse
# L'option na.rm est par défaut à TRUE dans le package
# Attention, dans le package, le poids s'indique par la variable w =
library(collapse)
sapply(donnees_rbase[, notes], function(x) c("Somme" = collapse::fsum(x, w = donnees_rbase$poids_sondage),
"Moyenne" = collapse::fmean(x, w = donnees_rbase$poids_sondage),
"Médiane" = collapse::fmedian(x, w = donnees_rbase$poids_sondage),
"Min" = collapse::fmin(x),
"Max" = collapse::fmax(x),
# Pour la variance, la somme des carrés est divisée par n - 1, où n est le nombre de données
"Variance" = collapse::fvar(x, w = donnees_rbase$poids_sondage),
"Ecart-type" = collapse::fsd(x, w = donnees_rbase$poids_sondage),
"N" = collapse::fnobs(x),
"NMiss" = collapse::fnobs(is.na(x)),
"Intervalle" = collapse::fmax(x) - collapse::fmin(x),
"Mode" = collapse::fmode(x, w = donnees_rbase$poids_sondage)
))
# Une solution pour obtenir les résultats pondérés est d'utiliser les fonctions du package collapse
# L'option na.rm est par défaut à TRUE dans le package
# Attention, dans le package, le poids s'indique par la variable w =
library(collapse)
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes <- function(x, w) {
StatsDescPond_tidyverse c(
Somme = collapse::fsum(x, w = w),
Moyenne = collapse::fmean(x, w = w),
Mediane = collapse::fmedian(x, w = w),
Min = collapse::fmin(x),
Max = collapse::fmax(x),
Variance = collapse::fvar(x, w = w),
Ecart_type = collapse::fsd(x, w = w),
N = collapse::fnobs(x),
NMiss = collapse::fnobs(is.na(x)),
Intervalle = collapse::fmax(x) - collapse::fmin(x),
Mode = collapse::fmode(x, w = w)
)
}%>%
donnees_tidyverse reframe(across(notes, ~ StatsDescPond_tidyverse(.x, poids_sondage))) %>%
bind_cols(tibble(Indicateur = c("Somme", "Moyenne", "Mediane", "Min", "Max", "Variance",
"Ecart_type", "N", "NMiss", "Intervalle", "Mode"))) %>%
relocate(Indicateur)
# Autre solution
%>%
donnees_tidyverse select(all_of(notes)) %>%
map(~StatsDescPond_tidyverse(.x, donnees_tidyverse$poids_sondage)) %>%
bind_cols()%>%
bind_cols(tibble(Indicateur = c("Somme", "Moyenne", "Mediane", "Min", "Max", "Variance",
"Ecart_type", "N", "NMiss", "Intervalle", "Mode"))) %>%
relocate(Indicateur)
# Une solution pour obtenir les résultats pondérés est d'utiliser les fonctions du package collapse
# L'option na.rm est par défaut à TRUE dans le package
# Attention, dans le package, le poids s'indique par la variable w =
library(collapse)
# À FAIRE : y-a-t-il plus simple ???
# Est-on obligés d'utiliser systématiquement donnees_datatable$poids_sondage ?
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes
<- function(x, poids = donnees_datatable$poids_sondage) {
StatsDescPond list(
Variables = names(x),
Somme = collapse::fsum(x, w = poids),
Moyenne = collapse::fmean(x, w = poids),
Mediane = collapse::fmedian(x, w = poids),
Min = collapse::fmin(x),
Max = collapse::fmax(x),
Variance = collapse::fvar(x, w = poids),
Ecart_type = collapse::fsd(x, w = poids),
N = collapse::fnobs(x),
NMiss = collapse::fnobs(is.na(x)),
Intervalle = collapse::fmax(x) - collapse::fmin(x),
Mode = collapse::fmode(x, w = poids)
)
}StatsDescPond(.SD), .SDcols = notes] donnees_datatable[,
19.7 Variance et variance pondérée
19.7.1 Variance
proc means data = donnees_sas var;
var note_contenu;
run;
var(donnees_rbase$note_contenu)
%>%
donnees_tidyverse summarise(var = var(note_contenu))
var(note_contenu)] donnees_datatable[,
19.7.2 Variance pondérée
proc means data = donnees_sas var vardef = wgt;
var note_contenu;
weight poids_sondage;run;
library(collapse)
with(donnees_rbase, collapse::fvar(note_contenu, w = poids_sondage))
library(collapse)
%>%
donnees_tidyverse summarise(var = collapse::fvar(note_contenu, w = poids_sondage))
library(collapse)
::fvar(note_contenu, w = poids_sondage)] donnees_datatable[, collapse
19.7.3 Calcul “manuel” de la variance pondérée
La variance pondérée se calcule par la formule : \[ \frac{\sum_i{w_i \times (x_i - \bar{x}) ^2}}{\sum_i{w_i}} = \frac{\sum_i{w_ix_i^2}} {\sum_i{w_i}} - \bar{x}^2 \]
où \(x_i\) désigne la variable et \(w_i\) le poids.
On la calcule “manuellement” en R
pour confirmer le résultat.
proc means data = donnees_sas var vardef = wgt;
var note_contenu;
weight poids_sondage;run;
<- with(donnees_rbase,
x_2 sum(poids_sondage * note_contenu**2 * complete.cases(note_contenu, poids_sondage), na.rm = TRUE))
<- with(donnees_rbase,
x_m sum(poids_sondage * note_contenu * complete.cases(note_contenu, poids_sondage), na.rm = TRUE))
<- with(donnees_rbase,
p sum(poids_sondage * complete.cases(note_contenu, poids_sondage), na.rm = TRUE))
/ p - (x_m / p) ** 2 x_2
<- donnees_tidyverse %>%
x_2 summarise(sum(poids_sondage * note_contenu**2 * complete.cases(note_contenu, poids_sondage), na.rm = TRUE)) %>%
pull()
<- donnees_tidyverse %>%
x_m summarise(sum(poids_sondage * note_contenu * complete.cases(note_contenu, poids_sondage), na.rm = TRUE)) %>%
pull()
<- donnees_tidyverse %>%
p summarise(sum(poids_sondage * complete.cases(note_contenu, poids_sondage), na.rm = TRUE)) %>%
pull()
/ p - (x_m / p) ** 2 x_2
<- donnees_datatable[, sum(poids_sondage * note_contenu**2 * complete.cases(note_contenu, poids_sondage),
x_2 na.rm = TRUE)]
<- donnees_datatable[, sum(poids_sondage * note_contenu * complete.cases(note_contenu, poids_sondage),
x_m na.rm = TRUE)]
<- donnees_datatable[, sum(poids_sondage * complete.cases(note_contenu, poids_sondage), na.rm = TRUE)]
p / p - (x_m / p) ** 2 x_2
19.8 Déciles et quantiles
/* On calcule déjà la moyenne des notes par individu */
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data donnees_sas;
set donnees_sas;
mean(of ¬es.);
Note_moyenne = run;
/* Déciles et quartiles de la note moyenne */
/* Par la proc means */
proc means data = donnees_sas StackODSOutput Min P10 P20 P30 P40 Median P60 P70 Q3 P80 P90 Max Q1 Median Q3 QRANGE;
var Note_moyenne;
output summary = Deciles_proc_means;
ods run;
/* Par la proc univariate */
proc univariate data = donnees_sas;
var Note_moyenne;
output out = Deciles_proc_univariate pctlpts=00 to 100 by 10 25 50 75 PCTLPRE=_;
run;
# On calcule déjà la moyenne des notes par individu
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes $note_moyenne <- rowMeans(donnees_rbase[, notes], na.rm = TRUE)
donnees_rbase
# Et les quantiles (déciles et quartiles)
quantile(donnees_rbase$note_moyenne, probs = c(seq(0, 1, 0.1), 0.25, 0.5, 0.75), na.rm = TRUE)
# Intervalle inter-quartile
IQR(donnees_rbase$note_moyenne, na.rm = TRUE)
# on calcule déjà la moyenne des notes par individu
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- donnees_tidyverse %>%
donnees_tidyverse mutate(note_moyenne = rowMeans(across(all_of(varNotes)), na.rm = TRUE))
# Et les quantiles (déciles et quartiles)
%>%
donnees_tidyverse pull(note_moyenne) %>%
quantile(probs = c(seq(0, 1, 0.1), 0.25, 0.5, 0.75), na.rm = TRUE)
# Intervalle inter-quartile
%>%
donnees_tidyverse pull(note_moyenne) %>%
IQR(na.rm = TRUE)
# on calcule déjà la moyenne des notes par individu
<- c("note_contenu","note_formateur","note_moyens","note_accompagnement","note_materiel")
notes := rowMeans(.SD, na.rm = TRUE), .SDcols = notes]
donnees_datatable[, note_moyenne
# Et les quantiles (déciles et quartiles)
quantile(.SD, probs = c(seq(0, 1, 0.1), 0.25, 0.5, 0.75), na.rm = TRUE), .SDcols = "note_moyenne"]
donnees_datatable[, lapply(.SD, quantile, probs = c(seq(0, 1, 0.1), 0.25, 0.5, 0.75), na.rm = TRUE), .SDcols = "note_moyenne"]
donnees_datatable[,
# Intervalle inter-quartile
IQR(note_moyenne, na.rm = TRUE)]
donnees_datatable[, lapply(.SD, function(x) IQR(x, na.rm = TRUE)), .SDcols = "note_moyenne"] donnees_datatable[,
19.9 Déciles et quantiles pondérés
/* On calcule déjà la moyenne des notes par individu */
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data donnees_sas;
set donnees_sas;
mean(of ¬es.);
Note_moyenne = run;
/* Par la proc means */
proc means data = donnees_sas StackODSOutput Min P10 P20 P30 P40 Median P60 P70 Q3 P80 P90 Max Q1 Median Q3 QRANGE;
var Note_moyenne;
output summary = Deciles_proc_means;
ods
weight poids_sondage;run;
/* Par la proc univariate */
proc univariate data = donnees_sas;
var Note_moyenne;
output out = Deciles_proc_univariate pctlpts=00 to 100 by 10 25 50 75 PCTLPRE=_;
weight poids_sondage;run;
# Une solution pour obtenir les résultats pondérés est d'utiliser la fonction fquantile du package collapse
# L'option na.rm est par défaut à TRUE dans le package
library(collapse)
# On calcule déjà la moyenne des notes par individu
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes $note_moyenne <- rowMeans(donnees_rbase[, notes], na.rm = TRUE)
donnees_rbase
# Les quantiles (déciles et quartiles)
::fquantile(donnees_rbase$note_moyenne, w = donnees_rbase$poids_sondage,
collapseprobs = c(seq(0, 1, 0.1), 0.25, 0.5, 0.75))
# Une solution pour obtenir les résultats pondérés est d'utiliser les fonctions du package collapse
# L'option na.rm est par défaut à TRUE dans le package
library(collapse)
# On calcule déjà la moyenne des notes par individu
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- donnees_tidyverse %>%
donnees_tidyverse mutate(note_moyenne = rowMeans(across(all_of(varNotes)), na.rm = TRUE))
# Les quantiles (déciles et quartiles)
%>%
donnees_tidyverse pull(note_moyenne) %>%
::fquantile(probs = c(seq(0, 1, 0.1), 0.25, 0.5, 0.75), w = donnees_tidyverse$poids_sondage) collapse
# Une solution pour obtenir les résultats pondérés est d'utiliser la fonction fquantile du package collapse
# L'option na.rm est par défaut à TRUE dans le package
library(collapse)
# On calcule déjà la moyenne des notes par individu
<- c("note_contenu","note_formateur","note_moyens","note_accompagnement","note_materiel")
notes := rowMeans(.SD, na.rm = TRUE), .SDcols = notes]
donnees_datatable[, note_moyenne
# Les quantiles (déciles et quartiles)
lapply(.SD, function(x) collapse::fquantile(x, w = poids_sondage,
donnees_datatable[, probs = c(seq(0, 1, 0.1), 0.25, 0.5, 0.75)
)),= "note_moyenne"] .SDcols
19.10 Rang de la note
Ajouter dans la base le rang de la note par ordre décroissant.
/* On calcule déjà la moyenne des notes par individu */
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data donnees_sas;
set donnees_sas;
mean(of ¬es.);
Note_moyenne = run;
proc rank data = donnees_sas out = donnees_sas descending;
var note_moyenne;
ranks rang_note_moyenne;run;
# On calcule déjà la moyenne des notes par individu
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes $note_moyenne <- rowMeans(donnees_rbase[, notes], na.rm = TRUE)
donnees_rbase
# Attention, en R, rank trie par ordre croissant par défaut, alors que le tri est par ordre décroissant en SAS
# On exprime le rang par ordre décroissant, avec le - devant
$rang_note_moyenne <- rank(-donnees_rbase$note_moyenne) donnees_rbase
# On calcule déjà la moyenne des notes par individu
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- donnees_tidyverse %>%
donnees_tidyverse mutate(note_moyenne = rowMeans(across(all_of(varNotes)), na.rm = TRUE)) %>%
# Attention, en R, rank trie par ordre croissant par défaut, alors que le tri est par ordre décroissant en SAS
# On exprime le rang par ordre décroissant, avec le - devant
mutate(rang_note_moyenne = rank(-note_moyenne))
# On calcule déjà la moyenne des notes par individu
<- c("note_contenu","note_formateur","note_moyens","note_accompagnement","note_materiel")
notes := rowMeans(.SD, na.rm = TRUE), .SDcols = notes]
donnees_datatable[, note_moyenne
# Attention, en R, frank trie par ordre croissant par défaut, alors que le tri est par ordre décroissant en SAS
# On exprime le rang par ordre décroissant, avec le - devant
:= frank(-note_moyenne)] donnees_datatable[, rang_note_moyenne
19.11 Covariance et corrélation linéaire
19.11.1 Covariance entre deux notes
/* Covariance et corrélation linéaire (Kendall, Pearson, Spearman) */
proc corr data = donnees_sas kendall pearson spearman cov;
var note_contenu note_formateur;
run;
# Covariance (Kendall, Pearson, Spearman)
with(donnees_rbase,
sapply(c("pearson", "spearman", "kendall"),
function(x) cov(note_contenu, note_formateur, method = x, use = "complete.obs")))
# Covariance (Kendall, Pearson, Spearman)
<- c("pearson", "spearman", "kendall")
methodes %>%
methodes ::map(~
purrr%>%
donnees_tidyverse summarise(cov = cov(note_contenu, note_formateur, method = .x, use = "complete.obs"))) %>%
setNames(methodes) %>%
as_tibble()
# Covariance (Kendall, Pearson, Spearman)
<- c("pearson", "spearman", "kendall")
methodes setNames(donnees_datatable[, lapply(methodes,
function(x) cov(note_contenu, note_formateur,
method = x,
use = "complete.obs"))],
methodes)
19.11.2 Corrélation linéaire entre deux notes
/* Corrélation linéaire (Kendall, Pearson, Spearman) */
proc corr data = donnees_sas kendall pearson spearman cov;
var note_contenu note_formateur;
run;
# Corrélation linéaire (Kendall, Pearson, Spearman)
with(donnees_rbase,
sapply(c("pearson", "spearman", "kendall"),
function(x) cor(note_contenu, note_formateur, method = x, use = "complete.obs")))
# Corrélation linéaire (Kendall, Pearson, Spearman)
<- c("pearson", "spearman", "kendall")
methodes %>%
methodes ::map(~ donnees_tidyverse %>%
purrrsummarise(cor = cor(note_contenu, note_formateur, method = .x, use = "complete.obs"))) %>%
setNames(methodes) %>%
as_tibble()
# Corrélation linéaire (Kendall, Pearson, Spearman)
<- c("pearson", "spearman", "kendall")
methodes setNames(donnees_datatable[, lapply(methodes,
function(x) cor(note_contenu, note_formateur,
method = x,
use = "complete.obs"))],
methodes)
19.12 Centrer et réduire les variables
On souhaite centrer (moyenne nulle) et réduire (écart-type égal à 1) les notes.
%let notes = Note_Contenu Note_Formateur Note_Moyens Note_Accompagnement Note_Materiel;
data centrer_reduire;set donnees_sas (keep = ¬es.);run;
proc stdize data = centrer_reduire out = centrer_reduire method = std;
var ¬es.;
run;
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes <- scale(donnees_rbase[, notes])
centrer_reduire
# Autre solution avec les fonctions apply et sweep
# Centrer la base
<- sweep(donnees_rbase[, notes], 2, colMeans(donnees_rbase[, notes], na.rm = TRUE), FUN = "-")
centrer # Réduire la base
# On calcule déjà l'écart-type par colonne
<- t(apply(centrer, 2, sd, na.rm = TRUE))
ecart_type # Et on réduit
< sweep(centrer, 2, ecart_type, "/") centrer_reduire
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes <- donnees_tidyverse %>%
centrer_reduire select(all_of(notes)) %>%
mutate(across(notes, ~ (.x - mean(.x, na.rm = TRUE)) / sd(.x, na.rm = TRUE) ))
<- tolower(c("Note_Contenu", "Note_Formateur", "Note_Moyens", "Note_Accompagnement", "Note_Materiel"))
notes <- donnees_datatable[, lapply(.SD, function(x) (x - mean(x, na.rm = TRUE)) / sd(x, na.rm = TRUE)), .SDcols = notes] centrer_reduire
20 Tableaux de fréquence (proc freq
de SAS
) pour 1 variable
20.1 Tableaux de fréquence pour 1 variable
proc freq data = donnees_sas;
tables Sexe CSP;format Sexe sexef. CSP $cspf.;
run;
# Tableaux de fréquence (proc freq) (sans les valeurs manquantes)
table(donnees_rbase$cspf)
table(donnees_rbase$sexef)
# Autre syntaxe, donnant une mise en forme différente
ftable(donnees_rbase$cspf)
# Pour enlever les "donnees_rbase$", on peut utiliser with pour se placer dans l'environnement de donnees_rbase
with(donnees_rbase, table(cspf))
# Pour les proportions
prop.table(table(donnees_rbase$cspf)) * 100
# Devient plus difficile si l'on souhaite plus (sommes et proportions cumulées par exemple)
<- setNames(as.data.frame(table(donnees_rbase$cspf)), c("cspf", "Freq"))
freq <- transform(freq, Prop = Freq / sum(Freq) * 100)
freq <- transform(freq,
freq Freq_cum = cumsum(Freq),
Prop_cum = cumsum(Prop))
freq
%>%
donnees_tidyverse count(cspf) %>%
mutate(prop = n / sum(n) * 100,
n_cum = cumsum(n),
prop_cum = cumsum(prop))
# Ou alors
%>%
donnees_tidyverse group_by(cspf) %>%
summarise(n = n()) %>%
mutate(prop = n / sum(n) * 100,
n_cum = cumsum(n),
prop_cum = cumsum(prop)
)
# Tableaux de fréquence (proc freq) (avec les valeurs manquantes)
table(cspf) ]
donnees_datatable[, table(sexef) ]
donnees_datatable[,
# Pour les proportions
prop.table(table(cspf)) ] * 100
donnees_datatable[, Nombre = .N,
donnees_datatable[, .(Pourcentage = .N / length(donnees_datatable[, cspf]) * 100),
= cspf]
keyby = .N; .SD[, .(frac = .N / tot * 100), keyby = cspf]} ]
donnees_datatable[, {tot
# Autre façon d'utiliser les méthodes de data.table, avec les fréquences et proportions cumulés
<- data.table::dcast(donnees_datatable, cspf ~ ., fun = length)
tab colnames(tab)[colnames(tab) == "."] <- "Nombre"
:= lapply(.SD, function(col) col / sum(col) * 100), .SDcols = is.numeric]
tab[, Prop c("Freq_cum", "Prop_cum") := list(cumsum(Nombre), cumsum(Prop))] tab[,
20.2 Tableaux de fréquence avec les valeurs manquantes
proc freq data = donnees_sas;
tables Sexe CSP / missing;format Sexe sexef. CSP $cspf.;
run;
table(donnees_rbase$cspf, useNA = "always")
prop.table(table(donnees_rbase$cspf, useNA = "always")) * 100
%>%
donnees_tidyverse count(cspf) %>%
mutate(prop = n / sum(n) * 100)
table(cspf, useNA = "always") ]
donnees_datatable[, prop.table(table(cspf, useNA = "always"))] * 100
donnees_datatable[, Nombre = .N,
donnees_datatable[, .(Pourcentage = .N / length(donnees_datatable[, cspf]) * 100),
= cspf]
keyby = .N; .SD[, .(frac = .N / tot * 100), keyby = cspf]} ] donnees_datatable[, {tot
20.3 Tableaux de fréquence triés par la modalité la plus courante
proc freq data = donnees_sas order = freq;
tables Sexe CSP / missing;format Sexe sexef. CSP $cspf.;
run;
<- setNames(as.data.frame(table(donnees_rbase$cspf)), c("cspf", "Freq"))
freq <- transform(freq, Prop = Freq / sum(Freq) * 100)
freq order(-freq$Freq), ] freq[
%>%
donnees_tidyverse count(cspf) %>%
arrange(desc(n)) %>%
mutate(prop = n / sum(n) * 100,
n_cum = cumsum(n),
prop_cum = cumsum(prop))
# Autre solution
%>%
donnees_tidyverse count(cspf, sort = TRUE) %>%
mutate(prop = n / sum(n) * 100,
n_cum = cumsum(n),
prop_cum = cumsum(prop))
Nombre = .N,
donnees_datatable[, .(Pourcentage = .N / length(donnees_datatable[, cspf]) * 100),
= cspf][order(-Nombre)] keyby
20.4 Tableaux de fréquence avec la pondération
proc freq data = donnees_sas;
tables Sexe CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
xtabs(poids_sondage ~ cspf, data = donnees_rbase, addNA = TRUE)
prop.table(xtabs(poids_sondage ~ cspf, data = donnees_rbase, addNA = TRUE))
%>%
donnees_tidyverse count(cspf, wt = poids_sondage) %>%
mutate(prop = n / sum(n) * 100,
n_cum = cumsum(n),
prop_cum = cumsum(prop))
xtabs(poids_sondage ~ cspf, data = donnees_datatable, addNA = TRUE) ]
donnees_datatable[, prop.table(xtabs(poids_sondage ~ cspf, data = donnees_datatable, addNA = TRUE)) ]
donnees_datatable[, prop = sum(poids_sondage, na.rm = TRUE) / sum(donnees_datatable[, poids_sondage]) * 100), keyby = cspf]
donnees_datatable[, .(= sum(poids_sondage, na.rm = TRUE); .SD[, .(prop = sum(poids_sondage, na.rm = TRUE) / tot * 100), by = cspf]} ] donnees_datatable[, {tot
21 Tableaux de contingence (proc freq
de SAS
) pour 2 variables
Cette tâche, immédiate en SAS
, est plus complexe à réaliser en R
. Plusieurs stratégies, avec et sans packages, sont proposées ici.
21.1 Tableaux de contingence pour 2 variables
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
run;
/* Tableau de contingence (tableau croisé) sans les proportions lignes, colonnes et totales */
proc freq data = donnees_sas;
tables CSP * Sexe / missing nofreq norow nocol;format Sexe sexef. CSP $cspf.;
run;
# Tableau simple
table(donnees_rbase$cspf, donnees_rbase$sexef, useNA = "always")
# Tableau avec les sommes
addmargins(table(donnees_rbase$cspf, donnees_rbase$sexef, useNA = "always"))
# Proportions
<- table(donnees_rbase$cspf, donnees_rbase$sexef, useNA = "always")
tab # Proportions par case
addmargins(prop.table(tab)) * 100
# Proportions par ligne
addmargins(prop.table(tab, margin = 1)) * 100
# Proportions par colonne
addmargins(prop.table(tab, margin = 2)) * 100
# Solution alternative, sans pondération
<- xtabs(~ cspf + sexef, data = donnees_rbase)
tab addmargins(prop.table(tab)) * 100
addmargins(prop.table(tab, margin = 1), margin = 2) * 100
addmargins(prop.table(tab, margin = 2), margin = 1) * 100
# Tableau de fréquence
<- donnees_tidyverse %>%
tab group_by(cspf, sexef) %>%
summarise(prop = n(), .groups = "drop_last") %>%
ungroup() %>%
mutate(prop = prop / sum(prop) * 100) %>%
spread(sexef, prop) %>%
mutate(Total = rowSums(across(where(is.numeric)), na.rm = TRUE))
<- bind_rows(tab, tab %>%
tab summarise(across(where(is.numeric), \(x) sum(x, na.rm = TRUE)),
across(where(is.character), ~"Total"))
)
tab
# Proportions par ligne
%>%
donnees_tidyverse group_by(cspf, sexef) %>%
summarise(prop = n()) %>%
mutate(prop = prop / sum(prop) * 100) %>%
spread(sexef, prop)
# Proportions par colonne
%>%
donnees_tidyverse group_by(sexef, cspf) %>%
summarise(prop = n()) %>%
mutate(prop = prop / sum(prop) * 100) %>%
spread(sexef, prop)
1ère solution
# Tableau simple
table(cspf, sexef, useNA = "always") ]
donnees_datatable[, # Tableau avec les sommes
addmargins(table(cspf, sexef, useNA = "always")) ]
donnees_datatable[, # Proportions
<- donnees_datatable[, table(cspf, sexef, useNA = "always") ]
tab # Proportions par case
addmargins(prop.table(tab)) * 100
# Proportions par ligne
addmargins(prop.table(tab, margin = 1)) * 100
# Proportions par colonne
addmargins(prop.table(tab, margin = 2)) * 100
2e solution
# Solution alternative, sans pondération
<- donnees_datatable[, xtabs(~ cspf + sexef, data = donnees_datatable) ]
tab
tabaddmargins(prop.table(tab)) * 100
addmargins(prop.table(tab, margin = 1), margin = 2) * 100
addmargins(prop.table(tab, margin = 2), margin = 1) * 100
3e solution
# Autre solution, avec les Grouping sets
<- data.table::cube(donnees_datatable, .(Nb = .N), by = c("cspf", "sexef"))
tab <- data.table::dcast(tab, cspf ~ sexef, value.var = "Nb")
tab # On harmonise le tableau
<- rbind(tab[2:nrow(tab)], tab[1,])
tab setcolorder(tab, c(setdiff(names(tab), "NA"), "NA"))
# On renomme la ligne et la colonne des totaux
nrow(tab), 1] <- "Total"
tab[names(tab)[which(names(tab) == "NA")] <- "Total"
tab
4e solution
# Autre façon d'utiliser les méthodes de data.table
<- data.table::dcast(donnees_datatable, cspf ~ sexef, fun.aggregate = length)
tab_prop # Proportion par ligne
/ Reduce(`+`, .SD), cspf]
tab_prop[, .SD # Proportion par colonne
<- unique(donnees_datatable[, (sexef)])
cols lapply(.SD, function(col) col / sum(col))), .SDcols = cols]
tab_prop[, (
# Pour avoir les sommes lignes
# À FAIRE : ne marche pas, à revoir !
#tab_prop <- data.table::dcast(donnees_datatable, cspf ~ sexef, fun.aggregate = length)
#tab_prop[, Total := rowSums(.SD), .SDcols = is.numeric]
#tab_prop <- rbind(tab_prop, tab_prop[, c(cspf = "Total", lapply(.SD, sum, na.rm = TRUE)),
# .SDcols = is.numeric],
# fill = TRUE)
#tab_prop[, (lapply(.SD, function(col) col / sum(col))), .SDcols = -1]
## Pour avoir les sommes colonnes
#tab[, sum(.SD), by = 1:nrow(tab), .SDcols = is.numeric]
#tab[, (lapply(.SD, function(col) col / sum(col))), .SDcols = -1]
#
## Autre solution plus pratique avec data.table
## Manipuler des formules sur R
#variable <- c("cspf", "sexef")
#formule <- as.formula(paste(paste(variable, collapse = " + "), ".", sep = " ~ "))
#tab_prop <- data.table::dcast(donnees_datatable, formule, fun.aggregate = length)
#colnames(tab_prop)[colnames(tab_prop) == "."] <- "total"
#tab_prop[, prop := total / sum(total)]
## Le tableau est remis sous forme croisée
#tab_prop <- dcast(tab_prop, cspf ~ sexef, value.var = c("prop"), fill = 0)
21.2 Tableau de contingence avec pondération
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
<- xtabs(poids_sondage ~ cspf + sexef, data = donnees_rbase, addNA = TRUE)
tab
tabaddmargins(prop.table(tab)) * 100
addmargins(prop.table(tab, margin = 1), margin = 2) * 100
addmargins(prop.table(tab, margin = 2), margin = 1) * 100
# Avec la fonction count
%>%
donnees_tidyverse count(cspf, sexef, wt = poids_sondage, name = "prop") %>%
ungroup() %>%
mutate(prop = prop / sum(prop) * 100) %>%
spread(sexef, prop)
# Avec la fonction summarise
%>%
donnees_tidyverse group_by(cspf, sexef) %>%
summarise(prop = sum(poids_sondage, na.rm = TRUE)) %>%
ungroup() %>%
mutate(prop = prop / sum(prop) * 100) %>%
spread(sexef, prop)
# Avec ajout des sommes par ligne et colonne
<- donnees_tidyverse %>%
tab count(cspf, sexef, wt = poids_sondage, name = "prop") %>%
ungroup() %>%
mutate(prop = prop / sum(prop) * 100) %>%
pivot_wider(names_from = sexef, values_from = prop) %>%
# Somme par ligne
mutate(Total = rowSums(across(where(is.numeric)), na.rm = TRUE))
# Somme par colonne
<- bind_rows(tab, tab %>%
tab summarise(across(where(is.numeric), sum, na.rm = TRUE),
across(where(is.character), ~"Total"))
) tab
<- donnees_datatable[, xtabs(poids_sondage ~ cspf + sexef, data = donnees_datatable, addNA = TRUE) ]
tab
tabaddmargins(prop.table(tab)) * 100
addmargins(prop.table(tab, margin = 1), margin = 2) * 100
addmargins(prop.table(tab, margin = 2), margin = 1) * 100
21.3 Copier-coller le tableau dans un tableur (Excel, etc.)
/* Copier-coller le résultat sur la fenêtre html "Results Viewer" */
proc freq data = donnees_sas;
tables Sexe * CSP / missing chisq;format Sexe sexef. CSP $cspf.;
run;
# On utilise les packages knitr et kableExtra
library(knitr)
library(kableExtra)
# Création d'un tableau
<- xtabs(~ cspf + sexef, data = donnees_rbase)
tab <- addmargins(prop.table(tab)) * 100
tab
# Afficher de façon plus jolie un tableau
::kable(tab)
knitr
# Copier-coller le résultat vers Excel
# Il suffit d'appliquer ce code ....
::kable_paper(kableExtra::kbl(tab), "hover", full_width = F)
kableExtra# ... et de copier-coller le résultat de la fenêtre Viewer vers Excel
# On utilise les packages knitr et kableExtra
library(knitr)
library(kableExtra)
# Création d'un tableau
<- donnees_tidyverse %>%
tab group_by(cspf, sexef) %>%
summarise(prop = n(), .groups = "drop_last") %>%
ungroup() %>%
mutate(prop = prop / sum(prop) * 100) %>%
spread(sexef, prop)
# Afficher de façon plus jolie un tableau
%>% knitr::kable()
tab
# Copier-coller le résultat vers Excel
# Il suffit d'appliquer ce code ....
%>%
tab ::kable() %>%
knitr::kable_paper("hover", full_width = F)
kableExtra# ... et de copier-coller le résultat de la fenêtre Viewer vers Excel
# On utilise les packages knitr et kableExtra
library(knitr)
library(kableExtra)
# Création d'un tableau
<- donnees_datatable[, xtabs(poids_sondage ~ cspf + sexef, data = donnees_datatable, addNA = TRUE) ]
tab <-
tab addmargins(prop.table(tab)) * 100
# Afficher de façon plus jolie un tableau
::kable(tab)
knitr
# Copier-coller le résultat vers Excel
# Il suffit d'appliquer ce code ....
::kable_paper(kableExtra::kbl(tab), "hover", full_width = F)
kableExtra# ... et de copier-coller le résultat de la fenêtre Viewer vers Excel
21.4 Tests d’associaton (Chi-Deux, etc.)
proc freq data = donnees_sas;
tables Sexe * CSP / missing chisq;format Sexe sexef. CSP $cspf.;
run;
# Test du Khi-Deux
with(donnees_rbase, chisq.test(cspf, sexef))
summary(table(donnees_rbase$cspf, donnees_rbase$sexef))
# Test du Khi-Deux
chisq.test(donnees_tidyverse %>% pull(cspf), donnees_tidyverse %>% pull(sexef))
# Test du Khi-Deux
chisq.test(cspf, sexef)] donnees_datatable[,
21.5 Solutions avec package R
permettant de pondérer
Autre possibilité, avec package R
, pour avoir la même présentation que la proc freq
de SAS
.
5 packages paraissent pertinents : descr
, flextable
, questionr
, survey
, procs
.
Des informations sur l’usage des packages en R
sont disponibles sur le site Utilit’R : https://book.utilitr.org/03_Fiches_thematiques/Fiche_comment_choisir_un_package.html.
21.5.1 Package descr
Lien vers la documentation : https://cran.r-project.org/web/packages/descr/descr.pdf.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(descr)
# Non pondéré
with(donnees_rbase, descr::crosstab(cspf, sexef, prop.r = TRUE, prop.c = TRUE, prop.t = TRUE))
# Pondéré
with(donnees_rbase, descr::crosstab(cspf, sexef, weight = poids_sondage, prop.r = TRUE, prop.c = TRUE, prop.t = TRUE))
# Sans les proportions par ligne et colonne
with(donnees_rbase, descr::crosstab(cspf, sexef, weight = poids_sondage, prop.r = FALSE, prop.c = FALSE, prop.t = TRUE))
library(descr)
# Non pondéré
with(donnees_tidyverse, descr::crosstab(cspf, sexef, prop.r = TRUE, prop.c = TRUE, prop.t = TRUE))
# Pondéré
with(donnees_tidyverse, descr::crosstab(cspf, sexef, weight = poids_sondage, prop.r = TRUE, prop.c = TRUE, prop.t = TRUE))
# Sans les proportions par ligne et colonne
with(donnees_tidyverse, descr::crosstab(cspf, sexef, weight = poids_sondage, prop.r = FALSE, prop.c = FALSE, prop.t = TRUE))
library(descr)
# Non pondéré
::crosstab(cspf, sexef, prop.r = TRUE, prop.c = TRUE, prop.t = TRUE)]
donnees_datatable[, descr# Pondéré
::crosstab(cspf, sexef, weight = poids_sondage, prop.r = TRUE, prop.c = TRUE, prop.t = TRUE)]
donnees_datatable[, descr# Sans les proportions par ligne et colonne
::crosstab(cspf, sexef, weight = poids_sondage, prop.r = FALSE, prop.c = FALSE, prop.t = TRUE)] donnees_datatable[, descr
21.5.2 Package flextable
Lien vers la documentation :
- https://ardata-fr.github.io/flextable-book/crosstabs.html
- https://cran.r-project.org/web/packages/flextable/flextable.pdf
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(flextable)
# Non pondéré
::proc_freq(donnees_rbase, "cspf", "sexef")
flextable# Pondéré
::proc_freq(donnees_rbase, "cspf", "sexef", weight = "poids_sondage")
flextable# Sans les proportions par ligne et colonne
::proc_freq(donnees_rbase, "cspf", "sexef", weight = "poids_sondage", include.row_percent = FALSE, include.column_percent = FALSE) flextable
library(flextable)
# Non pondéré
::proc_freq(donnees_tidyverse, "cspf", "sexef")
flextable# Pondéré
::proc_freq(donnees_tidyverse, "cspf", "sexef", weight = "poids_sondage")
flextable# Sans les proportions par ligne et colonne
::proc_freq(donnees_tidyverse, "cspf", "sexef", weight = "poids_sondage", include.row_percent = FALSE, include.column_percent = FALSE) flextable
library(flextable)
# Non pondéré
::proc_freq(donnees_datatable, "cspf", "sexef")
flextable# Pondéré
::proc_freq(donnees_datatable, "cspf", "sexef", weight = "poids_sondage")
flextable# Sans les proportions par ligne et colonne
::proc_freq(donnees_datatable, "cspf", "sexef", weight = "poids_sondage", include.row_percent = FALSE, include.column_percent = FALSE) flextable
21.5.3 Package questionr
Lien vers la documentation : https://cran.r-project.org/web/packages/questionr/questionr.pdf.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(questionr)
# Tableau croisé
# Sans pondération
<- with(donnees_rbase, questionr::wtd.table(cspf, sexef, useNA = "ifany"), na.rm = TRUE)
tab # Avec pondération
<- with(donnees_rbase, questionr::wtd.table(cspf, sexef, weights = poids_sondage, useNA = "ifany"), na.rm = TRUE)
tab
tab# Proportions
::prop(tab)
questionr# Proportions colonnes
::cprop(tab)
questionr# Proportions lignes
::rprop(tab) questionr
# Sans pondération
library(questionr)
# Tableau croisé
# Sans pondération
<- with(donnees_tidyverse, questionr::wtd.table(cspf, sexef, useNA = "ifany"), na.rm = TRUE)
tab # Avec pondération
<- with(donnees_tidyverse, questionr::wtd.table(cspf, sexef, weights = poids_sondage, useNA = "ifany"), na.rm = TRUE)
tab
tab# Proportions
::prop(tab)
questionr# Proportions colonnes
::cprop(tab)
questionr# Proportions lignes
::rprop(tab) questionr
# Sans pondération
library(questionr)
# Tableau croisé
# Sans pondération
<- donnees_datatable[, questionr::wtd.table(cspf, sexef, useNA = "ifany")]
tab # Avec pondération
<- donnees_datatable[, questionr::wtd.table(cspf, sexef, weights = poids_sondage, useNA = "ifany")]
tab
tab# Proportions
::prop(tab)
questionr# Proportions colonnes
::cprop(tab)
questionr# Proportions lignes
::rprop(tab) questionr
21.5.4 Package survey
Lien vers la documentation : https://cran.r-project.org/web/packages/survey/survey.pdf.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(survey)
<- survey::svydesign(id = ~1, weights = ~poids_sondage, data = donnees_rbase)
tab ::svytable(poids_sondage ~ sexef + cspf, design = tab) survey
# La syntaxe avec pipe n'est pas compatible avec le package survey
library(survey)
<- survey::svydesign(id = ~1, weights = ~poids_sondage, data = donnees_tidyverse)
tab ::svytable(poids_sondage ~ sexef + cspf, design = tab) survey
library(survey)
<- survey::svydesign(id = ~1, weights = ~poids_sondage, data = donnees_datatable)
tab ::svytable(poids_sondage ~ sexef + cspf, design = tab) survey
21.5.5 Package procs
Lien vers la documentation : https://cran.r-project.org/web/packages/procs/vignettes/procs-freq.html.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(procs)
::proc_freq(donnees_rbase, tables = cspf * sexef, options = v(missing))
procs# Ne fonctionne pas avec le poids !!!
#procs::proc_freq(donnees_rbase, tables = cspf * sexef, weight = poids_sondage, options = v(missing))
library(procs)
::proc_freq(donnees_tidyverse, tables = cspf * sexef, options = v(missing))
procs# Ne fonctionne pas avec le poids !!!
#procs::proc_freq(donnees_tidyverse, tables = cspf * sexef, weight = poids_sondage, options = v(missing))
library(procs)
# Il semble nécessaire de convertire l'objet en data.frame
::proc_freq(setDF(donnees_datatable), tables = cspf * sexef, options = v(missing))
procs# Ne fonctionne pas avec le poids !!!
#procs::proc_freq(setDF(donnees_datatable), tables = cspf * sexef, weight = poids_sondage, options = v(missing))
# On reconvertit en data.table
setDT(donnees_datatable)
21.6 Solutions avec package R
ne permettant apparemment pas de pondérer
Autres packages, qui semblent peu utiles, ne permettant apparemment pas de pondérer.
21.6.1 Package Janitor
Lien vers la documentation : https://cran.r-project.org/web/packages/janitor/vignettes/tabyls.html.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(janitor)
# Attention, la fonction tabyl ne permet pas de pondérer
<- janitor::tabyl(donnees_rbase, cspf, sexef)
tab
tab::adorn_totals(tab, c("row", "col"))
janitor# Pourcentages
::adorn_percentages(tab, denominator = "all", na.rm = TRUE)
janitor# Pourcentages lignes
::adorn_percentages(tab, denominator = "row", na.rm = TRUE)
janitor# Pourcentages colonnes
::adorn_percentages(tab, denominator = "col", na.rm = TRUE) janitor
library(janitor)
# Attention, la fonction tabyl ne permet pas de pondérer
<- donnees_tidyverse %>%
tab ::tabyl(cspf, sexef) %>%
janitor::adorn_totals(c("row", "col"))
janitor
tab# Pourcentages
%>% janitor::adorn_percentages(denominator = "all", na.rm = TRUE)
tab # Pourcentages lignes
%>% janitor::adorn_percentages(denominator = "row", na.rm = TRUE)
tab # Pourcentages colonnes
%>% janitor::adorn_percentages(denominator = "col", na.rm = TRUE) tab
library(janitor)
# Attention, la fonction tabyl ne permet pas de pondérer
<- janitor::tabyl(donnees_datatable, cspf, sexef)
tab
tab::adorn_totals(tab, c("row", "col"))
janitor# Pourcentages
::adorn_percentages(tab, denominator = "all", na.rm = TRUE)
janitor# Pourcentages lignes
::adorn_percentages(tab, denominator = "row", na.rm = TRUE)
janitor# Pourcentages colonnes
::adorn_percentages(tab, denominator = "col", na.rm = TRUE) janitor
21.6.2 Package crosstable
Lien vers la documentation : https://cran.r-project.org/web/packages/crosstable/crosstable.pdf.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(crosstable)
::crosstable(donnees_rbase, cspf, by = sexef, showNA = "always", percent_digits = 0, percent_pattern ="{n} ({p_col}/{p_row})") crosstable
library(crosstable)
::crosstable(donnees_tidyverse, cspf, by = sexef, showNA = "always",
crosstablepercent_digits = 0, percent_pattern ="{n} ({p_col}/{p_row})")
library(crosstable)
::crosstable(donnees_datatable, cspf, by = sexef, showNA = "always",
crosstablepercent_digits = 0, percent_pattern ="{n} ({p_col}/{p_row})")
21.6.3 Package gmodels
Lien vers la documentation : https://cran.r-project.org/web/packages/gmodels/gmodels.pdf.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(gmodels)
::CrossTable(donnees_rbase$cspf, donnees_rbase$sexef) gmodels
library(gmodels)
%>%
donnees_tidyverse summarise(gmodels::CrossTable(cspf, sexef))
library(gmodels)
::CrossTable(donnees_datatable$cspf, donnees_datatable$sexef) gmodels
21.6.4 Package gtsummary
Lien vers la documentation : https://cran.r-project.org/web/packages/gtsummary/gtsummary.pdf.
/* Sans objet pour SAS */
proc freq data = donnees_sas;
tables Sexe * CSP / missing;format Sexe sexef. CSP $cspf.;
weight poids_sondage;run;
library(gtsummary)
# Pourcentages par case, colonne, ligne
::tbl_cross(data = donnees_rbase, row = cspf, col = sexef, percent = c("cell"), margin = c("column", "row"), missing = c("always"))
gtsummary::tbl_cross(data = donnees_rbase, row = cspf, col = sexef, percent = c("column"), margin = c("column", "row"), missing = c("always"))
gtsummary::tbl_cross(data = donnees_rbase, row = cspf, col = sexef, percent = c("row"), margin = c("column", "row"), missing = c("always")) gtsummary
library(gtsummary)
# Pourcentages par case, colonne, ligne
::tbl_cross(data = donnees_tidyverse, row = cspf, col = sexef, percent = c("cell"), margin = c("column", "row"), missing = c("always"))
gtsummary::tbl_cross(data = donnees_tidyverse, row = cspf, col = sexef, percent = c("column"), margin = c("column", "row"), missing = c("always"))
gtsummary::tbl_cross(data = donnees_tidyverse, row = cspf, col = sexef, percent = c("row"), margin = c("column", "row"), missing = c("always")) gtsummary
library(gtsummary)
# Pourcentages par case, colonne, ligne
::tbl_cross(data = donnees_datatable, row = cspf, col = sexef, percent = c("cell"),
gtsummarymargin = c("column", "row"), missing = c("always"))
::tbl_cross(data = donnees_datatable, row = cspf, col = sexef, percent = c("column"),
gtsummarymargin = c("column", "row"), missing = c("always"))
::tbl_cross(data = donnees_datatable, row = cspf, col = sexef, percent = c("row"),
gtsummarymargin = c("column", "row"), missing = c("always"))
22 Tableaux de statistiques
22.1 Moyenne des notes par CSP (variable en ligne)
22.1.1 Moyenne non pondérée
/* Moyenne de note_contenu et nombre de personnes */
/* 1ère solution */
proc sort data = donnees_sas;by cspf;run;
proc means data = donnees_sas mean n;var note_contenu;class cspf;run;
/* 2e solution */
proc tabulate data = donnees_sas;
var note_contenu;
class cspf;table (cspf all = "Total"), note_contenu * (mean n);
run;
/* 3e solution */
proc sql;
select cspf, mean(note_contenu) as note_contenu_moyenne, count(*) as N
from donnees_sas
group by cspf
order by cspf;
quit;
# Moyenne de note_contenu et nombre de personnes
aggregate(note_contenu ~ cspf, donnees_rbase, function(x) c(Moyenne = mean(x, na.rm = TRUE), Nombre = length(x)))
# Moyenne de note_contenu
# Une seule variable, une seule variable de groupe, une seule fonction
aggregate(note_contenu ~ cspf, donnees_rbase, mean, na.rm = TRUE)
# rowsum (à ne pas confondre avec rowSums) calcule des sommes, et uniquement des sommes
rowsum(donnees_rbase$note_contenu, donnees_rbase$cspf, recorder = TRUE, na.rm = TRUE)
# Pour obtenir une moyenne, on peut écrire
rowsum(donnees_rbase$note_contenu, donnees_rbase$cspf, recorder = TRUE, na.rm = TRUE) / as.vector(table(donnees_rbase$cspf))
# Fonctions tapply et by
tapply(donnees_rbase$note_contenu, donnees_rbase$cspf, mean, na.rm = TRUE)
with(donnees_rbase, tapply(note_contenu, cspf, mean, na.rm = TRUE))
tapply(donnees_rbase$note_contenu, donnees_rbase$cspf, mean, na.rm = TRUE)
by(donnees_rbase$note_contenu, donnees_rbase$cspf, mean, na.rm = TRUE)
# Découpage avec la fonction split (très pratique en R base !)
sapply(split(donnees_rbase, donnees_rbase$cspf), function(x) mean(x$note_contenu, na.rm = TRUE))
# Moyenne de note_contenu et nombre de personnes
%>%
donnees_tidyverse group_by(cspf) %>%
summarise(Nombre = n(), Moyenne = mean(note_contenu, na.rm = TRUE))
# Ou alors :
summarise(Nombre = n(), Moyenne = mean(note_contenu, na.rm = TRUE), .by = cspf)
# Moyenne de note_contenu
# Une seule variable, une seule variable de groupe, une seule fonction
%>%
donnees_tidyverse group_by(cspf) %>%
summarise(Moyenne = mean(note_contenu, na.rm = TRUE))
%>%
donnees_tidyverse summarise(Moyenne = mean(note_contenu, na.rm = TRUE), .by = cspf)
# Moyenne de note_contenu et nombre de personnes
note_contenu_moyenne = mean(note_contenu, na.rm = TRUE), N = .N), by = cspf]
donnees_datatable[, .(note_contenu_moyenne = mean(note_contenu, na.rm = TRUE), N = .N), keyby = "cspf"]
donnees_datatable[, .(
# Variables définies à part
<- "note_contenu"
varNotes <- "cspf"
var_groupe # À FAIRE : les deux variables sont empilées, pourquoi ??
lapply(.SD, function(x) c(moyenne = mean(x, na.rm = TRUE), n = length(x))),
donnees_datatable[, = var_groupe,
by = varNotes] .SDcols
22.1.2 Moyenne pondérée
/* Moyenne de note_contenu et nombre de personnes */
proc sort data = donnees_sas;by cspf;run;
proc means data = donnees_sas mean n;
var note_contenu;class cspf;
weight poids_sondage;run;
/* Autre possibilité */
proc tabulate data = donnees_sas;
var note_contenu;
class cspf;
weight poids_sondage;table (cspf all = "Total"), note_contenu * (mean n);
run;
# Avec la pondération : tapply ne fonctionne pas, il faut découper la base en facteurs avec split
sapply(split(donnees_rbase, donnees_rbase$cspf), function(x) weighted.mean(x$note_contenu, x$poids_sondage, na.rm = TRUE))
# À FAIRE : autre solution ?
# Avec la pondération
%>%
donnees_tidyverse group_by(cspf) %>%
summarise(Moyenne = weighted.mean(note_contenu, poids_sondage, na.rm = TRUE))
%>%
donnees_tidyverse summarise(Moyenne = weighted.mean(note_contenu, poids_sondage, na.rm = TRUE), .by = cspf)
# Avec la pondération
<- "note_contenu"
varNotes <- "cspf"
var_groupe lapply(.SD, function(x) weighted.mean(x, poids_sondage, na.rm = TRUE)),
donnees_datatable[, = var_groupe,
keyby = varNotes] .SDcols
22.2 Moyenne des notes par CSP et Sexe (variables en ligne)
%let var_notes = note_contenu note_formateur note_moyens note_accompagnement note_materiel;
%let var_groupe = cspf sexef;
proc sort data = donnees_sas;by &var_groupe.;run;
proc means data = donnees_sas mean n;
&var_groupe.;
class var &var_notes.;
output out = Resultat;
run;
/* Autre solution */
%macro sel;
%global select;
%local i j;
%let select = ;
%do i = 1 %to %sysfunc(countw(&var_notes.));
%let j = %scan(&var_notes., &i., %str( ));
%let select = &select. mean(&j) as &j._moyenne,;
%end;
%mend sel;
%sel;
%let group = %sysfunc(tranwrd(&var_groupe., %str( ), %str(, )));
proc sql;
select &group., &select. count(*) as N
from donnees_sas
group by &group.
order by &group.;
quit;
# Plusieurs solutions avec aggregate (plutôt lent)
aggregate(note_contenu ~ cspf + sexef, donnees_rbase, function(x) c(mean = mean(x), n = length(x)))
aggregate(cbind(note_contenu, note_materiel) ~ cspf + sexef, donnees_rbase, function(x) c(moyenne = mean(x, na.rm = TRUE), n = length(x)))
# Via les formules
<- c("note_contenu")
variable <- c("cspf", "sexef")
varGroupement <- as.formula(paste(variable, paste(varGroupement, collapse = " + "), sep = " ~ "))
formule aggregate(formule, donnees_rbase, function(x) c(moyenne = mean(x, na.rm = TRUE), n = length(x)))
# Avec by
by(donnees_rbase[, variable], donnees_rbase[, varGroupement], function(x) c(mean = mean(x, na.rm = TRUE), n = length(x)))
# Avec rowsum (à ne pas confondre avec rowSums)
# Somme
rowsum(donnees_rbase[, variable], interaction(donnees_rbase[, varGroupement], sep = "_", lex.order = TRUE))
# Moyenne
rowsum(donnees_rbase[, variable], interaction(donnees_rbase[, varGroupement], sep = "_")) / as.vector(table(interaction(donnees_rbase[, varGroupement])))
%>%
donnees_tidyverse group_by(cspf, sexef) %>%
summarise(Moyenne = mean(note_contenu, na.rm = TRUE), n = n())
# Autre solution : l'ordre des modalités est modifié
%>%
donnees_tidyverse summarise(Moyenne = mean(note_contenu, na.rm = TRUE), n = n(), .by = c(cspf, sexef))
note_contenu_moyenne = mean(note_contenu, na.rm = TRUE), N = .N), keyby = c("cspf", "sexef")]
donnees_datatable[, .(
# Autre solution
::dcast(donnees_datatable, cspf + sexef ~ ., value.var = "note_contenu", fun.aggregate = mean, na.rm = TRUE)
data.table
# Variables définies à part
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- c("cspf", "sexef")
var_groupe # À FAIRE : les deux variables sont empilées, pourquoi ??
lapply(.SD, function(x) list(moyenne = mean(x, na.rm = TRUE), n = length(x))),
donnees_datatable[, = var_groupe,
keyby = varNotes]
.SDcols
# Nombre de femmes par CSP
# Il y a un recycling de gender = "M", utile de le mentionner
Femmes = sum(sexef == "Femme", na.rm = TRUE), Hommes = sum(sexef == "Homme", na.rm = TRUE)), by = .(cspf)]
donnees_datatable[, .(
# À FAIRE :
# Exemple avec les variables dans .SDcols
# data.table::setDT(DF)[, lapply(.SD, mean, na.rm = TRUE), .SDcols = c("x", "y"), by = list(g, h)]
# D'autres variations (par exemple, c(x, y) ou list("x", "y") ne fonctionnent pas !)
22.3 Tableaux croisés à 2 variables de groupement
proc tabulate data = donnees_sas;
class cspf sexef;var note_contenu;
table (cspf all = "Ensemble"), sexef * (note_contenu) * mean;
run;
# Tableau croisé Cspf par Sexef
<- c("cspf", "sexef")
varGroupement <- c("note_contenu")
variable
# Solution avec tapply
tapply(donnees_rbase[, variable], donnees_rbase[varGroupement], function(x) moyenne = mean(x, na.rm = TRUE))
# Solution avec xtabs
xtabs(note_contenu ~ cspf + sexef, aggregate(note_contenu ~ cspf + sexef, data = donnees_rbase, FUN = mean, na.rm = TRUE))
# Ou, sous forme de formule
<- as.formula(paste(variable, paste(varGroupement, collapse = " + "), sep = " ~ "))
formule xtabs(formule, aggregate(formule, data = donnees_rbase, FUN = mean, na.rm = TRUE))
# Et si l'on souhaite un dataframe
as.data.frame.matrix(xtabs(formule, aggregate(formule, data = donnees_rbase, FUN = mean, na.rm = TRUE)))
# Solution avec aggregate, en calculant un tableau "long" et en le transformant en "wide"
<- aggregate(note_contenu ~ cspf + sexef, data = donnees_rbase, FUN = mean, na.rm = TRUE)
tableau <- reshape(tableau,
tableau timevar = varGroupement[2],
idvar = varGroupement[1],
direction = "wide")
is.na(tableau)] <- 0 tableau[
# Tableau croisé Cspf par Sexef
<- c("cspf", "sexef")
varGroupement <- c("note_contenu")
variable %>%
donnees_tidyverse group_by(across(all_of(varGroupement))) %>%
summarise(across(all_of(variable), ~ mean(.x, na.rm = TRUE), .names = "Moyenne")) %>%
spread(varGroupement[2], Moyenne)
# Autre solution
%>%
donnees_tidyverse group_by(!!!syms(varGroupement)) %>%
summarise(Moyenne = mean(.data[[variable]], na.rm = TRUE)) %>%
spread(varGroupement[2], Moyenne)
# Tableau croisé Cspf par Sexef
<- c("cspf", "sexef")
varGroupement <- "note_contenu"
variable ::dcast(donnees_datatable, cspf ~ sexef, value.var = "note_contenu", fun.aggregate = mean, na.rm = TRUE)
data.table
# Avec références seulement
::dcast(donnees_datatable, get(varGroupement[1]) ~ get(varGroupement[2]), value.var = variable,
data.tablefun.aggregate = mean, na.rm = TRUE)
# Autre solution, plus indirecte
# À FAIRE : attention, toujours utiliser lapply, même avec une seule variable ! LE DIRE !!!
<- donnees_datatable[, lapply(.SD, mean, na.rm = TRUE), keyby = varGroupement, .SDcols = "note_contenu"]
tab ::dcast(tab, get(varGroupement[1]) ~ get(varGroupement[2]), value.var = variable) data.table
22.4 Tableaux croisés à 3 variables de groupement ou plus (1 variable en ligne, 2 en colonne par exemple)
/* Notes par croisement de CSP (en ligne) et de Sexe x Niveau */
%let notes = note_contenu note_formateur note_moyens note_accompagnement note_materiel;
proc tabulate data = donnees_sas;
class cspf sexef;var ¬es.;
table (cspf all = "Ensemble"), sexef * (¬es.) * mean;
run;
/* Note_contenu par croisement de CSP (en ligne) et de Sexe x Niveau */
proc tabulate data = donnees_sas;
class cspf sexef Niveau;var note_moyenne;
table (cspf all = "Ensemble"), (sexef * Niveau) * (note_moyenne) * mean;
run;
# 1er exemple : CSPF en ligne, et chacune des 5 notes croisées avec le sexe en colonne
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- c("cspf", "sexef")
var_groupe
<- aggregate(donnees_rbase[, varNotes], donnees_rbase[var_groupe], function(x) moyenne = mean(x, na.rm = TRUE))
tableau reshape(tableau,
timevar = var_groupe[2],
idvar = var_groupe[1],
direction = "wide")
# 2e exemple : CSPF en ligne, et croisement Sexe x Qualifié en colonne, note_contenu sommée
# À FAIRE : proposer une fonction ?
<- as.formula("note_contenu ~ cspf + sexef + niveau")
formule <- xtabs(formule, aggregate(formule, data = donnees_rbase, FUN = mean, na.rm = TRUE))
tab <- do.call(paste, c(expand.grid(dimnames(tab)[-1L]), sep = "_"))
nomsCol <- dimnames(tab)[[1L]]
nomsLig # Transformation du tableau de résultats (en format array) vers un format matrix, puis dataframe
# Permet d'exprimer le array (matrice multidimensionnelle) en un tableau à deux dimensions
# On transforme le tableau en matrice ayant en nombre de lignes dim(tab)[1], c'est-à-dire le nombre de lignes du array
# et en nombre de colonnes le reste des variables
<- data.frame(matrix(tab, nrow = dim(tab)[1L]))
tab # Renommage des noms des colonnes de la base
colnames(tab) <- nomsCol
# Renommage des noms des lignes de la base
row.names(tab) <- nomsLig
# On annule les valeurs manquantes
is.na(tab)] <- 0
tab[
tab# À FAIRE : développer autour de cet exemple
# Avec 3 variables
xtabs(cbind(note_contenu, note_materiel) ~ cspf + sexef, donnees_rbase)
# 1er exemple : CSPF en ligne, et chacune des 5 notes croisées avec le sexe en colonne
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- c("cspf", "sexef")
var_groupe %>%
donnees_tidyverse group_by(across(all_of(var_groupe))) %>%
summarise(across(all_of(varNotes), ~ mean(.x, na.rm = TRUE))) %>%
pivot_wider(names_from = sexef,
values_from = all_of(varNotes))
# 2e exemple : CSPF en ligne, et croisement Sexe x Qualifié en colonne, note_contenu sommée
<- c("note_contenu")
varNotes <- c("cspf", "sexef", "niveau")
var_groupe %>%
donnees_tidyverse group_by(across(all_of(var_groupe))) %>%
summarise(across(all_of(varNotes), ~ mean(.x, na.rm = TRUE))) %>%
pivot_wider(names_from = c(sexef, niveau),
values_from = all_of(varNotes),
values_fill = 0)
# 1er exemple : CSPF en ligne, et chacune des 5 notes croisées avec le sexe en colonne
<- c("note_contenu", "note_formateur", "note_moyens", "note_accompagnement", "note_materiel")
varNotes <- c("cspf", "sexef")
var_groupe ::dcast(donnees_datatable, get(varGroupement[1]) ~ get(varGroupement[2]), value.var = varNotes,
data.tablefun.aggregate = mean, na.rm = TRUE)
# 2e exemple : CSPF en ligne, et croisement Sexe x Qualifié en colonne, note_contenu sommée
::dcast(donnees_datatable, cspf ~ sexef + niveau, value.var = "note_contenu",
data.tablefun.aggregate = mean, na.rm = TRUE)
23 Boucles
23.1 Boucles imbriquées
data _null_;call symput('annee', strip(year(today())));run;
/* Ensemble des premiers jours de chaque mois entre 2020 et le 31 décembre de l'année courante */
%macro Boucles_Imbriquees(an_debut, an_fin);
%local i j;
%global liste_mois;
%let liste_mois = ;
%do i = &an_debut. %to &an_fin.;
%do j = 1 %to 12;
%let liste_mois = &liste_mois. %sysfunc(putn(%sysfunc(mdy(&j., 1, &i.)), ddmmyy10.));
%end;
%end;
%mend Boucles_Imbriquees;
%let annee = %sysfunc(year(%sysfunc(today())));
%Boucles_Imbriquees(an_debut = 2020, an_fin = &annee.);%put &liste_mois.;
# Ensemble des premiers jours de chaque mois entre 2020 et l'année courante
<- lubridate::year(Sys.Date())
annee # 1ère solution avec for (lente, à déconseiller !)
<- c()
listeMois for (i in seq(2020, annee)) {
for (j in 1:12) {
<- as.Date(c(listeMois, lubridate::ymd(sprintf("%02d-%02d-01", i, j))), origin = "1970-01-01")
listeMois
}
}
# 2e solution : 2 fonctions lapply imbriquées
<- as.Date(unlist(lapply(seq(2020, annee),
listeMois function(x) lapply(1:12, function(y) lubridate::ymd(sprintf("%02d-%02d-01", x, y))))),
origin = "1970-01-01")
# 3e solution : expand.grid
<- sort(as.Date(apply(expand.grid(seq(2020, annee), 1:12), 1,
listeMois function(x) lubridate::ymd(sprintf("%02d-%02d-01", x[1], x[2]))),
origin = "1970-01-01"))
# 4e solution, la plus simple !
seq.Date(lubridate::ymd(sprintf("%02d-01-01", 2020)), lubridate::ymd(sprintf("%02d-12-01", annee)), by = "month")
# Ensemble des premiers jours de chaque mois entre 2020 et l'année courante
<- lubridate::year(Sys.Date())
annee
# 1ère solution : 2 fonctions map imbriquées
<- purrr::map(seq(2020, annee),
listeMois function(x) purrr::map(1:12,
function(y) lubridate::ymd(sprintf("%02d-%02d-01", x, y)))) %>%
unlist() %>%
as.Date(, origin = "1970-01-01")
# 2e solution : expand_grid
<- tidyr::expand_grid(annee = seq(2020, annee), mois = 1:12) %>%
listeMois apply(1, function(x) lubridate::ymd(sprintf("%02d-%02d-01", x[1], x[2]))) %>%
as.Date(, origin = "1970-01-01") %>%
sort()
# 3e solution, la plus simple
seq.Date(lubridate::ymd(sprintf("%02d-01-01", 2020)), lubridate::ymd(sprintf("%02d-12-01", annee)), by = "month")
# Ensemble des premiers jours de chaque mois entre 2020 et l'année courante
<- lubridate::year(Sys.Date())
annee
# 1ère solution avec for (lente, à déconseiller !)
<- c()
listeMois for (i in seq(2020, annee)) {
for (j in 1:12) {
<- as.Date(c(listeMois, lubridate::ymd(sprintf("%02d-%02d-01", i, j))), origin = "1970-01-01")
listeMois
}
}
# 2e solution : 2 fonctions lapply imbriquées
<- as.Date(unlist(lapply(seq(2020, annee),
listeMois function(x) lapply(1:12, function(y) lubridate::ymd(sprintf("%02d-%02d-01", x, y))))),
origin = "1970-01-01")
# 3e solution : expand.grid
<- sort(as.Date(apply(expand.grid(seq(2020, annee), 1:12), 1,
listeMois function(x) lubridate::ymd(sprintf("%02d-%02d-01", x[1], x[2]))),
origin = "1970-01-01"))
# 4e solution, la plus simple
seq.Date(lubridate::ymd(sprintf("%02d-01-01", 2020)), lubridate::ymd(sprintf("%02d-12-01", annee)), by = "month")
23.2 Boucles imbriquées (second exemple)
/* Itérer sur toutes les années et les trimestres d'une certaine plage */
/* On va afficher les noms base_AAAA_tT_nmax où AAAA désigne les années, T les trimestres, depuis 2020 */
%macro iteration(debut, fin);
%global liste_an;
%let liste_an = ;
%do i = &debut. %to &fin.;
%let liste_an = &liste_an.&i.-;
%end;
%mend iteration;
%iteration(debut = 2020, fin = %sysfunc(year(%sysfunc(today()))));%put &liste_an.;
%let liste_trim = 1 2 3 4;
%let liste_niv = max min;
/* Supposons que nous ayons des noms de fichier suffixés par AXXXX_TY_NZ, avec X l'année, Y le trimestre et
Z max ou min. Par exemple, A2010_T2_NMax */
/* Pour obtenir l'ensemble de ces noms de 2010 à cette année */
%macro noms_fichiers(base = temp);
%global res;
%let res = ;
/* 1ère boucle */
%do j = 1 %to %sysfunc(countw(&liste_an., "-"));
%let y = %scan(&liste_an., &j., "-"); /* année */
/* 2e boucle */
%do i = 1 %to 4;
%let t = %scan(&liste_trim, &i.); /* trimestre */
/* 3e boucle */
%do g = 1 %to 2;
%let n = %scan(&liste_niv., &g.); /* niveau */
%let res = &res. &base._&y._t&t._n&n.;
%end;
%end;
%end;
%mend noms_fichiers;
%noms_fichiers(base = base);%put &res.;
# Itérer sur toutes les années et les trimestres d'une certaine plage
# on va afficher les noms base_AAAA_tT_nmax où AAAA désigne les années, T les trimestres, depuis 2020
<- 2020
debut <- lubridate::year(Sys.Date())
fin <- unlist(lapply(debut:fin, function(x) lapply(c("max", "min"), function(y) sprintf("base_%4d_t%d_n%s", x, 1:4, y)))) res
# Itérer sur toutes les années et les trimestres d'une certaine plage
# on va afficher les noms base_AAAA_tT_nmax où AAAA désigne les années, T les trimestres, depuis 2020
<- 2020
debut <- lubridate::year(Sys.Date())
fin <- purrr::map(debut:fin,
listeMois function(x) purrr::map(c("max", "min"),
function(y) sprintf("base_%4d_t%d_n%s", x, 1:4, y))) %>%
unlist()
# Itérer sur toutes les années et les trimestres d'une certaine plage
# on va afficher les noms base_AAAA_tT_nmax où AAAA désigne les années, T les trimestres, depuis 2020
<- 2020
debut <- lubridate::year(Sys.Date())
fin <- unlist(lapply(debut:fin, function(x) lapply(c("max", "min"), function(y) sprintf("base_%4d_t%d_n%s", x, 1:4, y)))) res
23.3 Boucles for
/* On va créer une base par année d'entrée */
proc sql noprint;
select year(min(date_entree)), year(max(date_entree)) into :an_min, :an_max
from donnees_sas;
quit;
%macro Base_par_mois(debut, fin);
/* %local impose que an n'est pas de signification hors de la macro */
%local an;
/* %global impose que nom_bases peut être utilisé en dehors de la macro */
%global nom_bases;
/* On initalise la création de la macri-variable nom_bases */
%let nom_bases = ;
/* On itère entre &debut. et &fin. */
%do an = &debut. %to &fin.;
data Entree_&an.;
set donnees_sas;
if year(date_entree) = &an.;
run;
/* On ajoute à la macro-variable le nom de la base */
%let nom_bases = &nom_bases. Entree_&an.;
%end;
%mend Base_par_mois;
%Base_par_mois(debut = &an_min., fin = &an_max.);%put &nom_bases.;
/* On va désormais empiler toutes les bases (concaténation par colonne) */
/* L'instruction set utilisée de cette façon permet cet empilement */
data concatene;
set &nom_bases.;
run;
# On va créer une base par année d'entrée
<- min(lubridate::year(donnees_rbase$date_entree), na.rm = TRUE)
anMin <- max(lubridate::year(donnees_rbase$date_entree), na.rm = TRUE)
anMax
for (i in anMin:anMax) {
# assign permet de faire passer une chaîne de caractères en variable R
assign(paste("entree", i, sep = "_"), donnees_rbase[which(lubridate::year(donnees_rbase$date_entree) == i), ])
}
# On va désormais empiler toutes les bases (concaténation par colonne)
# do.call applique la fonction rbind à l'ensemble des bases issues du lapply
# get permet de faire le chemin inverse de assign
<- do.call(rbind, lapply(paste("entree", anMin:anMax, sep = "_"), get)) concatene
# À FAIRE : problème pour les entrées où la date est manquante
# On va créer une base par année d'entrée
<- donnees_tidyverse %>% pull(date_entree) %>% lubridate::year() %>% min(na.rm = TRUE)
anMin <- donnees_tidyverse %>% pull(date_entree) %>% lubridate::year() %>% max(na.rm = TRUE)
anMax
for (i in anMin:anMax) {
# assign permet de faire passer une chaîne de caractères en variable R
assign(paste("entree", i, sep = "_"),
%>% filter(lubridate::year(date_entree) == as.name(i)))
donnees_tidyverse
}
# On va désormais empiler toutes les bases (concaténation par colonne)
# purrr::reduce applique la fonction bind_rows à l'ensemble des bases issues du purrr::map
# get permet de faire le chemin inverse de assign
<- purrr::map(paste("entree", anMin:anMax, sep = "_"), get) %>%
concatene ::reduce(bind_rows) purrr
# On va créer une base par année d'entrée
<- min(lubridate::year(donnees_datatable$date_entree), na.rm = TRUE)
anMin <- max(lubridate::year(donnees_datatable$date_entree), na.rm = TRUE)
anMax
for (i in anMin:anMax) {
# assign permet de faire passer une chaîne de caractères en variable R
assign(paste("entree", i, sep = "_"), donnees_datatable[lubridate::year(donnees_datatable$date_entree) == i, ])
}
# On va désormais empiler toutes les bases (concaténation par colonne)
# do.call applique la fonction rbind à l'ensemble des bases issues du lapply
# get permet de faire le chemin inverse de assign
<- rbindlist(lapply(paste("entree", anMin:anMax, sep = "_"), get)) concatene
23.4 Boucles for (second exemple)
/* On recherche toutes les valeurs de CSP différentes et on les met dans une variable.
On appelle la proc SQL :
- utilisation du quit et non run à la fin
- on récupère toutes les valeurs différentes de CSP, séparés par un espace (separated by)
- s'il y a un espace dans les noms, on le remplace par _
- on les met dans la macro-variable liste_csp
- on trier la liste par valeur de CSP */
/* On crée une variable de CSP formaté sans les accents et les espaces */
data donnees_sas;
set donnees_sas;
/* SAS ne pourra pas créer des bases de données avec des noms accentués */
/* On supprime dans le nom les lettres accentués. On le fait avec la fonction Translate */
tranwrd(strip(CSPF), " ", "_");
CSPF2 = translate(CSPF2, "eeeeaacio", "éèêëàâçîô");
CSPF2 = run;
/* Boucles et macros en SAS */
/* Les boucles ne peuvent être utilisées que dans le cadre de macros */
/* Ouverture de la macro */
%macro Boucles(base = donnees_sas, var = CSPF2);
/* Les modalités de la variable */
proc sql noprint;select distinct &var. into :liste separated by " " from &base. order by &var.;quit;
/* On affiche la liste de ces modalités */
%put &liste.;
/* %let permet à SAS d'affecter une valeur à une variable en dehors d'une manipulation de base de données */
/* %sysfunc indique à SAS qu'il doit utiliser la fonction countw dans le cadre d'une macro (pas important) */
/* countw est une fonction qui compte le nombre de mots (séparés par un espace) d'une chaîne de caractères */
/* => on compte le nombre de CSP différentes */
%let nb = %sysfunc(countw(&liste.));
%put Nombre de modalités différentes : &nb.;
/* On itère pour chaque CSP différente ... */
%do i = 1 %to &nb.;
/* %scan : donne le i-ème mot de &liste. (les mots sont séparés par un espace) */
/* => on récupère donc la CSP numéro i */
%let j = %scan(&liste., &i.);
%put Variable : &j.;
/* On crée une base avec seulement les individus de la CSP correspondante */
data &var.;set donnees_sas;if &var. = "&j.";run;
%end;
/* Fermeture de la macro */
%mend Boucles;
/* Lancement de la macro */
%Boucles(base = donnees_sas, var = CSPF2);
# Base par CSP
for (i in unique(donnees_rbase$cspf)) {
# Met en minuscule et enlève les accents
<- tolower(chartr("éèêëàâçîô", "eeeeaacio", i))
nomBase # assign permet de faire passer une chaîne de caractères en variable R
assign(nomBase, donnees_rbase[which(donnees_rbase$cspf == i), ])
}
# Base par CSP
for (i in donnees_tidyverse %>% distinct(cspf) %>% pull()) {
# Met en minuscule et enlève les accents
<- chartr("éèêëàâçîô", "eeeeaacio", i) %>% tolower()
nomBase # assign permet de faire passer une chaîne de caractères en variable R
assign(nomBase, donnees_tidyverse %>%
filter(cspf == as.name(i)))
}
# Créer une base pour chaque individu d'une certaine CSP
for (i in unique(donnees_datatable$cspf)) {
# Met en minuscule et enlève les accents
<- tolower(chartr("éèêëàâçîô", "eeeeaacio", i))
nomBase # assign permet de faire passer une chaîne de caractères en variable R
assign(nomBase, donnees_datatable[donnees_datatable$cspf == i, ])
}
24 Fonctions SAS
et R
utiles
24.1 Mesurer la durée d’exécution d’un programme
%let temps_debut = %sysfunc(datetime());
proc sort data = donnees_sas;by identifiant date_entree;run;
%let temps_fin = %sysfunc(datetime());
%let duree = %sysevalf((&temps_fin. - &temps_debut.) / 60);
%put Durée exécution : &duree minutes;
system.time(donnees_rbase <- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ])
# Autre possibilité
<- Sys.time()
debut <- donnees_rbase[order(donnees_rbase$identifiant, donnees_rbase$date_entree, na.last = FALSE), ]
donnees_rbase <- Sys.time()
fin sprintf("Temps d'exécution : %s secondes !", fin - debut)
system.time(donnees_tidyverse <- donnees_tidyverse %>%
arrange(identifiant, date_entree))
# Autre possibilité
<- Sys.time()
debut <- donnees_tidyverse %>%
donnees_tidyverse arrange(identifiant, date_entree)
<- Sys.time()
fin sprintf("Temps d'exécution : %s secondes !", fin - debut)
system.time(setorder(donnees_datatable, "identifiant", "date_entree", na.last = FALSE))
# Autres possibilités
<- Sys.time()
debut setorder(donnees_datatable, "identifiant", "date_entree", na.last = FALSE)
<- Sys.time()
fin sprintf("Temps d'exécution : %s secondes !", fin - debut)
<- proc.time()
started.at setorder(donnees_datatable, "identifiant", "date_entree", na.last = FALSE)
timetaken(started.at)
24.2 Exécuter le code d’un autre fichier
/* include("chemin") */
# encoding permet de gérer l'encodage des caractères accentués
# echo = TRUE affiche le script dans la console
# max.deparse.length permet de s'assurer qu'un texte long est bien visible
# source("chemin", encoding = "utf-8", echo = TRUE, max.deparse.length = 1e3)
# encoding permet de gérer l'encodage des caractères accentués
# echo = TRUE affiche le script dans la console
# max.deparse.length permet de s'assurer qu'un texte long est bien visible
# source("chemin", encoding = "utf-8", echo = TRUE, max.deparse.length = 1e3)
# encoding permet de gérer l'encodage des caractères accentués
# echo = TRUE affiche le script dans la console
# max.deparse.length permet de s'assurer qu'un texte long est bien visible
# source("chemin", encoding = "utf-8", echo = TRUE, max.deparse.length = 1e3)
24.3 Nombre de lignes affectées par un changement
/* Ne semble pas exister nativement */
# Ne semble pas exister nativement
# Ne semble pas exister nativement
:= tolower(sexef)]
donnees_datatable[, sexef2 sprintf("Nombre de lignes modifiées : %d", .Last.updated)
:= NULL] donnees_datatable[, sexef2
25 Créer ses propres fonctions
25.1 Documentation
Pour en savoir plus sur l’écriture de fonctions en R
:
Pour en savoir plus sur l’écriture de fonctions en tidyverse
:
https://dplyr.tidyverse.org/articles/programming.html
https://brad-cannell.github.io/r_notes/tidy-evaluation.html.
Pour en savoir plus sur l’écriture de fonctions en data.table
:
https://cran.r-project.org/web/packages/data.table/vignettes/datatable-programming.html
25.2 Sélection de données
On sélectionne des données (dans cet exemple, les femmes) dans une nouvelle base (dans cet exemple, extrait), via une fonction (une macro en SAS
).
%macro Selection (BaseInitiale, BaseFinale, condition);
data &BaseFinale.;
set &BaseInitiale. (&condition.);
run;
%mend Selection;
%Selection(BaseInitiale = donnees_sas, BaseFinale = extrait); %Selection(BaseInitiale = donnees_sas, BaseFinale = extrait, condition = where = (sexe = 2));
<- function(baseInitiale = donnees_rbase, condition) {
Selection return(eval(substitute(subset(baseInitiale, condition))))
}<- Selection(baseInitiale = donnees_rbase)
extrait <- Selection(baseInitiale = donnees_rbase, condition = sexe == "2") extrait
<- function(baseInitiale = donnees_tidyverse, condition = TRUE) {
Selection %>%
baseInitiale filter({{ condition }}) %>%
return()
}<- Selection(baseInitiale = donnees_tidyverse)
extrait <- Selection(baseInitiale = donnees_tidyverse, condition = sexe == "2") extrait
<- function(baseInitiale = donnees_datatable, condition) {
Selection = list(condition = condition)]
baseInitiale[condition, , env
}<- Selection(baseInitiale = donnees_datatable)
extrait <- Selection(baseInitiale = donnees_datatable, condition = quote(sexe == "2")) extrait
25.3 Moyenne d’un certain nombre de variables
%macro Moyenne (BaseInitiale, variables);
proc means data = &BaseInitiale. mean;
var &variables;
run;
%mend Moyenne;
%Moyenne(BaseInitiale = donnees_sas, variables = note_contenu note_formateur);
<- function(baseInitiale = donnees_rbase, variables) {
Moyenne <- unlist(lapply(baseInitiale[, variables], mean, na.rm = TRUE))
moyennes names(moyennes) <- paste("moyenne", names(moyennes), sep = "_")
return(moyennes)
}Moyenne(baseInitiale = donnees_rbase, variables = c("note_contenu", "note_formateur"))
<- function(baseInitiale = donnees_tidyverse, variables) {
Moyenne %>%
baseInitiale summarise(across({{ variables }}, function(x) mean(x,, na.rm = TRUE), .names = "Moyenne_{.col}")) %>%
return()
}Moyenne(baseInitiale = donnees_tidyverse, variables = c("note_contenu", "note_formateur"))
<- function(baseInitiale = donnees_datatable, variables) {
Moyenne <- baseInitiale[, lapply(.SD, mean, na.rm = TRUE), .SDcols = variables]
moyennes setnames(moyennes, paste("Moyenne", variables, sep = "_"))
return(moyennes)
}Moyenne(baseInitiale = donnees_datatable, variables = c("note_contenu", "note_formateur"))
25.4 Fonction calculant un indicateur statistique
Cet exemple de fonction propose de calculer un indicateur statistique au choix (par exemple, moyenne, médiane, maximum, etc.) sur un certain nombre de variables numériques (au choix) d’une certaine base de données (au choix) avec éventuellement une sélection de lignes, et des arguments supplémentaires (notamment na.rm = TRUE) via le paramètre …
%macro CalculMoyenne (baseDonnees, variables, statistique, condition);
proc means data = &baseDonnees. &statistique.;
var &variables.;
run;
%mend CalculMoyenne;
%CalculMoyenne(baseDonnees = donnees_sas, variables = note_formateur note_contenu);
%CalculMoyenne(baseDonnees = donnees_sas, variables = note_formateur note_contenu, statistique = mean sum median); %CalculMoyenne(baseDonnees = donnees_sas, variables = note_formateur note_contenu, statistique = mean sum, condition = where sexef = "Femme");
<- function(baseDonnees, variable, statistique = "mean", ..., selection = TRUE) {
CalculMoyenne <- eval(substitute(subset(baseDonnees, selection)))
baseDonnees <- lapply(baseDonnees[, variable], get(statistique), ...)
moyenne names(moyenne) <- paste(names(moyenne), statistique, sep = "_")
<- data.frame(moyenne)
moyenne return(moyenne)
}
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"))
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"), statistique = "median", na.rm = TRUE)
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"), "mean", na.rm = TRUE, selection = sexef == "Femme")
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"), "quantile", na.rm = TRUE, probs = seq(0, 1, 0.1), selection = sexef == "Femme")
<- function(baseDonnees, variable, statistique = "mean", ..., selection = TRUE) {
CalculMoyenne <- baseDonnees %>%
moyenne filter({{ selection }}) %>%
summarise(across(variable, ~ get(statistique)(.x, ...), .names = "{.col}_{ {{ statistique }} }"))
return(moyenne)
}
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"))
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"), statistique = "median", na.rm = TRUE)
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"), "mean", na.rm = TRUE, selection = sexef == "Femme")
CalculMoyenne(donnees_rbase, c("note_formateur", "note_contenu"), "quantile", na.rm = TRUE, probs = seq(0, 1, 0.1), selection = sexef == "Femme")
<- function(baseDonnees, variable, statistique = "mean", ..., selection = TRUE) {
CalculMoyenne <- baseDonnees[selection, lapply(.SD, statistique, ...), .SDcols = variable, env = list(statistique = statistique, selection = selection)]
moyenne setnames(moyenne, paste(names(moyenne), statistique, sep = "_"))
return(moyenne)
}
CalculMoyenne(donnees_datatable, c("note_formateur", "note_contenu"))
CalculMoyenne(donnees_datatable, c("note_formateur", "note_contenu"), statistique = "median", na.rm = TRUE)
CalculMoyenne(donnees_datatable, c("note_formateur", "note_contenu"), "mean", na.rm = TRUE, selection = quote(sexef == "Femme"))
CalculMoyenne(donnees_datatable, c("note_formateur", "note_contenu"), "quantile", na.rm = TRUE, probs = seq(0, 1, 0.1), selection = quote(sexef == "Femme"))
Autres exemples de fonctions possibles : statistiques par groupes (proc tabulate), proc freq, ajout dans la base d’indicatrices de présence en stock à la fin du mois (%local).
26 Débogage
26.1 Outils d’aide au débogage
options symbolgen mprint mlogic;
%macro Debogage;
%local phrase i j;
%let phrase = Voici une phrase;
%do i = 1 %to %sysfunc(countw(&phrase.));
%let j = %scan(&phrase., &i.);
%put Mot n°&i. = &j.;
%end;
%mend Debogage;
%Debogage;options nosymbolgen nomprint nomlogic;
#phrase <- c("voici", "une", "phrase")
#options(error=recover)
#for (i in phrase) print(k)
#options(error=NULL)
# À FAIRE : autres outils
#traceback()
#browser()
# À FAIRE : creuser
#phrase <- c("voici", "une", "phrase")
#options(error=recover)
#for (i in phrase) print(k)
#options(error=NULL)
# À FAIRE : autres outils
#traceback()
#browser()
#phrase <- c("voici", "une", "phrase")
#options(error=recover)
#for (i in phrase) print(k)
#options(error=NULL)
# À FAIRE : autres outils
#traceback()
#browser()
27 Points de vigilance en SAS
27.1 Emploi des guillemets et doubles guillemets
Une macro exprimée sous format caractère doit être entourée de ““, et non ’’.
/* Quelques points de vigilance en SAS (à ne connaître que si on est amené à modifier le programme SAS, pas utiles sinon) */
/* Double guillemets pour les macro-variables */
%let a = Bonjour;
%put '&a.'; /* Incorrect */
%put "&a."; /* Correct */
# Sans objet en R
# Sans objet en R
# Sans objet en R
# Sans objet en R
27.2 Macro-variable définie avec un statut global avant son appel dans le cadre d’un statut local
%macro test;
%let reponse = oui;
%mend test;
%test;
/* 1. Erreur car &reponse. n'est défini que dans le cas d'un environnement local */
%put &reponse.;
/* 2. Défini auparavant dans un environnement global, elle change de valeur à l'appel de la fonction */
%let reponse = non;
%put Reponse : &reponse.;
%test;%put Reponse après la macro : &reponse.;
/* 3. Problème corrigé, en imposant la variable à local dans la macro */
%macro test2;
%local reponse;
%let reponse = oui;
%mend test2;
%let reponse = non;
%put Reponse : &reponse.;
%test2;%put Reponse après la macro : &reponse.;
# Sans objet en R
# Sans objet en R
# Sans objet en R
# Sans objet en R
28 Fin du programme
28.1 Taille des objets en mémoire
/* Taille d'une base de données */
proc sql;
select libname, memname, filesize format = sizekmg., filesize format = sizek.
from Dictionary.Tables
where libname = "WORK" and memname = upcase("donnees_sas") and memtype = "DATA";
quit;
# Taille, en mémoire, d'une base (en Mb)
format(object.size(donnees_rbase), nsmall = 3, digits = 2, unit = "Mb")
# Taille des objets en mémoire, en Gb
sort( sapply(ls(), function(x) object.size(get(x)) ), decreasing = TRUE ) / 10**9
# Taille, en mémoire, d'une base (en Mb)
%>%
donnees_tidyverse object.size() %>%
format(nsmall = 3, digits = 2, unit = "Mb")
# Taille des objets en mémoire, en Gb
sort( sapply(ls(), function(x) object.size(get(x)) ), decreasing = TRUE ) / 10**9
# Liste des bases de données en mémoire
::tables()
data.table
# Taille, en mémoire, d'une base (en Mb)
format(object.size(donnees_datatable), nsmall = 3, digits = 2, unit = "Mb")
# Taille des objets en mémoire, en Gb
sort( sapply(ls(), function(x) object.size(get(x)) ), decreasing = TRUE ) / 10**9
28.2 Supprimer des bases
/* Supprimer une base */
proc datasets lib = work nolist;delete donnees_sas;run;
/* Supprimer toutes les bases dans la work */
proc datasets lib = work nolist kill;run;
# Supprimer une base
#rm(donnees_rbase)
# Supprimer toutes les bases et tous les objets de la mémoire vive
#rm(list = ls())
# Supprimer une base
#rm(donnees_tidyverse)
# Supprimer toutes les bases et tous les objets de la mémoire vive
#rm(list = ls())
# Supprimer une base
#rm(donnees_datatable)
# Supprimer toutes les bases et tous les objets de la mémoire vive
#rm(list = ls())