У меня есть data.table с 3 переменными даты: year
, start
, end
.
test <- data.table(year=2001:2003,start=c(2003,2002,2000),end=c(2003,2004,2002),x_desired=c(F,T,F))
Вы хотите создать новую переменную x
, указывающую для каждой строки, находится ли year
в диапазоне, определяемом start
и end
. Правильный желаемый результат находится в переменной x_desired
.
Я предполагал, что это можно сделать с помощью:
test[,x:=(year %in% start:end)]
но результат явно не правильный. Я хотел, чтобы диапазоны определялись построчно, но не знаю, как это выразить.
Вариант between
test[, x := between(year, start, end), 1:nrow(test)]
test
# year start end x_desired x
#1: 2001 2003 2003 FALSE FALSE
#2: 2002 2002 2004 TRUE TRUE
#3: 2003 2000 2002 FALSE FALSE
test[, x := year >= start & year <= end]
Или другой вариант Map
test[, x := unlist(do.call(Map, c(f = between, unname(.SD)))), .SDcols = year:end]
Или с pmap
от purrr
library(purrr)
test[, x := pmap_lgl(.SD[, .(x = year, left = start, right = end)], between)]
Добавлены тесты для новой опции (с использованием того же набора данных, что и большие данные @Wimpel).
microbenchmark(
wimpel = {
DT <- copy(dt)
DT[, x := FALSE ]
DT[ year %between% list(start,end), x := TRUE]
},
akrun = {
DT <- copy(dt)
DT[, x := year >= start & year <= end]
}, times = 3)
# Unit: milliseconds
# expr min lq mean median uq max neval
# wimpel 23.25196 40.72112 49.29130 58.19027 62.31098 66.43168 3
# akrun 19.56071 22.04272 22.96553 24.52473 24.66793 24.81114 3
мой полный рабочий процесс, если: начать с набора данных из 20 миллионов наблюдений, с годами начала и окончания. Расширьте каждое наблюдение, чтобы представить возможные 40 лет в наборе данных (расширьте набор данных с сеткой 20 мм с годами: 1980: 2019). Затем вычислить х.
@LucasMation Все они работают построчно, не проверяли. Если вы можете подмножить данные и проверить контрольные показатели для каждого метода, прежде чем работать с полными данными. было бы полезно
Другой вариант был бы test[, col := mapply(between, year, start, end)]
1:nrow(test)
необходимо? between
векторизован
Другой подход
#first, create a x-column with all FALSE
DT[, x := FALSE ]
#update the x-column subset where year is between start and end to TRUE
DT[ year %between% list(start,end), x := TRUE]
Должно работать быстро... Скоро последуют тесты
n = 1000000
set.seed(123)
dt <- data.table(year =sample( 2001:2003, n, replace = TRUE),
start=sample( c(2003,2002,2000), n, replace = TRUE),
end =sample( c(2003,2004,2002), n, replace = TRUE) )
microbenchmark::microbenchmark(
wimpel = {
DT <- copy(dt)
DT[, x := FALSE ]
DT[ year %between% list(start,end), x := TRUE]
},
akrun_nrow = {
DT <- copy(dt)
DT[, x := between(year, start, end), 1:nrow(DT)]
},
akrun_map = {
DT <- copy(dt)
DT[, x := unlist(do.call(Map, c(f = between, unname(.SD)))), .SDcols = year:end]
},
akrun_pmap = {
DT <- copy(dt)
DT[, x := purrr::pmap_lgl(.SD[, .(x = year, left = start, right = end)], between)]
},
markus = {
DT <- copy(dt)
DT[, col := mapply(between, year, start, end)]
},
times = 3
)
Результаты
Unit: milliseconds
expr min lq mean median uq max neval
wimpel 29.98388 30.41861 48.98399 30.85333 58.48404 86.11475 3
akrun_nrow 2741.35268 2755.01860 2944.58975 2768.68453 3046.20829 3323.73206 3
akrun_map 3673.21253 3683.22849 3711.51209 3693.24446 3730.66188 3768.07929 3
akrun_pmap 3281.13335 3291.04689 3406.46131 3300.96043 3469.12528 3637.29013 3
markus 3408.07869 3569.33044 3670.68141 3730.58219 3801.98277 3873.38334 3
Кажется, есть явный победитель... но, возможно, я что-то здесь упускаю?
@akrun (и markus): я узнал много из ваших ответов за последние годы ... Поскольку мой ответ измеряется намного быстрее: я что-то упустил или наконец понял что-то действительно быстрое? ;-)
Я проголосовал и счастлив, что оставил этот ответ только как комментарий;)
удивительно, @Wimpel, ваш вариант работал за 5 минут (предыдущий код работал 2 часа, когда я убил процесс). Я переключил ответ на этот из-за прироста производительности. Ткс Акрун, Маркус и Вимпел.
миллисекунды не являются ориентиром, попробуйте получить масштаб, который займет не менее секунды
другой путь
set(DT, NULL, "x", between(DT$year, DT$start, DT$end))
ориентир
library(data.table)
setDTthreads(40L)
n = 1e9
set.seed(123)
DT = data.table(year =sample( 2001:2003, n, replace = TRUE),
start=sample( c(2003,2002,2000), n, replace = TRUE),
end =sample( c(2003,2004,2002), n, replace = TRUE) )
d = copy(DT)
system.time({DT[, x := FALSE ]; DT[ year %between% list(start,end), x := TRUE]})
system.time(set(d, NULL, "x", between(DT$year, DT$start, DT$end)))
all.equal(d, DT)
тайминги
1e6
> system.time({DT[, x := FALSE ]; DT[ year %between% list(start,end), x := TRUE]})
user system elapsed
0.433 0.056 0.053
> system.time(set(d, NULL, "x", between(DT$year, DT$start, DT$end)))
user system elapsed
0.152 0.000 0.025
1e8
> system.time({DT[, x := FALSE ]; DT[ year %between% list(start,end), x := TRUE]})
user system elapsed
3.811 1.889 3.061
> system.time(set(d, NULL, "x", between(DT$year, DT$start, DT$end)))
user system elapsed
2.650 1.112 2.132
1e9
> system.time({DT[, x := FALSE ]; DT[ year %between% list(start,end), x := TRUE]})
user system elapsed
32.073 32.600 27.347
> system.time(set(d, NULL, "x", between(DT$year, DT$start, DT$end)))
user system elapsed
21.798 8.517 18.248
Никогда раньше не использовал set
так .. приятно знать!
какие-нибудь мысли о производительности вышеперечисленных вариантов? Мой фактический набор данных составляет 900 миллионов строк (сейчас я использую 1-й вариант).