Week 2 · Tuesday, Oct 6 · Lecture
| Time | What |
|---|---|
| 1:30 | Recap: two sentences from Thursday |
| 1:40 | Project folders and relative paths |
| 1:55 | Loading real data, and the first look |
| 2:15 | Reading str() line by line |
| 2:30 | Adding and removing rows and columns |
| 2:50 | A loading script you can source() |
Learning goals: load a CSV you didn’t make, say what’s in it, and change its shape on purpose.
Type along. Still no AI. Your DS printouts are due Thursday.
"data.frame" · "integer" · 2 (columns!) · "list"
stat266a/
├── data/ <- raw data. NEVER edited by hand.
├── scripts/ <- your .R files
├── figures/ <- plots you save
├── week01/
└── week02/
stat266a/, which is what makes the next slide workImportant
data/ is read-only. If you fix something, you fix it in code, so the fix is written down and repeatable. Never in Excel.
Warning
Error in file(file, "rt") : cannot open the connection plus No such file or directory almost always means your working directory is wrong, not that the file is missing. Run getwd() first.
Life expectancy, population and GDP per person, by country and year.
Don’t have the file? Download it once, in code:
Downloading in your script rather than clicking a link means the next person — including you in March — can rebuild the whole thing from the script alone.
str(), line by linestr(gapminder)
#> 'data.frame': 1704 obs. of 6 variables:
#> $ country : chr "Afghanistan" "Afghanistan" "Afghanistan" ...
#> $ year : int 1952 1957 1962 1967 1972 ...
#> $ pop : num 8425333 9240934 10267083 ...
#> $ continent: chr "Asia" "Asia" "Asia" "Asia" ...
#> $ lifeExp : num 28.8 30.3 32 34 36.1 ...
#> $ gdpPercap: num 779 821 853 836 740 ...obs. = rows, variables = columns$ name : type first values ...chr character · num double · int integer · logi logical1704 = 142 countries × 12 years. Knowing the shape should be is how you notice when it isn’t.
str() outputRead the same file, asking for text columns as factors:
Factor w/ 142 levels tells you there are exactly 142 distinct countries, for freenlevels(gapminder$country), levels(gapminder$continent)Note
A quick count of categories is what factors are good for. chr is friendlier for most other things. Neither is “the right one”.
summary() addsThree things to look for on a first pass:
Min. of -99 in a column of ages is a missing-data code, not an age.NA's, which summary() reports as an extra line only when they exist.head() shows the tidiest part of most datasets. Problems live in the middle and at the end.
Tip
View(gapminder) opens a spreadsheet-like viewer. Look, sort, filter — but every change goes in the script, never in the viewer.
⏱ 7 min
gapminder in bottomspot_checkspot_check twice. Why does it change? Then run it again after set.seed(266) twice. What changed?table(), check that every year has the same number of rowscbind() and its one ruleThe column must be as long as the data frame has rows:
Warning
If the length divides evenly into nrow(), R sometimes recycles it instead of erroring. You get a full column of nonsense, quietly. Always compare length() to nrow() yourself.
rbind()rbind() needs the same columns, by name. Miss one and you get:
A list() works as a row too: rbind(d, list("Chile", 2012, ...)). In that case the order has to match, which is easier to get wrong.
If country is a factor, "Chile" must be one of its levels:
Modern R (4.1+) adds the new level for you. Older code, older R, or a stricter function may instead give you NA and a warning:
Tip
After any rbind(), check: tail(d, 1) and str(d). You’re looking for an NA that appeared without being asked for.
Counting columns is fragile. Name them instead:
%in% gives a logical vector — one TRUE/FALSE per column. ! flips it. This pattern still reads correctly six months later, and after someone adds a column.
rbind() doesn’t check whether it’s stacking the same data twice. Run the same chunk twice and your data silently doubles.
Important
This is the strongest argument for a loading script: it always starts from the file, so re-running it can’t accumulate.
Warning
na.omit() drops the whole row if any column is missing. On a wide dataset that can quietly throw out most of your data. Always compare nrow() before and after, and say in your report how many rows you lost.
⏱ 10 min
In week02/trip.R:
data.frame(), create trip_log with one row and three columns: city (text), country (text), nights (a number)rbind(), add two more stopscbind(), add a logical column rained (one value per stop)str(trip_log). Are the types what you expected?nights_per_city that is nights divided by sum(nights)which.min()Step 6 hint: trip_log[-which.min(trip_log$nights), ]. Code that finds the row beats code that hard-codes 3.
str() like a pro⏱ 6 min
#> 'data.frame': 88 obs. of 6 variables:
#> $ country : Factor w/ 22 levels "Argentina","Australia",..: 3 3 3 3 1 ...
#> $ continent: Factor w/ 5 levels "Africa","Americas",..: 2 2 2 2 2 ...
#> $ year : int 1967 1987 2002 2007 1967 ...
#> $ lifeExp : num 57.6 65.2 71 72.4 65.6 ...
#> $ pop : int 8835000 11626000 12300000 12420476 ...
#> $ gdpPercap: num 2961 3448 3630 4172 8053 ...Using only that output, no other functions: how many rows? how many columns? how many distinct countries? what type is year? what is the first country, by name?
88 · 6 · 22 · int · the 3rd level alphabetically — you can’t name it from this output alone, and noticing that is the point.
scripts/load-gapminder.R:
# Load and prepare the gapminder data.
# Run with: source("scripts/load-gapminder.R")
if (!file.exists("data/gapminder_data.csv")) {
download.file(
"https://swcarpentry.github.io/r-novice-gapminder/data/gapminder_data.csv",
destfile = "data/gapminder_data.csv")
}
gapminder <- read.csv("data/gapminder_data.csv")
gapminder$pop_millions <- gapminder$pop / 1e6
str(gapminder)source() runs a whole scriptEverything the script creates appears in your session. In Positron, the Source button at the top of the editor does the same for the open file.
Tip
The restart test. Session → Restart R, then source("scripts/load-gapminder.R"). If gapminder is back exactly as it was, your analysis lives in your script, not in your session’s memory. If it isn’t, something you need was only ever typed in the console.
Do this at the end of every work session. It takes ten seconds and it is the whole idea of reproducibility.
⏱ 10 min
scripts/load-gapminder.R with the code from two slides agoreadLines("scripts/load-gapminder.R")source() itnrow(gapminder) and ncol(gapminder)Step 3 is the one that matters. A script that only works in an R session you’ve been typing in for two hours isn’t a script yet.
data/file.csvread.csv() reads a CSV; stringsAsFactors = TRUE makes text columns factorsstr() gives size, column types and first values in one line eachtail() and a random sample, not just head()cbind() adds columns (length must equal nrow()), rbind() adds rows (columns must match)%in% remove; na.omit() drops whole rowssource() re-runs a saved script, so your data can always be rebuiltThursday, Oct 8: bring your two printed DS PDFs (Lessons 3 and 4) and a pencil. We start with the discussion check.
Tonight or tomorrow: Lesson 5: Exploring Data Frames on the Intro to R site, to lock in today’s material. Not graded this week, still no AI.
Project: the proposal presentation is Tuesday, Oct 20. By this Thursday you should have a topic and a candidate data source. Come to office hours and talk it through with me.
Next week: subsetting data — pulling out exactly the rows and columns you want.

STAT 266A · Fall 2026 · UC Santa Cruz