# ============================================================================ # 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://lab.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. print(6 + 1) # Addition returns 7. print(2 * (1 + 3)) # Parentheses evaluated first; returns 8. print(5 + 3 * 2) # Multiplication precedes addition: 11. # ============================================================================ # 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://lab.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. x <- sum(1:10) # 1:10 is an inclusive integer sequence; sum gives 55. print(x / 10) # Stored result re-used; expected 5.5. print(ls()) # List current objects; not the same as printing values. # ============================================================================ # 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://lab.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. course_label <- "PALS practice" # Character/string is enclosed in quotes. participants <- 66L # L makes an integer literal. sleep_hours <- c(6, 8, 7, 5) # c() combines values into a numeric vector. print(sleep_hours[1]) # R indices start at 1, not 0. print(sleep_hours[sleep_hours > 6]) # Logical indexing -> c(8, 7). print(mean(sleep_hours)) # Expected 6.5 hours, for illustrative data. # ============================================================================ # 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://lab.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. crime <- USArrests # Built-in public teaching dataset, no CSV needed. print(dim(crime)) # Rows and columns: 50 by 4. print(names(crime)) # Murder, Assault, UrbanPop, Rape. print(head(crime)) # Display the first six rows. # ============================================================================ # 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://lab.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. print(max(c(2, 4, 6))) # max() needs a vector or individual numbers. print(crime$Murder) # $ extracts the column named Murder. print(max(crime$Murder)) # Expected 17.4 in USArrests. print(max(crime$Assault)) # Expected 337 in USArrests. # Interactive help: type ?max or help("max") in the RStudio Console. # ============================================================================ # 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://lab.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. rm(crime) # Remove only this named object from Environment. print(exists("crime")) # FALSE: object no longer exists. # ============================================================================ # 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://lab.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). question <- "Does sleep predict response time?" # A question is not a causal conclusion. rt_demo <- c(620, 610, 590, 605) # Fictional reaction times in milliseconds. print(data.frame(trial=seq_along(rt_demo), rt_ms=rt_demo)) # ============================================================================== # 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://lab.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. # Run install.packages("tidyverse") ONCE in Console if not installed. # library(tidyverse) loads dplyr, tidyr, ggplot2 etc. from your R library. pinfo <- read.csv("data/participant-info.csv", stringsAsFactors=FALSE) responses <- read.csv("data/ahi-cesd.csv", stringsAsFactors=FALSE) print(c(n_participants=nrow(pinfo), n_responses=nrow(responses))) # ============================================================================ # 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://lab.ferdo.net/r/pals-masterclass/#W2-M02 # OPTIONAL INDEPENDENT EXAMPLE (student/exam tables, one-to-many join): # https://lab.ferdo.net/r/pals-masterclass/extra-inner-join-two-keys/ # # 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. all_data <- dplyr::inner_join(pinfo, responses, by=c("id","intervention")) print(c(rows=nrow(all_data), unique_ids=dplyr::n_distinct(all_data$id))) stopifnot(nrow(all_data) <= nrow(responses)) # Key matching must not explode rows. # ============================================================================ # 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://lab.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. selected_vars <- dplyr::select(all_data, id, intervention, ahiTotal, cesdTotal, sex, age, educ, income, occasion) print(names(selected_vars)) # Exactly nine specified columns. # ============================================================================ # 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://lab.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. # MOODLE CORRECTION: worksheet says 1/2 are pre/post; codebook is authoritative: # occasion 0 = pretest; 1 = posttest; 2 = one-week follow-up. pre_post <- dplyr::filter(selected_vars, occasion %in% c(0,1)) print(table(pre_post$occasion)) # Inspect sample size/attrition per occasion. # ============================================================================ # 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://lab.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. pre_post_asc <- dplyr::arrange(pre_post, ahiTotal) print(head(dplyr::select(pre_post_asc, id, occasion, ahiTotal))) # ============================================================================ # 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://lab.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. overall_summary <- dplyr::summarise(pre_post, n=dplyr::n(), mean_ahi=mean(ahiTotal,na.rm=TRUE), mean_cesd=mean(cesdTotal,na.rm=TRUE)) print(overall_summary) # ============================================================================ # 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://lab.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. occasion_summary <- pre_post_asc |> dplyr::group_by(occasion) |> dplyr::summarise(n=dplyr::n(), mean_ahi=mean(ahiTotal), mean_cesd=mean(cesdTotal), .groups="drop") print(occasion_summary) # ============================================================================ # 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://lab.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. pre_post <- dplyr::mutate(pre_post, sadness=cesdTotal-ahiTotal) print(head(dplyr::select(pre_post, sadness, cesdTotal, ahiTotal))) # DO NOT interpret "sadness" clinically: AHI/CES-D scales have different constructs/ranges. # ============================================================================ # 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://lab.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. # MOODLE CORRECTION: worksheet labels conflict with source codebook. # Correct coding in code-book.pdf: 1=Female, 2=Male. pre_post <- dplyr::mutate(pre_post, sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male"))) print(table(pre_post$sex_label, useNA="ifany")) # ============================================================================ # 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://lab.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. paired_pre <- dplyr::filter(all_data, occasion==0) |> dplyr::select(id,intervention,ahi_pre=ahiTotal) paired_post <- dplyr::filter(all_data, occasion==1) |> dplyr::select(id,intervention,ahi_post=ahiTotal) paired_data <- dplyr::inner_join(paired_pre,paired_post,by=c("id","intervention")) |> dplyr::mutate(change_ahi=ahi_post-ahi_pre) print(dplyr::summarise(paired_data, paired_n=dplyr::n(), mean_change=mean(change_ahi))) # ============================================================================== # 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://lab.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. qformats <- read.csv("data/Qformats.csv", stringsAsFactors=FALSE) scoring <- read.csv("data/Scoring.csv", stringsAsFactors=FALSE) aq_responses <- read.csv("data/AQ.csv", stringsAsFactors=FALSE) print(c(participants=nrow(aq_responses),questions=nrow(qformats),rules=nrow(scoring))) # ============================================================================ # 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://lab.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. aq_long <- tidyr::pivot_longer(aq_responses, cols=dplyr::all_of(paste0("Q",1:10)),names_to="Question",values_to="Response") print(dim(aq_long)) # Real source: 66*10=660 rows; public demo differs. # ============================================================================ # 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://lab.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. aq_with_format <- dplyr::inner_join(aq_long,qformats,by="Question") aq_scored <- dplyr::inner_join(aq_with_format,scoring,by=c("QFormat","Response")) stopifnot(nrow(aq_scored)==nrow(aq_long), !anyNA(aq_scored$Score)) print(head(dplyr::select(aq_scored,Id,Question,Response,QFormat,Score))) # ============================================================================ # 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://lab.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. aq_scores <- aq_scored |> dplyr::group_by(Id) |> dplyr::summarise(AQTotal=sum(Score),.groups="drop") stopifnot(all(aq_scores$AQTotal >= 0 & aq_scores$AQTotal <= 10)) print(head(aq_scores)) # ============================================================================ # 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://lab.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. aq_pipe <- aq_responses |> tidyr::pivot_longer(cols=dplyr::all_of(paste0("Q",1:10)), names_to="Question",values_to="Response") |> dplyr::inner_join(qformats,by="Question") |> dplyr::inner_join(scoring,by=c("QFormat","Response")) |> dplyr::group_by(Id) |> dplyr::summarise(AQTotal=sum(Score),.groups="drop") stopifnot(identical(aq_scores,aq_pipe)) # Native |> parallels tidyverse %>% for these operations. # ============================================================================ # 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://lab.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. aq_summary <- dplyr::summarise(aq_scores, n=dplyr::n(), mean=mean(AQTotal), sd=sd(AQTotal), min=min(AQTotal), max=max(AQTotal), above6=sum(AQTotal>6)) print(aq_summary) print(dplyr::filter(aq_scores,AQTotal>6)) # ============================================================================ # 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://lab.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. p_aq <- ggplot2::ggplot(aq_scores,ggplot2::aes(x=AQTotal)) + ggplot2::geom_histogram(binwidth=1,boundary=-0.5,colour="white") + ggplot2::scale_x_continuous(breaks=0:10) + ggplot2::theme_classic() + ggplot2::labs(x="AQ-10 score",y="Number of participants",title="AQ-10 score distribution") print(p_aq) # ============================================================================ # 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://lab.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. variance_aq <- var(aq_scores$AQTotal) # Sample variance (n-1 denominator). se_aq <- sd(aq_scores$AQTotal)/sqrt(nrow(aq_scores)) print(c(sample_variance=variance_aq, standard_error=se_aq)) # ============================================================================== # 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://lab.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. p_hist <- ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) + ggplot2::geom_histogram(binwidth=5,colour="white") + ggplot2::labs(x="AHI total",y="Count",title="AHI distribution") print(p_hist) print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal)) + ggplot2::geom_histogram(binwidth=1,colour="white") + ggplot2::labs(x="AHI total",y="Count",title="AHI distribution: binwidth 1")) # ============================================================================ # 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://lab.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. all_data <- dplyr::mutate(all_data, sex_label=factor(sex,levels=c(1,2),labels=c("Female","Male"))) p_scatter <- ggplot2::ggplot(all_data, ggplot2::aes(x=ahiTotal,y=cesdTotal,colour=sex_label)) + ggplot2::geom_point(alpha=0.48) + ggplot2::labs(x="AHI total",y="CES-D total",colour="Sex") print(p_scatter) # ============================================================================ # 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://lab.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. p_box <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) + ggplot2::geom_boxplot(fill="#7cccd2",outlier.alpha=.4) + ggplot2::labs(x="Occasion code",y="AHI total") print(p_box) p_violin <- ggplot2::ggplot(all_data,ggplot2::aes(x=factor(occasion),y=ahiTotal)) + ggplot2::geom_violin(fill="#c3aaf5",alpha=.55) + ggplot2::geom_boxplot(width=.13,outlier.shape=NA) + ggplot2::geom_jitter(width=.1,alpha=.15,size=.45) print(p_violin) # ============================================================================ # 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://lab.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. intervention_labels <- c("Signature strengths","Three good things", "Gratitude visit","Early memories (control)") all_data <- dplyr::mutate(all_data, intervention_label=factor(intervention, levels=1:4,labels=intervention_labels)) means_occasion <- all_data |> dplyr::group_by(occasion) |> dplyr::summarise(mean_ahi=mean(ahiTotal),.groups="drop") print(ggplot2::ggplot(means_occasion,ggplot2::aes(x=factor(occasion),y=mean_ahi))+ ggplot2::geom_col(fill="#9fd8c2")) means_groups <- all_data |> dplyr::group_by(occasion,intervention_label) |> dplyr::summarise(n=dplyr::n(),mean_ahi=mean(ahiTotal),sd_ahi=sd(ahiTotal), .groups="drop") print(ggplot2::ggplot(means_groups,ggplot2::aes(x=factor(occasion),y=mean_ahi, fill=intervention_label)) + ggplot2::geom_col(position="dodge")) # ============================================================================ # 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://lab.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. p_line <- ggplot2::ggplot(means_groups, ggplot2::aes(x=occasion,y=mean_ahi,colour=intervention_label,group=intervention_label)) + ggplot2::geom_line() + ggplot2::geom_point() + ggplot2::geom_errorbar(ggplot2::aes(ymin=mean_ahi-sd_ahi,ymax=mean_ahi+sd_ahi), width=.13,na.rm=TRUE) + ggplot2::coord_cartesian(ylim=c(24,120)) + ggplot2::labs(x="Measurement occasion (0=pre, 1=post)",y="Mean AHI ± 1 SD") print(p_line) # ============================================================================ # 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://lab.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. print(p_scatter + ggplot2::theme_classic() + ggplot2::labs(title="Happiness and depressive symptoms")) print(p_scatter + ggplot2::theme_bw() + ggplot2::facet_wrap(~sex_label)) print(ggplot2::ggplot(all_data,ggplot2::aes(x=ahiTotal,y=cesdTotal)) + ggplot2::geom_point(colour="purple",size=2,shape=17,alpha=.3) + ggplot2::labs(title="Styled scatterplot",x="AHI",y="CES-D")) # ============================================================================ # 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://lab.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. cor_ahi_cesd <- cor(all_data$ahiTotal,all_data$cesdTotal,use="complete.obs") print(c(correlation=cor_ahi_cesd)) atsq <- read.csv("data/atsq_scores.csv",stringsAsFactors=FALSE) p_background <- read.csv("data/p_info.csv",stringsAsFactors=FALSE) atsq_joint <- dplyr::inner_join(atsq,p_background,by="id") print(c(atsq_rows=nrow(atsq),background_rows=nrow(p_background),joined=nrow(atsq_joint))) # ============================================================================== # 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://lab.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. rt_demo <- c(620,610,590,605,615,600,625,595,608,612,598,603) rt_n <- length(rt_demo) rt_mean <- mean(rt_demo) rt_sd <- sd(rt_demo) rt_se <- rt_sd / sqrt(rt_n) print(c(n=rt_n,mean_ms=rt_mean,sd_ms=rt_sd,se_ms=rt_se)) # ============================================================================ # 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://lab.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. set.seed(46) # Fix pseudo-random seed for reproducibility. means_n4 <- replicate(1000,mean(sample(rt_demo,4,replace=TRUE))) means_n10 <- replicate(1000,mean(sample(rt_demo,10,replace=TRUE))) print(c(sd_n4=sd(means_n4),sd_n10=sd(means_n10))) bootstrap_means <- replicate(1000,mean(sample(rt_demo,rt_n,replace=TRUE))) bootstrap_ci <- quantile(bootstrap_means,c(.025,.975)) print(bootstrap_ci) # ============================================================================ # 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://lab.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. par(mfrow=c(1,2)) hist(means_n4,main="Resample size 4",xlab="Mean RT (ms)") hist(means_n10,main="Resample size 10",xlab="Mean RT (ms)") par(mfrow=c(1,1)) # ============================================================================== # 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://lab.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. mu0 <- 600 # Hypothesised population mean, milliseconds. t_manual <- (rt_mean-mu0)/(rt_sd/sqrt(rt_n)) df_manual <- rt_n-1 two_sided_p <- 2*pt(-abs(t_manual),df=df_manual) print(c(t=t_manual,df=df_manual,p=two_sided_p)) model_t <- t.test(rt_demo,mu=mu0,alternative="two.sided",conf.level=.95) print(model_t) # ============================================================================ # 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://lab.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. critical_t <- qt(.975,df=rt_n-1) ci_manual <- rt_mean + c(-1,1)*critical_t*rt_se cohens_d <- (rt_mean-mu0)/rt_sd print(c(ci_lower=ci_manual[1],ci_upper=ci_manual[2],cohens_d=cohens_d)) stopifnot(isTRUE(all.equal(as.numeric(ci_manual),as.numeric(model_t$conf.int),tolerance=1e-8))) # ============================================================================ # 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://lab.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. fit <- lm(cesdTotal~ahiTotal,data=all_data) print(summary(fit)$coefficients) print(c(r=cor(all_data$ahiTotal,all_data$cesdTotal), r_squared=summary(fit)$r.squared)) # Assumptions: independence, approximately linear relationship, conditional errors. # END OF THE SIX-WEEK COURSE.