How can I filter by subjects who have all levels of a factor?

Viewed 77

I am trying to filter a data set to only include subjects who have data in all conditions (levels of a factor).

I have tried to filter by calculating the number of levels for each subject, but that does not work.

library(tidyverse)
Data <- data.frame(
        Subject = factor(c(rep(1, 3),
                           rep(2, 3),
                           rep(3, 1))),
        Condition = factor(c("A", "B", "C",
                             "A", "B", "C",
                             "A")),
        Val = c(1, 0, 1,
                0, 0, 1,
                1)
)

Data %>%
        semi_join(
                .,
                Data %>%
                        group_by(Subject) %>%
                        summarize(Num_Cond = length(levels(Condition))) %>%
                filter(Num_Cond == 3),
                by = "Subject"
        )

This attempt yields:

  Subject Condition Val
1       1         A   1
2       1         B   0
3       1         C   1
4       2         A   0
5       2         B   0
6       2         C   1
7       3         A   1

Desired output:

  Subject Condition Val
1       1         A   1
2       1         B   0
3       1         C   1
4       2         A   0
5       2         B   0
6       2         C   1

I want to filter subject 3 out because they only have data for one condition.

Is there a dplyr/tidyverse approach for this problem?

3 Answers

We can create a condition with all and levels

library(dplyr)
Data %>% 
    group_by(Subject) %>%
    filter(all(levels(Condition) %in% Condition))
# A tibble: 6 x 3
# Groups:   Subject [2]
#  Subject Condition   Val
#  <fct>   <fct>     <dbl>
#1 1       A             1
#2 1       B             0
#3 1       C             1
#4 2       A             0
#5 2       B             0
#6 2       C             1

Or with n_distinct and nlevels

Data %>% 
    group_by(Subject) %>% 
    filter(nlevels(Condition) == n_distinct(Condition))
# A tibble: 6 x 3
# Groups:   Subject [2]
#  Subject Condition   Val
#  <fct>   <fct>     <dbl>
#1 1       A             1
#2 1       B             0
#3 1       C             1
#4 2       A             0
#5 2       B             0
#6 2       C             1

Here is a solution testing wether the number of rows of each groupis equal to the number of levels of Condition.

Data %>%
  group_by(Subject) %>%
  filter(n() == nlevels(Condition))
## A tibble: 6 x 3
## Groups:   Subject [2]
#  Subject Condition   Val
#  <fct>   <fct>     <dbl>
#1 1       A             1
#2 1       B             0
#3 1       C             1
#4 2       A             0
#5 2       B             0
#6 2       C             1

Edit

Following the comment by user @akrun I tested with a data set having duplicate values for each row and the code above does fail.

bind_rows(Data, Data) %>%
  group_by(Subject) %>%
  #distinct() %>%
  filter(n() == nlevels(Condition))
## A tibble: 0 x 3
## Groups:   Subject [0]
## ... with 3 variables: Subject <fct>, Condition <fct>, Val <dbl>

To run the commented out code line would solve the problem.

I found a relatively simple solution by sub-setting on Subject:

Data %>%
        semi_join(
                .,
                Data %>%
                        group_by(Subject) %>%
                        droplevels() %>%
                        summarize(Num_Cond = length(levels(Condition)[Subject])) %>%
                filter(Num_Cond == 3),
                by = "Subject"
        )

This gives the desired output:

  Subject Condition Val
1       1         A   1
2       1         B   0
3       1         C   1
4       2         A   0
5       2         B   0
6       2         C   1
Related