R Programming / Language and data foundations / Lesson 4
Lists, data frames, and factors
Choose a structure that preserves column meaning, observation grain, and category semantics.
On this page
Lesson goals
- Distinguish a list component from a list subset.
- Validate data-frame columns and observation identity.
- Keep factor levels separate from numeric measurements.
A list can contain heterogeneous objects
An atomic vector has one common type, while a list can hold components with different types, lengths, and structures. A record can therefore contain a character identifier and a numeric measurement vector without coercing both into text. Names describe component meaning, and exact extraction with [[ makes a programmatic contract clearer than depending on partial matching. Lists are also common return values for model fits and structured summaries. Inspect a returned object before assuming it is a rectangular table. A component called values is not necessarily one value per observation; its grain must be documented.
A data frame represents a rectangular relationship
A data frame is a list-like structure whose columns share a row count and represent aligned observations. Columns can have different types. The first fixture contrasts extracting a one-component list with extracting the numeric vector inside it, then constructs a two-column data frame. Each row represents one synthetic item. Preserve an explicit stable id rather than treating row names or current position as permanent identity. Filtering and ordering must act on whole rows when columns belong together. Independently sorting one column can preserve dimensions while destroying the relationship between an id and its measurement.
record <- list(id = "sample-1", measurements = c(2, 4))
part <- record["measurements"]
values <- record[["measurements"]]
cat("part class=", class(part), "; values length=", length(values), "\n", sep = "")
records <- data.frame(id = c("a", "b"), score = c(2, 4), stringsAsFactors = FALSE)
cat("rows=", nrow(records), "; columns=", ncol(records), "\n", sep = "")
cat("score mean=", mean(records[["score"]]), "\n", sep = "")
part class=list; values length=2
rows=2; columns=2
score mean=3
A single record can contain components of different sizes; a data frame organizes equal-length columns into observations.
Construction convenience is not schema validation
Data-frame constructors can recycle some compatible-length inputs or modify names according to their documented settings. Import functions can infer types from tokens. A program requiring one row per item should therefore validate column names, types, lengths, id coverage, and uniqueness explicitly. Repeated ids can be legitimate for repeated measurements, but then the observation key includes another dimension such as time or replicate. A numeric-looking identifier with leading zeros should usually remain character if arithmetic has no meaning. Do not convert it merely because every token happens to contain digits.
Factors encode a declared category vocabulary
A factor stores category codes with a levels attribute, and an ordered factor additionally declares a category order. The second fixture gives low, medium, and high an explicit order even though high is unobserved. Its table retains a zero count for that declared category. Codes two, one, two identify vocabulary positions; averaging them would invent a spacing between categories. A factor can be appropriate for modeling or tabulation, but it is not interchangeable with free text or numeric measurement. Unknown labels need a policy before conversion so accidental missing values do not hide vocabulary changes.
grade <- factor(c("medium", "low", "medium"),
levels = c("low", "medium", "high"), ordered = TRUE)
cat("codes=", paste(as.integer(grade), collapse = ","), "\n", sep = "")
cat("labels=", paste(as.character(grade), collapse = ","), "\n", sep = "")
cat("counts=", paste(as.integer(table(grade)), collapse = ","), "\n", sep = "")
cat("ordered=", is.ordered(grade), "\n", sep = "")
codes=2,1,2
labels=medium,low,medium
counts=1,2,0
ordered=TRUE
Explicit category levels preserve an unobserved high category; codes identify levels and are not measured numeric grades.
Levels influence later analysis and models
Changing a factorโs levels or reference category can change displayed tables and the columns of a model matrix. Subsetting does not always mean unused levels should be dropped: a zero-count category may carry an important reporting contract, while a model fit may require a deliberate level policy. Training and new-data categories should align under an explicit rule. Keep label cleaning separate from factor construction so whitespace, case, and genuine semantic differences are reviewed. These fixtures use base R only and avoid dependence on an additional data-manipulation package; their expected outputs were derived without R execution.
Common errors and fixes
A list subset is passed to a numeric summary
- Why it happens
- [ retained a list wrapper instead of extracting its component.
- How to fix it
- Use exact [[ extraction for the required numeric component.
- Prevent it next time
- Assert component type and length at the function boundary.
Rows remain the same size but ids no longer match scores
- Why it happens
- A column was sorted independently of its data frame.
- How to fix it
- Order or filter complete rows using a shared index.
- Prevent it next time
- Check known id-to-value associations after row operations.
Factor codes are interpreted as measured quantities
- Why it happens
- Internal category positions were mistaken for numeric labels.
- How to fix it
- Use category-aware summaries or a justified explicit score mapping.
- Prevent it next time
- Keep levels, labels, and measurement units separate.
Check your understanding
1. Why can a zero count for the high category be useful?
Show worked solution
It states that high belongs to the declared vocabulary even though no current observation has that label. Removing the level can make reports inconsistent across periods. Preserve or drop unused levels according to an explicit reporting or modeling contract, not simply to shorten printed output.
2. A table has ids 001 and 010. Should they become integers?
Show worked solution
If they identify items rather than measured amounts, preserve them as character strings. Integer conversion loses leading zeros and can break joins or external references. Validate the identifier format and uniqueness separately from numeric measurement columns.
Key takeaways
- Lists and data frames represent different structural contracts.
- Stable observation identity survives row operations.
- Factor codes do not define numeric distance between categories.