How to use this guide: Type examples in order into your own RStudio script, predict each result, then run lines with Ctrl+Enter. Lines beginning with # are comments, not executed commands. ... is a placeholder to fill in. Start each week from its first block. Weeks 2–4 use tidyverse in the same session.
The four R practicals, relevant CSV columns, codebook and visualisation lecture notes were reviewed; the relevant documents and sections are identified below. Original files and student data are not republished. Code is rewritten with fictional teaching examples and is not an official UCL answer key.
WEEK 01
Week 1 · Familiarisation with R
Source: pr01. Familiarisation (3).r, sections 1, 2, 3, 5, 6, 7, 8 and 9. The original file has no section numbered 4.
Calculator
R evaluates parentheses first; the results are 7 and 8. * multiplies and + adds. Select a line and send it to the Console with Ctrl+Enter (Cmd+Enter on Mac).
6 + 1
2 * (1 + 3)
Assign an object
In name <- value, R evaluates the right-hand side and stores it under the name on the left. x is 55 and x / 10 is 5.5. Run the name separately to display its value.
x <- 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10
x
x / 10
Text, numbers and vectors
Quotes create a character (text) value; numbers need no quotes. c(...) combines values into a vector in order. Names are case-sensitive: score and Score differ. Use _ instead of spaces and do not start a name with a digit.
student_label <- "Example"
score <- 12
values <- c(4, 7, 9)
student_label
score
values
Data frame
USArrests is an example dataset included with R; assign it to crime. head() previews rows, names() lists columns, and str() shows structure. You can also click the object in RStudio’s Environment pane. Its columns are Murder, Assault, UrbanPop and Rape.
crime <- USArrests
head(crime)
names(crime)
str(crime)
Functions and help
In function(input), the function name selects the operation and the parentheses contain its input. max(values) returns 9. ?max opens help; the ... in an exercise is a placeholder, not a complete solution.
max(values)
?max
Function arguments and missing values
In mean(demo, na.rm = TRUE), mean is the function name, demo is the first input and na.rm = TRUE is a named setting. Commas separate arguments; parentheses pass them to the function. The first mean is NA; the second removes missing values and returns 3. Before using na.rm, inspect why values are missing and how many will be excluded.
demo <- c(2, 4, NA)
mean(demo)
mean(demo, na.rm = TRUE)
Columns and maxima
table$column selects one column. crime$Murder returns a numeric vector; max(...) finds its maximum. The two calls evaluate Murder and Assault separately. If values are missing, max(crime$Murder, na.rm = TRUE) excludes them; first investigate why they are missing.
crime$Murder
max(crime$Murder)
max(crime$Assault)
Preview and removal
head(crime) shows the first six rows by default. rm(crime) removes only the crime object from this session; it does not delete R’s USArrests dataset. Recreate it with crime <- USArrests.
head(crime)
rm(crime)
Back to the week pathway →
WEEK 02
Week 2 · Data wrangling
Source: pr02. Data Wrangling.r, Part A 1–3 and Part B; also code-book.pdf pp. 4–5. Example data are fictional.
A1–A2 · Setup and reading files
Practical 02, Part A, 1–2. Put the script and CSVs in the same folder and in RStudio choose Session → Set Working Directory → To Source File Location. getwd() shows the current folder. read.csv("file.csv") reads a file; pinfo <- ... stores its result. Remove the leading # only when your own authorised files are in place. library(tidyverse) loads the package each session; if absent, first run install.packages("tidyverse") once.
library(tidyverse)
getwd()
# pinfo <- read.csv("participant-info.csv")
# responses <- read.csv("ahi-cesd.csv")
A3 · Join on two keys
Part A, 3. These fictional tables are not real people. If you read your own data, do not run the two data.frame() assignments in this block: they replace objects with the same names. Use the two fictional tables for this guide’s example. inner_join(x, y, by = c("id", "intervention")) uses both columns as keys and retains matching rows only. One person can have multiple measurements, so the resulting row count need not equal the number of people. Check key types and unmatched or duplicate records.
pinfo <- data.frame(id = c(1, 2, 3), intervention = c(1, 2, 1),
sex = c(1, 2, 1), age = c(20, 22, 21),
educ = c(2, 3, 2), income = c(1, 2, 2))
responses <- data.frame(id = c(1, 1, 2, 3), intervention = c(1, 1, 2, 1),
occasion = c(0, 1, 0, 1),
ahiTotal = c(50, 55, 65, 60), cesdTotal = c(20, 18, 12, NA))
all_data <- inner_join(pinfo, responses, by = c("id", "intervention"))
all_data
B1 · Select columns
Part B, select. select(data, columns) retains the requested columns. %>% passes the table on the left as the next function’s first input. names() checks the result; creating a new table does not change the original.
selected_vars <- all_data %>%
select(id, intervention, ahiTotal, cesdTotal, sex, age, educ, income, occasion)
names(selected_vars)
B2–B3 · Filter time points and arrange
Part B, filter and arrange. %in% c(0, 1) keeps either code; == tests equality. arrange(ahiTotal) sorts ascending. Source conflict: the practical’s pre/post 1/2 hint conflicts with the Week 1 code-book.pdf, pp. 4–5: the codebook labels 0 pretest, 1 posttest and 2 follow-up. Confirm the discrepancy with your tutor for the real analysis.
pre_post <- selected_vars %>% filter(occasion %in% c(0, 1))
pre_post_asc <- pre_post %>% arrange(ahiTotal)
pre_post_asc
B2–B3 · OR and descending order
filter(data, condition) keeps rows matching a condition, whereas select(data, columns) chooses columns. == tests equality and | means OR: rows coded 0 or 1 are retained. Here this is equivalent to occasion %in% c(0, 1). desc(ahiTotal) sorts from largest to smallest; without desc() the order is ascending.
selected_vars %>%
filter(occasion == 0 | occasion == 1) %>%
arrange(desc(ahiTotal))
B4–B5 · Summaries and groups
Part B, summarise and group_by. group_by(occasion) creates a group for each time point; summarise() returns row counts and means per group. na.rm = TRUE excludes missing values; the example leaves the CES-D mean as NA if all are missing. Pre/post groups may contain different people, so their means alone do not measure individual change.
pre_post_asc %>%
group_by(occasion) %>%
summarise(n_rows = n(), n_ahi = sum(!is.na(ahiTotal)),
mean_ahi = mean(ahiTotal, na.rm = TRUE),
mean_cesd = if (all(is.na(cesdTotal))) NA_real_ else mean(cesdTotal, na.rm = TRUE),
.groups = "drop")
B6 · New column and labels
Part B, mutate and recode. mutate() creates columns; factor() labels numeric categories. The codebook p. 4 says 1=female, 2=male; some practical hints reverse these labels. The sadness subtraction is a programming exercise in the script, not a clinically meaningful difference between the two scales.
pre_post <- pre_post %>%
mutate(sadness = cesdTotal - ahiTotal,
sex_label = factor(sex, levels = c(1, 2), labels = c("Female", "Male")))
pre_post %>% select(id, sadness, sex, sex_label)
Back to the week pathway →
WEEK 03
Week 3 · Reshaping and summarising responses
Source: wk3_practical_summarising_data (1).r, Part A Tasks 1–4 and Parts B–C; column structures of AQ.csv, Qformats.csv and Scoring.csv. Example data and scoring rules are fictional.
A1 · Three input tables
Practical 03, Part A, Task 1. In the materials, AQ.csv contains responses, Qformats.csv each question’s F/R format, and Scoring.csv the format–response–score lookup. The three questions, two response categories and scoring rules below are fictional examples, not the real ten-item key. The commented lines show how to read your own files. If you loaded your own files, do not run the three data.frame() assignments below; they replace objects with the same names.
library(tidyverse)
# responses <- read.csv("AQ.csv")
# qformats <- read.csv("Qformats.csv")
# scoring <- read.csv("Scoring.csv")
responses <- data.frame(Id = c(1, 2), Q1 = c("Agree", "Disagree"),
Q2 = c("Disagree", "Agree"), Q3 = c("Agree", "Agree"))
qformats <- data.frame(Question = c("Q1", "Q2", "Q3"), QFormat = c("F", "R", "F"))
scoring <- data.frame(QFormat = c("F", "F", "R", "R"),
Response = c("Agree", "Disagree", "Agree", "Disagree"),
Score = c(1, 0, 0, 1))
A2 · Wide → long
Part A, Task 2. In the wide table each question is a column. pivot_longer() moves column names into Question and cell values into Response, retaining Id. Two people × three fictional questions gives six rows. In the real file confirm that Q1–Q10 are selected.
rlong <- responses %>%
pivot_longer(cols = starts_with("Q"), names_to = "Question", values_to = "Response")
rlong
A3 · Two joins
Part A, Task 3. The first inner_join() attaches F/R by question; the second attaches the score by both format and response. Inner joins drop unmatched rows: compare row counts and use anti_join() to inspect non-matches. Check spelling, whitespace and key types; duplicate lookup keys can multiply rows.
with_format <- inner_join(rlong, qformats, by = "Question")
scored <- inner_join(with_format, scoring, by = c("QFormat", "Response"))
with_format
scored
nrow(rlong); nrow(with_format); nrow(scored)
anti_join(with_format, scoring, by = c("QFormat", "Response"))
A4 and B · Per-person totals
Part A, Task 4 and Part B. group_by(Id) separates people; summarise() makes one total per person. The example computes a total when all three example items are present. For the real ten-item exercise check for 10 and ensure each question matched exactly once; row count alone is insufficient. The pipe orders operations in reading order.
aq_scores <- scored %>%
group_by(Id) %>%
summarise(n_items = n(), AQTotal = if (n() == 3 && !any(is.na(Score))) sum(Score) else NA_real_,
.groups = "drop")
aq_scores
C · Description and histogram
Part C. mean() is the mean and sd() the sample standard deviation; min and max give the range endpoints. The example histogram uses 0–3; the source uses 0–10 for ten items. The source also asks about a screening threshold above 6: consider it only for the correctly scored real scale, never for this three-item example. A screening score is not a diagnosis. The approximate 68% rule assumes a suitable normal distribution; do not apply it automatically to discrete scores.
aq_scores %>% summarise(mean_total = mean(AQTotal, na.rm = TRUE),
sd_total = sd(AQTotal, na.rm = TRUE),
min_total = min(AQTotal, na.rm = TRUE),
max_total = max(AQTotal, na.rm = TRUE))
ggplot(aq_scores, aes(x = AQTotal)) +
geom_histogram(binwidth = 1) + theme_classic() +
scale_x_continuous(breaks = 0:3)
# For a valid, complete 10-item score only; not for the fictional example above:
# aq_scores %>% filter(AQTotal > 6)
# aq_scores %>% summarise(n_above_6 = sum(AQTotal > 6, na.rm = TRUE))
Back to the week pathway →
WEEK 04
Week 4 · Visualising data
Source: wk4_practical_visualisation.r, setup and Parts 1–2; Lecture 4. Visualising data - notes.pdf pp. 1–3. Example data are fictional.
Setup and data
Week 4, setup. The script’s internal heading mistakenly says Practical 03, but its content is graphs. The instructions use participant-info.csv and ahi-cesd.csv from Weeks 1–2; this guide uses a fictional table instead. The folder’s atsq_scores.csv and p_info.csv do not substitute for the requested AHI/CES-D pair. factor() labels categories; check the codebook’s sex codes 1=female and 2=male.
library(tidyverse)
all_data <- data.frame(id = c(1, 1, 2, 2, 3, 3),
occasion = c(0, 1, 0, 1, 0, 1), intervention = c(1, 1, 2, 2, 1, 1),
sex = c(1, 1, 2, 2, 1, 1),
ahiTotal = c(50, 56, 65, 63, 60, 68), cesdTotal = c(20, 18, 12, 14, 16, 11))
all_data <- all_data %>% mutate(sex_label = factor(sex, levels = c(1, 2),
labels = c("Female", "Male")))
1 · Histogram
Part 1, histogram. ggplot(data, aes(x = variable)) sets data and x mapping; + adds a layer. geom_histogram() shows frequencies in numeric bins; binwidth controls their width. Try 1 instead of 5 and compare the shape. Lecture 4 p. 2 discusses histograms for distributions.
ggplot(all_data, aes(x = ahiTotal)) +
geom_histogram(binwidth = 5, boundary = 0, colour = "white") +
labs(x = "Example AHI total", y = "Count")
2 · Scatter and colour
Part 1, scatterplot. Each point is one measurement, with two scores on x and y; colour maps a category to colour. Measurements may include repeats from the same person. Inspect direction and unusual points; correlation alone is not causation. Lecture 4 p. 3 discusses scatterplots for two numeric variables.
ggplot(all_data, aes(x = ahiTotal, y = cesdTotal, colour = sex_label)) +
geom_point(size = 3, alpha = 0.7) +
labs(x = "Example AHI total", y = "Example CES-D total", colour = "Sex")
3–4 · Boxplot and violin
Part 1, boxplot and violin. factor(occasion) makes time points categorical on x. The violin shows a smoothed distribution, the box the median and quartiles, and points the observations. Lecture 4 p. 3 describes a boxplot using M/SD; a standard boxplot does not directly show mean ± SD. The violin shape is unreliable with a tiny sample.
ggplot(all_data, aes(x = factor(occasion), y = ahiTotal)) +
geom_violin(fill = "#91e2d5", alpha = 0.4) +
geom_boxplot(width = 0.2, outlier.shape = NA) +
geom_point(position = position_jitter(width = 0.06), alpha = 0.7) +
labs(x = "Occasion", y = "Example AHI total")
5 · Means and grouped bars
Part 1, bar plot. group_by() with two variables gives one summary per time–group combination. geom_col() plots the computed means; position_dodge() puts bars side by side. geom_bar() counts rows by default; for computed y use geom_col() or geom_bar(stat = "identity"). A one-observation group has NA SD.
summary_data <- all_data %>%
group_by(occasion, intervention) %>%
summarise(n = n(), mean_ahi = mean(ahiTotal, na.rm = TRUE),
sd_ahi = sd(ahiTotal, na.rm = TRUE), .groups = "drop")
summary_data
ggplot(summary_data, aes(x = factor(occasion), y = mean_ahi, fill = factor(intervention))) +
geom_col(position = position_dodge(width = 0.9)) +
labs(x = "Occasion", y = "Mean AHI", fill = "Intervention")
6 · Lines and standard deviation
Part 1, line graph. group = intervention connects points separately by group. geom_errorbar() shows mean minus SD to mean plus SD; ±1 SD is not a 95% confidence interval. SD bars are absent for one-observation groups in the small example. coord_cartesian() limits the view; inspect missingness and paired measurements before interpreting real data.
ggplot(summary_data, aes(x = occasion, y = mean_ahi,
colour = factor(intervention), group = intervention)) +
geom_line() + geom_point() +
geom_errorbar(aes(ymin = mean_ahi - sd_ahi, ymax = mean_ahi + sd_ahi), width = 0.08,
na.rm = TRUE) +
coord_cartesian(ylim = c(24, 120)) +
labs(x = "Occasion", y = "Mean AHI ± 1 SD", colour = "Intervention")
7 · Themes, labels, facets and appearance
Part 2, themes, labels, facet_wrap, size/shape/colour. theme_classic() controls the visual theme, labs() titles and axes; compare theme_bw(). facet_wrap(~ sex_label) creates a panel per category. size, shape and alpha set size, shape and opacity; shape = 17 is a triangle. Lecture 4 p. 1 emphasises clear labels and scale choices.
ggplot(all_data, aes(x = ahiTotal, y = cesdTotal)) +
geom_point(colour = "purple", size = 4, shape = 17, alpha = 0.3) +
facet_wrap(~ sex_label) + theme_classic() +
labs(x = "Example AHI total", y = "Example CES-D total", title = "Example relationship")
Back to the week pathway →