# SUPERPOWERED INSTRUCTOR'S KIT — annotated teacher script
# Version: 2026-11-15 | 38 exercises across six weeks | Language: English
# New companion artifact. The original Teacher_Master.R was not edited.
# Use: download the EXISTING open six-week ZIP, extract it, and place THIS
# script beside its data/ directory. Set RStudio working directory there.
# https://paths.ferdo.net/r/pals-masterclass/UCL-PALS-Integrated-6-Weeks-OPEN.zip
# Public data are fictional; output templates reflect that. R runtime not
# executed here, so any dataset-dependent values MUST be checked in RStudio.
# W1–W4 = Moodle-aligned rewritten exercises; W5–W6 = ferdo Lab extensions.
# ==========================================================================

# ============================================================================
# ferdo Lab | UCL PALS — SIX-WEEK R, PROGRAMMING AND STATISTICS MASTERCLASS
# ============================================================================
#
# VERSION: 3.0 | 12 November 2026
# LANGUAGE: English throughout the teaching materials.
# STATUS: Independent teaching companion; not official UCL solutions.
#
# WEEKS 1–4: Rewritten worked examples aligned to supplied Moodle practicals.
# WEEKS 5–6: Independent ferdo Lab extensions; not verified Moodle tasks.
#
# WORKFLOW:
#   1. Extract the entire ZIP; preserve this script beside index.html and data/.
#   2. Set the RStudio working directory to the extracted course folder.
#   3. Follow task IDs W1-M01 ... W6-E03 in numerical order.
#   4. For detailed syntax, click each task's DETAILS URL below.
#   5. Install tidyverse once (in Console) for Weeks 2–4 if needed:
#        install.packages("tidyverse")
#
# PUBLIC / PRIVATE DATA:
#   The public package contains fully fictional, schema-compatible datasets.
#   The private instructor ZIP contains supplied original sources for personal use.
# ============================================================================

# ROLE: INSTRUCTOR — completed code, short function notes and deep links.


# ==============================================================================
# WEEK 1 — R BASICS AND FAMILIARISATION
# ==============================================================================
# Setup: verify that the package data directory is present.
if (!dir.exists("data")) {
  stop("Missing data/ folder. Set the working directory to the course folder.")
}


# ============================================================================
#  W1-M01  |  R as calculator
# ============================================================================
#
# MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 1 [MOODLE-ALIGNED]
# LEARNING GOAL: Compute 6+1, 2*(1+3), then predict 5+3*2.
# CONCEPT: Arithmetic operators, operator precedence and parentheses.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W1-M01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Add 6 and 1, then print the result.
# Step 2. Multiply 2 by the sum of 1 and 3, then print the result.
# Step 3. Calculate 5 plus 3 times 2, then print the result.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# print [print(x)]: Display an object or calculated value in the R Console.

# SUPERPOWERED TEACHING NOTE:
# R distinguishes an expression from its printed result. Parentheses govern the order of operations, and the number [1] shown by the Console is a vector index, not part of the answer.
# IN-CLASS MOVE: Ask learners to predict each number before executing. Comparing the second and third expression reveals the precedence rules.
# COMMON PITFALL: Do not interpret R’s console prefix [1] as the result.
#
# NOTE: Show the result in the Console (or display a ggplot object).
print(6 + 1)                              # Addition returns 7.
# NOTE: Show the result in the Console (or display a ggplot object).
print(2 * (1 + 3))                      # Parentheses evaluated first; returns 8.
# NOTE: Show the result in the Console (or display a ggplot object).
print(5 + 3 * 2)                        # Multiplication precedes addition: 11.


# ============================================================================

# EXPECTED OUTPUT — Exact, deterministic example:
# [1] 7
# [1] 8
# [1] 11
# Data-dependent previews are templates, not verified Console output.
#  W1-M02  |  Assignment and reusable results
# ============================================================================
#
# MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 2 [MOODLE-ALIGNED]
# LEARNING GOAL: Save 1+...+10 as x. Divide x by ten.
# CONCEPT: Assignment, reusable objects, numeric sequences and the R environment.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W1-M02
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Add the integers from 1 to 10 and store the total in x.
# Step 2. Divide the stored total x by 10 and print the result.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values.
# print [print(x)]: Display an object or calculated value in the R Console.
# ls [ls()]: List names of objects currently in the R environment.
# <- [name <- expression]: Assign the result of the right-hand expression to an object
# name.

# SUPERPOWERED TEACHING NOTE:
# The assignment arrow stores the result of an expression, so later instructions can reuse it instead of recomputing it. 1:10 creates an inclusive integer sequence.
# IN-CLASS MOVE: Contrast an object name (x) with the value it currently holds; show how changing x changes subsequent calculations.
# COMMON PITFALL: ls() is session-dependent and may include other objects.
#
# NOTE: Assign the computed expression to x for reuse.
x <- sum(1:10)                          # 1:10 is an inclusive integer sequence; sum gives 55.
# NOTE: Show the result in the Console (or display a ggplot object).
print(x / 10)                           # Stored result re-used; expected 5.5.
# NOTE: Show the result in the Console (or display a ggplot object).
print(ls())                             # List current objects; not the same as printing values.


# ============================================================================

# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# [1] 5.5
# # ls() also lists current object names; that list depends on the R session.
# Data-dependent previews are templates, not verified Console output.
#  W1-M03  |  Text, numbers, vectors and indexing
# ============================================================================
#
# MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 3 and naming notes
# [MOODLE-ALIGNED]
# LEARNING GOAL: Store a character, integer, and vector; extract values above six.
# CONCEPT: Character and numeric values; vectors, logical masks and indexing.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W1-M03
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Store a short text label in course_label.
# Step 2. Store the number of participants as an integer in participants.
# Step 3. Create a numeric vector containing four fictional sleep durations.
# Step 4. Print only the sleep durations greater than 6 hours using logical indexing.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# c [c(a, b, ...)]: Combine multiple values into a vector.
# print [print(x)]: Display an object or calculated value in the R Console.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
# [ ] [x[i]]: Select one or more values by 1-based position or logical mask.

# SUPERPOWERED TEACHING NOTE:
# Character values need quotation marks; the L suffix creates an integer literal. A logical mask is a TRUE/FALSE vector that selects entries at matching positions.
# IN-CLASS MOVE: Have the learner evaluate sleep_hours > 6 before indexing and explain why R starts indexing at 1.
# COMMON PITFALL: In sleep_hours[sleep_hours > 6], the condition is applied element by element.
#
# NOTE: Create a named value or vector that later steps can inspect.
course_label <- "PALS practice"          # Character/string is enclosed in quotes.
# NOTE: Create a named value or vector that later steps can inspect.
participants <- 66L                      # L makes an integer literal.
# NOTE: Create a named value or vector that later steps can inspect.
sleep_hours <- c(6, 8, 7, 5)            # c() combines values into a numeric vector.
# NOTE: Show the result in the Console (or display a ggplot object).
print(sleep_hours[1])                    # R indices start at 1, not 0.
# NOTE: Show the result in the Console (or display a ggplot object).
print(sleep_hours[sleep_hours > 6])      # Logical indexing -> c(8, 7).
# NOTE: Show the result in the Console (or display a ggplot object).
print(mean(sleep_hours))                 # Expected 6.5 hours, for illustrative data.


# ============================================================================

# EXPECTED OUTPUT — Exact, deterministic example:
# [1] 6
# [1] 8 7
# [1] 6.5
# Data-dependent previews are templates, not verified Console output.
#  W1-M04  |  Built-in USArrests data frame
# ============================================================================
#
# MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 5 [MOODLE-ALIGNED]
# LEARNING GOAL: Load USArrests into crime; inspect variables and dimensions.
# CONCEPT: Data frames, observations (rows) and variables (columns).
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W1-M04
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Assign the built-in USArrests dataset to an object called crime.
# Step 2. Print the number of rows and columns in crime.
# Step 3. Print all column names in crime.
# Step 4. Print the first six rows of crime.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# print [print(x)]: Display an object or calculated value in the R Console.
# dim [dim(x)]: Return dimensions, usually rows and columns.
# names [names(data)]: Get or assign column or element names.
# head [head(x, n=6)]: Show the first n elements or rows.

# SUPERPOWERED TEACHING NOTE:
# USArrests is an R built-in data frame with one row per US state and four crime-rate/urbanisation variables. dim(), names() and head() inspect its structure.
# IN-CLASS MOVE: Distinguish rows (observations) from columns (variables); head() is a preview and does not alter the data.
# COMMON PITFALL: Built-in datasets are useful for syntax practice, not evidence about present-day conditions.
#
# NOTE: Assign a built-in teaching data frame to a working object.
crime <- USArrests                        # Built-in public teaching dataset, no CSV needed.
# NOTE: Show the result in the Console (or display a ggplot object).
print(dim(crime))                         # Rows and columns: 50 by 4.
# NOTE: Show the result in the Console (or display a ggplot object).
print(names(crime))                       # Murder, Assault, UrbanPop, Rape.
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(crime))                        # Display the first six rows.


# ============================================================================

# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# [1] 50 4
# [1] "Murder" "Assault" "UrbanPop" "Rape"
# # head(crime) prints six state rows with four columns.
# Data-dependent previews are templates, not verified Console output.
#  W1-M05  |  Functions, help and extraction
# ============================================================================
#
# MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · tasks 6–8 [MOODLE-ALIGNED]
# LEARNING GOAL: Find the maximum Murder and Assault; inspect max() help.
# CONCEPT: Function calls, named arguments, the dollar operator and help pages.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W1-M05
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Print the maximum value in the Murder column of crime.
# Step 2. Print the Assault column (or a relevant element of it) using the dollar operator.
# Step 3. Open R help for the max function.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# print [print(x)]: Display an object or calculated value in the R Console.
# max [max(x, na.rm=FALSE)]: Return the largest value.
# c [c(a, b, ...)]: Combine multiple values into a vector.

# SUPERPOWERED TEACHING NOTE:
# Functions take arguments and return values. The $ operator accesses a named data-frame column; max() returns its largest value.
# IN-CLASS MOVE: Contrast the complete Murder vector with max(crime$Murder), and use ?max to discover optional arguments.
# COMMON PITFALL: A maximum is a descriptive statistic; it does not identify its row unless you inspect which state attained it.
#
# NOTE: Show the result in the Console (or display a ggplot object).
print(max(c(2, 4, 6)))                   # max() needs a vector or individual numbers.
# NOTE: Show the result in the Console (or display a ggplot object).
print(crime$Murder)                       # $ extracts the column named Murder.
# NOTE: Show the result in the Console (or display a ggplot object).
print(max(crime$Murder))                  # Expected 17.4 in USArrests.
# NOTE: Show the result in the Console (or display a ggplot object).
print(max(crime$Assault))                 # Expected 337 in USArrests.
# Interactive help: type ?max or help("max") in the RStudio Console.


# ============================================================================

# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# [1] 6
# # print(crime$Murder) shows all 50 values (truncated here).
# [1] 17.4
# [1] 337
# Data-dependent previews are templates, not verified Console output.
#  W1-M06  |  Removing objects
# ============================================================================
#
# MOODLE SOURCE NOTE: pr01. Familiarisation (3).r · task 9 [MOODLE-ALIGNED]
# LEARNING GOAL: Remove crime from the environment.
# CONCEPT: Removing an in-memory object without deleting the source file.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W1-M06
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Remove the crime object from the current R environment.
# Step 2. Print whether an object called crime still exists.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# rm [rm(object)]: Remove an object from the current R environment.
# print [print(x)]: Display an object or calculated value in the R Console.
# exists [exists("x")]: Check whether an object of the specified name exists.

# SUPERPOWERED TEACHING NOTE:
# rm() removes an object from the current R environment; it does not delete a CSV or modify the built-in dataset. exists() checks whether a name is still defined.
# IN-CLASS MOVE: Ask what happens if the next command tries to use crime after rm(crime).
# COMMON PITFALL: Running this section twice without recreating crime can fail because rm(crime) cannot find it.
#
# NOTE: Remove the named object from the R environment.
rm(crime)                                # Remove only this named object from Environment.
# NOTE: Show the result in the Console (or display a ggplot object).
print(exists("crime"))                   # FALSE: object no longer exists.


# ============================================================================

# EXPECTED OUTPUT — Exact, deterministic example:
# [1] FALSE
# Data-dependent previews are templates, not verified Console output.
#  W1-E01  |  Additional: reproducible report
# ============================================================================
#
# MOODLE SOURCE NOTE: ferdo Lab original extension [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Define the outcome, predictor, unit, and one possible confounder.
# CONCEPT: Research questions, variables, units and possible confounding.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W1-E01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Write a fictional research question and store it as a text value.
# Step 2. Create a short numeric vector of fictional reaction times.
# Step 3. Print a small data frame with trial numbers and reaction times.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# c [c(a, b, ...)]: Combine multiple values into a vector.
# print [print(x)]: Display an object or calculated value in the R Console.
# data.frame [data.frame(x=..., y=...)]: Build a rectangular data frame from named columns.
# seq_along [seq_along(x)]: Create integer indices from 1 through length(x).

# SUPERPOWERED TEACHING NOTE:
# A research question, a numeric vector and a data frame represent different kinds of information. A small fictional dataset is a safe way to practise reporting.
# IN-CLASS MOVE: Separate a question from a claim of causation and make the measurement unit explicit (milliseconds).
# COMMON PITFALL: Four simulated trials do not support general population conclusions.
#
# NOTE: Assign the computed result to a named R object.
question <- "Does sleep predict response time?"  # A question is not a causal conclusion.
# NOTE: Define an illustrative vector of fictional reaction times.
rt_demo <- c(620, 610, 590, 605)         # Fictional reaction times in milliseconds.
# NOTE: Show the result in the Console (or display a ggplot object).
print(data.frame(trial=seq_along(rt_demo), rt_ms=rt_demo))

# EXPECTED OUTPUT — Exact, deterministic example:
#   trial rt_ms
# 1     1   620
# 2     2   610
# 3     3   590
# 4     4   605
# Data-dependent previews are templates, not verified Console output.


# ==============================================================================
# WEEK 2 — DATA WRANGLING
# ==============================================================================
# Setup: install packages only if needed; do not install during the lesson.
for (pkg in c("dplyr", "tidyr", "ggplot2")) {
  if (!requireNamespace(pkg, quietly = TRUE)) {
    stop(paste0("Package not installed: ", pkg, ". Install tidyverse in Console."))
  }
}


# ============================================================================
#  W2-M01  |  Packages and importing CSV
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part A tasks 1–2 [MOODLE-ALIGNED]
# 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.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Import data/participant-info.csv as the data frame pinfo.
# Step 2. Import data/ahi-cesd.csv as the data frame responses.
# Step 3. Print the participant and response row counts using nrow().

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# read.csv [read.csv("data/file.csv", stringsAsFactors=FALSE)]: Read a comma-separated file
# into a data frame.
# print [print(x)]: Display an object or calculated value in the R Console.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# nrow [nrow(data)]: Count the number of rows in a table.

# SUPERPOWERED TEACHING NOTE:
# read.csv() loads rows and columns from disk into R. stringsAsFactors=FALSE keeps text as text; the working directory determines where the relative data/ path begins.
# IN-CLASS MOVE: Identify each file’s unit of observation before joining: one participant-level table versus repeated measurements.
# COMMON PITFALL: Extract the full package and keep the data/ directory adjacent to the script.
#
# Run install.packages("tidyverse") ONCE in Console if not installed.
# library(tidyverse) loads dplyr, tidyr, ggplot2 etc. from your R library.
# NOTE: Read an input CSV into a data frame; check the working directory.
pinfo <- read.csv("data/participant-info.csv", stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
responses <- read.csv("data/ahi-cesd.csv", stringsAsFactors=FALSE)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(n_participants=nrow(pinfo), n_responses=nrow(responses)))


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# n_participants n_responses
# # Numeric counts depend on the included fictional public CSV files.
# Data-dependent previews are templates, not verified Console output.
#  W2-M02  |  Join by a compound key
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part A task 3 [MOODLE-ALIGNED]
# LEARNING GOAL: Join demographics and repeated responses by id AND intervention.
# CONCEPT: Joining tables on the correct compound key and checking cardinality.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M02
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Join participant information and responses by both id and intervention.
# Step 2. Print the row count and number of distinct participants after the join.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in
# both tables.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# print [print(x)]: Display an object or calculated value in the R Console.
# nrow [nrow(data)]: Count the number of rows in a table.
# n_distinct [dplyr::n_distinct(x)]: Count distinct values in a vector.
# stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is
# not true.

# SUPERPOWERED TEACHING NOTE:
# A compound join key uses BOTH id and intervention to associate each measurement with the correct participant information. A join can silently lose rows or multiply records if keys are wrong.
# IN-CLASS MOVE: Ask why joining only on intervention would create many-to-many matches, and how to check uniqueness of the participant table.
# COMMON PITFALL: The nrow inequality catches row explosions but does not prove that all records matched correctly.
#
# NOTE: Join matching records using the specified key columns.
all_data <- dplyr::inner_join(pinfo, responses, by=c("id","intervention"))
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(rows=nrow(all_data), unique_ids=dplyr::n_distinct(all_data$id)))
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(nrow(all_data) <= nrow(responses))  # Key matching must not explode rows.


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# rows unique_ids
# # Two named counts; values depend on the loaded public datasets.
# Data-dependent previews are templates, not verified Console output.
#  W2-M03  |  select(): columns
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B select [MOODLE-ALIGNED]
# LEARNING GOAL: Select nine requested columns.
# CONCEPT: Selecting columns rather than filtering rows.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M03
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Select the required analysis columns without removing any rows.
# Step 2. Print the resulting column names to verify your selection.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.
# print [print(x)]: Display an object or calculated value in the R Console.
# names [names(data)]: Get or assign column or element names.

# SUPERPOWERED TEACHING NOTE:
# select() controls WHICH variables are retained, not which observations. Keeping a small, explicitly named set makes later calculations easier to inspect.
# IN-CLASS MOVE: Ask learners to explain why the output has nine names but potentially many rows.
# COMMON PITFALL: select() preserves row count and existing order unless other operations are added.
#
# NOTE: Choose or rename variables; this does not filter rows.
selected_vars <- dplyr::select(all_data, id, intervention, ahiTotal, cesdTotal,
# NOTE: Continue this R expression with its arguments, values or formatting.
                                sex, age, educ, income, occasion)
# NOTE: Show the result in the Console (or display a ggplot object).
print(names(selected_vars))               # Exactly nine specified columns.


# ============================================================================

# EXPECTED OUTPUT — Exact, deterministic example:
# [1] "id" "intervention" "ahiTotal" "cesdTotal" "sex"
# [6] "age" "educ" "income" "occasion"
# Data-dependent previews are templates, not verified Console output.
#  W2-M04  |  filter(): observations and a source correction
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B filter, compared with code-book.pdf
# [MOODLE-ALIGNED]
# LEARNING GOAL: Filter baseline (0) and posttest (1) after reading codebook.
# CONCEPT: Filtering rows and interpreting the measurement-time codebook.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M04
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Keep only observations at baseline (occasion 0) and posttest (occasion 1).
# Step 2. Count and print observations for each occasion to check the filter.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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().
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# filter [dplyr::filter(data, condition)]: Keep rows satisfying the specified logical
# condition.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# print [print(x)]: Display an object or calculated value in the R Console.
# table [table(x)]: Count frequencies of unique values.
# %in% [x %in% c(0,1)]: Return TRUE when each element belongs to the specified set of
# values.

# SUPERPOWERED TEACHING NOTE:
# filter() controls WHICH rows are retained using a logical predicate. The verified codebook defines occasion 0 as pretest, 1 as posttest, and 2 as the one-week follow-up.
# IN-CLASS MOVE: Have learners explain %in% c(0,1) as the equivalent of occasion == 0 | occasion == 1.
# COMMON PITFALL: Do not use 1/2 as pre/post: the original worksheet shortcut contradicts the codebook.
#
# MOODLE CORRECTION: worksheet says 1/2 are pre/post; codebook is authoritative:
# occasion 0 = pretest; 1 = posttest; 2 = one-week follow-up.
# NOTE: Retain only observations that meet the filter condition.
pre_post <- dplyr::filter(selected_vars, occasion %in% c(0,1))
# NOTE: Show the result in the Console (or display a ggplot object).
print(table(pre_post$occasion))           # Inspect sample size/attrition per occasion.


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# 0   1
# # Counts for baseline and posttest; public fictional data values vary.
# Data-dependent previews are templates, not verified Console output.
#  W2-M05  |  arrange(): rows
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B arrange [MOODLE-ALIGNED]
# LEARNING GOAL: Sort AHI scores ascending.
# CONCEPT: Ordering observations without changing their values.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M05
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Sort the selected observations by ahiTotal from low to high.
# Step 2. Print the first rows of the sorted data frame.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# arrange [dplyr::arrange(data, col)]: Sort rows by one or more columns.
# print [print(x)]: Display an object or calculated value in the R Console.
# head [head(x, n=6)]: Show the first n elements or rows.
# select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.

# SUPERPOWERED TEACHING NOTE:
# arrange() sorts observations by a column but does not change the values or select a subset. It is especially useful for checking extreme observations.
# IN-CLASS MOVE: Compare ascending arrange(..., ahiTotal) with arrange(..., desc(ahiTotal)).
# COMMON PITFALL: Sorting and filtering are different: arrange() retains all input rows.
#
# NOTE: Sort rows by the given variable without changing their values.
pre_post_asc <- dplyr::arrange(pre_post, ahiTotal)
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(dplyr::select(pre_post_asc, id, occasion, ahiTotal)))


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   id occasion ahiTotal
# # First six rows after ascending sorting; exact values are data-dependent.
# Data-dependent previews are templates, not verified Console output.
#  W2-M06  |  summarise(): overall means
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B summarise [MOODLE-ALIGNED]
# LEARNING GOAL: Make one-row AHI and CES-D mean summary.
# CONCEPT: Descriptive summaries and explicit missing-value handling.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M06
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Calculate overall average AHI and CES-D scores in one summary row.
# Step 2. Print the completed overall summary table.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the
# whole table or each group.
# n [dplyr::n()]: Return the number of rows in the active dplyr group.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
# print [print(x)]: Display an object or calculated value in the R Console.

# SUPERPOWERED TEACHING NOTE:
# summarise() collapses a table to aggregate statistics. n() counts rows; mean(..., na.rm=TRUE) ignores missing values for each respective mean.
# IN-CLASS MOVE: Ask whether n() equals the number of NON-MISSING AHI values; discuss why the two could differ.
# COMMON PITFALL: n() counts all rows, including those with an NA in a measured variable.
#
# NOTE: Reduce each group to its aggregate statistics.
overall_summary <- dplyr::summarise(pre_post,
# NOTE: Compute an arithmetic mean of the selected numbers.
  n=dplyr::n(), mean_ahi=mean(ahiTotal,na.rm=TRUE),
# NOTE: Compute an arithmetic mean of the selected numbers.
  mean_cesd=mean(cesdTotal,na.rm=TRUE))
# NOTE: Show the result in the Console (or display a ggplot object).
print(overall_summary)


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   n mean_ahi mean_cesd
# # One row with three summary columns; numbers depend on input CSVs.
# Data-dependent previews are templates, not verified Console output.
#  W2-M07  |  group_by(): distinct occasion summaries
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B group_by [MOODLE-ALIGNED]
# LEARNING GOAL: Summarise each measurement occasion separately.
# CONCEPT: Grouped statistics and different sample sizes across occasions.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M07
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Start a pipe to summarise the sorted pre/post observations.
# Step 2. Group the piped observations by occasion.
# Step 3. Compute sample sizes and means separately for each occasion.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations.
# summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the
# whole table or each group.
# n [dplyr::n()]: Return the number of rows in the active dplyr group.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
# print [print(x)]: Display an object or calculated value in the R Console.
# |> [data |> function()]: Pass the preceding result to the next function call.

# SUPERPOWERED TEACHING NOTE:
# group_by(occasion) changes the unit of summarisation to a measurement occasion. A grouped mean is not necessarily the mean within-person change because different people can appear at each time.
# IN-CLASS MOVE: Contrast mean(post) - mean(pre) across two unmatched samples with mean(post_i - pre_i) on complete pairs.
# COMMON PITFALL: If a score contains missing values, mean() without na.rm=TRUE may return NA.
#
# NOTE: Assign the computed result to a named R object.
occasion_summary <- pre_post_asc |>
# NOTE: Define the groups to be summarised independently.
  dplyr::group_by(occasion) |>
# NOTE: Reduce each group to its aggregate statistics.
  dplyr::summarise(n=dplyr::n(), mean_ahi=mean(ahiTotal),
# NOTE: Compute an arithmetic mean of the selected numbers.
                   mean_cesd=mean(cesdTotal), .groups="drop")
# NOTE: Show the result in the Console (or display a ggplot object).
print(occasion_summary)


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   occasion   n mean_ahi mean_cesd
# # One summary row per occasion (0 and 1).
# Data-dependent previews are templates, not verified Console output.
#  W2-M08  |  mutate(): new column and scale caution
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B mutate [MOODLE-ALIGNED]
# LEARNING GOAL: Create sadness = cesdTotal - ahiTotal as a syntax exercise.
# CONCEPT: Creating derived columns; numerical differences versus validated scales.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M08
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Create a new sadness column by subtracting ahiTotal from cesdTotal.
# Step 2. Print the first rows of the new variable alongside its inputs.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns.
# print [print(x)]: Display an object or calculated value in the R Console.
# head [head(x, n=6)]: Show the first n elements or rows.
# select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.

# SUPERPOWERED TEACHING NOTE:
# mutate() adds a derived column while keeping the observation rows. Subtracting two different scale totals is mathematically possible but may have no validated clinical meaning.
# IN-CLASS MOVE: Ask learners to calculate one row manually and explain why naming a value sadness does not make it a mental-health measure.
# COMMON PITFALL: This 'sadness' variable is ONLY a syntax exercise; it is not a legitimate clinical index.
#
# NOTE: Create or recode columns while retaining row-level observations.
pre_post <- dplyr::mutate(pre_post, sadness=cesdTotal-ahiTotal)
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(dplyr::select(pre_post, sadness, cesdTotal, ahiTotal)))
# DO NOT interpret "sadness" clinically: AHI/CES-D scales have different constructs/ranges.


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   sadness cesdTotal ahiTotal
# # First six rows; sadness = cesdTotal - ahiTotal.
# Data-dependent previews are templates, not verified Console output.
#  W2-M09  |  Recode sex using the codebook
# ============================================================================
#
# MOODLE SOURCE NOTE: pr02. Data Wrangling.r · Part B recode, compared with code-book.pdf
# [MOODLE-ALIGNED]
# LEARNING GOAL: Recode sex=1 Female, sex=2 Male correctly.
# CONCEPT: Categorical recoding using an authoritative codebook.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-M09
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Add a correctly labelled sex variable to the pre/post data frame.
# Step 2. Use the codebook mapping 1 = Female and 2 = Male in factor().

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns.
# factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered
# levels and labels.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# print [print(x)]: Display an object or calculated value in the R Console.
# table [table(x)]: Count frequencies of unique values.

# SUPERPOWERED TEACHING NOTE:
# factor() maps numerical category codes to readable labels. The source codebook defines sex=1 as Female and sex=2 as Male, so the levels and labels must align.
# IN-CLASS MOVE: Inspect table(..., useNA='ifany') and check that all source values map to their intended labels.
# COMMON PITFALL: Reversing the codebook mappings would silently mislabel participants.
#
# MOODLE CORRECTION: worksheet labels conflict with source codebook.
# Correct coding in code-book.pdf: 1=Female, 2=Male.
# NOTE: Create or recode columns while retaining row-level observations.
pre_post <- dplyr::mutate(pre_post,
# NOTE: Map numeric category codes to readable factor levels.
    sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male")))
# NOTE: Show the result in the Console (or display a ggplot object).
print(table(pre_post$sex_label, useNA="ifany"))


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# Female Male
# # Category frequencies; an NA row appears if unmatched/missing values occur.
# Data-dependent previews are templates, not verified Console output.
#  W2-E01  |  Additional: paired change and attrition
# ============================================================================
#
# MOODLE SOURCE NOTE: ferdo Lab original extension [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Contrast paired change with unpaired occasion means.
# CONCEPT: Within-person paired differences, missing pairs and selection bias.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W2-E01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Filter baseline records for participants observed before the intervention.
# Step 2. Filter posttest records for participants observed after the intervention.
# Step 3. Join the two sets by participant and compute within-person change.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# filter [dplyr::filter(data, condition)]: Keep rows satisfying the specified logical
# condition.
# select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.
# inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in
# both tables.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns.
# print [print(x)]: Display an object or calculated value in the R Console.
# summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the
# whole table or each group.
# n [dplyr::n()]: Return the number of rows in the active dplyr group.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.

# SUPERPOWERED TEACHING NOTE:
# Pairing requires the SAME participant to have both a baseline (occasion 0) and posttest (occasion 1) measurement. inner_join retains matched pairs and mutate computes each individual's post-minus-pre change.
# IN-CLASS MOVE: Emphasise that paired change is not the same as subtracting two unpaired occasion means; attrition can bias the matched subset.
# COMMON PITFALL: Check uniqueness of (id, intervention, occasion); missing follow-ups and NA scores need explicit handling.
#
# NOTE: Keep baseline observations and preserve identifiers for later pairing.
paired_pre <- dplyr::filter(all_data, occasion==0) |>
# NOTE: Choose or rename variables; this does not filter rows.
 dplyr::select(id,intervention,ahi_pre=ahiTotal)
# NOTE: Keep posttest observations and preserve identifiers for later pairing.
paired_post <- dplyr::filter(all_data, occasion==1) |>
# NOTE: Choose or rename variables; this does not filter rows.
 dplyr::select(id,intervention,ahi_post=ahiTotal)
# NOTE: Join matching records using the specified key columns.
paired_data <- dplyr::inner_join(paired_pre,paired_post,by=c("id","intervention")) |>
# NOTE: Create or recode columns while retaining row-level observations.
 dplyr::mutate(change_ahi=ahi_post-ahi_pre)
# NOTE: Show the result in the Console (or display a ggplot object).
print(dplyr::summarise(paired_data, paired_n=dplyr::n(), mean_change=mean(change_ahi)))

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   paired_n mean_change
# # One row: count of matched pairs and their average ahi_post - ahi_pre.
# Data-dependent previews are templates, not verified Console output.


# ==============================================================================
# WEEK 3 — AQ-10 SCORING AND DESCRIPTIVE STATISTICS
# ==============================================================================


# ============================================================================
#  W3-M01  |  Read AQ, formats, scoring
# ============================================================================
#
# MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 1 [MOODLE-ALIGNED]
# LEARNING GOAL: Load Qformats, Scoring and AQ data.
# CONCEPT: Reading a questionnaire and the separate format/scoring lookups.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-M01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Read the question-format lookup file from the data folder.
# Step 2. Read the scoring-key lookup file from the data folder.
# Step 3. Read the AQ questionnaire response table from the data folder.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# read.csv [read.csv("data/file.csv", stringsAsFactors=FALSE)]: Read a comma-separated file
# into a data frame.
# print [print(x)]: Display an object or calculated value in the R Console.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# nrow [nrow(data)]: Count the number of rows in a table.

# SUPERPOWERED TEACHING NOTE:
# The AQ-10 practical stores questionnaire responses, item direction (forward/reverse) and scoring rules in three separate CSV files. Punctuation, case and spelling of response labels must agree across files.
# IN-CLASS MOVE: Explain the different roles: AQ.csv holds participant responses; Qformats.csv maps questions to direction; Scoring.csv maps direction+response to 0/1.
# COMMON PITFALL: The public data are fictional. Do not assume the original Moodle row counts are reproduced there.
#
# NOTE: Read an input CSV into a data frame; check the working directory.
qformats <- read.csv("data/Qformats.csv", stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
scoring <- read.csv("data/Scoring.csv", stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
aq_responses <- read.csv("data/AQ.csv", stringsAsFactors=FALSE)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(participants=nrow(aq_responses),questions=nrow(qformats),rules=nrow(scoring)))


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# participants questions rules
# # Three named counts; original private AQ source had 66 respondents and 10 items.
# Data-dependent previews are templates, not verified Console output.
#  W3-M02  |  Wide-to-long pivot
# ============================================================================
#
# MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 2 [MOODLE-ALIGNED]
# LEARNING GOAL: Convert Q1–Q10 into Question/Response rows.
# CONCEPT: Wide versus long data and person-by-question observations.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-M02
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Start turning the wide AQ response table into a long table.
# Step 2. Select the Q1 through Q10 columns for pivoting.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# pivot_longer [tidyr::pivot_longer(data, cols=..., names_to=..., values_to=...)]: Reshape
# many columns into a smaller number of key/value columns.
# all_of [dplyr::all_of(names)]: Select columns identified by a character vector of names.
# paste0 [paste0("Q", 1:10)]: Join text and values without separators.
# print [print(x)]: Display an object or calculated value in the R Console.
# dim [dim(x)]: Return dimensions, usually rows and columns.

# SUPERPOWERED TEACHING NOTE:
# pivot_longer() converts ten question columns (Q1 to Q10) into two fields, Question and Response, while repeating Id for every item. This changes shape, not substantive answers.
# IN-CLASS MOVE: Break down all_of(paste0('Q',1:10)), names_to and values_to; demonstrate how one participant becomes ten rows.
# COMMON PITFALL: all_of() requires all Q1–Q10 columns to exist; verify column names and each participant's expected ten items.
#
# NOTE: Pivot Q1–Q10 columns into long-form question/response rows.
aq_long <- tidyr::pivot_longer(aq_responses,
# NOTE: Generate exact field names Q1 through Q10 without separators.
  cols=dplyr::all_of(paste0("Q",1:10)),names_to="Question",values_to="Response")
# NOTE: Show the result in the Console (or display a ggplot object).
print(dim(aq_long))                       # Real source: 66*10=660 rows; public demo differs.


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# [1] <participants × 10> <column count after pivot>
# # In original private data, there were 66 × 10 = 660 long-form rows.
# Data-dependent previews are templates, not verified Console output.
#  W3-M03  |  Join directions and exact scoring rules
# ============================================================================
#
# MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 3 [MOODLE-ALIGNED]
# 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.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-M03
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Join each long-format AQ response with its forward/reverse item direction.
# Step 2. Join each response and direction combination to its 0/1 score.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in
# both tables.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is
# not true.
# nrow [nrow(data)]: Count the number of rows in a table.
# anyNA [anyNA(x)]: Check whether an object contains missing values.
# print [print(x)]: Display an object or calculated value in the R Console.
# head [head(x, n=6)]: Show the first n elements or rows.
# select [dplyr::select(data, col1, col2)]: Choose or rename columns in a data frame.

# SUPERPOWERED TEACHING NOTE:
# The FIRST inner_join attaches forward/reverse question direction by Question. The SECOND join attaches the binary Score by the combined (QFormat, Response) lookup key.
# IN-CLASS MOVE: Explain why response text alone is insufficient: the same response can receive different points depending on item direction.
# COMMON PITFALL: inner_join can drop unmatched answers; equal row counts alone do not prove one-to-one matching.
#
# NOTE: Join matching records using the specified key columns.
aq_with_format <- dplyr::inner_join(aq_long,qformats,by="Question")
# NOTE: Join matching records using the specified key columns.
aq_scored <- dplyr::inner_join(aq_with_format,scoring,by=c("QFormat","Response"))
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(nrow(aq_scored)==nrow(aq_long), !anyNA(aq_scored$Score))
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(dplyr::select(aq_scored,Id,Question,Response,QFormat,Score)))


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   Id Question Response QFormat Score
# # First six matched response rows, each with a binary Score (0 or 1).
# Data-dependent previews are templates, not verified Console output.
#  W3-M04  |  Participant total AQ score
# ============================================================================
#
# MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part A task 4 [MOODLE-ALIGNED]
# LEARNING GOAL: Sum ten 0/1 items per participant.
# CONCEPT: Participant-level grouping and summing binary questionnaire items.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-M04
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Begin a pipe that calculates a total AQ-10 score for each participant.
# Step 2. Group the scored questionnaire rows by participant ID.
# Step 3. Sum the scored items separately for each participant.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations.
# summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the
# whole table or each group.
# sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values.
# stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is
# not true.
# all [all(logical)]: Return TRUE only if every tested value is TRUE.
# print [print(x)]: Display an object or calculated value in the R Console.
# head [head(x, n=6)]: Show the first n elements or rows.

# SUPERPOWERED TEACHING NOTE:
# group_by(Id) makes each participant a separate calculation group. summarise(AQTotal=sum(Score)) adds the ten binary item scores and returns one row per respondent; .groups='drop' clears grouping.
# IN-CLASS MOVE: Explain >=, <=, &, all() and stopifnot(). The range check is necessary but not sufficient to confirm ten valid scored answers.
# COMMON PITFALL: A total within 0–10 may still come from fewer than ten scored questions. Check item counts per Id.
#
# NOTE: Compute one AQ-10 total for each participant.
aq_scores <- aq_scored |>
# NOTE: Define the groups to be summarised independently.
  dplyr::group_by(Id) |>
# NOTE: Reduce each group to its aggregate statistics.
  dplyr::summarise(AQTotal=sum(Score),.groups="drop")
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(all(aq_scores$AQTotal >= 0 & aq_scores$AQTotal <= 10))
# NOTE: Show the result in the Console (or display a ggplot object).
print(head(aq_scores))


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   Id AQTotal
# # First six participants with integer totals from 0 to 10.
# Data-dependent previews are templates, not verified Console output.
#  W3-M05  |  Pipe version of a complete workflow
# ============================================================================
#
# MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part B pipe [MOODLE-ALIGNED]
# LEARNING GOAL: Recreate scoring through a chain of operations.
# CONCEPT: Pipes, function composition and equivalent scoring workflows.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-M05
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Start a single piped workflow using the original AQ response table.
# Step 2. Reshape Q1–Q10 from wide to long format in the pipeline.
# Step 3. Join the reshaped rows to the question format key, then continue scoring.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# pivot_longer [tidyr::pivot_longer(data, cols=..., names_to=..., values_to=...)]: Reshape
# many columns into a smaller number of key/value columns.
# all_of [dplyr::all_of(names)]: Select columns identified by a character vector of names.
# paste0 [paste0("Q", 1:10)]: Join text and values without separators.
# inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in
# both tables.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations.
# summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the
# whole table or each group.
# sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values.
# stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is
# not true.
# identical [identical(x, y)]: Test strict equality, including data types and attributes.
# |> [data |> transform()]: Compose data-processing steps in readable left-to-right order.

# SUPERPOWERED TEACHING NOTE:
# The pipe chains the entire transformation: wide-to-long → join item direction → join scoring key → group by participant → sum item scores. It should reproduce the same totals as the separated steps.
# IN-CLASS MOVE: Use identical(aq_scores, aq_pipe) to test exact equality; isolate stages if it fails.
# COMMON PITFALL: identical() checks classes, row order and attributes as well as values; equivalent data can sometimes differ in attributes.
#
# NOTE: Perform the whole AQ workflow through one chained pipeline.
aq_pipe <- aq_responses |>
# NOTE: Pivot Q1–Q10 columns into long-form question/response rows.
 tidyr::pivot_longer(cols=dplyr::all_of(paste0("Q",1:10)),
# NOTE: Continue this R expression with its arguments, values or formatting.
                     names_to="Question",values_to="Response") |>
# NOTE: Join matching records using the specified key columns.
 dplyr::inner_join(qformats,by="Question") |>
# NOTE: Join matching records using the specified key columns.
 dplyr::inner_join(scoring,by=c("QFormat","Response")) |>
# NOTE: Define the groups to be summarised independently.
 dplyr::group_by(Id) |>
# NOTE: Reduce each group to its aggregate statistics.
 dplyr::summarise(AQTotal=sum(Score),.groups="drop")
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(identical(aq_scores,aq_pipe))   # Native |> parallels tidyverse %>% for these operations.


# ============================================================================

# EXPECTED OUTPUT — Exact, deterministic example:
# # No printed output when stopifnot(identical(...)) passes.
# # If it fails, R stops with an error.
# Data-dependent previews are templates, not verified Console output.
#  W3-M06  |  Descriptives, cut-off and counts
# ============================================================================
#
# MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part C tasks 1–3
# [MOODLE-ALIGNED]
# LEARNING GOAL: Compute mean/SD/range; list people over six.
# CONCEPT: Means, standard deviations, scores and screening thresholds.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-M06
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Calculate the mean, standard deviation and range of AQ totals.
# Step 2. Print the resulting descriptive summary.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the
# whole table or each group.
# n [dplyr::n()]: Return the number of rows in the active dplyr group.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
# sd [sd(x)]: Calculate the sample standard deviation.
# min [min(x, na.rm=FALSE)]: Return the smallest value.
# max [max(x, na.rm=FALSE)]: Return the largest value.
# sum [sum(x, na.rm=FALSE)]: Add numeric values; optionally ignore missing values.
# print [print(x)]: Display an object or calculated value in the R Console.
# filter [dplyr::filter(data, condition)]: Keep rows satisfying the specified logical
# condition.

# SUPERPOWERED TEACHING NOTE:
# mean and sd describe the distribution of participants' summed AQ scores. AQTotal > 6 is a screening-threshold count, not a diagnosis.
# IN-CLASS MOVE: Distinguish the number of people from the mean number of points; compare SD to the observed min/max.
# COMMON PITFALL: A questionnaire screening threshold must not be interpreted as a clinical diagnostic finding.
#
# NOTE: Reduce each group to its aggregate statistics.
aq_summary <- dplyr::summarise(aq_scores,
# NOTE: Compute an arithmetic mean of the selected numbers.
  n=dplyr::n(), mean=mean(AQTotal), sd=sd(AQTotal),
# NOTE: Continue this R expression with its arguments, values or formatting.
  min=min(AQTotal), max=max(AQTotal), above6=sum(AQTotal>6))
# NOTE: Show the result in the Console (or display a ggplot object).
print(aq_summary)
# NOTE: Show the result in the Console (or display a ggplot object).
print(dplyr::filter(aq_scores,AQTotal>6))


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#   n mean sd min max above6
# # One summary row; followed by rows with AQTotal > 6.
# Data-dependent previews are templates, not verified Console output.
#  W3-M07  |  AQ frequency histogram
# ============================================================================
#
# MOODLE SOURCE NOTE: wk3_practical_summarising_data (1).r · Part C final histogram
# [MOODLE-ALIGNED]
# LEARNING GOAL: Plot the integer-valued AQTotal distribution with bins of one.
# CONCEPT: Histograms for discrete integer-valued scores.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-M07
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Define a ggplot object for the AQ total-score distribution.
# Step 2. Add a histogram layer with a bin width of one point.
# Step 3. Display the completed AQ histogram.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot.
# aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features
# such as x, y or colour.
# geom_histogram [ggplot2::geom_histogram(binwidth=...)]: Draw bins showing a distribution
# of numeric values.
# scale_x_continuous [ggplot2::scale_x_continuous(breaks=...)]: Control the continuous
# x-axis scale and tick marks.
# theme_classic [ggplot2::theme_classic()]: Apply a simple plotting theme with minimal
# background decoration.
# labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and
# legend text.
# print [print(x)]: Display an object or calculated value in the R Console.

# SUPERPOWERED TEACHING NOTE:
# A histogram displays the frequency distribution of AQTotal. Binwidth=1 and boundary=-0.5 align bins to integer scores from 0 to 10.
# IN-CLASS MOVE: Ask whether the distribution is symmetric, discrete and concentrated at low scores.
# COMMON PITFALL: The familiar normal '68% within 1 SD' rule need not hold for a bounded discrete score.
#
# NOTE: Build a plot and specify how data map to visual variables.
p_aq <- ggplot2::ggplot(aq_scores,ggplot2::aes(x=AQTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_histogram(binwidth=1,boundary=-0.5,colour="white") +
# NOTE: Apply a plot theme; styling does not change source data.
 ggplot2::scale_x_continuous(breaks=0:10) + ggplot2::theme_classic() +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(x="AQ-10 score",y="Number of participants",title="AQ-10 score distribution")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_aq)


# ============================================================================

# EXPECTED OUTPUT — Expected visual output:
# [Plot] Ten-point integer-scale histogram; x-axis 0–10, y-axis participant counts.
# Data-dependent previews are templates, not verified Console output.
#  W3-E01  |  Additional: variance, SE, missingness
# ============================================================================
#
# MOODLE SOURCE NOTE: ferdo Lab original extension [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Explain why SD measures spread while SE relates to the sample mean.
# CONCEPT: Variance, standard deviation, standard error and missingness.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W3-E01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Compute the sample variance of the AQ total scores.
# Step 2. Compute the standard error using SD divided by the square root of n.
# Step 3. Print variance and standard error with meaningful labels.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# var [var(x)]: Calculate the sample variance.
# sd [sd(x)]: Calculate the sample standard deviation.
# sqrt [sqrt(x)]: Calculate the non-negative square root.
# nrow [nrow(data)]: Count the number of rows in a table.
# print [print(x)]: Display an object or calculated value in the R Console.
# c [c(a, b, ...)]: Combine multiple values into a vector.

# SUPERPOWERED TEACHING NOTE:
# Sample variance expresses squared dispersion (units of Score²); sample standard deviation retains score units. Standard error measures estimated uncertainty in the sample mean, not individual differences.
# IN-CLASS MOVE: Show SE = SD / sqrt(n) and discuss how doubling n affects SE.
# COMMON PITFALL: Variance, SD and SE answer different questions. Do not substitute SE for SD on participant-level charts.
#
# NOTE: Compute sample variance (square of SD).
variance_aq <- var(aq_scores$AQTotal)    # Sample variance (n-1 denominator).
# NOTE: Compute a sample standard deviation.
se_aq <- sd(aq_scores$AQTotal)/sqrt(nrow(aq_scores))
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(sample_variance=variance_aq, standard_error=se_aq))

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# sample_variance standard_error
# # Two positive numeric values; data-dependent.
# Data-dependent previews are templates, not verified Console output.


# ==============================================================================
# WEEK 4 — VISUALISATION WITH GGPLOT2
# ==============================================================================


# ============================================================================
#  W4-M01  |  Histogram and binwidth
# ============================================================================
#
# MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 histogram [MOODLE-ALIGNED]
# LEARNING GOAL: Create an AHI histogram; compare bins 5 and 1.
# CONCEPT: Distribution plots and the consequences of bin-width choices.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W4-M01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Start a ggplot of the distribution of AHI scores.
# Step 2. Add a histogram with a chosen bin width, then compare alternatives.
# Step 3. Print the finished histogram.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot.
# aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features
# such as x, y or colour.
# geom_histogram [ggplot2::geom_histogram(binwidth=...)]: Draw bins showing a distribution
# of numeric values.
# labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and
# legend text.
# print [print(x)]: Display an object or calculated value in the R Console.
# + [ggplot(...) + geom_histogram(...)]: Add a graphics layer; here + combines plot
# components, not numbers.

# SUPERPOWERED TEACHING NOTE:
# Histogram binwidth controls aggregation granularity. A five-point bin gives a broad overview, whereas a one-point bin reveals local discreteness and noise.
# IN-CLASS MOVE: Compare both plots without changing the underlying data and ask which binning supports the research question.
# COMMON PITFALL: Histogram appearance depends strongly on breaks; avoid claiming a shape is inherent to the raw data.
#
# NOTE: Build a plot and specify how data map to visual variables.
p_hist <- ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_histogram(binwidth=5,colour="white") +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(x="AHI total",y="Count",title="AHI distribution")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_hist)
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_histogram(binwidth=1,colour="white") +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(x="AHI total",y="Count",title="AHI distribution: binwidth 1"))


# ============================================================================

# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] AHI histogram with binwidth 5.
# [Plot 2] The same distribution with binwidth 1.
# Data-dependent previews are templates, not verified Console output.
#  W4-M02  |  Scatterplot and sex legend
# ============================================================================
#
# MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 scatterplot [MOODLE-ALIGNED]
# LEARNING GOAL: Show AHI–CES-D association, then colour points by sex.
# CONCEPT: Scatterplots, two numeric variables and categorical colour legends.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W4-M02
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Create meaningful factor labels for the sex variable using the codebook.
# Step 2. Start a scatterplot of AHI against CES-D, coloured by the sex labels.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns.
# factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered
# levels and labels.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot.
# aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features
# such as x, y or colour.
# geom_point [ggplot2::geom_point()]: Plot individual observations as points.
# labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and
# legend text.
# print [print(x)]: Display an object or calculated value in the R Console.

# SUPERPOWERED TEACHING NOTE:
# A scatterplot maps each record to (AHI, CES-D). Mapping colour to sex_label adds a legend and groups without changing the underlying x/y coordinates.
# IN-CLASS MOVE: Discuss overplotting, transparency alpha and the fact that association does not establish causation.
# COMMON PITFALL: Repeated observations for the same person can make apparent independence misleading.
#
# NOTE: Create or recode columns while retaining row-level observations.
all_data <- dplyr::mutate(all_data,
# NOTE: Map numeric category codes to readable factor levels.
 sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male")))
# NOTE: Build a plot and specify how data map to visual variables.
p_scatter <- ggplot2::ggplot(all_data,
# NOTE: Apply the ggplot operation to the current visualisation.
 ggplot2::aes(x=ahiTotal,y=cesdTotal,colour=sex_label)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_point(alpha=0.48) +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(x="AHI total",y="CES-D total",colour="Sex")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_scatter)


# ============================================================================

# EXPECTED OUTPUT — Expected visual output:
# [Plot] AHI on x-axis; CES-D on y-axis; coloured points and a Female/Male legend.
# Data-dependent previews are templates, not verified Console output.
#  W4-M03  |  Boxplot, violin, and raw points
# ============================================================================
#
# MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 box/violin [MOODLE-ALIGNED]
# LEARNING GOAL: Recreate boxplot and add violin overlay; describe what each reveals.
# CONCEPT: Boxplots, violin plots, medians and interquartile ranges.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W4-M03
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Create a ggplot object that compares the outcome across groups.
# Step 2. Add a boxplot layer to display medians and quartiles.
# Step 3. Create a separate violin plot to show the shape of group distributions.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot.
# aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features
# such as x, y or colour.
# factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered
# levels and labels.
# geom_boxplot [ggplot2::geom_boxplot()]: Draw median, quartile and whisker summaries.
# labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and
# legend text.
# print [print(x)]: Display an object or calculated value in the R Console.
# geom_violin [ggplot2::geom_violin()]: Draw smoothed density shapes by group.
# geom_jitter [ggplot2::geom_jitter(width=...)]: Add small position offsets to reveal
# overlapping points.

# SUPERPOWERED TEACHING NOTE:
# A boxplot summarises median, quartiles and whiskers; a violin plot displays a smoothed distribution density. Jittered points preserve visibility of individual observations.
# IN-CLASS MOVE: Compare whether both plot types tell a consistent story about occasion-level variation.
# COMMON PITFALL: A standard boxplot does not show mean ± SD; and a violin density may exaggerate thin groups.
#
# NOTE: Map numeric category codes to readable factor levels.
p_box <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_boxplot(fill="#7cccd2",outlier.alpha=.4) +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(x="Occasion code",y="AHI total")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_box)
# NOTE: Map numeric category codes to readable factor levels.
p_violin <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_violin(fill="#c3aaf5",alpha=.55) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_boxplot(width=.13,outlier.shape=NA) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_jitter(width=.1,alpha=.15,size=.45)
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_violin)


# ============================================================================

# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] AHI boxplot by occasion.
# [Plot 2] Violin + narrow boxplot + jittered individual points.
# Data-dependent previews are templates, not verified Console output.
#  W4-M04  |  Mean bar plot, grouped by intervention
# ============================================================================
#
# MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 bar plot [MOODLE-ALIGNED]
# LEARNING GOAL: Compute means, then plot overall and grouped means.
# CONCEPT: Group means and bar plots of precomputed summaries.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W4-M04
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Create readable labels for the intervention groups.
# Step 2. Start a pipeline from the full data to calculate group means.
# Step 3. Group the data by intervention before calculating each mean.
# Step 4. Start a bar chart of the precomputed group means.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# mutate [dplyr::mutate(data, new=x+y)]: Create or alter one or more columns.
# factor [factor(x, levels=..., labels=...)]: Represent categorical values with ordered
# levels and labels.
# group_by [dplyr::group_by(data, group)]: Set the grouping keys used by later operations.
# summarise [dplyr::summarise(data, metric=mean(x))]: Produce summary statistics for the
# whole table or each group.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
# ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot.
# aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features
# such as x, y or colour.
# geom_col [ggplot2::geom_col()]: Draw bars using already-calculated y values.
# n [dplyr::n()]: Return the number of rows in the active dplyr group.
# sd [sd(x)]: Calculate the sample standard deviation.
# |> [data |> group_by(...)]: Pass a data frame through consecutive transformations.

# SUPERPOWERED TEACHING NOTE:
# The summary table calculates means before plotting. geom_col() treats its y value as a precomputed height, unlike geom_bar() which counts observations by default.
# IN-CLASS MOVE: Explain why the 'dodge' position separates intervention groups within an occasion.
# COMMON PITFALL: Compare denominators n by group, and do not mistake a descriptive bar difference for a tested effect.
#
# NOTE: Create human-readable labels for coded intervention groups.
intervention_labels <- c("Signature strengths","Three good things",
# NOTE: Continue this R expression with its arguments, values or formatting.
                         "Gratitude visit","Early memories (control)")
# NOTE: Create or recode columns while retaining row-level observations.
all_data <- dplyr::mutate(all_data, intervention_label=factor(intervention,
# NOTE: Continue this R expression with its arguments, values or formatting.
 levels=1:4,labels=intervention_labels))
# NOTE: Assign the computed result to a named R object.
means_occasion <- all_data |>
# NOTE: Define the groups to be summarised independently.
 dplyr::group_by(occasion) |>
# NOTE: Reduce each group to its aggregate statistics.
 dplyr::summarise(mean_ahi=mean(ahiTotal),.groups="drop")
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(means_occasion,ggplot2::aes(x=factor(occasion),y=mean_ahi))+
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_col(fill="#9fd8c2"))
# NOTE: Assign the computed result to a named R object.
means_groups <- all_data |>
# NOTE: Define the groups to be summarised independently.
 dplyr::group_by(occasion,intervention_label) |>
# NOTE: Reduce each group to its aggregate statistics.
 dplyr::summarise(n=dplyr::n(),mean_ahi=mean(ahiTotal),sd_ahi=sd(ahiTotal),
# NOTE: Specify how groups, missing values or join keys are handled.
 .groups="drop")
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(means_groups,ggplot2::aes(x=factor(occasion),y=mean_ahi,
# NOTE: Add a visual layer; ggplot layers are combined with +.
 fill=intervention_label)) + ggplot2::geom_col(position="dodge"))


# ============================================================================

# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] One mean-AHI bar per occasion.
# [Plot 2] Side-by-side intervention mean bars per occasion.
# Data-dependent previews are templates, not verified Console output.
#  W4-M05  |  Line plot and SD error bars
# ============================================================================
#
# MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 1 line + ylim + SD
# [MOODLE-ALIGNED]
# LEARNING GOAL: Create line plot with 24–120 display range and ±1 SD bars.
# CONCEPT: Time-series group summaries and standard-deviation error bars.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W4-M05
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Start a plot of group mean scores across measurement occasions.
# Step 2. Add a line layer connecting the occasion means.
# Step 3. Add error bars for plus/minus one standard deviation, not a confidence interval.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot.
# aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features
# such as x, y or colour.
# geom_line [ggplot2::geom_line()]: Connect points in x-axis order within groups.
# geom_point [ggplot2::geom_point()]: Plot individual observations as points.
# geom_errorbar [ggplot2::geom_errorbar(aes(ymin=..., ymax=...))]: Draw uncertainty or
# variability intervals between ymin and ymax.
# coord_cartesian [ggplot2::coord_cartesian(ylim=c(lo, hi))]: Zoom a plot to a selected
# viewing range without discarding data.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and
# legend text.
# print [print(x)]: Display an object or calculated value in the R Console.

# SUPERPOWERED TEACHING NOTE:
# A group is tracked through time using colour and group aesthetics. The vertical bars run from mean − SD to mean + SD; they describe dispersion, not a 95% confidence interval.
# IN-CLASS MOVE: Read two occasion points on a line and compare the within-group direction of change.
# COMMON PITFALL: coord_cartesian() zooms without filtering original data; SD bars must not be called confidence intervals.
#
# NOTE: Build a plot and specify how data map to visual variables.
p_line <- ggplot2::ggplot(means_groups,
# NOTE: Apply the ggplot operation to the current visualisation.
 ggplot2::aes(x=occasion,y=mean_ahi,colour=intervention_label,group=intervention_label)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_line() + ggplot2::geom_point() +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_errorbar(ggplot2::aes(ymin=mean_ahi-sd_ahi,ymax=mean_ahi+sd_ahi),
# NOTE: Specify how groups, missing values or join keys are handled.
 width=.13,na.rm=TRUE) + ggplot2::coord_cartesian(ylim=c(24,120)) +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(x="Measurement occasion (0=pre, 1=post)",y="Mean AHI ± 1 SD")
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_line)


# ============================================================================

# EXPECTED OUTPUT — Expected visual output:
# [Plot] Intervention mean-AHI trajectories across occasions with vertical ±1 SD error bars.
# Data-dependent previews are templates, not verified Console output.
#  W4-M06  |  Themes, labels, facets and point style
# ============================================================================
#
# MOODLE SOURCE NOTE: wk4_practical_visualisation.r · Part 2 themes / labels / facet /
# size-shape-colour [MOODLE-ALIGNED]
# LEARNING GOAL: Compare themes, add titles, facet by sex, style points.
# CONCEPT: ggplot2 layers, themes, faceting and graphical annotations.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W4-M06
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Print the existing scatterplot using a classic ggplot theme.
# Step 2. Print a faceted version of the scatterplot, separated by the sex labels.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# print [print(x)]: Display an object or calculated value in the R Console.
# theme_classic [ggplot2::theme_classic()]: Apply a simple plotting theme with minimal
# background decoration.
# labs [ggplot2::labs(x="...", y="...", title="...")]: Set plot titles, axis labels and
# legend text.
# theme_bw [ggplot2::theme_bw()]: Apply a black-and-white plotting theme.
# facet_wrap [ggplot2::facet_wrap(~ group)]: Split a plot into small panels by one or more
# grouping variables.
# ggplot [ggplot2::ggplot(data, aes(...))]: Start a layered grammar-of-graphics plot.
# aes [ggplot2::aes(x=..., y=..., colour=...)]: Map data variables to graphical features
# such as x, y or colour.
# geom_point [ggplot2::geom_point()]: Plot individual observations as points.

# SUPERPOWERED TEACHING NOTE:
# Themes affect styling, titles communicate meaning and facet_wrap() creates panels by category. Geometric point aesthetics such as shape and alpha change appearance, not the observations.
# IN-CLASS MOVE: Ask which version best reveals subgroup patterns and whether the colours should also encode a variable.
# COMMON PITFALL: Visual style is not evidence of a statistical effect; transparency influences visibility.
#
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_scatter + ggplot2::theme_classic() +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(title="Happiness and depressive symptoms"))
# NOTE: Show the result in the Console (or display a ggplot object).
print(p_scatter + ggplot2::theme_bw() + ggplot2::facet_wrap(~sex_label))
# NOTE: Show the result in the Console (or display a ggplot object).
print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal,y=cesdTotal)) +
# NOTE: Add a visual layer; ggplot layers are combined with +.
 ggplot2::geom_point(colour="purple",size=2,shape=17,alpha=.3) +
# NOTE: Set interpretable axis labels and titles.
 ggplot2::labs(title="Styled scatterplot",x="AHI",y="CES-D"))


# ============================================================================

# EXPECTED OUTPUT — Expected visual output:
# [Plot 1] Classic scatter with title.
# [Plot 2] Black-and-white faceted scatter by sex.
# [Plot 3] Purple triangle scatter.
# Data-dependent previews are templates, not verified Console output.
#  W4-E01  |  Additional: correlation and ATSQ context
# ============================================================================
#
# MOODLE SOURCE NOTE: ferdo Lab original extension; ATSQ file is separate from original W4
# plot tasks [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Distinguish graphical association from causal effect; inspect separate
# ATSQ join.
# CONCEPT: Correlation is association, not causation; use caution with ATSQ.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W4-E01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Calculate the correlation between AHI and CES-D with missing-data handling.
# Step 2. Import the separately supplied ATSQ scores as a data frame.
# Step 3. Join ATSQ scores to participant information by participant ID.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# cor [cor(x, y, use="complete.obs")]: Calculate a correlation coefficient, Pearson by
# default.
# print [print(x)]: Display an object or calculated value in the R Console.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# read.csv [read.csv("data/file.csv", stringsAsFactors=FALSE)]: Read a comma-separated file
# into a data frame.
# inner_join [dplyr::inner_join(x, y, by="id")]: Keep and combine rows whose keys match in
# both tables.
# nrow [nrow(data)]: Count the number of rows in a table.

# SUPERPOWERED TEACHING NOTE:
# cor() gives a descriptive linear association coefficient for AHI and CES-D using complete cases. The second join illustrates linking two other tables by participant id.
# IN-CLASS MOVE: Contrast a correlation within repeated measurements with independent participant-level correlations.
# COMMON PITFALL: Correlation does not prove causality; missing pairs and repeated observations require care.
#
# NOTE: Compute the Pearson linear correlation coefficient.
cor_ahi_cesd <- cor(all_data$ahiTotal,all_data$cesdTotal,use="complete.obs")
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(correlation=cor_ahi_cesd))
# NOTE: Read an input CSV into a data frame; check the working directory.
atsq <- read.csv("data/atsq_scores.csv",stringsAsFactors=FALSE)
# NOTE: Read an input CSV into a data frame; check the working directory.
p_background <- read.csv("data/p_info.csv",stringsAsFactors=FALSE)
# NOTE: Join matching records using the specified key columns.
atsq_joint <- dplyr::inner_join(atsq,p_background,by="id")
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(atsq_rows=nrow(atsq),background_rows=nrow(p_background),joined=nrow(atsq_joint)))

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# correlation
# # One coefficient in [-1,1].
# atsq_rows background_rows joined
# # Three data-dependent row counts.
# Data-dependent previews are templates, not verified Console output.


# ==============================================================================
# WEEK 5 — SAMPLING AND BOOTSTRAP — INDEPENDENT EXTENSION
# ==============================================================================


# ============================================================================
#  W5-E01  |  Sampling distributions and descriptive variation
# ============================================================================
#
# MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson; no verified Moodle Week 5
# file [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Compare SD of observed times and standard error of their mean.
# CONCEPT: Sampling, sample variability, SD and SE of the sample mean.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W5-E01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Create a short fictional sample of reaction times in milliseconds.
# Step 2. Calculate how many reaction times are in the sample.
# Step 3. Calculate the sample mean reaction time.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# c [c(a, b, ...)]: Combine multiple values into a vector.
# length [length(x)]: Return the number of elements in a vector.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
# sd [sd(x)]: Calculate the sample standard deviation.
# sqrt [sqrt(x)]: Calculate the non-negative square root.
# print [print(x)]: Display an object or calculated value in the R Console.

# SUPERPOWERED TEACHING NOTE:
# The mean gives the centre of a numerical sample; SD estimates trial-to-trial dispersion; SE estimates the precision of the sample mean under independence.
# IN-CLASS MOVE: Have learners predict that SE is smaller than SD when n > 1.
# COMMON PITFALL: Fictional trial values illustrate estimation only, not generalisable experimental results.
#
# NOTE: Define an illustrative vector of fictional reaction times.
rt_demo <- c(620,610,590,605,615,600,625,595,608,612,598,603)
# NOTE: Assign the computed result to a named R object.
rt_n <- length(rt_demo)
# NOTE: Compute an arithmetic mean of the selected numbers.
rt_mean <- mean(rt_demo)
# NOTE: Compute a sample standard deviation.
rt_sd <- sd(rt_demo)
# NOTE: Assign the computed result to a named R object.
rt_se <- rt_sd / sqrt(rt_n)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(n=rt_n,mean_ms=rt_mean,sd_ms=rt_sd,se_ms=rt_se))


# ============================================================================

# EXPECTED OUTPUT — Exact, deterministic example:
# n = 12
# mean_ms = 606.75
# sd_ms ≈ 10.3320
# se_ms ≈ 2.9826
# Data-dependent previews are templates, not verified Console output.
#  W5-E02  |  Resampling and bootstrap percentile interval
# ============================================================================
#
# MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson; no verified Moodle Week 5
# file [INDEPENDENT-EXTENSION]
# 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.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W5-E02
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Set the random seed so the resampling results can be reproduced.
# Step 2. Simulate 1,000 resampled means using a sample size of four.
# Step 3. Simulate 1,000 resampled means using a sample size of ten.
# Step 4. Simulate a bootstrap distribution using the observed sample size.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# set.seed [set.seed(46)]: Fix the pseudorandom generator state for reproducible output.
# replicate [replicate(B, expression)]: Evaluate an expression repeatedly and collect the
# results.
# mean [mean(x, na.rm=FALSE)]: Calculate an arithmetic average.
# sample [sample(x, size, replace=TRUE)]: Draw elements with or without replacement.
# print [print(x)]: Display an object or calculated value in the R Console.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# sd [sd(x)]: Calculate the sample standard deviation.
# quantile [quantile(x, probs=c(.025,.975))]: Calculate the requested percentiles of a
# vector.
# replace=TRUE [sample(x, n, replace=TRUE)]: Allow an observed data point to appear more
# than once in a resample.

# SUPERPOWERED TEACHING NOTE:
# Bootstrap sampling with replacement builds a distribution of resampled means. Larger resample sizes usually produce narrower distributions; percentile intervals use quantiles of resampled statistics.
# IN-CLASS MOVE: Identify the fixed seed, the number of replicates and why replace=TRUE matters.
# COMMON PITFALL: The seed makes R reproducible, but no numeric bootstrap result is claimed without executing the R source.
#
# NOTE: Fix the R pseudorandom sequence for reproducible simulation.
set.seed(46)                              # Fix pseudo-random seed for reproducibility.
# NOTE: Compute an arithmetic mean of the selected numbers.
means_n4 <- replicate(1000,mean(sample(rt_demo,4,replace=TRUE)))
# NOTE: Compute an arithmetic mean of the selected numbers.
means_n10 <- replicate(1000,mean(sample(rt_demo,10,replace=TRUE)))
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(sd_n4=sd(means_n4),sd_n10=sd(means_n10)))
# NOTE: Compute an arithmetic mean of the selected numbers.
bootstrap_means <- replicate(1000,mean(sample(rt_demo,rt_n,replace=TRUE)))
# NOTE: Extract specified percentile values from the resampling distribution.
bootstrap_ci <- quantile(bootstrap_means,c(.025,.975))
# NOTE: Show the result in the Console (or display a ggplot object).
print(bootstrap_ci)


# ============================================================================

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
# sd_n4 sd_n10
# # Two simulation SDs (typically sd_n10 < sd_n4).
# 2.5% 97.5%
# # Two bootstrap percentile endpoints; exact values require running R.
# Data-dependent previews are templates, not verified Console output.
#  W5-E03  |  Visualising the resampling distributions
# ============================================================================
#
# MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Explain why the n=10 distribution is usually narrower.
# CONCEPT: Visual comparison of sampling distributions and mean precision.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W5-E03
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Arrange two Base R plots side by side using par().
# Step 2. Plot the resampled means for samples of size four.
# Step 3. Plot the resampled means for samples of size ten, then compare spreads.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# par [par(mfrow=c(1,2))]: Set graphical parameters such as the plot-panel layout.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# hist [hist(x, main="...", xlab="...")]: Draw a Base R histogram.

# SUPERPOWERED TEACHING NOTE:
# par(mfrow=c(1,2)) divides a base-R graphics device into two panels. The histograms compare distributions of resampled means at two sample sizes.
# IN-CLASS MOVE: Explain the change in spread without interpreting both panels as raw observations.
# COMMON PITFALL: The two histograms need comparable axes if you want a strong visual claim about width.
#
# NOTE: Configure the base-R plotting layout.
par(mfrow=c(1,2))
# NOTE: Draw a histogram of the supplied numeric vector.
hist(means_n4,main="Resample size 4",xlab="Mean RT (ms)")
# NOTE: Draw a histogram of the supplied numeric vector.
hist(means_n10,main="Resample size 10",xlab="Mean RT (ms)")
# NOTE: Configure the base-R plotting layout.
par(mfrow=c(1,1))

# EXPECTED OUTPUT — Expected visual output:
# [Plot] Two side-by-side resampled-mean histograms: n=4 and n=10.
# Data-dependent previews are templates, not verified Console output.


# ==============================================================================
# WEEK 6 — INFERENCE AND REGRESSION — INDEPENDENT EXTENSION
# ==============================================================================


# ============================================================================
#  W6-E01  |  Null hypothesis and one-sample t test
# ============================================================================
#
# MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson; no verified Moodle Week 6
# file [INDEPENDENT-EXTENSION]
# 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.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W6-E01
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Set the null-hypothesis population mean to 600 milliseconds.
# Step 2. Calculate the one-sample t statistic from the observed mean, SD and n.
# Step 3. Run t.test() against the same null hypothesis and compare the outputs.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# sqrt [sqrt(x)]: Calculate the non-negative square root.
# pt [pt(t, df)]: Evaluate the cumulative probability for a Student t distribution.
# abs [abs(x)]: Return the absolute value of each number.
# print [print(x)]: Display an object or calculated value in the R Console.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# t.test [t.test(x, mu=600)]: Perform a Student t test and report a confidence interval.
# p-value [2 * pt(-abs(t), df)]: Compute the two-sided tail probability under the null
# model.

# SUPERPOWERED TEACHING NOTE:
# A one-sample t statistic compares the observed mean to hypothesised mu0 in units of estimated standard errors. t.test() also returns a p-value and confidence interval under modelling assumptions.
# IN-CLASS MOVE: Show t=(mean−mu0)/(SD/sqrt(n)) and why the degrees of freedom are n−1.
# COMMON PITFALL: A small p-value does not establish substantive importance or guarantee the model's assumptions.
#
# NOTE: Assign the computed result to a named R object.
mu0 <- 600                               # Hypothesised population mean, milliseconds.
# NOTE: Compute t from a mean difference divided by its standard error.
t_manual <- (rt_mean-mu0)/(rt_sd/sqrt(rt_n))
# NOTE: Assign the computed result to a named R object.
df_manual <- rt_n-1
# NOTE: Assign the computed result to a named R object.
two_sided_p <- 2*pt(-abs(t_manual),df=df_manual)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(t=t_manual,df=df_manual,p=two_sided_p))
# NOTE: Run a one-sample t test and request its confidence interval.
model_t <- t.test(rt_demo,mu=mu0,alternative="two.sided",conf.level=.95)
# NOTE: Show the result in the Console (or display a ggplot object).
print(model_t)


# ============================================================================

# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# t ≈ 2.2631   df = 11   p ≈ 0.04484
# # t.test() additionally prints the 95% confidence interval and sample mean.
# Data-dependent previews are templates, not verified Console output.
#  W6-E02  |  Confidence interval and effect size
# ============================================================================
#
# MOODLE SOURCE NOTE: Independent ferdo Lab provisional lesson [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Check 95% t interval, and distinguish statistical and practical
# significance.
# CONCEPT: A 95% t confidence interval, effect size and practical significance.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W6-E02
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Find the critical t value for a two-sided 95% confidence interval.
# Step 2. Calculate the confidence interval manually from the sample mean and SE.
# Step 3. Calculate Cohen’s d to describe standardised effect size.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# qt [qt(.975, df)]: Get a quantile (critical value) of a Student t distribution.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# print [print(x)]: Display an object or calculated value in the R Console.
# stopifnot [stopifnot(condition)]: Stop execution with an error if a logical condition is
# not true.
# all.equal [all.equal(x, y)]: Compare objects approximately, allowing small numeric
# tolerances.
# as.numeric [as.numeric(x)]: Convert values to numeric form when conversion is meaningful.

# SUPERPOWERED TEACHING NOTE:
# A t confidence interval uses the critical t quantile times the standard error. Cohen's d scales the observed difference from mu0 by sample SD; all.equal() checks numerical agreement with t.test().
# IN-CLASS MOVE: Contrast an interval for a population mean with an interval for individual trials.
# COMMON PITFALL: An interval describes uncertainty about a mean under assumptions, not a probability that individual scores lie inside it.
#
# NOTE: Assign the computed result to a named R object.
critical_t <- qt(.975,df=rt_n-1)
# NOTE: Construct the t interval as mean ± critical t × SE.
ci_manual <- rt_mean + c(-1,1)*critical_t*rt_se
# NOTE: Standardise the mean difference using the sample SD.
cohens_d <- (rt_mean-mu0)/rt_sd
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(ci_lower=ci_manual[1],ci_upper=ci_manual[2],cohens_d=cohens_d))
# NOTE: Assert that a data-quality or numerical check is true; stop on failure.
stopifnot(isTRUE(all.equal(as.numeric(ci_manual),as.numeric(model_t$conf.int),tolerance=1e-8)))


# ============================================================================

# EXPECTED OUTPUT — Exact values where deterministic; otherwise preview:
# ci_lower ≈ 600.1854
# ci_upper ≈ 613.3146
# cohens_d ≈ 0.6533
# # stopifnot() prints nothing if the two CI calculations agree.
# Data-dependent previews are templates, not verified Console output.
#  W6-E03  |  Regression, correlation, model diagnostics
# ============================================================================
#
# MOODLE SOURCE NOTE: Independent ferdo Lab enrichment; not a verified Moodle Week 6
# worksheet [INDEPENDENT-EXTENSION]
# LEARNING GOAL: Interpret slope, R^2; explain confounding and dependence from repeated
# observations.
# CONCEPT: Linear regression, model coefficients, R-squared and assumptions.
#
# DETAILS: https://paths.ferdo.net/r/pals-masterclass/#W6-E03
#
# TASK INSTRUCTIONS — follow these steps in order:
# Step 1. Fit a linear regression model predicting one variable from another.
# Step 2. Print the model summary, including coefficients and R-squared.
# Step 3. Print the correlation coefficient for comparison with the regression.

# CODE STARTERS — uncomment and finish each line before running:

# 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 SOLUTION — complete code for the same steps
# ----------------------------------------------------------------------------
#
# EXPLANATION: 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.
#
# FUNCTION NOTES — short definitions (see DETAILS URL for full syntax):
# lm [lm(y ~ x, data=data)]: Fit a linear statistical model using a formula.
# print [print(x)]: Display an object or calculated value in the R Console.
# summary [summary(model)]: Show a descriptive or model-specific statistical summary.
# c [c(a, b, ...)]: Combine multiple values into a vector.
# cor [cor(x, y, use="complete.obs")]: Calculate a correlation coefficient, Pearson by
# default.

# SUPERPOWERED TEACHING NOTE:
# lm() estimates a linear relationship between CES-D and AHI. Coefficients, correlation r and R² summarise an association; repeated measures violate a simple independent-row assumption.
# IN-CLASS MOVE: Ask which variable is outcome and which is predictor, then evaluate whether the observations can be considered independent.
# COMMON PITFALL: Do not treat this basic repeated-record model as a causal or fully valid independent-observation analysis.
#
# NOTE: Fit an ordinary least-squares linear model.
fit <- lm(cesdTotal~ahiTotal,data=all_data)
# NOTE: Show the result in the Console (or display a ggplot object).
print(summary(fit)$coefficients)
# NOTE: Show the result in the Console (or display a ggplot object).
print(c(r=cor(all_data$ahiTotal,all_data$cesdTotal),
# NOTE: Continue this R expression with its arguments, values or formatting.
        r_squared=summary(fit)$r.squared))
# Assumptions: independence, approximately linear relationship, conditional errors.


# END OF THE SIX-WEEK COURSE.

# EXPECTED OUTPUT — Output format / checks (not fabricated values):
#                Estimate Std. Error t value Pr(>|t|)
# (Intercept)      ...      ...    ...      ...
# ahiTotal         ...      ...    ...      ...
# r = ... ; r_squared = ...
# # Coefficient values depend on the public dataset.
# Data-dependent previews are templates, not verified Console output.
