Number of switches in a vector by group

Viewed 32

Given the vector

vec <- c(1, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 0) 

the number of switches can simply be calculated with

cumsum(diff(vec) == 1)

However given the df

group <- c(1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2) 
df_test <- data.frame(vec,group)

and using

df_test <- df_test %>%
  group_by(group) %>%
  mutate(n_switches = cumsum(diff(vec) == 1))%>%
  ungroup()

the calculation fails because n_switches has 2 rows (1 per group) less than vec.

How can I overcome this problem and generate n_switches per group? Any help would be much appreciated!

3 Answers

With diff, the output is always an output with length 1 difference from the original output length. We can append an element to make the length same as mutate wants to return the column with the same length

library(dplyr)
df_test %>%
 group_by(group) %>%
 mutate(n_switches = cumsum(c(FALSE, diff(vec) == 1))) %>%
 ungroup

Here is another option besides diff, which applies gregexpr for positioning the shifts

df_test %>%
  group_by(group) %>%
  mutate(n_switches = cumsum(replace(0 * vec, gregexpr("(?<=0)1", paste0(vec, collapse = ""), perl = TRUE)[[1]], 1))) %>%
  ungroup()

# A tibble: 20 x 3
     vec group n_switches
   <dbl> <dbl>      <dbl>
 1     1     1          0
 2     0     1          0
 3     1     1          1
 4     1     1          1
 5     1     1          1
 6     0     1          1
 7     0     1          1
 8     1     1          2
 9     1     1          2
10     0     1          2
11     1     2          0
12     0     2          0
13     1     2          1
14     0     2          1
15     1     2          2
16     1     2          2
17     1     2          2
18     0     2          2
19     1     2          3
20     0     2          3

A similar realization but with data.table

setDT(df_test)[
  ,
  n_switches := cumsum(replace(0 * vec, gregexpr("(?<=0)1", paste0(vec, collapse = ""), perl = TRUE)[[1]], 1)),
  group
]

> df_test
    vec group n_switches
 1:   1     1          0
 2:   0     1          0
 3:   1     1          1
 4:   1     1          1
 5:   1     1          1
 6:   0     1          1
 7:   0     1          1
 8:   1     1          2
 9:   1     1          2
10:   0     1          2
11:   1     2          0
12:   0     2          0
13:   1     2          1
14:   0     2          1
15:   1     2          2
16:   1     2          2
17:   1     2          2
18:   0     2          2
19:   1     2          3
20:   0     2          3

You can define a switch as a position where the current value is 1 and previous value was 0 and then using cumsum create n_switches variable.

Using dplyr you can do this as :

library(dplyr)

df_test %>%
  group_by(group) %>%
  mutate(n_switches = cumsum(vec == 1 & lag(vec, default = 1) == 0))
  

#     vec group n_switches
#   <dbl> <dbl>      <int>
# 1     1     1          0
# 2     0     1          0
# 3     1     1          1
# 4     1     1          1
# 5     1     1          1
# 6     0     1          1
# 7     0     1          1
# 8     1     1          2
# 9     1     1          2
#10     0     1          2
#11     1     2          0
#12     0     2          0
#13     1     2          1
#14     0     2          1
#15     1     2          2
#16     1     2          2
#17     1     2          2
#18     0     2          2
#19     1     2          3
#20     0     2          3

and same logic with data.table :

library(data.table)
setDT(df_test)[, n_switches:=cumsum(vec == 1 & shift(vec, fill = 1) == 0), group]
Related