// R in SAS Viya 

Corresponding codes 

/*========================================================== 

 *  1. PROC R: MINIMAL EXAMPLE 

 *==========================================================*/ 

proc r; 

submit; 

x <- c(10, 20, 30, 40, 50) 

mean(x) 

endsubmit; 

run; 

/*========================================================== 

 *  2. MANAGING R ENVIRONMENTS: SETTINGS 

 *==========================================================*/ 

/* renv does not replace the administrator-level setup that makes R available 

   in SAS Viya. It adds dependency management per project on top of it. */ 

/* Folder of the R environment; the last folder is its name */ 

%let r_env_path = /sasdata/r_workspace/PROJECT; 

/* Location of r_env.R on the server */ 

%let r_scripts = /sasdata/r_workspace/_scripts/r_env.R; 

/* Packages to install, separated by spaces */ 

%let r_packages = jsonlite data.table arrow haven; 

#=========================================================== 

#  3. MANAGING R ENVIRONMENTS: HELPER FUNCTIONS (R_ENV.R) 

#=========================================================== 

# Helpers for working with renv environments from PROC R. 

# Source this file at the start of every PROC R block. 

# Server settings 

r_home  <- "/tmp" 

# The server has no compiler, so packages have to arrive prebuilt. Posit Package 

# Manager serves prebuilt Linux packages per distribution. 

r_ppm       <- "https://packagemanager.posit.co/cran/latest" 

r_ppm_linux <- "https://packagemanager.posit.co/cran/__linux__/%s/latest" 

# Reads /etc/os-release and turns it into the name Posit Package Manager uses. 

r_detect_distro <- function() { 

  if (!file.exists("/etc/os-release")) return(NULL) 

  lines <- readLines("/etc/os-release", warn = FALSE) 

  lines <- lines[grepl("^[A-Z_]+=", lines)] 

  keys  <- sub("=.*$", "", lines) 

  vals  <- gsub("\"", "", sub("^[^=]*=", "", lines)) 

  names(vals) <- keys 

  id      <- vals[["ID"]] 

  version <- if ("VERSION_ID" %in% keys) vals[["VERSION_ID"]] else "" 

  major   <- sub("\\..*$", "", version) 

  switch(id, 

    ubuntu = , debian = if ("VERSION_CODENAME" %in% keys) vals[["VERSION_CODENAME"]], 

    centos = if (major == "7") "centos7" else paste0("rhel", major), 

    rhel = , rocky = , almalinux = , ol = paste0("rhel", major), 

    `opensuse-leap` = , opensuse = , sles = paste0("opensuse", gsub("\\.", "", version)), 

    NULL) 

} 

# Everything a fresh R session needs. Every function below calls this, so the 

# code also works right after `proc r restart`. 

r_env_setup <- function(path) { 

  path <- normalizePath(path, mustWork = FALSE) 

  Sys.setenv(HOME = r_home, TMPDIR = r_home)           # the real HOME is read-only 

  options(renv.consent = TRUE) 

  Sys.setenv(RENV_CONFIG_CACHE_ENABLED = "FALSE",      # the renv cache is wiped every session 

             RENV_CONFIG_SYNCHRONIZED_CHECK = "FALSE") 

# Posit Package Manager only serves prebuilt (binary) packages if R identifies 
# its version and platform in the HTTP user agent. Without that it serves source 
# packages, which cannot be installed here because the server has no compiler. 
# So set the user agent before any package is downloaded. 

  options(HTTPUserAgent = sprintf("R/%s R (%s)", getRversion(), 

                                  paste(getRversion(), R.version$platform, 

                                        R.version$arch, R.version$os))) 

  distro <- r_detect_distro() 

  if (is.null(distro)) { 

    warning("Could not work out the Linux distribution. ", 

            "Packages will come as source and the install may fail.") 

  } 

  options(repos = c(PPM = if (is.null(distro)) r_ppm else sprintf(r_ppm_linux, distro))) 

  options(install.packages.compile.from.source = "never") 

  path 

} 

# Creates the environment. Run once; running it again changes nothing. 

create_r_env <- function(path) { 

  path <- r_env_setup(path) 

  dir.create(path, recursive = TRUE, showWarnings = FALSE) 

  if (!dir.exists(file.path(path, "renv"))) { 

    setwd(path) 

    # bare = TRUE: do not scan the folder and install what it finds, that step fails here 

    renv::init(project = path, bare = TRUE, restart = FALSE) 

  } 

  load_env(path) 

} 

# Activates the environment. Cheap, so call it in every PROC R block. 

load_env <- function(path, restore = TRUE) { 

  path <- r_env_setup(path) 

  setwd(path) 

  renv::load(project = path) 

  if (restore && file.exists(file.path(path, "renv.lock"))) { 

    renv::restore(project = path, library = renv::paths$library(project = path), 

                  prompt = FALSE) 

  } 

  invisible(path) 

} 

# Installs packages and records them in renv.lock. 

install_and_lock <- function(path, packages) { 

  path <- load_env(path, restore = FALSE) 

  lib  <- renv::paths$library(project = path) 

  packages <- unlist(strsplit(packages, "[ ,]+")) 

  packages <- packages[nzchar(packages)] 

  todo <- setdiff(packages, rownames(installed.packages(lib.loc = lib))) 

  if (length(todo)) install.packages(todo, lib = lib) 

  missing <- setdiff(packages, rownames(installed.packages(lib.loc = lib))) 

  if (length(missing)) stop("Not installed: ", paste(missing, collapse = ", ")) 

  # Lock by name: renv's default snapshot only looks at R scripts, which would 

  # leave the lockfile empty for an environment that is driven from SAS. 

  have <- installed.packages(lib.loc = lib) 

  renv::snapshot(project = path, library = lib, 

                 packages = rownames(have)[is.na(have[, "Priority"])], prompt = FALSE) 

  invisible(path) 

} 

# Deletes the environment. 

remove_env <- function(path) { 

  path <- normalizePath(path, mustWork = FALSE) 

  if (!dir.exists(file.path(path, "renv"))) stop(path, " is not an renv environment.") 

  setwd(dirname(path)) 

  unlink(path, recursive = TRUE, force = TRUE) 

  invisible(!dir.exists(path)) 

} 

# Shows where R is looking and what is installed. 

r_env_status <- function() { 

  cat("Working directory:", getwd(), "\n") 

  cat("renv project     :", Sys.getenv("RENV_PROJECT"), "\n") 

  cat("Repository       :", paste(getOption("repos"), collapse = ", "), "\n") 

  cat("Library paths    :\n"); cat(paste0("  ", .libPaths()), sep = "\n") 

  cat("Installed        :", 

      paste(rownames(installed.packages(lib.loc = .libPaths()[1])), collapse = ", "), "\n") 

} 

/*========================================================== 

 *  4. MANAGING R ENVIRONMENTS: CREATE THE ENVIRONMENT (ONCE) 

 *==========================================================*/ 

/* By default only the person who creates the environment has permissions on 

   it. Sharing it with colleagues requires a permission change on the server, 

   after which everyone in that group can also delete it. */ 

proc r restart; 

    submit; 

    source(symget("r_scripts")) 

    create_r_env(symget("r_env_path")) 

    r_env_status() 

    endsubmit; 

quit; 

/*========================================================== 

 *  5. MANAGING R ENVIRONMENTS: INSTALL PACKAGES AND UPDATE RENV.LOCK 

 *==========================================================*/ 

/* Packages that are already installed are skipped, so you can add new ones to 

   r_packages and run this step again. */ 

proc r; 

    submit; 

    source(symget("r_scripts")) 

    install_and_lock(symget("r_env_path"), symget("r_packages")) 

    endsubmit; 

quit; 

/*========================================================== 

 *  6. MANAGING R ENVIRONMENTS: USE THE ENVIRONMENT 

 *==========================================================*/ 

/* Start every PROC R block with source() and load_env(). If load_env() is 

   forgotten, packages end up in the wrong library and seem to disappear later. 

   load_env() restores the packages from renv.lock when it exists, so the 

   lockfile can also be used to rebuild an environment or to copy one: create 

   a new environment, place the renv.lock file in it and run load_env(). */ 

proc r restart; 

    submit; 

    source(symget("r_scripts")) 

    load_env(symget("r_env_path")) 

    show(mtcars) 

    endsubmit; 

quit; 

/*========================================================== 

 *  7. MANAGING R ENVIRONMENTS: REMOVE THE ENVIRONMENT 

 *==========================================================*/ 

/* Check the path first: this deletes the packages and renv.lock as well. */ 

proc r restart; 

    submit; 

    source(symget("r_scripts")) 

    remove_env(symget("r_env_path")) 

    endsubmit; 

quit; 

/*========================================================== 

 *  8. PROC R: MOVING DATA BETWEEN SAS AND R 

 *==========================================================*/ 

%let r_env_path = /sasdata/r_workspace/PROJECT; 

%let r_scripts  = /sasdata/r_workspace/_scripts/r_env.R; 

proc r restart; 

    submit; 

    source(symget("r_scripts")) 

    load_env(symget("r_env_path")) 

    # SAS -> R 

    class <- sd2df("SASHELP.CLASS") 

    # Work in R 

    means <- aggregate(cbind(Height, Weight) ~ Sex, data = class, FUN = mean) 

    # R -> SAS 

    df2sd(means, "means", "WORK") 

    endsubmit; 

quit; 

proc print data=work.means noobs; 

    title "Means per sex, calculated in R"; 

run; 

title; 

/*========================================================== 

 *  9. PROC R: SHOWING R OUTPUT IN THE RESULTS WINDOW 

 *==========================================================*/ 

%let r_env_path = /sasdata/r_workspace/PROJECT; 

%let r_scripts  = /sasdata/r_workspace/_scripts/r_env.R; 

proc r restart; 

    submit; 

    source(symget("r_scripts")) 

    load_env(symget("r_env_path")) 

    class <- sd2df("SASHELP.CLASS") 

    show(class, "Class", 10)  # requires the arrow package 

    endsubmit; 

quit; 

#=========================================================== 

#  10. STANDALONE R PROGRAM (R CODE EDITOR, NO PROC R WRAPPER) 

#     (SAS Viya Stable 2026.04 and later) 

#=========================================================== 

# Run this as an R program in the R code editor (SAS Viya Stable 2026.04 and 

# later). The macro variables of a SAS program are not available here, so the 

# paths are written out. 

source("/sasdata/r_workspace/_scripts/r_env.R") 

load_env("/sasdata/r_workspace/PROJECT") 

# SAS -> R 

class <- sd2df("SASHELP.CLASS") 

# Work in R 

means <- aggregate(cbind(Height, Weight) ~ Sex, data = class, FUN = mean) 

# R -> SAS 

df2sd(means, "means", "WORK") 

show(means, "Means per sex", 10) 

#=========================================================== 

#  11. R-SWAT: INSTALLING THE PACKAGE 

#=========================================================== 

install.packages( 

  "swat", 

  repos = c( 

    "https://sassoftware.r-universe.dev", 

    "https://cloud.r-project.org" 

  ) 

) 

#=========================================================== 

#  12. R-SWAT: CONNECTING TO CAS WITH USERNAME AND PASSWORD 

#     (not allowed on every server, use section 13 instead) 

#=========================================================== 

# The credentials are read from environment variables. Define them in your 

# ~/.Renviron file, which you can open from R with: file.edit("~/.Renviron") 

# 

# Add one NAME=value per line, without quotes, and end the file with an empty 

# line. Then save the file and restart R, because .Renviron is read at startup: 

#   SAS_USER=your_username 

#   SAS_PASSWORD=your_password 

#   SAS_TOKEN=your_access_token 

#   CAS_CLIENT_SSL_CA_LIST=/path/to/trustedcerts.pem 

# 

# CAS_CLIENT_SSL_CA_LIST is only needed for a self-signed or internal certificate. 

# Keep .Renviron out of version control, it contains credentials. 

# For a quick test you can set a value for the current session only: 

#   Sys.setenv(SAS_USER = "your_username") 

library(swat) 

conn <- CAS( 

  hostname = "https://myviya.example.com/cas-shared-default-http/", 

  port = 443, 

  username = Sys.getenv("SAS_USER"), 

  password = Sys.getenv("SAS_PASSWORD") 

) 

#=========================================================== 

#  13. R-SWAT: CONNECTING TO CAS WITH AN OAUTH TOKEN 

#     (use this when passwords are not accepted, and in production) 

#=========================================================== 

conn <- CAS( 

  hostname = "https://myviya.example.com", 

  port = 443, 

  protocol = "https", 

  path = "/cas-shared-default-http", 

  password = Sys.getenv("SAS_TOKEN") 

) 

#=========================================================== 

#  14. R-SWAT: INTERACTING WITH CAS: LISTING TABLES 

#=========================================================== 

cas.table.tableInfo(conn, caslib = "PUBLIC") 

#=========================================================== 

#  15. R-SWAT: RETRIEVING DATA FROM CAS 

#=========================================================== 

hmeq <- defCasTable(conn, table = "HMEQ", caslib = "PUBLIC") 

hmeq_r <- to.casDataFrame(hmeq) 

head(hmeq_r) 

summary(hmeq_r) 

#=========================================================== 

#  16. R-SWAT: SENDING DATA FROM R TO CAS 

#=========================================================== 

as.casTable( 

  conn, 

  hmeq_r, 

  casOut = list(name = "hmeq_r", caslib = "PUBLIC", promote = TRUE) 

)