Создайте флаг, указывающий, находится ли переменная года в диапазоне переменных start:end в data.table

У меня есть 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)]

но результат явно не правильный. Я хотел, чтобы диапазоны определялись построчно, но не знаю, как это выразить.

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
0
149
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вариант 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

какие-нибудь мысли о производительности вышеперечисленных вариантов? Мой фактический набор данных составляет 900 миллионов строк (сейчас я использую 1-й вариант).

LucasMation 27.05.2019 18:30

мой полный рабочий процесс, если: начать с набора данных из 20 миллионов наблюдений, с годами начала и окончания. Расширьте каждое наблюдение, чтобы представить возможные 40 лет в наборе данных (расширьте набор данных с сеткой 20 мм с годами: 1980: 2019). Затем вычислить х.

LucasMation 27.05.2019 18:33

@LucasMation Все они работают построчно, не проверяли. Если вы можете подмножить данные и проверить контрольные показатели для каждого метода, прежде чем работать с полными данными. было бы полезно

akrun 27.05.2019 19:28

Другой вариант был бы test[, col := mapply(between, year, start, end)]

markus 27.05.2019 20:33

1:nrow(test) необходимо? between векторизован

jangorecki 28.05.2019 14:41
Ответ принят как подходящий

Другой подход

#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] 

Должно работать быстро... Скоро последуют тесты

обновление: тесты на data.table из 1 млн строк

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 27.05.2019 20:44

Я проголосовал и счастлив, что оставил этот ответ только как комментарий;)

markus 27.05.2019 20:47

удивительно, @Wimpel, ваш вариант работал за 5 минут (предыдущий код работал 2 часа, когда я убил процесс). Я переключил ответ на этот из-за прироста производительности. Ткс Акрун, Маркус и Вимпел.

LucasMation 27.05.2019 21:49

миллисекунды не являются ориентиром, попробуйте получить масштаб, который займет не менее секунды

jangorecki 28.05.2019 14:40

другой путь

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 так .. приятно знать!

Wimpel 28.05.2019 15:26

Другие вопросы по теме