Section 1 of 6

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

You want one row per treatment arm holding that arm's mean HbA1c. Which pair of verbs does this?

Pre-test

The name for the grouped-summary pattern is split-apply-combine. Which step does summarise() perform?

Pre-test

Why should you add n() inside summarise() when you report a group mean?

Pre-test

You run summarise(mean_hba1c = mean(hba1c)) on the clinic table where hba1c has some missing values, and every result comes back as NA. What is the fix?

Pre-test

A demographics table has all 100 patients; a labs table has only the 80 who returned. You must keep every patient in the result. Which join do you use?

Pre-test

You inner_join() a demographics table with a labs table. What happens to a patient who has no lab result?

Pre-test

In left_join(demographics, labs, by = "patient_id"), what does the by argument name?

Pre-test

You meant to keep every patient but accidentally used inner_join() instead of left_join() on a labs table missing some patients. What is the clinical consequence?

Pre-confidence

I can produce a per-group summary table with group_by() and summarise() using the split-apply-combine pattern, and include a count with n() beside every mean.

Not at all confident
Fully confident
Pre-confidence

I can pass na.rm = TRUE to mean() inside summarise() so a group summary is not silently returned as NA when values are missing.

Not at all confident
Fully confident
Pre-confidence

I can choose left_join() versus inner_join() on a shared key so no patient silently disappears from my sample.

Not at all confident
Fully confident
Section 2 of 6

2 Introduction

In Part II you reshaped a single table one row and one column at a time — you filtered, selected, and added variables with mutate(). That is enough when the answer lives inside one table. Real clinical questions rarely do. You need the mean HbA1c per treatment arm, and a demographics table stitched to a labs table so every result carries its patient's age. This part gives you those two moves.

This part of the module covers two wrangling skills you reach for in almost every analysis:

  • Grouped summaries — group_by() then summarise() to collapse many rows into one row per group, such as the mean HbA1c per treatment arm, reported beside a count.
  • Joins — left_join() and inner_join() to combine a demographics table with a labs table on a shared key, keeping the rows you intend to keep.

By the end of this part you will be able to produce a per-group summary table with counts using the split-apply-combine pattern, and choose the right join so no patient silently disappears from your sample.

Try every snippet in the R Scratchpad on the right — the dataset diabetes_clinic.csv is already loaded for the grouped summaries, and you can build the small demographics and labs tables yourself with tibble() to watch each join behave.

Section 3 of 6

3 Grouped summaries: split, apply, combine

A grouped summary collapses many rows into one row per group. You name the grouping variable with group_by(), then compute one number per group with summarise(). The pattern has a name: split-apply-combine — split the rows into groups, apply a calculation to each, combine the results into a small table.

A grouped summary uses split-apply-combine, splitting rows into groups with group_by(), computing one number per group with summarise(), and combining those results into one row per group.
A grouped summary uses split-apply-combine, splitting rows into groups with group_by(), computing one number per group with summarise(), and combining those results into one row per group.

Here is the question made concrete: what is the mean HbA1c in each treatment arm of the diabetes clinic? You split by treatment, apply mean() to each arm's hba1c, and get one row per arm back.

Two details matter. First, this course uses the native pipe |>, which feeds the table on its left into the function on its right. Second, hba1c has missing values, so you must pass na.rm = TRUE to mean() or the answer comes back as NA.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
clinic <- readr::read_csv("diabetes_clinic.csv")
clinic |>
  group_by(treatment) |>
  summarise(mean_hba1c = mean(hba1c, na.rm = TRUE))
Grouping a data frame by a column and summarising splits the rows into groups, applies an aggregating function like mean() to each group (with na.rm = TRUE to ignore missing values), and returns one summary row per group.
Grouping a data frame by a column and summarising splits the rows into groups, applies an aggregating function like mean() to each group (with na.rm = TRUE to ignore missing values), and returns one summary row per group.

Add n() inside summarise() to count the rows in each group. A summary without a count is dangerous: a mean of 8.9 from two patients is not the same evidence as a mean of 8.9 from two hundred. Always report the count beside the mean.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
clinic <- readr::read_csv("diabetes_clinic.csv")
clinic |>
  group_by(treatment) |>
  summarise(
    n = n(),
    mean_hba1c = mean(hba1c, na.rm = TRUE)
  )
A group mean is only as trustworthy as the number of observations behind it, so report n() beside every mean.
A group mean is only as trustworthy as the number of observations behind it, so report n() beside every mean.

The classic trap is forgetting that summarise() returns a new, smaller table — one row per group, not the original rows. If you still see one row per patient, you reached for mutate() when you wanted summarise(). mutate() keeps every row; summarise() collapses them.

Section 4 of 6

4 Joins: combining two tables on a shared key

A grouped summary works inside one table. Often your information is split across two. A join combines two tables by matching rows on a shared column called the key — here, patient_id. Picture a one-row-per-patient demographics table and a many-rows-per-patient labs table, and you want each lab result to carry its patient's age and sex.

Build the two small tables so you can watch the join behave:

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
demographics <- tibble(
  patient_id = c("D001", "D002", "D003"),
  age = c(54, 61, 47)
)
labs <- tibble(
  patient_id = c("D001", "D001", "D002"),
  hba1c = c(7.2, 7.5, 8.1)
)
demographics
labs
A join stitches two tables into one by matching rows on a shared key, copying each patient's demographic details onto every lab result that shares their ID.
A join stitches two tables into one by matching rows on a shared key, copying each patient's demographic details onto every lab result that shares their ID.

The two joins you will use most differ in which rows they keep. A left_join() keeps every row of the left (first) table, and pulls in matching columns from the right; patients with no labs stay, with NA where the lab would be. An inner_join() keeps only rows that match in both tables; a patient with no labs is dropped entirely.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
demographics <- tibble(
  patient_id = c("D001", "D002", "D003"),
  age = c(54, 61, 47)
)
labs <- tibble(
  patient_id = c("D001", "D001", "D002"),
  hba1c = c(7.2, 7.5, 8.1)
)
left_join(demographics, labs, by = "patient_id")
A left_join keeps every row of the left table and fills NA where the right table has no match, whereas an inner_join keeps only rows that appear in both tables, so an unmatched record (D003) survives the left_join but is dropped entirely by the inner_join.
A left_join keeps every row of the left table and fills NA where the right table has no match, whereas an inner_join keeps only rows that appear in both tables, so an unmatched record (D003) survives the left_join but is dropped entirely by the inner_join.

Note D003 has no labs. In the left_join() D003 survives with NA for hba1c; in an inner_join() D003 vanishes. That difference is not cosmetic. Reaching for inner_join() when you meant left_join() silently shrinks your denominator — patients with no labs disappear, and your sample looks smaller and healthier than it really is. When in doubt, left_join() keeps everyone and makes missingness visible as NA.

Try it out

Try this snippet in the R Scratchpad on the right.

Try this snippet
library(dplyr)
demographics <- tibble(
  patient_id = c("D001", "D002", "D003"),
  age = c(54, 61, 47)
)
labs <- tibble(
  patient_id = c("D001", "D001", "D002"),
  hba1c = c(7.2, 7.5, 8.1)
)
inner_join(demographics, labs, by = "patient_id")
Am inner_join() keeps only patients with a matching row in the second table, silently shrinking your sample, whereas left_join() keeps every patient and records the missing lab as NA.
Am inner_join() keeps only patients with a matching row in the second table, silently shrinking your sample, whereas left_join() keeps every patient and records the missing lab as NA.
Section 5 of 6

5 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

You want one row per treatment arm holding that arm's mean HbA1c. Which pair of verbs does this?

Post-test

The name for the grouped-summary pattern is split-apply-combine. Which step does summarise() perform?

Post-test

Why should you add n() inside summarise() when you report a group mean?

Post-test

You run summarise(mean_hba1c = mean(hba1c)) on the clinic table where hba1c has some missing values, and every result comes back as NA. What is the fix?

Post-test

A demographics table has all 100 patients; a labs table has only the 80 who returned. You must keep every patient in the result. Which join do you use?

Post-test

You inner_join() a demographics table with a labs table. What happens to a patient who has no lab result?

Post-test

In left_join(demographics, labs, by = "patient_id"), what does the by argument name?

Post-test

You meant to keep every patient but accidentally used inner_join() instead of left_join() on a labs table missing some patients. What is the clinical consequence?

Post-confidence

I can produce a per-group summary table with group_by() and summarise() using the split-apply-combine pattern, and include a count with n() beside every mean.

Not at all confident
Fully confident
Post-confidence

I can pass na.rm = TRUE to mean() inside summarise() so a group summary is not silently returned as NA when values are missing.

Not at all confident
Fully confident
Post-confidence

I can choose left_join() versus inner_join() on a shared key so no patient silently disappears from my sample.

Not at all confident
Fully confident
Section 6 of 6

6 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)