Find duplicate in data frame and change identified value

Viewed 57

I am stuck with probably a stupid and easy to solve issue.

I have a trigger that code 1 when the computer key is pressed (and) and 0 when the key is released. I need to identify each trigger start and stop (i.e., first and last 1) and replace the 1 in between by 0. The data record is time (continuous, t below) and value (electrodermal activity, value). To process the data more quickly, I need to preprocess it, that is identify the 1 corresponding to the beginning and the end of the window of interest.

Please find an exemple of the code:

t <- seq(0.1,10,0.1)

value <- rnorm(length(t), mean=1, sd=2) 

trig <- c(rep(0,20),rep(c(rep(1,10), rep(0,10)),4))

id <- 1:length(t)

the expected output is

trig_result <- c(rep(0,20), rep(c(1, rep(0,8),1,rep(0,10)),4)); length(trig_result)

The use of duplicate only identify the first 1 and the last one but not the intermediate value. I have seen similar post, but none solve the identification issue.

I look into dplyr function but I cannot figure out how to replace the 1 in 0 to end the preprocessing phase. Your help will be greatly appreciated.

Sincerely your,

4 Answers

Here's a base R solution with rle and cumsum:

result <- rep(0,length(trig))
result[head(cumsum(rle(trig)$lengths)+c(1,0),-1)] <- 1
all.equal(result,trig_result)
#[1] TRUE

Note that this solution assumes the data begins and ends with 0.

Here is another base R solution, using logical vectors.

borders <- function(x, b = 1){
  n <- length(x)
  d1 <- c(x[1] == b, diff(x) != 0 & x[-1] == b)
  d2 <- c(rev(diff(rev(x)) != 0 & rev(x[-n]) == b), x[n] == b)
  d1 + d2
}

trig <- c(rep(0,20),rep(c(rep(1,10), rep(0,10)),4))
tr <- borders(trig)

The result is not identical() to the expected output because its class is different but the values are all.equal().

trig_result <- c(rep(0,20), rep(c(1, rep(0,8),1,rep(0,10)),4))
identical(trig_result, tr)  # FALSE
all.equal(trig_result, tr)  # TRUE

class(trig_result)
#[1] "numeric"
class(tr)
#[1] "integer"

One option is to create a grouping index with rle or rleid (from data.table)

library(data.table)
out <- ave(trig, rleid(trig), FUN = function(x)
      x == 1 & (!duplicated(x) | !duplicated(x, fromLast = TRUE)))
identical(trig_result, out)
#[1] TRUE

You'd like to find the starts and ends of runs of 1s, and remove all 1s that aren't the start or end of a run.

The start of a run of ones is where the value of the current row is a 1, and the value of the previous row is a 0. You can access the value of previous row using the lag function.

The end of a run of 1s is where the current row is a 1, and the next row is a zero. You can access the value of the next row using the lead function.

library(tidyverse)
result = tibble(Trig = trig) %>%
    mutate(StartOfRun = Trig == 1 & lag(Trig == 0),
           EndOfRun = Trig == 1 & lead(Trig == 0),
           Result = ifelse(StartOfRun | EndOfRun, 1, 0)) %>%
    pull(Result)
Related