How to count cases at intervarls with conditions? for a tibble

66 Views Asked by At

So I hope I can express my question, here I have the following example that I made up:

result <- c(1,1,1,1,1,1,1,1,1,1)
con1 <- c(1,2,2,2,1,1,2,2,2,2)
con2 <- c(2,1,2,2,1,1,2,2,2,1)
con3 <- c(2,2,1,1,1,2,2,2,2,1)
con4 <- c(2,1,2,2,1,1,2,1,1,2)
con5 <- c(1,2,2,2,1,2,2,2,2,1)
a <- tibble(Result=result,Con1=con1,Con2=con2,Con3=con3,Con4=con4,Con5=con5)

The above code gives me the following tibble, where each row is a patient:

> a
# A tibble: 10 x 6
   Result  Con1  Con2  Con3  Con4  Con5
    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
 1      1     1     2     2     2     1
 2      1     2     1     2     1     2
 3      1     2     2     1     2     2
 4      1     2     2     1     2     2
 5      1     1     1     1     1     1
 6      1     1     1     2     1     2
 7      1     2     2     2     2     2
 8      1     2     2     2     1     2
 9      1     2     2     2     1     2
10      1     2     1     1     2     1

The Result are cases that are positive for a mayor illnes (thats why all are 1's) while the Con_i are yes or no question for the patient where 1=yes and 2=no, I want to get the number of patients that said yes to: 0 questions, 1 questions, 2-3 questions and 4 or more questions.

So far I've tried to do this:

a1 <-a %>% add_column(X=1) 
a1$X <- case_when(a$Con1==2 & a$Con2==2 & a$Con3==2 & a$Con4==2 & a$Con5==2 ~ 0,
                    a$Con1==1 & a$Con2==2 & a$Con3==2 & a$Con4==2 & a$Con5==2 |
                    a$Con1==2 & a$Con2==1 & a$Con3==2 & a$Con4==2 & a$Con5==2|
                    a$Con1==2 & a$Con2==2 & a$Con3==1 & a$Con4==2 & a$Con5==2|
                    a$Con1==2 & a$Con2==2 & a$Con3==2 & a$Con4==1 & a$Con5==2|
                    a$Con1==2 & a$Con2==2 & a$Con3==2 & a$Con4==2 & a$Con5==1 ~ 1)
table <- a1 %>% group_by(X) %>% count(X,Result)
table
> table
# A tibble: 3 x 3
# Groups:   X [3]
      X Result     n
  <dbl>  <dbl> <int>
1     0      1     1
2     1      1     4
3    NA      1     5

But I know is not the most efficient way, plus i would need to make all the combinations for 2-3 cases and 4+ cases and is not scalable, so I'm looking for a much easier way to do it and scale it, hope I can get your help and thanks in advance!

4

There are 4 best solutions below

9
On BEST ANSWER

Perhaps the simplest:

table(rowSums(a[,-1] < 2))
# 0 1 2 3 5     <--- counts of "1" in each row
# 1 4 2 2 1     <--- number of patients with that count

Since you need to group 2-3 and 4+, then

table(cut(rowSums(a[,-1] < 2), c(0, 1, 2, 4, Inf), include.lowest = TRUE))
#   [0,1]   (1,2]   (2,4] (4,Inf] 
#       5       2       2       1 

While the logic is using < 2, it's just as easy to check for == 1L or similar equality.


Tracing this, step by step:

a[,-1] == 1
#        Con1  Con2  Con3  Con4  Con5
#  [1,]  TRUE FALSE FALSE FALSE  TRUE
#  [2,] FALSE  TRUE FALSE  TRUE FALSE
#  [3,] FALSE FALSE  TRUE FALSE FALSE
#  [4,] FALSE FALSE  TRUE FALSE FALSE
#  [5,]  TRUE  TRUE  TRUE  TRUE  TRUE
#  [6,]  TRUE  TRUE FALSE  TRUE FALSE
#  [7,] FALSE FALSE FALSE FALSE FALSE
#  [8,] FALSE FALSE FALSE  TRUE FALSE
#  [9,] FALSE FALSE FALSE  TRUE FALSE
# [10,] FALSE  TRUE  TRUE FALSE  TRUE
rowSums(a[,-1] == 1)
#  [1] 2 2 1 1 5 3 0 1 1 3

That last is the number of 1s for each "patient" (row).

From this, I count one 0, four 1s, two 2s plus two 3s, zero 4s plus one 5. This should total 5, 2, 2, 1 ... so @andrew_reece is correct, let's use cut(...,right=FALSE):

table(cut(rowSums(a[,-1] < 2), c(0, 1, 2, 4, Inf), right = FALSE))
#   [0,1)   [1,2)   [2,4) [4,Inf) 
#       1       4       4       1 

I should have caught earlier the [0,1] (previous answer), indicating 0 and 1 are close-ended, meaning both 0 and 1 are included in the same bin.

0
On

An option with Reduce and table

table(Reduce(`+`, lapply(a[-1], `<`, 2)))

# 0 1 2 3 5 
#1 4 2 2 1 
0
On

Pivot your data so all the Con vars are a column, and the yes/no values for each Con sit in a separate column. Then you can use group_by and summarise operations to get your grouping:

a %>%
  mutate(patient = letters[row_number()]) %>%
  pivot_longer(starts_with("Con")) %>%
  group_by(patient) %>%
  summarise(yes = sum(value == 1),
            no = sum(value == 2)) %>%
  group_by(yes) %>%
  summarise(yes_ct = n()) %>%
  mutate(yes_grp = case_when(
    yes %in% 2:3 ~ "2-3",
    yes >= 4 ~ "ge4",
    TRUE ~ as.character(yes)
  )) %>%
  group_by(yes_grp) %>%
  summarise(ct = sum(yes_ct))

# A tibble: 4 x 2
  yes_grp    ct
  <chr>   <int>
1 0           1
2 1           4
3 2-3         4
4 ge4         1

I made an explicit patient variable (just row numbers, basically) to make pivot and group operations easier.

2
On

Try this:

library(data.table)
df <- setDT(a) - 1

df$sum <- 5 - rowSums( df[,2:6] )
freq <- data.table(table(df$sum))
names(freq) <- c('Questions_Yes', 'Patients')

freq <- freq[,`:=`(
  Questions_Yes = case_when(
    Questions_Yes %in% c(2:3) ~ "2-3",
    Questions_Yes >= 4 ~ "4+",
    TRUE ~ as.character(Questions_Yes)
  ))
][, .(Patients = sum(Patients)), by = Questions_Yes] 


  Questions_Yes Patients
1:             0        1
2:             1        4
3:           2-3        4
4:            4+        1