Dplyr syntax selecting columns and converting them to a single list

Viewed 313

I am starting to learn how to use dplyr's pipe (%>%) command for manipulating data frames. I like that it seems much more streemlined. However, I just encountered a problem that I could not solve with only pipes.

I have a data frame which holds relationship (network) data which looks like this:

The first two columns indicate what items (genes) there is a relationship between, and the third column contains information about that relationship:

  a      b      c    
1 Gene_1 Gene_2 X    
2 Gene_2 Gene_3 R    
3 Gene_1 Gene_4 X    

My goal is to get a list of unique genes that share the same attribute. If the attribute X in col 3 is selected, I would get this data frame:

  a      b      c    
1 Gene_1 Gene_2 X     
3 Gene_1 Gene_4 X   

And I would want to end with this list of unique genes:

genes = c("Gene_1" "Gene_2" "Gene_4")

It does not matter if the item (Gene) comes from the first column or the second, I just want a unique list. I came up with this solution:

library(tidyr)

net = tibble(a = c("Gene_1", "Gene_2", "Gene_1"),
       b = c("Gene_2", "Gene_3", "Gene_4"),
       c = c("X", "R", "X"))

df = net %>% 
  filter(c == "X") %>%
  select(c(1,2)) 

genes = unique(c(df$a, df$b))

but am not satisfied, as I was not able to do everything within the dplyr pipe commands. I had to make a list outside of the pipe commands, and then call unique on it.

Is there a way to accomplish this task with a call to another pipe? I could not find anyway to do this. Thanks.

6 Answers

1) Use {...} like this:

net %>% 
  filter(c == "X") %>%
  select(c(1,2)) %>%
  { unique(c(.$a, .$b)) }
## [1] "Gene_1" "Gene_3" "Gene_2" "Gene_5"

2) or use magrittr's %$% pipe:

library(magrittr)

net %>% 
  filter(c == "X") %>%
  select(c(1,2)) %$%
  unique(c(a, b))
## [1] "Gene_1" "Gene_3" "Gene_2" "Gene_5"

3) or use with:

net %>% 
  filter(c == "X") %>%
  select(c(1,2)) %>%
  with(unique(c(a, b)))
## [1] "Gene_1" "Gene_3" "Gene_2" "Gene_5"

Since the result is not a data frame best not call it df.

I would suggest using tidyr::pivot_longer to reshape the multiple columns of potential matches from the two distinct gene columns, to a value column (which we care about) and a name column (referencing the original column name, which we don't care about and can ignore). Then distinct to get unique matches, and finally the match to column c:

net %>%
  pivot_longer(-c) %>%
  distinct(c, value) %>%
  filter(c == "X") 

If you want the result as a vector, you could add %>% pull(value).

One benefit of this approach is that we already have every distinct set of genes for every column c value calculated, and the last filter step just narrows it to one example c value.

Result

  c     value 
  <chr> <chr> 
1 X     Gene_1
2 X     Gene_2
3 X     Gene_4

[Note: I made a = c("Gene_1", "Gene_2", "Gene_1") and b = c("Gene_2", "Gene_3", "Gene_4") to match example.]

The unlist() function is probably what you are looking for.

Quoting from the built in documentation for ?unlist: "Given a list structure x, unlist simplifies it to produce a vector which contains all the atomic components which occur in x."

Since R data frames (and tibbles) are implemented as lists of column vectors with equal lengths, the unlist function will effectively convert a data frame into a vector.

Subset for the desired rows and columns with filter and select, then pipe the result through unlist() and then unique(). The result will be a vector with the distinct elements.

library(dplyr)

# The example data
tibble(a = c("Gene_1", "Gene_2", "Gene_1"),
       b = c("Gene_2", "Gene_3", "Gene_4"),
       c = c("X", "R", "X")) %>%
    
    # Subset data for desired feature
    filter(c == "X") %>%
    
    # Select identifier columns
    select(a, b) %>%
    
    # convert to a vector
    unlist() %>%
    
    # derive unique elements
    unique()

Result

[1] "Gene_1" "Gene_2" "Gene_4"

I realize this question has several answers, but I would have gone a slightly different way with it. Perhaps it will be useful to someone?

I created a data set to demonstrate, as well.

library(tidyverse) 
library(stringi)    # only used in data generation

# data set creation 100 rows
a = paste0("Gene_",1:100)
b = paste0("Gene_",round(runif(100, 10, 99),digits = 0))
cC = paste0(stringi::stri_rand_strings(100, 1, '[A-Z]'))

# put it together and strip the information    
data.frame(a = a, b = b, cC = cC) %>% # collect the data
   filter(cC == "X") %>%              # filter for attribute
   select(-cC) %>%                    # remove attribute field
   unlist() %>%                       # collapse the data frame into a vector
   unique()                           # show me what's unique

# output example
# [1] "Gene_10" "Gene_12" "Gene_28" "Gene_77" "Gene_22" "Gene_41" "Gene_75"
# [8] "Gene_19"
library(tidyverse)

net <- tibble(
  a = c("Gene_1", "Gene_1", "Gene_3"),
  b = c("Gene_2", "Gene_4", "Gene_5"),
  c = c("X", "R", "X")
)

df <- net %>%
  filter(c == "X") %>%
  select(a, b)
df
#> # A tibble: 2 x 2
#>   a      b     
#>   <chr>  <chr> 
#> 1 Gene_1 Gene_2
#> 2 Gene_3 Gene_5

genes <- net %>%
  select(-c) %>%
  unlist() %>%
  unique()
genes
#> [1] "Gene_1" "Gene_3" "Gene_2" "Gene_4" "Gene_5"

Though many enlightening answers have been proposed and accepted by OP too, I just want to add that in case, you want it simultaneously for all values in c, do this

library(tidyverse)

net %>%
  group_split(c, .keep = F) %>%
  setNames(unique(net$c)) %>%
  map(~ (.x %>% unlist() %>% unique()))

$X
[1] "Gene_2" "Gene_3"

$R
[1] "Gene_1" "Gene_2" "Gene_4"
Related