-
Gestion de données avec `dplyr` et `(My)SQL`
2016-05-31
SourceCet article fournit des exemples de gestion de données dans R avec la librairie
dplyr
, et les commandes équivalentes avecMySQL
.Commandes simples
Les exemples seront appliqués au tableau de données suivant :
dat1 <- data.frame(
Name = c("Joe", "Joe", "Bill", "Jim", "Kate"),
Value = c(10.1, 13, 9, 7.7, -3),
Indic = c(0L, 1L, 1L, 2L, 2L),
Status = c(TRUE, TRUE, TRUE, FALSE, FALSE),
stringsAsFactors = FALSE
)
Name Value Indic Status Joe 10.1 0 TRUE Joe 13.0 1 TRUE Bill 9.0 1 TRUE Jim 7.7 2 FALSE Kate -3.0 2 FALSE On peut le créer ainsi dans
MySQL
:CREATE TABLE `dat1` (
`Name` varchar(15),
`Value` double,
`Indic` int(11),
`Status` tinyint(1)
);
INSERT INTO `dat1` VALUES
('Joe',10.1,0,1),('Joe',13,1,1),('Bill',9,1,1),('Jim',7.7,2,0),('Kate',-3,2,0);
Sélectionner des colonnes
R: MySQL: select(dat1, Name, Value)
SELECT `Name`, `Value` FROM dat1;
Name Value Joe 10.1 Joe 13.0 Bill 9.0 Jim 7.7 Kate -3.0 Supprimer des colonnes
R: MySQL: select(dat1, -Name, -Value)
ALTER TABLE `dat1`
DROP COLUMN `Name`, DROP COLUMN `Value`;
Indic Status 0 TRUE 1 TRUE 1 TRUE 2 FALSE 2 FALSE Plus d’infos pour
SQL
:Supprimer des lignes
R: MySQL: dat1 <- dat1[-which(dat1$Name=="Joe"),]
DELETE FROM `dat1`
WHERE `Name`='Joe';
Name Value Indic Status 3 Bill 9.0 1 TRUE 4 Jim 7.7 2 FALSE 5 Kate -3.0 2 FALSE Renommer une colonne
R: MySQL: rename(dat1, Score = Value)
SELECT `Name`, `Value` AS `Score`, `Indic`, `Status`
FROM `dat1`;
Name Score Indic Status Joe 10.1 0 TRUE Joe 13.0 1 TRUE Bill 9.0 1 TRUE Jim 7.7 2 FALSE Kate -3.0 2 FALSE Dans
MySQL
, on peut aussi utiliser la méthode suivante qui est plus directe, avec elle on n’a pas besoin de taper les noms des autres colonnes :ALTER TABLE `dat1`
CHANGE `Value` `Score` double NOT NULL;
Notez qu’on dû spécifier le type de la colonne (
double
).Plus d’infos :
Filtrer
R: MySQL: filter(dat1, Value > 8)
SELECT * FROM dat1
WHERE `Value` > 8;
Name Value Indic Status Joe 10.1 0 TRUE Joe 13.0 1 TRUE Bill 9.0 1 TRUE R: MySQL: filter(dat1, Value > 8, Indic==1)
SELECT * FROM dat1
WHERE `Value` > 8 AND Indic = 1;
Name Value Indic Status Joe 13 1 TRUE Bill 9 1 TRUE Voici des exemples de conditions sur les chaînes de caractères :
R: MySQL: library(stringr)
# noms contenant "J"
filter(dat1, str_detect(Name, "J"))
# noms commençant par "J"
filter(dat1, str_detect(Name, "^J"))
# noms terminant par "e"
filter(dat1, str_detect(Name, "e$"))
# noms contenant "o" ou "K"
filter(dat1, str_detect(Name, "[oK]"))
/* noms contenant "J" */
SELECT * FROM `dat1`
WHERE `Name` LIKE '%J%';
/* noms commençant par "J" */
SELECT * FROM `dat1`
WHERE `Name` LIKE 'J%';
/* noms terminant par "e" */
SELECT * FROM `dat1`
WHERE `Name` LIKE '%e';
/* noms contenant "o" ou "K" */
SELECT * FROM `dat1`
WHERE `Name` LIKE '%o%' OR Name LIKE '%K%';
Transformer une colonne
R: MySQL: mutate(dat1, Value=Value*2)
UPDATE `dat1` SET `Value` = `Value`*2;
Name Value Indic Status Joe 20.2 0 TRUE Joe 26.0 1 TRUE Bill 18.0 1 TRUE Jim 15.4 2 FALSE Kate -6.0 2 FALSE La fonction
mutate
permet aussi de mettre le résultat dans une nouvelle colonne :R: MySQL: mutate(dat1, x=Value*2)
SELECT `Name`, `Value`, `Indic`, `Value` * 2 AS `x`
FROM `dat1`;
Name Value Indic Status x Joe 10.1 0 TRUE 20.2 Joe 13.0 1 TRUE 26.0 Bill 9.0 1 TRUE 18.0 Jim 7.7 2 FALSE 15.4 Kate -3.0 2 FALSE -6.0 Pour éviter de sélectionner toutes les colonnes dans
SQL
, on peut d’abord créer une nouvelle colonne puis utiliserUPDATE
comme précédemment :ALTER TABLE `dat1` ADD `x` double;
UPDATE `dat1` SET `x` = `Value`*2;
On peut facilement ajouter une colonne constante au tableau :
R: MySQL: mutate(dat1, x='Hello')
SELECT *, 'Hello' AS x
FROM `dat1`;
Name Value Indic Status x Joe 10.1 0 TRUE Hello Joe 13.0 1 TRUE Hello Bill 9.0 1 TRUE Hello Jim 7.7 2 FALSE Hello Kate -3.0 2 FALSE Hello Avec la fonction
transmute
, le résultat ne contient que les nouvelles colonnes :R: MySQL: transmute(dat1, x=Value*2, y=Indic+1)
SELECT `Value` * 2 AS `x`, `Indic` + 1 AS `y`
FROM `dat1`;
x y 20.2 1 26.0 2 18.0 2 15.4 3 -6.0 3 Modifier la valeur dans une cellule
R: MySQL: dat1$Value[which(dat1$Name=="Bill")] <- 8.5
UPDATE `dat1`
SET `Value` = 8.5
WHERE `Name` = 'Bill';
Name Value Indic Status Joe 10.1 0 TRUE Joe 13.0 1 TRUE Bill 8.5 1 TRUE Jim 7.7 2 FALSE Kate -3.0 2 FALSE Trier selon une colonne
- dans l’ordre croissant :
R: MySQL: arrange(dat1, Value)
SELECT * FROM `dat1`
ORDER BY `Value`;
Name Value Indic Status Kate -3.0 2 FALSE Jim 7.7 2 FALSE Bill 9.0 1 TRUE Joe 10.1 0 TRUE Joe 13.0 1 TRUE - dans l’ordre décroissant :
R: MySQL: arrange(dat1, desc(Value))
SELECT * FROM `dat1`
ORDER BY `Value` DESC;
Name Value Indic Status Joe 13.0 1 TRUE Joe 10.1 0 TRUE Bill 9.0 1 TRUE Jim 7.7 2 FALSE Kate -3.0 2 FALSE Aggrégation
Moyennes
On obtient aussi la moyenne de
Values
pour chaque valeur deName
:R: MySQL: dat1 %>% group_by(Name) %>% summarise(m=mean(Value))
SELECT `Name`, AVG(`Value`) AS `m`
FROM `dat1`
GROUP BY `Name`;
Name m Bill 9.00 Jim 7.70 Joe 11.55 Kate -3.00 Si on veut obtenir les moyennes puis appliquer une autre requête, il suffit d’enchaîner les commandes avec
dplyr
; par contre, avecSQL
, il faut encapsuler le premier tableau avant d’appliquer la seconde requête :R: MySQL: dat1 %>% group_by(Name) %>% summarise(m=mean(Value)) %>% filter(m>8)
SELECT `Name`, `m`
FROM (SELECT `Name`, AVG(`Value`) AS `m`
FROM `dat1`
GROUP BY `Name`)
AS `temp`
WHERE `m`>8;
Name m Bill 9.00 Joe 11.55 Comptages
R: MySQL: dat1 %>% group_by(Name) %>% summarise(Count=n())
SELECT `Name`, count(*) AS `Count`
FROM `dat1`
GROUP BY `Name`;
Name Count Bill 1 Jim 1 Joe 2 Kate 1 Jonction de deux tableaux
Dans les exemples qui suivent, nous utilisons ce deuxième tableau de données :
dat2 <- data.frame(
Name = c("Bill", "Brian"),
Country = c("USA", "Scotland"),
stringsAsFactors = FALSE
)
Name Country Bill USA Brian Scotland Jonction interne
La commande R classique (qui n’utilise pas
dplyr
) estmerge(dat1, dat2, by="Name")
.R: MySQL: inner_join(dat1, dat2, by="Name")
SELECT * FROM
`dat1` INNER JOIN `dat2`
USING (`Name`);
Name Value Indic Status Country Bill 9 1 TRUE USA Jonction à gauche
commande R classique :
merge(dat1, dat2, by="Name", all.x=TRUE)
R: MySQL: left_join(dat1, dat2, by="Name")
SELECT * FROM
`dat1` LEFT JOIN `dat2`
USING (`Name`);
Name Value Indic Status Country Joe 10.1 0 TRUE NA Joe 13.0 1 TRUE NA Bill 9.0 1 TRUE USA Jim 7.7 2 FALSE NA Kate -3.0 2 FALSE NA Jonction à droite
commande R classique :
merge(dat1, dat2, by="Name", all.y=TRUE)
R: MySQL: right_join(dat1, dat2, by="Name")
SELECT * FROM
`dat1` RIGHT JOIN `dat2`
USING (`Name`);
Name Value Indic Status Country Bill 9 1 TRUE USA Brian NA NA NA Scotland Jonction complète
commande R classique :
merge(dat1, dat2, all=TRUE)
R: MySQL: full_join(dat1, dat2)
(SELECT * FROM
`dat1` LEFT JOIN `dat2`
USING (`Name`))
UNION
(SELECT * FROM
`dat1` RIGHT JOIN `dat2`
USING (`Name`)
WHERE `dat1`.`Name` IS NULL);
Name Value Indic Status Country Joe 10.1 0 TRUE NA Joe 13.0 1 TRUE NA Bill 9.0 1 TRUE USA Jim 7.7 2 FALSE NA Kate -3.0 2 FALSE NA Brian NA NA NA Scotland Autres types de jonction (
semi_join
,anti_join
):Du format large au format long (“unpivot”)
Nous utilisons ici le tableau de données suivant :
longdata <- data.frame(
Name = c("Joe", "Bill"),
Age = c(19L, 21L),
Score1 = c(5, 9),
Score2 = c(4, 8)
)
Name Age Score1 Score2 Joe 19 5 4 Bill 21 9 8 Pour transformer ce tableau en un tableau au format long, avec
Name
etAge
comme variables identifiantes, on peut utiliser la fonctionmelt
de la librairiereshape2
:reshape2::melt(longdata, id=c("Name", "Age"), value.name="Value")
La librairie
tidyr
est le successeur de la librairiereshape2
.R: MySQL: library(tidyr)
gather(longdata, Score, Value, -c(Name,Age))
SELECT `Name`, `Age`, 'Score1' AS `Variable`, `Score1` AS `Value`
FROM `longdata`
UNION ALL
SELECT `Name`, `Age`, 'Score2' AS `Variable`, `Score2` AS `Value`
FROM `longdata`;
Name Age Score Value Joe 19 Score1 5 Bill 21 Score1 9 Joe 19 Score2 4 Bill 21 Score2 8 Avec
MySQL
, nous avons simplement créé un tableau pour chaque valeur de la variableScore
puis nous avons empilé les deux tableaux avecUNION ALL
.mysql> SELECT `Name`, `Age`, 'Score1' AS `Score`, `Score1` AS `Value`
-> FROM `longdata`;
+------+------+--------+-------+
| Name | Age | Score | Value |
+------+------+--------+-------+
| Joe | 19 | Score1 | 5 |
| Bill | 21 | Score1 | 9 |
+------+------+--------+-------+
2 rows in set (0.00 sec)
Du format large au format long (“pivot”)
Nous utilisons ici le tableau créé précedemment à partir du tableau
longdata
, et nous allons reconstruirelongdata
à partir de celui-ci.R: MySQL: widedata <- gather(longdata, Score, Value, -c(Name,Age))
CREATE TABLE `widedata` AS
SELECT `Name`, `Age`, 'Score1' AS `Score`, `Score1` AS `Value`
FROM `longdata`
UNION ALL
SELECT `Name`, `Age`, 'Score2' AS `Score`, `Score2` AS `Value`
FROM `longdata`;
Dans R, on peut utiliser la commande
dcast
de la librairiereshape2
:reshape2::dcast(widedata, Name + Age ~ Score, value.var="Value")
R: MySQL: library(tidyr)
spread(widedata, Score, Value)
SELECT Name, Age,
MAX( IF( Score='Score1', `Value`, NULL ) ) AS `Score1`,
MAX( IF( Score='Score2', `Value`, NULL ) ) AS `Score2`
FROM widedata
GROUP BY `Name`, `Age`;
Name Age Score1 Score2 Bill 21 9 8 Joe 19 5 4 Détaillons la procédure
MySQL
. Si on retire l’opérateurMAX
et la commandeGROUP BY
, on obtient :mysql> SELECT Name, Age,
-> IF( Score='Score1', `Value`, NULL ) AS `Score1`,
-> IF( Score='Score2', `Value`, NULL ) AS `Score2`
-> FROM widedata;
+------+------+--------+--------+
| Name | Age | Score1 | Score2 |
+------+------+--------+--------+
| Joe | 19 | 5 | NULL |
| Bill | 21 | 9 | NULL |
| Joe | 19 | NULL | 4 |
| Bill | 21 | NULL | 8 |
+------+------+--------+--------+
4 rows in set (0.00 sec)
(on aurait le même résultat si on utilisait
CASE WHEN `Score`='Score1' THEN `Value` END
à la place deIF(...)
.)L’idée consiste alors à manipuler le tableau en regroupant par
Name
etAge
, et à utiliserMAX
pour garder la valeur numérique (par exemple,MAX
appliqué à5
etNULL
retourne5
).