rsasexportr-haven

Exported file from R using `haven` cannot be opened by SAS


When exporting data from R using haven::write_sas(), the resulting sas7bdat file is not recognized (i.e. cannot be loaded) by SAS EG/9.4. Although there are several other packages such as foreign that provide alternative approaches, I was hoping to find a relatively automated way to push a dataset from my R session directly into SAS.

When using haven, the file is made but cannot be opened by SAS EG nor 9.4:

# Load package
library(haven)

# Save data
write_sas(mtcars, "mtcars.sas7bdat")

enter image description here


Using foreign as alternative to haven:

library(foreign)

write.foreign(df = mtcars,
              datafile = 'mtcars.txt',
              codefile = 'mtcars.sas',
              dataname = 'libraryname.tablename', # Destination in SAS to save the data
              package = 'SAS')

Running the SAS code output from foreign is successful.

* Written by R;
*  write.foreign(df = mtcars, datafile = "mtcars.txt", codefile = "mtcars.sas",  ;

DATA  libraryname.tablename ;
INFILE  "mtcars.txt" 
     DSD 
     LRECL= 43 ;
INPUT
 mpg
 cyl
 disp
 hp
 drat
 wt
 qsec
 vs
 am
 gear
 carb
;
RUN;

However, neither of these methods help with automatically pushing the data directly from R into a SAS library, which would be preferable.


Solution

  • There is a lengthy discussion on GitHub describing some of the challenges when exporting data from R for use in SAS via haven. In addition to providing a solution on how to automate data transfer from R to SAS, I hope this can serve as an answer to some related questions.

    If one wants to use tools designed by SAS for interoperability with R, RSWAT on GitHub is likely a more robust option. However, this will assume that you have access to SAS Cloud Analytics Services configured for this purpose.

    If you are working with a SAS 9.4 on your machine and perhaps also connect to SAS servers (i.e. using rsubmit; commands), it should be relatively straightforward to pass a data-set directly from R into a SAS library. There are three steps:

    1. Format dataset for SAS; although foreign will do a lot of the formatting changes, I prefer converting factors back to characters and having NA replaced with "". This I find ensures that no special formatting is needed by colleagues to open the final table in SAS.
    # Example data
    data <- data.frame(ID = c(123, NA, 125),
                            disease = factor(c('syphilis', 'gonorrhea', NA)),
                            AdmitDate = as.Date(c("2014-04-05", NA, "2016-02-03")),
                            DOB = as.Date(c("1990-01-01", NA, NA)))
    
    # Function defined for converting factors and blanks
    convert_format_r2sas <- function(data){
      data <- data %>%
        dplyr::mutate_if(is.factor, as.character) %>%
        dplyr::mutate_if(is.character, tidyr::replace_na, replace = "")
      return(data)
    }
    
    # Convert some formatting
    data <- convert_format_r2sas(data)
    
    1. Use foreign to export the data and associated code
    library(foreign)
    
    # Ensure the data and code files are saved in an easily accessible location (ideally in or downstream of your R project directory)
    write.foreign(df = data ,
                  datafile = 'data.txt',
                  codefile = 'data.sas',
                  dataname = 'libraryname.tablename', # Destination in SAS to save the data
                  package = 'SAS')
    
    1. Pass code to local SAS installation using custom function. You may need to adjust the location of the SAS.exe as well as the configuration file. This will work both passing a list of SAS files, or SAS code written directly in R as a character vector.
    # Define function for passing the code to SAS and upload data (may require tweaking the local SAS installation location and configuration file)
    pass_code_to_sas <- function(sas_file_list = NULL, inputstring = NULL,
             sas_path = "C:/LocationTo/SASHome/SASFoundation/9.4/sas.exe",
             configFile = "C:/LocationTo/SASHome/SASFoundation/9.4/SASV9.CFG") {
    
      # If provided list of scripts, check they are all valid
      if(!is.null(sas_file_list)){
        if(any(purrr::map_lgl(sas_file_list, file.exists)) == FALSE | is.list(sas_file_list) == F){
          stop("You entered an invalid file location or did not provide the locations as a list of characters")
        }
      }
    
      sink(file.path(R.home(), "temp_codePass.sas"))
    
      if(!is.null(sas_file_list)){
        for(i in 1:length(sas_file_list)){
          cat(readLines(sas_file_list[[i]]), sep = "\n")
        }
      }
    
      cat(inputstring)
      sink()
    
      # Output message to view what code was sent...
      message(paste0("The above info was passed to SAS: ",
                     if(!is.null(sas_file_list)){for(i in 1:length(sas_file_list)){cat(readLines(sas_file_list[[i]]), sep = "\n")}},
                     print(inputstring)))
    
      # Run SAS
      system2(sas_path,
              args = paste0(
                "\"", file.path(R.home(), "temp_codePass.sas"), "\"",
                if(!is.null(configFile)) { paste0(" -config \"", configFile, "\"")}
              )
      )
    
      # Delete the SAS file
      file.remove(file.path(R.home(), "temp_codePass.sas"))
    }
    
    # Pass data to SAS
    pass_code_to_sas(sas_file_list = 'path2codefile/data.sas')