Character Functions: From SCAN, SUBSTR & TRANWRD to stringr
Every string operation you rely on in the DATA step has a clean, readable equivalent in base R or the stringr package.
Learning objectives
- 1Translate the most common SAS character functions to base R and stringr.
- 2Understand 1-based indexing and how SUBSTR maps to substr() / str_sub().
- 3Handle case conversion, trimming, concatenation, and find-and-replace.
- 4Recognize why stringr's consistent str_* naming makes code easier to review.
Key SAS® → R comparison
| Task | SAS® | R |
|---|---|---|
| Substring | SUBSTR(x, 2, 3) | substr(x, 2, 4) / str_sub(x, 2, 4) |
| Uppercase | UPCASE(x) | toupper(x) / str_to_upper(x) |
| Lowercase | LOWCASE(x) | tolower(x) / str_to_lower(x) |
| Trim blanks | STRIP(x) / TRIM(x) | trimws(x) / str_trim(x) |
| Length | LENGTH(x) | nchar(x) / str_length(x) |
| Concatenate | CATX(' ', a, b) | paste(a, b) / str_c(a, b, sep=' ') |
| Find position | INDEX(x, 'AE') | regexpr('AE', x) / str_locate(x,'AE') |
| Replace text | TRANWRD(x,'mg','MG') | gsub('mg','MG',x) / str_replace_all() |
| Split / token | SCAN(x, 2, ' ') | strsplit(x,' ')[[1]][2] / str_split() |
Code examples
data ae;
set ae;
soc3 = upcase(substr(aedecod, 1, 3));
run;library(stringr)
ae <- ae |>
mutate(soc3 = str_to_upper(str_sub(aedecod, 1, 3)))Note: str_sub uses start/end positions (both inclusive), while SAS SUBSTR uses start/length. substr(x,2,4) in R returns characters 2 through 4.
data dm;
set dm;
fullname = catx(' ', firstnm, lastnm);
dose_u = tranwrd(dose, 'mg', 'MG');
run;dm <- dm |>
mutate(
fullname = str_c(firstnm, lastnm, sep = " "),
dose_u = str_replace_all(dose, "mg", "MG")
)Note: str_c with sep mirrors CATX. By default str_c returns NA if any input is NA — use str_replace_na() if you need SAS-style blank handling.
data _null_;
term = "RANDOMIZED-DOUBLE-BLIND";
word2 = scan(term, 2, '-');
put word2=; /* DOUBLE */
run;term <- "RANDOMIZED-DOUBLE-BLIND"
word2 <- str_split(term, "-")[[1]][2]
word2 # "DOUBLE"Note: str_split returns a list; index [[1]] to get the vector of tokens, then [2] for the second word.
Explanation
Character handling is where many SAS programmers first feel at home in R, because the operations map almost one-to-one. The two things to internalize are indexing and the stringr naming convention. R, like SAS, is 1-based — the first character is position 1 — but substr() takes a start and an end position rather than a start and a length, so SUBSTR(x,2,3) becomes substr(x,2,4).
The stringr package gives every function a str_ prefix, takes the string as the first argument, and returns predictable types. This consistency matters enormously in a regulated environment where code is read and reviewed far more often than it is written. Base R equivalents (toupper, gsub, nchar) work fine and require no package, but stringr code is usually clearer in a pipeline.
One genuine difference: SAS character variables are fixed-length and blank-padded, so trailing spaces appear constantly and STRIP/TRIM are everyday tools. R strings are variable length with no padding, so you trim far less often — usually only when cleaning imported data.