2014-05-13 1 views
14

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 dplyrrbind è 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:

+4

Prova 'mutare (a = sostituire (a, id, b [id]))' –

+0

@ 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

+1

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ò. –

risposta

7

Ecco una modifica-in-place approccio molto più veloce:

library(data.table) 

# select rows we want, then assign b to a for those rows, in place 
fun_dt = function(dt) dt[dt[, .I[id], by = grp]$V1, a := b] 

# benchmark 
df4 = data.frame(grp = rep(1:20000, each = 3), 
       a = rnorm(60000), 
       b = rnorm(60000)) 
dt4 = as.data.table(df4) 

library(microbenchmark) 

# using fastest function from OP 
microbenchmark(fun_dt(dt4), fun_replace(df4), times = 10) 
#Unit: milliseconds 
#    expr  min  lq median  uq  max neval 
#  fun_dt(dt4) 15.62325 17.22828 18.42445 20.83768 21.25371 10 
# fun_replace(df4) 99.03505 107.31529 116.74830 188.89134 286.50199 10 
+0

@Henrik np - se stai cercando grandi velocità e grande sintassi, non vedo alcun motivo per non imparare a usare 'data.table'; FYI se/quando [FR 2793] (https://r-forge.r-project.org/tracker/index.php?func=detail&aid=2793&group_id=240&atid=978) viene implementato la sintassi per questo problema diventerà molto più semplice/più naturale: 'dt [, a [id]: = b [id], di = grp]' – eddi

+0

@Henrik la mia comprensione è che la filosofia di 'dplyr' è contro la modifica in atto, che pone un limite inferiore su come veloce può essere per questi tipi di operazioni – eddi

+2

@Henrik Credo fermamente che l'inerzia della sintassi 'data.table' sia un mito originato da persone che erano troppo abituate alla sintassi' plyr'.Se ti avvicini con una lavagna pulita - è estremamente semplice - 'd [i, j, per = b]' viene letto come * "prendi' d', applica 'i', calcola' j' raggruppato per 'b'" *. Una volta compreso questo, capirai il 90% della sintassi 'data.table'. – eddi