Obererpel
Obererpel

Reputation: 55

Split vector into chunks by delimiter

I have the following structure:

timestamp = c(1,2,3,4,5,6,7,8,9,10)
values = c(1337,42,NA,23,67,2,NA,NA,NA,5)
df = data.frame(timestamp,values)

#    timestamp values
# 1          1   1337
# 2          2     42
# 3          3     NA
# 4          4     23
# 5          5     67
# 6          6      2
# 7          7     NA
# 8          8     NA
# 9          9     NA
# 10        10      5

Now I want to know, how many coherent chunks there are (in this case 3: [1337,42] [23,67,2] and [5]. Maybe I can even split it to sub data frames or something like this?

Upvotes: 2

Views: 314

Answers (4)

Bulat
Bulat

Reputation: 6969

Here is a way to do this with data.table:

library(data.table)
timestamp = c(1,2,3,4,5,6,7,8,9,10)
values = c(1337,42,NA,23,67,2,NA,NA,NA,5)
dt = data.table(timestamp,values)
dt[, previous:=shift(values)]
res <- dt[!is.na(values) & is.na(previous), .N]
res
# [1] 3 

As performance was mentioned in the comment, here is the benchmark of solutions (on 1e5 rows):

    Unit: milliseconds
          expr        min        lq      mean     median        max neval
     dt[shift]   3.966346   4.03936   4.90822   4.728635   7.345617    10
 split[cumsum]  15.693565  17.38094  18.79429  17.739346  31.370630    10
           rle  42.375227  42.65068  45.82327  45.326625  51.473468    10
         dplyr 645.156377 655.90239 676.37797 678.966334 711.393856    10

Upvotes: 5

Obererpel
Obererpel

Reputation: 55

As rawr suggested in the comments i am using the following solution:

foo <- function( x ){
   idx <- 1 + cumsum( is.na( x ) )
   not.na <- ! is.na( x )
   result <- split( x[not.na], idx[not.na] )
   return(result)
}

Reasons:

  • It was the first solution
  • It works
  • I understand it
  • It does not use any packages/libraries.

Still thanks for all answers!

I will mark this as answered as soo as i can (in two days).

Upvotes: 3

akuiper
akuiper

Reputation: 214927

You can also use rle and rleid function:

library(data.table)
values = c(1337,42,NA,23,67,2,NA,NA,NA,5)
split(values, rleid(is.na(values)))[rle(!is.na(values))$values]
$`1`
[1] 1337   42

$`3`
[1] 23 67  2

$`5`
[1] 5

Upvotes: 3

Gopala
Gopala

Reputation: 10473

Using library dplyr, you can do something like this:

library(dplyr)
timestamp = c(1,2,3,4,5,6,7,8,9,10)
values = c(1337,42,NA,23,67,2,NA,NA,NA,5)
df = data.frame(timestamp,values)
df %>%
  mutate(id = cumsum(is.na(values) | is.na(lag(values)))) %>%
  filter(!is.na(values)) %>%
  group_by(id) %>%
  summarise(chunks = paste(values, collapse = ',')) %>%
  select(-id)

Output is:

Source: local data frame [3 x 1]

   chunks
    <chr>
1 1337,42
2 23,67,2
3       5

Upvotes: 3

Related Questions