Week 1 — R fundamentals
W1-M01 · Moodle-aligned rewritten exercise
R as calculator
Original lesson: W1-M01 · pr01. Familiarisation (3).r · task 1 [MOODLE-ALIGNED]
What this teaches — concept breakdown
R distinguishes an expression from its printed result. Parentheses govern the order of operations, and the number [1] shown by the Console is a vector index, not part of the answer.
R evaluates parentheses before multiplication/division and then addition/subtraction. The [1] displayed in a Console result denotes the index of the first vector element; it is not part of the arithmetic answer. Compare print(6 + 1) with typing 6 + 1 directly into the Console.
Step-by-step classroom instructions
- Add 6 and 1, then print the result.
- Multiply 2 by the sum of 1 and 3, then print the result.
- Calculate 5 plus 3 times 2, then print the result.
How to teach it
Ask learners to predict each number before executing. Comparing the second and third expression reveals the precedence rules.
Common trap / interpretation warning
Do not interpret R’s console prefix [1] as the result.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Show the result in the Console (or display a ggplot object).
print(6 + 1) # Addition returns 7.
# NOTE: Show the result in the Console (or display a ggplot object).
print(2 * (1 + 3)) # Parentheses evaluated first; returns 8.
# NOTE: Show the result in the Console (or display a ggplot object).
print(5 + 3 * 2) # Multiplication precedes addition: 11.
# ============================================================================
# EXPECTED OUTPUT — Exact, deterministic example:
# [1] 7
# [1] 8
# [1] 11
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact, deterministic example. Use the unmodified example code for comparison.
[1] 7
[1] 8
[1] 11
Function and syntax quick-reference
print [print(x)]: Display an object or calculated value in the R Console.
Use the matching masterclass section for the student starter and source-alignment note.
W1-M02 · Moodle-aligned rewritten exercise
Assignment and reusable results
Original lesson: W1-M02 · pr01. Familiarisation (3).r · task 2 [MOODLE-ALIGNED]
What this teaches — concept breakdown
The assignment arrow stores the result of an expression, so later instructions can reuse it instead of recomputing it. 1:10 creates an inclusive integer sequence.
The assignment operator <- stores a value in an object for subsequent statements. In R, 1:10 includes both endpoints; sum(1:10) is 55. Reusing x differs from recalculating the sum in every line. Check objects with ls(), and explain why names and values are different.
Step-by-step classroom instructions
- Add the integers from 1 to 10 and store the total in x.
- Divide the stored total x by 10 and print the result.
How to teach it
Contrast an object name (x) with the value it currently holds; show how changing x changes subsequent calculations.
Common trap / interpretation warning
ls() is session-dependent and may include other objects.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Assign the computed expression to x for reuse.
x <- sum(1:10) # 1:10 is an inclusive integer sequence; sum gives 55.
# NOTE: Show the result in the Console (or display a ggplot object).
print(x / 10) # Stored result re-used; expected 5.5.
# NOTE: Show the result in the Console (or display a ggplot object).
print(ls()) # List current objects; not the same as printing values.
# ============================================================================
# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# [1] 5.5
# # ls() also lists current object names; that list depends on the R session.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact values where deterministic; otherwise preview. Use the unmodified example code for comparison.
[1] 5.5
# ls() also lists current object names; that list depends on the R session.
Function and syntax quick-reference
sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values. print [print(x)]: Display an object or calculated value in the R Console. ls [ls()]: List names of objects currently in the R environment. <- [name <- expression]: Assign the result of the right-hand expression to an object name.
Use the matching masterclass section for the student starter and source-alignment note.
W1-M03 · Moodle-aligned rewritten exercise
Text, numbers, vectors and indexing
Original lesson: W1-M03 · pr01. Familiarisation (3).r · task 3 and naming notes
What this teaches — concept breakdown
Character values need quotation marks; the L suffix creates an integer literal. A logical mask is a TRUE/FALSE vector that selects entries at matching positions.
A vector typically stores values of a common atomic type; combining character and numeric elements can coerce types. R indexes from 1, unlike Python. A logical condition such as sleep_hours > 6 produces a TRUE/FALSE mask, and x[mask] retains only matching elements.
Step-by-step classroom instructions
- Store a short text label in course_label.
- Store the number of participants as an integer in participants.
- Create a numeric vector containing four fictional sleep durations.
- Print only the sleep durations greater than 6 hours using logical indexing.
How to teach it
Have the learner evaluate sleep_hours > 6 before indexing and explain why R starts indexing at 1.
Common trap / interpretation warning
In sleep_hours[sleep_hours > 6], the condition is applied element by element.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Create a named value or vector that later steps can inspect.
course_label <- "PALS practice" # Character/string is enclosed in quotes.
# NOTE: Create a named value or vector that later steps can inspect.
participants <- 66L # L makes an integer literal.
# NOTE: Create a named value or vector that later steps can inspect.
sleep_hours <- c(6, 8, 7, 5) # c() combines values into a numeric vector.
# NOTE: Show the result in the Console (or display a ggplot object).
print(sleep_hours[1]) # R indices start at 1, not 0.
# NOTE: Show the result in the Console (or display a ggplot object).
print(sleep_hours[sleep_hours > 6]) # Logical indexing -> c(8, 7).
# NOTE: Show the result in the Console (or display a ggplot object).
print(mean(sleep_hours)) # Expected 6.5 hours, for illustrative data.
# ============================================================================
# EXPECTED OUTPUT — Exact, deterministic example:
# [1] 6
# [1] 8 7
# [1] 6.5
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact, deterministic example. Use the unmodified example code for comparison.
[1] 6
[1] 8 7
[1] 6.5
Function and syntax quick-reference
c [c(a, b, ...)]: Combine multiple values into a vector. print [print(x)]: Display an object or calculated value in the R Console. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average. [ ] [x[i]]: Select one or more values by 1-based position or logical mask.
Use the matching masterclass section for the student starter and source-alignment note.
W1-M04 · Moodle-aligned rewritten exercise
Built-in USArrests data frame
Original lesson: W1-M04 · pr01. Familiarisation (3).r · task 5 [MOODLE-ALIGNED]
What this teaches — concept breakdown
USArrests is an R built-in data frame with one row per US state and four crime-rate/urbanisation variables. dim(), names() and head() inspect its structure.
USArrests is a built-in teaching data frame. Each row is a US state and each column a variable. dim(data) reports rows and columns, names(data) returns variable names, and head(data) provides a first look without printing the entire table.
Step-by-step classroom instructions
- Assign the built-in USArrests dataset to an object called crime.
- Print the number of rows and columns in crime.
- Print all column names in crime.
- Print the first six rows of crime.
How to teach it
Distinguish rows (observations) from columns (variables); head() is a preview and does not alter the data.
Common trap / interpretation warning
Built-in datasets are useful for syntax practice, not evidence about present-day conditions.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Assign a built-in teaching data frame to a working object.
crime <- USArrests # Built-in public teaching dataset, no CSV needed.
# NOTE: Show the result in the Console (or display a ggplot object).
print(dim(crime)) # Rows and columns: 50 by 4.
# NOTE: Show the result in the Console (or display a ggplot object).
print(names(crime)) # Murder, Assault, UrbanPop, Rape.
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(crime)) # Display the first six rows.
# ============================================================================
# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# [1] 50 4
# [1] "Murder" "Assault" "UrbanPop" "Rape"
# # head(crime) prints six state rows with four columns.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact values where deterministic; otherwise preview. Use the unmodified example code for comparison.
[1] 50 4
[1] "Murder" "Assault" "UrbanPop" "Rape"
# head(crime) prints six state rows with four columns.
Function and syntax quick-reference
print [print(x)]: Display an object or calculated value in the R Console. dim [dim(x)]: Return dimensions, usually rows and columns. names [names(data)]: Get or assign column or element names. head [head(x, n=6)]: Show the first n elements or rows.
Use the matching masterclass section for the student starter and source-alignment note.
W1-M05 · Moodle-aligned rewritten exercise
Functions, help and extraction
Original lesson: W1-M05 · pr01. Familiarisation (3).r · tasks 6–8 [MOODLE-ALIGNED]
What this teaches — concept breakdown
Functions take arguments and return values. The $ operator accesses a named data-frame column; max() returns its largest value.
A function call has the form function_name(argument, named_argument=value). The $ operator extracts a named column from a data frame; max() finds the largest value. For datasets with missing values, na.rm=TRUE controls whether NA is omitted. Help pages document parameters and outputs.
Step-by-step classroom instructions
- Print the maximum value in the Murder column of crime.
- Print the Assault column (or a relevant element of it) using the dollar operator.
- Open R help for the max function.
How to teach it
Contrast the complete Murder vector with max(crime$Murder), and use ?max to discover optional arguments.
Common trap / interpretation warning
A maximum is a descriptive statistic; it does not identify its row unless you inspect which state attained it.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Show the result in the Console (or display a ggplot object).
print(max(c(2, 4, 6))) # max() needs a vector or individual numbers.
# NOTE: Show the result in the Console (or display a ggplot object).
print(crime$Murder) # $ extracts the column named Murder.
# NOTE: Show the result in the Console (or display a ggplot object).
print(max(crime$Murder)) # Expected 17.4 in USArrests.
# NOTE: Show the result in the Console (or display a ggplot object).
print(max(crime$Assault)) # Expected 337 in USArrests.
# Interactive help: type ?max or help("max") in the RStudio Console.
# ============================================================================
# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# [1] 6
# # print(crime$Murder) shows all 50 values (truncated here).
# [1] 17.4
# [1] 337
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact values where deterministic; otherwise preview. Use the unmodified example code for comparison.
[1] 6
# print(crime$Murder) shows all 50 values (truncated here).
[1] 17.4
[1] 337
Function and syntax quick-reference
print [print(x)]: Display an object or calculated value in the R Console. max [max(x, na.rm=FALSE)]: Return the largest value. c [c(a, b, ...)]: Combine multiple values into a vector.
Use the matching masterclass section for the student starter and source-alignment note.
W1-M06 · Moodle-aligned rewritten exercise
Removing objects
Original lesson: W1-M06 · pr01. Familiarisation (3).r · task 9 [MOODLE-ALIGNED]
What this teaches — concept breakdown
rm() removes an object from the current R environment; it does not delete a CSV or modify the built-in dataset. exists() checks whether a name is still defined.
rm(crime) removes the object only from the active R environment; it does not uninstall a package or delete a CSV file. Saving scripts that recreate objects is essential for reproducibility. exists("crime") tests the object’s presence after removal.
Step-by-step classroom instructions
- Remove the crime object from the current R environment.
- Print whether an object called crime still exists.
How to teach it
Ask what happens if the next command tries to use crime after rm(crime).
Common trap / interpretation warning
Running this section twice without recreating crime can fail because rm(crime) cannot find it.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Remove the named object from the R environment.
rm(crime) # Remove only this named object from Environment.
# NOTE: Show the result in the Console (or display a ggplot object).
print(exists("crime")) # FALSE: object no longer exists.
# ============================================================================
# EXPECTED OUTPUT — Exact, deterministic example:
# [1] FALSE
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact, deterministic example. Use the unmodified example code for comparison.
[1] FALSE
Function and syntax quick-reference
rm [rm(object)]: Remove an object from the current R environment. print [print(x)]: Display an object or calculated value in the R Console. exists [exists("x")]: Check whether an object of the specified name exists.
Use the matching masterclass section for the student starter and source-alignment note.
W1-E01 · Independent ferdo Lab extension
Additional: reproducible report
Original lesson: W1-E01 · ferdo Lab original extension [INDEPENDENT-EXTENSION]
What this teaches — concept breakdown
A research question, a numeric vector and a data frame represent different kinds of information. A small fictional dataset is a safe way to practise reporting.
Distinguish the outcome variable (what is measured), predictor (a possible explanation), units, and a potential confounder (a factor associated with both). Fictional observational values are useful for teaching code but cannot establish a real causal claim.
Step-by-step classroom instructions
- Write a fictional research question and store it as a text value.
- Create a short numeric vector of fictional reaction times.
- Print a small data frame with trial numbers and reaction times.
How to teach it
Separate a question from a claim of causation and make the measurement unit explicit (milliseconds).
Common trap / interpretation warning
Four simulated trials do not support general population conclusions.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Assign the computed result to a named R object.
question <- "Does sleep predict response time?" # A question is not a causal conclusion.
# NOTE: Define an illustrative vector of fictional reaction times.
rt_demo <- c(620, 610, 590, 605) # Fictional reaction times in milliseconds.
# NOTE: Show the result in the Console (or display a ggplot object).
print(data.frame(trial=seq_along(rt_demo), rt_ms=rt_demo))
# EXPECTED OUTPUT — Exact, deterministic example:
# trial rt_ms
# 1 1 620
# 2 2 610
# 3 3 590
# 4 4 605
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact, deterministic example. Use the unmodified example code for comparison.
trial rt_ms
1 1 620
2 2 610
3 3 590
4 4 605
Function and syntax quick-reference
c [c(a, b, ...)]: Combine multiple values into a vector. print [print(x)]: Display an object or calculated value in the R Console. data.frame [data.frame(x=..., y=...)]: Build a rectangular data frame from named columns. seq_along [seq_along(x)]: Create integer indices from 1 through length(x).
Use the matching masterclass section for the student starter and source-alignment note.
Week 2 — Data wrangling and paired change
W2-M01 · Moodle-aligned rewritten exercise
Packages and importing CSV
Original lesson: W2-M01 · pr02. Data Wrangling.r · Part A tasks 1–2 [MOODLE-ALIGNED]
What this teaches — concept breakdown
read.csv() loads rows and columns from disk into R. stringsAsFactors=FALSE keeps text as text; the working directory determines where the relative data/ path begins.
CSV files are plain-text tables until read.csv() imports them as data frames. Relative paths, such as data/participant-info.csv, are resolved from getwd(), not necessarily the script’s location. Check nrow(), names() and head() after importing. The open package uses synthetic records.
Step-by-step classroom instructions
- Import data/participant-info.csv as the data frame pinfo.
- Import data/ahi-cesd.csv as the data frame responses.
- Print the participant and response row counts using nrow().
How to teach it
Identify each file’s unit of observation before joining: one participant-level table versus repeated measurements.
Common trap / interpretation warning
Extract the full package and keep the data/ directory adjacent to the script.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# Run install.packages("tidyverse") ONCE in Console if not installed.
# library(tidyverse) loads dplyr, tidyr, ggplot2 etc. from your R library.
# NOTE: Read an input CSV into a data frame; check the working directory.
pinfo <- read.csv("data/participant-info.csv", stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
responses <- read.csv("data/ahi-cesd.csv", stringsAsFactors=FALSE)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(n_participants=nrow(pinfo), n_responses=nrow(responses)))
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# n_participants n_responses
# # Numeric counts depend on the included fictional public CSV files.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
n_participants n_responses
# Numeric counts depend on the included fictional public CSV files.
Function and syntax quick-reference
read.csv [read.csv("data/file.csv", stringsAsFactors=FALSE)]: Read a comma-separated file into a data frame. print [print(x)]: Display an object or calculated value in the R Console. c [c(a, b, ...)]: Combine multiple values into a vector. nrow [nrow(data)]: Count the number of rows in a table.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M02 · Moodle-aligned rewritten exercise
Join by a compound key
Original lesson: W2-M02 · pr02. Data Wrangling.r · Part A task 3 [MOODLE-ALIGNED]
What this teaches — concept breakdown
A compound join key uses BOTH id and intervention to associate each measurement with the correct participant information. A join can silently lose rows or multiply records if keys are wrong.
A join matches rows using agreed key columns; here both participant id and intervention matter. Check uniqueness on each side before joining: repeated keys may create an unexpected many-to-many expansion. A successful join should be checked by both row count and distinct IDs.
Step-by-step classroom instructions
- Join participant information and responses by both id and intervention.
- Print the row count and number of distinct participants after the join.
How to teach it
Ask why joining only on intervention would create many-to-many matches, and how to check uniqueness of the participant table.
Common trap / interpretation warning
The nrow inequality catches row explosions but does not prove that all records matched correctly.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Join matching records using the specified key columns.
all_data <- dplyr::inner_join(pinfo, responses, by=c("id","intervention"))
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(rows=nrow(all_data), unique_ids=dplyr::n_distinct(all_data$id)))
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(nrow(all_data) <= nrow(responses)) # Key matching must not explode rows.
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# rows unique_ids
# # Two named counts; values depend on the loaded public datasets.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
rows unique_ids
# Two named counts; values depend on the loaded public datasets.
Function and syntax quick-reference
inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in both tables. c [c(a, b, ...)]: Combine multiple values into a vector. print [print(x)]: Display an object or calculated value in the R Console. nrow [nrow(data)]: Count the number of rows in a table. n_distinct [dplyr::n_distinct(x)]: Count distinct values in a vector. stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is not true.
Related original worked example: Two-key inner join with student/exam data — two small source tables, a one-key join (6 rows) versus a two-key join (4 rows), explanations for each excluded record, and aligned instructor/student R scripts. Independent supplement, not a Moodle task.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M03 · Moodle-aligned rewritten exercise
select(): columns
Original lesson: W2-M03 · pr02. Data Wrangling.r · Part B select [MOODLE-ALIGNED]
What this teaches — concept breakdown
select() controls WHICH variables are retained, not which observations. Keeping a small, explicitly named set makes later calculations easier to inspect.
select() chooses variables (columns), while filter() chooses observations (rows). Explicitly naming columns documents the analysis plan and reduces accidental use of unrelated measures. Verify the selection with names() and preserve the intended data types.
Step-by-step classroom instructions
- Select the required analysis columns without removing any rows.
- Print the resulting column names to verify your selection.
How to teach it
Ask learners to explain why the output has nine names but potentially many rows.
Common trap / interpretation warning
select() preserves row count and existing order unless other operations are added.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Choose or rename variables; this does not filter rows.
selected_vars <- dplyr::select(all_data, id, intervention, ahiTotal, cesdTotal,
# NOTE: Continue this R expression with its arguments, values or formatting.
sex, age, educ, income, occasion)
# NOTE: Show the result in the Console (or display a ggplot object).
print(names(selected_vars)) # Exactly nine specified columns.
# ============================================================================
# EXPECTED OUTPUT — Exact, deterministic example:
# [1] "id" "intervention" "ahiTotal" "cesdTotal" "sex"
# [6] "age" "educ" "income" "occasion"
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact, deterministic example. Use the unmodified example code for comparison.
[1] "id" "intervention" "ahiTotal" "cesdTotal" "sex"
[6] "age" "educ" "income" "occasion"
Function and syntax quick-reference
select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame. print [print(x)]: Display an object or calculated value in the R Console. names [names(data)]: Get or assign column or element names.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M04 · Moodle-aligned rewritten exercise
filter(): observations and a source correction
Original lesson: W2-M04 · pr02. Data Wrangling.r · Part B filter, compared with code-book.pdf
What this teaches — concept breakdown
filter() controls WHICH rows are retained using a logical predicate. The verified codebook defines occasion 0 as pretest, 1 as posttest, and 2 as the one-week follow-up.
The uploaded codebook gives occasion=0 as baseline, 1 as posttest, and 2 as one-week follow-up. An inconsistent worksheet shortcut must not override the codebook. The expression occasion %in% c(0,1) keeps both desired times. Verify actual frequency counts with table().
Step-by-step classroom instructions
- Keep only observations at baseline (occasion 0) and posttest (occasion 1).
- Count and print observations for each occasion to check the filter.
How to teach it
Have learners explain %in% c(0,1) as the equivalent of occasion == 0 | occasion == 1.
Common trap / interpretation warning
Do not use 1/2 as pre/post: the original worksheet shortcut contradicts the codebook.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# MOODLE CORRECTION: worksheet says 1/2 are pre/post; codebook is authoritative:
# occasion 0 = pretest; 1 = posttest; 2 = one-week follow-up.
# NOTE: Retain only observations that meet the filter condition.
pre_post <- dplyr::filter(selected_vars, occasion %in% c(0,1))
# NOTE: Show the result in the Console (or display a ggplot object).
print(table(pre_post$occasion)) # Inspect sample size/attrition per occasion.
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# 0 1
# # Counts for baseline and posttest; public fictional data values vary.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
0 1
# Counts for baseline and posttest; public fictional data values vary.
Function and syntax quick-reference
filter [dplyr::filter(data, condition)]: Keep rows satisfying the specified logical condition. c [c(a, b, ...)]: Combine multiple values into a vector. print [print(x)]: Display an object or calculated value in the R Console. table [table(x)]: Count frequencies of unique values. %in% [x %in% c(0,1)]: Return TRUE when each element belongs to the specified set of values.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M05 · Moodle-aligned rewritten exercise
arrange(): rows
Original lesson: W2-M05 · pr02. Data Wrangling.r · Part B arrange [MOODLE-ALIGNED]
What this teaches — concept breakdown
arrange() sorts observations by a column but does not change the values or select a subset. It is especially useful for checking extreme observations.
arrange(x, ahiTotal) sorts rows in ascending AHI order; desc(ahiTotal) reverses the direction. Sorting changes only order, not the number of observations or their measured values. Use head() to verify the smallest or largest entries.
Step-by-step classroom instructions
- Sort the selected observations by ahiTotal from low to high.
- Print the first rows of the sorted data frame.
How to teach it
Compare ascending arrange(..., ahiTotal) with arrange(..., desc(ahiTotal)).
Common trap / interpretation warning
Sorting and filtering are different: arrange() retains all input rows.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Sort rows by the given variable without changing their values.
pre_post_asc <- dplyr::arrange(pre_post, ahiTotal)
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(dplyr::select(pre_post_asc, id, occasion, ahiTotal)))
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# id occasion ahiTotal
# # First six rows after ascending sorting; exact values are data-dependent.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
id occasion ahiTotal
# First six rows after ascending sorting; exact values are data-dependent.
Function and syntax quick-reference
arrange [dplyr::arrange(data, col)]: Sort rows by one or more columns. print [print(x)]: Display an object or calculated value in the R Console. head [head(x, n=6)]: Show the first n elements or rows. select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M06 · Moodle-aligned rewritten exercise
summarise(): overall means
Original lesson: W2-M06 · pr02. Data Wrangling.r · Part B summarise [MOODLE-ALIGNED]
What this teaches — concept breakdown
summarise() collapses a table to aggregate statistics. n() counts rows; mean(..., na.rm=TRUE) ignores missing values for each respective mean.
The arithmetic mean is the sum of available measurements divided by the number of non-missing values included. na.rm=TRUE removes NA values from the calculation; it does not replace them with zero. Different variables may therefore have different effective sample sizes.
Step-by-step classroom instructions
- Calculate overall average AHI and CES-D scores in one summary row.
- Print the completed overall summary table.
How to teach it
Ask whether n() equals the number of NON-MISSING AHI values; discuss why the two could differ.
Common trap / interpretation warning
n() counts all rows, including those with an NA in a measured variable.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Reduce each group to its aggregate statistics.
overall_summary <- dplyr::summarise(pre_post,
# NOTE: Compute an arithmetic mean of the selected numbers.
n=dplyr::n(), mean_ahi=mean(ahiTotal,na.rm=TRUE),
# NOTE: Compute an arithmetic mean of the selected numbers.
mean_cesd=mean(cesdTotal,na.rm=TRUE))
# NOTE: Show the result in the Console (or display a ggplot object).
print(overall_summary)
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# n mean_ahi mean_cesd
# # One row with three summary columns; numbers depend on input CSVs.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
n mean_ahi mean_cesd
# One row with three summary columns; numbers depend on input CSVs.
Function and syntax quick-reference
summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the whole table or each group. n [dplyr::n()]: Return the number of rows in the active dplyr group. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average. print [print(x)]: Display an object or calculated value in the R Console.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M07 · Moodle-aligned rewritten exercise
group_by(): distinct occasion summaries
Original lesson: W2-M07 · pr02. Data Wrangling.r · Part B group_by [MOODLE-ALIGNED]
What this teaches — concept breakdown
group_by(occasion) changes the unit of summarisation to a measurement occasion. A grouped mean is not necessarily the mean within-person change because different people can appear at each time.
group_by(occasion) controls how a later summarise() operates: one summary row per measurement occasion. Counts at baseline and posttest need not be equal. A difference between group means across occasions is not, by itself, a within-person change or a treatment effect.
Step-by-step classroom instructions
- Start a pipe to summarise the sorted pre/post observations.
- Group the piped observations by occasion.
- Compute sample sizes and means separately for each occasion.
How to teach it
Contrast mean(post) - mean(pre) across two unmatched samples with mean(post_i - pre_i) on complete pairs.
Common trap / interpretation warning
If a score contains missing values, mean() without na.rm=TRUE may return NA.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Assign the computed result to a named R object.
occasion_summary <- pre_post_asc |>
# NOTE: Define the groups to be summarised independently.
dplyr::group_by(occasion) |>
# NOTE: Reduce each group to its aggregate statistics.
dplyr::summarise(n=dplyr::n(), mean_ahi=mean(ahiTotal),
# NOTE: Compute an arithmetic mean of the selected numbers.
mean_cesd=mean(cesdTotal), .groups="drop")
# NOTE: Show the result in the Console (or display a ggplot object).
print(occasion_summary)
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# occasion n mean_ahi mean_cesd
# # One summary row per occasion (0 and 1).
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
occasion n mean_ahi mean_cesd
# One summary row per occasion (0 and 1).
Function and syntax quick-reference
group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations. summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the whole table or each group. n [dplyr::n()]: Return the number of rows in the active dplyr group. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average. print [print(x)]: Display an object or calculated value in the R Console. |> [data |> function()]: Pass the preceding result to the next function call.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M08 · Moodle-aligned rewritten exercise
mutate(): new column and scale caution
Original lesson: W2-M08 · pr02. Data Wrangling.r · Part B mutate [MOODLE-ALIGNED]
What this teaches — concept breakdown
mutate() adds a derived column while keeping the observation rows. Subtracting two different scale totals is mathematically possible but may have no validated clinical meaning.
mutate() creates a new column computed from existing columns. The numerical expression cesdTotal - ahiTotal may be useful to demonstrate syntax, but a difference between two unrelated psychometric scales is not automatically a meaningful clinical “sadness” measure.
Step-by-step classroom instructions
- Create a new sadness column by subtracting ahiTotal from cesdTotal.
- Print the first rows of the new variable alongside its inputs.
How to teach it
Ask learners to calculate one row manually and explain why naming a value sadness does not make it a mental-health measure.
Common trap / interpretation warning
This 'sadness' variable is ONLY a syntax exercise; it is not a legitimate clinical index.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Create or recode columns while retaining row-level observations.
pre_post <- dplyr::mutate(pre_post, sadness=cesdTotal-ahiTotal)
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(dplyr::select(pre_post, sadness, cesdTotal, ahiTotal)))
# DO NOT interpret "sadness" clinically: AHI/CES-D scales have different constructs/ranges.
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# sadness cesdTotal ahiTotal
# # First six rows; sadness = cesdTotal - ahiTotal.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
sadness cesdTotal ahiTotal
# First six rows; sadness = cesdTotal - ahiTotal.
Function and syntax quick-reference
mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns. print [print(x)]: Display an object or calculated value in the R Console. head [head(x, n=6)]: Show the first n elements or rows. select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.
Use the matching masterclass section for the student starter and source-alignment note.
W2-M09 · Moodle-aligned rewritten exercise
Recode sex using the codebook
Original lesson: W2-M09 · pr02. Data Wrangling.r · Part B recode, compared with code-book.pdf
What this teaches — concept breakdown
factor() maps numerical category codes to readable labels. The source codebook defines sex=1 as Female and sex=2 as Male, so the levels and labels must align.
The uploaded codebook specifies sex=1 for Female and sex=2 for Male. A reversed factor label silently inverts subgroup interpretations. Use factor(x, levels=c(1,2), labels=c("Female","Male")) and verify frequency counts after recoding.
Step-by-step classroom instructions
- Add a correctly labelled sex variable to the pre/post data frame.
- Use the codebook mapping 1 = Female and 2 = Male in factor().
How to teach it
Inspect table(..., useNA='ifany') and check that all source values map to their intended labels.
Common trap / interpretation warning
Reversing the codebook mappings would silently mislabel participants.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# MOODLE CORRECTION: worksheet labels conflict with source codebook.
# Correct coding in code-book.pdf: 1=Female, 2=Male.
# NOTE: Create or recode columns while retaining row-level observations.
pre_post <- dplyr::mutate(pre_post,
# NOTE: Map numeric category codes to readable factor levels.
sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male")))
# NOTE: Show the result in the Console (or display a ggplot object).
print(table(pre_post$sex_label, useNA="ifany"))
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# Female Male
# # Category frequencies; an NA row appears if unmatched/missing values occur.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
Female Male
# Category frequencies; an NA row appears if unmatched/missing values occur.
Function and syntax quick-reference
mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns. factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered levels and labels. c [c(a, b, ...)]: Combine multiple values into a vector. print [print(x)]: Display an object or calculated value in the R Console. table [table(x)]: Count frequencies of unique values.
Use the matching masterclass section for the student starter and source-alignment note.
W2-E01 · Independent ferdo Lab extension
Additional: paired change and attrition
Original lesson: W2-E01 · ferdo Lab original extension [INDEPENDENT-EXTENSION]
What this teaches — concept breakdown
Pairing requires the SAME participant to have both a baseline (occasion 0) and posttest (occasion 1) measurement. inner_join retains matched pairs and mutate computes each individual's post-minus-pre change.
For paired change, match the same participant’s baseline and posttest values and calculate the difference within each matched pair. Subtracting the two unpaired occasion means is not generally equivalent. Missing follow-ups may introduce attrition and selection bias.
Step-by-step classroom instructions
- Filter baseline records for participants observed before the intervention.
- Filter posttest records for participants observed after the intervention.
- Join the two sets by participant and compute within-person change.
How to teach it
Emphasise that paired change is not the same as subtracting two unpaired occasion means; attrition can bias the matched subset.
Common trap / interpretation warning
Check uniqueness of (id, intervention, occasion); missing follow-ups and NA scores need explicit handling.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Keep baseline observations and preserve identifiers for later pairing.
paired_pre <- dplyr::filter(all_data, occasion==0) |>
# NOTE: Choose or rename variables; this does not filter rows.
dplyr::select(id,intervention,ahi_pre=ahiTotal)
# NOTE: Keep posttest observations and preserve identifiers for later pairing.
paired_post <- dplyr::filter(all_data, occasion==1) |>
# NOTE: Choose or rename variables; this does not filter rows.
dplyr::select(id,intervention,ahi_post=ahiTotal)
# NOTE: Join matching records using the specified key columns.
paired_data <- dplyr::inner_join(paired_pre,paired_post,by=c("id","intervention")) |>
# NOTE: Create or recode columns while retaining row-level observations.
dplyr::mutate(change_ahi=ahi_post-ahi_pre)
# NOTE: Show the result in the Console (or display a ggplot object).
print(dplyr::summarise(paired_data, paired_n=dplyr::n(), mean_change=mean(change_ahi)))
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# paired_n mean_change
# # One row: count of matched pairs and their average ahi_post - ahi_pre.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
paired_n mean_change
# One row: count of matched pairs and their average ahi_post - ahi_pre.
Function and syntax quick-reference
filter [dplyr::filter(data, condition)]: Keep rows satisfying the specified logical condition. select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame. inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in both tables. c [c(a, b, ...)]: Combine multiple values into a vector. mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns. print [print(x)]: Display an object or calculated value in the R Console. summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the whole table or each group. n [dplyr::n()]: Return the number of rows in the active dplyr group. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
Use the matching masterclass section for the student starter and source-alignment note.
Week 3 — AQ-10 scoring and descriptive statistics
W3-M01 · Moodle-aligned rewritten exercise
Read AQ, formats, scoring
Original lesson: W3-M01 · wk3_practical_summarising_data (1).r · Part A task 1 [MOODLE-ALIGNED]
What this teaches — concept breakdown
The AQ-10 practical stores questionnaire responses, item direction (forward/reverse) and scoring rules in three separate CSV files. Punctuation, case and spelling of response labels must agree across files.
AQ responses, question directions and scoring rules are stored in three files. The exact column spellings and key values must be checked before joining. The scoring table connects direction (forward/reverse) and the normalised response category to a 0 or 1 point.
Step-by-step classroom instructions
- Read the question-format lookup file from the data folder.
- Read the scoring-key lookup file from the data folder.
- Read the AQ questionnaire response table from the data folder.
How to teach it
Explain the different roles: AQ.csv holds participant responses; Qformats.csv maps questions to direction; Scoring.csv maps direction+response to 0/1.
Common trap / interpretation warning
The public data are fictional. Do not assume the original Moodle row counts are reproduced there.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Read an input CSV into a data frame; check the working directory.
qformats <- read.csv("data/Qformats.csv", stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
scoring <- read.csv("data/Scoring.csv", stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
aq_responses <- read.csv("data/AQ.csv", stringsAsFactors=FALSE)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(participants=nrow(aq_responses),questions=nrow(qformats),rules=nrow(scoring)))
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# participants questions rules
# # Three named counts; original private AQ source had 66 respondents and 10 items.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
participants questions rules
# Three named counts; original private AQ source had 66 respondents and 10 items.
Function and syntax quick-reference
read.csv [read.csv("data/file.csv", stringsAsFactors=FALSE)]: Read a comma-separated file into a data frame. print [print(x)]: Display an object or calculated value in the R Console. c [c(a, b, ...)]: Combine multiple values into a vector. nrow [nrow(data)]: Count the number of rows in a table.
Use the matching masterclass section for the student starter and source-alignment note.
W3-M02 · Moodle-aligned rewritten exercise
Wide-to-long pivot
Original lesson: W3-M02 · wk3_practical_summarising_data (1).r · Part A task 2 [MOODLE-ALIGNED]
What this teaches — concept breakdown
pivot_longer() converts ten question columns (Q1 to Q10) into two fields, Question and Response, while repeating Id for every item. This changes shape, not substantive answers.
In wide format, each participant occupies one row and Q1–Q10 are separate columns. In long format, each participant-question response occupies one row, with Question and Response fields. This makes scoring and question-level grouping easier.
Step-by-step classroom instructions
- Start turning the wide AQ response table into a long table.
- Select the Q1 through Q10 columns for pivoting.
How to teach it
Break down all_of(paste0('Q',1:10)), names_to and values_to; demonstrate how one participant becomes ten rows.
Common trap / interpretation warning
all_of() requires all Q1–Q10 columns to exist; verify column names and each participant's expected ten items.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Pivot Q1–Q10 columns into long-form question/response rows.
aq_long <- tidyr::pivot_longer(aq_responses,
# NOTE: Generate exact field names Q1 through Q10 without separators.
cols=dplyr::all_of(paste0("Q",1:10)),names_to="Question",values_to="Response")
# NOTE: Show the result in the Console (or display a ggplot object).
print(dim(aq_long)) # Real source: 66*10=660 rows; public demo differs.
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# [1] <participants × 10> <column count after pivot>
# # In original private data, there were 66 × 10 = 660 long-form rows.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
[1] <participants × 10> <column count after pivot>
# In original private data, there were 66 × 10 = 660 long-form rows.
Function and syntax quick-reference
pivot_longer [tidyr::pivot_longer(data, cols=..., names_to=..., values_to=...)]: Reshape many columns into a smaller number of key/value columns. all_of [dplyr::all_of(names)]: Select columns identified by a character vector of names. paste0 [paste0("Q", 1:10)]: Join text and values without separators. print [print(x)]: Display an object or calculated value in the R Console. dim [dim(x)]: Return dimensions, usually rows and columns.
Use the matching masterclass section for the student starter and source-alignment note.
W3-M03 · Moodle-aligned rewritten exercise
Join directions and exact scoring rules
Original lesson: W3-M03 · wk3_practical_summarising_data (1).r · Part A task 3 [MOODLE-ALIGNED]
What this teaches — concept breakdown
The FIRST inner_join attaches forward/reverse question direction by Question. The SECOND join attaches the binary Score by the combined (QFormat, Response) lookup key.
Two matching steps are essential: first attach the forward/reverse direction to each question; then attach its numerical score using the direction and response. Failing to join on both required fields, or mismatching response text, can silently drop or duplicate records.
Step-by-step classroom instructions
- Join each long-format AQ response with its forward/reverse item direction.
- Join each response and direction combination to its 0/1 score.
How to teach it
Explain why response text alone is insufficient: the same response can receive different points depending on item direction.
Common trap / interpretation warning
inner_join can drop unmatched answers; equal row counts alone do not prove one-to-one matching.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Join matching records using the specified key columns.
aq_with_format <- dplyr::inner_join(aq_long,qformats,by="Question")
# NOTE: Join matching records using the specified key columns.
aq_scored <- dplyr::inner_join(aq_with_format,scoring,by=c("QFormat","Response"))
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(nrow(aq_scored)==nrow(aq_long), !anyNA(aq_scored$Score))
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(dplyr::select(aq_scored,Id,Question,Response,QFormat,Score)))
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# Id Question Response QFormat Score
# # First six matched response rows, each with a binary Score (0 or 1).
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
Id Question Response QFormat Score
# First six matched response rows, each with a binary Score (0 or 1).
Function and syntax quick-reference
inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in both tables. c [c(a, b, ...)]: Combine multiple values into a vector. stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is not true. nrow [nrow(data)]: Count the number of rows in a table. anyNA [anyNA(x)]: Check whether an object contains missing values. print [print(x)]: Display an object or calculated value in the R Console. head [head(x, n=6)]: Show the first n elements or rows. select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.
Use the matching masterclass section for the student starter and source-alignment note.
W3-M04 · Moodle-aligned rewritten exercise
Participant total AQ score
Original lesson: W3-M04 · wk3_practical_summarising_data (1).r · Part A task 4 [MOODLE-ALIGNED]
What this teaches — concept breakdown
group_by(Id) makes each participant a separate calculation group. summarise(AQTotal=sum(Score)) adds the ten binary item scores and returns one row per respondent; .groups='drop' clears grouping.
group_by(id) ensures a sum is computed for each respondent rather than over the entire data set. The AQ-10 contains ten binary items, so a complete total has a theoretical range of 0–10. Count missing or unmatched item scores before interpreting totals.
Step-by-step classroom instructions
- Begin a pipe that calculates a total AQ-10 score for each participant.
- Group the scored questionnaire rows by participant ID.
- Sum the scored items separately for each participant.
How to teach it
Explain >=, <=, &, all() and stopifnot(). The range check is necessary but not sufficient to confirm ten valid scored answers.
Common trap / interpretation warning
A total within 0–10 may still come from fewer than ten scored questions. Check item counts per Id.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Compute one AQ-10 total for each participant.
aq_scores <- aq_scored |>
# NOTE: Define the groups to be summarised independently.
dplyr::group_by(Id) |>
# NOTE: Reduce each group to its aggregate statistics.
dplyr::summarise(AQTotal=sum(Score),.groups="drop")
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(all(aq_scores$AQTotal >= 0 & aq_scores$AQTotal <= 10))
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(aq_scores))
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# Id AQTotal
# # First six participants with integer totals from 0 to 10.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
Id AQTotal
# First six participants with integer totals from 0 to 10.
Function and syntax quick-reference
group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations. summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the whole table or each group. sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values. stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is not true. all [all(logical)]: Return TRUE only if every tested value is TRUE. print [print(x)]: Display an object or calculated value in the R Console. head [head(x, n=6)]: Show the first n elements or rows.
Use the matching masterclass section for the student starter and source-alignment note.
W3-M05 · Moodle-aligned rewritten exercise
Pipe version of a complete workflow
Original lesson: W3-M05 · wk3_practical_summarising_data (1).r · Part B pipe [MOODLE-ALIGNED]
What this teaches — concept breakdown
The pipe chains the entire transformation: wide-to-long → join item direction → join scoring key → group by participant → sum item scores. It should reproduce the same totals as the separated steps.
The pipe operator |> passes the output of one transformation into the next. A long pipeline should reproduce the same participant totals as separate intermediate objects. Compare sorted scores for equality and debug each transformation stage if results disagree.
Step-by-step classroom instructions
- Start a single piped workflow using the original AQ response table.
- Reshape Q1–Q10 from wide to long format in the pipeline.
- Join the reshaped rows to the question format key, then continue scoring.
How to teach it
Use identical(aq_scores, aq_pipe) to test exact equality; isolate stages if it fails.
Common trap / interpretation warning
identical() checks classes, row order and attributes as well as values; equivalent data can sometimes differ in attributes.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Perform the whole AQ workflow through one chained pipeline.
aq_pipe <- aq_responses |>
# NOTE: Pivot Q1–Q10 columns into long-form question/response rows.
tidyr::pivot_longer(cols=dplyr::all_of(paste0("Q",1:10)),
# NOTE: Continue this R expression with its arguments, values or formatting.
names_to="Question",values_to="Response") |>
# NOTE: Join matching records using the specified key columns.
dplyr::inner_join(qformats,by="Question") |>
# NOTE: Join matching records using the specified key columns.
dplyr::inner_join(scoring,by=c("QFormat","Response")) |>
# NOTE: Define the groups to be summarised independently.
dplyr::group_by(Id) |>
# NOTE: Reduce each group to its aggregate statistics.
dplyr::summarise(AQTotal=sum(Score),.groups="drop")
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(identical(aq_scores,aq_pipe)) # Native |> parallels tidyverse %>% for these operations.
# ============================================================================
# EXPECTED OUTPUT — Exact, deterministic example:
# # No printed output when stopifnot(identical(...)) passes.
# # If it fails, R stops with an error.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact, deterministic example. Use the unmodified example code for comparison.
# No printed output when stopifnot(identical(...)) passes.
# If it fails, R stops with an error.
Function and syntax quick-reference
pivot_longer [tidyr::pivot_longer(data, cols=..., names_to=..., values_to=...)]: Reshape many columns into a smaller number of key/value columns. all_of [dplyr::all_of(names)]: Select columns identified by a character vector of names. paste0 [paste0("Q", 1:10)]: Join text and values without separators. inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in both tables. c [c(a, b, ...)]: Combine multiple values into a vector. group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations. summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the whole table or each group. sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values. stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is not true. identical [identical(x, y)]: Test strict equality, including data types and attributes. |> [data |> transform()]: Compose data-processing steps in readable left-to-right order.
Use the matching masterclass section for the student starter and source-alignment note.
W3-M06 · Moodle-aligned rewritten exercise
Descriptives, cut-off and counts
Original lesson: W3-M06 · wk3_practical_summarising_data (1).r · Part C tasks 1–3
What this teaches — concept breakdown
mean and sd describe the distribution of participants' summed AQ scores. AQTotal > 6 is a screening-threshold count, not a diagnosis.
The mean describes central tendency and SD describes participant-to-participant dispersion. An illustrative threshold above 6 is a screening criterion, not a diagnosis. AQ-10 is discrete and bounded; do not assume it has a normal distribution just because mean and SD are reported.
Step-by-step classroom instructions
- Calculate the mean, standard deviation and range of AQ totals.
- Print the resulting descriptive summary.
How to teach it
Distinguish the number of people from the mean number of points; compare SD to the observed min/max.
Common trap / interpretation warning
A questionnaire screening threshold must not be interpreted as a clinical diagnostic finding.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Reduce each group to its aggregate statistics.
aq_summary <- dplyr::summarise(aq_scores,
# NOTE: Compute an arithmetic mean of the selected numbers.
n=dplyr::n(), mean=mean(AQTotal), sd=sd(AQTotal),
# NOTE: Continue this R expression with its arguments, values or formatting.
min=min(AQTotal), max=max(AQTotal), above6=sum(AQTotal>6))
# NOTE: Show the result in the Console (or display a ggplot object).
print(aq_summary)
# NOTE: Show the result in the Console (or display a ggplot object).
print(dplyr::filter(aq_scores,AQTotal>6))
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# n mean sd min max above6
# # One summary row; followed by rows with AQTotal > 6.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
n mean sd min max above6
# One summary row; followed by rows with AQTotal > 6.
Function and syntax quick-reference
summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the whole table or each group. n [dplyr::n()]: Return the number of rows in the active dplyr group. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average. sd [sd(x)]: Calculate the sample standard deviation. min [min(x, na.rm=FALSE)]: Return the smallest value. max [max(x, na.rm=FALSE)]: Return the largest value. sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values. print [print(x)]: Display an object or calculated value in the R Console. filter [dplyr::filter(data, condition)]: Keep rows satisfying the specified logical condition.
Use the matching masterclass section for the student starter and source-alignment note.
W3-M07 · Moodle-aligned rewritten exercise
AQ frequency histogram
Original lesson: W3-M07 · wk3_practical_summarising_data (1).r · Part C final histogram
What this teaches — concept breakdown
A histogram displays the frequency distribution of AQTotal. Binwidth=1 and boundary=-0.5 align bins to integer scores from 0 to 10.
A histogram shows frequency across score bins. AQ totals are integer-valued, so binwidth=1 with appropriate boundaries avoids splitting equal integer scores across arbitrary intervals. The bar heights describe the sample, not the general population.
Step-by-step classroom instructions
- Define a ggplot object for the AQ total-score distribution.
- Add a histogram layer with a bin width of one point.
- Display the completed AQ histogram.
How to teach it
Ask whether the distribution is symmetric, discrete and concentrated at low scores.
Common trap / interpretation warning
The familiar normal '68% within 1 SD' rule need not hold for a bounded discrete score.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Build a plot and specify how data map to visual variables.
p_aq <- ggplot2::ggplot(aq_scores,ggplot2::aes(x=AQTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_histogram(binwidth=1,boundary=-0.5,colour="white") +
# NOTE: Apply a plot theme; styling does not change source data.
ggplot2::scale_x_continuous(breaks=0:10) + ggplot2::theme_classic() +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(x="AQ-10 score",y="Number of participants",title="AQ-10 score distribution")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_aq)
# ============================================================================
# EXPECTED OUTPUT — Expected visual output:
# [Plot] Ten-point integer-scale histogram; x-axis 0–10, y-axis participant counts.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot] Ten-point integer-scale histogram; x-axis 0–10, y-axis participant counts.
Function and syntax quick-reference
ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot. aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features such as x, y or colour. geom_histogram [ggplot2::geom_histogram(binwidth=...)]: Draw bins showing a distribution of numeric values. scale_x_continuous [ggplot2::scale_x_continuous(breaks=...)]: Control the continuous x-axis scale and tick marks. theme_classic [ggplot2::theme_classic()]: Apply a simple plotting theme with minimal background decoration. labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and legend text. print [print(x)]: Display an object or calculated value in the R Console.
Use the matching masterclass section for the student starter and source-alignment note.
W3-E01 · Independent ferdo Lab extension
Additional: variance, SE, missingness
Original lesson: W3-E01 · ferdo Lab original extension [INDEPENDENT-EXTENSION]
What this teaches — concept breakdown
Sample variance expresses squared dispersion (units of Score²); sample standard deviation retains score units. Standard error measures estimated uncertainty in the sample mean, not individual differences.
Variance is expressed in squared units; SD returns to the original score units. The standard error of a sample mean is approximately SD/sqrt(n) for independent identically distributed observations. SE quantifies precision of the estimated mean, not participant variability.
Step-by-step classroom instructions
- Compute the sample variance of the AQ total scores.
- Compute the standard error using SD divided by the square root of n.
- Print variance and standard error with meaningful labels.
How to teach it
Show SE = SD / sqrt(n) and discuss how doubling n affects SE.
Common trap / interpretation warning
Variance, SD and SE answer different questions. Do not substitute SE for SD on participant-level charts.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Compute sample variance (square of SD).
variance_aq <- var(aq_scores$AQTotal) # Sample variance (n-1 denominator).
# NOTE: Compute a sample standard deviation.
se_aq <- sd(aq_scores$AQTotal)/sqrt(nrow(aq_scores))
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(sample_variance=variance_aq, standard_error=se_aq))
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# sample_variance standard_error
# # Two positive numeric values; data-dependent.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
sample_variance standard_error
# Two positive numeric values; data-dependent.
Function and syntax quick-reference
var [var(x)]: Calculate the sample variance. sd [sd(x)]: Calculate the sample standard deviation. sqrt [sqrt(x)]: Calculate the non-negative square root. nrow [nrow(data)]: Count the number of rows in a table. print [print(x)]: Display an object or calculated value in the R Console. c [c(a, b, ...)]: Combine multiple values into a vector.
Use the matching masterclass section for the student starter and source-alignment note.
Week 4 — Visualisation with ggplot2
W4-M01 · Moodle-aligned rewritten exercise
Histogram and binwidth
Original lesson: W4-M01 · wk4_practical_visualisation.r · Part 1 histogram [MOODLE-ALIGNED]
What this teaches — concept breakdown
Histogram binwidth controls aggregation granularity. A five-point bin gives a broad overview, whereas a one-point bin reveals local discreteness and noise.
Histograms reveal concentration, skewness and possible unusual values. Reducing binwidth creates more, narrower bins; it does not change the data. The horizontal scale and sample size should remain clear when comparing visual appearances.
Step-by-step classroom instructions
- Start a ggplot of the distribution of AHI scores.
- Add a histogram with a chosen bin width, then compare alternatives.
- Print the finished histogram.
How to teach it
Compare both plots without changing the underlying data and ask which binning supports the research question.
Common trap / interpretation warning
Histogram appearance depends strongly on breaks; avoid claiming a shape is inherent to the raw data.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Build a plot and specify how data map to visual variables.
p_hist <- ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_histogram(binwidth=5,colour="white") +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(x="AHI total",y="Count",title="AHI distribution")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_hist)
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_histogram(binwidth=1,colour="white") +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(x="AHI total",y="Count",title="AHI distribution: binwidth 1"))
# ============================================================================
# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] AHI histogram with binwidth 5.
# [Plot 2] The same distribution with binwidth 1.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot 1] AHI histogram with binwidth 5.
[Plot 2] The same distribution with binwidth 1.
Function and syntax quick-reference
ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot. aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features such as x, y or colour. geom_histogram [ggplot2::geom_histogram(binwidth=...)]: Draw bins showing a distribution of numeric values. labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and legend text. print [print(x)]: Display an object or calculated value in the R Console. + [ggplot(...) + geom_histogram(...)]: Add a graphics layer; here + combines plot components, not numbers.
Use the matching masterclass section for the student starter and source-alignment note.
W4-M02 · Moodle-aligned rewritten exercise
Scatterplot and sex legend
Original lesson: W4-M02 · wk4_practical_visualisation.r · Part 1 scatterplot [MOODLE-ALIGNED]
What this teaches — concept breakdown
A scatterplot maps each record to (AHI, CES-D). Mapping colour to sex_label adds a legend and groups without changing the underlying x/y coordinates.
A scatterplot shows paired observations for two numeric variables. Mapping colour inside aes() creates groups and a legend; setting colour outside aes() gives all points one colour. Correlation visible in a plot does not establish cause and effect. Verify categorical codebook labels.
Step-by-step classroom instructions
- Create meaningful factor labels for the sex variable using the codebook.
- Start a scatterplot of AHI against CES-D, coloured by the sex labels.
How to teach it
Discuss overplotting, transparency alpha and the fact that association does not establish causation.
Common trap / interpretation warning
Repeated observations for the same person can make apparent independence misleading.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Create or recode columns while retaining row-level observations.
all_data <- dplyr::mutate(all_data,
# NOTE: Map numeric category codes to readable factor levels.
sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male")))
# NOTE: Build a plot and specify how data map to visual variables.
p_scatter <- ggplot2::ggplot(all_data,
# NOTE: Apply the ggplot operation to the current visualisation.
ggplot2::aes(x=ahiTotal,y=cesdTotal,colour=sex_label)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_point(alpha=0.48) +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(x="AHI total",y="CES-D total",colour="Sex")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_scatter)
# ============================================================================
# EXPECTED OUTPUT — Expected visual output:
# [Plot] AHI on x-axis; CES-D on y-axis; coloured points and a Female/Male legend.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot] AHI on x-axis; CES-D on y-axis; coloured points and a Female/Male legend.
Function and syntax quick-reference
mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns. factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered levels and labels. c [c(a, b, ...)]: Combine multiple values into a vector. ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot. aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features such as x, y or colour. geom_point [ggplot2::geom_point()]: Plot individual observations as points. labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and legend text. print [print(x)]: Display an object or calculated value in the R Console.
Use the matching masterclass section for the student starter and source-alignment note.
W4-M03 · Moodle-aligned rewritten exercise
Boxplot, violin, and raw points
Original lesson: W4-M03 · wk4_practical_visualisation.r · Part 1 box/violin [MOODLE-ALIGNED]
What this teaches — concept breakdown
A boxplot summarises median, quartiles and whiskers; a violin plot displays a smoothed distribution density. Jittered points preserve visibility of individual observations.
A standard boxplot displays the median and quartiles, with whiskers following a rule such as 1.5 times the IQR. It is not a mean ± SD plot. A violin plot displays a smoothed density estimate, whose shape depends on smoothing and sample size; raw points can help.
Step-by-step classroom instructions
- Create a ggplot object that compares the outcome across groups.
- Add a boxplot layer to display medians and quartiles.
- Create a separate violin plot to show the shape of group distributions.
How to teach it
Compare whether both plot types tell a consistent story about occasion-level variation.
Common trap / interpretation warning
A standard boxplot does not show mean ± SD; and a violin density may exaggerate thin groups.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Map numeric category codes to readable factor levels.
p_box <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_boxplot(fill="#7cccd2",outlier.alpha=.4) +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(x="Occasion code",y="AHI total")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_box)
# NOTE: Map numeric category codes to readable factor levels.
p_violin <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_violin(fill="#c3aaf5",alpha=.55) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_boxplot(width=.13,outlier.shape=NA) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_jitter(width=.1,alpha=.15,size=.45)
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_violin)
# ============================================================================
# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] AHI boxplot by occasion.
# [Plot 2] Violin + narrow boxplot + jittered individual points.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot 1] AHI boxplot by occasion.
[Plot 2] Violin + narrow boxplot + jittered individual points.
Function and syntax quick-reference
ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot. aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features such as x, y or colour. factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered levels and labels. geom_boxplot [ggplot2::geom_boxplot()]: Draw median, quartile and whisker summaries. labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and legend text. print [print(x)]: Display an object or calculated value in the R Console. geom_violin [ggplot2::geom_violin()]: Draw smoothed density shapes by group. geom_jitter [ggplot2::geom_jitter(width=...)]: Add small position offsets to reveal overlapping points.
Use the matching masterclass section for the student starter and source-alignment note.
W4-M04 · Moodle-aligned rewritten exercise
Mean bar plot, grouped by intervention
Original lesson: W4-M04 · wk4_practical_visualisation.r · Part 1 bar plot [MOODLE-ALIGNED]
What this teaches — concept breakdown
The summary table calculates means before plotting. geom_col() treats its y value as a precomputed height, unlike geom_bar() which counts observations by default.
If means have already been calculated in a summary table, geom_col() displays those values as bar heights. geom_bar() instead counts rows by default. Grouping by intervention provides distinct means but does not prove any intervention caused a difference.
Step-by-step classroom instructions
- Create readable labels for the intervention groups.
- Start a pipeline from the full data to calculate group means.
- Group the data by intervention before calculating each mean.
- Start a bar chart of the precomputed group means.
How to teach it
Explain why the 'dodge' position separates intervention groups within an occasion.
Common trap / interpretation warning
Compare denominators n by group, and do not mistake a descriptive bar difference for a tested effect.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Create human-readable labels for coded intervention groups.
intervention_labels <- c("Signature strengths","Three good things",
# NOTE: Continue this R expression with its arguments, values or formatting.
"Gratitude visit","Early memories (control)")
# NOTE: Create or recode columns while retaining row-level observations.
all_data <- dplyr::mutate(all_data, intervention_label=factor(intervention,
# NOTE: Continue this R expression with its arguments, values or formatting.
levels=1:4,labels=intervention_labels))
# NOTE: Assign the computed result to a named R object.
means_occasion <- all_data |>
# NOTE: Define the groups to be summarised independently.
dplyr::group_by(occasion) |>
# NOTE: Reduce each group to its aggregate statistics.
dplyr::summarise(mean_ahi=mean(ahiTotal),.groups="drop")
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(means_occasion,ggplot2::aes(x=factor(occasion),y=mean_ahi))+
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_col(fill="#9fd8c2"))
# NOTE: Assign the computed result to a named R object.
means_groups <- all_data |>
# NOTE: Define the groups to be summarised independently.
dplyr::group_by(occasion,intervention_label) |>
# NOTE: Reduce each group to its aggregate statistics.
dplyr::summarise(n=dplyr::n(),mean_ahi=mean(ahiTotal),sd_ahi=sd(ahiTotal),
# NOTE: Specify how groups, missing values or join keys are handled.
.groups="drop")
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(means_groups,ggplot2::aes(x=factor(occasion),y=mean_ahi,
# NOTE: Add a visual layer; ggplot layers are combined with +.
fill=intervention_label)) + ggplot2::geom_col(position="dodge"))
# ============================================================================
# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] One mean-AHI bar per occasion.
# [Plot 2] Side-by-side intervention mean bars per occasion.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot 1] One mean-AHI bar per occasion.
[Plot 2] Side-by-side intervention mean bars per occasion.
Function and syntax quick-reference
mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns. factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered levels and labels. group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations. summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the whole table or each group. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average. ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot. aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features such as x, y or colour. geom_col [ggplot2::geom_col()]: Draw bars using already-calculated y values. n [dplyr::n()]: Return the number of rows in the active dplyr group. sd [sd(x)]: Calculate the sample standard deviation. |> [data |> group_by(...)]: Pass a data frame through consecutive transformations.
Use the matching masterclass section for the student starter and source-alignment note.
W4-M05 · Moodle-aligned rewritten exercise
Line plot and SD error bars
Original lesson: W4-M05 · wk4_practical_visualisation.r · Part 1 line + ylim + SD
What this teaches — concept breakdown
A group is tracked through time using colour and group aesthetics. The vertical bars run from mean − SD to mean + SD; they describe dispersion, not a 95% confidence interval.
A line plot connecting occasion-level means visualises a sequence of group summaries. These are not necessarily trajectories of the same individual. Error bars labelled ±1 SD describe within-group variability and are not the same as standard errors or 95% confidence intervals.
Step-by-step classroom instructions
- Start a plot of group mean scores across measurement occasions.
- Add a line layer connecting the occasion means.
- Add error bars for plus/minus one standard deviation, not a confidence interval.
How to teach it
Read two occasion points on a line and compare the within-group direction of change.
Common trap / interpretation warning
coord_cartesian() zooms without filtering original data; SD bars must not be called confidence intervals.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Build a plot and specify how data map to visual variables.
p_line <- ggplot2::ggplot(means_groups,
# NOTE: Apply the ggplot operation to the current visualisation.
ggplot2::aes(x=occasion,y=mean_ahi,colour=intervention_label,group=intervention_label)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_line() + ggplot2::geom_point() +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_errorbar(ggplot2::aes(ymin=mean_ahi-sd_ahi,ymax=mean_ahi+sd_ahi),
# NOTE: Specify how groups, missing values or join keys are handled.
width=.13,na.rm=TRUE) + ggplot2::coord_cartesian(ylim=c(24,120)) +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(x="Measurement occasion (0=pre, 1=post)",y="Mean AHI ± 1 SD")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_line)
# ============================================================================
# EXPECTED OUTPUT — Expected visual output:
# [Plot] Intervention mean-AHI trajectories across occasions with vertical ±1 SD error bars.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot] Intervention mean-AHI trajectories across occasions with vertical ±1 SD error bars.
Function and syntax quick-reference
ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot. aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features such as x, y or colour. geom_line [ggplot2::geom_line()]: Connect points in x-axis order within groups. geom_point [ggplot2::geom_point()]: Plot individual observations as points. geom_errorbar [ggplot2::geom_errorbar(aes(ymin=..., ymax=...))]: Draw uncertainty or variability intervals between ymin and ymax. coord_cartesian [ggplot2::coord_cartesian(ylim=c(lo, hi))]: Zoom a plot to a selected viewing range without discarding data. c [c(a, b, ...)]: Combine multiple values into a vector. labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and legend text. print [print(x)]: Display an object or calculated value in the R Console.
Use the matching masterclass section for the student starter and source-alignment note.
W4-M06 · Moodle-aligned rewritten exercise
Themes, labels, facets and point style
Original lesson: W4-M06 · wk4_practical_visualisation.r · Part 2 themes / labels / facet /
What this teaches — concept breakdown
Themes affect styling, titles communicate meaning and facet_wrap() creates panels by category. Geometric point aesthetics such as shape and alpha change appearance, not the observations.
ggplot objects combine datasets, aesthetic mappings, geometric layers, scales and themes. A theme changes presentation rather than numerical values; facet_wrap splits one plot into group panels. Always label axes, units and the meaning of visual encodings.
Step-by-step classroom instructions
- Print the existing scatterplot using a classic ggplot theme.
- Print a faceted version of the scatterplot, separated by the sex labels.
How to teach it
Ask which version best reveals subgroup patterns and whether the colours should also encode a variable.
Common trap / interpretation warning
Visual style is not evidence of a statistical effect; transparency influences visibility.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_scatter + ggplot2::theme_classic() +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(title="Happiness and depressive symptoms"))
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_scatter + ggplot2::theme_bw() + ggplot2::facet_wrap(~sex_label))
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal,y=cesdTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
ggplot2::geom_point(colour="purple",size=2,shape=17,alpha=.3) +
# NOTE: Set interpretable axis labels and titles.
ggplot2::labs(title="Styled scatterplot",x="AHI",y="CES-D"))
# ============================================================================
# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] Classic scatter with title.
# [Plot 2] Black-and-white faceted scatter by sex.
# [Plot 3] Purple triangle scatter.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot 1] Classic scatter with title.
[Plot 2] Black-and-white faceted scatter by sex.
[Plot 3] Purple triangle scatter.
Function and syntax quick-reference
print [print(x)]: Display an object or calculated value in the R Console. theme_classic [ggplot2::theme_classic()]: Apply a simple plotting theme with minimal background decoration. labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and legend text. theme_bw [ggplot2::theme_bw()]: Apply a black-and-white plotting theme. facet_wrap [ggplot2::facet_wrap(~ group)]: Split a plot into small panels by one or more grouping variables. ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot. aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features such as x, y or colour. geom_point [ggplot2::geom_point()]: Plot individual observations as points.
Use the matching masterclass section for the student starter and source-alignment note.
W4-E01 · Independent ferdo Lab extension
Additional: correlation and ATSQ context
Original lesson: W4-E01 · ferdo Lab original extension; ATSQ file is separate from original W4
What this teaches — concept breakdown
cor() gives a descriptive linear association coefficient for AHI and CES-D using complete cases. The second join illustrates linking two other tables by participant id.
The Pearson correlation coefficient describes the direction and strength of a linear association, typically between -1 and 1. It does not identify a causal mechanism. ATSQ is a separate scale whose substantive meaning has not been verified in these teaching materials.
Step-by-step classroom instructions
- Calculate the correlation between AHI and CES-D with missing-data handling.
- Import the separately supplied ATSQ scores as a data frame.
- Join ATSQ scores to participant information by participant ID.
How to teach it
Contrast a correlation within repeated measurements with independent participant-level correlations.
Common trap / interpretation warning
Correlation does not prove causality; missing pairs and repeated observations require care.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Compute the Pearson linear correlation coefficient.
cor_ahi_cesd <- cor(all_data$ahiTotal,all_data$cesdTotal,use="complete.obs")
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(correlation=cor_ahi_cesd))
# NOTE: Read an input CSV into a data frame; check the working directory.
atsq <- read.csv("data/atsq_scores.csv",stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
p_background <- read.csv("data/p_info.csv",stringsAsFactors=FALSE)
# NOTE: Join matching records using the specified key columns.
atsq_joint <- dplyr::inner_join(atsq,p_background,by="id")
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(atsq_rows=nrow(atsq),background_rows=nrow(p_background),joined=nrow(atsq_joint)))
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# correlation
# # One coefficient in [-1,1].
# atsq_rows background_rows joined
# # Three data-dependent row counts.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
correlation
# One coefficient in [-1,1].
atsq_rows background_rows joined
# Three data-dependent row counts.
Function and syntax quick-reference
cor [cor(x, y, use="complete.obs")]: Calculate a correlation coefficient, Pearson by default. print [print(x)]: Display an object or calculated value in the R Console. c [c(a, b, ...)]: Combine multiple values into a vector. read.csv [read.csv("data/file.csv", stringsAsFactors=FALSE)]: Read a comma-separated file into a data frame. inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in both tables. nrow [nrow(data)]: Count the number of rows in a table.
Use the matching masterclass section for the student starter and source-alignment note.
Week 5 — Sampling and bootstrap
W5-E01 · Independent ferdo Lab extension
Sampling distributions and descriptive variation
Original lesson: W5-E01 · Independent ferdo Lab provisional lesson; no verified Moodle Week 5
What this teaches — concept breakdown
The mean gives the centre of a numerical sample; SD estimates trial-to-trial dispersion; SE estimates the precision of the sample mean under independence.
SD measures the spread of observations; the standard error (SE) estimates uncertainty in the mean. Under standard independent-sampling assumptions SE≈SD/sqrt(n); as n increases, the SE of a mean tends to decrease. This does not resolve sampling bias.
Step-by-step classroom instructions
- Create a short fictional sample of reaction times in milliseconds.
- Calculate how many reaction times are in the sample.
- Calculate the sample mean reaction time.
How to teach it
Have learners predict that SE is smaller than SD when n > 1.
Common trap / interpretation warning
Fictional trial values illustrate estimation only, not generalisable experimental results.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Define an illustrative vector of fictional reaction times.
rt_demo <- c(620,610,590,605,615,600,625,595,608,612,598,603)
# NOTE: Assign the computed result to a named R object.
rt_n <- length(rt_demo)
# NOTE: Compute an arithmetic mean of the selected numbers.
rt_mean <- mean(rt_demo)
# NOTE: Compute a sample standard deviation.
rt_sd <- sd(rt_demo)
# NOTE: Assign the computed result to a named R object.
rt_se <- rt_sd / sqrt(rt_n)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(n=rt_n,mean_ms=rt_mean,sd_ms=rt_sd,se_ms=rt_se))
# ============================================================================
# EXPECTED OUTPUT — Exact, deterministic example:
# n = 12
# mean_ms = 606.75
# sd_ms ≈ 10.3320
# se_ms ≈ 2.9826
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact, deterministic example. Use the unmodified example code for comparison.
n = 12
mean_ms = 606.75
sd_ms ≈ 10.3320
se_ms ≈ 2.9826
Function and syntax quick-reference
c [c(a, b, ...)]: Combine multiple values into a vector. length [length(x)]: Return the number of elements in a vector. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average. sd [sd(x)]: Calculate the sample standard deviation. sqrt [sqrt(x)]: Calculate the non-negative square root. print [print(x)]: Display an object or calculated value in the R Console.
Use the matching masterclass section for the student starter and source-alignment note.
W5-E02 · Independent ferdo Lab extension
Resampling and bootstrap percentile interval
Original lesson: W5-E02 · Independent ferdo Lab provisional lesson; no verified Moodle Week 5
What this teaches — concept breakdown
Bootstrap sampling with replacement builds a distribution of resampled means. Larger resample sizes usually produce narrower distributions; percentile intervals use quantiles of resampled statistics.
Bootstrap resampling draws observations with replacement from the observed sample. Sampling n=4 versus n=10 illustrates different variability of resampled means, whereas a bootstrap percentile interval for the observed n=12 mean should resample n=12. Set the seed for reproducibility.
Step-by-step classroom instructions
- Set the random seed so the resampling results can be reproduced.
- Simulate 1,000 resampled means using a sample size of four.
- Simulate 1,000 resampled means using a sample size of ten.
- Simulate a bootstrap distribution using the observed sample size.
How to teach it
Identify the fixed seed, the number of replicates and why replace=TRUE matters.
Common trap / interpretation warning
The seed makes R reproducible, but no numeric bootstrap result is claimed without executing the R source.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Fix the R pseudorandom sequence for reproducible simulation.
set.seed(46) # Fix pseudo-random seed for reproducibility.
# NOTE: Compute an arithmetic mean of the selected numbers.
means_n4 <- replicate(1000,mean(sample(rt_demo,4,replace=TRUE)))
# NOTE: Compute an arithmetic mean of the selected numbers.
means_n10 <- replicate(1000,mean(sample(rt_demo,10,replace=TRUE)))
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(sd_n4=sd(means_n4),sd_n10=sd(means_n10)))
# NOTE: Compute an arithmetic mean of the selected numbers.
bootstrap_means <- replicate(1000,mean(sample(rt_demo,rt_n,replace=TRUE)))
# NOTE: Extract specified percentile values from the resampling distribution.
bootstrap_ci <- quantile(bootstrap_means,c(.025,.975))
# NOTE: Show the result in the Console (or display a ggplot object).
print(bootstrap_ci)
# ============================================================================
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# sd_n4 sd_n10
# # Two simulation SDs (typically sd_n10 < sd_n4).
# 2.5% 97.5%
# # Two bootstrap percentile endpoints; exact values require running R.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
sd_n4 sd_n10
# Two simulation SDs (typically sd_n10 < sd_n4).
2.5% 97.5%
# Two bootstrap percentile endpoints; exact values require running R.
Function and syntax quick-reference
set.seed [set.seed(46)]: Fix the pseudorandom generator state for reproducible output. replicate [replicate(B, expression)]: Evaluate an expression repeatedly and collect the results. mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average. sample [sample(x, size, replace=TRUE)]: Draw elements with or without replacement. print [print(x)]: Display an object or calculated value in the R Console. c [c(a, b, ...)]: Combine multiple values into a vector. sd [sd(x)]: Calculate the sample standard deviation. quantile [quantile(x, probs=c(.025,.975))]: Calculate the requested percentiles of a vector. replace=TRUE [sample(x, n, replace=TRUE)]: Allow an observed data point to appear more than once in a resample.
Use the matching masterclass section for the student starter and source-alignment note.
W5-E03 · Independent ferdo Lab extension
Visualising the resampling distributions
Original lesson: W5-E03 · Independent ferdo Lab provisional lesson [INDEPENDENT-EXTENSION]
What this teaches — concept breakdown
par(mfrow=c(1,2)) divides a base-R graphics device into two panels. The histograms compare distributions of resampled means at two sample sizes.
Compare the widths of the simulated mean distributions rather than only their centres. Repeated means based on ten values usually vary less than those based on four. Resampling one empirical sample cannot guarantee that it represents a wider population.
Step-by-step classroom instructions
- Arrange two Base R plots side by side using par().
- Plot the resampled means for samples of size four.
- Plot the resampled means for samples of size ten, then compare spreads.
How to teach it
Explain the change in spread without interpreting both panels as raw observations.
Common trap / interpretation warning
The two histograms need comparable axes if you want a strong visual claim about width.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Configure the base-R plotting layout.
par(mfrow=c(1,2))
# NOTE: Draw a histogram of the supplied numeric vector.
hist(means_n4,main="Resample size 4",xlab="Mean RT (ms)")
# NOTE: Draw a histogram of the supplied numeric vector.
hist(means_n10,main="Resample size 10",xlab="Mean RT (ms)")
# NOTE: Configure the base-R plotting layout.
par(mfrow=c(1,1))
# EXPECTED OUTPUT — Expected visual output:
# [Plot] Two side-by-side resampled-mean histograms: n=4 and n=10.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Expected visual output. A graphical result is expected; no fabricated raster image.
[Plot] Two side-by-side resampled-mean histograms: n=4 and n=10.
Function and syntax quick-reference
par [par(mfrow=c(1,2))]: Set graphical parameters such as the plot-panel layout. c [c(a, b, ...)]: Combine multiple values into a vector. hist [hist(x, main="...", xlab="...")]: Draw a Base R histogram.
Use the matching masterclass section for the student starter and source-alignment note.
Week 6 — Inference and regression
W6-E01 · Independent ferdo Lab extension
Null hypothesis and one-sample t test
Original lesson: W6-E01 · Independent ferdo Lab provisional lesson; no verified Moodle Week 6
What this teaches — concept breakdown
A one-sample t statistic compares the observed mean to hypothesised mu0 in units of estimated standard errors. t.test() also returns a p-value and confidence interval under modelling assumptions.
Under H0: μ=600 ms, the one-sample t statistic is (sample_mean−600)/(sample_SD/sqrt(n)) with df=n−1. The two-sided p value measures how extreme the observed statistic is under H0; it is not the probability that H0 is true.
Step-by-step classroom instructions
- Set the null-hypothesis population mean to 600 milliseconds.
- Calculate the one-sample t statistic from the observed mean, SD and n.
- Run t.test() against the same null hypothesis and compare the outputs.
How to teach it
Show t=(mean−mu0)/(SD/sqrt(n)) and why the degrees of freedom are n−1.
Common trap / interpretation warning
A small p-value does not establish substantive importance or guarantee the model's assumptions.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Assign the computed result to a named R object.
mu0 <- 600 # Hypothesised population mean, milliseconds.
# NOTE: Compute t from a mean difference divided by its standard error.
t_manual <- (rt_mean-mu0)/(rt_sd/sqrt(rt_n))
# NOTE: Assign the computed result to a named R object.
df_manual <- rt_n-1
# NOTE: Assign the computed result to a named R object.
two_sided_p <- 2*pt(-abs(t_manual),df=df_manual)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(t=t_manual,df=df_manual,p=two_sided_p))
# NOTE: Run a one-sample t test and request its confidence interval.
model_t <- t.test(rt_demo,mu=mu0,alternative="two.sided",conf.level=.95)
# NOTE: Show the result in the Console (or display a ggplot object).
print(model_t)
# ============================================================================
# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# t ≈ 2.2631 df = 11 p ≈ 0.04484
# # t.test() additionally prints the 95% confidence interval and sample mean.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact values where deterministic; otherwise preview. Use the unmodified example code for comparison.
t ≈ 2.2631 df = 11 p ≈ 0.04484
# t.test() additionally prints the 95% confidence interval and sample mean.
Function and syntax quick-reference
sqrt [sqrt(x)]: Calculate the non-negative square root. pt [pt(t, df)]: Evaluate the cumulative probability for a Student t distribution. abs [abs(x)]: Return the absolute value of each number. print [print(x)]: Display an object or calculated value in the R Console. c [c(a, b, ...)]: Combine multiple values into a vector. t.test [t.test(x, mu=600)]: Perform a Student t test and report a confidence interval. p-value [2 * pt(-abs(t), df)]: Compute the two-sided tail probability under the null model.
Use the matching masterclass section for the student starter and source-alignment note.
W6-E02 · Independent ferdo Lab extension
Confidence interval and effect size
Original lesson: W6-E02 · Independent ferdo Lab provisional lesson [INDEPENDENT-EXTENSION]
What this teaches — concept breakdown
A t confidence interval uses the critical t quantile times the standard error. Cohen's d scales the observed difference from mu0 by sample SD; all.equal() checks numerical agreement with t.test().
A two-sided 95% t confidence interval is sample_mean ± t_critical × SE. It combines sample variability, sample size and a t quantile. Cohen’s d standardises the observed mean difference relative to sample SD; practical importance and statistical significance are distinct.
Step-by-step classroom instructions
- Find the critical t value for a two-sided 95% confidence interval.
- Calculate the confidence interval manually from the sample mean and SE.
- Calculate Cohen’s d to describe standardised effect size.
How to teach it
Contrast an interval for a population mean with an interval for individual trials.
Common trap / interpretation warning
An interval describes uncertainty about a mean under assumptions, not a probability that individual scores lie inside it.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Assign the computed result to a named R object.
critical_t <- qt(.975,df=rt_n-1)
# NOTE: Construct the t interval as mean ± critical t × SE.
ci_manual <- rt_mean + c(-1,1)*critical_t*rt_se
# NOTE: Standardise the mean difference using the sample SD.
cohens_d <- (rt_mean-mu0)/rt_sd
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(ci_lower=ci_manual[1],ci_upper=ci_manual[2],cohens_d=cohens_d))
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(isTRUE(all.equal(as.numeric(ci_manual),as.numeric(model_t$conf.int),tolerance=1e-8)))
# ============================================================================
# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# ci_lower ≈ 600.1854
# ci_upper ≈ 613.3146
# cohens_d ≈ 0.6533
# # stopifnot() prints nothing if the two CI calculations agree.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Exact values where deterministic; otherwise preview. Use the unmodified example code for comparison.
ci_lower ≈ 600.1854
ci_upper ≈ 613.3146
cohens_d ≈ 0.6533
# stopifnot() prints nothing if the two CI calculations agree.
Function and syntax quick-reference
qt [qt(.975, df)]: Get a quantile (critical value) of a Student t distribution. c [c(a, b, ...)]: Combine multiple values into a vector. print [print(x)]: Display an object or calculated value in the R Console. stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is not true. all.equal [all.equal(x, y)]: Compare objects approximately, allowing small numeric tolerances. as.numeric [as.numeric(x)]: Convert values to numeric form when conversion is meaningful.
Use the matching masterclass section for the student starter and source-alignment note.
W6-E03 · Independent ferdo Lab extension
Regression, correlation, model diagnostics
Original lesson: W6-E03 · Independent ferdo Lab enrichment; not a verified Moodle Week 6
What this teaches — concept breakdown
lm() estimates a linear relationship between CES-D and AHI. Coefficients, correlation r and R² summarise an association; repeated measures violate a simple independent-row assumption.
A linear-regression slope estimates the expected change in a response for a one-unit increase in a predictor, conditional on the model. Inspect residual patterns and independence assumptions. Repeated records for one participant are not independent, so a simple model here is only a mechanical illustration.
Step-by-step classroom instructions
- Fit a linear regression model predicting one variable from another.
- Print the model summary, including coefficients and R-squared.
- Print the correlation coefficient for comparison with the regression.
How to teach it
Ask which variable is outcome and which is predictor, then evaluate whether the observations can be considered independent.
Common trap / interpretation warning
Do not treat this basic repeated-record model as a causal or fully valid independent-observation analysis.
Instructor: completed R solution — annotated line by line
The executable statements below reproduce the original completed teaching solution; the short # NOTE comments are additional and do not change program behaviour.
# NOTE: Fit an ordinary least-squares linear model.
fit <- lm(cesdTotal~ahiTotal,data=all_data)
# NOTE: Show the result in the Console (or display a ggplot object).
print(summary(fit)$coefficients)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(r=cor(all_data$ahiTotal,all_data$cesdTotal),
# NOTE: Continue this R expression with its arguments, values or formatting.
r_squared=summary(fit)$r.squared))
# Assumptions: independence, approximately linear relationship, conditional errors.
# END OF THE SIX-WEEK COURSE.
# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# Estimate Std. Error t value Pr(>|t|)
# (Intercept) ... ... ... ...
# ahiTotal ... ... ... ...
# r = ... ; r_squared = ...
# # Coefficient values depend on the public dataset.
# Data-dependent previews are templates, not verified Console output.
Expected output / output excerpt
Output format / checks (not fabricated values). Data-dependent: column layouts, placeholders or constraints only.
Estimate Std. Error t value Pr(>|t|)
(Intercept) ... ... ... ...
ahiTotal ... ... ... ...
r = ... ; r_squared = ...
# Coefficient values depend on the public dataset.
Function and syntax quick-reference
lm [lm(y ~ x, data=data)]: Fit a linear statistical model using a formula. print [print(x)]: Display an object or calculated value in the R Console. summary [summary(model)]: Show a descriptive or model-specific statistical summary. c [c(a, b, ...)]: Combine multiple values into a vector. cor [cor(x, y, use="complete.obs")]: Calculate a correlation coefficient, Pearson by default.
Use the matching masterclass section for the student starter and source-alignment note.