Looking for a dplyr function to apply a filter conditionally

Viewed 109

I have a data frame of various hematology values and their collection times. Those values should only be collected at specific times, but occasionally an extra one is added. I want to remove any instances where a value was collected outside the scheduled time.

To illustrate the issue, here's some code to create a very simplified version of the data frame I'm working with (plus some example schedules):

example <- tibble("Parameter" = c(rep("hgb", 3), rep("bili", 3), rep("LDH", 3)), 
                  "Collection" = c(1, 3, 4, 1, 5, 6, 0, 4, 8))

hgb_sampling <- c(1, 4)
bili_sampling <- c(1, 5)
ldh_sampling <- c(0, 4)

So, I need an way to conditionally apply a filter based on the value in the Parameter column. The solution needs to fit into a dyplr pipeline and yield something like this:

filtered <- tibble("Parameter" = c(rep("hemoglobin", 2), rep("bilirubin", 2), rep("LDH", 2)), 
                  "Collection" = c(1, 4, 1, 5, 0, 4))

I've tried a couple things (they all amount to something like the below) but the use of "Parameter" trips things up:

df <- example %>%
  {if (Parameter == "hgb") filter(., Collection %in% hgb_sampling)} 

Any suggestions?

6 Answers

You could create a reference tibble, join it with example and keep only selected rows.

library(dplyr)

ref_df <- tibble::tibble(Parameter = c("hgb","bili", "LDH"), 
                         value  = list(c(1, 4), c(1, 5), c(0, 4)))

example %>%
  inner_join(ref_df, by = 'Parameter') %>%
  group_by(Parameter) %>%
  filter(Collection %in% unique(unlist(value))) %>%
  select(Parameter, Collection)

#  Parameter Collection
#  <chr>          <dbl>
#1 hgb                1
#2 hgb                4
#3 bili               1
#4 bili               5
#5 LDH                0
#6 LDH                4

Put your valid times in a list with names matching the names in Collection, then group by the values in Collection and filter by the values of each list element in sample_list:

sample_list <- list(hgb = c(1, 4), bili = c(1, 5), LDH = c(0, 4))

example %>% 
    group_by(Parameter) %>% 
    filter(Collection %in% sample_list[[first(Parameter)]])

Output:

# A tibble: 6 x 2
  Parameter  Collection
  <chr>           <dbl>
1 hemoglobin          1
2 hemoglobin          4
3 bilirubin           1
4 bilirubin           5
5 LDH                 0
6 LDH                 4

Try purrr::imap_dfr:

library(tidyverse)

example <- tibble("Parameter" = c(rep("hgb", 3), rep("bili", 3), rep("LDH", 3)), 
                  "Collection" = c(1, 3, 4, 1, 5, 6, 0, 4, 8))

l <- list(hgb = c(1, 4), bili = c(1, 5), LDH = c(0, 4))

imap_dfr(l, ~example %>%
           filter(Parameter == .y & Collection %in% .x))

# # A tibble: 6 x 2
# Parameter Collection
# <chr>          <dbl>
#   1 hgb                1
# 2 hgb                4
# 3 bili               1
# 4 bili               5
# 5 LDH                0
# 6 LDH                4

One option involving dplyr, stringr and tibble could be:

enframe(mget(ls(pattern = "sampling"))) %>%
 mutate(name = str_extract(name, "[^_]+")) %>%
 right_join(example %>%
             mutate(Parameter = tolower(Parameter)), by = c("name" = "Parameter")) %>%
 filter(Collection %in% unlist(value)) %>%
 select(-value)

  name  Collection
  <chr>      <dbl>
1 hgb            1
2 hgb            4
3 bili           1
4 bili           5
5 ldh            0
6 ldh            4

If stored in a separate df as shown by @Ronak Shah, then you can do:

example %>%
 filter(Collection %in% unlist(ref_df$value[match(Parameter, ref_df$Parameter)]))

additional solution

library(tidyverse)
library(purrr)
fltr <- list(hgb = c(1, 4), bili = c(1, 5), LDH = c(0,4)) %>% 
  enframe(name = "Parameter")

example %>% 
  group_by(Parameter) %>% 
  nest() %>% 
  left_join(fltr) %>% 
  mutate(out = map2(.x = data, .y = value, .f = ~ filter(.x, Collection %in% .y))) %>% 
  unnest(out) %>% 
  select(Parameter, Collection)

Simple method that is very easy to modify, add, remove, debug, ...

library(dplyr)

example %>%
  filter(Parameter=="hgb" & Collection %in% c(1, 4) |
         Parameter=="bili" & Collection %in% c(1, 5) |
         Parameter=="LDH" & Collection %in% c(0, 4) )

Or if you want the values to be within a range:

example %>%
  filter(Parameter=="hgb" & between(Collection, 1, 4) |
         Parameter=="bili" & between(Collection, 1, 5) |
         Parameter=="LDH" & between(Collection, 0, 4))
Related