30-Day Course
Clinical SAS → R
Day 4 of 30·R Basics & SAS Equivalents

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

TaskSAS®R
One column of valuesA variable within a datasetAn atomic vector
A full tableSAS datasetdata.frame / tibble
Mixed-type container(no direct equivalent)list
Inspect metadataPROC CONTENTSstr(df) / glimpse(df)
First N rowsPROC PRINT (obs=5)head(df, 5)
DimensionsObs & Vars in logdim(df) / nrow() / ncol()
Pick a columnkeep var; / var namedf$var / df[['var']] / select()
Pick rowswhere / if subsettingdf[cond, ] / filter()

Code examples

Building a data.frame / tibble from scratch
SAS®
data dm;
  input usubjid $ age sex $;
  datalines;
101 47 M
102 53 F
103 39 M
;
run;
R
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.

Subsetting rows and columns
SAS®
data males;
  set dm;
  where sex = 'M';
  keep usubjid age;
run;
R
# 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.

Lists — for holding mixed objects
SAS®
/* SAS has no direct equivalent;
   you'd use multiple datasets or macro vars */
R
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.

Practice exercise

Construct a tibble of five subjects with usubjid, age, sex, and a baseline weight. Use glimpse() and dim() to inspect it. Then produce (a) a subset of female subjects keeping only usubjid and weight, and (b) a list that bundles the full tibble, the subject count, and the mean age. Access each list element by name.