In una fase della catena più lunga delle funzioni dplyr
, ho bisogno di sostituire parti di una variabile usando indici numerici per specificare quali elementi sostituire.Sostituire parti di una variabile utilizzando indici numerici in dplyr. Devo creare una colonna indice e usare ifelse?
miei dati simile a questa:
df1 <- data.frame(grp = rep(1:2, each = 3),
a = 1:6,
b = rep(c(10, 20), each = 3))
df1
# grp a b
# 1 1 1 10
# 2 1 2 10
# 3 1 3 10
# 4 2 4 20
# 5 2 5 20
# 6 2 6 20
Si supponga che, all'interno di ciascun gruppo, desidera sostituire elementi variabili a
con i corrispondenti elementi in b
, in una o più posizioni. In questo semplice esempio io uso un singolo indice (id
), ma questo potrebbe essere un vettore di indici. In primo luogo, ecco come lo farei con ddply
:
library(plyr)
id <- 2
ddply(.data = df1, .variables = .(grp), function(x){
x$a[id] <- x$b[id]
x
})
# grp a b
# 1 1 1 10
# 2 1 10 10
# 3 1 3 10
# 4 2 4 20
# 5 2 20 20
# 6 2 6 20
In dplyr
riuscivo a pensare ad alcuni modi diversi per eseguire la sostituzione. (1) Utilizzare do
con una funzione anonima, simile a quella utilizzata in ddply
. (2) Usa mutate
: concatena un vettore in cui la sostituzione è 'inserita' utilizzando l'indicizzazione numerica. Questo è probabilmente fruttuoso solo per un singolo indice. (3) Utilizzare mutate
: creare un vettore indice e utilizzare la sostituzione condizionale con ifelse
(vedere ad esempio here, here, e here).
detach("package:plyr", unload = TRUE)
library(dplyr)
# (1)
fun_do <- function(df){
l <- df %.%
group_by(grp) %.%
do(function(dat){
dat$a[id] <- dat$b[id]
dat
})
do.call(rbind, l)
}
# (2)
fun_mut <- function(df){
df %.%
group_by(grp) %.%
mutate(
a = c(a[1:(id - 1)], b[id], a[(id + 1):length(a)])
)
}
# (3)
fun_mut_ifelse <- function(df){
df %.%
group_by(grp) %.%
mutate(
idx = 1:n(),
a = ifelse(idx %in% id, b, a)) %.%
select(-idx)
}
fun_do(df1)
fun_mut(df1)
fun_mut_ifelse(df1)
In un punto di riferimento con un insieme di dati leggermente più grande, l' 'inserimento puzzle' è più veloce, ma ancora una volta, questo metodo è probabilmente adatto solo per le singole sostituzioni. E non sembra molto pulito ...
set.seed(123)
df2 <- data.frame(grp = rep(1:200, each = 3),
a = rnorm(600),
b = rnorm(600))
library(microbenchmark)
microbenchmark(fun_do(df2),
fun_mut(df2),
fun_mut_ifelse(df2),
times = 10)
# Unit: microseconds
# expr min lq median uq max neval
# fun_do(df2) 48443.075 49912.682 51356.631 53369.644 55108.769 10
# fun_mut(df2) 891.420 933.996 1019.906 1066.663 1155.235 10
# fun_mut_ifelse(df2) 2503.579 2667.798 2869.270 3027.407 3138.787 10
solo per controllare l'influenza della parte do.call(rbind
nella funzione do
, provare a farne a meno:
fun_do2 <- function(df){
df %.%
group_by(grp) %.%
do(function(dat){
dat$a[2] <- dat$b[2]
dat
})
}
fun_do2(df1)
Poi un nuovo punto di riferimento su una più grande set di dati:
df3 <- data.frame(grp = rep(1:2000, each = 3),
a = rnorm(6000),
b = rnorm(6000))
microbenchmark(fun_do(df3),
fun_do2(df3),
fun_mut(df3),
fun_mut_ifelse(df3),
times = 10)
Ancora, un semplice 'inserimento' è più veloce, mentre la funzione do
sta perdendo terreno. Nel testo della guida, do
viene descritto come "complemento generico" alle altre funzioni dplyr
. Per me è sembrata una scelta naturale per una funzione anonima. Tuttavia, sono rimasto sorpreso dal fatto che lo do
sia stato molto più lento, anche quando la parte non dplyr
rbind
è stata saltata. Attualmente, la documentazione di do
è piuttosto scarsa, quindi mi chiedo se sto abusando della funzione e che potrebbero esserci modi più appropriati (non documentati?) A do
?
Non ho trovato alcun indice su indici/indici quando ho cercato il dplyr
help text o vignette. Così ora mi chiedo:
Esistono altri metodi dplyr
per sostituire parti di una variabile utilizzando indici numerici che ho trascurato? In particolare, è la creazione di una colonna indice in combinazione con ifelse
la strada da percorrere, o ci sono più alternative dirette a[i] <- b[i]
-like?
Modifica seguente commento da @ G.Grothendieck (Grazie!). Aggiunta l'alternativa replace
(un candidato per "Vedere anche" in ?[
).
fun_replace <- function(df){
df %.%
group_by(grp) %.%
mutate(
a = replace(a, id, b[id]))
}
fun_replace(df1)
microbenchmark(fun_do(df3),
fun_do2(df3),
fun_mut(df3),
fun_mut_ifelse(df3),
fun_replace(df3),
times = 10)
# Unit: milliseconds
# expr min lq median uq max neval
# fun_do(df3) 685.154605 693.327160 706.055271 712.180410 851.757790 10
# fun_do2(df3) 291.787455 294.047747 297.753888 299.624730 302.368554 10
# fun_mut(df3) 5.736640 5.883753 6.206679 6.353222 7.381871 10
# fun_mut_ifelse(df3) 24.321894 26.091049 29.361553 32.649924 52.981525 10
# fun_replace(df3) 4.616757 4.748665 4.981689 5.279716 5.911503 10
replace
funzione è più veloce, e di sicuro più facile da usare rispetto fun_mut
quando ci sono più di un indice.
Edit 2fun_do
e fun_do2
non funziona più in dplyr 0.2
; Error: Results are not data frames at positions:
Prova 'mutare (a = sostituire (a, id, b [id]))' –
@ G.Grothendieck, grazie mille per il tuo suggerimento Ho aggiunto la tua funzione al benchmark. È il più veloce finora e in effetti più pulito della mia funzione di "concatenamento". Saluti. – Henrik
Puoi modificare leggermente il tuo 'mutate' in' fun_mut_ifelse' per 'mutare (a = ifelse (1: n()% in% id, b, a))' quindi non devi creare prima un indice e deselezionarlo in un secondo momento sopra. Non so se questo ha un impatto sulle prestazioni, però. –