#r #dplyr #data.table
#r #dplyr #data.table
Вопрос:
Для каждого 0
in x
я хочу случайным образом вставить число в соотношении 1: 10, но я ищу эффективный способ сделать это в dplyr
и / или data.table
, поскольку у меня очень большой набор данных (10 строк).
library(tidyverse)
df <- data.frame(x = 1:10)
df[4, 1] = 0
df[6, 1] = 0
df
# x
# 1 1
# 2 2
# 3 3
# 4 0
# 5 5
# 6 0
# 7 7
# 8 8
# 9 9
# 10 10
Это не работает, поскольку он заменяет каждый год одним и тем же значением:
set.seed(1)
df %>%
mutate(x2 = ifelse(x == 0, sample(1:10, 1), x))
# x x2
# 1 1 1
# 2 2 2
# 3 3 3
# 4 0 9
# 5 5 5
# 6 0 9
# 7 7 7
# 8 8 8
# 9 9 9
# 10 10 10
Это может быть достигнуто, хотя и с rowwise
помощью, но медленно для большого набора данных:
set.seed(1)
#use rowwise
df %>%
rowwise() %>%
mutate(x2 = ifelse(x == 0, sample(1:10, 1), x))
# x x2
# <dbl> <dbl>
# 1 1 1
# 2 2 2
# 3 3 3
# 4 0 9
# 5 5 5
# 6 0 4
# 7 7 7
# 8 8 8
# 9 9 9
# 10 10 10
Любые предложения по ускорению этого?
Спасибо
Ответ №1:
Не в tidyverse, но вы могли бы просто сделать что-то вроде этого:
is_zero <- (df$x == 0)
replacements <- sample(1:10, sum(is_zero))
df$x[is_zero] <- replacements
Конечно, вы можете свернуть это, если хотите.
df$x[df$x == 0] <- sample(1:10, sum(df$x == 0))
Комментарии:
1. я предпочитаю это решение, хотя
data.table
оно быстрее, см. Ниже!
Ответ №2:
Используя вышеуказанные решения microbenchmark
и небольшое изменение набора данных для настройки:
library(data.table)
library(tidyverse)
df <- data.frame(x = 1:100000, y = rbinom(100000, size = 1, 0.5)) %>%
mutate(x = ifelse(y == 0, 0, x)) %>%
dplyr::select(-y)
dt <- setDT(df)
test <- microbenchmark::microbenchmark(
base1 = {
df$x[df$x == 0] <- sample(1:10, sum(df$x == 0), replace = T)
},
dplyr1 = {
df %>%
mutate(x2 = replace(x, which(x == 0), sample(1:10, sum(x == 0), replace = T)))
},
dplyr2 = {
df %>% group_by(id=row_number()) %>%
mutate(across(c(x),.fns = list(x2 = ~ ifelse(.==0, sample(1:10, 1, replace = T), .)) )) %>%
ungroup() %>% select(-id)
},
data.table = {
dt[x == 0, x := sample(1:10, .N, replace = T)]
},
times = 500L
)
test
# Unit: microseconds
# expr min lq mean median uq max neval cld
# base1 733.7 785.9 979.0938 897.25 1137.0 1839.4 500 a
# dplyr1 5207.1 5542.1 6129.2276 5967.85 6476.0 21790.7 500 a
# dplyr2 15963406.4 16156889.2 16367969.8704 16395715.00 16518252.9 19276215.5 500 b
# data.table 1547.4 2229.3 2422.1278 2455.60 2573.7 15076.0 500 a
Я думал data.table
, что это будет быстрее всего, но базовое решение кажется лучшим (при условии, что я правильно настроил mircobenchmark
?).
РЕДАКТИРОВАТЬ на основе комментария @chinsoon12
1e5
строки:
Unit: microseconds
expr min lq mean median uq max neval cld
base1 730.4 839.30 1380.465 1238.00 1322.85 28977.3 500 a
data.table 1394.8 1831.85 2030.215 1946.95 2060.40 29821.9 500 b
1e6
строки:
Unit: milliseconds
expr min lq mean median uq max neval cld
base1 9.8703 11.6596 16.030715 11.76195 12.04145 326.0118 500 b
data.table 2.3772 2.7939 3.855672 3.04700 3.25900 61.4083 500 a
data.table
является самым быстрым
Комментарии:
1. я получаю от вас разные тайминги.
data.table
решение все еще быстрее, даже с 1e5 строками или 1e6 строками
Ответ №3:
Возможно, попробуйте использовать across()
from dplyr
таким образом:
library(tidyverse)
#Data
df <- data.frame(x = 1:10)
df[4, 1] = 0
df[6, 1] = 0
#Code
df %>% group_by(id=row_number()) %>%
mutate(across(c(x),.fns = list(x2 = ~ ifelse(.==0, sample(1:10, 1), .)) )) %>%
ungroup() %>% select(-id)
Вывод:
# A tibble: 10 x 2
x x_x2
<dbl> <dbl>
1 1 1
2 2 2
3 3 3
4 0 5
5 5 5
6 0 6
7 7 7
8 8 8
9 9 9
10 10 10
Комментарии:
1. Разве это не существенно
rowwise
, потому что вы группируете в каждой строке?
Ответ №4:
Я добавляю другой ответ, потому что по базовому варианту, который я предоставил, уже есть голоса. Но здесь может быть dplyr
способ использования replace
.
library(dplyr)
df %>%
mutate(x2 = replace(x, which(x == 0), sample(1:10, sum(x == 0))))
Ответ №5:
Вот data.table
вариант, использующий логику, аналогичную ответу Адама. Это фильтрует строки, которые соответствуют вашим критериям: x == 0
, а затем 1:10
.N
время выборки (которое без переменной группировки является количеством отфильтрованных строк data.table
).
library(data.table)
set.seed(1)
setDT(df)[x == 0, x := sample(1:10, .N)]
df
x
1: 1
2: 2
3: 3
4: 9
5: 5
6: 4
7: 7
8: 8
9: 9
10: 10