Заменяйте части переменной с помощью числовых индексов в dplyr. Нужно ли создавать индексный столбец и использовать ifelse?
На одном этапе в более длинной цепочке функций dplyr
мне нужно заменить части переменной с помощью числовых индексов, чтобы указать, какие элементы заменить.
Мои данные выглядят следующим образом:
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
Предположим, что в каждой группе мы хотим заменить элементы в переменной a
соответствующими элементами в b
в одной или нескольких позициях. В этом простом примере я использую один индекс (id
), но это может быть вектор индексов. Во-первых, вот как я сделал бы это с помощью 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
В dplyr
я мог бы подумать о различных способах замены. (1) Используйте do
с анонимной функцией, аналогичной функции, используемой в ddply
. (2) Используйте mutate
: объедините вектор, где замена "вставлена" с помощью числовой индексации. Это, вероятно, только полезно для одного индекса. (3) Используйте mutate
: создайте вектор индекса и используйте условную замену с помощью ifelse
(см. Здесь здесь, здесь, здесь и здесь).
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)
В тесте с немного большим набором данных "вставка головоломки" выполняется быстрее, но опять же этот метод, вероятно, подходит только для отдельных замен. И это выглядит не очень чисто...
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
Чтобы проверить влияние части do.call(rbind
в функции do
, попробуйте без нее:
fun_do2 <- function(df){
df %.%
group_by(grp) %.%
do(function(dat){
dat$a[2] <- dat$b[2]
dat
})
}
fun_do2(df1)
Затем новый тест большего набора данных:
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)
Опять же, простая "вставка" выполняется быстрее, а функция do
теряет почву. В тексте справки do
описывается как "дополнение общего назначения" к другим функциям dplyr
. Для меня это казалось естественным выбором для анонимной функции. Однако я был удивлен, что do
был намного медленнее, также когда пропущена часть не dplyr
rbind
. В настоящее время документация do
довольно скудная, поэтому я задаюсь вопросом, злоупотребляю ли я этой функцией и что могут быть более подходящими (недокументированными?) Способами do
?
У меня не было хитов индексов/индексов, когда я искал dplyr
текст справки или vignette. Итак, теперь я задаюсь вопросом:
Существуют ли другие методы dplyr
для замены частей переменной с использованием числовых индексов, которые я забыл? В частности, является ли создание столбца индекса в сочетании с ifelse
способом перехода или существуют более прямые альтернативы a[i] <- b[i]
?
Изменить следующий комментарий от @G.Grothendieck(Спасибо!). Добавлена альтернатива replace
(кандидат для 'См. Также' в ?[
).
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
функция наиболее быстрая и, безусловно, проще в использовании, чем fun_mut
, когда имеется более одного индекса.
Изменить 2 fun_do
и fun_do2
больше не работает в dplyr 0.2
; Error: Results are not data frames at positions:
Ответы
Ответ 1
Здесь гораздо более быстрый подход к изменению:
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