Data Types & Structures: Vectors, data.frames and the tibble
Before manipulating clinical data you need a feel for R's containers — vectors, lists, data.frames and tibbles — and how they relate to a SAS dataset.
Learning objectives
- 1Distinguish vectors, lists, data.frames, and tibbles and when to use each.
- 2Understand that a data.frame column is a typed vector — like a SAS variable.
- 3Subset rows and columns with [ ], $, and dplyr equivalents.
- 4Inspect structure with str(), glimpse(), head() — your PROC CONTENTS toolkit.
Key SAS® → R comparison
| Task | SAS® | R |
|---|---|---|
| One column of values | A variable within a dataset | An atomic vector |
| A full table | SAS dataset | data.frame / tibble |
| Mixed-type container | (no direct equivalent) | list |
| Inspect metadata | PROC CONTENTS | str(df) / glimpse(df) |
| First N rows | PROC PRINT (obs=5) | head(df, 5) |
| Dimensions | Obs & Vars in log | dim(df) / nrow() / ncol() |
| Pick a column | keep var; / var name | df$var / df[['var']] / select() |
| Pick rows | where / if subsetting | df[cond, ] / filter() |
Code examples
data dm;
input usubjid $ age sex $;
datalines;
101 47 M
102 53 F
103 39 M
;
run;library(tibble)
dm <- tibble(
usubjid = c("101", "102", "103"),
age = c(47, 53, 39),
sex = c("M", "F", "M")
)
glimpse(dm)Note: A tibble is a modern data.frame: it prints cleanly, never silently converts strings to factors, and shows column types up front.
data males;
set dm;
where sex = 'M';
keep usubjid age;
run;# Base R
males <- dm[dm$sex == "M", c("usubjid", "age")]
# dplyr (preferred, reads like the SAS intent)
library(dplyr)
males <- dm |>
filter(sex == "M") |>
select(usubjid, age)Note: filter() handles WHERE/IF row selection; select() handles KEEP/DROP column selection. The pipe |> chains them in reading order.
/* SAS has no direct equivalent;
you'd use multiple datasets or macro vars */results <- list(
data = dm,
n_subj = nrow(dm),
fitted = lm(age ~ 1, data = dm)
)
results$n_subj # 3
results[["data"]]Note: Lists hold anything — datasets, numbers, model objects — under named slots. They underpin many advanced workflows (purrr, nested data).
Explanation
Everything in R is built from vectors. A vector is an ordered set of values of one type — exactly like a single SAS variable read down the rows. Stack several equal-length vectors side by side and you have a data.frame, R's table. The crucial insight is that a data.frame column IS a vector, so everything you learned about numeric and character functions applies column-by-column.
A tibble is the tidyverse's refined data.frame. It behaves the same way but prints a tidy preview with column types, never mangles your character columns into factors, and is the default output of readr and dplyr. For clinical work you can treat 'tibble' and 'data.frame' as interchangeable and prefer tibbles for their cleaner behavior.
Lists are the one structure with no clean SAS analog. A list can hold objects of different types and shapes — a dataset, a count, a fitted model — under named slots. You won't need them on day one, but they are the foundation of R's functional, iterate-over-many-datasets style that we reach in the purrr module.