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

Import & Export: Reading SAS, CSV and Excel with haven & readr

Bring your existing .sas7bdat and .xpt files into R — and write them back out — using haven, readr and readxl.

Learning objectives

  • 1Read SAS7BDAT and XPORT (.xpt) files into R with the haven package.
  • 2Import and export CSV and Excel files with readr and readxl/writexl.
  • 3Preserve variable labels and formats when moving between SAS and R.
  • 4Understand where PROC IMPORT/EXPORT and LIBNAME map in an R workflow.

Key SAS® → R comparison

TaskSAS®R
Read .sas7bdatLIBNAME + sethaven::read_sas('dm.sas7bdat')
Read transport .xptPROC CIMPORT / xport enginehaven::read_xpt('dm.xpt')
Write transport .xptPROC COPY / xport enginehaven::write_xpt(dm, 'dm.xpt')
Read CSVPROC IMPORT dbms=csvreadr::read_csv('dm.csv')
Write CSVPROC EXPORT dbms=csvreadr::write_csv(dm, 'dm.csv')
Read ExcelPROC IMPORT dbms=xlsxreadxl::read_excel('dm.xlsx')
Write ExcelPROC EXPORT dbms=xlsxwritexl::write_xlsx(dm, 'dm.xlsx')
Variable labelLABEL statementattr(df$var, 'label') / labelled

Code examples

Reading a SAS dataset and keeping labels
SAS®
libname adam "/data/adam";
data adsl;
  set adam.adsl;
run;
R
library(haven)
adsl <- read_sas("/data/adam/adsl.sas7bdat")

# Labels survive the trip:
attr(adsl$AGE, "label")     # "Age"

# View all labels at once:
library(labelled)
var_label(adsl)

Note: haven imports SAS labels as a 'label' attribute on each column and SAS formats as labelled values — so your metadata travels with the data.

CSV and Excel round-trip
SAS®
proc import datafile="raw/lb.csv"
  out=lb dbms=csv replace; getnames=yes;
run;
proc export data=lb outfile="out/lb.xlsx"
  dbms=xlsx replace;
run;
R
library(readr); library(writexl)

lb <- read_csv("raw/lb.csv")        # types guessed per column
write_xlsx(lb, "out/lb.xlsx")

# Control column types explicitly when needed:
lb <- read_csv("raw/lb.csv",
               col_types = cols(USUBJID = col_character(),
                                AVAL     = col_double()))

Note: read_csv guesses types from the data; for regulatory work, specify col_types so an all-numeric-looking ID column is never silently turned into a number.

Producing a submission-ready transport file
SAS®
libname out xport "out/dm.xpt";
proc copy in=work out=out;
  select dm;
run;
R
library(haven)
# Basic transport write:
write_xpt(dm, "out/dm.xpt", version = 5)

# For SDTM/ADaM submissions, xportr adds
# compliant labels, lengths, types & ordering
# (covered in the SDTM module).

Note: haven::write_xpt produces a v5 transport file; the xportr package layers on the CDISC compliance checks you need for an actual submission.

Explanation

Import and export is the bridge that lets you adopt R incrementally without abandoning your existing SAS data. The haven package reads and writes .sas7bdat and .xpt transport files directly, and critically it preserves the metadata you care about: variable labels become column attributes and SAS formats become 'labelled' vectors that remember both code and decode. This means you can pull an existing ADSL into R, work on it, and retain its labels throughout.

For flat files, readr's read_csv/write_csv are faster and more predictable than base R's read.csv, and readxl/writexl handle Excel without any Java dependency. The professional habit to build is being explicit about column types on import. read_csv guesses types from the first chunk of rows, which is convenient for exploration but risky for clinical data — a USUBJID that looks numeric can be coerced incorrectly. Supplying col_types removes that ambiguity.

When it's time to deliver, write_xpt creates a version-5 transport file, the format regulators expect. On its own it does not enforce the full set of CDISC conformance rules — variable lengths, label lengths, ordering — which is exactly what the xportr package adds. We build a complete, compliant export in the SDTM Implementation module.

Practice exercise

Take any sample .sas7bdat (or export one from SAS) and read it with read_sas(). Confirm the labels survived using var_label(). Write it out as both a CSV and an Excel file, then read the CSV back specifying col_types so that USUBJID stays character. Finally, write a version-5 .xpt with write_xpt() and confirm it can be read back.