Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Find start and end positions/indices of runs/consecutive values

Problem: Given an atomic vector, find the start and end indices of runs in the vector.

Example vector with runs:

x = rev(rep(6:10, 1:5))
# [1] 10 10 10 10 10  9  9  9  9  8  8  8  7  7  6

Output from rle():

rle(x)
# Run Length Encoding
#  lengths: int [1:5] 5 4 3 2 1
#  values : int [1:5] 10 9 8 7 6

Desired output:

#   start end
# 1     1   5
# 2     6   9
# 3    10  12
# 4    13  14
# 5    15  15

The base rle class doesn't appear to provide this functionality, but the class Rle and function rle2 do. However, given how minor the functionality is, sticking to base R seems more sensible than installing and loading additional packages.

There are examples of code snippets (here, here and on SO) which solve the slightly different problem of finding start and end indices for runs which satisfy some condition. I wanted something that would be more general, could be performed in one line, and didn't involve the assignment of temporary variables or values.

Answering my own question because I was frustrated by the lack of search results. I hope this helps somebody!

like image 511
Clara Avatar asked May 09 '17 16:05

Clara


People also ask

How do you find the start and end positions of runs?

runs.lengths.cumsum = cumsum(runs$lengths) ends = runs.lengths.cumsum[myruns] Next, we find the start positions of these runs. newindex = ifelse(myruns>1, myruns-1, 0) starts = runs.lengths.cumsum[newindex] + 1 if (0 %in% newindex) starts = c(1,starts)

How to find the starting and ending position of a key?

Given an array of integers, task is to find the starting and ending position of a given key. Input : arr [] = {1, 2, 3, 4, 5, 5} Key = 5 Output : Start index: 4 Last index: 5 Explanation: Starting index where 5 is present is 4 and ending address is 5.

How to do a cumulative sum of the run lengths?

Next, we can do a cumulative sum of the run lengths and extract the end positions of the runs with length of at least 5 using the above found indices. runs.lengths.cumsum = cumsum(runs$lengths) ends = runs.lengths.cumsum[myruns]

How do you find the start and end of a sequence?

Q: Given a sequence of random numbers, find the start and end positions of runs of five or more consecutive numbers that are greater than zero. A: Use the rle () function. For example, let’s apply rle () to the following sequence of numbers. seq3 = c(2,2,2,2,2,5,3,7,7,7,2,2,5,5,5,3,3,3) ( rle.seq3 = rle(seq3) )


2 Answers

A data.table possibility, where .I and .N are used to pick relevant indices, per group defined by rleid runs.

library(data.table)
data.table(x)[ , .(start = .I[1], end = .I[.N]), by = rleid(x)][, rleid := NULL][]
#    start end
# 1:     1   5
# 2:     6   9
# 3:    10  12
# 4:    13  14
# 5:    15  15
like image 184
Henrik Avatar answered Oct 18 '22 18:10

Henrik


Core logic:

# Example vector and rle object
x = rev(rep(6:10, 1:5))
rle_x = rle(x)

# Compute endpoints of run
end = cumsum(rle_x$lengths)
start = c(1, lag(end)[-1] + 1)

# Display results
data.frame(start, end)
#   start end
# 1     1   5
# 2     6   9
# 3    10  12
# 4    13  14
# 5    15  15

Tidyverse/dplyr way (data frame-centric):

library(dplyr)

rle(x) %>%
  unclass() %>%
  as.data.frame() %>%
  mutate(end = cumsum(lengths),
         start = c(1, dplyr::lag(end)[-1] + 1)) %>%
  magrittr::extract(c(1,2,4,3)) # To re-order start before end for display

Because the start and end vectors are the same length as the values component of the rle object, solving the related problem of identifying endpoints for runs meeting some condition is straightforward: filter or subset the start and end vectors using the condition on the run values.

like image 40
Clara Avatar answered Oct 18 '22 18:10

Clara