简体   繁体   中英

Create a block column based on id and the value of another column in R

Given the following first two columns(id and time_diff), i want to generate the 'block' column

test
   id time_diff   block
1   a        NA       1
2   a         1       1
3   a         1       1
4   a         1       1
5   a         3       1
6   a         3       1
7   b        NA       2
8   b        11       3
9   b         1       3
10  b         1       3
11  b         1       3
12  b        12       4
13  b         1       4
14  c        NA       5
15  c         4       5
16  c         7       5

The data is already sorted by id and time. The time_diff was computed based on the difference of the previous time and the time value for the row, given the same id. I want to create a block id which is an auto-increment value and increases when a new ID or a time_diff of >10 with the same id is encountered.

How can I achieve this in R?

Importing your data as a data frame with something like:

df = read.table(text='
   id time_diff   block
1   a        NA       1
2   a         1       1
3   a         1       1
4   a         1       1
5   a         3       1
6   a         3       1
7   b        NA       2
8   b        11       3
9   b         1       3
10  b         1       3
11  b         1       3
12  b        12       4
13  b         1       4
14  c        NA       5
15  c         4       5
16  c         7       5')

You can do a one-liner like this to get occurrences satisfying your two conditions:

> new_col = as.vector(cumsum(
     na.exclude(
       c(F,diff(as.numeric(as.factor(df$id)))) |     # change of id OR
       df$time_diff > 10                             # time_diff greater than 10
     )
  ))
> new_col
 [1] 0 0 0 0 0 1 2 2 2 2 3 3 4 4 4

And finally append this new column to your dataframe with cbind :

> cbind(df, block = c(0,new_col))
   id time_diff block block
1   a        NA     1     0
2   a         1     1     0
3   a         1     1     0
4   a         1     1     0
5   a         3     1     0
6   a         3     1     0
7   b        NA     2     1
8   b        11     3     2
9   b         1     3     2
10  b         1     3     2
11  b         1     3     2
12  b        12     4     3
13  b         1     4     3
14  c        NA     5     4
15  c         4     5     4
16  c         7     5     4

You will notice an offset between your wanted block variable and mine: correcting it is easy and can be done at several different step, I will leave it to you :)

Another variation of @Jealie's method would be:

with(test, cumsum(c(TRUE,id[-1]!=id[-nrow(test)])|time_diff>10))
#[1] 1 1 1 1 1 1 2 3 3 3 3 4 4 5 5 5

After learning from Jealie and akrun, I came up with this idea.

mydf %>%
    mutate(group = cumsum(time_diff > 10 |!duplicated(id)))

#   id time_diff block group
#1   a        NA     1     1
#2   a         1     1     1
#3   a         1     1     1
#4   a         1     1     1
#5   a         3     1     1
#6   a         3     1     1
#7   b        NA     2     2
#8   b        11     3     3
#9   b         1     3     3
#10  b         1     3     3
#11  b         1     3     3
#12  b        12     4     4
#13  b         1     4     4
#14  c        NA     5     5
#15  c         4     5     5
#16  c         7     5     5

Here is an approach using dplyr :

require(dplyr)

set.seed(999)
test <- data.frame(
  id = rep(letters[1:4], each = 3),
  time_diff = sample(4:15)
)


test %>%
  mutate(
    b = as.integer(id) - lag(as.integer(id)),
    more10 = time_diff > 10,
    increment = pmax(b, more10, na.rm = TRUE),
    increment = ifelse(row_number() == 1, 1, increment),
    block = cumsum(increment)
  ) %>%
  select(id, time_diff, block)

Try:

> df
   id time_diff
1   a        NA
2   a         1
3   a         1
4   a         1
5   a         3
6   a         3
7   b        NA
8   b        11
9   b         1
10  b         1
11  b         1
12  b        12
13  b         1
14  c        NA
15  c         4
16  c         7

block= c(1)
for(i in 2:nrow(df))
    block[i] = ifelse(df$time_diff[i]>10 || df$id[i]!=df$id[i-1], 
                  block[i-1]+1,
                  block[i-1])
df$block = block

df
   id time_diff block
1   a        NA     1
2   a         1     1
3   a         1     1
4   a         1     1
5   a         3     1
6   a         3     1
7   b        NA     2
8   b        11     3
9   b         1     3
10  b         1     3
11  b         1     3
12  b        12     4
13  b         1     4
14  c        NA     5
15  c         4     5
16  c         7     5

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM