De SQL à R avec dplyr

Guide de survie à destination d’un DIM qui veut migrer avant la foule.

Pourquoi R quand on connaît déjà SAS ou SQL ?

Avec la révolution des bases nationales qui passent en 2026 de SAS à TERADATA avec interfaçage en R, on peut dire que cela bouscule des routines bien établies chez certains médecins DIM qui se sentent un peu perdus.

Je vous propose quelques articles visant à accompagner en douceur la migration. La masse d’informations peut faire peur mais voyez ces articles plutôt comme des pense-bêtes que vous reviendrez consulter aussi souvent que nécessaire.

Quelle saveur de R ?

Tout d’abord, nous n’allons pas faire du R de base. Nous sommes au premier quart du 21ème siècle que diantre, nous allons donc utiliser dplyr (et nous verrons dans un deuxième temps dbplyr).

dplyr

Dplyr est une librairie qui vise à simplifier l’écriture de retraitements. Elle est faite d’abord pour traiter des données présentes dans R sous forme de data.frame ou équivalent, c’est à dire directement en mémoire (un peu comme lorsque vous faites un « export excel » d’une requête SQL depuis votre SGBD favori puis que vous ouvrez le fichier généré dans Excel pour le retraiter). Et c’est dbplyr qui sert de couche d’interrogation préalable des bases de données (c’est pour cela que nous le verrons ensuite) si vous n’avez pas les données déjà extraites.

Ces librairies font partie du tidyverse et à ce titre font un usage intensif du « pipe » qui permet de chainer les fonctions et rend leur enchainement plus lisible. Pour rappel, cela donne ça :

> f1(f2(f3(x)))
# devient avec le pipe intégré à dplyr :
> x %>% f3 %>% f2 %>% f1
# ou avec le pipe désormais inclus directement dans R 
# composé d'une barre verticale | et d'un "plus grand que" > :
> x |> f3() |>f2() |>f1()
R

Et on peut facilement lire le code en remplaçant mentalement le symbole par « puis » :

Je prends x puis j’applique f3 puis j’applique f2 puis j’applique f1 (et ça me donne un résultat)

A titre personnel, je préfère le premier qui je trouve est plus facile à lire et à taper (entre autre il n’y a pas besoin des parenthèses après les noms de fonction). Il s’accompagne par contre d’une perte infinitésimale de temps de traitement1.

… Et paradoxalement plus compliqué, quelle saveur de SQL ?

car SQL n’est pas un langage homogène entre les différents moteurs de SGBD. Je ne peux donc être exhaustif sur les traductions/transcriptions de code, il faudra donc plus se référer à l’esprit des exemples qu’à leur lettre.

Une des différences qui va nous impacter est la spécification obligatoire des tables dans les listes de champs :

/* Certains SGBD acceptent : */
SELECT champ1, champ2, champ3
FROM table1;

/* Tandis que d'autres n'acceptent que : */
SELECT table1.champ1, table1.champ2, table1.champ3
FROM table1;

/* ou d'autres privilégient le "!" : */
SELECT table1!champ1, table1!champ2, table1!champ3
FROM table1;
SQL

Pour la facilité de lecture autant que possible j’utiliserai la syntaxe 1 (implicite) mais sachez que la syntaxe 2-3 (explicite) est plus fréquente.

Le parallèle SQL/R

Le typage des données

R (comme de leur côté la plupart des SGBD donc les jeux de données qu’on en reçoit) n’accepte pas les types de données mixées dans les champs (donc un champ est un texte, un nombre mais ne peut pas contenir les 2 sauf à transformer les nombres en texte et ne plus pouvoir faire d’opération mathématique). Par ailleurs, les bases de données imposent de types de champs qui sont assez simples et courants (INTEGER, FLOAT, (VAR)CHAR/TEXT,DATE…), et dont le pendant existe en R (integer, number/double, character, Date/POSIXct/POSIXlt,…).

Le transtypage est donc rarement nécessaire. Mais si vraiment vous le vouliez la fonction SQL CAST(... AS ...) ou CONVERT(... , ...) le permettrait selon les moteurs. En R, on découvre notre premier faux-ami, la fonction s’appelle as.<type>() (ou <type> correspond au type de destination) qui n’a rien à voir avec le AS de SQL :

# convertir un vecteur numérique en vecteur de chaines :
valeur <- as.character(valeur)

# convertir une vecteur de chaines en dates depuis 
# le format habituel de l'ATIH :
dates <- as.Date(dates, format = "%d%m%Y")
R

On peut aussi (pour les types de base, mais pas toujours pour d’autres) écrire une version plus ressemblante au SQL avec :

valeur <- as(valeur, "character")
R

mais franchement, personne ne l’utilise si il n’y est pas obligé. Elle est plus réservée pour de la programmation pure et dure.

Le nommage des champs et des variables

En R comme en SQL il est possible d’avoir des noms de champs « bizarres ». Dans un cas comme dans l’autre, il suffit de mettre le motif entre backticks ou backquotes ( ` ` – AltGr+7 sur un clavier PC, sur la touche £ sur un clavier Mac) :

`variable(1234)` # la variable dont le nom est "variable(1234)" 
# et non l'exécution de la fonction variable() avec le paramètre 1234

# SQL : SELECT `champ bizarre` FROM rss;

# R :
rss[,`champ bizarre`]
# aussi valable dans dplyr que l'on va utiliser dans le reste de l'article
rss %>% select(`champ bizarre`)
R

Une autre des particularités à, par contre, ne surtout pas oublier : SQL n’est pas sensible à la casse alors que R oui ! Donc attention aux erreurs d’étourderies.

Je vous rappelle l’usage habituel que j’essaye de tenir pour faciliter la lecture du code.

Les variables d’un programme sont en minuscules

Les noms de champs/colonnes originaux sont en majuscules,

Les nouveaux champs créés sont en minuscules sauf si ils sont intrinsèques aux données :

  • Par exemple si je rajoute une colonne contenant l’âge d’un patient à partir de la date d’entrée et de sa date de naissance, alors je le mets en majuscules : AGE_SORTIE
  • Par contre, si c’est l’âge par rapport à la date actuelle (qui est une donnée extrinsèque), je le mets en minuscule : age_actuel

J’évite au maximum les backticks et donc évite les espaces et autres symboles syntaxiquement significatifs en R dans les noms de variables et de champs pour la lisibilité.

Cela donne :

var_de_test <- "1234567A"
substitution <- "BLA"

(...)

table1 <- table0 %>%
 filter(CHAMP0 == var_de_test) %>%
 mutate(nouveau = paste(substitution, substr(CHAMP0,1,2))
 
 # var_de_test, substitution, table0 et table1 sont des variables R.
 # CHAMP0 est un champ de table0
 # nouveau est un nouveau champ de table0
R

Ainsi je vois tout de suite d’où vient une donnée. Cependant, c’est une question de gouts et de couleurs. L’important comme dit plus haut c’est de bien respecter la casse.

Un vrai piège, NULL.

La plupart des SGBD acceptent ou produisent la valeur particulière NULL (représentant l’absence de données par contraste par exemple avec 0 ou la chaine vide) et celle-ci n’est PAS la même chose que le type R NULL. L’équivalent en R est NA. Soyez vigilant ! Et vous pouvez toujours vous reporter à l’article Le bon, la brute et le truand : NA, NULL et «  » pour avoir plus de précisions.

Pour rappel, on teste avec la fonction is.na(...) qui renvoit TRUE si c’est le cas et FALSE sinon et remplace donc la syntaxe SQL IS [NOT] NULL.

#SQL : x IS NULL
#R :
is.na(x)

#SQL : x IS NOT NULL
#R :
!is.na(x)
R

Il y a d’autres différences minimes et négligeables pour notre usage.

Le parallèle SQL/dplyr

Requête vs Pipeline

Le principe de constitution de la démarche est différent mais la finalité est la même. C’est simple au lieu de penser « requête » (SQL), pensez en « pipeline » (R/dplyr).

En SQL, vous écrivez (version courte) :

SELECT ...
FROM ...
WHERE ...
GROUP BY ...;
SQL

C’est à dire une requête finie. Si vous vouliez repartir de ces données pour un autre retraitement il faudrait soit réécrire une requête soit intégrer ce SELECT dans le champ FROM d’un autre SELECT…

En dplyr, vous enchaînez des opérations ligne par ligne, étape par étape, comme une chaîne de traitement logique :

Je prends une table → je filtre → je transforme → j’agrège → j’obtiens un résultat. (et je continue si besoin)

Continuer le traitement consiste juste à rajouter un « pipe » et les nouvelles étapes. Avec la possibilité de faire des checkpoints pour ne pas réinterroger toute la chaine ou en cas de prétraitement long :

rss0 <- rss %>% (... plein de choses...) %>% retraitement_initial

rss1 <- rss0 %>% (... plein d autres choses...) %>% premier_retraitement

rss2 <- rss0 %>% (... plein d encore autres choses...)%>% autre_retraitement
R

Il est courant d’appeler les fonctions de dplyr des verbes (d’action). Car c’est comme cela qu’ils se lisent dans la « phrase » créée par le pipeline :

« (prends) rss puis filtre par sexe= »H » puis trie par nom puis sélectionne les champs nom et sexe » :
rss %>% filter(...) %>% arrange(...) %>% select(...)

Rentrons maintenant dans le vif du sujet !

La brique élémentaire SQL : SELECT… FROM…

(je vous épargne les bizarreries du genre SELECT 1; qui ne servent à rien)

(nous verrons SELECT DISTINCT ... avec l’agrégation de données la prochaine fois)

La sélection des champs du jeu de sortie : select()

La plus simple des requêtes consiste à extraire la totalité des enregistrements d’une table (disons « rss ») :

#SQL : SELECT * FROM rss;

#R/dplyr :
rss
R

(on a rien fait et rien à faire. Les données « sont »…)

Si par contre on veut uniquement certains champs (par exemple NDA, IPP et DATE_ENTREE) on peut écrire :

#SQL : SELECT NDA, IPP, DATE_ENTREE FROM rss;

#R/dplyr :
rss %>% select(NDA, IPP, DATE_ENTREE)
R

Quand même bien ressemblant ? L’ordre des champs dans le résultat est fixé par l’ordre des paramètres du select().

En réalité le « pipe » a transformé le code que vous avez tapé (mais ça n’a aucune espèce d’importance). Je vous le rappelle une fois et on ne reviendra pas dessus :

rss %>% select(NDA, IPP, DATE_ENTREE)
# est devenu :
select(rss, NDA, IPP, DATE_ENTREE)
R

Il est possible de renommer les champs à la volée comme avec AS (notre faux-ami vu plus haut) en SQL grâce au signe « = » (d’attribution, pas de comparaison) en R :

#SQL : SELECT NDA AS no_admin, IPP AS nip, DATE_ENTREE AS dent FROM rss;

# R/dplyr :
rss %>% select(no_admin = NDA, nip = IPP, dent = DATE_ENTREE)
R

Il faut savoir qu’en R, le select() est plus puissant qu’en SQL. Par exemple, il est possible de spécifier plus finement les colonnes, en particulier en négatif ou par motif voire de façon totalement paramétrique (programmable donc) :

# Toutes les colonnes sauf IPP :
rss %>% select(-IPP)

# Toutes les colonnes sauf IPP et NDA :
rss %>% select(-IPP,-NDA)
# ou 
rss %>% select(-c(IPP,NDA))

# Toutes les colonnes commençant par "DATE" :
rss %>% select(starts_with("DATE"))

# Et tout cela est accumulable :
rss %>% select(NDA, -starts_with("DATE"), -DP)
# donnera NDA et toutes les colonnes sauf DP et sauf celles qui commencent par "DATE"
R

Les fonctions de sélection comme starts_with() sont assez puissantes on en retrouve pas mal :

  • Les sélections par position :
    • last_col() la dernière colonne
    • everything() la valeur par défaut qui sélectionne toutes les colonnes
  • Les sélections par liste limitative :
    • all_of(liste_de_chaines) : toutes les colonnes se trouvant dans liste_de_chaines (et génère une erreur si il en manque)
    • any_of(liste_de_chaines) : uniquement les colonnes se trouvant dans liste_de_chaines (et ne génère pas d’erreur si il en manque mais ne la crée pas pour autant. Si vous la voulez, il faut la créer par un mutate() préalablement)
  • Les sélections par motif de chaines de caractères :
    • starts_with("") qui va filtrer les champs dont le nom commence par la chaine de caractères en paramètre
    • ends_with("") pour la fin de chaine
    • contains("") pour n’importe où dans la chaine
    • matches("") qui va appliquer une expression régulière
    • et un petit plus bizarre num_range("",0:10) qui permet de sélectionner un début suivi d’un ensemble de nombres.
  • et enfin des sélections encore plus complexes par l’appel d’une fonction avec : where(nom_de_fonction) (si nom_de_fonction(nom_de_colonne) retourne TRUE alors la colonne est sélectionnée.)

Et pour couronner vous pouvez utiliser & et | pour faire des ET et OU entre sélections de colonnes.

La création de nouveaux champs : mutate()

A la différence du SELECT (SQL), select() ne peut pas faire de calcul ni créer de nouvelle colonne. Il y a une fonction différente pour cela : mutate() et celle-ci ne fait pas de sélection2 :

#SQL : SELECT NDA, (DATE_ENTREE + 1) AS date1, CONSTANTE = 1 
#      FROM rss;

#R/dplyr :
rss %>% mutate(date1 = DATE_ENTREE + 1, CONSTANTE = 1) %>% select(NDA, date1, CONSTANTE = 1)

# Il est assez courant d'écrire les pipelines sur plusieurs
# lignes pour améliorer la lisibilité lorsqu'il y a plus d'un traitement
# dans ce cas, il faut terminer la ligne par le pipe ou la virgule :
rss %>%
 mutate(date1 = DATE_ENTREE + 1,
        CONSTANTE = 1) %>%
 select(NDA, date1, CONSTANTE = 1)
 
R

Vous pouvez bien sûr si vous le désirer réutiliser les noms de champs déjà existants, la colonne sera alors remplacée sur place et grâce au « pipe », enchaîner le mutate() et le select() :

#SQL : SELECT NDA, (DATE_ENTREE + 1) AS DATE_ENTREE FROM rss;

#R/dplyr :
rss %>% 
 mutate(DATE_ENTREE = DATE_ENTREE + 1) %>%
 select(NDA, DATE_ENTREE) 
R

Il est aussi possible d’utiliser au sein d’un seul mutate() le résultat d’une précédente mutation dans une suivante3.

rss %>% 
 mutate(LENDEMAIN = DATE_ENTREE + 1,
        SURLENDEMAIN = LENDEMAIN +1
        ) %>%
 select(NDA, DATE_ENTREE, LENDEMAIN, SURLENDEMAIN) 
R

un même mutate() sur plusieurs colonnes : across()

Sachez qu’il est possible d’utiliser across(cols, fonction) pour appliquer à tous les champs cols= la fonction en paramètre. Pratique si par exemple vous avez des incohérences minuscules/majuscules (Attention tout de même à ne pas être trop « brutal » dans le traitement en utilisant éventuellement les critères vus plus haut) :

# Met le contenu de tout le data.frame en majuscule
rss %>% mutate(across(everything(),toupper))
# problème, cela a transformé la nature des champs

# donc mieux, uniquement sur les colonnes s'y prétant (de type character)
rss %>% mutate(across(where(is.character),toupper))
R

Positionner les nouvelles colonnes

Vous pouvez choisir où insérer le(s) nouveau(x) champ(s) sans faire appel à un select() en utilisant l’un des paramètres .before=, .after= (par défaut, les nouveaux champs sont insérés dans l’ordre de création en bout de tableau) de mutate() :

rss %>% mutate(LENDEMAIN = DATE_ENTREE + 1, .after = DATE_ENTREE)
# va insérer le nouveau champ LENDEMAIN juste après DATE_ENTREE

# on peut aussi spécifier par position :
rss %>% mutate(LENDEMAIN = DATE_ENTREE + 1, .before = 1)
# va insérer LENDEMAIN avant le premier champ en faisant donc
# le nouveau premier champ

# on peut aussi préciser via un sélecteur :
rss %>% mutate(LENDEMAIN = DATE_ENTREE + 1, .before = last_col())
# LENDEMAIN sera inséré avant la dernière colonne donc en avant dernier
R

Si vous voulez mettre plusieurs colonnes à des endroits différents, alors il faudra par contre enchainer autant de mutate() différents en spécifiant alors à chaque fois .before= ou .after=.

Enfin, on peut déplacer des colonnes existantes sans passer par un select() ou mutate() avec relocate() qui fonctionne de la même façon. Cette fonction permet aussi de les renommer comme avec select().

# renomme le champ DATE_ENTREE en ENTREE et le met en 1ère position :
rss %>% relocate(ENTREE = DATE_ENTREE, .before = 1) 
R

Retour sur les noms des champs

Tant en SQL qu’en R, on peut avoir des champs qui défient la grammaticalité du langage. Il suffit dans un cas comme dans l’autre de mettre des « backticks » (des « accents aigus ») autour. Attention cependant à la lisibilité. Et je rappelle que R est sensible à la casse, à l’inverse de SQL.

AS

Nous avons vu précédemment que dans les mots d’actions de dplyr, utiliser « nouveau_nom = ancien_nom » (select(), relocate(), ancien_nom est supprimé) ou « nouveau_nom = resultat_du_calcul » (mutate(), si resultat_du_calcul est un champ existant, il n’est pas supprimé par défaut) remplace très bien AS (SQL).

Renommer sans modifier et sans sélectionner : rename()

On peut aussi renommer des champs sans les sélectionner ni les modifier avec rename() et rename_with(). On s’en sert peu en venant de SQL où il n’y a pas d’équivalent et parce que select() permet de le faire en un temps donc je ne m’étendrai pas ici. Regardez la documentation si besoin.

A titre personnel, j’ai tout de même créé une fonction passe-partout prefix() 4:

prefix <- function(x, str = "", sep = "."){
  rename_with(x, .fn = function(x){
    paste(str, x, sep = sep)
  }
  )
}
R

Elle me permet de faire ressembler facilement les noms de champs à la fin d’un traitement dplyr à du SQL :

W rss %>% select(IPP,NOM) %>% prefix("RSS")
# donne une table de sortie avec des colonnes nommées
> names(rss)
[1] "RSS.IPP"  "RSS.NOM"
R

Nous verrons son utilité un autre jour, lorsque nous aborderons les jointures.

La clause WHERE (Les filtres) : filter()

On va s’occuper de la clause WHERE désormais.

Listons tous les séjours du patient ayant l’IPP 1234567A en SQL :

SELECT *
FROM rss
WHERE IPP = "1234567A";
SQL

Et en R/dplyr :

rss %>% filter(IPP == "1234567A")
R

Tout de même très ressemblant.

Vous pouvez mettre absolument ce que vous voulez dans filter() tant que le résultat de l’évaluation est assimilable à TRUE ou FALSE. La seule chose à contrôler est l’apparition de valeurs NA qui se propagent dans les calculs (p. ex sum(c(1:50, NA)) = NA, il faut utiliser sum(c(1:50, NA), na.rm=TRUE) pour que le calcul donne un résultat) et risquent de produire des résultats incomplets.

C’est un des fonctionnements qui n’est pas raccord avec le SQL (SUM(1, 2, NULL) = 3 en SQL).

Attention au « égal » qui n’est pas un « égal d’attribution » mais un « égal de comparaison » est doit donc être double : on écrit A == B (A égal-égal B). dplyr saura vous le signaler car c’est une erreur très fréquente qui fait planter le code.

Parmi les fonctions pratiques, on peut noter between() pour comparer avec une borne basse et haute et near() (pour les comparaisons avec marge d’erreur intégrée à la place de ==), l’opérateur %in% similaire à IN en SQL, mais aussi is.na() :

#SQL : SELECT * FROM rss WHERE DATE_ENTREE BETWEEN "2025-01-01" AND "2025-12-31";

#R/dplyr :
rss %>% filter(between(DATE_ENTREE, "2025-01-01", "2025-12-31"))

#SQL : SELECT * FROM rss WHERE IPP IS NULL;

#R/dplyr : 
rss %>% filter(is.na(IPP))

#SQL : SELECT * FROM rss WHERE IPP IS NOT NULL;

#R/dplyr : 
rss %>% filter(!is.na(IPP))

#SQL : SELECT * FROM rss WHERE IPP IN ("1234567A", "ABCDEFG1");

#R/dplyr : 
rss %>% filter(IPP %in% c("1234567A", "ABCDEFG1"))
R

Bien sûr vous avez accès à toutes les fonctions de R et aux opérateurs logiques AND, OR, XOR avec &, | et xor(). La négativation se fait avec ! comme dans le 3ème exemple ci-dessus (la différence est qu’il négative le résultat qui suit et ne se met donc pas au sein de l’expression. Il est parfois nécessaire de mettre des parenthèses pour englober l’expression à négativer pour gérer correctement les priorités).

il n’y a pas d’équivalent direct de LIKE mais on peut aisément le remplacer par grepl() disponible de base ou str_detect() de la librairie stringr :

#SQL : SELECT * FROM rss WHERE IPP LIKE "%A";

#R/dplyr :
rss %>% filter(grepl(".*A", IPP))
# Techniquement le .* n'est pas nécessaire mais c'est
# pour faire le pendant graphique.
rss %>% filter(grepl("A", IPP))


#SQL : SELECT* FROM rss WHERE IPP LIKE "%456%";

#R/dply/stringr :
rss %>% filter(str_detect(IPP, "456"))
# et grâce au pipe vous pouvez même écrire :
rss %>% filter(IPP %>% str_detect("456"))

# vous pourriez même réécrire LIKE ainsi en tant qu'opérateur sur mesure:
%like% <- function(x, y) {grepl(pattern = sprintf(".*%s.*", y), x = x)}
# et l'utiliser à loisir :
rss %>% filter(IPP %like% "456")
# ou être encore plus jusqu'au-boutiste et réinterpréter 
# les _ et % du motif original pour composer l'expression régulière.
# mais cela dépasse le périmètre de cet article
R

Chacun ses goûts ! A titre personnel, je m’en tiens à grepl() par habitude.

Et pour faire simple, remplacez le « _ » par un « . » (point unique, signifie « un et un seul caractère ») et le « % » par « .* » (point-étoile, signifie 0 à une infinité de caractères).

Trier avec ORDER BY

En SQL, on précise l’ordre de tri via la clause ORDER BY suivi d’une liste de champs et de leurs sens respectif (ASC ou DESC).

Ici il faut faire attention car en R il n’y a pas un mais deux faux amis !

  • order() existe dans la librairie de base de R est sert bien à trier mais n’est pas faite pour travailler sur un tableau de données mais sur un ou des vecteurs. De plus elle ne renvoie pas le résultat trié mais un vecteur représentant les index à utiliser pour trier le vecteur source. Dans le cas d’un data.frame, cette fonction génère une erreur…5
  • order_by() existe dans dplyr mais sert au sein d’un mutate() pour ordonner un vecteur résultat par rapport à un champ indépendamment de l’ordre des lignes. (c’est un peu abscons et son pendant en SQL est en rapport avec les partitions qu’on ne verra au mieux pas tout de suite)

Non, le vrai verbe dplyr à utiliser est arrange() (ça n’est pas pour perturber c’est parce que les autres étaient déjà pris…) :

#SQL: (le ASC est facultatif)
SELECT * FROM rss
ORDER BY DATE_ENTREE ASC;

#R/dplyr : 
rss %>% arrange(DATE_ENTREE)

#SQL: 
SELECT * FROM rss
ORDER BY DATE_ENTREE DESC;

#R/dplyr :
rss %>% arrange(desc(DATE_ENTREE))
R

Comme vous pouvez le voir desc() existe pour classer par ordre décroissant, mais il n’existe pas asc(). Il suffit de ne rien mettre. Dans le cas de champs assimilable à des données ordonnables (nombres), on peut utiliser "-" (on tire par l’opposée de la valeur du champ concerné) mais seul desc() fonctionne à tous les coups

Attention comme souvent au traitement des NULL. Selon votre SGBD ceux-ci peuvent être placés en tête ou en queue selon le comportement par défaut du serveur et inversé par DESC. En R/dplyr, lors d’un arrange(), les NA sont toujours placés en fin de jeu de données avec ou sans desc() !

Limiter le jeu de données

Pour limiter le nombre d’enregistrements en sortie, nous tombons là aussi sur un polymorphisme SQL selon le SGBD… Il s’agit de transcrire LIMIT/OFFSET (SQL) :

-- 10 premiers enregistrements
... LIMIT 10;
-- Tout sauf les 10 premiers :
... OFFSET 10; # ne fonctionne pas partout
-- enregistrements de 6 à 16 :
... LIMIT 10 OFFSET 5; # MariaDB, PostgreSQL, ORACLE
... LIMIT 5, 10; # MariaDB, MySQL
-- mais aussi
... OFFSET 5 FETCH 10; 
SQL

En R/dplyr on peut utiliser de multiples méthodes :

# 10 premiers enregistrements :
rss %>% head(10)
# 10 derniers enregistrements :
rss %>% tail(10)

# Tout sauf les 10 premiers :
rss %>% tail(-10)
# Tout sauf les 10 derniers :
rss %>% head(-10)
#Notez bien le "-" 

# enregistrements de 6 à 16 :
# en R/base :
rss[6:16,] # Attention la "," est importante
# en R/dplyr :
rss %>% slice(6:16)

# On peut aussi inverser la sélection :
rss[-(6:16),]
rss %>% slice(-(6:16))

# Mais on peut aussi avoir une sélection à trous :
rss[c(1, 3, 5, 10, 12, 14, 20:nrow(rss)),]
rss %>% slice(1, 3, 5, 10, 12, 14, 20:n())
# Ce qui signifie les lignes "1, 3, 5, 10, 12, 14 et de 20 à la fin"
R

Notez bien le basage qui est différent :

  • SQL : lignes à sauter + nombre de lignes à prendre
  • R ou dplyr : position des lignes à garder ou en cas d’utilisation de : la 1ère ligne à garder, position de la dernière ligne à garder.

Conclusion

Voilà, nous avons fait le parallèle pour les commandes SQL de base dans le cadre d’une requête sélection mono-table. La prochaine fois, nous verrons l’agrégation puis ensuite les jointures.

D’ici-là, entrainez-vous avec ces exercices (cliquez sur le code SQL pour voir la solution) :

Exercice 1 : Traduire en R/dplyr ce code SQL :
SELECT patients.ipp, patients.nom, patients.prenom, patients.sexe
FROM t_pat AS patients
WHERE patients.code_postal LIKE « 75% »
ORDER BY patients.nom ASC, patients.prenom, patients.sexe DESC;
t_pat %>%
 filter(grepl("^75.*", code_postal) %>%
 arrange(nom, prenom, desc(sexe)) %>%
 select(ipp, nom, prenom, sexe) %>%
 # en utilisant ma fonction
 prefix("patients")
 # ou sinon remplacez la dernière ligne par :
 rename_with(\(x){paste("patients",x,sep=".")})
R

Pas de piège, il faut juste être méthodique et penser « pipeline ».

Exercice 2 : Traduire en R/dplyr ce code SQL :
SELECT sejours.nda AS NDA, UPPER(sejours.nomprenom) as PATIENT, sejour.date_ entree AS ENTREE, sortie – entree AS DS
FROM t_sej AS sejours
WHERE sejours.um_entree IN (« CHIR01 », « CHIR02 »)
ORDER BY ENTREE, DS;
t_sej %>%
filter(um_entree %in% c("CHIR01", "CHIR02")) %>
mutate(PATIENT = to_upper(nomprenom),
       DS      = sortie - entree) %>%
select(NDA = nda, PATIENT, ENTREE = date_entree, DS) %>%
arrange(ENTREE, DS)
R

Comme vous pouvez le voir, il n’est pas nécessaire de renommer les champs préalablement ou de mettre des prefixes. Par contre, il faut bien respecter où utiliser quel nom. Le mutate() ainsi utilisé crée 2 champs de plus et le select() supprime les champs source. A titre personnel, je trouve que de garder un select() contenant l’intégralité des champs à conserver est plus lisible que de bidouiller en amont.


  1. Il y a d’autres finesses conceptuelles sans gros impact et que vous découvrirez si un jour il vous prend de mettre les mains dans le cambouis. Elles sont totalement ignorable pour l’utilisation. ↩︎
  2. Elle peut mais je vous le déconseille donc ne vous montre pas comment😉 ↩︎
  3. Attention, nous parlons de dplyr, cela n’est pas obligatoirement vrai dans d’autres librairies utilisant les mêmes mécanismes. C’est le cas par exemple lorsqu’on utiliser les bases nationales via dbplyr. ↩︎
  4. Le vrai code est prefix <- function(x, prefix = deparse(substitute(x)), sep = "."){rename_with(x, \(x){paste(prefix, x, sep = sep)})} mais je n’ai pas voulu vous faire peur et devoir expliquer le deparse/substitute. ↩︎
  5. Pour les curieux, order() s’utilise ainsi :
    a <- c(2,4,1) ; a[order(a)]
    dans le cadre d’un data.frame on utiliserait : df[order(df$colonne,...),] ou colonne est la ou les colonnes servant au classement ↩︎

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *