Skip to content

Subsetting Data with dplyr

Objectives

Today we will begin to wrangle data using the tidyverse package, dplyr. To this end, you will learn:

  1. how to filter data frames using dplyr
  2. how to employ the pipe (%>% or |>) operator to link functions

What is dplyr?

dplyr is a grammar of data manipulation, providing a consistent set of verbs that help you solve the most common data manipulation challenges:

mutate() adds new variables that are functions of existing variables select() picks variables based on their names. filter() picks cases based on their values. summarise() reduces multiple values down to a single summary. arrange() changes the ordering of the rows. - dplyr.tidyverse.org

dplyr is also used to combine data tables sharing common IDs and to manipulate data in data backends.

Loading dplyr

We do not need to load the dplyr package separately, as it is a core tidyverse package. If you need to install and load only dplyr, use install.packages("dplyr") and library(dplyr).

library(tidyverse)
── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.2     ✔ tibble    3.3.0
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     
── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors

Importing data

For this lesson, we will use sample metadata and differential expression results derived from the airway RNA-Seq project. See here for instructions on accessing the data.

Let's begin by importing the data.

#sample information  
smeta<-read_delim("./data/airway_sampleinfo.txt")
Rows: 8 Columns: 9
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (8): SampleName, cell, dex, albut, Run, Experiment, Sample, BioSample
dbl (1): avgLength

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
smeta
# A tibble: 8 × 9
  SampleName cell    dex   albut Run       avgLength Experiment Sample BioSample
  <chr>      <chr>   <chr> <chr> <chr>         <dbl> <chr>      <chr>  <chr>    
1 GSM1275862 N61311  untrt untrt SRR10395…       126 SRX384345  SRS50… SAMN0242…
2 GSM1275863 N61311  trt   untrt SRR10395…       126 SRX384346  SRS50… SAMN0242…
3 GSM1275866 N052611 untrt untrt SRR10395…       126 SRX384349  SRS50… SAMN0242…
4 GSM1275867 N052611 trt   untrt SRR10395…        87 SRX384350  SRS50… SAMN0242…
5 GSM1275870 N080611 untrt untrt SRR10395…       120 SRX384353  SRS50… SAMN0242…
6 GSM1275871 N080611 trt   untrt SRR10395…       126 SRX384354  SRS50… SAMN0242…
7 GSM1275874 N061011 untrt untrt SRR10395…       101 SRX384357  SRS50… SAMN0242…
8 GSM1275875 N061011 trt   untrt SRR10395…        98 SRX384358  SRS50… SAMN0242…
#let's use our differential expression results
dexp<-read_delim("./data/diffexp_results_edger_airways.txt")
Rows: 15926 Columns: 10
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (4): feature, albut, transcript, ref_genome
dbl (5): logFC, logCPM, F, PValue, FDR
lgl (1): .abundant

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
dexp
# A tibble: 15,926 × 10
   feature  albut transcript ref_genome .abundant   logFC logCPM       F  PValue
   <chr>    <chr> <chr>      <chr>      <lgl>       <dbl>  <dbl>   <dbl>   <dbl>
 1 ENSG000… untrt TSPAN6     hg38       TRUE      -0.390    5.06 32.8    3.12e-4
 2 ENSG000… untrt DPM1       hg38       TRUE       0.198    4.61  6.90   2.81e-2
 3 ENSG000… untrt SCYL3      hg38       TRUE       0.0292   3.48  0.0969 7.63e-1
 4 ENSG000… untrt C1orf112   hg38       TRUE      -0.124    1.47  0.377  5.55e-1
 5 ENSG000… untrt CFH        hg38       TRUE       0.417    8.09 29.3    4.63e-4
 6 ENSG000… untrt FUCA2      hg38       TRUE      -0.250    5.91 14.9    4.05e-3
 7 ENSG000… untrt GCLC       hg38       TRUE      -0.0581   4.84  0.167  6.92e-1
 8 ENSG000… untrt NFYA       hg38       TRUE      -0.509    4.13 44.9    1.00e-4
 9 ENSG000… untrt STPG1      hg38       TRUE      -0.136    3.12  1.04   3.35e-1
10 ENSG000… untrt NIPAL3     hg38       TRUE      -0.0500   7.04  0.350  5.69e-1
# ℹ 15,916 more rows
# ℹ 1 more variable: FDR <dbl>

We can get an idea of the structure of these data by using str() or glimpse(). glimpse(), from tidyverse, is similar to str() but provides somewhat cleaner output.

glimpse(smeta)  
Rows: 8
Columns: 9
$ SampleName <chr> "GSM1275862", "GSM1275863", "GSM1275866", "GSM1275867", "GS…
$ cell       <chr> "N61311", "N61311", "N052611", "N052611", "N080611", "N0806…
$ dex        <chr> "untrt", "trt", "untrt", "trt", "untrt", "trt", "untrt", "t…
$ albut      <chr> "untrt", "untrt", "untrt", "untrt", "untrt", "untrt", "untr…
$ Run        <chr> "SRR1039508", "SRR1039509", "SRR1039512", "SRR1039513", "SR…
$ avgLength  <dbl> 126, 126, 126, 87, 120, 126, 101, 98
$ Experiment <chr> "SRX384345", "SRX384346", "SRX384349", "SRX384350", "SRX384…
$ Sample     <chr> "SRS508568", "SRS508567", "SRS508571", "SRS508572", "SRS508…
$ BioSample  <chr> "SAMN02422669", "SAMN02422675", "SAMN02422678", "SAMN024226…
glimpse(dexp)
Rows: 15,926
Columns: 10
$ feature    <chr> "ENSG00000000003", "ENSG00000000419", "ENSG00000000457", "E…
$ albut      <chr> "untrt", "untrt", "untrt", "untrt", "untrt", "untrt", "untr…
$ transcript <chr> "TSPAN6", "DPM1", "SCYL3", "C1orf112", "CFH", "FUCA2", "GCL…
$ ref_genome <chr> "hg38", "hg38", "hg38", "hg38", "hg38", "hg38", "hg38", "hg…
$ .abundant  <lgl> TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE, TRUE,…
$ logFC      <dbl> -0.390100222, 0.197802179, 0.029160865, -0.124382022, 0.417…
$ logCPM     <dbl> 5.059704, 4.611483, 3.482462, 1.473375, 8.089146, 5.909668,…
$ F          <dbl> 3.284948e+01, 6.903534e+00, 9.685073e-02, 3.772134e-01, 2.9…
$ PValue     <dbl> 0.0003117656, 0.0280616149, 0.7629129276, 0.5546956332, 0.0…
$ FDR        <dbl> 0.002831504, 0.077013489, 0.844247837, 0.682326613, 0.00376…

Always know how your data is structured.

Before you do anything with your data, always check out the structure of your data to avoid surprises.

Now that we have some data to work with, let's start subsetting.

Subsetting data in base R

If you remember back to "Getting Started with R", Base R uses bracket notation for subsetting.

For example, if we want to subset the data frame iris to include only the first 5 rows and the first 3 columns, we could use

iris[1:5,1:3]
  Sepal.Length Sepal.Width Petal.Length
1          5.1         3.5          1.4
2          4.9         3.0          1.4
3          4.7         3.2          1.3
4          4.6         3.1          1.5
5          5.0         3.6          1.4

While this type of subsetting is useful, it is not always the most readable or easy to employ, especially for beginners. This is where dplyr comes in. The dplyr package in the tidyverse world simplifies data wrangling with easy to employ and easy to understand functions specific for data manipulation in data frames.

Subsetting with dplyr

How can we select only columns of interest and rows of interest? We can use select() and filter() from dplyr.

Subsetting by column (select())

To subset by column, we use the function select(). We can include and exclude columns, reorder columns, and rename columns using select().

Select a few columns from our differential expression results (dexp).

We can select the columns we are interested in by first calling the data frame object (dexp) followed by the columns we want to select (transcript,logFC,FDR). All arguments are separated by a comma. Just as in Base R subsetting, the order of the columns will determine the order of the columns in the new data frame.

Let's select the transcript, logFC, and FDR corrected p-value columns:

#first argument is the df followed by columns to select
ex1<-select(dexp, transcript, logFC, FDR) 
ex1
# A tibble: 15,926 × 3
   transcript   logFC     FDR
   <chr>        <dbl>   <dbl>
 1 TSPAN6     -0.390  0.00283
 2 DPM1        0.198  0.0770 
 3 SCYL3       0.0292 0.844  
 4 C1orf112   -0.124  0.682  
 5 CFH         0.417  0.00376
 6 FUCA2      -0.250  0.0186 
 7 GCLC       -0.0581 0.794  
 8 NFYA       -0.509  0.00126
 9 STPG1      -0.136  0.478  
10 NIPAL3     -0.0500 0.695  
# ℹ 15,916 more rows

We can rename while selecting.

The syntax to rename is new_name = old_name.

#rename using the syntax new_name = old_name
ex1<-select(dexp, gene=transcript, logFoldChange = logFC, FDRpvalue=FDR)
ex1
# A tibble: 15,926 × 3
   gene     logFoldChange FDRpvalue
   <chr>            <dbl>     <dbl>
 1 TSPAN6         -0.390    0.00283
 2 DPM1            0.198    0.0770 
 3 SCYL3           0.0292   0.844  
 4 C1orf112       -0.124    0.682  
 5 CFH             0.417    0.00376
 6 FUCA2          -0.250    0.0186 
 7 GCLC           -0.0581   0.794  
 8 NFYA           -0.509    0.00126
 9 STPG1          -0.136    0.478  
10 NIPAL3         -0.0500   0.695  
# ℹ 15,916 more rows
new name old name
gene transcript
logFoldChange logFC
FDRpvalue FDR

Using rename() or rename_with()

If you want to retain all columns, you could also use rename() from dplyr to rename columns.
For example, let's rename only transcript to gene from dexp.

rename(dexp, gene=transcript)
# A tibble: 15,926 × 10
   feature       albut gene  ref_genome .abundant   logFC logCPM       F  PValue
   <chr>         <chr> <chr> <chr>      <lgl>       <dbl>  <dbl>   <dbl>   <dbl>
 1 ENSG00000000… untrt TSPA… hg38       TRUE      -0.390    5.06 32.8    3.12e-4
 2 ENSG00000000… untrt DPM1  hg38       TRUE       0.198    4.61  6.90   2.81e-2
 3 ENSG00000000… untrt SCYL3 hg38       TRUE       0.0292   3.48  0.0969 7.63e-1
 4 ENSG00000000… untrt C1or… hg38       TRUE      -0.124    1.47  0.377  5.55e-1
 5 ENSG00000000… untrt CFH   hg38       TRUE       0.417    8.09 29.3    4.63e-4
 6 ENSG00000001… untrt FUCA2 hg38       TRUE      -0.250    5.91 14.9    4.05e-3
 7 ENSG00000001… untrt GCLC  hg38       TRUE      -0.0581   4.84  0.167  6.92e-1
 8 ENSG00000001… untrt NFYA  hg38       TRUE      -0.509    4.13 44.9    1.00e-4
 9 ENSG00000001… untrt STPG1 hg38       TRUE      -0.136    3.12  1.04   3.35e-1
10 ENSG00000001… untrt NIPA… hg38       TRUE      -0.0500   7.04  0.350  5.69e-1
# ℹ 15,916 more rows
# ℹ 1 more variable: FDR <dbl>

Excluding columns

We can select all columns, leaving out ones that do not interest us using a - sign. This is helpful if the columns to keep far outweigh those to exclude. We can similarly use the ! to negate a selection.

ex2<-select(dexp, -feature) 
ex2
# A tibble: 15,926 × 9
   albut transcript ref_genome .abundant   logFC logCPM       F   PValue     FDR
   <chr> <chr>      <chr>      <lgl>       <dbl>  <dbl>   <dbl>    <dbl>   <dbl>
 1 untrt TSPAN6     hg38       TRUE      -0.390    5.06 32.8    0.000312 0.00283
 2 untrt DPM1       hg38       TRUE       0.198    4.61  6.90   0.0281   0.0770 
 3 untrt SCYL3      hg38       TRUE       0.0292   3.48  0.0969 0.763    0.844  
 4 untrt C1orf112   hg38       TRUE      -0.124    1.47  0.377  0.555    0.682  
 5 untrt CFH        hg38       TRUE       0.417    8.09 29.3    0.000463 0.00376
 6 untrt FUCA2      hg38       TRUE      -0.250    5.91 14.9    0.00405  0.0186 
 7 untrt GCLC       hg38       TRUE      -0.0581   4.84  0.167  0.692    0.794  
 8 untrt NFYA       hg38       TRUE      -0.509    4.13 44.9    0.000100 0.00126
 9 untrt STPG1      hg38       TRUE      -0.136    3.12  1.04   0.335    0.478  
10 untrt NIPAL3     hg38       TRUE      -0.0500   7.04  0.350  0.569    0.695  
# ℹ 15,916 more rows
ex2<-select(dexp, !feature) 
ex2
# A tibble: 15,926 × 9
   albut transcript ref_genome .abundant   logFC logCPM       F   PValue     FDR
   <chr> <chr>      <chr>      <lgl>       <dbl>  <dbl>   <dbl>    <dbl>   <dbl>
 1 untrt TSPAN6     hg38       TRUE      -0.390    5.06 32.8    0.000312 0.00283
 2 untrt DPM1       hg38       TRUE       0.198    4.61  6.90   0.0281   0.0770 
 3 untrt SCYL3      hg38       TRUE       0.0292   3.48  0.0969 0.763    0.844  
 4 untrt C1orf112   hg38       TRUE      -0.124    1.47  0.377  0.555    0.682  
 5 untrt CFH        hg38       TRUE       0.417    8.09 29.3    0.000463 0.00376
 6 untrt FUCA2      hg38       TRUE      -0.250    5.91 14.9    0.00405  0.0186 
 7 untrt GCLC       hg38       TRUE      -0.0581   4.84  0.167  0.692    0.794  
 8 untrt NFYA       hg38       TRUE      -0.509    4.13 44.9    0.000100 0.00126
 9 untrt STPG1      hg38       TRUE      -0.136    3.12  1.04   0.335    0.478  
10 untrt NIPAL3     hg38       TRUE      -0.0500   7.04  0.350  0.569    0.695  
# ℹ 15,916 more rows

We can reorder using select().

For readability, let's move the transcript column to the front.

#you can reorder columns and call a range of columns using select().
ex3<-select(dexp, transcript:FDR,albut)  
ex3
# A tibble: 15,926 × 9
   transcript ref_genome .abundant   logFC logCPM       F   PValue     FDR albut
   <chr>      <chr>      <lgl>       <dbl>  <dbl>   <dbl>    <dbl>   <dbl> <chr>
 1 TSPAN6     hg38       TRUE      -0.390    5.06 32.8    0.000312 0.00283 untrt
 2 DPM1       hg38       TRUE       0.198    4.61  6.90   0.0281   0.0770  untrt
 3 SCYL3      hg38       TRUE       0.0292   3.48  0.0969 0.763    0.844   untrt
 4 C1orf112   hg38       TRUE      -0.124    1.47  0.377  0.555    0.682   untrt
 5 CFH        hg38       TRUE       0.417    8.09 29.3    0.000463 0.00376 untrt
 6 FUCA2      hg38       TRUE      -0.250    5.91 14.9    0.00405  0.0186  untrt
 7 GCLC       hg38       TRUE      -0.0581   4.84  0.167  0.692    0.794   untrt
 8 NFYA       hg38       TRUE      -0.509    4.13 44.9    0.000100 0.00126 untrt
 9 STPG1      hg38       TRUE      -0.136    3.12  1.04   0.335    0.478   untrt
10 NIPAL3     hg38       TRUE      -0.0500   7.04  0.350  0.569    0.695   untrt
# ℹ 15,916 more rows
#Note: this also would have excluded the feature column 

If we are interested in moving a column without selection, we can use relocate(). We should include the columns we want to move and where we would like to put them.

relocate(dexp, transcript, .before=feature)
# A tibble: 15,926 × 10
   transcript feature  albut ref_genome .abundant   logFC logCPM       F  PValue
   <chr>      <chr>    <chr> <chr>      <lgl>       <dbl>  <dbl>   <dbl>   <dbl>
 1 TSPAN6     ENSG000… untrt hg38       TRUE      -0.390    5.06 32.8    3.12e-4
 2 DPM1       ENSG000… untrt hg38       TRUE       0.198    4.61  6.90   2.81e-2
 3 SCYL3      ENSG000… untrt hg38       TRUE       0.0292   3.48  0.0969 7.63e-1
 4 C1orf112   ENSG000… untrt hg38       TRUE      -0.124    1.47  0.377  5.55e-1
 5 CFH        ENSG000… untrt hg38       TRUE       0.417    8.09 29.3    4.63e-4
 6 FUCA2      ENSG000… untrt hg38       TRUE      -0.250    5.91 14.9    4.05e-3
 7 GCLC       ENSG000… untrt hg38       TRUE      -0.0581   4.84  0.167  6.92e-1
 8 NFYA       ENSG000… untrt hg38       TRUE      -0.509    4.13 44.9    1.00e-4
 9 STPG1      ENSG000… untrt hg38       TRUE      -0.136    3.12  1.04   3.35e-1
10 NIPAL3     ENSG000… untrt hg38       TRUE      -0.0500   7.04  0.350  5.69e-1
# ℹ 15,916 more rows
# ℹ 1 more variable: FDR <dbl>

Note

By default, relocate() will move columns to the left-hand side of the data frame.

Selecting a range of columns

Notice that we can select a range of columns using the :. We could also deselect a range of columns or deselect a range of columns while adding a column back.

ex3<-select(dexp, -(albut:F),logFC)
ex3
# A tibble: 15,926 × 4
   feature           PValue     FDR   logFC
   <chr>              <dbl>   <dbl>   <dbl>
 1 ENSG00000000003 0.000312 0.00283 -0.390 
 2 ENSG00000000419 0.0281   0.0770   0.198 
 3 ENSG00000000457 0.763    0.844    0.0292
 4 ENSG00000000460 0.555    0.682   -0.124 
 5 ENSG00000000971 0.000463 0.00376  0.417 
 6 ENSG00000001036 0.00405  0.0186  -0.250 
 7 ENSG00000001084 0.692    0.794   -0.0581
 8 ENSG00000001167 0.000100 0.00126 -0.509 
 9 ENSG00000001460 0.335    0.478   -0.136 
10 ENSG00000001461 0.569    0.695   -0.0500
# ℹ 15,916 more rows

Helper functions

We can also include helper functions such as starts_with() and ends_with(), and operators (!, &, |) for combining selections.

select(dexp, transcript, starts_with("log"), FDR)
# A tibble: 15,926 × 4
   transcript   logFC logCPM     FDR
   <chr>        <dbl>  <dbl>   <dbl>
 1 TSPAN6     -0.390    5.06 0.00283
 2 DPM1        0.198    4.61 0.0770 
 3 SCYL3       0.0292   3.48 0.844  
 4 C1orf112   -0.124    1.47 0.682  
 5 CFH         0.417    8.09 0.00376
 6 FUCA2      -0.250    5.91 0.0186 
 7 GCLC       -0.0581   4.84 0.794  
 8 NFYA       -0.509    4.13 0.00126
 9 STPG1      -0.136    3.12 0.478  
10 NIPAL3     -0.0500   7.04 0.695  
# ℹ 15,916 more rows
#or 
select(dexp, transcript, starts_with("log") | ends_with("r"))
# A tibble: 15,926 × 4
   transcript   logFC logCPM     FDR
   <chr>        <dbl>  <dbl>   <dbl>
 1 TSPAN6     -0.390    5.06 0.00283
 2 DPM1        0.198    4.61 0.0770 
 3 SCYL3       0.0292   3.48 0.844  
 4 C1orf112   -0.124    1.47 0.682  
 5 CFH         0.417    8.09 0.00376
 6 FUCA2      -0.250    5.91 0.0186 
 7 GCLC       -0.0581   4.84 0.794  
 8 NFYA       -0.509    4.13 0.00126
 9 STPG1      -0.136    3.12 0.478  
10 NIPAL3     -0.0500   7.04 0.695  
# ℹ 15,916 more rows

There are a number of other selection helpers. See the help documentation for select for more information (?dplyr::select()) or this reference from tidyselect.

Select columns of a particular type

There are many other ways to select multiple columns. You may commonly be interested in selecting all numeric columns or all factors. The syntax below can be used for this purpose.

select(dexp, where(is.numeric)) #or
# A tibble: 15,926 × 5
     logFC logCPM       F   PValue     FDR
     <dbl>  <dbl>   <dbl>    <dbl>   <dbl>
 1 -0.390    5.06 32.8    0.000312 0.00283
 2  0.198    4.61  6.90   0.0281   0.0770 
 3  0.0292   3.48  0.0969 0.763    0.844  
 4 -0.124    1.47  0.377  0.555    0.682  
 5  0.417    8.09 29.3    0.000463 0.00376
 6 -0.250    5.91 14.9    0.00405  0.0186 
 7 -0.0581   4.84  0.167  0.692    0.794  
 8 -0.509    4.13 44.9    0.000100 0.00126
 9 -0.136    3.12  1.04   0.335    0.478  
10 -0.0500   7.04  0.350  0.569    0.695  
# ℹ 15,916 more rows
# Not recommended
select_if(dexp, is.numeric) #scoped verbs are superseded  
# A tibble: 15,926 × 5
     logFC logCPM       F   PValue     FDR
     <dbl>  <dbl>   <dbl>    <dbl>   <dbl>
 1 -0.390    5.06 32.8    0.000312 0.00283
 2  0.198    4.61  6.90   0.0281   0.0770 
 3  0.0292   3.48  0.0969 0.763    0.844  
 4 -0.124    1.47  0.377  0.555    0.682  
 5  0.417    8.09 29.3    0.000463 0.00376
 6 -0.250    5.91 14.9    0.00405  0.0186 
 7 -0.0581   4.84  0.167  0.692    0.794  
 8 -0.509    4.13 44.9    0.000100 0.00126
 9 -0.136    3.12  1.04   0.335    0.478  
10 -0.0500   7.04  0.350  0.569    0.695  
# ℹ 15,916 more rows

Subsetting by row (filter())

To subset by row, we use the function filter().

filter() only includes rows where the condition is TRUE; it excludes both FALSE and NA values. ---R4DS

Now let's filter the rows from smeta based on a condition. Let's look at only the treated samples in dex (i.e., trt) using the function filter(). The first argument is the data frame (e.g., smeta) followed by the expression(s) to filter the data frame.

filter(smeta, dex == "trt") #we've seen == notation before

To complete these filter phrases you will often need to include comparison operators such as the == above. These operators help us evaluate relations. For example, a == b is asking if a and b are equivalent. It is a logical comparison that when evaluated will return TRUE or FALSE. The filter function will then return rows that evaluate to TRUE.

Try the following:

a <- 1
b <- 1
a == b
[1] TRUE

Keep these comparison operators in mind for filtering.

Comparison operators

Comparison Operator Description
> greater than
>= greater than or equal to
< less than
<= less than or equal to
!= Not equal
== equal
a | b a or b
a & b a and b

We may want to combine filtering parameters using AND or OR phrasing and the operators & and |.

For example, if we only wanted to return rows where dex == trt and cell==N61311, we can use:

filter(smeta, dex == "trt" & cell == "N61311")
# A tibble: 1 × 9
  SampleName cell   dex   albut Run        avgLength Experiment Sample BioSample
  <chr>      <chr>  <chr> <chr> <chr>          <dbl> <chr>      <chr>  <chr>    
1 GSM1275863 N61311 trt   untrt SRR1039509       126 SRX384346  SRS50… SAMN0242…

A , is treated the same as & in the case of filter().

filter(smeta, dex == "trt", cell == "N61311")
# A tibble: 1 × 9
  SampleName cell   dex   albut Run        avgLength Experiment Sample BioSample
  <chr>      <chr>  <chr> <chr> <chr>          <dbl> <chr>      <chr>  <chr>    
1 GSM1275863 N61311 trt   untrt SRR1039509       126 SRX384346  SRS50… SAMN0242…

We can also filter by one condition or another using the |.

filter(smeta,cell == "N080611" | cell == "N61311")
# A tibble: 4 × 9
  SampleName cell    dex   albut Run       avgLength Experiment Sample BioSample
  <chr>      <chr>   <chr> <chr> <chr>         <dbl> <chr>      <chr>  <chr>    
1 GSM1275862 N61311  untrt untrt SRR10395…       126 SRX384345  SRS50… SAMN0242…
2 GSM1275863 N61311  trt   untrt SRR10395…       126 SRX384346  SRS50… SAMN0242…
3 GSM1275870 N080611 untrt untrt SRR10395…       120 SRX384353  SRS50… SAMN0242…
4 GSM1275871 N080611 trt   untrt SRR10395…       126 SRX384354  SRS50… SAMN0242…

The %in% operator

Used to match elements of a vector.

%in% returns a logical vector indicating if there is a match or not for its left operand. --- match R Documentation.

The returned logical vector will be the length of the vector to the left. Its basic usage:

smeta$SampleName %in% c("GSM1275871","GSM1275863")  
[1] FALSE  TRUE FALSE FALSE FALSE  TRUE FALSE FALSE
c("GSM1275871","GSM1275863") %in% smeta$SampleName 
[1] TRUE TRUE

We can combine the %in% operator with filter().

#filter for two cell lines
filter(smeta,cell %in% c("N061011", "N052611"))
# A tibble: 4 × 9
  SampleName cell    dex   albut Run       avgLength Experiment Sample BioSample
  <chr>      <chr>   <chr> <chr> <chr>         <dbl> <chr>      <chr>  <chr>    
1 GSM1275866 N052611 untrt untrt SRR10395…       126 SRX384349  SRS50… SAMN0242…
2 GSM1275867 N052611 trt   untrt SRR10395…        87 SRX384350  SRS50… SAMN0242…
3 GSM1275874 N061011 untrt untrt SRR10395…       101 SRX384357  SRS50… SAMN0242…
4 GSM1275875 N061011 trt   untrt SRR10395…        98 SRX384358  SRS50… SAMN0242…

Including multiple phrases

We can use multiple expressions in a single call to filter(). For example, let's filter dexp to include only named transcripts (i.e.,no NAs), values of |log fold change| is greater than 2, and either a p-value or FDR corrected p_value is less than or equal to 0.01.

#use `|` operator 
#look at only results with named genes (not NAs) 
#and those with a log fold change greater than 2 
#and either a p-value or an FDR corrected p_value < or = to 0.01
#The comma acts as &
sig_annot_transcripts<-
  filter(dexp, !is.na(transcript),
         abs(logFC) > 2, (PValue | FDR <= 0.01))
sig_annot_transcripts
# A tibble: 178 × 10
   feature     albut transcript ref_genome .abundant logFC  logCPM     F  PValue
   <chr>       <chr> <chr>      <chr>      <lgl>     <dbl>   <dbl> <dbl>   <dbl>
 1 ENSG000000… untrt PDK4       hg38       TRUE       2.55  5.41    37.5 1.94e-4
 2 ENSG000000… untrt SLC7A14    hg38       TRUE      -2.89  3.54   277.  6.13e-8
 3 ENSG000000… untrt NPC1L1     hg38       TRUE      -2.61 -0.0372  63.6 2.65e-5
 4 ENSG000000… untrt CHDH       hg38       TRUE      -2.01  2.14   112.  2.77e-6
 5 ENSG000000… untrt HSD17B6    hg38       TRUE      -2.03  3.02    98.6 4.60e-6
 6 ENSG000000… untrt POU2F2     hg38       TRUE      -2.06  0.835  104.  3.65e-6
 7 ENSG000000… untrt GPM6B      hg38       TRUE       2.43  5.67   177.  4.08e-7
 8 ENSG000000… untrt PER3       hg38       TRUE      -2.21  3.22    80.5 1.04e-5
 9 ENSG000000… untrt COL11A1    hg38       TRUE       2.41  4.06   404.  1.23e-8
10 ENSG000000… untrt FGFR2      hg38       TRUE      -2.26  0.499   60.0 3.31e-5
# ℹ 168 more rows
# ℹ 1 more variable: FDR <dbl>

Filtering across columns

Past versions of dplyr included powerful variants of filter, select, and other functions to help perform tasks across columns. You may see functions such as filter_all, filter_if, and filter_at. Functions like these can still be used but have been superseded by across. However, across has been deprecated in the case of filter and replaced by if_any() and if_all().

Both functions operate similarly to across() but go the extra mile of aggregating the results to indicate if all the results are true when using if_all(), or if at least one is true when using if_any() ---tidyverse.org

Let's briefly see this in action. Let's return only rows with values of less than 0.05 in the columns PValue and FDR.

f<-filter(dexp, if_all(PValue:FDR, ~ .x < 0.05))
f
# A tibble: 4,967 × 10
   feature     albut transcript ref_genome .abundant  logFC logCPM     F  PValue
   <chr>       <chr> <chr>      <chr>      <lgl>      <dbl>  <dbl> <dbl>   <dbl>
 1 ENSG000000… untrt TSPAN6     hg38       TRUE      -0.390   5.06 32.8  3.12e-4
 2 ENSG000000… untrt CFH        hg38       TRUE       0.417   8.09 29.3  4.63e-4
 3 ENSG000000… untrt FUCA2      hg38       TRUE      -0.250   5.91 14.9  4.05e-3
 4 ENSG000000… untrt NFYA       hg38       TRUE      -0.509   4.13 44.9  1.00e-4
 5 ENSG000000… untrt SEMA3F     hg38       TRUE      -0.259   4.81 12.3  6.98e-3
 6 ENSG000000… untrt ANKIB1     hg38       TRUE      -0.236   6.38 14.5  4.41e-3
 7 ENSG000000… untrt RAD52      hg38       TRUE      -0.319   3.13  9.03 1.53e-2
 8 ENSG000000… untrt LASP1      hg38       TRUE       0.388   8.39 22.7  1.11e-3
 9 ENSG000000… untrt SNX11      hg38       TRUE       0.395   3.56 18.7  2.05e-3
10 ENSG000000… untrt TMEM176A   hg38       TRUE       0.357   4.65 12.1  7.30e-3
# ℹ 4,957 more rows
# ℹ 1 more variable: FDR <dbl>

Anonymous function

The code above includes an anonymous function. Read more here. You may also find this Stack Overflow post useful.
Therefore, the above line could have been written as follows: This function could be written like this:

my_func <- function(x) {
  x < 0.05
}

filter(dexp, if_all(PValue:FDR, my_func))
# A tibble: 4,967 × 10
  feature     albut transcript ref_genome .abundant  logFC logCPM     F  PValue
  <chr>       <chr> <chr>      <chr>      <lgl>      <dbl>  <dbl> <dbl>   <dbl>
1 ENSG000000… untrt TSPAN6     hg38       TRUE      -0.390   5.06 32.8  3.12e-4
2 ENSG000000… untrt CFH        hg38       TRUE       0.417   8.09 29.3  4.63e-4
3 ENSG000000… untrt FUCA2      hg38       TRUE      -0.250   5.91 14.9  4.05e-3
4 ENSG000000… untrt NFYA       hg38       TRUE      -0.509   4.13 44.9  1.00e-4
5 ENSG000000… untrt SEMA3F     hg38       TRUE      -0.259   4.81 12.3  6.98e-3
6 ENSG000000… untrt ANKIB1     hg38       TRUE      -0.236   6.38 14.5  4.41e-3
7 ENSG000000… untrt RAD52      hg38       TRUE      -0.319   3.13  9.03 1.53e-2
8 ENSG000000… untrt LASP1      hg38       TRUE       0.388   8.39 22.7  1.11e-3
9 ENSG000000… untrt SNX11      hg38       TRUE       0.395   3.56 18.7  2.05e-3
10 ENSG000000… untrt TMEM176A   hg38       TRUE       0.357   4.65 12.1  7.30e-3
# ℹ 4,957 more rows
# ℹ 1 more variable: FDR <dbl>

Subsetting rows by position

There are times when you may want to subset your data by position, for example, the first or last number of rows. There are a series of functions in the tidyverse that facilitate this type of subsetting. The primary function is slice(), which has several commonly used helper functions including slice_head(), slice_tail(), slice_min(), and slice_max(). See the slice() documentation for more information.

Introducing the pipe

Often we will apply multiple functions to wrangle a data frame into the state that we need it. For example, maybe you want to select and filter. What are our options? We could run one step after another, saving an object for each step, or we could nest a function within a function, but these can affect code readability and clutter our work space, making it difficult to follow what we or someone else did.

Step by Step

#Run one step at a time with intermediate objects. 
#We've done this a few times above
#select gene, logFC, FDR
dexp_s<-select(dexp, transcript, logFC, FDR)

#Now filter for only the genes "TSPAN6" and DPM1
#Note: we could have used %in%
tspanDpm<- filter(dexp_s, transcript == "TSPAN6" | transcript=="DPM1") 

Nesting Code

#Nested code example
tspanDpm<- filter(select(dexp, c(transcript, logFC, FDR)), 
                  transcript == "TSPAN6" | transcript=="DPM1" )

Using the pipe (%>%,|>)

Let's explore how piping streamlines this. Piping (using %>%) allows you to employ multiple functions consecutively, while improving readability. The output of one function is passed directly to another without storing the intermediate steps as objects. You can pipe from the beginning (reading in the data) all the way to plotting without storing the data or intermediate objects, if you want. You can use either the magrittr pipe (%>%), which loads with the tidyverse, or the native R pipe (|>, R version +4.1).

!!! info %>% vs |> These pipes behave in largely the same way. However, %>% does have some special behaviors. You can read more here

To pipe, we have to first call the data and then pipe it into a function. The output of each step is then piped into the next step.

Let's see how this works

 dexp %>% #call the data and pipe to select()
  select(transcript, logFC, FDR) |> #select columns of interest 
  filter(transcript == "TSPAN6" | transcript=="DPM1" ) #filter 
# A tibble: 2 × 3
  transcript  logFC     FDR
  <chr>       <dbl>   <dbl>
1 TSPAN6     -0.390 0.00283
2 DPM1        0.198 0.0770 

Notice that the data argument has been dropped from select() and filter(). This is because the pipe passes the input from the left to the right. The %>% must be at the end of each line.

Piping from the beginning:

read_delim("./data/diffexp_results_edger_airways.txt") |> #read data
  select(transcript, logFC, FDR) |> #select columns of interest 
  filter(transcript == "TSPAN6" | transcript=="DPM1" ) |> #filter 
  ggplot(aes(x=transcript,y=logFC,fill=FDR)) + #plot
  geom_bar(stat = "identity") +
  theme_classic() + 
  geom_hline(yintercept=0, linetype="dashed", color = "black")
Rows: 15926 Columns: 10
── Column specification ────────────────────────────────────────────────────────
Delimiter: "\t"
chr (4): feature, albut, transcript, ref_genome
dbl (5): logFC, logCPM, F, PValue, FDR
lgl (1): .abundant

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Note

ggplot2 will be covered in Part 3 of this course.

The dplyr functions by themselves are somewhat simple, but by combining them into linear workflows with the pipe, we can accomplish more complex manipulations of data frames. ---datacarpentry.org

Acknowledgments

Some material from this lesson was either taken directly or adapted from the Intro to R and RStudio for Genomics lesson provided by datacarpentry.org. Additional content was inspired by Chapter 3, Wrangling Data in the Tidyverse, from Tidyverse Skills for Data Science and Suzan Baert's dplyr tutorials.