Six-week R, programming and statistics masterclass

One teaching sequence, three perfectly parallel resources: a completed instructor R script, an unfinished student R workbook and this detailed HTML teaching book. Instructions, examples and explanatory notes are entirely in English.

Instructor R scriptStudent R workbookDownload complete open ZIP

Classroom workflow

Student workflow

Open Student_Workbook.R. Every task includes a clear, step-by-step English instruction in a comment, followed by an unfinished, commented-out R code prefix. Remove the # from one prefix, finish the expression and run it. The full student worksheet is intentionally not executable as a finished exercise.

Instructor workflow

Open Teacher_Master.R. The same ID, instructions and code prefixes appear in the same order; underneath are completed R statements, short function notes and a direct DETAILS link to the matching section of this page.

Setup: Extract the entire ZIP to a folder. Keep data/ next to both R files. In RStudio set the working directory to that folder. If needed, install the tidyverse package for Weeks 2–4 using install.packages("tidyverse") in the Console.

Course map

Core R and programming syntax

SyntaxUseExample / note
x <- 7AssignmentStore 7 in an object named x.
c(6,8,7)VectorCombine several values into an atomic vector.
v[1]IndexingR starts indexing at 1.
v[v > 6]Logical filteringRetain values meeting the condition.
df$ageColumn accessExtract the age column from a data frame.
read.csv("data/file.csv")File inputRelative paths begin at the working directory.
data |> f()PipePass the data into the next operation.
group_by() + summarise()Grouped summariesUse the pipe in dplyr; plus is for ggplot layers, not dplyr data pipes.
ggplot(...) + geom_point()Layered graphicsBuild a scatterplot by adding a graphical layer.

Week 1 — R Basics And Familiarisation

R as a calculator; objects, vectors, functions and research questions.

W1-M01Moodle-aligned rewritten task

R as calculator

MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 1. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Compute 6+1, 2*(1+3), then predict 5+3*2.

Concept: Arithmetic operators, operator precedence and parentheses.

Task instructions — what the student must do

  1. Add 6 and 1, then print the result.
  2. Multiply 2 by the sum of 1 and 3, then print the result.
  3. Calculate 5 plus 3 times 2, then print the result.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Add 6 and 1, then print the result.
#   print(6 +

# Step 2. Multiply 2 by the sum of 1 and 3, then print the result.
#   print(2 * (

# Step 3. Calculate 5 plus 3 times 2, then print the result.
#   print(5 +
Instructor: completed R solution — W1-M01
print(6 + 1)                              # Addition returns 7.
print(2 * (1 + 3))                      # Parentheses evaluated first; returns 8.
print(5 + 3 * 2)                        # Multiplication precedes addition: 11.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W1-M01

W1-M02Moodle-aligned rewritten task

Assignment and reusable results

MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 2. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Save 1+...+10 as x. Divide x by ten.

Concept: Assignment, reusable objects, numeric sequences and the R environment.

Task instructions — what the student must do

  1. Add the integers from 1 to 10 and store the total in x.
  2. Divide the stored total x by 10 and print the result.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

sum()
Add numeric values; optionally ignore missing values. Check argument names and data types before running the code.sum(x, na.rm=FALSE)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
ls()
List names of objects currently in the R environment. Check argument names and data types before running the code.ls()
<-
Assign the result of the right-hand expression to an object name.name <- expression

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Add the integers from 1 to 10 and store the total in x.
#   x <- sum(

# Step 2. Divide the stored total x by 10 and print the result.
#   print(x /
Instructor: completed R solution — W1-M02
x <- sum(1:10)                          # 1:10 is an inclusive integer sequence; sum gives 55.
print(x / 10)                           # Stored result re-used; expected 5.5.
print(ls())                             # List current objects; not the same as printing values.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W1-M02

W1-M03Moodle-aligned rewritten task

Text, numbers, vectors and indexing

MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 3 and naming notes. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Store a character, integer, and vector; extract values above six.

Concept: Character and numeric values; vectors, logical masks and indexing.

Task instructions — what the student must do

  1. Store a short text label in course_label.
  2. Store the number of participants as an integer in participants.
  3. Create a numeric vector containing four fictional sleep durations.
  4. Print only the sleep durations greater than 6 hours using logical indexing.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)
[ ]
Select one or more values by 1-based position or logical mask.x[i]

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Store a short text label in course_label.
#   course_label <-

# Step 2. Store the number of participants as an integer in participants.
#   participants <-

# Step 3. Create a numeric vector containing four fictional sleep durations.
#   sleep_hours <- c(

# Step 4. Print only the sleep durations greater than 6 hours using logical indexing.
#   print(sleep_hours[
Instructor: completed R solution — W1-M03
course_label <- "PALS practice"          # Character/string is enclosed in quotes.
participants <- 66L                      # L makes an integer literal.
sleep_hours <- c(6, 8, 7, 5)            # c() combines values into a numeric vector.
print(sleep_hours[1])                    # R indices start at 1, not 0.
print(sleep_hours[sleep_hours > 6])      # Logical indexing -> c(8, 7).
print(mean(sleep_hours))                 # Expected 6.5 hours, for illustrative data.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W1-M03

W1-M04Moodle-aligned rewritten task

Built-in USArrests data frame

MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 5. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Load USArrests into crime; inspect variables and dimensions.

Concept: Data frames, observations (rows) and variables (columns).

Task instructions — what the student must do

  1. Assign the built-in USArrests dataset to an object called crime.
  2. Print the number of rows and columns in crime.
  3. Print all column names in crime.
  4. Print the first six rows of crime.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
dim()
Return dimensions, usually rows and columns. Check argument names and data types before running the code.dim(x)
names()
Get or assign column or element names. Check argument names and data types before running the code.names(data)
head()
Show the first n elements or rows. Check argument names and data types before running the code.head(x, n=6)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Assign the built-in USArrests dataset to an object called crime.
#   crime <-

# Step 2. Print the number of rows and columns in crime.
#   print(dim(

# Step 3. Print all column names in crime.
#   print(names(

# Step 4. Print the first six rows of crime.
#   print(head(
Instructor: completed R solution — W1-M04
crime <- USArrests                        # Built-in public teaching dataset, no CSV needed.
print(dim(crime))                         # Rows and columns: 50 by 4.
print(names(crime))                       # Murder, Assault, UrbanPop, Rape.
print(head(crime))                        # Display the first six rows.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W1-M04

W1-M05Moodle-aligned rewritten task

Functions, help and extraction

MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · tasks 6–8. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Find the maximum Murder and Assault; inspect max() help.

Concept: Function calls, named arguments, the dollar operator and help pages.

Task instructions — what the student must do

  1. Print the maximum value in the Murder column of crime.
  2. Print the Assault column (or a relevant element of it) using the dollar operator.
  3. Open R help for the max function.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
max()
Return the largest value. Check argument names and data types before running the code.max(x, na.rm=FALSE)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Print the maximum value in the Murder column of crime.
#   print(max(

# Step 2. Print the Assault column (or a relevant element of it) using the dollar operator.
#   print(crime$

# Step 3. Open R help for the max function.
#   help(
Instructor: completed R solution — W1-M05
print(max(c(2, 4, 6)))                   # max() needs a vector or individual numbers.
print(crime$Murder)                       # $ extracts the column named Murder.
print(max(crime$Murder))                  # Expected 17.4 in USArrests.
print(max(crime$Assault))                 # Expected 337 in USArrests.
# Interactive help: type ?max or help("max") in the RStudio Console.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W1-M05

W1-M06Moodle-aligned rewritten task

Removing objects

MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 9. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Remove crime from the environment.

Concept: Removing an in-memory object without deleting the source file.

Task instructions — what the student must do

  1. Remove the crime object from the current R environment.
  2. Print whether an object called crime still exists.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

rm()
Remove an object from the current R environment. Check argument names and data types before running the code.rm(object)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
exists()
Check whether an object of the specified name exists. Check argument names and data types before running the code.exists("x")

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Remove the crime object from the current R environment.
#   rm(

# Step 2. Print whether an object called crime still exists.
#   print(exists(
Instructor: completed R solution — W1-M06
rm(crime)                                # Remove only this named object from Environment.
print(exists("crime"))                   # FALSE: object no longer exists.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W1-M06

W1-E01Independent ferdo Lab extension

Additional: reproducible report

MOODLE SOURCE NOTE: ferdo Lab original extension. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Define the outcome, predictor, unit, and one possible confounder.

Concept: Research questions, variables, units and possible confounding.

Task instructions — what the student must do

  1. Write a fictional research question and store it as a text value.
  2. Create a short numeric vector of fictional reaction times.
  3. Print a small data frame with trial numbers and reaction times.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
data.frame()
Build a rectangular data frame from named columns. Check argument names and data types before running the code.data.frame(x=..., y=...)
seq_along()
Create integer indices from 1 through length(x). Check argument names and data types before running the code.seq_along(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Write a fictional research question and store it as a text value.
#   question <-

# Step 2. Create a short numeric vector of fictional reaction times.
#   rt_demo <- c(

# Step 3. Print a small data frame with trial numbers and reaction times.
#   print(data.frame(
Instructor: completed R solution — W1-E01
question <- "Does sleep predict response time?"  # A question is not a causal conclusion.
rt_demo <- c(620, 610, 590, 605)         # Fictional reaction times in milliseconds.
print(data.frame(trial=seq_along(rt_demo), rt_ms=rt_demo))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W1-E01

Week 2 — Data Wrangling

Data frames, CSV imports, joins, row filters, grouping, missingness and coding.

W2-M01Moodle-aligned rewritten task

Packages and importing CSV

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part A tasks 1–2. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Read participant-info.csv and ahi-cesd.csv as pinfo and responses.

Concept: Packages, relative paths and importing CSV files as data frames.

Task instructions — what the student must do

  1. Import data/participant-info.csv as the data frame pinfo.
  2. Import data/ahi-cesd.csv as the data frame responses.
  3. Print the participant and response row counts using nrow().

Concept explanation and statistical interpretation

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.

R functions and syntax reference

read.csv()
Read a comma-separated file into a data frame. Check argument names and data types before running the code.read.csv("data/file.csv", stringsAsFactors=FALSE)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
nrow()
Count the number of rows in a table. Check argument names and data types before running the code.nrow(data)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Import data/participant-info.csv as the data frame pinfo.
#   pinfo <- read.csv(

# Step 2. Import data/ahi-cesd.csv as the data frame responses.
#   responses <- read.csv(

# Step 3. Print the participant and response row counts using nrow().
#   print(c(n_participants=
Instructor: completed R solution — W2-M01
# Run install.packages("tidyverse") ONCE in Console if not installed.
# library(tidyverse) loads dplyr, tidyr, ggplot2 etc. from your R library.
pinfo <- read.csv("data/participant-info.csv", stringsAsFactors=FALSE)
responses <- read.csv("data/ahi-cesd.csv", stringsAsFactors=FALSE)
print(c(n_participants=nrow(pinfo), n_responses=nrow(responses)))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M01

W2-M02Moodle-aligned rewritten task

Join by a compound key

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part A task 3. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Join demographics and repeated responses by id AND intervention.

Concept: Joining tables on the correct compound key and checking cardinality.

Task instructions — what the student must do

  1. Join participant information and responses by both id and intervention.
  2. Print the row count and number of distinct participants after the join.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

inner_join()
Keep and combine rows whose keys match in both tables. Check join keys, missing matches and possible duplicate-key expansion.dplyr::inner_join(x, y, by="id")
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
nrow()
Count the number of rows in a table. Check argument names and data types before running the code.nrow(data)
n_distinct()
Count distinct values in a vector. Check argument names and data types before running the code.dplyr::n_distinct(x)
stopifnot()
Stop execution with an error if a logical condition is not true. Check argument names and data types before running the code.stopifnot(condition)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Join participant information and responses by both id and intervention.
#   all_data <- dplyr::inner_join(

# Step 2. Print the row count and number of distinct participants after the join.
#   print(c(rows=
Instructor: completed R solution — W2-M02
all_data <- dplyr::inner_join(pinfo, responses, by=c("id","intervention"))
print(c(rows=nrow(all_data), unique_ids=dplyr::n_distinct(all_data$id)))
stopifnot(nrow(all_data) <= nrow(responses))  # Key matching must not explode rows.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M02

W2-M03Moodle-aligned rewritten task

select(): columns

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B select. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Select nine requested columns.

Concept: Selecting columns rather than filtering rows.

Task instructions — what the student must do

  1. Select the required analysis columns without removing any rows.
  2. Print the resulting column names to verify your selection.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

select()
Choose or rename columns in a data frame. Check argument names and data types before running the code.dplyr::select(data, col1, col2)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
names()
Get or assign column or element names. Check argument names and data types before running the code.names(data)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Select the required analysis columns without removing any rows.
#   selected_vars <- dplyr::select(

# Step 2. Print the resulting column names to verify your selection.
#   print(names(
Instructor: completed R solution — W2-M03
selected_vars <- dplyr::select(all_data, id, intervention, ahiTotal, cesdTotal,
                                sex, age, educ, income, occasion)
print(names(selected_vars))               # Exactly nine specified columns.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M03

W2-M04Moodle-aligned rewritten task

filter(): observations and a source correction

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B filter, compared with code-book.pdf. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Filter baseline (0) and posttest (1) after reading codebook.

Concept: Filtering rows and interpreting the measurement-time codebook.

Task instructions — what the student must do

  1. Keep only observations at baseline (occasion 0) and posttest (occasion 1).
  2. Count and print observations for each occasion to check the filter.

Concept explanation and statistical interpretation

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().

R functions and syntax reference

filter()
Keep rows satisfying the specified logical condition. Rows with an NA condition are not retained.dplyr::filter(data, condition)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
table()
Count frequencies of unique values. Check argument names and data types before running the code.table(x)
%in%
Return TRUE when each element belongs to the specified set of values.x %in% c(0,1)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Keep only observations at baseline (occasion 0) and posttest (occasion 1).
#   pre_post <- dplyr::filter(

# Step 2. Count and print observations for each occasion to check the filter.
#   print(table(
Instructor: completed R solution — W2-M04
# MOODLE CORRECTION: worksheet says 1/2 are pre/post; codebook is authoritative:
# occasion 0 = pretest; 1 = posttest; 2 = one-week follow-up.
pre_post <- dplyr::filter(selected_vars, occasion %in% c(0,1))
print(table(pre_post$occasion))           # Inspect sample size/attrition per occasion.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M04

W2-M05Moodle-aligned rewritten task

arrange(): rows

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B arrange. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Sort AHI scores ascending.

Concept: Ordering observations without changing their values.

Task instructions — what the student must do

  1. Sort the selected observations by ahiTotal from low to high.
  2. Print the first rows of the sorted data frame.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

arrange()
Sort rows by one or more columns. Check argument names and data types before running the code.dplyr::arrange(data, col)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
head()
Show the first n elements or rows. Check argument names and data types before running the code.head(x, n=6)
select()
Choose or rename columns in a data frame. Check argument names and data types before running the code.dplyr::select(data, col1, col2)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Sort the selected observations by ahiTotal from low to high.
#   pre_post_asc <- dplyr::arrange(

# Step 2. Print the first rows of the sorted data frame.
#   print(head(
Instructor: completed R solution — W2-M05
pre_post_asc <- dplyr::arrange(pre_post, ahiTotal)
print(head(dplyr::select(pre_post_asc, id, occasion, ahiTotal)))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M05

W2-M06Moodle-aligned rewritten task

summarise(): overall means

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B summarise. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Make one-row AHI and CES-D mean summary.

Concept: Descriptive summaries and explicit missing-value handling.

Task instructions — what the student must do

  1. Calculate overall average AHI and CES-D scores in one summary row.
  2. Print the completed overall summary table.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

summarise()
Produce summary statistics for the whole table or each group. Check argument names and data types before running the code.dplyr::summarise(data, metric=mean(x))
n()
Return the number of rows in the active dplyr group. Check argument names and data types before running the code.dplyr::n()
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Calculate overall average AHI and CES-D scores in one summary row.
#   overall_summary <- dplyr::summarise(

# Step 2. Print the completed overall summary table.
#   print(overall_summary
Instructor: completed R solution — W2-M06
overall_summary <- dplyr::summarise(pre_post,
  n=dplyr::n(), mean_ahi=mean(ahiTotal,na.rm=TRUE),
  mean_cesd=mean(cesdTotal,na.rm=TRUE))
print(overall_summary)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M06

W2-M07Moodle-aligned rewritten task

group_by(): distinct occasion summaries

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B group_by. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Summarise each measurement occasion separately.

Concept: Grouped statistics and different sample sizes across occasions.

Task instructions — what the student must do

  1. Start a pipe to summarise the sorted pre/post observations.
  2. Group the piped observations by occasion.
  3. Compute sample sizes and means separately for each occasion.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

group_by()
Set the grouping keys used by later operations. Remember to inspect or remove grouping after grouped summaries.dplyr::group_by(data, group)
summarise()
Produce summary statistics for the whole table or each group. Check argument names and data types before running the code.dplyr::summarise(data, metric=mean(x))
n()
Return the number of rows in the active dplyr group. Check argument names and data types before running the code.dplyr::n()
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
|>
Pass the preceding result to the next function call.data |> function()

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Start a pipe to summarise the sorted pre/post observations.
#   occasion_summary <- pre_post_asc |>

# Step 2. Group the piped observations by occasion.
#     dplyr::group_by(

# Step 3. Compute sample sizes and means separately for each occasion.
#     dplyr::summarise(
Instructor: completed R solution — W2-M07
occasion_summary <- pre_post_asc |>
  dplyr::group_by(occasion) |>
  dplyr::summarise(n=dplyr::n(), mean_ahi=mean(ahiTotal),
                   mean_cesd=mean(cesdTotal), .groups="drop")
print(occasion_summary)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M07

W2-M08Moodle-aligned rewritten task

mutate(): new column and scale caution

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B mutate. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Create sadness = cesdTotal - ahiTotal as a syntax exercise.

Concept: Creating derived columns; numerical differences versus validated scales.

Task instructions — what the student must do

  1. Create a new sadness column by subtracting ahiTotal from cesdTotal.
  2. Print the first rows of the new variable alongside its inputs.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

mutate()
Create or alter one or more columns. Check argument names and data types before running the code.dplyr::mutate(data, new=x+y)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
head()
Show the first n elements or rows. Check argument names and data types before running the code.head(x, n=6)
select()
Choose or rename columns in a data frame. Check argument names and data types before running the code.dplyr::select(data, col1, col2)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Create a new sadness column by subtracting ahiTotal from cesdTotal.
#   pre_post <- dplyr::mutate(

# Step 2. Print the first rows of the new variable alongside its inputs.
#   print(head(
Instructor: completed R solution — W2-M08
pre_post <- dplyr::mutate(pre_post, sadness=cesdTotal-ahiTotal)
print(head(dplyr::select(pre_post, sadness, cesdTotal, ahiTotal)))
# DO NOT interpret "sadness" clinically: AHI/CES-D scales have different constructs/ranges.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M08

W2-M09Moodle-aligned rewritten task

Recode sex using the codebook

MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B recode, compared with code-book.pdf. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Recode sex=1 Female, sex=2 Male correctly.

Concept: Categorical recoding using an authoritative codebook.

Task instructions — what the student must do

  1. Add a correctly labelled sex variable to the pre/post data frame.
  2. Use the codebook mapping 1 = Female and 2 = Male in factor().

Concept explanation and statistical interpretation

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.

R functions and syntax reference

mutate()
Create or alter one or more columns. Check argument names and data types before running the code.dplyr::mutate(data, new=x+y)
factor()
Represent categorical values with ordered levels and labels. Incorrect levels or labels can reverse category meanings.factor(x, levels=..., labels=...)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
table()
Count frequencies of unique values. Check argument names and data types before running the code.table(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Add a correctly labelled sex variable to the pre/post data frame.
#   pre_post <- dplyr::mutate(

# Step 2. Use the codebook mapping 1 = Female and 2 = Male in factor().
#     sex_label=factor(
Instructor: completed R solution — W2-M09
# MOODLE CORRECTION: worksheet labels conflict with source codebook.
# Correct coding in code-book.pdf: 1=Female, 2=Male.
pre_post <- dplyr::mutate(pre_post,
    sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male")))
print(table(pre_post$sex_label, useNA="ifany"))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-M09

W2-E01Independent ferdo Lab extension

Additional: paired change and attrition

MOODLE SOURCE NOTE: ferdo Lab original extension. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Contrast paired change with unpaired occasion means.

Concept: Within-person paired differences, missing pairs and selection bias.

Task instructions — what the student must do

  1. Filter baseline records for participants observed before the intervention.
  2. Filter posttest records for participants observed after the intervention.
  3. Join the two sets by participant and compute within-person change.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

filter()
Keep rows satisfying the specified logical condition. Rows with an NA condition are not retained.dplyr::filter(data, condition)
select()
Choose or rename columns in a data frame. Check argument names and data types before running the code.dplyr::select(data, col1, col2)
inner_join()
Keep and combine rows whose keys match in both tables. Check join keys, missing matches and possible duplicate-key expansion.dplyr::inner_join(x, y, by="id")
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
mutate()
Create or alter one or more columns. Check argument names and data types before running the code.dplyr::mutate(data, new=x+y)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
summarise()
Produce summary statistics for the whole table or each group. Check argument names and data types before running the code.dplyr::summarise(data, metric=mean(x))
n()
Return the number of rows in the active dplyr group. Check argument names and data types before running the code.dplyr::n()
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Filter baseline records for participants observed before the intervention.
#   paired_pre <- dplyr::filter(

# Step 2. Filter posttest records for participants observed after the intervention.
#   paired_post <- dplyr::filter(

# Step 3. Join the two sets by participant and compute within-person change.
#   paired_data <- dplyr::inner_join(
Instructor: completed R solution — W2-E01
paired_pre <- dplyr::filter(all_data, occasion==0) |>
 dplyr::select(id,intervention,ahi_pre=ahiTotal)
paired_post <- dplyr::filter(all_data, occasion==1) |>
 dplyr::select(id,intervention,ahi_post=ahiTotal)
paired_data <- dplyr::inner_join(paired_pre,paired_post,by=c("id","intervention")) |>
 dplyr::mutate(change_ahi=ahi_post-ahi_pre)
print(dplyr::summarise(paired_data, paired_n=dplyr::n(), mean_change=mean(change_ahi)))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W2-E01

Week 3 — Aq-10 Scoring And Descriptive Statistics

AQ-10 questionnaire reshaping, scoring and descriptive statistics.

W3-M01Moodle-aligned rewritten task

Read AQ, formats, scoring

MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 1. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Load Qformats, Scoring and AQ data.

Concept: Reading a questionnaire and the separate format/scoring lookups.

Task instructions — what the student must do

  1. Read the question-format lookup file from the data folder.
  2. Read the scoring-key lookup file from the data folder.
  3. Read the AQ questionnaire response table from the data folder.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

read.csv()
Read a comma-separated file into a data frame. Check argument names and data types before running the code.read.csv("data/file.csv", stringsAsFactors=FALSE)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
nrow()
Count the number of rows in a table. Check argument names and data types before running the code.nrow(data)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Read the question-format lookup file from the data folder.
#   qformats <- read.csv(

# Step 2. Read the scoring-key lookup file from the data folder.
#   scoring <- read.csv(

# Step 3. Read the AQ questionnaire response table from the data folder.
#   aq_responses <- read.csv(
Instructor: completed R solution — W3-M01
qformats <- read.csv("data/Qformats.csv", stringsAsFactors=FALSE)
scoring <- read.csv("data/Scoring.csv", stringsAsFactors=FALSE)
aq_responses <- read.csv("data/AQ.csv", stringsAsFactors=FALSE)
print(c(participants=nrow(aq_responses),questions=nrow(qformats),rules=nrow(scoring)))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-M01

W3-M02Moodle-aligned rewritten task

Wide-to-long pivot

MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 2. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Convert Q1–Q10 into Question/Response rows.

Concept: Wide versus long data and person-by-question observations.

Task instructions — what the student must do

  1. Start turning the wide AQ response table into a long table.
  2. Select the Q1 through Q10 columns for pivoting.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

pivot_longer()
Reshape many columns into a smaller number of key/value columns. Check argument names and data types before running the code.tidyr::pivot_longer(data, cols=..., names_to=..., values_to=...)
all_of()
Select columns identified by a character vector of names. Check argument names and data types before running the code.dplyr::all_of(names)
paste0()
Join text and values without separators. Check argument names and data types before running the code.paste0("Q", 1:10)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
dim()
Return dimensions, usually rows and columns. Check argument names and data types before running the code.dim(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Start turning the wide AQ response table into a long table.
#   aq_long <- tidyr::pivot_longer(

# Step 2. Select the Q1 through Q10 columns for pivoting.
#     cols=dplyr::all_of(
Instructor: completed R solution — W3-M02
aq_long <- tidyr::pivot_longer(aq_responses,
  cols=dplyr::all_of(paste0("Q",1:10)),names_to="Question",values_to="Response")
print(dim(aq_long))                       # Real source: 66*10=660 rows; public demo differs.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-M02

W3-M03Moodle-aligned rewritten task

Join directions and exact scoring rules

MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 3. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Join each question to F/R and each response to its point value.

Concept: Two joins, reverse scoring and exact response-string matching.

Task instructions — what the student must do

  1. Join each long-format AQ response with its forward/reverse item direction.
  2. Join each response and direction combination to its 0/1 score.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

inner_join()
Keep and combine rows whose keys match in both tables. Check join keys, missing matches and possible duplicate-key expansion.dplyr::inner_join(x, y, by="id")
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
stopifnot()
Stop execution with an error if a logical condition is not true. Check argument names and data types before running the code.stopifnot(condition)
nrow()
Count the number of rows in a table. Check argument names and data types before running the code.nrow(data)
anyNA()
Check whether an object contains missing values. Check argument names and data types before running the code.anyNA(x)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
head()
Show the first n elements or rows. Check argument names and data types before running the code.head(x, n=6)
select()
Choose or rename columns in a data frame. Check argument names and data types before running the code.dplyr::select(data, col1, col2)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Join each long-format AQ response with its forward/reverse item direction.
#   aq_with_format <- dplyr::inner_join(

# Step 2. Join each response and direction combination to its 0/1 score.
#   aq_scored <- dplyr::inner_join(
Instructor: completed R solution — W3-M03
aq_with_format <- dplyr::inner_join(aq_long,qformats,by="Question")
aq_scored <- dplyr::inner_join(aq_with_format,scoring,by=c("QFormat","Response"))
stopifnot(nrow(aq_scored)==nrow(aq_long), !anyNA(aq_scored$Score))
print(head(dplyr::select(aq_scored,Id,Question,Response,QFormat,Score)))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-M03

W3-M04Moodle-aligned rewritten task

Participant total AQ score

MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 4. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Sum ten 0/1 items per participant.

Concept: Participant-level grouping and summing binary questionnaire items.

Task instructions — what the student must do

  1. Begin a pipe that calculates a total AQ-10 score for each participant.
  2. Group the scored questionnaire rows by participant ID.
  3. Sum the scored items separately for each participant.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

group_by()
Set the grouping keys used by later operations. Remember to inspect or remove grouping after grouped summaries.dplyr::group_by(data, group)
summarise()
Produce summary statistics for the whole table or each group. Check argument names and data types before running the code.dplyr::summarise(data, metric=mean(x))
sum()
Add numeric values; optionally ignore missing values. Check argument names and data types before running the code.sum(x, na.rm=FALSE)
stopifnot()
Stop execution with an error if a logical condition is not true. Check argument names and data types before running the code.stopifnot(condition)
all()
Return TRUE only if every tested value is TRUE. Check argument names and data types before running the code.all(logical)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
head()
Show the first n elements or rows. Check argument names and data types before running the code.head(x, n=6)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Begin a pipe that calculates a total AQ-10 score for each participant.
#   aq_scores <- aq_scored |>

# Step 2. Group the scored questionnaire rows by participant ID.
#     dplyr::group_by(

# Step 3. Sum the scored items separately for each participant.
#     dplyr::summarise(
Instructor: completed R solution — W3-M04
aq_scores <- aq_scored |>
  dplyr::group_by(Id) |>
  dplyr::summarise(AQTotal=sum(Score),.groups="drop")
stopifnot(all(aq_scores$AQTotal >= 0 & aq_scores$AQTotal <= 10))
print(head(aq_scores))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-M04

W3-M05Moodle-aligned rewritten task

Pipe version of a complete workflow

MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part B pipe. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Recreate scoring through a chain of operations.

Concept: Pipes, function composition and equivalent scoring workflows.

Task instructions — what the student must do

  1. Start a single piped workflow using the original AQ response table.
  2. Reshape Q1–Q10 from wide to long format in the pipeline.
  3. Join the reshaped rows to the question format key, then continue scoring.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

pivot_longer()
Reshape many columns into a smaller number of key/value columns. Check argument names and data types before running the code.tidyr::pivot_longer(data, cols=..., names_to=..., values_to=...)
all_of()
Select columns identified by a character vector of names. Check argument names and data types before running the code.dplyr::all_of(names)
paste0()
Join text and values without separators. Check argument names and data types before running the code.paste0("Q", 1:10)
inner_join()
Keep and combine rows whose keys match in both tables. Check join keys, missing matches and possible duplicate-key expansion.dplyr::inner_join(x, y, by="id")
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
group_by()
Set the grouping keys used by later operations. Remember to inspect or remove grouping after grouped summaries.dplyr::group_by(data, group)
summarise()
Produce summary statistics for the whole table or each group. Check argument names and data types before running the code.dplyr::summarise(data, metric=mean(x))
sum()
Add numeric values; optionally ignore missing values. Check argument names and data types before running the code.sum(x, na.rm=FALSE)
stopifnot()
Stop execution with an error if a logical condition is not true. Check argument names and data types before running the code.stopifnot(condition)
identical()
Test strict equality, including data types and attributes. Check argument names and data types before running the code.identical(x, y)
|>
Compose data-processing steps in readable left-to-right order.data |> transform()

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Start a single piped workflow using the original AQ response table.
#   aq_pipe <- aq_responses |>

# Step 2. Reshape Q1–Q10 from wide to long format in the pipeline.
#     tidyr::pivot_longer(

# Step 3. Join the reshaped rows to the question format key, then continue scoring.
#     dplyr::inner_join(
Instructor: completed R solution — W3-M05
aq_pipe <- aq_responses |>
 tidyr::pivot_longer(cols=dplyr::all_of(paste0("Q",1:10)),
                     names_to="Question",values_to="Response") |>
 dplyr::inner_join(qformats,by="Question") |>
 dplyr::inner_join(scoring,by=c("QFormat","Response")) |>
 dplyr::group_by(Id) |>
 dplyr::summarise(AQTotal=sum(Score),.groups="drop")
stopifnot(identical(aq_scores,aq_pipe))   # Native |> parallels tidyverse %>% for these operations.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-M05

W3-M06Moodle-aligned rewritten task

Descriptives, cut-off and counts

MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part C tasks 1–3. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Compute mean/SD/range; list people over six.

Concept: Means, standard deviations, scores and screening thresholds.

Task instructions — what the student must do

  1. Calculate the mean, standard deviation and range of AQ totals.
  2. Print the resulting descriptive summary.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

summarise()
Produce summary statistics for the whole table or each group. Check argument names and data types before running the code.dplyr::summarise(data, metric=mean(x))
n()
Return the number of rows in the active dplyr group. Check argument names and data types before running the code.dplyr::n()
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)
sd()
Calculate the sample standard deviation. With the default sample definition, SD uses n−1 in the denominator.sd(x)
min()
Return the smallest value. Check argument names and data types before running the code.min(x, na.rm=FALSE)
max()
Return the largest value. Check argument names and data types before running the code.max(x, na.rm=FALSE)
sum()
Add numeric values; optionally ignore missing values. Check argument names and data types before running the code.sum(x, na.rm=FALSE)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
filter()
Keep rows satisfying the specified logical condition. Rows with an NA condition are not retained.dplyr::filter(data, condition)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Calculate the mean, standard deviation and range of AQ totals.
#   aq_summary <- dplyr::summarise(

# Step 2. Print the resulting descriptive summary.
#   print(aq_summary
Instructor: completed R solution — W3-M06
aq_summary <- dplyr::summarise(aq_scores,
  n=dplyr::n(), mean=mean(AQTotal), sd=sd(AQTotal),
  min=min(AQTotal), max=max(AQTotal), above6=sum(AQTotal>6))
print(aq_summary)
print(dplyr::filter(aq_scores,AQTotal>6))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-M06

W3-M07Moodle-aligned rewritten task

AQ frequency histogram

MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part C final histogram. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Plot the integer-valued AQTotal distribution with bins of one.

Concept: Histograms for discrete integer-valued scores.

Task instructions — what the student must do

  1. Define a ggplot object for the AQ total-score distribution.
  2. Add a histogram layer with a bin width of one point.
  3. Display the completed AQ histogram.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

ggplot()
Start a layered grammar-of-graphics plot. Check argument names and data types before running the code.ggplot2::ggplot(data, aes(...))
aes()
Map data variables to graphical features such as x, y or colour. Check argument names and data types before running the code.ggplot2::aes(x=..., y=..., colour=...)
geom_histogram()
Draw bins showing a distribution of numeric values. Check argument names and data types before running the code.ggplot2::geom_histogram(binwidth=...)
scale_x_continuous()
Control the continuous x-axis scale and tick marks. Check argument names and data types before running the code.ggplot2::scale_x_continuous(breaks=...)
theme_classic()
Apply a simple plotting theme with minimal background decoration. Check argument names and data types before running the code.ggplot2::theme_classic()
labs()
Set plot titles, axis labels and legend text. Check argument names and data types before running the code.ggplot2::labs(x="...", y="...", title="...")
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Define a ggplot object for the AQ total-score distribution.
#   p_aq <- ggplot2::ggplot(

# Step 2. Add a histogram layer with a bin width of one point.
#     ggplot2::geom_histogram(

# Step 3. Display the completed AQ histogram.
#   print(p_aq
Instructor: completed R solution — W3-M07
p_aq <- ggplot2::ggplot(aq_scores,ggplot2::aes(x=AQTotal)) +
 ggplot2::geom_histogram(binwidth=1,boundary=-0.5,colour="white") +
 ggplot2::scale_x_continuous(breaks=0:10) + ggplot2::theme_classic() +
 ggplot2::labs(x="AQ-10 score",y="Number of participants",title="AQ-10 score distribution")
print(p_aq)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-M07

W3-E01Independent ferdo Lab extension

Additional: variance, SE, missingness

MOODLE SOURCE NOTE: ferdo Lab original extension. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Explain why SD measures spread while SE relates to the sample mean.

Concept: Variance, standard deviation, standard error and missingness.

Task instructions — what the student must do

  1. Compute the sample variance of the AQ total scores.
  2. Compute the standard error using SD divided by the square root of n.
  3. Print variance and standard error with meaningful labels.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

var()
Calculate the sample variance. Variance has squared measurement units.var(x)
sd()
Calculate the sample standard deviation. With the default sample definition, SD uses n−1 in the denominator.sd(x)
sqrt()
Calculate the non-negative square root. Check argument names and data types before running the code.sqrt(x)
nrow()
Count the number of rows in a table. Check argument names and data types before running the code.nrow(data)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Compute the sample variance of the AQ total scores.
#   variance_aq <- var(

# Step 2. Compute the standard error using SD divided by the square root of n.
#   se_aq <- sd(

# Step 3. Print variance and standard error with meaningful labels.
#   print(c(
Instructor: completed R solution — W3-E01
variance_aq <- var(aq_scores$AQTotal)    # Sample variance (n-1 denominator).
se_aq <- sd(aq_scores$AQTotal)/sqrt(nrow(aq_scores))
print(c(sample_variance=variance_aq, standard_error=se_aq))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W3-E01

Week 4 — Visualisation With Ggplot2

Histograms, scatterplots, group summaries, ggplot2 syntax and clear interpretation.

W4-M01Moodle-aligned rewritten task

Histogram and binwidth

MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 histogram. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Create an AHI histogram; compare bins 5 and 1.

Concept: Distribution plots and the consequences of bin-width choices.

Task instructions — what the student must do

  1. Start a ggplot of the distribution of AHI scores.
  2. Add a histogram with a chosen bin width, then compare alternatives.
  3. Print the finished histogram.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

ggplot()
Start a layered grammar-of-graphics plot. Check argument names and data types before running the code.ggplot2::ggplot(data, aes(...))
aes()
Map data variables to graphical features such as x, y or colour. Check argument names and data types before running the code.ggplot2::aes(x=..., y=..., colour=...)
geom_histogram()
Draw bins showing a distribution of numeric values. Check argument names and data types before running the code.ggplot2::geom_histogram(binwidth=...)
labs()
Set plot titles, axis labels and legend text. Check argument names and data types before running the code.ggplot2::labs(x="...", y="...", title="...")
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
+
Add a graphics layer; here + combines plot components, not numbers.ggplot(...) + geom_histogram(...)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Start a ggplot of the distribution of AHI scores.
#   p_hist <- ggplot2::ggplot(

# Step 2. Add a histogram with a chosen bin width, then compare alternatives.
#     ggplot2::geom_histogram(

# Step 3. Print the finished histogram.
#   print(p_hist
Instructor: completed R solution — W4-M01
p_hist <- ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) +
 ggplot2::geom_histogram(binwidth=5,colour="white") +
 ggplot2::labs(x="AHI total",y="Count",title="AHI distribution")
print(p_hist)
print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) +
 ggplot2::geom_histogram(binwidth=1,colour="white") +
 ggplot2::labs(x="AHI total",y="Count",title="AHI distribution: binwidth 1"))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W4-M01

W4-M02Moodle-aligned rewritten task

Scatterplot and sex legend

MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 scatterplot. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Show AHI–CES-D association, then colour points by sex.

Concept: Scatterplots, two numeric variables and categorical colour legends.

Task instructions — what the student must do

  1. Create meaningful factor labels for the sex variable using the codebook.
  2. Start a scatterplot of AHI against CES-D, coloured by the sex labels.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

mutate()
Create or alter one or more columns. Check argument names and data types before running the code.dplyr::mutate(data, new=x+y)
factor()
Represent categorical values with ordered levels and labels. Incorrect levels or labels can reverse category meanings.factor(x, levels=..., labels=...)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
ggplot()
Start a layered grammar-of-graphics plot. Check argument names and data types before running the code.ggplot2::ggplot(data, aes(...))
aes()
Map data variables to graphical features such as x, y or colour. Check argument names and data types before running the code.ggplot2::aes(x=..., y=..., colour=...)
geom_point()
Plot individual observations as points. Check argument names and data types before running the code.ggplot2::geom_point()
labs()
Set plot titles, axis labels and legend text. Check argument names and data types before running the code.ggplot2::labs(x="...", y="...", title="...")
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Create meaningful factor labels for the sex variable using the codebook.
#   all_data <- dplyr::mutate(

# Step 2. Start a scatterplot of AHI against CES-D, coloured by the sex labels.
#   p_scatter <- ggplot2::ggplot(
Instructor: completed R solution — W4-M02
all_data <- dplyr::mutate(all_data,
 sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male")))
p_scatter <- ggplot2::ggplot(all_data,
 ggplot2::aes(x=ahiTotal,y=cesdTotal,colour=sex_label)) +
 ggplot2::geom_point(alpha=0.48) +
 ggplot2::labs(x="AHI total",y="CES-D total",colour="Sex")
print(p_scatter)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W4-M02

W4-M03Moodle-aligned rewritten task

Boxplot, violin, and raw points

MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 box/violin. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Recreate boxplot and add violin overlay; describe what each reveals.

Concept: Boxplots, violin plots, medians and interquartile ranges.

Task instructions — what the student must do

  1. Create a ggplot object that compares the outcome across groups.
  2. Add a boxplot layer to display medians and quartiles.
  3. Create a separate violin plot to show the shape of group distributions.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

ggplot()
Start a layered grammar-of-graphics plot. Check argument names and data types before running the code.ggplot2::ggplot(data, aes(...))
aes()
Map data variables to graphical features such as x, y or colour. Check argument names and data types before running the code.ggplot2::aes(x=..., y=..., colour=...)
factor()
Represent categorical values with ordered levels and labels. Incorrect levels or labels can reverse category meanings.factor(x, levels=..., labels=...)
geom_boxplot()
Draw median, quartile and whisker summaries. The box is not a mean ± SD interval.ggplot2::geom_boxplot()
labs()
Set plot titles, axis labels and legend text. Check argument names and data types before running the code.ggplot2::labs(x="...", y="...", title="...")
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
geom_violin()
Draw smoothed density shapes by group. Check argument names and data types before running the code.ggplot2::geom_violin()
geom_jitter()
Add small position offsets to reveal overlapping points. Check argument names and data types before running the code.ggplot2::geom_jitter(width=...)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Create a ggplot object that compares the outcome across groups.
#   p_box <- ggplot2::ggplot(

# Step 2. Add a boxplot layer to display medians and quartiles.
#     ggplot2::geom_boxplot(

# Step 3. Create a separate violin plot to show the shape of group distributions.
#   p_violin <- ggplot2::ggplot(
Instructor: completed R solution — W4-M03
p_box <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) +
 ggplot2::geom_boxplot(fill="#7cccd2",outlier.alpha=.4) +
 ggplot2::labs(x="Occasion code",y="AHI total")
print(p_box)
p_violin <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) +
 ggplot2::geom_violin(fill="#c3aaf5",alpha=.55) +
 ggplot2::geom_boxplot(width=.13,outlier.shape=NA) +
 ggplot2::geom_jitter(width=.1,alpha=.15,size=.45)
print(p_violin)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W4-M03

W4-M04Moodle-aligned rewritten task

Mean bar plot, grouped by intervention

MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 bar plot. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Compute means, then plot overall and grouped means.

Concept: Group means and bar plots of precomputed summaries.

Task instructions — what the student must do

  1. Create readable labels for the intervention groups.
  2. Start a pipeline from the full data to calculate group means.
  3. Group the data by intervention before calculating each mean.
  4. Start a bar chart of the precomputed group means.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

mutate()
Create or alter one or more columns. Check argument names and data types before running the code.dplyr::mutate(data, new=x+y)
factor()
Represent categorical values with ordered levels and labels. Incorrect levels or labels can reverse category meanings.factor(x, levels=..., labels=...)
group_by()
Set the grouping keys used by later operations. Remember to inspect or remove grouping after grouped summaries.dplyr::group_by(data, group)
summarise()
Produce summary statistics for the whole table or each group. Check argument names and data types before running the code.dplyr::summarise(data, metric=mean(x))
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)
ggplot()
Start a layered grammar-of-graphics plot. Check argument names and data types before running the code.ggplot2::ggplot(data, aes(...))
aes()
Map data variables to graphical features such as x, y or colour. Check argument names and data types before running the code.ggplot2::aes(x=..., y=..., colour=...)
geom_col()
Draw bars using already-calculated y values. Check argument names and data types before running the code.ggplot2::geom_col()
n()
Return the number of rows in the active dplyr group. Check argument names and data types before running the code.dplyr::n()
sd()
Calculate the sample standard deviation. With the default sample definition, SD uses n−1 in the denominator.sd(x)
|>
Pass a data frame through consecutive transformations.data |> group_by(...)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Create readable labels for the intervention groups.
#   intervention_labels <- c(

# Step 2. Start a pipeline from the full data to calculate group means.
#   means_groups <- all_data |>

# Step 3. Group the data by intervention before calculating each mean.
#     dplyr::group_by(

# Step 4. Start a bar chart of the precomputed group means.
#   p_bar <- ggplot2::ggplot(
Instructor: completed R solution — W4-M04
intervention_labels <- c("Signature strengths","Three good things",
                         "Gratitude visit","Early memories (control)")
all_data <- dplyr::mutate(all_data, intervention_label=factor(intervention,
 levels=1:4,labels=intervention_labels))
means_occasion <- all_data |>
 dplyr::group_by(occasion) |>
 dplyr::summarise(mean_ahi=mean(ahiTotal),.groups="drop")
print(ggplot2::ggplot(means_occasion,ggplot2::aes(x=factor(occasion),y=mean_ahi))+
 ggplot2::geom_col(fill="#9fd8c2"))
means_groups <- all_data |>
 dplyr::group_by(occasion,intervention_label) |>
 dplyr::summarise(n=dplyr::n(),mean_ahi=mean(ahiTotal),sd_ahi=sd(ahiTotal),
 .groups="drop")
print(ggplot2::ggplot(means_groups,ggplot2::aes(x=factor(occasion),y=mean_ahi,
 fill=intervention_label)) + ggplot2::geom_col(position="dodge"))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W4-M04

W4-M05Moodle-aligned rewritten task

Line plot and SD error bars

MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 line + ylim + SD. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Create line plot with 24–120 display range and ±1 SD bars.

Concept: Time-series group summaries and standard-deviation error bars.

Task instructions — what the student must do

  1. Start a plot of group mean scores across measurement occasions.
  2. Add a line layer connecting the occasion means.
  3. Add error bars for plus/minus one standard deviation, not a confidence interval.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

ggplot()
Start a layered grammar-of-graphics plot. Check argument names and data types before running the code.ggplot2::ggplot(data, aes(...))
aes()
Map data variables to graphical features such as x, y or colour. Check argument names and data types before running the code.ggplot2::aes(x=..., y=..., colour=...)
geom_line()
Connect points in x-axis order within groups. Check argument names and data types before running the code.ggplot2::geom_line()
geom_point()
Plot individual observations as points. Check argument names and data types before running the code.ggplot2::geom_point()
geom_errorbar()
Draw uncertainty or variability intervals between ymin and ymax. Label whether intervals are SD, SE or confidence intervals.ggplot2::geom_errorbar(aes(ymin=..., ymax=...))
coord_cartesian()
Zoom a plot to a selected viewing range without discarding data. Check argument names and data types before running the code.ggplot2::coord_cartesian(ylim=c(lo, hi))
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
labs()
Set plot titles, axis labels and legend text. Check argument names and data types before running the code.ggplot2::labs(x="...", y="...", title="...")
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Start a plot of group mean scores across measurement occasions.
#   p_line <- ggplot2::ggplot(

# Step 2. Add a line layer connecting the occasion means.
#     ggplot2::geom_line(

# Step 3. Add error bars for plus/minus one standard deviation, not a confidence interval.
#     ggplot2::geom_errorbar(
Instructor: completed R solution — W4-M05
p_line <- ggplot2::ggplot(means_groups,
 ggplot2::aes(x=occasion,y=mean_ahi,colour=intervention_label,group=intervention_label)) +
 ggplot2::geom_line() + ggplot2::geom_point() +
 ggplot2::geom_errorbar(ggplot2::aes(ymin=mean_ahi-sd_ahi,ymax=mean_ahi+sd_ahi),
 width=.13,na.rm=TRUE) + ggplot2::coord_cartesian(ylim=c(24,120)) +
 ggplot2::labs(x="Measurement occasion (0=pre, 1=post)",y="Mean AHI ± 1 SD")
print(p_line)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W4-M05

W4-M06Moodle-aligned rewritten task

Themes, labels, facets and point style

MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 2 themes / labels / facet / size-shape-colour. VERIFIED W1-4 MOODLE TASK ALIGNMENT (rewritten solution).

Learning goal: Compare themes, add titles, facet by sex, style points.

Concept: ggplot2 layers, themes, faceting and graphical annotations.

Task instructions — what the student must do

  1. Print the existing scatterplot using a classic ggplot theme.
  2. Print a faceted version of the scatterplot, separated by the sex labels.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
theme_classic()
Apply a simple plotting theme with minimal background decoration. Check argument names and data types before running the code.ggplot2::theme_classic()
labs()
Set plot titles, axis labels and legend text. Check argument names and data types before running the code.ggplot2::labs(x="...", y="...", title="...")
theme_bw()
Apply a black-and-white plotting theme. Check argument names and data types before running the code.ggplot2::theme_bw()
facet_wrap()
Split a plot into small panels by one or more grouping variables. Check argument names and data types before running the code.ggplot2::facet_wrap(~ group)
ggplot()
Start a layered grammar-of-graphics plot. Check argument names and data types before running the code.ggplot2::ggplot(data, aes(...))
aes()
Map data variables to graphical features such as x, y or colour. Check argument names and data types before running the code.ggplot2::aes(x=..., y=..., colour=...)
geom_point()
Plot individual observations as points. Check argument names and data types before running the code.ggplot2::geom_point()

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Print the existing scatterplot using a classic ggplot theme.
#   print(p_scatter + ggplot2::theme_classic(

# Step 2. Print a faceted version of the scatterplot, separated by the sex labels.
#   print(p_scatter + ggplot2::facet_wrap(
Instructor: completed R solution — W4-M06
print(p_scatter + ggplot2::theme_classic() +
 ggplot2::labs(title="Happiness and depressive symptoms"))
print(p_scatter + ggplot2::theme_bw() + ggplot2::facet_wrap(~sex_label))
print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal,y=cesdTotal)) +
 ggplot2::geom_point(colour="purple",size=2,shape=17,alpha=.3) +
 ggplot2::labs(title="Styled scatterplot",x="AHI",y="CES-D"))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W4-M06

W4-E01Independent ferdo Lab extension

Additional: correlation and ATSQ context

MOODLE SOURCE NOTE: ferdo Lab original extension; ATSQ file is separate from original W4 plot tasks. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Distinguish graphical association from causal effect; inspect separate ATSQ join.

Concept: Correlation is association, not causation; use caution with ATSQ.

Task instructions — what the student must do

  1. Calculate the correlation between AHI and CES-D with missing-data handling.
  2. Import the separately supplied ATSQ scores as a data frame.
  3. Join ATSQ scores to participant information by participant ID.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

cor()
Calculate a correlation coefficient, Pearson by default. Correlation does not establish causation.cor(x, y, use="complete.obs")
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
read.csv()
Read a comma-separated file into a data frame. Check argument names and data types before running the code.read.csv("data/file.csv", stringsAsFactors=FALSE)
inner_join()
Keep and combine rows whose keys match in both tables. Check join keys, missing matches and possible duplicate-key expansion.dplyr::inner_join(x, y, by="id")
nrow()
Count the number of rows in a table. Check argument names and data types before running the code.nrow(data)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Calculate the correlation between AHI and CES-D with missing-data handling.
#   cor_ahi_cesd <- cor(

# Step 2. Import the separately supplied ATSQ scores as a data frame.
#   atsq <- read.csv(

# Step 3. Join ATSQ scores to participant information by participant ID.
#   atsq_merged <- dplyr::inner_join(
Instructor: completed R solution — W4-E01
cor_ahi_cesd <- cor(all_data$ahiTotal,all_data$cesdTotal,use="complete.obs")
print(c(correlation=cor_ahi_cesd))
atsq <- read.csv("data/atsq_scores.csv",stringsAsFactors=FALSE)
p_background <- read.csv("data/p_info.csv",stringsAsFactors=FALSE)
atsq_joint <- dplyr::inner_join(atsq,p_background,by="id")
print(c(atsq_rows=nrow(atsq),background_rows=nrow(p_background),joined=nrow(atsq_joint)))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W4-E01

Week 5 — Sampling And Bootstrap — Independent Extension

Standard deviation, standard error, random resampling and bootstrap demonstrations.

W5-E01Independent ferdo Lab extension

Sampling distributions and descriptive variation

MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson; no verified Moodle Week 5 file. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Compare SD of observed times and standard error of their mean.

Concept: Sampling, sample variability, SD and SE of the sample mean.

Task instructions — what the student must do

  1. Create a short fictional sample of reaction times in milliseconds.
  2. Calculate how many reaction times are in the sample.
  3. Calculate the sample mean reaction time.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
length()
Return the number of elements in a vector. Check argument names and data types before running the code.length(x)
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)
sd()
Calculate the sample standard deviation. With the default sample definition, SD uses n−1 in the denominator.sd(x)
sqrt()
Calculate the non-negative square root. Check argument names and data types before running the code.sqrt(x)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Create a short fictional sample of reaction times in milliseconds.
#   rt_demo <- c(

# Step 2. Calculate how many reaction times are in the sample.
#   rt_n <- length(

# Step 3. Calculate the sample mean reaction time.
#   rt_mean <- mean(
Instructor: completed R solution — W5-E01
rt_demo <- c(620,610,590,605,615,600,625,595,608,612,598,603)
rt_n <- length(rt_demo)
rt_mean <- mean(rt_demo)
rt_sd <- sd(rt_demo)
rt_se <- rt_sd / sqrt(rt_n)
print(c(n=rt_n,mean_ms=rt_mean,sd_ms=rt_sd,se_ms=rt_se))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W5-E01

W5-E02Independent ferdo Lab extension

Resampling and bootstrap percentile interval

MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson; no verified Moodle Week 5 file. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Observe spread of mean for resampling n=4 vs n=10; use n=12 for the bootstrap CI.

Concept: Resampling distributions, reproducibility and percentile bootstrap intervals.

Task instructions — what the student must do

  1. Set the random seed so the resampling results can be reproduced.
  2. Simulate 1,000 resampled means using a sample size of four.
  3. Simulate 1,000 resampled means using a sample size of ten.
  4. Simulate a bootstrap distribution using the observed sample size.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

set.seed()
Fix the pseudorandom generator state for reproducible output. Check argument names and data types before running the code.set.seed(46)
replicate()
Evaluate an expression repeatedly and collect the results. Check argument names and data types before running the code.replicate(B, expression)
mean()
Calculate an arithmetic average. Use na.rm=TRUE to omit missing values explicitly.mean(x, na.rm=FALSE)
sample()
Draw elements with or without replacement. Bootstrap sampling usually uses replace=TRUE and the observed sample size.sample(x, size, replace=TRUE)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
sd()
Calculate the sample standard deviation. With the default sample definition, SD uses n−1 in the denominator.sd(x)
quantile()
Calculate the requested percentiles of a vector. A percentile interval is only as credible as its resampling assumptions.quantile(x, probs=c(.025,.975))
replace=TRUE
Allow an observed data point to appear more than once in a resample.sample(x, n, replace=TRUE)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Set the random seed so the resampling results can be reproduced.
#   set.seed(

# Step 2. Simulate 1,000 resampled means using a sample size of four.
#   means_n4 <- replicate(

# Step 3. Simulate 1,000 resampled means using a sample size of ten.
#   means_n10 <- replicate(

# Step 4. Simulate a bootstrap distribution using the observed sample size.
#   bootstrap_means <- replicate(
Instructor: completed R solution — W5-E02
set.seed(46)                              # Fix pseudo-random seed for reproducibility.
means_n4 <- replicate(1000,mean(sample(rt_demo,4,replace=TRUE)))
means_n10 <- replicate(1000,mean(sample(rt_demo,10,replace=TRUE)))
print(c(sd_n4=sd(means_n4),sd_n10=sd(means_n10)))
bootstrap_means <- replicate(1000,mean(sample(rt_demo,rt_n,replace=TRUE)))
bootstrap_ci <- quantile(bootstrap_means,c(.025,.975))
print(bootstrap_ci)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W5-E02

W5-E03Independent ferdo Lab extension

Visualising the resampling distributions

MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Explain why the n=10 distribution is usually narrower.

Concept: Visual comparison of sampling distributions and mean precision.

Task instructions — what the student must do

  1. Arrange two Base R plots side by side using par().
  2. Plot the resampled means for samples of size four.
  3. Plot the resampled means for samples of size ten, then compare spreads.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

par()
Set graphical parameters such as the plot-panel layout. Check argument names and data types before running the code.par(mfrow=c(1,2))
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
hist()
Draw a Base R histogram. Check argument names and data types before running the code.hist(x, main="...", xlab="...")

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Arrange two Base R plots side by side using par().
#   par(mfrow=

# Step 2. Plot the resampled means for samples of size four.
#   hist(means_n4,

# Step 3. Plot the resampled means for samples of size ten, then compare spreads.
#   hist(means_n10,
Instructor: completed R solution — W5-E03
par(mfrow=c(1,2))
hist(means_n4,main="Resample size 4",xlab="Mean RT (ms)")
hist(means_n10,main="Resample size 10",xlab="Mean RT (ms)")
par(mfrow=c(1,1))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W5-E03

Week 6 — Inference And Regression — Independent Extension

One-sample t testing, confidence intervals, effect sizes, correlation and linear models.

W6-E01Independent ferdo Lab extension

Null hypothesis and one-sample t test

MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson; no verified Moodle Week 6 file. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Compute t manually, compare with t.test, interpret p and confidence interval.

Concept: Null hypothesis, t statistic, degrees of freedom and p value.

Task instructions — what the student must do

  1. Set the null-hypothesis population mean to 600 milliseconds.
  2. Calculate the one-sample t statistic from the observed mean, SD and n.
  3. Run t.test() against the same null hypothesis and compare the outputs.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

sqrt()
Calculate the non-negative square root. Check argument names and data types before running the code.sqrt(x)
pt()
Evaluate the cumulative probability for a Student t distribution. For a two-sided t test, account for both tails of the distribution.pt(t, df)
abs()
Return the absolute value of each number. Check argument names and data types before running the code.abs(x)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
t.test()
Perform a Student t test and report a confidence interval. Check argument names and data types before running the code.t.test(x, mu=600)
p-value
Compute the two-sided tail probability under the null model.2 * pt(-abs(t), df)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Set the null-hypothesis population mean to 600 milliseconds.
#   mu0 <-

# Step 2. Calculate the one-sample t statistic from the observed mean, SD and n.
#   t_manual <-

# Step 3. Run t.test() against the same null hypothesis and compare the outputs.
#   model_t <- t.test(
Instructor: completed R solution — W6-E01
mu0 <- 600                               # Hypothesised population mean, milliseconds.
t_manual <- (rt_mean-mu0)/(rt_sd/sqrt(rt_n))
df_manual <- rt_n-1
two_sided_p <- 2*pt(-abs(t_manual),df=df_manual)
print(c(t=t_manual,df=df_manual,p=two_sided_p))
model_t <- t.test(rt_demo,mu=mu0,alternative="two.sided",conf.level=.95)
print(model_t)

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W6-E01

W6-E02Independent ferdo Lab extension

Confidence interval and effect size

MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Check 95% t interval, and distinguish statistical and practical significance.

Concept: A 95% t confidence interval, effect size and practical significance.

Task instructions — what the student must do

  1. Find the critical t value for a two-sided 95% confidence interval.
  2. Calculate the confidence interval manually from the sample mean and SE.
  3. Calculate Cohen’s d to describe standardised effect size.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

qt()
Get a quantile (critical value) of a Student t distribution. For a two-sided 95% CI, the upper reference quantile is 0.975.qt(.975, df)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
stopifnot()
Stop execution with an error if a logical condition is not true. Check argument names and data types before running the code.stopifnot(condition)
all.equal()
Compare objects approximately, allowing small numeric tolerances. Check argument names and data types before running the code.all.equal(x, y)
as.numeric()
Convert values to numeric form when conversion is meaningful. Check argument names and data types before running the code.as.numeric(x)

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Find the critical t value for a two-sided 95% confidence interval.
#   critical_t <- qt(

# Step 2. Calculate the confidence interval manually from the sample mean and SE.
#   ci_manual <-

# Step 3. Calculate Cohen’s d to describe standardised effect size.
#   cohens_d <-
Instructor: completed R solution — W6-E02
critical_t <- qt(.975,df=rt_n-1)
ci_manual <- rt_mean + c(-1,1)*critical_t*rt_se
cohens_d <- (rt_mean-mu0)/rt_sd
print(c(ci_lower=ci_manual[1],ci_upper=ci_manual[2],cohens_d=cohens_d))
stopifnot(isTRUE(all.equal(as.numeric(ci_manual),as.numeric(model_t$conf.int),tolerance=1e-8)))

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W6-E02

W6-E03Independent ferdo Lab extension

Regression, correlation, model diagnostics

MOODLE SOURCE NOTE: Independent ferdo Lab enrichment; not a verified Moodle Week 6 worksheet. INDEPENDENT ADDITION (not an official Moodle task).

Learning goal: Interpret slope, R^2; explain confounding and dependence from repeated observations.

Concept: Linear regression, model coefficients, R-squared and assumptions.

Task instructions — what the student must do

  1. Fit a linear regression model predicting one variable from another.
  2. Print the model summary, including coefficients and R-squared.
  3. Print the correlation coefficient for comparison with the regression.

Concept explanation and statistical interpretation

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.

R functions and syntax reference

lm()
Fit a linear statistical model using a formula. A simple linear model requires careful checks of its assumptions.lm(y ~ x, data=data)
print()
Display an object or calculated value in the R Console. Printing does not change the object being displayed.print(x)
summary()
Show a descriptive or model-specific statistical summary. Check argument names and data types before running the code.summary(model)
c()
Combine multiple values into a vector. Mixed types may be coerced to a single common type.c(a, b, ...)
cor()
Calculate a correlation coefficient, Pearson by default. Correlation does not establish causation.cor(x, y, use="complete.obs")

Student workbook: instructions and unfinished code

Each commented prefix corresponds to the step above. Remove #, finish the expression, and run that line in RStudio.

# Step 1. Fit a linear regression model predicting one variable from another.
#   fit <- lm(

# Step 2. Print the model summary, including coefficients and R-squared.
#   print(summary(

# Step 3. Print the correlation coefficient for comparison with the regression.
#   print(c(r=
Instructor: completed R solution — W6-E03
fit <- lm(cesdTotal~ahiTotal,data=all_data)
print(summary(fit)$coefficients)
print(c(r=cor(all_data$ahiTotal,all_data$cesdTotal),
        r_squared=summary(fit)$r.squared))
# Assumptions: independence, approximately linear relationship, conditional errors.

Permanent section link: https://lab.ferdo.net/r/pals-masterclass/#W6-E03

Statistics, mathematics and interpretation reference

Descriptive statistics

Mean: the sum of values divided by the number of included observations. Sample variance: sum of squared deviations from the mean divided by n-1. SD: square root of variance, expressed in the original measurement units. SE: approximately SD/sqrt(n) for independent observations.

Missing values should be handled transparently; na.rm=TRUE excludes them from that calculation rather than replacing them with zero.

Inferential statistics

One-sample t statistic: (sample_mean - mu0)/(sample_sd/sqrt(n)). The reference degrees of freedom are n-1. A two-sided 95% t interval is sample_mean ± qt(0.975, df) * SE. A p value is calculated under the null model; it is not the probability that the null is true.

Bootstrap examples use repeated sampling with replacement. Always distinguish a simulation of sampling variability from evidence about representativeness.

Regression and causality

lm(y ~ x, data=df) fits a linear model. The estimated slope gives a model-based expected change in y for one unit of x. Linear association is not causality, and repeated observations for one participant do not satisfy a naive independence assumption.

Psychology data ethics

Codebook truth takes priority when a worksheet uses conflicting shortcuts: occasion 0 = baseline, occasion 1 = posttest, occasion 2 = follow-up; sex 1 = Female, sex 2 = Male. The AQ-10 screening threshold must not be described as a clinical diagnosis. Synthetic examples are not evidence about real populations.

Data dictionary and file roles

FilePurposeKey fields
participant-info.csvDemographics and interventionid, intervention, sex, age
ahi-cesd.csvRepeated wellbeing scoresid, intervention, occasion, ahiTotal, cesdTotal
AQ.csvTen questionnaire responsesparticipant ID, Q1–Q10
Qformats.csvForward/reverse question directionquestion, format
Scoring.csvMapping responses to pointsformat, response, score
atsq_scores.csvSeparate illustrative outcome scoresid, atsqTotal
p_info.csvAdditional background informationid and background variables

Suggested 90-minute lesson structure

0–10 minutes: recall and student predictions. 10–30 minutes: concept and code syntax. 30–55 minutes: instructor demonstration. 55–75 minutes: student workbook completion. 75–90 minutes: output interpretation, debugging and an exit question. The timing is flexible and can be spread across multiple sessions per week.

Download or teach offline

Download instructor RDownload student RDownload ZIP with data

When extracted, opening index.html in any modern browser provides offline navigation. Running R requires a local R/RStudio installation; no server is needed for the teaching files.