La mutation peut-elle être utilisée lorsque la mutation est conditionnelle (en fonction des valeurs de certaines valeurs de colonne)?
Cet exemple aide à montrer ce que je veux dire.
structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4,
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4,
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4,
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA,
8L), class = "data.frame")
a b c d e f
1 1 1 6 6 1 2
2 3 3 3 2 2 3
3 4 4 6 4 4 4
4 6 2 5 5 5 2
5 3 6 3 3 6 2
6 2 7 6 7 7 7
7 5 2 5 2 6 5
8 1 6 3 6 3 2
J'espérais trouver une solution à mon problème en utilisant le paquet dplyr (et oui je sais que ce n'est pas du code qui devrait fonctionner, mais j'imagine que cela explique clairement le but) pour créer une nouvelle colonne g:
library(dplyr)
df <- mutate(df,
if (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)){g = 2},
if (a == 0 | a == 1 | a == 4 | a == 3 | c == 4) {g = 3})
Le résultat du code que je cherche devrait avoir ce résultat dans cet exemple particulier:
a b c d e f g
1 1 1 6 6 1 2 3
2 3 3 3 2 2 3 3
3 4 4 6 4 4 4 3
4 6 2 5 5 5 2 NA
5 3 6 3 3 6 2 NA
6 2 7 6 7 7 7 2
7 5 2 5 2 6 5 2
8 1 6 3 6 3 2 3
Quelqu'un at-il une idée sur la façon de faire cela dans Dplyr? Cette trame de données n’est qu’un exemple, les trames de données que je traite sont beaucoup plus grandes. En raison de sa vitesse, j’ai essayé d’utiliser dplyr, mais peut-être existe-t-il d’autres moyens plus efficaces de gérer ce problème?
Utilisez ifelse
df %>%
mutate(g = ifelse(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
ifelse(a == 0 | a == 1 | a == 4 | a == 3 | c == 4, 3, NA)))
Ajouté - if_else: Notez que dans dplyr 0.5, une fonction if_else
est définie. Une alternative serait donc de remplacer ifelse
par if_else
; notez cependant que, puisque if_else
est plus strict que ifelse
(les deux branches de la condition doivent avoir le même type), le NA
doit alors être remplacé par NA_real_
.
df %>%
mutate(g = if_else(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4), 2,
if_else(a == 0 | a == 1 | a == 4 | a == 3 | c == 4, 3, NA_real_)))
Added - case_when Depuis que cette question a été postée, dplyr a ajouté case_when
. Une autre alternative serait:
df %>% mutate(g = case_when(a == 2 | a == 5 | a == 7 | (a == 1 & b == 4) ~ 2,
a == 0 | a == 1 | a == 4 | a == 3 | c == 4 ~ 3,
TRUE ~ NA_real_))
Puisque vous demandez d’autres moyens de mieux gérer le problème, voici une autre façon d’utiliser data.table
:
require(data.table) ## 1.9.2+
setDT(df)
df[a %in% c(0,1,3,4) | c == 4, g := 3L]
df[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
Notez que l'ordre des instructions conditionnelles est inversé pour obtenir g
correctement. Il n'y a pas de copie de g
faite, même pendant la deuxième affectation - elle est remplacée sur place .
Sur des données plus volumineuses, cela donnerait de meilleures performances que d'utiliser imbriqué if-else
, comme il peut évaluer à la fois "oui" et "non" 'cas , et l'imbrication peut devenir plus difficile à lire/maintenir à mon humble avis.
Voici un point de repère sur des données relativement plus grandes:
# R version 3.1.0
require(data.table) ## 1.9.2
require(dplyr)
DT <- setDT(lapply(1:6, function(x) sample(7, 1e7, TRUE)))
setnames(DT, letters[1:6])
# > dim(DT)
# [1] 10000000 6
DF <- as.data.frame(DT)
DT_fun <- function(DT) {
DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}
DPLYR_fun <- function(DF) {
mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
BASE_fun <- function(DF) { # R v3.1.0
transform(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
system.time(ans1 <- DT_fun(DT))
# user system elapsed
# 2.659 0.420 3.107
system.time(ans2 <- DPLYR_fun(DF))
# user system elapsed
# 11.822 1.075 12.976
system.time(ans3 <- BASE_fun(DF))
# user system elapsed
# 11.676 1.530 13.319
identical(as.data.frame(ans1), as.data.frame(ans2))
# [1] TRUE
identical(as.data.frame(ans1), as.data.frame(ans3))
# [1] TRUE
Je ne sais pas si c'est une alternative que vous aviez demandée, mais j'espère que cela vous aidera.
dplyr a maintenant une fonction case_when
qui offre un if vectorisé. La syntaxe est un peu étrange comparée à mosaic:::derivedFactor
car vous ne pouvez pas accéder aux variables de la manière standard dplyr et devez déclarer le mode de NA, mais elle est considérablement plus rapide que mosaic:::derivedFactor
.
df %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L,
a %in% c(0,1,3,4) | c == 4 ~ 3L,
TRUE~as.integer(NA)))
EDIT: Si vous utilisez dplyr::case_when()
d’avant la version 0.7.0 du paquet, vous devez faire précéder les noms de variables par '.$
' (par exemple, write .$a == 1
dans case_when
).
Benchmark: Pour le benchmark (réutilisation de fonctions du post d’Arun) et réduction de la taille de l’échantillon:
require(data.table)
require(mosaic)
require(dplyr)
require(microbenchmark)
DT <- setDT(lapply(1:6, function(x) sample(7, 10000, TRUE)))
setnames(DT, letters[1:6])
DF <- as.data.frame(DT)
DPLYR_case_when <- function(DF) {
DF %>%
mutate(g = case_when(a %in% c(2,5,7) | (a==1 & b==4) ~ 2L,
a %in% c(0,1,3,4) | c==4 ~ 3L,
TRUE~as.integer(NA)))
}
DT_fun <- function(DT) {
DT[(a %in% c(0,1,3,4) | c == 4), g := 3L]
DT[a %in% c(2,5,7) | (a==1 & b==4), g := 2L]
}
DPLYR_fun <- function(DF) {
mutate(DF, g = ifelse(a %in% c(2,5,7) | (a==1 & b==4), 2L,
ifelse(a %in% c(0,1,3,4) | c==4, 3L, NA_integer_)))
}
mosa_fun <- function(DF) {
mutate(DF, g = derivedFactor(
"2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
"3" = (a == 0 | a == 1 | a == 4 | a == 3 | c == 4),
.method = "first",
.default = NA
))
}
microbenchmark(
DT_fun(DT),
DPLYR_fun(DF),
DPLYR_case_when(DF),
mosa_fun(DF),
times=20
)
Cela donne:
expr min lq mean median uq max neval
DT_fun(DT) 1.503589 1.626971 2.054825 1.755860 2.292157 3.426192 20
DPLYR_fun(DF) 2.420798 2.596476 3.617092 3.484567 4.184260 6.235367 20
DPLYR_case_when(DF) 2.153481 2.252134 6.124249 2.365763 3.119575 72.344114 20
mosa_fun(DF) 396.344113 407.649356 413.743179 412.412634 416.515742 459.974969 20
La fonction derivedFactor
de mosaic
semble être conçue pour gérer cela. En utilisant cet exemple, cela ressemblerait à:
library(dplyr)
library(mosaic)
df <- mutate(df, g = derivedFactor(
"2" = (a == 2 | a == 5 | a == 7 | (a == 1 & b == 4)),
"3" = (a == 0 | a == 1 | a == 4 | a == 3 | c == 4),
.method = "first",
.default = NA
))
(Si vous voulez que le résultat soit numérique au lieu d'un facteur, vous pouvez envelopper derivedFactor
dans un appel as.numeric
.)
derivedFactor
peut également être utilisé pour un nombre arbitraire de conditions.
case_when
est maintenant une implémentation assez propre du cas de style SQL lorsque:
structure(list(a = c(1, 3, 4, 6, 3, 2, 5, 1), b = c(1, 3, 4,
2, 6, 7, 2, 6), c = c(6, 3, 6, 5, 3, 6, 5, 3), d = c(6, 2, 4,
5, 3, 7, 2, 6), e = c(1, 2, 4, 5, 6, 7, 6, 3), f = c(2, 3, 4,
2, 2, 7, 5, 2)), .Names = c("a", "b", "c", "d", "e", "f"), row.names = c(NA,
8L), class = "data.frame") -> df
df %>%
mutate( g = case_when(
a == 2 | a == 5 | a == 7 | (a == 1 & b == 4 ) ~ 2,
a == 0 | a == 1 | a == 4 | a == 3 | c == 4 ~ 3
))
Utiliser dplyr 0.7.4
Le manuel: http://dplyr.tidyverse.org/reference/case_when.html