sas2r: SAS to R for clinical programming

sas2r helps statistical programmers and biostatisticians translate SAS programs for SDTM, ADaM, tables, listings and figures into R. It combines rule-based translation with separate AI translation, review and repair roles, then runs the R programs and reports the checks that passed or need attention.

The package does not require SAS to translate or run generated R. Comparing results with SAS requires matching reference outputs. AI review does not replace independent programming or your organization’s statistical QC procedures.

Release status: 0.5.8 is being prepared for CRAN resubmission; it has not yet been accepted. main is the release branch. Ongoing development and the complete test suite live on develop. See the release workflow for validation and branch roles.

Quickstart · AI models · Parallel translation · FAQ · Guides

What it helps with

How a migration runs

flowchart TD
    source["Preflight: check sources, inputs and dependencies"] --> coordinator["Coordinator: assign programs when dependencies are ready"]
    coordinator --> translator
    subgraph program["Each program or called macro"]
        translator["AI translator: write R from SAS"] --> checks["Automated code checks"]
        checks --> reviewer["AI reviewer: independently check R against SAS"]
        reviewer --> execution["Run R and check execution"]
        checks -->|"Code problem"| fixer["AI fixer: repair source-supported problems"]
        reviewer -->|"Review finding"| fixer
        execution -->|"Execution problem"| fixer
        fixer -->|"Recheck revised code"| checks
    end
    execution -->|"Initial processing finished"| final_review["Complete reviews for the selected code"]
    final_review --> study["Run the full study pipeline and check outputs / supplied references"]
    study -->|"Source-supported repair; allowance remains"| fixer
    study --> report["Save R, outputs, findings and run status"]

The translator, reviewer and fixer are separate AI roles. The coordinator is package logic that assigns work and controls program order, repair limits and spending. Repairs return through the checks; unresolved problems are reported rather than treated as successful translations. A reference difference prompts investigation against the SAS source before repair.

Dependencies come from the SAS sources. For example, a summary program waits for the program that creates its analysis dataset. Independent translation and review can overlap; execution tests, repairs and the full study run happen one at a time. See the workflow guide for review timing and how earlier passing results are protected.

Quickstart

1. Install

AI translation requires ellmer 0.4.2 or newer. We recommend ellmer 0.5.0 for new installations; both versions pass our offline compatibility tests. Deterministic workflows can run without ellmer.

# install.packages("remotes")
install.packages("ellmer")
remotes::install_github("songyue-chen/sas2r")

For unreleased development changes, install songyue-chen/sas2r@develop instead.

Installed examples are available without a GitHub checkout. Locate them with system.file("examples", package = "sas2r"); its README explains how to copy the migration demo into a writable directory and prepare its synthetic input. The annotated YAML is a configuration reference to adapt to your own study.

2. Describe your study in _sas2r.yml

Create one small file, _sas2r.yml, in your project directory. It says where your data lives, which outputs matter, and which AI model to use. The folder of SAS programs to translate is not part of it: you pass that folder, or a single .sas file, as the path argument in steps 3 and 4, with recursive = TRUE to include subfolders. Called macro folders are named under macros.

project: my_clinical_study

libraries:            # your SAS librefs and where their data files live
  sdtm:
    path: data/sdtm
    engine: xpt       # how to read members: xpt, sas7bdat, or rds
  adam:
    path: data/adam
    engine: xpt
    write: rds        # how translated programs save results: rds or xpt

outputs:              # what the migration must produce
  datasets:
    - adam.adsl
    - adam.adae
  references:         # optional: gold-standard SAS outputs to compare against
    adam.adsl: data/reference/adsl.xpt
    adam.adae: data/reference/adae.xpt

llm:                  # a recommended Flash starting profile
  provider: deepseek
  auth_mode: api_key
  model: deepseek-flash
  max_output_tokens: 393216
  capabilities:
    structured_output: fallback
    tool_calling: native
    reasoning_effort: unsupported # connector cannot set effort; server thinking stays on
  timeout_seconds: 1800
  max_tries: 1

Make DEEPSEEK_API_KEY available to your R session before translation. For Gemini Flash or another provider, replace the llm: block with its complete provider profile.

Every budget: limit is commented out by default: observe mode records usage without stopping requests, so translation finishes without interruption. Uncomment a limit only if you want the run to stop when it is reached. The code style keys dialect and allowlist are described in code style and packages.

3. Check the setup offline

library(sas2r)
migration_dir <- tempfile("sas2r-migration-")
check <- sas_preflight(
  "programs/",                     # the folder of SAS programs to translate
  config = "_sas2r.yml", out_dir = migration_dir, diagnose = "off"
)
print(check)
check$inputs       # availability and producer-order status
check$budget       # effective limits; preflight makes no model calls

With diagnose = "off", preflight checks the source setup without model calls or reading dataset contents. The default diagnose = "auto" reports missing LLM configuration or uses one bounded request to suggest corrections for findings. Advice never clears static findings. To specify a shared-session program sequence, set migration.execution_order; see execution order. Review the findings: missing resources usually allow translation with warnings. See the preflight guide for library paths, output requirements and QC profiles.

4. Run it

These examples write under the R session temporary directory. Supply your own migration_dir and export_dir when you want to retain the files.

library(sas2r)

result <- sas_translate(
  path = check$project,            # reuse the unchanged source scan
  out_dir = migration_dir,
  max_parallel_translations = 1,   # programs or macros translated at once; see below
  execute = TRUE,                  # actually run the translated programs
  max_program_repair_rounds = 1,   # immediate repair attempts per program
  max_bundle_repairs_per_component = 2, # bundle repairs per component
  max_bundle_repair_rounds = NULL, # optional overall cap on bundle fixer calls
  agent_evidence = "code_only"     # what repair evidence the AI may see
  # Optional limits, commented out by default. Uncomment to enforce them; the
  # run stops admitting requests when a limit is reached. Requests and tool calls per program
  # vary by model and study: size ceilings from a completed run's usage summary.
  # Catalog costs are estimates; an in-flight request may exceed the threshold.
  # , budget_usd = 10, budget_mode = "soft"
  # , usage_limits = list(max_calls = 700, max_tool_calls = 700, max_wall_time = 14400)
)

# What you get back
result$status        # one of the four statuses below
result$bundle_dir    # all available selected programs and macros, including failed code
result$outputs_dir   # saved deliverables: datasets/<library>/ and tlf/ (NULL if none)
result$report_path   # a readable report of everything that happened

# Read a translated program
cat(sas_code(result, 1))

# Export the selected code, generated outputs, reports, and run guide
export_dir <- tempfile("sas2r-export-")
sas_write(result, export_dir)

Every limit is commented out by default: a run records usage in observe mode and finishes without interruption. Uncomment budget_usd or usage_limits to enforce a limit, knowing that the run stops when it is reached. How many requests and tool calls a program or macro needs varies by model, reasoning setting and study, so size ceilings from the usage summary of a completed run rather than from a rule of thumb. As one labelled example, a 17-component study with GPT-5.6 Luna at high reasoning used about 340 requests, 390 tool executions and an hour with four workers. usage_limits = list(max_calls = 0) remains the way to forbid provider calls entirely.

Reading the results

Open file.path(result$out_dir, result$run_id, "START_HERE.html") first. It links the code, outputs, diagnostics and comparison reports, and states what ran, what was skipped, and why a run was blocked or failed. Saved code can be incomplete or unvalidated; file existence alone does not establish success.

The four statuses

Status Meaning
blocked A required part failed: for example, execution, an output check or a dependency needed to continue.
needs_review Required review or other evidence is incomplete. Some programs may have run successfully.
migration_ready Required checks passed, but no required output has a passing SAS reference comparison.
validated Required checks passed and at least one required output matched its reference within the configured tolerances. Other outputs may have no reference.

This is not proof of SAS equivalence. Check which outputs were compared and which remain unverified. Matching a reference does not establish that the original SAS or the analysis specification is correct.

For a blocked run, inspect the named problem before changing code. A reference mismatch alone does not justify a repair: first confirm that both results use the same source programs, inputs and analysis definitions.

Use resume = TRUE to reuse compatible saved work; repair counts and recorded spending are retained. options(sas2r.progress = FALSE) hides progress updates. Wrap the call in suppressMessages() to hide all routine messages, including progress, the final summary and report locations. Returned results, saved reports and execution error logs remain available. Source parsing caches use session-temporary storage and do not modify input projects. See logs, repairs and resume.

Parallel translation (opt-in)

The default is one program or called macro at a time. To allow up to two, pass the argument to sas_translate():

result <- sas_translate(path = check$project, out_dir = migration_dir,
                        max_parallel_translations = 2)

A worker is an R process handling an assigned translation or review task. The limit covers whole workflows, including their review and repair steps; it does not create that many of each AI role. All work shares the same run budgets and quality checks. The count is not a CPU count: remote AI requests spend time waiting, but memory and provider quotas still limit useful concurrency.

Parallel mode requires ellmer 0.5.0 or newer and llm.max_tries: 1. If both max_parallel_translations and max_tries exceed 1, the run stops with instructions to change one. Older ellmer uses one workflow and reports why. Missing inputs or source dependencies produce warnings while available code keeps translating. A failed component does not stop other programs. Reports distinguish saved code from execution that could not run. Use execute = FALSE for code only; omitting SAS references still permits execution. Unusable configuration and pipeline omissions remain errors.

Start with one, then compare two on representative programs. Equal live-model quality and a particular speedup are not established by the offline tests. The speed FAQ explains why more workers do not give proportional time savings. See parallel details and provider settings.

Choosing an AI model

Start with Gemini Flash or DeepSeek Flash. They offer a practical balance of speed, cost and coding capability, making them our recommended first choice for translation trials. Confirm accuracy on representative study programs with the same code review, execution and output checks you would use with a frontier model. Use a frontier model when the first choice leaves unresolved translation problems. See the current Gemini model documentation and DeepSeek model and pricing information.

For OpenAI, start with GPT-5.6 Luna, which is designed for cost-sensitive, high-volume work. Apply the same study-level accuracy checks. See the official Luna model documentation.

These are suggested profiles, not automatic package defaults. Model availability and settings were checked on September 18, 2026. Use the documented maximum output allowance when your endpoint accepts it and the request fits the context window, to give reasoning and complete R code room to finish.

Starting choice Model Reasoning Output token ceiling Request timeout
Gemini Flash gemini-3.8-flash high 65536 900 seconds
DeepSeek Flash deepseek-flash Keep server thinking default 393216 1800 seconds
OpenAI Luna gpt-5.6-luna high 128000 900 seconds
Claude Sonnet 5 claude-sonnet-5 high with adaptive thinking 128000 (see below) 900 seconds

Claude qualification: Sonnet 5 replaces the older Sonnet 4.6 recommendation. Its 128,000-token maximum is conditional guidance for this package: sas2r currently uses non-streaming requests, and very long responses need transport validation. Anthropic recommends streaming or batch processing for long requests; increasing the token ceiling or timeout does not enable either. See the Claude profile before using its full allowance.

Use the provider guide for complete connection profiles, model availability checks, output allowances, timeouts and tuning guidance. The full configuration template lists the available study and model settings. The program folder itself is the path argument of sas_preflight() and sas_translate(), not a configuration key. Called macro folders are configured through macros.search_path; see macro setup and limitations.

Privacy: What Your Model Provider Can Receive

Dataset processing and comparisons run on your infrastructure. AI requests contain SAS/R code, comments, paths and structured technical errors. Dataset row numbers, subject identifiers, key values, cell values, previews and raw runtime messages stay local under both code_only and the legacy bounded policy. Source code and comments are not automatically de-identified.

Focused mismatch investigation can receive relative sizes, column/type differences and categorical patterns. Fixers and candidate acceptance reviews remain reference-blind: SAS is the source of truth. Human report explanations never drive code repairs. Use an organization-approved endpoint and confirm its data residency and retention terms. Read the full privacy guidance before using confidential study material; generated R is not a filesystem sandbox.

Frequently asked questions

What should I prepare before translating a study?

Provide the SAS programs, called macros and include files, the input data locations, and the list of required outputs. Add matching SAS reference outputs if available. Use _sas2r.yml to describe the study, then run sas_preflight(..., diagnose = "off") to find setup problems before AI calls. See the quickstart.

Do I need SAS installed? Can I start without SAS reference outputs?

SAS is not required to translate code or run the generated R. Without reference outputs, the package can still review code, test execution and apply configured output checks, but it cannot compare results with SAS. A migration_ready result is different from validated; see the four statuses. For comparisons, use SAS outputs produced from the same programs, inputs, macros and parameters.

Does the AI reviewer replace independent programming or statistical QC?

No. It reviews translated R against the SAS source; it does not independently implement the analysis from the protocol, SAP or programming specifications. Your team’s independent review and QC procedures still apply. The saved code, findings and comparison reports help reviewers inspect the work.

Does validated mean every dataset and analysis is correct?

No. It means the configured reference comparisons passed within the declared tolerances, alongside the package’s required checks. An output without a reference has not received that comparison. Read the report’s coverage and unresolved findings; the status does not establish that the original SAS, the analysis specification or every statistical result is correct.

Will sas2r correct my ADaM derivations or make them CDISC compliant?

The translation follows the supplied SAS. It does not independently decide whether a population definition, baseline rule, imputation or analysis method is appropriate for the study. Review those decisions against the SAP and dataset specifications, and perform your usual CDISC checks separately. A problem in the original SAS can remain a problem in a faithful translation.

Are tables, listings and figures checked as fully as datasets?

SAS graphics can be translated into native R plotting and PDF output; an R copy of the SAS graphics engine is not required. The translation must preserve the requested statistics, labels, summary tables and pagination. Fonts and spacing may need refinement.

Dataset comparisons check the configured reference data and tolerances. A readable table or figure file alone does not establish that its content is correct. Review the analysis populations, denominators, statistics, rounding, labels and presentation, as applicable. See the output-evidence guide for comparison coverage.

Can dependent programs translate at the same time?

A program waits for its required upstream programs to finish initial translation and checks. For example, a summary program waits for the derivation it reads. Unrelated programs or macros can use the other available workers. The full study run still follows dependency order. Parallel mode retains the existing checks, but unchanged live-model quality and speedup have not yet been established by a paired study run. See parallel translation.

Will two workers halve the run time, or four workers quarter it?

No. Two workers do not guarantee 50% of the one-worker time, and four do not guarantee 25%. The setting allows up to that many workflows at once; it does not divide every step of a study evenly among them:

For illustration, suppose a one-worker run takes 60 minutes: 20 minutes must run one at a time and 40 minutes can be shared perfectly. Even with no extra overhead, two workers would take 40 minutes, and four 30 minutes. This is an example, not a measured sas2r benchmark. Compare elapsed time and output checks on the same representative study before increasing the setting. One observed point: a 17-component study took 79 minutes with four workers on sas2r 0.5.1 and 59 minutes on 0.5.2 with GPT-5.6 Luna at high reasoning; other models and studies differ.

What should I do when a run is blocked or a comparison fails?

Start with the console message and the saved START_HERE.html report, when available. They identify the reason, what ran and what still needs attention. Check input paths, missing programs or macros, and the reported code error. For comparison failures, first confirm that the SAS reference uses the same inputs and specifications. Do not change R code merely to match a reference that represents a different analysis. See reference differences.

Must I restart everything after an interruption or a fix?

Use resume = TRUE to reuse compatible saved work. Changed source code, inputs or other relevant settings can require fresh translation or checks. Repair allowances and recorded spending are retained; resume does not reset them. See resume and limit provider calls.

Can patient data reach the AI provider?

Data processing runs locally. Agents cannot retrieve dataset records, comparison cells, previews or raw runtime logs. Source code, comments and project paths are still sent to the configured provider and may themselves contain confidential information. Approved aggregate comparison patterns go only to focused investigation and human-report diagnosis. Review the privacy details before using confidential study material.

Can our programmers edit and run the R code without sas2r or AI?

Yes. Export with sas_write() and keep the complete exported set of scripts and supporting files. Its guide lists required R packages and input paths; run.R launches the programs in order without sas2r or an AI key. Human edits and manual reruns need new QC and do not automatically update the saved migration report.

Guides

I want to… Read
Set up libraries, required outputs and QC checks Preflight and QC
Understand logs, repairs, resume, macros or exported files Running migrations
Choose a provider and configure model settings AI providers
Compare saved datasets and investigate differences Output comparisons
Understand acceptance rules and recorded evidence Migration evidence
Understand what can be sent to an AI provider Privacy

For regulated work, review migrated code and outputs under your organization’s SOPs. Automated translation does not replace required independent review or QC.

For quick browser-based code translation, see the separate sas2r.ai companion site. Its workflow differs from this R package.

License

Apache License 2.0. See LICENSE.md.