How to count TRUE values for a condition until a FALSE is found in R

Viewed 475

I have a data frame that have a column for Chromosome, another one with a Physical Position and the last one is a condition with TRUE or FALSE values. This condition is TRUE when the difference between the i+1 value for Position and i value of Position is not greater than a certain value (in the example is for 1000).

I want to count how many TRUE values there are in a determined range for that Position until a FALSE is found.

As my original data frame is to long I'll leave an example.

CHR <- c(1,1,1,1,2,2,2,3,3,3,3)
POS <- c(10,10000,12000,15000,25,75,50000,50,100,40000,45000)
CONDITION <- c(F,T,T,F,T,F,F,T,F,T,F)
df <- data.frame(CHR,POS,CONDITION)

I want to get something like this:

CHR_r <- c(1,1,2,2,3,3)
from <- c(10,10000,25,50000,50,40000)
to <- c(10,15000,75,50000,100,45000)
count <- c(1,3,2,1,2,2)
result <- data.frame(CHR_r,from,to,count)

Which is the best way to acomplish this? I thought of using a while loop, but I had the problem that it exits the loop each time a FALSE is found. Other thing I thought about was using sequence(rle), but the result was not nearly what I expected, maybe because it's more complicated what I am wanting.

3 Answers

I started with data.table::rleid, but that doesn't work well because you want to include a FALSE with the previous TRUE. Instead, I ended up making a condition for the start of a new group, and using cumsum on that condition to create group indices. As far as I can tell, within each CHR group, you want to start a new group if (a) it's the first row, (b) if there is a TRUE preceded by a FALSE, or (c) if there is a FALSE preceded by a FALSE--so I put that in a case_when statement. (Writing this out, it seems conditions (b) and (c) could be easily condensed to "the previous row is FALSE", but I'll leave it as-is in case there are missing values or something.)

library(dplyr)
df %>%
  group_by(CHR) %>%
  mutate(group_break = case_when(
    row_number() == 1 ~ 1,
    CONDITION & !lag(CONDITION, 1) ~ 1,
    !CONDITION & !lag(CONDITION, 1) ~ 1,
    TRUE ~ 0
  ),
  group_ind = cumsum(group_break)
  ) %>%
  group_by(CHR, group_ind) %>%
  summarize(from = first(POS), to = last(POS), count = n())
# # A tibble: 6 x 5
# # Groups:   CHR [3]
#     CHR group_ind  from    to count
#   <dbl>     <dbl> <dbl> <dbl> <int>
# 1     1         1    10    10     1
# 2     1         2 10000 15000     3
# 3     2         1    25    75     2
# 4     2         2 50000 50000     1
# 5     3         1    50   100     2
# 6     3         2 40000 45000     2

Another option with rleid

library(dplyr)
library(data.table)
df %>% 
   group_by(CHR) %>% 
   group_by(grp = pmax(rleid(pmax(CONDITION, lag(CONDITION, 
         default = first(CONDITION)))), cumsum(CONDITION)), .add = TRUE) %>%
   summarise(from = first(POS), to = last(POS), count = n())  %>% 
   ungroup %>%
   select(-grp)
# A tibble: 6 x 4
#    CHR  from    to count
#  <dbl> <dbl> <dbl> <int>
#1     1    10    10     1
#2     1 10000 15000     3
#3     2    25    75     2
#4     2 50000 50000     1
#5     3    50   100     2
#6     3 40000 45000     2

What you seem to be looking for is an histogram of the positions (i.e. count how much positions found in specific intervals). Your arrays from and to seem a bit weird as the first interval is from 10 to 10.

R can do it for you you would have to control the intervals with the function's options.

What you seem to be asking is how to do it yourself. You could use the which function to test through your arrays. Try that code after what you put in your question

count_of=c(0,length(from))
for  (i in c(1:length(from))){
  ind=which(POS>from[i] & POS<to[i]) 
  count_of[i]=length(ind)
}

Let me know if that works. Thanks!

Related