Section 1 of 9

1 Before you start

Before you begin, take a few minutes to check what you already know and how confident you feel. You will see the same questions again at the end of the module — this helps both you and us measure what you have learned. Click an option for every question and confidence rating, then click Next to continue.

Pre-test

What does a dplyr verb such as filter() give you back?

Pre-test

Why can you chain dplyr verbs one after another?

Pre-test

How should you read the native pipe |> when you say a pipeline aloud?

Pre-test

The line patients |> filter(age > 60) is exactly the same call as which of the following?

Pre-test

Which verb keeps only the ROWS of a tibble that meet a condition, such as age over 60?

Pre-test

Which verb keeps only the COLUMNS patient_id, age, and bmi and drops the rest?

Pre-test

You want the patients who are over 60 AND are smokers. Which filter() call is correct?

Pre-test

Why does filter(patients, smoker = "yes") throw an error?

Pre-confidence

I can explain that every dplyr verb takes a data frame and returns one, which is why the verbs can be chained together.

Not at all confident
Fully confident
Pre-confidence

I can read and write a |> pipeline aloud as "take the data, THEN ...", and know it is the same as passing the data as the first argument.

Not at all confident
Fully confident
Pre-confidence

I can use filter() to keep the rows that meet a condition and select() to keep the columns I name, using == (not =) for equality.

Not at all confident
Fully confident
Section 2 of 9

2 Introduction

In Module 1 you loaded a clinical dataset into R and confirmed it came in correctly. Now you start to reshape it. Real data almost never arrives in the shape your analysis needs: you may want one age group, or just a handful of columns. This part gives you the shared rule behind every reshaping tool, the pipe that strings those tools together, and the first two verbs you will reach for.

This part of the module covers three foundations you will use in almost every analysis:

  • One grammar — every dplyr verb takes a data frame as its first argument and returns a new data frame, so verbs chain together.
  • The native pipe — read a pipeline aloud as "take the data, THEN do this, THEN do that".
  • filter() and select() — keep the rows you want and the columns you need.

By the end of this part you will be able to explain why any dplyr verb can feed straight into the next, read and write a |> pipeline aloud as a sequence of steps, and keep exactly the rows and columns you want from a clinical table.

Try every snippet in the R Scratchpad on the right — the dataset patients.csv is already loaded and waiting for you.

Section 3 of 9

3 One grammar: every verb takes a data frame and returns one

Everything in this part comes from one package, dplyr, which gives you a small set of verbs — functions named after the action they do, like filter, select, and mutate. dplyr is part of the tidyverse, the family of packages this course uses throughout.

dplyr is a single tidyverse package that gives you a small set of verbs — functions named after the action they perform, so filter() keeps rows, select() keeps columns, and mutate() adds a column.
dplyr is a single tidyverse package that gives you a small set of verbs — functions named after the action they perform, so filter() keeps rows, select() keeps columns, and mutate() adds a column.

The verbs share one rule, and that rule is the whole trick. Every dplyr verb takes a data frame as its first argument and returns a new data frame. The output of one verb is therefore a valid input to the next, so you can chain them into a pipeline that reads like a recipe.

A data frame is just a table: one row per patient, one column per variable. The tidyverse's tidy version of a table is a tibble, which is what read_csv() handed you in Module 1. Verb in, verb out — same shape of object every time.

Every dplyr verb takes a data frame as its first argument and returns a brand-new data frame of the same kind (a tibble), so each verb's output is a ready-made input for the next — verb in, verb out, same shape every time.
Every dplyr verb takes a data frame as its first argument and returns a brand-new data frame of the same kind (a tibble), so each verb's output is a ready-made input for the next — verb in, verb out, same shape every time.

First, switch the package on for the session with library(). You call library(dplyr) once at the top of your script, not before every line.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
library(readr)
patients <- read_csv("patients.csv")
patients
library(dplyr) is a power switch you flip once at the top of a script — it turns the package's verbs on for the whole session rather than line by line — and once it is on, read_csv() loads patients.csv into a tibble you can pipe straight into those verbs.
library(dplyr) is a power switch you flip once at the top of a script — it turns the package's verbs on for the whole session rather than line by line — and once it is on, read_csv() loads patients.csv into a tibble you can pipe straight into those verbs.
Section 4 of 9

4 The native pipe |>: reading a pipeline aloud

Because every verb returns a data frame, you could nest them — but that reads inside-out and quickly becomes unreadable. Instead you use the native pipe, written |>, which takes the result on its left and feeds it as the first argument to the function on its right.

Read |> aloud as the word then. The line patients |> filter(age > 60) says "take patients, THEN filter to ages over 60". The data flows left to right, top to bottom, in the same order you would say the steps out loud.

The native pipe |> takes the result on its left and feeds it as the first argument to the function on its right, so patients |> filter(age > 60) is exactly the same call as filter(patients, age > 60) — read |> aloud as the word "then" and let the data flow left to right.
The native pipe |> takes the result on its left and feeds it as the first argument to the function on its right, so patients |> filter(age > 60) is exactly the same call as filter(patients, age > 60) — read |> aloud as the word "then" and let the data flow left to right.

Writing each step on its own line makes a pipeline read like a numbered procedure. The pipe at the end of a line tells R the pipeline continues onto the next line.

Because the pipe just supplies the data frame as the first argument, patients |> filter(age > 60) does exactly the same thing as writing the data frame inside the verb: filter(patients, age > 60). The pipeline form is easier to read once you chain several steps, so prefer it — but the two are interchangeable, and you will see both.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
library(readr)
patients <- read_csv("patients.csv")
patients |>
    filter(age > 60)
Writing each verb on its own line turns a pipeline into a numbered procedure you read top to bottom, and the trailing |> at the end of a line is the signal that tells R the recipe continues onto the next step.
Writing each verb on its own line turns a pipeline into a numbered procedure you read top to bottom, and the trailing |> at the end of a line is the signal that tells R the recipe continues onto the next step.
Section 5 of 9

5 filter() keeps rows while select() keeps columns

The filter() and select() are two verbs you will use most often do opposite jobs. One trims the table down the page; the other trims it across. Keeping them straight is the single most useful habit in this part.

Section 5.1 of 9

5.1 filter(): keep the ROWS that match a condition

filter() keeps the rows where a condition is TRUE and drops the rest. You give it a test such as age > 60 or smoker == "yes". Note the double equals == for "is equal to" — a single = means assignment, not a comparison.

Combine conditions with & for "and" and | for "or". filter(age > 60 & smoker == "yes") keeps patients who are both over 60 and smokers.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
library(readr)
patients <- read_csv("patients.csv")
patients |>
    filter(age > 60, smoker == "yes")
filter() tests its condition on every row and keeps only those where it is TRUE, dropping the rest — write == (not =) for equality, and separate conditions with a comma to require them all (AND).
filter() tests its condition on every row and keeps only those where it is TRUE, dropping the rest — write == (not =) for equality, and separate conditions with a comma to require them all (AND).

Watch the ==. Writing filter(smoker = "yes") with one equals is a classic trap — R reads it as trying to set an argument named smoker and throws an error. Use == whenever you mean "is equal to".

Debug & fix

The code below is broken. Type a fixed version into the editor, then click Run & check. Success means your code runs without errors. Use Show hint only if you get stuck.

Broken code (do not copy verbatim)
library(dplyr)
library(readr)
patients <- read_csv("patients.csv")
filter(patients, smoker = "yes")
Inside filter(), a single = makes R try to name an argument and throws an error, while == asks "is this value equal to that?" — so the fix for the classic trap is literally one extra equals sign that turns the question from broken to answerable.
Inside filter(), a single = makes R try to name an argument and throws an error, while == asks "is this value equal to that?" — so the fix for the classic trap is literally one extra equals sign that turns the question from broken to answerable.
Section 5.2 of 9

5.2 select(): keep the columns you name

select() keeps only the columns you list by name and drops the rest. select(patient_id, age, bmi) returns a narrower table with just those three columns, in the order you wrote them.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
library(readr)
patients <- read_csv("patients.csv")
patients |>
    select(patient_id, age, bmi)
select() keeps only the columns you name and discards the rest, returning them in the exact order you listed — trimming the table across (by column), the sideways counterpart to filter() trimming it down (by row).
select() keeps only the columns you name and discards the rest, returning them in the exact order you listed — trimming the table across (by column), the sideways counterpart to filter() trimming it down (by row).

The classic mix-up is using filter() when you meant select(), or the reverse. A mnemonic fixes it: filteR ends in R for Rows; seleCt has a C for Columns.

Let’s try and practise by trimming the table down the page and across in one pipeline.

Worked example · Filter rows, then keep columns

Work through this example in three stages. You unlock each stage only after the tutor confirms the previous one. Each stage removes more of the scaffolding — by the end you are writing it yourself.

Problem: From patients, keep patients older than 50, then keep only their patient_id and diagnosis columns.

Stage 1 · Study the solved example
Fully solved solution
library(dplyr)
library(readr)
patients <- read_csv("patients.csv")
patients |>
    filter(age > 50) |>
    select(patient_id, diagnosis)
Walk-through
  1. filter() keeps the rows where age exceeds 50
  2. select() then keeps only the two named columns
  3. the pipe feeds the filtered table straight into select()
filteR ends in R for Rows and trims the table down the page, while seleCt has a C for Columns and trims it across, so the letter hidden in each verb's own name tells you which way it cuts.
filteR ends in R for Rows and trims the table down the page, while seleCt has a C for Columns and trims it across, so the letter hidden in each verb's own name tells you which way it cuts.
Section 6 of 9

6 Check your understanding

You have reached the end of the module. Try the same questions again — your answers here, paired with your pre-test answers, are how we measure what the module taught you. Answer every question and confidence rating, then click Submit and see results to view your score.

Post-test

What does a dplyr verb such as filter() give you back?

Post-test

Why can you chain dplyr verbs one after another?

Post-test

How should you read the native pipe |> when you say a pipeline aloud?

Post-test

The line patients |> filter(age > 60) is exactly the same call as which of the following?

Post-test

Which verb keeps only the ROWS of a tibble that meet a condition, such as age over 60?

Post-test

Which verb keeps only the COLUMNS patient_id, age, and bmi and drops the rest?

Post-test

You want the patients who are over 60 AND are smokers. Which filter() call is correct?

Post-test

Why does filter(patients, smoker = "yes") throw an error?

Post-confidence

I can explain that every dplyr verb takes a data frame and returns one, which is why the verbs can be chained together.

Not at all confident
Fully confident
Post-confidence

I can read and write a |> pipeline aloud as "take the data, THEN ...", and know it is the same as passing the data as the first argument.

Not at all confident
Fully confident
Post-confidence

I can use filter() to keep the rows that meet a condition and select() to keep the columns I name, using == (not =) for equality.

Not at all confident
Fully confident
Section 7 of 9

7 Your results

Here is how your post-test answers compare with your pre-test answers. The pre/post pairing is the most reliable way to see what this module actually taught you.

Your score

Submit the post-test to see your results.

Muddiest point

What is the one thing from this module that is still unclear to you?

Rate this module

Overall, how would you rate this module?

How likely are you to recommend this module to a peer? (0 = not at all, 10 = extremely likely)