Week 5 · Thursday, Oct 22 · Lecture
| Time | What |
|---|---|
| 1:30 | DS 4 discussion check |
| 1:45 | Vectorization: one operation, every element |
| 2:15 | Functions: name a piece of work |
| 2:40 | Functions that fail loudly, and functions on data frames |
| 2:55 | Tuesday: how the day will run |
Learning goals: replace a loop with one vectorized line, predict what recycling does, and write a small function that takes arguments, returns a value, and stops on bad input.
Laptops closed for the first 15 minutes. Still no AI.
Note
From now on, printing is optional. A printout is still the easiest way to scribble on your code, so keep doing it if it helps you. The PDF on Canvas is what’s graded.
1. In Exercise 3, which line made the lines draw on top of the points? What happens if you swap the two layers?
The layer added later is drawn on top. Swap them and the points cover the lines where they overlap.
2. In Exercise 5, how many lm lines did you get? Point to the line of code that decides that number.
Five, one per continent, because colour = continent is in ggplot() and geom_smooth() inherits it. Move it into geom_point() and you get one line.
3. You wrote colour = "red" inside aes(). What does the legend say, and what colour are the points?
A legend with one entry, red, and salmon points: inside aes(), "red" is treated as data.
Create week05/vectorization-functions.R:
Three source() lines and you have both datasets, loaded and checked. That’s what Weeks 3 and 4 built.
You’ve been doing this since Week 2:
1704 divisions, one line, no loop.
gapminder$gdp_billions <- gapminder$pop * gapminder$gdpPercap / 1e9
head(gapminder[, c("country", "year", "pop", "gdpPercap", "gdp_billions")], 3)
#> country year pop gdpPercap gdp_billions
#> 1 Afghanistan 1952 8425333 779.4453 6.567086
#> 2 Afghanistan 1957 9240934 820.8530 7.585449
#> 3 Afghanistan 1962 10267083 853.1007 8.758856pop[1] * gdpPercap[1], then pop[2] * gdpPercap[2], and so on, for every rowWeek 3: a loop
Tip
Vectorized code is shorter, easier to read, and much faster. For a million values, the loop above is many times slower. Loops still have their place: when each step depends on the previous one, or when you repeat a whole block (a plot, a check) per group.
R reuses the shorter vector from the start until it covers the longer one:
If the longer length isn’t a multiple of the shorter one, you get an answer and a warning:
Warning
A length-1 vector (x * 10) is recycling too, and that’s the one you want. Any other mismatch is almost always a bug. Read that warning.
⏱ 5 min
On paper first. Write the result of each, then run them:
Hint: a matrix is a vector stored column by column.
The first one is clean: row 2 is always multiplied by −1. The second one runs 10, 20, 30 down the columns, not across the rows. No warning, because 6 is a multiple of 3. If you expected 11 23 35 on the first row, that’s the trap.
ifelse(): a vectorized if, one answer per element:
sum(condition) counts, mean(condition) gives a proportion. You’ll use these two in every project.
NA spreads, againNote
Same rule as Week 3: one NA in, NA out. With na.rm = TRUE you drop the NAs, so first know how many you’re dropping: sum(is.na(x)).
& and && are not the same| Works on | Use it in | |
|---|---|---|
&, \| |
whole vectors, element by element | subsetting, ifelse(), new columns |
&&, \|\| |
one TRUE/FALSE each | if () conditions |
any() and all() turn a vector of TRUE/FALSE into one value, so they can go inside if (). That’s how your check scripts work.
⏱ 8 min
In your script, one line each, no loops:
commute_h: the commute in hourslong_commute: "yes" or "no", for more than 20 minutestable(survey$long_commute, useNA = "ifany") agree with step 2?17 yes, 27 no, 1 NA (id 35, the missing commute). ifelse() keeps the NA: it doesn’t guess. The proportion is out of 44, not 45.
You just wrote this:
Copied the same line three times? Time for a function.
| Part | Here |
|---|---|
| name | prop_above |
| the keyword | function |
| arguments | x, cutoff |
| body | inside { } |
| returned value | the last line, or whatever you give to return() |
And it’s vectorized for free: inside, x > cutoff works on the whole column, because > does.
x, cutoff and above exist only while the function runsEach piece is easy to check on its own. When min_to_days() gives a weird answer, you test the two small ones first. Same idea as Week 3’s check script: small steps you can trust.
⏱ 7 min
count_na(x): how many NAs are in x. Test it on survey$age (should be 3) and survey$site (1)pct(x): turns a proportion into a percentage, rounded to 1 decimal: pct(0.3863636) gives 38.6prop_above() and pct()site is text. R compared "east" > 20 as text (alphabetically), and every site name sorts after "20".
“100% of sites are above 20” is nonsense, and R said nothing.
This is the worst kind of bug: a number that looks plausible. A function should stop when it gets input it can’t handle.
stopifnot() and stop()| Use it for | |
|---|---|
stopifnot(cond1, cond2) |
quick checks; R writes the message |
if (...) stop("message") |
your own message, for a reader who isn’t you |
Same idea as your check scripts from Week 3, now built into the tool.
col = "lifeExp" is a default: leave it out and that’s what you getdat is an argument, so it works on gapminder, gm07, or any gapminder-style data framerows[[col]] takes a column whose name is stored in a variable⏱ 6 min
NaN? (Hint: how many rows does rows have?)cont is not one of the continents in dat, stop with a message that says which continent wasn’t found"africa", then with "Africa"stat266a/
├── data/
├── scripts/
│ ├── load-gapminder.R
│ ├── check-gapminder.R
│ ├── clean-survey.R
│ └── functions.R ← new
└── week05/
Then, at the top of any analysis script: source("scripts/functions.R").
Tip
For your project: if you’ve cleaned two columns the same way, that’s your first function.
sum(condition) counts, mean(condition) gives a proportion; ifelse() is a vectorized if& for vectors, && inside if ()name <- function(args) { body }: the last line is the resultstopifnot() and stop()9:50-10:55 am · DS 5
Lesson 9: Vectorization and Lesson 10: Functions, alone, no AI. Two PDFs.
1:30-3:05 pm · Regression + AI audit #1
Starts with the DS 5 check: you swap laptops with a classmate and check each other’s DS. Then linear regression, and your first AI audit.
Important
Bring your laptop, charged, to the afternoon. The DS check happens on your classmate’s screen, not on paper. No printing needed.
Before Tuesday: read the Lesson 9 and Lesson 10 pages. The exercises will feel familiar after today.
Next: Tuesday, Oct 27 · DS 5 in the morning, regression in the afternoon.

STAT 266A · Fall 2026 · UC Santa Cruz