Section 1 of 11

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

Which line sorts patients so the highest BMI is on top?

Pre-test

By default, arrange(age) orders the rows how?

Pre-test

Which line tells you how many patients fall into each smoking-status group?

Pre-test

In the tibble returned by count(sex), what does the column n contain?

Pre-test

Which verb adds a new column while leaving every existing column in place?

Pre-test

You pipe patients into mutate() to add an obese column but do not assign the result. What happens to the patients tibble?

Pre-test

Which verb and helper would you use to label patients as "underweight", "normal", "overweight", or "obese" by BMI?

Pre-test

In case_when(), when several rules could match a row, which rule's value is used?

Pre-confidence

I can use arrange() with desc() to sort a table in either direction, and count() to tally how many patients fall in each group.

Not at all confident
Fully confident
Pre-confidence

I can use mutate() to add a new clinical variable computed from existing columns, and explain that a dplyr verb returns a new data frame rather than changing the original.

Not at all confident
Fully confident
Pre-confidence

I can use if_else() for a single two-way rule and case_when() for several ranked rules to label patients, and assign the result back with <- to keep it.

Not at all confident
Fully confident
Section 2 of 11

2 Introduction

In Part I you kept the rows and columns you wanted with filter() and select(), and joined your steps with the native pipe |>. Now you finish the toolkit. A trimmed table still needs to be read: you want the rows in a sensible order, a quick tally of how many patients fall in each group, and brand-new variables like a BMI category that were never in the raw file. This part gives you the verbs that do exactly that.

This part of the module covers three tools you will reach for in almost every analysis:

  • arrange() and count() — sort the rows into order, and tally how many fall in each group.
  • mutate() — derive a brand-new clinical variable from the columns you already have.
  • if_else() and case_when() — write one yes/no rule, or several ranked rules, to label each patient.

By the end of this part you will be able to sort a dataset by any column in either direction, tally patients into group counts, and add a derived clinical variable such as an obesity flag or a BMI category — then chain all of these steps into one readable pipeline.

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

Section 3 of 11

3 arrange() sorts rows while count() tallies them

Once you have the rows and columns you want, the two more verbs help you read the table are arrange() and count(). One orders the rows; the other summarises them into group counts.

Section 3.1 of 11

3.1 arrange(): sort the rows

arrange() sorts the rows by a column. arrange(age) orders patients from youngest to oldest — ascending order, the default. To sort the other way, wrap the column in desc(): arrange(desc(bmi)) puts the highest BMI first.

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 |>
    arrange(desc(bmi))
arrange() sorts the whole table by one column from smallest to largest by default, and wrapping that column in desc() reverses the order so the row with the highest value — here the largest BMI — rises to the top.
arrange() sorts the whole table by one column from smallest to largest by default, and wrapping that column in desc() reverses the order so the row with the highest value — here the largest BMI — rises to the top.
Section 3.2 of 11

3.2 count(): tally how many fall in each group

count() tallies the rows. count(sex) returns one row per sex with a column n giving how many patients have each value. It is the fastest way to answer "how many in each group?".

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 |>
    count(smoker)
count(smoker) sorts every patient into one pile per smoking status and reports the size of each pile as the column n, collapsing a long column of repeated values into one tidy row per group — the fastest way to answer "how many in each group?".
count(smoker) sorts every patient into one pile per smoking status and reports the size of each pile as the column n, collapsing a long column of repeated values into one tidy row per group — the fastest way to answer "how many in each group?".

You can build a tiny tibble by hand to see exactly how a count works. Here are six recorded smoking statuses; counting how many are "yes" is just summing a logical test.

Section 4 of 11

4 mutate() derives new clinical variables

Filtering, selecting, and sorting only ever show you data that is already there. mutate() is different: it adds a new column computed from existing ones, leaving every original column in place. This is how you derive the clinical variables your analysis actually needs.

mutate(bmi_rounded = round(bmi, 1)) adds a column called bmi_rounded. The name on the left is new; the expression on the right is computed for every row. You can derive several columns in one mutate() call, separated by commas.

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 |>
    mutate(bmi_rounded = round(bmi, 1))
mutate() adds a brand-new column to the table — the name on the left of the = is the new column, the expression on the right is computed for every row, and every original column stays exactly where it was.
mutate() adds a brand-new column to the table — the name on the left of the = is the new column, the expression on the right is computed for every row, and every original column stays exactly where it was.
Section 4.1 of 11

4.1 if_else(): one yes/no rule

For a single two-way rule, use if_else(). You give it a condition, the value to use when it is TRUE, and the value when it is FALSE. if_else(bmi >= 30, "obese", "not obese") labels each patient by one threshold.

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 |>
    mutate(obese = if_else(bmi >= 30, "obese", "not obese"))
if_else() evaluates one condition per row and returns a single value when it is TRUE and a single value when it is FALSE, so every row is sorted into exactly one of two mutually exclusive outcomes.
if_else() evaluates one condition per row and returns a single value when it is TRUE and a single value when it is FALSE, so every row is sorted into exactly one of two mutually exclusive outcomes.
Section 4.2 of 11

4.2 case_when(): several rules at once

When you need more than two categories, reach for case_when(). You write each rule as condition ~ value, and R checks them top to bottom, using the first one that is TRUE. This is how you build a BMI category from the standard cut-points.

case_when() stops at the first matching rule, so order the rules from lowest cut-point upward and they will not overlap. End with TRUE ~ "..." as a catch-all for anything left over.

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 |>
    mutate(bmi_cat = case_when(
    bmi < 18.5 ~ "underweight",
    bmi < 25 ~ "normal",
    bmi < 30 ~ "overweight",
    TRUE ~ "obese"
  ))
case_when() checks its condition ~ value rules from top to bottom and assigns the value of the first rule that is TRUE, so you order the cut-points from lowest upward to keep the bands from overlapping and finish with TRUE ~ "..." as a catch-all for everything that slips past.
case_when() checks its condition ~ value rules from top to bottom and assigns the value of the first rule that is TRUE, so you order the cut-points from lowest upward to keep the bands from overlapping and finish with TRUE ~ "..." as a catch-all for everything that slips past.

Here is the trap that catches everyone once. A dplyr verb returns a NEW data frame and never changes the original. Running patients |> mutate(...) prints a changed table, but patients itself is untouched. To keep the result, assign it back with <-, or keep piping into the next step.

Now feel the payoff of the pipe: chain three verbs into one readable pipeline. Read it aloud as "take patients, THEN keep ages over 50, THEN keep three columns, THEN sort by BMI".

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 > 50) |>
  select(patient_id, age, bmi) |>
  arrange(desc(bmi))
A pipe chain reads aloud as "take patients, THEN filter, THEN select, THEN arrange," and because every verb returns a brand-new data frame the original patients table is never touched — to keep a result you must assign it back with <- or keep piping.
A pipe chain reads aloud as "take patients, THEN filter, THEN select, THEN arrange," and because every verb returns a brand-new data frame the original patients table is never touched — to keep a result you must assign it back with <- or keep piping.
Section 5 of 11

5 Put it together

Now chain the verbs into one analysis. The worked example below writes each step as result <- verb(result, ...), which runs the verbs in order exactly as a |> pipeline would. It fades the support as you go: first you study a full solution, then you fill the gaps, then you solve a fresh one on your own.

Worked example · Filter, derive, and sort in three steps

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.csv, keep only patients aged over 50, add a column flagging obesity (BMI of 30 or more), and sort the result so the highest BMI is on top.

Stage 1 · Study the solved example
Fully solved solution
library(dplyr)
library(readr)
patients <- read_csv("patients.csv")
result <- filter(patients, age > 50)
result <- mutate(result, obese = if_else(bmi >= 30, "obese", "not obese"))
result <- arrange(result, desc(bmi))
result
Walk-through
  1. Read patients.csv into a tibble and switch on dplyr
  2. filter() keeps only the rows where age is over 50
  3. mutate() adds the obese flag with one if_else() rule
  4. arrange() with desc() sorts so the highest BMI is first
A worked example fades its support in three stages — first you study the fully solved filter -> mutate -> arrange pipeline, then you fill in a blanked-out verb, then you write the whole pipeline yourself — so the scaffolding disappears exactly as fast as your confidence grows.
A worked example fades its support in three stages — first you study the fully solved filter -> mutate -> arrange pipeline, then you fill in a blanked-out verb, then you write the whole pipeline yourself — so the scaffolding disappears exactly as fast as your confidence grows.

This Parsons problem gives you the right lines in the wrong order, plus a few lines that do not belong. Each line reassigns result, so the ordering is what matters: you must filter before you sort. Drag the correct lines into order and leave the wrong ones in the bank.

Parsons problem · Order a filter, mutate, arrange sequence

All the lines you need are in the Line bank on the left — some may be distractors you should leave behind. Drag the lines you need into the Your solution column on the right, in the correct order, then click Check.

Task: Switch on the packages and read patients.csv, then keep ages over 60, add a BMI category with case_when(), and sort by that category. Each step reassigns result.

Line bank
  • result <- mutate(result, bmi_cat = if_else(bmi < 25, bmi < 30, "obese"))
  • result <- select(patients, age > 60)
  • result <- filter(patients) > 60
  • result <- arrange(result, bmi_cat)
  • result <- mutate(result, bmi_cat = case_when(bmi < 25 ~ "normal", bmi < 30 ~ "overweight", TRUE ~ "obese"))
  • result <- filter(patients, age > 60)
  • library(readr)
  • patients <- read_csv("patients.csv")
  • library(dplyr)
Your solution
  • Drop lines here, in order.
Section 6 of 11

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

Which line sorts patients so the highest BMI is on top?

Post-test

By default, arrange(age) orders the rows how?

Post-test

Which line tells you how many patients fall into each smoking-status group?

Post-test

In the tibble returned by count(sex), what does the column n contain?

Post-test

Which verb adds a new column while leaving every existing column in place?

Post-test

You pipe patients into mutate() to add an obese column but do not assign the result. What happens to the patients tibble?

Post-test

Which verb and helper would you use to label patients as "underweight", "normal", "overweight", or "obese" by BMI?

Post-test

In case_when(), when several rules could match a row, which rule's value is used?

Post-confidence

I can use arrange() with desc() to sort a table in either direction, and count() to tally how many patients fall in each group.

Not at all confident
Fully confident
Post-confidence

I can use mutate() to add a new clinical variable computed from existing columns, and explain that a dplyr verb returns a new data frame rather than changing the original.

Not at all confident
Fully confident
Post-confidence

I can use if_else() for a single two-way rule and case_when() for several ranked rules to label patients, and assign the result back with <- to keep it.

Not at all confident
Fully confident
Section 7 of 11

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)