Избегать медленного L oop в R - dplyr решение? - PullRequest
0 голосов
/ 12 марта 2020

У меня есть проблема, которую я могу решить с помощью медленного и неуклюжего l oop в R. Однако я надеюсь, что есть более элегантное (и более быстрое) решение ...

самое простое объяснение, которое я могу придумать: каждая строка данных описывает действие на переключателе. Строки сортируются по идентификатору переключателя (переключатель 1, переключатель 2 и т. Д. c.) И по хронологическому порядку действий. Каждый переключатель может быть включен или выключен в любой момент времени. Действие может быть «включить», «выключить» или «оставить в покое». Для каждой строки я хочу знать состояние переключателя (включен или выключен) как до, так и после действия, описанного в этой строке.

Каждый переключатель запускается в положении «выключено».

(данные, с которыми я работаю, на самом деле относятся к данным страхового полиса, но эта аналогия на основе коммутаторов работает и, вероятно, проще для понимания)

Воспроизводимый пример:

df <- data.frame(switch_id = c(1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 3, 3, 3, 3),
                  counter = c(1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 1, 2, 3, 4),
                  action = c("on", "off", "on", "off", "on", "same", "same", "same", "on", "same", "same", "same", "off", "off", "off", "on", "off", "same", "on"))

Я могу доберись туда, где я хочу использовать не очень элегантный l oop:

df$status_before <- NA
df$status_after <- NA

for(i in 1:nrow(df)) 
{

  if(df$counter[i] == 1)
  {
    df$status_before[i] <- FALSE # switch always starts in the "off" position
  }
  else
  {
    df$status_before[i] <- df$status_after[i-1]
  }

  if(df$action[i] == "on") {
    df$status_after[i] <- TRUE
  }
  else if(df$action[i] == "off")
  {
    df$status_after[i] <- FALSE  
  }
  else # "same"
  {
    df$status_after[i] <- df$status_before[i] # leave everything alone
  }

}

... но, очевидно, в R циклы лучше избегать, потому что они работают очень медленно. Конечно, это не имеет значения в этом крошечном наборе данных, но реальные данные, с которыми я работаю, имеют ~ 1 млн строк, поэтому это может быть проблемой.

Есть ли «векторизованное» решение для этого, возможно, с использованием dplyr введите команды?

Спасибо.

Ответы [ 2 ]

1 голос
/ 12 марта 2020

Вот решение для data.table:

Редактировать : необходимо использовать switch_id; начиная с data.table v.1.12.4, есть собственный способ заполнить пропущенные значения (nafill), используемые в этом редактировании; добавлены некоторые комментарии

library(data.table)
df <- data.table(switch_id = c(1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2),
    counter = c(1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7),
    action = c("on", "off", "on", "off", "on", "same", "same", "same", "on", "same", "same", "same", "off", "off", "off"))

# in "status_after", replace "same" by NA and set "off" and "on" to FALSE and TRUE
df[, status_after := as.logical(factor(action, labels=c(FALSE, TRUE, NA)))]

# fill in NA using last observation carried forward, by switch_id
df[, status_after := as.logical(nafill(+(status_after), type = "locf")), by = switch_id]

# status_before: shift status_after (default: lag one), by switch_id
df[, status_before := shift(status_after), by = switch_id]

# set first instance of status_before per switch_id to FALSE
df[, status_before := c(FALSE, status_before[-1]), by = switch_id]

# reorder columns
setcolorder(df, c(1:3, 5, 4))
df
#>     switch_id counter action status_before status_after
#>  1:         1       1     on         FALSE         TRUE
#>  2:         1       2    off          TRUE        FALSE
#>  3:         1       3     on         FALSE         TRUE
#>  4:         1       4    off          TRUE        FALSE
#>  5:         1       5     on         FALSE         TRUE
#>  6:         1       6   same          TRUE         TRUE
#>  7:         1       7   same          TRUE         TRUE
#>  8:         1       8   same          TRUE         TRUE
#>  9:         2       1     on         FALSE         TRUE
#> 10:         2       2   same          TRUE         TRUE
#> 11:         2       3   same          TRUE         TRUE
#> 12:         2       4   same          TRUE         TRUE
#> 13:         2       5    off          TRUE        FALSE
#> 14:         2       6    off         FALSE        FALSE
#> 15:         2       7    off         FALSE        FALSE

Создано в 2020-03-12 пакетом prex (v0.3.0)

1 голос
/ 12 марта 2020

Насколько я понимаю, когда я смотрю на ваш l oop, вы хотите в status_before a TRUE / FALSE в зависимости от действия предыдущего counter и в status_after a TRUE / FALSE зависит от действия фактического counter. Я правильно понял? Не совсем уверен, что вы хотите с действиями same ...

Чтобы посмотреть значения из предыдущих строк, вы можете использовать функцию lag() из dplyr (и посмотреть "вперед", используйте lead() вместо). Этот код дает тот же вывод, что и ваш l oop:

РЕДАКТИРОВАНИЕ:

# change "same" to last value of action (if you don't want to change the actual action column, create a new one)
df <- df %>%
  group_by(switch_id) %>%
  mutate(action = ifelse(action == "same", NA, action)) %>% # mark "same" as NA
  fill(action) # make sure action is a character string!

# do the actual evaluation
df <- df %>%
  group_by(switch_id) %>%
  mutate(status_before = case_when(lag(action) == "on" ~ "TRUE",
                                   lag(action) == "off" ~ "FALSE"),
         status_after = case_when(action == "on" ~ "TRUE",
                                  action == "off" ~ "FALSE"), 
         status_before = replace(status_before, is.na(status_before), "FALSE"))

Теперь это должно быть правильно!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...